Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
44b658b
chore(deps): apply safe Dependabot updates on staging (#602)
biodrone Jul 18, 2026
1fc11cf
feat: parse FFmpeg progress and configurable log destinations (#603)
biodrone Jul 19, 2026
a6f0fd7
docs: opt-in Intel /dev/dri passthrough for Quick Sync hosts
cursoragent Jul 19, 2026
406e731
chore(deps): apply safe Dependabot updates on staging (#629)
biodrone Aug 15, 2026
c310dfc
chore(deps): apply safe Dependabot updates on staging (#645)
biodrone Sep 1, 2026
8954d3c
chore 🤖(deps): bump curl-cffi from 0.16.2 to 0.16.3
dependabot[bot] Sep 7, 2026
cf013ad
chore 🤖(deps): bump grpcio from 1.83.1 to 1.84.0
dependabot[bot] Sep 17, 2026
32587be
chore 🤖(deps): bump modernc.org/sqlite from 1.57.0 to 1.59.0
dependabot[bot] Sep 18, 2026
32a60db
chore 🤖(deps): bump streamlink from 8.5.0 to 8.6.1
dependabot[bot] Sep 21, 2026
2a82776
chore 🤖(deps): bump protobuf from 7.36.1 to 7.36.2
dependabot[bot] Sep 21, 2026
0ee6fc4
chore 🤖(deps): bump google.golang.org/grpc from 1.83.2 to 1.84.0
dependabot[bot] Sep 21, 2026
c54cddf
chore 🤖(deps): bump github/codeql-action from 4.37.9 to 4.38.1
dependabot[bot] Sep 21, 2026
7c2fb93
Merge pull request #656 from dangeroustech/dependabot/github_actions/…
cursor[bot] Sep 28, 2026
e56f8af
Merge pull request #655 from dangeroustech/dependabot/go_modules/stag…
cursor[bot] Sep 28, 2026
e7b9d88
Merge pull request #652 from dangeroustech/dependabot/go_modules/stag…
cursor[bot] Sep 28, 2026
78c3dfe
Merge pull request #654 from dangeroustech/dependabot/uv/staging/prot…
cursor[bot] Sep 28, 2026
f60c8e9
Merge pull request #648 from dangeroustech/dependabot/uv/staging/curl…
cursor[bot] Sep 28, 2026
275191f
Merge pull request #651 from dangeroustech/dependabot/uv/staging/grpc…
cursor[bot] Sep 28, 2026
4abae1f
Merge pull request #653 from dangeroustech/dependabot/uv/staging/stre…
cursor[bot] Sep 28, 2026
4e86cd1
chore 🤖(deps): bump streamlink from 8.6.1 to 8.6.2
dependabot[bot] Oct 14, 2026
5ce406c
chore 🤖(deps): bump github/codeql-action from 4.38.1 to 4.38.3
dependabot[bot] Oct 14, 2026
45d4b64
Merge pull request #662 from dangeroustech/dependabot/uv/staging/stre…
biodrone Oct 18, 2026
78922c8
Merge pull request #663 from dangeroustech/dependabot/github_actions/…
biodrone Oct 18, 2026
8eecc33
chore 🤖(deps): bump github/codeql-action from 4.38.3 to 4.39.0
dependabot[bot] Oct 19, 2026
4d97e0c
fix: unwrap yt-dlp/streamlink urllib3 _PERCENT_RE override clash
biodrone Oct 25, 2026
c79ba80
fix: handle class-shaped streamlink _PERCENT_RE override
biodrone Oct 25, 2026
731dcc9
Merge pull request #666 from dangeroustech/fix/urllib3-percent-re-clash
biodrone Oct 25, 2026
ecac1f6
Merge pull request #664 from dangeroustech/dependabot/github_actions/…
biodrone Oct 25, 2026
001854b
Merge pull request #613 from dangeroustech/cursor/qsv-dri-passthrough…
biodrone Oct 25, 2026
82928f7
fix(deps): patch open security alerts on staging
biodrone Oct 25, 2026
bd706b1
Merge pull request #667 from dangeroustech/fix/staging-security-deps
biodrone Oct 25, 2026
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: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@ STREAMDL_GRPC_ADDR=server
STREAMDL_GRPC_PORT=50051
TICK_TIME=60
LOG_LEVEL=INFO
# Primary log destination: file (default), stdout, or both
LOG_DEST=file
# Optional override; default is /app/data/streamdl.log when LOG_DEST is file/both
# LOG_FILE=/app/data/streamdl.log

# Intel /dev/dri passthrough is configured in docker-compose (devices/group_add),
# not via env. See README "Intel Quick Sync / /dev/dri passthrough".
# Default client FFmpeg is static and has no QSV yet (#611).
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ updates:
interval: daily
open-pull-requests-limit: 10
target-branch: staging
# Include transitive deps (e.g. certifi, urllib3) so their fixes land on staging
allow:
- dependency-type: all
assignees:
- biodrone
labels:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy_master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
uses: docker/setup-buildx-action@v4

- name: Login to DockerHub
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down Expand Up @@ -110,7 +110,7 @@ jobs:
--sarif

- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v4.39.0
continue-on-error: true
with:
sarif_file: snyk.sarif
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
uses: docker/setup-buildx-action@v4

- name: Login to DockerHub
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:

- name: Upload result to GitHub Code Scanning
id: snyk_results_upload
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v4.39.0
continue-on-error: true
with:
sarif_file: snyk.sarif
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: astral-sh/setup-uv@v7
- name: Set Up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- uses: actions/setup-go@v6
- uses: actions/setup-go@v7
with:
go-version: ${{ matrix.go-version }}
- name: Install Deps
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile.server
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ WORKDIR /app

# Copy in app files
COPY --from=deps /app/pyproject.toml /app/pyproject.toml
COPY --from=deps /app/uv.lock /app/uv.lock
COPY --from=deps /app/README.md /app/README.md
COPY --from=deps /app/streamdl_proto_srv.py /app/streamdl_proto_srv.py
COPY --from=deps /app/stream_pb2.py /app/stream_pb2.py
COPY --from=deps /app/stream_pb2_grpc.py /app/stream_pb2_grpc.py
COPY entrypoint_server.sh /app/entrypoint_server.sh
COPY streamdl_server_entrypoint.sh /app/streamdl_server_entrypoint.sh

RUN pip install --no-cache-dir uv==0.4.28 && uv sync && \
RUN pip install --no-cache-dir uv==0.4.28 && uv sync --frozen --no-dev && \
chmod +x /app/entrypoint_server.sh /app/streamdl_server_entrypoint.sh

# Add health check
Expand Down
55 changes: 50 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ Why not get some use out of it? Archivists everywhere, rejoice!
| `-batch` | Time betwen URL checks (seconds): increase for rate limiting | `5` |
| `-subfolder` | Add streams to a subfolder with the channel name | `false` |
| `-log-level` | Set logging level (debug, info, warn, error, etc) | `info` |
| `-data` | Directory for persistent data (VOD tracking database) | `/app/data` |
| `-log-dest` | Primary log destination: `file`, `stdout`, or `both` | `file` |
| `-log-file` | Log file path (used when `-log-dest` is `file` or `both`) | `<data>/streamdl.log` |
| `-data` | Directory for persistent data (VOD tracking database + default log) | `/app/data` |
| `-vod-out` | Output location for VOD downloads (defaults to `-out`) | Same as `-out` |
| `-vod-move` | Move location for completed VOD downloads (defaults to `-move`) | Same as `-move` |

Expand Down Expand Up @@ -68,12 +70,53 @@ When using Docker, be aware of the following:
Example directory setup before launching:

```shell
mkdir -p downloads/{,in}complete config
mkdir -p downloads/{,in}complete config data
```

Logs are piped to stdout by default so that `docker compose logs` works.
_If you know what you're doing, you can change this value in `entrypoint.sh`._
_Make sure to rebuild the container with `docker compose build` after editing this._
### Intel Quick Sync / `/dev/dri` passthrough

StreamDL can optionally receive the host Intel graphics device so hardware encode/decode is possible **when FFmpeg supports it**.

**Important limitations today**

- The default client image uses `mwader/static-ffmpeg`, which does **not** include Quick Sync (QSV) or VAAPI. Passing `/dev/dri` into that image alone will not accelerate encodes.
- Most StreamDL downloads remux with stream copy (`-c copy` for VODs; optional via `FFMPEG_STREAM_COPY` for live). Remuxing does not use the GPU. Quick Sync only helps when something **re-encodes** (in-process via `FFMPEG_EXTRA_*`, or a `post_script` transcoder).
- A QSV/VAAPI-capable client image is tracked in #611; first-class hwaccel presets in #612.

**Enable device access (opt-in)**

1. Confirm the host has a render node, e.g. `ls -l /dev/dri`.
2. Note the `video` / `render` group IDs (they vary by distro):
```shell
getent group video render
stat -c '%g' /dev/dri/renderD128
```
3. In `docker-compose.yml`, uncomment the client `devices:` / `group_add:` block from `docker-compose.yml.example` and replace the example GIDs with yours.
4. Keep `PUID`/`PGID` as usual for download file ownership; `group_add` is separate and only grants DRM device access.

Example (GIDs illustrative only):

```yaml
devices:
- /dev/dri:/dev/dri
group_add:
- "44" # video
- "992" # render
```

On TrueNAS / Kubernetes-style apps, map the same device and supplemental groups through the UI or runtime equivalent of Docker `devices` + `group_add`.

### Logging

StreamDL can send full application logs to a file, container stdout, or both:

| `LOG_DEST` / `-log-dest` | Primary logs | Container stdout (`docker compose logs`) |
| ------------------------ | ------------ | ---------------------------------------- |
| `file` (default) | `<data>/streamdl.log` | Active Downloads summary each tick only |
| `stdout` | stdout | Full logs (legacy-style) |
| `both` | file + stdout | Full logs, and the same content in the log file |

Override the file path with `LOG_FILE` / `-log-file` when using `file` or `both`. Mount `/app/data` (see `docker-compose.yml.example`) so the default log file and VOD database persist on the host.

### Bare Metal

Expand Down Expand Up @@ -204,6 +247,8 @@ Some of these are also available as flags to the `streamdl` command, this is a #
| `STREAMDL_GRPC_PORT` | The port number for the gRPC server | `50051` |
| `TICK_TIME` | Time interval (in seconds) between stream checks | `60` |
| `LOG_LEVEL` | Logging verbosity level (DEBUG, INFO, WARN, ERROR) | `INFO` |
| `LOG_DEST` | Primary log destination: `file`, `stdout`, or `both` | `file` |
| `LOG_FILE` | Log file path when `LOG_DEST` is `file` or `both` | `/app/data/streamdl.log` |
| `UMASK` | File permission mask in octal format (e.g. "022"). Controls default permissions for created files and directories | `022` |
| `PUID` | User ID that will own the files/directories created by the container (Docker only) | `1000` |
| `PGID` | Group ID that will own the files/directories created by the container (Docker only) | `1000` |
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,16 @@ services:
- ./downloads/incomplete:/app/dl # in-progress downloads location
- ./downloads/complete:/app/out # completed downloads location
- ./config:/app/config # config folder - using a folder allows host updates without cycling the container
- ./data:/app/data # VOD DB + default streamdl.log when LOG_DEST=file/both
# Optional: Intel Quick Sync / VAAPI device passthrough (see README).
# Uncomment only on hosts with /dev/dri. GIDs are host-specific — run
# `stat -c '%g' /dev/dri/renderD128` and `getent group video render`.
# NOTE: The default client image ships static FFmpeg without QSV/VAAPI.
# Device access alone is not enough until a QSV-capable image is available (#611).
# devices:
# - /dev/dri:/dev/dri
# group_add:
# - "44" # video — replace with your host video GID
# - "992" # render — replace with your host render GID
depends_on:
- server
32 changes: 25 additions & 7 deletions download_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package main

import (
"bytes"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -89,13 +88,20 @@ func downloadStream(user string, site string, quality string, initialURLs Stream
naturalFinish := make(chan error, 1)
sigint := make(chan bool)
t := time.Now().Format("2006-01-02_15-04-05")
progressKey := progressKeyLive(user)
downloadProgress.Start(progressKey, ProgressMeta{
Channel: user,
Kind: DownloadKindLive,
})
ffLogs := newFFmpegLogWriter(progressKey, downloadProgress)

// Always ensure the user is removed from the active list when this goroutine exits
defer func() {
activeUsersMu.Lock()
delete(activeUsers, user)
activeUsersMu.Unlock()
tickNotices.ClearChannel(user)
downloadProgress.End(progressKey)
log.Debugf("Removed %s from active list", user)
}()

Expand Down Expand Up @@ -184,13 +190,12 @@ func downloadStream(user string, site string, quality string, initialURLs Stream
}
url := streamURLs.Video

buf := &bytes.Buffer{}
cmd := fluentffmpeg.
NewCommand("").
InputPath(url).
OutputFormat("mp4").
OutputPath(outPath).
OutputLogs(buf).
OutputLogs(ffLogs).
Build()

// Inject network hygiene before the FFmpeg input ("-i") argument.
Expand Down Expand Up @@ -308,7 +313,7 @@ func downloadStream(user string, site string, quality string, initialURLs Stream
if err != nil {
// Emit a compact tail of FFmpeg logs to aid diagnosis
log.Warnf("FFmpeg failed for %s: %v", user, err)
ffLog := tailString(buf.String(), 50)
ffLog := tailString(ffLogs.String(), 50)
if ffLog != "" {
log.Warnf("FFmpeg log tail for %s:\n%s", user, sanitizeLog(ffLog))
}
Expand Down Expand Up @@ -542,6 +547,20 @@ func downloadVOD(user string, vod VodResult, url string, outLoc string, moveLoc

naturalFinish := make(chan error, 1)
sigint := make(chan bool)
progressKey := progressKeyVOD(user, vod.ID)
var totalDur time.Duration
if vod.DurationSeconds > 0 {
totalDur = time.Duration(vod.DurationSeconds) * time.Second
}
downloadProgress.Start(progressKey, ProgressMeta{
Channel: user,
Kind: DownloadKindVOD,
VodID: vod.ID,
Title: vod.Title,
TotalDuration: totalDur,
})
ffLogs := newFFmpegLogWriter(progressKey, downloadProgress)
defer downloadProgress.End(progressKey)

// Always ensure base directories have correct permissions first
if err := createDirWithUmask(outLoc); err != nil {
Expand Down Expand Up @@ -593,13 +612,12 @@ func downloadVOD(user string, vod VodResult, url string, outLoc string, moveLoc
}
}()

buf := &bytes.Buffer{}
cmd := fluentffmpeg.
NewCommand("").
InputPath(url).
OutputFormat("mp4").
OutputPath(outPath).
OutputLogs(buf).
OutputLogs(ffLogs).
Build()

// Prefer stream copy for VODs to avoid re-encoding
Expand Down Expand Up @@ -647,7 +665,7 @@ func downloadVOD(user string, vod VodResult, url string, outLoc string, moveLoc
case err := <-naturalFinish:
if err != nil {
log.Warnf("FFmpeg failed for VOD %s: %v", vod.ID, err)
ffLog := tailString(buf.String(), 50)
ffLog := tailString(ffLogs.String(), 50)
if ffLog != "" {
log.Debugf("FFmpeg log tail for VOD %s:\n%s", vod.ID, sanitizeLog(ffLog))
}
Expand Down
23 changes: 11 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ toolchain go1.25.3

require (
github.com/modfy/fluent-ffmpeg v0.1.0
github.com/sirupsen/logrus v1.9.3
github.com/sirupsen/logrus v1.10.2
github.com/x-cray/logrus-prefixed-formatter v0.5.2
google.golang.org/grpc v1.82.0
google.golang.org/protobuf v1.36.11
google.golang.org/grpc v1.84.0
google.golang.org/protobuf v1.36.12
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.48.2
modernc.org/sqlite v1.59.0
)

require (
Expand All @@ -20,23 +20,22 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.23.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/crypto v0.55.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/libc v1.75.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/memory v1.12.1 // indirect
)
Loading
Loading