Context
GIFs autoplay inline in GitHub issues, pull requests, READMEs, Notion and many chat tools. Today people export an MP4 and convert it with another tool.
Where to look
Sources/SnapcapCore/Render/VideoExporter.swift: MP4 export, with a .partial temp file and atomic replace, a progress callback, and ExportRecord.
Sources/SnapcapCore/Render/CompositionBuilder.swift: build(folder:project:purpose: .export) returns an AVMutableComposition and a videoComposition that already include the camera bubble and respect the trim. Keep sourceAssets alive while reading.
Sources/Snapcap/Views/EditorView.swift: ExportPanel (the buttons) and EditorModel.export(copyWhenDone:).
Sources/snapcap-cli/SnapcapCLI.swift: the export command. Add new value flags to Arguments.valued.
Tests/SnapcapCoreTests/ExportTests.swift: fixture-based export tests to copy.
Proposal
- Add a
GIFExporter in Sources/SnapcapCore/Render/. Read frames at a fixed rate with AVAssetReaderVideoCompositionOutput (fast for many frames), or with AVAssetImageGenerator (set videoComposition, zero tolerances and maximumSize).
- Write the GIF with ImageIO:
CGImageDestinationCreateWithURL(url, UTType.gif.identifier as CFString, frameCount, nil), file properties with kCGImagePropertyGIFLoopCount: 0, and per-frame kCGImagePropertyGIFDelayTime (plus kCGImagePropertyGIFUnclampedDelayTime).
- Default to 12 fps at 960 px wide (keeping the aspect ratio, with even dimensions), with 10 or 15 fps and 640, 960 or 1280 px options. Warn when the trimmed duration is over about 30 seconds, since GIFs get large fast.
- Write the output next to the MP4 as
<name>.gif, using the same partial-then-replace approach. GIFs have no audio.
Acceptance criteria
Context
GIFs autoplay inline in GitHub issues, pull requests, READMEs, Notion and many chat tools. Today people export an MP4 and convert it with another tool.
Where to look
Sources/SnapcapCore/Render/VideoExporter.swift: MP4 export, with a.partialtemp file and atomic replace, a progress callback, andExportRecord.Sources/SnapcapCore/Render/CompositionBuilder.swift:build(folder:project:purpose: .export)returns anAVMutableCompositionand avideoCompositionthat already include the camera bubble and respect the trim. KeepsourceAssetsalive while reading.Sources/Snapcap/Views/EditorView.swift:ExportPanel(the buttons) andEditorModel.export(copyWhenDone:).Sources/snapcap-cli/SnapcapCLI.swift: theexportcommand. Add new value flags toArguments.valued.Tests/SnapcapCoreTests/ExportTests.swift: fixture-based export tests to copy.Proposal
GIFExporterinSources/SnapcapCore/Render/. Read frames at a fixed rate withAVAssetReaderVideoCompositionOutput(fast for many frames), or withAVAssetImageGenerator(setvideoComposition, zero tolerances andmaximumSize).CGImageDestinationCreateWithURL(url, UTType.gif.identifier as CFString, frameCount, nil), file properties withkCGImagePropertyGIFLoopCount: 0, and per-framekCGImagePropertyGIFDelayTime(pluskCGImagePropertyGIFUnclampedDelayTime).<name>.gif, using the same partial-then-replace approach. GIFs have no audio.Acceptance criteria
snapcap-cli export <session> --format gif [--fps 12] [--width 960]works, and the default format stays MP4.CGImageSourceGetCountand the frame size.