#!/usr/bin/env python
#
# Python script for conveting from and to various audio formats.
# Should work recursively and handle funny names.
#
#
# Todo: Add an option to remove spaces and strange characters from
# filenames of output files (low priority).
# rearrange layout to be more readable.
#
# Version 0.4.5.3
#
# Changes:
# 0.1 Uses a system tempfile instead of a file named "tempfile", so multiple
# instances can be running at the same time.
#
# 0.2 Redesign for cleaner code, and slightly simpler use. Added flac and wav
# as input and output formats
#
# 0.3 Ported to windows (with downloaded exe's), and a newer version of mplayer
# that uses a different syntax.
#
# 0.3.1 Jim Hanley added support for decoding realaudio .rpm streams and rm/ra files.
#
# 0.3.2 Added a strict filter to remove genre tags that are not in a 'genre list',
# should solve the problem of Lame (or other encoders) failing to encode due to crazy
# genre tags.
#
# 0.3.3 Made default bitrate of output file equal to the input bitrate unless the bitrate
# option is specified. Skipping files that have a bitrate less than or equal to the specified
# output bitrate, this can be bypassed with the --force option.
#
# 0.4 Added support for Metaflac tags, and the option of using a different output (destination)
# directory as requested by Joe Oldak.
#
# 0.4.1 Small bugfix for import formats read by mplayer.
#
# 0.4.2
# -added -nolirc -nojoystick and to the mplayer decoder commands to reduce the warnings.
# -Changed the creation of a tempfile when decoding with mplayer. Unix not uses a normal tempfile,
# while 'nt' systems use a tempfile in the local directory.
# -There is a new --dry-run option that lists the files to be converted
# -There is also an --encoder-option option that allows passing of options to the encoder (currently lame,
# oggenc and flac). This is useful for things like vbr that are hard to make universal to every encoder.
#
# 0.4.3 Minor feature added. Added the 'tempfile' option to specify the location to be used for the pcm tempfile.
#
# 0.4.4 Refactor of the recursion logic. This should make recursion more robust and allow directories with square
# brackets and other strange characters. Previously this was causing problems with Glob. Done in a multi-platform way
# but still must test in windows.
#
# 0.4.5 Added an extra check so the original input file won't be deleted if the output file does not exist.
#
# 0.4.5.1 Small bugfix for flac decoding (and small cleanup), so filenames with spaces will be handled
# (Thanks to J. Huckabay).
#
# 0.4.5.2 Improved the "--dry-run" option; it will now show the path of the input and output file. Also switched from
# using os.system() to using subprocess.Popen() because control-c works much better among other things. Refactored other
# try statements so control-c won't mess them up. Added the ability to handle files with double quotes in the filename
# (crazy, but they exist). These can exist in *nix, but windows won't allow them - so its an easy fix.
#
# 0.4.5.3 Now replacing double quotes with single quotes for parsing of flac, mp3,
# and ogg meta tags.
#
# Chris LeBlanc, 2006
#
#
#
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU program General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# program General Public License for more details.
#
# You should have received a copy of the GNU program General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys
import os
from subprocess import *
import shutil
import glob
import StringIO
from random import randint
from string import join
from optparse import OptionParser
from tempfile import NamedTemporaryFile
# If the binaries are in the path (linux or windows).
MPLAYER = "mplayer"
OGGENC = "oggenc"
OGGDEC = "oggdec"
OGGINFO = "ogginfo"
LAME = "lame"
MP3INFO = "mp3info"
FLAC = "flac"
METAFLAC = "metaflac"
NORMALIZE = "normalize-audio"
### If the binaries are not in the path, list them here (eg windows). Using double slashes to exclude things like \n from
### being interpreted as newlines and such
##MPLAYER = "c:\\chris\\audio_conv\\mplayer\\mplayer.exe"
##OGGENC = "c:\\chris\\audio_conv\\oggenc.exe"
##OGGDEC = "c:\\chris\\audio_conv\\oggdec.exe"
##OGGINFO = "c:\\chris\\audio_conv\\ogginfo.exe"
##LAME = "C:\\chris\\audio_conv\\lame.exe"
##MP3INFO = "C:\\chris\\audio_conv\\mp3info.exe"
##FLAC = "C:\\chris\\audio_conv\\bin\\flac.exe"
##METAFLAC = "C:\\chris\\audio_conv\\bin\\metaflac.exe"
##NORMALIZE = "C:\\chris\\audio_conv\\normalize.exe"
def getCmdLineArgs():
parser = OptionParser()
parser.usage = """audio_conv.py -i [options] --to-mp3 | --to-ogg | --to-wav | --to-flac
This script is for conveting from an input audio format to an
intermediate wav file, then outputting to the selected output
format while keeping the tag info. At first this sounds crazy,
why go from one lossy format to another? This may be the only
option for your portable mp3 player. It is also an easy way to
convert a music collection from a high quality format (eg: flac
or wav) to a different format (eg: mp3 or ogg vorbis).
Optionally works recursively, keeps the tag information, and handles
filenames with strange characters such as spaces, single quotes, and
question marks. It can also output the converted file to a different
directory, maintaining the directory structure. Most of the other
wma2ogg or ogg2mp3 scripts fail on these or dont do recursion, which
is why this script exists.
Supported input formats:
wma
flac
wav
ogg
mp3
rpm RealAudio streams
rm/ra RealAudio files
Supported output formats:
wav
flac
mp3
ogg
Requires mplayer (for wma), vorbis tools, flac, lame, normalize
and mp3info. Note: if there are no tags in the input file, the
filename will be used as the 'title' tag in the output file.
Examples:
This example converts the ogg vorbis file 'foo.ogg' to 'foo.mp3':
audio_conv.py -i foo.ogg --to-mp3
This example converts all wma files to ogg vorbis files in a
directory (not recursive and wont delete the original files):
audio_conv.py -i "*.wma" --to-ogg
This command passes a string to the encoder (lame in this case,
since mp3s are being created), which can be any sort of option
(variable bitrate, mode, etc). The option here specifies that
variable bitrate level 6 is to be used (see encoder documentation
for more info):
audio_conv.py -i file.mp3 --to-mp3 --encoder-option "-V 6"
This dry-run example lists all the files that would be processed,
but are simply listed instead (an output format is needed, so
--to-mp3 is chosen though no conversion is done):
audio_conv.py -i "*.wma" -r --to-mp3 --dry-run
This command converts all recoginized files to mp3s recursively,
performs normalization and deletes the original files. If the
file doesn't have a recognized extension, then it is skipped.
audio_conv.py -i "*.*" --to-mp3 --recursive --normalize --delete
Here is the same command with short options:
audio_conv.py -i "*.*" --to-mp3 -r -n -d
This is the same command, but with a different destination directory.
This will duplicate the directory structure present in the source
to subdirectories of the destination directory populating it only with
the converted files.
audio_conv.py -i "*.*" --to-mp3 -r -n -d --dest-dir /home/user/newdir
"""
parser.add_option("-i", "--input", dest="inFile", \
help="Specify input file, accepts wildcards in quotes.", \
type="string", metavar="INPUT")
# parser.add_option("-o", "--output", dest="outFile", \
# help="Specify output filename (optional).", \
# type="string", metavar="OUTPUT")
parser.add_option("-b", "--bitrate", dest="bitrate", \
help="Specify bitrate of output file. Some encoders" + \
"(eg Lame) will only accept certain values such as 32, " + \
"40, 48, 56, 64, 80, 96, 112, 128, etc. The default bitrate " + \
"of the output file is the same as the input bitrate. This " + \
"option will be ignored if the '--encoder-option' is used.", \
type="int", metavar="BITRATE", default=None) #the default bitrate is set later
parser.add_option("-f", "--force", action="store_true", \
dest="force", help="Force conversion of files even " + \
"if the input and output bitrate are the same " + \
"[default skips these files].")
parser.add_option("-r", "--recursive", action="store_true", \
dest="recursive", help="Convert all files matching " + \
"the input filename in current directory and all " + \
"subdirectories. If using wildcards, they must be in " + \
"quotes.")
parser.add_option("--dry-run", action="store_true", \
dest="dryRun", help="List the path of each input file " + \
"and output file but do not convert anything.")
parser.add_option("--to-ogg", action="store_true", \
dest="oggOutput", help="Ogg vorbis output format.")
parser.add_option("--to-mp3", action="store_true", \
dest="mp3Output", help="Mp3 output format")
parser.add_option("--to-wav", action="store_true", \
dest="wavOutput", help="Wav output format")
parser.add_option("--to-flac", action="store_true", \
dest="flacOutput", help="Flac output format")
parser.add_option("-n", "--normalize", action="store_true", \
dest="normalize", help="Normalize the volume of " + \
"output files.")
parser.add_option("-d", "--delete", action="store_true", \
dest="delSource", help="Delete the input file after " + \
"conversion (use with caution!).")
parser.add_option("--dest-dir", dest="destDir", \
help="Specify a different output directory. This will " + \
"duplicate the same file and directory structure seen in " + \
"the source directory, and populate it with converted files. " + \
"If the destination directory does not exist, it will be created. " + \
"Works especially well with the recursive option.", \
type="string", metavar="DESTINATION", default=None)
parser.add_option("-t", "--tempfile", dest="tempFile", \
help="Specify the path for a user defined tempfile. The default " + \
"tempfile uses a system tempfile.", \
type="string", metavar="PATH", default=None)
parser.add_option("-e", "--encoder-option", dest="encodeOption", \
help="Specify options to be passed to the encoder. May " + \
"include any option supported by the encoder such as " + \
"variable bitrate encoding. This option overrides the 'bitrate' " + \
"option, so a custom bitrate or vbr setting should be specified " + \
"otherwsie the encoder will use its default bitrate. Be careful not " + \
"to specify tags that may conflict with tags taken from input file. " + \
"String must be encapsulated by quotes.", \
type="string", metavar="STRING", default="")
parser.add_option("-v", "--verbose", action="store_true", \
dest="verbose", help="Show all standard output and error " + \
"messages from backend programs. Default is to hide these messages.")
return parser.parse_args()
def mplayerTags(tagInfo):
# getting tag info from mplayer (it seems only name and author are given)
tagName = ""
tagAuthor = ""
inBitrate = None
for line in tagInfo:
fields = line.split()
try:
if fields[0] == "name:":
tagName = join(fields[1:], " ")
if fields[0] == "author:":
tagAuthor = join(fields[1:], " ")
if fields[0] == "AUDIO:":
inBitrate = fields[-2]
inBitrate = inBitrate.replace("(","")
except:
pass
return tagName, tagAuthor, inBitrate
def ogginfoTags(tagInfo):
tagName = ""
tagAuthor = ""
tagGenre = ""
tagDate = ""
tagAlbum = ""
inBitrate = ""
for line in tagInfo:
# replacing equals sign with whitespace
line = line.replace("="," ")
# replacing double quotes with single quotes
line = line.replace('"',"'")
# splitting on whitespace
fields = line.split()
try:
# lowercase so it can be used to parse metaflac info.
fields[0] = fields[0].lower()
if fields[0] == "title":
tagName = join(fields[1:], " ")
if fields[0] == "artist":
tagAuthor = join(fields[1:], " ")
if fields[0] == "genre":
tagGenre = join(fields[1:], " ")
if fields[0] == "date":
tagDate = join(fields[1:], " ")
if fields[0] == "album":
tagAlbum = join(fields[1:], " ")
if fields[0:2] == ['nominal', 'bitrate:']:
# getting the input bitrate, will cast to an int later.
inBitrate = fields[2]
except:
pass
return tagName, tagAuthor, tagGenre, tagDate, tagAlbum, inBitrate
# skipping files that have the same or less input bitrate as the desired output (and the same in/output format).
def badBitrate(file, inBitrate, options, inFileExtension, outFileExtension):
if outFileExtension == inFileExtension:
if options.force:
# forcing by making it look like the bitrates are not identical
return False
elif int(float(inBitrate)) == options.bitrate:
print("skipping %s, input and output bitrate are identical. Use -f to force conversion." % file)
return True
elif int(float(inBitrate)) < options.bitrate:
print("skipping %s, input bitrate less than output. Use -f to force conversion." % file)
return True
else:
return False
# if in/out file extensions are different, don't skip transcoding.
else:
return False
# duplicating old recursive directory structure in new destination directory. Tricky! TODO: test in windows
# the use of absolute paths may mess up the windows version of mplayer...
def destinationDir(file, destDir, dryRun):
# some variable needed for creating the destination directory structure.
fileBasename = os.path.basename(file) # basename of file, no leading directories.
fileAbs = os.path.abspath(file) # absolute, not relative
topDirAbs = os.path.abspath(topDir) # the 'top' directory, the subdris from here will be duplicated in the new dir.
destDirAbs = os.path.abspath(destDir) # absolute path of the destination directory
strippedPath = fileAbs.replace(topDirAbs, "")[1:] # stripping the 'top' directory from the path of the file to process.
newFileDir = os.path.dirname(strippedPath) # the directory of the stripped file, relative to the top directory (NOT absolute). Needs to be created if it doesn't exist!
newFileDirAbs = os.path.join(destDirAbs, newFileDir) # absolute path of the new directory including new subdirs.
newFilePath = os.path.join(newFileDirAbs, fileBasename) # joining the new path and basename
# making a copy of the directory structure, because it will be modified.
newFileDirCopy = newFileDir
dirList = []
while newFileDirCopy:
deepDir = newFileDirCopy
dirList.append(deepDir)
# modifying newFileDirCopy by stripping off the deepest subdir.
newFileDirCopy = os.path.split(newFileDirCopy)[0]
# no need to make new directories for a dry run.
if not dryRun:
# creating the dest-dir if it doesn't exist (not really necessary, but what the heck). May remove this later (?).
if not os.path.isdir(destDirAbs):
os.mkdir(destDirAbs)
# reversing the list so I make parent directories (if needed) before subdirectories
dirList.reverse()
for directory in dirList:
fullDirPath = os.path.join(destDirAbs, directory)
if not os.path.isdir(fullDirPath):
print "The following output driectory doesn't exist, creating:", fullDirPath
os.mkdir(fullDirPath)
return newFilePath
def runPopen(popenString, verbose):
popenOuput = None
if verbose:
# letting standard output go to terminal for verbosity
popenOutput = Popen(popenString, shell=True).communicate()[0]
else:
# capturing standard output from command instead
popenOutput = Popen(popenString, shell=True, stdout=PIPE, stderr=PIPE).communicate()[0]
popenInfo = StringIO.StringIO(popenOutput)
# print popenOutput
# for i in popenInfo:
# print i
return popenInfo
def gracefulExit():
# exiting program gracefully instead of messing up try statements and continuing on to process other files.
# may want to delete the tempfile here.
sys.exit()
if __name__ == "__main__":
# getting the command line options from the parser
(options,args)= getCmdLineArgs()
if not options.inFile:
print("Error: you must supply an input file (--input). \nType 'audio_conv.py -h' for help")
sys.exit()
topDir, wildCard = os.path.split(options.inFile)
absTopDir = os.path.abspath(topDir)
baseName = os.path.splitext(wildCard)[0]
wildCardExt = os.path.splitext(wildCard)[1]
if len(topDir) == 0:
topDir = "."
# The file(s) to process if not doing the recursive thing. Glob handles wildcards
# but you have to use quotes in *nix.
filesToProcess = []
for possibleFile in glob.glob(options.inFile):
if os.path.isfile(possibleFile):
filesToProcess.append(possibleFile)
elif not os.path.isdir(possibleFile):
print possibleFile, "not a regular file, skipping."
# handling the recursive case, still using glob for wildcards. Glob handles some paths poorly, so
# chaning to those dirs and using glob to expand contents of each dir.
workingDir = os.getcwd()
os.chdir(absTopDir)
if options.recursive:
# walking the present working dir, since we've changed to that dir.
for directories, subdirs, files in os.walk("."):
for subdir in subdirs:
subDirPath = os.path.join(directories, subdir)
# changing directory to the dirPath, because glob will fail on
# directories with square brackets in the path.
os.chdir(subDirPath)
for file in glob.glob(wildCard):
#only want real files and links
if os.path.isfile(file):
# 'file' is just the filename, joining with the path and appending to list
# of files to process.
absFilePath = os.path.join(topDir, directories, subdir, file)
# cleansing the path with normpath to make it easier for mplayer and such.
absFilePath = os.path.normpath(absFilePath)
filesToProcess.append(absFilePath)
# changing back to the parent directory of recursion for looping in other dirs.
os.chdir(absTopDir)
# returning to the original directory
os.chdir(workingDir)
# Using os.system line calls for all the heavy lifting
for file in filesToProcess:
# unix specific way of being able to read files with double quotes.
# Fine since double quotes are not allowed in windows.
file = file.replace('"', '\\"')
# user defined tempfile location for the pcm file.
# mplayer decoding uses a special tempfile for windows.
if options.tempFile:
tempFile = options.tempFile
else:
tempFile = NamedTemporaryFile().name
# leave the taginfo file as a system tempfile
tagInfo = NamedTemporaryFile().name
# metadata tags and input bitrate value
tagName = ""
tagAuthor = ""
tagGenre = ""
tagDate = ""
tagAlbum = ""
inBitrate = ""
# need to know output extension to see if conversion can be skipped with badBitrate().
if options.oggOutput:
outFileExtension = ".ogg"
elif options.mp3Output:
outFileExtension = ".mp3"
elif options.wavOutput:
outFileExtension = ".wav"
elif options.flacOutput:
outFileExtension = ".flac"
else:
print "Error: Audio output format not chosen, please select one."
break
# using the file extension to determine what format it is (there could be a better way,
# something like the unix command 'file')
# converting all to lower case for simplicity
fileCaseless = file.lower()
inFileExtension = os.path.splitext(fileCaseless)[1]
# Dry run, not doing conversion. Just listing files to be precessed
if options.dryRun:
if file == filesToProcess[0]:
print "Dry run file(s) to process, and new output file(s):"
# converting everything to a wav file, and getting the tag data
elif inFileExtension == ".mp3":
print "decoding:" + file
# using mp3info because it gives a lot of nice options for formatting of tag output.
# formatting so I can use ogginfoTags to parse the info. Using popen to subprocess.Popen to drive command line
popenString = ('%s %s "%s"' % (MP3INFO, '-x -r m -p "title=%t \\nartist=%a \\ngenre=%g \\ndate=%y \\nalbum=%l\\n \\nNominal bitrate: %r\\n"', file))
# tagInfo captures stdout and stderr from mp3info, stores as a string.
tagInfo = runPopen(popenString, verbose=False)
# using ogginfoTags to parse the tag info. Maybe I should rename it.
tagName, tagAuthor, tagGenre, tagDate, tagAlbum, inBitrate = ogginfoTags(tagInfo)
if badBitrate(file, inBitrate, options, inFileExtension, outFileExtension):
continue
# decoding mp3 with lame
decodeString = ('%s --decode "%s" "%s"' % (LAME, file, tempFile))
runPopen(decodeString, options.verbose)
elif inFileExtension in (".wma", ".rm", ".ra"):
print "decoding:" + file
# converting from wma to wav
# the 'pcm -aofile ' options has changed to '-ao pcm:file='
# which doesn't like dos filenames! (c:\bla\...) so I'm changing the tempfile path to
# point to the working directory. Also letting user set a tempfile location with a CLI option.
if (os.name == 'nt' and not options.tempFile):
tempFile = os.path.basename(tempFile)
# newer syntax for newer version of mplayer (1.0pre7-3.4.2) and dos/win compatible:
popenString = ('%s -quiet -nolirc -nojoystick -ao pcm:file="%s" -vo null -vc dummy "%s"' % (MPLAYER, tempFile, file))
# # older syntax, for mplayer 1.0pre5-3.3.4 and similar
# popenString = ('%s -quiet -nolirc -nojoystick -ao pcm -aofile "%s" -vo null -vc dummy "%s"' % (MPLAYER, tempFile, file))
tagInfo = runPopen(popenString, verbose=False)
if options.verbose:
for line in tagInfo:
line = line.replace("\n", "")
print line
# getting tag info from the info file created by mplayer in the last step.
tagName, tagAuthor, inBitrate = mplayerTags(tagInfo)
if badBitrate(file, inBitrate, options, inFileExtension, outFileExtension):
continue
elif inFileExtension == ".rpm":
readFile = open(file)
readLines = readFile.readlines()
for stream in readLines :
print "decoding stream:" + stream
# only process non blank lines
if len(stream) == 0:
continue
# os.system( '%s -cache 1280 -dumpstream -dumpfile essselection.ra %s' \
# % (MPLAYER, stream))
# syntax for newer mplayer, see above section for .wma files for older syntax
if (os.name == 'nt' and not options.tempFile):
tempFile = os.path.basename(tempFile)
# new mplayer syntax (not tested yet! get appropriate file to test)
popenString = ('%s -cache 1280 -quiet -nolirc -nojoystick -ao pcm:file="%s" -vo null -vc dummy "%s"' % (MPLAYER, tempFile, stream))
## old mplayer syntax
#popenString = ('%s -cache 1280 -quiet -nolirc -nojoystick -ao pcm -aofile="%s" -vo null -vc dummy "%s"' % (MPLAYER, tempFile, stream))
tagInfo = runPopen(popenString, verbose=False)
if options.verbose:
for line in tagInfo:
line = line.replace("\n", "")
print line
tagName, tagAuthor, inBitrate = mplayerTags(tagInfo)
if badBitrate(file, inBitrate, options, inFileExtension, outFileExtension):
continue
elif inFileExtension == ".ogg":
print "decoding:" + file
# getting tag info
popenTagString = ('%s "%s"' % (OGGINFO, file))
tagInfo = runPopen(popenTagString, verbose=False)
tagName, tagAuthor, tagGenre, tagDate, tagAlbum, inBitrate = ogginfoTags(tagInfo)
if badBitrate(file, inBitrate, options, inFileExtension, outFileExtension):
continue
# converting ogg to wav
popenString = ('%s "%s" -o "%s"' % (OGGDEC, file, tempFile))
encodeInfo = runPopen(popenString, options.verbose)
elif inFileExtension == ".flac":
print "decoding:" + file
# decoding from flac to wav
popenString = ('%s -f --decode "%s" -o "%s"' % (FLAC, file, tempFile))
encodeInfo = runPopen(popenString, options.verbose)
try:
# using metaflac to extract the tags from the flac file
popenTagString = ('%s --show-tag=TITLE --show-tag=ARTIST --show-tag=ALBUM --show-tag=DATE --show-tag=GENRE "%s"' \
% (METAFLAC, file))
# verbose is false because we want to capture the standard output instead of letting it go to the terminal.
# metaflac is quick, so it can be run again if the verbose option is given.
tagInfo = runPopen(popenTagString, verbose=False)
# we can use ogginfoTags to parse the tag info file since its almost the same format.
tagName, tagAuthor, tagGenre, tagDate, tagAlbum, inBitrate = ogginfoTags(tagInfo)
# this except statement is for handling control-c from command line. Otherwise if control-c is hit,
# the other except will be run. This allows the program to exit normally.
except (KeyboardInterrupt, SystemExit):
gracefulExit()
except:
print "No tags in flac file"
elif inFileExtension == ".wav":
# if its already a wave, leave it as is.
tempFile = file
else:
print "Error processing file: " + file
print "input format not recognized, please check file extension."
continue
# checking the genre tag to make sure its acceptable for Lame and other encoders (got listing from id3v2)
# should probably have dictionary in a different file, but its nice to have everything in one script.
# Testing the genre tag. Ignoring the 'genre as number' case. Not trying to handle crazy cases.
# Genre list generated by id3v2 program with -L option ('Bebob' looks like a typo but 'Bebop' wont work with Lame).
genreList = ('Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip-Hop', \
'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', \
'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', \
'Ambient', 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', \
'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', 'Alt. Rock', 'Bass', 'Soul', 'Punk', \
'Space', 'Meditative', 'Instrum. Pop', 'Instrum. Rock', 'Ethnic', 'Gothic', 'Darkwave', \
'Techno-Indust.', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 'Southern Rock', 'Comedy', \
'Cult', 'Gangsta', 'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', \
'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', \
'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', 'Folk', \
'Folk/Rock', 'National Folk', 'Swing', 'Fusion', 'Bebob', 'Latin', 'Revival', 'Celtic', \
'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Progress. Rock', 'Psychadel. Rock', 'Symphonic Rock', \
'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', \
'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 'Porn Groove', 'Satire', \
'Slow Jam', 'Club', 'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', \
'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'A Capella', 'Euro-House', 'Dance Hall', 'Goa', \
'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie', 'BritPop', 'Negerpunk', 'Polsk Punk', \
'Beat', 'Christian Gangsta Rap', 'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian', \
'Christian Rock', 'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'Jpop', 'Synthpop')
# skipping these steps if its a dry run
if not options.dryRun:
# discarding anything not in the list of genres.
if tagGenre not in genreList:
tagGenre = ""
# If there is no title tag, set it to the filename (without extension). Otherwise the file will show nothing in XMMS.
# Must test this, crazy filenames might cause problems with some encoders.
if not tagName:
print "No title tag, setting the title of song to the filename"
filePathless = os.path.split(file)[1]
fileBaseName = os.path.splitext(filePathless)[0]
tagName = fileBaseName
# setting the bitrate of the output file the same as the input file unless
# a bitrate is specified as an option. Bitrate not used for wav or flac.
if not options.bitrate and not (options.wavOutput or options.flacOutput):
try:
# nasty syntax but handles casting of the string 128.0000 to an int (example).
options.bitrate = int(float(inBitrate))
except (KeyboardInterrupt, SystemExit):
gracefulExit()
except:
print "cannot determine bitrate of input, setting output to 128 kbps."
options.bitrate = 128
if options.encodeOption:
options.bitrate = None
print "Custom encoder options specified, ignoring bitrate option if specified."
# optional normalization of the wav file
if options.normalize:
print "normalizing intermediate wav file"
normalizeString = ('%s "%s"' % (NORMALIZE, tempFile))
normalizeInfo = runPopen(normalizeString, options.verbose)
# setting the output filename by replacing the extension with the new one determined by the
# output format option.
if options.destDir:
# using the destinationDir function to handle making new directories and the new path names.
# dont want to make new directories if its only a dry run.
newOutFilePath = destinationDir(file, options.destDir, options.dryRun)
outFile = os.path.splitext(newOutFilePath)[0] + outFileExtension
else:
outFile = os.path.splitext(file)[0] + outFileExtension
# dry run, output info to terminal
bitrateStr = ""
if options.dryRun:
print "Input File:", file, "\nOutput File:", outFile
# writing to an ogg file
elif options.oggOutput:
print "encoding:", outFile
if options.bitrate:
bitrateStr = "-b " + str(options.bitrate)
# # converting from wav to ogg with some tag info included
encodeString = ('%s -t "%s" -a "%s" -G "%s" -d "%s" -l "%s" %s %s "%s" -o "%s"' % \
(OGGENC, tagName, tagAuthor, tagGenre, tagDate, tagAlbum, bitrateStr, options.encodeOption, tempFile, outFile))
runPopen(encodeString, options.verbose)
elif options.mp3Output:
print "encoding:", outFile
if options.bitrate:
bitrateStr = "-b " + str(options.bitrate)
# converting wav to mp3
encodeString = ('%s --tt "%s" --ta "%s" --tg "%s" --ty "%s" --tl "%s" -h %s %s "%s" -o "%s"' % \
(LAME, tagName, tagAuthor, tagGenre, tagDate, tagAlbum, bitrateStr, options.encodeOption, tempFile, outFile))
runPopen(encodeString, options.verbose)
elif options.wavOutput:
print "outputting:", outFile
# just copying the tempfile (wav) to the output filename - easy.
shutil.copyfile(tempFile, outFile)
elif options.flacOutput:
print "encoding:", outFile
# writing out from wav to flac format.
encodeString = ('%s -f "%s" %s -o "%s"' % (FLAC, tempFile, options.encodeOption, outFile))
runPopen(encodeString, options.verbose)
## Updating tags with metaflac
flacTagString = ('%s --set-tag=TITLE="%s" --set-tag=ARTIST="%s" --set-tag=ALBUM="%s" --set-tag=DATE="%s" --set-tag=GENRE="%s" "%s"' \
% (METAFLAC, tagName, tagAuthor, tagAlbum, tagDate, tagGenre, outFile))
runPopen(flacTagString, options.verbose)
else:
print "No output format chosen, output file not written. Please select output format."
# dangerous option here, deleting the input file after conversion
# Todo: only run these two cleanup items if no exceptions have been raised.
if options.delSource:
# if the output file doesn't exist, something went wrong and we should not delete the source
# even if --delete is specified.
if not os.path.isfile(outFile):
print "Output file does not exist, input file will not be deleted."
continue
# if the new output filename is the same as the original input, dont delete original
# because it has already been overwritten by the new one.
if file == outFile:
continue
# removing the input file
try:
os.remove(file)
# exiting normally if control-c is hit instead of deleting file!
except (KeyboardInterrupt, SystemExit):
gracefulExit()
except:
print "could not remove input file:", file
# manually removing tempfiles just to make sure the disk doesn't get cluttered
try:
# only trying to remove the tempfile if it exists. This will make the dry run output clearer.
if os.path.isfile(tempFile):
os.remove(tempFile)
except (KeyboardInterrupt, SystemExit):
gracefulExit()
except:
print "error: could not remove tempfile"
print "----"