From 187697eca63aed913e3c02a7c2c6480a0ddf6e3c Mon Sep 17 00:00:00 2001 From: "S.Gromov" Date: Thu, 22 Jan 2026 10:43:54 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=B5=D0=BA=D0=B0=20=D0=BF=D1=80=D0=B8=20GPU?= =?UTF-8?q?=20=D1=81=D0=BA=D0=B5=D0=B9=D0=BB=D0=B8=D0=BD=D0=B3=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B0=20nv12=20fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B5=D0=BC=D0=B0=20=D1=81=20=D0=B7=D0=B2=D1=83=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=B8=20=D1=82=D0=B5=D0=BB=D0=B5=D0=BF=D0=BE=D1=80?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=B8=20=D0=BF=D0=BE=20=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=8F=D0=BC=20=D0=B2=D0=B8=D0=B4=D0=B5=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli.ts | 6 +++--- src/core/encoding.ts | 27 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index ac77020..c56a285 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -282,7 +282,7 @@ console.log(` File: ${input}`); console.log(` Size: ${fileSizeMB} MB`); console.log(` Resolution: ${metadata.width}x${metadata.height}`); console.log(` FPS: ${metadata.fps.toFixed(2)}`); -console.log(` Duration: ${Math.floor(metadata.duration / 60)}m ${Math.floor(metadata.duration % 60)}s`); +console.log(` Duration: ${metadata.duration.toFixed(2)}s`); console.log(` Codec: ${metadata.codec}`); if (metadata.videoBitrate) { console.log(` Video Bitrate: ${(metadata.videoBitrate / 1000).toFixed(2)} Mbps`); @@ -313,7 +313,7 @@ if (customProfiles && customProfiles.length > 0) { profileResult.warnings.forEach(warn => console.warn(` - ${warn}`)); } - displayProfiles = profileResult.profiles.map(p => p.name); + displayProfiles = profileResult.profiles.map(p => p.fps ? `${p.name}@${p.fps}` : p.name); } else { const autoProfiles = selectProfiles( metadata.width, @@ -321,7 +321,7 @@ if (customProfiles && customProfiles.length > 0) { metadata.fps, metadata.videoBitrate ); - displayProfiles = autoProfiles.map(p => p.name); + displayProfiles = autoProfiles.map(p => p.fps ? `${p.name}@${p.fps}` : p.name); } const manifestDesc = [ diff --git a/src/core/encoding.ts b/src/core/encoding.ts index 48dd7e7..47a1438 100644 --- a/src/core/encoding.ts +++ b/src/core/encoding.ts @@ -161,7 +161,8 @@ export async function encodeProfileToMP4( const targetWidth = profile.width; const targetHeight = profile.height; - if (decoderAccel === 'nvenc') { + const useCudaScale = decoderAccel === 'nvenc'; + if (useCudaScale) { // CUDA path: вписываем в профиль с сохранением исходного AR filters.push(`scale_cuda=${targetWidth}:${targetHeight}:force_original_aspect_ratio=decrease:force_divisible_by=2`); } else { @@ -181,24 +182,32 @@ export async function encodeProfileToMP4( } } + // Если использовали GPU-скейл, возвращаем кадры в системную память перед CPU-фильтрами + if (useCudaScale) { + filters.push('hwdownload', 'format=nv12'); + } + // Центрируем кадр, чтобы браузеры (Firefox/videotoolbox) не игнорировали PAR - filters.push( - `pad=${targetWidth}:${targetHeight}:(ow-iw)/2:(oh-ih)/2`, - 'setsar=1' - ); + filters.push(`pad=${targetWidth}:${targetHeight}:(ow-iw)/2:(oh-ih)/2`, 'setsar=1'); args.push('-vf', filters.join(',')); if (!muted) { - // Audio encoding + // Audio encoding с нормализацией таймингов и автоподпаддингом тишиной const targetAudioBitrate = parseInt(profile.audioBitrate) || 256; const optimalAudioBitrate = selectAudioBitrate(sourceAudioBitrate, targetAudioBitrate); args.push('-c:a', 'aac', '-b:a', optimalAudioBitrate); - - // Audio optimizations + + const targetDur = duration.toFixed(3); + const audioFilters: string[] = [ + 'aresample=async=1:min_hard_comp=0.1:first_pts=0', + `apad=whole_dur=${targetDur}`, + `atrim=0:${targetDur}` + ]; if (optimizations?.audioNormalize) { - args.push('-af', 'loudnorm'); + audioFilters.push('loudnorm'); } + args.push('-af', audioFilters.join(',')); } else { args.push('-an'); // без аудио дорожки }