From ed0d3400c46ac5fa80eead7e4ecfac774a913d53 Mon Sep 17 00:00:00 2001 From: Marcel Laverdet Date: Mon, 29 Dec 2014 21:05:58 -0600 Subject: [PATCH 1/7] Fix support for titles with symbols in them The shell code here was all wrong and unsafe. This fixes the shell argument passing so that all kinds of symbols and characters are allowed. The "*" -> "_" replacement isn't strictly necessary but it's usually polite not to put *'s in your file names Test with: jbripper.py u p spotify:track:4XoP1AkbOurU9CeZ2rMEz2 --- jbripper.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/jbripper.py b/jbripper.py index a56c605..228962b 100755 --- a/jbripper.py +++ b/jbripper.py @@ -20,22 +20,20 @@ def printstr(str): # print without newline sys.stdout.write(str) sys.stdout.flush() -def shell(cmdline): # execute shell commands (unicode support) - call(cmdline, shell=True) - def rip_init(session, track): global pipe, ripping, pcmfile, rawpcm num_track = "%02d" % (track.index(),) - mp3file = track.name()+".mp3" - pcmfile = track.name()+".pcm" - directory = os.getcwd() + "/" + track.artists()[0].name() + "/" + track.album().name() + "/" + file_prefix = os.getcwd() + "/" + track.artists()[0].name() + "/" + track.album().name() + "/" + track.name() + file_prefix = file_prefix.replace('*', '_') + mp3file = file_prefix+".mp3" + directory = os.path.dirname(file_prefix) if not os.path.exists(directory): os.makedirs(directory) - printstr("ripping " + mp3file + " ...") - p = Popen("lame --silent -V0 -h -r - \""+ directory + mp3file+"\"", stdin=PIPE, shell=True) + printstr("ripping " + file_prefix + ".mp3 ...") + p = Popen(["lame", "--silent", "-V0", "-h", "-r", "-", file_prefix + ".mp3"], stdin=PIPE) pipe = p.stdin if rawpcm: - pcmfile = open(directory + pcmfile, 'w') + pcmfile = open(file_prefix + ".pcm", 'w') ripping = True @@ -73,19 +71,19 @@ def rip_id3(session, track): # write ID3 data fh_cover.close() # write id3 data - cmd = "eyeD3" + \ - " --add-image cover.jpg:FRONT_COVER" + \ - " -t \"" + title + "\"" + \ - " -a \"" + artist + "\"" + \ - " -A \"" + album + "\"" + \ - " -n " + str(num_track) + \ - " -Y " + str(year) + \ - " -Q " + \ - " \"" + directory + mp3file + "\"" - shell(cmd) + call(["eyeD3", + "--add-image", "cover.jpg:FRONT_COVER", + "-t", title, + "-a", artist, + "-A", album, + "-n", str(num_track), + "-Y", str(year), + "-Q", + (directory + mp3file).replace('*', '_') + ]) # delete cover - shell("rm -f cover.jpg") + call(["rm", "-f", "cover.jpg"]) class RipperThread(threading.Thread): def __init__(self, ripper): From 7684463f3ce0fcb5386acd51010db047dbdbb697 Mon Sep 17 00:00:00 2001 From: Avi Alkalay Date: Fri, 27 Mar 2015 22:21:11 -0300 Subject: [PATCH 2/7] =?UTF-8?q?=E2=80=A2=20Better=20tag=20and=20filename?= =?UTF-8?q?=20handling=20=E2=80=A2=20Truncate=20very=20long=20file=20names?= =?UTF-8?q?=20=E2=80=A2=20Transliterate=20symbols=20and=20accents=20in=20a?= =?UTF-8?q?=20nice=20and=20filesystem-safe=20way=20=E2=80=A2=20Tag=20ID3v2?= =?UTF-8?q?.3=20(more=20compatible=20with=20Windows)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jbripper.py | 131 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/jbripper.py b/jbripper.py index 228962b..a49e303 100755 --- a/jbripper.py +++ b/jbripper.py @@ -8,82 +8,151 @@ import threading import time +reload(sys) +sys.setdefaultencoding('utf8') + + playback = False # set if you want to listen to the tracks that are currently ripped (start with "padsp ./jbripper.py ..." if using pulse audio) -rawpcm = False # also saves a .pcm file with the raw PCM data as delivered by libspotify () +wav = False # also saves a .pcm file with the raw PCM data as delivered by libspotify () +fileNameMaxSize=255 # your filesystem's maximum filename size. Linux' Ext4 is 255. filename/filename/filename pcmfile = None pipe = None ripping = False +size = 0 end_of_track = threading.Event() def printstr(str): # print without newline sys.stdout.write(str) sys.stdout.flush() + +def transliterate(str): + transliterated=str + transliterated=transliterated.replace('/',u'/') + transliterated=transliterated.replace('*',u'✱') + transliterated=transliterated.replace('#',u'♯') + transliterated=transliterated.replace(':',u'∶') + transliterated=transliterated.replace('?',u'⁇') + transliterated=transliterated.replace('\\',u'\') + transliterated=transliterated.replace('|',u'│') + transliterated=transliterated.replace('>',u'>') + transliterated=transliterated.replace('<',u'<') + transliterated=transliterated.replace('&',u'&') + return transliterated + + +def unicode_truncate(s, length, encoding='utf-8'): + encoded = s.encode(encoding)[:length] + return encoded.decode(encoding, 'ignore') + +def track_path(track): + global fileNameMaxSize + + oalbum=track.album() + num_track = track.index() + year=oalbum.year() + album_artist=transliterate(oalbum.artist().name()) + track_artist=transliterate(u' • '.join([str(x.name()) for x in track.artists()])) + track_name=transliterate(track.name()) + album_name=transliterate(oalbum.name()) + + if (album_artist == track_artist): + track_file="{:02d} {}".format(num_track, track_name) + else: + track_file="{:02d} {} ♫ {}".format(num_track, track_artist, track_name) + + return "{aartist}/{year:04d} • {album}/{file}".format(year=year, + aartist = unicode_truncate(album_artist, fileNameMaxSize), + album = unicode_truncate(album_name, fileNameMaxSize-4-2-3), + file = unicode_truncate(track_file, fileNameMaxSize-4)) + + def rip_init(session, track): - global pipe, ripping, pcmfile, rawpcm - num_track = "%02d" % (track.index(),) - file_prefix = os.getcwd() + "/" + track.artists()[0].name() + "/" + track.album().name() + "/" + track.name() - file_prefix = file_prefix.replace('*', '_') + global pipe, ripping, wpipe, size + + size = 0 + file_prefix = track_path(track) mp3file = file_prefix+".mp3" directory = os.path.dirname(file_prefix) + if not os.path.exists(directory): os.makedirs(directory) - printstr("ripping " + file_prefix + ".mp3 ...") - p = Popen(["lame", "--silent", "-V0", "-h", "-r", "-", file_prefix + ".mp3"], stdin=PIPE) + printstr("ripping " + file_prefix + ".mp3 ...\n") + p = Popen(["lame", "--silent", "-V0", "-m", "s", "-h", "-r", "-", file_prefix + ".mp3"], stdin=PIPE) pipe = p.stdin - if rawpcm: - pcmfile = open(file_prefix + ".pcm", 'w') + if wav: + w=Popen(["ffmpeg", + "-loglevel", "quiet", + "-f", "s16le", + "-ar", "44100", + "-ac", "2", + "-i", "-", + file_prefix + ".wav"], + stdin=PIPE) + wpipe=w.stdin ripping = True def rip_terminate(session, track): global ripping, pipe, pcmfile, rawpcm if pipe is not None: - print(' done!') + print('\ndone!') pipe.close() - if rawpcm: - pcmfile.close() + if wav: + wpipe.close() ripping = False def rip(session, frames, frame_size, num_frames, sample_type, sample_rate, channels): + global size + printstr('.') if ripping: - printstr('.') pipe.write(frames); - if rawpcm: - pcmfile.write(frames) +# printstr(" " + size + " bytes\r") + if wav: + wpipe.write(frames) def rip_id3(session, track): # write ID3 data - num_track = "%02d" % (track.index(),) - mp3file = track.name()+".mp3" - artist = track.artists()[0].name() - album = track.album().name() - title = track.name() - year = track.album().year() - directory = os.getcwd() + "/" + track.artists()[0].name() + "/" + track.album().name() + "/" + file_prefix = track_path(track) + mp3file = file_prefix+".mp3" + directory = os.path.dirname(file_prefix) # download cover image = session.image_create(track.album().cover()) while not image.is_loaded(): # does not work from MainThread! time.sleep(0.1) - fh_cover = open('cover.jpg','wb') + fh_cover = open(directory + '/folder.jpg','wb') fh_cover.write(image.data()) fh_cover.close() + + oalbum=track.album() + num_track = str("%02d" % (track.index(),)) + year=str(oalbum.year()) + album_artist=transliterate(oalbum.artist().name()) + track_artist=u' • '.join([str(x.name()) for x in track.artists()]) + track_name=track.name() + album_name=oalbum.name() + +# spotify_link="This track on Spotify is {}".format(track.link()) + # write id3 data call(["eyeD3", - "--add-image", "cover.jpg:FRONT_COVER", - "-t", title, - "-a", artist, - "-A", album, - "-n", str(num_track), - "-Y", str(year), + "--add-image", directory + "/folder.jpg:FRONT_COVER", + "-t", track_name, + "-a", track_artist, + "-b", album_artist, + # "-c", spotify_link, + "-A", album_name, + "-n", num_track, + "-Y", year, + "--to-v2.3", "-Q", - (directory + mp3file).replace('*', '_') + mp3file ]) # delete cover - call(["rm", "-f", "cover.jpg"]) + # call(["rm", "-f", "folder.jpg"]) class RipperThread(threading.Thread): def __init__(self, ripper): From a78dad3ad17a7bafd4e3f1aa53a6b13f772e018b Mon Sep 17 00:00:00 2001 From: Avi Alkalay Date: Tue, 5 May 2015 14:31:48 -0300 Subject: [PATCH 3/7] eyeD3 API being used instead of command line tool --- jbripper.py | 107 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 28 deletions(-) diff --git a/jbripper.py b/jbripper.py index a49e303..b1c4ed8 100755 --- a/jbripper.py +++ b/jbripper.py @@ -7,6 +7,8 @@ import os, sys import threading import time +from eyed3 import id3 +import eyed3 reload(sys) sys.setdefaultencoding('utf8') @@ -15,6 +17,7 @@ playback = False # set if you want to listen to the tracks that are currently ripped (start with "padsp ./jbripper.py ..." if using pulse audio) wav = False # also saves a .pcm file with the raw PCM data as delivered by libspotify () fileNameMaxSize=255 # your filesystem's maximum filename size. Linux' Ext4 is 255. filename/filename/filename +defaultgenre = u'☣ UNKNOWN ♺' pcmfile = None pipe = None @@ -69,7 +72,7 @@ def track_path(track): def rip_init(session, track): - global pipe, ripping, wpipe, size + global pipe, ripping, wpipe, size, defaultgenre size = 0 file_prefix = track_path(track) @@ -79,18 +82,36 @@ def rip_init(session, track): if not os.path.exists(directory): os.makedirs(directory) printstr("ripping " + file_prefix + ".mp3 ...\n") - p = Popen(["lame", "--silent", "-V0", "-m", "s", "-h", "-r", "-", file_prefix + ".mp3"], stdin=PIPE) + p = Popen(["lame", + "--silent", + "-V0", # VBR highest quality + "-m", "s", # plain stereo (no joint stereo) + "-h", # high quality, same as -q 2 + "-r", # input is raw PCM +# "--id3v2-utf16", +# "--id3v2-only", +# "--tg", defaultgenre, +# "--tt", track.name(), +# "--ta", u' • '.join([str(x.name()) for x in track.artists()]), +# "--tl", track.album(), +# "--ty", str(track.album().year()), +# "--tn", str("%02d" % (track.index(),)), + "-", file_prefix + ".mp3"], + stdin=PIPE) + pipe = p.stdin + if wav: - w=Popen(["ffmpeg", - "-loglevel", "quiet", - "-f", "s16le", - "-ar", "44100", - "-ac", "2", - "-i", "-", - file_prefix + ".wav"], - stdin=PIPE) - wpipe=w.stdin + w=Popen(["ffmpeg", + "-loglevel", "quiet", + "-f", "s16le", + "-ar", "44100", + "-ac", "2", + "-i", "-", + file_prefix + ".wav"], + stdin=PIPE) + wpipe=w.stdin + ripping = True @@ -106,6 +127,8 @@ def rip_terminate(session, track): def rip(session, frames, frame_size, num_frames, sample_type, sample_rate, channels): global size printstr('.') +# printstr("frame_size={}, num_frames={}, sample_type={}, sample_rate={}, channels={}\n".format( +# frame_size, num_frames, sample_type, sample_rate, channels)) if ripping: pipe.write(frames); # printstr(" " + size + " bytes\r") @@ -119,8 +142,10 @@ def rip_id3(session, track): # write ID3 data # download cover image = session.image_create(track.album().cover()) + while not image.is_loaded(): # does not work from MainThread! time.sleep(0.1) + fh_cover = open(directory + '/folder.jpg','wb') fh_cover.write(image.data()) fh_cover.close() @@ -134,25 +159,51 @@ def rip_id3(session, track): # write ID3 data track_name=track.name() album_name=oalbum.name() -# spotify_link="This track on Spotify is {}".format(track.link()) +# spotify_links="Spotify track: {0}\nSpotify album: {1}".format( +# track.link().url, +# oalbum.link().url +# ) # write id3 data - call(["eyeD3", - "--add-image", directory + "/folder.jpg:FRONT_COVER", - "-t", track_name, - "-a", track_artist, - "-b", album_artist, - # "-c", spotify_link, - "-A", album_name, - "-n", num_track, - "-Y", year, - "--to-v2.3", - "-Q", - mp3file - ]) - - # delete cover - # call(["rm", "-f", "folder.jpg"]) +# call(["eyeD3", +# "--add-image", directory + "/folder.jpg:FRONT_COVER", +# "-t", track_name, +# "-a", track_artist, +# "-b", album_artist, +# # "-c", spotify_link, +# "-A", album_name, +# "-n", num_track, +# "-Y", year, +# "--to-v2.3", +# "-Q", +# mp3file +# ]) + + + id3.ID3_DEFAULT_VERSION = (2, 3, 0) + + audiofile = eyed3.load(mp3file) + audiofile.initTag() + + audiofile.tag.album_artist = album_artist + audiofile.tag.album = album_name + audiofile.tag.release_date = year + audiofile.tag.artist = track_artist + audiofile.tag.track_num = (num_track, None) + audiofile.tag.title = track_name + audiofile.tag.genre = defaultgenre +# audiofile.tag.comments.set( spotify_links) + + # append image to tags +# audiofile.tag.images.set(3,image.data(), +# u'image/jpeg', +# u'Front Cover') + + audiofile.tag.save() + + + + class RipperThread(threading.Thread): def __init__(self, ripper): From 3dc086d406e187ac2e54c8ba3ee06f0ab8437b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Avi=20=D7=90=D7=91=D7=99=20Alkalay=20=D7=90=D7=9C=D7=A7?= =?UTF-8?q?=D7=9C=D7=A2=D7=99?= Date: Tue, 5 May 2015 15:11:22 -0300 Subject: [PATCH 4/7] Better date handling --- .gitignore | 0 README.md | 0 jbripper.py | 12 ++++++++---- jukebox.py | 0 4 files changed, 8 insertions(+), 4 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 README.md mode change 100644 => 100755 jukebox.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/jbripper.py b/jbripper.py index b1c4ed8..e24afaa 100755 --- a/jbripper.py +++ b/jbripper.py @@ -180,7 +180,7 @@ def rip_id3(session, track): # write ID3 data # ]) - id3.ID3_DEFAULT_VERSION = (2, 3, 0) +# id3.ID3_DEFAULT_VERSION = (2, 3, 0) audiofile = eyed3.load(mp3file) audiofile.initTag() @@ -188,6 +188,8 @@ def rip_id3(session, track): # write ID3 data audiofile.tag.album_artist = album_artist audiofile.tag.album = album_name audiofile.tag.release_date = year + audiofile.tag.recording_date = year + audiofile.tag.original_release_date = year audiofile.tag.artist = track_artist audiofile.tag.track_num = (num_track, None) audiofile.tag.title = track_name @@ -197,10 +199,12 @@ def rip_id3(session, track): # write ID3 data # append image to tags # audiofile.tag.images.set(3,image.data(), # u'image/jpeg', -# u'Front Cover') - - audiofile.tag.save() +# u'Front Cover' +# ) + # Save ID3 using version 2.3 for maximum compatibility. UTF-16 is required for 2.3 + audiofile.tag.save(version=(2, 3, 0), encoding='utf16') + diff --git a/jukebox.py b/jukebox.py old mode 100644 new mode 100755 From 9a5896eb9fb7229c9b3b912c31305b9af6c87678 Mon Sep 17 00:00:00 2001 From: Avi Alkalay Date: Sun, 17 Jul 2016 07:07:19 -0300 Subject: [PATCH 5/7] support for FAAC --- jbripper.py | 80 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/jbripper.py b/jbripper.py index e24afaa..2e449f0 100755 --- a/jbripper.py +++ b/jbripper.py @@ -23,6 +23,8 @@ pipe = None ripping = False size = 0 +feedbackchar = "-" +feedbackcharDelay = 0 end_of_track = threading.Event() def printstr(str): # print without newline @@ -76,29 +78,48 @@ def rip_init(session, track): size = 0 file_prefix = track_path(track) - mp3file = file_prefix+".mp3" +# mp3file = file_prefix+".mp3" + m4afile = file_prefix+".m4a" directory = os.path.dirname(file_prefix) if not os.path.exists(directory): os.makedirs(directory) - printstr("ripping " + file_prefix + ".mp3 ...\n") - p = Popen(["lame", - "--silent", - "-V0", # VBR highest quality - "-m", "s", # plain stereo (no joint stereo) - "-h", # high quality, same as -q 2 - "-r", # input is raw PCM -# "--id3v2-utf16", -# "--id3v2-only", -# "--tg", defaultgenre, -# "--tt", track.name(), -# "--ta", u' • '.join([str(x.name()) for x in track.artists()]), -# "--tl", track.album(), -# "--ty", str(track.album().year()), -# "--tn", str("%02d" % (track.index(),)), - "-", file_prefix + ".mp3"], + printstr("ripping " + file_prefix + ".m4a ...\n") + p = Popen(["faac", + "-P", + "-C", "2", + "-w", + "-s", + "-q", "300", + "--genre", defaultgenre, + "--title", track.name(), + "--artist", u' • '.join([str(x.name()) for x in track.artists()]), + "--album", track.album(), + "--year", str(track.album().year()), + "--track", str("%02d" % (track.index(),)), + "--cover-art", directory + "/folder.jpg", + "-o", file_prefix + ".m4a"], + "-", stdin=PIPE) +# printstr("ripping " + file_prefix + ".mp3 ...\n") +# p = Popen(["lame", +# "--silent", +# "-V2", # VBR slightly less than highest quality +# "-m", "s", # plain stereo (no joint stereo) +# "-h", # high quality, same as -q 2 +# "-r", # input is raw PCM +# # "--id3v2-utf16", +# # "--id3v2-only", +# # "--tg", defaultgenre, +# # "--tt", track.name(), +# # "--ta", u' • '.join([str(x.name()) for x in track.artists()]), +# # "--tl", track.album(), +# # "--ty", str(track.album().year()), +# # "--tn", str("%02d" % (track.index(),)), +# "-", file_prefix + ".mp3"], +# stdin=PIPE) + pipe = p.stdin if wav: @@ -118,15 +139,32 @@ def rip_init(session, track): def rip_terminate(session, track): global ripping, pipe, pcmfile, rawpcm if pipe is not None: - print('\ndone!') + print(' done!') pipe.close() if wav: wpipe.close() ripping = False def rip(session, frames, frame_size, num_frames, sample_type, sample_rate, channels): - global size - printstr('.') + global size, feedbackchar, feedbackcharDelay + + sys.stdout.write('\r' + feedbackchar) + sys.stdout.flush() + + if feedbackcharDelay > 5: + feedbackcharDelay = 0 + if feedbackchar == '-': + feedbackchar='\\' + elif feedbackchar == '\\': + feedbackchar='|' + elif feedbackchar == '|': + feedbackchar='/' + elif feedbackchar == '/': + feedbackchar='-' + + feedbackcharDelay += 1 + +# printstr('.') # printstr("frame_size={}, num_frames={}, sample_type={}, sample_rate={}, channels={}\n".format( # frame_size, num_frames, sample_type, sample_rate, channels)) if ripping: @@ -246,7 +284,7 @@ def run(self): end_of_track.clear() # TODO check if necessary rip_terminate(session, track) - rip_id3(session, track) + # rip_id3(session, track) self.ripper.disconnect() From dcbcbeb81fcaf5790489bb2f049825d89ad71597 Mon Sep 17 00:00:00 2001 From: Avi Alkalay Date: Sun, 17 Jul 2016 21:04:33 -0300 Subject: [PATCH 6/7] support for simultaneous multiple encoders (FAAC, MP3, WAC) --- jbripper.py | 131 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 54 deletions(-) diff --git a/jbripper.py b/jbripper.py index 2e449f0..dd4f1b6 100755 --- a/jbripper.py +++ b/jbripper.py @@ -7,6 +7,7 @@ import os, sys import threading import time +import pprint from eyed3 import id3 import eyed3 @@ -16,6 +17,8 @@ playback = False # set if you want to listen to the tracks that are currently ripped (start with "padsp ./jbripper.py ..." if using pulse audio) wav = False # also saves a .pcm file with the raw PCM data as delivered by libspotify () +m4a = False +mp3 = True fileNameMaxSize=255 # your filesystem's maximum filename size. Linux' Ext4 is 255. filename/filename/filename defaultgenre = u'☣ UNKNOWN ♺' @@ -73,78 +76,92 @@ def track_path(track): file = unicode_truncate(track_file, fileNameMaxSize-4)) +# Setup all pipes def rip_init(session, track): global pipe, ripping, wpipe, size, defaultgenre size = 0 file_prefix = track_path(track) -# mp3file = file_prefix+".mp3" - m4afile = file_prefix+".m4a" directory = os.path.dirname(file_prefix) if not os.path.exists(directory): os.makedirs(directory) - printstr("ripping " + file_prefix + ".m4a ...\n") - p = Popen(["faac", - "-P", - "-C", "2", - "-w", - "-s", - "-q", "300", - "--genre", defaultgenre, - "--title", track.name(), - "--artist", u' • '.join([str(x.name()) for x in track.artists()]), - "--album", track.album(), - "--year", str(track.album().year()), - "--track", str("%02d" % (track.index(),)), - "--cover-art", directory + "/folder.jpg", - "-o", file_prefix + ".m4a"], - "-", - stdin=PIPE) -# printstr("ripping " + file_prefix + ".mp3 ...\n") -# p = Popen(["lame", -# "--silent", -# "-V2", # VBR slightly less than highest quality -# "-m", "s", # plain stereo (no joint stereo) -# "-h", # high quality, same as -q 2 -# "-r", # input is raw PCM -# # "--id3v2-utf16", -# # "--id3v2-only", -# # "--tg", defaultgenre, -# # "--tt", track.name(), -# # "--ta", u' • '.join([str(x.name()) for x in track.artists()]), -# # "--tl", track.album(), -# # "--ty", str(track.album().year()), -# # "--tn", str("%02d" % (track.index(),)), -# "-", file_prefix + ".mp3"], -# stdin=PIPE) - - pipe = p.stdin + + pipe = [] + + if m4a: + # FAAC 1.28 doesn't work with standard inputs. This code is useless. + printstr("ripping " + file_prefix + ".m4a ...\n") + m4aPipe = Popen(["faac", + "-P", + "-R", str("44100"), + "-w", + "-s", + "-q", str("400"), + "--genre", str(defaultgenre), + "--title", str(track.name()), + "--artist", u' • '.join([str(x.name()) for x in track.artists()]), + "--album", str(track.album()), + "--year", str(track.album().year()), + "--track", str("%02d" % (track.index(),)), +# "--cover-art", str(directory + "/folder.jpg"), + "--comment", "Spotify PCM + 'faac -q 400'", + "-o", file_prefix + ".m4a", + "-"], + stdin=PIPE) + + pipe.append(m4aPipe.stdin) + + if mp3: + printstr("ripping " + file_prefix + ".mp3 ...\n") + mp3Pipe = Popen(["lame", + "--silent", + "-V2", # VBR slightly less than highest quality + "-m", "s", # plain stereo (no joint stereo) + "-h", # high quality, same as -q 2 + "-r", # input is raw PCM + "--id3v2-utf16", + "--id3v2-only", + "--tg", str(defaultgenre), + "--tt", str(track.name()), + "--ta", u' • '.join([str(x.name()) for x in track.artists()]), + "--tl", str(track.album()), + "--ty", str(track.album().year()), + "--tn", str("%02d" % (track.index(),)), + "--tc", "Spotify PCM + 'lame -V2 -m s -h'", + "-", file_prefix + ".mp3"], + stdin=PIPE) + + pipe.append(mp3Pipe.stdin) if wav: - w=Popen(["ffmpeg", - "-loglevel", "quiet", - "-f", "s16le", - "-ar", "44100", - "-ac", "2", - "-i", "-", - file_prefix + ".wav"], + wavPipe=Popen(["ffmpeg", + "-loglevel", "quiet", + "-f", "s16le", + "-ar", "44100", + "-ac", "2", + "-i", "-", + file_prefix + ".wav"], stdin=PIPE) - wpipe=w.stdin + + pipe.append(wavPipe.stdin) ripping = True def rip_terminate(session, track): global ripping, pipe, pcmfile, rawpcm + if pipe is not None: + for p in pipe: + p.close() print(' done!') - pipe.close() - if wav: - wpipe.close() + ripping = False + +# This callback is called for each frame of each track def rip(session, frames, frame_size, num_frames, sample_type, sample_rate, channels): global size, feedbackchar, feedbackcharDelay @@ -167,11 +184,17 @@ def rip(session, frames, frame_size, num_frames, sample_type, sample_rate, chann # printstr('.') # printstr("frame_size={}, num_frames={}, sample_type={}, sample_rate={}, channels={}\n".format( # frame_size, num_frames, sample_type, sample_rate, channels)) - if ripping: - pipe.write(frames); -# printstr(" " + size + " bytes\r") - if wav: - wpipe.write(frames) + +# pprint.pprint(pipe) + + for p in pipe: + p.write(frames) + +# if ripping: +# pipe.write(frames); +# # printstr(" " + size + " bytes\r") +# if wav: +# wpipe.write(frames) def rip_id3(session, track): # write ID3 data file_prefix = track_path(track) From d108e560120ff449198ed6d9e134855814236194 Mon Sep 17 00:00:00 2001 From: Avi Alkalay Date: Fri, 29 Jul 2016 23:02:55 -0300 Subject: [PATCH 7/7] support for paralel ALAC, AAC, MP3 and WAV compression --- .gitignore | 0 README.md | 0 jbripper.py | 114 ++++++++++++++++++++++++++++++++++++---------------- jukebox.py | 0 4 files changed, 80 insertions(+), 34 deletions(-) mode change 100755 => 100644 .gitignore mode change 100755 => 100644 README.md mode change 100755 => 100644 jukebox.py diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/jbripper.py b/jbripper.py index dd4f1b6..1b81b60 100755 --- a/jbripper.py +++ b/jbripper.py @@ -16,9 +16,10 @@ playback = False # set if you want to listen to the tracks that are currently ripped (start with "padsp ./jbripper.py ..." if using pulse audio) -wav = False # also saves a .pcm file with the raw PCM data as delivered by libspotify () -m4a = False +wav = True # also saves a .pcm file with the raw PCM data as delivered by libspotify () +aac = True mp3 = True +alac = True fileNameMaxSize=255 # your filesystem's maximum filename size. Linux' Ext4 is 255. filename/filename/filename defaultgenre = u'☣ UNKNOWN ♺' @@ -54,7 +55,7 @@ def unicode_truncate(s, length, encoding='utf-8'): encoded = s.encode(encoding)[:length] return encoded.decode(encoding, 'ignore') -def track_path(track): +def track_path(track, format): global fileNameMaxSize oalbum=track.album() @@ -70,7 +71,8 @@ def track_path(track): else: track_file="{:02d} {} ♫ {}".format(num_track, track_artist, track_name) - return "{aartist}/{year:04d} • {album}/{file}".format(year=year, + return "{aartist}/{year:04d} • {album}/{ff}/{file}".format( + year=year, ff=format, aartist = unicode_truncate(album_artist, fileNameMaxSize), album = unicode_truncate(album_name, fileNameMaxSize-4-2-3), file = unicode_truncate(track_file, fileNameMaxSize-4)) @@ -81,39 +83,17 @@ def rip_init(session, track): global pipe, ripping, wpipe, size, defaultgenre size = 0 - file_prefix = track_path(track) - directory = os.path.dirname(file_prefix) - - if not os.path.exists(directory): - os.makedirs(directory) pipe = [] - if m4a: - # FAAC 1.28 doesn't work with standard inputs. This code is useless. - printstr("ripping " + file_prefix + ".m4a ...\n") - m4aPipe = Popen(["faac", - "-P", - "-R", str("44100"), - "-w", - "-s", - "-q", str("400"), - "--genre", str(defaultgenre), - "--title", str(track.name()), - "--artist", u' • '.join([str(x.name()) for x in track.artists()]), - "--album", str(track.album()), - "--year", str(track.album().year()), - "--track", str("%02d" % (track.index(),)), -# "--cover-art", str(directory + "/folder.jpg"), - "--comment", "Spotify PCM + 'faac -q 400'", - "-o", file_prefix + ".m4a", - "-"], - stdin=PIPE) - - pipe.append(m4aPipe.stdin) - if mp3: + file_prefix = track_path(track,"mp3") + directory = os.path.dirname(file_prefix) + + if not os.path.exists(directory): + os.makedirs(directory) + printstr("ripping " + file_prefix + ".mp3 ...\n") mp3Pipe = Popen(["lame", "--silent", @@ -136,6 +116,14 @@ def rip_init(session, track): pipe.append(mp3Pipe.stdin) if wav: + file_prefix = track_path(track,"wav") + directory = os.path.dirname(file_prefix) + + if not os.path.exists(directory): + os.makedirs(directory) + + printstr("ripping " + file_prefix + ".wav ...\n") + wavPipe=Popen(["ffmpeg", "-loglevel", "quiet", "-f", "s16le", @@ -143,21 +131,79 @@ def rip_init(session, track): "-ac", "2", "-i", "-", file_prefix + ".wav"], - stdin=PIPE) + stdin=PIPE) pipe.append(wavPipe.stdin) + if alac: + file_prefix = track_path(track,"alac") + directory = os.path.dirname(file_prefix) + + if not os.path.exists(directory): + os.makedirs(directory) + + printstr("ripping " + file_prefix + ".m4a (ALAC) ...\n") + + alacPipe=Popen(["ffmpeg", + "-loglevel", "quiet", + "-y", + "-f", "s16le", + "-ar", "44100", + "-ac", "2", + "-i", "-", + + "-acodec", "alac", + + "-metadata", "genre=" + str(defaultgenre), + "-metadata", "title=" + str(track.name()), + "-metadata", "artist=" + u' • '.join([str(x.name()) for x in track.artists()]), + "-metadata", "album=" + str(track.album()), + "-metadata", "album_artist=" + str(track.album().artist().name()), + "-metadata", "date=" + str(track.album().year()), + "-metadata", "track=" + str("%02d" % (track.index(),)), + "-metadata", "encoder=" + "Spotify PCM + 'ffmpeg -acodec alac'", + + file_prefix + ".m4a"], + stdin=PIPE) + + pipe.append(alacPipe.stdin) + ripping = True def rip_terminate(session, track): - global ripping, pipe, pcmfile, rawpcm + global ripping, pipe, defaultgenre if pipe is not None: for p in pipe: p.close() print(' done!') + + if aac: + wfile_prefix = track_path(track,"wav") + file_prefix = track_path(track,"aac") + directory = os.path.dirname(file_prefix) + + if not os.path.exists(directory): + os.makedirs(directory) + + printstr("ripping " + file_prefix + ".m4a ...\n") + Popen(["faac", + "-w", + "-s", + "-q", str("300"), + "--genre", str(defaultgenre), + "--title", str(track.name()), + "--artist", u' • '.join([str(x.name()) for x in track.artists()]), + "--album", str(track.album()), + "--year", str(track.album().year()), + "--track", str("%02d" % (track.index(),)), +# "--cover-art", str(directory + "/folder.jpg"), + "--comment", "Spotify PCM + 'faac -q 300'", + "-o", file_prefix + ".m4a", + wfile_prefix + ".wav"]) + ripping = False diff --git a/jukebox.py b/jukebox.py old mode 100755 new mode 100644