commit
a0e50ceaf7
@ -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> |
@ -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 |
||||
|
@ -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) |
||||
|
@ -0,0 +1,2 @@ |
||||
import musicman.utils |
||||
|
Binary file not shown.
Binary file not shown.
@ -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 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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.
@ -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 |
||||
|
Binary file not shown.
@ -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', |
||||
} |
||||
} |
Binary file not shown.
Loading…
Reference in new issue