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.
Files changed (41) hide show
  1. {ipodsync-0.1.0 → ipodsync-0.1.2}/PKG-INFO +34 -7
  2. {ipodsync-0.1.0 → ipodsync-0.1.2}/README.md +28 -6
  3. {ipodsync-0.1.0 → ipodsync-0.1.2}/pyproject.toml +6 -1
  4. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/__init__.py +1 -1
  5. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/cli.py +52 -33
  6. {ipodsync-0.1.0 → ipodsync-0.1.2}/.github/workflows/ci.yml +0 -0
  7. {ipodsync-0.1.0 → ipodsync-0.1.2}/.github/workflows/publish.yml +0 -0
  8. {ipodsync-0.1.0 → ipodsync-0.1.2}/.gitignore +0 -0
  9. {ipodsync-0.1.0 → ipodsync-0.1.2}/LICENSE +0 -0
  10. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/__main__.py +0 -0
  11. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_art_templates.py +0 -0
  12. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen.py +0 -0
  13. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen2.py +0 -0
  14. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen_rt.py +0 -0
  15. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_py.py +0 -0
  16. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.bin +0 -0
  17. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.py +0 -0
  18. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/_lib_templates.py +0 -0
  19. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/artwork_writer.py +0 -0
  20. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/cbk.py +0 -0
  21. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/export.py +0 -0
  22. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/hashab.py +0 -0
  23. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/importer.py +0 -0
  24. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/library.py +0 -0
  25. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/models.py +0 -0
  26. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/sysinfo.py +0 -0
  27. {ipodsync-0.1.0 → ipodsync-0.1.2}/src/ipodsync/transport.py +0 -0
  28. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/ArtworkDB +0 -0
  29. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/Locations.itdb +0 -0
  30. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/Locations.itdb.cbk +0 -0
  31. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/hashab-traces.json +0 -0
  32. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/data/hashab-vectors.json +0 -0
  33. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_artwork_writer.py +0 -0
  34. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_cbk.py +0 -0
  35. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_hashab.py +0 -0
  36. {ipodsync-0.1.0 → ipodsync-0.1.2}/tests/test_hashab_port.py +0 -0
  37. {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/build_gen2.py +0 -0
  38. {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_art_templates.py +0 -0
  39. {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_hashab_tables.py +0 -0
  40. {ipodsync-0.1.0 → ipodsync-0.1.2}/tools/gen_lib_templates.py +0 -0
  41. {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.0
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
  [![CI](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml/badge.svg)](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
31
- [![PyPI](https://img.shields.io/pypi/v/ipodsync.svg)](https://pypi.org/project/ipodsync/)
32
- [![Python](https://img.shields.io/pypi/pyversions/ipodsync.svg)](https://pypi.org/project/ipodsync/)
36
+ [![PyPI](https://img.shields.io/pypi/v/ipodsync.svg?v=2)](https://pypi.org/project/ipodsync/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/ipodsync.svg?v=2)](https://pypi.org/project/ipodsync/)
33
38
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
- > **Keep backups** (the tool makes one before every edit) and don't open this iPod
41
- > in iTunes.
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
- pipx install ipodsync # recommended (isolated), or: pip install ipodsync
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
  [![CI](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml/badge.svg)](https://github.com/buldiei/ipodsync/actions/workflows/ci.yml)
4
- [![PyPI](https://img.shields.io/pypi/v/ipodsync.svg)](https://pypi.org/project/ipodsync/)
5
- [![Python](https://img.shields.io/pypi/pyversions/ipodsync.svg)](https://pypi.org/project/ipodsync/)
4
+ [![PyPI](https://img.shields.io/pypi/v/ipodsync.svg?v=2)](https://pypi.org/project/ipodsync/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/ipodsync.svg?v=2)](https://pypi.org/project/ipodsync/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
- > **Keep backups** (the tool makes one before every edit) and don't open this iPod
14
- > in iTunes.
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
- pipx install ipodsync # recommended (isolated), or: pip install ipodsync
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.0"
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 = [
@@ -13,7 +13,7 @@ from .library import ItlpLibrary
13
13
  from .models import Track
14
14
  from .transport import IPod, IPodNotFound, find_ipod
15
15
 
16
- __version__ = "0.1.0"
16
+ __version__ = "0.1.2"
17
17
 
18
18
  __all__ = [
19
19
  "find_ipod",
@@ -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
- Do not open this iPod in iTunes.
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
- sub = ap.add_subparsers(dest="cmd", required=True)
260
- sub.add_parser("list")
261
- pe = sub.add_parser("export")
262
- pe.add_argument("dest")
263
- pe.add_argument("--pid", type=int)
264
- pe.add_argument("--by-album", action="store_true")
265
- pe.add_argument("--no-tag", action="store_true")
266
- pr = sub.add_parser("rm")
267
- pr.add_argument("pid", type=int)
268
- pr.add_argument("--delete-file", action="store_true")
269
- pa = sub.add_parser("add")
270
- pa.add_argument("file", help="path to MP3")
271
- pa.add_argument("--title")
272
- pa.add_argument("--artist")
273
- pa.add_argument("--album")
274
- pa.add_argument("--no-cover", action="store_true",
275
- help="don't attach the embedded cover")
276
- sub.add_parser("status")
277
- pw = sub.add_parser("wait")
278
- pw.add_argument("--timeout", type=float, default=120)
279
- pw.add_argument("--interval", type=float, default=2)
280
-
281
- sub.add_parser("playlists")
282
- pc = sub.add_parser("pl-create"); pc.add_argument("name")
283
- ppa = sub.add_parser("pl-add")
284
- ppa.add_argument("playlist", type=int); ppa.add_argument("track", type=int, nargs="+")
285
- ppr = sub.add_parser("pl-rm")
286
- ppr.add_argument("playlist", type=int); ppr.add_argument("track", type=int)
287
- ppd = sub.add_parser("pl-del"); ppd.add_argument("playlist", type=int)
288
- pcv = sub.add_parser("cover")
289
- pcv.add_argument("pid", type=int)
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