1 
   2 """
   3 This script converts Vorbis OGG and mp3 files to low-bitrate mp3 files.
   4 Requires at least the following Debian packages:
   5     python, python-pyvorbis, python-eyeD3, vorbistools, lame
   6 """
   7 
   8 import sys, os, ogg, ogg.vorbis, eyeD3
   9 import getopt, re
  10 
  11 usage = """Usage:
  12 %s [options] <source> [<source>...]"""%(os.path.basename(sys.argv[0]))
  13 help = """
  14     -h --help       this help
  15     -n --simple-names       Remove non-alnum chars from filename
  16     -d --single-directory   Do not create "artist - album" directory
  17     -l --list-files         List source and target files only
  18     -F --force-convert      Force conversion even if target file exists
  19 
  20     source      File or directory, directories are searched for ogg
  21             and mp3 -files.
  22 
  23 Found files are converted to 96kbps mp3-files, "-lbr" is appended to the
  24 filename. By default the files are placed in a "artist - album" directory.
  25 """
  26 
  27 #
  28 # ======================================================================
  29 # metaclass autosuper
  30 #
  31 class autosuper(type):
  32     """Automatically adds __super to the class"""
  33     def __init__(cls, name, bases, dict):
  34         super(autosuper, cls).__init__(name, bases, dict)
  35         setattr(cls, "_%s__super"%(name), super(cls))
  36 
  37 #
  38 # ======================================================================
  39 # Class AudioStream
  40 #
  41 class AudioStream:
  42     """Generic AudioStream base-class."""
  43     __metaclass__ = autosuper
  44     def __init__(self, filename):
  45         self.filename=filename
  46         self.artist=None
  47         self.track=None
  48         self.title=None
  49         self.album=None
  50         self.outputname=None
  51         self.outputflags=None
  52         self.lame_opts="--lowpass 14 --preset 96"
  53 
  54     def convertToMP3(self, outputname=None):
  55         """Method to convert the file into LBR MP3, the actual conversion is
  56         left to sub-classes. This class only make sure the directory exists."""
  57         path = os.path.dirname(target)
  58         if path:
  59             if not os.path.exists(path):
  60                 os.makedirs(path)
  61             elif not os.path.isdir(path):
  62                 print >>sys.stderr, "ERROR:"\
                    "'%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:"\
                    "'%s' is not a file or a directory."%source
 199 
 200     def __subfiles(self, path):
 201         for dirpath, dirnames, filenames in os.walk(path):
 202             for filename in filenames:
 203                 outputname=os.path.join(dirpath, filename)
 204                 if os.path.isfile(outputname) and\
                        filename[-4:].lower() in (".ogg", ".mp3"):
 205                     yield outputname
 206                 else:
 207                     print >>sys.stderr, "ERROR: ignoring '%s'"%(filename)
 208 
 209 
 210 #
 211 # ======================================================================
 212 # main program
 213 #
 214 if __name__ == "__main__":
 215     converter = Converter(sys.argv[1:])
 216 
 217     # TODO read these from a config file.
 218     converter.registerExtension("mp3", MP3Stream)
 219     converter.registerExtension("MP3", MP3Stream)
 220     converter.registerExtension("OGG", OGGStream)
 221     converter.registerExtension("ogg", OGGStream)
 222 
 223     for stream in converter.getStreams():
 224         print "Source:", stream.filename
 225         target=stream.getNewName(converter.keep_filenames, converter.single_directory)
 226         if converter.list_only:
 227             print "Target: %s"%(target)
 228         elif converter.force_convert or not os.path.exists(target):
 229             stream.convertToMP3()
 230         else:
 231             print "Target exists, not converting. Use -F to force."

Rakovalkea: AnttiKuntsi/mp3lbr (last edited 2009-11-03 15:03:39 by localhost)