Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
8a345ce
perf(viewer): pi4-64/pi5 use mpv --vo=gpu --gpu-context=drm
vpetersson May 13, 2026
6da684f
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 13, 2026
0a309df
perf(viewer): drop --drm-mode pin on Pi4-64/Pi5 under --gpu-context=drm
vpetersson May 13, 2026
0bf027a
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 13, 2026
4bac962
refactor(viewer): consolidate Qt6 boards onto cage + Wayland, pin Pi …
vpetersson May 13, 2026
2be4eb6
fix(ansible): propagate tags into boot.yml include_tasks
vpetersson May 13, 2026
5789cf8
fix(viewer): keep Pi 4 on linuxfb; only Pi 5 / x86 / arm64 go cage
vpetersson May 13, 2026
52ac3ce
fix(viewer): mirror host render-GID for all Qt 6 boards, not just cage
vpetersson May 13, 2026
2384851
fix(viewer): Pi 4 stays on --vo=drm (Qt linuxfb DRM master contention)
vpetersson May 13, 2026
81f3eba
fix(viewer): cage opens on the first connected connector, not HDMI-A-1
vpetersson May 13, 2026
2329388
feat(viewer): mpv from apt.raspberrypi.com on Pi 4 / Pi 5, hwdec auto…
vpetersson May 13, 2026
d86d43b
fix(viewer): use deb-packaged Pi keyring for archive.raspberrypi.com
vpetersson May 13, 2026
797a6f2
feat(viewer): per-codec hwdec on Pi via Lua hook
vpetersson May 13, 2026
bb27b18
fix(viewer,server): HW-decode everywhere on Pi 4 / Pi 5 / x86
vpetersson May 13, 2026
43c36fb
test(viewer,server): mock _probe_video_codec; fix mypy on Popen IO types
vpetersson May 13, 2026
6dc0504
feat(server): downscale 4K HEVC → 1080p on Pi 5 (CMA workaround)
vpetersson May 13, 2026
bc37d98
fix(viewer,system): Pi 5 4K HEVC HW + display-resampled VO sync
vpetersson May 13, 2026
0b6bea0
feat(server): introduce PlaybackEnvelope dataclass + matrix + cache
vpetersson May 14, 2026
3a7a118
feat(server): envelope-driven asset processor with sibling-original
vpetersson May 14, 2026
a5e5afd
feat(server): re-render walker + startup envelope reconciler
vpetersson May 14, 2026
5603ec0
fix(server): preserve `.original.<ext>` siblings during orphan sweep
vpetersson May 14, 2026
eb3940b
test(server): cover sibling-original storage across normalisation paths
vpetersson May 14, 2026
0af7e47
docs(board-enablement): replace codec policy table with playback enve…
vpetersson May 14, 2026
83ac4a5
fix(tests): mypy on `_make_video_asset` + boolean is_enabled
vpetersson May 14, 2026
36aee8b
fix(server,tests): envelope-aware container gate + startup hook safety
vpetersson May 14, 2026
bb4a5e9
fix(server,tests): drop PYTEST_CURRENT_TEST gate; align stale summaries
vpetersson May 14, 2026
7577705
feat(envelope): add generic-arm64 key for Rock Pi / Armbian SBCs
vpetersson May 14, 2026
f6bc491
fix(server): make celery_tasks.py top-level django.setup() reentrant-…
vpetersson May 14, 2026
b325b55
fix(server): commit `original_uri` to DB before transcode (crash safety)
vpetersson May 14, 2026
70997b0
docs(board-enablement): script-driven 1-minute sample pack
vpetersson May 14, 2026
dde1b20
feat(envelope,viewer): runtime Rock Pi 4 detection unlocks v4l2m2m HW…
vpetersson May 14, 2026
91ca8d0
refactor(envelope,viewer): publish board subtype via host_agent + Redis
vpetersson May 14, 2026
6517aa9
perf(celery): cap walker to --concurrency=1 so transcodes can't choke…
vpetersson May 14, 2026
c249ae1
fix(viewer): force MPV on legacy ``generic-arm64`` DEVICE_TYPE
vpetersson May 14, 2026
a817b45
Merge remote-tracking branch 'origin/master' into perf/pi-vo-gpu-drm
vpetersson May 14, 2026
37d21b8
fix(viewer): route ``generic-arm64`` through cage + ALSA-default like…
vpetersson May 14, 2026
09e1b49
fix(envelope): Rock Pi 4 stays on H.264 1080p30 -- stock ffmpeg has n…
vpetersson May 14, 2026
ba8d470
feat(viewer,envelope): extend +rpt1 ffmpeg to arm64; Rock Pi 4 = HEVC…
vpetersson May 14, 2026
06a5461
perf(celery): cgroup CPU hard cap (`cpus: 1.0`) so encodes never star…
vpetersson May 14, 2026
a9d893c
perf(celery): scale CFS quota with host cores (half of \$(nproc), min…
vpetersson May 14, 2026
76ef458
perf(walker): hardware-decode the source in the transcode pipeline
vpetersson May 14, 2026
4d0184f
feat(server-image): extend +rpt1 ffmpeg pin to anthias-server too
vpetersson May 14, 2026
0340b4f
perf(celery,viewer): four hardening fixes so the player survives an u…
vpetersson May 14, 2026
80b58cd
fix(tests): use sh.ErrorReturnCode_1 in hwaccel fallback test
vpetersson May 14, 2026
2870358
refactor(asset-processor): drop on-device video transcoding
vpetersson May 14, 2026
cc2572c
feat(asset-processor): gate uploads to hardware-decoded codecs only
vpetersson May 14, 2026
321e735
feat(dashboard): surface unsupported-codec failures with copyable recipe
vpetersson May 14, 2026
a35b38f
fix(processing): give recipe output a codec suffix so it doesn't over…
vpetersson May 14, 2026
f85f803
chore: drop transcode-era defensive hardening on celery + server image
vpetersson May 15, 2026
0f12a8a
chore: keep nice -n 19 ionice -c 3 on celery
vpetersson May 15, 2026
5d9e76c
fix(dashboard): address review feedback on codec gate UX
vpetersson May 15, 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
12 changes: 11 additions & 1 deletion ansible/roles/system/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
---
- name: Configure boot partition (/boot/{config,cmdline}.txt)
ansible.builtin.include_tasks: boot.yml
ansible.builtin.include_tasks:
file: boot.yml
# Without apply:, Ansible's --tags filter would let the include
# itself match (so the tasks get *included*) but then filter every
# task inside boot.yml back out — touches-boot-partition would
# become a no-op. apply: copies these tags onto each included
# task so they actually run when the include matches.
apply:
tags:
- touches-boot-partition
- raspberry-pi
tags:
- touches-boot-partition
- raspberry-pi
Expand Down
9 changes: 9 additions & 0 deletions ansible/roles/system/templates/cmdline.txt.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
here as a token list so additions/removals stay easy to review.
Layout: Anthias-managed tokens first, then any tokens preserved from
cmdline.txt.orig (e.g. cfg80211.* from raspi-config).
Pi 5 4K HEVC CMA: NOT done via cmdline. Adding ``cma=512M`` here
makes the kernel take the cmdline value over the device-tree
``linux,cma`` node, orphaning rpi-hevc-dec entirely (returns
``Failed to probe hardware -517`` and ``/dev/video*`` disappears,
killing HEVC HW at every resolution). The CMA bump lives in
config.txt.j2 instead — ``dtoverlay=cma,cma-512`` rewrites the
DT linux,cma size in place, which preserves the codec driver's
``memory-region`` reference.
-#}
{%- set tokens = [
'console=serial0,115200',
Expand Down
12 changes: 12 additions & 0 deletions ansible/roles/system/templates/config.txt.j2
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,19 @@ dtparam=audio=on
camera_auto_detect=1
display_auto_detect=1
auto_initramfs=1
{# vc4-kms-v3d carries the CMA-size parameter on Pi 4/5; we override
the default on Pi 5 to give Hantro G2 enough buffer pool headroom for
4K HEVC dst frames. The standalone `dtoverlay=cma,cma-512` route
silently no-ops on Pi 5 because vc4-kms-v3d initialises the CMA
region first; reusing the v3d overlay's own cma-512 param is the
documented merge. Pi 4 already gets 512 MB CMA by default so the
override is a no-op there; the Pi 5 conditional below keeps the
extra arg local to the board that strictly needs it. #}
{% if device_type == 'pi5' -%}
dtoverlay=vc4-kms-v3d,cma-512
{%- else -%}
dtoverlay=vc4-kms-v3d
{%- endif %}
max_framebuffers=2

# Don't have the firmware add a video= line to cmdline.txt — let the
Expand Down
147 changes: 147 additions & 0 deletions bin/generate_board_enablement_testbed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
# Generate the 8-clip board-enablement test pack used by
# docs/board-enablement.md.
#
# Output: 4 H.264 + 4 HEVC clips at 1080p30/60 + 4K30/60, each ~1
# minute. That's long enough to read a stable Dropped: count and
# capture mpv's hwdec-current banner, short enough that re-encoding
# the whole pack on a Pi 4 doesn't take an afternoon.
#
# Idempotent: clips that already exist (and pass an ffprobe sanity
# check) are skipped. Re-run after a power cycle and the script
# only redoes what's missing.
#
# Usage:
# bash bin/generate_board_enablement_testbed.sh [DEST_DIR]
#
# DEST_DIR defaults to ~/bbb-testbed. Set EARLY_EXIT=1 to stop on
# the first missing output (useful when iterating on a single clip
# in CI).

set -euo pipefail

DEST="${1:-$HOME/bbb-testbed}"
CUT_SECONDS="${CUT_SECONDS:-60}"
HEVC_CRF="${HEVC_CRF:-23}"
BBB_BASE='https://download.blender.org/demo/movies/BBB'

mkdir -p "$DEST"
cd "$DEST"

log() { printf '[testbed] %s\n' "$*" >&2; }

# Returns 0 if $1 exists and ffprobe can parse it. Used so a
# previous half-written file (power cycle, ctrl-C) doesn't get
# silently kept on a re-run.
file_ok() {
[[ -s "$1" ]] || return 1

Check warning on line 37 in bin/generate_board_enablement_testbed.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=Screenly_screenly-ose&issues=AZ4lrfsISR9ZwIZSo_PZ&open=AZ4lrfsISR9ZwIZSo_PZ&pullRequest=2885
ffprobe -v error -show_format -of default=nw=1:nk=1 "$1" \

Check warning on line 38 in bin/generate_board_enablement_testbed.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=Screenly_screenly-ose&issues=AZ4lrfsJSR9ZwIZSo_Pa&open=AZ4lrfsJSR9ZwIZSo_Pa&pullRequest=2885
> /dev/null 2>&1 || return 1
return 0
}

# Sources: H.264 + AAC, full-length, from blender.org. We trim
# rather than downloading shorter variants because the trims also
# exercise the upload path's handling of files whose container
# trailer isn't strictly mp4-spec-aligned (ffmpeg's mp4 muxer is
# tolerant; the Pi V3D V4L2 M2M decoder less so).
SOURCES=(
bbb_sunflower_1080p_30fps_normal.mp4
bbb_sunflower_1080p_60fps_normal.mp4
bbb_sunflower_2160p_30fps_normal.mp4
bbb_sunflower_2160p_60fps_normal.mp4
)

log "step 1: download originals (skips existing)"
for f in "${SOURCES[@]}"; do
if file_ok "$f"; then
log " $f: present"
continue
fi
url="$BBB_BASE/$f"
log " $f: downloading from $url"
curl -sSL --fail -o "$f.tmp" "$url"
mv "$f.tmp" "$f"
done

# Step 2: cut H.264 sources to CUT_SECONDS via -c copy. Avoids a
# re-encode (instant on any host), keeps the bitstream identical
# so the resulting clip exercises the same V3D / Hantro G1 path as
# the full-length original.
declare -A H264_TARGETS=(
[bbb_1080p_30fps.mp4]=bbb_sunflower_1080p_30fps_normal.mp4
[bbb_1080p_60fps.mp4]=bbb_sunflower_1080p_60fps_normal.mp4
[bbb_4k_30fps.mp4]=bbb_sunflower_2160p_30fps_normal.mp4
[bbb_4k_60fps.mp4]=bbb_sunflower_2160p_60fps_normal.mp4
)

log "step 2: trim H.264 sources to ${CUT_SECONDS}s (-c copy)"
for out in "${!H264_TARGETS[@]}"; do
src="${H264_TARGETS[$out]}"
if file_ok "$out"; then
log " $out: present"
continue
fi
log " $out: trim from $src"
ffmpeg -hide_banner -loglevel error -y \
-ss 0 -t "$CUT_SECONDS" -i "$src" \
-c copy -avoid_negative_ts make_zero "$out.tmp.mp4"
mv "$out.tmp.mp4" "$out"
done

# Step 3: HEVC re-encode each H.264 cut. ``-tag:v hvc1`` writes
# the iOS-friendly codec tag (matches what the asset processor
# emits at upload time -- keeps the cross-board fleet sha256 test
# meaningful). CRF defaults to 23 to roughly match the source's
# perceived quality so a passthrough vs re-encode A/B comparison
# isn't muddied by a visible quality delta.
declare -A HEVC_TARGETS=(
[bbb_1080p_30fps_hevc.mp4]=bbb_1080p_30fps.mp4
[bbb_1080p_60fps_hevc.mp4]=bbb_1080p_60fps.mp4
[bbb_4k_30fps_hevc.mp4]=bbb_4k_30fps.mp4
[bbb_4k_60fps_hevc.mp4]=bbb_4k_60fps.mp4
)

log "step 3: HEVC re-encode (libx265 preset=medium crf=$HEVC_CRF)"
for out in "${!HEVC_TARGETS[@]}"; do
src="${HEVC_TARGETS[$out]}"
if file_ok "$out"; then
log " $out: present"
continue
fi
log " $out: encode from $src (this can take a minute or two)"
ffmpeg -hide_banner -loglevel error -y \
-i "$src" \
-c:v libx265 -preset medium -crf "$HEVC_CRF" -tag:v hvc1 \
-c:a copy "$out.tmp.mp4"
mv "$out.tmp.mp4" "$out"
done

# Step 4: report.
log "step 4: pack summary"
printf '\n%-32s %-10s %-12s %-10s %-8s\n' \
file codec resolution fps duration_s
printf '%-32s %-10s %-12s %-10s %-8s\n' \
-------- ----- ---------- --- ---
for f in bbb_1080p_30fps.mp4 bbb_1080p_60fps.mp4 \
bbb_4k_30fps.mp4 bbb_4k_60fps.mp4 \
bbb_1080p_30fps_hevc.mp4 bbb_1080p_60fps_hevc.mp4 \
bbb_4k_30fps_hevc.mp4 bbb_4k_60fps_hevc.mp4; do
[[ -f "$f" ]] || continue
codec=$(ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name -of csv=p=0 "$f")
width=$(ffprobe -v error -select_streams v:0 \
-show_entries stream=width -of csv=p=0 "$f")
height=$(ffprobe -v error -select_streams v:0 \
-show_entries stream=height -of csv=p=0 "$f")
fps_rat=$(ffprobe -v error -select_streams v:0 \
-show_entries stream=r_frame_rate -of csv=p=0 "$f")
fps=$(echo "$fps_rat" | awk -F/ '{ printf "%.0f", $1/$2 }')
dur=$(ffprobe -v error \
-show_entries format=duration -of csv=p=0 "$f")
printf '%-32s %-10s %-12s %-10s %-8s\n' \
"$f" "$codec" "${width}x${height}" "$fps" \
"$(printf '%.1f' "$dur")"
done

log "done. pack lives at $DEST"
128 changes: 98 additions & 30 deletions bin/start_viewer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,49 @@
chgrp -f video /dev/vchiq
chmod -f g+rwX /dev/vchiq

# Recreate the kernel's ``/dev/video-dec*`` symlinks inside the
# container for boards whose v4l2_request decoders are reachable
# from upstream mpv (RK3399 / Rock Pi 4 today; future Rockchip /
# Allwinner / Amlogic SBCs likely too). Privileged docker passes
# the underlying ``/dev/video*`` char devices through but mounts
# its own ``/dev`` tmpfs without the udev rules that produce the
# decoder symlinks on the host. ffmpeg's ``hevc_v4l2m2m`` /
# ``h264_v4l2m2m`` lookup expects ``/dev/video-dec*`` and dies
# with "Could not find a valid device" otherwise.
#
# We can't run udev inside the container (no privileged
# udevd, and /sys/class/video4linux is read-only via /sys
# bind), but we don't need to — the rule is mechanical: any
# /dev/video* whose /sys/class/video4linux/<name>/name reads as
# a stateless decoder driver gets a symlink. Iterate explicitly
# instead of shelling udev.
for dev_node in /dev/video*; do
[ -c "$dev_node" ] || continue

Check failure on line 29 in bin/start_viewer.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=Screenly_screenly-ose&issues=AZ4mkMdUOyQc2LVr0PMJ&open=AZ4mkMdUOyQc2LVr0PMJ&pullRequest=2885
base=$(basename "$dev_node")
drv_name_file="/sys/class/video4linux/$base/name"
[ -r "$drv_name_file" ] || continue

Check failure on line 32 in bin/start_viewer.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=Screenly_screenly-ose&issues=AZ4mkMdUOyQc2LVr0PMK&open=AZ4mkMdUOyQc2LVr0PMK&pullRequest=2885
name=$(cat "$drv_name_file" 2>/dev/null)
# Rockchip / Allwinner / Amlogic stateless decoders. The
# canonical kernel naming is:
#
# * ``rkvdec`` — Rock Pi 4's RK3399 HEVC + VP9 stateless
# decoder (and equivalents on RK3328 / RK356x / RK3588);
# * ``rockchip,<soc>-vpu-dec`` — the legacy "VPU" H.264 /
# MPEG block, exposed as a separate v4l2 node;
# * ``hantro-vpu`` / ``hantro-g*`` — same silicon family,
# different vendor-tree naming on a handful of boards;
# * ``cedrus`` — Allwinner H6 / H616 stateless decoder.
#
# We match the suffix ``-dec`` plus the ``rkvdec`` / ``cedrus``
# / ``hantro`` prefixes so all the above hit the same alias
# rule without us enumerating every kernel build's exact name.
case "$name" in

Check failure on line 48 in bin/start_viewer.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=Screenly_screenly-ose&issues=AZ4mkMdUOyQc2LVr0PML&open=AZ4mkMdUOyQc2LVr0PML&pullRequest=2885
rkvdec*|cedrus*|hantro*|*-vpu-dec|*-dec)
ln -snf "$dev_node" "/dev/video-dec${base#video}"
;;
esac
done

# Set permission for sha file
chown -f viewer /dev/snd/*
chown -f viewer /data/.anthias/latest_anthias_sha
Expand Down Expand Up @@ -116,36 +159,44 @@
# we just set; -E alone is subject to env_check / env_delete and is not
# guaranteed for XDG_* on Debian's default sudoers.
#
# x86 boards run under `cage`, a kiosk wlroots compositor, because
# balenaOS x86 doesn't expose /dev/fb0 (Qt's linuxfb plugin has nothing
# to draw to) and there's no host display server. cage acquires DRM
# master as root, exports WAYLAND_DISPLAY for its child, and exits when
# the child exits — so the existing kill -0 watchdog below still works.
# The inner sudo drops back to the viewer user; WAYLAND_DISPLAY has to
# be added to --preserve-env to survive sudo's env scrub.
if [ "$DEVICE_TYPE" = "x86" ] || [ "$DEVICE_TYPE" = "arm64" ]; then
# /dev/dri/renderD128 carries the host's `render` group, whose
# numeric GID is distro-dependent (typically 992 on Debian/Ubuntu,
# 109 elsewhere) and not present in the container's /etc/group.
# Without membership the `viewer` user can open card0 (group
# `video`, GID 44 — already a member) but not the render node, and
# VAAPI silently fails with "wayland: failed to open
# /dev/dri/renderD128". mpv then falls back to software decode and
# frames drop at 1080p on entry-level x86. Mirror the host GID
# into the container as a synthetic `host-render` group and add
# `viewer` to it, so the supplementary group list `sudo -u viewer`
# later resolves from /etc/group already includes render access.
if [ -e /dev/dri/renderD128 ]; then
render_gid=$(stat -c %g /dev/dri/renderD128)
if [ "$render_gid" -ne 0 ]; then
if ! getent group "$render_gid" >/dev/null; then
groupadd -g "$render_gid" host-render
fi
host_render_group=$(getent group "$render_gid" | cut -d: -f1)
usermod -aG "$host_render_group" viewer
# /dev/dri/renderD128 carries the host's `render` group, whose
# numeric GID is distro-dependent (typically 992 on Debian/Ubuntu,
# 109 elsewhere, 106 on Pi OS Bookworm) and not always present in
# the container's /etc/group. Without membership the `viewer` user
# can open card0 (group `video`, GID 44 — already a member) but
# not the render node. mpv uses the render node for --vo=gpu on
# every Qt 6 board, whether via wayland (cage path: x86 / arm64 /
# pi5) or drm (linuxfb path: pi4-64). Mirror the host GID into
# the container as a synthetic `host-render` group and add
# `viewer` to it; the supplementary group list `sudo -u viewer`
# later resolves from /etc/group then includes render access.
if [ -e /dev/dri/renderD128 ]; then
render_gid=$(stat -c %g /dev/dri/renderD128)
if [ "$render_gid" -ne 0 ]; then
if ! getent group "$render_gid" >/dev/null; then
groupadd -g "$render_gid" host-render
fi
host_render_group=$(getent group "$render_gid" | cut -d: -f1)
usermod -aG "$host_render_group" viewer
fi
fi

# x86 / arm64 / pi5 run under `cage`, a kiosk wlroots compositor.
# cage acquires DRM master as root, exports WAYLAND_DISPLAY for its
# child, and exits when the child exits — so the existing kill -0
# watchdog below still works. The inner sudo drops back to the
# viewer user; WAYLAND_DISPLAY has to be added to --preserve-env to
# survive sudo's env scrub.
#
# Pi 4 falls through to the legacy direct-sudo path that runs under
# QT_QPA_PLATFORM=linuxfb. The V3D 6.0 doesn't have the bandwidth
# to composite cage on top of video at 4K (738 vo drops/30 s under
# cage vs 3-6 on the linuxfb + --gpu-context=drm path), so Pi 4
# stays on linuxfb until either a newer mpv with v4l2request hwdec
# or a future Pi platform lets us re-evaluate. Qt5 boards (pi2/pi3)
# share the same direct-sudo fallback path.
case "$DEVICE_TYPE" in
x86|arm64|pi5)
# libseat's default `logind` backend D-Buses into systemd-logind to
# acquire a session, but containers have no logind session — cage
# exits with "Could not get primary session for user". Switch to
Expand All @@ -155,23 +206,40 @@
# devices — a digital-signage kiosk has no keyboard or mouse.
export LIBSEAT_BACKEND=builtin
export WLR_LIBINPUT_NO_DEVICES=1

# cage default `-m extend` spans all enumerated DRM outputs,
# including ones that are physically disconnected — so a Pi user
# who plugs into the second micro-HDMI port (HDMI-A-2 instead of
# HDMI-A-1) ends up with cage rendering to a portion of the
# virtual canvas that lands on the disconnected connector, and a
# black screen. Trixie ships cage 0.1.x which has no `-o
# <connector>` flag, but `-m last` restricts output to whichever
# connector came up most recently — for the boot-time case
# (which the kernel detects in enumeration order) that's the
# last connected output rather than the first. Good enough for
# the single-display kiosk path; dual-head signage is a separate
# workflow.
cage_mode=(-m last)

# cage runs as root (Dockerfile's USER root) and creates the
# Wayland socket with root:root 0600 perms, so `sudo -u viewer`
# below can't connect (Qt: "Failed to create wl_display
# (Permission denied)"). Chown the socket to viewer in cage's
# child *before* dropping privileges. cage exports WAYLAND_DISPLAY
# before exec'ing the child, so the path is fully resolved here.
cage -- bash -c '
cage "${cage_mode[@]}" -- bash -c '
chown viewer "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}" 2>/dev/null || true
exec sudo \
--preserve-env=XDG_RUNTIME_DIR,QT_SCALE_FACTOR,PYTHONPATH,WAYLAND_DISPLAY,LANG,LANGUAGE,LC_ALL \
-E -u viewer \
dbus-run-session /venv/bin/python -m anthias_viewer
' &
else
;;
*)
sudo --preserve-env=XDG_RUNTIME_DIR,QT_SCALE_FACTOR,PYTHONPATH,LANG,LANGUAGE,LC_ALL -E -u viewer \
dbus-run-session /venv/bin/python -m anthias_viewer &
fi
;;
esac

# Wait for the viewer
while true; do
Expand Down
6 changes: 6 additions & 0 deletions bin/upgrade_containers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TOTAL_MEMORY_KB=$(grep MemTotal /proc/meminfo | awk {'print $2'})
export VIEWER_MEMORY_LIMIT_KB=$(echo "$TOTAL_MEMORY_KB" \* 0.8 | bc)
export SHM_SIZE_KB="$(echo "$TOTAL_MEMORY_KB" \* 0.3 | bc | cut -d'.' -f1)"
# Memory cap for anthias-celery. 60% of host RAM is conservative
# headroom for the remaining celery workloads (ffprobe metadata,
# HEIC → WebP image conversion); the cap is here as a safety net
# against a decompression-bomb fixture or runaway ffprobe, not
# because routine workloads come anywhere near it.
export CELERY_MEMORY_LIMIT_KB=$(echo "$TOTAL_MEMORY_KB * 0.6" | bc | cut -d'.' -f1)
GIT_BRANCH="${GIT_BRANCH:-master}"

MODE="${MODE:-pull}"
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.balena.dev.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ services:
# Runs on the same image as anthias-server with a CMD override.
# See docker-compose.yml.tmpl for context on the merge.
image: ghcr.io/screenly/anthias-server:${GIT_SHORT_HASH}-${BOARD}
# nice + ionice keep the upload-time transcode pipeline from
# starving the on-device viewer; see docker-compose.yml.tmpl.
command: >
nice -n 19 ionice -c 3
celery -A anthias_server.celery_tasks.celery worker -B -n worker@anthias
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.balena.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ services:
# Runs on the same image as anthias-server with a CMD override.
# See docker-compose.yml.tmpl for context on the merge.
image: ghcr.io/screenly/anthias-server:${GIT_SHORT_HASH}-${BOARD}
# nice + ionice keep the upload-time transcode pipeline from
# starving the on-device viewer; see docker-compose.yml.tmpl.
command: >
nice -n 19 ionice -c 3
celery -A anthias_server.celery_tasks.celery worker -B -n worker@anthias
Expand Down
Loading