From fd6150c2db8a6c09665f1fe7b6424cfca9a16cd6 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 25 Aug 2021 11:57:21 -0400 Subject: [PATCH 01/21] follow symlinks, avoid duplicates from following links --- ipod-shuffle-4g.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 12d1860..2e0f30c 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -510,6 +510,8 @@ class Playlist(Record): # Only add valid music files to playlist if os.path.splitext(filename)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"): fullPath = os.path.abspath(os.path.join(dirpath, filename)) + if os.path.islink(fullPath): + fullPath = os.path.realpath(fullPath) listtracks.append(fullPath) if not recursive: break @@ -617,14 +619,18 @@ class Shuffler(object): # Ignore hidden files if not filename.startswith("."): fullPath = os.path.abspath(os.path.join(dirpath, filename)) + if os.path.islink(fullPath): + fullPath = os.path.realpath(fullPath) if os.path.splitext(filename)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"): - self.tracks.append(fullPath) + if fullPath not in self.tracks: + self.tracks.append(fullPath) if os.path.splitext(filename)[1].lower() in (".pls", ".m3u"): - self.lists.append(fullPath) + if fullPath not in self.lists: + self.lists.append(fullPath) # Create automatic playlists in music directory. # Ignore the (music) root and any hidden directories. - if self.auto_dir_playlists and "iPod_Control/Music/" in dirpath and "/." not in dirpath: + if self.auto_dir_playlists and ("iPod_Control/Music/" in dirpath or "iPod_Control/Podcasts/" in dirpath) and "/." not in dirpath: # Only go to a specific depth. -1 is unlimted, 0 is ignored as there is already a master playlist. depth = dirpath[len(self.path) + len(os.path.sep):].count(os.path.sep) - 1 if self.auto_dir_playlists < 0 or depth <= self.auto_dir_playlists: From bab8af033fc59869d2229ed48555d45dae737654 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 25 Aug 2021 13:57:01 -0400 Subject: [PATCH 02/21] mark tracks and playlists as podcasts --- ipod-shuffle-4g.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 2e0f30c..bb19d0d 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -294,7 +294,7 @@ class TunesSD(Record): self["playlist_header_offset"] = self.play_header.base_offset self["total_number_of_tracks"] = self.track_header["number_of_tracks"] - self["total_tracks_without_podcasts"] = self.track_header["number_of_tracks"] + self["total_tracks_without_podcasts"] = self.track_header.total_tracks_without_podcasts() self["total_number_of_playlists"] = self.play_header["number_of_playlists"] output = Record.construct(self) @@ -303,6 +303,7 @@ class TunesSD(Record): class TrackHeader(Record): def __init__(self, parent): self.base_offset = 0 + self.total_podcasts = 0 Record.__init__(self, parent) self._struct = collections.OrderedDict([ ("header_id", ("4s", b"hths")), # shth @@ -322,14 +323,20 @@ class TrackHeader(Record): track = Track(self) verboseprint("[*] Adding track", i) track.populate(i) + if track.is_podcast: + self.total_podcasts += 1 output += struct.pack("I", self.base_offset + self["total_length"] + len(track_chunk)) track_chunk += track.construct() return output + track_chunk + def total_tracks_without_podcasts(self): + return self["number_of_tracks"] - self.total_podcasts + class Track(Record): def __init__(self, parent): Record.__init__(self, parent) + self.is_podcast = False self._struct = collections.OrderedDict([ ("header_id", ("4s", b"rths")), # shtr ("header_length", ("I", 0x174)), @@ -364,6 +371,10 @@ class Track(Record): if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"): self["filetype"] = 2 + if "/iPod_Control/Podcasts/" in filename: + self.is_podcast = True + self["dontskip"] = 0 + text = os.path.splitext(os.path.basename(filename))[0] # Try to get album and artist information with mutagen @@ -374,6 +385,10 @@ class Track(Record): except: print("Error calling mutagen. Possible invalid filename/ID3Tags (hyphen in filename?)") if audio: + if "Podcast" in audio.get("genre", ["Unknown"]): + self.is_podcast = True + self["dontskip"] = 0 + # Note: Rythmbox IPod plugin sets this value always 0. self["stop_at_pos_ms"] = int(audio.info.length * 1000) @@ -408,7 +423,7 @@ class PlaylistHeader(Record): ("header_id", ("4s", b"hphs")), #shph ("total_length", ("I", 0)), ("number_of_playlists", ("I", 0)), - ("number_of_non_podcast_lists", ("2s", b"\xFF\xFF")), + ("number_of_non_podcast_lists", ("H", 65535)), ("number_of_master_lists", ("2s", b"\x01\x00")), ("number_of_non_audiobook_lists", ("2s", b"\xFF\xFF")), ("unknown2", ("2s", b"\x00" * 2)), @@ -423,18 +438,23 @@ class PlaylistHeader(Record): # Build all the remaining playlists playlistcount = 1 + podcastlistcount = 0 for i in self.lists: playlist = Playlist(self) verboseprint("[+] Adding playlist", (i[0] if type(i) == type(()) else i)) playlist.populate(i) construction = playlist.construct(tracks) if playlist["number_of_songs"] > 0: + if playlist["listtype"] == 3: + podcastlistcount += 1 playlistcount += 1 chunks += [construction] else: print("Error: Playlist does not contain a single track. Skipping playlist.") self["number_of_playlists"] = playlistcount + if podcastlistcount > 0: + self["number_of_non_podcast_lists"] = playlistcount - podcastlistcount self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) # Start the header @@ -470,6 +490,9 @@ class Playlist(Record): self["listtype"] = 1 self.listtracks = tracks + def set_podcast(self): + self["listtype"] = 3 + def populate_m3u(self, data): listtracks = [] for i in data: @@ -500,6 +523,8 @@ class Playlist(Record): # Folders containing no music and only a single Album # would generate duplicated playlists. That is intended and "wont fix". # Empty folders (inside the music path) will generate an error -> "wont fix". + if "/iPod_Control/Podcasts/" in playlistpath: + self.set_podcast() listtracks = [] for (dirpath, dirnames, filenames) in os.walk(playlistpath): dirnames.sort() From 571407953ed48a0f4a365eb49ed10015c5be029f Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 25 Aug 2021 16:13:35 -0400 Subject: [PATCH 03/21] create podcast directory --- ipod-shuffle-4g.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index bb19d0d..a96a436 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -623,7 +623,7 @@ class Shuffler(object): # remove existing voiceover files (they are either useless or will be overwritten anyway) for dirname in ('iPod_Control/Speakable/Playlists', 'iPod_Control/Speakable/Tracks'): shutil.rmtree(os.path.join(self.path, dirname), ignore_errors=True) - for dirname in ('iPod_Control/iTunes', 'iPod_Control/Music', 'iPod_Control/Speakable/Playlists', 'iPod_Control/Speakable/Tracks'): + for dirname in ('iPod_Control/iTunes', 'iPod_Control/Music', 'iPod_Control/Podcasts', 'iPod_Control/Speakable/Playlists', 'iPod_Control/Speakable/Tracks'): make_dir_if_absent(os.path.join(self.path, dirname)) def dump_state(self): From 7dc7d4ca91d51152f22e654544416446c1e7f22c Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 25 Aug 2021 17:40:58 -0400 Subject: [PATCH 04/21] exclude podcasts from master playlist --- ipod-shuffle-4g.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index a96a436..af9adde 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -589,6 +589,9 @@ class Playlist(Record): for i in self.listtracks: path = self.ipod_to_path(i) position = -1 + if self["listtype"] == 1 and "/iPod_Control/Podcasts/" in path: + print ('not including podcast in master playlist: {}'.format(path)) + continue try: position = tracks.index(path) except: From 368a9b26831a58a4aef1f2882e1528dd88424775 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 28 Aug 2021 14:10:57 -0400 Subject: [PATCH 05/21] podcasts should remember their last playback position --- ipod-shuffle-4g.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index af9adde..0862271 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -374,6 +374,7 @@ class Track(Record): if "/iPod_Control/Podcasts/" in filename: self.is_podcast = True self["dontskip"] = 0 + self["remember"] = 1 text = os.path.splitext(os.path.basename(filename))[0] @@ -388,6 +389,7 @@ class Track(Record): if "Podcast" in audio.get("genre", ["Unknown"]): self.is_podcast = True self["dontskip"] = 0 + self["remember"] = 1 # Note: Rythmbox IPod plugin sets this value always 0. self["stop_at_pos_ms"] = int(audio.info.length * 1000) From dd0ccd935e52cb56c53fb5bd44da256362a333eb Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sun, 29 Aug 2021 10:19:09 -0400 Subject: [PATCH 06/21] readme entries for podcast and symlink support --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 89ad287..8603645 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ The file can be found in the [extras](extras) folder. #### Compress/Convert your music files ([#11](https://github.com/nims11/IPod-Shuffle-4g/issues/11)) Shuffle is short on storage, and you might want to squeeze in more of your collection by sacrificing some bitrate off your files. In rarer cases, you might also possess music in formats not supported by your ipod. Although `ffmpeg` can handle almost all your needs, if you are looking for a friendly alternative, try [Soundconverter](http://soundconverter.org/). +#### Podcast support +Place podcast tracks in `iPod_Control/Podcasts` to generate playlists. These tracks will be skipped when shuffling, will be marked to remember their last playback position, and won't be included in the "All Songs" playlist. + +#### Use symlinks to create playlists +Symlinks will be followed to their original location on the iPod Shuffle. This allows creating additional playlists without duplicating physical files or duplicate entries in the iTunesSD database. + #### Use Rhythmbox to manage your music and playlists As described [in the blog post](https://nims11.wordpress.com/2013/10/12/ipod-shuffle-4g-under-linux/) you can use Rythmbox to sync your personal music library to your IPod From 78e1f59804b2c06d966612c5ddcae600cdb989dc Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Tue, 31 Aug 2021 23:00:10 -0400 Subject: [PATCH 07/21] remove symlink support --- README.md | 3 --- ipod-shuffle-4g.py | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8603645..33bfd49 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,6 @@ The file can be found in the [extras](extras) folder. #### Podcast support Place podcast tracks in `iPod_Control/Podcasts` to generate playlists. These tracks will be skipped when shuffling, will be marked to remember their last playback position, and won't be included in the "All Songs" playlist. -#### Use symlinks to create playlists -Symlinks will be followed to their original location on the iPod Shuffle. This allows creating additional playlists without duplicating physical files or duplicate entries in the iTunesSD database. - #### Use Rhythmbox to manage your music and playlists As described [in the blog post](https://nims11.wordpress.com/2013/10/12/ipod-shuffle-4g-under-linux/) you can use Rythmbox to sync your personal music library to your IPod diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 0862271..a4f676f 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -537,8 +537,6 @@ class Playlist(Record): # Only add valid music files to playlist if os.path.splitext(filename)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"): fullPath = os.path.abspath(os.path.join(dirpath, filename)) - if os.path.islink(fullPath): - fullPath = os.path.realpath(fullPath) listtracks.append(fullPath) if not recursive: break @@ -649,14 +647,10 @@ class Shuffler(object): # Ignore hidden files if not filename.startswith("."): fullPath = os.path.abspath(os.path.join(dirpath, filename)) - if os.path.islink(fullPath): - fullPath = os.path.realpath(fullPath) if os.path.splitext(filename)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"): - if fullPath not in self.tracks: - self.tracks.append(fullPath) + self.tracks.append(fullPath) if os.path.splitext(filename)[1].lower() in (".pls", ".m3u"): - if fullPath not in self.lists: - self.lists.append(fullPath) + self.lists.append(fullPath) # Create automatic playlists in music directory. # Ignore the (music) root and any hidden directories. From 020433ae7404dba25e8df1bdb3601bd20d358388 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 08/21] documentation and comments --- ipod-shuffle-4g.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index a4f676f..e3772d8 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -373,8 +373,8 @@ class Track(Record): if "/iPod_Control/Podcasts/" in filename: self.is_podcast = True - self["dontskip"] = 0 - self["remember"] = 1 + self["dontskip"] = 0 # podcasts should not be "not skipped" (re: should be skipped) when shuffling + self["remember"] = 1 # podcasts should remember their last playback position text = os.path.splitext(os.path.basename(filename))[0] From e0a2f6cf8570e09641e6fe661c74a58d6d5ae215 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 09/21] documentation and comments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bfd49..28c0c00 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ The file can be found in the [extras](extras) folder. ([#11](https://github.com/nims11/IPod-Shuffle-4g/issues/11)) Shuffle is short on storage, and you might want to squeeze in more of your collection by sacrificing some bitrate off your files. In rarer cases, you might also possess music in formats not supported by your ipod. Although `ffmpeg` can handle almost all your needs, if you are looking for a friendly alternative, try [Soundconverter](http://soundconverter.org/). #### Podcast support -Place podcast tracks in `iPod_Control/Podcasts` to generate playlists. These tracks will be skipped when shuffling, will be marked to remember their last playback position, and won't be included in the "All Songs" playlist. +Place podcast tracks in `iPod_Control/Podcasts`, or add "Podcast" to the ID3 Genre, to generate playlists. These tracks will be skipped when shuffling, will be marked to remember their last playback position, and won't be included in the "All Songs" playlist. #### Use Rhythmbox to manage your music and playlists As described [in the blog post](https://nims11.wordpress.com/2013/10/12/ipod-shuffle-4g-under-linux/) From 3ca83b627309078a73b6f45d12243229c719f333 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 10/21] use Enums for (Playlist,File)Types --- ipod-shuffle-4g.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index e3772d8..1b184f3 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -14,6 +14,8 @@ import shutil import re import tempfile import signal +import enum +import functools # External libraries try: @@ -21,8 +23,25 @@ try: except ImportError: mutagen = None -audio_ext = (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav") -list_ext = (".pls", ".m3u") +class PlaylistType(enum.Enum): + ALL_SONGS = 1 + NORMAL = 2 + PODCAST = 3 + AUDIOBOOK = 4 + +class FileType(enum.Enum): + MP3 = (1, {'.mp3'}) + AAC = (2, {'.m4a', '.m4b', '.m4p', '.aa'}) + WAV = (4, {'.wav'}) + + def __init__(self, filetype, extensions): + self.filetype = filetype + self.extensions = extensions + +audio_ext = functools.reduce(lambda j,k: j.union(k), map(lambda i: i.extensions, FileType)) +list_ext = {".pls", ".m3u"} +all_ext = audio_ext.union(list_ext) + def make_dir_if_absent(path): try: os.makedirs(path) @@ -368,8 +387,12 @@ class Track(Record): def populate(self, filename): self["filename"] = self.path_to_ipod(filename).encode('utf-8') - if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"): - self["filetype"] = 2 + # assign the "filetype" based on the extension + ext = os.path.splitext(filename)[1].lower() + for type in FileType: + if ext in type.extensions: + self.filetype = type.filetype + break if "/iPod_Control/Podcasts/" in filename: self.is_podcast = True @@ -472,6 +495,7 @@ class PlaylistHeader(Record): class Playlist(Record): def __init__(self, parent): self.listtracks = [] + self.listtype = PlaylistType.NORMAL Record.__init__(self, parent) self._struct = collections.OrderedDict([ ("header_id", ("4s", b"lphs")), # shpl @@ -489,11 +513,14 @@ class Playlist(Record): if self.playlist_voiceover and (Text2Speech.valid_tts['pico2wave'] or Text2Speech.valid_tts['espeak']): self["dbid"] = hashlib.md5(b"masterlist").digest()[:8] self.text_to_speech("All songs", self["dbid"], True) - self["listtype"] = 1 + self.listtype = PlaylistType.ALL_SONGS self.listtracks = tracks + def set_audiobook(self): + self.listtype = PlaylistType.AUDIOBOOK + def set_podcast(self): - self["listtype"] = 3 + self.listtype = PlaylistType.PODCAST def populate_m3u(self, data): listtracks = [] @@ -584,6 +611,7 @@ class Playlist(Record): def construct(self, tracks): self["total_length"] = 44 + (4 * len(self.listtracks)) self["number_of_songs"] = 0 + self["listtype"] = self.listtype.value chunks = bytes() for i in self.listtracks: @@ -696,7 +724,7 @@ def check_unicode(path): ret_flag = False # True if there is a recognizable file within this level for item in os.listdir(path): if os.path.isfile(os.path.join(path, item)): - if os.path.splitext(item)[1].lower() in audio_ext+list_ext: + if os.path.splitext(item)[1].lower() in all_ext: ret_flag = True if raises_unicode_error(item): src = os.path.join(path, item) From 0b277a967ca0d5000b87a670deb42068b7c8c523 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 11/21] use set_podcast method to dedup podcast feature setting --- ipod-shuffle-4g.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 1b184f3..22bee31 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -384,20 +384,19 @@ class Track(Record): ("unknown5", ("32s", b"\x00" * 32)), ]) + def set_podcast(self): + self.is_podcast = True + self["dontskip"] = 0 # podcasts should not be "not skipped" when shuffling (re: should not be shuffled) + self["remember"] = 1 # podcasts should remember their last playback position + def populate(self, filename): self["filename"] = self.path_to_ipod(filename).encode('utf-8') - # assign the "filetype" based on the extension - ext = os.path.splitext(filename)[1].lower() - for type in FileType: - if ext in type.extensions: - self.filetype = type.filetype - break + if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"): + self["filetype"] = 2 if "/iPod_Control/Podcasts/" in filename: - self.is_podcast = True - self["dontskip"] = 0 # podcasts should not be "not skipped" (re: should be skipped) when shuffling - self["remember"] = 1 # podcasts should remember their last playback position + self.set_podcast() text = os.path.splitext(os.path.basename(filename))[0] @@ -410,9 +409,7 @@ class Track(Record): print("Error calling mutagen. Possible invalid filename/ID3Tags (hyphen in filename?)") if audio: if "Podcast" in audio.get("genre", ["Unknown"]): - self.is_podcast = True - self["dontskip"] = 0 - self["remember"] = 1 + self.set_podcast() # Note: Rythmbox IPod plugin sets this value always 0. self["stop_at_pos_ms"] = int(audio.info.length * 1000) From dde2e3b4059e3a8b7b9c52582b4084e8ae2f5a9e Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 12/21] comments --- ipod-shuffle-4g.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 22bee31..2a174de 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -476,6 +476,9 @@ class PlaylistHeader(Record): self["number_of_playlists"] = playlistcount if podcastlistcount > 0: + # "number_of_non_podcast_lists" should default to 65535 if there + # aren't any podcast playlists, so only calculate the count if + # the podcastlistcount is greater than 0 self["number_of_non_podcast_lists"] = playlistcount - podcastlistcount self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) # Start the header From d4a9a145be895d9e613cce7cea961060a68cf8ea Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 13/21] comments --- ipod-shuffle-4g.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 2a174de..593ed2e 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -33,13 +33,15 @@ class FileType(enum.Enum): MP3 = (1, {'.mp3'}) AAC = (2, {'.m4a', '.m4b', '.m4p', '.aa'}) WAV = (4, {'.wav'}) - def __init__(self, filetype, extensions): self.filetype = filetype self.extensions = extensions +# collect all the supported audio extensions audio_ext = functools.reduce(lambda j,k: j.union(k), map(lambda i: i.extensions, FileType)) +# the supported playlist extensions list_ext = {".pls", ".m3u"} +# all the supported file extensions all_ext = audio_ext.union(list_ext) def make_dir_if_absent(path): From 767f37163760e6adb137db962a595a28f42e1436 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 14/21] more enum use --- ipod-shuffle-4g.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 593ed2e..2c264bf 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -469,7 +469,7 @@ class PlaylistHeader(Record): playlist.populate(i) construction = playlist.construct(tracks) if playlist["number_of_songs"] > 0: - if playlist["listtype"] == 3: + if playlist["listtype"] == PlaylistType.PODCAST.value: podcastlistcount += 1 playlistcount += 1 chunks += [construction] @@ -518,12 +518,6 @@ class Playlist(Record): self.listtype = PlaylistType.ALL_SONGS self.listtracks = tracks - def set_audiobook(self): - self.listtype = PlaylistType.AUDIOBOOK - - def set_podcast(self): - self.listtype = PlaylistType.PODCAST - def populate_m3u(self, data): listtracks = [] for i in data: @@ -555,7 +549,7 @@ class Playlist(Record): # would generate duplicated playlists. That is intended and "wont fix". # Empty folders (inside the music path) will generate an error -> "wont fix". if "/iPod_Control/Podcasts/" in playlistpath: - self.set_podcast() + self.listtype = PlaylistType.PODCAST listtracks = [] for (dirpath, dirnames, filenames) in os.walk(playlistpath): dirnames.sort() @@ -619,8 +613,8 @@ class Playlist(Record): for i in self.listtracks: path = self.ipod_to_path(i) position = -1 - if self["listtype"] == 1 and "/iPod_Control/Podcasts/" in path: - print ('not including podcast in master playlist: {}'.format(path)) + if PlaylistType.ALL_SONGS == self.listtype and "/iPod_Control/Podcasts/" in path: + # exclude podcasts from the "All Songs" playlist continue try: position = tracks.index(path) From a61317df67e411662dba8efc0ec0b53f0baabdde Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 12:30:00 -0400 Subject: [PATCH 15/21] restore extension/filetype detection --- ipod-shuffle-4g.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 2c264bf..e48ab47 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -394,8 +394,12 @@ class Track(Record): def populate(self, filename): self["filename"] = self.path_to_ipod(filename).encode('utf-8') - if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"): - self["filetype"] = 2 + # assign the "filetype" based on the extension + ext = os.path.splitext(filename)[1].lower() + for type in FileType: + if ext in type.extensions: + self.filetype = type.filetype + break if "/iPod_Control/Podcasts/" in filename: self.set_podcast() From 3d7189c36e6321d9ae3befa989b6d39b6a540c67 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Wed, 1 Sep 2021 13:18:52 -0400 Subject: [PATCH 16/21] set total_podcasts = 0 before processing tracks in construct() --- ipod-shuffle-4g.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index e48ab47..ba62a0f 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -337,6 +337,7 @@ class TrackHeader(Record): self["number_of_tracks"] = len(self.tracks) self["total_length"] = 20 + (len(self.tracks) * 4) output = Record.construct(self) + self.total_podcasts = 0 # Construct the underlying tracks track_chunk = bytes() From aa740c22dc527daae4db87b054df5e2d72bb22ad Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Thu, 2 Sep 2021 12:30:00 -0400 Subject: [PATCH 17/21] move podcast playlist detection to cover pls and m3u handling --- ipod-shuffle-4g.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index ba62a0f..7bcf67a 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -553,8 +553,6 @@ class Playlist(Record): # Folders containing no music and only a single Album # would generate duplicated playlists. That is intended and "wont fix". # Empty folders (inside the music path) will generate an error -> "wont fix". - if "/iPod_Control/Podcasts/" in playlistpath: - self.listtype = PlaylistType.PODCAST listtracks = [] for (dirpath, dirnames, filenames) in os.walk(playlistpath): dirnames.sort() @@ -584,6 +582,8 @@ class Playlist(Record): text = obj[0] else: filename = obj + if "/iPod_Control/Podcasts/" in filename: + self.listtype = PlaylistType.PODCAST if os.path.isdir(filename): self.listtracks = self.populate_directory(filename) text = os.path.splitext(os.path.basename(filename))[0] From 299813bbaa1abb7245baad158e47a85cdf272bfb Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Thu, 2 Sep 2021 12:30:00 -0400 Subject: [PATCH 18/21] use a default of 0xFFFF instead of 65535 --- ipod-shuffle-4g.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 7bcf67a..3f2828a 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -452,7 +452,7 @@ class PlaylistHeader(Record): ("header_id", ("4s", b"hphs")), #shph ("total_length", ("I", 0)), ("number_of_playlists", ("I", 0)), - ("number_of_non_podcast_lists", ("H", 65535)), + ("number_of_non_podcast_lists", ("H", b"\xFF\xFF")), ("number_of_master_lists", ("2s", b"\x01\x00")), ("number_of_non_audiobook_lists", ("2s", b"\xFF\xFF")), ("unknown2", ("2s", b"\x00" * 2)), @@ -483,7 +483,7 @@ class PlaylistHeader(Record): self["number_of_playlists"] = playlistcount if podcastlistcount > 0: - # "number_of_non_podcast_lists" should default to 65535 if there + # "number_of_non_podcast_lists" should default to 0xFFFF if there # aren't any podcast playlists, so only calculate the count if # the podcastlistcount is greater than 0 self["number_of_non_podcast_lists"] = playlistcount - podcastlistcount From 622f55f62c8fdd6f994452f19e59a61caabc9368 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Thu, 2 Sep 2021 12:30:00 -0400 Subject: [PATCH 19/21] better enum comparison --- ipod-shuffle-4g.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 3f2828a..7d3d71e 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -474,7 +474,7 @@ class PlaylistHeader(Record): playlist.populate(i) construction = playlist.construct(tracks) if playlist["number_of_songs"] > 0: - if playlist["listtype"] == PlaylistType.PODCAST.value: + if PlaylistType(playlist["listtype"]) == PlaylistType.PODCAST: podcastlistcount += 1 playlistcount += 1 chunks += [construction] From c68063eeb72c6a780f779c36b40ef51d5bd16780 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Thu, 2 Sep 2021 12:30:00 -0400 Subject: [PATCH 20/21] use the existing audio_ext set for checking file extensions --- ipod-shuffle-4g.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 7d3d71e..8e4520a 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -561,7 +561,7 @@ class Playlist(Record): if "/." not in dirpath: for filename in sorted(filenames, key = lambda x: x.lower()): # Only add valid music files to playlist - if os.path.splitext(filename)[1].lower() in (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav"): + if os.path.splitext(filename)[1].lower() in audio_ext: fullPath = os.path.abspath(os.path.join(dirpath, filename)) listtracks.append(fullPath) if not recursive: From ce2b20ebf5a82bc24bea3d19b5c51d802ce388a2 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Tue, 22 Mar 2022 22:13:00 -0400 Subject: [PATCH 21/21] fix setting filetype --- ipod-shuffle-4g.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipod-shuffle-4g.py b/ipod-shuffle-4g.py index 8e4520a..61cf9e2 100755 --- a/ipod-shuffle-4g.py +++ b/ipod-shuffle-4g.py @@ -399,7 +399,7 @@ class Track(Record): ext = os.path.splitext(filename)[1].lower() for type in FileType: if ext in type.extensions: - self.filetype = type.filetype + self["filetype"] = type.filetype break if "/iPod_Control/Podcasts/" in filename: