Skip to content

Instantly share code, notes, and snippets.

@KL13NT
Last active October 6, 2023 05:11
Show Gist options
  • Save KL13NT/1b96c8888b384272a426ebcdf825b9ae to your computer and use it in GitHub Desktop.
Save KL13NT/1b96c8888b384272a426ebcdf825b9ae to your computer and use it in GitHub Desktop.
A script to transform Animated Webp files into Webm files. Tested on Windows only. Depends on ffmpeg and libwebp. Make sure to add them to your PATH.
/**
* This script transforms animated webp files which are not widely supported yet
* to simple animated webm. Please state the source of your share this.
*
* # How this works
* This script depends on ffmpeg and libwebp provided by Google. Make sure
* they're in your PATH.
*
* The way this works is based off this answer https://askubuntu.com/a/1141049.
* It starts by extracting the input webp frames to a ./frames directory and,
* using webpmux, determines whether it's actually animated or not. If it is
* animated then it proceeds to use ffmpeg to create a webm based on the
* extracted frames. Cleanup is done after the script finishes successfully.
*
* It also takes a switch that determines whether you want to permanently delete
* the original animated webp file.
*
* libwebp: https://developers.google.com/speed/webp/docs/precompiled
* ffmpeg: https://ffmpeg.org/
*/
const child = require('child_process')
const fs = require('fs')
const path = require('path')
const util = require('util')
const exec = util.promisify(child.exec)
const terminateWithError = (error = '[fatal] error') => {
console.log(error)
process.exit(1)
}
const params = process.argv.slice(2)
if (!params[0]) terminateWithError('[fatal] need file name or absolute path')
const deleteOriginal = params[1] === 'true'
const filename = path.isAbsolute(params[0])
? path.basename(params[0])
: params[0]
if (deleteOriginal) console.log('[info] will delete original when successful')
const nameWithoutExt = filename.replace('.webp', '')
const frames = path.resolve(process.cwd(), 'frames')
if (fs.existsSync(frames)) fs.rmdirSync(frames, { recursive: true })
fs.mkdirSync(frames)
process.chdir('frames')
console.log('[info]', process.cwd())
console.log('[info]', `anim_dump ../${filename}`)
exec(`anim_dump ../${filename}`)
.then(() => {
process.chdir('..')
console.log('[info]', process.cwd())
const command = `webpmux -info ./${filename}`
console.log('[info]', command)
return exec(command)
})
.then(({ stdout, stderr }) => {
if (stderr) return Promise.reject(stderr)
const isAnimation = stdout.match(/Features present: animation/) !== null
if (!isAnimation) return Promise.reject('This is not an animated webp file')
const firstLine = stdout.match(/1:.+[\r]?\n/g)
if (!firstLine) return
const frameLength = firstLine[0].split(/\s+/g)[6]
const framerate = Math.round(1000 / frameLength) // frames/second
const dump = path.resolve(frames, 'dump_%04d.png')
const command = `ffmpeg -framerate ${framerate} -i "${dump}" "${nameWithoutExt}.webm" -y`
console.log('[info]', command)
return exec(command)
})
.then(({ stdout, stderr }) => {
if (/error/gm.test(stderr)) return Promise.reject(stderr)
// cleanup
fs.rmdirSync(frames, { recursive: true })
if (deleteOriginal) fs.rmSync(path.resolve(process.cwd(), filename))
console.log('[info] Success!\n')
})
.catch(err => {
terminateWithError(`[fatal] ${err}`)
fs.rmdirSync(frames, { recursive: true })
})
@KL13NT
Copy link
Author

KL13NT commented Oct 6, 2023

@enzomtpYT I never did that but you can probably just switch up the extensions in the script and have it work just as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment