ipodsync 0.1.1__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.1 → ipodsync-0.1.2}/PKG-INFO +29 -4
  2. {ipodsync-0.1.1 → ipodsync-0.1.2}/README.md +23 -3
  3. {ipodsync-0.1.1 → ipodsync-0.1.2}/pyproject.toml +6 -1
  4. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/__init__.py +1 -1
  5. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/cli.py +50 -32
  6. {ipodsync-0.1.1 → ipodsync-0.1.2}/.github/workflows/ci.yml +0 -0
  7. {ipodsync-0.1.1 → ipodsync-0.1.2}/.github/workflows/publish.yml +0 -0
  8. {ipodsync-0.1.1 → ipodsync-0.1.2}/.gitignore +0 -0
  9. {ipodsync-0.1.1 → ipodsync-0.1.2}/LICENSE +0 -0
  10. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/__main__.py +0 -0
  11. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_art_templates.py +0 -0
  12. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen.py +0 -0
  13. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen2.py +0 -0
  14. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_gen_rt.py +0 -0
  15. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_py.py +0 -0
  16. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.bin +0 -0
  17. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_hashab_tables.py +0 -0
  18. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/_lib_templates.py +0 -0
  19. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/artwork_writer.py +0 -0
  20. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/cbk.py +0 -0
  21. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/export.py +0 -0
  22. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/hashab.py +0 -0
  23. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/importer.py +0 -0
  24. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/library.py +0 -0
  25. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/models.py +0 -0
  26. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/sysinfo.py +0 -0
  27. {ipodsync-0.1.1 → ipodsync-0.1.2}/src/ipodsync/transport.py +0 -0
  28. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/data/ArtworkDB +0 -0
  29. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/data/Locations.itdb +0 -0
  30. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/data/Locations.itdb.cbk +0 -0
  31. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/data/hashab-traces.json +0 -0
  32. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/data/hashab-vectors.json +0 -0
  33. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/test_artwork_writer.py +0 -0
  34. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/test_cbk.py +0 -0
  35. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/test_hashab.py +0 -0
  36. {ipodsync-0.1.1 → ipodsync-0.1.2}/tests/test_hashab_port.py +0 -0
  37. {ipodsync-0.1.1 → ipodsync-0.1.2}/tools/build_gen2.py +0 -0
  38. {ipodsync-0.1.1 → ipodsync-0.1.2}/tools/gen_art_templates.py +0 -0
  39. {ipodsync-0.1.1 → ipodsync-0.1.2}/tools/gen_hashab_tables.py +0 -0
  40. {ipodsync-0.1.1 → ipodsync-0.1.2}/tools/gen_lib_templates.py +0 -0
  41. {ipodsync-0.1.1 → 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.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,8 +33,8 @@ 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
@@ -54,9 +59,29 @@ edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
54
59
 
55
60
  ## Install
56
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
+
57
79
  ```bash
58
- 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
59
83
  ```
84
+ </details>
60
85
 
61
86
  Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
62
87
 
@@ -1,8 +1,8 @@
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
@@ -27,9 +27,29 @@ edits its SQLite library, signs it (hashAB) and **generates cover art itself**.
27
27
 
28
28
  ## Install
29
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
+
30
47
  ```bash
31
- 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
32
51
  ```
52
+ </details>
33
53
 
34
54
  Requires Python ≥ 3.9. Pure-Python — no compiler or native library needed.
35
55
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ipodsync"
7
- version = "0.1.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 = [
@@ -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.1"
16
+ __version__ = "0.1.2"
17
17
 
18
18
  __all__ = [
19
19
  "find_ipod",
@@ -256,39 +256,57 @@ def cmd_cover(ipod, args):
256
256
 
257
257
 
258
258
  def main() -> int:
259
- ap = argparse.ArgumentParser()
260
- sub = ap.add_subparsers(dest="cmd", required=True)
261
- sub.add_parser("list")
262
- pe = sub.add_parser("export")
263
- pe.add_argument("dest")
264
- pe.add_argument("--pid", type=int)
265
- pe.add_argument("--by-album", action="store_true")
266
- pe.add_argument("--no-tag", action="store_true")
267
- pr = sub.add_parser("rm")
268
- pr.add_argument("pid", type=int)
269
- pr.add_argument("--delete-file", action="store_true")
270
- pa = sub.add_parser("add")
271
- pa.add_argument("file", help="path to MP3")
272
- pa.add_argument("--title")
273
- pa.add_argument("--artist")
274
- pa.add_argument("--album")
275
- pa.add_argument("--no-cover", action="store_true",
276
- help="don't attach the embedded cover")
277
- sub.add_parser("status")
278
- pw = sub.add_parser("wait")
279
- pw.add_argument("--timeout", type=float, default=120)
280
- pw.add_argument("--interval", type=float, default=2)
281
-
282
- sub.add_parser("playlists")
283
- pc = sub.add_parser("pl-create"); pc.add_argument("name")
284
- ppa = sub.add_parser("pl-add")
285
- ppa.add_argument("playlist", type=int); ppa.add_argument("track", type=int, nargs="+")
286
- ppr = sub.add_parser("pl-rm")
287
- ppr.add_argument("playlist", type=int); ppr.add_argument("track", type=int)
288
- ppd = sub.add_parser("pl-del"); ppd.add_argument("playlist", type=int)
289
- pcv = sub.add_parser("cover")
290
- 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")
291
308
  pcv.add_argument("--image", help="cover image file (otherwise the track's APIC is used)")
309
+
292
310
  args = ap.parse_args()
293
311
 
294
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