discstation 0.1.0

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.
@@ -0,0 +1,19 @@
1
+ # Optional YouTube access settings. Copy to ~/.local/share/discstation/discstation.env.
2
+ # Do not commit cookies, user-agent values, or PO tokens.
3
+ YTDLP_PLAYER_CLIENTS=web_embedded,android_vr
4
+ YTDLP_HTTP_CHUNK_SIZE=1M
5
+ YTDLP_RETRIES=3
6
+ YTDLP_FRAGMENT_RETRIES=3
7
+ DISC_OUTPUT_LIMIT_BYTES=4300000000
8
+ DISC_DL_OUTPUT_LIMIT_BYTES=8000000000
9
+
10
+ # TMDb API key for video (DVD / burned movie) metadata lookup. Get a free key at
11
+ # https://www.themoviedb.org/settings/api . Without it, ripped videos keep their
12
+ # disc-label / filename naming. Alternatively put the key in
13
+ # ~/.local/share/discstation/tmdb.key
14
+ # DISCSTATION_TMDB_API_KEY=
15
+ # YTDLP_COOKIES_FROM_BROWSER=firefox
16
+ # YTDLP_COOKIES=/home/phuju/.config/discstation/youtube-cookies.txt
17
+ # YTDLP_USER_AGENT=Mozilla/5.0 ...
18
+ # YTDLP_PO_TOKEN=web_embedded.gvs+TOKEN
19
+ # YTDLP_EXTRACTOR_ARGS=youtube:player_client=web_embedded
@@ -0,0 +1,39 @@
1
+ # DiscStation Platform Support
2
+
3
+ ## Linux
4
+
5
+ Full host support: DVD-Video, data discs, ISO images, audio CDs, ripping,
6
+ playback, HTTPS web UI, ESP32 serial, and ESP32 Wi-Fi/OTA.
7
+
8
+ Run `./install.sh` on Debian, Ubuntu, or Raspberry Pi OS.
9
+
10
+ ## macOS
11
+
12
+ The Python host and web/control workflow run with Homebrew dependencies. Data
13
+ and DVD image burning use `hdiutil`; DVD-Video discs can be mounted read-only
14
+ for playback and VIDEO_TS ripping; audio CD burning uses cdrdao. Optical-device
15
+ discovery and video/audio workflows are experimental; use `DISC_DEVICE`
16
+ when automatic detection does not find the external drive. Apple Music owns
17
+ audio CD playback and ripping on macOS, so DiscStation does not probe or access
18
+ audio tracks for those operations. Audio burns include CD-TEXT from the source
19
+ folder/file name and track metadata. Apple Music may still show generic track
20
+ names when its online CD lookup has no match; CD-TEXT-capable players can read
21
+ the embedded names. The macOS installer installs a persistent `launchd` service.
22
+
23
+ ## Windows
24
+
25
+ The Python host and web/control workflow can run from PowerShell. Optical
26
+ burning needs a Windows IMAPI backend or a separately installed compatible
27
+ burning tool. The Windows installer deliberately reports this limitation
28
+ instead of silently attempting Linux commands.
29
+
30
+ ## Shared Components
31
+
32
+ - DiscStation workflows and web UI
33
+ - ESP32 serial protocol
34
+ - File upload and folder preservation
35
+ - Video/audio metadata handling
36
+ - QR-code web URL display
37
+
38
+ The optical-drive backend is the platform boundary. It should be completed per
39
+ OS before claiming full native burning support on that platform.
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
5
+ APP_DIR="${DISCSTATION_APP_DIR:-$HOME/Library/Application Support/DiscStation/app}"
6
+ VENV_DIR="${DISCSTATION_VENV_DIR:-$HOME/Library/Application Support/DiscStation/venv}"
7
+ CONFIG_DIR="${DISCSTATION_CONFIG_DIR:-$HOME/Library/Application Support/DiscStation}"
8
+ PLIST="$HOME/Library/LaunchAgents/com.discstation.agent.plist"
9
+
10
+ if [[ "$(uname -s)" != "Darwin" ]]; then
11
+ printf 'Run this script on macOS.\n' >&2
12
+ exit 1
13
+ fi
14
+ if ! command -v brew >/dev/null 2>&1; then
15
+ printf 'Homebrew is missing. Starting the official Homebrew installer...\n'
16
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
17
+ fi
18
+
19
+ if [[ -x /opt/homebrew/bin/brew ]]; then
20
+ eval "$(/opt/homebrew/bin/brew shellenv)"
21
+ elif [[ -x /usr/local/bin/brew ]]; then
22
+ eval "$(/usr/local/bin/brew shellenv)"
23
+ fi
24
+
25
+ if ! command -v brew >/dev/null 2>&1; then
26
+ printf 'Homebrew installation did not complete.\n' >&2
27
+ exit 1
28
+ fi
29
+
30
+ brew install python ffmpeg cdrdao dvdauthor node yt-dlp xorriso mpv libdiscid handbrake
31
+ mkdir -p "$APP_DIR" "$VENV_DIR" "$CONFIG_DIR"
32
+ cp -R "$ROOT_DIR/src/." "$APP_DIR/"
33
+ python3 -m venv "$VENV_DIR"
34
+ "$VENV_DIR/bin/python" -m pip install --upgrade pip
35
+ if [[ -f "$ROOT_DIR/requirements.txt" ]]; then
36
+ "$VENV_DIR/bin/python" -m pip install -r "$ROOT_DIR/requirements.txt"
37
+ else
38
+ "$VENV_DIR/bin/python" -m pip install pyserial mutagen requests yt-dlp
39
+ fi
40
+
41
+ DISC_DEVICE="${DISC_DEVICE:-}"
42
+ DISC_PORT="${DISC_PORT:-$(ls /dev/cu.usbserial-* /dev/cu.usbmodem-* 2>/dev/null | head -1)}"
43
+
44
+ if [[ ! -f "$CONFIG_DIR/server.crt" || ! -f "$CONFIG_DIR/server.key" ]]; then
45
+ openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
46
+ -keyout "$CONFIG_DIR/server.key" \
47
+ -out "$CONFIG_DIR/server.crt" \
48
+ -subj "/CN=DiscStation"
49
+ chmod 600 "$CONFIG_DIR/server.key"
50
+ fi
51
+
52
+ mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs"
53
+ cat > "$PLIST" <<PLIST
54
+ <?xml version="1.0" encoding="UTF-8"?>
55
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56
+ <plist version="1.0"><dict>
57
+ <key>Label</key><string>com.discstation.agent</string>
58
+ <key>ProgramArguments</key><array>
59
+ <string>$VENV_DIR/bin/python</string>
60
+ <string>$APP_DIR/discstation.py</string>
61
+ <string>--port</string><string>8080</string>
62
+ </array>
63
+ <key>EnvironmentVariables</key><dict>
64
+ <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
65
+ <key>DISC_DEVICE</key><string>${DISC_DEVICE:-}</string>
66
+ <key>DISC_PORT</key><string>${DISC_PORT:-}</string>
67
+ </dict>
68
+ <key>WorkingDirectory</key><string>$APP_DIR</string>
69
+ <key>RunAtLoad</key><true/>
70
+ <key>KeepAlive</key><true/>
71
+ <key>StandardOutPath</key><string>$HOME/Library/Logs/DiscStation.log</string>
72
+ <key>StandardErrorPath</key><string>$HOME/Library/Logs/DiscStation.log</string>
73
+ </dict></plist>
74
+ PLIST
75
+ launchctl bootout "gui/$(id -u)" "$PLIST" 2>/dev/null || true
76
+ launchctl bootstrap "gui/$(id -u)" "$PLIST"
77
+
78
+ printf '\nDiscStation macOS host files installed at:\n%s\n' "$APP_DIR"
79
+ printf 'Optical-device support is experimental; set DISC_DEVICE if automatic detection fails.\n'
80
+ printf 'Run: %q %q\n' "$VENV_DIR/bin/python" "$APP_DIR/discstation.py"
@@ -0,0 +1,24 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $Root = Split-Path -Parent $MyInvocation.MyCommand.Path
4
+ $App = if ($env:DISCSTATION_APP_DIR) { $env:DISCSTATION_APP_DIR } else { Join-Path $env:LOCALAPPDATA "DiscStation\app" }
5
+ $Venv = if ($env:DISCSTATION_VENV_DIR) { $env:DISCSTATION_VENV_DIR } else { Join-Path $env:LOCALAPPDATA "DiscStation\venv" }
6
+
7
+ if (-not (Get-Command py -ErrorAction SilentlyContinue)) {
8
+ throw "Install Python 3 from https://www.python.org/downloads/windows/ first."
9
+ }
10
+
11
+ New-Item -ItemType Directory -Force -Path $App, $Venv | Out-Null
12
+ Copy-Item -Recurse -Force (Join-Path $Root "src\*") $App
13
+ py -3 -m venv $Venv
14
+ & (Join-Path $Venv "Scripts\python.exe") -m pip install --upgrade pip
15
+ $Req = Join-Path $Root "requirements.txt"
16
+ if (Test-Path $Req) {
17
+ & (Join-Path $Venv "Scripts\python.exe") -m pip install -r $Req
18
+ } else {
19
+ & (Join-Path $Venv "Scripts\python.exe") -m pip install pyserial mutagen requests yt-dlp
20
+ }
21
+
22
+ Write-Host "DiscStation host files installed at $App"
23
+ Write-Host "The web/control workflow is available; optical burning requires a Windows IMAPI backend or compatible burning tools."
24
+ Write-Host "Run: $Venv\Scripts\python.exe $App\discstation.py"
package/install.sh ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
5
+ APP_DIR="${DISCSTATION_APP_DIR:-$HOME/.local/share/discstation/app}"
6
+ VENV_DIR="${DISCSTATION_VENV_DIR:-$HOME/.local/share/discstation/venv}"
7
+ CONFIG_DIR="${DISCSTATION_CONFIG_DIR:-$HOME/.local/share/discstation}"
8
+
9
+ if [[ "$(uname -s)" != "Linux" ]]; then
10
+ printf 'This installer is for Linux. Use install-macos.sh or install-windows.ps1.\n' >&2
11
+ exit 1
12
+ fi
13
+
14
+ if command -v apt-get >/dev/null 2>&1; then
15
+ sudo apt-get update
16
+ sudo apt-get install -y \
17
+ cdrdao dvdauthor ffmpeg genisoimage growisofs handbrake-cli lsdvd mpv \
18
+ nodejs openssl python3 python3-pip python3-serial python3-mutagen \
19
+ python3-requests python3-pyudev python3-libdiscid python3-musicbrainzngs \
20
+ libdiscid0 python3-venv wodim
21
+ else
22
+ printf 'Unsupported Linux package manager. Install the DiscStation dependencies manually.\n' >&2
23
+ exit 1
24
+ fi
25
+
26
+ mkdir -p "$APP_DIR" "$CONFIG_DIR" "$VENV_DIR"
27
+ cp -R "$ROOT_DIR/src/." "$APP_DIR/"
28
+ if [[ ! -f "$CONFIG_DIR/discstation.env" && -f "$ROOT_DIR/discstation.env.example" ]]; then
29
+ cp "$ROOT_DIR/discstation.env.example" "$CONFIG_DIR/discstation.env"
30
+ fi
31
+
32
+ python3 -m venv --system-site-packages "$VENV_DIR"
33
+ "$VENV_DIR/bin/python" -m pip install --upgrade pip
34
+ if [[ -f "$ROOT_DIR/requirements.txt" ]]; then
35
+ "$VENV_DIR/bin/python" -m pip install -r "$ROOT_DIR/requirements.txt"
36
+ else
37
+ "$VENV_DIR/bin/python" -m pip install pyserial mutagen requests yt-dlp
38
+ fi
39
+
40
+ if [[ ! -f "$CONFIG_DIR/server.crt" || ! -f "$CONFIG_DIR/server.key" ]]; then
41
+ openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
42
+ -keyout "$CONFIG_DIR/server.key" \
43
+ -out "$CONFIG_DIR/server.crt" \
44
+ -subj "/CN=DiscStation"
45
+ chmod 600 "$CONFIG_DIR/server.key"
46
+ fi
47
+
48
+ sudo usermod -aG dialout,cdrom "$USER" || true
49
+ mkdir -p "$HOME/.config/systemd/user"
50
+ cp "$ROOT_DIR/systemd/discstation.service" "$HOME/.config/systemd/user/discstation.service"
51
+ systemctl --user daemon-reload
52
+ systemctl --user enable --now discstation.service
53
+
54
+ printf '\nDiscStation installed. Log out/in if device permissions changed.\n'
55
+ printf 'App: %s\n' "$APP_DIR"
56
+ printf 'Web: https://%s:8080\n' "$(hostname -I | awk '{print $1}')"
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "discstation",
3
+ "version": "0.1.0",
4
+ "description": "DiscStation optical-media appliance host — one cross-OS installer",
5
+ "bin": {
6
+ "discstation-setup": "scripts/setup.mjs"
7
+ },
8
+ "scripts": {
9
+ "setup": "node scripts/setup.mjs"
10
+ },
11
+ "engines": {
12
+ "node": ">=16"
13
+ },
14
+ "os": [
15
+ "linux",
16
+ "darwin",
17
+ "win32"
18
+ ],
19
+ "files": [
20
+ "scripts/",
21
+ "src/*.py",
22
+ "src/static/",
23
+ "arduino/",
24
+ "systemd/",
25
+ "docs/",
26
+ "install.sh",
27
+ "install-macos.sh",
28
+ "install-windows.ps1",
29
+ "requirements.txt",
30
+ "discstation.env.example",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/phuju/dvd-station.git"
37
+ },
38
+ "keywords": [
39
+ "dvd",
40
+ "cd",
41
+ "optical",
42
+ "burn",
43
+ "rip",
44
+ "appliance"
45
+ ],
46
+ "license": "MIT",
47
+ "private": false
48
+ }
@@ -0,0 +1,20 @@
1
+ # DiscStation Python dependencies.
2
+ #
3
+ # On Debian/Ubuntu, prefer the system packages (see install.sh):
4
+ # python3-serial python3-mutagen python3-requests python3-pyudev
5
+ # python3-libdiscid python3-musicbrainzngs
6
+ # The pip names below are for venv / macOS / Windows installs.
7
+
8
+ pyserial>=3.5
9
+ mutagen>=1.45
10
+ requests>=2.28
11
+ yt-dlp>=2024.1.1
12
+
13
+ # Disc detection / metadata (all optional at runtime: the code degrades to the
14
+ # previous subprocess-based paths if an import fails).
15
+ pyudev>=0.24 ; sys_platform == "linux"
16
+ python-libdiscid>=2.0 ; sys_platform != "win32"
17
+ musicbrainzngs>=0.7.1
18
+
19
+ # Video metadata (TMDb). Needs DISCSTATION_TMDB_API_KEY to do anything.
20
+ tmdbsimple>=2.9
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ // One entry point for installing the DiscStation host on any OS.
3
+ // It just dispatches to the platform installer that already exists in the repo
4
+ // (install.sh / install-macos.sh / install-windows.ps1); extra CLI args and the
5
+ // DISCSTATION_* / DISC_* env vars are forwarded through.
6
+
7
+ import { spawnSync } from 'node:child_process';
8
+ import { existsSync } from 'node:fs';
9
+ import { dirname, join, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
13
+ const args = process.argv.slice(2);
14
+
15
+ if (args.includes('-h') || args.includes('--help')) {
16
+ process.stdout.write(
17
+ `discstation-setup — install the DiscStation host
18
+
19
+ Runs the installer for the current OS:
20
+ linux install.sh full: apt deps, venv, systemd --user service, self-signed cert
21
+ darwin install-macos.sh full: Homebrew deps, venv, launchd agent, cert (optical is experimental)
22
+ windows install-windows.ps1 files + venv only; web/serial control, no burn backend
23
+
24
+ Prereqs: Node 16+, Python 3 (python3 / py on PATH), and either bash (linux/macOS)
25
+ or PowerShell (Windows). Homebrew is auto-installed on macOS if missing; the
26
+ Linux script uses sudo for apt + group membership.
27
+
28
+ Env vars (forwarded): DISCSTATION_APP_DIR, DISCSTATION_VENV_DIR,
29
+ DISCSTATION_CONFIG_DIR, DISCSTATION_HTTP_PORT, DISC_DEVICE, DISC_PORT.
30
+
31
+ Any extra arguments are passed straight to the platform script.
32
+ `
33
+ );
34
+ process.exit(0);
35
+ }
36
+
37
+ function run(cmd, cmdArgs) {
38
+ const r = spawnSync(cmd, cmdArgs, { stdio: 'inherit', cwd: ROOT });
39
+ if (r.error) {
40
+ console.error(`\n${cmd}: ${r.error.message}`);
41
+ process.exit(1);
42
+ }
43
+ process.exit(r.status ?? 1);
44
+ }
45
+
46
+ function needPython(bin) {
47
+ const r = spawnSync(bin, ['--version'], { stdio: 'ignore' });
48
+ return r.status === 0;
49
+ }
50
+
51
+ switch (process.platform) {
52
+ case 'linux':
53
+ case 'darwin': {
54
+ const script = process.platform === 'linux' ? 'install.sh' : 'install-macos.sh';
55
+ if (!needPython('python3')) {
56
+ console.error('Python 3 not found on PATH. Install it first, then re-run.');
57
+ process.exit(1);
58
+ }
59
+ run('bash', [join(ROOT, script), ...args]);
60
+ break;
61
+ }
62
+ case 'win32': {
63
+ if (!needPython('py') && !needPython('python')) {
64
+ console.error('Python 3 not found. Install from https://www.python.org/downloads/windows/');
65
+ process.exit(1);
66
+ }
67
+ const ps1 = join(ROOT, 'install-windows.ps1');
68
+ if (!existsSync(ps1)) {
69
+ console.error('install-windows.ps1 missing from the package.');
70
+ process.exit(1);
71
+ }
72
+ run('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...args]);
73
+ break;
74
+ }
75
+ default:
76
+ console.error(`Unsupported platform: ${process.platform}. Run one of the install.* scripts by hand.`);
77
+ process.exit(1);
78
+ }