scripts/bin/compress-videos-in-dir.ts

130 lines
3.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
import {spawn} from 'child_process';
import {readdir} from 'fs/promises';
import {createInterface} from 'node:readline';
import {runInBackground} from '../lib/ai-generated.ts';
import { extname } from 'node:path';
function formatTime(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
async function getDuration(inputFile: string): Promise<number> {
return new Promise((resolve, reject) => {
const ffprobe = spawn('ffprobe', [
'-v', 'quiet',
'-show_entries', 'format=duration',
'-of', 'csv=p=0',
inputFile
]);
let output = '';
ffprobe.stdout.on('data', (data) => {
output += data.toString();
});
ffprobe.on('close', (code) => {
if (code === 0) {
const duration = parseFloat(output.trim());
resolve(isNaN(duration) ? 0 : duration);
} else {
reject(new Error(`ffprobe failed with code ${code}`));
}
});
});
}
async function compressVideo(inputFile: string, outputFile: string) {
const duration = await getDuration(inputFile);
return new Promise<void>((resolve, reject) => {
try {
const ffmpeg = spawn('ffmpeg', [
'-hwaccel', 'cuda',
'-hide_banner',
'-loglevel', 'error',
'-progress', 'pipe:1',
'-i', inputFile,
// '-c:v', 'h264_nvenc',
// '-cq', '23',
'-c:v', 'hevc_nvenc',
'-cq', '28',
'-preset', 'p4',
'-c:a', 'copy',
outputFile
], {
stdio: ['pipe', 'pipe', 'pipe']
});
const rl = createInterface({
input: ffmpeg.stdout
});
const startTime = Date.now();
rl.on('line', (line: string) => {
if (line.startsWith('out_time_us=')) {
const currentUs = parseInt(line.split('=')[1]);
const currentSec = Math.floor(currentUs / 1000000);
if (duration > 0) {
const percent = Math.min(100, Math.floor((currentSec * 100) / duration));
// Вычисляем оставшееся время
const elapsedMs = Date.now() - startTime;
const elapsedSec = Math.floor(elapsedMs / 1000);
let remainingStr = '';
if (percent > 0 && elapsedSec > 3) { // Показываем только после 3 секунд для точности
const totalEstimatedSec = Math.floor((elapsedSec * 100) / percent);
const remainingSec = Math.max(0, totalEstimatedSec - elapsedSec);
remainingStr = ` | ETA: ${formatTime(remainingSec)}`;
}
process.stdout.write(`\r${percent.toString().padStart(3)}%${remainingStr}`);
}
}
});
ffmpeg.on('close', (code) => {
console.log(); // Новая строка в конце
if (code === 0) {
console.log(`✅ Completed: ${outputFile}`);
resolve();
} else {
console.error(`❌ Failed with code ${code}`);
reject();
}
});
ffmpeg.stderr.on('data', (data) => {
console.error(`Error: ${data}`);
});
} catch (error) {
console.error(`Error: ${error}`);
}
});
}
const files = (await readdir('.')).filter(name => {
return (!name.startsWith('HEVC') && ['mp4', 'mov'].includes(extname(name).toLowerCase().slice(1)))
});
let filesCount = files.length;
for (let i = 0; i < filesCount; i++){
const file = files[i];
// const outputFile = `HEVC_${file.slice(4)}`;
const outputFile = `HEVC_${file}`;
console.log(`\nProcessing: ${file} [${i + 1}/${filesCount}]`);
await compressVideo(file, outputFile);
// runInBackground('identity', [file, outputFile])
}