| _id: video_processing |
| author: Anton Breslavskii | https://github.com/breslavsky |
| description: Using FFmpeg process videos |
| readme: Fix ffmpeg fluent |
| title: en=Video processing;ru=Работа с видео |
| url: https://huggingface.co/PiperMy/Node-Packages/resolve/main/video_processing.yaml |
| version: 3 |
| nodes: |
| split_video_by_frames: |
| _id: split_video_by_frames |
| arrange: |
| x: 120 |
| y: 120 |
| category: |
| _id: video_processing |
| title: "title: en=Video processing;ru=Обработка видео" |
| environment: {} |
| inputs: |
| video: |
| order: 1 |
| title: en=Video;ru=Видео |
| type: video |
| required: true |
| fps: |
| order: 2 |
| title: en=Frame rate;ru=Частота кадров |
| type: integer |
| min: 1 |
| max: 60 |
| step: 1 |
| default: 12 |
| outputs: |
| frames: |
| title: en=Frames;ru=Кадры |
| type: image[] |
| package: video_processing |
| script: |- |
| export async function run({ inputs }) { |
| const { video, fps } = inputs; |
| |
| const { FatalError, NextNode } = DEFINITIONS; |
| const ffmpeg = require('fluent-ffmpeg'); |
| const path = require('path'); |
| const fs = require('fs/promises'); |
| const { fileTypeFromBuffer } = require('file-type'); |
|
|
| const frames = []; |
|
|
| await useTempFolder(async (tmpFolder) => { |
|
|
| const { data } = await download(video); |
| const { ext } = await fileTypeFromBuffer(data); |
| const videoPath = path.join(tmpFolder, `source.${ext}`); |
| await fs.writeFile(videoPath, data); |
|
|
| await new Promise((resolve, reject) => { |
| ffmpeg(videoPath) |
| .outputOptions([ |
| `-r ${fps || 12}`, |
| '-q:v 1' |
| ]) |
| .output(`${tmpFolder}/%05d.jpg`) |
| .on('start', (cmd) => console.log(cmd)) |
| .on('end', () => resolve()) |
| .on('error', (err) => reject(new FatalError(err))) |
| .run(); |
| }); |
|
|
| const files = await fs.readdir(tmpFolder); |
| for (const file of files) { |
| if (file.endsWith('jpg')) { |
| frames.push(await fs.readFile(path.join(tmpFolder, file))); |
| } |
| } |
|
|
| }); |
|
|
| return NextNode.from({ outputs: { frames } }); |
| } |
| source: catalog |
| title: en=Split video by frames;ru=Разбить видео по кадрам |
| version: 1 |
| join_frames_to_video: |
| _id: join_frames_to_video |
| arrange: |
| x: 480 |
| y: 120 |
| category: |
| _id: video_processing |
| title: "title: en=Video processing;ru=Обработка видео" |
| execution: deferred |
| inputs: |
| frames: |
| order: 1 |
| title: en=Frames;ru=Кадры |
| type: image[] |
| required: true |
| fps: |
| order: 2 |
| title: en=Frame rate;ru=Частота кадров |
| type: integer |
| min: 1 |
| max: 60 |
| step: 1 |
| default: 12 |
| outputs: |
| video: |
| title: en=Video;ru=Видео |
| type: video |
| package: video_processing |
| script: |- |
| export async function run({ inputs }) { |
| const { frames, fps = 12 } = inputs; |
| |
| const { FatalError, NextNode } = DEFINITIONS; |
| const ffmpeg = require('fluent-ffmpeg'); |
| const path = require('path'); |
| const fs = require('fs/promises'); |
|
|
| let output; |
|
|
| await useTempFolder(async (tmpFolder) => { |
|
|
| const buffers = (await Promise.all(frames.map(frame => download(frame)))).map(({ data }) => data); |
| for (let i = 0; i < buffers.length; i++) { |
| const fileName = path.join(tmpFolder, `${String(i).padStart(5, '0')}.jpg`); |
| await fs.writeFile(fileName, buffers[i]); |
| } |
|
|
| const outputPath = path.join(tmpFolder, 'output.mp4'); |
|
|
| await new Promise((resolve, reject) => { |
| ffmpeg() |
| .input(path.join(tmpFolder, '%05d.jpg')) |
| .inputFPS(fps) |
| .outputFPS(fps) |
| .outputOptions([ |
| '-c:v libx264', |
| '-pix_fmt yuv420p', |
| '-crf 23' |
| ]) |
| .output(outputPath) |
| .on('start', cmd => console.log(cmd)) |
| .on('end', resolve) |
| .on('error', err => reject(new FatalError(err))) |
| .run(); |
| }); |
|
|
| output = await fs.readFile(outputPath); |
| }); |
|
|
| return NextNode.from({ outputs: { video: output } }); |
| } |
| source: catalog |
| title: en=Join frames to video;ru=Собрать кадры в видео |
| version: 1 |
|
|