refactor: убрать useNvenc и разделить выбор энкодера/декодера
This commit is contained in:
75
bin/cli.js
75
bin/cli.js
File diff suppressed because one or more lines are too long
43
src/cli.ts
43
src/cli.ts
@@ -10,7 +10,7 @@
|
|||||||
* create-vod ./video.mp4 ./output -r 720,1080
|
* create-vod ./video.mp4 ./output -r 720,1080
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { convertToDash, checkFFmpeg, checkMP4Box, getVideoMetadata, detectHardwareEncoders } from './index';
|
import { convertToDash, checkFFmpeg, checkMP4Box, getVideoMetadata, detectHardwareEncoders, detectHardwareDecoders } from './index';
|
||||||
import cliProgress from 'cli-progress';
|
import cliProgress from 'cli-progress';
|
||||||
import { statSync } from 'node:fs';
|
import { statSync } from 'node:fs';
|
||||||
import { basename, extname } from 'node:path';
|
import { basename, extname } from 'node:path';
|
||||||
@@ -31,6 +31,7 @@ let h264CRF: number | undefined;
|
|||||||
let av1CQ: number | undefined;
|
let av1CQ: number | undefined;
|
||||||
let av1CRF: number | undefined;
|
let av1CRF: number | undefined;
|
||||||
let accelerator: HardwareAccelerationOption | undefined;
|
let accelerator: HardwareAccelerationOption | undefined;
|
||||||
|
let decoder: HardwareAccelerationOption | undefined;
|
||||||
|
|
||||||
// First pass: extract flags and their values
|
// First pass: extract flags and their values
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
@@ -101,15 +102,24 @@ for (let i = 0; i < args.length; i++) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
i++; // Skip next arg
|
i++; // Skip next arg
|
||||||
} else if (args[i] === '--accel' || args[i] === '--hardware') {
|
} else if (args[i] === '--accel' || args[i] === '--hardware' || args[i] === '-e' || args[i] === '--encoder') {
|
||||||
const acc = args[i + 1];
|
const acc = args[i + 1];
|
||||||
const allowed: HardwareAccelerationOption[] = ['auto', 'nvenc', 'qsv', 'amf', 'cpu'];
|
const allowed: HardwareAccelerationOption[] = ['auto', 'nvenc', 'qsv', 'amf', 'cpu', 'vaapi', 'videotoolbox', 'v4l2'];
|
||||||
if (!allowed.includes(acc as HardwareAccelerationOption)) {
|
if (!allowed.includes(acc as HardwareAccelerationOption)) {
|
||||||
console.error(`❌ Invalid accelerator: ${acc}. Valid: auto, nvenc, qsv, amf, cpu`);
|
console.error(`❌ Invalid accelerator: ${acc}. Valid: auto, nvenc, qsv, amf, vaapi, videotoolbox, v4l2, cpu`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
accelerator = acc as HardwareAccelerationOption;
|
accelerator = acc as HardwareAccelerationOption;
|
||||||
i++;
|
i++;
|
||||||
|
} else if (args[i] === '-d' || args[i] === '--decoder') {
|
||||||
|
const acc = args[i + 1];
|
||||||
|
const allowed: HardwareAccelerationOption[] = ['auto', 'nvenc', 'qsv', 'amf', 'vaapi', 'videotoolbox', 'v4l2', 'cpu'];
|
||||||
|
if (!allowed.includes(acc as HardwareAccelerationOption)) {
|
||||||
|
console.error(`❌ Invalid decoder: ${acc}. Valid: auto, nvenc, qsv, amf, vaapi, videotoolbox, v4l2, cpu`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
decoder = acc as HardwareAccelerationOption;
|
||||||
|
i++;
|
||||||
} else if (!args[i].startsWith('-')) {
|
} else if (!args[i].startsWith('-')) {
|
||||||
// Positional argument
|
// Positional argument
|
||||||
positionalArgs.push(args[i]);
|
positionalArgs.push(args[i]);
|
||||||
@@ -127,7 +137,8 @@ if (!input) {
|
|||||||
console.error(' -c, --codec Video codec: av1, h264, or dual (default: dual)');
|
console.error(' -c, --codec Video codec: av1, h264, or dual (default: dual)');
|
||||||
console.error(' -f, --format Streaming format: dash, hls, or both (default: both)');
|
console.error(' -f, --format Streaming format: dash, hls, or both (default: both)');
|
||||||
console.error(' -p, --poster Poster timecode (e.g., 00:00:05 or 10)');
|
console.error(' -p, --poster Poster timecode (e.g., 00:00:05 or 10)');
|
||||||
console.error(' --accel <type> Hardware accelerator: auto|nvenc|qsv|amf|cpu (default: auto)');
|
console.error(' -e, --encoder <type> Hardware encoder: auto|nvenc|qsv|amf|vaapi|videotoolbox|v4l2|cpu (default: auto)');
|
||||||
|
console.error(' -d, --decoder <type> Hardware decoder: auto|nvenc|qsv|amf|vaapi|videotoolbox|v4l2|cpu (default: auto)');
|
||||||
console.error('\nQuality Options (override defaults):');
|
console.error('\nQuality Options (override defaults):');
|
||||||
console.error(' --h264-cq <value> H.264 GPU CQ value (0-51, lower = better, default: auto)');
|
console.error(' --h264-cq <value> H.264 GPU CQ value (0-51, lower = better, default: auto)');
|
||||||
console.error(' --h264-crf <value> H.264 CPU CRF value (0-51, lower = better, default: auto)');
|
console.error(' --h264-crf <value> H.264 CPU CRF value (0-51, lower = better, default: auto)');
|
||||||
@@ -153,6 +164,7 @@ const hasFFmpeg = await checkFFmpeg();
|
|||||||
const hasMP4Box = await checkMP4Box();
|
const hasMP4Box = await checkMP4Box();
|
||||||
const hwEncoders = await detectHardwareEncoders();
|
const hwEncoders = await detectHardwareEncoders();
|
||||||
const hasAv1Hardware = hwEncoders.some(item => item.av1Encoder);
|
const hasAv1Hardware = hwEncoders.some(item => item.av1Encoder);
|
||||||
|
const hwDecoders = await detectHardwareDecoders();
|
||||||
|
|
||||||
const accelPriority: Record<string, number> = {
|
const accelPriority: Record<string, number> = {
|
||||||
nvenc: 100,
|
nvenc: 100,
|
||||||
@@ -176,6 +188,10 @@ const accelLabel = bestAccelName
|
|||||||
? `✅ ${bestAccelName}${accelRest.length > 0 ? ` (${accelRest.join(', ')})` : ''}`
|
? `✅ ${bestAccelName}${accelRest.length > 0 ? ` (${accelRest.join(', ')})` : ''}`
|
||||||
: '❌';
|
: '❌';
|
||||||
console.log(`Hardware: ${accelLabel}`);
|
console.log(`Hardware: ${accelLabel}`);
|
||||||
|
if (hwDecoders.length > 0) {
|
||||||
|
const decList = Array.from(new Set(hwDecoders.map((d) => d.accelerator.toUpperCase())));
|
||||||
|
console.log(`Decoders: ${decList.join(', ')}`);
|
||||||
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
if (!hasFFmpeg) {
|
if (!hasFFmpeg) {
|
||||||
@@ -267,7 +283,13 @@ const manifestDesc =
|
|||||||
const thumbnailsPlanned = true;
|
const thumbnailsPlanned = true;
|
||||||
const posterPlanned = posterTimecode || '00:00:00';
|
const posterPlanned = posterTimecode || '00:00:00';
|
||||||
const codecDisplay = codecType === 'dual' ? 'dual (AV1 + H.264)' : codecType;
|
const codecDisplay = codecType === 'dual' ? 'dual (AV1 + H.264)' : codecType;
|
||||||
const codecNote = codecType === 'h264' && accelRest && accelRest.length >= 0 && !hasAv1Hardware ? ' (AV1 disabled: no HW)' : '';
|
const codecNote = codecType === 'h264' && !hasAv1Hardware ? ' (AV1 disabled: no HW)' : '';
|
||||||
|
const plannedAccel = accelerator ? accelerator.toUpperCase() : (bestAccelName || 'CPU');
|
||||||
|
const plannedDecoder = decoder ? decoder.toUpperCase() : (hwDecoders[0]?.accelerator.toUpperCase() || 'CPU');
|
||||||
|
const acceleratorDisplay = plannedAccel === 'AUTO' ? (bestAccelName || 'CPU') : plannedAccel;
|
||||||
|
const decoderDisplay = plannedDecoder === 'AUTO'
|
||||||
|
? (hwDecoders[0]?.accelerator.toUpperCase() || 'CPU')
|
||||||
|
: plannedDecoder;
|
||||||
|
|
||||||
console.log('\n📦 Parameters:');
|
console.log('\n📦 Parameters:');
|
||||||
console.log(` Input: ${input}`);
|
console.log(` Input: ${input}`);
|
||||||
@@ -277,7 +299,8 @@ console.log(` Profiles: ${displayProfiles.join(', ')}`);
|
|||||||
console.log(` Manifests: ${manifestDesc}`);
|
console.log(` Manifests: ${manifestDesc}`);
|
||||||
console.log(` Poster: ${posterPlanned} (will be generated)`);
|
console.log(` Poster: ${posterPlanned} (will be generated)`);
|
||||||
console.log(` Thumbnails: ${thumbnailsPlanned ? 'yes (with VTT)' : 'no'}`);
|
console.log(` Thumbnails: ${thumbnailsPlanned ? 'yes (with VTT)' : 'no'}`);
|
||||||
console.log(` Accelerator: ${bestAccel ? bestAccel.accelerator.toUpperCase() : 'CPU'}`);
|
console.log(` Accelerator: ${acceleratorDisplay}`);
|
||||||
|
console.log(` Decoder: ${decoderDisplay}`);
|
||||||
|
|
||||||
// Build quality settings if any are specified
|
// Build quality settings if any are specified
|
||||||
let quality: QualitySettings | undefined;
|
let quality: QualitySettings | undefined;
|
||||||
@@ -316,6 +339,7 @@ const bars: Record<string, any> = {};
|
|||||||
let overallBar: any = null;
|
let overallBar: any = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const startedAt = Date.now();
|
||||||
const result = await convertToDash({
|
const result = await convertToDash({
|
||||||
input,
|
input,
|
||||||
outputDir,
|
outputDir,
|
||||||
@@ -325,6 +349,7 @@ try {
|
|||||||
format: formatType,
|
format: formatType,
|
||||||
segmentDuration: 2,
|
segmentDuration: 2,
|
||||||
hardwareAccelerator: accelerator,
|
hardwareAccelerator: accelerator,
|
||||||
|
hardwareDecoder: decoder,
|
||||||
quality,
|
quality,
|
||||||
generateThumbnails: true,
|
generateThumbnails: true,
|
||||||
generatePoster: true,
|
generatePoster: true,
|
||||||
@@ -368,7 +393,9 @@ try {
|
|||||||
|
|
||||||
multibar.stop();
|
multibar.stop();
|
||||||
|
|
||||||
console.log('\n✅ Conversion completed successfully!\n');
|
const elapsedMs = Date.now() - startedAt;
|
||||||
|
const elapsedSec = (elapsedMs / 1000).toFixed(2);
|
||||||
|
console.log(`\n✅ Conversion completed successfully! (${elapsedSec}s)\n`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
multibar.stop();
|
multibar.stop();
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import type {
|
|||||||
StreamingFormat,
|
StreamingFormat,
|
||||||
HardwareAccelerationOption,
|
HardwareAccelerationOption,
|
||||||
HardwareAccelerator,
|
HardwareAccelerator,
|
||||||
HardwareEncoderInfo
|
HardwareEncoderInfo,
|
||||||
|
HardwareDecoderInfo
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import {
|
import {
|
||||||
checkFFmpeg,
|
checkFFmpeg,
|
||||||
@@ -19,7 +20,8 @@ import {
|
|||||||
getVideoMetadata,
|
getVideoMetadata,
|
||||||
ensureDir,
|
ensureDir,
|
||||||
setLogFile,
|
setLogFile,
|
||||||
detectHardwareEncoders
|
detectHardwareEncoders,
|
||||||
|
detectHardwareDecoders
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { selectProfiles, createProfilesFromStrings } from '../config/profiles';
|
import { selectProfiles, createProfilesFromStrings } from '../config/profiles';
|
||||||
import { generateThumbnailSprite, generatePoster } from './thumbnails';
|
import { generateThumbnailSprite, generatePoster } from './thumbnails';
|
||||||
@@ -41,7 +43,7 @@ export async function convertToDash(
|
|||||||
customProfiles,
|
customProfiles,
|
||||||
codec = 'dual',
|
codec = 'dual',
|
||||||
format = 'both',
|
format = 'both',
|
||||||
useNvenc,
|
hardwareDecoder,
|
||||||
hardwareAccelerator,
|
hardwareAccelerator,
|
||||||
quality,
|
quality,
|
||||||
generateThumbnails = true,
|
generateThumbnails = true,
|
||||||
@@ -87,8 +89,8 @@ Format: ${format}
|
|||||||
customProfiles,
|
customProfiles,
|
||||||
codec,
|
codec,
|
||||||
format,
|
format,
|
||||||
useNvenc,
|
|
||||||
hardwareAccelerator,
|
hardwareAccelerator,
|
||||||
|
hardwareDecoder,
|
||||||
quality,
|
quality,
|
||||||
generateThumbnails,
|
generateThumbnails,
|
||||||
thumbnailConfig,
|
thumbnailConfig,
|
||||||
@@ -127,8 +129,8 @@ async function convertToDashInternal(
|
|||||||
customProfiles: string[] | undefined,
|
customProfiles: string[] | undefined,
|
||||||
codec: CodecType,
|
codec: CodecType,
|
||||||
format: StreamingFormat,
|
format: StreamingFormat,
|
||||||
useNvenc: boolean | undefined,
|
|
||||||
hardwareAccelerator: HardwareAccelerationOption | undefined,
|
hardwareAccelerator: HardwareAccelerationOption | undefined,
|
||||||
|
hardwareDecoder: HardwareAccelerationOption | undefined,
|
||||||
quality: DashConvertOptions['quality'],
|
quality: DashConvertOptions['quality'],
|
||||||
generateThumbnails: boolean,
|
generateThumbnails: boolean,
|
||||||
thumbnailConfig: ThumbnailConfig,
|
thumbnailConfig: ThumbnailConfig,
|
||||||
@@ -164,13 +166,10 @@ async function convertToDashInternal(
|
|||||||
const preferredAccelerator: HardwareAccelerationOption =
|
const preferredAccelerator: HardwareAccelerationOption =
|
||||||
hardwareAccelerator && hardwareAccelerator !== 'auto'
|
hardwareAccelerator && hardwareAccelerator !== 'auto'
|
||||||
? hardwareAccelerator
|
? hardwareAccelerator
|
||||||
: useNvenc === true
|
|
||||||
? 'nvenc'
|
|
||||||
: useNvenc === false
|
|
||||||
? 'cpu'
|
|
||||||
: 'auto';
|
: 'auto';
|
||||||
|
|
||||||
const hardwareEncoders = await detectHardwareEncoders();
|
const hardwareEncoders = await detectHardwareEncoders();
|
||||||
|
const hardwareDecoders = await detectHardwareDecoders();
|
||||||
|
|
||||||
const { selected, h264Encoder, av1Encoder, warnings: accelWarnings } = selectHardwareEncoders(
|
const { selected, h264Encoder, av1Encoder, warnings: accelWarnings } = selectHardwareEncoders(
|
||||||
hardwareEncoders,
|
hardwareEncoders,
|
||||||
@@ -184,6 +183,11 @@ async function convertToDashInternal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { selected: selectedDecoder } = selectHardwareDecoders(
|
||||||
|
hardwareDecoders,
|
||||||
|
hardwareDecoder || 'auto'
|
||||||
|
);
|
||||||
|
|
||||||
const av1HardwareAvailable = hardwareEncoders.some(info => info.av1Encoder);
|
const av1HardwareAvailable = hardwareEncoders.some(info => info.av1Encoder);
|
||||||
|
|
||||||
let effectiveCodec: CodecType = codec;
|
let effectiveCodec: CodecType = codec;
|
||||||
@@ -275,7 +279,7 @@ async function convertToDashInternal(
|
|||||||
|
|
||||||
const codecNames = codecs.map(c => c.type.toUpperCase()).join(' + ');
|
const codecNames = codecs.map(c => c.type.toUpperCase()).join(' + ');
|
||||||
const accelLabel = selected === 'cpu' ? 'CPU' : selected.toUpperCase();
|
const accelLabel = selected === 'cpu' ? 'CPU' : selected.toUpperCase();
|
||||||
reportProgress('analyzing', 20, `Using ${codecNames} encoding (${accelLabel})`, undefined);
|
reportProgress('analyzing', 20, `Using ${codecNames} encoding (${accelLabel}, decoder ${selectedDecoder.toUpperCase()})`, undefined);
|
||||||
|
|
||||||
const maxConcurrent = selected === 'cpu' ? 2 : 3;
|
const maxConcurrent = selected === 'cpu' ? 2 : 3;
|
||||||
|
|
||||||
@@ -306,6 +310,7 @@ async function convertToDashInternal(
|
|||||||
type, // Pass codec type to differentiate output files
|
type, // Pass codec type to differentiate output files
|
||||||
codecQuality, // Pass quality settings (CQ/CRF)
|
codecQuality, // Pass quality settings (CQ/CRF)
|
||||||
undefined, // optimizations - for future use
|
undefined, // optimizations - for future use
|
||||||
|
selectedDecoder === 'cpu' ? undefined : selectedDecoder,
|
||||||
(profileName, percent) => {
|
(profileName, percent) => {
|
||||||
const profileIndex = profiles.findIndex(p => p.name === profileName);
|
const profileIndex = profiles.findIndex(p => p.name === profileName);
|
||||||
const baseProgress = 25 + codecProgress * 40;
|
const baseProgress = 25 + codecProgress * 40;
|
||||||
@@ -412,6 +417,7 @@ async function convertToDashInternal(
|
|||||||
profiles,
|
profiles,
|
||||||
usedNvenc: codecs.some(c => c.codec.includes('nvenc')),
|
usedNvenc: codecs.some(c => c.codec.includes('nvenc')),
|
||||||
selectedAccelerator: selected,
|
selectedAccelerator: selected,
|
||||||
|
selectedDecoder,
|
||||||
codecType: effectiveCodec,
|
codecType: effectiveCodec,
|
||||||
format
|
format
|
||||||
};
|
};
|
||||||
@@ -518,6 +524,36 @@ function selectHardwareEncoders(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectHardwareDecoders(
|
||||||
|
available: HardwareDecoderInfo[],
|
||||||
|
preferred: HardwareAccelerationOption
|
||||||
|
): {
|
||||||
|
selected: HardwareAccelerator;
|
||||||
|
} {
|
||||||
|
const supportedForAuto = new Set<HardwareAccelerator>(['nvenc', 'qsv', 'vaapi', 'videotoolbox', 'v4l2']);
|
||||||
|
|
||||||
|
const pick = (acc: HardwareAccelerator) => available.find(info => info.accelerator === acc);
|
||||||
|
|
||||||
|
if (preferred !== 'auto') {
|
||||||
|
if (preferred === 'cpu') {
|
||||||
|
return { selected: 'cpu' };
|
||||||
|
}
|
||||||
|
const item = pick(preferred);
|
||||||
|
return { selected: item ? item.accelerator : 'cpu' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = available.filter(info => supportedForAuto.has(info.accelerator));
|
||||||
|
if (pool.length === 0) {
|
||||||
|
return { selected: 'cpu' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const best = pool.sort(
|
||||||
|
(a, b) => (ACCEL_PRIORITY[b.accelerator] || 0) - (ACCEL_PRIORITY[a.accelerator] || 0)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
return { selected: best.accelerator };
|
||||||
|
}
|
||||||
|
|
||||||
function resolvePresetForEncoder(encoder: string, codecType: 'h264' | 'av1'): string {
|
function resolvePresetForEncoder(encoder: string, codecType: 'h264' | 'av1'): string {
|
||||||
if (encoder.includes('nvenc')) return 'p4';
|
if (encoder.includes('nvenc')) return 'p4';
|
||||||
if (encoder.includes('qsv')) return 'medium';
|
if (encoder.includes('qsv')) return 'medium';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { execFFmpeg, selectAudioBitrate } from '../utils';
|
import { execFFmpeg, selectAudioBitrate } from '../utils';
|
||||||
import type { VideoProfile, VideoOptimizations, CodecQualitySettings } from '../types';
|
import type { VideoProfile, VideoOptimizations, CodecQualitySettings, HardwareAccelerator } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default CQ/CRF value based on resolution and codec
|
* Get default CQ/CRF value based on resolution and codec
|
||||||
@@ -53,15 +53,29 @@ export async function encodeProfileToMP4(
|
|||||||
codecType: 'h264' | 'av1',
|
codecType: 'h264' | 'av1',
|
||||||
qualitySettings?: CodecQualitySettings,
|
qualitySettings?: CodecQualitySettings,
|
||||||
optimizations?: VideoOptimizations,
|
optimizations?: VideoOptimizations,
|
||||||
|
decoderAccel?: HardwareAccelerator,
|
||||||
onProgress?: (percent: number) => void
|
onProgress?: (percent: number) => void
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const outputPath = join(tempDir, `video_${codecType}_${profile.name}.mp4`);
|
const outputPath = join(tempDir, `video_${codecType}_${profile.name}.mp4`);
|
||||||
|
|
||||||
const args = [
|
const args = ['-y'];
|
||||||
'-y',
|
|
||||||
'-i', input,
|
// Hardware decode (optional)
|
||||||
'-c:v', videoCodec
|
if (decoderAccel) {
|
||||||
];
|
if (decoderAccel === 'nvenc') {
|
||||||
|
args.push('-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda');
|
||||||
|
} else if (decoderAccel === 'qsv') {
|
||||||
|
args.push('-hwaccel', 'qsv');
|
||||||
|
} else if (decoderAccel === 'vaapi') {
|
||||||
|
args.push('-hwaccel', 'vaapi');
|
||||||
|
} else if (decoderAccel === 'videotoolbox') {
|
||||||
|
args.push('-hwaccel', 'videotoolbox');
|
||||||
|
} else if (decoderAccel === 'v4l2') {
|
||||||
|
args.push('-hwaccel', 'v4l2');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push('-i', input, '-c:v', videoCodec);
|
||||||
|
|
||||||
// Determine if using GPU or CPU encoder
|
// Determine if using GPU or CPU encoder
|
||||||
const isGPU = videoCodec.includes('nvenc') || videoCodec.includes('qsv') || videoCodec.includes('amf') || videoCodec.includes('vaapi') || videoCodec.includes('videotoolbox') || videoCodec.includes('v4l2');
|
const isGPU = videoCodec.includes('nvenc') || videoCodec.includes('qsv') || videoCodec.includes('amf') || videoCodec.includes('vaapi') || videoCodec.includes('videotoolbox') || videoCodec.includes('v4l2');
|
||||||
@@ -196,6 +210,7 @@ export async function encodeProfilesToMP4(
|
|||||||
codecType: 'h264' | 'av1',
|
codecType: 'h264' | 'av1',
|
||||||
qualitySettings?: CodecQualitySettings,
|
qualitySettings?: CodecQualitySettings,
|
||||||
optimizations?: VideoOptimizations,
|
optimizations?: VideoOptimizations,
|
||||||
|
decoderAccel?: HardwareAccelerator,
|
||||||
onProgress?: (profileName: string, percent: number) => void
|
onProgress?: (profileName: string, percent: number) => void
|
||||||
): Promise<Map<string, string>> {
|
): Promise<Map<string, string>> {
|
||||||
const mp4Files = new Map<string, string>();
|
const mp4Files = new Map<string, string>();
|
||||||
@@ -217,6 +232,7 @@ export async function encodeProfilesToMP4(
|
|||||||
codecType,
|
codecType,
|
||||||
qualitySettings,
|
qualitySettings,
|
||||||
optimizations,
|
optimizations,
|
||||||
|
decoderAccel,
|
||||||
(percent) => {
|
(percent) => {
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress(profile.name, percent);
|
onProgress(profile.name, percent);
|
||||||
@@ -246,6 +262,7 @@ export async function encodeProfilesToMP4(
|
|||||||
codecType,
|
codecType,
|
||||||
qualitySettings,
|
qualitySettings,
|
||||||
optimizations,
|
optimizations,
|
||||||
|
decoderAccel,
|
||||||
(percent) => {
|
(percent) => {
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress(profile.name, percent);
|
onProgress(profile.name, percent);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export type {
|
|||||||
CodecType,
|
CodecType,
|
||||||
HardwareAccelerator,
|
HardwareAccelerator,
|
||||||
HardwareAccelerationOption,
|
HardwareAccelerationOption,
|
||||||
HardwareEncoderInfo
|
HardwareEncoderInfo,
|
||||||
|
HardwareDecoderInfo
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
// Utility exports
|
// Utility exports
|
||||||
@@ -24,7 +25,8 @@ export {
|
|||||||
checkAV1Support,
|
checkAV1Support,
|
||||||
getVideoMetadata,
|
getVideoMetadata,
|
||||||
selectAudioBitrate,
|
selectAudioBitrate,
|
||||||
detectHardwareEncoders
|
detectHardwareEncoders,
|
||||||
|
detectHardwareDecoders
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
// Profile exports
|
// Profile exports
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ export interface HardwareEncoderInfo {
|
|||||||
av1Encoder?: string;
|
av1Encoder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Набор доступных декодеров/accel
|
||||||
|
*/
|
||||||
|
export interface HardwareDecoderInfo {
|
||||||
|
accelerator: HardwareAccelerator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quality settings for a codec
|
* Quality settings for a codec
|
||||||
*/
|
*/
|
||||||
@@ -74,11 +81,10 @@ export interface DashConvertOptions {
|
|||||||
/** Streaming format to generate: 'dash', 'hls', or 'both' (default: 'both') */
|
/** Streaming format to generate: 'dash', 'hls', or 'both' (default: 'both') */
|
||||||
format?: StreamingFormat;
|
format?: StreamingFormat;
|
||||||
|
|
||||||
/** Enable NVENC hardware acceleration (auto-detect if undefined) — устарело, используйте hardwareAccelerator */
|
|
||||||
useNvenc?: boolean;
|
|
||||||
|
|
||||||
/** Предпочитаемый аппаратный ускоритель (auto по умолчанию) */
|
/** Предпочитаемый аппаратный ускоритель (auto по умолчанию) */
|
||||||
hardwareAccelerator?: HardwareAccelerationOption;
|
hardwareAccelerator?: HardwareAccelerationOption;
|
||||||
|
/** Предпочитаемый аппаратный ускоритель для декодера (auto по умолчанию) */
|
||||||
|
hardwareDecoder?: HardwareAccelerationOption;
|
||||||
|
|
||||||
/** Quality settings for video encoding (CQ/CRF values) */
|
/** Quality settings for video encoding (CQ/CRF values) */
|
||||||
quality?: QualitySettings;
|
quality?: QualitySettings;
|
||||||
@@ -195,6 +201,8 @@ export interface DashConvertResult {
|
|||||||
|
|
||||||
/** Выбранный аппаратный ускоритель */
|
/** Выбранный аппаратный ускоритель */
|
||||||
selectedAccelerator: HardwareAccelerator;
|
selectedAccelerator: HardwareAccelerator;
|
||||||
|
/** Выбранный аппаратный декодер */
|
||||||
|
selectedDecoder: HardwareAccelerator;
|
||||||
|
|
||||||
/** Codec type used for encoding */
|
/** Codec type used for encoding */
|
||||||
codecType: CodecType;
|
codecType: CodecType;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export {
|
|||||||
checkNvenc,
|
checkNvenc,
|
||||||
checkAV1Support,
|
checkAV1Support,
|
||||||
detectHardwareEncoders,
|
detectHardwareEncoders,
|
||||||
|
detectHardwareDecoders,
|
||||||
execFFmpeg,
|
execFFmpeg,
|
||||||
execMP4Box,
|
execMP4Box,
|
||||||
setLogFile
|
setLogFile
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { appendFile } from 'node:fs/promises';
|
import { appendFile } from 'node:fs/promises';
|
||||||
import type { HardwareAccelerator, HardwareEncoderInfo } from '../types';
|
import type { HardwareAccelerator, HardwareDecoderInfo, HardwareEncoderInfo } from '../types';
|
||||||
|
|
||||||
// Global variable for log file path
|
// Global variable for log file path
|
||||||
let currentLogFile: string | null = null;
|
let currentLogFile: string | null = null;
|
||||||
@@ -159,6 +159,40 @@ export async function detectHardwareEncoders(): Promise<HardwareEncoderInfo[]> {
|
|||||||
return detected;
|
return detected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить список доступных аппаратных декодеров (по выводу ffmpeg -hwaccels)
|
||||||
|
*/
|
||||||
|
export async function detectHardwareDecoders(): Promise<HardwareDecoderInfo[]> {
|
||||||
|
const output: string = await new Promise((resolve) => {
|
||||||
|
const proc = spawn('ffmpeg', ['-hide_banner', '-hwaccels']);
|
||||||
|
let data = '';
|
||||||
|
proc.stdout.on('data', (chunk) => data += chunk.toString());
|
||||||
|
proc.on('error', () => resolve(''));
|
||||||
|
proc.on('close', () => resolve(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines = output.split('\n').map(l => l.trim()).filter(Boolean);
|
||||||
|
const decoders: HardwareDecoderInfo[] = [];
|
||||||
|
|
||||||
|
const map: Record<string, HardwareAccelerator> = {
|
||||||
|
cuda: 'nvenc',
|
||||||
|
qsv: 'qsv',
|
||||||
|
vaapi: 'vaapi',
|
||||||
|
videotoolbox: 'videotoolbox',
|
||||||
|
v4l2m2m: 'v4l2',
|
||||||
|
dxva2: 'amf'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const acc = map[line];
|
||||||
|
if (acc) {
|
||||||
|
decoders.push({ accelerator: acc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoders;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute FFmpeg command with progress tracking
|
* Execute FFmpeg command with progress tracking
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user