Tags: SysAdminDoc/StreamKeep
Tags
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 - 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.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 - 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 - 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 - 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 - 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 - 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 - 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 — 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.
PreviousNext