This commit is contained in:
2025-11-09 13:24:10 +03:00
parent 8c61e0e9db
commit b843bdf897
6 changed files with 70 additions and 58 deletions

View File

@@ -12,7 +12,7 @@ npx @grom13/dvc-cli video.mp4
# Или глобальная установка
npm install -g @grom13/dvc-cli
dvc video.mp4
dvc-cli video.mp4
```
**Системные требования:**
@@ -32,7 +32,7 @@ brew install ffmpeg gpac
## Параметры CLI
```bash
dvc <input-video> [output-dir] [-r resolutions] [-p poster-timecode]
dvc-cli <input-video> [output-dir] [-r resolutions] [-p poster-timecode]
```
### Основные параметры
@@ -53,25 +53,25 @@ dvc <input-video> [output-dir] [-r resolutions] [-p poster-timecode]
```bash
# Базовая конвертация (результат в текущей папке)
dvc video.mp4
dvc-cli video.mp4
# Указать выходную директорию
dvc video.mp4 ./output
dvc-cli video.mp4 ./output
# Только выбранные разрешения
dvc video.mp4 -r 720,1080,1440
dvc-cli video.mp4 -r 720,1080,1440
# Высокий FPS для игровых стримов
dvc video.mp4 -r 720@60,1080@60
dvc-cli video.mp4 -r 720@60,1080@60
# Постер с 5-й секунды
dvc video.mp4 -p 5
dvc-cli video.mp4 -p 5
# Постер в формате времени
dvc video.mp4 -p 00:01:30
dvc-cli video.mp4 -p 00:01:30
# Комбинация параметров
dvc video.mp4 ./output -r 720,1080@60,1440@60 -p 00:00:10
dvc-cli video.mp4 ./output -r 720,1080@60,1440@60 -p 00:00:10
```
### Поддерживаемые разрешения

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,12 @@
{
"name": "@grom13/dvc-cli",
"version": "0.1.0",
"version": "0.1.3",
"description": "Fast DASH video converter with NVENC acceleration and thumbnail sprites",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"dvc": "./bin/cli.js"
"dvc-cli": "./bin/cli.js"
},
"exports": {
".": {
@@ -44,12 +44,12 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/grom13/dvc-cli.git"
"url": "https://gromlab.ru/gromov/dvc-cli.git"
},
"bugs": {
"url": "https://github.com/grom13/dvc-cli/issues"
"url": "https://gromlab.ru/gromov/dvc-cli/issues"
},
"homepage": "https://github.com/grom13/dvc-cli#readme",
"homepage": "https://gromlab.ru/gromov/dvc-cli#readme",
"engines": {
"node": ">=18.0.0"
},

View File

@@ -4,59 +4,64 @@
* DASH Video Converter CLI
*
* Usage:
* dvc <input-video> [output-dir]
* dvc-cli <input-video> [output-dir] [-r resolutions] [-p poster-timecode]
*
* Example:
* dvc ./video.mp4 ./output
* dvc-cli ./video.mp4 ./output -r 720,1080
*/
import { convertToDash, checkFFmpeg, checkNvenc, checkMP4Box, getVideoMetadata } from './index';
import cliProgress from 'cli-progress';
import { statSync } from 'node:fs';
const input = process.argv[2];
const outputDir = process.argv[3] || '.'; // Текущая директория по умолчанию
// Parse optional -r or --resolutions argument
// Parse arguments
const args = process.argv.slice(2);
let customProfiles: string[] | undefined;
let posterTimecode: string | undefined;
const positionalArgs: string[] = [];
for (let i = 4; i < process.argv.length; i++) {
if (process.argv[i] === '-r' || process.argv[i] === '--resolutions') {
// First pass: extract flags and their values
for (let i = 0; i < args.length; i++) {
if (args[i] === '-r' || args[i] === '--resolutions') {
// Collect all arguments after -r until next flag or end
const profilesArgs: string[] = [];
for (let j = i + 1; j < process.argv.length; j++) {
for (let j = i + 1; j < args.length; j++) {
// Stop if we hit another flag (starts with -)
if (process.argv[j].startsWith('-')) {
if (args[j].startsWith('-')) {
break;
}
profilesArgs.push(process.argv[j]);
profilesArgs.push(args[j]);
i = j; // Skip these args in main loop
}
// If there's only one arg, it might contain commas: "720,1080"
// If there are multiple args, they might be: "720" "1080" or "720," "1080"
// Solution: join with comma, then split by comma/space
// Parse profiles
const joinedArgs = profilesArgs.join(',');
customProfiles = joinedArgs
.split(/[,\s]+/) // Split by comma or whitespace
.map(s => s.trim())
.filter(s => s.length > 0);
}
if (process.argv[i] === '-p' || process.argv[i] === '--poster') {
posterTimecode = process.argv[i + 1];
} else if (args[i] === '-p' || args[i] === '--poster') {
posterTimecode = args[i + 1];
i++; // Skip next arg
} else if (!args[i].startsWith('-')) {
// Positional argument
positionalArgs.push(args[i]);
}
}
// Extract positional arguments
const input = positionalArgs[0];
const outputDir = positionalArgs[1] || '.'; // Текущая директория по умолчанию
if (!input) {
console.error('❌ Usage: dvc <input-video> [output-dir] [-r resolutions] [-p poster-timecode]');
console.error('❌ Usage: dvc-cli <input-video> [output-dir] [-r resolutions] [-p poster-timecode]');
console.error('\nExamples:');
console.error(' dvc video.mp4');
console.error(' dvc video.mp4 ./output');
console.error(' dvc video.mp4 -r 360,480,720');
console.error(' dvc video.mp4 -r 720@60,1080@60,2160@60');
console.error(' dvc video.mp4 -p 00:00:05');
console.error(' dvc video.mp4 ./output -r 720,1080 -p 10');
console.error(' dvc-cli video.mp4');
console.error(' dvc-cli video.mp4 ./output');
console.error(' dvc-cli video.mp4 -r 360,480,720');
console.error(' dvc-cli video.mp4 -r 720@60,1080@60,2160@60');
console.error(' dvc-cli video.mp4 -p 00:00:05');
console.error(' dvc-cli video.mp4 ./output -r 720,1080 -p 10');
process.exit(1);
}

View File

@@ -1,7 +1,7 @@
import { join } from 'node:path';
import type { ThumbnailConfig } from '../types';
import { execFFmpeg, formatVttTime } from '../utils';
import { exists, readdir, unlink, rmdir } from 'node:fs/promises';
import { execFFmpeg, formatVttTime, ensureDir } from '../utils';
import { readdir, unlink, rmdir, writeFile } from 'node:fs/promises';
/**
* Generate poster image from video at specific timecode
@@ -45,7 +45,8 @@ export async function generateThumbnailSprite(
// Create temp directory for individual thumbnails
const tempDir = join(outputDir, '.thumbnails_temp');
await Bun.write(join(tempDir, '.keep'), '');
await ensureDir(tempDir);
await writeFile(join(tempDir, '.keep'), '');
// Generate individual thumbnails
const thumbnailPattern = join(tempDir, 'thumb_%04d.jpg');
@@ -95,7 +96,7 @@ export async function generateThumbnailSprite(
'thumbnails.jpg'
);
await Bun.write(vttPath, vttContent);
await writeFile(vttPath, vttContent);
// Clean up temp files
for (const file of thumbFiles) {

View File

@@ -1,10 +1,12 @@
import { mkdir, exists } from 'node:fs/promises';
import { mkdir, access, constants } from 'node:fs/promises';
/**
* Ensure directory exists
*/
export async function ensureDir(dirPath: string): Promise<void> {
if (!await exists(dirPath)) {
try {
await access(dirPath, constants.F_OK);
} catch {
await mkdir(dirPath, { recursive: true });
}
}