Skip to content

Instantly share code, notes, and snippets.

@stephenhandley
Last active July 25, 2020 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephenhandley/3bcf4d6ea8450d7281a2626aaa3130f6 to your computer and use it in GitHub Desktop.
Save stephenhandley/3bcf4d6ea8450d7281a2626aaa3130f6 to your computer and use it in GitHub Desktop.
hacky script for generating ffmpeg command to tile videos
ffmpeg -i 0.mp4 -i 1.mp4 -i 2.mp4 -i 3.mp4 -i 4.mp4 -i 5.mp4 -i 6.mp4 -i 7.mp4 -i 8.mp4 -i 9.mp4 -i 10.mp4 -i 11.mp4 -i 12.mp4 -i 13.mp4 -i 14.mp4 -i 15.mp4 -i 16.mp4 -i 17.mp4 -i 18.mp4 -i 19.mp4 -i 20.mp4 -i 21.mp4 -i 22.mp4 -i 23.mp4 -i 24.mp4 -t 00:12:16 -filter_complex "amix=inputs=25:duration=longest; [0:v] setpts=PTS-STARTPTS, scale=768x432 [_00]; [1:v] setpts=PTS-STARTPTS, scale=768x432 [_01]; [2:v] setpts=PTS-STARTPTS, scale=768x432 [_02]; [3:v] setpts=PTS-STARTPTS, scale=768x432 [_03]; [4:v] setpts=PTS-STARTPTS, scale=768x432 [_04]; [5:v] setpts=PTS-STARTPTS, scale=768x432 [_10]; [6:v] setpts=PTS-STARTPTS, scale=768x432 [_11]; [7:v] setpts=PTS-STARTPTS, scale=768x432 [_12]; [8:v] setpts=PTS-STARTPTS, scale=768x432 [_13]; [9:v] setpts=PTS-STARTPTS, scale=768x432 [_14]; [10:v] setpts=PTS-STARTPTS, scale=768x432 [_20]; [11:v] setpts=PTS-STARTPTS, scale=768x432 [_21]; [12:v] setpts=PTS-STARTPTS, scale=768x432 [_22]; [13:v] setpts=PTS-STARTPTS, scale=768x432 [_23]; [14:v] setpts=PTS-STARTPTS, scale=768x432 [_24]; [15:v] setpts=PTS-STARTPTS, scale=768x432 [_30]; [16:v] setpts=PTS-STARTPTS, scale=768x432 [_31]; [17:v] setpts=PTS-STARTPTS, scale=768x432 [_32]; [18:v] setpts=PTS-STARTPTS, scale=768x432 [_33]; [19:v] setpts=PTS-STARTPTS, scale=768x432 [_34]; [20:v] setpts=PTS-STARTPTS, scale=768x432 [_40]; [21:v] setpts=PTS-STARTPTS, scale=768x432 [_41]; [22:v] setpts=PTS-STARTPTS, scale=768x432 [_42]; [23:v] setpts=PTS-STARTPTS, scale=768x432 [_43]; [24:v] setpts=PTS-STARTPTS, scale=768x432 [_44]; nullsrc=size=3840x2160 [tmp0]; [tmp0][_00] overlay=x=0:y=0 [tmp1]; [tmp1][_01] overlay=x=0:y=432 [tmp2]; [tmp2][_02] overlay=x=0:y=864 [tmp3]; [tmp3][_03] overlay=x=0:y=1296 [tmp4]; [tmp4][_04] overlay=x=0:y=1728 [tmp5]; [tmp5][_10] overlay=x=768:y=0 [tmp6]; [tmp6][_11] overlay=x=768:y=432 [tmp7]; [tmp7][_12] overlay=x=768:y=864 [tmp8]; [tmp8][_13] overlay=x=768:y=1296 [tmp9]; [tmp9][_14] overlay=x=768:y=1728 [tmp10]; [tmp10][_20] overlay=x=1536:y=0 [tmp11]; [tmp11][_21] overlay=x=1536:y=432 [tmp12]; [tmp12][_22] overlay=x=1536:y=864 [tmp13]; [tmp13][_23] overlay=x=1536:y=1296 [tmp14]; [tmp14][_24] overlay=x=1536:y=1728 [tmp15]; [tmp15][_30] overlay=x=2304:y=0 [tmp16]; [tmp16][_31] overlay=x=2304:y=432 [tmp17]; [tmp17][_32] overlay=x=2304:y=864 [tmp18]; [tmp18][_33] overlay=x=2304:y=1296 [tmp19]; [tmp19][_34] overlay=x=2304:y=1728 [tmp20]; [tmp20][_40] overlay=x=3072:y=0 [tmp21]; [tmp21][_41] overlay=x=3072:y=432 [tmp22]; [tmp22][_42] overlay=x=3072:y=864 [tmp23]; [tmp23][_43] overlay=x=3072:y=1296 [tmp24]; [tmp24][_44] overlay=x=3072:y=1728 " -c:v libx264 ./output.mp4
// putting this here until i cmdargs it and make it easier to use
const Fs = require('fs');
function aspect (dims) {
return (dims[0] / dims[1]);
}
function size (dims) {
return dims.join('x')
}
// TODO: convert to be inputs
const count = 25;
const input_dims = [1920, 1080];
const ext = '.mp4';
const max_time = '00:12:16';
const output_dims = [3840, 2160];
const dim = Math.sqrt(count);
const square = ((count % dim) === 0);
if (!square) {
throw new Error('Not square');
}
const input_aspect = aspect(input_dims);
const output_aspect = aspect(output_dims);
if (input_aspect !== output_aspect) {
throw new Error(`Aspect ratio mismatch. ${input_aspect}}=>${output_aspect}`);
}
function xy (i) {
return [Math.floor(i / dim), i % dim];
}
// 1. Build input section
let inputs = [];
for (let i = 0; i < count; i++) {
inputs.push(`-i ${i}${ext}`);
}
inputs = inputs.join(' ');
// 2. time
const time = `-t ${max_time}`;
// 3. filter
let filter = [];
// 3a. audio
const amix = `amix=inputs=${count}:duration=longest`;
filter.push(amix);
// 3b. scaling
const scaled_dims = output_dims.map((d)=> d / dim);
for (let i = 0; i < count; i++) {
const [x, y] = xy(i);
const scale = `[${i}:v] setpts=PTS-STARTPTS, scale=${size(scaled_dims)} [_${x}${y}]`;
filter.push(scale);
}
// 3c. sections
const [scaled_x, scaled_y] = scaled_dims;
for (let i = 0; i < count; i++) {
const first = (i === 0);
const last = (i === (count - 1));
if (first) {
filter.push(`nullsrc=size=${size(output_dims)} [tmp0]`);
}
const [x, y] = xy(i);
const start_x = x * scaled_x;
const start_y = y * scaled_y;
let tmp_out = '';
if (!last) {
tmp_out = `[tmp${i + 1}]`;
}
filter.push(`[tmp${i}][_${x}${y}] overlay=x=${start_x}:y=${start_y} ${tmp_out}`);
}
console.log(filter);
filter = `-filter_complex "${filter.join('; ')}"`;
const output = `-c:v libx264 ./output.mp4`;
const command = [
'ffmpeg',
inputs,
time,
filter,
output
].join(' ');
console.log(command);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment