iopenpod 1.0.47__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ArtworkDB_Parser/__init__.py +3 -0
- ArtworkDB_Parser/chunk_parser.py +66 -0
- ArtworkDB_Parser/constants.py +37 -0
- ArtworkDB_Parser/mhfd_parser.py +61 -0
- ArtworkDB_Parser/mhii_parser.py +52 -0
- ArtworkDB_Parser/mhli_parser.py +16 -0
- ArtworkDB_Parser/mhni_parser.py +196 -0
- ArtworkDB_Parser/mhod_parser.py +73 -0
- ArtworkDB_Parser/mhsd_parser.py +16 -0
- ArtworkDB_Parser/parser.py +15 -0
- ArtworkDB_Writer/__init__.py +63 -0
- ArtworkDB_Writer/art_extractor.py +303 -0
- ArtworkDB_Writer/artwork_writer.py +1139 -0
- ArtworkDB_Writer/ithmb_codecs.py +631 -0
- ArtworkDB_Writer/rgb565.py +250 -0
- GUI/__init__.py +3 -0
- GUI/app.py +1871 -0
- GUI/auto_updater.py +590 -0
- GUI/fonts.py +88 -0
- GUI/glyphs.py +104 -0
- GUI/hidpi.py +79 -0
- GUI/imgMaker.py +588 -0
- GUI/ipod_images.py +68 -0
- GUI/notifications.py +121 -0
- GUI/styles.py +1847 -0
- GUI/widgets/MBGridView.py +504 -0
- GUI/widgets/MBGridViewItem.py +180 -0
- GUI/widgets/MBListView.py +2878 -0
- GUI/widgets/__init__.py +0 -0
- GUI/widgets/backupBrowser.py +1245 -0
- GUI/widgets/browserChrome.py +115 -0
- GUI/widgets/devicePicker.py +444 -0
- GUI/widgets/dropOverlay.py +107 -0
- GUI/widgets/formatters.py +286 -0
- GUI/widgets/gridHeaderBar.py +141 -0
- GUI/widgets/musicBrowser.py +399 -0
- GUI/widgets/photoBrowser.py +1045 -0
- GUI/widgets/photoTile.py +190 -0
- GUI/widgets/photoViewer.py +358 -0
- GUI/widgets/playlistBrowser.py +1426 -0
- GUI/widgets/playlistEditor.py +1387 -0
- GUI/widgets/podcastBrowser.py +1856 -0
- GUI/widgets/podcastSearchDialog.py +343 -0
- GUI/widgets/scrollingLabel.py +96 -0
- GUI/widgets/selectiveSyncBrowser.py +1839 -0
- GUI/widgets/settingsPage.py +2533 -0
- GUI/widgets/sidebar.py +1096 -0
- GUI/widgets/syncReview.py +2828 -0
- GUI/widgets/trackListTitleBar.py +218 -0
- PodcastManager/__init__.py +1 -0
- PodcastManager/downloader.py +481 -0
- PodcastManager/feed_parser.py +287 -0
- PodcastManager/itunes_search.py +89 -0
- PodcastManager/models.py +241 -0
- PodcastManager/podcast_sync.py +629 -0
- PodcastManager/subscription_store.py +193 -0
- SQLiteDB_Writer/__init__.py +21 -0
- SQLiteDB_Writer/_helpers.py +64 -0
- SQLiteDB_Writer/cbk_writer.py +171 -0
- SQLiteDB_Writer/dynamic_writer.py +124 -0
- SQLiteDB_Writer/extras_writer.py +82 -0
- SQLiteDB_Writer/genius_writer.py +56 -0
- SQLiteDB_Writer/library_writer.py +1184 -0
- SQLiteDB_Writer/locations_writer.py +145 -0
- SQLiteDB_Writer/sqlite_writer.py +211 -0
- SyncEngine/__init__.py +135 -0
- SyncEngine/_db_io.py +244 -0
- SyncEngine/_formats.py +60 -0
- SyncEngine/_playlist_builder.py +397 -0
- SyncEngine/_track_conversion.py +388 -0
- SyncEngine/audio_fingerprint.py +454 -0
- SyncEngine/backup_manager.py +1268 -0
- SyncEngine/contracts.py +114 -0
- SyncEngine/dependency_manager.py +292 -0
- SyncEngine/fingerprint_diff_engine.py +1604 -0
- SyncEngine/integrity.py +328 -0
- SyncEngine/itunes_prefs.py +563 -0
- SyncEngine/mapping.py +389 -0
- SyncEngine/pc_library.py +1499 -0
- SyncEngine/photos.py +2092 -0
- SyncEngine/playlist_parser.py +181 -0
- SyncEngine/quick_writes.py +538 -0
- SyncEngine/scrobbler.py +590 -0
- SyncEngine/spl_evaluator.py +567 -0
- SyncEngine/sync_executor.py +2090 -0
- SyncEngine/transcode_cache.py +692 -0
- SyncEngine/transcoder.py +1274 -0
- app_core/__init__.py +122 -0
- app_core/bootstrap.py +220 -0
- app_core/context.py +157 -0
- app_core/controllers.py +335 -0
- app_core/device_identity.py +110 -0
- app_core/errors.py +19 -0
- app_core/jobs.py +1765 -0
- app_core/progress.py +210 -0
- app_core/runtime.py +928 -0
- app_core/services.py +440 -0
- app_core/sync_options.py +13 -0
- app_core/sync_plan_builder.py +203 -0
- app_core/sync_review_model.py +161 -0
- assets/fonts/NotoEmoji-Regular.ttf +0 -0
- assets/fonts/NotoSans-Italic.ttf +0 -0
- assets/fonts/NotoSans-Regular.ttf +0 -0
- assets/fonts/NotoSansMono-Regular.ttf +0 -0
- assets/fonts/NotoSansSymbols2-Regular.ttf +0 -0
- assets/fonts/README.md +17 -0
- assets/glyphs/advisory-clean.svg +5 -0
- assets/glyphs/advisory-explicit.svg +8 -0
- assets/glyphs/annotation-dots.svg +6 -0
- assets/glyphs/annotation-warning.svg +5 -0
- assets/glyphs/archive.svg +5 -0
- assets/glyphs/arrow-down.svg +4 -0
- assets/glyphs/arrow-left.svg +4 -0
- assets/glyphs/arrow-right.svg +4 -0
- assets/glyphs/arrow-up.svg +4 -0
- assets/glyphs/book.svg +4 -0
- assets/glyphs/box.svg +7 -0
- assets/glyphs/broadcast.svg +8 -0
- assets/glyphs/chart.svg +6 -0
- assets/glyphs/check-circle.svg +4 -0
- assets/glyphs/check.svg +3 -0
- assets/glyphs/chevron-down.svg +3 -0
- assets/glyphs/chevron-left.svg +3 -0
- assets/glyphs/chevron-right.svg +3 -0
- assets/glyphs/chevron-up.svg +3 -0
- assets/glyphs/clock.svg +4 -0
- assets/glyphs/close-circle.svg +5 -0
- assets/glyphs/close.svg +4 -0
- assets/glyphs/cloud.svg +3 -0
- assets/glyphs/code.svg +4 -0
- assets/glyphs/download.svg +5 -0
- assets/glyphs/edit.svg +4 -0
- assets/glyphs/eject.svg +4 -0
- assets/glyphs/film.svg +10 -0
- assets/glyphs/filter.svg +3 -0
- assets/glyphs/flag.svg +3 -0
- assets/glyphs/floppy-disc.svg +5 -0
- assets/glyphs/folder.svg +4 -0
- assets/glyphs/grid.svg +6 -0
- assets/glyphs/heart.svg +3 -0
- assets/glyphs/home.svg +4 -0
- assets/glyphs/minus.svg +3 -0
- assets/glyphs/monitor.svg +4 -0
- assets/glyphs/music.svg +5 -0
- assets/glyphs/photo.svg +3 -0
- assets/glyphs/plus.svg +4 -0
- assets/glyphs/refresh.svg +6 -0
- assets/glyphs/search.svg +3 -0
- assets/glyphs/settings-sliders.svg +8 -0
- assets/glyphs/settings.svg +4 -0
- assets/glyphs/shield-warning.svg +5 -0
- assets/glyphs/spinner.svg +1 -0
- assets/glyphs/star.svg +3 -0
- assets/glyphs/tablet.svg +4 -0
- assets/glyphs/trash.svg +5 -0
- assets/glyphs/user.svg +4 -0
- assets/glyphs/video.svg +4 -0
- assets/glyphs/warning-triangle.svg +5 -0
- assets/glyphs/wifi-no-connection.svg +7 -0
- assets/glyphs/wifi.svg +6 -0
- assets/icons/icon-128.png +0 -0
- assets/icons/icon-16.png +0 -0
- assets/icons/icon-24.png +0 -0
- assets/icons/icon-256.png +0 -0
- assets/icons/icon-32.png +0 -0
- assets/icons/icon-48.png +0 -0
- assets/icons/icon-64.png +0 -0
- assets/icons/icon.ico +0 -0
- assets/ipod_images/iPod1.png +0 -0
- assets/ipod_images/iPod11-Black.png +0 -0
- assets/ipod_images/iPod11-Silver.png +0 -0
- assets/ipod_images/iPod11B-Black.png +0 -0
- assets/ipod_images/iPod12-Black.png +0 -0
- assets/ipod_images/iPod12-Blue.png +0 -0
- assets/ipod_images/iPod12-Green.png +0 -0
- assets/ipod_images/iPod12-Pink.png +0 -0
- assets/ipod_images/iPod12-Red.png +0 -0
- assets/ipod_images/iPod12-Silver.png +0 -0
- assets/ipod_images/iPod128.png +0 -0
- assets/ipod_images/iPod130-Blue.png +0 -0
- assets/ipod_images/iPod130-Green.png +0 -0
- assets/ipod_images/iPod130-Orange.png +0 -0
- assets/ipod_images/iPod130-Pink.png +0 -0
- assets/ipod_images/iPod130-Silver.png +0 -0
- assets/ipod_images/iPod130C-Blue.png +0 -0
- assets/ipod_images/iPod130C-Green.png +0 -0
- assets/ipod_images/iPod130C-Purple.png +0 -0
- assets/ipod_images/iPod130C-Red.png +0 -0
- assets/ipod_images/iPod130F-Blue.png +0 -0
- assets/ipod_images/iPod130F-Gold.png +0 -0
- assets/ipod_images/iPod130F-Green.png +0 -0
- assets/ipod_images/iPod130F-Pink.png +0 -0
- assets/ipod_images/iPod130F-Red.png +0 -0
- assets/ipod_images/iPod132-Blue.png +0 -0
- assets/ipod_images/iPod132-DarkGray.png +0 -0
- assets/ipod_images/iPod132-Green.png +0 -0
- assets/ipod_images/iPod132-Pink.png +0 -0
- assets/ipod_images/iPod132-Silver.png +0 -0
- assets/ipod_images/iPod132B-Silver.png +0 -0
- assets/ipod_images/iPod133-Blue.png +0 -0
- assets/ipod_images/iPod133-Green.png +0 -0
- assets/ipod_images/iPod133-Orange.png +0 -0
- assets/ipod_images/iPod133-Pink.png +0 -0
- assets/ipod_images/iPod133-Silver.png +0 -0
- assets/ipod_images/iPod133B-Blue.png +0 -0
- assets/ipod_images/iPod133B-DarkGray.png +0 -0
- assets/ipod_images/iPod133B-Green.png +0 -0
- assets/ipod_images/iPod133B-Pink.png +0 -0
- assets/ipod_images/iPod133B-Purple.png +0 -0
- assets/ipod_images/iPod133B-Red.png +0 -0
- assets/ipod_images/iPod133B-Silver.png +0 -0
- assets/ipod_images/iPod133B-SpaceGray.png +0 -0
- assets/ipod_images/iPod133B-Yellow.png +0 -0
- assets/ipod_images/iPod133D-Blue.png +0 -0
- assets/ipod_images/iPod133D-Gold.png +0 -0
- assets/ipod_images/iPod133D-Pink.png +0 -0
- assets/ipod_images/iPod133D-Red.png +0 -0
- assets/ipod_images/iPod133D-Silver.png +0 -0
- assets/ipod_images/iPod133D-SpaceGray.png +0 -0
- assets/ipod_images/iPod15-Black.png +0 -0
- assets/ipod_images/iPod15-Blue.png +0 -0
- assets/ipod_images/iPod15-Green.png +0 -0
- assets/ipod_images/iPod15-Orange.png +0 -0
- assets/ipod_images/iPod15-Pink.png +0 -0
- assets/ipod_images/iPod15-Purple.png +0 -0
- assets/ipod_images/iPod15-Red.png +0 -0
- assets/ipod_images/iPod15-Silver.png +0 -0
- assets/ipod_images/iPod15-Yellow.png +0 -0
- assets/ipod_images/iPod16-Black.png +0 -0
- assets/ipod_images/iPod16-Blue.png +0 -0
- assets/ipod_images/iPod16-Green.png +0 -0
- assets/ipod_images/iPod16-Orange.png +0 -0
- assets/ipod_images/iPod16-Pink.png +0 -0
- assets/ipod_images/iPod16-Purple.png +0 -0
- assets/ipod_images/iPod16-Red.png +0 -0
- assets/ipod_images/iPod16-Silver.png +0 -0
- assets/ipod_images/iPod16-Yellow.png +0 -0
- assets/ipod_images/iPod17-Blue.png +0 -0
- assets/ipod_images/iPod17-DarkGray.png +0 -0
- assets/ipod_images/iPod17-Green.png +0 -0
- assets/ipod_images/iPod17-Orange.png +0 -0
- assets/ipod_images/iPod17-Pink.png +0 -0
- assets/ipod_images/iPod17-Red.png +0 -0
- assets/ipod_images/iPod17-Silver.png +0 -0
- assets/ipod_images/iPod18-Blue.png +0 -0
- assets/ipod_images/iPod18-DarkGray.png +0 -0
- assets/ipod_images/iPod18-Green.png +0 -0
- assets/ipod_images/iPod18-Pink.png +0 -0
- assets/ipod_images/iPod18-Purple.png +0 -0
- assets/ipod_images/iPod18-Red.png +0 -0
- assets/ipod_images/iPod18-Silver.png +0 -0
- assets/ipod_images/iPod18-SpaceGray.png +0 -0
- assets/ipod_images/iPod18-Yellow.png +0 -0
- assets/ipod_images/iPod18A-Blue.png +0 -0
- assets/ipod_images/iPod18A-Gold.png +0 -0
- assets/ipod_images/iPod18A-Pink.png +0 -0
- assets/ipod_images/iPod18A-Red.png +0 -0
- assets/ipod_images/iPod18A-Silver.png +0 -0
- assets/ipod_images/iPod18A-SpaceGray.png +0 -0
- assets/ipod_images/iPod2.png +0 -0
- assets/ipod_images/iPod3-Blue.png +0 -0
- assets/ipod_images/iPod3-Gold.png +0 -0
- assets/ipod_images/iPod3-Green.png +0 -0
- assets/ipod_images/iPod3-Pink.png +0 -0
- assets/ipod_images/iPod3-Silver.png +0 -0
- assets/ipod_images/iPod3B-Blue.png +0 -0
- assets/ipod_images/iPod3B-Green.png +0 -0
- assets/ipod_images/iPod3B-Pink.png +0 -0
- assets/ipod_images/iPod4-BlackRed.png +0 -0
- assets/ipod_images/iPod4-White.png +0 -0
- assets/ipod_images/iPod5-BlackRed.png +0 -0
- assets/ipod_images/iPod5-White.png +0 -0
- assets/ipod_images/iPod6-Black.png +0 -0
- assets/ipod_images/iPod6-BlackRed.png +0 -0
- assets/ipod_images/iPod6-White.png +0 -0
- assets/ipod_images/iPod7-Black.png +0 -0
- assets/ipod_images/iPod7-White.png +0 -0
- assets/ipod_images/iPod9-Black.png +0 -0
- assets/ipod_images/iPod9-Blue.png +0 -0
- assets/ipod_images/iPod9-Green.png +0 -0
- assets/ipod_images/iPod9-Pink.png +0 -0
- assets/ipod_images/iPod9-Red.png +0 -0
- assets/ipod_images/iPod9-Silver.png +0 -0
- assets/ipod_images/iPodGeneric.png +0 -0
- iTunesDB_Parser/__init__.py +22 -0
- iTunesDB_Parser/_parsing.py +69 -0
- iTunesDB_Parser/chunk_parser.py +143 -0
- iTunesDB_Parser/exceptions.py +59 -0
- iTunesDB_Parser/ipod_library.py +134 -0
- iTunesDB_Parser/mhbd_parser.py +38 -0
- iTunesDB_Parser/mhia_parser.py +26 -0
- iTunesDB_Parser/mhii_parser.py +29 -0
- iTunesDB_Parser/mhip_parser.py +26 -0
- iTunesDB_Parser/mhit_parser.py +29 -0
- iTunesDB_Parser/mhod_parser.py +612 -0
- iTunesDB_Parser/mhsd_parser.py +29 -0
- iTunesDB_Parser/mhyp_parser.py +33 -0
- iTunesDB_Parser/otg.py +187 -0
- iTunesDB_Parser/parser.py +112 -0
- iTunesDB_Parser/playcounts.py +260 -0
- iTunesDB_Shared/__init__.py +33 -0
- iTunesDB_Shared/constants.py +378 -0
- iTunesDB_Shared/extraction.py +99 -0
- iTunesDB_Shared/field_base.py +414 -0
- iTunesDB_Shared/mhbd_defs.py +48 -0
- iTunesDB_Shared/mhia_defs.py +21 -0
- iTunesDB_Shared/mhii_defs.py +18 -0
- iTunesDB_Shared/mhip_defs.py +26 -0
- iTunesDB_Shared/mhit_defs.py +141 -0
- iTunesDB_Shared/mhod_defs.py +586 -0
- iTunesDB_Shared/mhsd_defs.py +15 -0
- iTunesDB_Shared/mhyp_defs.py +35 -0
- iTunesDB_Writer/__init__.py +148 -0
- iTunesDB_Writer/hash58.py +280 -0
- iTunesDB_Writer/hash72.py +476 -0
- iTunesDB_Writer/hashab.py +287 -0
- iTunesDB_Writer/mhbd_writer.py +1151 -0
- iTunesDB_Writer/mhip_writer.py +138 -0
- iTunesDB_Writer/mhit_writer.py +385 -0
- iTunesDB_Writer/mhla_writer.py +217 -0
- iTunesDB_Writer/mhli_writer.py +136 -0
- iTunesDB_Writer/mhlp_writer.py +238 -0
- iTunesDB_Writer/mhlt_writer.py +59 -0
- iTunesDB_Writer/mhod52_writer.py +334 -0
- iTunesDB_Writer/mhod_spl_writer.py +283 -0
- iTunesDB_Writer/mhod_writer.py +434 -0
- iTunesDB_Writer/mhsd_writer.py +101 -0
- iTunesDB_Writer/mhyp_writer.py +538 -0
- iTunesDB_Writer/wasm/calcHashAB.wasm +0 -0
- infrastructure/__init__.py +1 -0
- infrastructure/settings_paths.py +83 -0
- infrastructure/settings_persistence.py +93 -0
- infrastructure/settings_runtime.py +441 -0
- infrastructure/settings_schema.py +94 -0
- infrastructure/settings_secrets.py +127 -0
- infrastructure/version.py +18 -0
- iopenpod-1.0.47.dist-info/METADATA +234 -0
- iopenpod-1.0.47.dist-info/RECORD +363 -0
- iopenpod-1.0.47.dist-info/WHEEL +4 -0
- iopenpod-1.0.47.dist-info/entry_points.txt +2 -0
- iopenpod-1.0.47.dist-info/licenses/LICENSE +21 -0
- ipod_device/__init__.py +131 -0
- ipod_device/artwork.py +173 -0
- ipod_device/artwork_presets.py +114 -0
- ipod_device/authority.py +671 -0
- ipod_device/capabilities.py +737 -0
- ipod_device/checksum.py +41 -0
- ipod_device/diagnostic_log.py +178 -0
- ipod_device/dump.py +496 -0
- ipod_device/eject.py +1007 -0
- ipod_device/images.py +409 -0
- ipod_device/info.py +2408 -0
- ipod_device/lookup.py +171 -0
- ipod_device/models.py +509 -0
- ipod_device/scanner.py +2496 -0
- ipod_device/sysinfo.py +528 -0
- ipod_device/usb_backend.py +132 -0
- ipod_device/vpd_iokit.py +618 -0
- ipod_device/vpd_libusb.py +1354 -0
- ipod_device/vpd_linux.py +267 -0
- ipod_device/vpd_usb_control.py +215 -0
- ipod_device/vpd_windows.py +310 -0
- main.py +9 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_chunk(data, offset) -> dict[str, Any]:
|
|
6
|
+
chunk_type = data[offset: offset + 4].decode("utf-8")
|
|
7
|
+
header_length = struct.unpack("<I", data[offset + 4: offset + 8])[0]
|
|
8
|
+
chunk_length = struct.unpack("<I", data[offset + 8: offset + 12])[0]
|
|
9
|
+
|
|
10
|
+
match chunk_type:
|
|
11
|
+
case "mhfd":
|
|
12
|
+
# Data file
|
|
13
|
+
from .mhfd_parser import parse_mhfd
|
|
14
|
+
|
|
15
|
+
result = parse_mhfd(data, offset, header_length, chunk_length)
|
|
16
|
+
return result
|
|
17
|
+
case "mhsd":
|
|
18
|
+
# Data Set
|
|
19
|
+
from .mhsd_parser import parse_mhsd
|
|
20
|
+
|
|
21
|
+
result = parse_mhsd(data, offset, header_length, chunk_length)
|
|
22
|
+
return result
|
|
23
|
+
case "mhli":
|
|
24
|
+
# Image List
|
|
25
|
+
from .mhli_parser import parse_mhli
|
|
26
|
+
|
|
27
|
+
result = parse_mhli(data, offset, header_length, chunk_length)
|
|
28
|
+
return result
|
|
29
|
+
case "mhii":
|
|
30
|
+
# Image Item
|
|
31
|
+
from .mhii_parser import parse_imageItem
|
|
32
|
+
|
|
33
|
+
result = parse_imageItem(data, offset, header_length, chunk_length)
|
|
34
|
+
return result
|
|
35
|
+
case "mhni":
|
|
36
|
+
# Image Name
|
|
37
|
+
from .mhni_parser import parse_mhni
|
|
38
|
+
|
|
39
|
+
result = parse_mhni(data, offset, header_length, chunk_length)
|
|
40
|
+
return result
|
|
41
|
+
case "mhla":
|
|
42
|
+
# Photo Album List
|
|
43
|
+
return {}
|
|
44
|
+
case "mhba":
|
|
45
|
+
# Photo Album
|
|
46
|
+
return {}
|
|
47
|
+
case "mhia":
|
|
48
|
+
# Photo Album Item
|
|
49
|
+
return {}
|
|
50
|
+
case "mhlf":
|
|
51
|
+
# File List
|
|
52
|
+
return {}
|
|
53
|
+
case "mhif":
|
|
54
|
+
# File Item
|
|
55
|
+
return {}
|
|
56
|
+
case "mhod":
|
|
57
|
+
# Data Object
|
|
58
|
+
from .mhod_parser import parse_mhod
|
|
59
|
+
|
|
60
|
+
result = parse_mhod(data, offset, header_length, chunk_length)
|
|
61
|
+
return result
|
|
62
|
+
case "mhaf":
|
|
63
|
+
# Unknown
|
|
64
|
+
return {}
|
|
65
|
+
case _:
|
|
66
|
+
raise ValueError(f"Unknown chunk type: {chunk_type}")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# maps the id used in mhsd to the proper header marker
|
|
2
|
+
chunk_type_map = {
|
|
3
|
+
1: "mhli", # Image List chunk
|
|
4
|
+
2: "mhla", # Photo ALbum List chunk
|
|
5
|
+
3: "mhlf", # File List Chunk
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
# maps the chunk header marker to a readable name
|
|
9
|
+
# the identifier appears to be backward, I estimate that it should read like
|
|
10
|
+
# DataBaseHeaderMarker(DBHM) and DataStructureHeaderMarker(DSHM) and
|
|
11
|
+
# TrackListHeaderMarker(TLHM)...
|
|
12
|
+
identifier_readable_map = {
|
|
13
|
+
"mhfd": "Data File",
|
|
14
|
+
"mhsd": "Data Set",
|
|
15
|
+
"mhli": "Image List",
|
|
16
|
+
"mhii": "Image Item",
|
|
17
|
+
"mhni": "Image Name",
|
|
18
|
+
"mhla": "Photo Album List",
|
|
19
|
+
"mhba": "Photo Album",
|
|
20
|
+
"mhia": "Photo Album Item",
|
|
21
|
+
"mhlf": "File List",
|
|
22
|
+
"mhif": "File List Item",
|
|
23
|
+
"mhod": "Data Object",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# maps the mhod type to its readable name
|
|
27
|
+
# There are 2 groups of types of MHODs in the ArtworkDB:
|
|
28
|
+
# container MHODs contain a MHNI as a child,
|
|
29
|
+
# while 'normal' string MHODs contain a string.
|
|
30
|
+
|
|
31
|
+
mhod_type_map = {
|
|
32
|
+
1: {"type": "String", "name": "Album Name"},
|
|
33
|
+
2: {"type": "Container", "name": "Thumbnail Image"},
|
|
34
|
+
3: {"type": "String", "name": "File Name"},
|
|
35
|
+
5: {"type": "Container", "name": "Full Res Image"},
|
|
36
|
+
6: {"type": "Container", "name": "UNK MHOD 6"},
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import struct
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_mhfd(data, offset, header_length, chunk_length) -> dict[str, Any]:
|
|
7
|
+
from .chunk_parser import parse_chunk
|
|
8
|
+
from .constants import chunk_type_map
|
|
9
|
+
|
|
10
|
+
datafile: dict[str, Any] = {}
|
|
11
|
+
|
|
12
|
+
datafile["unk1"] = struct.unpack(
|
|
13
|
+
"<I", data[offset + 12:offset + 16])[0] # always 0
|
|
14
|
+
datafile["unk2"] = struct.unpack(
|
|
15
|
+
"<I", data[offset + 16:offset + 20])[0] # 1 until iTunes 4.9. 2 after
|
|
16
|
+
|
|
17
|
+
datafile["childCount"] = struct.unpack("<I", data[offset + 20:offset + 24])[0]
|
|
18
|
+
|
|
19
|
+
datafile["unk3"] = struct.unpack(
|
|
20
|
+
"<I", data[offset + 24:offset + 28])[0] # always 0
|
|
21
|
+
|
|
22
|
+
# ID of last mhii + 1. On iPod video seems to be last mhii + mhba count + 1
|
|
23
|
+
datafile["next_mhii_id"] = struct.unpack(
|
|
24
|
+
"<I", data[offset + 28:offset + 32])[0]
|
|
25
|
+
|
|
26
|
+
datafile["unk4"] = struct.unpack("<Q", data[offset + 32:offset + 40])[0]
|
|
27
|
+
datafile["unk5"] = struct.unpack("<Q", data[offset + 40:offset + 48])[0]
|
|
28
|
+
|
|
29
|
+
datafile["unk6"] = struct.unpack(
|
|
30
|
+
"<I", data[offset + 48:offset + 52])[0] # always 2
|
|
31
|
+
datafile["unk7"] = struct.unpack(
|
|
32
|
+
"<I", data[offset + 52:offset + 56])[0] # always 0
|
|
33
|
+
datafile["unk8"] = struct.unpack(
|
|
34
|
+
"<I", data[offset + 56:offset + 60])[0] # always 0
|
|
35
|
+
|
|
36
|
+
datafile["unk9"] = struct.unpack("<I", data[offset + 60:offset + 64])[0]
|
|
37
|
+
datafile["unk10"] = struct.unpack("<I", data[offset + 64:offset + 68])[0]
|
|
38
|
+
|
|
39
|
+
# parse children
|
|
40
|
+
next_offset = offset + header_length
|
|
41
|
+
for i in range(datafile["childCount"]):
|
|
42
|
+
childResult = parse_chunk(data, next_offset)
|
|
43
|
+
next_offset = childResult["nextOffset"]
|
|
44
|
+
resultData = childResult["result"]
|
|
45
|
+
resultType = childResult["datasetType"]
|
|
46
|
+
datafile[chunk_type_map[resultType]] = resultData
|
|
47
|
+
|
|
48
|
+
# Convert byte fields to base64 for JSON serialization
|
|
49
|
+
def replace_bytes_with_base64(data: Any) -> Any:
|
|
50
|
+
if isinstance(data, dict): # If it's a dictionary, process each key-value pair
|
|
51
|
+
return {key: replace_bytes_with_base64(value) for key, value in data.items()}
|
|
52
|
+
elif isinstance(data, list): # If it's a list, process each item
|
|
53
|
+
return [replace_bytes_with_base64(item) for item in data]
|
|
54
|
+
elif isinstance(data, bytes): # If it's bytes, encode to Base64
|
|
55
|
+
return base64.b64encode(data).decode("utf-8")
|
|
56
|
+
else:
|
|
57
|
+
return data # If it's not bytes, return as-is
|
|
58
|
+
|
|
59
|
+
cleaned_database = replace_bytes_with_base64(datafile)
|
|
60
|
+
|
|
61
|
+
return {"nextOffset": next_offset, "result": cleaned_database}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_imageItem(data, offset, header_length, chunk_length) -> dict:
|
|
5
|
+
from .chunk_parser import parse_chunk
|
|
6
|
+
from .constants import mhod_type_map
|
|
7
|
+
|
|
8
|
+
image = {}
|
|
9
|
+
|
|
10
|
+
childCount = struct.unpack("<I", data[offset + 12: offset + 16])[0]
|
|
11
|
+
|
|
12
|
+
image["img_id"] = struct.unpack("<I", data[offset + 16: offset + 20])[0]
|
|
13
|
+
|
|
14
|
+
# First mhii is 0x40, second is 0x41, ...
|
|
15
|
+
# (on mobile phones the first mhii appears to be 0x64, second 0x65, ...)
|
|
16
|
+
|
|
17
|
+
image["songId"] = struct.unpack("<Q", data[offset + 20: offset + 28])[0]
|
|
18
|
+
# unique ID that matches the db_track_id field in the iTunesDB Track Item record.
|
|
19
|
+
# this is what is used to map the ArtworkDB items to iTunesDB Items.
|
|
20
|
+
|
|
21
|
+
image["unk1"] = struct.unpack(
|
|
22
|
+
"<I", data[offset + 28: offset + 32])[0] # always 0
|
|
23
|
+
|
|
24
|
+
image["rating"] = struct.unpack("<I", data[offset + 32: offset + 36])[
|
|
25
|
+
0
|
|
26
|
+
] # iPhoto rating x20
|
|
27
|
+
|
|
28
|
+
image["unk2"] = struct.unpack(
|
|
29
|
+
"<I", data[offset + 36: offset + 40])[0] # always 0
|
|
30
|
+
|
|
31
|
+
image["originalDate"] = struct.unpack(
|
|
32
|
+
"<I", data[offset + 40: offset + 44])[0]
|
|
33
|
+
# always 0 in ArtworkDB. creation timestamp of file in photo database.
|
|
34
|
+
|
|
35
|
+
image["exifTakenDate"] = struct.unpack(
|
|
36
|
+
"<I", data[offset + 44: offset + 48])[0]
|
|
37
|
+
# always 0 in ArtworkDB. timestamp of taken time probably from exif in photo db.
|
|
38
|
+
|
|
39
|
+
image["srcImgSize"] = struct.unpack(
|
|
40
|
+
"<I", data[offset + 48: offset + 52])[0]
|
|
41
|
+
# size in bytes of the original source image.
|
|
42
|
+
|
|
43
|
+
# Parse Children
|
|
44
|
+
next_offset = offset + header_length
|
|
45
|
+
for i in range(childCount):
|
|
46
|
+
response = parse_chunk(data, next_offset)
|
|
47
|
+
next_offset = response["nextOffset"]
|
|
48
|
+
|
|
49
|
+
mhodData = response["result"]
|
|
50
|
+
image[mhod_type_map[mhodData["mhodType"]]["name"]] = mhodData
|
|
51
|
+
|
|
52
|
+
return {"nextOffset": offset + chunk_length, "result": image}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_mhli(data, offset, header_length, imageCount) -> dict[str, Any]:
|
|
5
|
+
from .chunk_parser import parse_chunk
|
|
6
|
+
|
|
7
|
+
imageList = []
|
|
8
|
+
|
|
9
|
+
# Parse Children
|
|
10
|
+
next_offset = offset + header_length
|
|
11
|
+
for i in range(imageCount):
|
|
12
|
+
response = parse_chunk(data, next_offset)
|
|
13
|
+
next_offset = response["nextOffset"]
|
|
14
|
+
imageList.append(response["result"])
|
|
15
|
+
|
|
16
|
+
return {"nextOffset": next_offset, "result": imageList}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
from ipod_device import ITHMB_FORMAT_MAP
|
|
4
|
+
from ipod_device.artwork_presets import artwork_format_candidates
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _expected_img_size_bytes(candidate) -> int:
|
|
8
|
+
pf = candidate.pixel_format
|
|
9
|
+
if pf in (
|
|
10
|
+
"RGB565_LE",
|
|
11
|
+
"RGB565_BE",
|
|
12
|
+
"RGB565_BE_90",
|
|
13
|
+
"UYVY",
|
|
14
|
+
"RGB555_LE",
|
|
15
|
+
"RGB555_BE",
|
|
16
|
+
):
|
|
17
|
+
return candidate.row_bytes * candidate.height
|
|
18
|
+
if pf.startswith("REC_RGB555"):
|
|
19
|
+
return candidate.row_bytes * candidate.height
|
|
20
|
+
if pf == "I420_LE":
|
|
21
|
+
w = candidate.width & ~1
|
|
22
|
+
h = candidate.height & ~1
|
|
23
|
+
return (w * h * 3) // 2
|
|
24
|
+
if pf == "JPEG":
|
|
25
|
+
return 0
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_mhni(data, offset, header_length, chunk_length) -> dict:
|
|
30
|
+
from .chunk_parser import parse_chunk
|
|
31
|
+
|
|
32
|
+
imageName = {}
|
|
33
|
+
|
|
34
|
+
childCount = struct.unpack("<I", data[offset + 12: offset + 16])[0]
|
|
35
|
+
# a type 3 mhod
|
|
36
|
+
|
|
37
|
+
imageName["correlationID"] = struct.unpack(
|
|
38
|
+
"<I", data[offset + 16: offset + 20])[0]
|
|
39
|
+
# maps to mhif correlationID. generates name of the file
|
|
40
|
+
# Also serves as the format_id to identify image format (libgpod approach)
|
|
41
|
+
|
|
42
|
+
imageName["ithmbOffset"] = struct.unpack(
|
|
43
|
+
"<I", data[offset + 20: offset + 24])[0]
|
|
44
|
+
# where the image data starts in the .ithmb file
|
|
45
|
+
|
|
46
|
+
imageName["imgSize"] = struct.unpack(
|
|
47
|
+
"<I", data[offset + 24: offset + 28])[0]
|
|
48
|
+
# in bytes
|
|
49
|
+
|
|
50
|
+
imageName["verticalPadding"] = struct.unpack("<h", data[offset + 28: offset + 30])[
|
|
51
|
+
0
|
|
52
|
+
]
|
|
53
|
+
imageName["horizontalPadding"] = struct.unpack(
|
|
54
|
+
"<h", data[offset + 30: offset + 32]
|
|
55
|
+
)[0]
|
|
56
|
+
|
|
57
|
+
imageName["imageHeight"] = struct.unpack(
|
|
58
|
+
"<H", data[offset + 32: offset + 34])[0]
|
|
59
|
+
imageName["imageWidth"] = struct.unpack(
|
|
60
|
+
"<H", data[offset + 34: offset + 36])[0]
|
|
61
|
+
|
|
62
|
+
imageName["unk1"] = struct.unpack("<I", data[offset + 36: offset + 40])[0]
|
|
63
|
+
# always 0
|
|
64
|
+
|
|
65
|
+
imageName["imgSize2"] = struct.unpack(
|
|
66
|
+
"<I", data[offset + 40: offset + 44])[0]
|
|
67
|
+
# Same as imgSize, seen after iTunes 7.4
|
|
68
|
+
|
|
69
|
+
# Estimate pixmap dimensions (for debugging/fallback)
|
|
70
|
+
imageName["estimatedPixmapHeight"] = (
|
|
71
|
+
imageName["verticalPadding"] + imageName["imageHeight"]
|
|
72
|
+
)
|
|
73
|
+
imageName["estimatedPixmapWidth"] = (
|
|
74
|
+
imageName["horizontalPadding"] + imageName["imageWidth"]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
image_format = None
|
|
78
|
+
|
|
79
|
+
format_id = imageName["correlationID"]
|
|
80
|
+
candidates = artwork_format_candidates()
|
|
81
|
+
same_id_candidates = [
|
|
82
|
+
candidate
|
|
83
|
+
for candidate in candidates
|
|
84
|
+
if candidate.format_id == format_id
|
|
85
|
+
]
|
|
86
|
+
est_w = imageName["estimatedPixmapWidth"]
|
|
87
|
+
est_h = imageName["estimatedPixmapHeight"]
|
|
88
|
+
img_size = imageName["imgSize"]
|
|
89
|
+
|
|
90
|
+
# Prefer correlationID mapping only when it is plausibly compatible with
|
|
91
|
+
# observed MHNI geometry or payload size. Some legacy/corrupt databases
|
|
92
|
+
# carry mismatched correlation IDs (e.g. 140x140 metadata for ~57x58 data).
|
|
93
|
+
if same_id_candidates:
|
|
94
|
+
best_match = None
|
|
95
|
+
best_match_score = float("inf")
|
|
96
|
+
for af in same_id_candidates:
|
|
97
|
+
expected = _expected_img_size_bytes(af)
|
|
98
|
+
corr_exact = expected > 0 and expected == img_size
|
|
99
|
+
corr_close = (
|
|
100
|
+
est_w > 0
|
|
101
|
+
and est_h > 0
|
|
102
|
+
and (
|
|
103
|
+
(abs(est_w - af.width) <= 2 and abs(est_h - af.height) <= 2)
|
|
104
|
+
or (abs(est_w - af.height) <= 2 and abs(est_h - af.width) <= 2)
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
# For variable-sized payloads (e.g. JPEG), trust correlation ID + geometry.
|
|
108
|
+
if expected == 0 and corr_close:
|
|
109
|
+
corr_exact = True
|
|
110
|
+
if not (corr_exact or corr_close):
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
size_delta = abs(img_size - expected) if expected > 0 else 0
|
|
114
|
+
dim_delta = abs(est_w - af.width) + abs(est_h - af.height)
|
|
115
|
+
score = size_delta + dim_delta
|
|
116
|
+
if score < best_match_score:
|
|
117
|
+
best_match = af
|
|
118
|
+
best_match_score = score
|
|
119
|
+
|
|
120
|
+
if best_match is not None:
|
|
121
|
+
image_format = {
|
|
122
|
+
"height": best_match.height,
|
|
123
|
+
"width": best_match.width,
|
|
124
|
+
"format": best_match.pixel_format,
|
|
125
|
+
"description": best_match.description,
|
|
126
|
+
"format_id": format_id,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if image_format is None:
|
|
130
|
+
af = ITHMB_FORMAT_MAP.get(format_id)
|
|
131
|
+
else:
|
|
132
|
+
af = None
|
|
133
|
+
|
|
134
|
+
if image_format is None and af is not None:
|
|
135
|
+
expected = _expected_img_size_bytes(af)
|
|
136
|
+
corr_exact = expected > 0 and expected == img_size
|
|
137
|
+
corr_close = (
|
|
138
|
+
est_w > 0
|
|
139
|
+
and est_h > 0
|
|
140
|
+
and (
|
|
141
|
+
(abs(est_w - af.width) <= 2 and abs(est_h - af.height) <= 2)
|
|
142
|
+
or (abs(est_w - af.height) <= 2 and abs(est_h - af.width) <= 2)
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
# For variable-sized payloads (e.g. JPEG), trust correlation ID + geometry.
|
|
146
|
+
if expected == 0 and corr_close:
|
|
147
|
+
corr_exact = True
|
|
148
|
+
if corr_exact or corr_close:
|
|
149
|
+
image_format = {
|
|
150
|
+
"height": af.height,
|
|
151
|
+
"width": af.width,
|
|
152
|
+
"format": af.pixel_format,
|
|
153
|
+
"description": af.description,
|
|
154
|
+
"format_id": format_id,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if image_format is None:
|
|
158
|
+
# Fallback: choose the candidate that best matches observed geometry
|
|
159
|
+
# and payload pixel count.
|
|
160
|
+
best_candidate = None
|
|
161
|
+
best_score = float("inf")
|
|
162
|
+
|
|
163
|
+
for candidate in candidates:
|
|
164
|
+
dim_diff = abs(est_h - candidate.height) + abs(est_w - candidate.width)
|
|
165
|
+
expected = _expected_img_size_bytes(candidate)
|
|
166
|
+
if expected > 0:
|
|
167
|
+
size_delta = abs(img_size - expected)
|
|
168
|
+
score = dim_diff + (
|
|
169
|
+
size_delta / max(1, candidate.row_bytes, candidate.width)
|
|
170
|
+
)
|
|
171
|
+
else:
|
|
172
|
+
score = dim_diff
|
|
173
|
+
if score < best_score:
|
|
174
|
+
best_score = score
|
|
175
|
+
best_candidate = candidate
|
|
176
|
+
|
|
177
|
+
if best_candidate is not None:
|
|
178
|
+
image_format = {
|
|
179
|
+
"height": best_candidate.height,
|
|
180
|
+
"width": best_candidate.width,
|
|
181
|
+
"format": best_candidate.pixel_format,
|
|
182
|
+
"description": best_candidate.description,
|
|
183
|
+
"format_id": best_candidate.format_id,
|
|
184
|
+
"score": best_score,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
imageName["image_format"] = image_format
|
|
188
|
+
|
|
189
|
+
# parse children
|
|
190
|
+
next_offset = offset + header_length
|
|
191
|
+
for i in range(childCount):
|
|
192
|
+
response = parse_chunk(data, next_offset)
|
|
193
|
+
next_offset = response["nextOffset"]
|
|
194
|
+
imageName[response["result"]["mhodType"]] = response["result"]
|
|
195
|
+
|
|
196
|
+
return {"nextOffset": offset + chunk_length, "result": imageName}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_mhod(data, offset, header_length, chunk_length) -> dict:
|
|
5
|
+
from .constants import mhod_type_map
|
|
6
|
+
from .chunk_parser import parse_chunk
|
|
7
|
+
|
|
8
|
+
dataObject = {}
|
|
9
|
+
|
|
10
|
+
dataObject["mhodType"] = struct.unpack(
|
|
11
|
+
"<H", data[offset + 12: offset + 14])[0]
|
|
12
|
+
|
|
13
|
+
# unk0 = struct.unpack("<B", data[offset + 14: offset + 15])[0] # always 0
|
|
14
|
+
|
|
15
|
+
# paddingLength = struct.unpack("<B", data[offset + 15: offset + 16])[0]
|
|
16
|
+
# all MHOD pad to be be a multiple of 4. the length will be 0,1,3
|
|
17
|
+
|
|
18
|
+
# There is a bug in the iPod code that causes an MHBA to have an MHOD
|
|
19
|
+
# of type 2 that is ont a container but is actually a string
|
|
20
|
+
|
|
21
|
+
# MHOD type 2 contain a MHNI that cotains a MHOD type 3 with a thmbnl ref
|
|
22
|
+
# MHOD type 5 contain a MHNI that cotains a MHOD type 3 with a fulrez ref
|
|
23
|
+
|
|
24
|
+
if dataObject["mhodType"] not in mhod_type_map:
|
|
25
|
+
return {
|
|
26
|
+
"nextOffset": offset + chunk_length,
|
|
27
|
+
"result": {"mhodType": dataObject["mhodType"], "_unknown": True},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
match mhod_type_map[dataObject["mhodType"]]["type"]:
|
|
31
|
+
case "String":
|
|
32
|
+
content_offset = offset + header_length
|
|
33
|
+
|
|
34
|
+
stringByteLength = struct.unpack(
|
|
35
|
+
"<I", data[content_offset: content_offset + 4])[0]
|
|
36
|
+
|
|
37
|
+
# Encoding byte at content_offset+4 (ArtworkDB_MhodHeaderString.encoding)
|
|
38
|
+
# Per libgpod db-itunes-parser.h: 0,1 = UTF-8; 2 = UTF-16-LE
|
|
39
|
+
encoding = data[content_offset + 4]
|
|
40
|
+
|
|
41
|
+
# content_offset+8: unknown (always 0)
|
|
42
|
+
|
|
43
|
+
stringContent = data[
|
|
44
|
+
content_offset + 12: content_offset + 12 + stringByteLength]
|
|
45
|
+
|
|
46
|
+
# padding would be offset+stringByteLength:offset+paddingLength
|
|
47
|
+
# but for the purposes of parsing it is not needed.
|
|
48
|
+
|
|
49
|
+
if encoding == 2:
|
|
50
|
+
string_decode = stringContent.decode("utf-16-le", errors="replace")
|
|
51
|
+
else:
|
|
52
|
+
string_decode = stringContent.decode("utf-8", errors="replace")
|
|
53
|
+
|
|
54
|
+
dataObject[mhod_type_map[dataObject["mhodType"]]
|
|
55
|
+
["name"]] = string_decode
|
|
56
|
+
|
|
57
|
+
return {"nextOffset": offset + chunk_length, "result": dataObject}
|
|
58
|
+
case "Container":
|
|
59
|
+
|
|
60
|
+
# parse children (MHNI)
|
|
61
|
+
next_offset = offset + header_length
|
|
62
|
+
childResult = parse_chunk(data, next_offset)
|
|
63
|
+
|
|
64
|
+
dataObject[mhod_type_map[dataObject["mhodType"]]
|
|
65
|
+
["name"]] = childResult
|
|
66
|
+
|
|
67
|
+
return {"nextOffset": offset + chunk_length, "result": dataObject}
|
|
68
|
+
|
|
69
|
+
case _:
|
|
70
|
+
return {
|
|
71
|
+
"nextOffset": offset + chunk_length,
|
|
72
|
+
"result": {"mhodType": "ERROR"},
|
|
73
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parse_mhsd(data, offset, header_length, chunk_length) -> dict:
|
|
5
|
+
from .chunk_parser import parse_chunk
|
|
6
|
+
|
|
7
|
+
# ArtworkDB MHSD index is u16, not u32 like iTunesDB
|
|
8
|
+
# (per libgpod ArtworkDB_MhsdHeader struct)
|
|
9
|
+
datasetType = struct.unpack("<H", data[offset + 12:offset + 14])[0]
|
|
10
|
+
|
|
11
|
+
# Parse Child
|
|
12
|
+
next_offset = offset + header_length
|
|
13
|
+
childResult = parse_chunk(data, next_offset)
|
|
14
|
+
# Extract the actual result from the wrapper
|
|
15
|
+
result = childResult.get("result", childResult)
|
|
16
|
+
return {"datasetType": datasetType, "result": result, "nextOffset": offset + chunk_length}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
def parse_artworkdb(file) -> dict:
|
|
2
|
+
from .chunk_parser import parse_chunk
|
|
3
|
+
|
|
4
|
+
if isinstance(file, str): # If it's a file path, open the file
|
|
5
|
+
with open(file, "rb") as f:
|
|
6
|
+
data = f.read()
|
|
7
|
+
elif hasattr(file, "read"): # If it's a file-like object, read it directly
|
|
8
|
+
data = file.read()
|
|
9
|
+
else:
|
|
10
|
+
raise TypeError("file must be a path (str) or a file-like object")
|
|
11
|
+
|
|
12
|
+
result = parse_chunk(data, 0)
|
|
13
|
+
|
|
14
|
+
# Return just the parsed data, not the wrapper with nextOffset
|
|
15
|
+
return result.get("result", result)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ArtworkDB Writer for iPod Classic/Nano.
|
|
3
|
+
|
|
4
|
+
Writes ArtworkDB binary files and .ithmb image files from PC music
|
|
5
|
+
file embedded album art.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from ArtworkDB_Writer import write_artworkdb
|
|
9
|
+
|
|
10
|
+
# pc_file_paths maps track db_track_id → PC source file path
|
|
11
|
+
db_track_id_to_art = write_artworkdb(
|
|
12
|
+
ipod_path="/media/ipod",
|
|
13
|
+
tracks=track_list,
|
|
14
|
+
pc_file_paths={12345: "/home/user/Music/song.mp3", ...},
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Then set mhiiLink and artworkSize on each track in iTunesDB
|
|
18
|
+
for track in tracks:
|
|
19
|
+
art_info = db_track_id_to_art.get(track.db_track_id)
|
|
20
|
+
if art_info:
|
|
21
|
+
img_id, src_size = art_info
|
|
22
|
+
track.mhii_link = img_id
|
|
23
|
+
track.artwork_size = src_size
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .artwork_writer import write_artworkdb, ArtworkEntry
|
|
27
|
+
from .art_extractor import extract_art, art_hash
|
|
28
|
+
from .rgb565 import (
|
|
29
|
+
convert_art_for_ipod,
|
|
30
|
+
image_from_bytes,
|
|
31
|
+
rgb888_to_rgb565,
|
|
32
|
+
get_artwork_formats,
|
|
33
|
+
IPOD_CLASSIC_FORMATS,
|
|
34
|
+
IPOD_NANO_1G2G_FORMATS,
|
|
35
|
+
IPOD_PHOTO_FORMATS,
|
|
36
|
+
IPOD_VIDEO_FORMATS,
|
|
37
|
+
IPOD_NANO_4G_FORMATS,
|
|
38
|
+
IPOD_NANO_5G_FORMATS,
|
|
39
|
+
ALL_KNOWN_FORMATS,
|
|
40
|
+
)
|
|
41
|
+
# Re-export canonical format lookups from ipod_device
|
|
42
|
+
from ipod_device import ITHMB_FORMAT_MAP, ITHMB_SIZE_MAP, ithmb_formats_for_device
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
'write_artworkdb',
|
|
46
|
+
'ArtworkEntry',
|
|
47
|
+
'extract_art',
|
|
48
|
+
'art_hash',
|
|
49
|
+
'convert_art_for_ipod',
|
|
50
|
+
'image_from_bytes',
|
|
51
|
+
'rgb888_to_rgb565',
|
|
52
|
+
'get_artwork_formats',
|
|
53
|
+
'IPOD_CLASSIC_FORMATS',
|
|
54
|
+
'IPOD_NANO_1G2G_FORMATS',
|
|
55
|
+
'IPOD_PHOTO_FORMATS',
|
|
56
|
+
'IPOD_VIDEO_FORMATS',
|
|
57
|
+
'IPOD_NANO_4G_FORMATS',
|
|
58
|
+
'IPOD_NANO_5G_FORMATS',
|
|
59
|
+
'ALL_KNOWN_FORMATS',
|
|
60
|
+
'ITHMB_FORMAT_MAP',
|
|
61
|
+
'ITHMB_SIZE_MAP',
|
|
62
|
+
'ithmb_formats_for_device',
|
|
63
|
+
]
|