最新的视频,24秒,但播放两三秒就结束了

This commit is contained in:
2026-06-03 20:03:39 +08:00
parent f742499b33
commit 600f64dc6d
3 changed files with 80 additions and 22 deletions

View File

@@ -60,6 +60,11 @@ type VideoTimelineSegment = {
};
const jobs: Map<string, RecordingCompositionJob> = new Map<string, RecordingCompositionJob>();
const COMPOSITION_OUTPUT_WIDTH = 2560;
const COMPOSITION_OUTPUT_HEIGHT = 1440;
const COMPOSITION_OUTPUT_FPS = 60;
const COMPOSITION_HOST_HEIGHT = 1080;
const COMPOSITION_VIDEO_BITRATE = '16000k';
function nowIso(): string {
return new Date().toISOString();
@@ -207,6 +212,19 @@ function formatSeconds(value: number): string {
return value.toFixed(3).replace(/\.?0+$/, '');
}
function getDurationBoundVideoFilters(segmentDurationSeconds: number | null): string[] {
if (segmentDurationSeconds === null) {
return [];
}
const duration = formatSeconds(segmentDurationSeconds);
return [
`tpad=stop_mode=clone:stop_duration=${duration}`,
`trim=duration=${duration}`,
'setpts=PTS-STARTPTS'
];
}
function getBottomTileWidth(index: number, inputCount: number, outputWidth: number): number {
const sideCount = inputCount - 1;
if (sideCount <= 1) {
@@ -304,9 +322,10 @@ export function buildFfmpegCompositionArgs(input: {
outputPath: string;
format: string;
}): string[] {
const outputWidth = 1280;
const outputHeight = 720;
const hostHeight = 540;
const outputWidth = COMPOSITION_OUTPUT_WIDTH;
const outputHeight = COMPOSITION_OUTPUT_HEIGHT;
const outputFps = COMPOSITION_OUTPUT_FPS;
const hostHeight = COMPOSITION_HOST_HEIGHT;
const bottomHeight = outputHeight - hostHeight;
const videoInputs = orderVideoInputsForComposition(input.videoInputs);
const timelineOriginMs = getTimelineOriginMs(videoInputs.concat(input.audioInputs));
@@ -343,7 +362,7 @@ export function buildFfmpegCompositionArgs(input: {
if (segmentDurationSeconds === null) {
return;
}
filters.push(`color=color=black:size=${outputWidth}x${outputHeight}:rate=30:duration=${formatSeconds(segmentDurationSeconds)},format=yuv420p[seg${segmentIndex}]`);
filters.push(`color=color=black:size=${outputWidth}x${outputHeight}:rate=${outputFps}:duration=${formatSeconds(segmentDurationSeconds)},format=yuv420p[seg${segmentIndex}]`);
return;
}
@@ -365,16 +384,24 @@ export function buildFfmpegCompositionArgs(input: {
if (segmentDurationSeconds !== null) {
trimOptions.push(`duration=${formatSeconds(segmentDurationSeconds)}`);
}
filters.push(`[${inputLabel}]trim=${trimOptions.join(':')},setpts=PTS-STARTPTS,scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black,setsar=1[seg${segmentIndex}v${activeIndex}]`);
const videoFilters = [
`trim=${trimOptions.join(':')}`,
'setpts=PTS-STARTPTS',
...getDurationBoundVideoFilters(segmentDurationSeconds),
`scale=${width}:${height}:force_original_aspect_ratio=decrease`,
`pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:black`,
'setsar=1'
];
filters.push(`[${inputLabel}]${videoFilters.join(',')}[seg${segmentIndex}v${activeIndex}]`);
});
if (segment.activeInputs.length === 1) {
filters.push(`[seg${segmentIndex}v0]fps=30,format=yuv420p[seg${segmentIndex}]`);
filters.push(`[seg${segmentIndex}v0]fps=${outputFps},format=yuv420p[seg${segmentIndex}]`);
return;
}
const segmentVideoLabels = segment.activeInputs.map((_file, activeIndex) => `[seg${segmentIndex}v${activeIndex}]`).join('');
filters.push(`${segmentVideoLabels}xstack=inputs=${segment.activeInputs.length}:layout=${createHostBottomLayout(segment.activeInputs.length, outputWidth, hostHeight)}:fill=black,fps=30,format=yuv420p[seg${segmentIndex}]`);
filters.push(`${segmentVideoLabels}xstack=inputs=${segment.activeInputs.length}:layout=${createHostBottomLayout(segment.activeInputs.length, outputWidth, hostHeight)}:fill=black,fps=${outputFps},format=yuv420p[seg${segmentIndex}]`);
});
if (videoSegments.length === 1) {
@@ -388,7 +415,7 @@ export function buildFfmpegCompositionArgs(input: {
const audioInputIndex = videoInputs.length;
const offsetMs = Math.round(getInputOffsetSeconds(input.audioInputs[0], timelineOriginMs) * 1000);
const offsetFilter = offsetMs > 1 ? `,adelay=${offsetMs}:all=1` : '';
filters.push(`[${audioInputIndex}:a]aresample=async=1:first_pts=0${offsetFilter}[aout]`);
filters.push(`[${audioInputIndex}:a]aresample=async=1:first_pts=0${offsetFilter},asetpts=N/SR/TB[aout]`);
} else if (input.audioInputs.length > 1) {
const audioLabels = input.audioInputs.map((file, index) => {
const audioInputIndex = videoInputs.length + index;
@@ -397,7 +424,7 @@ export function buildFfmpegCompositionArgs(input: {
filters.push(`[${audioInputIndex}:a]aresample=async=1:first_pts=0${offsetFilter}[a${index}]`);
return `[a${index}]`;
}).join('');
filters.push(`${audioLabels}amix=inputs=${input.audioInputs.length}:duration=longest:dropout_transition=2[aout]`);
filters.push(`${audioLabels}amix=inputs=${input.audioInputs.length}:duration=longest:dropout_transition=2,asetpts=N/SR/TB[aout]`);
}
args.push('-filter_complex', filters.join(';'), '-map', '[vout]');
@@ -406,12 +433,12 @@ export function buildFfmpegCompositionArgs(input: {
}
if (input.format === 'mp4') {
args.push('-c:v', 'libx264', '-preset', 'veryfast', '-pix_fmt', 'yuv420p');
args.push('-c:v', 'libx264', '-preset', 'veryfast', '-pix_fmt', 'yuv420p', '-b:v', COMPOSITION_VIDEO_BITRATE, '-r', String(outputFps));
if (input.audioInputs.length > 0) {
args.push('-c:a', 'aac');
}
} else {
args.push('-c:v', 'libvpx-vp9', '-deadline', 'realtime', '-cpu-used', '4');
args.push('-c:v', 'libvpx-vp9', '-deadline', 'good', '-cpu-used', '4', '-b:v', COMPOSITION_VIDEO_BITRATE, '-r', String(outputFps));
if (input.audioInputs.length > 0) {
args.push('-c:a', 'libopus');
}