Skip to content

feat: Twitch VOD download support#553

Closed
biodrone wants to merge 25 commits into
masterfrom
feat/twitch-vod-support
Closed

feat: Twitch VOD download support#553
biodrone wants to merge 25 commits into
masterfrom
feat/twitch-vod-support

Conversation

@biodrone

@biodrone biodrone commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds per-channel vod: true config toggle to download Twitch past broadcasts instead of live streams
  • New GetVods gRPC RPC enumerates VODs via yt-dlp; existing GetStream resolves each VOD URL through Streamlink/yt-dlp
  • SQLite database tracks download state with status lifecycle (downloading/completed/failed) and stale threshold detection for crash recovery
  • Separate -vod-out / -vod-move flags allow routing VOD downloads to different directories than live streams
  • VOD files use {user}_vod_{id}_{title}.mp4 naming with stream copy (no re-encoding)

Test plan

  • TestParseConfig_VODFields — VOD config parsing
  • TestVodDB_InitAndClose — DB creation
  • TestVodDB_FullLifecycle — started → completed lifecycle
  • TestVodDB_FailedVODIsRetried — failed VODs are retried
  • TestVodDB_StaleDownloadIsRetried — stale downloads detected and retried
  • TestVodDB_DifferentVODsAreIndependent — VOD isolation
  • Integration test: VOD download phase added to tests/integration/run.sh
  • Manual: configure a channel with vod: true and verify VODs download and are tracked in DB

Summary by CodeRabbit

Release Notes

  • New Features

    • Added VOD (Video On Demand) support for Twitch with per-channel configuration options (vod and vod_limit)
    • Added CLI flags -data, -vod-out, and -vod-move for managing VOD downloads and persistent state tracking
    • VOD status is now persistently tracked to avoid duplicate downloads
  • Documentation

    • Updated documentation with VOD configuration and usage guidance
  • Dependencies

    • Updated Go, Python, and gRPC dependencies to newer versions
    • Bumped project version to 3.6.0
  • Chores

    • Updated GitHub Actions workflows to newer major versions

dependabot Bot and others added 25 commits April 13, 2026 17:37
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/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
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6aee85fa-26c8-474a-a2ed-43f5d68d6c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 9f841c0 and af000db.

⛔ Files ignored due to path filters (6)
  • go.sum is excluded by !**/*.sum
  • node_modules/.package-lock.json is excluded by !**/node_modules/**
  • package-lock.json is excluded by !**/package-lock.json
  • protos/stream.pb.go is excluded by !**/*.pb.go
  • protos/stream_grpc.pb.go is excluded by !**/*.pb.go
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .github/workflows/deploy_master.yml
  • .github/workflows/deploy_staging.yml
  • .github/workflows/test.yml
  • .github/workflows/update-actions.yml
  • CHANGELOG.md
  • README.md
  • config.go
  • config/config.yml.example
  • config_reader_test.go
  • download_stream.go
  • entrypoint_client.sh
  • go.mod
  • grpc_client.go
  • protos/stream.proto
  • pyproject.toml
  • stream_pb2.py
  • stream_pb2_grpc.py
  • streamdl.go
  • streamdl_proto_srv.py
  • tests/integration/docker-compose.integration.yml
  • tests/integration/run.sh
  • vod_db.go
  • vod_db_test.go

📝 Walkthrough

Walkthrough

This PR implements a VOD (Video On Demand) download feature for Twitch, including SQLite-backed download state tracking, gRPC service extension, CLI/config support, and integration tests. GitHub Actions workflows are updated to newer major versions, and Python/Go dependencies are bumped.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/deploy_master.yml, deploy_staging.yml, test.yml, update-actions.yml
Updated multiple third-party GitHub Actions to newer major versions: actions/checkout v4→v6, actions/setup-node v4→v6, actions/setup-go v5→v6, Docker actions (setup-qemu, setup-buildx, login) v3→v4, github/codeql-action/upload-sarif v3→v4, and WyriHaximus/github-action-get-previous-tag v1→v2. No workflow logic changes.
Documentation & Configuration
README.md, CHANGELOG.md, config.go, config/config.yml.example
Added VOD download feature documentation (new vod and vod_limit YAML fields, -data/-vod-out/-vod-move CLI flags). Extended Streamer config struct with VOD and VODLimit fields. Added v3.6.0 changelog entry.
VOD Database & Persistence
vod_db.go, vod_db_test.go
Introduced VodDB type backed by SQLite with downloaded_vods table. Provides initialization, state lifecycle methods (ShouldDownloadVOD, MarkVODStarted/Completed/Failed), and stale-download retry logic. Five unit tests validate initialization, full lifecycle, failure/retry scenarios, and per-VOD independence.
Download Stream Logic
download_stream.go
Added sanitizeFilename() for safe VOD title filesystem usage and new downloadVOD() function that builds output/move paths, manages VOD state in database, launches FFmpeg with stream-copy args (-c:v copy -c:a copy -movflags +faststart), and implements graceful shutdown via control-channel-to-SIGINT forwarding.
gRPC Protocol & Service
protos/stream.proto, grpc_client.go, streamdl_proto_srv.py
Extended Stream service with new GetVods RPC. Added VodRequest, VodInfo, VodResponse protobuf messages. Implemented Go client getVods() with gRPC error mapping (NotFound→404, ResourceExhausted→429). Added Python server method GetVods with yt_dlp integration for VOD discovery and error handling.
Generated gRPC Code
stream_pb2.py, stream_pb2_grpc.py
Regenerated protobuf Python bindings for new GetVods method. Updated module metadata, added gRPC version compatibility checks, extended StreamStub/StreamServicer with GetVods handlers, refactored descriptor-building calls, and added experimental client/server method wrappers.
Main Application & Config Parsing
streamdl.go, config_reader_test.go
Added CLI flags (-data, -vod-out, -vod-move) with sensible defaults. Initialized persistent VOD database at startup. Refactored per-streamer loop to branch on streamer.VOD: VOD mode queries database for stale/pending downloads, resolves URLs via gRPC, spawns downloadVOD tasks; live mode preserved with reduced 60s→30s retry sleep. Added test TestParseConfig_VODFields to validate YAML config parsing.
Entrypoint & Dependencies
entrypoint_client.sh, go.mod, pyproject.toml
Extended shell entrypoint to create /app/data directory and update ownership attempts. Bumped Go indirect dependencies (golang.org/x/sys, added SQLite/modern.org modules). Updated Python version to 3.6.0 and tightened runtime constraints: curl-cffi≥0.15.0, grpcio≥1.80.0, streamlink≥8.3.0, yt-dlp≥2026.3.17; added grpcio-tools≥1.71.2 dev dependency.
Integration Tests
tests/integration/docker-compose.integration.yml, tests/integration/run.sh
Added /app/data volume mount to test client service. Extended integration test suite with Phase 5: VOD download test that writes VOD-enabled config, restarts client, polls for *_vod_* output files (180s timeout, 5s intervals), and validates file integrity (≥1000 bytes). Updated live-stream test messaging for clarity.

Sequence Diagram(s)

sequenceDiagram
    participant Client as StreamDL Client
    participant CtxMgr as Control Logic<br/>(streamdl.go)
    participant GrpcSrv as gRPC Server<br/>(Python)
    participant YtDlp as yt-dlp
    participant VodDB as SQLite DB<br/>(VOD State)
    participant FFmpeg as FFmpeg Process
    participant FileIO as Output Directories

    CtxMgr->>GrpcSrv: getVods(site, user, limit)
    GrpcSrv->>YtDlp: extract_info(url, download=False)
    YtDlp-->>GrpcSrv: VOD metadata list
    GrpcSrv-->>CtxMgr: [VodResult{id, title, ...}]

    loop For each discovered VOD
        CtxMgr->>VodDB: ShouldDownloadVOD(vodID, staleThreshold)
        alt VOD not yet downloaded
            VodDB-->>CtxMgr: true (proceed)
            CtxMgr->>GrpcSrv: getStream(site, "videos/"+vodID, quality)
            GrpcSrv-->>CtxMgr: resolved URL
            
            CtxMgr->>VodDB: MarkVODStarted(vodID, user, site, title)
            CtxMgr->>FFmpeg: downloadVOD(user, vod, url, outLoc, moveLoc, vodDB)
            
            FFmpeg->>FileIO: Write video with stream-copy
            FFmpeg-->>CtxMgr: completion status
            
            alt Success
                CtxMgr->>VodDB: MarkVODCompleted(vodID)
                CtxMgr->>FileIO: Move to final destination
            else Failure
                CtxMgr->>VodDB: MarkVODFailed(vodID)
            end
        else VOD already downloaded or in-progress
            VodDB-->>CtxMgr: false (skip)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hop, hop! VOD dreams take flight,
SQLite tracks each download night,
gRPC whispers, FFmpeg sings,
VOD persistence—what joy it brings!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/twitch-vod-support

Comment @coderabbitai help to get the list of available commands and usage tips.

@biodrone

Copy link
Copy Markdown
Collaborator Author

Duplicate — updating existing draft PR #551 instead.

@biodrone biodrone closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant