compress-videos-in-dir.ts to compress my videos lib, chatgpt-helped

master
Grief 2025-09-14 17:08:13 +01:00
parent 76e3f8917c
commit b6065abbac
2 changed files with 122 additions and 0 deletions

View File

@ -0,0 +1,121 @@
#!/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<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 => {
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);
}

View File

@ -2,6 +2,7 @@
"compilerOptions": {
"allowImportingTsExtensions": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "nodenext"
}
}