- Remove trailing comma in SUPPORTED_LOCALES that caused Locale type to
include undefined, fixing all downstream type errors
- Remove unused webcamSizePreset from useMemo dependency array
- Use parsed.toString() instead of raw url in shell.openExternal per
Electron security best practice
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The main branch has already applied the same tutorial.help key
restructuring with slightly different intermediate values.
Adopting main's version to resolve merge conflicts.
AudioProcessor.process and renderPitchPreservedTimelineAudio accepted
validatedDurationSec as optional, so the speed-aware path fell back to
media.duration when it was absent. HTMLMediaElement.duration can be
Infinity for the same MediaRecorder/Chromium Linux containers this PR
targets, which would make effectiveEnd and the playback stop checks
unreliable.
The only caller (VideoExporter.process) already threads
streamingDecoder's validatedDuration through, so make the parameter
required. Drop the media.duration fallback, the Number.isFinite guard
on readEndSec, and the two `!== undefined` checks in the tick loop.
While here:
- Document that +0.5 on readEndSec mirrors streamingDecoder.decodeAll's
read window so trim-only and speed-aware paths stay in sync.
- Replace the unreachable silent-blob fallback at the end of
renderPitchPreservedTimelineAudio with a loud invariant throw, so a
broken recorder contract surfaces instead of yielding empty audio.
The earlier NaN/Infinity guard collapsed both duration hints to 0 when
the container reported invalid values, which turned scanEndSec into
0.5s. The packet scan then read only the first half-second, scannedDuration
capped there, and validateDuration fell back to that wrong value for the
entire export — exactly the Chromium Linux case this PR is meant to fix.
Use a 24h sentinel as the read endpoint when no hint is usable. An
explicit end is still required (some containers are truncated without
one, per prior comment), but the sentinel is large enough to exceed any
realistic recording so the scan reaches real EOF.
Startup trim-skip only consulted the first active region at t=0, so
back-to-back or overlapping trims starting at zero (e.g. [0,500ms]
followed by [500ms,1000ms]) left the second region un-skipped. The
in-flight tick loop would catch it, but MediaRecorder was already
running by then, capturing up to one rAF frame of trimmed audio into
the blob and shifting the downstream timeline.
Loop findActiveTrimRegion from the advancing startPosition until no
region matches or startPosition >= effectiveEnd, bounded by
trimRegions.length for safety. Recompute initialSpeedRegion from the
final startPosition so playbackRate reflects the true start point.
mediaInfo.duration from web-demuxer can be NaN or Infinity on Chromium
Linux (same MediaRecorder bug this PR otherwise addresses). That value
flowed straight into Math.max + demuxer.read() as scanEndSec, producing
an invalid range argument and breaking the ground-truth packet scan.
Guard both mediaInfo.duration and videoStream.duration with
Number.isFinite before Math.max; validateDuration() already handled the
downstream use.
Drop redundant WebDemuxer.read() / getDecoderConfig() type casts while
here — the generics infer the chunk/config type from the media string
literal, so the `as ReadableStream<EncodedVideoChunk>` and
`as AudioDecoderConfig` are no-ops.
- validateDuration returns 0 instead of NaN when both container is
NaN and scanned is zero
- Use Math.abs for divergence check so container under-reporting is
also corrected (not just over-reporting)
Some containers are truncated when read() has no end bound.
Use container/stream duration + buffer as scan range, matching
the same pattern used in decodeAll().
Two bugs in the export pipeline:
1. Container duration from WebM metadata can be unreliable (Chromium bug
on Linux — reports Infinity, 0, or inflated values). The pipeline
trusted this value, causing inflated exports, frozen video, and
"decode ended early" errors.
Fix: scan actual packet timestamps in loadMetadata() and compare
against container duration. Use packet-based ground truth when they
diverge.
2. The speed-aware audio path (renderPitchPreservedTimelineAudio)
recorded in real-time via MediaRecorder but never paused recording
during trim-region seeks. Seek dead time was captured as audio,
inflating the audio track beyond the video duration.
Fix: pause MediaRecorder during trim seeks, skip past initial trim
before recording starts, wait for seek completion before resuming.
Fixes#276, #433. Partially addresses #428.
Updated Discord webhook handling to allow for a fallback to DISCORD_PR_FORUM_WEBHOOK if DISCORD_WEBHOOK_URL is not set. Added checks to ensure webhook URL is provided, especially for fork PR events.