Investigation · Measured · Decision ready
Potato, Social, and Max cover the useful export range without exposing encoder math
9.650 MiB is the Potato result from the exact 16-page Pearl strategy that prompted this work: 54.500 seconds at 1920×1080 / 30 fps. The same field sample produced 15.907 MiB in Social and 17.517 MiB in Max, validating 10 MiB, 20 MiB, and maximum quality as recognizable outcomes without making either size target a rejection rule. Potato stays at 1080p while detail still wins and drops to 720p only when duration forces the request to the 250 kbps floor. Social remains the default; any successful Potato or Social encode is saved even when unusual content lands above its target.
Decision in one screen
Recommended decision: ship Potato, Social, and Max, with Social selected by default. Potato gets character in its name and copy; codec, bitrate, retry mechanics, and the adaptive resolution threshold stay out of the user’s way.
Implemented export dialog · three recognizable outcomes
The advanced encoder vocabulary stays in the implementation. The person exporting a strategy only chooses the outcome they need.
Source: implemented in PR 126 against lib/widgets/dialogs/export_video_dialog.dart and Icarus DESIGN.md.
The constraint is duration, not a magic codec setting
Discord’s official FAQ still documents a 10 MB base maximum while saying it is experimenting with other upload limits and that selected users may see larger limits. Icarus’s product direction is to anticipate the move toward 20 MB, but treat that number as a best-effort optimization rather than a promise. A user with a smaller limit may need another sharing path; an unusually complex or long strategy must still export successfully. See Discord’s upload reference and File Attachments FAQ.
# File-size arithmetic · no audio track
payload bytes ≈ bitrate bits/s × duration s ÷ 8
7,000 kbps × 22 s ÷ 8 = 19,250,000 bytes = 18.36 MiB
18.5 MiB × 8 ÷ 22 s = 7,054 kbps total budget
7,054 kbps − 64 kbps = 6,990 kbps first video request
The 18.5 MiB working budget leaves headroom under the 20 MiB target. The 64 kbps reserve covers MP4 muxing and fixed encoder/header behavior; the finished-byte check decides whether one lower-bitrate retry is useful.
| Included pages | Planned duration | 18.5 MiB total budget | First video request | Reading |
|---|---|---|---|---|
| 10 | 33.900 s | 4,578 kbps | 4,514 kbps | Comfortable 1080p / 30 fps |
| 20 | 68.233 s | 2,274 kbps | 2,210 kbps | Comfortable 1080p / 30 fps |
| 30 | 102.567 s | 1,513 kbps | 1,449 kbps | Sweet spot for the reported use case |
| 40 | 136.900 s | 1,134 kbps | 1,070 kbps | Still comfortable for strategy content |
| 60 | 205.567 s | 755 kbps | 691 kbps | Near the measured visual candidate |
Source: 30 fps schedule derived from VideoExporter.plannedDurationSeconds · 3.000 s step duration · 13/30 s between included pages · 18.5 MiB working target.
The previous exporter asked for quality and hoped the bytes followed
The path before PR 126 was fixed at 1920×1080, 60 fps. On Windows it asked Media Foundation H.264 for -b:v 8M, but never calculated a budget from duration or checked the final size. If H.264 failed, it fell through to MPEG-4 Part 2 with -q:v 3, which is quality-targeted and even less predictable in bytes. The implemented path keeps that behavior only for Max; Potato and Social now derive a bitrate from duration and inspect the finished bytes.
Encoding control · current path versus Discord-size path
Source: previous behavior at 4cdcbcf; implemented path in lib/services/video_export/ffmpeg_video_encoder.dart and lib/services/video_export/video_exporter.dart on PR 126.
The exact field strategy lands Potato below 10 MiB
10,118,396 bytes is the finished Potato file for size-strat.ica. The source is a valid current-format archive with 16 attack-side Pearl pages, agents on every page, and a mix of abilities, text, and utility. At Icarus’s normal 3-second step duration, the 30 fps presets schedule 54.500 seconds; Max schedules 54.250 seconds because its 60 fps transition rounding is slightly different.
Exact field sample · finished MP4 size · 16 pages
Source: release-mode Icarus desktop export of size-strat.ica · SHA-256 908c2dd64e9cacaba50a3fcc15cbd32ee2dc4e734d3bae41e5de267027e91e90 · FFmpeg / ffprobe 8.1.2 · 3.000 s step duration.
| Preset | Video | Duration | Finished bytes | Outcome |
|---|---|---|---|---|
| Potato | H.264 · 1920×1080 · 30 fps | 54.500 s | 10,118,396 B · 9.650 MiB | 0.350 MiB below its best-effort target |
| Social | H.264 · 1920×1080 · 30 fps | 54.500 s | 16,680,180 B · 15.907 MiB | 4.093 MiB below its best-effort target |
| Max | H.264 · 1920×1080 · 60 fps | 54.250 s | 18,367,663 B · 17.517 MiB | No size target; highest motion fidelity |
Source: ffprobe -show_entries stream=codec_name,width,height,r_frame_rate,nb_frames -show_entries format=duration,size. Representative page 9 frames were inspected for all presets; the strategy source and MP4s remain local and are not PR artifacts.
Reading: Potato is worth shipping as a real mode, not a novelty label. On the motivating strategy it preserves 1080p detail and uses 96.497% of its byte envelope. Social buys 6.258 MiB over Potato for more encoding headroom, while Max adds 1.609 MiB over Social for 60 fps.
The controlled experiment favors 1080p / 30 fps over 720p or H.265-first
I built a 22.000-second reference from Icarus’s best-test-preview.png: 1920×1080, 60 fps, a static tactical map plus intermittent moving widgets and drawing growth during 420 ms transition windows. Media Foundation H.264 was tested through the same encoder family Icarus ships. VMAF is directional here—its model was built for natural video, not tactical diagrams—so exact scores are less important than the controlled comparison.
Output size · 22.000 s strategy-motion sample
Source: FFmpeg 8.1.2 on Windows · Media Foundation H.264, MPEG-4 Part 2, and local libx265 comparison · full command matrix in Appendix. Field row: exact 16-page strategy exported through Icarus in release mode.
Detail retention at a 750 kbps request · VMAF mean · axis truncated to 85–100
Source: libvmaf over the same 22.000 s reference · 30 fps variants compared against a 30 fps reference · 720p upscaled with Lanczos before scoring.
| Variant | Output | Actual average | VMAF mean | Verdict |
|---|---|---|---|---|
| H.264 MF · 8M · 1080p/60 | 3.943 MiB | 1,503.521 kbps | 96.646 | Current path; spends frames unnecessarily |
| H.264 MF · 750k · 1080p/60 | 2.213 MiB | 843.884 kbps | 92.875 | Transition motion consumes the budget |
| H.264 MF · 750k · 1080p/30 | 2.139 MiB | 815.487 kbps | 96.296 | Best tested low-bitrate balance recommended |
| H.264 MF · 750k · 720p/30 | 1.719 MiB | 655.537 kbps | 91.104 | Small labels pay for the downscale |
| H.264 MF · 450k · 1080p/30 | 1.334 MiB | 508.730 kbps | 93.524 | Measured floor remains usable |
| MPEG-4 · q3 · 1080p/60 | 18.278 MiB | 6,969.429 kbps | 96.769 | Too unpredictable for the Social target |
| libx265 · 3,400k · 1080p/60 | 2.415 MiB | 920.784 kbps | 97.090 | Theoretical only; not the shippable encoder |
| Requested bitrate | 1080p / 30 | 720p / 30 | Decision |
|---|---|---|---|
| 450 kbps | 1.340 MiB · 92.025 | 1.276 MiB · 90.030 | Keep 1080p |
| 300 kbps | 0.829 MiB · 90.548 | 0.867 MiB · 86.445 | Keep 1080p |
| 250 kbps | 0.690 MiB · 80.169 | 0.692 MiB · 84.163 | Drop to 720p |
Source: controlled 22.000 s tactical-motion benchmark · each cell is encoded size then VMAF · 720p candidates upscaled with Lanczos for scoring.
Potato earns the name without becoming a blunt 720p switch: it aims for 10 MiB, keeps the 1080p detail that wins at 300 kbps and above, and changes the encoded output to 720p only at the 250 kbps floor. With the 9.25 MiB working budget and 64 kbps overhead reserve, that threshold begins around 247 seconds.
H.265 saves bytes but loses the reliability contest
Discord accepts HEVC/H.265 MP4 uploads, but that does not make HEVC the safest file to hand teammates. MDN calls H.264 the pragmatic maximum-compatibility choice and says HEVC has significant support gaps outside Apple platforms. Microsoft documents HEVC playback in Edge as conditional on a valid HEVC Video Extension. Sources: MDN codec selection and Microsoft’s Edge playback guide.
The local result was sharper: FFmpeg advertised hevc_mf, but the first submitted frame failed with 0x80004005 and produced an empty MP4. Microsoft documents the Media Foundation HEVC encoder from Windows 10 onward, but availability on paper is not proof that the transform works on the user’s machine. The software libx265 experiment shows compression potential, but that encoder is not part of Icarus’s pinned LGPL runtime and cannot justify a default path. See Microsoft’s Media Foundation HEVC encoder reference.
Conclusion: H.265 can return later as an explicitly compatibility-limited option after device probing, license review, and a real playback matrix. It should not sit between a user and the teammate who needs to watch the strategy now.
The implementation is a small policy layer over the existing safe write path
- Add the serialization-free export policy values
potato,social, andmax. This is not library data and requires no Hive model migration. - For Potato and Social, render transitions at 30 fps. A 30-page export falls from 755 rendered images to 407, a 46.1% reduction; the 420 ms transition is represented by 13 images.
- Calculate the first H.264 request from the preset’s working budget: 9.25 MiB for Potato or 18.5 MiB for Social, minus 64 kbps reserved overhead, clamped from 250 kbps to 8 Mbps.
- Keep Potato at 1080p unless its initial request reaches 250 kbps; at that floor, add one Lanczos downscale to 1280×720 in the FFmpeg filter.
- Invoke
h264_mfwith explicit CBR and archive intent. Keepyuv420pand+faststartfor compatibility. - Encode to
.icarus-partial.mp4. Above 10 MiB or 20 MiB, scale bitrate toward the preset’s retry budget and retry once when a lower bitrate is possible. After that correction, replace the destination with any successful result even if it remains oversized. - Hydrate the isolated screenshot providers before mounting each offscreen frame. Screenshot text uses the existing read-only presentation so debug and release builds render the same strategy without provider writes during
build(). - If H.264 is unavailable in a size-targeted preset, fail clearly without saving. The existing
mpeg4 -q:v 3fallback remains acceptable for Max, but cannot follow a file-size target predictably.
# Shared size-target policy · Potato shown
workingTargetBytes = 9.25 × 1024 × 1024
targetBytes = 10 × 1024 × 1024
totalBudget = floor(workingTargetBytes × 8 ÷ plannedSeconds)
videoBitrate = totalBudget − 64,000
scale = videoBitrate == 250,000 ? "scale=1280:720:flags=lanczos," : ""
ffmpeg ... -vf ${scale}fps=30,format=yuv420p \
-c:v h264_mf -rate_control cbr -scenario archive \
-quality 80 -b:v ${videoBitrate} -movflags +faststart partial.mp4
if partialBytes > targetBytes and a lower bitrate is possible:
retryBitrate = videoBitrate × (9.5 MiB ÷ partialBytes)
encode once more
replace destination with the successful partial, regardless of final size
The destination-safety property remains: Icarus writes beside the destination and renames only after success. File size can trigger one corrective retry, but never turns a successful Potato or Social encode into a rejection.
| File | Change | Verification |
|---|---|---|
export_video_dialog.dart | Add the three-choice quality control and duration-aware summary | Copy tests plus real Windows dialog inspection |
video_exporter.dart | Accept output fps; derive transition frames and duration from it | 30/60 fps timing tests; 30-page frame-count test |
ffmpeg_video_encoder.dart | Share size policy; CBR args; adaptive scale; verify and retry partial | Filter and policy tests; two encoder smokes; one complete UI export |
screenshot_view.dart | Hydrate isolated capture providers before mounting instead of writing during build | Regression widget test plus clean debug field export |
text_widget.dart | Use the read-only text presentation while capturing screenshots | Page-update resilience test; exact field frame inspection |
video_export_timing_test.dart | Cover profile-dependent schedules | 420 ms rounding is explicit at both frame rates |
docs/adr/0005-… | Supersede ADR 0003’s fixed-quality decision | Record all three outcomes, adaptive threshold, best-effort semantics |
Source: repository inspection plus implementation and review on PR 126.
Acceptance should be stated in user outcomes
- A Potato export aims for 10 MiB and a Social export aims for 20 MiB; neither rejects a successful encode solely because the result is larger.
- A 30-page strategy at 3-second step duration exports at 1080p / 30 fps with bitrate derived from its planned duration.
- The dialog calls the smallest preset Potato and says Aims for 10 MB. Tiny file, full strat.; it does not ask the user to understand codecs or kbps.
- Potato reports and outputs 720p only when the duration-derived initial bitrate reaches the 250 kbps floor; otherwise it remains 1080p.
- At 30 fps, page transitions end on the destination page and stay within one output-frame interval of 420 ms.
- Cancellation or any failed/retried encode leaves an existing destination untouched and removes the partial file best-effort.
- If H.264 encoding is unavailable, Potato and Social report that they could not create the optimized video; they do not silently emit MPEG-4 quality mode.
Confidence, limits, and the next evidence to collect
| Finding | Confidence | Reason |
|---|---|---|
| 7,000 kbps places 22 seconds near the 20 MiB target | High | Deterministic arithmetic plus 18.65 MiB Windows smoke result |
| H.264 is the default compatibility choice | High | Current Icarus runtime plus browser/Windows documentation |
| 1080p / 30 fps is the tactical-content sweet spot | High for tested sample | Controlled 750 and 450 kbps comparisons agree |
| 720p becomes preferable at the 250 kbps floor | High for tested sample | Three paired 1080p / 720p runs place the crossover below 300 kbps |
| ≈1,449 kbps is a comfortable first request for 30 pages | High | It sits well above the tested 750 kbps candidate |
| Potato works on the motivating 16-page strategy | High | 54.500 s field export: H.264, 1920×1080, 30 fps, 9.650 MiB; release and clean debug replay |
| H.265 can be universal on Icarus Windows machines | Low / contradicted | hevc_mf failed locally; playback is conditional |
Source: arithmetic, repository code, local FFmpeg/ffprobe/libvmaf experiment, and cited platform documentation.
The motivating strategy is now measured rather than reported-only, but one field sample is not a corpus. The next useful evidence is 5–10 additional real strategies: sparse, drawing-heavy, image-heavy, 10 pages, 30 pages, and one export long enough to activate Potato’s 720p floor. Run the same presets, inspect the smallest labels at transition boundaries, and tune only the warning floor—not the product shape—unless those results contradict this matrix.
Appendix
Raw evidence · benchmark commands and observed rows
# Exact field sample · source retained locally, not committed or uploaded
size-strat.ica
SHA256 908c2dd64e9cacaba50a3fcc15cbd32ee2dc4e734d3bae41e5de267027e91e90
archive 4,824 B · JSON 56,084 B · format version 95
Pearl · 16 pages · 3.000 s steps
potato,H.264,1920x1080,30/1,54.500000 s,1,635 frames,10,118,396 B,9.649654 MiB
social,H.264,1920x1080,30/1,54.500000 s,1,635 frames,16,680,180 B,15.907459 MiB
max,H.264,1920x1080,60/1,54.250000 s,3,255 frames,18,367,663 B,17.516768 MiB
# Debug replay after capture-provider fix
potato,16 pages,54.500000 s,10,118,396 B,25,370 ms wall
result: no Flutter exception · representative page 9 frame inspected
# Synthetic reference: 22.000 s · 1920×1080 · 60 fps · lossless H.264
ffmpeg -loop 1 -framerate 60 -i best-test-preview.png -t 22 \
-vf "scale=1920:1080:force_original_aspect_ratio=decrease,
pad=1920:1080:(ow-iw)/2:(oh-ih)/2,
[intermittent moving widget filters],format=yuv420p" \
-c:v libx264 -crf 0 -preset ultrafast reference.mkv
# Representative H.264 candidate
ffmpeg -i reference.mkv -vf "fps=30,format=yuv420p" \
-c:v h264_mf -rate_control cbr -scenario archive \
-quality 80 -b:v 750k -movflags +faststart candidate.mp4
# Objective comparison
ffmpeg -i candidate.mp4 -i reference.mkv \
-lavfi "[distorted][reference]libvmaf" -f null NUL
current-h264-mf-8m-1080p60,22.000000,4,134,685 B,1,503,521 bps,VMAF 96.646
fit110-h264-mf-750k-1080p60,22.000000,2,320,682 B,843,884 bps,VMAF 92.875
fit110-h264-mf-750k-1080p30,22.000000,2,242,590 B,815,487 bps,VMAF 96.296
fit110-h264-mf-750k-720p30,22.000000,1,802,728 B,655,537 bps,VMAF 91.104
fit180-h264-mf-450k-1080p30,22.000000,1,399,009 B,508,730 bps,VMAF 93.524
fit180-h264-mf-450k-720p30,22.000000,1,307,683 B,475,521 bps,VMAF 90.063
potato-450k-1080p30,22.000000,1,405,421 B,VMAF 92.025
potato-450k-720p30,22.000000,1,337,931 B,VMAF 90.030
potato-300k-1080p30,22.000000,869,413 B,VMAF 90.548
potato-300k-720p30,22.000000,908,778 B,VMAF 86.445
potato-250k-1080p30,22.000000,723,711 B,VMAF 80.169
potato-250k-720p30,22.000000,725,550 B,VMAF 84.163
fallback-mpeg4-q3-1080p60,22.000000,19,165,931 B,6,969,429 bps,VMAF 96.769
h265-theory-3400k-1080p60,22.000000,2,532,156 B,920,784 bps,VMAF 97.090
# Media Foundation HEVC probe
hevc_mf: failed processing input 0x80004005
output: 258-byte empty MP4 · no video stream
Captured 2026-08-15 · Windows · exact field run through Icarus release and debug desktop harnesses · FFmpeg 8.1.2 full local build · VMAF comparisons normalized to the matching frame rate and 1920×1080.
Non-goals · deliberately not decided here
- No per-page step duration.
- No exposed codec picker or raw bitrate slider.
- No AV1/WebM packaging change.
- No promise that every export will fit a particular Discord account’s current limit.
- No hard rejection when a successful Potato or Social export remains above its target.
Generated 2026-08-15 · Codex for Dara / Icarus · inputs: attached Discord report, local size-strat.ica (SHA-256 908c…e90), best-test-preview.png, PR 126 after adb8150, FFmpeg 8.1.2, official Discord / MDN / Microsoft documentation · revision v4