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": {
|
"compilerOptions": {
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
"moduleResolution": "nodenext"
|
"moduleResolution": "nodenext"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue