mirror of
https://github.com/rembo10/headphones.git
synced 2026-05-16 08:35:32 +01:00
XLD encoding
+ bonus post processing xld cue splitting. Currently uses Advanced Encoding Options to determine xld and the xld profile to use, e.g xld Apple Lossless. Would like to use congig instead but can't figure out how to do it
This commit is contained in:
181
headphones/getXldProfile.py
Executable file
181
headphones/getXldProfile.py
Executable file
@@ -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)
|
||||
@@ -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':
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user