mirror of
https://github.com/nims11/IPod-Shuffle-4g.git
synced 2025-12-08 00:18:01 +09:00
Merge cab4d83fea into e02ef0fff6
This commit is contained in:
commit
77c749c604
2 changed files with 27 additions and 22 deletions
|
|
@ -115,7 +115,7 @@ Here's the general layout of an iTunesSD file:<br>
|
||||||
</td>
|
</td>
|
||||||
<td>1<br>
|
<td>1<br>
|
||||||
</td>
|
</td>
|
||||||
<td><br>
|
<td>Only applies for tracks, not for playlists.<br>
|
||||||
</td>
|
</td>
|
||||||
<td><span style="font-family: 'Courier New',Courier,monospace;">1</span><br>
|
<td><span style="font-family: 'Courier New',Courier,monospace;">1</span><br>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
47
shuffle.py
47
shuffle.py
|
|
@ -70,14 +70,20 @@ class Text2Speech(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def text2speech(out_wav_path, text):
|
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 existing", out_wav_path
|
||||||
|
return True
|
||||||
|
|
||||||
# ensure we deal with unicode later
|
# ensure we deal with unicode later
|
||||||
if not isinstance(text, unicode):
|
if not isinstance(text, unicode):
|
||||||
text = unicode(text, 'utf-8')
|
text = unicode(text, 'utf-8')
|
||||||
lang = Text2Speech.guess_lang(text)
|
lang = Text2Speech.guess_lang(text)
|
||||||
if lang == "ru-RU":
|
if lang == "ru-RU":
|
||||||
Text2Speech.rhvoice(out_wav_path, text)
|
return Text2Speech.rhvoice(out_wav_path, text)
|
||||||
else:
|
else:
|
||||||
Text2Speech.pico2wave(out_wav_path, text)
|
return Text2Speech.pico2wave(out_wav_path, text)
|
||||||
|
|
||||||
# guess-language seems like an overkill for now
|
# guess-language seems like an overkill for now
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -92,6 +98,7 @@ class Text2Speech(object):
|
||||||
if not Text2Speech.valid_tts['pico2wave']:
|
if not Text2Speech.valid_tts['pico2wave']:
|
||||||
return False
|
return False
|
||||||
subprocess.call(["pico2wave", "-l", "en-GB", "-w", out_wav_path, unicodetext])
|
subprocess.call(["pico2wave", "-l", "en-GB", "-w", out_wav_path, unicodetext])
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rhvoice(out_wav_path, unicodetext):
|
def rhvoice(out_wav_path, unicodetext):
|
||||||
|
|
@ -107,6 +114,7 @@ class Text2Speech(object):
|
||||||
subprocess.call(["sox", tmp_file.name, out_wav_path, "norm"])
|
subprocess.call(["sox", tmp_file.name, out_wav_path, "norm"])
|
||||||
|
|
||||||
os.remove(tmp_file.name)
|
os.remove(tmp_file.name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Record(object):
|
class Record(object):
|
||||||
|
|
@ -141,7 +149,8 @@ class Record(object):
|
||||||
# Create the voiceover wav file
|
# Create the voiceover wav file
|
||||||
fn = "".join(["{0:02X}".format(ord(x)) for x in reversed(dbid)])
|
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")
|
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):
|
def path_to_ipod(self, filename):
|
||||||
if os.path.commonprefix([os.path.abspath(filename), self.base]) != self.base:
|
if os.path.commonprefix([os.path.abspath(filename), self.base]) != self.base:
|
||||||
|
|
@ -189,7 +198,7 @@ class TunesSD(Record):
|
||||||
self.play_header = PlaylistHeader(self)
|
self.play_header = PlaylistHeader(self)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", "shdb")),
|
("header_id", ("4s", "shdb")),
|
||||||
("unknown1", ("I", 0x02010001)),
|
("unknown1", ("I", 0x02000003)),
|
||||||
("total_length", ("I", 64)),
|
("total_length", ("I", 64)),
|
||||||
("total_number_of_tracks", ("I", 0)),
|
("total_number_of_tracks", ("I", 0)),
|
||||||
("total_number_of_playlists", ("I", 0)),
|
("total_number_of_playlists", ("I", 0)),
|
||||||
|
|
@ -320,16 +329,10 @@ class PlaylistHeader(Record):
|
||||||
("header_id", ("4s", "shph")),
|
("header_id", ("4s", "shph")),
|
||||||
("total_length", ("I", 0)),
|
("total_length", ("I", 0)),
|
||||||
("number_of_playlists", ("I", 0)),
|
("number_of_playlists", ("I", 0)),
|
||||||
("number_of_podcast_lists", ("I", 0xffffffff)),
|
("number_of_non_podcast_lists", ("2s", "\x03\x00")), #TODO check if really ffff is okay
|
||||||
("number_of_master_lists", ("I", 0)),
|
("number_of_master_lists", ("2s", "\x01\x00")),
|
||||||
("number_of_audiobook_lists", ("I", 0xffffffff)),
|
("number_of_non_audiobook_lists", ("2s", "\x03\x00")), #TODO as above
|
||||||
("unknown1", ("I", 0)),
|
("unknown2", ("2s", "\x00" * 2)),
|
||||||
("unknown2", ("I", 0xffffffff)),
|
|
||||||
("unknown3", ("I", 0)),
|
|
||||||
("unknown4", ("I", 0xffffffff)),
|
|
||||||
("unknown5", ("I", 0)),
|
|
||||||
("unknown6", ("I", 0xffffffff)),
|
|
||||||
("unknown7", ("20s", "\x00" * 20)),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def construct(self, tracks): #pylint: disable-msg=W0221
|
def construct(self, tracks): #pylint: disable-msg=W0221
|
||||||
|
|
@ -351,8 +354,7 @@ class PlaylistHeader(Record):
|
||||||
chunks += [construction]
|
chunks += [construction]
|
||||||
|
|
||||||
self["number_of_playlists"] = playlistcount
|
self["number_of_playlists"] = playlistcount
|
||||||
self["number_of_master_lists"] = 0
|
self["total_length"] = 0x14 + (self["number_of_playlists"] * 4)
|
||||||
self["total_length"] = 0x44 + (self["number_of_playlists"] * 4)
|
|
||||||
# Start the header
|
# Start the header
|
||||||
|
|
||||||
output = Record.construct(self)
|
output = Record.construct(self)
|
||||||
|
|
@ -379,9 +381,12 @@ class Playlist(Record):
|
||||||
])
|
])
|
||||||
|
|
||||||
def set_master(self, tracks):
|
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["listtype"] = 1
|
||||||
self.text_to_speech("All songs", self["dbid"], True)
|
|
||||||
self.listtracks = tracks
|
self.listtracks = tracks
|
||||||
|
|
||||||
def populate_m3u(self, data):
|
def populate_m3u(self, data):
|
||||||
|
|
@ -562,10 +567,10 @@ 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()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--disable-voiceover', action='store_true', help='Disable Voiceover Feature')
|
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('--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('--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()
|
result = parser.parse_args()
|
||||||
|
|
||||||
checkPathValidity(result.path)
|
checkPathValidity(result.path)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue