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 npm install -g @grom13/dvc-cli
dvc video.mp4 dvc-cli video.mp4
``` ```
**Системные требования:** **Системные требования:**
@@ -32,7 +32,7 @@ brew install ffmpeg gpac
## Параметры CLI ## Параметры CLI
```bash ```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 ```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 для игровых стримов # Высокий FPS для игровых стримов
dvc video.mp4 -r 720@60,1080@60 dvc-cli video.mp4 -r 720@60,1080@60
# Постер с 5-й секунды # Постер с 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", "name": "@grom13/dvc-cli",
"version": "0.1.0", "version": "0.1.3",
"description": "Fast DASH video converter with NVENC acceleration and thumbnail sprites", "description": "Fast DASH video converter with NVENC acceleration and thumbnail sprites",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"bin": { "bin": {
"dvc": "./bin/cli.js" "dvc-cli": "./bin/cli.js"
}, },
"exports": { "exports": {
".": { ".": {
@@ -44,12 +44,12 @@
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/grom13/dvc-cli.git" "url": "https://gromlab.ru/gromov/dvc-cli.git"
}, },
"bugs": { "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": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
}, },

View File

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

View File

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