fix(export): validate edited-track fast-path inputs

This commit is contained in:
wiiiii123
2026-04-21 18:09:23 +07:00
parent fcdc842371
commit 2b19493bb7
4 changed files with 103 additions and 6 deletions
+15
View File
@@ -40,4 +40,19 @@ describe("buildEditedTrackSourceAudioFilter", () => {
),
).toBeNull();
});
it("returns null when the edited-track segments are malformed", () => {
expect(
buildEditedTrackSourceAudioFilter(
[{ startMs: Number.NaN, endMs: 2_000, speed: 1.5 }],
44_100,
),
).toBeNull();
expect(
buildEditedTrackSourceAudioFilter([{ startMs: 0, endMs: 2_000, speed: 0 }], 44_100),
).toBeNull();
expect(
buildEditedTrackSourceAudioFilter([{ startMs: 0, endMs: 2_000, speed: 1 }], 0.4),
).toBeNull();
});
});
+25 -3
View File
@@ -181,24 +181,46 @@ export function buildEditedTrackSourceAudioFilter(
}
const normalizedSourceSampleRate = Math.round(sourceSampleRate);
if (normalizedSourceSampleRate < 1) {
return null;
}
const filterParts: string[] = [];
const segmentLabels: string[] = [];
let hasInvalidSegment = false;
segments.forEach((segment, index) => {
if (!Number.isFinite(segment.startMs) || !Number.isFinite(segment.endMs)) {
hasInvalidSegment = true;
return;
}
if (segment.endMs - segment.startMs <= 0.5) {
hasInvalidSegment = true;
return;
}
const label = `edited_audio_${index}`;
const speed = Number.isFinite(segment.speed) && segment.speed > 0 ? segment.speed : 1;
const speed = segment.speed;
if (!Number.isFinite(speed) || speed <= 0) {
hasInvalidSegment = true;
return;
}
const segmentFilter = [
`[1:a]atrim=start=${formatFfmpegSeconds(segment.startMs)}:end=${formatFfmpegSeconds(segment.endMs)}`,
"asetpts=PTS-STARTPTS",
];
if (Math.abs(speed - 1) > 0.0001) {
const adjustedSampleRate = Math.round(normalizedSourceSampleRate * speed);
if (adjustedSampleRate < 1) {
hasInvalidSegment = true;
return;
}
segmentFilter.push(
`asetrate=${Math.max(1, Math.round(normalizedSourceSampleRate * speed))}`,
`asetrate=${adjustedSampleRate}`,
`aresample=${normalizedSourceSampleRate}`,
);
}
@@ -207,7 +229,7 @@ export function buildEditedTrackSourceAudioFilter(
segmentLabels.push(`[${label}]`);
});
if (segmentLabels.length === 0) {
if (hasInvalidSegment || segmentLabels.length === 0) {
return null;
}
@@ -100,6 +100,23 @@ describe("editedTrackStrategy", () => {
).toBe("offline-render-fallback");
});
it("falls back when source duration or speed-region bounds are invalid", () => {
const invalidBounds: SpeedRegion[] = [
{ id: "speed-1", startMs: Number.NaN, endMs: 10_000, speed: 1.5 },
];
expect(
classifyEditedTrackStrategy({
primaryAudioSourcePath: "recording.mp4",
sourceDurationMs: Number.POSITIVE_INFINITY,
trimRegions: EMPTY_TRIMS,
speedRegions: invalidBounds,
audioRegions: [],
sourceAudioFallbackPaths: [],
}),
).toBe("offline-render-fallback");
});
it("builds source segments that preserve trims and speed boundaries", () => {
const trimRegions: TrimRegion[] = [{ id: "trim-1", startMs: 10_000, endMs: 12_000 }];
const speedRegions: SpeedRegion[] = [
@@ -118,4 +135,12 @@ describe("editedTrackStrategy", () => {
{ startMs: 18_000, endMs: 20_000, speed: 1 },
]);
});
it("returns no source segments when speed-region bounds are invalid", () => {
expect(
buildEditedTrackSourceSegments(SOURCE_DURATION_MS, EMPTY_TRIMS, [
{ id: "speed-1", startMs: 15_000, endMs: 10_000, speed: 1.5 },
]),
).toEqual([]);
});
});
+38 -3
View File
@@ -26,8 +26,29 @@ function isSafeFiltergraphSpeed(speed: number): boolean {
);
}
function hasSafeFiltergraphSpeedRegions(speedRegions: SpeedRegion[]): boolean {
return speedRegions.every((region) => isSafeFiltergraphSpeed(region.speed));
function hasFiniteTimelineRange(startMs: number, endMs: number, sourceDurationMs: number): boolean {
return (
Number.isFinite(startMs) &&
Number.isFinite(endMs) &&
startMs >= 0 &&
endMs > startMs &&
endMs <= sourceDurationMs
);
}
function hasSafeFiltergraphSpeedRegions(
speedRegions: SpeedRegion[],
sourceDurationMs: number,
): boolean {
if (!Number.isFinite(sourceDurationMs) || sourceDurationMs <= 0) {
return false;
}
return speedRegions.every(
(region) =>
hasFiniteTimelineRange(region.startMs, region.endMs, sourceDurationMs) &&
isSafeFiltergraphSpeed(region.speed),
);
}
function buildKeptRanges(
@@ -64,6 +85,20 @@ export function buildEditedTrackSourceSegments(
trimRegions: TrimRegion[],
speedRegions: SpeedRegion[],
): EditedTrackSourceSegment[] {
if (!Number.isFinite(sourceDurationMs) || sourceDurationMs <= 0) {
return [];
}
if (
speedRegions.some(
(region) =>
!hasFiniteTimelineRange(region.startMs, region.endMs, sourceDurationMs) ||
!isSafeFiltergraphSpeed(region.speed),
)
) {
return [];
}
const segments: EditedTrackSourceSegment[] = [];
const keptRanges = buildKeptRanges(sourceDurationMs, trimRegions);
@@ -119,7 +154,7 @@ export function classifyEditedTrackStrategy(input: EditedTrackStrategyInput): Ed
return "offline-render-fallback";
}
if (!hasSafeFiltergraphSpeedRegions(input.speedRegions)) {
if (!hasSafeFiltergraphSpeedRegions(input.speedRegions, input.sourceDurationMs)) {
return "offline-render-fallback";
}