discstation 0.1.35 → 0.1.37

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.
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- import datetime
3
2
  import json
4
3
  import os
5
4
  import re
6
5
  import serial
7
6
  import shutil
8
7
  import subprocess
9
- import sys
10
8
  import threading
11
9
  import time
12
10
  import unicodedata
@@ -169,24 +167,6 @@ def reset_drive(device=None):
169
167
  print("reset_drive: no matching USB optical device found to reset")
170
168
  return False
171
169
 
172
- def _esp32_port_from_sysfs():
173
- """Find the ESP32's USB serial port by matching VID/PID 303a:1001
174
- in sysfs. Returns the device path (e.g. /dev/ttyACM0) or None."""
175
- for tty in Path("/sys/class/tty").glob("ttyACM*"):
176
- uevent = tty / "device" / "uevent"
177
- if uevent.exists():
178
- modalias = uevent.read_text()
179
- if "303a/1001" in modalias or "303a:1001" in modalias:
180
- dev = Path("/dev") / tty.name
181
- if dev.exists():
182
- return str(dev)
183
- return None
184
-
185
-
186
- def detect_esp32_port():
187
- return discstation_host.serial_port() or ""
188
-
189
- PORT = detect_esp32_port()
190
170
  BAUD = 115200
191
171
 
192
172
 
@@ -204,19 +184,26 @@ def check_cancel(ser):
204
184
  return False
205
185
 
206
186
 
207
- def iter_proc_or_cancel(proc, ser):
208
- lines = Queue()
209
- finished = object()
187
+ def proc_line_queue(proc):
188
+ """Pump proc.stdout into a Queue from a daemon thread. Returns
189
+ (queue, done): `done` is the sentinel put on the queue at EOF."""
190
+ lines, done = Queue(), object()
210
191
 
211
- def read_output():
192
+ def pump():
212
193
  try:
213
194
  for line in proc.stdout:
214
195
  lines.put(line.rstrip("\r\n"))
215
196
  finally:
216
- lines.put(finished)
197
+ lines.put(done)
198
+
199
+ threading.Thread(target=pump, daemon=True).start()
200
+ return lines, done
217
201
 
218
- reader = threading.Thread(target=read_output, daemon=True)
219
- reader.start()
202
+
203
+ def iter_proc_or_cancel(proc, ser):
204
+ """Yield proc's output lines, PINGing the remote every 5s; a CANCEL from
205
+ the remote stops the process and ends the iteration."""
206
+ lines, done = proc_line_queue(proc)
220
207
  last_ping = time.time()
221
208
  output_done = False
222
209
  while proc.poll() is None or not output_done:
@@ -231,14 +218,12 @@ def iter_proc_or_cancel(proc, ser):
231
218
  line = lines.get(timeout=0.2)
232
219
  except Empty:
233
220
  continue
234
- if line is finished:
221
+ if line is done:
235
222
  output_done = True
236
223
  else:
237
224
  yield line
238
- reader.join(timeout=1)
239
225
 
240
226
 
241
- DVD_DEVICE = os.environ.get("DISC_DEVICE") or os.environ.get("DVD_DEVICE")
242
227
  DISC_SPEED = os.environ.get("DISC_SPEED")
243
228
  DISC_DISC_BYTES = 4_700_000_000
244
229
  DVD_DL_BYTES = 8_500_000_000
@@ -1737,18 +1722,7 @@ def _run_windows_burn(ser, script, *script_args):
1737
1722
  cmd, kwargs = discstation_host.ps_cmd(script, *script_args)
1738
1723
  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, **kwargs)
1739
1724
 
1740
- lines = Queue()
1741
- finished = object()
1742
-
1743
- def read_output():
1744
- try:
1745
- for line in proc.stdout:
1746
- lines.put(line.rstrip("\r\n"))
1747
- finally:
1748
- lines.put(finished)
1749
-
1750
- reader = threading.Thread(target=read_output, daemon=True)
1751
- reader.start()
1725
+ lines, finished = proc_line_queue(proc)
1752
1726
 
1753
1727
  out_lines, last_pct, last_real_progress, start = [], -1, 0.0, time.time()
1754
1728
  last_ping, output_done = time.time(), False
@@ -1784,7 +1758,6 @@ def _run_windows_burn(ser, script, *script_args):
1784
1758
  if est_pct > last_pct:
1785
1759
  last_pct = est_pct
1786
1760
  send(ser, f"PROGRESS:{est_pct}%")
1787
- reader.join(timeout=1)
1788
1761
  except (KeyboardInterrupt, SystemExit):
1789
1762
  stop_process(proc)
1790
1763
  raise
@@ -1841,10 +1814,8 @@ def remux_and_author(ser, mpg, disc_label, disc_capacity, dvd_aspect=None):
1841
1814
  silent blocking subprocess call that lets the ESP32's 30s watchdog
1842
1815
  flip the display to "disconnected" partway through.
1843
1816
 
1844
- This is the single place the remux+author sequence lives — the
1845
- full download/convert pipeline, the OLED burn_mpg_flow picker, and
1846
- any CLI entry point all call this (via remux_and_burn below, or
1847
- directly), so a fix here only has to happen once.
1817
+ This is the single place the remux+author sequence lives (burn_flow
1818
+ calls it after the download/convert pipeline).
1848
1819
  """
1849
1820
  check_encoded_size(ser, mpg, disc_capacity)
1850
1821
  send(ser, f"TITLE:{disc_label}")
@@ -1894,163 +1865,3 @@ def remux_and_author(ser, mpg, disc_label, disc_capacity, dvd_aspect=None):
1894
1865
  dvd_dir = author(ser, fixed, fixed.parent, dvd_aspect)
1895
1866
  check_dvd_size(ser, dvd_dir, disc_capacity)
1896
1867
  return dvd_dir
1897
-
1898
- def remux_and_burn(ser, mpg, disc_label, disc_capacity, dl_info, burn_speed=None, dvd_aspect=None):
1899
- dvd_dir = remux_and_author(ser, mpg, disc_label, disc_capacity, dvd_aspect)
1900
- wait_for_burn_confirm(ser, dvd_dir, disc_capacity)
1901
- burn(ser, dvd_dir, disc_label, burn_speed, dl_info["is_dual_layer"])
1902
- safe_send(ser, "DONE:Burn complete!")
1903
- print(f"Burned {mpg} as {disc_label}")
1904
-
1905
-
1906
- def main():
1907
- if len(sys.argv) < 2:
1908
- print("Usage: python3 discstation_burn.py 'video URL or file path'")
1909
- sys.exit(1)
1910
- url = sys.argv[1]
1911
- WORK.mkdir(parents=True, exist_ok=True)
1912
- job_dir = WORK / time.strftime("job_%Y%m%d_%H%M%S")
1913
- job_dir.mkdir()
1914
- ser = None
1915
- try:
1916
- ser = serial.Serial(PORT, BAUD, timeout=1)
1917
- reset_serial_state()
1918
- time.sleep(2)
1919
- print("Connected to DiscStation")
1920
- print("Running preflight...")
1921
- send(ser, "STATUS:Preflight...")
1922
- info = get_video_info(url, ser)
1923
- title = info["title"]
1924
- duration = info["duration"]
1925
- duration_line, fit_line, can_fit = preflight_lines(duration)
1926
- print(f"Title: {title}")
1927
- print(f"Duration: {format_duration(duration)}")
1928
- print(f"Preflight: {fit_line}")
1929
- print(f"DVD drive: {disc_device()}")
1930
- disc_label = sanitize_disc_label(title)
1931
- print(f"Disc label: {disc_label}")
1932
- send(ser, f"TITLE:{title}")
1933
- send(ser, f"META:{duration_line}")
1934
- send(ser, f"FIT:{fit_line}")
1935
- if not can_fit:
1936
- raise RuntimeError("Video too long for DVD5")
1937
-
1938
- device = disc_device()
1939
- dl_info = detect_disc_type(device)
1940
- if dl_info["is_dual_layer"]:
1941
- sl_target = int(os.environ.get("DISC_TARGET_BYTES", "4300000000"))
1942
- try:
1943
- sl_plan = bitrate_plan(duration, "AUTO", sl_target)
1944
- if sl_plan:
1945
- warn = f"DL disc for SL content"
1946
- print(f"WARNING: {warn}")
1947
- safe_send(ser, f"WARNING:{warn}")
1948
- time.sleep(3)
1949
- except RuntimeError:
1950
- pass
1951
-
1952
- print("Waiting for button press...")
1953
- selected_mode = "AUTO"
1954
- burn_speed = None
1955
- while True:
1956
- if ser.in_waiting:
1957
- resp = ser.readline().decode(errors='ignore').strip()
1958
- note_serial_activity()
1959
- if resp == "CANCEL":
1960
- print("Cancelled by user")
1961
- safe_send(ser, "CANCELLED:Cancelled")
1962
- sys.exit(130)
1963
- elif resp.startswith("MODE:"):
1964
- selected_mode = normalize_mode(resp.split(":", 1)[1])
1965
- print(f"Mode: {selected_mode}")
1966
- elif resp.startswith("SPEED:"):
1967
- burn_speed = resp.split(":", 1)[1].strip()
1968
- print(f"Burn speed: {burn_speed}")
1969
- elif resp == "START" or resp.startswith("START:"):
1970
- if ":" in resp:
1971
- selected_mode = normalize_mode(resp.split(":", 1)[1])
1972
- print(f"Button pressed - starting in {selected_mode} mode!")
1973
- send(ser, f"STATUS:Starting {selected_mode}...")
1974
- break
1975
- time.sleep(0.1)
1976
-
1977
- start_time = time.time()
1978
- disc_type_label = "DL" if dl_info["is_dual_layer"] else "SL"
1979
- try:
1980
- disc_bytes = dl_info["capacity"]
1981
- plan = bitrate_plan(duration, selected_mode, disc_bytes)
1982
- video = download(ser, url, job_dir)
1983
- mpg, dvd_aspect = convert(ser, video, job_dir, selected_mode, disc_bytes)
1984
- dvd = remux_and_author(ser, mpg, disc_label, disc_bytes, dvd_aspect)
1985
- if plan["burn"]:
1986
- burn(ser, dvd, disc_label, burn_speed, dl_info["is_dual_layer"])
1987
- send(ser, "DONE:Disc complete!")
1988
- print("Done!")
1989
- else:
1990
- send(ser, "DONE:Test complete!")
1991
- print("Test complete. DVD folder was built but not burned.")
1992
- append_history({
1993
- "timestamp": datetime.datetime.now().isoformat(),
1994
- "title": title,
1995
- "disc_type": disc_type_label,
1996
- "mode": selected_mode,
1997
- "speed": burn_speed or "Auto",
1998
- "success": True,
1999
- "duration_s": round(time.time() - start_time),
2000
- })
2001
- except (KeyboardInterrupt, SystemExit):
2002
- append_history({
2003
- "timestamp": datetime.datetime.now().isoformat(),
2004
- "title": title,
2005
- "disc_type": disc_type_label,
2006
- "mode": selected_mode,
2007
- "speed": burn_speed or "Auto",
2008
- "success": False,
2009
- "error": "Cancelled",
2010
- "duration_s": round(time.time() - start_time),
2011
- })
2012
- raise
2013
- except Exception as e:
2014
- append_history({
2015
- "timestamp": datetime.datetime.now().isoformat(),
2016
- "title": title,
2017
- "disc_type": disc_type_label,
2018
- "mode": selected_mode,
2019
- "speed": burn_speed or "Auto",
2020
- "success": False,
2021
- "error": str(e)[:100],
2022
- "duration_s": round(time.time() - start_time),
2023
- })
2024
- raise
2025
- except (KeyboardInterrupt, SystemExit):
2026
- safe_send(ser, "CANCELLED:Stopped")
2027
- print("Cancelled")
2028
- sys.exit(130)
2029
- except RuntimeError as e:
2030
- msg = str(e)
2031
- if msg == "Cancelled":
2032
- safe_send(ser, "CANCELLED:Stopped")
2033
- else:
2034
- safe_send(ser, f"ERROR:{msg[:20]}")
2035
- print(f"Error: {e}")
2036
- sys.exit(1)
2037
- except Exception as e:
2038
- safe_send(ser, f"ERROR:{str(e)[:20]}")
2039
- print(f"Error: {e}")
2040
- sys.exit(1)
2041
- finally:
2042
- if ser:
2043
- ser.close()
2044
-
2045
-
2046
- BURN_HISTORY = WORK / "burn_history.jsonl"
2047
-
2048
-
2049
- def append_history(entry):
2050
- BURN_HISTORY.parent.mkdir(parents=True, exist_ok=True)
2051
- with open(BURN_HISTORY, "a") as f:
2052
- f.write(json.dumps(entry) + "\n")
2053
-
2054
-
2055
- if __name__ == "__main__":
2056
- main()
@@ -177,13 +177,6 @@ h1 {
177
177
  font-weight: 900;
178
178
  }
179
179
  h1 em { font-style: normal; -webkit-text-stroke: 1px var(--ink); color: transparent; }
180
- .lede {
181
- max-width: 560px;
182
- margin: 28px 0 26px;
183
- color: var(--muted);
184
- font-size: 12px;
185
- line-height: 1.8;
186
- }
187
180
  .hero-specs {
188
181
  display: flex;
189
182
  flex-wrap: wrap;
@@ -0,0 +1,66 @@
1
+ # Shared IMAPI2 helpers, dot-sourced by the burn scripts (same pattern as _json.ps1).
2
+
3
+ function Get-Recorder([string]$letter) {
4
+ $master = New-Object -ComObject "IMAPI2.MsftDiscMaster2"
5
+ for ($i = 0; $i -lt $master.Count; $i++) {
6
+ $rec = New-Object -ComObject "IMAPI2.MsftDiscRecorder2"
7
+ $rec.InitializeDiscRecorder($master.Item($i))
8
+ foreach ($p in $rec.VolumePathNames) {
9
+ if ($p -and $p.TrimEnd('\') -ieq $letter) { return $rec }
10
+ }
11
+ }
12
+ throw "No optical recorder for $letter"
13
+ }
14
+
15
+ # Apply an optional "8x"-style write speed; not fatal if the drive refuses it.
16
+ function Set-WriteSpeed($fmt, [string]$speed) {
17
+ if ($speed -and $speed -match '^\d+') {
18
+ try { $fmt.SetWriteSpeed([int]($speed -replace '\D',''), $false) } catch {}
19
+ }
20
+ }
21
+
22
+ # A data-disc (IMAPI2.MsftDiscFormat2Data) writer bound to the drive letter.
23
+ function New-DataWriter([string]$letter, [string]$speed) {
24
+ $rec = Get-Recorder $letter
25
+ $fmt = New-Object -ComObject "IMAPI2.MsftDiscFormat2Data"
26
+ if (-not $fmt.IsRecorderSupported($rec)) { Write-Error "recorder not supported"; exit 3 }
27
+ $fmt.Recorder = $rec
28
+ $fmt.ClientName = "DiscStation"
29
+ # Without this the disc session never finalizes - the drive reports the
30
+ # disc as still blank afterward even though the data is physically there.
31
+ try { $fmt.ForceMediaToBeClosed = $true } catch {}
32
+ Set-WriteSpeed $fmt $speed
33
+ return $fmt
34
+ }
35
+
36
+ # Write $stream to the disc, streaming "PROGRESS:<pct>" lines, then eject.
37
+ # Sets $global:BurnExit (0 ok, 1 failed) for the caller to `exit` with.
38
+ function Write-DiscStream($fmt, $stream) {
39
+ # IMAPI2 raises an Update event with sector counts. Not fatal if
40
+ # registration fails (seen on some setups: "Cannot register for the
41
+ # specified event... does not exist") - the burn itself doesn't need it,
42
+ # just no live PROGRESS lines.
43
+ try {
44
+ Register-ObjectEvent -InputObject $fmt -EventName "Update" -SourceIdentifier "burn" -Action {
45
+ $s = $EventArgs
46
+ try {
47
+ $done = [double]$s.LastWrittenLba
48
+ $tot = [double]$s.SectorCount
49
+ if ($tot -gt 0) { Write-Output ("PROGRESS:" + [int]([math]::Min(99, $done * 100.0 / $tot))) }
50
+ } catch {}
51
+ } | Out-Null
52
+ } catch {}
53
+
54
+ try {
55
+ $fmt.Write($stream)
56
+ Write-Output "PROGRESS:100"
57
+ $global:BurnExit = 0
58
+ } catch {
59
+ Write-Error ("burn failed: " + $_.Exception.Message)
60
+ $global:BurnExit = 1
61
+ } finally {
62
+ try { $stream.Close() } catch {}
63
+ Unregister-Event -SourceIdentifier "burn" -ErrorAction SilentlyContinue
64
+ try { $fmt.Recorder.EjectMedia() } catch {}
65
+ }
66
+ }
@@ -6,15 +6,7 @@ param([Parameter(Mandatory = $true)] [string] $Drive)
6
6
  $ErrorActionPreference = "Stop"
7
7
  . (Join-Path $PSScriptRoot "_json.ps1")
8
8
 
9
- function Get-Recorder([string]$letter) {
10
- $master = New-Object -ComObject "IMAPI2.MsftDiscMaster2"
11
- for ($i = 0; $i -lt $master.Count; $i++) {
12
- $rec = New-Object -ComObject "IMAPI2.MsftDiscRecorder2"
13
- $rec.InitializeDiscRecorder($master.Item($i))
14
- foreach ($p in $rec.VolumePathNames) { if ($p -and $p.TrimEnd('\') -ieq $letter) { return $rec } }
15
- }
16
- throw "No optical recorder for $letter"
17
- }
9
+ . (Join-Path $PSScriptRoot "_imapi.ps1")
18
10
 
19
11
  $rec = Get-Recorder $Drive
20
12
  $raw = New-Object -ComObject "IMAPI2.MsftDiscFormat2RawCD"
@@ -11,17 +11,7 @@ $ErrorActionPreference = "Stop"
11
11
  $wavs = @(Get-ChildItem -LiteralPath $WavDir -Filter *.wav | Sort-Object Name)
12
12
  if ($wavs.Count -eq 0) { Write-Error "no WAV files in $WavDir"; exit 2 }
13
13
 
14
- function Get-Recorder([string]$letter) {
15
- $master = New-Object -ComObject "IMAPI2.MsftDiscMaster2"
16
- for ($i = 0; $i -lt $master.Count; $i++) {
17
- $rec = New-Object -ComObject "IMAPI2.MsftDiscRecorder2"
18
- $rec.InitializeDiscRecorder($master.Item($i))
19
- foreach ($p in $rec.VolumePathNames) {
20
- if ($p -and $p.TrimEnd('\') -ieq $letter) { return $rec }
21
- }
22
- }
23
- throw "No optical recorder for $letter"
24
- }
14
+ . (Join-Path $PSScriptRoot "_imapi.ps1")
25
15
 
26
16
  $rec = Get-Recorder $Drive
27
17
  $fmt = New-Object -ComObject "IMAPI2.MsftDiscFormat2TrackAtOnce"
@@ -29,9 +19,7 @@ if (-not $fmt.IsRecorderSupported($rec)) { Write-Error "recorder not supported";
29
19
  $fmt.Recorder = $rec
30
20
  $fmt.ClientName = "DiscStation"
31
21
  try { $fmt.NumberOfExistingTracks } catch {}
32
- if ($Speed -and $Speed -match '^\d+') {
33
- try { $fmt.SetWriteSpeed([int]($Speed -replace '\D',''), $false) } catch {}
34
- }
22
+ Set-WriteSpeed $fmt $Speed
35
23
 
36
24
  $prepared = @()
37
25
  foreach ($w in $wavs) {
@@ -11,29 +11,9 @@ param(
11
11
  $ErrorActionPreference = "Stop"
12
12
  if (-not (Test-Path $Source)) { Write-Error "source not found: $Source"; exit 2 }
13
13
 
14
- function Get-Recorder([string]$letter) {
15
- $master = New-Object -ComObject "IMAPI2.MsftDiscMaster2"
16
- for ($i = 0; $i -lt $master.Count; $i++) {
17
- $rec = New-Object -ComObject "IMAPI2.MsftDiscRecorder2"
18
- $rec.InitializeDiscRecorder($master.Item($i))
19
- foreach ($p in $rec.VolumePathNames) {
20
- if ($p -and $p.TrimEnd('\') -ieq $letter) { return $rec }
21
- }
22
- }
23
- throw "No optical recorder for $letter"
24
- }
14
+ . (Join-Path $PSScriptRoot "_imapi.ps1")
25
15
 
26
- $rec = Get-Recorder $Drive
27
- $fmt = New-Object -ComObject "IMAPI2.MsftDiscFormat2Data"
28
- if (-not $fmt.IsRecorderSupported($rec)) { Write-Error "recorder not supported"; exit 3 }
29
- $fmt.Recorder = $rec
30
- $fmt.ClientName = "DiscStation"
31
- # Without this the disc session never finalizes - the drive reports the
32
- # disc as still blank afterward even though the data is physically there.
33
- try { $fmt.ForceMediaToBeClosed = $true } catch {}
34
- if ($Speed -and $Speed -match '^\d+') {
35
- try { $fmt.SetWriteSpeed([int]($Speed -replace '\D',''), $false) } catch {}
36
- }
16
+ $fmt = New-DataWriter $Drive $Speed
37
17
 
38
18
  # Filesystem image: ISO9660 + Joliet + UDF, sized to the loaded media.
39
19
  $fsi = New-Object -ComObject "IMAPI2FS.MsftFileSystemImage"
@@ -56,28 +36,5 @@ if ($item.PSIsContainer) {
56
36
  $result = $fsi.CreateResultImage()
57
37
  $stream = $result.ImageStream
58
38
 
59
- # Not fatal if registration fails (seen on some setups: "Cannot register
60
- # for the specified event... does not exist") - the burn itself doesn't
61
- # need it, just no live PROGRESS lines.
62
- try {
63
- Register-ObjectEvent -InputObject $fmt -EventName "Update" -SourceIdentifier "burn" -Action {
64
- $s = $EventArgs
65
- try {
66
- $done = [double]$s.LastWrittenLba
67
- $tot = [double]$s.SectorCount
68
- if ($tot -gt 0) { Write-Output ("PROGRESS:" + [int]([math]::Min(99, $done * 100.0 / $tot))) }
69
- } catch {}
70
- } | Out-Null
71
- } catch {}
72
-
73
- try {
74
- $fmt.Write($stream)
75
- Write-Output "PROGRESS:100"
76
- exit 0
77
- } catch {
78
- Write-Error ("burn failed: " + $_.Exception.Message)
79
- exit 1
80
- } finally {
81
- Unregister-Event -SourceIdentifier "burn" -ErrorAction SilentlyContinue
82
- try { $rec.EjectMedia() } catch {}
83
- }
39
+ Write-DiscStream $fmt $stream
40
+ exit $global:BurnExit
@@ -8,61 +8,12 @@ param(
8
8
 
9
9
  $ErrorActionPreference = "Stop"
10
10
  if (-not (Test-Path $Iso)) { Write-Error "ISO not found: $Iso"; exit 2 }
11
+ . (Join-Path $PSScriptRoot "_imapi.ps1")
11
12
 
12
- function Get-Recorder([string]$letter) {
13
- $master = New-Object -ComObject "IMAPI2.MsftDiscMaster2"
14
- for ($i = 0; $i -lt $master.Count; $i++) {
15
- $rec = New-Object -ComObject "IMAPI2.MsftDiscRecorder2"
16
- $rec.InitializeDiscRecorder($master.Item($i))
17
- foreach ($p in $rec.VolumePathNames) {
18
- if ($p -and $p.TrimEnd('\') -ieq $letter) { return $rec }
19
- }
20
- }
21
- throw "No optical recorder for $letter"
22
- }
23
-
24
- $rec = Get-Recorder $Drive
25
- $fmt = New-Object -ComObject "IMAPI2.MsftDiscFormat2Data"
26
- if (-not $fmt.IsRecorderSupported($rec)) { Write-Error "recorder not supported"; exit 3 }
27
- $fmt.Recorder = $rec
28
- $fmt.ClientName = "DiscStation"
29
- try { $fmt.ForceMediaToBeClosed = $true } catch {}
30
- if ($Speed -and $Speed -match '^\d+') {
31
- try { $fmt.SetWriteSpeed([int]($Speed -replace '\D',''), $false) } catch {}
32
- }
33
-
34
- # Progress: IMAPI2 raises an Update event with sector counts. Not fatal if
35
- # registration fails (seen on some setups: "Cannot register for the
36
- # specified event... does not exist") - the burn itself doesn't need it,
37
- # just no live PROGRESS lines.
38
- try {
39
- Register-ObjectEvent -InputObject $fmt -EventName "Update" -SourceIdentifier "burn" -Action {
40
- $s = $EventArgs
41
- try {
42
- $done = [double]$s.LastWrittenLba
43
- $tot = [double]$s.SectorCount
44
- if ($tot -gt 0) {
45
- $pct = [int]([math]::Min(99, $done * 100.0 / $tot))
46
- Write-Output "PROGRESS:$pct"
47
- }
48
- } catch {}
49
- } | Out-Null
50
- } catch {}
51
-
13
+ $fmt = New-DataWriter $Drive $Speed
52
14
  $stream = New-Object -ComObject "ADODB.Stream"
53
15
  $stream.Type = 1 # binary
54
16
  $stream.Open()
55
17
  $stream.LoadFromFile($Iso)
56
-
57
- try {
58
- $fmt.Write($stream)
59
- Write-Output "PROGRESS:100"
60
- exit 0
61
- } catch {
62
- Write-Error ("burn failed: " + $_.Exception.Message)
63
- exit 1
64
- } finally {
65
- $stream.Close()
66
- Unregister-Event -SourceIdentifier "burn" -ErrorAction SilentlyContinue
67
- try { $rec.EjectMedia() } catch {}
68
- }
18
+ Write-DiscStream $fmt $stream
19
+ exit $global:BurnExit