forked from upstream/IPod-Shuffle-4g
Add id3 based auto playlist generation
This commit is contained in:
parent
44fc42a2e4
commit
f22fdee042
1 changed files with 74 additions and 24 deletions
98
shuffle.py
98
shuffle.py
|
|
@ -67,6 +67,30 @@ def get_relpath(path, basepath):
|
||||||
def is_path_prefix(prefix, path):
|
def is_path_prefix(prefix, path):
|
||||||
return prefix == os.sep.join(os.path.commonprefix(map(splitpath, [prefix, path])))
|
return prefix == os.sep.join(os.path.commonprefix(map(splitpath, [prefix, path])))
|
||||||
|
|
||||||
|
def group_tracks_by_id3_template(tracks, template):
|
||||||
|
grouped_tracks_dict = {}
|
||||||
|
template_vars = set(re.findall(r'{.*?}', template))
|
||||||
|
for track in tracks:
|
||||||
|
try:
|
||||||
|
id3_dict = mutagen.File(track, easy=True)
|
||||||
|
except:
|
||||||
|
id3_dict = {}
|
||||||
|
|
||||||
|
key = template
|
||||||
|
single_var_present = False
|
||||||
|
for var in template_vars:
|
||||||
|
val = id3_dict.get(var[1:-1], [''])[0]
|
||||||
|
if len(val) > 0:
|
||||||
|
single_var_present = True
|
||||||
|
key = key.replace(var, val)
|
||||||
|
|
||||||
|
if single_var_present:
|
||||||
|
if key not in grouped_tracks_dict:
|
||||||
|
grouped_tracks_dict[key] = []
|
||||||
|
grouped_tracks_dict[key].append(track)
|
||||||
|
|
||||||
|
return sorted(grouped_tracks_dict.items())
|
||||||
|
|
||||||
class Text2Speech(object):
|
class Text2Speech(object):
|
||||||
valid_tts = {'pico2wave': True, 'RHVoice': True, 'espeak': True}
|
valid_tts = {'pico2wave': True, 'RHVoice': True, 'espeak': True}
|
||||||
|
|
||||||
|
|
@ -395,7 +419,7 @@ class PlaylistHeader(Record):
|
||||||
playlistcount = 1
|
playlistcount = 1
|
||||||
for i in self.lists:
|
for i in self.lists:
|
||||||
playlist = Playlist(self)
|
playlist = Playlist(self)
|
||||||
print "[+] Adding playlist", i
|
print "[+] Adding playlist", (i[0] if type(i) == type(()) else i)
|
||||||
playlist.populate(i)
|
playlist.populate(i)
|
||||||
construction = playlist.construct(tracks)
|
construction = playlist.construct(tracks)
|
||||||
if playlist["number_of_songs"] > 0:
|
if playlist["number_of_songs"] > 0:
|
||||||
|
|
@ -492,30 +516,35 @@ class Playlist(Record):
|
||||||
fullPath = relative
|
fullPath = relative
|
||||||
return fullPath
|
return fullPath
|
||||||
|
|
||||||
def populate(self, filename):
|
def populate(self, obj):
|
||||||
# Create a playlist of the folder and all subfolders
|
# Create a playlist of the folder and all subfolders
|
||||||
if os.path.isdir(filename):
|
if type(obj) == type(()):
|
||||||
self.listtracks = self.populate_directory(filename)
|
self.listtracks = obj[1]
|
||||||
|
text = obj[0]
|
||||||
# Read the playlist file
|
|
||||||
else:
|
else:
|
||||||
with open(filename, 'rb') as f:
|
filename = obj
|
||||||
data = f.readlines()
|
if os.path.isdir(filename):
|
||||||
|
self.listtracks = self.populate_directory(filename)
|
||||||
extension = os.path.splitext(filename)[1].lower()
|
text = os.path.splitext(os.path.basename(filename))[0]
|
||||||
if extension == '.pls':
|
|
||||||
self.listtracks = self.populate_pls(data)
|
|
||||||
elif extension == '.m3u':
|
|
||||||
self.listtracks = self.populate_m3u(data)
|
|
||||||
else:
|
else:
|
||||||
raise
|
# Read the playlist file
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
data = f.readlines()
|
||||||
|
|
||||||
# Ensure all paths are not relative to the playlist file
|
extension = os.path.splitext(filename)[1].lower()
|
||||||
for i in range(len(self.listtracks)):
|
if extension == '.pls':
|
||||||
self.listtracks[i] = self.remove_relatives(self.listtracks[i], filename)
|
self.listtracks = self.populate_pls(data)
|
||||||
|
elif extension == '.m3u':
|
||||||
|
self.listtracks = self.populate_m3u(data)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Ensure all paths are not relative to the playlist file
|
||||||
|
for i in range(len(self.listtracks)):
|
||||||
|
self.listtracks[i] = self.remove_relatives(self.listtracks[i], filename)
|
||||||
|
text = os.path.splitext(os.path.basename(filename))[0]
|
||||||
|
|
||||||
# Handle the VoiceOverData
|
# Handle the VoiceOverData
|
||||||
text = os.path.splitext(os.path.basename(filename))[0]
|
|
||||||
self["dbid"] = hashlib.md5(text).digest()[:8] #pylint: disable-msg=E1101
|
self["dbid"] = hashlib.md5(text).digest()[:8] #pylint: disable-msg=E1101
|
||||||
self.text_to_speech(text, self["dbid"], True)
|
self.text_to_speech(text, self["dbid"], True)
|
||||||
|
|
||||||
|
|
@ -543,7 +572,7 @@ class Playlist(Record):
|
||||||
return output + chunks
|
return output + chunks
|
||||||
|
|
||||||
class Shuffler(object):
|
class Shuffler(object):
|
||||||
def __init__(self, path, voiceover=False, playlist_voiceover=False, rename=False, trackgain=0, auto_playlists=None):
|
def __init__(self, path, voiceover=False, playlist_voiceover=False, rename=False, trackgain=0, auto_dir_playlists=None, auto_id3_playlists=None):
|
||||||
self.path = os.path.abspath(path)
|
self.path = os.path.abspath(path)
|
||||||
self.tracks = []
|
self.tracks = []
|
||||||
self.albums = []
|
self.albums = []
|
||||||
|
|
@ -554,7 +583,8 @@ class Shuffler(object):
|
||||||
self.playlist_voiceover = playlist_voiceover
|
self.playlist_voiceover = playlist_voiceover
|
||||||
self.rename = rename
|
self.rename = rename
|
||||||
self.trackgain = trackgain
|
self.trackgain = trackgain
|
||||||
self.auto_playlists = auto_playlists
|
self.auto_dir_playlists = auto_dir_playlists
|
||||||
|
self.auto_id3_playlists = auto_id3_playlists
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
# remove existing voiceover files (they are either useless or will be overwritten anyway)
|
# remove existing voiceover files (they are either useless or will be overwritten anyway)
|
||||||
|
|
@ -650,24 +680,40 @@ def handle_interrupt(signal, frame):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
signal.signal(signal.SIGINT, handle_interrupt)
|
signal.signal(signal.SIGINT, handle_interrupt)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=
|
parser = argparse.ArgumentParser(description=
|
||||||
'Python script for building the Track and Playlist database '
|
'Python script for building the Track and Playlist database '
|
||||||
'for the newer gen IPod Shuffle. Version 1.3')
|
'for the newer gen IPod Shuffle. Version 1.3')
|
||||||
|
|
||||||
parser.add_argument('--voiceover', action='store_true',
|
parser.add_argument('--voiceover', action='store_true',
|
||||||
help='Enable track voiceover feature')
|
help='Enable track voiceover feature')
|
||||||
|
|
||||||
parser.add_argument('--playlist-voiceover', action='store_true',
|
parser.add_argument('--playlist-voiceover', action='store_true',
|
||||||
help='Enable playlist voiceover feature')
|
help='Enable playlist voiceover feature')
|
||||||
|
|
||||||
parser.add_argument('--rename-unicode', action='store_true',
|
parser.add_argument('--rename-unicode', action='store_true',
|
||||||
help='Rename files causing unicode errors, will do minimal required renaming')
|
help='Rename files causing unicode errors, will do minimal required renaming')
|
||||||
|
|
||||||
parser.add_argument('--track-gain', type=nonnegative_int, default='0',
|
parser.add_argument('--track-gain', type=nonnegative_int, default='0',
|
||||||
help='Specify volume gain (0-99) for all tracks; '
|
help='Specify volume gain (0-99) for all tracks; '
|
||||||
'0 (default) means no gain and is usually fine; '
|
'0 (default) means no gain and is usually fine; '
|
||||||
'e.g. 60 is very loud even on minimal player volume')
|
'e.g. 60 is very loud even on minimal player volume')
|
||||||
parser.add_argument('--auto-playlists', type=int, default=None, const=-1, nargs='?',
|
|
||||||
|
parser.add_argument('--auto-dir-playlists', type=int, default=None, const=-1, nargs='?',
|
||||||
help='Generate automatic playlists for each folder recursively inside '
|
help='Generate automatic playlists for each folder recursively inside '
|
||||||
'"IPod_Control/Music/". You can optionally limit the depth: '
|
'"IPod_Control/Music/". You can optionally limit the depth: '
|
||||||
'0=root, 1=artist, 2=album, n=subfoldername, default=-1 (No Limit).')
|
'0=root, 1=artist, 2=album, n=subfoldername, default=-1 (No Limit).')
|
||||||
|
|
||||||
|
parser.add_argument('--auto-id3-playlists', type=str, default=None, metavar='ID3_TEMPLATE', const='{artist}', nargs='?',
|
||||||
|
help='Generate automatic playlists based on the id3 tags of any music '
|
||||||
|
'added to the iPod. You can optionally specify a template string '
|
||||||
|
'based on which id3 tags are used to generate playlists. For eg. '
|
||||||
|
'\'{artist} - {album}\' will use the pair of artist and album to group '
|
||||||
|
'tracks under one playlist. Similarly \'{genre}\' will group tracks based '
|
||||||
|
'on their genre tag. Default template used is \'{artist}\'')
|
||||||
|
|
||||||
parser.add_argument('path', help='Path to the IPod\'s root directory')
|
parser.add_argument('path', help='Path to the IPod\'s root directory')
|
||||||
|
|
||||||
result = parser.parse_args()
|
result = parser.parse_args()
|
||||||
|
|
||||||
checkPathValidity(result.path)
|
checkPathValidity(result.path)
|
||||||
|
|
@ -675,11 +721,15 @@ if __name__ == '__main__':
|
||||||
if result.rename_unicode:
|
if result.rename_unicode:
|
||||||
check_unicode(result.path)
|
check_unicode(result.path)
|
||||||
|
|
||||||
if result.voiceover and not Text2Speech.check_support():
|
if result.auto_id3_playlists != None or result.auto_dir_playlists != None:
|
||||||
|
result.playlist_voiceover = True
|
||||||
|
|
||||||
|
if (result.voiceover or result.playlist_voiceover) and not Text2Speech.check_support():
|
||||||
print "Error: Did not find any voiceover program. Voiceover disabled."
|
print "Error: Did not find any voiceover program. Voiceover disabled."
|
||||||
result.voiceover = False
|
result.voiceover = False
|
||||||
|
result.playlist_voiceover = False
|
||||||
|
|
||||||
shuffle = Shuffler(result.path, voiceover=result.voiceover, playlist_voiceover=result.playlist_voiceover, rename=result.rename_unicode, trackgain=result.track_gain, auto_playlists=result.auto_playlists)
|
shuffle = Shuffler(result.path, voiceover=result.voiceover, playlist_voiceover=result.playlist_voiceover, rename=result.rename_unicode, trackgain=result.track_gain, auto_dir_playlists=result.auto_dir_playlists, auto_id3_playlists=result.auto_id3_playlists)
|
||||||
shuffle.initialize()
|
shuffle.initialize()
|
||||||
shuffle.populate()
|
shuffle.populate()
|
||||||
shuffle.write_database()
|
shuffle.write_database()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue