ipodsync 0.1.0__tar.gz → 0.1.2__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 → ipodsync-0.1.2}/PKG-INFO +34 -7
- {ipodsync-0.1.0 → ipodsync-0.1.2}/README.md +28 -6
- {ipodsync-0.1.0 → ipodsync-0.1.2}/pyproject.toml +6 -1
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/__init__.py +1 -1
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/cli.py +52 -33
- {ipodsync-0.1.0 → ipodsync-0.1.2}/.github/workflows/ci.yml +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/.github/workflows/publish.yml +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/.gitignore +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/LICENSE +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/__main__.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_art_templates.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen2.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen_rt.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_py.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.bin +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_lib_templates.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/artwork_writer.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/cbk.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/export.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/hashab.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/importer.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/library.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/models.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/sysinfo.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/transport.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/ArtworkDB +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/Locations.itdb +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/Locations.itdb.cbk +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/hashab-traces.json +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/hashab-vectors.json +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_artwork_writer.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_cbk.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_hashab.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_hashab_port.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/build_gen2.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_art_templates.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_hashab_tables.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_lib_templates.py +0 -0
- {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/transpile_hashab.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipodsync
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Upload music to an iPod nano 6G/7G without iTunes: SQLite library, hashAB signing, cover art (pure-Python).
|
|
5
5
|
Project-URL: Homepage, https://github.com/buldiei/ipodsync
|
|
6
6
|
Project-URL: Repository, https://github.com/buldiei/ipodsync
|
|
@@ -17,6 +17,11 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
17
17
|
Classifier: Operating System :: MacOS
|
|
18
18
|
Classifier: Operating System :: POSIX :: Linux
|
|
19
19
|
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
25
|
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
21
26
|
Requires-Python: >=3.9
|
|
22
27
|
Requires-Dist: mutagen>=1.47
|
|
@@ -28,17 +33,19 @@ Description-Content-Type: text/markdown
|
|
|
28
33
|
# ipodsync
|
|
29
34
|
|
|
30
35
|
[](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
|
|
31
|
-
[](https://pypi.org/project/ipodsync/)
|
|
32
|
-
[](https://pypi.org/project/ipodsync/)
|
|
36
|
+
[](https://pypi.org/project/ipodsync/)
|
|
37
|
+
[](https://pypi.org/project/ipodsync/)
|
|
33
38
|
[](LICENSE)
|
|
34
39
|
|
|
35
40
|
Upload music to an **iPod nano 6G/7G** without iTunes/Apple Music — straight from
|
|
36
41
|
the terminal, on macOS and Linux. The iPod mounts as a plain volume; `ipodsync`
|
|
37
42
|
edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
38
43
|
|
|
39
|
-
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database
|
|
40
|
-
> **
|
|
41
|
-
>
|
|
44
|
+
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database,
|
|
45
|
+
> so **keep backups** (the tool makes one before every edit). Browsing the iPod in
|
|
46
|
+
> Finder or the Music app is fine — just don't let Apple's software **auto-sync** it:
|
|
47
|
+
> a sync rebuilds the library from your computer and would drop manually-added tracks
|
|
48
|
+
> (keep the iPod in "manually manage music" mode).
|
|
42
49
|
|
|
43
50
|
## Features
|
|
44
51
|
|
|
@@ -52,9 +59,29 @@ edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
|
52
59
|
|
|
53
60
|
## Install
|
|
54
61
|
|
|
62
|
+
`ipodsync` is a command-line tool, so install it with **pipx** — it lands in an
|
|
63
|
+
isolated environment while the `ipodsync` command stays available everywhere:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pipx install ipodsync
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No pipx yet? `brew install pipx` (macOS) or `sudo apt install pipx` (Debian/Ubuntu),
|
|
70
|
+
then `pipx ensurepath`.
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary><code>pip install ipodsync</code> fails with <code>externally-managed-environment</code>?</summary>
|
|
74
|
+
|
|
75
|
+
On modern Debian/Ubuntu (PEP 668) the system Python is locked, so a plain
|
|
76
|
+
`pip install` into it is refused — that's an OS guard, not an ipodsync issue.
|
|
77
|
+
Use `pipx` (above), or a virtualenv:
|
|
78
|
+
|
|
55
79
|
```bash
|
|
56
|
-
|
|
80
|
+
python3 -m venv ~/.venvs/ipodsync
|
|
81
|
+
~/.venvs/ipodsync/bin/pip install ipodsync
|
|
82
|
+
~/.venvs/ipodsync/bin/ipodsync --help
|
|
57
83
|
```
|
|
84
|
+
</details>
|
|
58
85
|
|
|
59
86
|
Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
|
|
60
87
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# ipodsync
|
|
2
2
|
|
|
3
3
|
[](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/ipodsync/)
|
|
5
|
-
[](https://pypi.org/project/ipodsync/)
|
|
4
|
+
[](https://pypi.org/project/ipodsync/)
|
|
5
|
+
[](https://pypi.org/project/ipodsync/)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
8
|
Upload music to an **iPod nano 6G/7G** without iTunes/Apple Music — straight from
|
|
9
9
|
the terminal, on macOS and Linux. The iPod mounts as a plain volume; `ipodsync`
|
|
10
10
|
edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
11
11
|
|
|
12
|
-
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database
|
|
13
|
-
> **
|
|
14
|
-
>
|
|
12
|
+
> ⚠️ Alpha. Works on real hardware, but it writes to the player's binary database,
|
|
13
|
+
> so **keep backups** (the tool makes one before every edit). Browsing the iPod in
|
|
14
|
+
> Finder or the Music app is fine — just don't let Apple's software **auto-sync** it:
|
|
15
|
+
> a sync rebuilds the library from your computer and would drop manually-added tracks
|
|
16
|
+
> (keep the iPod in "manually manage music" mode).
|
|
15
17
|
|
|
16
18
|
## Features
|
|
17
19
|
|
|
@@ -25,9 +27,29 @@ edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
|
|
|
25
27
|
|
|
26
28
|
## Install
|
|
27
29
|
|
|
30
|
+
`ipodsync` is a command-line tool, so install it with **pipx** — it lands in an
|
|
31
|
+
isolated environment while the `ipodsync` command stays available everywhere:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pipx install ipodsync
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
No pipx yet? `brew install pipx` (macOS) or `sudo apt install pipx` (Debian/Ubuntu),
|
|
38
|
+
then `pipx ensurepath`.
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><code>pip install ipodsync</code> fails with <code>externally-managed-environment</code>?</summary>
|
|
42
|
+
|
|
43
|
+
On modern Debian/Ubuntu (PEP 668) the system Python is locked, so a plain
|
|
44
|
+
`pip install` into it is refused — that's an OS guard, not an ipodsync issue.
|
|
45
|
+
Use `pipx` (above), or a virtualenv:
|
|
46
|
+
|
|
28
47
|
```bash
|
|
29
|
-
|
|
48
|
+
python3 -m venv ~/.venvs/ipodsync
|
|
49
|
+
~/.venvs/ipodsync/bin/pip install ipodsync
|
|
50
|
+
~/.venvs/ipodsync/bin/ipodsync --help
|
|
30
51
|
```
|
|
52
|
+
</details>
|
|
31
53
|
|
|
32
54
|
Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
|
|
33
55
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ipodsync"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Upload music to an iPod nano 6G/7G without iTunes: SQLite library, hashAB signing, cover art (pure-Python)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -19,6 +19,11 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: MacOS",
|
|
20
20
|
"Operating System :: POSIX :: Linux",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
22
27
|
"Topic :: Multimedia :: Sound/Audio",
|
|
23
28
|
]
|
|
24
29
|
dependencies = [
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
ipodsync cover PID [--image IMG] # attach a cover to a track
|
|
9
9
|
|
|
10
10
|
Export is read-only for the device. add/rm/cover write to the database (they back up .itlp).
|
|
11
|
-
|
|
11
|
+
Browsing the iPod in Finder/Music is fine; just don't let Apple's software auto-sync it,
|
|
12
|
+
or a sync will drop manually-added tracks.
|
|
12
13
|
"""
|
|
13
14
|
from __future__ import annotations
|
|
14
15
|
|
|
@@ -255,39 +256,57 @@ def cmd_cover(ipod, args):
|
|
|
255
256
|
|
|
256
257
|
|
|
257
258
|
def main() -> int:
|
|
258
|
-
ap = argparse.ArgumentParser(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
sub.add_parser("
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
259
|
+
ap = argparse.ArgumentParser(
|
|
260
|
+
prog="ipodsync",
|
|
261
|
+
description="Upload music to an iPod nano 6G/7G without iTunes (macOS + Linux). "
|
|
262
|
+
"The iPod mounts as a plain volume; ipodsync edits its SQLite library, "
|
|
263
|
+
"signs it (hashAB) and writes cover art.",
|
|
264
|
+
epilog="Set IPODSYNC_MOUNT=/path to point at the iPod explicitly. "
|
|
265
|
+
"add/rm/cover back up the library before editing. Run "
|
|
266
|
+
"`ipodsync <command> -h` for per-command help.")
|
|
267
|
+
sub = ap.add_subparsers(dest="cmd", required=True, metavar="<command>")
|
|
268
|
+
|
|
269
|
+
sub.add_parser("list", help="show tracks on the iPod")
|
|
270
|
+
|
|
271
|
+
pe = sub.add_parser("export", help="download tracks from the iPod (read-only)")
|
|
272
|
+
pe.add_argument("dest", help="destination directory")
|
|
273
|
+
pe.add_argument("--pid", type=int, help="export only this track (by pid)")
|
|
274
|
+
pe.add_argument("--by-album", action="store_true", help="lay out as Artist/Album/")
|
|
275
|
+
pe.add_argument("--no-tag", action="store_true", help="don't write ID3/MP4 tags")
|
|
276
|
+
|
|
277
|
+
pr = sub.add_parser("rm", help="remove a track from the iPod")
|
|
278
|
+
pr.add_argument("pid", type=int, help="track pid (see `list`)")
|
|
279
|
+
pr.add_argument("--delete-file", action="store_true", help="also delete the audio file")
|
|
280
|
+
|
|
281
|
+
pa = sub.add_parser("add", help="upload an MP3 (cover attached automatically)")
|
|
282
|
+
pa.add_argument("file", help="path to the MP3 to upload")
|
|
283
|
+
pa.add_argument("--title", help="override the title tag")
|
|
284
|
+
pa.add_argument("--artist", help="override the artist tag")
|
|
285
|
+
pa.add_argument("--album", help="override the album tag")
|
|
286
|
+
pa.add_argument("--no-cover", action="store_true", help="don't attach the embedded cover")
|
|
287
|
+
|
|
288
|
+
sub.add_parser("status", help="report ready / no-access / not-connected")
|
|
289
|
+
|
|
290
|
+
pw = sub.add_parser("wait", help="block until an iPod is ready")
|
|
291
|
+
pw.add_argument("--timeout", type=float, default=120, help="seconds to wait (0 = forever)")
|
|
292
|
+
pw.add_argument("--interval", type=float, default=2, help="poll interval, seconds")
|
|
293
|
+
|
|
294
|
+
sub.add_parser("playlists", help="list playlists")
|
|
295
|
+
pc = sub.add_parser("pl-create", help="create a playlist")
|
|
296
|
+
pc.add_argument("name", help="playlist name")
|
|
297
|
+
ppa = sub.add_parser("pl-add", help="add tracks to a playlist")
|
|
298
|
+
ppa.add_argument("playlist", type=int, help="playlist pid")
|
|
299
|
+
ppa.add_argument("track", type=int, nargs="+", help="track pid(s)")
|
|
300
|
+
ppr = sub.add_parser("pl-rm", help="remove a track from a playlist")
|
|
301
|
+
ppr.add_argument("playlist", type=int, help="playlist pid")
|
|
302
|
+
ppr.add_argument("track", type=int, help="track pid")
|
|
303
|
+
ppd = sub.add_parser("pl-del", help="delete a playlist")
|
|
304
|
+
ppd.add_argument("playlist", type=int, help="playlist pid")
|
|
305
|
+
|
|
306
|
+
pcv = sub.add_parser("cover", help="attach a cover to an existing track")
|
|
307
|
+
pcv.add_argument("pid", type=int, help="track pid")
|
|
290
308
|
pcv.add_argument("--image", help="cover image file (otherwise the track's APIC is used)")
|
|
309
|
+
|
|
291
310
|
args = ap.parse_args()
|
|
292
311
|
|
|
293
312
|
# commands that don't require a ready iPod
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|