From fa160cee18f131d5dab56239cfe339b4f368ad3d Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:26:07 +0100 Subject: [PATCH 01/42] Added 'path' help entry --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index e1fa0e9..7aac187 100755 --- a/shuffle.py +++ b/shuffle.py @@ -552,7 +552,7 @@ if __name__ == '__main__': parser.add_argument('--disable-voiceover', action='store_true', help='Disable Voiceover Feature') parser.add_argument('--rename-unicode', action='store_true', help='Rename Files Causing Unicode Errors, will do minimal required renaming') parser.add_argument('--track-gain', type=nonnegative_int, default=0, help='Specify volume gain (0-99) for all tracks; 0 (default) means no gain and is usually fine; e.g. 60 is very loud even on minimal player volume') - parser.add_argument('path') + parser.add_argument('path', help='Path to the IPod\'s root directory') result = parser.parse_args() if not os.path.isdir(result.path): From 594ca8f964b0492a7784ac3267c51959c0de4482 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:33:21 +0100 Subject: [PATCH 02/42] Made help message lower case --- shuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle.py b/shuffle.py index 7aac187..8193cbe 100755 --- a/shuffle.py +++ b/shuffle.py @@ -549,8 +549,8 @@ def nonnegative_int(string): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--disable-voiceover', action='store_true', help='Disable Voiceover Feature') - parser.add_argument('--rename-unicode', action='store_true', help='Rename Files Causing Unicode Errors, will do minimal required renaming') + parser.add_argument('--disable-voiceover', action='store_true', help='Disable voiceover feature') + parser.add_argument('--rename-unicode', action='store_true', help='Rename files causing unicode errors, will do minimal required renaming') parser.add_argument('--track-gain', type=nonnegative_int, default=0, help='Specify volume gain (0-99) for all tracks; 0 (default) means no gain and is usually fine; e.g. 60 is very loud even on minimal player volume') parser.add_argument('path', help='Path to the IPod\'s root directory') result = parser.parse_args() From 255bd8931b0022106cf13247f94552492a2c8650 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 14:04:46 +0100 Subject: [PATCH 03/42] Added default "All Songs" male voice if voiceover is disabled --- shuffle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shuffle.py b/shuffle.py index 8193cbe..7480934 100755 --- a/shuffle.py +++ b/shuffle.py @@ -378,9 +378,12 @@ class Playlist(Record): ]) def set_master(self, tracks): - self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 + # By default use "All Songs" builtin voiceover (dbid all zero) + # Else generate alternative "All Songs" to fit the speaker voice of other playlists + if self.voiceover: + self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 + self.text_to_speech("All songs", self["dbid"], True) self["listtype"] = 1 - self.text_to_speech("All songs", self["dbid"], True) self.listtracks = tracks def populate_m3u(self, data): From 0acb957993fbc84bb373324ca40203366b8a66ea Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 20:44:30 +0100 Subject: [PATCH 04/42] Additional voiceover doc info --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 913189c..9e1900b 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -115,7 +115,7 @@ Here's the general layout of an iTunesSD file:
1
-
+ Only applies for tracks, not for playlists.
1
From c7a2ed164082d5e4f73e72ac236fbae6777cc581 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 20:45:58 +0100 Subject: [PATCH 05/42] Skip existing voiceover files, add proper return value --- shuffle.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/shuffle.py b/shuffle.py index 7480934..8bdd58d 100755 --- a/shuffle.py +++ b/shuffle.py @@ -69,14 +69,20 @@ class Text2Speech(object): @staticmethod def text2speech(out_wav_path, text): + # Skip voiceover geneartion if a track with the same name is used. + # This might happen with "Track001" or "01. Intro" names for example. + if os.path.isfile(out_wav_path): + print "Using eExisting", out_wav_path + return + # ensure we deal with unicode later if not isinstance(text, unicode): text = unicode(text, 'utf-8') lang = Text2Speech.guess_lang(text) if lang == "ru-RU": - Text2Speech.rhvoice(out_wav_path, text) + return Text2Speech.rhvoice(out_wav_path, text) else: - Text2Speech.pico2wave(out_wav_path, text) + return Text2Speech.pico2wave(out_wav_path, text) # guess-language seems like an overkill for now @staticmethod @@ -91,6 +97,7 @@ class Text2Speech(object): if not Text2Speech.valid_tts['pico2wave']: return False subprocess.call(["pico2wave", "-l", "en-GB", "-w", out_wav_path, unicodetext]) + return True @staticmethod def rhvoice(out_wav_path, unicodetext): @@ -106,6 +113,7 @@ class Text2Speech(object): subprocess.call(["sox", tmp_file.name, out_wav_path, "norm"]) os.remove(tmp_file.name) + return True class Record(object): @@ -140,7 +148,8 @@ class Record(object): # Create the voiceover wav file fn = "".join(["{0:02X}".format(ord(x)) for x in reversed(dbid)]) path = os.path.join(self.base, "iPod_Control", "Speakable", "Tracks" if not playlist else "Playlists", fn + ".wav") - Text2Speech.text2speech(path, text) + return Text2Speech.text2speech(path, text) + return False def path_to_ipod(self, filename): if os.path.commonprefix([os.path.abspath(filename), self.base]) != self.base: From 69f3b87b6edcd69f659757c9f34bbb56b316af7a Mon Sep 17 00:00:00 2001 From: NicoHood Date: Mon, 18 Jan 2016 18:33:13 +0100 Subject: [PATCH 06/42] Skip already generated voiceover files --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 8bdd58d..78c9719 100755 --- a/shuffle.py +++ b/shuffle.py @@ -73,7 +73,7 @@ class Text2Speech(object): # This might happen with "Track001" or "01. Intro" names for example. if os.path.isfile(out_wav_path): print "Using eExisting", out_wav_path - return + return True # ensure we deal with unicode later if not isinstance(text, unicode): From 21ec20723ec45309a8d403540b3e8a3f8eb3837b Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 09:53:55 +0100 Subject: [PATCH 07/42] Update iTunesSD3gen.md The hex value was written in big endian, but we have little endian. Small mistake. I confirmed this with the rythmbox output. BTW: We are currently using 0x02010001 for any reason. --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 8580603..bddd62d 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -43,7 +43,7 @@ Here's the general layout of an iTunesSD file:
?
- 0x03000002
+ 0x02000003
03 00 00 02
From 587a6d132dab546a730ccdbd96f8f5a928d885f8 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 10:02:30 +0100 Subject: [PATCH 08/42] Added stop_at_pos_ms rythmbox note --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 8580603..ad51b25 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -364,7 +364,7 @@ Here's the general layout of an iTunesSD file:
4
-
+ Rythmbox IPod plugin sets this value always 0.
112169
From 103cc6e60687a422d977834078f1f40c0901df16 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 12:10:25 +0100 Subject: [PATCH 09/42] Update iTunesSD3gen.md --- docs/iTunesSD3gen.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 8580603..237e598 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -41,9 +41,10 @@ Here's the general layout of an iTunesSD file:
4
- ?
+ Version number?
- 0x03000002
+ 0x03000002
+ Old values:
0x02010001
Gen 2:
0x010600
0x010800

03 00 00 02
@@ -115,7 +116,7 @@ Here's the general layout of an iTunesSD file:
1
-
+ Turns on any track voiceover feedback
1
From a07873497d8c4b50fca88ea2128d4aeafec03b36 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 12:14:14 +0100 Subject: [PATCH 10/42] Use default speaker when voiceover is disabled #17 --- shuffle.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/shuffle.py b/shuffle.py index 78c9719..6d53afd 100755 --- a/shuffle.py +++ b/shuffle.py @@ -197,7 +197,7 @@ class TunesSD(Record): self.play_header = PlaylistHeader(self) self._struct = collections.OrderedDict([ ("header_id", ("4s", "shdb")), - ("unknown1", ("I", 0x02010001)), + ("unknown1", ("I", 0x02000003)), ("total_length", ("I", 64)), ("total_number_of_tracks", ("I", 0)), ("total_number_of_playlists", ("I", 0)), @@ -328,16 +328,10 @@ class PlaylistHeader(Record): ("header_id", ("4s", "shph")), ("total_length", ("I", 0)), ("number_of_playlists", ("I", 0)), - ("number_of_podcast_lists", ("I", 0xffffffff)), - ("number_of_master_lists", ("I", 0)), - ("number_of_audiobook_lists", ("I", 0xffffffff)), - ("unknown1", ("I", 0)), - ("unknown2", ("I", 0xffffffff)), - ("unknown3", ("I", 0)), - ("unknown4", ("I", 0xffffffff)), - ("unknown5", ("I", 0)), - ("unknown6", ("I", 0xffffffff)), - ("unknown7", ("20s", "\x00" * 20)), + ("number_of_non_podcast_lists", ("2s", "\x03\x00")), #TODO check if really ffff is okay + ("number_of_master_lists", ("2s", "\x01\x00")), + ("number_of_non_audiobook_lists", ("2s", "\x03\x00")), #TODO as above + ("unknown2", ("2s", "\x00" * 2)), ]) def construct(self, tracks): #pylint: disable-msg=W0221 @@ -359,8 +353,7 @@ class PlaylistHeader(Record): chunks += [construction] self["number_of_playlists"] = playlistcount - self["number_of_master_lists"] = 0 - self["total_length"] = 0x44 + (self["number_of_playlists"] * 4) + self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) # Start the header output = Record.construct(self) From cab4d83fea5165a5cdd9e5e76cd5bd61957aa7f5 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 12:18:56 +0100 Subject: [PATCH 11/42] Typo --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 6d53afd..90522ce 100755 --- a/shuffle.py +++ b/shuffle.py @@ -72,7 +72,7 @@ class Text2Speech(object): # Skip voiceover geneartion if a track with the same name is used. # This might happen with "Track001" or "01. Intro" names for example. if os.path.isfile(out_wav_path): - print "Using eExisting", out_wav_path + print "Using existing", out_wav_path return True # ensure we deal with unicode later From b38534e6a419c061741c162ff5d2dc550457c149 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 13:07:50 +0100 Subject: [PATCH 12/42] Only use voiceover if available --- shuffle.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/shuffle.py b/shuffle.py index 90522ce..3b23174 100755 --- a/shuffle.py +++ b/shuffle.py @@ -60,12 +60,25 @@ class Text2Speech(object): @staticmethod def check_support(): + voiceoverAvailable = False + + # Check for pico2wave voiceover if not exec_exists_in_path("pico2wave"): Text2Speech.valid_tts['pico2wave'] = False - print "Error executing pico2wave, voicever won't be generated using it" + print "Error executing pico2wave, voicever won't be generated using it." + else: + voiceoverAvailable = True + + # Check for Russian RHVoice voiceover if not exec_exists_in_path("RHVoice"): Text2Speech.valid_tts['RHVoice'] = False - print "Error executing RHVoice, voicever won't be generated using it" + print "Warning: Error executing RHVoice, Russian voicever won't be generated." + else: + voiceoverAvailable = True + + # Return if we at least found one voiceover program. + # Otherwise this will result in silent voiceover for tracks and "Playlist N" for playlists. + return voiceoverAvailable @staticmethod def text2speech(out_wav_path, text): @@ -382,7 +395,7 @@ class Playlist(Record): def set_master(self, tracks): # By default use "All Songs" builtin voiceover (dbid all zero) # Else generate alternative "All Songs" to fit the speaker voice of other playlists - if self.voiceover: + if self.voiceover and Text2Speech.valid_tts['pico2wave']: self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 self.text_to_speech("All songs", self["dbid"], True) self["listtype"] = 1 @@ -567,8 +580,9 @@ if __name__ == '__main__': if result.rename_unicode: check_unicode(result.path) - if not result.disable_voiceover: - Text2Speech.check_support() + if not result.disable_voiceover and not Text2Speech.check_support(): + print "Error: Did not find any voiceover program. Voiceover disabled." + result.disable_voiceover = True shuffle = Shuffler(result.path, voiceover=not result.disable_voiceover, rename=result.rename_unicode, trackgain=result.track_gain) shuffle.initialize() From cdb1652eb19b0c06a014a178c5864348120e4ce0 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 13:40:39 +0100 Subject: [PATCH 13/42] Added Tips and Tricks section to readme --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 3b9d1ce..aa4e4fb 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,32 @@ ACCEPT_KEYWORDS="~amd64" emerge -av app-accessibility/svox app-accessibility/rhv ``` References to the overlays above: [ikelos](http://git.overlays.gentoo.org/gitweb/?p=dev/ikelos.git;a=summary), [ahippo-rhvoice-overlay](https://github.com/ahippo/rhvoice-gentoo-overlay) +##Tips and Tricks + +##### Disable trash for IPod +To avoid that linux moves deleted files into trash you can create an empty file `.Trash-1000`. +This forces linux to delete the files permanently instead of moving them to the trash. +Of course you can also use `shift + delete` to permanently delete files without this trick. + +##### Use Rythmbox 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 +but still make use of the additional features this script provides (such as voiceover). + +Simply place a file called `.is_audio_player` into the root directory of your IPod and add the following content: +``` +name="Name's IPOD" +audio_folders=iPod_Control/Music/ +``` + +Now disable the IPod plugin of Rhythmbox and enable the MTP plugin instead. +You can use Rythmbox now to generate playlists and sync them to your IPod. +The script will recognize the .pls playlists and generate a proper iTunesSD file. + +##### Carry the script with your IPod +If you want to use this script on different computers it makes sense +to simply copy the script into the IPod's root directory. + ##TODO * Last.fm Scrobbler * Qt frontend From f313664a97c09c43d5c873580c30deaaed3bf110 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 14:31:43 +0100 Subject: [PATCH 14/42] Added additional Readme information --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa4e4fb..2cb259b 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,12 @@ References to the overlays above: [ikelos](http://git.overlays.gentoo.org/gitweb ##Tips and Tricks -##### Disable trash for IPod +#### Disable trash for IPod To avoid that linux moves deleted files into trash you can create an empty file `.Trash-1000`. This forces linux to delete the files permanently instead of moving them to the trash. Of course you can also use `shift + delete` to permanently delete files without this trick. -##### Use Rythmbox to manage your music and playlists +#### 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 but still make use of the additional features this script provides (such as voiceover). @@ -81,17 +81,26 @@ Now disable the IPod plugin of Rhythmbox and enable the MTP plugin instead. You can use Rythmbox now to generate playlists and sync them to your IPod. The script will recognize the .pls playlists and generate a proper iTunesSD file. -##### Carry the script with your IPod +##### Known Rhythmbox syncing issues +* Creating playlists with names like `K.I.Z.` will fail, because the FAT Filesystem does not support a dot `.` at the end of a directory/file. +* Sometimes bad ID3 tags can also cause corrupted playlists. + +In all cases you can try to update Rythmbox to the latest version, sync again or fix the wrong filenames yourself. + +#### Carry the script with your IPod If you want to use this script on different computers it makes sense to simply copy the script into the IPod's root directory. -##TODO +## TODO * Last.fm Scrobbler * Qt frontend -##EXTRA READING +## EXTRA READING * [shuffle3db specification](docs/iTunesSD3gen.md) * [Using shuffle.py and Rhythmbox for easy syncing of playlists and songs](http://nims11.wordpress.com/2013/10/12/ipod-shuffle-4g-under-linux/) +* [gtkpod](http://www.gtkpod.org/wiki/Home) +* [German Ubuntu IPod tutorial](https://wiki.ubuntuusers.de/iPod/) +* [IPod management apps](https://wiki.archlinux.org/index.php/IPod#iPod_management_apps) The original shuffle3db website went offline. This repository contains a copy of the information inside the `docs` folder. Original data can be found via [wayback machine](https://web.archive.org/web/20131016014401/http://shuffle3db.wikispaces.com/iTunesSD3gen). From 92d121330c1ed29cbe959343d0e793df9480fae2 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 14:36:58 +0100 Subject: [PATCH 15/42] Better handle broken playlist track path --- shuffle.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/shuffle.py b/shuffle.py index 3b23174..5b41d5b 100755 --- a/shuffle.py +++ b/shuffle.py @@ -364,6 +364,8 @@ class PlaylistHeader(Record): if playlist["number_of_songs"] > 0: playlistcount += 1 chunks += [construction] + else: + print "Error: Playlist does not contain a single track. Skipping playlist." self["number_of_playlists"] = playlistcount self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) @@ -461,11 +463,15 @@ class Playlist(Record): chunks = "" for i in self.listtracks: + path = self.ipod_to_path(i) + position = -1 try: - position = tracks.index(self.ipod_to_path(i)) + position = tracks.index(path) except: - print tracks - raise + # Print an error if no track was found. + # Empty playlists are handeled in the PlaylistHeader class. + print "Error: Could not find track \"" + path + "\"." + print "Maybe its an invalid FAT filesystem name. Please fix your playlist. Skipping track." if position > -1: chunks += struct.pack("I", position) self["number_of_songs"] += 1 From b471e1c7297ddf514d7786d7aecaa472a1cbf519 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 15:20:38 +0100 Subject: [PATCH 16/42] Added changelog --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2cb259b..b96f158 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,6 @@ optional arguments: is very loud even on minimal player volume ``` -#### Additions to the original -* Option to disable voiceover -* Initialize the IPod Directory tree -* Using the --rename-unicode flag, filenames with strange characters and different language are renamed which avoids the script to crash with a Unicode Error - #### Dependencies This script requires: @@ -104,3 +99,58 @@ to simply copy the script into the IPod's root directory. The original shuffle3db website went offline. This repository contains a copy of the information inside the `docs` folder. Original data can be found via [wayback machine](https://web.archive.org/web/20131016014401/http://shuffle3db.wikispaces.com/iTunesSD3gen). + + +# Version History + +``` +1.2 Release (04.02.2016) +* Additional fixes from NicoHood +* Fixed "All Songs" and "Playlist N" sounds when voiceover is disabled #17 +* Better handle broken playlist paths #16 +* Skip existing voiceover files with the same name (e.g. "Track 1.mp3") +* Only use voiceover if dependencies are installed +* Added Path help entry +* Made help message lower case +* Improved Readme +* Improved docs +* Added MIT License +* Added this changelog + +1.1 Release (11.10.2013 - 23.01.2016) +* Fixes from nimms11 fork +* Option to disable voiceover +* Initialize the IPod Directory tree +* Using the --rename-unicode flag + filenames with strange characters and different language are renamed + which avoids the script to crash with a Unicode Error +* Other small fixes + +1.0 Release (15.08.2012 - 17.10.2012) +* Original release by ikelos +``` + +# License and Copyright + +``` +Copyright (c) 2012-2016 ikelos, nims11, NicoHood +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` From c16855cef6ce32563f7e72966fab53e0d29489ad Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:15:27 +0100 Subject: [PATCH 17/42] Corrected minor comment --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 7c07f9c..88ecf98 100755 --- a/shuffle.py +++ b/shuffle.py @@ -203,7 +203,7 @@ class TunesSD(Record): ]) def construct(self): - # The header is a fixed length, so no need to precalculate it + # The header is a fixed length, so no need to calculate it self.track_header.base_offset = 64 track_header = self.track_header.construct() From 2dbdc0f09b6179c6f7ecf5a6c5f809759e624802 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:15:43 +0100 Subject: [PATCH 18/42] Check if path exists --- shuffle.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shuffle.py b/shuffle.py index 88ecf98..e1fa0e9 100755 --- a/shuffle.py +++ b/shuffle.py @@ -555,6 +555,10 @@ if __name__ == '__main__': parser.add_argument('path') result = parser.parse_args() + if not os.path.isdir(result.path): + print "Error finding IPod directory. Maybe it is not connected or mounted?" + sys.exit() + if result.rename_unicode: check_unicode(result.path) From 4b211ec8dc0f96648a4ed70930fd91db651e2cec Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:26:07 +0100 Subject: [PATCH 19/42] Added 'path' help entry --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index e1fa0e9..7aac187 100755 --- a/shuffle.py +++ b/shuffle.py @@ -552,7 +552,7 @@ if __name__ == '__main__': parser.add_argument('--disable-voiceover', action='store_true', help='Disable Voiceover Feature') parser.add_argument('--rename-unicode', action='store_true', help='Rename Files Causing Unicode Errors, will do minimal required renaming') parser.add_argument('--track-gain', type=nonnegative_int, default=0, help='Specify volume gain (0-99) for all tracks; 0 (default) means no gain and is usually fine; e.g. 60 is very loud even on minimal player volume') - parser.add_argument('path') + parser.add_argument('path', help='Path to the IPod\'s root directory') result = parser.parse_args() if not os.path.isdir(result.path): From e143eb53e6185b796fd30619a3f8909f4918711c Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 12:33:21 +0100 Subject: [PATCH 20/42] Made help message lower case --- shuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle.py b/shuffle.py index 7aac187..8193cbe 100755 --- a/shuffle.py +++ b/shuffle.py @@ -549,8 +549,8 @@ def nonnegative_int(string): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--disable-voiceover', action='store_true', help='Disable Voiceover Feature') - parser.add_argument('--rename-unicode', action='store_true', help='Rename Files Causing Unicode Errors, will do minimal required renaming') + parser.add_argument('--disable-voiceover', action='store_true', help='Disable voiceover feature') + parser.add_argument('--rename-unicode', action='store_true', help='Rename files causing unicode errors, will do minimal required renaming') parser.add_argument('--track-gain', type=nonnegative_int, default=0, help='Specify volume gain (0-99) for all tracks; 0 (default) means no gain and is usually fine; e.g. 60 is very loud even on minimal player volume') parser.add_argument('path', help='Path to the IPod\'s root directory') result = parser.parse_args() From ed7af6a2216e2d190f2dedc7a8a23d1f53e13e7b Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 14:04:46 +0100 Subject: [PATCH 21/42] Added default "All Songs" male voice if voiceover is disabled --- shuffle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shuffle.py b/shuffle.py index 8193cbe..7480934 100755 --- a/shuffle.py +++ b/shuffle.py @@ -378,9 +378,12 @@ class Playlist(Record): ]) def set_master(self, tracks): - self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 + # By default use "All Songs" builtin voiceover (dbid all zero) + # Else generate alternative "All Songs" to fit the speaker voice of other playlists + if self.voiceover: + self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 + self.text_to_speech("All songs", self["dbid"], True) self["listtype"] = 1 - self.text_to_speech("All songs", self["dbid"], True) self.listtracks = tracks def populate_m3u(self, data): From 6fb1789e7c691cd2223b9b1060279184f8f17c3a Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 20:44:30 +0100 Subject: [PATCH 22/42] Additional voiceover doc info --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 913189c..9e1900b 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -115,7 +115,7 @@ Here's the general layout of an iTunesSD file:
1
-
+ Only applies for tracks, not for playlists.
1
From bf2c405bba7222dbbf7a9f1134177aa74ffb91f2 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 17 Jan 2016 20:45:58 +0100 Subject: [PATCH 23/42] Skip existing voiceover files, add proper return value --- shuffle.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/shuffle.py b/shuffle.py index 7480934..8bdd58d 100755 --- a/shuffle.py +++ b/shuffle.py @@ -69,14 +69,20 @@ class Text2Speech(object): @staticmethod def text2speech(out_wav_path, text): + # Skip voiceover geneartion if a track with the same name is used. + # This might happen with "Track001" or "01. Intro" names for example. + if os.path.isfile(out_wav_path): + print "Using eExisting", out_wav_path + return + # ensure we deal with unicode later if not isinstance(text, unicode): text = unicode(text, 'utf-8') lang = Text2Speech.guess_lang(text) if lang == "ru-RU": - Text2Speech.rhvoice(out_wav_path, text) + return Text2Speech.rhvoice(out_wav_path, text) else: - Text2Speech.pico2wave(out_wav_path, text) + return Text2Speech.pico2wave(out_wav_path, text) # guess-language seems like an overkill for now @staticmethod @@ -91,6 +97,7 @@ class Text2Speech(object): if not Text2Speech.valid_tts['pico2wave']: return False subprocess.call(["pico2wave", "-l", "en-GB", "-w", out_wav_path, unicodetext]) + return True @staticmethod def rhvoice(out_wav_path, unicodetext): @@ -106,6 +113,7 @@ class Text2Speech(object): subprocess.call(["sox", tmp_file.name, out_wav_path, "norm"]) os.remove(tmp_file.name) + return True class Record(object): @@ -140,7 +148,8 @@ class Record(object): # Create the voiceover wav file fn = "".join(["{0:02X}".format(ord(x)) for x in reversed(dbid)]) path = os.path.join(self.base, "iPod_Control", "Speakable", "Tracks" if not playlist else "Playlists", fn + ".wav") - Text2Speech.text2speech(path, text) + return Text2Speech.text2speech(path, text) + return False def path_to_ipod(self, filename): if os.path.commonprefix([os.path.abspath(filename), self.base]) != self.base: From 81b8293a182ea8596a725f8dcc3a0bcf91632afa Mon Sep 17 00:00:00 2001 From: NicoHood Date: Mon, 18 Jan 2016 18:33:13 +0100 Subject: [PATCH 24/42] Skip already generated voiceover files --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 8bdd58d..78c9719 100755 --- a/shuffle.py +++ b/shuffle.py @@ -73,7 +73,7 @@ class Text2Speech(object): # This might happen with "Track001" or "01. Intro" names for example. if os.path.isfile(out_wav_path): print "Using eExisting", out_wav_path - return + return True # ensure we deal with unicode later if not isinstance(text, unicode): From f7eafd1c4c8ace3d228a6a3b71e026fd2503c124 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 12:14:14 +0100 Subject: [PATCH 25/42] Use default speaker when voiceover is disabled #17 --- shuffle.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/shuffle.py b/shuffle.py index 78c9719..82fb8e7 100755 --- a/shuffle.py +++ b/shuffle.py @@ -197,7 +197,7 @@ class TunesSD(Record): self.play_header = PlaylistHeader(self) self._struct = collections.OrderedDict([ ("header_id", ("4s", "shdb")), - ("unknown1", ("I", 0x02010001)), + ("unknown1", ("I", 0x02000003)), ("total_length", ("I", 64)), ("total_number_of_tracks", ("I", 0)), ("total_number_of_playlists", ("I", 0)), @@ -328,16 +328,10 @@ class PlaylistHeader(Record): ("header_id", ("4s", "shph")), ("total_length", ("I", 0)), ("number_of_playlists", ("I", 0)), - ("number_of_podcast_lists", ("I", 0xffffffff)), - ("number_of_master_lists", ("I", 0)), - ("number_of_audiobook_lists", ("I", 0xffffffff)), - ("unknown1", ("I", 0)), - ("unknown2", ("I", 0xffffffff)), - ("unknown3", ("I", 0)), - ("unknown4", ("I", 0xffffffff)), - ("unknown5", ("I", 0)), - ("unknown6", ("I", 0xffffffff)), - ("unknown7", ("20s", "\x00" * 20)), + ("number_of_non_podcast_lists", ("2s", "\xFF\xFF")), + ("number_of_master_lists", ("2s", "\x01\x00")), + ("number_of_non_audiobook_lists", ("2s", "\xFF\xFF")), + ("unknown2", ("2s", "\x00" * 2)), ]) def construct(self, tracks): #pylint: disable-msg=W0221 @@ -359,8 +353,7 @@ class PlaylistHeader(Record): chunks += [construction] self["number_of_playlists"] = playlistcount - self["number_of_master_lists"] = 0 - self["total_length"] = 0x44 + (self["number_of_playlists"] * 4) + self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) # Start the header output = Record.construct(self) From d92019c9081e927dec946cde08a77a092e8355f9 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 12:18:56 +0100 Subject: [PATCH 26/42] Typo --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 82fb8e7..8e70907 100755 --- a/shuffle.py +++ b/shuffle.py @@ -72,7 +72,7 @@ class Text2Speech(object): # Skip voiceover geneartion if a track with the same name is used. # This might happen with "Track001" or "01. Intro" names for example. if os.path.isfile(out_wav_path): - print "Using eExisting", out_wav_path + print "Using existing", out_wav_path return True # ensure we deal with unicode later From 0e5d20c3887745cf74f1054051d7042d05733e40 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 13:07:50 +0100 Subject: [PATCH 27/42] Only use voiceover if available --- shuffle.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/shuffle.py b/shuffle.py index 8e70907..e218a1b 100755 --- a/shuffle.py +++ b/shuffle.py @@ -60,12 +60,25 @@ class Text2Speech(object): @staticmethod def check_support(): + voiceoverAvailable = False + + # Check for pico2wave voiceover if not exec_exists_in_path("pico2wave"): Text2Speech.valid_tts['pico2wave'] = False - print "Error executing pico2wave, voicever won't be generated using it" + print "Error executing pico2wave, voicever won't be generated using it." + else: + voiceoverAvailable = True + + # Check for Russian RHVoice voiceover if not exec_exists_in_path("RHVoice"): Text2Speech.valid_tts['RHVoice'] = False - print "Error executing RHVoice, voicever won't be generated using it" + print "Warning: Error executing RHVoice, Russian voicever won't be generated." + else: + voiceoverAvailable = True + + # Return if we at least found one voiceover program. + # Otherwise this will result in silent voiceover for tracks and "Playlist N" for playlists. + return voiceoverAvailable @staticmethod def text2speech(out_wav_path, text): @@ -382,7 +395,7 @@ class Playlist(Record): def set_master(self, tracks): # By default use "All Songs" builtin voiceover (dbid all zero) # Else generate alternative "All Songs" to fit the speaker voice of other playlists - if self.voiceover: + if self.voiceover and Text2Speech.valid_tts['pico2wave']: self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101 self.text_to_speech("All songs", self["dbid"], True) self["listtype"] = 1 @@ -567,8 +580,9 @@ if __name__ == '__main__': if result.rename_unicode: check_unicode(result.path) - if not result.disable_voiceover: - Text2Speech.check_support() + if not result.disable_voiceover and not Text2Speech.check_support(): + print "Error: Did not find any voiceover program. Voiceover disabled." + result.disable_voiceover = True shuffle = Shuffler(result.path, voiceover=not result.disable_voiceover, rename=result.rename_unicode, trackgain=result.track_gain) shuffle.initialize() From b3d8e939e24384f2f25af3d411eb15fa57b07bd8 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Sun, 24 Jan 2016 13:40:39 +0100 Subject: [PATCH 28/42] Added Tips and Tricks section to readme --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 3b9d1ce..aa4e4fb 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,32 @@ ACCEPT_KEYWORDS="~amd64" emerge -av app-accessibility/svox app-accessibility/rhv ``` References to the overlays above: [ikelos](http://git.overlays.gentoo.org/gitweb/?p=dev/ikelos.git;a=summary), [ahippo-rhvoice-overlay](https://github.com/ahippo/rhvoice-gentoo-overlay) +##Tips and Tricks + +##### Disable trash for IPod +To avoid that linux moves deleted files into trash you can create an empty file `.Trash-1000`. +This forces linux to delete the files permanently instead of moving them to the trash. +Of course you can also use `shift + delete` to permanently delete files without this trick. + +##### Use Rythmbox 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 +but still make use of the additional features this script provides (such as voiceover). + +Simply place a file called `.is_audio_player` into the root directory of your IPod and add the following content: +``` +name="Name's IPOD" +audio_folders=iPod_Control/Music/ +``` + +Now disable the IPod plugin of Rhythmbox and enable the MTP plugin instead. +You can use Rythmbox now to generate playlists and sync them to your IPod. +The script will recognize the .pls playlists and generate a proper iTunesSD file. + +##### Carry the script with your IPod +If you want to use this script on different computers it makes sense +to simply copy the script into the IPod's root directory. + ##TODO * Last.fm Scrobbler * Qt frontend From c6f22eb3ad217ad26206ca39e042070e28c14da0 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 14:31:43 +0100 Subject: [PATCH 29/42] Added additional Readme information --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa4e4fb..2cb259b 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,12 @@ References to the overlays above: [ikelos](http://git.overlays.gentoo.org/gitweb ##Tips and Tricks -##### Disable trash for IPod +#### Disable trash for IPod To avoid that linux moves deleted files into trash you can create an empty file `.Trash-1000`. This forces linux to delete the files permanently instead of moving them to the trash. Of course you can also use `shift + delete` to permanently delete files without this trick. -##### Use Rythmbox to manage your music and playlists +#### 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 but still make use of the additional features this script provides (such as voiceover). @@ -81,17 +81,26 @@ Now disable the IPod plugin of Rhythmbox and enable the MTP plugin instead. You can use Rythmbox now to generate playlists and sync them to your IPod. The script will recognize the .pls playlists and generate a proper iTunesSD file. -##### Carry the script with your IPod +##### Known Rhythmbox syncing issues +* Creating playlists with names like `K.I.Z.` will fail, because the FAT Filesystem does not support a dot `.` at the end of a directory/file. +* Sometimes bad ID3 tags can also cause corrupted playlists. + +In all cases you can try to update Rythmbox to the latest version, sync again or fix the wrong filenames yourself. + +#### Carry the script with your IPod If you want to use this script on different computers it makes sense to simply copy the script into the IPod's root directory. -##TODO +## TODO * Last.fm Scrobbler * Qt frontend -##EXTRA READING +## EXTRA READING * [shuffle3db specification](docs/iTunesSD3gen.md) * [Using shuffle.py and Rhythmbox for easy syncing of playlists and songs](http://nims11.wordpress.com/2013/10/12/ipod-shuffle-4g-under-linux/) +* [gtkpod](http://www.gtkpod.org/wiki/Home) +* [German Ubuntu IPod tutorial](https://wiki.ubuntuusers.de/iPod/) +* [IPod management apps](https://wiki.archlinux.org/index.php/IPod#iPod_management_apps) The original shuffle3db website went offline. This repository contains a copy of the information inside the `docs` folder. Original data can be found via [wayback machine](https://web.archive.org/web/20131016014401/http://shuffle3db.wikispaces.com/iTunesSD3gen). From 676ce190475e99c209f03d1dcf49c89e231a2106 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 14:36:58 +0100 Subject: [PATCH 30/42] Better handle broken playlist track path --- shuffle.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/shuffle.py b/shuffle.py index e218a1b..c2ad1a3 100755 --- a/shuffle.py +++ b/shuffle.py @@ -364,6 +364,8 @@ class PlaylistHeader(Record): if playlist["number_of_songs"] > 0: playlistcount += 1 chunks += [construction] + else: + print "Error: Playlist does not contain a single track. Skipping playlist." self["number_of_playlists"] = playlistcount self["total_length"] = 0x14 + (self["number_of_playlists"] * 4) @@ -461,11 +463,15 @@ class Playlist(Record): chunks = "" for i in self.listtracks: + path = self.ipod_to_path(i) + position = -1 try: - position = tracks.index(self.ipod_to_path(i)) + position = tracks.index(path) except: - print tracks - raise + # Print an error if no track was found. + # Empty playlists are handeled in the PlaylistHeader class. + print "Error: Could not find track \"" + path + "\"." + print "Maybe its an invalid FAT filesystem name. Please fix your playlist. Skipping track." if position > -1: chunks += struct.pack("I", position) self["number_of_songs"] += 1 From ec891811ba4b945915acd472474fe81bffa1160c Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sun, 17 Jan 2016 16:49:18 +0530 Subject: [PATCH 31/42] Original Source added --- docs/iTunesSD3gen.md | 1 + docs/iTunesStats3gen.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 9e1900b..61b2b47 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -931,4 +931,5 @@ It seems to be ignored when shuffling within a playlist!
A dbid of all zeros yields a voiceover of All songs. Also playlist dbids without a corresponding voiceover file will yield a voiceover of playlist n or audiobook n where n is the playlist number. The shuffle assumes the podcast playlist is last.

The iTunesStats file is also different in the 3gen iPod. +

Original Source: http://shuffle3db.wikispaces.com/iTunesSD3gen (expired)

diff --git a/docs/iTunesStats3gen.md b/docs/iTunesStats3gen.md index b8a60b9..2bc62b6 100644 --- a/docs/iTunesStats3gen.md +++ b/docs/iTunesStats3gen.md @@ -109,3 +109,4 @@ Here's the general layout of an iTunesSD file:

00 00 00 00 +

Original Source: http://shuffle3db.wikispaces.com/iTunesStats3gen (expired)

From 6b60656db6a2060bac3633eeb7e85c9086779fb6 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sun, 17 Jan 2016 16:54:22 +0530 Subject: [PATCH 32/42] missing end tags --- docs/iTunesStats3gen.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/iTunesStats3gen.md b/docs/iTunesStats3gen.md index 2bc62b6..bc6816e 100644 --- a/docs/iTunesStats3gen.md +++ b/docs/iTunesStats3gen.md @@ -108,5 +108,8 @@ Here's the general layout of an iTunesSD file:

- 00 00 00 00 + 00 00 00 00
+ + +

Original Source: http://shuffle3db.wikispaces.com/iTunesStats3gen (expired)

From 002be77edb64cd8d584509c2f393fb4f98c4a588 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sun, 17 Jan 2016 17:14:46 +0530 Subject: [PATCH 33/42] directory permission check, non zero return status --- shuffle.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/shuffle.py b/shuffle.py index c2ad1a3..561ff30 100755 --- a/shuffle.py +++ b/shuffle.py @@ -571,6 +571,15 @@ def nonnegative_int(string): raise argparse.ArgumentTypeError("Track gain value should be in range 0-99") return intval +def checkPathValidity(path): + if not os.path.isdir(result.path): + print "Error finding IPod directory. Maybe it is not connected or mounted?" + sys.exit(1) + + if not os.access(result.path, os.W_OK): + print 'Unable to get write permissions in the IPod directory' + sys.exit(1) + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--disable-voiceover', action='store_true', help='Disable voiceover feature') @@ -579,9 +588,7 @@ if __name__ == '__main__': parser.add_argument('path', help='Path to the IPod\'s root directory') result = parser.parse_args() - if not os.path.isdir(result.path): - print "Error finding IPod directory. Maybe it is not connected or mounted?" - sys.exit() + checkPathValidity(result.path) if result.rename_unicode: check_unicode(result.path) From bcde228a625c3bd793551e1d4b232843d370c8b4 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sat, 23 Jan 2016 22:25:59 +0530 Subject: [PATCH 34/42] handle keyboard interrupt --- shuffle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shuffle.py b/shuffle.py index 561ff30..65ffcf6 100755 --- a/shuffle.py +++ b/shuffle.py @@ -14,6 +14,7 @@ import argparse import shutil import re import tempfile +import signal audio_ext = (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav") list_ext = (".pls", ".m3u") @@ -580,7 +581,12 @@ def checkPathValidity(path): print 'Unable to get write permissions in the IPod directory' sys.exit(1) +def handle_interrupt(signal, frame): + print "Interrupt detected, exiting..." + sys.exit(1) + if __name__ == '__main__': + signal.signal(signal.SIGINT, handle_interrupt) parser = argparse.ArgumentParser() parser.add_argument('--disable-voiceover', action='store_true', help='Disable voiceover feature') parser.add_argument('--rename-unicode', action='store_true', help='Rename files causing unicode errors, will do minimal required renaming') From 15b305739aa8431280fca43b84a5811b7e44dd51 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sat, 23 Jan 2016 23:02:19 +0530 Subject: [PATCH 35/42] Safe file handling, closes #18 --- shuffle.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/shuffle.py b/shuffle.py index 65ffcf6..90ef3bc 100755 --- a/shuffle.py +++ b/shuffle.py @@ -440,9 +440,8 @@ class Playlist(Record): return fullPath def populate(self, filename): - f = open(filename, "rb") - data = f.readlines() - f.close() + with open(filename, 'rb') as f: + data = f.readlines() extension = os.path.splitext(filename)[1].lower() if extension == '.pls': @@ -529,9 +528,8 @@ class Shuffler(object): self.lists.append(os.path.abspath(os.path.join(dirpath, filename))) def write_database(self): - f = open(os.path.join(self.base, "iPod_Control", "iTunes", "iTunesSD"), "wb") - f.write(self.tunessd.construct()) - f.close() + with open(os.path.join(self.base, "iPod_Control", "iTunes", "iTunesSD"), "wb") as f: + f.write(self.tunessd.construct()) # # Read all files from the directory From 0daf101f324862ddb8df41adff2da6e01a2f439d Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 09:53:55 +0100 Subject: [PATCH 36/42] Update iTunesSD3gen.md The hex value was written in big endian, but we have little endian. Small mistake. I confirmed this with the rythmbox output. BTW: We are currently using 0x02010001 for any reason. --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 61b2b47..3e18da8 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -43,7 +43,7 @@ Here's the general layout of an iTunesSD file:
?
- 0x03000002
+ 0x02000003
03 00 00 02
From 4b6e3999de4c7679f5deee1fb727a17794c22477 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 10:02:30 +0100 Subject: [PATCH 37/42] Added stop_at_pos_ms rythmbox note --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 3e18da8..a8ee8fe 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -364,7 +364,7 @@ Here's the general layout of an iTunesSD file:
4
-
+ Rythmbox IPod plugin sets this value always 0.
112169
From a1ce8de49a2a9bb67a32529f4e58b22ca560c4f3 Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 24 Jan 2016 12:10:25 +0100 Subject: [PATCH 38/42] Update iTunesSD3gen.md --- docs/iTunesSD3gen.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index a8ee8fe..920bcc6 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -41,9 +41,10 @@ Here's the general layout of an iTunesSD file:
4
- ?
+ Version number?
- 0x02000003
+ 0x03000002
+ Old values:
0x02010001
Gen 2:
0x010600
0x010800

03 00 00 02
From 8415d11b7edf0033958f91b9f95a4278ea26e943 Mon Sep 17 00:00:00 2001 From: NicoHood Date: Thu, 4 Feb 2016 15:20:38 +0100 Subject: [PATCH 39/42] Added changelog --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2cb259b..b96f158 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,6 @@ optional arguments: is very loud even on minimal player volume ``` -#### Additions to the original -* Option to disable voiceover -* Initialize the IPod Directory tree -* Using the --rename-unicode flag, filenames with strange characters and different language are renamed which avoids the script to crash with a Unicode Error - #### Dependencies This script requires: @@ -104,3 +99,58 @@ to simply copy the script into the IPod's root directory. The original shuffle3db website went offline. This repository contains a copy of the information inside the `docs` folder. Original data can be found via [wayback machine](https://web.archive.org/web/20131016014401/http://shuffle3db.wikispaces.com/iTunesSD3gen). + + +# Version History + +``` +1.2 Release (04.02.2016) +* Additional fixes from NicoHood +* Fixed "All Songs" and "Playlist N" sounds when voiceover is disabled #17 +* Better handle broken playlist paths #16 +* Skip existing voiceover files with the same name (e.g. "Track 1.mp3") +* Only use voiceover if dependencies are installed +* Added Path help entry +* Made help message lower case +* Improved Readme +* Improved docs +* Added MIT License +* Added this changelog + +1.1 Release (11.10.2013 - 23.01.2016) +* Fixes from nimms11 fork +* Option to disable voiceover +* Initialize the IPod Directory tree +* Using the --rename-unicode flag + filenames with strange characters and different language are renamed + which avoids the script to crash with a Unicode Error +* Other small fixes + +1.0 Release (15.08.2012 - 17.10.2012) +* Original release by ikelos +``` + +# License and Copyright + +``` +Copyright (c) 2012-2016 ikelos, nims11, NicoHood +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` From 2712451e253d5a6afea140848783ddf039d60d73 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Sun, 7 Feb 2016 22:33:49 +0530 Subject: [PATCH 40/42] Fixed minor type --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b96f158..8e1d133 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Original data can be found via [wayback machine](https://web.archive.org/web/201 * Added this changelog 1.1 Release (11.10.2013 - 23.01.2016) -* Fixes from nimms11 fork +* Fixes from nims11 fork * Option to disable voiceover * Initialize the IPod Directory tree * Using the --rename-unicode flag From 62c8fcd191e27e7df2011a35296d9bcff689e136 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Mon, 8 Feb 2016 00:40:34 +0530 Subject: [PATCH 41/42] typo in comment --- shuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle.py b/shuffle.py index 435c181..d1cc202 100755 --- a/shuffle.py +++ b/shuffle.py @@ -83,7 +83,7 @@ class Text2Speech(object): @staticmethod def text2speech(out_wav_path, text): - # Skip voiceover geneartion if a track with the same name is used. + # Skip voiceover generation if a track with the same name is used. # This might happen with "Track001" or "01. Intro" names for example. if os.path.isfile(out_wav_path): print "Using existing", out_wav_path From cb5b72d8a49a9cc8378e1527696c54658927f971 Mon Sep 17 00:00:00 2001 From: Nimesh Ghelani Date: Mon, 8 Feb 2016 00:56:00 +0530 Subject: [PATCH 42/42] corrected data written in little endian --- docs/iTunesSD3gen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/iTunesSD3gen.md b/docs/iTunesSD3gen.md index 920bcc6..b959fc8 100644 --- a/docs/iTunesSD3gen.md +++ b/docs/iTunesSD3gen.md @@ -43,7 +43,7 @@ Here's the general layout of an iTunesSD file:
Version number?
- 0x03000002
+ 0x02000003
Old values:
0x02010001
Gen 2:
0x010600
0x010800

03 00 00 02