diff --git a/headphones/getXldProfile.py b/headphones/getXldProfile.py new file mode 100755 index 00000000..e9d27015 --- /dev/null +++ b/headphones/getXldProfile.py @@ -0,0 +1,181 @@ +import os.path +import plistlib +import sys +import xml.parsers.expat as expat +import commands +from headphones import logger + +def getXldProfile(xldProfile): + xldProfileNotFound = xldProfile + expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist') + try: + preferences = plistlib.Plist.fromFile(expandedPath) + except (expat.ExpatError): + os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath ) + try: + preferences = plistlib.Plist.fromFile(expandedPath) + except (ImportError): + os.system("/usr/bin/plutil -convert binary1 %s" % expandedPath ) + logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath) + return(xldProfileNotFound, None, None) + except (ImportError): + logger.info('The plist at "%s" has a date in it, and therefore is not useable.' % expandedPath) + except: + logger.info('Unexpected error:', sys.exc_info()[0]) + return(xldProfileNotFound, None, None) + + xldProfile = xldProfile.lower() + profiles = preferences.get('Profiles') + + for profile in profiles: + + profilename = profile.get('XLDProfileManager_ProfileName') + xldProfileForCmd = profilename + profilename = profilename.lower() + xldFormat = None + xldBitrate = None + + if profilename == xldProfile: + + OutputFormatName = profile.get('OutputFormatName') + ShortDesc = profile.get('ShortDesc') + + # Determine format and bitrate + + if OutputFormatName == 'WAV': + xldFormat = 'wav' + + elif OutputFormatName == 'AIFF': + xldFormat = 'aiff' + + elif 'PCM' in OutputFormatName: + xldFormat = 'pcm' + + elif OutputFormatName == 'Wave64': + xldFormat = 'w64' + + elif OutputFormatName == 'MPEG-4 AAC': + xldFormat = 'm4a' + if 'CBR' in ShortDesc or 'ABR' in ShortDesc or 'CVBR' in ShortDesc: + xldBitrate = int(profile.get('XLDAacOutput2_Bitrate')) + elif 'TVBR' in ShortDesc: + XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality')) + if XLDAacOutput2_VBRQuality > 122: + xldBitrate = 320 + elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122: + xldBitrate = 285 + elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113: + xldBitrate = 255 + elif XLDAacOutput2_VBRQuality > 95 and XLDAacOutput2_VBRQuality <= 104: + xldBitrate = 225 + elif XLDAacOutput2_VBRQuality > 86 and XLDAacOutput2_VBRQuality <= 95: + xldBitrate = 195 + elif XLDAacOutput2_VBRQuality > 77 and XLDAacOutput2_VBRQuality <= 86: + xldBitrate = 165 + elif XLDAacOutput2_VBRQuality > 68 and XLDAacOutput2_VBRQuality <= 77: + xldBitrate = 150 + elif XLDAacOutput2_VBRQuality > 58 and XLDAacOutput2_VBRQuality <= 68: + xldBitrate = 135 + elif XLDAacOutput2_VBRQuality > 49 and XLDAacOutput2_VBRQuality <= 58: + xldBitrate = 115 + elif XLDAacOutput2_VBRQuality > 40 and XLDAacOutput2_VBRQuality <= 49: + xldBitrate = 105 + elif XLDAacOutput2_VBRQuality > 31 and XLDAacOutput2_VBRQuality <= 40: + xldBitrate = 95 + elif XLDAacOutput2_VBRQuality > 22 and XLDAacOutput2_VBRQuality <= 31: + xldBitrate = 80 + elif XLDAacOutput2_VBRQuality > 13 and XLDAacOutput2_VBRQuality <= 22: + xldBitrate = 75 + elif XLDAacOutput2_VBRQuality > 4 and XLDAacOutput2_VBRQuality <= 13: + xldBitrate = 45 + elif XLDAacOutput2_VBRQuality >= 0 and XLDAacOutput2_VBRQuality <= 4: + xldBitrate = 40 + + elif OutputFormatName == 'Apple Lossless': + xldFormat = 'm4a' + + elif OutputFormatName == 'FLAC': + if 'ogg' in ShortDesc: + xldFormat = 'oga' + else: + xldFormat = 'flac' + + elif OutputFormatName == 'MPEG-4 HE-AAC': + xldFormat = 'm4a' + xldBitrate = int(profile.get('Bitrate')) + + elif OutputFormatName == 'LAME MP3': + xldFormat = 'mp3' + if 'VBR' in ShortDesc: + VbrQuality = float(profile.get('VbrQuality')) + if VbrQuality < 1: + xldBitrate = 260 + elif VbrQuality >= 1 and VbrQuality < 2: + xldBitrate = 250 + elif VbrQuality >= 2 and VbrQuality < 3: + xldBitrate = 210 + elif VbrQuality >= 3 and VbrQuality < 4: + xldBitrate = 195 + elif VbrQuality >= 4 and VbrQuality < 5: + xldBitrate = 185 + elif VbrQuality >= 5 and VbrQuality < 6: + xldBitrate = 150 + elif VbrQuality >= 6 and VbrQuality < 7: + xldBitrate = 130 + elif VbrQuality >= 7 and VbrQuality < 8: + xldBitrate = 120 + elif VbrQuality >= 8 and VbrQuality < 9: + xldBitrate = 105 + elif VbrQuality >= 9: + xldBitrate = 85 + elif 'CBR' in ShortDesc: + xldBitrate = int(profile.get('Bitrate')) + elif 'ABR' in ShortDesc: + xldBitrate = int(profile.get('AbrBitrate')) + + elif OutputFormatName == 'Opus': + xldFormat = 'opus' + xldBitrate = int(profile.get('XLDOpusOutput_Bitrate')) + + elif OutputFormatName == 'Ogg Vorbis': + xldFormat = 'ogg' + XLDVorbisOutput_Quality = float(profile.get('XLDVorbisOutput_Quality')) + if XLDVorbisOutput_Quality <= -2: + xldBitrate = 32 + elif XLDVorbisOutput_Quality > -2 and XLDVorbisOutput_Quality <= -1: + xldBitrate = 48 + elif XLDVorbisOutput_Quality > -1 and XLDVorbisOutput_Quality <= 0: + xldBitrate = 64 + elif XLDVorbisOutput_Quality > 0 and XLDVorbisOutput_Quality <= 1: + xldBitrate = 80 + elif XLDVorbisOutput_Quality > 1 and XLDVorbisOutput_Quality <= 2: + xldBitrate = 96 + elif XLDVorbisOutput_Quality > 2 and XLDVorbisOutput_Quality <= 3: + xldBitrate = 112 + elif XLDVorbisOutput_Quality > 3 and XLDVorbisOutput_Quality <= 4: + xldBitrate = 128 + elif XLDVorbisOutput_Quality > 4 and XLDVorbisOutput_Quality <= 5: + xldBitrate = 160 + elif XLDVorbisOutput_Quality > 5 and XLDVorbisOutput_Quality <= 6: + xldBitrate = 192 + elif XLDVorbisOutput_Quality > 6 and XLDVorbisOutput_Quality <= 7: + xldBitrate = 224 + elif XLDVorbisOutput_Quality > 7 and XLDVorbisOutput_Quality <= 8: + xldBitrate = 256 + elif XLDVorbisOutput_Quality > 8 and XLDVorbisOutput_Quality <= 9: + xldBitrate = 320 + elif XLDVorbisOutput_Quality > 9: + xldBitrate = 500 + + elif OutputFormatName == 'WavPack': + xldFormat = 'wv' + if ShortDesc != 'normal': + xldBitrate = int(profile.get('XLDWavpackOutput_BitRate')) + + # Lossless + if xldFormat and not xldBitrate: + xldBitrate = 500 + + return(xldProfileForCmd, xldFormat, xldBitrate) + + return(xldProfileNotFound, None, None) \ No newline at end of file diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index 04dec8f5..8a66228e 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -27,7 +27,26 @@ try: except ImportError: import lib.argparse as argparse +# xld + +if headphones.ADVANCEDENCODER.lower().startswith('xld'): + XLDPROFILE = headphones.ADVANCEDENCODER[4:] + import getXldProfile + XLD = True +else: + XLD = False + def encode(albumPath): + + # Return if xld details not found + + if XLD: + global xldProfile + (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(XLDPROFILE) + if not xldFormat: + logger.error(u'Details for xld profile "%s" not found, will not be reencoded' % (xldProfile)) + return None + tempDirEncode=os.path.join(albumPath,"temp") musicFiles=[] musicFinalFiles=[] @@ -46,26 +65,48 @@ def encode(albumPath): for r,d,f in os.walk(albumPath): for music in f: if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + + if not XLD: + encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING) + else: + xldMusicFile = os.path.join(r, music) + xldInfoMusic = MediaFile(xldMusicFile) + encoderFormat = xldFormat + if (headphones.ENCODERLOSSLESS): - if (music.lower().endswith('.flac')): + ext = os.path.normpath(os.path.splitext(music)[1].lstrip(".")).lower() + if not XLD and ext == 'flac' or XLD and (ext != xldFormat and (xldInfoMusic.bitrate / 1000 > 500)): musicFiles.append(os.path.join(r, music)) - musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)) + musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat) musicTempFiles.append(os.path.join(tempDirEncode, musicTemp)) else: logger.debug('Music "%s" is already encoded' % (music)) else: musicFiles.append(os.path.join(r, music)) - musicTemp = os.path.normpath(os.path.splitext(music)[0]+'.'+headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)) + musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat) musicTempFiles.append(os.path.join(tempDirEncode, musicTemp)) - - if headphones.ENCODER=='lame': + + if XLD: + if headphones.ENCODERFOLDER: + encoder = os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING), 'xld') + else: + encoder = os.path.join('/Applications', 'xld') + elif headphones.ENCODER=='lame': encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'lame') elif headphones.ENCODER=='ffmpeg': encoder=os.path.join(headphones.ENCODERFOLDER.encode(headphones.SYS_ENCODING),'ffmpeg') + i=0 for music in musicFiles: infoMusic=MediaFile(music) - if headphones.ENCODER == 'lame': + + if XLD: + if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate): + logger.info('Music "%s" has bitrate <= "%skbit", will not be reencoded' % (music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate)) + else: + command(encoder,music,musicTempFiles[i],albumPath) + ifencoded=1 + elif headphones.ENCODER == 'lame': if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]): logger.warn(u'Lame cant encode "%s" format for "%s", use ffmpeg' % (os.path.splitext(music)[1].decode(headphones.SYS_ENCODING, 'replace'),music.decode(headphones.SYS_ENCODING, 'replace'))) else: @@ -106,7 +147,17 @@ def command(encoder,musicSource,musicDest,albumPath): return_code=1 cmd='' startMusicTime=time.time() - if headphones.ENCODER == 'lame': + + if XLD: + xldDestDir = os.path.split(musicDest)[0] + cmd = encoder + cmd = cmd + ' "' + musicSource + '"' + cmd = cmd + ' --profile' + cmd = cmd + ' "' + xldProfile + '"' + cmd = cmd + ' -o' + cmd = cmd + ' "' + xldDestDir + '"' + + elif headphones.ENCODER == 'lame': if headphones.ADVANCEDENCODER =='': cmd=encoder + ' -h' if headphones.ENCODERVBRCBR=='cbr': diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index ecabb271..5f79009b 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -28,6 +28,15 @@ from lib.beets.mediafile import MediaFile import headphones from headphones import db, albumart, librarysync, lyrics, logger, helpers +# xld + +if headphones.ADVANCEDENCODER.lower().startswith('xld'): + XLDPROFILE = headphones.ADVANCEDENCODER[4:] + import getXldProfile + XLD = True +else: + XLD = False + postprocessor_lock = threading.Lock() def checkFolder(): @@ -147,12 +156,73 @@ def verify(albumid, albumpath): tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) downloaded_track_list = [] - + downloaded_cuecount = 0 + for r,d,f in os.walk(albumpath): for files in f: if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): downloaded_track_list.append(os.path.join(r, files)) + elif files.lower().endswith('.cue'): + downloaded_cuecount += 1 + # use xld to split cue + + if XLD and headphones.MUSIC_ENCODER and downloaded_cuecount and downloaded_cuecount >= len(downloaded_track_list): + + (xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(XLDPROFILE) + if not xldFormat: + logger.info(u'Details for xld profile "%s" not found, cannot split cue' % (xldProfile)) + else: + if headphones.ENCODERFOLDER: + xldencoder = os.path.join(headphones.ENCODERFOLDER, 'xld') + else: + xldencoder = os.path.join('/Applications','xld') + + for r,d,f in os.walk(albumpath): + xldfolder = r + xldfile = '' + xldcue = '' + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS) and not xldfile: + xldfile = os.path.join(r, file) + elif file.lower().endswith('.cue') and not xldcue: + xldcue = os.path.join(r, file) + + if xldfile and xldcue and xldfolder: + xldcmd = xldencoder + xldcmd = xldcmd + ' "' + xldfile + '"' + xldcmd = xldcmd + ' -c' + xldcmd = xldcmd + ' "' + xldcue + '"' + xldcmd = xldcmd + ' --profile' + xldcmd = xldcmd + ' "' + xldProfile + '"' + xldcmd = xldcmd + ' -o' + xldcmd = xldcmd + ' "' + xldfolder + '"' + logger.info(u"Cue found, splitting file " + xldfile.decode(headphones.SYS_ENCODING, 'replace')) + logger.debug(xldcmd) + os.system(xldcmd) + + # count files, should now be more than original if xld successfully split + + new_downloaded_track_list_count = 0 + for r,d,f in os.walk(albumpath): + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + new_downloaded_track_list_count += 1 + + if new_downloaded_track_list_count > len(downloaded_track_list): + + # rename original unsplit files + for downloaded_track in downloaded_track_list: + os.rename(downloaded_track, downloaded_track + '.original') + + #reload + + downloaded_track_list = [] + for r,d,f in os.walk(albumpath): + for file in f: + if any(file.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): + downloaded_track_list.append(os.path.join(r, file)) + # test #1: metadata - usually works logger.debug('Verifying metadata...') @@ -233,9 +303,12 @@ def verify(albumid, albumpath): def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list): logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle'])) - #start enconding + #start encoding if headphones.MUSIC_ENCODER: downloaded_track_list=music_encoder.encode(albumpath) + + if not downloaded_track_list: + return album_art_path = albumart.getAlbumArt(albumid)