Compare commits
4 Commits
970b58c2a4
...
0813bea1d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 0813bea1d4 | |||
| 3bc980ef1d | |||
| 13b624480d | |||
| b7e264d56f |
43
README.md
43
README.md
@@ -11,18 +11,8 @@ CLI tool to convert videos to DASH and HLS with hardware acceleration (NVENC / I
|
|||||||
- 🖼️ Preview: thumbnail sprite + VTT, poster from the first frame
|
- 🖼️ Preview: thumbnail sprite + VTT, poster from the first frame
|
||||||
- ⏱️ Progress: per-profile and overall CLI progress bars
|
- ⏱️ Progress: per-profile and overall CLI progress bars
|
||||||
|
|
||||||
## Quick Start
|
## Install
|
||||||
|
For the CLI to work correctly, FFmpeg and MP4Box must be installed in the system.
|
||||||
```bash
|
|
||||||
# Run via npx (no install)
|
|
||||||
npx @gromlab/create-vod video.mp4
|
|
||||||
|
|
||||||
# Or install globally
|
|
||||||
npm install -g @gromlab/create-vod
|
|
||||||
create-vod video.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
**System requirements:**
|
|
||||||
```bash
|
```bash
|
||||||
# Arch Linux
|
# Arch Linux
|
||||||
sudo pacman -S ffmpeg gpac
|
sudo pacman -S ffmpeg gpac
|
||||||
@@ -34,12 +24,24 @@ sudo apt install ffmpeg gpac
|
|||||||
brew install ffmpeg gpac
|
brew install ffmpeg gpac
|
||||||
```
|
```
|
||||||
|
|
||||||
**Output:** A folder `video/` in the current directory with segments under `{profile}-{codec}/`, DASH/HLS manifests in the root, poster, and thumbnail sprite/VTT.
|
## Quick Start
|
||||||
|
Before running, make sure `FFmpeg` and `MP4Box` are installed (see Install).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run via npx (no install)
|
||||||
|
npx @gromlab/create-vod video.mp4
|
||||||
|
|
||||||
|
# Or install globally
|
||||||
|
npm install -g @gromlab/create-vod
|
||||||
|
create-vod video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:** A folder `video/` in the current directory with segments under `{profile}-{codec}/`, DASH/HLS manifests in the root, poster, and thumbnail sprite/VTT (both DASH and HLS are always generated).
|
||||||
|
|
||||||
## CLI Usage
|
## CLI Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-f format] [-p poster-timecode]
|
create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-p poster-timecode] [-e encoder] [-d decoder]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Main arguments
|
### Main arguments
|
||||||
@@ -54,8 +56,7 @@ create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-f format] [-
|
|||||||
| Option | Description | Values / Format | Default | Example |
|
| Option | Description | Values / Format | Default | Example |
|
||||||
|--------|----------------------------|----------------------------|----------|---------------------------------|
|
|--------|----------------------------|----------------------------|----------|---------------------------------|
|
||||||
| `-r, --resolutions` | Quality profiles | `360`, `720@60`, `1080-60` | auto | `-r 720,1080,1440@60` |
|
| `-r, --resolutions` | Quality profiles | `360`, `720@60`, `1080-60` | auto | `-r 720,1080,1440@60` |
|
||||||
| `-c, --codec` | Video codec | `h264`, `av1` | auto (h264 + AV1 if HW) | `-c h264` |
|
| `-c, --codec` | Video codec(s) | `h264`, `av1` (comma/space separated) | `h264` | `-c h264,av1` |
|
||||||
| `-f, --format` | Streaming format | `dash`, `hls` | auto (dash + hls) | `-f dash` |
|
|
||||||
| `-p, --poster` | Poster timecode | `HH:MM:SS` or seconds | `00:00:00` | `-p 00:00:05` or `-p 10` |
|
| `-p, --poster` | Poster timecode | `HH:MM:SS` or seconds | `00:00:00` | `-p 00:00:05` or `-p 10` |
|
||||||
| `-e, --encoder` | Video encoder | `auto`, `nvenc`, `qsv`, `amf`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-e nvenc` |
|
| `-e, --encoder` | Video encoder | `auto`, `nvenc`, `qsv`, `amf`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-e nvenc` |
|
||||||
| `-d, --decoder` | Video decoder (hwaccel) | `auto`, `nvenc`, `qsv`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-d cpu` |
|
| `-d, --decoder` | Video decoder (hwaccel) | `auto`, `nvenc`, `qsv`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-d cpu` |
|
||||||
@@ -63,7 +64,7 @@ create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-f format] [-
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Default (DASH + HLS, auto codec, auto profiles)
|
# Default (DASH + HLS, auto profiles)
|
||||||
create-vod video.mp4
|
create-vod video.mp4
|
||||||
|
|
||||||
# Custom output directory
|
# Custom output directory
|
||||||
@@ -75,12 +76,6 @@ create-vod video.mp4 -r 720,1080,1440
|
|||||||
# High FPS
|
# High FPS
|
||||||
create-vod video.mp4 -r 720@60,1080@60
|
create-vod video.mp4 -r 720@60,1080@60
|
||||||
|
|
||||||
# DASH only
|
|
||||||
create-vod video.mp4 -f dash
|
|
||||||
|
|
||||||
# HLS only (Safari/iOS)
|
|
||||||
create-vod video.mp4 -f hls -c h264
|
|
||||||
|
|
||||||
# Poster from 5th second
|
# Poster from 5th second
|
||||||
create-vod video.mp4 -p 5
|
create-vod video.mp4 -p 5
|
||||||
|
|
||||||
@@ -116,6 +111,6 @@ High FPS (60/90/120) are generated only if the source supports that FPS.
|
|||||||
- Thumbnails: auto sprite (160×90, 1s interval) + VTT
|
- Thumbnails: auto sprite (160×90, 1s interval) + VTT
|
||||||
- Poster: first frame (0:00:00, configurable via `-p`)
|
- Poster: first frame (0:00:00, configurable via `-p`)
|
||||||
- Parallel encoding: enabled
|
- Parallel encoding: enabled
|
||||||
- AV1: enabled only if hardware AV1 encoder detected (in auto mode); otherwise остаётся H.264
|
- AV1: enabled only if hardware AV1 encoder detected (in auto mode); otherwise stays on H.264
|
||||||
|
|
||||||
**Requirements:** Node.js ≥18.0.0, FFmpeg, MP4Box (gpac), optional NVIDIA/Intel/AMD GPU for acceleration
|
**Requirements:** Node.js ≥18.0.0, FFmpeg, MP4Box (gpac), optional NVIDIA/Intel/AMD GPU for acceleration
|
||||||
|
|||||||
36
README_RU.md
36
README_RU.md
@@ -11,18 +11,8 @@ CLI инструмент для конвертации видео в форма
|
|||||||
- 🖼️ Превью: thumbnail спрайты + VTT, постер с первого кадра
|
- 🖼️ Превью: thumbnail спрайты + VTT, постер с первого кадра
|
||||||
- ⏱️ Прогресс: CLI прогресс-бары по профилям и общему этапу
|
- ⏱️ Прогресс: CLI прогресс-бары по профилям и общему этапу
|
||||||
|
|
||||||
## Быстрый старт
|
## Install
|
||||||
|
Для корректной работы CLI требуется установленые в системе FFmpeg и MP4Box.
|
||||||
```bash
|
|
||||||
# Использование через npx (без установки)
|
|
||||||
npx @gromlab/create-vod video.mp4
|
|
||||||
|
|
||||||
# Или глобальная установка
|
|
||||||
npm install -g @gromlab/create-vod
|
|
||||||
create-vod video.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
**Системные требования:**
|
|
||||||
```bash
|
```bash
|
||||||
# Arch Linux
|
# Arch Linux
|
||||||
sudo pacman -S ffmpeg gpac
|
sudo pacman -S ffmpeg gpac
|
||||||
@@ -34,12 +24,23 @@ sudo apt install ffmpeg gpac
|
|||||||
brew install ffmpeg gpac
|
brew install ffmpeg gpac
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
Перед запуском убедитесь, что в системе установлены `FFmpeg` и `MP4Box` (см. Install).
|
||||||
|
```bash
|
||||||
|
# Использование через npx (без установки)
|
||||||
|
npx @gromlab/create-vod video.mp4
|
||||||
|
|
||||||
|
# Или глобальная установка
|
||||||
|
npm install -g @gromlab/create-vod
|
||||||
|
create-vod video.mp4
|
||||||
|
```
|
||||||
|
|
||||||
**Результат:** В текущей директории будет создана папка `video/` с сегментами в папках `{profile}-{codec}/`, манифестами DASH и HLS в корне, постером и превью спрайтами.
|
**Результат:** В текущей директории будет создана папка `video/` с сегментами в папках `{profile}-{codec}/`, манифестами DASH и HLS в корне, постером и превью спрайтами.
|
||||||
|
|
||||||
## Параметры CLI
|
## Параметры CLI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-f format] [-p poster-timecode]
|
create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-p poster-timecode] [-e encoder] [-d decoder]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Основные параметры
|
### Основные параметры
|
||||||
@@ -54,8 +55,7 @@ create-vod <input-video> [output-dir] [-r resolutions] [-c codec] [-f format] [-
|
|||||||
| Ключ | Описание | Значения / формат | По умолчанию | Пример |
|
| Ключ | Описание | Значения / формат | По умолчанию | Пример |
|
||||||
|------|----------|-------------------|--------------|--------|
|
|------|----------|-------------------|--------------|--------|
|
||||||
| `-r, --resolutions` | Выбор профилей качества | `360`, `720@60`, `1080-60` | авто | `-r 720,1080,1440@60` |
|
| `-r, --resolutions` | Выбор профилей качества | `360`, `720@60`, `1080-60` | авто | `-r 720,1080,1440@60` |
|
||||||
| `-c, --codec` | Видео кодек | `h264`, `av1` | авто (h264 + AV1 при наличии HW) | `-c h264` |
|
| `-c, --codec` | Видео кодек(и) | `h264`, `av1` (через пробел или запятую) | `h264` | `-c h264,av1` |
|
||||||
| `-f, --format` | Формат стриминга | `dash`, `hls` | авто (dash + hls) | `-f dash` |
|
|
||||||
| `-p, --poster` | Таймкод для постера | `HH:MM:SS` или секунды | `00:00:00` | `-p 00:00:05` или `-p 10` |
|
| `-p, --poster` | Таймкод для постера | `HH:MM:SS` или секунды | `00:00:00` | `-p 00:00:05` или `-p 10` |
|
||||||
| `-e, --encoder` | Видео энкодер | `auto`, `nvenc`, `qsv`, `amf`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-e nvenc` |
|
| `-e, --encoder` | Видео энкодер | `auto`, `nvenc`, `qsv`, `amf`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-e nvenc` |
|
||||||
| `-d, --decoder` | Видео декодер (hwaccel) | `auto`, `nvenc`, `qsv`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-d cpu` |
|
| `-d, --decoder` | Видео декодер (hwaccel) | `auto`, `nvenc`, `qsv`, `vaapi`, `videotoolbox`, `v4l2`, `cpu` | `auto` | `-d cpu` |
|
||||||
@@ -75,12 +75,6 @@ create-vod video.mp4 -r 720,1080,1440
|
|||||||
# Высокий FPS для игровых стримов
|
# Высокий FPS для игровых стримов
|
||||||
create-vod video.mp4 -r 720@60,1080@60
|
create-vod video.mp4 -r 720@60,1080@60
|
||||||
|
|
||||||
# Только DASH формат
|
|
||||||
create-vod video.mp4 -f dash
|
|
||||||
|
|
||||||
# Только HLS для Safari/iOS
|
|
||||||
create-vod video.mp4 -f hls -c h264
|
|
||||||
|
|
||||||
# Постер с 5-й секунды
|
# Постер с 5-й секунды
|
||||||
create-vod video.mp4 -p 5
|
create-vod video.mp4 -p 5
|
||||||
|
|
||||||
|
|||||||
61
bin/cli.js
61
bin/cli.js
File diff suppressed because one or more lines are too long
40
docs/QUALITY.md
Normal file
40
docs/QUALITY.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Регулировка качества и измерение
|
||||||
|
|
||||||
|
## Целевое качество
|
||||||
|
- Ориентир: средний VMAF ≥ 93 для кодированного видео.
|
||||||
|
|
||||||
|
## Что регулируем
|
||||||
|
- GPU-энкодеры используют CQ (Constant Quality).
|
||||||
|
- CPU-энкодеры используют CRF (Constant Rate Factor).
|
||||||
|
- Значения по умолчанию взяты из текущей логики кодирования (см. `src/core/encoding.ts`) и могут быть переопределены через CLI (`--h264-cq`, `--h264-crf`, `--av1-cq`, `--av1-crf`).
|
||||||
|
|
||||||
|
## Дефолтные CQ/CRF
|
||||||
|
|
||||||
|
| Энкодер | H.264 | AV1 |
|
||||||
|
|------------------|-----------------|----------------|
|
||||||
|
| NVENC | CQ 32 | CQ 42 |
|
||||||
|
| QSV | CQ 32 | CQ 42 |
|
||||||
|
| AMF | CQ 32 | CQ 42 |
|
||||||
|
| VAAPI | CQ 32 | CQ 42 |
|
||||||
|
| videotoolbox | CQ 32 | CQ 42 |
|
||||||
|
| v4l2 | CQ 32 | CQ 42 |
|
||||||
|
| CPU (libx264 / libsvtav1) | CRF 25→20* | CRF 40→28* |
|
||||||
|
|
||||||
|
\* CPU значения зависят от целевого разрешения:
|
||||||
|
- 360p: H.264 CRF 25, AV1 CRF 40
|
||||||
|
- 480p: H.264 CRF 24, AV1 CRF 38
|
||||||
|
- 720p: H.264 CRF 23, AV1 CRF 35
|
||||||
|
- 1080p: H.264 CRF 22, AV1 CRF 32
|
||||||
|
- 1440p: H.264 CRF 21, AV1 CRF 30
|
||||||
|
- 2160p: H.264 CRF 20, AV1 CRF 28
|
||||||
|
|
||||||
|
## Как мерить качество (VMAF)
|
||||||
|
1. Закодируйте тестовый ролик с нужными параметрами (CQ/CRF).
|
||||||
|
2. Сравните с исходником по VMAF (разрешения должны совпадать, при необходимости приведите к одному размеру).
|
||||||
|
3. Пример команды FFmpeg c libvmaf:
|
||||||
|
```bash
|
||||||
|
ffmpeg -i encoded.mp4 -i source.mp4 \
|
||||||
|
-lavfi "[0:v]setpts=PTS-STARTPTS[enc];[1:v]setpts=PTS-STARTPTS[ref];[enc][ref]libvmaf=log_path=vmaf.json:log_fmt=json" \
|
||||||
|
-f null -
|
||||||
|
```
|
||||||
|
В логах ищите `VMAF score`; если < 93 — увеличьте качество (понизьте CQ/CRF), если > 95 и нужен меньший битрейт — можно чуть поднять CQ/CRF.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@gromlab/create-vod",
|
"name": "@gromlab/create-vod",
|
||||||
"author": "Gromov Sergei",
|
"author": "Gromov Sergei",
|
||||||
"version": "0.1.7",
|
"version": "0.1.9",
|
||||||
"description": "DASH/HLS video converter with hardware acceleration (NVENC/QSV/AMF/VAAPI), thumbnails and poster generation",
|
"description": "DASH/HLS video converter with hardware acceleration (NVENC/QSV/AMF/VAAPI), thumbnails and poster generation",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
64
src/cli.ts
64
src/cli.ts
@@ -14,15 +14,14 @@ import { convertToDash, checkFFmpeg, checkMP4Box, getVideoMetadata, detectHardwa
|
|||||||
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';
|
||||||
import type { CodecChoice, StreamingFormatChoice, QualitySettings, HardwareAccelerationOption, HardwareAccelerator } from './types';
|
import type { QualitySettings, HardwareAccelerationOption, HardwareAccelerator, CodecType } from './types';
|
||||||
import { selectProfiles, createProfilesFromStrings } from './config/profiles';
|
import { selectProfiles, createProfilesFromStrings } from './config/profiles';
|
||||||
|
|
||||||
// Parse arguments
|
// Parse arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
let customProfiles: string[] | undefined;
|
let customProfiles: string[] | undefined;
|
||||||
let posterTimecode: string | undefined;
|
let posterTimecode: string | undefined;
|
||||||
let codecChoice: CodecChoice = 'auto'; // h264 + AV1 if HW
|
let codecChoice: Array<CodecType> | undefined; // default h264
|
||||||
let formatChoice: StreamingFormatChoice = 'auto'; // DASH + HLS
|
|
||||||
const positionalArgs: string[] = [];
|
const positionalArgs: string[] = [];
|
||||||
|
|
||||||
// Quality settings
|
// Quality settings
|
||||||
@@ -57,22 +56,16 @@ for (let i = 0; i < args.length; i++) {
|
|||||||
posterTimecode = args[i + 1];
|
posterTimecode = args[i + 1];
|
||||||
i++; // Skip next arg
|
i++; // Skip next arg
|
||||||
} else if (args[i] === '-c' || args[i] === '--codec') {
|
} else if (args[i] === '-c' || args[i] === '--codec') {
|
||||||
const codec = args[i + 1];
|
const codecArg = args[i + 1];
|
||||||
if (codec === 'av1' || codec === 'h264') {
|
const parts = codecArg.split(/[,\s]+/).map(p => p.trim()).filter(Boolean);
|
||||||
codecChoice = codec;
|
const allowed = new Set(['h264', 'av1']);
|
||||||
} else {
|
for (const p of parts) {
|
||||||
console.error(`❌ Invalid codec: ${codec}. Valid options: av1, h264`);
|
if (!allowed.has(p)) {
|
||||||
process.exit(1);
|
console.error(`❌ Invalid codec: ${p}. Valid options: av1, h264`);
|
||||||
}
|
process.exit(1);
|
||||||
i++; // Skip next arg
|
}
|
||||||
} else if (args[i] === '-f' || args[i] === '--format') {
|
|
||||||
const format = args[i + 1];
|
|
||||||
if (format === 'dash' || format === 'hls') {
|
|
||||||
formatChoice = format;
|
|
||||||
} else {
|
|
||||||
console.error(`❌ Invalid format: ${format}. Valid options: dash, hls`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
codecChoice = Array.from(new Set(parts)) as Array<'h264' | 'av1'>;
|
||||||
i++; // Skip next arg
|
i++; // Skip next arg
|
||||||
} else if (args[i] === '--h264-cq') {
|
} else if (args[i] === '--h264-cq') {
|
||||||
h264CQ = parseInt(args[i + 1]);
|
h264CQ = parseInt(args[i + 1]);
|
||||||
@@ -255,29 +248,24 @@ if (!hasMP4Box) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve codec selection
|
// Resolve codec selection
|
||||||
let includeH264 = codecChoice === 'h264' || codecChoice === 'auto';
|
const codecsRequested = codecChoice && codecChoice.length > 0 ? codecChoice : ['h264'];
|
||||||
let includeAv1 = codecChoice === 'av1' || codecChoice === 'auto';
|
let includeH264 = codecsRequested.includes('h264');
|
||||||
|
let includeAv1 = codecsRequested.includes('av1');
|
||||||
|
|
||||||
if (includeAv1 && !hasAv1Hardware && codecChoice === 'auto') {
|
if (!includeH264) {
|
||||||
includeAv1 = false;
|
console.warn('⚠️ H.264 is mandatory for compatibility. Adding H.264.');
|
||||||
|
includeH264 = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecChoice === 'av1' && !hasAv1Hardware) {
|
if (includeAv1 && !hasAv1Hardware) {
|
||||||
console.error(`⚠️ Warning: AV1 encoding requested but no hardware AV1 encoder found.`);
|
console.error(`⚠️ AV1 requested but no hardware AV1 encoder found.`);
|
||||||
console.error(` CPU-based AV1 encoding (libsvtav1) will be VERY slow.`);
|
console.error(` CPU-based AV1 encoding (libsvtav1) will be VERY slow.`);
|
||||||
console.error(` Consider using --codec h264 for faster encoding.\n`);
|
console.error(` Consider using --codec h264 for faster encoding.\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve formats
|
// Formats are always both
|
||||||
const wantDash = formatChoice === 'dash' || formatChoice === 'auto';
|
const wantDash = true;
|
||||||
const wantHls = formatChoice === 'hls' || formatChoice === 'auto';
|
const wantHls = true;
|
||||||
|
|
||||||
// Validate HLS requires H.264
|
|
||||||
if (wantHls && !includeH264) {
|
|
||||||
console.error(`❌ Error: HLS format requires H.264 codec for Safari/iOS compatibility.`);
|
|
||||||
console.error(` Please use --codec h264 or omit --codec to keep H.264.\n`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get video metadata and file size
|
// Get video metadata and file size
|
||||||
console.log('📊 Analyzing video...\n');
|
console.log('📊 Analyzing video...\n');
|
||||||
@@ -343,7 +331,7 @@ const codecListDisplay = [
|
|||||||
includeH264 ? 'h264' : null,
|
includeH264 ? 'h264' : null,
|
||||||
includeAv1 ? 'av1' : null
|
includeAv1 ? 'av1' : null
|
||||||
].filter(Boolean).join(', ');
|
].filter(Boolean).join(', ');
|
||||||
const codecNote = (!includeAv1 && codecChoice === 'auto' && !hasAv1Hardware) ? ' (AV1 disabled: no HW)' : '';
|
const codecNote = (!includeAv1 && codecsRequested.includes('av1')) ? ' (AV1 disabled: no HW)' : '';
|
||||||
const bestAccelName = (bestAccel && bestAccel.toUpperCase()) || 'CPU';
|
const bestAccelName = (bestAccel && bestAccel.toUpperCase()) || 'CPU';
|
||||||
const bestDecoderName = (bestDecoder && bestDecoder.toUpperCase()) || 'CPU';
|
const bestDecoderName = (bestDecoder && bestDecoder.toUpperCase()) || 'CPU';
|
||||||
const plannedAccel = accelerator ? accelerator.toUpperCase() : bestAccelName;
|
const plannedAccel = accelerator ? accelerator.toUpperCase() : bestAccelName;
|
||||||
@@ -407,8 +395,10 @@ try {
|
|||||||
outputDir,
|
outputDir,
|
||||||
customProfiles,
|
customProfiles,
|
||||||
posterTimecode,
|
posterTimecode,
|
||||||
codec: codecChoice,
|
codec: [
|
||||||
format: formatChoice,
|
...(includeH264 ? ['h264'] as const : []),
|
||||||
|
...(includeAv1 ? ['av1'] as const : [])
|
||||||
|
],
|
||||||
segmentDuration: 2,
|
segmentDuration: 2,
|
||||||
hardwareAccelerator: accelerator,
|
hardwareAccelerator: accelerator,
|
||||||
hardwareDecoder: decoder,
|
hardwareDecoder: decoder,
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const DEFAULT_PROFILES: VideoProfile[] = [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Select appropriate profiles based on input video resolution
|
* Select appropriate profiles based on input video resolution
|
||||||
* Only creates profiles that are equal to or smaller than input resolution
|
* Oriented by height: only profiles with height <= source height
|
||||||
* Always generates 30 FPS profiles by default
|
* Always generates 30 FPS profiles by default
|
||||||
* For high FPS (>30), user must explicitly specify in customProfiles
|
* For high FPS (>30), user must explicitly specify in customProfiles
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +101,7 @@ export function selectProfiles(
|
|||||||
|
|
||||||
// Standard 30 FPS profiles (always created)
|
// Standard 30 FPS profiles (always created)
|
||||||
const baseProfiles = DEFAULT_PROFILES.filter(profile => {
|
const baseProfiles = DEFAULT_PROFILES.filter(profile => {
|
||||||
return profile.width <= inputWidth && profile.height <= inputHeight;
|
return profile.height <= inputHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add standard 30fps profiles with bitrate limit
|
// Add standard 30fps profiles with bitrate limit
|
||||||
@@ -206,8 +206,8 @@ export function validateProfile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if source supports this resolution
|
// Check if source supports this resolution
|
||||||
if (profile.width > sourceWidth || profile.height > sourceHeight) {
|
if (profile.height > sourceHeight) {
|
||||||
return { error: `Source resolution (${sourceWidth}x${sourceHeight}) is lower than ${profileStr} (${profile.width}x${profile.height})` };
|
return { error: `Source height (${sourceHeight}px) is lower than requested ${profileStr} height (${profile.height}px)` };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if requested FPS exceeds source FPS
|
// Check if requested FPS exceeds source FPS
|
||||||
@@ -271,4 +271,3 @@ export function createProfilesFromStrings(
|
|||||||
|
|
||||||
return { profiles, errors, warnings };
|
return { profiles, errors, warnings };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import type {
|
|||||||
ThumbnailConfig,
|
ThumbnailConfig,
|
||||||
ConversionProgress,
|
ConversionProgress,
|
||||||
CodecType,
|
CodecType,
|
||||||
CodecChoice,
|
|
||||||
StreamingFormat,
|
StreamingFormat,
|
||||||
StreamingFormatChoice,
|
|
||||||
HardwareAccelerationOption,
|
HardwareAccelerationOption,
|
||||||
HardwareAccelerator,
|
HardwareAccelerator,
|
||||||
HardwareEncoderInfo,
|
HardwareEncoderInfo,
|
||||||
@@ -43,8 +41,8 @@ export async function convertToDash(
|
|||||||
segmentDuration = 2,
|
segmentDuration = 2,
|
||||||
profiles: userProfiles,
|
profiles: userProfiles,
|
||||||
customProfiles,
|
customProfiles,
|
||||||
codec = 'auto',
|
codec = ['h264'],
|
||||||
format = 'auto',
|
formats = ['dash', 'hls'],
|
||||||
hardwareDecoder,
|
hardwareDecoder,
|
||||||
hardwareAccelerator,
|
hardwareAccelerator,
|
||||||
quality,
|
quality,
|
||||||
@@ -76,8 +74,8 @@ DASH Conversion Log
|
|||||||
Started: ${new Date().toISOString()}
|
Started: ${new Date().toISOString()}
|
||||||
Input: ${input}
|
Input: ${input}
|
||||||
Output: ${videoOutputDir}
|
Output: ${videoOutputDir}
|
||||||
Codec: ${codec}
|
Codec: ${Array.isArray(codec) ? codec.join(',') : codec}
|
||||||
Format: ${format}
|
Formats: ${formats?.join(',') || 'dash,hls'}
|
||||||
===========================================\n`;
|
===========================================\n`;
|
||||||
await writeFile(logFile, header, 'utf-8');
|
await writeFile(logFile, header, 'utf-8');
|
||||||
|
|
||||||
@@ -90,7 +88,7 @@ Format: ${format}
|
|||||||
userProfiles,
|
userProfiles,
|
||||||
customProfiles,
|
customProfiles,
|
||||||
codec,
|
codec,
|
||||||
format,
|
formats,
|
||||||
hardwareAccelerator,
|
hardwareAccelerator,
|
||||||
hardwareDecoder,
|
hardwareDecoder,
|
||||||
quality,
|
quality,
|
||||||
@@ -129,8 +127,8 @@ async function convertToDashInternal(
|
|||||||
segmentDuration: number,
|
segmentDuration: number,
|
||||||
userProfiles: VideoProfile[] | undefined,
|
userProfiles: VideoProfile[] | undefined,
|
||||||
customProfiles: string[] | undefined,
|
customProfiles: string[] | undefined,
|
||||||
codec: CodecChoice,
|
codec: CodecType | CodecType[],
|
||||||
format: StreamingFormatChoice,
|
formats: StreamingFormat[] | undefined,
|
||||||
hardwareAccelerator: HardwareAccelerationOption | undefined,
|
hardwareAccelerator: HardwareAccelerationOption | undefined,
|
||||||
hardwareDecoder: HardwareAccelerationOption | undefined,
|
hardwareDecoder: HardwareAccelerationOption | undefined,
|
||||||
quality: DashConvertOptions['quality'],
|
quality: DashConvertOptions['quality'],
|
||||||
@@ -175,12 +173,9 @@ async function convertToDashInternal(
|
|||||||
|
|
||||||
const av1HardwareAvailable = hardwareEncoders.some(info => info.av1Encoder);
|
const av1HardwareAvailable = hardwareEncoders.some(info => info.av1Encoder);
|
||||||
|
|
||||||
let wantH264 = codec === 'h264' || codec === 'auto';
|
const codecList = Array.isArray(codec) ? codec : [codec];
|
||||||
let wantAv1 = codec === 'av1' || codec === 'auto';
|
const wantH264 = codecList.includes('h264');
|
||||||
|
const wantAv1 = codecList.includes('av1');
|
||||||
if (codec === 'auto' && !av1HardwareAvailable) {
|
|
||||||
wantAv1 = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selected, h264Encoder, av1Encoder, warnings: accelWarnings } = selectHardwareEncoders(
|
const { selected, h264Encoder, av1Encoder, warnings: accelWarnings } = selectHardwareEncoders(
|
||||||
hardwareEncoders,
|
hardwareEncoders,
|
||||||
@@ -200,7 +195,7 @@ async function convertToDashInternal(
|
|||||||
hardwareDecoder || 'auto'
|
hardwareDecoder || 'auto'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (codec === 'av1' && !av1HardwareAvailable) {
|
if (wantAv1 && !av1HardwareAvailable) {
|
||||||
console.warn('⚠️ AV1 hardware encoder not detected. AV1 will use CPU encoder (slow).');
|
console.warn('⚠️ AV1 hardware encoder not detected. AV1 will use CPU encoder (slow).');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,10 +204,7 @@ async function convertToDashInternal(
|
|||||||
if (wantAv1) codecsSelected.push('av1');
|
if (wantAv1) codecsSelected.push('av1');
|
||||||
if (codecsSelected.length === 0) codecsSelected.push('h264');
|
if (codecsSelected.length === 0) codecsSelected.push('h264');
|
||||||
|
|
||||||
const formatsSelected: StreamingFormat[] = [];
|
const formatsSelected: StreamingFormat[] = formats && formats.length > 0 ? Array.from(new Set(formats)) : ['dash', 'hls'];
|
||||||
if (format === 'dash' || format === 'auto') formatsSelected.push('dash');
|
|
||||||
if (format === 'hls' || format === 'auto') formatsSelected.push('hls');
|
|
||||||
if (formatsSelected.length === 0) formatsSelected.push('dash');
|
|
||||||
|
|
||||||
// Select profiles
|
// Select profiles
|
||||||
let profiles: VideoProfile[];
|
let profiles: VideoProfile[];
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ export type {
|
|||||||
VideoMetadata,
|
VideoMetadata,
|
||||||
VideoOptimizations,
|
VideoOptimizations,
|
||||||
CodecType,
|
CodecType,
|
||||||
CodecChoice,
|
|
||||||
StreamingFormat,
|
StreamingFormat,
|
||||||
StreamingFormatChoice,
|
|
||||||
HardwareAccelerator,
|
HardwareAccelerator,
|
||||||
HardwareAccelerationOption,
|
HardwareAccelerationOption,
|
||||||
HardwareEncoderInfo,
|
HardwareEncoderInfo,
|
||||||
|
|||||||
@@ -8,16 +8,6 @@ export type CodecType = 'av1' | 'h264';
|
|||||||
*/
|
*/
|
||||||
export type StreamingFormat = 'dash' | 'hls';
|
export type StreamingFormat = 'dash' | 'hls';
|
||||||
|
|
||||||
/**
|
|
||||||
* Пользовательский выбор кодека (auto = h264 + av1 при наличии HW)
|
|
||||||
*/
|
|
||||||
export type CodecChoice = CodecType | 'auto';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Пользовательский выбор форматов (auto = dash + hls)
|
|
||||||
*/
|
|
||||||
export type StreamingFormatChoice = StreamingFormat | 'auto';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Тип аппаратного ускорителя
|
* Тип аппаратного ускорителя
|
||||||
*/
|
*/
|
||||||
@@ -85,11 +75,11 @@ export interface DashConvertOptions {
|
|||||||
/** Custom resolution profiles as strings (e.g., ['360p', '480p', '720p@60']) */
|
/** Custom resolution profiles as strings (e.g., ['360p', '480p', '720p@60']) */
|
||||||
customProfiles?: string[];
|
customProfiles?: string[];
|
||||||
|
|
||||||
/** Video codec selection: h264, av1, or auto (default: auto = h264 + AV1 if HW) */
|
/** Video codec selection: one or multiple (default: ['h264']) */
|
||||||
codec?: CodecChoice;
|
codec?: CodecType | CodecType[];
|
||||||
|
|
||||||
/** Streaming formats: dash, hls, or auto (default: auto = оба) */
|
/** Streaming formats: list (default: ['dash','hls']) */
|
||||||
format?: StreamingFormatChoice;
|
formats?: StreamingFormat[];
|
||||||
|
|
||||||
/** Предпочитаемый аппаратный ускоритель (auto по умолчанию) */
|
/** Предпочитаемый аппаратный ускоритель (auto по умолчанию) */
|
||||||
hardwareAccelerator?: HardwareAccelerationOption;
|
hardwareAccelerator?: HardwareAccelerationOption;
|
||||||
|
|||||||
Reference in New Issue
Block a user