Initial commit

This commit is contained in:
root 2016-07-03 23:43:32 -04:00
commit a0e50ceaf7
18 changed files with 1086 additions and 0 deletions

17
.project Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>mediamanager_media4</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>

11
musicman.ini Normal file
View file

@ -0,0 +1,11 @@
[origin]
Path = /srv/public/Music-Lossless
format = flac
[target]
Path = /srv/public/Music-iTunes
format = m4a
[working]
Path = /srv/public/Music-iTunes.new

564
musicman.py Executable file
View file

@ -0,0 +1,564 @@
#!/usr/bin/env python3
from __future__ import division, absolute_import, print_function, unicode_literals
import os
import signal
import sys
#import mutagen
import time
#from path import path
import musicman
#from musicman.utils.constants import (
# VERSION,
# NO_TAGS
#)
#from musicman.utils import (
# parse_args,
# load_config
#)
#from musicman.utils.metadata import MetaTag
def spinning_cursor():
while True:
for cursor in '|/-\\':
yield cursor
spinner = spinning_cursor()
def supports_color():
"""
Returns True if the running system's terminal supports color, and False
otherwise.
"""
plat = sys.platform
supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
'ANSICON' in os.environ)
# isatty is not always implemented, #6223.
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
if not supported_platform or not is_a_tty:
return False
return True
def clearLine():
if supports_color():
sys.stdout.write('\033[K')
sys.stdout.flush()
def sanitize(text):
newtext = text
newtext = newtext.replace('<', '').replace('>', '').replace(':', '').replace('"', '').replace('|', '').replace('?', '').replace('*', '')
newtext = newtext.replace('/', '-')
newtext = newtext.strip()
#if newtext != text:
# clearLine()
# print("text:", text)
# print(" new:", newtext)
return newtext
def getLibrary(path, excludeDirs=[]):
if os.path.isdir(path):
for root, directories, filenames in sorted(os.walk(path)):
if root not in excludeDirs:
for filename in filenames:
yield os.path.join(root, filename)
# files=[]
#
# if os.path.isdir(path):
# for libraryDir, artists, dummy in os.walk(path):
# for artist in sorted(artists):
# for artistDir, albums, dummy in os.walk(os.path.join(libraryDir, artist)):
# if artistDir in excludeDirs:
# #clearLine()
# #print("Excluding:", artistDir)
# #print()
# continue
# for album in sorted(albums):
# for albumDir, dummy, songs in os.walk(os.path.join(artistDir, album)):
# if albumDir in excludeDirs:
# #clearLine()
# #print("Excluding:", albumDir)
# #print()
# continue
# #print("AlbumDir:", albumDir)
# #print(" Artist:", artist)
# #print(" Album:", album)
# for song in songs:
# print("Scanning", path, next(spinner), end="\r")
# files.append(os.path.join(albumDir, song))
# #print("Path:", os.path.join(albumDir, song))
# clearLine()
# print("Scan complete!")
# return files
def getEmptyDirs(path, excludeDirs=[]):
if os.path.isdir(path):
for root, directories, filenames in sorted(os.walk(path)):
if root not in excludeDirs:
if not directories and not filenames:
yield root
def getSong(file):
from musicman.utils.metadata import MetaTag
from musicman.utils.constants import INTERNAL_FORMATS
global config
song = {}
#if file.endswith(INTERNAL_FORMATS):
if (os.path.splitext(file)[1][1:] in INTERNAL_FORMATS):
metadata = MetaTag(file)
if metadata.tags.get("artist") is None or len(metadata.tags.get("artist")) < 1:
return {'metadata': None}
elif metadata.tags.get("albumartist") is None or len(metadata.tags.get("albumartist")) < 1:
return {'metadata': None}
elif metadata.tags.get('album') is None or len(metadata.tags.get("album")) < 1:
return {'metadata': None}
elif metadata.tags.get("musicbrainz_albumid") is None or len(metadata.tags.get("musicbrainz_albumid")) < 5:
return {'metadata': None}
else:
song['metadata'] = metadata
song['artistName'] = sanitize(metadata.tags["albumartist"])
song['albumName'] = sanitize(metadata.tags["album"])
song['titleName'] = sanitize(metadata.tags["title"])
if isinstance(metadata.tags.get("totaldiscs", 0), tuple):
song['discnumber'] = int(metadata.tags.get('totaldiscs', 0)[0])
song['totaldiscs'] = int(metadata.tags.get("totaldiscs", 0)[1])
else:
song['discnumber'] = int(metadata.tags.get('discnumber', 0))
song['totaldiscs'] = int(metadata.tags.get("totaldiscs", 0))
if isinstance(metadata.tags.get('totaltracks', 0), tuple):
song['tracknumber'] = int(metadata.tags.get('totaltracks', 0)[0])
song['totaltracks'] = int(metadata.tags.get('totaltracks', 0)[1])
else:
song['tracknumber'] = int(metadata.tags.get('tracknumber', 0))
song['totaltracks'] = int(metadata.tags.get('totaltracks', 0))
if song['totaldiscs'] > 1:
song['outPath'] = os.path.join(song['artistName'], song['albumName'])
song['outFile'] = '{0:d}-{1:02d}-{2}'.format(song['discnumber'],
song['tracknumber'],
song['titleName'])
else:
if metadata.tags.get("tracknumber") is not None:
song['outPath'] = os.path.join(song['artistName'], song['albumName'])
song['outFile'] = '{0:02d}-{1}'.format(song['tracknumber'], song['titleName'])
else:
song['outPath'] = os.path.join(song['artistName'], song['albumName'])
outFile = '{0}'.format(song['titleName'])
return song
else:
clearLine()
print("FATAL: File extension \"{0}\" is not supported.".format(os.path.splitext(file)[1]))
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):
global config
if excludeDirs is None:
excludeDirs=[]
print("Clean Dirs")
#while getEmptyDirs(originDir, excludeDirs) != [] and act == True and tries > 0:
if act:
try:
while getEmptyDirs(originDir, excludeDirs).__next__():
for path in getEmptyDirs(originDir, excludeDirs):
print("Removing:", path)
if act:
try:
os.rmdir(path)
except OSError as err:
print("ERROR: Failed to remove directory:", err)
sys.exit(5)
except StopIteration:
pass
else:
for path in getEmptyDirs(originDir, excludeDirs):
print("Empty:", path)
print("Processing Complete!")
def renameLibrary(originDir, excludeDirs=[], act=False, verbose=0):
global config
if excludeDirs is None:
excludeDirs=[]
print("Rename")
for file in getLibrary(originDir, excludeDirs):
#print("File:", file)
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)
#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)):
clearLine()
print("Found:", file)
if verbose > 0:
print(" New:", os.path.join(originDir, song['outPath'], song['outFile'] + file_ext))
if (act):
if verbose > 1:
print("Renaming: \"{0}\" -> \"{1}\"".format(file, os.path.join(originDir, song['outPath'], song['outFile'] + file_ext)))
try:
os.renames(file,
os.path.join(originDir,
song['outPath'],
song['outFile'] + file_ext))
except OSError as err:
print("ERROR: Failed to move:", err)
sys.exit(5)
clearLine()
print("Processing Complete!")
def findUntagged(originDir, excludeDirs=[], verbose=0):
global config
if excludeDirs is None:
excludeDirs=[]
print("Find Untagged")
for file in getLibrary(originDir, 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)
if song['metadata'] is None:
clearLine()
print("Untagged: {0}".format(file))
clearLine()
print("Processing Complete!")
def findNew(originDir, workingDir, targetDir, targetFormat, excludeDirs=[], verbose=0):
global config
if excludeDirs is None:
excludeDirs=[]
print("Find New Media")
for file in getLibrary(originDir, 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']))):
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)
clearLine()
print("Processing Complete!")
def syncWorking(workingDir, targetDir, excludeDirs=[], act=False, verbose=0):
global config
if excludeDirs is None:
excludeDirs=[]
print("Sync Target Media")
for file in getLibrary(workingDir, 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)
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(targetDir, song['outPath'], song['outFile'] + file_ext)):
print("Sync:", file)
if verbose > 0:
print(" To:", os.path.join(targetDir, song['outPath'], song['outFile'] + file_ext))
if act:
print("would've acted")
clearLine()
print("Processing Complete!")
if __name__ == '__main__':
global opt
global config
global originPath, targetPath, targetFormat, workPath
import configparser
#opt, files = parse_args()
opt = musicman.utils.parse_args()
config = musicman.utils.load_config()
print("opt:", opt)
try:
originDir = config['origin']['path'] if opt.originDir is None else opt.originDir
except AttributeError:
originDir = config['origin']['path']
try:
targetDir = config['target']['path'] if opt.targetDir is None else opt.targetDir
except AttributeError:
targetDir = config['target']['path']
try:
targetFormat = config['target']['format'] if opt.targetFormat is None else opt.targetFormat
except AttributeError:
targetFormat = config['target']['format']
try:
workingDir = config['working']['path'] if opt.workingDir is None else opt.workingDir
except AttributeError:
workingDir = config['working']['path']
if opt.mode is None:
print("ERROR: No command provided.")
sys.exit(1)
try:
if opt.mode == 'clean':
cleanLibrary(originDir, opt.excludeDirs, opt.act, opt.verbose)
elif opt.mode == 'rename':
renameLibrary(originDir, opt.excludeDirs, opt.act, opt.verbose)
elif opt.mode == 'scan':
if opt.scanMode is None:
print("ERROR: Subcommand for scan not provided.")
sys.exit(1)
elif opt.scanMode == 'untagged':
findUntagged(originDir, 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)
except KeyboardInterrupt:
clearLine()
print(end='\r')
print("Aborted by user")
#for file in getLibrary(config['origin']['path']):
# #print("File:", file)
# 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")
# #time.sleep(0.01)
#
# #print("Path:", os.path.dirname(file))
# #print("File:", os.path.basename(file))
#clearLine()
#print("Processing Complete!")
sys.exit(0)
#config = configparser.ConfigParser()
#config.read('library.ini')
#print(config.sections())
#print(config["lossless"]["Path"])
#try:
# #print("Test1:", config.get('lossless', 'test'))
# config.get('lossless', 'path')
# config.get('lossless', 'format')
# config.get('converted', 'path')
# config.get('converted', 'format')
#except configparser.NoOptionError as err:
# print("ERROR: Configuration of required settings are missing:", err)
# sys.exit(1)
LosslessLibraryRoot = '/srv/public/Music-Lossless'
#LosslessLibraryRoot = '/srv/public/Music-iTunes'
iTunesLibraryRoot = '/srv/public/Music-iTunes'
#artistWalker = os.walk(LosslessLibraryRoot)
#dest_dir, artists, files = artistWalker.next()
#print "dest_dir: %s" % dest_dir
#print "artists: %s" % artists
#print "files: %s" % files
#for artist in artists:
# print "Artist: %s" % artist
# albumWalker = os.walk(os.path.join(dest_dir, artist))
# artist_dir, albums, artist_files = albumWalker.next()
# print "Albums: %s" % albums
# print "Album Dir: %s" % artist_dir
# for album in albums:
# songWalker = os.walk(os.path.join(artist_dir, album)
# album_dir, dummy, songs = songWalker.next()
for libraryDir, artists, dummy in os.walk(config['origin']['path']):
for artist in sorted(artists):
for artistDir, albums, dummy in os.walk(os.path.join(libraryDir, artist)):
for album in sorted(albums):
for albumDir, dummy, songs in os.walk(os.path.join(artistDir, album)):
#print("AlbumDir:", albumDir)
#print(" Artist:", artist)
#print(" Album:", album)
for song in songs:
if song.endswith('.flac'):
#print " Song: %s" % song
metadata = MetaTag(os.path.join(albumDir, song))
#if os.path.isfile(os.path.join()
#print "MetaData: %s" % metadata.tags
#os.path.join(iTunesLibraryRoot, [metadata.tags["artist"], metadata.tags["album"]])
#print("\033[KArtist:", metadata.tags["artist"], end="\r")
clearLine()
print("Scanning", artistDir, next(spinner), end="\r")
#if int(metadata.tags["totaldiscs"]) > 1:
if metadata.tags.get("artist") is None:
continue
if metadata.tags.get("albumartist") is None:
continue
if metadata.tags.get('album') is None:
continue
if metadata.tags.get("musicbrainz_albumid") is None or len(metadata.tags.get("musicbrainz_albumid")) < 5:
clearLine()
print("Skipping:", os.path.join(albumDir, song))
continue
#if 'Centennial' in song:
# print
# print
# print("DEBUG")
# print("Path:", artistDir)
# print("Song:", song)
# print("musicbrainz_albumid:", metadata.tags.get("musicbrainz_albumid"))
# print(type(metadata.tags.get("musicbrainz_albumid")))
# sys.exit(0)
artistName = sanitize(metadata.tags["albumartist"])
albumName = sanitize(metadata.tags["album"])
titleName = sanitize(metadata.tags["title"])
outPath = ''
outFile = ''
if int(metadata.tags.get("totaldiscs", 0)) > 1:
outPath = os.path.join(config['converted']['path'],
artistName,
albumName)
outFile = '{0:d}-{1:02d}-{2}.{3}'.format(int(metadata.tags["discnumber"]),
int(metadata.tags["tracknumber"]),
titleName,
'm4a')
#print("iTunes:", os.path.join(iTunesLibraryRoot,
# metadata.tags["artist"],
# metadata.tags["album"],
# '{0:d}-{1:02d}-{2}.{3}'.format(int(metadata.tags["discnumber"]),
# int(metadata.tags["tracknumber"]),
# metadata.tags["title"],
# 'm4a')))
# #int(metadata.tags["discnumber"]) + '-' + '{0:02d}'.format(int(metadata.tags["tracknumber"])) + '-' + metadata.tags["title"] + ".m4a")
else:
if metadata.tags.get("tracknumber") is not None:
outPath = os.path.join(config['converted']['path'],
artistName,
albumName)
outFile = '{0:02d}-{1}.{2}'.format(int(metadata.tags["tracknumber"]), titleName, 'm4a')
#print("iTunes:", os.path.join(iTunesLibraryRoot,
# metadata.tags["artist"],
# metadata.tags["album"],
# '{0:02d}'.format(int(metadata.tags["tracknumber"])) + '-' + metadata.tags["title"] + ".m4a"))
else:
outPath = os.path.join(config['converted']['path'],
artistName,
albumName)
outFile = '{0}.{1}'.format(titleName, 'm4a')
#print("iTunes:", os.path.join(iTunesLibraryRoot,
# metadata.tags["artist"],
# metadata.tags["album"],
# metadata.tags["title"] + ".m4a"))
#print "iTunes: %s" % os.path.join(iTunesLibraryRoot, metadata.tags["artist"], metadata.tags["album"])
if not os.path.isfile(os.path.join(outPath, outFile)):
print("NEW:", os.path.join(outPath, outFile))
if song.endswith('.m4a'):
#print " Song: %s" % song
metadata = MetaTag(os.path.join(albumDir, song))
#print "MetaData: %s" % metadata.tags
clearLine()
print()
#LosslessLibraryRoot = '/srv/public/Music-Lossless'
#
#for subdir, dirs, files in os.walk(LosslessLibraryRoot):
# print subdir
#
# for file in files:
# if file.endswith('.flac'):
# audio = mutagen.File(os.path.join(subdir, file))
# print file
# #print audio.tags.pprint()
# print "Artist: %s" % audio['ARTIST'][0]
# print "Album: %s" % audio['ALBUM'][0]
# print "Title: %s" % audio['TITLE'][0]
#
# #for file in files:
# # print os.path.join(subdir, file)

2
musicman/__init__.py Normal file
View file

@ -0,0 +1,2 @@
import musicman.utils

BIN
musicman/__init__.pyc Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,93 @@
import os
#import tempfile
from musicman.utils.constants import SUPPORTED_FORMATS, VERSION
def parse_args():
import argparse
#parser = optparse.OptionParser(usage="%prog [options] [files]",
# version=VERSION)
#parser = argparse.ArgumentParser(description="Media Library Management Tools",
# usage="%(prog)s [command] [options]")
parser = argparse.ArgumentParser(description="Media Library Management Tools")
#subparsers = parser.add_subparsers(help="sub-command-help")
#parser.add_argument('-b', '--base', help="Origin Directory for Library (overrides config)", type=str, dest="originDir")
#parser.add_argument('-t', '--target', help="Target Directory for Library (overrides config)", type=str, dest="targetDir")
parser.add_argument('--version', help="show version information and exit", action="version", version='%(prog)s '+VERSION)
parser.add_argument('-v', help='Increase verbosity of processing', dest='verbose', action='count')
subparsers = parser.add_subparsers(title="Command Modes", dest="mode")
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('-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')
clean.set_defaults(act=False)
convert = subparsers.add_parser('convert', help='Convert Mode', description='Conversion mode scans for media in the origin library, and converts them into the target format. Conversion depends on a fully tagged library, including MusicBrainz metadata. This insures that the data provide is accurate as it uses that metadata for the destination artist/album/song.')
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('--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')
convert.set_defaults(act=False)
info = subparsers.add_parser('info', help='Info Mode', description='Displays file and metadata information about specified files and files within specified directories.')
info.add_argument('paths', help="Show information about files, or all files in directory.", metavar="PATH", nargs='+')
rename = subparsers.add_parser('rename', help='Rename Mode', description='Library rename tool renames media into their respective Artist/Album/[Disc-][Track-]Title in relation to their metadata.')
rename_lib = rename.add_argument_group('Library Options')
rename_lib.add_argument('-o', '--origin', help="Origin Directory for Library (overrides config)", metavar="DIR", type=str, dest="originDir")
rename_lib.add_argument('-e', '--exclude', help="Exclude Directory from origin (can be used multiple times)", metavar="DIR", dest='excludeDirs', action='append')
rename_act = rename.add_argument_group('Action Options')
rename_act.add_argument('-g', '--go', help="Process renaming (default just shows what would be done", dest="act", action='store_true')
rename.set_defaults(act=False)
scan = subparsers.add_parser('scan', help='Scan Mode', description='Scan library for various different operational purposes.')
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('--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.')
scan_new = scan_subparsers.add_parser('new', help='Find new unconverted media', description='Scans for new media that is not in the target library for conversion.')
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('-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')
sync_act = sync.add_argument_group('Action Options')
sync_act.add_argument('-g', '--go', help="Move media (default just what would be done)", dest="act", action='store_true')
parser.set_defaults(verbose=0)
return parser.parse_args()
def load_config():
import configparser
config = configparser.ConfigParser()
config.read('musicman.ini')
try:
#print("Test1:", config.get('lossless', 'test'))
config.get('origin', 'path')
config.get('origin', 'format')
config.get('target', 'path')
config.get('target', 'format')
config.get('working', 'path')
except configparser.NoOptionError as err:
print("ERROR: Configuration of required settings are missing:", err)
sys.exit(1)
return config

BIN
musicman/utils/__init__.pyc Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,7 @@
NO_TAGS = False
SILENT = False
VERSION = "0.0.1"
SUPPORTED_FORMATS = {'mp3', 'wma', 'wav', 'ogg', 'flac', 'm4a', 'mpc', 'wv', 'avi'}
EXTERNAL_FORMATS = {'mpc', 'wv'}
INTERNAL_FORMATS = SUPPORTED_FORMATS - EXTERNAL_FORMATS

Binary file not shown.

103
musicman/utils/metadata.py Normal file
View file

@ -0,0 +1,103 @@
import os
import base64
try:
import mutagen
import mutagen.id3
import mutagen.mp3
import mutagen.mp4
import mutagen.asf
import mutagen.flac
import mutagen.apev2
import mutagen.musepack
import mutagen.oggvorbis
NO_TAGS = False
except ImportError:
NO_TAGS = True
from musicman.utils.tagmap import tags as tagmap
class MetaTag(object):
"""
handles tag extraction and insertion into and/or from audio files
"""
__tag_mapping = tagmap.copy()
exts = __tag_mapping.keys()
if not NO_TAGS:
__id3_mapping = {
'artist' : mutagen.id3.TPE1,
'album' : mutagen.id3.TALB,
'title' : mutagen.id3.TIT2,
'genre' : mutagen.id3.TCON,
'year' : mutagen.id3.TDRC,
'tracknumber' : mutagen.id3.TRCK,
'totaltracks' : mutagen.id3.TRCK,
'composer' : mutagen.id3.TCOM,
'lyrics' : mutagen.id3.USLT,
'disc' : mutagen.id3.TPOS,
'discnumber' : mutagen.id3.TPOS,
}
__opener = {
'.mp3' : mutagen.mp3.Open,
'.wma' : mutagen.asf.Open,
'.m4a' : mutagen.mp4.Open,
'.flac' : mutagen.flac.Open,
'.wv' : mutagen.apev2.APEv2,
'.mpc' : mutagen.musepack.Open,
'.ogg' : mutagen.oggvorbis.Open,
}
else:
__id3_mapping = {}
__opener = {}
def __init__(self, input_file):
self.input_file = input_file
self.tags = {key: None for key in self.__id3_mapping}
self.extract()
def extract(self):
"""
extracts metadata tags from the audio file
"""
ext = os.path.splitext(self.input_file)[1].lower()
if ext in self.exts:
tags = mutagen.File(self.input_file)
#for tag, key in self.__tag_mapping[ext].iteritems():
for tag, key in self.__tag_mapping[ext].items():
if tag == 'albumart':
try:
self._extract_album_art(ext, tags)
except:
continue
elif key in tags:
#print "tag: %s, key: %s" % (tag, key)
self.tags[tag] = tags[key][0]
elif tag == 'lyrics' and key == 'USLT':
self.tags.update({tag: tags[id3tag].text for id3tag in tags if id3tag.startswith(key)})
def _extract_album_art(self, ext, tags):
tag = self.__tag_mapping[ext].get('albumart')
if tag is None:
return
if tag in tags:
self.coverart['ext'] = ext
if ext == '.mp3':
apic = tags[tag]
self.coverart['mime'] = apic.mime
self.coverart['data'] = apic.data
elif ext == '.m4a':
self.coverart['data'] = tags[tag][0]
elif ext in ('.ogg', '.flac'):
encoded_image = tags[tag][0]
image = mutagen.flac.Picture(base64.b64decode(encoded_image))
self.coverart['data'] = image.data
self.coverart['mime'] = image.mime
elif ext == '.mp3':
for key in tags:
if key.startswith(tag):
apic = tags[key]
self.coverart['mime'] = apic.mime
self.coverart['data'] = apic.data

BIN
musicman/utils/metadata.pyc Normal file

Binary file not shown.

289
musicman/utils/tagmap.py Normal file
View file

@ -0,0 +1,289 @@
tags = {
'.mp3': {
'album' : 'TALB',
'albumsort' : 'TSOA',
'title' : 'TIT2',
'titlesort' : 'TSOT',
'work' : 'TOAL',
'artist' : 'TPE1',
'artistsort' : 'TSOP',
'albumartist' : 'TPE2',
'albumartistsort' : 'TSO2',
'date' : 'TDRC',
'originaldate' : 'TDOR',
'composer' : 'TCOM',
'composersort' : 'TSOC',
'lyricist' : 'TEXT',
'writer' : 'TXXX:Writer',
'conductor' : 'TPE3',
'performer:instrument': 'TMCL:instrument',
'remixer' : 'TPE4',
'arranger' : 'TIPL:arranger',
'engineer' : 'TIPL:engineer',
'producer' : 'TIPL:producer',
'djmixer' : 'TIPL:DJ-mix',
'mixer' : 'TIPL:mix',
'label' : 'TPUB',
'grouping' : 'TIT1',
'subtitle' : 'TIT3',
'discsubtitle' : 'TSST',
'tracknumber' : 'TRCK',
'totaltracks' : 'TRCK',
'discnumber' : 'TPOS',
'totaldiscs' : 'TPOS',
'compilation' : 'TCMP',
'comment:description' : 'COMM:description',
'genre' : 'TCON',
'_rating' : 'POPM',
'bpm' : 'TBPM',
'mood' : 'TMOO',
'lyrics:description' : 'USLT:description',
'media' : 'TMED',
'catalognumber' : 'TXXX:CATALOGNUMBER',
'releasestatus' : 'TXXX:MusicBrainz Album Status',
'releasetype' : 'TXXX:MusicBrainz Album Type',
'releasecountry' : 'TXXX:MusicBrainz Album Release Country',
'script' : 'TXXX:SCRIPT',
'language' : 'TLAN',
'copyright' : 'TCOP',
'license' : 'WCOP',
'encodedby' : 'TENC',
'encodersettings' : 'TSSE',
'barcode' : 'TXXX:BARCODE',
'isrc' : 'TSRC',
'asin' : 'TXXX:ASIN',
'albumart' : 'APIC:',
'musicbrainz_recordingid' : 'UFID:http://musicbrainz.org',
'musicbrainz_releasetrackid' : 'TXXX:MusicBrainz Release Track Id',
'musicbrainz_albumid' : 'TXXX:MusicBrainz Album Id',
'musicbrainz_artistid' : 'TXXX:MusicBrainz Artist Id',
'musicbrainz_albumartistid' : 'TXXX:MusicBrainz Album Artist Id',
'musicbrainz_releasegroupid' : 'TXXX:MusicBrainz Release Group Id',
'musicbrainz_workid' : 'TXXX:MusicBrainz Work Id',
'musicbrainz_trmid' : 'TXXX:MusicBrainz TRM Id',
'musicbrainz_discid' : 'TXXX:MusicBrainz Disc Id',
'acoustic_id' : 'TXXX:Acoustic Id',
'acousticid_fingerprint' : 'TXXX:Acousticid Fingerprint',
'musicip_puid' : 'TXXX:MusicIP PUID',
'website' : 'WOAR',
},
'.wma': {
'artist' : 'Author',
'album' : 'WM/AlbumTitle',
'title' : 'Title',
'genre' : 'WM/Genre',
'year' : 'WM/Year',
'tracknumber' : 'WM/TrackNumber',
'composer' : 'WM/Composer',
'publisher' : 'WM/Publisher',
'lyrics' : 'WM/Lyrics',
'albumartist' : 'WM/AlbumArtist',
},
'.wmv': {
'artist' : 'Author',
'album' : 'WM/AlbumTitle',
'title' : 'Title',
'genre' : 'WM/Genre',
'year' : 'WM/Year',
'tracknumber' : 'WM/TrackNumber',
'composer' : 'WM/Composer',
'publisher' : 'WM/Publisher',
'lyrics' : 'WM/Lyrics',
},
'.m4a': {
'album' : '\xa9alb',
'albumsort' : 'soal',
'title' : '\xa9nam',
'titlesort' : 'sonm',
'artist' : '\xa9ART',
'artistsort' : 'soar',
'albumartist' : 'aART',
'albumartistsort' : 'soaa',
'date' : '\xa9day',
'composer' : '\xa9wrt',
'composersort' : 'soco',
'lyricist' : '----:com.apple.iTunes:LYRICIST',
'conductor' : '----:com.apple.iTunes:CONDUCTOR',
'remixer' : '----:com.apple.iTunes:REMIXER',
'engineer' : '----:com.apple.iTunes:ENGINEER',
'producer' : '----:com.apple.iTunes:PRODUCER',
'djmixer' : '----:com.apple.iTunes:DJMIXER',
'mixer' : '----:com.apple.iTunes:MIXER',
'label' : '----:com.apple.iTunes:LABEL',
'grouping' : '\xa9grp',
'subtitle' : '----:com.apple.iTunes:SUBTITLE',
'discsubtitle' : '----:com.apple.iTunes:DISCSUBTITLE',
'tracknumber' : 'trkn',
'totaltracks' : 'trkn',
'discnumber' : 'disk',
'totaldiscs' : 'disk',
#'compilation' : 'cpil',
'comment' : '\xa9cmt',
'genre' : '\xa9gen',
'bpm' : 'tmpo',
'mood' : '----:com.apple.iTunes:MOOD',
'lyrics' : '\xa9lyr',
'media' : '----:com.apple.iTunes:MEDIA',
'catalognumber' : '----:com.apple.iTunes:CATALOGNUMBER',
'show' : 'tvsh',
'showsort' : 'sosn',
'podcast' : 'pcst',
'podcasturl' : 'purl',
'releasestatus' : '----:com.apple.iTunes:MusicBrainz Album Status',
'releasetype' : '----:com.apple.iTunes:MusicBrainz Album Type',
'releasecountry' : '----:com.apple.iTunes:MusicBrainz Album Release Country',
'script' : '----:com.apple.iTunes:SCRIPT',
'language' : '----:com.apple.iTunes:LANGUAGE',
'copyright' : 'cprt',
'license' : '----:com.apple.iTunes:LICENSE',
'encodedby' : '\xa9too',
'barcode' : '----:com.apple.iTunes:BARCODE',
'isrc' : '----:com.apple.iTunes:ISRC',
'asin' : '----:com.apple.iTunes:ASIN',
'albumart' : 'covr',
'musicbrainz_recordingid' : '----:com.apple.iTunes:MusicBrainz Track Id',
'musicbrainz_releasetrackid' : '----:com.apple.iTunes:MusicBrainz Release Track Id',
'musicbrainz_albumid' : '----:com.apple.iTunes:MusicBrainz Album Id',
'musicbrainz_artistid' : '----:com.apple.iTunes:MusicBrainz Artist Id',
'musicbrainz_albumartistid' : '----:com.apple.iTunes:MusicBrainz Album Artist Id',
'musicbrainz_releasegroupid' : '----:com.apple.iTunes:MusicBrainz Release Group Id',
'musicbrainz_workid' : '----:com.apple.iTunes:MusicBrainz Work Id',
'musicbrainz_trmid' : '----:com.apple.iTunes:MusicBrainz TRM Id',
'musicbrainz_discid' : '----:com.apple.iTunes:MusicBrainz Disc Id',
'acoustic_id' : '----:com.apple.iTunes:Acoustid Id',
'acousticid_fingerprint' : '----:com.apple.iTunes:Acoustid Fingerprint',
'musicip_puid' : '----:com.apple.iTunes:MusicIP PUID',
},
'.mp4': {
'artist' : '\xa9ART',
'album' : '\xa9alb',
'title' : '\xa9nam',
'genre' : '\xa9gen',
'year' : '\xa9day',
'tracknumber' : 'trkn',
'composer' : '\xa9wrt',
'lyrics' : '\xa9lyr',
'albumartist' : 'aART',
'disk' : 'disk',
'comment' : '\xa9cmt',
'albumart' : 'covr',
},
'.aac': {
'artist' : '\xa9ART',
'album' : '\xa9alb',
'title' : '\xa9nam',
'genre' : '\xa9gen',
'year' : '\xa9day',
'tracknumber' : 'trkn',
'composer' : '\xa9wrt',
'lyrics' : '\xa9lyr',
'albumartist' : 'aART',
'disk' : 'disk',
'comment' : '\xa9cmt',
},
'.ogg': {
'artist' : 'artist',
'album' : 'album',
'title' : 'title',
'genre' : 'genre',
'year' : 'date',
'tracknumber' : 'tracknumber',
'composer' : 'composer',
'albumart' : 'metadata_block_picture',
'lyrics' : 'lyrics',
},
'.flac': {
'artist' : 'artist',
'album' : 'album',
'title' : 'title',
'genre' : 'genre',
'year' : 'date',
'originaldate' : 'originaldate',
'originalyear' : 'originalyear',
'tracknumber' : 'tracknumber',
'totaltracks' : 'totaltracks',
'discnumber' : 'discnumber',
'totaldiscs' : 'totaldiscs',
'bpm' : 'bpm',
'mood' : 'mood',
'composer' : 'composer',
'albumartist' : 'albumartist',
'lyricist' : 'lyricist',
'writer' : 'writer',
'conductor' : 'conductor',
'remixer' : 'remixer',
'arranger' : 'arranger',
'engineer' : 'engineer',
'producer' : 'producer',
'djmixer' : 'djmixer',
'mixer' : 'mixer',
'label' : 'label',
'catalognumber' : 'catalognumber',
'grouping' : 'grouping',
'subtitle' : 'subtitle',
'discsubtitle' : 'discsubtitle',
'compilation' : 'compilation',
'comment' : 'comment',
'albumart' : 'metadata_block_picture',
'lyrics' : 'lyrics',
'albumsort' : 'albumsort',
'titlesort' : 'titlesort',
'artistsort' : 'artistsort',
'albumartistsort' : 'albumartistsort',
'composersort' : 'composersort',
'work' : 'work',
'releasestatus' : 'releasestatus',
'releasetype' : 'releasetype',
'releasecountry' : 'releasecountry',
'script' : 'script',
'language' : 'language',
'copyright' : 'copyright',
'license' : 'license',
'barcode' : 'barcode',
'isrc' : 'isrc',
'asin' : 'asin',
'musicbrainz_trackid' : 'musicbrainz_trackid',
'musicbrainz_releasetrackid' : 'musicbrainz_releasetrackid',
'musicbrainz_albumid' : 'musicbrainz_albumid',
'musicbrainz_artistid' : 'musicbrainz_artistid',
'musicbrainz_albumartistid' : 'musicbrainz_albumartistid',
'musicbrainz_releasegroupid' : 'musicbrainz_releasegroupid',
'musicbrainz_workid' : 'musicbrainz_workid',
'musicbrainz_trmid' : 'musicbrainz_trmid',
'musicbrainz_discid' : 'musicbrainz_discid',
'acoustic_id' : 'acoustic_id',
'acousticid_fingerprint' : 'acousticid_fingerprint',
'musicip_puid' : 'musicp_puid',
},
'.mpc': {
'artist' : 'Artist',
'album' : 'Album',
'title' : 'Title',
'genre' : 'Genre',
'year' : 'Year',
'tracknumber' : 'Track',
'composer' : 'Composer',
},
'.3gp': {
'artist' : '\xa9ART',
'album' : '\xa9alb',
'title' : '\xa9nam',
'genre' : '\xa9gen',
'year' : '\xa9day',
'tracknumber' : 'trkn',
'composer' : '\xa9wrt',
'lyrics' : '\xa9lyr',
'albumartist' : 'aART',
'disk' : 'disk',
'comment' : '\xa9cmt',
'albumart' : 'covr',
},
'.wv': {
'artist' : 'artist',
'album' : 'album',
'title' : 'title',
'genre' : 'genre',
'year' : 'year',
'tracknumber' : 'track',
}
}

BIN
musicman/utils/tagmap.pyc Normal file

Binary file not shown.