#!/usr/bin/env node import {spawn} from 'child_process'; import {readdir} from 'fs/promises'; import {createInterface} from 'node:readline'; 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 { 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(resolve => { 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}`); } else { console.error(`❌ Failed with code ${code}`); } resolve(); }); ffmpeg.stderr.on('data', (data) => { console.error(`Error: ${data}`); }); } catch (error) { console.error(`Error: ${error}`); } }); } const files = await readdir('.'); const mp4Files = files.filter(file => file.match(/^VID_.*\.mp4$/)); const filesCount = mp4Files.length; for (let i = 0; i < filesCount; i++){ const file = mp4Files[i]; const outputFile = `HEVC_${file.slice(4)}`; console.log(`\nProcessing: ${file} [${i + 1}/${filesCount}]`); await compressVideo(file, outputFile); }