From b1c05c352fafc2f505b17d0a4167c1420f63b2c7 Mon Sep 17 00:00:00 2001 From: Eric Renfro Date: Mon, 4 Jul 2016 18:50:52 -0400 Subject: [PATCH] Almost finished. Cleanup and some functionality left to do. --- .gitignore | 3 + musicman.py | 202 ++++++++++++------ musicman/utils/__init__.py | 8 +- .../utils/__pycache__/__init__.cpython-33.pyc | Bin 5382 -> 0 bytes musicman/utils/copytags.py | 125 +++++++++++ 5 files changed, 268 insertions(+), 70 deletions(-) create mode 100644 .gitignore delete mode 100644 musicman/utils/__pycache__/__init__.cpython-33.pyc create mode 100644 musicman/utils/copytags.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5da7ef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.pyo + diff --git a/musicman.py b/musicman.py index 5f7cb0f..a19dc51 100755 --- a/musicman.py +++ b/musicman.py @@ -10,6 +10,7 @@ import time #from path import path import musicman +import mutagen #from musicman.utils.constants import ( # VERSION, @@ -101,13 +102,21 @@ def getEmptyDirs(path, excludeDirs=[]): if not directories and not filenames: yield root -def getSong(file): +def getSong(file, orgDir=None, tmpDir=None, dstDir=None): from musicman.utils.metadata import MetaTag from musicman.utils.constants import INTERNAL_FORMATS global config - song = {} + global originDir + global workingDir + global targetDir + global targetFormat + + song = {} + orgDir = originDir if orgDir is None else orgDir + tmpDir = workingDir if tmpDir is None else tmpDir + dstDir = targetDir if dstDir is None else dstDir + - #if file.endswith(INTERNAL_FORMATS): if (os.path.splitext(file)[1][1:] in INTERNAL_FORMATS): metadata = MetaTag(file) @@ -150,6 +159,12 @@ def getSong(file): song['outPath'] = os.path.join(song['artistName'], song['albumName']) outFile = '{0}'.format(song['titleName']) + song['originFile'] = os.path.join(orgDir, song['outPath'], song['outFile']) + song['workingFile'] = os.path.join(tmpDir, song['outPath'], song['outFile']) + song['targetFile'] = os.path.join(dstDir, song['outPath'], song['outFile']) + song['originExt'] = os.path.splitext(file)[1] + song['targetExt'] = '.' + targetFormat + return song else: clearLine() @@ -157,31 +172,20 @@ def getSong(file): print(" Supported:", ", ".join(INTERNAL_FORMATS)) sys.exit(2) -def getSongPaths(song): - global originDir - global workingDir - global targetDir - - print("DEBUG:", originDir) - print("DEBUG:", workingDir) - print("DEBUG:", targetDir) - - sys.exit(0) - -def cleanLibrary(originDir, excludeDirs=[], act=False, verbose=0): +def cleanLibrary(rootDir, excludeDirs=[], act=False, verbose=0): global config if excludeDirs is None: excludeDirs=[] - print("Clean Dirs") + print("Clean Dirs:", rootDir) - #while getEmptyDirs(originDir, excludeDirs) != [] and act == True and tries > 0: + #while getEmptyDirs(rootDir, excludeDirs) != [] and act == True and tries > 0: if act: try: - while getEmptyDirs(originDir, excludeDirs).__next__(): - for path in getEmptyDirs(originDir, excludeDirs): + while getEmptyDirs(rootDir, excludeDirs).__next__(): + for path in getEmptyDirs(rootDir, excludeDirs): print("Removing:", path) if act: @@ -193,64 +197,56 @@ def cleanLibrary(originDir, excludeDirs=[], act=False, verbose=0): except StopIteration: pass else: - for path in getEmptyDirs(originDir, excludeDirs): + for path in getEmptyDirs(rootDir, excludeDirs): print("Empty:", path) - print("Processing Complete!") + #print("Processing Complete!") -def renameLibrary(originDir, excludeDirs=[], act=False, verbose=0): +def renameLibrary(rootDir, excludeDirs=[], act=False, verbose=0): global config if excludeDirs is None: excludeDirs=[] - print("Rename") - for file in getLibrary(originDir, excludeDirs): - #print("File:", file) + print("Renaming:", rootDir) + for file in getLibrary(rootDir, excludeDirs): if (os.path.isdir(os.path.dirname(file)) and os.path.isfile(file)): clearLine() print("Processing:", os.path.dirname(os.path.dirname(file)), next(spinner), end="\r") - song = getSong(file) + song = getSong(file, rootDir) - #print("song", song) if song['metadata'] is None: if verbose > 2: clearLine() print("Skipping: {0} due to lack of metadata".format(file)) else: - filename = os.path.basename(file) - file_ext = os.path.splitext(filename)[1] - - if not os.path.isfile(os.path.join(originDir, song['outPath'], song['outFile'] + file_ext)): + if not os.path.isfile(song['originFile'] + song['originExt']): clearLine() print("Found:", file) if verbose > 0: - print(" New:", os.path.join(originDir, song['outPath'], song['outFile'] + file_ext)) + print(" New:", song['originFile'] + song['originExt']) if (act): if verbose > 1: - print("Renaming: \"{0}\" -> \"{1}\"".format(file, os.path.join(originDir, song['outPath'], song['outFile'] + file_ext))) + print("Renaming: \"{0}\" -> \"{1}\"".format(file, song['originFile'] + song['originExt'])) try: - os.renames(file, - os.path.join(originDir, - song['outPath'], - song['outFile'] + file_ext)) + os.renames(file, song['originFile'] + song['originExt']) except OSError as err: print("ERROR: Failed to move:", err) sys.exit(5) clearLine() - print("Processing Complete!") + #print("Processing Complete!") -def findUntagged(originDir, excludeDirs=[], verbose=0): +def findUntagged(rootDir, excludeDirs=[], verbose=0): global config if excludeDirs is None: excludeDirs=[] - print("Find Untagged") - for file in getLibrary(originDir, excludeDirs): + print("Find Untagged:", rootDir) + for file in getLibrary(rootDir, excludeDirs): if (os.path.isdir(os.path.dirname(file)) and os.path.isfile(file)): clearLine() print("Processing:", os.path.dirname(os.path.dirname(file)), next(spinner), end="\r") @@ -260,49 +256,43 @@ def findUntagged(originDir, excludeDirs=[], verbose=0): if song['metadata'] is None: clearLine() print("Untagged: {0}".format(file)) + clearLine() - print("Processing Complete!") + #print("Processing Complete!") -def findNew(originDir, workingDir, targetDir, targetFormat, excludeDirs=[], verbose=0): +def findNew(rootDir, tmpDir, dstDir, dstFormat, excludeDirs=[], verbose=0): global config if excludeDirs is None: excludeDirs=[] print("Find New Media") - for file in getLibrary(originDir, excludeDirs): + for file in getLibrary(rootDir, excludeDirs): if (os.path.isdir(os.path.dirname(file)) and os.path.isfile(file)): clearLine() print("Processing:", os.path.dirname(os.path.dirname(file)), next(spinner), end="\r") song = getSong(file) - getSongPaths(song) - if song['metadata'] is None: if verbose > 2: clearLine() print("Skipping: {0} due to lack of metadata".format(file)) else: - filename = os.path.basename(file) - file_ext = os.path.splitext(filename)[1] - - if not (os.path.isfile(os.path.join(workingDir, song['outPath'], song['outFile'] + '.' + config['target']['format'])) or - os.path.isfile(os.path.join(targetDir, song['outPath'], song['outFile'] + '.' + config['target']['format']))): + if not (os.path.isfile(song['workingFile'] + song['targetExt']) or + os.path.isfile(song['targetFile'] + song['targetExt'])): + clearLine() print("New:", file) if verbose > 0: - if not os.path.isfile(os.path.join(targetDir, song['outPath'], song['outFile'] + '.' + config['target']['format'])): - print(" No:", os.path.join(targetDir, song['outPath'], song['outFile'] + '.' + config['target']['format'])) - elif not os.path.isfile(os.path.join(workingDir, song['outPath'], song['outFile'] + '.' + config['target']['format'])): - print(" No:", os.path.join(workingDir, song['outPath'], song['outFile'] + '.' + config['target']['format'])) - #if not os.path.isfile(os.path.join(originDir, song['outPath'], song['outFile'] + file_ext)): - # clearLine() - # print("New:", file) + if not os.path.isfile(os.path.isfile(song['workingFile'] + song['targetExt'])): + print(" No:", song['workingFile'] + song['targetExt']) + elif not os.path.isfile(os.path.isfile(song['targetFile'] + song['targetExt'])): + print(" No:", song['targetFile'] + song['targetExt']) - clearLine() - print("Processing Complete!") + #clearLine() + #print("Processing Complete!") -def syncWorking(workingDir, targetDir, excludeDirs=[], act=False, verbose=0): +def syncWorking(tmpDir, excludeDirs=[], act=False, verbose=0): global config if excludeDirs is None: @@ -310,10 +300,9 @@ def syncWorking(workingDir, targetDir, excludeDirs=[], act=False, verbose=0): print("Sync Target Media") - for file in getLibrary(workingDir, excludeDirs): + for file in getLibrary(tmpDir, excludeDirs): if (os.path.isdir(os.path.dirname(file)) and os.path.isfile(file)): clearLine() - #print("Checking:", file) print("Processing:", os.path.dirname(os.path.dirname(file)), next(spinner), end="\r") song = getSong(file) @@ -326,17 +315,84 @@ def syncWorking(workingDir, targetDir, excludeDirs=[], act=False, verbose=0): filename = os.path.basename(file) file_ext = os.path.splitext(filename)[1] - if not os.path.isfile(os.path.join(targetDir, song['outPath'], song['outFile'] + file_ext)): + if not os.path.isfile(song['targetDir'] + song['originExt']): print("Sync:", file) if verbose > 0: - print(" To:", os.path.join(targetDir, song['outPath'], song['outFile'] + file_ext)) + print(" To:", song['targetFile'], song['originExt']) if act: print("would've acted") + + + #if not os.path.isfile(os.path.join(destDir, song['outPath'], song['outFile'] + file_ext)): + # print("Sync:", file) + # if verbose > 0: + # print(" To:", os.path.join(destDir, song['outPath'], song['outFile'] + file_ext)) + # + # if act: + # print("would've acted") - clearLine() - print("Processing Complete!") + #clearLine() + #print("Processing Complete!") +def convertMedia(rootDir, tmpDir, dstDir, dstFormat, excludeDirs=[], act=False, verbose=0): + import subprocess + from musicman.utils.copytags import ( + copy_tags + ) + + if excludeDirs is None: + excludeDirs=[] + + print("Convert Library Media") + + for file in getLibrary(rootDir, excludeDirs): + if (os.path.isdir(os.path.dirname(file)) and os.path.isfile(file)): + clearLine() + print("Processing:", os.path.dirname(os.path.dirname(file)), next(spinner), end="\r") + + song = getSong(file, rootDir, tmpDir, targetDir) + + if song['metadata'] is None: + if verbose > 2: + clearLine() + print("Skipping: {0} due to lack of metadata".format(file)) + else: + if not (os.path.isfile(song['workingFile'] + song['targetExt']) or + os.path.isfile(song['targetFile'] + song['targetExt'])): + clearLine() + print("New:", file) + if verbose > 2: + if not os.path.isfile(os.path.isfile(song['workingFile'] + song['targetExt'])): + print(" No:", song['workingFile'] + song['targetExt']) + elif not os.path.isfile(os.path.isfile(song['targetFile'] + song['targetExt'])): + print(" No:", song['targetFile'] + song['targetExt']) + + if act: + #print("would've acted") + if not os.path.isdir(os.path.dirname(song['workingFile'])): + os.makedirs(os.path.dirname(song['workingFile'])) + #subprocess.call("ffmpeg", "-loglevel", "quiet", "-stats", "-i", file, "-vn", "-c:a", "libfdk_aac", "-vbr", "5", "-nostdin", "-y", song['workingFile'] + song['targetExt']) + try: + subprocess.call('/usr/bin/ffmpeg -loglevel quiet -stats -i "{0}" -vn -c:a libfdk_aac -vbr 5 -nostdin -y "{1}"'.format(file, song['workingFile'] + song['targetExt']), shell=True) + #new = MetaData(song['workingFile'] + song['targetExt']) + #new.update(song['metadata']) + #new.save() + + #old = mutagen.File(file) + #new = mutagen.File(song['workingFile'] + song['targetExt']) + #new.update(old) + #new.save() + copy_tags(file, song['workingFile'] + song['targetExt']) + except KeyboardInterrupt: + if os.path.isfile(song['workingFile'] + song['targetExt']): + os.remove(song['workingFile'] + song['targetExt']) + clearLine() + print(end='\r') + print("Aborted by user") + sys.exit(0) + + clearLine() if __name__ == '__main__': global opt @@ -377,9 +433,13 @@ if __name__ == '__main__': try: if opt.mode == 'clean': cleanLibrary(originDir, opt.excludeDirs, opt.act, opt.verbose) + cleanLibrary(targetDir, opt.excludeDirs, opt.act, opt.verbose) + cleanLibrary(workingDir, opt.excludeDirs, opt.act, opt.verbose) elif opt.mode == 'rename': renameLibrary(originDir, opt.excludeDirs, opt.act, opt.verbose) + renameLibrary(targetDir, opt.excludeDirs, opt.act, opt.verbose) + renameLibrary(workingDir, opt.excludeDirs, opt.act, opt.verbose) elif opt.mode == 'scan': if opt.scanMode is None: @@ -387,15 +447,23 @@ if __name__ == '__main__': sys.exit(1) elif opt.scanMode == 'untagged': findUntagged(originDir, opt.excludeDirs, opt.verbose) + findUntagged(targetDir, opt.excludeDirs, opt.verbose) + findUntagged(workingDir, opt.excludeDirs, opt.verbose) elif opt.scanMode == 'new': findNew(originDir, workingDir, targetDir, targetFormat, opt.excludeDirs, opt.verbose) elif opt.mode == 'sync': syncWorking(workingDir, targetDir, opt.excludeDirs, opt.act, opt.verbose) + + elif opt.mode == 'convert': + convertMedia(originDir, workingDir, targetDir, targetFormat, opt.excludeDirs, opt.act, opt.verbose) except KeyboardInterrupt: clearLine() print(end='\r') print("Aborted by user") + sys.exit(0) + clearLine() + print("Processing Complete!") #for file in getLibrary(config['origin']['path']): diff --git a/musicman/utils/__init__.py b/musicman/utils/__init__.py index 0370c73..9b8846b 100644 --- a/musicman/utils/__init__.py +++ b/musicman/utils/__init__.py @@ -23,6 +23,8 @@ def parse_args(): clean = subparsers.add_parser('clean', help='Clean Mode', description='Library cleanup operations. Allows you to remove empty directories.') clean_lib = clean.add_argument_group('Library Options') clean_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir") + clean_lib.add_argument('-t', '--target', help="Target Directory for Library (overrides config)", metavar="DIR", type=str, dest="targetDir") + clean_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workingDir") clean_lib.add_argument('-e', '--exclude', help="Exclude Directory from origin (can be used multiple times)", metavar="DIR", dest='excludeDirs', action='append') clean_act = clean.add_argument_group('Action Options') clean_act.add_argument('-g', '--go', help="Clean up library (default just shows what would be done)", dest="act", action='store_true') @@ -32,8 +34,8 @@ def parse_args(): convert_lib = convert.add_argument_group('Library Options') convert_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir") convert_lib.add_argument('-t', '--target', help="Target Directory for Library (overrides config)", metavar="DIR", type=str, dest="targetDir") + convert_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workingDir") convert_lib.add_argument('--format', help="Target directory library format", metavar="FORMAT", type=str, dest="targetFormat") - convert_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workDir") convert_lib.add_argument('-e', '--exclude', help="Exclude Directory from origin (can be used multiple times)", metavar="DIR", dest='excludeDirs', action='append') convert_act = convert.add_argument_group('Action Options') convert_act.add_argument('-g', '--go', help="Convert media (default just shows new items)", dest="act", action='store_true') @@ -54,8 +56,8 @@ def parse_args(): scan_lib = scan.add_argument_group('Library Options') scan_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir") scan_lib.add_argument('-t', '--target', help="Target Directory for Library (overrides config)", metavar="DIR", type=str, dest="targetDir") + scan_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workingDir") scan_lib.add_argument('--format', help="Target directory library format", metavar="FORMAT", type=str, dest="targetFormat") - scan_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workDir") scan_lib.add_argument('-e', '--exclude', help="Exclude Directory from origin (can be used multiple times)", metavar="DIR", dest='excludeDirs', action='append') scan_subparsers = scan.add_subparsers(title='Scan Modes', dest='scanMode') scan_untagged = scan_subparsers.add_parser('untagged', help='Find untagged media', description='Scans for untagged or insufficiently tagged media in the library.') @@ -63,7 +65,7 @@ def parse_args(): sync = subparsers.add_parser('sync', help='Sync Mode', description='Moves media from working DIR into target DIR') sync_lib = sync.add_argument_group('Library Options') - sync_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir") + #sync_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir") sync_lib.add_argument('-t', '--target', help="Target Directory for Library (overrides config)", metavar="DIR", type=str, dest="targetDir") sync_lib.add_argument('-w', '--work', help="Working Directory for new processed files (overrides config)", metavar="DIR", type=str, dest="workingDir") sync_lib.add_argument('-e', '--exclude', help="Exclude Directory from origin (can be used multiple times)", metavar="DIR", dest='excludeDirs', action='append') diff --git a/musicman/utils/__pycache__/__init__.cpython-33.pyc b/musicman/utils/__pycache__/__init__.cpython-33.pyc deleted file mode 100644 index ba5ef8c5c8c0b08301b1fc4a0abb612726180ede..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5382 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