video: generate a recording's telemetry subtitles when it has none - #3001
video: generate a recording's telemetry subtitles when it has none#3001rafaellehmkuhl wants to merge 2 commits into
Conversation
📝 MINOR SUGGESTIONS (Automated PR Review — round 1)
When you download the raw pieces of a recording from the video library in the browser version, Cockpit used to look for the telemetry subtitle file and, if the recording never got one, tell you it failed and hand you a download with no telemetry. This PR makes both the browser and the desktop version build that subtitle file on the spot from the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, and otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. The desktop version already had that fallback ladder; the change lifts it into a small shared module with unit tests and deletes the desktop-only copy. What still needs attention
Change map — what was established before judgingClaims (from the commit body; the PR body is the same text)
Failure site — Entry points
Both call sites already log the interaction ( Invariants
1. Correctness & Implementation Bugs — 2 findings1.1 — On the base ref, tier 1 wrapped the storage listing in its own Fix: put the lookup inside a Consequence: if Cockpit momentarily cannot list its stored video files, the desktop version stops producing telemetry subtitles for a processed recording instead of rebuilding them. 1.2 — const assContent = datalogger.toAssOverlay(telemetryLog, candidate.width, candidate.height, startEpoch)
const blob = new Blob([assContent], { type: 'text/plain' })
await videoStore.videoStorage.setItem(subtitlesFileName, blob)
return { blob, filename: subtitlesFileName }
Fix: catch around the Consequence: when the browser's storage is full, the user's chunk download loses its telemetry subtitles even though Cockpit had already built them. 2. Persistence & User Data — inventory, no findings
Judged: both are media/log artifacts rather than 7. Code Quality & Style — 1 findingComplexity: the report attributes one trigger to this PR — 7.1 — The PR extracts "which window and which resolution do we render this recording's overlay for" into const telemetryLog = await datalogger.generateLog(recordingData.dateStart!, recordingData.dateFinish!)
const assLog = datalogger.toAssOverlay(telemetryLog, recordingData.vWidth!, recordingData.vHeight!, …)
Consequence: a recording that never stored its video size still gets a subtitle file declaring an invalid size, so players scale the telemetry overlay wrongly or refuse it — and the fallback this PR introduces does not reach that case. 11. Nitpicks / Optional — 1 finding11.1 —
Sections with nothing to report (7)3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (read 5. Performance — ✅ (both changed functions trace only to video-library button clicks, never to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 8. Commit Hygiene — ✅ (one commit, 9. Tests — ✅ ( 10. Documentation — ✅ (the change narrows a Lite/Standalone gap instead of adding a Lite-only limitation, so the README parity table needs no row; JSDoc is added on both new public functions and on the new interface) Generated by Claude. This is advisory; a human reviewer must still approve. |
c8b3d72 to
a915b43
Compare
Review follow-up — round 1Done
Won't change (with reasoning)
|
|
/review |
📝 MINOR SUGGESTIONS (Automated PR Review — round 2)
When you download the raw pieces of a recording from the video library, Cockpit used to look for the telemetry subtitle file and, if the recording never got one, tell you it failed and hand you a download with no telemetry. This PR makes both the browser and the desktop version build that subtitle file on the spot from the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, and otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. A second commit added this round points the place recordings normally get their subtitles at the same shared helper, so a recording that never stored its video size no longer gets a subtitle file declaring an invalid one. What still needs attention
Since round 1 — 3 closed, 1 disputed, comparing c8b3d72 → a915b43The range could not be used. No 1.1 — ✅ Addressed. The finding asked for the existing-file lookup to sit inside a 1.2 — ✅ Addressed. The finding asked for a 7.1 — ✅ Addressed. The finding asked for 11.1 — 💬 Disputed, still open. rafaellehmkuhl declined it in the round-1 follow-up comment (link), arguing in plain terms that the download path needs the file's bytes so a file descriptor is the contract that fits the callers, and that returning only a key would make the Lite path re-read from storage an overlay it had just built in memory. Verified against the diff: Discussion since round 1. The only other new comment is the bare Change map — what was established before judgingClaims (two commits now; the PR body is the first commit's text)
Failure sites — two, both in the diff. The Lite download gap lives at Entry points
All three call sites already log the interaction ( Invariants
2. Persistence & User Data — inventory, no findings
Judged: both are media/log artifacts rather than 11. Nitpicks / Optional — 1 finding11.1 —
The author declined it, reasoning that the download path needs the bytes so a file descriptor is the contract that fits both callers, and that returning a key would only move the read to IndexedDB on Lite. That reasoning is sound as a trade; per the re-review rules an argument leaves the finding open rather than closing it, and at Consequence: on the desktop version, every processed recording that already has subtitles reads the whole subtitle file over IPC before copying it, which is work nobody needs. Sections with nothing to report (9)1. Correctness & Implementation Bugs — ✅ (re-read 3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (read 5. Performance — ✅ (all three changed functions trace to a button click or to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 7. Code Quality & Style — ✅ (the complexity report attributes one trigger to this PR — 8. Commit Hygiene — ✅ (two commits, 9. Tests — ✅ ( 10. Documentation — ✅ (the change narrows a Lite/Standalone gap instead of adding a Lite-only limitation, so the README parity table needs no row; JSDoc is present on both new public functions and on the new interface, and the store change needs none since Generated by Claude. This is advisory; a human reviewer must still approve. |
🙋 Decision needed — 11.1Standalone path reads the .ass blob over IPC only to use its filename The author's argument: Both callers are served by returning the whole file descriptor because the download path needs the bytes, and narrowing the resolver to a key would only trade an IPC read on Standalone for an IndexedDB re-read on Lite of an overlay it had just built in memory, plus a branchier signature. How to vote on this disputeReact to this comment and the next
The two reactions already here were left by the bot so that either answer is one click, and neither of them counts. Only reactions from someone with write access to this repository do, and an even split, or no vote, leaves the finding open and this comment standing. Move your reaction to change your mind while the vote is open — once a |
Review follow-up — round 2Nothing actionable this round: 1.1, 1.2 and 7.1 were confirmed addressed at One correction to my round-1 reasoning, for the record: the |
|
/review |
1 similar comment
|
/review |
📝 MINOR SUGGESTIONS (Automated PR Review — round 3)
When you download the raw pieces of a recording from the video library, Cockpit used to look for the telemetry subtitle file and, if the recording never got one, tell you it failed and hand you a download with no telemetry. This PR makes both the browser and the desktop version build that subtitle file on the spot from the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, and otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. A second commit points the place recordings normally get their subtitles at the same shared helper, so a recording that never stored its video size no longer gets a subtitle file declaring an invalid one. What still needs attention
Since round 2 — 1 settled by vote, 1 new finding, no new commits (head unchanged at a915b43)The range is empty, and legitimately so. 11.1 — ☑️ Resolved by vote. rafaellehmkuhl accepted the author's argument on the decision comment (link); the tally was gated against the carried ledger, so it answers the argument the ledger was actually showing. The resolver keeps returning the whole file descriptor, and the finding leaves the open set. No code changed for it, which is exactly what the vote is for. No 1.1, 1.2 and 7.1 stay closed. They were confirmed addressed at 1.3 — new, Discussion since round 2. rafaellehmkuhl left a follow-up (link) stating that nothing was pushed and that the re-review was triggered only to apply the vote — both checked and true ( Change map — what was established before judgingClaims (the PR body is the first commit's text; the second commit's message carries its own)
Failure sites — two, both in the diff. The Lite download gap lives at Entry points
All three call sites already log the interaction ( Invariants
1. Correctness & Implementation Bugs — 1 finding1.3 —
On the base ref this surfaced: tier 3's The asymmetry is the point: for the download the store write is a cache, for the processing path it is the delivery mechanism. Smallest fix that keeps 1.2 intact is to let the caller know which happened — have the resolver report whether the blob was persisted (or have Consequence: on the desktop version, a recording whose rebuilt subtitle file cannot be written to disk is reported as processed successfully and silently ends up with no telemetry overlay. 2. Persistence & User Data — inventory, no findings
Judged: both are media/log artifacts rather than Sections with nothing to report (9)3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (re-read 5. Performance — ✅ (all four changed functions trace to a button click or to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 7. Code Quality & Style — ✅ (the complexity report attributes one trigger to this PR — 8. Commit Hygiene — ✅ (the same two commits as round 2, 9. Tests — ✅ ( 10. Documentation — ✅ (the change narrows a Lite/Standalone gap instead of adding a Lite-only limitation, so the README parity table needs no row; JSDoc is present on both new public functions and on the new interface, and 11. Nitpicks / Optional — ✅ (the one nit ever raised here — the Standalone path reading the Generated by Claude. This is advisory; a human reviewer must still approve. |
a915b43 to
9d90583
Compare
Review follow-up — round 3Done
|
|
/review |
📝 MINOR SUGGESTIONS (Automated PR Review — round 4)
When you download the raw pieces of a recording from the video library, Cockpit used to look for the telemetry subtitle file and, when the recording never got one, tell you it failed and hand you an archive with no telemetry. This PR makes the browser download and the desktop chunk-processing flow build that subtitle file on the spot out of the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. A second commit points the place recordings normally get their subtitles at that same shared helper, so a recording that never stored its video size no longer produces a subtitle file declaring an invalid one. This push adds the missing failure signal on the desktop path: when the rebuilt file cannot be saved, processing now reports it instead of claiming success. What still needs attention
Since round 3 — 1 addressed, 1 new finding, comparing a915b43 → 9d90583 (range rebuilt from pr.diff after a rebase)The range is not usable as delivered, and I did not use it. 1.3 — ✅ Addressed. The finding asked for two things: that the resolver report whether the generated overlay actually reached the video storage, and that the existing 1.4 — new, No 1.1, 1.2, 7.1 and 11.1 stay closed and are not re-judged; they are carried in the ledger with the statuses they were closed under. Discussion since round 3. rafaellehmkuhl posted a follow-up (link) describing the 1.3 fix. Every claim in it checks out against the code rather than being taken on trust: the resolver does report storage success and Change map — what was established before judgingClaims (the PR body is the first commit's text; the second commit carries its own)
Failure sites — three, two of them in the diff. The Lite download gap lives at base Entry points
All three call sites already log the interaction ( Invariants
1. Correctness & Implementation Bugs — 1 finding1.4 —
Two things make this worth closing rather than leaving. The capability is already in the renderer: the same button on the same row in the same list behaves differently per build, and the PR's own framing is that the overlay should be recoverable from the telemetry the app still holds. And the fix is small, because the main-process lookup reads the very folder the resolver writes to — if (isElectron()) {
try {
await resolveTelemetryOverlayFile(group)
} catch (error) {
openSnackbar({ message: `Creating the ZIP without a telemetry subtitle file. ${error}`, variant: 'warning' })
}
const zipFilePath = await window.electronAPI?.createVideoChunksZip(group.hash)
…No main-process change, no new IPC, and Consequence: on the desktop version, downloading a recording's raw pieces still produces an archive with no telemetry subtitles and reports success, even though Cockpit could rebuild them the way the browser version now does. 2. Persistence & User Data — inventory, no findings
Judged: the first two are media/log artifacts rather than Sections with nothing to report (9)3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (re-read 5. Performance — ✅ (all four changed functions trace to a button click or to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 7. Code Quality & Style — ✅ (the complexity report, measured at head 8. Commit Hygiene — ✅ (the branch was rebased since round 3 — same two subjects, new SHAs 9. Tests — ✅ ( 10. Documentation — ✅ (the change narrows a Lite/Standalone gap rather than adding a Lite-only limitation, so the README parity table needs no row — the gap 1.4 describes runs the other way and is a wiring gap, not a platform limitation to document; JSDoc is present on both new public functions, on the new interface and on the interface added this round, and 11. Nitpicks / Optional — ✅ (the one nit ever raised here — the Standalone path reading the Generated by Claude. This is advisory; a human reviewer must still approve. |
9d90583 to
ad37f59
Compare
Review follow-up — round 4Done
|
|
/review |
📝 MINOR SUGGESTIONS (Automated PR Review — round 5)
When you download the raw pieces of a recording from the video library, Cockpit used to look for the telemetry subtitle file and, when the recording never got one, tell you it failed and hand you an archive with no telemetry. This PR makes both the browser download and the desktop flows rebuild that subtitle on the spot out of the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. A second commit points the place recordings normally get their subtitles at the same shared helper, so a recording that never stored its video size no longer produces a subtitle file declaring an invalid one. This push closes the last gap between the two builds: the desktop download now rebuilds the subtitle before asking for the archive, and warns when there is no telemetry to rebuild it from. What still needs attention
Since round 4 — 1 addressed, 1 new finding, comparing 9d90583 → ad37f59 (range rebuilt from pr.diff after a rebase)The range is not usable as delivered, and I did not use it. 1.4 — ✅ Addressed. The finding asked for two things: that the Electron half of 1.5 — new, No 1.1, 1.2, 7.1, 11.1 and 1.3 stay closed and are not re-judged; they are carried in the ledger with the statuses they were closed under. Discussion since round 4. rafaellehmkuhl posted a follow-up (link) describing the 1.4 fix. Its claims check out against the code rather than being taken on trust: the Electron branch does resolve the overlay before the IPC call and does warn when it cannot, and the premise they say they verified — that the ZIP builder reads the folder Change map — what was established before judgingClaims (the PR body is the first commit's text; the second commit carries its own)
Failure sites — three, all now answered in the renderer. The Lite download gap lives at base Entry points
All three call sites already log the interaction ( Invariants
1. Correctness & Implementation Bugs — 1 finding1.5 — The new priming block is: if (isElectron()) {
// The main process picks the overlay up from the videos folder, so it has to be there already.
try {
await resolveTelemetryOverlayFile(group)
} catch (error) {
const message = `Creating the ZIP without a telemetry subtitle file. ${error}`
openSnackbar({ message, variant: 'warning' })
}
const zipFilePath = await window.electronAPI?.createVideoChunksZip(group.hash)The comment above it states the invariant precisely — the main process picks the overlay up from the videos folder — and then the code does not check that it got there. This is the same defect try {
const { stored } = await resolveTelemetryOverlayFile(group)
if (!stored) throw new Error('Could not store the telemetry overlay next to the recording.')
} catch (error) {
…
}
Consequence: on the desktop version, if Cockpit cannot save the rebuilt subtitle file, the downloaded archive silently has no telemetry subtitles while the message says it was created successfully. 2. Persistence & User Data — inventory, no findings
Judged: the first two are media/log artifacts rather than Sections with nothing to report (9)3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (re-read 5. Performance — ✅ (all four changed functions trace to a button click or to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 7. Code Quality & Style — ✅ (the complexity report, measured at head 8. Commit Hygiene — ✅ (the branch was rebased again since round 4 — same two subjects, new SHAs 9. Tests — ✅ ( 10. Documentation — ✅ (the change closes a Lite/Standalone gap rather than adding a Lite-only limitation, and this push closes the last of it, so the README parity table needs no row; JSDoc is present on both new public functions and on both new interfaces, 11. Nitpicks / Optional — ✅ (the one nit ever raised here — the Standalone path reading the Generated by Claude. This is advisory; a human reviewer must still approve. |
Downloading a chunk group on Lite only looked an .ass file up and gave up when the recording never got one, which is the state older versions left behind when the video processing died before writing it. The telemetry those subtitles are built from is still in the browser, so there was nothing to recover it with - Standalone regenerates the overlay while processing chunks, but reads its own telemetry, not the browser's. Move the fallback the Standalone path had - the recording metadata first, the chunk timestamps second - into the candidate windows a recording's overlay can be generated for, and resolve the overlay the same way on both paths, storing what it generates next to the recording so the file also survives the download.
The overlay written when a recording finishes asserted the stored video size away, so a recording that never got one wrote "PlayResX: undefined" into the subtitle header and players scale the telemetry wrongly or refuse the file. Read the window and the resolution from the same candidate helper the chunk recovery uses, which already falls back to a standard resolution, so the recording-time and the recovery paths agree on what they render for.
ad37f59 to
52bb993
Compare
Review follow-up — round 5Done
|
|
/review |
✅ READY TO MERGE (Automated PR Review — round 6)
When you download the raw pieces of a recording from the video library, Cockpit used to look for the telemetry subtitle file and, when the recording never got one, tell you it failed and hand you an archive with no telemetry. This PR makes both the browser download and the desktop flows rebuild that subtitle on the spot out of the telemetry Cockpit already logged — using the recording's own start and end time when it still knows them, otherwise reconstructing the time span from the timestamps of the recorded pieces — and saves the result next to the recording so it is there next time too. A second commit points the place recordings normally get their subtitles at the same shared helper, so a recording that never stored its video size no longer produces a subtitle file declaring an invalid one. This push closes the last gap: the desktop download now checks that the rebuilt subtitle actually reached disk and warns when it did not, instead of reporting success over an archive without it. What still needs attentionNothing is open. Every finding raised over the six rounds is closed — six by code changes in the PR, one by a maintainer vote. Since round 5 — 1 addressed, 0 new findings, comparing ad37f59 → 52bb993 (range rebuilt from pr.diff after another rebase)The range is not usable as delivered, and I did not use it. 1.5 — ✅ Addressed. The finding asked for one thing: that the Electron priming call stop discarding the const { filename, stored } = await resolveTelemetryOverlayFile(group)
if (!stored) {
throw new Error(`Could not store the telemetry overlay as '${filename}'.`)
}The throw is inside the existing No 1.1, 1.2, 7.1, 11.1, 1.3 and 1.4 stay closed and are not re-judged; they are carried in the ledger with the statuses they were closed under. Discussion since round 5. rafaellehmkuhl posted a follow-up (link) describing the 1.5 fix. Its claims check out against the code rather than being taken on trust: the priming block does read Change map — what was established before judgingClaims (the PR body is the first commit's text; the second commit carries its own)
Failure sites — three, all now answered in the renderer. The Lite download gap lives at base Entry points
All three call sites already log the interaction ( Invariants
2. Persistence & User Data — inventory, no findings
Judged: the first two are media/log artifacts rather than Sections with nothing to report (10)1. Correctness & Implementation Bugs — ✅ (the last open defect, 1.5, is closed by this push and re-verified above against the three consumers of the resolver's return; re-walked the whole diff independently rather than only the delta: the candidate loop's fall-through is sound because 3. AGENTS.md Adherence — ✅ (no new dependency and 4. Security — ✅ (re-read 5. Performance — ✅ (all four changed functions trace to a button click or to 6. UI / UX — ✅ (no dialog, control, overlay or icon is added or restyled, so the dialog-anatomy, 7. Code Quality & Style — ✅ (the complexity report, measured at head 8. Commit Hygiene — ✅ (the branch was rebased again since round 5 — same two subjects, new SHAs 9. Tests — ✅ ( 10. Documentation — ✅ (the change closes a Lite/Standalone gap rather than adding a Lite-only limitation, and this push closes the last of it, so the README parity table needs no row; JSDoc is present on both new public functions and on both new interfaces, 11. Nitpicks / Optional — ✅ (the one nit ever raised here — the Standalone path reading the Generated by Claude. This is advisory; a human reviewer must still approve. |
Summary
A recording whose
.asssubtitle file was never written could not get it back from the application, even with the telemetry it is built from still sitting in the browser. Downloading a chunk group on Cockpit Lite only looked the file up and reportedFailed to find .ass telemetry file for the recording., while the Standalone chunk processing already knew how to regenerate the overlay — from the recording metadata, and failing that from the chunk timestamps — but reads its own telemetry storage, not the browser's, so exporting to Standalone does not recover a Lite recording either.This moves that fallback into the candidate windows an overlay can be generated for (
src/libs/video-telemetry.ts), and resolves the overlay the same way on both paths, generating it when the recording has none and storing it next to the recording so it also survives the download. The Standalone path keeps its behavior and loses its duplicate copy of the fallback, which is where the net deletion comes from.Test plan
.assfrom thecockpit-video-recovery-dbIndexedDB store, leaving the chunks in place..ass, and the subtitle should play over the video once processed..assdoes exist and confirm it is the stored file that ships, not a fresh one..ass, and confirm the overlay lands next to the MP4 in both cases.Checks
1280x720, 10 s of logged telemetry, subtitle removed. Before:cockpit-video-recovery-dbkeys[]and the red "Failed to find .ass telemetry file" snackbar. After: keyCockpit (...) #a1b2c3d4.ass, 7854 bytes,PlayResX: 1280 PlayResY: 720, 90 dialogue lines carrying the logged values, and no error snackbar.yarn lint:fix,yarn typecheckand the newsrc/tests/libs/video-telemetry.test.ts(5 cases) are clean.src/tests/libs/cosmos.test.tsandsrc/tests/libs/connection/connection.test.tsfail onmastertoo, unrelated to this change.Closes #3000