Skip to content

Add caption sidecar export option#669

Open
webadderall wants to merge 1 commit into
mainfrom
pr/export-caption-sidecars
Open

Add caption sidecar export option#669
webadderall wants to merge 1 commit into
mainfrom
pr/export-caption-sidecars

Conversation

@webadderall

@webadderall webadderall commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add an export modal checkbox for caption sidecar files when captions are present
  • carry the sidecar option through renderer export and retry-save flows
  • write matching .srt and .vtt files alongside exported MP4 files in Electron

Credit

Testing

  • npm exec vitest run src/components/video-editor/exportStartSettings.test.ts

Summary by CodeRabbit

Release Notes

  • New Features
    • Added the ability to export caption files alongside video exports, supporting both SRT and VTT subtitle formats.
    • A new toggle in export settings enables users to include captions with their MP4 exports.
    • Exported caption files are automatically generated with properly timed cues matching video playback.
    • Users can choose to export captions in SRT format, VTT format, or both simultaneously.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds optional caption sidecar (SRT/VTT) file generation when exporting MP4 videos. Type definitions span the Electron API, preload layer, and React types. The Electron IPC handlers validate, serialize, and write caption files alongside exported videos. Export settings thread the includeCaptionSidecar flag for MP4 only. VideoEditor computes caption payloads from filtered auto-captions, manages the toggle state, and forwards sidecars through all save paths.

Changes

Caption Sidecar Export Feature

Layer / File(s) Summary
Type contracts and data shapes
src/lib/exporter/types.ts, electron/electron-env.d.ts, electron/preload.ts
Type definitions across the stack define the CaptionSidecar shape (format: "srt" | "vtt" | "both" and cues array with startMs, endMs, text), and extend Electron API signatures (finalizeExportedVideo, saveExportedVideo, writeExportedVideoToPath) to accept optional sidecar data. ExportSettings gains includeCaptionSidecar?: boolean.
Electron IPC handlers and sidecar serialization
electron/ipc/register/export.ts
Introduces helper functions to parse unknown payload into validated {format, cues}, serialize cues into SRT/VTT timestamped content, and write sidecar files alongside video. All three IPC handlers (save-exported-video, write-exported-video-to-path, finalize-exported-video) now accept optional captionSidecar and write the files to the final destination after video save.
Export settings flag threading
src/components/video-editor/exportStartSettings.ts, src/components/video-editor/exportStartSettings.test.ts
resolveExportStartSettings threads includeCaptionSidecar input into ExportSettings, forcing it to false for non-MP4 formats. Tests verify MP4 preserves the flag and GIF overrides it.
ExportSettingsMenu caption toggle UI
src/components/video-editor/ExportSettingsMenu.tsx
Adds optional props (showCaptionSidecarOption, includeCaptionSidecar, onIncludeCaptionSidecarChange) and renders a conditional "Export captions file" Switch in the MP4 export section when captions are available.
VideoEditor state, computation, and export wiring
src/components/video-editor/VideoEditor.tsx
Adds includeCaptionSidecar state (default true), derives captionSidecarPayload by filtering autoCaptions for valid timing/text, and forwards the sidecar through all export paths: temp-file finalize, Blob save/write, and retry scenarios. Wires UI state to export settings menu and threads the flag through resolveExportStartSettings. PendingExportSave stores sidecar for retry consistency.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Export UI
  participant VE as VideoEditor
  participant Settings as resolveExportStartSettings
  participant Save as saveBlobExport
  participant Electron as electronAPI
  participant IPC as IPC Handler
  UI->>VE: toggle includeCaptionSidecar
  VE->>VE: compute captionSidecarPayload
  VE->>Settings: includeCaptionSidecar flag
  Settings->>Settings: MP4: keep, else: false
  VE->>Save: sidecarForThisExport
  Save->>Electron: finalizeExportedVideo + captionSidecar
  Electron->>IPC: invoke with captionSidecar payload
  IPC->>IPC: parse & serialize cues
  IPC->>IPC: write .srt/.vtt files
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The PR spans multiple layers (types, Electron handlers, preload, React state, UI) with moderate complexity. The Electron sidecar implementation (serialization, file writing) and VideoEditor integration (state computation, multi-path wiring) require careful review, but changes are cohesive and follow a clear contract. Export settings resolution and UI toggle are straightforward. Test updates are minor.

Possibly related PRs

  • webadderallorg/Recordly#567: Main PR extends resolveExportStartSettings module introduced in PR #567 by adding the includeCaptionSidecar flag threading and related test updates.
  • webadderallorg/Recordly#392: PR #392 enables inline editing of caption cues in autoCaptions; this PR consumes those edited cues directly in captionSidecarPayload computation for export.

Suggested labels

feature, video-export, captions

Poem

🐰 A rabbit hops through exports bright,
With caption sidecars burning right—
SRT, VTT, both in sight,
Video files bundled tight!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description covers the main changes (caption sidecar export, Electron file writing) but lacks several template sections: detailed motivation, explicit type of change selection, and comprehensive testing guide. Add sections for motivation (why captions should be exportable), explicitly select the Type of Change (likely 'New Feature'), and provide a more detailed testing guide beyond just the test command.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add caption sidecar export option' accurately summarizes the main feature added: exporting caption sidecar files alongside MP4 exports.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/export-caption-sidecars

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/register/export.ts`:
- Around line 989-990: The handler currently awaits writeCaptionSidecars(...)
after the video file is already persisted and, on sidecar failure, returns an
overall failure even though the video succeeded; change the flow in the
save-exported-video and write-exported-video-to-path handlers (and the code
paths invoking writeCaptionSidecars) so that the video write/move (the
fs.writeFile or video persistence logic) determines success first, and any
errors from writeCaptionSidecars are caught and handled separately: log the
sidecar error (with context) and return a partial-success/result that indicates
the video was saved while sidecar writing failed (or still return success for
the video write but include sidecar failure detail), rather than treating
sidecar failure as a full export failure. Ensure references to
writeCaptionSidecars, save-exported-video, and write-exported-video-to-path are
updated accordingly so retries won’t re-do already-persisted video writes.
- Around line 347-362: The writeCaptionSidecars function currently writes
sidecar files for any videoPath when payload is present; update it to enforce
MP4-only sidecar writes by checking the parsed extension (from videoPath) and
returning early unless parsed.ext === '.mp4' (or lowercased '.mp4') before
constructing basePath and calling fs.writeFile for serializeSrt/serializeVtt;
keep the existing payload.format branches intact so sidecars are only written
for MP4 outputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bcd5caac-c3c1-494a-97d5-2e84fb8f4c49

📥 Commits

Reviewing files that changed from the base of the PR and between 5785180 and 795cda2.

📒 Files selected for processing (8)
  • electron/electron-env.d.ts
  • electron/ipc/register/export.ts
  • electron/preload.ts
  • src/components/video-editor/ExportSettingsMenu.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/exportStartSettings.test.ts
  • src/components/video-editor/exportStartSettings.ts
  • src/lib/exporter/types.ts

Comment on lines +347 to +362
async function writeCaptionSidecars(videoPath: string, payload: CaptionSidecarPayload | null) {
if (!payload) {
return;
}

const parsed = path.parse(videoPath);
const basePath = path.join(parsed.dir, parsed.name);

if (payload.format === "srt" || payload.format === "both") {
await fs.writeFile(`${basePath}.srt`, serializeSrt(payload.cues), "utf8");
}

if (payload.format === "vtt" || payload.format === "both") {
await fs.writeFile(`${basePath}.vtt`, serializeVtt(payload.cues), "utf8");
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce MP4-only sidecar writes in the main process.

Line 355 currently writes sidecars whenever a payload is present, regardless of output extension. This allows .gif (or any other extension) exports to receive sidecars if an upstream caller sends the payload, which breaks the MP4-only contract.

Suggested fix
 async function writeCaptionSidecars(videoPath: string, payload: CaptionSidecarPayload | null) {
 	if (!payload) {
 		return;
 	}
+	if (path.extname(videoPath).toLowerCase() !== ".mp4") {
+		return;
+	}
 
 	const parsed = path.parse(videoPath);
 	const basePath = path.join(parsed.dir, parsed.name);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/register/export.ts` around lines 347 - 362, The
writeCaptionSidecars function currently writes sidecar files for any videoPath
when payload is present; update it to enforce MP4-only sidecar writes by
checking the parsed extension (from videoPath) and returning early unless
parsed.ext === '.mp4' (or lowercased '.mp4') before constructing basePath and
calling fs.writeFile for serializeSrt/serializeVtt; keep the existing
payload.format branches intact so sidecars are only written for MP4 outputs.

Comment on lines 989 to +990
await fs.writeFile(result.filePath, Buffer.from(videoData));
await writeCaptionSidecars(result.filePath, sidecarPayload);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t report full export failure after video persistence succeeds.

In these paths, writeCaptionSidecars(...) is awaited after the video file is already written/moved. If sidecar writing fails, the handler returns failure even though the video is already saved, causing partial-success misreporting and brittle retry behavior.

Suggested direction
- await moveExportedTempFile(tempPath, resolvedPath);
- await writeCaptionSidecars(resolvedPath, sidecarPayload);
- releaseOwnedExportPath(tempPath);
- approveUserPath(resolvedPath);
- return { success: true, path: resolvedPath, canceled: false, message: "Video exported successfully" };
+ await moveExportedTempFile(tempPath, resolvedPath);
+ releaseOwnedExportPath(tempPath);
+ approveUserPath(resolvedPath);
+
+ let sidecarError: string | undefined;
+ try {
+   await writeCaptionSidecars(resolvedPath, sidecarPayload);
+ } catch (error) {
+   sidecarError = error instanceof Error ? error.message : String(error);
+   console.warn("[export] Video saved but caption sidecar write failed:", sidecarError);
+ }
+
+ return {
+   success: true,
+   path: resolvedPath,
+   canceled: false,
+   message: sidecarError
+     ? "Video exported, but caption sidecar files could not be written."
+     : "Video exported successfully",
+   error: sidecarError,
+ };

Apply the same pattern to save-exported-video and write-exported-video-to-path so all three flows behave consistently.

Also applies to: 1031-1032, 1089-1130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/register/export.ts` around lines 989 - 990, The handler
currently awaits writeCaptionSidecars(...) after the video file is already
persisted and, on sidecar failure, returns an overall failure even though the
video succeeded; change the flow in the save-exported-video and
write-exported-video-to-path handlers (and the code paths invoking
writeCaptionSidecars) so that the video write/move (the fs.writeFile or video
persistence logic) determines success first, and any errors from
writeCaptionSidecars are caught and handled separately: log the sidecar error
(with context) and return a partial-success/result that indicates the video was
saved while sidecar writing failed (or still return success for the video write
but include sidecar failure detail), rather than treating sidecar failure as a
full export failure. Ensure references to writeCaptionSidecars,
save-exported-video, and write-exported-video-to-path are updated accordingly so
retries won’t re-do already-persisted video writes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant