Files
obs-config/loading/convert.sh
Jakub Zych 0a73f7e254 Music daemon + cross-source sync via OBS WebSocket
Music persistence across scene switches by extracting playback into a
separate browser source (music/index.html) — single source instance
shared across scenes via OBS's "Add Existing", so it doesn't restart on
scene change.

Loading and Game overlays subscribe to mpd:state CustomEvents over OBS
WebSocket; the daemon broadcasts at 1 Hz plus on every track change.
File-based command pipeline lets bash control playback without a
WebSocket client of its own:

- vendor/obs-ws-mini.js — minimal WS v5 client with HMAC-SHA256 auth,
  hard timeout, close-rejection, and a pure-JS SHA-256 fallback for
  CEF file:// origins where crypto.subtle is unavailable.
- setup.sh — generates vendor/obs-config.js from the OBS plugin config;
  checks the live socket instead of the (lagging) config file.
- music/index.html — invisible audio worker. Plays through a shuffled
  queue with hardened error/stall recovery, polls loading/cmd.js for
  bash-issued commands, broadcasts mpd:state via WS. Includes a
  diagnostic status block (visible if the source's eye-icon is on).
- loading/index.html — strips local audio playback; subscribes to
  mpd:state to drive the now-playing terminal line live. Removes the
  redundant trailing prompt; terminal lines anchor at the bottom.
- loading/playlist.sh — rebuilt as subcommand API:
    sync (default), skip, prev, pause, resume, status
  control commands write loading/cmd.js (id, type, ts), which the
  daemon polls every 250ms.
- loading/convert.sh — strips video tracks from .webm music downloads
  to audio-only m4a (~95% smaller, ~20× faster decode); fixes the
  CPU pegging from CEF decoding 1080p VP9 video for music playback.
- .gitignore — track manifests, ignore generated wrappers + secrets +
  the audio cache.
2026-04-26 02:33:50 +02:00

81 lines
2.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# loading/convert.sh — strip video tracks out of audio files in
# ~/.config/obs-studio/playlist/ and re-encode to AAC m4a in place.
#
# WHY: yt-dlp .webm downloads include 1080p video. Chromium's <audio>
# element decodes the video stream too (just doesn't render it) — pegs
# CPU and stalls playback mid-track. Audio-only m4a is ~95% smaller
# and decodes ~20× faster.
#
# ./convert.sh — convert all video-bearing files
# ./convert.sh --dry-run — show what would happen, do nothing
#
# DO NOT run this while OBS has a playlist track open (between streams
# is fine; mid-stream you'll lock the file ffmpeg is reading from).
#
# After conversion, re-index: ./playlist.sh
set -euo pipefail
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OBS_DIR="$(cd "$DIR/.." && pwd)"
AUDIO_DIR="$OBS_DIR/playlist"
DRY=0
[[ "${1:-}" == "--dry-run" ]] && DRY=1
if [[ ! -d "$AUDIO_DIR" ]]; then
echo "$AUDIO_DIR does not exist" >&2
exit 1
fi
if ! command -v ffprobe >/dev/null 2>&1 || ! command -v ffmpeg >/dev/null 2>&1; then
echo " ✗ ffmpeg / ffprobe required" >&2
exit 1
fi
shopt -s nullglob nocaseglob
# Files that contain a video stream (and aren't already m4a)
declare -a TODO=()
for f in "$AUDIO_DIR"/*.{webm,mp4,mkv,mov,avi}; do
[[ -f "$f" ]] || continue
# Skip yt-dlp intermediate files
case "$(basename "$f")" in
*.f[0-9]*.*|*.temp.*|*.part) continue ;;
esac
# Only consider files that actually contain a video stream
if ffprobe -v error -select_streams v -show_entries stream=codec_type \
-of csv=p=0 "$f" 2>/dev/null | grep -q video; then
TODO+=("$f")
fi
done
if [[ ${#TODO[@]} -eq 0 ]]; then
echo " ✓ nothing to convert (no video-bearing files in $AUDIO_DIR)"
exit 0
fi
echo "── Converting ${#TODO[@]} file(s) → AAC m4a ──"
for f in "${TODO[@]}"; do
base="${f%.*}"
out="$base.m4a"
if [[ -f "$out" ]]; then
echo " ⊘ skip (m4a exists): $(basename "$f")"
continue
fi
echo "$(basename "$f")"
if [[ $DRY -eq 1 ]]; then
echo " (dry-run: would write $(basename "$out") and remove the source)"
continue
fi
if ffmpeg -nostdin -loglevel error -i "$f" -vn -c:a aac -b:a 192k "$out"; then
rm -f "$f"
echo "${out##*/} ($(du -h "$out" | cut -f1))"
else
echo " ✗ ffmpeg failed; leaving $(basename "$f") in place" >&2
rm -f "$out"
fi
done
echo
echo " → next: ./playlist.sh (re-index for the new filenames)"