compress-videos-in-dir.ts to compress my videos lib, chatgpt-helped
parent
76e3f8917c
commit
b6065abbac
|
@ -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);
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue