sync
This commit is contained in:
18
README.md
18
README.md
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Поддерживаемые разрешения
|
### Поддерживаемые разрешения
|
||||||
|
|||||||
30
bin/cli.js
30
bin/cli.js
File diff suppressed because one or more lines are too long
10
package.json
10
package.json
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
55
src/cli.ts
55
src/cli.ts
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user