Skip to content

Instantly share code, notes, and snippets.

@mikhin
Created June 19, 2024 10:02
Show Gist options
  • Save mikhin/3abf12ab5a9965f167197d239cf275d8 to your computer and use it in GitHub Desktop.
Save mikhin/3abf12ab5a9965f167197d239cf275d8 to your computer and use it in GitHub Desktop.
generate many random reels from footage
const fs = require("fs-extra");
const path = require("path");
const ffmpeg = require("fluent-ffmpeg");
const bpm = 100; // Измените это значение на ваш BPM
const beatsPerSegment = 2; // Количество битов на каждый сегмент (1 для каждого бита, 2 для каждого такта и т.д.)
const footageFolder = "../footage"; // Путь к папке с футажами
const musicFile = "..//music.wav"; // Путь к музыкальному треку
// const outputWidth = 1920; // Ширина вывода
// const outputHeight = 1080; // Высота вывода
const outputFolder = "./output";
const numberOfVideos = 30; // Количество видео, которое нужно сгенерировать
const beatsPerVideo = 32; // Количество битов на каждое видео. Если undefined, то количество битов будет равно длительности музыки
async function main() {
// Проверка существования папки вывода
if (!fs.existsSync(outputFolder)) {
fs.mkdirSync(outputFolder);
}
// Проверка существования папки с футажами
if (!fs.existsSync(footageFolder)) {
console.error(`Папка с футажами не существует: ${footageFolder}`);
process.exit(1);
}
// Проверка существования музыкального трека
if (!fs.existsSync(musicFile)) {
console.error(`Музыкальный файл не существует: ${musicFile}`);
process.exit(1);
}
// Расчет длительности одного сегмента
const secondsPerBeat = 60 / bpm;
const secondsPerSegment = secondsPerBeat * beatsPerSegment;
console.log(`Длительность одного сегмента: ${secondsPerSegment} секунд`);
// Получение списка всех футажей и их сортировка
let footageFiles = fs
.readdirSync(footageFolder)
.filter((file) => file.endsWith(".mp4"))
.sort();
// Фильтрация видео по длительности
footageFiles = await Promise.all(
footageFiles.map(async (file) => {
const filePath = path.join(footageFolder, file);
const duration = await getVideoDuration(filePath);
return duration >= secondsPerSegment ? filePath : null;
})
);
footageFiles = footageFiles.filter((file) => file !== null);
// Проверка, есть ли футажи после фильтрации
if (footageFiles.length === 0) {
console.error(
`Нет футажей длительностью больше ${secondsPerSegment} секунд в папке ${footageFolder}`
);
process.exit(1);
}
console.log(`Найдено подходящих футажей: ${footageFiles.length}`);
for (let videoIndex = 0; videoIndex < numberOfVideos; videoIndex++) {
const videoOutputFolder = path.join(outputFolder, `video${videoIndex + 1}`);
if (!fs.existsSync(videoOutputFolder)) {
fs.mkdirSync(videoOutputFolder);
}
// Расчет количества сегментов для текущего видео
const segmentCount = Math.floor(beatsPerVideo / beatsPerSegment);
console.log(
`Количество сегментов для видео ${videoIndex + 1}: ${segmentCount}`
);
// Создание уникальных сегментов из случайных футажей
const segments = [];
const usedFootageIndexes = new Set();
for (let i = 0; i < segmentCount; i++) {
let footageIndex;
do {
footageIndex = Math.floor(Math.random() * footageFiles.length);
} while (usedFootageIndexes.has(footageIndex));
usedFootageIndexes.add(footageIndex);
const footageFile = footageFiles[footageIndex];
const segmentFile = path.join(videoOutputFolder, `segment${i}.mp4`);
await new Promise((resolve, reject) => {
console.log(
`Начало обработки сегмента ${i} из файла ${footageFile}...`
);
ffmpeg()
.input(footageFile)
.output(segmentFile)
.outputOptions([
`-ss 0`,
`-t ${secondsPerSegment}`,
`-r 30`, // установка фреймрейта для предотвращения зависаний
`-an`, // отключить аудио
])
.on("progress", (progress) => {
console.log(`Сегмент ${i} прогресс: ${progress.percent}%`);
})
.on("end", () => {
console.log(`Сегмент ${i} завершен`);
segments.push(segmentFile);
resolve();
})
.on("error", (err) => {
console.error(`Ошибка при обработке сегмента ${i}: ${err}`);
reject(err);
})
.run();
});
}
// Создание файла со списком сегментов
const concatList = segments
.map((file) => `file '${path.resolve(file)}'`)
.join("\n");
fs.writeFileSync(
path.join(videoOutputFolder, "concat_list.txt"),
concatList
);
// Объединение всех сегментов в одно видео без аудио
await new Promise((resolve, reject) => {
const concatListPath = path.join(videoOutputFolder, "concat_list.txt");
ffmpeg()
.input(concatListPath)
.inputOptions(["-f concat", "-safe 0"])
.outputOptions([
"-c:v libx264", // Перекодирование в один кодек для консистенции
"-pix_fmt yuv420p", // Обеспечение совместимости с большинством плееров
"-fflags +genpts", // генерация временных меток
])
.output(path.join(videoOutputFolder, "output_without_audio.mp4"))
.on("start", (commandLine) => {
console.log("ffmpeg command: " + commandLine);
})
.on("end", () => {
console.log("Видео без аудио создано: output_without_audio.mp4");
resolve();
})
.on("error", (err) => {
console.error("Ошибка при создании видео без аудио:", err);
reject(err);
})
.run();
});
// Добавление аудио к видео и обрезка видео до длины аудио
await new Promise((resolve, reject) => {
console.log("Начало добавления аудио...");
ffmpeg()
.input(path.join(videoOutputFolder, "output_without_audio.mp4"))
.input(musicFile)
.outputOptions([
"-c:v copy",
"-c:a aac",
"-strict experimental",
"-shortest", // Вырезает видео до длины аудио
])
.output(
path.join(videoOutputFolder, `final_video${videoIndex + 1}.mp4`)
)
.on("progress", (progress) => {
console.log(`Добавление аудио прогресс: ${progress.percent}%`);
})
.on("end", () => {
console.log(
`Добавление аудио завершено: video${videoIndex + 1}/final_video${
videoIndex + 1
}.mp4`
);
resolve();
})
.on("error", (err) => {
console.error("Ошибка при добавлении аудио:", err);
reject(err);
})
.run();
});
console.log(
`Финальное видео с аудио создано: video${videoIndex + 1}/final_video${
videoIndex + 1
}.mp4`
);
}
}
// Функция для получения длительности видео
function getVideoDuration(file) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(file, (err, metadata) => {
if (err) {
reject(err);
} else {
resolve(metadata.format.duration);
}
});
});
}
main().catch((err) => {
console.error("Ошибка:", err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment