Commit Graph

37 Commits

Author SHA1 Message Date
Myron Blair 628a729afd Speed up ABR transcoding: cap variants at 2, drop x264 preset to superfast
Host CPU is an old Xeon E5530 (2010-era, no hardware encode available at all)
already fairly loaded, so raw compute is the real bottleneck. Two changes:
- Cap ABR renditions to the 2 highest applicable instead of up to 4 — roughly
  halves total encode work per job while still giving a real high/low
  adaptive-bitrate pair.
- veryfast -> superfast x264 preset, a meaningful further speed win at the
  cost of somewhat larger files and slightly softer quality per bitrate.
Combined, remaining variants also get more threads each since fewer variants
now share the same TRANSCODE_THREADS budget.
2026-07-27 20:41:30 -05:00
Myron Blair eed071dd8f Fix Now Processing panel never showing once the queue backlog exceeds ~500 jobs
The main queue list sorts by created_at desc and caps at 500 rows, but the FIFO
worker processes oldest-first — with a large backlog the actually-running job
was routinely outside that window and never reached the frontend at all.
Fetch it via a dedicated /transcode/queue/running endpoint instead.
2026-07-27 17:57:50 -05:00
Myron Blair 8fa1291e0f Fix progress flags being inserted before nice's own -n flag, breaking every job
-progress pipe:1 -nostats belong to ffmpeg, not nice — they were landing right
after cmd[0] (nice) instead of after the ffmpeg token, which made nice choke
on an unrecognized option and fail before ffmpeg ever started.
2026-07-27 17:47:25 -05:00
Myron Blair 71e731124a Add live transcode progress with a dedicated now-processing panel
- ffmpeg progress streamed via -progress pipe:1, persisted on the job as a percent
- retry/retry-all now mark the old row as superseded instead of deleting it,
  preserving the failed/cancelled audit trail
- frontend shows the running job in its own panel above the list with a live
  progress bar instead of burying it as just another row
2026-07-27 17:38:53 -05:00
Myron Blair 6438407f5b Fix retry-all-failed silently no-oping when content already has a queued job
Also lift the artificial 500-row cap on the retry-failed query.
2026-07-27 17:25:52 -05:00
Myron Blair e54803abb0 Add app version number, shown in Navbar next to the logo
New backend/VERSION file (starts at 1.0.0) served via GET /api/version,
displayed as a small "v1.0.0" next to the StreamHoard wordmark. Going
forward I'll bump this on meaningful changes -- patch for fixes, minor
for new features, major for big/breaking redesigns.
2026-07-27 00:01:04 -05:00
Myron Blair b74d8a2652 Add fast audio-only transcode mode; fixes no-sound from AC3/DTS/EAC3
Root cause of "movies have no volume control / can't hear anything":
0 of 200 movies had finished transcoding, so every movie streamed its
raw source file — and BDRip/AVI sources very commonly carry AC3/DTS/EAC3
audio, which no browser can decode natively. Browsers respond by hiding
the volume control entirely when they can't find a playable audio track,
which is exactly the symptom reported.

New "audio" transcode quality: video is stream-copied untouched, only
the audio track is re-encoded to AAC — dramatically faster than a full
ABR pass since no video re-encoding happens. Added:
- transcode_audio_fix() in transcode.py
- "audio" as a valid quality value everywhere quick/abr were validated
- POST /transcode/queue/fix-audio-all: cancels every still-pending job
  and replaces it with the fast audio fix for the same content (running
  jobs are left alone), so a library-wide backlog of slow ABR jobs can
  be swapped for something that actually restores sound soon
- Per-movie "Audio" button in Admin, "Fix Audio (Fast, All)" button on
  the Transcode Queue page

Note: this doesn't help HEVC/x265 sources, whose video (not just audio)
isn't natively browser-playable either — those still need a full ABR
re-encode to H.264.
2026-07-26 23:31:23 -05:00
Myron Blair cf8c7890be Add confirm+stop to Library Cleanup scan; branded album art placeholder
- Library Cleanup scan is now a real background job (POST .../start,
  GET .../status, POST .../stop) instead of one long synchronous request,
  so a Stop button can actually abandon it instead of just waiting.
  Frontend confirms before starting (explains it can take a minute+)
  and polls status while running.
- New shared AlbumArt component: shows real art when available, otherwise
  a branded "Streamhoard" placeholder instead of a bare icon in an empty
  box. Used in the Music grid, album detail header, and the player bar —
  swaps to real art automatically once enrichment/a manual edit sets it.
2026-07-26 23:15:07 -05:00
Myron Blair 43340c0f49 Remove redundant nav entries; parallelize cleanup file checks
- Navbar: drop the Users link (already reachable from Admin's quick-links)
- Admin quick-links: drop Settings (already reachable from the navbar
  for every user)
- library_cleanup.py: parallelize the missing-file stat checks across a
  small thread pool instead of one file at a time — sequential NAS round
  trips were taking minutes for a library of any real size, even after
  moving them off the event loop.
2026-07-26 23:05:53 -05:00
Myron Blair ef5c13d587 Fix Library Cleanup scan freezing the entire server
scan() was doing hundreds of blocking Path.is_file()/stat() calls directly
in an async function -- movie/episode/track files often live on the
NFS/CIFS-mounted NAS, so each stat call has real network latency. Since
the backend runs a single-threaded asyncio event loop, this froze every
other request (confirmed live: a plain GET /api/movies hung for 60s+ while
a cleanup scan was in flight) for as long as the scan took.

Moved every filesystem-touching check (missing files, orphaned HLS dirs)
into a plain sync function run via asyncio.to_thread, so the scan can no
longer block anything else.
2026-07-26 22:35:37 -05:00
Myron Blair 5fc37e46e4 Fix 8 code-review findings across transcode queue, requests, cleanup, music
- Episode transcode (single + bulk) now accepts storage_type=scan, matching
  stream_episode and the movie-transcode endpoint — Library-Scan-imported
  TV episodes can finally be transcoded, not just streamed.
- retry_job now forwards content_type, fixing retry for failed/cancelled
  episode jobs (was silently treating them as movies and failing again).
- cancel_job now updates the correct collection (movies vs episodes) based
  on content_type instead of always writing to db.movies.
- approve_request atomically claims the request (pending -> approving)
  before contacting Radarr/Sonarr, so a double-click or two concurrent
  admins can't both add the same content; releases the claim back to
  pending on any failure so a fixable error can be retried.
- Music enrichment's update_many now re-checks album_art_url is still
  empty, so it no longer clobbers art an admin manually set via PATCH.
- Library Cleanup's dangling-refs scan now also checks episode_progress
  for rows referencing a deleted episode (previously only watchlist/
  progress against movie_id were checked).
- Settings now surfaces a failed bulk-TMDB-enrich run as an error instead
  of a false "Enrichment done" success summary.
- Music player's isPlaying now syncs from the audio element's native
  play/pause events instead of only being set inside togglePlay/track-
  change, so it can't drift from actual playback state (buffering stalls,
  OS media-key pauses, etc).
2026-07-26 22:24:09 -05:00
Myron Blair 5ac78654d4 Code review fix: Library Cleanup left orphaned rows on episode delete
Fixing a missing-file or duplicate-import episode only deleted the
episode itself, leaving its episode_progress/subtitle rows behind for
the next scan to catch as separate orphaned_subtitles/dangling findings.
Consolidated movie/episode/track deletion (with all their dependent rows)
into one _delete_content() helper, used consistently by missing_files,
orphaned_episodes, and duplicate_paths.
2026-07-26 22:11:05 -05:00
Myron Blair 9af505b45c Add live progress status to bulk TMDB movie enrichment
Was a single synchronous request with no visibility into progress —
large libraries gave no indication whether it was working or just slow.
Now runs as a background job (matching the existing music-enrich pattern)
with GET /api/tmdb/enrich-all/status polled every 2s; Settings shows
"Enriching... N/total" while running and a summary once done.
2026-07-26 22:01:12 -05:00
Myron Blair a313008440 Fix ABR thread cap actually stacking per variant instead of capping total
Setting -threads:v:i to the full TRANSCODE_THREADS on every variant let
each of the (up to 4) ABR renditions ask for the full budget independently
-- confirmed live on CT104: ffmpeg was still pulling ~7 of 8 cores despite
the "cap". Now divides the budget across however many variants are in
this job (6 threads / 4 variants = ~1-2 each), so the total stays near
the intended cap regardless of source resolution.
2026-07-26 21:26:35 -05:00
Myron Blair 4e72f568f3 Speed up transcoding without overloading the VM, add bulk retry
- nice level 19 -> 10 (was the absolute lowest CPU priority) and cap
  ffmpeg to 6 threads on this 8-core host, leaving headroom for Mongo,
  the *arr stack, and the API itself instead of an unbounded encode.
- Add POST /transcode/queue/retry-failed to re-queue every failed/
  cancelled job at once; label the per-job Retry button with text
  (was icon-only) and clarify in the page copy that failed/cancelled
  jobs never delete the movie — retry just re-queues the same file.
2026-07-26 21:21:22 -05:00
Myron Blair c174ac60b1 Add Library Cleanup admin page — missing files, orphaned records, stuck jobs, duplicates
New read-only scan (GET /api/library/cleanup/scan) across movies, episodes,
tracks, subtitles, HLS output, transcode queue, and watchlist/progress rows:
- Missing files: DB record exists but the file is gone from disk
- Orphaned episodes: show_id no longer exists
- Orphaned HLS output: transcoded folders left behind by deleted content
- Stuck transcode jobs: "running" 2+ hours with no completion (backend-restart artifact)
- Duplicate imports: same file imported into the library more than once
- Orphaned subtitles / dangling watchlist+progress rows

Nothing is deleted until an admin clicks Fix (per-item or Fix All per
category) via POST /api/library/cleanup/fix.
2026-07-26 21:13:16 -05:00
Myron Blair 3d2b7a4122 Wire Requests approval into Radarr/Sonarr with live download status
Admin can now Approve a pending request as Movie or TV, which looks up
the title via Radarr/Sonarr's own lookup API, adds the best match with
search-on-add enabled, and tracks the resulting radarr_id/sonarr_id on
the request. A live status line (searching/queued/downloading/downloaded,
polled every 15s) shows next to each approved request.
2026-07-26 20:55:22 -05:00
Myron Blair 64d8945460 Fix wrong Content-Type for m4a/flac/ogg/wav/aac track streams
mimetypes on the backend image doesn't recognize these extensions, so
every non-mp3 track streamed as audio/mpeg — wrong codec hint, breaks
playback in strict browsers. Add an explicit fallback map.
2026-07-26 20:40:59 -05:00
Myron Blair cf31a14ae1 Speed up music scan by deferring tag reads to import time
Reading embedded ID3/MP4 tags for every file during the scan itself was
taking minutes even for a small (146-track) library, since each mutagen
open is a slow round trip over the CIFS-mounted NAS share. Scan now uses
fast filename/path parsing only; tag reads happen once, only for the
tracks actually selected for import.
2026-07-26 20:38:09 -05:00
Myron Blair 514cbad4ab Add filesystem Library Scan (movies/tv/music) and a full Music library
- New admin Library Scan page walks the NAS mounts directly for movies,
  TV, and music not already in Radarr/Sonarr/local storage, and imports
  them with storage_type=scan (fully integrated: streamable, transcodable,
  listed in Admin) — Radarr/Sonarr import flows are untouched.
- New Music section (nav tab, album-grid browse, per-album track list,
  persistent bottom player with queue/seek) backed by a Track model and
  range-request audio streaming endpoint.
- MusicBrainz + Cover Art Archive integration (free, no API key) to fill
  in album art for scanned tracks, paced to their 1 req/sec rate limit.
- Mounted the NAS music share into CT104 (new mp2 LXC mountpoint,
  CIFS read-only) alongside the existing video mount.
- Optional Bluetooth/output-device picker on the player bar via the Audio
  Output Devices API (setSinkId) for already-paired speakers — Chrome/Edge
  only, since browsers can't pair new BT devices directly.
2026-07-26 20:25:41 -05:00
Myron Blair f7626ced2e Merge watch profiles into user accounts — one profile per login, no picker
- Removed the "Who's Watching" sub-profile picker entirely: ProfileProvider now
  auto-resolves the single profile tied to the account immediately after login
- Backend: removed POST/DELETE /profiles (no more creating/deleting extra
  profiles — lifecycle is entirely driven by user create/delete, already wired
  in the admin Users CRUD); kept GET (internal) and PATCH (edit your own
  profile's avatar/kids-mode/rating cap)
- Deleted ProfileSelect page and the now-unused ProtectedRoute component
- Navbar: removed the "switch profile" action, account avatar is now just a
  static display
- api.js: dropped the now-dead X-Profile-Id header logic (backend already
  falls back to the account's single profile when the header is absent)
2026-07-26 19:40:12 -05:00
Myron Blair 5b191da471 Add user management (CRUD + admin toggle), make Settings available to all users
- New /admin/users page: create/delete users, toggle admin role, guarded against
  self-demotion/self-deletion
- Backend: GET/POST/PATCH/DELETE /api/admin/users, admin-only
- Settings moved from /admin/settings to /settings (all logged-in users); the
  password-change section shows for everyone, integration/API-key sections only
  render (and only fetch) for admins, since those endpoints stay admin-gated
- Navbar: Settings link now visible to all users; added Users nav link for admins
2026-07-26 19:21:50 -05:00
Myron Blair 4d77a36f99 Add admin-only service status panel with restart, wired to Docker directly 2026-07-26 17:53:19 -05:00
Myron Blair 19374b694e Add self-service admin password change (requires current password) 2026-07-23 22:48:32 -05:00
Myron Blair 5b881a902d Auto-enqueue ABR transcode on Sonarr import, matching Radarr import behavior 2026-07-23 22:45:40 -05:00
Myron Blair e27bb37f7b Rebrand StreamHoard, strip Emergent.sh platform artifacts
- Remove .emergent/ state dir, tracked .gitconfig, @emergentbase/visual-edits
  dep + craco wiring, emergent.sh script/meta tags, PostHog telemetry snippet
- Rename UI/docs branding Kino -> StreamHoard (README, DEPLOY.md, Navbar,
  Login, Register, AdminShowEpisodes, FastAPI title, health check response)
- Rename docker-compose container/network names to streamhoard-*
- Default admin email domain -> admin@streamhoard.local
2026-07-23 20:20:29 -05:00
Myron Blair 3e30f1491c Remove emergentintegrations dep - not on PyPI, unused in code 2026-07-23 19:50:31 -05:00
emergent-agent-e1 211d4c23d5 auto-commit for 296e06eb-544f-4710-845e-c5a611b15f0e 2026-07-23 23:03:09 +00:00
emergent-agent-e1 bd8480bd14 auto-commit for 0175172f-4f7e-4804-ad7f-68312dcdad0d 2026-07-23 22:23:41 +00:00
emergent-agent-e1 22d8479b39 auto-commit for b082c1c8-f6e3-4cfd-82ee-0396c70d1a00 2026-07-23 22:18:08 +00:00
emergent-agent-e1 1471d58385 auto-commit for 28f1e986-c2af-4742-ad64-e0661980843c 2026-07-23 22:01:21 +00:00
emergent-agent-e1 21cdd24116 auto-commit for b6946a78-11ff-4dc8-85b6-12e35b57d256 2026-04-29 16:34:49 +00:00
emergent-agent-e1 da8a2903b4 auto-commit for b5490b7b-d57f-4f53-9a99-bd8ed84027db 2026-04-29 16:33:26 +00:00
emergent-agent-e1 20aba50fa1 auto-commit for cf4f0369-14b7-4d06-b915-b8f7098fd48d 2026-04-29 16:21:32 +00:00
emergent-agent-e1 cdc8c8955f auto-commit for 14921357-f5e2-4aba-b4c1-a07a52c800cc 2026-04-29 16:01:20 +00:00
emergent-agent-e1 356aa13063 auto-commit for df4b0748-985b-4592-8c48-1e56102f3613 2026-04-29 14:49:07 +00:00
emergent-agent-e1 7673090279 auto-commit for a6c922a7-7216-4688-a9a9-38dcd5c45488 2026-04-29 14:33:29 +00:00