Skip to content

Instantly share code, notes, and snippets.

@Moondarker
Last active April 7, 2022 18:16
Show Gist options
  • Save Moondarker/430bfac0b48b8f29d6b3633ce97f17b0 to your computer and use it in GitHub Desktop.
Save Moondarker/430bfac0b48b8f29d6b3633ce97f17b0 to your computer and use it in GitHub Desktop.
Barebones Node.JS + ffmpeg multithreaded image processing scripts used for my r/place zoomed in timelapses
~~used for name only~~
rem Last stage tool after rplace_crop.js
ffmpeg -r 30 -start_number 0 -i out/crop/frame_%03d.png -c:v libx264 -vf fps=60,scale=1024:1024:flags=neighbor -pix_fmt yuv420p result.mp4
/*
Was used to create r/place timelapses
Compiles stuff prepared by rplace_subsector_timestamper.js in one video
Raw data source: https://place.thatguyalex.com/
*/
const { spawn } = require('child_process')
const fs = require('fs')
function processSubsector (sector, subsector) {
return new Promise(resolve => {
const files = fs.readdirSync(`out/${sector}/${subsector}`)
fs.writeFileSync(`out/${sector}/${sector}-${subsector}_filelist.txt`, files.sort((a, b) => parseInt(a.match(/(\d+)/)[0]) - parseInt(b.match(/(\d+)/)[0])).map(f => `file '${subsector}/${f}'`).join('\n'))
const ffmpeg = spawn('ffmpeg', ['-n', '-r', '30', '-f', 'concat', '-i', `out/${sector}/${sector}-${subsector}_filelist.txt`, '-c:v', 'libx264', '-vf', 'fps=60,scale=1000:1064:flags=neighbor', '-pix_fmt', 'yuv420p', `out/results/${sector}-${subsector}_timestamped.mp4`])
ffmpeg.stdout.on('data', msg => {})
ffmpeg.stderr.on('data', msg => {})
ffmpeg.on('close', code => {
resolve(code)
})
})
}
async function main () {
for (let sector = 1; sector <= 16; sector += 1) {
for (let subsector = 1; subsector <= 16; subsector += 1) {
const retCode = await processSubsector(sector, subsector)
if (retCode !== 0 && retCode !== 1) console.log(`Return code ${retCode}`)
}
console.log(`Sector ${sector} done`)
}
}
main()
/*
Was used to create r/place timelapses
Crops (line 13, argument goes as follows: width:height:x:y) and numbers images incrementally
Can be used after rplace_merge.js or separately
Raw data source: https://place.thatguyalex.com/
*/
const { spawn } = require('child_process')
const fs = require('fs')
const chunkSize = 100
function processImage (filename, id) {
return new Promise(resolve => {
const ls = spawn('ffmpeg', ['-n', '-i', `out/full/${filename}`, '-vf', 'crop=128:128:0:963', `out/crop/frame_${id}.png`])
ls.on('close', code => {
resolve(code)
})
})
}
async function chunkThread (chunk, threadId) {
let currentPos = 0
while (currentPos < chunk.length) {
const retCode = await processImage(chunk[currentPos], (chunkSize * threadId) + currentPos)
if (retCode !== 0 && retCode !== 1) console.log(`Return code ${retCode}`)
currentPos++
}
console.log(`Thread ${threadId} done`)
}
const srcArray = fs.readdirSync('out/full')
const srcArrayChunks = []
for (let i = 0; i < srcArray.length; i += chunkSize) {
srcArrayChunks[srcArrayChunks.length] = srcArray.slice(i, i + chunkSize)
}
srcArrayChunks.forEach((chunk, idx) => {
chunkThread(chunk, idx)
})
/*
Was used to create r/place timelapses
Stitches together two (or more - just adjust ffmpeg arguments) images
Use if your art was placed on/near the borders
First stage tool for rplace_crop.js
Raw data source: https://place.thatguyalex.com/
*/
const { spawn } = require('child_process')
const fs = require('fs')
function processImage (filename) {
return new Promise(resolve => {
const ls = spawn('ffmpeg', ['-n', '-i', `src/0/${filename}`, '-i', `src/2/${filename.replace('0-', '2-')}`, '-filter_complex', 'vstack', `out/full/${filename.replace('0-', '')}`])
ls.on('close', code => {
resolve(code)
})
})
}
async function chunkThread (chunk, threadId) {
let currentPos = 0
while (currentPos < chunk.length) {
const retCode = await processImage(chunk[currentPos])
if (retCode !== 0 && retCode !== 1) console.log(`Return code ${retCode}`)
currentPos++
}
console.log(`Thread ${threadId} done`)
}
const srcArray = fs.readdirSync('src/0')
const srcArrayChunks = []
const chunkSize = 100
for (let i = 0; i < srcArray.length; i += chunkSize) {
srcArrayChunks[srcArrayChunks.length] = srcArray.slice(i, i + chunkSize)
}
srcArrayChunks.forEach((chunk, idx) => {
chunkThread(chunk, idx)
})
/*
Was used to create r/place timelapses
Gets raw images, splits into sectors and subsectors, adds timestamps
First stage tool for rplace_compile_timestamped.js
IMPORTANT: Expects a font file named sp.ttf - I recommend using small_pixel.ttf
Raw data source: https://place.thatguyalex.com/
*/
const { spawn } = require('child_process')
const fs = require('fs')
const canvas = 0
const chunkSize = 250 // Make bigger if your PC can't handle so many processes
const sectors = [[[1, [0, 0]], [2, [500, 0]], [5, [0, 500]], [6, [500, 500]]], [[3, [0, 0]], [4, [500, 0]], [7, [0, 500]], [8, [500, 500]]],
[[9, [0, 0]], [10, [500, 0]], [13, [0, 500]], [14, [500, 500]]], [[11, [0, 0]], [12, [500, 0]], [15, [0, 500]], [16, [500, 500]]]]
function processImage (filename, id) {
return new Promise(resolve => {
const args = ['-n', '-i', `src/${canvas}/${filename}`]
sectors[canvas].forEach((sectorData, sectorId) => {
const sector = sectorData[0]
const sectorCoords = sectorData[1]
for (let subsector = 1; subsector <= 16; subsector++) {
args.push('-vf', `crop=125:125:${sectorCoords[0] + 125 * ((subsector - 1) % 4)}:${sectorCoords[1] + 125 * Math.floor((subsector - 1) / 4)},pad=125:133:0:0:black,drawtext=text='${(new Date(parseInt(filename.substring(2, 12)) * 1000)).toGMTString().substring(5).replaceAll(':', '\\:')}':fontcolor=white:fontfile=sp.ttf:fontsize=8:x=1:y=126`, `out/${sector}/${subsector}/${id}_${filename}`)
}
})
const ffmpeg = spawn('ffmpeg', args)
ffmpeg.on('error', err => {
console.err(`ffmpeg #${id}: ${err}`)
})
ffmpeg.on('close', code => {
resolve(code)
})
})
}
async function chunkThread (chunk, threadId) {
let currentPos = 0
while (currentPos < chunk.length) {
const retCode = await processImage(chunk[currentPos], (chunkSize * threadId) + currentPos)
if (retCode !== 0 && retCode !== 1) console.log(`Return code ${retCode}`)
currentPos++
}
console.log(`Thread ${threadId} done`)
}
const srcArray = fs.readdirSync(`src/${canvas}`)
const srcArrayChunks = []
for (let i = 0; i < srcArray.length; i += chunkSize) {
srcArrayChunks[srcArrayChunks.length] = srcArray.slice(i, i + chunkSize)
}
srcArrayChunks.forEach((chunk, idx) => {
chunkThread(chunk, idx)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment