Conversation
Bumps [WyriHaximus/github-action-get-previous-tag](https://github.com/wyrihaximus/github-action-get-previous-tag) from 1 to 2. - [Release notes](https://github.com/wyrihaximus/github-action-get-previous-tag/releases) - [Commits](WyriHaximus/github-action-get-previous-tag@v1...v2) --- updated-dependencies: - dependency-name: WyriHaximus/github-action-get-previous-tag dependency-version: '2' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@v3...v4) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v4...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](docker/setup-qemu-action@v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](docker/setup-buildx-action@v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [grpcio](https://github.com/grpc/grpc) from 1.76.0 to 1.80.0. - [Release notes](https://github.com/grpc/grpc/releases) - [Commits](grpc/grpc@v1.76.0...v1.80.0) --- updated-dependencies: - dependency-name: grpcio dependency-version: 1.80.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](actions/setup-go@v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [yt-dlp](https://github.com/yt-dlp/yt-dlp) from 2025.10.22 to 2026.3.17. - [Release notes](https://github.com/yt-dlp/yt-dlp/releases) - [Changelog](https://github.com/yt-dlp/yt-dlp/blob/master/Changelog.md) - [Commits](yt-dlp/yt-dlp@2025.10.22...2026.03.17) --- updated-dependencies: - dependency-name: yt-dlp dependency-version: 2026.3.17 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [streamlink](https://github.com/streamlink/streamlink) from 7.6.0 to 8.3.0. - [Release notes](https://github.com/streamlink/streamlink/releases) - [Changelog](https://github.com/streamlink/streamlink/blob/master/CHANGELOG.md) - [Commits](streamlink/streamlink@7.6.0...8.3.0) --- updated-dependencies: - dependency-name: streamlink dependency-version: 8.3.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [curl-cffi](https://github.com/lexiforest/curl_cffi) from 0.13.0 to 0.15.0. - [Release notes](https://github.com/lexiforest/curl_cffi/releases) - [Changelog](https://github.com/lexiforest/curl_cffi/blob/main/docs/changelog.rst) - [Commits](lexiforest/curl_cffi@v0.13.0...v0.15.0) --- updated-dependencies: - dependency-name: curl-cffi dependency-version: 0.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0 * Revert "chore(release): update dependencies and bump version to 3.5.0" This reverts commit 847aca3. * chore(release)🤖: v3.5.1 [skip ci] [skip ci] * chore(release)🤖: v3.6.0 [skip ci] [skip ci] * chore(deps): bump pygments from 2.18.0 to 2.20.0 Bumps [pygments](https://github.com/pygments/pygments) from 2.18.0 to 2.20.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](pygments/pygments@2.18.0...2.20.0) --- updated-dependencies: - dependency-name: pygments dependency-version: 2.20.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Josh J <josh@joshjacobs.net> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0 * Revert "chore(release): update dependencies and bump version to 3.5.0" This reverts commit 847aca3. * chore(release)🤖: v3.5.1 [skip ci] [skip ci] * chore(release)🤖: v3.6.0 [skip ci] [skip ci] * chore(deps): bump requests from 2.32.3 to 2.33.0 Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.33.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](psf/requests@v2.32.3...v2.33.0) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Josh J <josh@joshjacobs.net> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0 * Revert "chore(release): update dependencies and bump version to 3.5.0" This reverts commit 847aca3. * chore(release)🤖: v3.5.1 [skip ci] [skip ci] * chore(release)🤖: v3.6.0 [skip ci] [skip ci] * chore(deps): bump pytest from 8.3.3 to 9.0.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.3 to 9.0.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](pytest-dev/pytest@8.3.3...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Josh J <josh@joshjacobs.net> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): update dependencies and bump version to 3.5.0 * Revert "chore(release): update dependencies and bump version to 3.5.0" This reverts commit 847aca3. * chore(release)🤖: v3.5.1 [skip ci] [skip ci] * chore(release)🤖: v3.6.0 [skip ci] [skip ci] * chore(deps): bump urllib3 from 2.2.3 to 2.6.3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.3 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.2.3...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Josh Jacobs <josh.jacobs@datasolace.com> Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com> Co-authored-by: Josh J <josh@joshjacobs.net> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…g/actions/checkout-6' into staging
…g/actions/setup-node-6' into staging
…g/docker/login-action-4' into staging
…g/docker/setup-qemu-action-4' into staging
…g/docker/setup-buildx-action-4' into staging
…g/WyriHaximus/github-action-get-previous-tag-2' into staging
Add ability to download past broadcasts (VODs) from Twitch channels. Per-channel `vod: true` config toggle switches from live stream to VOD mode, with a SQLite database tracking download state to avoid re-downloading and handle crash recovery via stale threshold detection. - Add GetVods gRPC RPC using yt-dlp for VOD enumeration - Add SQLite VOD tracking with status-based lifecycle (downloading/completed/failed) - Add downloadVOD function with stream copy and VOD-specific filenames - Add -data, -vod-out, -vod-move flags for configurable paths - Wire VOD branch into main tick loop with ShouldDownloadVOD gating - Add VOD phase to integration test - Update README and example config
VOD downloads now use a WaitGroup so the shutdown path waits for all in-progress VOD jobs before exiting, not just live streams.
Eliminates TOCTOU race by combining the check-and-claim into a single SQL operation that uses ON CONFLICT with a WHERE clause and checks RowsAffected. Also fixes dropped errors in tests.
Return an error when the VOD is not found in the database instead of silently succeeding with zero rows affected.
Only create the SQLite database when a channel with vod:true is encountered, avoiding failures on unwritable default paths when VOD is not configured. Handles config reloads that add VOD channels.
Removes the data directory so the SQLite DB is fresh, preventing previously tracked VODs from causing the test to skip downloads.
Use a known high-profile channel (kaicenat) that reliably has VODs instead of reusing the live stream channel. Overridable via VOD_CHANNEL env var.
Bump minimum protobuf version to match generated code requirements. Add stream_pb2*.py to CodeRabbit path filters since they are auto-generated and should not be reviewed.
Accept in-progress downloads (>1KB in /incomplete) as proof the pipeline works, not just completed files. Makes the test faster and less flaky for long VODs. VOD_TIMEOUT is now configurable via env var.
Iterate a prioritized list of channels (teampgp first) and probe for published VODs using yt-dlp before running Phase 5. Skip gracefully if no candidates have VODs. Overridable via VOD_CHANNEL env var.
Include server logs alongside client logs when VOD download times out. Fail the test if all VOD probes errored (connectivity issue) rather than skipping gracefully, which is reserved for when probes succeed but no channels have VODs.
feat: Twitch VOD support
Bumps [wheel](https://github.com/pypa/wheel) from 0.46.1 to 0.46.3. - [Release notes](https://github.com/pypa/wheel/releases) - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](pypa/wheel@0.46.1...0.46.3) --- updated-dependencies: - dependency-name: wheel dependency-version: 0.46.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fixes prototype pollution vulnerability in baseUnset function.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds Twitch VOD support end-to-end: new GetVods gRPC/proto, server-side yt-dlp discovery, client-side getVods/VodResult, FFmpeg-based downloadVOD with sanitized filenames, a SQLite VodDB for claiming/tracking, CLI flags and data dir handling, tests, docs, and CI/dependency bumps. Changes
Sequence DiagramsequenceDiagram
participant App as StreamDL App
participant GRPC as gRPC Server
participant YtDlp as yt-dlp
participant DB as SQLite VodDB
participant FFmpeg as FFmpeg
loop VOD discovery
App->>GRPC: GetVods(site,user,limit)
GRPC->>YtDlp: extract_info(site/user/videos)
YtDlp-->>GRPC: vod metadata list
GRPC-->>App: VodResult[]
end
loop per VOD
App->>DB: ClaimVOD(vodID,user,site,title,staleThreshold)
DB-->>App: claimed true/false
alt claimed
App->>GRPC: GetStream(videos/vodID)
GRPC-->>App: download URL
App->>FFmpeg: exec(download URL → outPath)
FFmpeg-->>App: exit success/failure
App->>DB: MarkVODCompleted / MarkVODFailed
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/integration/run.sh (1)
233-382:⚠️ Potential issue | 🟠 MajorDon't let the live-phase skip short-circuit the VOD phase.
Phase 5 is appended after the existing
exit 0at Line 119, so off-peak runs still skip the new VOD coverage entirely even though this phase has its own candidate probing. The=== PASSbanner at Line 233 is also premature for the same reason. Split the live and VOD phases so a live skip doesn't exit before the VOD path is exercised.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/integration/run.sh` around lines 233 - 382, The script currently exits early when the live-phase prints "=== PASS: Live stream integration test succeeded ===" or when the live-phase skip path does exit 0, preventing the subsequent VOD phase (the "=== VOD Download Test ===" block and its VOD_CHANNEL probing logic using CANDIDATE_VOD_CHANNELS, ANY_PROBE_OK, and VOD_CHANNEL) from running; remove or change that early exit so the VOD phase always runs (or only skip the live-specific success banner), move or rename the premature "=== PASS: Live stream integration test succeeded ===" banner so final success is printed only after both live and VOD checks complete, and ensure the skip branch that currently does exit 0 continues to the VOD probing logic instead of terminating the script.
🧹 Nitpick comments (2)
vod_db_test.go (1)
160-162: Consider checking errors from ClaimVOD.These calls discard the error with
_. While the test intent is clear (checking the boolean), silently ignoring errors could mask unexpected failures.🔧 Suggested improvement
- d1, _ := db.ClaimVOD("111", "user1", "twitch.tv", "Title A", staleThreshold) - d2, _ := db.ClaimVOD("222", "user2", "twitch.tv", "Title B", staleThreshold) - d3, _ := db.ClaimVOD("333", "user3", "twitch.tv", "Title C", staleThreshold) + d1, err := db.ClaimVOD("111", "user1", "twitch.tv", "Title A", staleThreshold) + if err != nil { + t.Fatalf("ClaimVOD 111 failed: %v", err) + } + d2, err = db.ClaimVOD("222", "user2", "twitch.tv", "Title B", staleThreshold) + if err != nil { + t.Fatalf("ClaimVOD 222 failed: %v", err) + } + d3, err := db.ClaimVOD("333", "user3", "twitch.tv", "Title C", staleThreshold) + if err != nil { + t.Fatalf("ClaimVOD 333 failed: %v", err) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@vod_db_test.go` around lines 160 - 162, The test is discarding errors from ClaimVOD calls (d1, d2, d3) which can hide failures; update the test to capture and assert the returned error for each ClaimVOD invocation (or call t.Fatalf/t.Errorf if err != nil) so failures surface rather than being ignored—locate the ClaimVOD(...) calls that assign to d1/d2/d3 and add error checks immediately after each call.grpc_client.go (1)
26-84: Extract the shared gRPC setup before this drifts further.This repeats the same addr/port lookup,
grpc.NewClient, close, and timeout plumbing asgetStream, and the two paths are already diverging on logging/error translation. A small shared helper would keep live/VOD transport behavior in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@grpc_client.go` around lines 26 - 84, Refactor the duplicated gRPC setup in getVods to a shared helper (e.g., newGRPCConn or dialStreamGRPC) that encapsulates reading STREAMDL_GRPC_ADDR/PORT, calling grpc.NewClient with grpc.WithTransportCredentials(insecure.NewCredentials()), returning the grpc.ClientConn (or client interface), a context with timeout (and its cancel func) and an io.Closer-style cleanup (or ensure conn.Close is called by caller); then update getVods (and getStream) to call this helper, use the returned ctx for RPCs, and remove the duplicated addr/port/dial/close/timeout plumbing so both functions share connection and timeout behavior while keeping their own logging/error translation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CHANGELOG.md`:
- Around line 1-83: Add a concise bullet under the 3.6.0 changelog (near the New
Features or top-level summary) documenting the Twitch VOD support and the new
config/CLI surface: mention the feature name (Twitch VOD support) and list the
new config/flags (vod, vod_limit, -data, -vod-out, -vod-move) plus a short note
on what each does (e.g., enable VOD downloads, limit VOD count, data dir, VOD
output path, atomically move completed VODs). Ensure this appears alongside
other headline changes in the 3.6.0 notes so users scanning the changelog see
the functional impact.
In `@download_stream.go`:
- Around line 540-541: The code currently calls both cmd.Process.Wait() and
cmd.Wait(), which duplicate reaping the child and can cause errors; remove the
redundant call and use cmd.Wait() to both wait for and reap the process (or, if
you intentionally need low-level Process.Wait(), then remove the subsequent
cmd.Wait() and propagate/handle that error accordingly), updating any error
handling around cmd.Wait()/cmd.Process.Wait() to reflect the single wait path.
In `@streamdl.go`:
- Around line 70-74: The process is exiting with os.Exit(0) before the deferred
vodDB.Close() in main() can run; change the early termination in main() to
return instead of calling os.Exit so the defer that checks vodDB and calls
vodDB.Close() executes, and ensure the defer that wraps vodDB.Close() (the func
that checks if vodDB != nil then vodDB.Close()) remains in main() so resources
are closed on return.
- Around line 191-203: The nested retry shadows err and therefore the "thrice"
branch never sees the second retry result; in the block that calls getStream
after sleeping, replace the short declaration (url, err := getStream(...)) with
an assignment that reuses the outer err (url, err = getStream(...)) or use a new
variable name and then check that variable, then change the subsequent else-if
to inspect the second-retry error (the reused err or the new name) so that the
"rate limited thrice" branch in the getStream / downloadStream retry logic for
streamer.User can be reached and other error paths are not silently dropped;
ensure you still lock/update urls via urlsMu and call downloadStream when the
second retry succeeds.
---
Outside diff comments:
In `@tests/integration/run.sh`:
- Around line 233-382: The script currently exits early when the live-phase
prints "=== PASS: Live stream integration test succeeded ===" or when the
live-phase skip path does exit 0, preventing the subsequent VOD phase (the "===
VOD Download Test ===" block and its VOD_CHANNEL probing logic using
CANDIDATE_VOD_CHANNELS, ANY_PROBE_OK, and VOD_CHANNEL) from running; remove or
change that early exit so the VOD phase always runs (or only skip the
live-specific success banner), move or rename the premature "=== PASS: Live
stream integration test succeeded ===" banner so final success is printed only
after both live and VOD checks complete, and ensure the skip branch that
currently does exit 0 continues to the VOD probing logic instead of terminating
the script.
---
Nitpick comments:
In `@grpc_client.go`:
- Around line 26-84: Refactor the duplicated gRPC setup in getVods to a shared
helper (e.g., newGRPCConn or dialStreamGRPC) that encapsulates reading
STREAMDL_GRPC_ADDR/PORT, calling grpc.NewClient with
grpc.WithTransportCredentials(insecure.NewCredentials()), returning the
grpc.ClientConn (or client interface), a context with timeout (and its cancel
func) and an io.Closer-style cleanup (or ensure conn.Close is called by caller);
then update getVods (and getStream) to call this helper, use the returned ctx
for RPCs, and remove the duplicated addr/port/dial/close/timeout plumbing so
both functions share connection and timeout behavior while keeping their own
logging/error translation.
In `@vod_db_test.go`:
- Around line 160-162: The test is discarding errors from ClaimVOD calls (d1,
d2, d3) which can hide failures; update the test to capture and assert the
returned error for each ClaimVOD invocation (or call t.Fatalf/t.Errorf if err !=
nil) so failures surface rather than being ignored—locate the ClaimVOD(...)
calls that assign to d1/d2/d3 and add error checks immediately after each call.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 482d0821-9fab-49a2-9fd3-2ae2d57f31bd
⛔ Files ignored due to path filters (25)
go.sumis excluded by!**/*.sumnode_modules/.package-lock.jsonis excluded by!**/node_modules/**node_modules/lodash/README.mdis excluded by!**/node_modules/**node_modules/lodash/_baseOrderBy.jsis excluded by!**/node_modules/**node_modules/lodash/_baseUnset.jsis excluded by!**/node_modules/**node_modules/lodash/_setCacheHas.jsis excluded by!**/node_modules/**node_modules/lodash/compact.jsis excluded by!**/node_modules/**node_modules/lodash/core.jsis excluded by!**/node_modules/**node_modules/lodash/core.min.jsis excluded by!**/node_modules/**,!**/*.min.jsnode_modules/lodash/flake.lockis excluded by!**/node_modules/**,!**/*.locknode_modules/lodash/flake.nixis excluded by!**/node_modules/**node_modules/lodash/fromPairs.jsis excluded by!**/node_modules/**node_modules/lodash/lodash.jsis excluded by!**/node_modules/**node_modules/lodash/lodash.min.jsis excluded by!**/node_modules/**,!**/*.min.jsnode_modules/lodash/package.jsonis excluded by!**/node_modules/**node_modules/lodash/random.jsis excluded by!**/node_modules/**node_modules/lodash/release.mdis excluded by!**/node_modules/**node_modules/lodash/template.jsis excluded by!**/node_modules/**node_modules/lodash/templateSettings.jsis excluded by!**/node_modules/**package-lock.jsonis excluded by!**/package-lock.jsonprotos/stream.pb.gois excluded by!**/*.pb.goprotos/stream_grpc.pb.gois excluded by!**/*.pb.gostream_pb2.pyis excluded by!stream_pb2.pystream_pb2_grpc.pyis excluded by!stream_pb2_grpc.pyuv.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
.coderabbit.yaml.github/workflows/deploy_master.yml.github/workflows/deploy_staging.yml.github/workflows/test.yml.github/workflows/update-actions.ymlCHANGELOG.mdREADME.mdconfig.goconfig/config.yml.exampleconfig_reader_test.godownload_stream.goentrypoint_client.shgo.modgrpc_client.goprotos/stream.protopyproject.tomlstreamdl.gostreamdl_proto_srv.pytests/integration/docker-compose.integration.ymltests/integration/run.shvod_db.govod_db_test.go
| ## [3.6.0](https://github.com/dangeroustech/StreamDL/compare/v3.5.1...v3.6.0) (2026-04-13) | ||
|
|
||
|
|
||
| ### 🏭 Build | ||
|
|
||
| * **deps:** bump golang.org/x/net from 0.37.0 to 0.38.0 ([dfe5251](https://github.com/dangeroustech/StreamDL/commit/dfe5251a7feb8d90eab675a173bbf6ea12ca2085)) | ||
|
|
||
|
|
||
| ### 🎉 New Features | ||
|
|
||
| * add unit tests for config reader and improve error handling ([9b9601f](https://github.com/dangeroustech/StreamDL/commit/9b9601f41bab1f368598a018663899ab735ce95d)) | ||
| * add unit tests for moveFile function and enhance cross-device handling ([2046aaa](https://github.com/dangeroustech/StreamDL/commit/2046aaaaf618439e65ac3692002a00f53917e099)) | ||
| * enhance downloadStream function with retry logic and network resilience ([8947e7f](https://github.com/dangeroustech/StreamDL/commit/8947e7f1c2c62af3f5faa266c33fec4fa669f951)) | ||
|
|
||
|
|
||
| ### 📃 Refactor | ||
|
|
||
| * enhance entrypoint scripts to handle user/group creation and permissions based on root status ([7d2cf50](https://github.com/dangeroustech/StreamDL/commit/7d2cf505f34516b0625818411f3851cf45e0ce3e)) | ||
| * enhance error handling and logging in downloadStream, moveFile, and gRPC connection management ([e4f66f5](https://github.com/dangeroustech/StreamDL/commit/e4f66f55788c11fb84a18e42d80a47dcb0be3b53)) | ||
| * improve code readability and consistency in configuration and file handling tests ([30d79a5](https://github.com/dangeroustech/StreamDL/commit/30d79a558f9a18ff733e23f16194ed7901afd66b)) | ||
| * improve entrypoint scripts with debug information and adjust permissions for .pdm-build directory ([fbfbff2](https://github.com/dangeroustech/StreamDL/commit/fbfbff299e2bcf3ab52f73d117296a9c5bcec24d)) | ||
| * improve logging format and streamline code readability in streamdl_proto_srv.py ([d45c340](https://github.com/dangeroustech/StreamDL/commit/d45c340036484bdf0ed262e4331d0d1ceb047333)) | ||
| * simplify entrypoint scripts by removing debug output and consolidating user/group creation steps ([f67994f](https://github.com/dangeroustech/StreamDL/commit/f67994f5b81802d7934e8471e2d45c782fdd144b)) | ||
| * streamline downloadStream function and enhance logging ([4f70983](https://github.com/dangeroustech/StreamDL/commit/4f70983afddd132efd0e296025e580585ae2af79)) | ||
| * streamline entrypoint script by removing redundant ownership changes and clarifying permissions for .venv ([88bcbee](https://github.com/dangeroustech/StreamDL/commit/88bcbee921d6e4d7e53cc867115741d8f7f14e9d)) | ||
|
|
||
|
|
||
| ### 🧪 Tests | ||
|
|
||
| * add end-to-end integration test for live stream download ([8f817b8](https://github.com/dangeroustech/StreamDL/commit/8f817b89d81e5034ee8d3d8873949c6d1b35fb93)) | ||
|
|
||
|
|
||
| ### 📚 Documentation | ||
|
|
||
| * add docstrings to Python server functions and classes ([98eea44](https://github.com/dangeroustech/StreamDL/commit/98eea44884464491245b29a308351f452b60e507)) | ||
| * add FFmpeg resilience settings to README ([8a28341](https://github.com/dangeroustech/StreamDL/commit/8a283414f5b871fc2cf44dbe90230322f6c33f5f)) | ||
|
|
||
|
|
||
| ### ✍ Chore | ||
|
|
||
| * add --sarif option to Snyk scan parameters in deploy workflows ([627c6b3](https://github.com/dangeroustech/StreamDL/commit/627c6b31fe1aae39f5ce4cb55b905b555e272fe7)) | ||
| * add category parameter for Snyk SARIF file in deploy workflows ([71d9730](https://github.com/dangeroustech/StreamDL/commit/71d973012182ec1509300d88f1e5118bf23d0f01)) | ||
| * add docs/ to gitignore ([9ad1ade](https://github.com/dangeroustech/StreamDL/commit/9ad1adedad4b315eda746cf5ad8ecbcd52592f4f)) | ||
| * add go.work file for Go module management ([8aa3642](https://github.com/dangeroustech/StreamDL/commit/8aa3642ddde6e8a79925f1cb505129106f300458)) | ||
| * add go.work.sum file for dependency management ([a14c088](https://github.com/dangeroustech/StreamDL/commit/a14c088c8e30cb6dc99965a1ee0aa76d0ffa9119)) | ||
| * add health checks and curl installation in Dockerfiles, implement health server in streamdl_proto_srv.py ([ba5199a](https://github.com/dangeroustech/StreamDL/commit/ba5199a478d8ddfb6be4f27c1ccd88edb4286462)) | ||
| * **ci:** update action versions, Go, Python, and Node versions ([fc86f54](https://github.com/dangeroustech/StreamDL/commit/fc86f5496da0905d9b16d350ad7234b735da8d46)) | ||
| * implement non-root user and directory permissions in Dockerfiles ([ab7fb75](https://github.com/dangeroustech/StreamDL/commit/ab7fb750d81fcf17d566c2206da30f197e2ef772)) | ||
| * permissions debug ([624be49](https://github.com/dangeroustech/StreamDL/commit/624be491182092d47e8a83ab902f36f8dbaaa8a6)) | ||
| * pin su-exec version in Dockerfile.client to ensure compatibility ([2879e47](https://github.com/dangeroustech/StreamDL/commit/2879e474cd5bc43b5007312cb63cca40b3bab2ed)) | ||
| * remove exclusion of /usr/local/go/** from Snyk scan parameters in deploy workflows ([edd43a3](https://github.com/dangeroustech/StreamDL/commit/edd43a3dcf3c77f30e8fde9f078aeb216adca53e)) | ||
| * remove go.work and go.work.sum files as they are no longer needed ([1a1c85d](https://github.com/dangeroustech/StreamDL/commit/1a1c85df5b15be988924aba68ccb32e044e7508c)) | ||
| * rename Docker Scan to Snyk Scan and update scan parameters in deploy_staging.yml ([4e38833](https://github.com/dangeroustech/StreamDL/commit/4e38833ac66e1d275bcdbb0f038c8c2a1153f839)) | ||
| * update category parameter for Snyk SARIF file in deploy workflows to include event name ([27c0b55](https://github.com/dangeroustech/StreamDL/commit/27c0b55a907dbac49f9a2cfc15d41270940f3b2d)) | ||
| * update category parameter for Snyk SARIF file in deploy workflows to remove redundant prefixes ([1dafbcd](https://github.com/dangeroustech/StreamDL/commit/1dafbcdbaacfe25f6a276490ceb89517f0fed5aa)) | ||
| * update ffmpeg version in Dockerfile.client from 7.1.1 to 8.0 ([9ac837d](https://github.com/dangeroustech/StreamDL/commit/9ac837de6f18e3226674e9d2bd5149097c95fa17)) | ||
| * update Go version and dependencies in Dockerfile and go.mod ([8cd053e](https://github.com/dangeroustech/StreamDL/commit/8cd053e0de91f7355e152c6859271ce8eeff2dfe)) | ||
| * update Snyk scan parameters in deploy workflows to include app vulnerabilities and exclude specific paths ([bc00136](https://github.com/dangeroustech/StreamDL/commit/bc001368ad67ce6f8eca6626cc99a02176f23937)) | ||
|
|
||
|
|
||
| ### 🐛 Bug Fixes | ||
|
|
||
| * --twitch--disable-ads is now the default ([c645c01](https://github.com/dangeroustech/StreamDL/commit/c645c0194e7acf6265b8f95d03cfca1a51f7f31c)) | ||
| * add sync.RWMutex for urls map, protect delete in downloadStream ([f2c8567](https://github.com/dangeroustech/StreamDL/commit/f2c8567aba37d432bc346e4e962ba4b81009aa43)) | ||
| * address CodeRabbit PR [#507](https://github.com/dangeroustech/StreamDL/issues/507) review items [#4](https://github.com/dangeroustech/StreamDL/issues/4),7-12 ([d96852d](https://github.com/dangeroustech/StreamDL/commit/d96852d325660391ff7a22cc28854206571718f2)) | ||
| * address remaining CodeRabbit PR [#507](https://github.com/dangeroustech/StreamDL/issues/507) review items [#13](https://github.com/dangeroustech/StreamDL/issues/13)-18 ([5269d40](https://github.com/dangeroustech/StreamDL/commit/5269d40ebb1b95d92ea42db7bda44eb8009f5c40)), closes [#13-18](https://github.com/dangeroustech/StreamDL/issues/13-18) | ||
| * address second round of CodeRabbit review comments ([ab2db99](https://github.com/dangeroustech/StreamDL/commit/ab2db99df40b7d3f481f5e16240e14b76d316793)) | ||
| * **ci:** drop Python 3.14 from test matrix (lxml lacks 3.14 wheels) ([1bd5ae1](https://github.com/dangeroustech/StreamDL/commit/1bd5ae1c3e413f652fac7f12da342d921048c221)) | ||
| * **ci:** exclude validator's own grep line from action version check ([e314026](https://github.com/dangeroustech/StreamDL/commit/e314026deb66e7c0123671ba4a001d6f6c9211bf)) | ||
| * **ci:** pin Snyk action to v1, lower severity threshold, quote GITHUB_STEP_SUMMARY ([52aa178](https://github.com/dangeroustech/StreamDL/commit/52aa178e16f2d47442c5f1bdeb477be2c463edc3)) | ||
| * **ci:** use setup-uv@v7 (v8 major tag not yet available) ([03412e6](https://github.com/dangeroustech/StreamDL/commit/03412e6855d61414927d3e3dd61d6c8151946a17)) | ||
| * **deps:** pin go-jose/v4 to v4.1.4 to resolve CVE-2026-34986 ([35ab99a](https://github.com/dangeroustech/StreamDL/commit/35ab99a3488fc21383f5748b69089b1aec6c4dc7)) | ||
| * **deps:** update grpc and transitive deps to resolve Snyk vulnerabilities ([e31c62a](https://github.com/dangeroustech/StreamDL/commit/e31c62a92a7a19046695a03500404c798186374d)) | ||
| * ensure user cleanup in downloadStream goroutine ([d9998f8](https://github.com/dangeroustech/StreamDL/commit/d9998f8e00a157817ed12499f2e393983630e2d8)) | ||
| * increase default FFMPEG reconnect delay from 5 to 15 seconds ([047e290](https://github.com/dangeroustech/StreamDL/commit/047e29072be2ed4d0f601da716fe0989058f7792)) | ||
| * protect urls map reads in ticker loop with urlsMu RLock ([a2b8955](https://github.com/dangeroustech/StreamDL/commit/a2b8955eae068c3f7d68260d6cdda1c0884f5f15)) | ||
| * protect urls map writes in ticker loop with urlsMu ([b36a499](https://github.com/dangeroustech/StreamDL/commit/b36a499a255f9768b008e1446aa793ce5761bd3a)) | ||
| * update curl package version in Dockerfile.server ([31f74d5](https://github.com/dangeroustech/StreamDL/commit/31f74d5692c14c1cb98a6be7d771f4a01405b4cd)) | ||
| * update curl version in Dockerfile.client from 8.14.1-r1 to 8.14.1-r2 ([296b3cb](https://github.com/dangeroustech/StreamDL/commit/296b3cb9915d798c339030ff24d60e8a5c9e3117)) | ||
| * update gRPC client connection method and increase timeout ([e9cdc75](https://github.com/dangeroustech/StreamDL/commit/e9cdc75cbc966c0a0916de65851accfb9d0e95f7)) | ||
| * update ownership and permissions for app directory and .venv in entrypoint script ([2fdc5db](https://github.com/dangeroustech/StreamDL/commit/2fdc5dba973dcf84c128eb796c5861f73b00e92e)) | ||
| * yt_dlp error handling bug that caused some plugins to always fail ([d816e98](https://github.com/dangeroustech/StreamDL/commit/d816e986ea4d601d9b4f84854de6c1132622e24a)) | ||
|
|
There was a problem hiding this comment.
Document the VOD feature in the 3.6.0 notes.
The new entry reads mostly like dependency/test churn, but this release's headline change is Twitch VOD support and its new config/CLI surface. Without a bullet for vod, vod_limit, -data, -vod-out, and -vod-move, users scanning the changelog won't discover the biggest functional change in 3.6.0.
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CHANGELOG.md` around lines 1 - 83, Add a concise bullet under the 3.6.0
changelog (near the New Features or top-level summary) documenting the Twitch
VOD support and the new config/CLI surface: mention the feature name (Twitch VOD
support) and list the new config/flags (vod, vod_limit, -data, -vod-out,
-vod-move) plus a short note on what each does (e.g., enable VOD downloads,
limit VOD count, data dir, VOD output path, atomically move completed VODs).
Ensure this appears alongside other headline changes in the 3.6.0 notes so users
scanning the changelog see the functional impact.
- Remove response channel from downloadVOD to prevent blocking on the live-stream-only drain loop (VODs use vodWg for shutdown) - Fix shadowed err in rate-limit retry so the "thrice" branch is reachable - Replace double Wait() with single cmd.Wait() after SIGINT - Use return instead of os.Exit(0) so deferred vodDB.Close() runs
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
download_stream.go (1)
449-450:⚠️ Potential issue | 🔴 CriticalEliminate double call to
cmd.Wait()which causes a race condition.The code calls
cmd.Wait()twice concurrently—once in a goroutine at line 526 and again in the shutdown branch at line 536. Go'sos/exec.Cmd.Wait()cannot be called more than once or concurrently from multiple goroutines. The second call will fail with "exec: Wait was already called" or cause a data race, and the control-listener goroutine never exits after a natural completion.Consolidate to a single
cmd.Wait()call by removing the unnecessarysigintchannel and control-listener goroutine, then listen for<-controldirectly in the select. In the shutdown case, read from<-naturalFinishinstead of callingcmd.Wait()again, and callMarkVODFailed()immediately to allow a restart to reclaim the VOD right away.Suggested fix
- sigint := make(chan bool) - // Always ensure base directories have correct permissions first if err := createDirWithUmask(outLoc); err != nil { log.Errorf("Failed to create output directory %s: %v", outLoc, err) @@ - // Single control listener - go func() { - for { - _, more := <-control - if !more { - sigint <- true - return - } - } - }() - buf := &bytes.Buffer{} cmd := fluentffmpeg. @@ select { - case <-sigint: + case <-control: log.Tracef("Sending SIGINT to VOD %s process", vod.ID) if err := cmd.Process.Signal(syscall.SIGINT); err != nil { log.Errorf("Failed to send SIGINT to VOD %s: %v", vod.ID, err) } - // Use only cmd.Wait() which handles process reaping internally - if err := cmd.Wait(); err != nil { + if err := <-naturalFinish; err != nil { log.Tracef("VOD %s process exited after SIGINT: %v", vod.ID, err) } - // Interrupted — leave as 'downloading'; stale threshold will handle retry + if vodDB != nil { + if err := vodDB.MarkVODFailed(vod.ID); err != nil { + log.Errorf("Failed to mark VOD %s as failed: %v", vod.ID, err) + } + } return🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@download_stream.go` around lines 449 - 450, The code currently calls cmd.Wait() from two places causing races; remove the control-listener goroutine and the sigint channel, stop calling cmd.Wait() twice, and centralize waiting on the process through the existing naturalFinish channel: have the control branch in the select read from the control channel directly (instead of signaling sigint), and in the shutdown handling read the error from <-naturalFinish (do not call cmd.Wait() again) and call MarkVODFailed() immediately so the VOD lock can be reclaimed; update any logic referencing sigint or the removed goroutine to use naturalFinish and control and ensure only the goroutine that sends to naturalFinish invokes cmd.Wait() once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@download_stream.go`:
- Around line 452-470: The setup block that creates directories
(createDirWithUmask) can return early while the VOD is already claimed
(outLoc/moveLoc/subfolder/user), so mirror the cmd.Start() error-path cleanup:
before each early return on directory/permission errors call the same
VOD-release/cleanup function used in the cmd.Start() failure handling (the
function invoked in that branch to release the VOD claim) so the database row
isn't left in `downloading` state; locate the cmd.Start() failure branch to copy
the exact release call and insert it before each return in this setup block.
In `@streamdl.go`:
- Around line 154-159: ClaimVOD moved the VOD into the in-progress/downloading
state before calling getStream, so if getStream(site.Site, "videos/"+vod.ID,
streamer.Quality) returns an error you must release that claim so the VOD can be
retried; on getStream error call the counterpart that reverts the claim (the
function that unclaims/releases a VOD—e.g., ReleaseVOD or the existing "release"
function used elsewhere for ClaimVOD) with the vod ID, handle/log any error from
that release call, then continue without leaving the row stuck in downloading;
ensure this change is made in the same block where getStream is called (around
resolvedURL, err := getStream(...)) and that no downloader goroutine is started
when releasing the claim.
---
Duplicate comments:
In `@download_stream.go`:
- Around line 449-450: The code currently calls cmd.Wait() from two places
causing races; remove the control-listener goroutine and the sigint channel,
stop calling cmd.Wait() twice, and centralize waiting on the process through the
existing naturalFinish channel: have the control branch in the select read from
the control channel directly (instead of signaling sigint), and in the shutdown
handling read the error from <-naturalFinish (do not call cmd.Wait() again) and
call MarkVODFailed() immediately so the VOD lock can be reclaimed; update any
logic referencing sigint or the removed goroutine to use naturalFinish and
control and ensure only the goroutine that sends to naturalFinish invokes
cmd.Wait() once.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0e3c8962-3488-411f-83ea-8a30f2ba6683
📒 Files selected for processing (2)
download_stream.gostreamdl.go
Mark VODs as failed when directory creation or URL resolution errors occur after ClaimVOD, so they can be retried on the next tick instead of staying stuck as 'downloading' until the stale threshold expires.
Summary
New Feature: Twitch VOD Download Support
vod,vod_limit), and graceful shutdownClaimVOD, lazy DB init,RowsAffectedchecks, server logging on failure pathsDependency Bumps (13 packages)
baseUnset)Misc
.get()on format fields to avoid KeyErrorStats
Test plan
Summary by CodeRabbit
New Features
Documentation
Tests
Chores