1 """
   2 This script converts Vorbis OGG and mp3 files to low-bitrate mp3 files.
   3 Requires at least the following Debian packages:
   4     python, python-pyvorbis, python-eyeD3, vorbistools, lame
   5 """
   6 
   7 import sys, os, ogg, ogg.vorbis, eyeD3
   8 import getopt, re
   9 
  10 usage = """Usage:
  11 %s [options] <source> [<source>...]"""%(os.path.basename(sys.argv[0]))
  12 help = """
  13     -h --help       this help
  14     -n --simple-names       Remove non-alnum chars from filename
  15     -d --single-directory   Do not create "artist - album" directory
  16     -l --list-files         List source and target files only
  17     -F --force-convert      Force conversion even if target file exists
  18 
  19     source      File or directory, directories are searched for ogg
  20             and mp3 -files.
  21 
  22 Found files are converted to 96kbps mp3-files, "-lbr" is appended to the
  23 filename. By default the files are placed in a "artist - album" directory.
  24 """
  25 
  26 #
  27 # ======================================================================
  28 # metaclass autosuper
  29 #
  30 class autosuper(type):
  31     """Automatically adds __super to the class"""
  32     def __init__(cls, name, bases, dict):
  33         super(autosuper, cls).__init__(name, bases, dict)
  34         setattr(cls, "_%s__super"%(name), super(cls))
  35 
  36 #
  37 # ======================================================================
  38 # Class AudioStream
  39 #
  40 class AudioStream:
  41     """Generic AudioStream base-class."""
  42     __metaclass__ = autosuper
  43     def __init__(self, filename):
  44         self.filename=filename
  45         self.artist=None
  46         self.track=None
  47         self.title=None
  48         self.album=None
  49         self.outputname=None
  50         self.outputflags=None
  51         self.lame_opts="--lowpass 14 --preset 96"
  52     
  53     def convertToMP3(self, outputname=None):
  54         """Method to convert the file into LBR MP3, the actual conversion is
  55         left to sub-classes. This class only make sure the directory exists."""
  56         path = os.path.dirname(target)
  57         if path:
  58             if not os.path.exists(path):
  59                 os.makedirs(path)
  60             elif not os.path.isdir(path):
  61                 print >>sys.stderr, "ERROR:"\
  62                     "'%s' exists and is not a directory"%(path)
  63         
  64     
  65     def getNewName(self, keep_names=1, single_dir=1):
  66         """Returns the name of the resulting file"""
  67         if self.outputname and self.outputflags == (keep_names, single_dir):
  68             return self.outputname
  69         if not keep_names:
  70             result = re.sub("\W+", "",
  71             os.path.basename(self.filename[:-4]))+"-lbr.mp3"
  72         else:
  73             result = os.path.basename(self.filename)[:-4]+"-lbr.mp3"
  74         if not single_dir:
  75             result = "%s - %s%s%s"%(self.artist.replace(os.sep, ""),
  76             self.album.replace(os.sep, ""), os.sep, result)
  77         self.outputname=result
  78         self.outputflags = (keep_names, single_dir)
  79         return result
  80         
  81     def _escape(self, s):
  82         """Escapes " -chars for os.system() calls"""
  83         return s.replace('"', '\\"')
  84 
  85 #
  86 # ======================================================================
  87 # Class MP3Stream
  88 #
  89 class MP3Stream(AudioStream):
  90     def __init__(self, filename):
  91         self.__super.__init__(filename)
  92         # FIXME to use id3v2 and ID3, they seem to behave better
  93         tags = eyeD3.Mp3AudioFile(self.filename).getTag()
  94         self.artist = tags.getArtist() or "unknown"
  95         self.album = tags.getAlbum() or "unknown"
  96         self.track = str(tags.getTrackNum()[0] or "")
  97         self.title = tags.getTitle() or "unknown"
  98 
  99     def convertToMP3(self, filename=None):
 100         if not self.outputname and not filename:
 101             self.getNewName()
 102         self.__super.convertToMP3(filename)
 103         os.system('lame "%s" %s "%s" --tt "%s" --ta "%s"'%(
 104             self._escape(self.filename), self.lame_opts,
 105             self._escape(filename or self.outputname),
 106             " - ".join(filter(None, (self.track, self._escape(self.title)))),
 107             " - ".join(filter(None, (self._escape(self.artist),
 108                 self._escape(self.album))))))
 109 
 110 #
 111 # ======================================================================
 112 # Class OGGStream
 113 #
 114 class OGGStream(AudioStream):
 115     def __init__(self, filename):
 116         self.__super.__init__(filename)
 117         stream = ogg.vorbis.VorbisFile(self.outputname)
 118         comments = stream.comment().as_dict()
 119         self.artist = comments.get("ARTIST", [""])[0].encode("latin_1", "ignore")
 120         self.album = comments.get("ALBUM", [""])[0].encode("latin_1", "ignore")
 121         self.track = comments.get("TRACKNUMBER", [""])[0].encode("latin_1", "ignore")
 122         self.title = comments.get("TITLE", [""])[0].encode("latin_1", "ignore")
 123 
 124     def convertToMP3(self, filename=None):
 125         if not self.outputname and not filename:
 126             self.getNewName()
 127         self.__super.convertToMP3(filename)
 128         tempname = "/tmp/oggconvert-%d.wav"%(os.getpid())
 129         print "Converting %s to wav"%(os.path.basename(self.filename))
 130         os.system('ogg123 -q -d wav -f "%s" "%s"'%(
 131             self._escape(tempname), self._escape(self.filename)))
 132         os.system('lame "%s" %s "%s" --tt "%s" --ta "%s"'%(
 133             self._escape(tempname), self.lame_opts,
 134             self._escape(filename or self.outputname),
 135             " - ".join(filter(None, (self.track, self._escape(self.title)))),
 136             " - ".join(filter(None, (self._escape(self.artist),
 137                 self._escape(self.album))))))
 138         os.remove(tempname)
 139 #
 140 # ======================================================================
 141 # class Converter
 142 #
 143 class Converter(object):
 144     def __init__(self, args=[]):
 145         self.keep_filenames = 1
 146         self.single_directory = 0
 147         self.list_only=0
 148         self.force_convert=0
 149         self.registry={}
 150         self.source=[]
 151         quit=self.parseArgs(sys.argv[1:])
 152         if quit:
 153             sys.exit(quit-1)
 154 
 155     def parseArgs(self, args):
 156         opts, args=getopt.getopt(args, "hndlF",
 157             ("help", "simple-names", "single-directory", "list-files", 
 158             "force-convert"))
 159         self.sources=args
 160         for name, value in opts:
 161             if name in ("-h", "--help"):
 162                 print usage
 163                 print help
 164                 return 1
 165             elif name in ("-n", "--simple-names"):
 166                 self.keep_filenames=0
 167             elif name in ("-d", "--single-directory"):
 168                 self.single_directory=1
 169             elif name in ("-l", "--list-files"):
 170                 self.list_only=1
 171 	    elif name in ("-F", "--force-convert"):
 172                 self.force_convert=1
 173         if not args:
 174             print usage
 175             return 1
 176         return 0
 177 
 178     def registerExtension(self, extension, classref):
 179         """Registers the extension to a class. The class init is called with a
 180            filename as the only argument."""
 181         self.registry[extension]=classref
 182 
 183     def getAudioStream(self, filename):
 184         try:
 185             ext=filename.split(".")[-1]
 186         except:
 187             raise Warning, "No extension found."
 188         return self.registry[ext](filename)
 189 
 190     def getStreams(self):
 191         for source in self.sources:
 192             if os.path.isdir(source):
 193                 for file in self.__subfiles(source):
 194                     yield self.getAudioStream(file)
 195             elif os.path.isfile(source):
 196                 yield self.getAudioStream(source)
 197             else:
 198                 print>>sys.stderr, "ERROR:"\
 199                     "'%s' is not a file or a directory."%source
 200 
 201     def __subfiles(self, path):
 202         for dirpath, dirnames, filenames in os.walk(path):
 203             for filename in filenames:
 204                 outputname=os.path.join(dirpath, filename)
 205                 if os.path.isfile(outputname) and\
 206                         filename[-4:].lower() in (".ogg", ".mp3"):
 207                     yield outputname
 208                 else:
 209                     print >>sys.stderr, "ERROR: ignoring '%s'"%(filename)
 210 
 211 
 212 #
 213 # ======================================================================
 214 # main program
 215 #
 216 if __name__ == "__main__":
 217     converter = Converter(sys.argv[1:])
 218 
 219     # TODO read these from a config file.
 220     converter.registerExtension("mp3", MP3Stream)
 221     converter.registerExtension("MP3", MP3Stream)
 222     converter.registerExtension("OGG", OGGStream)
 223     converter.registerExtension("ogg", OGGStream)
 224 
 225     for stream in converter.getStreams():
 226         print "Source:", stream.filename
 227         target=stream.getNewName(converter.keep_filenames, converter.single_directory)
 228         if converter.list_only:
 229             print "Target: %s"%(target)
 230         elif converter.force_convert or not os.path.exists(target):
 231             stream.convertToMP3()
 232         else:
 233             print "Target exists, not converting. Use -F to force."

Rakovalkea: AnttiKuntsi/mp3lbr (viimeksi muuttanut localhost, ajankohta 2009-11-03 15:03:39)