discstation 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/discstation.py +43 -23
- package/src/discstation_burn.py +52 -3
- package/src/discstation_host.py +12 -7
package/package.json
CHANGED
package/src/discstation.py
CHANGED
|
@@ -2933,15 +2933,16 @@ def _run_mpv(ser, cmd, label, kind=None, track_titles=None, track_starts=None):
|
|
|
2933
2933
|
pass
|
|
2934
2934
|
|
|
2935
2935
|
env = os.environ.copy()
|
|
2936
|
-
if "
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2936
|
+
if discstation_host.system_name() == "linux":
|
|
2937
|
+
if "DISPLAY" not in env:
|
|
2938
|
+
env["DISPLAY"] = ":0"
|
|
2939
|
+
try:
|
|
2940
|
+
uid = os.getuid()
|
|
2941
|
+
home_xauth = Path.home() / ".Xauthority"
|
|
2942
|
+
env.setdefault("XAUTHORITY", str(home_xauth) if home_xauth.exists() else f"/run/user/{uid}/.Xauthority")
|
|
2943
|
+
env.setdefault("XDG_RUNTIME_DIR", f"/run/user/{uid}")
|
|
2944
|
+
except Exception:
|
|
2945
|
+
pass
|
|
2945
2946
|
|
|
2946
2947
|
proc = subprocess.Popen(run_as_desktop_user(cmd), env=env)
|
|
2947
2948
|
|
|
@@ -3082,23 +3083,36 @@ def play_flow(ser):
|
|
|
3082
3083
|
|
|
3083
3084
|
if kind == "dvd_video":
|
|
3084
3085
|
if discstation_host.system_name() == "darwin":
|
|
3085
|
-
|
|
3086
|
-
video_ts = mount_dir / "VIDEO_TS"
|
|
3087
|
-
files = sorted(
|
|
3088
|
-
path for path in video_ts.glob("VTS_01_*.VOB")
|
|
3089
|
-
if re.search(r"_\d+\.VOB$", path.name, re.IGNORECASE)
|
|
3090
|
-
and not path.name.upper().endswith("_0.VOB")
|
|
3091
|
-
)
|
|
3092
|
-
if not files:
|
|
3093
|
-
raise RuntimeError("No playable DVD title found")
|
|
3086
|
+
try:
|
|
3094
3087
|
cmd = [
|
|
3095
3088
|
"mpv",
|
|
3096
3089
|
"--input-ipc-server=" + MPV_SOCKET,
|
|
3097
3090
|
"--force-window=yes",
|
|
3098
3091
|
"--idle=no",
|
|
3099
|
-
|
|
3092
|
+
"--dvd-device=" + rip_device(device),
|
|
3093
|
+
"dvdnav://",
|
|
3100
3094
|
]
|
|
3101
3095
|
_run_mpv(ser, cmd, "Playing DVD", kind)
|
|
3096
|
+
except RuntimeError:
|
|
3097
|
+
# libdvdnav couldn't open the disc — fall back to playing the
|
|
3098
|
+
# main title's VOBs off the mounted volume (no menus).
|
|
3099
|
+
with mounted_disc(device) as mount_dir:
|
|
3100
|
+
video_ts = mount_dir / "VIDEO_TS"
|
|
3101
|
+
files = sorted(
|
|
3102
|
+
path for path in video_ts.glob("VTS_01_*.VOB")
|
|
3103
|
+
if re.search(r"_\d+\.VOB$", path.name, re.IGNORECASE)
|
|
3104
|
+
and not path.name.upper().endswith("_0.VOB")
|
|
3105
|
+
)
|
|
3106
|
+
if not files:
|
|
3107
|
+
raise RuntimeError("No playable DVD title found")
|
|
3108
|
+
cmd = [
|
|
3109
|
+
"mpv",
|
|
3110
|
+
"--input-ipc-server=" + MPV_SOCKET,
|
|
3111
|
+
"--force-window=yes",
|
|
3112
|
+
"--idle=no",
|
|
3113
|
+
*[str(path) for path in files],
|
|
3114
|
+
]
|
|
3115
|
+
_run_mpv(ser, cmd, "Playing DVD", kind)
|
|
3102
3116
|
else:
|
|
3103
3117
|
cmd = [
|
|
3104
3118
|
"mpv",
|
|
@@ -3880,10 +3894,16 @@ def check_pidfile():
|
|
|
3880
3894
|
old_pid = int(f.read().strip())
|
|
3881
3895
|
try:
|
|
3882
3896
|
os.kill(old_pid, 0)
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3897
|
+
if sys.platform == "linux":
|
|
3898
|
+
with open(f"/proc/{old_pid}/cmdline") as f:
|
|
3899
|
+
alive = "discstation" in f.read()
|
|
3900
|
+
else:
|
|
3901
|
+
ps = subprocess.run(["ps", "-p", str(old_pid), "-o", "command="],
|
|
3902
|
+
capture_output=True, text=True)
|
|
3903
|
+
alive = "discstation" in ps.stdout
|
|
3904
|
+
if alive:
|
|
3905
|
+
print(f"Already running (PID {old_pid}), exiting")
|
|
3906
|
+
sys.exit(0)
|
|
3887
3907
|
except (OSError, IOError):
|
|
3888
3908
|
pass
|
|
3889
3909
|
except (ValueError, OSError):
|
package/src/discstation_burn.py
CHANGED
|
@@ -121,6 +121,12 @@ def reset_drive(device=None):
|
|
|
121
121
|
"""Power-cycle the USB optical enclosure by toggling its sysfs 'authorized'
|
|
122
122
|
flag (or via DISCSTATION_USB_RESET_CMD). Best-effort; returns True on a
|
|
123
123
|
completed toggle/hook, False otherwise."""
|
|
124
|
+
if discstation_host.system_name() == "darwin":
|
|
125
|
+
# ponytail: no USB re-enumeration on macOS; an eject/reload is the only
|
|
126
|
+
# soft reset available and it drops whatever disc is loaded.
|
|
127
|
+
for cmd in (["/usr/bin/drutil", "eject"], ["/usr/bin/drutil", "tray", "close"]):
|
|
128
|
+
subprocess.run(cmd, capture_output=True, timeout=15, check=False)
|
|
129
|
+
return True
|
|
124
130
|
if discstation_host.system_name() != "linux":
|
|
125
131
|
return False
|
|
126
132
|
if not device:
|
|
@@ -1251,6 +1257,38 @@ def _run_growisofs(ser, growisofs_cmd, log_path, device=None):
|
|
|
1251
1257
|
print(f"Disc eject skipped: {e}")
|
|
1252
1258
|
|
|
1253
1259
|
|
|
1260
|
+
def _run_hdiutil_burn(ser, image_path, device=None):
|
|
1261
|
+
"""Burn a pre-built ISO on macOS via `hdiutil burn -puppetstrings`, streaming
|
|
1262
|
+
its PERCENT: lines to the ESP32."""
|
|
1263
|
+
send(ser, "STATUS:Burning image...")
|
|
1264
|
+
send(ser, "PROGRESS:0%")
|
|
1265
|
+
cmd = discstation_host.iso_burn_command(device or disc_device(), image_path)
|
|
1266
|
+
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
|
1267
|
+
out_lines = []
|
|
1268
|
+
last_pct = -1
|
|
1269
|
+
try:
|
|
1270
|
+
for line in iter_proc_or_cancel(proc, ser):
|
|
1271
|
+
out_lines.append(line)
|
|
1272
|
+
m = re.search(r"PERCENT:([\d.]+)", line)
|
|
1273
|
+
if m:
|
|
1274
|
+
pct = int(float(m.group(1)))
|
|
1275
|
+
if 0 <= pct <= 100 and pct != last_pct:
|
|
1276
|
+
last_pct = pct
|
|
1277
|
+
send(ser, f"PROGRESS:{min(pct, 99)}%")
|
|
1278
|
+
except (KeyboardInterrupt, SystemExit):
|
|
1279
|
+
stop_process(proc)
|
|
1280
|
+
raise
|
|
1281
|
+
rc = proc.wait()
|
|
1282
|
+
if rc != 0:
|
|
1283
|
+
detail = next((l for l in reversed(out_lines) if l.strip()), "hdiutil burn failed")
|
|
1284
|
+
raise RuntimeError(f"Disc burn failed: {detail[:120]}")
|
|
1285
|
+
safe_send(ser, "PROGRESS:100%")
|
|
1286
|
+
try:
|
|
1287
|
+
discstation_host.eject_device(device or disc_device())
|
|
1288
|
+
except Exception as e:
|
|
1289
|
+
print(f"Disc eject skipped: {e}")
|
|
1290
|
+
|
|
1291
|
+
|
|
1254
1292
|
def burn(ser, dvd_dir, disc_label, speed=None, is_dual_layer=False):
|
|
1255
1293
|
if discstation_host.system_name() != "linux":
|
|
1256
1294
|
WORK.mkdir(parents=True, exist_ok=True)
|
|
@@ -1403,8 +1441,15 @@ def burn_audio_cd(ser, audio_files, disc_label, speed=None):
|
|
|
1403
1441
|
# The cooked generic-mmc writer does NOT lay down the CD-TEXT lead-in on most
|
|
1404
1442
|
# ATAPI drives; the raw writer does. Override with DISCSTATION_CDRDAO_DRIVER
|
|
1405
1443
|
# (set it empty to let cdrdao auto-pick).
|
|
1444
|
+
try:
|
|
1445
|
+
cdrdao_write_dev = discstation_host.cdrdao_device(disc_device())
|
|
1446
|
+
except RuntimeError:
|
|
1447
|
+
if discstation_host.system_name() == "darwin":
|
|
1448
|
+
raise RuntimeError("Audio CD burning is not supported on this Mac "
|
|
1449
|
+
"(cdrdao cannot access the optical drive)")
|
|
1450
|
+
raise
|
|
1406
1451
|
cdrdao_cmd = [tool('cdrdao'), 'write', '--buffers', '64',
|
|
1407
|
-
'--device',
|
|
1452
|
+
'--device', cdrdao_write_dev]
|
|
1408
1453
|
driver = os.environ.get("DISCSTATION_CDRDAO_DRIVER", "generic-mmc-raw")
|
|
1409
1454
|
if driver:
|
|
1410
1455
|
cdrdao_cmd += ['--driver', driver]
|
|
@@ -1454,6 +1499,9 @@ def burn_audio_cd(ser, audio_files, disc_label, speed=None):
|
|
|
1454
1499
|
|
|
1455
1500
|
def burn_iso(ser, iso_path, speed=None, is_dual_layer=False):
|
|
1456
1501
|
"""Burn a pre-built ISO directly to disc — no filesystem building."""
|
|
1502
|
+
if discstation_host.system_name() == "darwin":
|
|
1503
|
+
_run_hdiutil_burn(ser, iso_path)
|
|
1504
|
+
return
|
|
1457
1505
|
if discstation_host.system_name() != "linux":
|
|
1458
1506
|
send(ser, "STATUS:Burning image...")
|
|
1459
1507
|
_run_growisofs(ser, discstation_host.iso_burn_command(disc_device(), iso_path), iso_path.parent / "discstation-burn.log")
|
|
@@ -1493,8 +1541,9 @@ def remux_and_author(ser, mpg, disc_label, disc_capacity, dvd_aspect=None):
|
|
|
1493
1541
|
send(ser, f"INFO:Burning {mpg.parent.name}")
|
|
1494
1542
|
|
|
1495
1543
|
send(ser, "STATUS:Remuxing to fix timestamps...")
|
|
1496
|
-
|
|
1497
|
-
|
|
1544
|
+
WORK.mkdir(parents=True, exist_ok=True)
|
|
1545
|
+
v_es = WORK / f"video_{os.getpid()}.m2v"
|
|
1546
|
+
a_es = WORK / f"audio_{os.getpid()}.ac3"
|
|
1498
1547
|
fixed = mpg.parent / "movie_fixed.mpg"
|
|
1499
1548
|
if fixed.exists():
|
|
1500
1549
|
fixed.unlink()
|
package/src/discstation_host.py
CHANGED
|
@@ -295,12 +295,16 @@ def can_use_linux_optical_backend():
|
|
|
295
295
|
|
|
296
296
|
def build_data_image(source_paths, output_path, label, video=False):
|
|
297
297
|
system = system_name()
|
|
298
|
-
if system
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
command = [tool("xorriso"), "-as", "mkisofs", "-
|
|
303
|
-
|
|
298
|
+
if system in ("darwin", "windows"):
|
|
299
|
+
# xorriso's mkisofs emulation is the same lineage as Linux's
|
|
300
|
+
# genisoimage/growisofs; -dvd-video gives a set-top-compatible
|
|
301
|
+
# VIDEO_TS layout (the arg is the dir *containing* VIDEO_TS).
|
|
302
|
+
command = [tool("xorriso"), "-as", "mkisofs", "-V", label, "-o", str(output_path)]
|
|
303
|
+
if video:
|
|
304
|
+
command += ["-dvd-video", "-udf", str(source_paths[0])]
|
|
305
|
+
else:
|
|
306
|
+
command += ["-iso-level", "3", "-J", "-R", "-udf",
|
|
307
|
+
*[str(path) for path in source_paths]]
|
|
304
308
|
else:
|
|
305
309
|
raise RuntimeError("Image building is only used by non-Linux optical backends")
|
|
306
310
|
subprocess.run(command, check=True, capture_output=True, text=True)
|
|
@@ -310,7 +314,8 @@ def build_data_image(source_paths, output_path, label, video=False):
|
|
|
310
314
|
def iso_burn_command(device, image_path):
|
|
311
315
|
system = system_name()
|
|
312
316
|
if system == "darwin":
|
|
313
|
-
|
|
317
|
+
# -puppetstrings emits machine-readable PERCENT: / MESSAGE: lines.
|
|
318
|
+
return [tool("hdiutil"), "burn", "-puppetstrings", str(image_path)]
|
|
314
319
|
if system == "windows":
|
|
315
320
|
return [tool("isoburn.exe"), "/Q", device, str(image_path)]
|
|
316
321
|
raise RuntimeError("ISO command requested on Linux; use growisofs backend")
|