Created
June 19, 2024 10:02
-
-
Save mikhin/3abf12ab5a9965f167197d239cf275d8 to your computer and use it in GitHub Desktop.
generate many random reels from footage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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