Commit Graph

17 Commits

Author SHA1 Message Date
xRangerDE
fab263ae4c feat: live recording meta + events viewer modal
Two finishing touches on the live-recording stack.

1. Live recording meta line. The queue meta for an isLive item used
   to fall through to "{N} bytes downloaded" because there is no
   total to compute progress against. Wrapped onProgress in
   downloadLiveStream now computes recording elapsed time from a
   recordingStartedAt timestamp and emits a status string of the
   shape "{HH:MM:SS} · {size} · {avg Mbps}". Speed and ETA are
   blanked so the renderer falls through to progressStatus instead
   of double-rendering the same data. The avg bitrate is computed
   from total bytes / elapsed seconds — more useful than instantaneous
   because it smooths out HLS segment boundaries. Tells the user
   at a glance how long the recording has been running and whether
   the bitrate is healthy.

2. Events viewer modal. Companion to the chat viewer from 4.6.8.
   Queue items with a sibling .events.jsonl get a new "View events"
   button next to "View chat". Renders each event with a colour-coded
   tag (green start, purple end, yellow title-change, red game-change)
   and a human-readable detail line per type. Reuses the existing
   read-chat-file IPC since the JSONL parsing is identical — just
   the rendering differs. Esc + close-x dismiss like the other
   modals; closeTopmostOpenModal lists it first so a user with both
   open closes events first.

DE + EN locale strings for the new button + every event-type detail
line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:50:13 +02:00
xRangerDE
3129c9b5be feat: in-app chat replay viewer — read .chat.json/.chat.jsonl without leaving the app
Up to now, the app saved chat data (4.6.2 VOD replay, 4.6.3 live
capture) but had no way to view it — users had to open the file in
Notepad or write a custom parser. New in-app modal closes that loop:
queue items with a sibling .chat.json or .chat.jsonl get a "View
chat" button next to Open file / Show in folder; click pops a modal
with a scrollable, filterable, formatted message list.

Server:
- New ipcMain.handle("read-chat-file") parses both formats. JSON
  Lines (.jsonl) is split per line, header row skipped, malformed
  lines silently dropped — that way a partial / killed live capture
  still renders. JSON object (.json) is the VOD replay shape with
  messages array. Hard-capped at 50k messages so a multi-day archive
  can't kill the renderer; truncation is reported via {truncated,
  total} in the result.

Renderer:
- New chatViewerModal in index.html — full-height list with a filter
  input + status line.
- openChatViewer(filePath, title) loads the file via IPC, normalises
  the message shape (supports both .chat.json and .chat.jsonl
  fields), renders in 500-message chunks via setTimeout(0) so the
  main thread stays responsive on a 30k-message archive.
- Each row: time marker (offset for replays, wall-clock for live),
  user (in their stored color), message text. Non-msg event types
  (subs, raids, clears) get a faint italic [type] tag.
- Filter substring-matches user OR text, case-insensitive, instant.
- Esc + outside-click + the close-x dismiss; Esc handler in
  closeTopmostOpenModal lists the chat viewer first so a user
  with multiple modals open closes the foreground one.

Queue UI:
- renderQueueItemFileActions detects sibling chat files (regex
  /\.chat\.json(l)?$/) in item.outputFiles and surfaces the View
  chat button. The button is shown for both 4.6.2-style replays
  and 4.6.3-style live captures because both formats parse.

DE + EN locales for the button label, loading state, error,
message count, truncation suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 21:42:41 +02:00
xRangerDE
56261216a9 feat: live stream recording — record streamers as they go live
VODs disappear from Twitch after 7-60 days depending on the channel
partnership tier. Anyone serious about archiving needs to capture
streams while they are still live, not after. The downloader is now
a recorder too.

End-user surface:
- Each streamer in the sidebar has a small red "REC" pill next to
  the remove-x. Click it -> server checks Helix (or public GQL when
  no client_id is configured) for live status. If the channel is
  online a new queue item is added with isLive: true, status:
  pending; the existing queue scheduler picks it up. Toast feedback
  for offline / already-recording / generic-failure cases.
- Live items render with a pulsing red REC badge in the queue title
  row and skip the bulk-select checkbox + the merge-group selector
  (they don't make sense for an open-ended capture).
- Output goes to {download_path}/{streamer}/live/
  {streamer}_LIVE_{YYYY-MM-DD}_{HH-mm-ss}.mp4 — timestamped so back-
  to-back recordings of the same channel never collide.
- Streamlink runs without --hls-start-offset / --hls-duration so it
  records until the stream actually ends or the user hits cancel /
  remove. The existing per-item filename claim, integrity check on
  close, and downloaded_vod_ids tracking apply unchanged (live
  recordings are not added to downloaded_vod_ids since they have
  no Twitch VOD ID).

Server plumbing:
- New getLiveStreamInfo(login) helper. Helix /streams when an app
  token is available (better metadata: title + game), public GQL
  fallback otherwise so users in public-mode still get live status.
- New IPC start-live-recording(streamerName) does the live check,
  refuses with ALREADY_RECORDING if a live item for the same
  channel is already pending or downloading.
- downloadVOD branches into a small downloadLiveStream helper when
  item.isLive — computes the timestamped filename, ensures the
  per-streamer/live folder exists, hands off to downloadVODPart
  with null start/end times.
- sanitizeQueueItem preserves the isLive flag across queue file
  reload so a recording in progress survives an app restart in
  state (though streamlink itself dies on app exit and the user
  has to re-trigger).

DE + EN locale strings for every toast + tooltip + the queue badge.
CSS animation for the pulsing badge so it visually distinguishes
live recordings from regular VOD downloads at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:30:08 +02:00
xRangerDE
1f2b5e583c feat: --twitch-disable-ads + queue context menu + remove redundant flag
Three Phase-12 wins.

1. Streamlink --twitch-disable-ads is now a setting (default on, since
   most users hit this — Twitch mid-roll ads otherwise get embedded
   into the VOD output as black-screen audio gaps). Off only when the
   user explicitly opts out via the new checkbox in Download Settings.
   Applied in downloadVODPart args; clip downloads are unaffected
   (Twitch clips do not carry mid-roll ads).

2. Right-click context menu on queue items. Items vary by status:
   pending/paused -> Move to top, Move to bottom; failed -> Retry;
   completed -> Open file (when 1 output) / Show in folder; always
   -> Copy URL, Open on Twitch, Remove from queue. Move-to-top/
   bottom calls existing reorderQueue IPC. Menu auto-dismisses on
   outside-click / Escape / scroll, repositions to stay inside the
   viewport.

3. Removed the global currentDownloadCancelled flag. It was a
   leftover from before per-item tracking — every site that set it
   (pause-download / cancel-download / remove-from-queue) already
   added every active item to cancelledItemIds via the activeDownloads
   loop. The four read sites (downloadVODPart close handler,
   processOneQueueItem retry-loop guard, processDownloadMergeGroup
   phase 1 and phase 3 guards, splitMergedFile loop) now check
   cancelledItemIds.has(itemId) directly. splitMergedFile reads
   from its itemId parameter (added in cycle 1) so the per-item
   intent threads through correctly. Net: -8 lines, one less
   global flag to reason about, no behaviour change for the
   intended cases (per-item cancel via remove + bulk cancel via
   pause/cancel both still work because they each populate the
   per-item set).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:09:33 +02:00
xRangerDE
3e1d4e188c feat: cutter/merge i18n + per-item retry + status-bar queue summary
Three Phase-6 wins.

1. Cutter & Merge tab labels were the same i18n gap as the trim-VOD
   dialog before 4.5.20: Dauer / Aufloesung / FPS / Auswahl / Start: /
   Ende: / Schneiden / Zusammenfuegen were hardcoded German in
   index.html. Each got an id + setText wiring + DE/EN locale strings
   (cutter.infoDuration / .infoResolution / .infoFps / .infoSelection
   / .startLabel / .endLabel; cutter.cut + merge.merge already existed
   for dynamic state, now also used as initial text on btnCut /
   btnMerge).

2. Per-item retry button on failed queue entries. The existing
   "retry failed" queue-action retried ALL failed items at once;
   when only one specific item should be retried (e.g. transient
   network blip on one URL), the user had to remove every other
   failed item first. New ipcMain.handle("retry-queue-item", id)
   resets that single item to status: pending and triggers
   processQueue if idle. A small ↻ icon now sits next to the
   remove (x) button on items in the error state.

3. Status bar queue summary. The footer previously showed only the
   connection status + version. With longer queues the user had to
   scroll the queue panel to see how many downloads were active
   versus pending. New span between the status indicator and the
   version reads "{downloading} dl, {pending} queued" (locale-aware,
   hidden when queue is empty). Updated on onQueueUpdated and
   onDownloadProgress so it stays live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:02:42 +02:00
xRangerDE
933af6a6da feat: queue "Open file" / "Show in folder" + clickable finish notification
After a download completes there was no way to jump to the result
without manually navigating the download folder.

Server-side:
- DownloadResult and QueueItem gain optional outputFiles: string[]
  (single entry for VOD/clip, multi for parts/merge-group splits).
  Threaded through every downloadVOD / processDownloadMergeGroup
  branch into processOneQueueItem which attaches it to the queue
  item on success. Persisted via sanitizeQueueItem so the actions
  survive a queue file reload.
- New IPC handlers open-file (shell.openPath) and show-in-folder
  (shell.showItemInFolder), both with existence + type checks.
- The "downloads finished" Notification gets a click handler that
  brings the window to the foreground and opens the download folder.

Renderer-side:
- Expanded queue-item details now render an action row when
  status === completed and outputFiles is non-empty.
- "Open file" only shown when there is exactly one file (so multi-
  part downloads do not surprise the user by opening just part 1).
  "Show in folder" always shown.
- DE / EN locale strings + a graceful toast if the file was moved
  or deleted between completion and click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:19:29 +02:00
xRangerDE
feebfc86a1 ui: data-id queue lookup + persisted active tab + Esc/Ctrl+N shortcuts
Renderer-side polish bundle.

- updateQueueItemProgress now looks up items by [data-id] selector instead
  of array index. Resilient against queue/DOM divergence between renders.
  Determinate vs indeterminate progress logic tightened.

- Active tab persisted to localStorage on every showTab; restored on init
  via loadPersistedActiveTab (whitelisted to known tab IDs so a future
  rename cannot strand the user on a missing tab). Page title now only
  shows the streamer name on the VODs tab — it no longer leaks into
  Settings / Cutter / Merge.

- Escape closes the topmost open modal regardless of focus (clip dialog,
  template guide, update modal — in that priority order).

- Ctrl+1..5 (Cmd+1..5 on macOS) jumps directly to a tab. The existing Del
  (delete selected) and S (start/pause) shortcuts still work and remain
  blocked while typing in inputs.

Adds docs/IMPROVEMENT_LOG.md (new, single dated section for this cycle).

Build: tsc clean. Full smoke suite green (failures: [], runtimeIssues: []).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:10:28 +02:00
xRangerDE
2b379e5e6a fix: correct dragstart cancel method and release claimed filenames after download
- Use dataTransfer.effectAllowed='none' instead of preventDefault() for dragstart
  (preventDefault does not cancel dragstart events per HTML spec)
- Clear claimedFilenames Set in processOneQueueItem finally block to prevent
  stale claims from blocking re-downloads of same VODs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:19:30 +01:00
xRangerDE
cf9d7b8334 fix: selector overflow for 10+ items, drag-drop status guard, filename claim set for parallel safety
- Queue selector uses min-width instead of fixed width for double-digit numbers
- Drag-start handler validates item is still pending before allowing drag
- ensureUniqueFilename uses in-memory claim set to prevent TOCTOU race

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:15:25 +01:00
xRangerDE
4607ba9cc6 fix: persist expanded details across re-renders, guard drag-drop init against duplicates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:03:20 +01:00
xRangerDE
0133569104 fix: include selection state in queue render fingerprint so numbered selectors update visually
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:06:42 +01:00
xRangerDE
fbcf3935d0 feat: add drag & drop queue reordering and expandable queue item details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:42:35 +01:00
xRangerDE
76ecbc652d perf: parallel init with Promise.all, targeted DOM updates for download progress
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:26:19 +01:00
xRangerDE
0e6b219455 feat(merge-split): numbered selection instead of checkboxes, user-defined merge order
Replace checkboxes with numbered selectors (1, 2, 3...) that show the
merge order. Click order determines VOD sequence in the merged result.
Chronological auto-sort removed — user controls the order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 08:55:35 +01:00
xRangerDE
6c082a87ab fix(merge-split): fix premature 100% progress, add ffmpeg preflight, clean partial splits, use locale metaLabel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:12:50 +01:00
xRangerDE
ad4e540952 feat(merge-split): add queue checkboxes and merge-group selection UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 17:55:33 +01:00
xRangerDE
2631924ef5 chore: migrate repository to Codeberg, bump version to 4.2.0, update update logic 2026-03-01 20:23:21 +01:00