ipodsync 0.1.3__tar.gz → 0.1.4__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.3 → ipodsync-0.1.4}/PKG-INFO +1 -1
  2. {ipodsync-0.1.3 → ipodsync-0.1.4}/pyproject.toml +1 -1
  3. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/__init__.py +1 -1
  4. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/artwork_writer.py +19 -9
  5. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/test_artwork_writer.py +14 -0
  6. {ipodsync-0.1.3 → ipodsync-0.1.4}/.github/workflows/ci.yml +0 -0
  7. {ipodsync-0.1.3 → ipodsync-0.1.4}/.github/workflows/publish.yml +0 -0
  8. {ipodsync-0.1.3 → ipodsync-0.1.4}/.gitignore +0 -0
  9. {ipodsync-0.1.3 → ipodsync-0.1.4}/LICENSE +0 -0
  10. {ipodsync-0.1.3 → ipodsync-0.1.4}/README.md +0 -0
  11. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/__main__.py +0 -0
  12. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_art_templates.py +0 -0
  13. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_gen.py +0 -0
  14. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_gen2.py +0 -0
  15. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_gen_rt.py +0 -0
  16. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_py.py +0 -0
  17. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_tables.bin +0 -0
  18. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_hashab_tables.py +0 -0
  19. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/_lib_templates.py +0 -0
  20. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/cbk.py +0 -0
  21. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/cli.py +0 -0
  22. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/export.py +0 -0
  23. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/hashab.py +0 -0
  24. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/importer.py +0 -0
  25. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/library.py +0 -0
  26. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/models.py +0 -0
  27. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/sysinfo.py +0 -0
  28. {ipodsync-0.1.3 → ipodsync-0.1.4}/src/ipodsync/transport.py +0 -0
  29. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/data/ArtworkDB +0 -0
  30. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/data/Locations.itdb +0 -0
  31. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/data/Locations.itdb.cbk +0 -0
  32. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/data/hashab-traces.json +0 -0
  33. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/data/hashab-vectors.json +0 -0
  34. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/test_cbk.py +0 -0
  35. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/test_hashab.py +0 -0
  36. {ipodsync-0.1.3 → ipodsync-0.1.4}/tests/test_hashab_port.py +0 -0
  37. {ipodsync-0.1.3 → ipodsync-0.1.4}/tools/build_gen2.py +0 -0
  38. {ipodsync-0.1.3 → ipodsync-0.1.4}/tools/gen_art_templates.py +0 -0
  39. {ipodsync-0.1.3 → ipodsync-0.1.4}/tools/gen_hashab_tables.py +0 -0
  40. {ipodsync-0.1.3 → ipodsync-0.1.4}/tools/gen_lib_templates.py +0 -0
  41. {ipodsync-0.1.3 → ipodsync-0.1.4}/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
3
+ Version: 0.1.4
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ipodsync"
7
- version = "0.1.3"
7
+ version = "0.1.4"
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"
@@ -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.3"
16
+ __version__ = "0.1.4"
17
17
 
18
18
  __all__ = [
19
19
  "find_ipod",
@@ -58,20 +58,30 @@ FIRST_IMAGE_ID = 101
58
58
 
59
59
  # --- encoding a cover to RGB565-LE ----------------------------------------------
60
60
  def to_rgb565(image_bytes: bytes, width: int, height: int, slot: int) -> bytes:
61
- """Resize the cover to width×height, pack into RGB565-LE, pad to slot."""
61
+ """Resize the cover to width×height, pack into RGB565-LE.
62
+
63
+ Each row is padded to the device's row stride (`slot // height`), matching
64
+ iTunes: e.g. F1016 is 57×57 pixels but stored with a 116-byte (58 px) stride —
65
+ 2 zero bytes at the end of every row. Getting this wrong shears the thumbnail.
66
+ """
62
67
  from PIL import Image
63
68
 
64
69
  im = Image.open(io.BytesIO(image_bytes)).convert("RGB").resize(
65
70
  (width, height), Image.LANCZOS)
66
71
  rgb = im.tobytes() # width*height*3
67
- out = bytearray(width * height * 2)
68
- j = 0
69
- for i in range(0, len(rgb), 3):
70
- v = ((rgb[i] >> 3) << 11) | ((rgb[i + 1] >> 2) << 5) | (rgb[i + 2] >> 3)
71
- out[j] = v & 0xFF
72
- out[j + 1] = v >> 8
73
- j += 2
74
- if len(out) < slot:
72
+ stride = slot // height # bytes per row the device reads
73
+ out = bytearray()
74
+ for y in range(height):
75
+ row = bytearray(stride) # zero-padded to the stride
76
+ base, j = y * width * 3, 0
77
+ for _ in range(width):
78
+ v = ((rgb[base] >> 3) << 11) | ((rgb[base + 1] >> 2) << 5) | (rgb[base + 2] >> 3)
79
+ row[j] = v & 0xFF
80
+ row[j + 1] = v >> 8
81
+ base += 3
82
+ j += 2
83
+ out += row
84
+ if len(out) < slot: # safety (slot == height*stride already)
75
85
  out.extend(b"\x00" * (slot - len(out)))
76
86
  return bytes(out)
77
87
 
@@ -137,3 +137,17 @@ def test_row_or_template_fallback_on_empty():
137
137
  got["title"] = "x"
138
138
  assert library._EMPTY_TEMPLATES["item"].get("title") != "x"
139
139
  c.close()
140
+
141
+
142
+ def test_f1016_row_stride_padding():
143
+ """F1016 is 57×57 pixels but stored with a 116-byte (58 px) row stride — two
144
+ zero bytes at the end of every row, like iTunes. Wrong stride shears the thumb."""
145
+ from PIL import Image
146
+ import io
147
+ buf = io.BytesIO()
148
+ Image.new("RGB", (80, 80), (200, 30, 30)).save(buf, format="PNG")
149
+ data = aw.to_rgb565(buf.getvalue(), 57, 57, 6612)
150
+ assert len(data) == 6612
151
+ stride = 6612 // 57 # 116
152
+ for y in range(57):
153
+ assert data[y * stride + 114:y * stride + 116] == b"\x00\x00", f"row {y}"
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