av1 кодек

This commit is contained in:
2025-11-11 21:07:51 +03:00
parent b843bdf897
commit 2da2b584fa
9 changed files with 274 additions and 98 deletions

View File

@@ -6,12 +6,14 @@ import type {
DashConvertResult,
VideoProfile,
ThumbnailConfig,
ConversionProgress
ConversionProgress,
CodecType
} from '../types';
import {
checkFFmpeg,
checkMP4Box,
checkNvenc,
checkAV1Support,
getVideoMetadata,
ensureDir
} from '../utils';
@@ -33,6 +35,7 @@ export async function convertToDash(
segmentDuration = 2,
profiles: userProfiles,
customProfiles,
codec = 'dual',
useNvenc,
generateThumbnails = true,
thumbnailConfig = {},
@@ -54,6 +57,7 @@ export async function convertToDash(
segmentDuration,
userProfiles,
customProfiles,
codec,
useNvenc,
generateThumbnails,
thumbnailConfig,
@@ -82,6 +86,7 @@ async function convertToDashInternal(
segmentDuration: number,
userProfiles: VideoProfile[] | undefined,
customProfiles: string[] | undefined,
codec: CodecType,
useNvenc: boolean | undefined,
generateThumbnails: boolean,
thumbnailConfig: ThumbnailConfig,
@@ -177,61 +182,92 @@ async function convertToDashInternal(
await ensureDir(videoOutputDir);
reportProgress('analyzing', 20, `Using ${willUseNvenc ? 'NVENC' : 'CPU'} encoding`, undefined);
// Determine which codecs to use based on codec parameter
const codecs: Array<{ type: 'h264' | 'av1'; codec: string; preset: string }> = [];
if (codec === 'h264' || codec === 'dual') {
const h264Codec = willUseNvenc ? 'h264_nvenc' : 'libx264';
const h264Preset = willUseNvenc ? 'p4' : 'medium';
codecs.push({ type: 'h264', codec: h264Codec, preset: h264Preset });
}
if (codec === 'av1' || codec === 'dual') {
// Check for AV1 hardware encoder
const av1Support = await checkAV1Support();
const av1Codec = av1Support.available ? av1Support.encoder! : 'libsvtav1';
const av1Preset = av1Support.available ? (av1Codec === 'av1_nvenc' ? 'p4' : 'medium') : '8';
codecs.push({ type: 'av1', codec: av1Codec, preset: av1Preset });
}
const codecNames = codecs.map(c => c.type.toUpperCase()).join(' + ');
reportProgress('analyzing', 20, `Using ${codecNames} encoding (${willUseNvenc ? 'GPU' : 'CPU'})`, undefined);
// Video codec selection
const videoCodec = willUseNvenc ? 'h264_nvenc' : 'libx264';
const codecPreset = willUseNvenc ? 'p4' : 'medium';
const maxConcurrent = willUseNvenc ? 3 : 2;
// STAGE 1: Encode profiles to MP4 (parallel - heavy work)
reportProgress('encoding', 25, `Stage 1: Encoding ${profiles.length} profiles to MP4...`);
// STAGE 1: Encode profiles to MP4 for each codec (parallel - heavy work)
const codecMP4Paths = new Map<'h264' | 'av1', Map<string, string>>();
const tempMP4Paths = await encodeProfilesToMP4(
input,
tempDir,
profiles,
videoCodec,
codecPreset,
metadata.duration,
segmentDuration,
metadata.fps || 25, // Use detected FPS or default to 25
metadata.audioBitrate, // Source audio bitrate for smart selection
parallel,
maxConcurrent,
undefined, // optimizations - for future use
(profileName, percent) => {
const profileIndex = profiles.findIndex(p => p.name === profileName);
const baseProgress = 25 + (profileIndex / profiles.length) * 40;
const profileProgress = (percent / 100) * (40 / profiles.length);
reportProgress('encoding', baseProgress + profileProgress, `Encoding ${profileName}...`, profileName);
// Also report individual profile progress
if (onProgress) {
onProgress({
stage: 'encoding',
percent: baseProgress + profileProgress,
currentProfile: profileName,
profilePercent: percent, // Actual profile progress 0-100
message: `Encoding ${profileName}...`
});
for (let codecIndex = 0; codecIndex < codecs.length; codecIndex++) {
const { type, codec: videoCodec, preset: codecPreset } = codecs[codecIndex];
const codecProgress = codecIndex / codecs.length;
const codecProgressRange = 1 / codecs.length;
reportProgress('encoding', 25 + codecProgress * 40, `Stage 1: Encoding ${type.toUpperCase()} (${profiles.length} profiles)...`);
const tempMP4Paths = await encodeProfilesToMP4(
input,
tempDir,
profiles,
videoCodec,
codecPreset,
metadata.duration,
segmentDuration,
metadata.fps || 25,
metadata.audioBitrate,
parallel,
maxConcurrent,
type, // Pass codec type to differentiate output files
undefined, // optimizations - for future use
(profileName, percent) => {
const profileIndex = profiles.findIndex(p => p.name === profileName);
const baseProgress = 25 + codecProgress * 40;
const profileProgress = (percent / 100) * (40 * codecProgressRange / profiles.length);
reportProgress('encoding', baseProgress + profileProgress, `Encoding ${type.toUpperCase()} ${profileName}...`, `${type}-${profileName}`);
// Also report individual profile progress
if (onProgress) {
onProgress({
stage: 'encoding',
percent: baseProgress + profileProgress,
currentProfile: `${type}-${profileName}`,
profilePercent: percent,
message: `Encoding ${type.toUpperCase()} ${profileName}...`
});
}
}
}
);
);
codecMP4Paths.set(type, tempMP4Paths);
}
reportProgress('encoding', 65, 'Stage 1 complete: All profiles encoded');
reportProgress('encoding', 65, 'Stage 1 complete: All codecs and profiles encoded');
// STAGE 2: Package to DASH using MP4Box (light work, fast)
reportProgress('encoding', 70, `Stage 2: Creating DASH with MP4Box...`);
const manifestPath = await packageToDash(
tempMP4Paths,
codecMP4Paths,
videoOutputDir,
profiles,
segmentDuration
segmentDuration,
codec
);
const videoPaths = Array.from(tempMP4Paths.values());
// Collect all video paths from all codecs
const videoPaths: string[] = [];
for (const mp4Paths of codecMP4Paths.values()) {
videoPaths.push(...Array.from(mp4Paths.values()));
}
reportProgress('encoding', 80, 'Stage 2 complete: DASH created');
@@ -293,7 +329,8 @@ async function convertToDashInternal(
posterPath,
duration: metadata.duration,
profiles,
usedNvenc: willUseNvenc
usedNvenc: willUseNvenc,
codecType: codec
};
}