ipodsync 0.1.0__tar.gz
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.
- ipodsync-0.1.0/.github/workflows/ci.yml +22 -0
- ipodsync-0.1.0/.github/workflows/publish.yml +35 -0
- ipodsync-0.1.0/.gitignore +25 -0
- ipodsync-0.1.0/LICENSE +21 -0
- ipodsync-0.1.0/PKG-INFO +114 -0
- ipodsync-0.1.0/README.md +87 -0
- ipodsync-0.1.0/pyproject.toml +48 -0
- ipodsync-0.1.0/src/ipodsync/__init__.py +27 -0
- ipodsync-0.1.0/src/ipodsync/__main__.py +5 -0
- ipodsync-0.1.0/src/ipodsync/_art_templates.py +29 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_gen.py +1588 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_gen2.py +838 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_gen_rt.py +134 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_py.py +285 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_tables.bin +0 -0
- ipodsync-0.1.0/src/ipodsync/_hashab_tables.py +31 -0
- ipodsync-0.1.0/src/ipodsync/_lib_templates.py +16 -0
- ipodsync-0.1.0/src/ipodsync/artwork_writer.py +252 -0
- ipodsync-0.1.0/src/ipodsync/cbk.py +44 -0
- ipodsync-0.1.0/src/ipodsync/cli.py +314 -0
- ipodsync-0.1.0/src/ipodsync/export.py +91 -0
- ipodsync-0.1.0/src/ipodsync/hashab.py +44 -0
- ipodsync-0.1.0/src/ipodsync/importer.py +137 -0
- ipodsync-0.1.0/src/ipodsync/library.py +360 -0
- ipodsync-0.1.0/src/ipodsync/models.py +31 -0
- ipodsync-0.1.0/src/ipodsync/sysinfo.py +50 -0
- ipodsync-0.1.0/src/ipodsync/transport.py +165 -0
- ipodsync-0.1.0/tests/data/ArtworkDB +0 -0
- ipodsync-0.1.0/tests/data/Locations.itdb +0 -0
- ipodsync-0.1.0/tests/data/Locations.itdb.cbk +0 -0
- ipodsync-0.1.0/tests/data/hashab-traces.json +35 -0
- ipodsync-0.1.0/tests/data/hashab-vectors.json +502 -0
- ipodsync-0.1.0/tests/test_artwork_writer.py +139 -0
- ipodsync-0.1.0/tests/test_cbk.py +28 -0
- ipodsync-0.1.0/tests/test_hashab.py +16 -0
- ipodsync-0.1.0/tests/test_hashab_port.py +75 -0
- ipodsync-0.1.0/tools/build_gen2.py +316 -0
- ipodsync-0.1.0/tools/gen_art_templates.py +77 -0
- ipodsync-0.1.0/tools/gen_hashab_tables.py +62 -0
- ipodsync-0.1.0/tools/gen_lib_templates.py +89 -0
- ipodsync-0.1.0/tools/transpile_hashab.py +233 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [macos-latest, ubuntu-latest]
|
|
14
|
+
python: ["3.9", "3.12"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- run: pip install -e ".[dev]"
|
|
22
|
+
- run: pytest -q
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Build and publish to PyPI on a version tag (vX.Y.Z), using PyPI Trusted
|
|
4
|
+
# Publishing (OIDC) — no API token stored anywhere.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: pip install build
|
|
18
|
+
- run: python -m build
|
|
19
|
+
- uses: actions/upload-artifact@v4
|
|
20
|
+
with:
|
|
21
|
+
name: dist
|
|
22
|
+
path: dist/
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: build
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment: pypi # must match the environment set on the PyPI trusted publisher
|
|
28
|
+
permissions:
|
|
29
|
+
id-token: write # required for OIDC / trusted publishing
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/download-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
.venv/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
|
|
10
|
+
# object files (none expected — pure-Python package)
|
|
11
|
+
*.o
|
|
12
|
+
*.obj
|
|
13
|
+
|
|
14
|
+
# iPod database backups — never commit (personal data)
|
|
15
|
+
*.bak
|
|
16
|
+
ipod-backups/
|
|
17
|
+
|
|
18
|
+
# local-only material (non-English notes, scratch, personal docs)
|
|
19
|
+
local/
|
|
20
|
+
|
|
21
|
+
# editors / IDEs
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
24
|
+
|
|
25
|
+
.DS_Store
|
ipodsync-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Buldiei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ipodsync-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ipodsync
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Upload music to an iPod nano 6G/7G without iTunes: SQLite library, hashAB signing, cover art (pure-Python).
|
|
5
|
+
Project-URL: Homepage, https://github.com/buldiei/ipodsync
|
|
6
|
+
Project-URL: Repository, https://github.com/buldiei/ipodsync
|
|
7
|
+
Project-URL: Issues, https://github.com/buldiei/ipodsync/issues
|
|
8
|
+
Project-URL: LinkedIn, https://www.linkedin.com/in/alex-buldiei-b10a9a150/
|
|
9
|
+
Author-email: Alex Buldiei <abuldiei@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: artwork,ipod,itunes,music,nano,sync
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: mutagen>=1.47
|
|
23
|
+
Requires-Dist: pillow>=10.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ipodsync
|
|
29
|
+
|
|
30
|
+
[](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
|
|
31
|
+
[](https://pypi.org/project/ipodsync/)
|
|
32
|
+
[](https://pypi.org/project/ipodsync/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
35
|
+
Upload music to an **iPod nano 6G/7G** without iTunes/Apple Music — straight from
|
|
36
|
+
the terminal, on macOS and Linux. The iPod mounts as a plain volume; `ipodsync`
|
|
37
|
+
edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
38
|
+
|
|
39
|
+
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database.
|
|
40
|
+
> **Keep backups** (the tool makes one before every edit) and don't open this iPod
|
|
41
|
+
> in iTunes.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- `list` / `export` — inspect and download tracks from the device (with tags).
|
|
46
|
+
- `add` — upload an MP3, metadata from tags, **cover art attached automatically**
|
|
47
|
+
(from an embedded APIC/covr): shown both in Now Playing and in list/Albums views.
|
|
48
|
+
- `rm` — remove a track (+file), re-sign.
|
|
49
|
+
- playlists — `playlists` / `pl-create` / `pl-add` / `pl-rm` / `pl-del`.
|
|
50
|
+
- `cover` — attach a cover to an already-uploaded track.
|
|
51
|
+
- works on an **empty (wiped) library** too — no iTunes needed to initialize it.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pipx install ipodsync # recommended (isolated), or: pip install ipodsync
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
|
|
60
|
+
|
|
61
|
+
> Note: uploading currently supports **MP3 only**. FLAC/AAC → ALAC transcoding
|
|
62
|
+
> (which will need `ffmpeg`) is on the roadmap and not implemented yet.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ipodsync status # ready / no access / not connected
|
|
68
|
+
ipodsync list
|
|
69
|
+
ipodsync add "Song.mp3" # + cover auto
|
|
70
|
+
ipodsync add "Song.mp3" --no-cover
|
|
71
|
+
ipodsync export ~/Music/ipod --by-album
|
|
72
|
+
ipodsync cover 123456789 --image cover.jpg
|
|
73
|
+
ipodsync rm 123456789 --delete-file
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The iPod is discovered under `/Volumes` (macOS) and `/media`, `/run/media`, `/mnt`
|
|
77
|
+
(Linux) by the presence of `iPod_Control/`. To point at it explicitly, set
|
|
78
|
+
`IPODSYNC_MOUNT=/path/to/mount`.
|
|
79
|
+
|
|
80
|
+
## How it works
|
|
81
|
+
|
|
82
|
+
- **Transport** — mass storage: files are written under `iPod_Control/`.
|
|
83
|
+
- **Database** — SQLite `iTunes Library.itlp/*.itdb` (not `iTunesCDB`, which the
|
|
84
|
+
device regenerates on its own). Only `Locations.itdb` is hash-protected (`.cbk`).
|
|
85
|
+
- **hashAB** — anti-tamper `.cbk` signature (white-box AES). **Pure-Python** port of
|
|
86
|
+
[dstaley/hashab](https://github.com/dstaley/hashab) (public domain), 100/100 test
|
|
87
|
+
vectors. No compiler or native lib — the package installs everywhere.
|
|
88
|
+
- **Cover art** — pure-Python writer for `ArtworkDB` + `F<fmt>_1.ithmb` (RGB565 LE,
|
|
89
|
+
formats 1010/1013/1015/1016), appended incrementally.
|
|
90
|
+
|
|
91
|
+
## Status / roadmap
|
|
92
|
+
|
|
93
|
+
| | Status |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Upload / remove / export / playlists (MP3) | ✅ confirmed on nano 7G |
|
|
96
|
+
| Cover art (pure-Python) | ✅ confirmed on nano 7G |
|
|
97
|
+
| Empty-library bootstrap | ✅ |
|
|
98
|
+
| **Pure-Python hashAB** (no native lib, installs everywhere) | ✅ 100/100 vectors |
|
|
99
|
+
| Cross-platform device discovery (macOS + Linux) | ✅ |
|
|
100
|
+
| FLAC/AAC → ALAC | ⬜ |
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git clone … && cd ipodsync
|
|
106
|
+
python -m venv .venv && . .venv/bin/activate
|
|
107
|
+
pip install -e ".[dev]"
|
|
108
|
+
pytest
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT (see [LICENSE](LICENSE)). The vendored hashAB algorithm is public domain.
|
|
114
|
+
Not affiliated with Apple; iPod and iTunes are trademarks of Apple.
|
ipodsync-0.1.0/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ipodsync
|
|
2
|
+
|
|
3
|
+
[](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/ipodsync/)
|
|
5
|
+
[](https://pypi.org/project/ipodsync/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Upload music to an **iPod nano 6G/7G** without iTunes/Apple Music — straight from
|
|
9
|
+
the terminal, on macOS and Linux. The iPod mounts as a plain volume; `ipodsync`
|
|
10
|
+
edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
11
|
+
|
|
12
|
+
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database.
|
|
13
|
+
> **Keep backups** (the tool makes one before every edit) and don't open this iPod
|
|
14
|
+
> in iTunes.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- `list` / `export` — inspect and download tracks from the device (with tags).
|
|
19
|
+
- `add` — upload an MP3, metadata from tags, **cover art attached automatically**
|
|
20
|
+
(from an embedded APIC/covr): shown both in Now Playing and in list/Albums views.
|
|
21
|
+
- `rm` — remove a track (+file), re-sign.
|
|
22
|
+
- playlists — `playlists` / `pl-create` / `pl-add` / `pl-rm` / `pl-del`.
|
|
23
|
+
- `cover` — attach a cover to an already-uploaded track.
|
|
24
|
+
- works on an **empty (wiped) library** too — no iTunes needed to initialize it.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pipx install ipodsync # recommended (isolated), or: pip install ipodsync
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
|
|
33
|
+
|
|
34
|
+
> Note: uploading currently supports **MP3 only**. FLAC/AAC → ALAC transcoding
|
|
35
|
+
> (which will need `ffmpeg`) is on the roadmap and not implemented yet.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ipodsync status # ready / no access / not connected
|
|
41
|
+
ipodsync list
|
|
42
|
+
ipodsync add "Song.mp3" # + cover auto
|
|
43
|
+
ipodsync add "Song.mp3" --no-cover
|
|
44
|
+
ipodsync export ~/Music/ipod --by-album
|
|
45
|
+
ipodsync cover 123456789 --image cover.jpg
|
|
46
|
+
ipodsync rm 123456789 --delete-file
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The iPod is discovered under `/Volumes` (macOS) and `/media`, `/run/media`, `/mnt`
|
|
50
|
+
(Linux) by the presence of `iPod_Control/`. To point at it explicitly, set
|
|
51
|
+
`IPODSYNC_MOUNT=/path/to/mount`.
|
|
52
|
+
|
|
53
|
+
## How it works
|
|
54
|
+
|
|
55
|
+
- **Transport** — mass storage: files are written under `iPod_Control/`.
|
|
56
|
+
- **Database** — SQLite `iTunes Library.itlp/*.itdb` (not `iTunesCDB`, which the
|
|
57
|
+
device regenerates on its own). Only `Locations.itdb` is hash-protected (`.cbk`).
|
|
58
|
+
- **hashAB** — anti-tamper `.cbk` signature (white-box AES). **Pure-Python** port of
|
|
59
|
+
[dstaley/hashab](https://github.com/dstaley/hashab) (public domain), 100/100 test
|
|
60
|
+
vectors. No compiler or native lib — the package installs everywhere.
|
|
61
|
+
- **Cover art** — pure-Python writer for `ArtworkDB` + `F<fmt>_1.ithmb` (RGB565 LE,
|
|
62
|
+
formats 1010/1013/1015/1016), appended incrementally.
|
|
63
|
+
|
|
64
|
+
## Status / roadmap
|
|
65
|
+
|
|
66
|
+
| | Status |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Upload / remove / export / playlists (MP3) | ✅ confirmed on nano 7G |
|
|
69
|
+
| Cover art (pure-Python) | ✅ confirmed on nano 7G |
|
|
70
|
+
| Empty-library bootstrap | ✅ |
|
|
71
|
+
| **Pure-Python hashAB** (no native lib, installs everywhere) | ✅ 100/100 vectors |
|
|
72
|
+
| Cross-platform device discovery (macOS + Linux) | ✅ |
|
|
73
|
+
| FLAC/AAC → ALAC | ⬜ |
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone … && cd ipodsync
|
|
79
|
+
python -m venv .venv && . .venv/bin/activate
|
|
80
|
+
pip install -e ".[dev]"
|
|
81
|
+
pytest
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT (see [LICENSE](LICENSE)). The vendored hashAB algorithm is public domain.
|
|
87
|
+
Not affiliated with Apple; iPod and iTunes are trademarks of Apple.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ipodsync"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Upload music to an iPod nano 6G/7G without iTunes: SQLite library, hashAB signing, cover art (pure-Python)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Alex Buldiei", email = "abuldiei@gmail.com" }]
|
|
13
|
+
keywords = ["ipod", "nano", "itunes", "music", "sync", "artwork"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: End Users/Desktop",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: POSIX :: Linux",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"mutagen>=1.47", # audio metadata + embedded cover art
|
|
26
|
+
"Pillow>=10.0", # resize cover art to RGB565 for .ithmb
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/buldiei/ipodsync"
|
|
31
|
+
Repository = "https://github.com/buldiei/ipodsync"
|
|
32
|
+
Issues = "https://github.com/buldiei/ipodsync/issues"
|
|
33
|
+
LinkedIn = "https://www.linkedin.com/in/alex-buldiei-b10a9a150/"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
ipodsync = "ipodsync.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = ["pytest>=8.0"]
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/ipodsync"]
|
|
43
|
+
# hashAB is pure-Python; no native lib needed. The white-box AES tables (433 KB)
|
|
44
|
+
# ship as package data.
|
|
45
|
+
artifacts = ["*.bin"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""ipodsync — upload music to iPod nano 6G/7G without iTunes/Apple Music.
|
|
2
|
+
|
|
3
|
+
Transport is a plain volume (mass storage); it edits the SQLite library
|
|
4
|
+
`iTunes Library.itlp` and the `Locations.itdb.cbk` signature (hashAB). Cover art
|
|
5
|
+
is written in pure Python (ArtworkDB + .ithmb). Knows nothing about where the
|
|
6
|
+
files came from.
|
|
7
|
+
|
|
8
|
+
Public API:
|
|
9
|
+
from ipodsync import find_ipod, ItlpLibrary, Track, HashAB
|
|
10
|
+
"""
|
|
11
|
+
from .hashab import HashAB, HashABError
|
|
12
|
+
from .library import ItlpLibrary
|
|
13
|
+
from .models import Track
|
|
14
|
+
from .transport import IPod, IPodNotFound, find_ipod
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"find_ipod",
|
|
20
|
+
"IPod",
|
|
21
|
+
"IPodNotFound",
|
|
22
|
+
"ItlpLibrary",
|
|
23
|
+
"Track",
|
|
24
|
+
"HashAB",
|
|
25
|
+
"HashABError",
|
|
26
|
+
"__version__",
|
|
27
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Constant byte templates of nano 7G ArtworkDB atoms.
|
|
2
|
+
|
|
3
|
+
Generated by scripts/gen_art_templates.py from the iTunes reference
|
|
4
|
+
native/fixtures/itunes_artwork/ArtworkDB. Do not edit by hand —
|
|
5
|
+
these are real iTunes bytes; the writer patches only image_id/song_id/ithmb_offset.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
MHII_HEADER = bytes.fromhex("6d68696998000000a0030000050000006500000000000000000000000000000000000000000000000000000000000000cd4401000000000001000000010000000000000000000000000000000000f87f000000000000f87f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
9
|
+
MHOD_CONTAINER = bytes.fromhex("6d686f6418000000a4000000020000000000000000000000")
|
|
10
|
+
MHOD_MHAF = bytes.fromhex("6d686f6418000000780000000600000000000000000000006d686166600000003c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
11
|
+
MHFD_TEMPLATE = bytes.fromhex("6d6866648400000020290a000000000006000000030000000000000031030000ea356c5898804379b5100a34de06a1c70100000000000000000000005b96a7fed08a706600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
12
|
+
MHLI_TEMPLATE = bytes.fromhex("6d686c695c000000cc0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
13
|
+
MHLA_TEMPLATE = bytes.fromhex("6d686c615c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
14
|
+
MHLF_TEMPLATE = bytes.fromhex("6d686c665c000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
15
|
+
MHIF_ALL = bytes.fromhex("6d6869667c0000007c00000000000000ed03000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d6869667c0000007c00000000000000ef03000000a80c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d6869667c0000007c00000000000000f203000000c20100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d6869667c0000007c00000000000000f503000088130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d6869667c0000007c00000000000000f7030000481a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d6869667c0000007c00000000000000f8030000d419000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
|
16
|
+
|
|
17
|
+
MHNI = {
|
|
18
|
+
1010: bytes.fromhex("6d686e694c0000008c00000001000000f20300000000000000c2010000000000f000f0000000000000c201000000000000000000000000000000000000000000000000000000000000000000"),
|
|
19
|
+
1013: bytes.fromhex("6d686e694c0000008c00000001000000f50300000000000088130000000000003200320000000000881300000000000000000000000000000000000000000000000000000000000000000000"),
|
|
20
|
+
1015: bytes.fromhex("6d686e694c0000008c00000001000000f703000000000000481a0000000000003a003a0000000000481a00000000000000000000000000000000000000000000000000000000000000000000"),
|
|
21
|
+
1016: bytes.fromhex("6d686e694c0000008c00000001000000f803000000000000d4190000000000003900390000000000d41900000000000000000000000000000000000000000000000000000000000000000000"),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
STRMHOD = {
|
|
25
|
+
1010: bytes.fromhex("6d686f6418000000400000000300000000000000000000001c00000002000000000000003a00460031003000310030005f0031002e006900740068006d006200"),
|
|
26
|
+
1013: bytes.fromhex("6d686f6418000000400000000300000000000000000000001c00000002000000000000003a00460031003000310033005f0031002e006900740068006d006200"),
|
|
27
|
+
1015: bytes.fromhex("6d686f6418000000400000000300000000000000000000001c00000002000000000000003a00460031003000310035005f0031002e006900740068006d006200"),
|
|
28
|
+
1016: bytes.fromhex("6d686f6418000000400000000300000000000000000000001c00000002000000000000003a00460031003000310036005f0031002e006900740068006d006200"),
|
|
29
|
+
}
|