diff --git a/.gitignore b/.gitignore index ba0430d..342eae9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__/ \ No newline at end of file +__pycache__/ +repear.log \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..06224ea --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +This branch includes my edits of the 2009 software. I have removed Shuffle support. + +Target device is an Nano 3G \ No newline at end of file diff --git a/iTunesDB.py b/iTunesDB.py index e6c361c..982b573 100644 --- a/iTunesDB.py +++ b/iTunesDB.py @@ -19,49 +19,67 @@ import struct, random, array, sys, os, stat, time from dataclasses import dataclass +from enum import StrEnum from functools import cmp_to_key try: from PIL import Image PILAvailable = True -except ImportError: - PILAvailable = False - +except ImportError: PILAvailable = False def DefaultLoggingFunction(text, force_flush=True): sys.stdout.write(text) if force_flush: sys.stdout.flush() log = DefaultLoggingFunction -################################################################################ -## some helper classes to represent ITDB records, and some helper functions ## -################################################################################ - class Field: def __bytes__(self): raise Exception("abstract function call") def __len__(self): raise Exception("abstract function call") +class Tags(StrEnum): + BD = "mhbd" # Database object, root: everything is a child of this + SD = "mhsd" # Data set, stores data of a type + LT = "mhlt" # Track list, stores a list of tracks (wow) + IT = "mhit" # Track + LP = "mhlp" # Stores the playlists + YP = "mhyp" # Playlist + IP = "mhip" # Playlist item + OD = "mhod" # Stores literal data, see http://www.ipodlinux.org/ITunesDB/#Data_Object + LA = "mhla" # Album list + LF = "mhlf" # File list + IF = "mhif" # Image file + FD = "mhfd" # Database object of artwork db + LI = "mhli" # Image list + II = "mhii" # Image object ("Image Item") + NI = "mhni" # Image object ("Image Name") + class F_Tag(Field): - def __init__(self, tag: bytes): - self.tag = tag - assert isinstance(tag, bytes) + def __init__(self, tag: Tags): + self.tag = tag.value.encode() + assert isinstance(self.tag, bytes) def __bytes__(self): return self.tag def __len__(self): return len(self.tag) +class F_Bytes(Field): + def __init__(self, data: bytes, padding: int = 0): + self.data = bytes(data) + while len(self) < padding: self.data += b"\0" + def __bytes__(self): return self.data + def __len__(self): return len(self.data) class F_Formatable(Field): - def __init__(self, format, value): + def __init__(self, format: str | bytes, value: int): self.format = format self.value = int(value) - def __bytes__(self): return struct.pack("<"+self.format, self.value) + def __bytes__(self): return struct.pack(f"<{self.format}", self.value) def __len__(self): return struct.calcsize(self.format) class F_Int64(F_Formatable): - def __init__(self, value): F_Formatable.__init__(self, "Q", value) + def __init__(self, value: int): F_Formatable.__init__(self, "Q", value) class F_Int32(F_Formatable): - def __init__(self, value): F_Formatable.__init__(self, "L", value) + def __init__(self, value: int): F_Formatable.__init__(self, "L", value) class F_Int16(F_Formatable): - def __init__(self, value): F_Formatable.__init__(self, "H", value) + def __init__(self, value: int): F_Formatable.__init__(self, "H", value) class F_Int8(F_Formatable): - def __init__(self, value): F_Formatable.__init__(self, "B", value) + def __init__(self, value: int): F_Formatable.__init__(self, "B", value) class F_HeaderLength(F_Int32): def __init__(self): F_Int32.__init__(self, 0) @@ -82,13 +100,13 @@ class Record: self.child_count_at = None data = b"" for field in header: - if field.__class__ == F_HeaderLength: self.header_length_at = len(data) - if field.__class__ == F_TotalLength: self.total_length_at = len(data) - if field.__class__ == F_ChildCount: self.child_count_at = len(data) + if isinstance(field, F_HeaderLength): self.header_length_at = len(data) + elif isinstance(field, F_TotalLength): self.total_length_at = len(data) + elif isinstance(field, F_ChildCount): self.child_count_at = len(data) + d = field if isinstance(d, str): d = d.encode() - elif not isinstance(d, bytes): d = bytes(d) - data += d + data += bytes(d) if self.header_length_at: data = data[:self.header_length_at] + struct.pack(" val_b: # type: ignore - return 1 + + if val_a < val_b: return -1 # type: ignore + elif val_a > val_b: return 1 # type: ignore return 0 def ifelse(condition, then_val, else_val=None): @@ -133,7 +144,7 @@ def ifelse(condition, then_val, else_val=None): MAC_TIME_OFFSET = 2082844800 if time.daylight: tzoffset = time.altzone -else: tzoffset = time.timezone +else: tzoffset = time.timezone def unixtime2mactime(t): if not t: return t return t + MAC_TIME_OFFSET - tzoffset @@ -141,7 +152,6 @@ def mactime2unixtime(t): if not t: return t return t - MAC_TIME_OFFSET + tzoffset - # "fuzzy" mtime comparison, allows for two types of slight deviations: # 1. differences of exact multiples of one hour (usually time zome problems) # 2. differences of less than 2 seconds (FAT timestamps are imprecise) @@ -150,45 +160,38 @@ def compare_mtime(a, b): if diff > 86402: return False return ((diff % 3600) in (0, 1, 2, 3598, 3599)) - -################################################################################ -## some higher-level ITDB record classes ## -################################################################################ - class StringDataObject(Record): def __init__(self, mhod_type, content): if isinstance(content, bytes): encoded = content else: encoded = content.encode('utf_16_le', 'replace') - Record.__init__(self, ( - F_Tag(b"mhod"), - F_Int32(0x18), + super().__init__(( + F_Tag(Tags.OD), + F_Int32(0x18), # Header size F_TotalLength(), F_Int32(mhod_type), - F_Padding(8), - F_Int32(1), + F_Padding(8), # This contains unknown data + F_Int32(1), # Position F_Int32(len(encoded)), - F_Int32(1), - F_Padding(4) + F_Int32(1), # Not sure what this is, thought before this was a UTF-8/UTF-16 toggle + F_Padding(4) # Unknown )) self.add(encoded) class OrderDataObject(Record): def __init__(self, order): - Record.__init__(self, ( - F_Tag(b"mhod"), - F_Int32(0x18), - F_Int32(0x2C), - F_Int32(100), - F_Padding(8), - F_Int32(order), - F_Padding(16) + super().__init__(( + F_Tag(Tags.OD), + F_Int32(0x18), # Header size + F_Int32(0x2C), # Total lenght, static? + F_Int32(100), # Type 100, ipodlinux says "Seems to vary. iTunes uses it for column sizing info as well as an order indicator in playlists." + F_Padding(8), # Unknown + F_Int32(order), # Position + F_Padding(16) # All rest is null )) - class TrackItemRecord(Record): def __init__(self, info): - if not 'id' in info: - raise KeyError("no track ID set") + if not 'id' in info: raise KeyError("no track ID set") format = info.get('format', "mp3-cbr") if info.get('artwork', None): default_has_artwork = True @@ -196,34 +199,32 @@ class TrackItemRecord(Record): else: default_has_artwork = False default_artwork_size = 0 - if 'video format' in info: - media_type = 2 - else: - media_type = 1 - Record.__init__(self, ( - F_Tag(b"mhit"), + if 'video format' in info: media_type = 2 + else: media_type = 1 + super().__init__(( + F_Tag(Tags.IT), # This describes a track F_HeaderLength(), F_TotalLength(), - F_ChildCount(), - F_Int32(info.get('id', 0)), - F_Int32(info.get('visible', 1)), # visible - F_Tag({"mp3": " 3PM", "aac": " CAA", "mp4a": "A4PM"}.get(format[:3], "\0\0\0\0").encode()), - F_Int16({"mp3-cbr": 0x100, "mp3-vbr": 0x101, "aac": 0, "mp4a": 0}.get(format, 0)), - F_Int8(info.get('compilation', 0)), - F_Int8(info.get('rating', 0)), - F_Int32(unixtime2mactime(info.get('mtime', 0))), - F_Int32(info.get('size', 0)), - F_Int32(int(info.get('length', 0) * 1000)), - F_Int32(info.get('track number', 0)), - F_Int32(info.get('total tracks', 0)), - F_Int32(info.get('year', 0)), - F_Int32(info.get('bitrate', 0)), - F_Int16(0), - F_Int16(info.get('sample rate', 0)), - F_Int32(info.get('volume', 0)), + F_ChildCount(), # Described as number of strings + F_Int32(info.get('id', 0)), # ID for the track, for tracking in the playlists + F_Int32(info.get('visible', 1)), # visible, hides the track if 0 + F_Bytes({"mp3": " 3PM", "aac": " CAA", "mp4a": "A4PM"}.get(format[:3], "\0\0\0\0").encode()), # File type, mp3, aac or m4a + F_Int16({"mp3-cbr": 0x100, "mp3-vbr": 0x101, "aac": 0, "mp4a": 0}.get(format, 0)), # type1 as in the wiki (and type2?) + F_Int8(info.get('compilation', 0)), # is this from a compilation + F_Int8(info.get('rating', 0)), # rating times 20, not updated by ipod + F_Int32(unixtime2mactime(info.get('mtime', 0))), # mod time + F_Int32(info.get('size', 0)), # size of track in bytes + F_Int32(int(info.get('length', 0) * 1000)), # length of track in milliseconds + F_Int32(info.get('track number', 0)), # track number + F_Int32(info.get('total tracks', 0)), # album track count + F_Int32(info.get('year', 0)), # track year + F_Int32(info.get('bitrate', 0)), # bitrate of track + F_Int16(0), # sample rate times 0x10000 + F_Int16(info.get('sample rate', 0)), # sample rate times 0x10000 (not sure why they split this into two bytes in this impl) + F_Int32(info.get('volume', 0)), # -255 to 255 what volume you want at playback F_Int32(info.get('start time', 0)), F_Int32(info.get('stop time', 0)), - F_Int32(info.get('soundcheck', 0)), + F_Int32(info.get('soundcheck', 0)), # used to normalize tracks F_Int32(info.get('play count', 0)), F_Int32(0), F_Int32(unixtime2mactime(info.get('last played time', 0))), @@ -275,20 +276,19 @@ class TrackItemRecord(Record): for mhod_type, key in ((1,'title'), (4,'artist'), (3,'album'), (5,'genre'), (6,'filetype'), (2,'path')): if key in info: value = info[key] - if key=="path": - value = ":" + value.replace("/", ":").replace("\\", ":") + if key == "path": value = ":" + value.replace("/", ":").replace("\\", ":") self.add(StringDataObject(mhod_type, value)) class PlaylistItemRecord(Record): def __init__(self, order, trackid, timestamp=0): - Record.__init__(self, ( - F_Tag(b"mhip"), + super().__init__(( + F_Tag(Tags.IP), # Playlist item F_HeaderLength(), F_TotalLength(), F_ChildCount(), F_Int32(0), - F_Int32((trackid + 0x1337) & 0xFFFF), + F_Int32((trackid + 0x1337) & 0xFFFF), # 1337 is not in the specs... F_Int32(trackid), F_Int32(timestamp), F_Int32(0), @@ -296,12 +296,11 @@ class PlaylistItemRecord(Record): )) self.add(OrderDataObject(order)) - class PlaylistRecord(Record): def __init__(self, name, track_count, order=0, master=0, timestamp=0, plid=None, sort_order=1): if not plid: plid = random.randrange(0, 18446744073709551615) - Record.__init__(self, ( - F_Tag(b"mhyp"), + super().__init__(( + F_Tag(Tags.YP), F_HeaderLength(), F_TotalLength(), F_ChildCount(), @@ -322,7 +321,7 @@ class PlaylistRecord(Record): order = list(range(len(tracklist))) order.sort(key=cmp_to_key(lambda a, b: compare_dict(tracklist[a], tracklist[b], fields))) mhod = Record(( - F_Tag(b"mhod"), + F_Tag(Tags.OD), F_Int32(24), F_TotalLength(), F_Int32(52), @@ -334,74 +333,63 @@ class PlaylistRecord(Record): arr = array.array('L', order) # the array module doesn't directly support endianness, so we detect # the machine's endianness and swap if it is big-endian - if array.array('L', [1]).tobytes()[3] == 1: - arr.byteswap() - data = bytes(arr) - mhod.add(data) + if array.array('L', [1]).tobytes()[3] == 1: arr.byteswap() + mhod.add(arr) self.add(mhod) - def set_playlist(self, track_ids): - for i in range(len(track_ids)): - self.add(PlaylistItemRecord(i+1, track_ids[i]), 0) - - - -################################################################################ -## the toplevel ITDB class ## -################################################################################ + def set_playlist(self, track_ids): [self.add(PlaylistItemRecord(i+1, track_ids[i]), 0) for i in range(len(track_ids))] class iTunesDB: - def __init__(self, tracklist, name="Unnamed", dbid=None, dbversion=0x19): - if not dbid: dbid = random.randrange(0, 18446744073709551615) + def __init__(self, tracklist, name="Unnamed", dbid=None): + if not dbid: dbid = random.randrange(0, 0xffffffffffffffff) # random 64 bit integer self.mhbd = Record(( - F_Tag(b"mhbd"), + F_Tag(Tags.BD), F_HeaderLength(), F_TotalLength(), F_Int32(0), - F_Int32(dbversion), + F_Int32(0x19), F_ChildCount(), F_Int64(dbid), F_Int16(2), F_Padding(14), F_Int16(0), # hash indicator (set later by hash58) F_Padding(20), # first hash - F_Tag(b"en"), # language = 'en' - F_Tag(b"\0rePear!"), # library persistent ID + F_Bytes(b"en"), # language = 'en' + F_Bytes(b"\0rePear!"), # library persistent ID F_Padding(20), # hash58 F_Padding(80) )) self.mhsd = Record(( - F_Tag(b"mhsd"), + F_Tag(Tags.SD), F_HeaderLength(), F_TotalLength(), F_Int32(1), F_Padding(80) )) self.mhlt = Record(( - F_Tag(b"mhlt"), + F_Tag(Tags.LT), F_HeaderLength(), F_ChildCount(), F_Padding(80) )) - for track in tracklist: - self.mhlt.add(TrackItemRecord(track)) + for track in tracklist: self.mhlt.add(TrackItemRecord(track)) self.mhsd.add(self.mhlt) del self.mhlt self.mhbd.add(self.mhsd) self.mhsd = Record(( - F_Tag(b"mhsd"), + F_Tag(Tags.SD), F_HeaderLength(), F_TotalLength(), F_Int32(2), F_Padding(80) )) self.mhlp = Record(( - F_Tag(b"mhlp"), + F_Tag(Tags.LP), F_HeaderLength(), F_ChildCount(), F_Padding(80) @@ -426,12 +414,10 @@ class iTunesDB: del self.mhlp self.mhbd.add(self.mhsd) del self.mhsd - result = self.mhbd.__bytes__() + result = bytes(self.mhbd) del self.mhbd return result - - ################################################################################ ## ArtworkDB / PhotoDB record classes ## ################################################################################ @@ -448,7 +434,6 @@ class RGB565_LE: res[io|1] = (ord(data[ii]) & 0xF8) | (g >> 3) io += 2 return str(res) - convert = staticmethod(convert) ImageFormats = { 'nano': ((1027, 100, 100, RGB565_LE), @@ -491,38 +476,27 @@ class ArtworkFormat: # check if the cache file can be used try: s = os.stat(self.fullname) - use_cache = stat.S_ISREG(s[stat.ST_MODE]) \ - and compare_mtime(cache_info[0], s[stat.ST_MTIME]) \ - and (s[stat.ST_SIZE] == cache_info[1]) - except OSError: - use_cache = False + use_cache = stat.S_ISREG(s[stat.ST_MODE]) and compare_mtime(cache_info[0], s[stat.ST_MTIME]) and (s[stat.ST_SIZE] == cache_info[1]) + except OSError: use_cache = False # load the cache if use_cache: try: - f = open(self.fullname, "rb") - self.cache = f.read() - f.close() - except IOError: - use_cache = False - if not use_cache: - self.cache = None + with open(self.fullname, "rb") as f: self.cache = f.read() + except IOError: use_cache = False + if not use_cache: self.cache = None - # open the destination file - try: - self.f = open(self.fullname, "wb") - except IOError as e: + try: self.f = open(self.fullname, "wb") + except IOError: log("WARNING: Error opening the artwork data file `%s'\n", self.filename) self.f = None def close(self): - if self.f: - self.f.close() + if self.f: self.f.close() try: s = os.stat(self.fullname) cache_info = (s[stat.ST_MTIME], s[stat.ST_SIZE]) - except OSError: - cache_info = (0, 0) + except OSError: cache_info = (0, 0) return (self.fid, cache_info) def GenerateImage(self, image, index, cache_entry=None): @@ -559,10 +533,8 @@ class ArtworkFormat: assert self.f self.f.seek(self.size * index) self.f.write(data) - except IOError: - log(" [WRITE ERROR]", True) + except IOError: log(" [WRITE ERROR]", True) - # return image metadata iinfo = ImageInfo() iinfo.format = self iinfo.index = index @@ -572,19 +544,15 @@ class ArtworkFormat: iinfo.my = my return iinfo - - class ArtworkDBStringDataObject(Record): def __init__(self, mhod_type, content): - if isinstance(content, bytes): - content = content.decode(sys.getfilesystemencoding(), 'replace') - elif not isinstance(content, str): - content = str(content) + if isinstance(content, bytes): content = content.decode(sys.getfilesystemencoding(), 'replace') + elif not isinstance(content, str): content = str(content) content = content.encode('utf_16_le', 'replace') padding = len(content) % 4 if padding: padding = 4 - padding - Record.__init__(self, ( - F_Tag(b"mhod"), + super().__init__(( + F_Tag(Tags.OD), F_Int32(0x18), F_TotalLength(), F_Int16(mhod_type), @@ -595,14 +563,13 @@ class ArtworkDBStringDataObject(Record): F_Int32(0) )) self.add(content) - if padding: - self.add("\0" * padding) + if padding: self.add("\0" * padding) class ImageDataObject(Record): def __init__(self, iinfo): - Record.__init__(self, ( - F_Tag(b"mhod"), + super().__init__( ( + F_Tag(Tags.OD), F_Int32(0x18), F_TotalLength(), F_Int32(2), @@ -610,7 +577,7 @@ class ImageDataObject(Record): )) mhni = Record(( - F_Tag(b"mhni"), + F_Tag(Tags.NI), F_Int32(0x4C), F_TotalLength(), F_ChildCount(), @@ -626,15 +593,14 @@ class ImageDataObject(Record): F_Padding(32) )) - mhod = ArtworkDBStringDataObject(3, ":" + iinfo.format.filename) - mhni.add(mhod) + mhni.add(ArtworkDBStringDataObject(3, ":" + iinfo.format.filename)) self.add(mhni) class ImageItemRecord(Record): def __init__(self, img_id, dbid, iinfo_list, orig_size=0): - Record.__init__(self, ( - F_Tag(b"mhii"), + super().__init__( ( + F_Tag(Tags.II), F_Int32(0x98), F_TotalLength(), F_ChildCount(), @@ -644,37 +610,31 @@ class ImageItemRecord(Record): F_Int32(orig_size), F_Padding(100) )) - - for iinfo in iinfo_list: - self.add(ImageDataObject(iinfo)) + for iinfo in iinfo_list: self.add(ImageDataObject(iinfo)) def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): - while isinstance(ImageFormats.get(model, None), str): - model = ImageFormats[model] - if not model in ImageFormats: - return None + while isinstance(ImageFormats.get(model, None), str): model = ImageFormats[model] + if not model in ImageFormats: return None format_cache, image_cache = cache_data formats = [] for descriptor in ImageFormats[model]: - formats.append(ArtworkFormat(descriptor, - cache_info = format_cache.get(descriptor[0], (0,0)))) + formats.append(ArtworkFormat(descriptor, cache_info = format_cache.get(descriptor[0], (0,0)))) # if there's at least one format whose image file isn't cache-clean, # invalidate the cache - if not formats[-1].cache: - image_cache = {} + if not formats[-1].cache: image_cache = {} # Image List mhsd = Record(( - F_Tag(b"mhsd"), + F_Tag(Tags.SD), F_HeaderLength(), F_TotalLength(), F_Int32(1), F_Padding(80) )) mhli = Record(( - F_Tag(b"mhli"), + F_Tag(Tags.LI), F_HeaderLength(), F_ChildCount(), F_Padding(80) @@ -689,8 +649,7 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): log(source, False) # stat this image - try: - s = os.stat(source) + try: s = os.stat(source) except OSError as e: log(" [Error: %s]\n" % e.strerror, True) continue @@ -746,7 +705,7 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): # Date File Header mhfd = Record(( - F_Tag(b"mhfd"), + F_Tag(Tags.FD), F_HeaderLength(), F_TotalLength(), F_Int32(0), @@ -764,14 +723,14 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): # Album List (dummy) mhsd = Record(( - F_Tag(b"mhsd"), + F_Tag(Tags.SD), F_HeaderLength(), F_TotalLength(), F_Int32(2), F_Padding(80) )) mhsd.add(Record(( - F_Tag(b"mhla"), + F_Tag(Tags.LA), F_HeaderLength(), F_Int32(0), F_Padding(80) @@ -780,7 +739,7 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): # File List mhsd = Record(( - F_Tag(b"mhsd"), + F_Tag(Tags.SD), F_HeaderLength(), F_TotalLength(), F_Int32(3), @@ -788,7 +747,7 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): )) mhlf = Record(( - F_Tag(b"mhlf"), + F_Tag(Tags.LF), F_HeaderLength(), F_Int32(len(formats)), F_Padding(80) @@ -796,7 +755,7 @@ def ArtworkDB(model, imagelist, base_id=0x40, cache_data=({}, {})): for format in formats: mhlf.add(Record(( - F_Tag(b"mhif"), + F_Tag(Tags.IF), F_HeaderLength(), F_TotalLength(), F_Int32(0), @@ -834,91 +793,67 @@ class InvalidFormat(Exception): pass class DatabaseReader: def __init__(self, f="iPod_Control/iTunes/iTunesDB"): - if isinstance(f, str): - f = open(f, "rb") + if isinstance(f, str): f = open(f, "rb") self.f = f - self._skip_header("mhbd") + self._skip_header(Tags.BD) while True: - h = self._skip_header("mhsd") - if len(h) < 16: - raise InvalidFormat + h = self._skip_header(Tags.SD) + if len(h) < 16: raise InvalidFormat size, mhsd_type = struct.unpack(' yeah! - if size < len(h): - raise InvalidFormat + if mhsd_type == 1: break # found the mhlt entry -> yeah! + if size < len(h): raise InvalidFormat self.f.seek(size - len(h), 1) - self._skip_header("mhlt") + self._skip_header(Tags.LT) - def _skip_header(self, tag): # a little helper function + def _skip_header(self, tag: Tags): hh = self.f.read(8) - if (len(hh) != 8) or (hh[:4] != tag): - raise InvalidFormat + if (len(hh) != 8) or (hh[:4] != tag.value.encode()): raise InvalidFormat size = struct.unpack(' 40) and (data[:4] == "mhod"): + while (len(data) > 40) and (data[:4] == b"mhod"): size, mhod_type = struct.unpack('> 16, (x >> 8) & 0xFF, x & 0xFF) - -SD_type_map = { "aac": 2, "mp4a": 2, "wave": 4} - -def MakeSDEntry(info): - path = info['path'] - - if isinstance(path, bytes): - path = path.decode(sys.getfilesystemencoding(), 'replace') - elif not isinstance(path, str): - path = str(path) - - path_bytes = ('/' + path).encode("utf_16_le", 'replace') - - return b"\0\x02\x2E\x5A\xA5\x01" + (20 * b"\0") + b"\x64\0\0" + bytes([SD_type_map.get(info.get('type', None), 1)]) + b"\0\x02\0" + \ - path_bytes + (261 * 2 - len(path_bytes)) * b"\0" + bytes([info.get('shuffle flag', 1), info.get('bookmark flag', 0), 0]) - - -def iTunesSD(tracklist): - header = b"\0\x02\x2E\x5A\xA5\x01" + (20*b"\0") + b"\x64\0\0\0x01\0\0x02\0" - return be3(len(tracklist)) + b"\x01\x06\0\0\0\x12" + (9*b"\0") + \ - b"".join(map(MakeSDEntry, tracklist)) - - -################################################################################ -## some useful helper functions for "fine tuning" of track lists ## -################################################################################ - def GenerateIDs(tracklist): trackid = random.randint(0, (0xFFFF-0x1337) - len(tracklist)) - dbid = random.randrange(0, 18446744073709551615 - len(tracklist)) + dbid = random.randrange(0, 0xffffffffffffffff - len(tracklist)) for track in tracklist: track['id'] = trackid track['dbid'] = dbid trackid += 1 dbid += 1 - def GuessTitleAndArtist(filename): info = {} filename = os.path.split(filename)[1] @@ -989,39 +889,19 @@ def GuessTitleAndArtist(filename): filename = filename[i+1:] break parts = filename.split(' - ', 1) - if len(parts)==2: + if len(parts) == 2: info['artist'] = parts[0].strip() info['title'] = parts[1].strip(" -\r\n\t\v") - else: - info['title'] = filename.strip() + else: info['title'] = filename.strip() return info def FillMissingTitleAndArtist(track_or_list): if isinstance(track_or_list, list): - for track in track_or_list: - FillMissingTitleAndArtist(track) + for track in track_or_list: FillMissingTitleAndArtist(track) else: if track_or_list.get('title',None) and track_or_list.get('artist',None): return # no need to do something, it's fine already guess = GuessTitleAndArtist(track_or_list['path']) for key in ('title', 'artist', 'track number'): if not(track_or_list.get(key,None)) and guess.get(key,None): - track_or_list[key] = guess[key] - - -################################################################################ -## some additional general purpose helper functions ## -################################################################################ - -def ASCIIMap(c): - if ord(c) < 32: return "." - if ord(c) == 127: return "." - return c - -def DisplayTitle(info): - s = info.get('title', "") - if 'album' in info: s = "%s -> %s" % ((info['album']), s) - if 'artist' in info: s = "%s: %s" % ((info['artist']), s) - q = [str((info[key])) for key in ('genre','year') if key in info] - if q: s = "%s [%s]" % (s, ", ".join(q)) - return s \ No newline at end of file + track_or_list[key] = guess[key] \ No newline at end of file diff --git a/repear.log b/repear.log deleted file mode 100644 index 787bea3..0000000 --- a/repear.log +++ /dev/null @@ -1,10 +0,0 @@ -Welcome to rePear, version 0.4.1 --------------------------------- - -iPod root directory is `F:/' - -Moving tracks back to their original locations ... -s2/Sade - Your Love Is King.mp3 [OK] -Operation complete: 1 tracks total, 1 moved back, 0 failed. - -You can now manage the music files on your iPod. diff --git a/repear.py b/repear.py index 956754f..7565b59 100644 --- a/repear.py +++ b/repear.py @@ -42,10 +42,6 @@ from pathlib import Path import iTunesDB, mp3info, hash58 Options = {} -################################################################################ -## Some internal management functions ## -################################################################################ - broken_log = False homedir = "" logfile = None @@ -1369,24 +1365,6 @@ NOTE: The database is already frozen. "ERROR: The iTunesDB file could not be written. This means that the iPod will\n" + "not play anything.\n") - # write iPod shuffle stuff (if necessary) - if os.path.exists(CONTROL_DIR + "iTunesSD"): - backup(CONTROL_DIR + "iTunesSD") - log("Creating iTunesSD ... ", True) - db = iTunesDB.iTunesSD(tracklist) - try: - f = open(CONTROL_DIR + "iTunesSD", "wb") - f.write(db) - f.close() - log("\n") - except IOError as e: - write_ok = False - log("FAILED: %s\n" % e.strerror + - "ERROR: The iTunesSD file could not be written. This means that the iPod will\n" + - "not play anything.\n") - delete(CONTROL_DIR + "iTunesShuffle") - delete(CONTROL_DIR + "iTunesPState") - # generate statistics if write_ok: log("\nYou can now unmount the iPod and listen to your music.\n")