Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@


def _default_allow_network() -> bool:
# Off by default everywhere — the user explicitly opts other devices in.
# STEMDECK_ALLOW_NETWORK=1 can pre-enable it (e.g. headless/Docker deploys).
# STEMDECK_ALLOW_NETWORK takes precedence when set explicitly.
# Otherwise: desktop keeps network off (user opts in via UI toggle);
# server/Docker deployments open it by default since network access is
# the entire point of a headless deployment.
env = os.environ.get("STEMDECK_ALLOW_NETWORK")
if env is not None:
return env.strip() == "1"
return False
return os.environ.get("STEMDECK_DESKTOP") != "1"


def _load() -> dict:
Expand Down
6 changes: 6 additions & 0 deletions app/pipeline/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def normalize_youtube_url(url: str) -> str:
playlists embed the seed in the list ID; YouTube refuses to view the
playlist directly with "This playlist type is unviewable.")
* `youtu.be/<videoId>` -> `watch?v=<videoId>`
* `youtube.com/shorts/<videoId>` -> `watch?v=<videoId>`
Everything else (PL/OL/algorithmic playlists with no derivable seed) is
left alone -- yt-dlp will surface its own error.
"""
Expand Down Expand Up @@ -146,6 +147,11 @@ def normalize_youtube_url(url: str) -> str:
if _VIDEO_ID_RE.match(vid):
return f"https://www.youtube.com/watch?v={vid}"

if host == "youtube.com" and parsed.path.startswith("/shorts/"):
vid = parsed.path[len("/shorts/") :].lstrip("/").split("/")[0]
if _VIDEO_ID_RE.match(vid):
return f"https://www.youtube.com/watch?v={vid}"

return url


Expand Down
3 changes: 1 addition & 2 deletions build/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ services:
image: stemdeck
container_name: stemdeck
ports:
# Bind to loopback only -- StemDeck has no auth and is local-only.
- "127.0.0.1:8000:8000"
- "8000:8000"
volumes:
# Stems and downloaded audio land in <project root>/jobs on the
# host so you can grab them without going through the container.
Expand Down
1 change: 1 addition & 0 deletions static/css/daw.css
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ input, textarea { font-family: inherit; }
.settings-net-list { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.settings-net-list code { color: var(--accent); background: rgba(244,183,64,0.1); padding: 3px 9px; border-radius: 5px; font-size: 11.5px; }
.settings-net-empty { color: var(--muted); }
.settings-server-note { font-size: 10.5px; color: var(--muted); margin: 0 0 12px; line-height: 1.5; }
.settings-subhead { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; margin: 4px 0 7px; }

.library-editor-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 11px; }
Expand Down
10 changes: 10 additions & 0 deletions static/js/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2046,8 +2046,18 @@ function openLibraryEditor() {
document.body.appendChild(overlay);
libraryEditor = overlay;
refreshLibrarySyncSummary();
const isDesktop = Boolean(window.__TAURI__?.core?.invoke);
wireGeneralSettings(overlay);
wireNetworkSetting(overlay);
if (!isDesktop) {
overlay.querySelector(".net-access-input")?.setAttribute("disabled", "");
overlay.querySelector(".set-port")?.setAttribute("readonly", "");
overlay.querySelector(".set-port")?.setAttribute("disabled", "");
const note = document.createElement("p");
note.className = "settings-server-note";
note.textContent = "These settings are read-only in server mode. To change them, update your server configuration (e.g. docker-compose.yml) and restart.";
overlay.querySelector("[data-pane='advanced']")?.prepend(note);
}
}

// Poll a job until it reaches a terminal state, so auto-restores run one at a
Expand Down
12 changes: 10 additions & 2 deletions tests/test_network_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,20 @@ def test_host_request_recognizes_own_lan_ip(monkeypatch):
assert _is_host_request("192.168.1.99") is False # a different device


def test_default_is_off(monkeypatch):
# Off by default everywhere — the user must opt in.
def test_default_is_off_in_desktop_mode(monkeypatch):
# Desktop keeps network off; the user opts in via the UI toggle.
monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False)
monkeypatch.setenv("STEMDECK_DESKTOP", "1")
assert settings_mod._default_allow_network() is False


def test_default_is_on_in_server_mode(monkeypatch):
# Server/Docker deployments open the gate by default.
monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False)
monkeypatch.delenv("STEMDECK_DESKTOP", raising=False)
assert settings_mod._default_allow_network() is True


def test_env_var_pre_enables(monkeypatch):
monkeypatch.setenv("STEMDECK_ALLOW_NETWORK", "1")
assert settings_mod._default_allow_network() is True
Expand Down
8 changes: 8 additions & 0 deletions tests/test_url_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
" https://www.youtube.com/watch?v=dQw4w9WgXcQ ",
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
),
(
"https://www.youtube.com/shorts/dQw4w9WgXcQ",
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
),
(
"https://m.youtube.com/shorts/dQw4w9WgXcQ",
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
),
],
)
def test_accepts_youtube_urls(url: str, expected: str) -> None:
Expand Down