Skip to content

Tags: SysAdminDoc/StreamKeep

Tags

v4.31.2

Toggle v4.31.2's commit message
v4.31.2 — audit phase 2: 16 fixes across 18 files (GQL injection, typ…

…e bugs, TLS, performance)

Critical: GQL injection in schedule.py, Kick tuple unpacking dead code, twitch_recover QualityInfo type mismatch.
Security: yt-dlp flag injection (-- separator), Twitch IRC plaintext->TLS.
Reliability: Reddit .json query param, SoundCloud stale client_id, UTC timestamps, PP_LOCK shared.
Performance: highlight.py array bulk decode, chat_render binary search truncation.
UX: thumb cache collision, preview LRU cap, podcast HTML entities, CLI thread lifecycle, summarize error logging.

v4.31.0

Toggle v4.31.0's commit message
v4.31.0 - Phase 14 UX & Extensibility (F73-F80)

F73: Custom accent color picker - 8 presets + apply_accent() in theme.py
F74: i18n infrastructure - streamkeep/i18n/ with QTranslator loading
F75: Layout density modes - compact/cozy/spacious presets
F76: First-run onboarding wizard - 4-page dialog with ffmpeg check
F77: Plugin/Extension SDK - plugins.py with discovery + importlib loading
F78: High contrast theme - CAT_HIGH_CONTRAST palette (WCAG AAA)
F79: Encrypted config - secrets.py with DPAPI + protect/unprotect
F80: Native OS notifications - Windows Toast + Qt fallback

Wave 2 roadmap complete (F41-F80, all 40 features shipped).

v4.27.4

Toggle v4.27.4's commit message
v4.28.5 - Audio Normalization Profiles (F62)

New postprocess/normalization.py: two-pass EBU R128 loudnorm with 4 named
profiles (Broadcast -24 LUFS, Podcast -16, YouTube -14, Streaming -18).
Pass 1 measures, pass 2 applies with measured values for optimal results.
PostProcessor._run_loudnorm upgraded to use profiles. Phase 11 complete.

v4.17.0

Toggle v4.17.0's commit message
v4.17.0 - Kick chat, share bundles, Whisper, per-platform quality, No…

…tifications Center

1. Kick live chat (Pusher WebSocket)
   - streamkeep/chat/kick_ws.py: KickChatReader
   - Resolves chatroom via /api/v2/channels/{slug}
   - Subscribes chatrooms.<id>.v2, parses ChatMessageEvent
   - Hard-coded Pusher key+cluster + HTML-scrape fallback
   - ChatWorker dispatches on platform (twitch|kick)
   - Depends on websocket-client (optional bootstrap dep)

2. Share-archive export bundle
   - streamkeep/postprocess/bundle_worker.py: BundleWorker
   - Zips media + sidecars, skips dot-caches, path-traversal guard
   - Right-click in History / Storage -> Export share bundle (.zip)

3. Whisper transcription + auto-chapters
   - streamkeep/postprocess/transcribe_worker.py: TranscribeWorker
   - Prefers faster-whisper, falls back to whisper.cpp in PATH
   - Outputs .srt .vtt .transcript.json .chapters.auto.txt
   - Right-click in History -> Transcribe (Whisper)...

4. Per-platform default quality
   - _choose_default_quality_index() honors config["quality_defaults"]
   - Settings grid: Twitch/Kick/Rumble/YouTube/Other x quality combo
   - Legacy "1080 or source" heuristic as fallback

5. Notifications Center
   - streamkeep/notifications.py: NotificationCenter ring buffer
   - Header bell shows unread count, click -> QMenu dropdown
   - Hooked to download complete, live detected, update available,
     bundle exported, transcribe complete
   - Optional QApplication.beep() on success/warning/error levels

Build workflow adds websocket-client.

v4.16.0

Toggle v4.16.0's commit message
v4.16.0 - Auto-update, recurring schedules, bulk monitor, live chat, …

…browser companion

1. Auto-update checker (streamkeep/updater.py)
   - UpdateCheckWorker + DownloadUpdateWorker + arm_self_replace()
   - Opt-in via Settings toggle, banner in Download tab
   - Windows self-replace via detached .update.bat
   - Dismissed tags remembered so the banner doesn't nag

2. Recurring schedules
   - Queue items gain optional recurrence ("daily"/"weekly"/"mon,wed,fri")
   - _compute_next_fire handles wrap-around + pivots off "now"
   - Queue right-click menu to set recurrence

3. Drag-sort + bulk monitor ops
   - Monitor table: InternalMove drag-drop, persists via rowsMoved
   - Right-click context menu with multi-select bulk actions:
     enable/disable auto-record, set schedule window, remove

4. Live chat capture (Twitch)
   - streamkeep/chat/ package: TwitchIRCReader + ChatWorker
   - Anonymous justinfan IRC, IRCv3 tag parsing
   - Writes chat.jsonl + optional chat.ass sidecar (VLC/mpv auto-pick)
   - Download-tab dock panel shows live chat as it streams
   - Kick deferred (Pusher WS is its own feature)

5. Browser companion
   - streamkeep/local_server.py: 127.0.0.1-only HTTP, per-launch
     32-byte bearer token, hmac.compare_digest gate
   - Endpoints: GET /ping, POST /send_url
   - browser-extension/ MV3 source: popup, background, README
   - Settings tab exposes port + token + enable toggle

Theme: new updateBanner style. Shutdown hooks for server + chat.

v4.15.0

Toggle v4.15.0's commit message
v4.15.0 - Visual scrubber, parallel auto-record, disk preflight, thum…

…bs, live auto-split

Why: five power-user features that deepen existing capabilities or lift
real ceilings on v4.14.0.

1. Visual trim scrubber (ui/clip_dialog.py rewrite)
   - QGraphicsView filmstrip with 20 thumbnails, draggable start/end
     handles, live preview pane, clip-length readout
   - HH:MM:SS fields stay in sync with handles
   - Backed by new shared ThumbWorker

2. Parallel auto-record (main_window.py refactor + new panel)
   - self._autorecord_workers / _resolvers / _contexts dicts keyed by
     channel_id — independent of self.download_worker foreground
   - parallel_autorecords setting (default 2, cap 4)
   - Active recordings panel on Monitor tab shows live status rows

3. Disk-space preflight (utils.py + main_window hook)
   - free_space_bytes() / estimate_download_bytes() helpers
   - "X GB free" on Download output card
   - QMessageBox warning when estimate > 80% of free

4. Thumbnail columns on History + Storage
   - Shared ThumbLoader with 2-concurrent cap
   - Cached at <recording_dir>/.streamkeep_thumb.jpg per file
     size+mtime signature
   - Rows 72px tall, placeholder cell while thumb loads

5. Live-stream auto-split (workers/download.py chunked mode)
   - DownloadWorker.chunk_length_secs param
   - ffmpeg -f segment -segment_time N -reset_timestamps 1
   - <label>_part000.mp4, _part001.mp4, ...
   - Settings toggle + chunk-length spinbox (default 2h)

Shared: new postprocess/thumb_worker.py (filmstrip + single modes,
cached next to source).

Backward-compat: legacy _active_auto_record_channel and
_auto_record_resolve_worker kept as unused singletons so closeEvent
teardown path stays untouched.

v4.14.0

Toggle v4.14.0's commit message
v4.14.0 - Resume, Trim, per-channel profiles, Storage manager

Why: four S-tier user-facing features shipped together. All extend
existing capabilities (download, post-process, monitor, history) rather
than pulling the tool in a new direction.

1. Crash-safe resume for interrupted downloads
   - New streamkeep/resume.py + ResumeState dataclass
   - DownloadWorker writes a .streamkeep_resume.json sidecar next to the
     output, refreshes on each segment_done, clears on clean all_done
   - Startup scan walks the output root (+ per-channel override dirs)
     for orphan sidecars and surfaces a Download-tab banner with
     Resume / Discard actions
   - Resume re-resolves the source URL via the extractor to refresh
     short-lived playlist tokens before handing only the remaining
     segments to a new DownloadWorker

2. Lossless trim / clip
   - New postprocess/clip_worker.py (ClipWorker QThread, mirrors the
     ConvertWorker pattern)
   - New ui/clip_dialog.py with HH:MM:SS range fields, ffprobe-derived
     duration, and a frame-accurate re-encode toggle that exposes the
     existing video-codec picker
   - Entry points: History right-click -> Trim/Clip, and a Trim button
     in the footer after a successful download

3. Per-channel monitor profiles
   - MonitorEntry extended with override_output_dir, override_quality_pref,
     override_filename_template, schedule_start/end_hhmm, schedule_days_mask,
     retention_keep_last (all optional, config is forward-compatible)
   - New ui/monitor_entry_dialog.py, opened from an Edit button in the
     monitor table
   - ChannelMonitor._poll_tick now gates on entry_in_schedule_window
     (handles midnight-wrap and days-mask), and the auto-record start
     path also honours the window + per-channel output + quality pref
   - Retention after successful auto-record: recycle-bin oldest sibling
     recordings beyond keep_last via send2trash. Falls back to skip +
     log when send2trash is unavailable - NEVER permanent delete

4. Storage manager (new 4th tab of 5)
   - New streamkeep/storage.py scanner (3-level walk, metadata.json wins
     over dir-name heuristic)
   - New ui/tabs/storage.py: total/files/platform/channel metric cards,
     sortable table, "Recycle selected" with count+size+sample-paths
     confirm dialog. All deletes go through send2trash
   - Auto-scans on tab switch, explicit Rescan button

Bootstrap: send2trash added to optional deps. Build workflow updated.
Theme: resumeBanner styling added. Still Catppuccin Mocha.

Verified: py_compile + pyflakes clean across 16 touched files;
entry_in_schedule_window covers in/out/wrap/days-mask; resume sidecar
roundtrip + orphan scan; storage scan respects metadata.json sidecar.

v4.13.1

Toggle v4.13.1's commit message
v4.13.1 - QA audit follow-up fixes

Why: post-v4.13.0 audit found a real defect in the background-finalize
path where a single stale postprocess config key would silently kill the
entire metadata/NFO/chapters pass for that download. Plus pyflakes-level
cleanup across main_window and the yt-dlp extractor.

- workers/finalize.py: guard snapshot capture + apply with hasattr() so
  a stale/unknown PostProcessor attribute in the snapshot dict no longer
  AttributeError-aborts finalize before metadata is saved.
- ui/main_window.py: fix inverted wait() in _clear_monitor_seed_worker
  (was waiting only when the thread was already NOT running).
- ui/main_window.py: drop dead finalize_total local, unused 're' import,
  and fix 5 placeholder-free f-strings.
- extractors/ytdlp.py: drop unused subprocess / _CREATE_NO_WINDOW imports
  (dead after the http.run_capture_interruptible migration).

v4.13.0

Toggle v4.13.0's commit message
v4.13.0 - QA/UX/performance pass + background finalize

Why: eliminate remaining UI-thread hitches (post-download finalize, monitor
seed, auto-record resolve) and close a cluster of queue/auto-start metadata
bugs found in live use.

- New FinalizeWorker (streamkeep/workers/finalize.py): moves metadata save,
  NFO writing, chapter export, Twitch chat download, and post-processing off
  the UI thread with a planned-steps progress signal. Post-process settings
  are snapshotted per-task so mid-run Settings edits cannot corrupt an
  in-flight finalize. closeEvent cancels the worker cleanly.
- New monitor workers (streamkeep/workers/monitor_ops.py): SeedArchiveWorker
  (VOD-source seeding for newly-added monitored channels) and
  AutoRecordResolveWorker (live-stream resolve for auto-record). Both wrap
  HTTP calls in http_interruptible() so app close cancels inflight curl.
- Queue/auto-start correctness: queued, subscribed, and manually-selected
  VODs now preserve direct-source metadata. Twitch numeric VODs auto-start
  correctly; selected VOD title/channel metadata is not lost on dispatch.
  Queue locks while a download is active.
- Auto-record retry + monitor responsiveness: retries on transient resolve
  failures, no stacked pokes on a channel whose previous poll is still
  running.
- FetchWorker / PageScrapeWorker / PlaylistExpandWorker: per-stage
  interruption checks + http_interruptible() wrappers so cancel during
  fetch/scrape returns immediately.
- Live-capable channel URLs (Kick / Twitch / Rumble) probe live first and
  fall back to VOD listing, so a bare channel URL during a live broadcast
  starts live capture instead of enumerating old VODs.
- History/channel metadata persistence + analytics: HistoryEntry tracks
  channel id/name explicitly; Download / History / Monitor tabs share a
  single metadata contract.
- Premium UI polish across Download / Monitor / History / Settings: dense
  spacing, shimmer CTAs, hover lifts, finalize-aware status messaging.
  Still Catppuccin Mocha.

v4.12.1

Toggle v4.12.1's commit message
v4.12.1 — HOTFIX: frozen-exe fork bomb

The v4.12.0 StreamKeep.exe would spawn itself in a runaway loop on
launch. `streamkeep/bootstrap.py` called
`subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])`
whenever an optional dep failed to import — but in a PyInstaller-frozen
exe, `sys.executable` is the exe itself. Each failing import spawned
another StreamKeep.exe, which re-entered bootstrap, which spawned
another StreamKeep.exe — exponential process explosion.

Fixes:
- bootstrap.py: early-return when sys.frozen / sys._MEIPASS is set.
  Also dropped the bogus 'deno' optional entry (not a pip package).
- StreamKeep.py: multiprocessing.freeze_support() as the first call in
  main() so a future multiprocessing.Pool can never re-enter the
  launcher in child processes.
- scrape.py ensure_playwright_browser: same sys.executable fork hazard
  on the playwright chromium install path. No-ops auto-install when
  frozen and surfaces a manual-install hint instead.