forked from upstream/IPod-Shuffle-4g
Compare commits
No commits in common. "master" and "NicoHood-patch-1" have entirely different histories.
master
...
NicoHood-p
3 changed files with 169 additions and 259 deletions
111
CHANGELOG.md
111
CHANGELOG.md
|
|
@ -1,111 +0,0 @@
|
||||||
# Changelog
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
This changlog uses the [ISO 8601 date format](https://www.iso.org/iso-8601-date-and-time-format.html) of (YYYY-MM-DD).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
## [1.5.1] - 2021-06-05
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Moved Changelog from Readme to Changelog.md file
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Fix TypeError when reading playlist files [#50](https://github.com/nims11/IPod-Shuffle-4g/pull/50)
|
|
||||||
|
|
||||||
## [1.5.0] - 2020-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Port Script to Python3
|
|
||||||
* Mutagen support is now optional
|
|
||||||
|
|
||||||
## [1.4.0] - 2016-08-28
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Added optional `--verbose` output
|
|
||||||
* Added files to `extras` folder
|
|
||||||
* Added shortcut parameters (`-p`, `-t`, `-d`, etc.)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Renamed `--voiceover` to `--track-voiceover`
|
|
||||||
* Renamed script from `shuffle.py` to `ipod-shuffle-4g.py`
|
|
||||||
* Ignore hidden filenames
|
|
||||||
* Do not force playlist voiceover with auto playlists
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Catch "no space left" error [#30](https://github.com/nims11/IPod-Shuffle-4g/issues/30)
|
|
||||||
* Fix UnicodeEncodeError for non-ascii playlist names [#35](https://github.com/nims11/IPod-Shuffle-4g/issues/35)
|
|
||||||
|
|
||||||
## [1.3.0] - 2016-06-08
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Directory based auto playlist building (`--auto-dir-playlists`) [#13](https://github.com/nims11/IPod-Shuffle-4g/issues/13)
|
|
||||||
* ID3 tags based auto playlist building (`--auto-id3-playlists`)
|
|
||||||
* Added short program description
|
|
||||||
* Differentiate track and playlist voiceover [#26](https://github.com/nims11/IPod-Shuffle-4g/issues/26)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Voiceover disabled by default [#26](https://github.com/nims11/IPod-Shuffle-4g/issues/26) (Playlist voiceover enabled with auto playlist generation)
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Fix hyphen in filename [#4](https://github.com/nims11/IPod-Shuffle-4g/issues/4)
|
|
||||||
* Fixed mutagen bug [#5](https://github.com/nims11/IPod-Shuffle-4g/issues/5)
|
|
||||||
|
|
||||||
## [1.2.0] - 2016-02-04
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Added Path help entry
|
|
||||||
* Added MIT License
|
|
||||||
* Added this changelog
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* Skip existing voiceover files with the same name (e.g. "Track 1.mp3")
|
|
||||||
* Made help message lower case
|
|
||||||
* Improved Readme
|
|
||||||
* Improved docs
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
* Additional fixes from NicoHood
|
|
||||||
* Fixed "All Songs" and "Playlist N" sounds when voiceover is disabled [#17](https://github.com/nims11/IPod-Shuffle-4g/issues/17)
|
|
||||||
* Better handle broken playlist paths [#16](https://github.com/nims11/IPod-Shuffle-4g/issues/16)
|
|
||||||
* Only use voiceover if dependencies are installed
|
|
||||||
|
|
||||||
## [1.1.0] - 2016-01-23
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Fixes from nims11 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.0] - 2012-10-17
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
* Original release by ikelos
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/nims11/IPod-Shuffle-4g/compare/1.5.1...HEAD
|
|
||||||
[1.5.1]: https://github.com/nims11/IPod-Shuffle-4g/compare/v1.5...1.5.1
|
|
||||||
[1.5.0]: https://github.com/nims11/IPod-Shuffle-4g/compare/v1.4...v1.5
|
|
||||||
[1.4.0]: https://github.com/nims11/IPod-Shuffle-4g/compare/v1.3...v1.4
|
|
||||||
[1.3.0]: https://github.com/nims11/IPod-Shuffle-4g/compare/v1.2...v1.3
|
|
||||||
[1.2.0]: https://github.com/nims11/IPod-Shuffle-4g/compare/v1.1...v1.2
|
|
||||||
[1.1.0]: https://github.com/nims11/IPod-Shuffle-4g/compare/646b7def4c498c59b063e535a5b64695d8d87e6b...v1.1
|
|
||||||
[1.0.0]: https://github.com/nims11/IPod-Shuffle-4g/commit/646b7def4c498c59b063e535a5b64695d8d87e6b
|
|
||||||
66
README.md
66
README.md
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
Python script for building the Track and Playlist database for the newer gen IPod Shuffle.
|
Python script for building the Track and Playlist database for the newer gen IPod Shuffle.
|
||||||
Forked from the [shuffle-db-ng project](https://code.google.com/p/shuffle-db-ng/)
|
Forked from the [shuffle-db-ng project](https://code.google.com/p/shuffle-db-ng/)
|
||||||
and improved my nims11 and NicoHood.
|
|
||||||
|
|
||||||
Just put your audio files into the mass storage of your IPod and shuffle.py will do the rest.
|
Just put your audio files into the mass storage of your IPod and shuffle.py will do the rest.
|
||||||
```
|
```
|
||||||
|
|
@ -14,7 +13,7 @@ usage: ipod-shuffle-4g.py [-h] [-t] [-p] [-u] [-g TRACK_GAIN]
|
||||||
path
|
path
|
||||||
|
|
||||||
Python script for building the Track and Playlist database for the newer gen
|
Python script for building the Track and Playlist database for the newer gen
|
||||||
IPod Shuffle. Version 1.5
|
IPod Shuffle. Version 1.4
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
path Path to the IPod's root directory
|
path Path to the IPod's root directory
|
||||||
|
|
@ -51,9 +50,7 @@ optional arguments:
|
||||||
#### Dependencies
|
#### Dependencies
|
||||||
|
|
||||||
This script requires:
|
This script requires:
|
||||||
* [Python 3](https://www.python.org/download/releases/3.0/)
|
* [Python 2.7](http://www.python.org/download/releases/2.7/)
|
||||||
|
|
||||||
Optional album/artist and auto-id3-playlists support:
|
|
||||||
* [Mutagen](https://code.google.com/p/mutagen/)
|
* [Mutagen](https://code.google.com/p/mutagen/)
|
||||||
|
|
||||||
Optional Voiceover support
|
Optional Voiceover support
|
||||||
|
|
@ -61,29 +58,28 @@ Optional Voiceover support
|
||||||
* [PicoSpeaker](http://picospeaker.tk/readme.php)
|
* [PicoSpeaker](http://picospeaker.tk/readme.php)
|
||||||
* [RHVoice (master branch, 3e31edced402a08771d2c48c73213982cbe9333e)](https://github.com/Olga-Yakovleva/RHVoice) -- (Russian files only)
|
* [RHVoice (master branch, 3e31edced402a08771d2c48c73213982cbe9333e)](https://github.com/Olga-Yakovleva/RHVoice) -- (Russian files only)
|
||||||
* [SoX](http://sox.sourceforge.net) -- (Russian files)
|
* [SoX](http://sox.sourceforge.net) -- (Russian files)
|
||||||
* say (macOS)
|
|
||||||
|
|
||||||
##### Ubuntu
|
##### Ubuntu
|
||||||
|
|
||||||
`apt-get install python3 python-mutagen libttspico*`
|
`apt-get install python-mutagen libttspico*`
|
||||||
|
|
||||||
##### Arch Linux
|
##### Arch Linux
|
||||||
|
|
||||||
From the **Extra** repository: `pacman -S python` and optional `pacman -S python-mutagen espeak` or from the AUR: `svox-pico-bin` ([link](https://aur.archlinux.org/packages/svox-pico-bin/))
|
From the **Extra** repository: `pacman -S python2 mutagen` and optional `pacman -S espeak` or from the AUR: `svox-pico-bin` ([link](https://aur.archlinux.org/packages/svox-pico-bin/))
|
||||||
|
|
||||||
You can also [install the script from AUR](https://aur.archlinux.org/packages/ipod-shuffle-4g/).
|
You can also [install the script from AUR](https://aur.archlinux.org/packages/ipod-shuffle-4g/).
|
||||||
|
|
||||||
##### Gentoo Linux
|
##### Gentoo Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PYTHON_TARGETS="python3" emerge -av media-libs/mutagen
|
PYTHON_TARGETS="python2_7" emerge -av media-libs/mutagen
|
||||||
layman --add=ikelos
|
layman --add=ikelos
|
||||||
layman --overlays="https://raw.githubusercontent.com/ahippo/rhvoice-gentoo-overlay/master/repositories.xml" --fetch --add=ahippo-rhvoice-overlay
|
layman --overlays="https://raw.githubusercontent.com/ahippo/rhvoice-gentoo-overlay/master/repositories.xml" --fetch --add=ahippo-rhvoice-overlay
|
||||||
ACCEPT_KEYWORDS="~amd64" emerge -av app-accessibility/svox app-accessibility/rhvoice
|
ACCEPT_KEYWORDS="~amd64" emerge -av app-accessibility/svox app-accessibility/rhvoice
|
||||||
```
|
```
|
||||||
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)
|
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
|
##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`.
|
To avoid that linux moves deleted files into trash you can create an empty file `.Trash-1000`.
|
||||||
|
|
@ -144,3 +140,53 @@ Your IPod should work and play music again now.
|
||||||
|
|
||||||
The original shuffle3db website went offline. This repository contains a copy of the information inside the `docs` folder.
|
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).
|
Original data can be found via [wayback machine](https://web.archive.org/web/20131016014401/http://shuffle3db.wikispaces.com/iTunesSD3gen).
|
||||||
|
|
||||||
|
|
||||||
|
# Version History
|
||||||
|
|
||||||
|
```
|
||||||
|
1.4 Release (27.08.2016)
|
||||||
|
* Catch "no space left" error #30
|
||||||
|
* Renamed --voiceover to --track-voiceover
|
||||||
|
* Added optional --verbose output
|
||||||
|
* Renamed script from shuffle.py to ipod-shuffle-4g.py
|
||||||
|
* Added files to `extras` folder
|
||||||
|
* Ignore hidden filenames
|
||||||
|
* Do not force playlist voiceover with auto playlists
|
||||||
|
* Added shortcut parameters (-p, -t, -d, etc.)
|
||||||
|
* Fix UnicodeEncodeError for non-ascii playlist names (#35)
|
||||||
|
|
||||||
|
1.3 Release (08.06.2016)
|
||||||
|
* Directory based auto playlist building (--auto-dir-playlists) (#13)
|
||||||
|
* ID3 tags based auto playlist building (--auto-id3-playlists)
|
||||||
|
* Added short program description
|
||||||
|
* Fix hyphen in filename #4
|
||||||
|
* Fixed mutagen bug #5
|
||||||
|
* Voiceover disabled by default #26 (Playlist voiceover enabled with auto playlist generation)
|
||||||
|
* Differentiate track and playlist voiceover #26
|
||||||
|
|
||||||
|
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 nims11 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
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python2.7
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Builtin libraries
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import mutagen
|
||||||
|
import binascii
|
||||||
import subprocess
|
import subprocess
|
||||||
import collections
|
import collections
|
||||||
import errno
|
import errno
|
||||||
|
|
@ -15,12 +16,6 @@ import re
|
||||||
import tempfile
|
import tempfile
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
# External libraries
|
|
||||||
try:
|
|
||||||
import mutagen
|
|
||||||
except ImportError:
|
|
||||||
mutagen = None
|
|
||||||
|
|
||||||
audio_ext = (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav")
|
audio_ext = (".mp3", ".m4a", ".m4b", ".m4p", ".aa", ".wav")
|
||||||
list_ext = (".pls", ".m3u")
|
list_ext = (".pls", ".m3u")
|
||||||
def make_dir_if_absent(path):
|
def make_dir_if_absent(path):
|
||||||
|
|
@ -32,19 +27,19 @@ def make_dir_if_absent(path):
|
||||||
|
|
||||||
def raises_unicode_error(str):
|
def raises_unicode_error(str):
|
||||||
try:
|
try:
|
||||||
str.encode('latin-1')
|
str.decode('utf-8').encode('latin-1')
|
||||||
return False
|
return False
|
||||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def hash_error_unicode(item):
|
def hash_error_unicode(item):
|
||||||
item_bytes = item.encode('utf-8')
|
return "".join(["{0:02X}".format(ord(x)) for x in reversed(hashlib.md5(item).digest()[:8])])
|
||||||
return "".join(["{0:02X}".format(ord(x)) for x in reversed(hashlib.md5(item_bytes).hexdigest()[:8])])
|
pass
|
||||||
|
|
||||||
def validate_unicode(path):
|
def validate_unicode(path):
|
||||||
path_list = path.split('/')
|
path_list = path.split('/')
|
||||||
last_raise = False
|
last_raise = False
|
||||||
for i in range(len(path_list)):
|
for i in xrange(len(path_list)):
|
||||||
if raises_unicode_error(path_list[i]):
|
if raises_unicode_error(path_list[i]):
|
||||||
path_list[i] = hash_error_unicode(path_list[i])
|
path_list[i] = hash_error_unicode(path_list[i])
|
||||||
last_raise = True
|
last_raise = True
|
||||||
|
|
@ -66,11 +61,11 @@ def splitpath(path):
|
||||||
return path.split(os.sep)
|
return path.split(os.sep)
|
||||||
|
|
||||||
def get_relpath(path, basepath):
|
def get_relpath(path, basepath):
|
||||||
commonprefix = os.sep.join(os.path.commonprefix(list(map(splitpath, [path, basepath]))))
|
commonprefix = os.sep.join(os.path.commonprefix(map(splitpath, [path, basepath])))
|
||||||
return os.path.relpath(path, commonprefix)
|
return os.path.relpath(path, commonprefix)
|
||||||
|
|
||||||
def is_path_prefix(prefix, path):
|
def is_path_prefix(prefix, path):
|
||||||
return prefix == os.sep.join(os.path.commonprefix(list(map(splitpath, [prefix, path]))))
|
return prefix == os.sep.join(os.path.commonprefix(map(splitpath, [prefix, path])))
|
||||||
|
|
||||||
def group_tracks_by_id3_template(tracks, template):
|
def group_tracks_by_id3_template(tracks, template):
|
||||||
grouped_tracks_dict = {}
|
grouped_tracks_dict = {}
|
||||||
|
|
@ -97,37 +92,30 @@ def group_tracks_by_id3_template(tracks, template):
|
||||||
return sorted(grouped_tracks_dict.items())
|
return sorted(grouped_tracks_dict.items())
|
||||||
|
|
||||||
class Text2Speech(object):
|
class Text2Speech(object):
|
||||||
valid_tts = {'pico2wave': True, 'RHVoice': True, 'espeak': True, 'say': True}
|
valid_tts = {'pico2wave': True, 'RHVoice': True, 'espeak': True}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_support():
|
def check_support():
|
||||||
voiceoverAvailable = False
|
voiceoverAvailable = False
|
||||||
|
|
||||||
# Check for macOS say voiceover
|
|
||||||
if not exec_exists_in_path("say"):
|
|
||||||
Text2Speech.valid_tts['say'] = False
|
|
||||||
print("Warning: macOS say not found, voicever won't be generated using it.")
|
|
||||||
else:
|
|
||||||
voiceoverAvailable = True
|
|
||||||
|
|
||||||
# Check for pico2wave voiceover
|
# Check for pico2wave voiceover
|
||||||
if not exec_exists_in_path("pico2wave"):
|
if not exec_exists_in_path("pico2wave"):
|
||||||
Text2Speech.valid_tts['pico2wave'] = False
|
Text2Speech.valid_tts['pico2wave'] = False
|
||||||
print("Warning: pico2wave not found, voicever won't be generated using it.")
|
print "Warning: pico2wave not found, voicever won't be generated using it."
|
||||||
else:
|
else:
|
||||||
voiceoverAvailable = True
|
voiceoverAvailable = True
|
||||||
|
|
||||||
# Check for espeak voiceover
|
# Check for espeak voiceover
|
||||||
if not exec_exists_in_path("espeak"):
|
if not exec_exists_in_path("espeak"):
|
||||||
Text2Speech.valid_tts['espeak'] = False
|
Text2Speech.valid_tts['espeak'] = False
|
||||||
print("Warning: espeak not found, voicever won't be generated using it.")
|
print "Warning: espeak not found, voicever won't be generated using it."
|
||||||
else:
|
else:
|
||||||
voiceoverAvailable = True
|
voiceoverAvailable = True
|
||||||
|
|
||||||
# Check for Russian RHVoice voiceover
|
# Check for Russian RHVoice voiceover
|
||||||
if not exec_exists_in_path("RHVoice"):
|
if not exec_exists_in_path("RHVoice"):
|
||||||
Text2Speech.valid_tts['RHVoice'] = False
|
Text2Speech.valid_tts['RHVoice'] = False
|
||||||
print("Warning: RHVoice not found, Russian voicever won't be generated.")
|
print "Warning: RHVoice not found, Russian voicever won't be generated."
|
||||||
else:
|
else:
|
||||||
voiceoverAvailable = True
|
voiceoverAvailable = True
|
||||||
|
|
||||||
|
|
@ -144,8 +132,8 @@ class Text2Speech(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# ensure we deal with unicode later
|
# ensure we deal with unicode later
|
||||||
if not isinstance(text, str):
|
if not isinstance(text, unicode):
|
||||||
text = str(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":
|
||||||
return Text2Speech.rhvoice(out_wav_path, text)
|
return Text2Speech.rhvoice(out_wav_path, text)
|
||||||
|
|
@ -154,8 +142,6 @@ class Text2Speech(object):
|
||||||
return True
|
return True
|
||||||
elif Text2Speech.espeak(out_wav_path, text):
|
elif Text2Speech.espeak(out_wav_path, text):
|
||||||
return True
|
return True
|
||||||
elif Text2Speech.say(out_wav_path, text):
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -163,7 +149,7 @@ class Text2Speech(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def guess_lang(unicodetext):
|
def guess_lang(unicodetext):
|
||||||
lang = 'en-GB'
|
lang = 'en-GB'
|
||||||
if re.search("[А-Яа-я]", unicodetext) is not None:
|
if re.search(u"[А-Яа-я]", unicodetext) is not None:
|
||||||
lang = 'ru-RU'
|
lang = 'ru-RU'
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
|
|
@ -171,21 +157,14 @@ class Text2Speech(object):
|
||||||
def pico2wave(out_wav_path, unicodetext):
|
def pico2wave(out_wav_path, unicodetext):
|
||||||
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
|
|
||||||
def say(out_wav_path, unicodetext):
|
|
||||||
if not Text2Speech.valid_tts['say']:
|
|
||||||
return False
|
|
||||||
subprocess.call(["say", "-o", out_wav_path, '--data-format=LEI16', '--file-format=WAVE', '--', unicodetext])
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def espeak(out_wav_path, unicodetext):
|
def espeak(out_wav_path, unicodetext):
|
||||||
if not Text2Speech.valid_tts['espeak']:
|
if not Text2Speech.valid_tts['espeak']:
|
||||||
return False
|
return False
|
||||||
subprocess.call(["espeak", "-v", "english_rp", "-s", "150", "-w", out_wav_path, '--', unicodetext])
|
subprocess.call(["espeak", "-v", "english_rp", "-s", "150", "-w", out_wav_path, unicodetext])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -217,7 +196,7 @@ class Record(object):
|
||||||
self.trackgain = parent.trackgain
|
self.trackgain = parent.trackgain
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
if item not in list(self._struct.keys()):
|
if item not in self._struct.keys():
|
||||||
raise KeyError
|
raise KeyError
|
||||||
return self._fields.get(item, self._struct[item][1])
|
return self._fields.get(item, self._struct[item][1])
|
||||||
|
|
||||||
|
|
@ -225,16 +204,18 @@ class Record(object):
|
||||||
self._fields[item] = value
|
self._fields[item] = value
|
||||||
|
|
||||||
def construct(self):
|
def construct(self):
|
||||||
output = bytes()
|
output = ""
|
||||||
for i in list(self._struct.keys()):
|
for i in self._struct.keys():
|
||||||
(fmt, default) = self._struct[i]
|
(fmt, default) = self._struct[i]
|
||||||
|
if fmt == "4s":
|
||||||
|
fmt, default = "I", int(binascii.hexlify(default), 16)
|
||||||
output += struct.pack("<" + fmt, self._fields.get(i, default))
|
output += struct.pack("<" + fmt, self._fields.get(i, default))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def text_to_speech(self, text, dbid, playlist = False):
|
def text_to_speech(self, text, dbid, playlist = False):
|
||||||
if self.track_voiceover and not playlist or self.playlist_voiceover and playlist:
|
if self.track_voiceover and not playlist or self.playlist_voiceover and playlist:
|
||||||
# Create the voiceover wav file
|
# Create the voiceover wav file
|
||||||
fn = ''.join(format(x, '02x') 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")
|
||||||
return Text2Speech.text2speech(path, text)
|
return Text2Speech.text2speech(path, text)
|
||||||
return False
|
return False
|
||||||
|
|
@ -284,7 +265,7 @@ class TunesSD(Record):
|
||||||
self.track_header = TrackHeader(self)
|
self.track_header = TrackHeader(self)
|
||||||
self.play_header = PlaylistHeader(self)
|
self.play_header = PlaylistHeader(self)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", b"bdhs")), # shdb
|
("header_id", ("4s", "shdb")),
|
||||||
("unknown1", ("I", 0x02000003)),
|
("unknown1", ("I", 0x02000003)),
|
||||||
("total_length", ("I", 64)),
|
("total_length", ("I", 64)),
|
||||||
("total_number_of_tracks", ("I", 0)),
|
("total_number_of_tracks", ("I", 0)),
|
||||||
|
|
@ -296,7 +277,7 @@ class TunesSD(Record):
|
||||||
("total_tracks_without_podcasts", ("I", 0)),
|
("total_tracks_without_podcasts", ("I", 0)),
|
||||||
("track_header_offset", ("I", 64)),
|
("track_header_offset", ("I", 64)),
|
||||||
("playlist_header_offset", ("I", 0)),
|
("playlist_header_offset", ("I", 0)),
|
||||||
("unknown4", ("20s", b"\x00" * 20)),
|
("unknown4", ("20s", "\x00" * 20)),
|
||||||
])
|
])
|
||||||
|
|
||||||
def construct(self):
|
def construct(self):
|
||||||
|
|
@ -321,7 +302,7 @@ class TrackHeader(Record):
|
||||||
self.base_offset = 0
|
self.base_offset = 0
|
||||||
Record.__init__(self, parent)
|
Record.__init__(self, parent)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", b"hths")), # shth
|
("header_id", ("4s", "shth")),
|
||||||
("total_length", ("I", 0)),
|
("total_length", ("I", 0)),
|
||||||
("number_of_tracks", ("I", 0)),
|
("number_of_tracks", ("I", 0)),
|
||||||
("unknown1", ("Q", 0)),
|
("unknown1", ("Q", 0)),
|
||||||
|
|
@ -333,7 +314,7 @@ class TrackHeader(Record):
|
||||||
output = Record.construct(self)
|
output = Record.construct(self)
|
||||||
|
|
||||||
# Construct the underlying tracks
|
# Construct the underlying tracks
|
||||||
track_chunk = bytes()
|
track_chunk = ""
|
||||||
for i in self.tracks:
|
for i in self.tracks:
|
||||||
track = Track(self)
|
track = Track(self)
|
||||||
verboseprint("[*] Adding track", i)
|
verboseprint("[*] Adding track", i)
|
||||||
|
|
@ -347,13 +328,13 @@ class Track(Record):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
Record.__init__(self, parent)
|
Record.__init__(self, parent)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", b"rths")), # shtr
|
("header_id", ("4s", "shtr")),
|
||||||
("header_length", ("I", 0x174)),
|
("header_length", ("I", 0x174)),
|
||||||
("start_at_pos_ms", ("I", 0)),
|
("start_at_pos_ms", ("I", 0)),
|
||||||
("stop_at_pos_ms", ("I", 0)),
|
("stop_at_pos_ms", ("I", 0)),
|
||||||
("volume_gain", ("I", int(self.trackgain))),
|
("volume_gain", ("I", int(self.trackgain))),
|
||||||
("filetype", ("I", 1)),
|
("filetype", ("I", 1)),
|
||||||
("filename", ("256s", b"\x00" * 256)),
|
("filename", ("256s", "\x00" * 256)),
|
||||||
("bookmark", ("I", 0)),
|
("bookmark", ("I", 0)),
|
||||||
("dontskip", ("B", 1)),
|
("dontskip", ("B", 1)),
|
||||||
("remember", ("B", 0)),
|
("remember", ("B", 0)),
|
||||||
|
|
@ -371,49 +352,46 @@ class Track(Record):
|
||||||
("unknown4", ("Q", 0)),
|
("unknown4", ("Q", 0)),
|
||||||
("dbid", ("8s", 0)),
|
("dbid", ("8s", 0)),
|
||||||
("artistid", ("I", 0)),
|
("artistid", ("I", 0)),
|
||||||
("unknown5", ("32s", b"\x00" * 32)),
|
("unknown5", ("32s", "\x00" * 32)),
|
||||||
])
|
])
|
||||||
|
|
||||||
def populate(self, filename):
|
def populate(self, filename):
|
||||||
self["filename"] = self.path_to_ipod(filename).encode('utf-8')
|
self["filename"] = self.path_to_ipod(filename)
|
||||||
|
|
||||||
if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"):
|
if os.path.splitext(filename)[1].lower() in (".m4a", ".m4b", ".m4p", ".aa"):
|
||||||
self["filetype"] = 2
|
self["filetype"] = 2
|
||||||
|
|
||||||
text = os.path.splitext(os.path.basename(filename))[0]
|
text = os.path.splitext(os.path.basename(filename))[0]
|
||||||
|
audio = None
|
||||||
|
try:
|
||||||
|
audio = mutagen.File(filename, easy = True)
|
||||||
|
except:
|
||||||
|
print "Error calling mutagen. Possible invalid filename/ID3Tags (hyphen in filename?)"
|
||||||
|
if audio:
|
||||||
|
# Note: Rythmbox IPod plugin sets this value always 0.
|
||||||
|
self["stop_at_pos_ms"] = int(audio.info.length * 1000)
|
||||||
|
|
||||||
# Try to get album and artist information with mutagen
|
artist = audio.get("artist", [u"Unknown"])[0]
|
||||||
if mutagen:
|
if artist in self.artists:
|
||||||
audio = None
|
self["artistid"] = self.artists.index(artist)
|
||||||
try:
|
else:
|
||||||
audio = mutagen.File(filename, easy = True)
|
self["artistid"] = len(self.artists)
|
||||||
except:
|
self.artists.append(artist)
|
||||||
print("Error calling mutagen. Possible invalid filename/ID3Tags (hyphen in filename?)")
|
|
||||||
if audio:
|
|
||||||
# Note: Rythmbox IPod plugin sets this value always 0.
|
|
||||||
self["stop_at_pos_ms"] = int(audio.info.length * 1000)
|
|
||||||
|
|
||||||
artist = audio.get("artist", ["Unknown"])[0]
|
album = audio.get("album", [u"Unknown"])[0]
|
||||||
if artist in self.artists:
|
if album in self.albums:
|
||||||
self["artistid"] = self.artists.index(artist)
|
self["albumid"] = self.albums.index(album)
|
||||||
else:
|
else:
|
||||||
self["artistid"] = len(self.artists)
|
self["albumid"] = len(self.albums)
|
||||||
self.artists.append(artist)
|
self.albums.append(album)
|
||||||
|
|
||||||
album = audio.get("album", ["Unknown"])[0]
|
if audio.get("title", "") and audio.get("artist", ""):
|
||||||
if album in self.albums:
|
text = u" - ".join(audio.get("title", u"") + audio.get("artist", u""))
|
||||||
self["albumid"] = self.albums.index(album)
|
|
||||||
else:
|
|
||||||
self["albumid"] = len(self.albums)
|
|
||||||
self.albums.append(album)
|
|
||||||
|
|
||||||
if audio.get("title", "") and audio.get("artist", ""):
|
|
||||||
text = " - ".join(audio.get("title", "") + audio.get("artist", ""))
|
|
||||||
|
|
||||||
# Handle the VoiceOverData
|
# Handle the VoiceOverData
|
||||||
if isinstance(text, str):
|
if isinstance(text, unicode):
|
||||||
text = text.encode('utf-8', 'ignore')
|
text = text.encode('utf-8', 'ignore')
|
||||||
self["dbid"] = hashlib.md5(text).digest()[:8]
|
self["dbid"] = hashlib.md5(text).digest()[:8] #pylint: disable-msg=E1101
|
||||||
self.text_to_speech(text, self["dbid"])
|
self.text_to_speech(text, self["dbid"])
|
||||||
|
|
||||||
class PlaylistHeader(Record):
|
class PlaylistHeader(Record):
|
||||||
|
|
@ -421,16 +399,16 @@ class PlaylistHeader(Record):
|
||||||
self.base_offset = 0
|
self.base_offset = 0
|
||||||
Record.__init__(self, parent)
|
Record.__init__(self, parent)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", b"hphs")), #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_non_podcast_lists", ("2s", b"\xFF\xFF")),
|
("number_of_non_podcast_lists", ("2s", "\xFF\xFF")),
|
||||||
("number_of_master_lists", ("2s", b"\x01\x00")),
|
("number_of_master_lists", ("2s", "\x01\x00")),
|
||||||
("number_of_non_audiobook_lists", ("2s", b"\xFF\xFF")),
|
("number_of_non_audiobook_lists", ("2s", "\xFF\xFF")),
|
||||||
("unknown2", ("2s", b"\x00" * 2)),
|
("unknown2", ("2s", "\x00" * 2)),
|
||||||
])
|
])
|
||||||
|
|
||||||
def construct(self, tracks):
|
def construct(self, tracks): #pylint: disable-msg=W0221
|
||||||
# Build the master list
|
# Build the master list
|
||||||
masterlist = Playlist(self)
|
masterlist = Playlist(self)
|
||||||
verboseprint("[+] Adding master playlist")
|
verboseprint("[+] Adding master playlist")
|
||||||
|
|
@ -448,7 +426,7 @@ class PlaylistHeader(Record):
|
||||||
playlistcount += 1
|
playlistcount += 1
|
||||||
chunks += [construction]
|
chunks += [construction]
|
||||||
else:
|
else:
|
||||||
print("Error: Playlist does not contain a single track. Skipping playlist.")
|
print "Error: Playlist does not contain a single track. Skipping playlist."
|
||||||
|
|
||||||
self["number_of_playlists"] = playlistcount
|
self["number_of_playlists"] = playlistcount
|
||||||
self["total_length"] = 0x14 + (self["number_of_playlists"] * 4)
|
self["total_length"] = 0x14 + (self["number_of_playlists"] * 4)
|
||||||
|
|
@ -461,27 +439,27 @@ class PlaylistHeader(Record):
|
||||||
output += struct.pack("I", offset)
|
output += struct.pack("I", offset)
|
||||||
offset += len(chunks[i])
|
offset += len(chunks[i])
|
||||||
|
|
||||||
return output + b"".join(chunks)
|
return output + "".join(chunks)
|
||||||
|
|
||||||
class Playlist(Record):
|
class Playlist(Record):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.listtracks = []
|
self.listtracks = []
|
||||||
Record.__init__(self, parent)
|
Record.__init__(self, parent)
|
||||||
self._struct = collections.OrderedDict([
|
self._struct = collections.OrderedDict([
|
||||||
("header_id", ("4s", b"lphs")), # shpl
|
("header_id", ("4s", "shpl")),
|
||||||
("total_length", ("I", 0)),
|
("total_length", ("I", 0)),
|
||||||
("number_of_songs", ("I", 0)),
|
("number_of_songs", ("I", 0)),
|
||||||
("number_of_nonaudio", ("I", 0)),
|
("number_of_nonaudio", ("I", 0)),
|
||||||
("dbid", ("8s", b"\x00" * 8)),
|
("dbid", ("8s", "\x00" * 8)),
|
||||||
("listtype", ("I", 2)),
|
("listtype", ("I", 2)),
|
||||||
("unknown1", ("16s", b"\x00" * 16))
|
("unknown1", ("16s", "\x00" * 16))
|
||||||
])
|
])
|
||||||
|
|
||||||
def set_master(self, tracks):
|
def set_master(self, tracks):
|
||||||
# By default use "All Songs" builtin voiceover (dbid all zero)
|
# By default use "All Songs" builtin voiceover (dbid all zero)
|
||||||
# Else generate alternative "All Songs" to fit the speaker voice of other playlists
|
# Else generate alternative "All Songs" to fit the speaker voice of other playlists
|
||||||
if self.playlist_voiceover and (Text2Speech.valid_tts['pico2wave'] or Text2Speech.valid_tts['espeak'] or Text2Speech.valid_tts['say']):
|
if self.playlist_voiceover and (Text2Speech.valid_tts['pico2wave'] or Text2Speech.valid_tts['espeak']):
|
||||||
self["dbid"] = hashlib.md5(b"masterlist").digest()[:8]
|
self["dbid"] = hashlib.md5("masterlist").digest()[:8] #pylint: disable-msg=E1101
|
||||||
self.text_to_speech("All songs", self["dbid"], True)
|
self.text_to_speech("All songs", self["dbid"], True)
|
||||||
self["listtype"] = 1
|
self["listtype"] = 1
|
||||||
self.listtracks = tracks
|
self.listtracks = tracks
|
||||||
|
|
@ -502,7 +480,7 @@ class Playlist(Record):
|
||||||
dataarr = i.strip().split("=", 1)
|
dataarr = i.strip().split("=", 1)
|
||||||
if dataarr[0].lower().startswith("file"):
|
if dataarr[0].lower().startswith("file"):
|
||||||
num = int(dataarr[0][4:])
|
num = int(dataarr[0][4:])
|
||||||
filename = urllib.parse.unquote(dataarr[1]).strip()
|
filename = urllib.unquote(dataarr[1]).strip()
|
||||||
if filename.lower().startswith('file://'):
|
if filename.lower().startswith('file://'):
|
||||||
filename = filename[7:]
|
filename = filename[7:]
|
||||||
if self.rename:
|
if self.rename:
|
||||||
|
|
@ -550,7 +528,7 @@ class Playlist(Record):
|
||||||
text = os.path.splitext(os.path.basename(filename))[0]
|
text = os.path.splitext(os.path.basename(filename))[0]
|
||||||
else:
|
else:
|
||||||
# Read the playlist file
|
# Read the playlist file
|
||||||
with open(filename, 'r', errors="replace") as f:
|
with open(filename, 'rb') as f:
|
||||||
data = f.readlines()
|
data = f.readlines()
|
||||||
|
|
||||||
extension = os.path.splitext(filename)[1].lower()
|
extension = os.path.splitext(filename)[1].lower()
|
||||||
|
|
@ -567,14 +545,16 @@ class Playlist(Record):
|
||||||
text = os.path.splitext(os.path.basename(filename))[0]
|
text = os.path.splitext(os.path.basename(filename))[0]
|
||||||
|
|
||||||
# Handle the VoiceOverData
|
# Handle the VoiceOverData
|
||||||
self["dbid"] = hashlib.md5(text.encode('utf-8')).digest()[:8]
|
if isinstance(text, unicode):
|
||||||
|
text = text.encode('utf-8', 'ignore')
|
||||||
|
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)
|
||||||
|
|
||||||
def construct(self, tracks):
|
def construct(self, tracks): #pylint: disable-msg=W0221
|
||||||
self["total_length"] = 44 + (4 * len(self.listtracks))
|
self["total_length"] = 44 + (4 * len(self.listtracks))
|
||||||
self["number_of_songs"] = 0
|
self["number_of_songs"] = 0
|
||||||
|
|
||||||
chunks = bytes()
|
chunks = ""
|
||||||
for i in self.listtracks:
|
for i in self.listtracks:
|
||||||
path = self.ipod_to_path(i)
|
path = self.ipod_to_path(i)
|
||||||
position = -1
|
position = -1
|
||||||
|
|
@ -583,8 +563,8 @@ class Playlist(Record):
|
||||||
except:
|
except:
|
||||||
# Print an error if no track was found.
|
# Print an error if no track was found.
|
||||||
# Empty playlists are handeled in the PlaylistHeader class.
|
# Empty playlists are handeled in the PlaylistHeader class.
|
||||||
print("Error: Could not find track \"" + path + "\".")
|
print "Error: Could not find track \"" + path + "\"."
|
||||||
print("Maybe its an invalid FAT filesystem name. Please fix your playlist. Skipping track.")
|
print "Maybe its an invalid FAT filesystem name. Please fix your playlist. Skipping track."
|
||||||
if position > -1:
|
if position > -1:
|
||||||
chunks += struct.pack("I", position)
|
chunks += struct.pack("I", position)
|
||||||
self["number_of_songs"] += 1
|
self["number_of_songs"] += 1
|
||||||
|
|
@ -616,11 +596,11 @@ class Shuffler(object):
|
||||||
make_dir_if_absent(os.path.join(self.path, dirname))
|
make_dir_if_absent(os.path.join(self.path, dirname))
|
||||||
|
|
||||||
def dump_state(self):
|
def dump_state(self):
|
||||||
print("Shuffle DB state")
|
print "Shuffle DB state"
|
||||||
print("Tracks", self.tracks)
|
print "Tracks", self.tracks
|
||||||
print("Albums", self.albums)
|
print "Albums", self.albums
|
||||||
print("Artists", self.artists)
|
print "Artists", self.artists
|
||||||
print("Playlists", self.lists)
|
print "Playlists", self.lists
|
||||||
|
|
||||||
def populate(self):
|
def populate(self):
|
||||||
self.tunessd = TunesSD(self)
|
self.tunessd = TunesSD(self)
|
||||||
|
|
@ -647,28 +627,22 @@ class Shuffler(object):
|
||||||
self.lists.append(os.path.abspath(dirpath))
|
self.lists.append(os.path.abspath(dirpath))
|
||||||
|
|
||||||
if self.auto_id3_playlists != None:
|
if self.auto_id3_playlists != None:
|
||||||
if mutagen:
|
for grouped_list in group_tracks_by_id3_template(self.tracks, self.auto_id3_playlists):
|
||||||
for grouped_list in group_tracks_by_id3_template(self.tracks, self.auto_id3_playlists):
|
self.lists.append(grouped_list)
|
||||||
self.lists.append(grouped_list)
|
|
||||||
else:
|
|
||||||
print("Error: No mutagen found. Cannot generate auto-id3-playlists.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def write_database(self):
|
def write_database(self):
|
||||||
print("Writing database. This may take a while...")
|
|
||||||
with open(os.path.join(self.path, "iPod_Control", "iTunes", "iTunesSD"), "wb") as f:
|
with open(os.path.join(self.path, "iPod_Control", "iTunes", "iTunesSD"), "wb") as f:
|
||||||
try:
|
try:
|
||||||
f.write(self.tunessd.construct())
|
f.write(self.tunessd.construct())
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
print("I/O error({0}): {1}".format(e.errno, e.strerror))
|
print "I/O error({0}): {1}".format(e.errno, e.strerror)
|
||||||
print("Error: Writing iPod database failed.")
|
print "Error: Writing iPod database failed."
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
print "Database written successfully:"
|
||||||
print("Database written successfully:")
|
print "Tracks", len(self.tracks)
|
||||||
print("Tracks", len(self.tracks))
|
print "Albums", len(self.albums)
|
||||||
print("Albums", len(self.albums))
|
print "Artists", len(self.artists)
|
||||||
print("Artists", len(self.artists))
|
print "Playlists", len(self.lists)
|
||||||
print("Playlists", len(self.lists))
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Read all files from the directory
|
# Read all files from the directory
|
||||||
|
|
@ -687,7 +661,7 @@ def check_unicode(path):
|
||||||
if raises_unicode_error(item):
|
if raises_unicode_error(item):
|
||||||
src = os.path.join(path, item)
|
src = os.path.join(path, item)
|
||||||
dest = os.path.join(path, hash_error_unicode(item)) + os.path.splitext(item)[1].lower()
|
dest = os.path.join(path, hash_error_unicode(item)) + os.path.splitext(item)[1].lower()
|
||||||
print('Renaming %s -> %s' % (src, dest))
|
print 'Renaming %s -> %s' % (src, dest)
|
||||||
os.rename(src, dest)
|
os.rename(src, dest)
|
||||||
else:
|
else:
|
||||||
ret_flag = (check_unicode(os.path.join(path, item)) or ret_flag)
|
ret_flag = (check_unicode(os.path.join(path, item)) or ret_flag)
|
||||||
|
|
@ -695,7 +669,7 @@ def check_unicode(path):
|
||||||
src = os.path.join(path, item)
|
src = os.path.join(path, item)
|
||||||
new_name = hash_error_unicode(item)
|
new_name = hash_error_unicode(item)
|
||||||
dest = os.path.join(path, new_name)
|
dest = os.path.join(path, new_name)
|
||||||
print('Renaming %s -> %s' % (src, dest))
|
print 'Renaming %s -> %s' % (src, dest)
|
||||||
os.rename(src, dest)
|
os.rename(src, dest)
|
||||||
return ret_flag
|
return ret_flag
|
||||||
|
|
||||||
|
|
@ -711,15 +685,15 @@ def nonnegative_int(string):
|
||||||
|
|
||||||
def checkPathValidity(path):
|
def checkPathValidity(path):
|
||||||
if not os.path.isdir(result.path):
|
if not os.path.isdir(result.path):
|
||||||
print("Error finding IPod directory. Maybe it is not connected or mounted?")
|
print "Error finding IPod directory. Maybe it is not connected or mounted?"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not os.access(result.path, os.W_OK):
|
if not os.access(result.path, os.W_OK):
|
||||||
print('Unable to get write permissions in the IPod directory')
|
print 'Unable to get write permissions in the IPod directory'
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def handle_interrupt(signal, frame):
|
def handle_interrupt(signal, frame):
|
||||||
print("Interrupt detected, exiting...")
|
print "Interrupt detected, exiting..."
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
@ -727,7 +701,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
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.5')
|
'for the newer gen IPod Shuffle. Version 1.4')
|
||||||
|
|
||||||
parser.add_argument('-t', '--track-voiceover', action='store_true',
|
parser.add_argument('-t', '--track-voiceover', action='store_true',
|
||||||
help='Enable track voiceover feature')
|
help='Enable track voiceover feature')
|
||||||
|
|
@ -764,33 +738,34 @@ if __name__ == '__main__':
|
||||||
result = parser.parse_args()
|
result = parser.parse_args()
|
||||||
|
|
||||||
# Enable verbose printing if desired
|
# Enable verbose printing if desired
|
||||||
verboseprint = print if result.verbose else lambda *a, **k: None
|
# Smaller version for python3 available.
|
||||||
|
# See https://stackoverflow.com/questions/5980042/how-to-implement-the-verbose-or-v-option-into-a-script
|
||||||
|
if result.verbose:
|
||||||
|
def verboseprint(*args):
|
||||||
|
# Print each argument separately so caller doesn't need to
|
||||||
|
# stuff everything to be printed into a single string
|
||||||
|
for arg in args:
|
||||||
|
print arg,
|
||||||
|
print
|
||||||
|
else:
|
||||||
|
verboseprint = lambda *a: None # do-nothing function
|
||||||
|
|
||||||
checkPathValidity(result.path)
|
checkPathValidity(result.path)
|
||||||
|
|
||||||
if result.rename_unicode:
|
if result.rename_unicode:
|
||||||
check_unicode(result.path)
|
check_unicode(result.path)
|
||||||
|
|
||||||
if not mutagen:
|
|
||||||
print("Warning: No mutagen found. Database will not contain any album nor artist information.")
|
|
||||||
|
|
||||||
verboseprint("Playlist voiceover requested:", result.playlist_voiceover)
|
verboseprint("Playlist voiceover requested:", result.playlist_voiceover)
|
||||||
verboseprint("Track voiceover requested:", result.track_voiceover)
|
verboseprint("Track voiceover requested:", result.track_voiceover)
|
||||||
if (result.track_voiceover or result.playlist_voiceover):
|
if (result.track_voiceover or result.playlist_voiceover):
|
||||||
if not Text2Speech.check_support():
|
if 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.track_voiceover = False
|
result.track_voiceover = False
|
||||||
result.playlist_voiceover = False
|
result.playlist_voiceover = False
|
||||||
else:
|
else:
|
||||||
verboseprint("Voiceover available.")
|
verboseprint("Voiceover available.")
|
||||||
|
|
||||||
shuffle = Shuffler(result.path,
|
shuffle = Shuffler(result.path, track_voiceover=result.track_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)
|
||||||
track_voiceover=result.track_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