discstation 0.1.33 → 0.1.34

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.
package/install-macos.sh CHANGED
@@ -50,7 +50,7 @@ if [[ -f "$ROOT_DIR/requirements-optional.txt" ]]; then
50
50
  fi
51
51
 
52
52
  DISC_DEVICE="${DISC_DEVICE:-}"
53
- DISC_PORT="${DISC_PORT:-$(ls /dev/cu.usbserial-* /dev/cu.usbmodem-* 2>/dev/null | head -1)}"
53
+ DISC_PORT="${DISC_PORT:-$(ls /dev/cu.usbserial-* /dev/cu.usbmodem-* 2>/dev/null | head -1 || true)}"
54
54
 
55
55
  if [[ ! -f "$CONFIG_DIR/server.crt" || ! -f "$CONFIG_DIR/server.key" ]]; then
56
56
  openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discstation",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "DiscStation optical-media appliance host — one cross-OS installer",
5
5
  "bin": {
6
6
  "discstation-setup": "scripts/setup.mjs",
@@ -1567,30 +1567,27 @@ def burn_audio_cd(ser, audio_files, disc_label, speed=None):
1567
1567
 
1568
1568
  album_t = _cdt(album_title) or "Audio CD"
1569
1569
  album_p = _cdt(album_artist) or "Unknown Artist"
1570
- toc_lines = ["CD_DA"]
1571
- toc_lines.append("CD_TEXT {")
1572
- toc_lines.append(" LANGUAGE_MAP { 0: EN }")
1573
- toc_lines.append(" LANGUAGE 0 {")
1574
- toc_lines.append(f' TITLE "{album_t}"')
1575
- toc_lines.append(f' PERFORMER "{album_p}"')
1576
- toc_lines.append(" }")
1577
- toc_lines.append("}")
1578
- toc_lines.append("")
1579
- for i, (artist, title) in enumerate(track_meta):
1580
- wav = tmp_dir / f"track_{i + 1:02d}.wav"
1581
- track_t = _cdt(title) or f"Track {i + 1:02d}"
1582
- track_p = _cdt(artist) or album_p
1583
- toc_lines.append("TRACK AUDIO")
1584
- toc_lines.append("CD_TEXT {")
1585
- toc_lines.append(" LANGUAGE 0 {")
1586
- toc_lines.append(f' TITLE "{track_t}"')
1587
- toc_lines.append(f' PERFORMER "{track_p}"')
1588
- toc_lines.append(" }")
1589
- toc_lines.append("}")
1590
- toc_lines.append(f'FILE "{wav}" 0')
1591
- toc_lines.append("")
1592
1570
  toc_path = tmp_dir / "disc.toc"
1593
- toc_path.write_text("\n".join(toc_lines) + "\n")
1571
+
1572
+ def _write_toc(include_cdtext):
1573
+ toc_lines = ["CD_DA"]
1574
+ if include_cdtext:
1575
+ toc_lines += ["CD_TEXT {", " LANGUAGE_MAP { 0: EN }", " LANGUAGE 0 {",
1576
+ f' TITLE "{album_t}"', f' PERFORMER "{album_p}"',
1577
+ " }", "}", ""]
1578
+ for i, (artist, title) in enumerate(track_meta):
1579
+ wav = tmp_dir / f"track_{i + 1:02d}.wav"
1580
+ toc_lines.append("TRACK AUDIO")
1581
+ if include_cdtext:
1582
+ track_t = _cdt(title) or f"Track {i + 1:02d}"
1583
+ track_p = _cdt(artist) or album_p
1584
+ toc_lines += ["CD_TEXT {", " LANGUAGE 0 {", f' TITLE "{track_t}"',
1585
+ f' PERFORMER "{track_p}"', " }", "}"]
1586
+ toc_lines.append(f'FILE "{wav}" 0')
1587
+ toc_lines.append("")
1588
+ toc_path.write_text("\n".join(toc_lines) + "\n")
1589
+
1590
+ _write_toc(include_cdtext=True)
1594
1591
  print(f"CD-TEXT: album={album_t!r} performer={album_p!r}, "
1595
1592
  f"{len(track_meta)} track titles")
1596
1593
  send(ser, "PROGRESS:35%")
@@ -1607,9 +1604,8 @@ def burn_audio_cd(ser, audio_files, disc_label, speed=None):
1607
1604
  return
1608
1605
 
1609
1606
  send(ser, "STATUS:Burning audio CD...")
1610
- # The cooked generic-mmc writer does NOT lay down the CD-TEXT lead-in on most
1611
- # ATAPI drives; the raw writer does. Override with DISCSTATION_CDRDAO_DRIVER
1612
- # (set it empty to let cdrdao auto-pick).
1607
+ # DISCSTATION_CDRDAO_DRIVER forces one specific driver (set it empty for
1608
+ # cdrdao to auto-pick) instead of the try-in-order fallback below.
1613
1609
  try:
1614
1610
  cdrdao_write_dev = discstation_host.cdrdao_device(disc_device())
1615
1611
  except RuntimeError:
@@ -1617,39 +1613,71 @@ def burn_audio_cd(ser, audio_files, disc_label, speed=None):
1617
1613
  raise RuntimeError("Audio CD burning is not supported on this Mac "
1618
1614
  "(cdrdao cannot access the optical drive)")
1619
1615
  raise
1620
- cdrdao_cmd = [tool('cdrdao'), 'write', '--buffers', '64',
1621
- '--device', cdrdao_write_dev]
1622
- driver = os.environ.get("DISCSTATION_CDRDAO_DRIVER", "generic-mmc-raw")
1623
- if driver:
1624
- cdrdao_cmd += ['--driver', driver]
1625
- speed_ = speed or DISC_SPEED
1626
- if speed_ and speed_.lower() != "auto":
1627
- cdrdao_cmd += ['--speed', speed_.rstrip('x')]
1628
- cdrdao_cmd.append(str(toc_path)) # toc-file must come after all options
1629
-
1630
- proc = subprocess.Popen(cdrdao_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
1631
- out_lines = []
1616
+ def _run_cdrdao(driver):
1617
+ cmd = [tool('cdrdao'), 'write', '--buffers', '64', '--device', cdrdao_write_dev]
1618
+ if driver:
1619
+ cmd += ['--driver', driver]
1620
+ speed_ = speed or DISC_SPEED
1621
+ if speed_ and speed_.lower() != "auto":
1622
+ cmd += ['--speed', speed_.rstrip('x')]
1623
+ cmd.append(str(toc_path)) # toc-file must come after all options
1624
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
1625
+ lines = []
1626
+ last_prog = 0
1627
+ try:
1628
+ for line in iter_proc_or_cancel(proc, ser):
1629
+ lines.append(line)
1630
+ # "Wrote 12 of 650 MB (Buffers 99% 100%)." is the real write
1631
+ # progress - matching on the first bare NN% instead (as this
1632
+ # used to) grabs the *buffer fill* percentage, which BURN-Proof
1633
+ # holds near 100% for virtually the whole burn, so the OLED
1634
+ # jumped straight from "Converting" to 100% and sat there.
1635
+ m = re.search(r'Wrote\s+(\d+)\s+of\s+(\d+)\s+MB', line)
1636
+ if m and int(m.group(2)) > 0:
1637
+ frac = int(m.group(1)) / int(m.group(2))
1638
+ pct = 35 + int(frac * 65)
1639
+ now = time.time()
1640
+ if now - last_prog >= 0.2:
1641
+ send(ser, f"PROGRESS:{pct}%")
1642
+ last_prog = now
1643
+ except (KeyboardInterrupt, SystemExit):
1644
+ stop_process(proc)
1645
+ raise
1646
+ return proc.wait(), lines
1647
+
1632
1648
  log_path = WORK / "cdrdao.log"
1633
- last_prog = 0
1649
+ override = os.environ.get("DISCSTATION_CDRDAO_DRIVER")
1650
+ # generic-mmc-raw (raw P-W sub-channel writing) is what guarantees CD-TEXT,
1651
+ # but some drives reject raw sub-channel writing outright - confirmed live
1652
+ # on this drive with --simulate: it fails at the lead-in even with CD-TEXT
1653
+ # stripped out entirely, so it's the raw *driver* that's incompatible here,
1654
+ # not CD-TEXT itself. generic-mmc (cooked) writes fine on the same drive
1655
+ # (also confirmed with --simulate) and still carries the CD-TEXT blocks in
1656
+ # the TOC, so it's worth trying with labels intact before dropping them -
1657
+ # only the last resort actually removes CD-TEXT from the TOC.
1658
+ drivers_to_try = [override] if override else ["generic-mmc-raw", "generic-mmc"]
1634
1659
  try:
1635
- for line in iter_proc_or_cancel(proc, ser):
1636
- out_lines.append(line)
1637
- m = re.search(r'(\d+)\s*%', line)
1638
- if m:
1639
- pct = 35 + int(int(m.group(1)) * 0.65)
1640
- now = time.time()
1641
- if now - last_prog >= 0.2:
1642
- send(ser, f"PROGRESS:{pct}%")
1643
- last_prog = now
1644
- except (KeyboardInterrupt, SystemExit):
1645
- stop_process(proc)
1646
- raise
1660
+ rc, out_lines = -1, []
1661
+ for i, driver in enumerate(drivers_to_try):
1662
+ if i > 0:
1663
+ print(f"cdrdao: write failed with previous driver - retrying with --driver {driver or 'auto'}")
1664
+ send(ser, "STATUS:Retrying burn...")
1665
+ rc, lines = _run_cdrdao(driver)
1666
+ out_lines += ([f"--- driver {driver or 'auto'} ---"] if i else []) + lines
1667
+ if rc == 0 or rc == -15:
1668
+ break
1669
+ if rc != 0 and rc != -15 and not override:
1670
+ print("cdrdao: all driver modes failed - retrying without CD-TEXT")
1671
+ send(ser, "STATUS:Retrying without CD-TEXT...")
1672
+ _write_toc(include_cdtext=False)
1673
+ rc2, lines2 = _run_cdrdao(None)
1674
+ out_lines += ["--- retry without CD-TEXT ---"] + lines2
1675
+ rc = rc2
1647
1676
  finally:
1648
1677
  for w in tmp_dir.glob("*.wav"):
1649
1678
  w.unlink(missing_ok=True)
1650
1679
  toc_path.unlink(missing_ok=True)
1651
1680
  shutil.rmtree(str(tmp_dir), ignore_errors=True)
1652
- rc = proc.wait()
1653
1681
  log_path.write_text("\n".join(out_lines) + "\n")
1654
1682
  if rc != 0:
1655
1683
  for line in out_lines[-10:]: