Last active
April 14, 2025 22:37
-
-
Save do-adams/1f24d84c7e84a7ad54b8ba08441ebed6 to your computer and use it in GitHub Desktop.
Convert .mp4 to. webm with ffmpeg using Vuetify 2.x breakpoints
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
| #!/usr/bin/env tsx | |
| import { execSync } from 'child_process' | |
| import { existsSync } from 'fs' | |
| import { extname } from 'path' | |
| interface VideoConfig { | |
| name: string | |
| resolution: string | |
| bitrate: string | |
| frameRate: number | |
| tileColumns: number | |
| mediaQuery: string | |
| } | |
| const configs: VideoConfig[] = [ | |
| { | |
| name: 'xs', | |
| resolution: '640:384', | |
| bitrate: '400k', | |
| frameRate: 20, | |
| tileColumns: 1, | |
| mediaQuery: '(max-width: 959px)', | |
| }, | |
| { | |
| name: 'md', | |
| resolution: '960:576', | |
| bitrate: '800k', | |
| frameRate: 24, | |
| tileColumns: 2, | |
| mediaQuery: '(min-width: 960px) and (max-width: 1263px)', | |
| }, | |
| { | |
| name: 'lg', | |
| resolution: '1200:720', | |
| bitrate: '1.5M', | |
| frameRate: 24, | |
| tileColumns: 2, | |
| mediaQuery: '(min-width: 1264px)', | |
| }, | |
| ] | |
| const commonWebMOptions = { | |
| cpuUsed: 2, | |
| noAudio: '-an', | |
| noMetadata: '-map_metadata -1', | |
| } | |
| function runFFmpeg(command: string): void { | |
| try { | |
| execSync(command, { stdio: 'inherit' }) | |
| } catch (error) { | |
| console.error(`FFmpeg error: ${(error as Error).message}`) | |
| process.exit(1) | |
| } | |
| } | |
| function generateWebMFFmpegCommands( | |
| input: string, | |
| config: VideoConfig | |
| ): string[] { | |
| const baseCommand = `ffmpeg -i "${input}" -c:v libvpx-vp9 -b:v ${config.bitrate} -vf scale=${config.resolution} -r ${config.frameRate} -cpu-used ${commonWebMOptions.cpuUsed} -tile-columns ${config.tileColumns} ${commonWebMOptions.noAudio} ${commonWebMOptions.noMetadata}` | |
| return [ | |
| `${baseCommand} -pass 1 -f webm /dev/null`, | |
| `${baseCommand} -pass 2 ${config.name}.webm`, | |
| ] | |
| } | |
| function generateMP4FFmpegCommands(input: string): string[] { | |
| const baseCommand = `ffmpeg -i "${input}" -c:v libx264 -b:v 800k -maxrate 1000k -bufsize 2000k -crf 25 -preset veryslow -an -movflags +faststart -vf "scale=1200:720:force_original_aspect_ratio=decrease" -color_primaries bt709 -color_trc bt709 -colorspace bt709` | |
| return [ | |
| `${baseCommand} -pass 1 -f mp4 /dev/null`, | |
| `${baseCommand} -pass 2 fallback.mp4`, | |
| ] | |
| } | |
| function generateHTMLBoilerplate(): string { | |
| const sources = configs | |
| .map( | |
| (config) => | |
| ` <source src="${config.name}.webm" type="video/webm" media="${config.mediaQuery}">` | |
| ) | |
| .join('\n') | |
| return ` | |
| <video autoplay loop muted playsinline disablepictureinpicture> | |
| ${sources} | |
| <source src="fallback.mp4" type="video/mp4"> | |
| </video> | |
| ` | |
| } | |
| function main() { | |
| const inputFile = process.argv[2] | |
| // Validate input | |
| if (!inputFile) { | |
| console.error('Usage: tsx convert-m2w.ts <input.mp4>') | |
| process.exit(1) | |
| } | |
| if (!existsSync(inputFile)) { | |
| console.error(`Input file "${inputFile}" does not exist.`) | |
| process.exit(1) | |
| } | |
| if (extname(inputFile).toLowerCase() !== '.mp4') { | |
| console.error('Input file must be an .mp4.') | |
| process.exit(1) | |
| } | |
| console.log( | |
| `Converting "${inputFile}" to xs.webm, md.webm, lg.webm, and fallback.mp4...` | |
| ) | |
| // Convert WebM files for each breakpoint | |
| for (const config of configs) { | |
| console.log( | |
| `Creating ${config.name}.webm (${config.resolution} large resolution, ${config.bitrate}, ${config.frameRate}fps)...` | |
| ) | |
| const [pass1, pass2] = generateWebMFFmpegCommands(inputFile, config) | |
| runFFmpeg(pass1) | |
| runFFmpeg(pass2) | |
| } | |
| // Convert fallback MP4 | |
| console.log(`Creating fallback.mp4 (1200x720, 800k, H.264)...`) | |
| const [mp4Pass1, mp4Pass2] = generateMP4FFmpegCommands(inputFile) | |
| runFFmpeg(mp4Pass1) | |
| runFFmpeg(mp4Pass2) | |
| // Generate and display completion message | |
| const htmlBoilerplate = generateHTMLBoilerplate() | |
| console.log(` | |
| Conversion complete! Created: | |
| - xs.webm (for xs: 0-959px) | |
| - md.webm (for md: 960-1263px) | |
| - lg.webm (for lg: 1264px+) | |
| - fallback.mp4 (H.264 fallback) | |
| Use this HTML in your Vuetify 2.x app: | |
| ${htmlBoilerplate} | |
| `) | |
| } | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Convert .mp4 to .webm with ffmpeg using Vuetify 2.x breakpoints
Overview
This TypeScript script,
convert-m2w.ts, converts a high-resolution, silent.mp4video into three optimized.webmfiles (xs.webm,md.webm,lg.webm) and one H.264.mp4fallback (fallback.mp4) tailored for Vuetify 2.x breakpoints:<source>mediaattributes or have.webmplayback issues.The
.webmfiles are encoded with VP9 using two-pass FFmpeg for minimal size, optimized for a looping, audio-free animation (e.g., a homepage background). The.mp4fallback uses H.264 for broad compatibility. The script outputs HTML boilerplate for a<video>element with responsive<source>tags.Features
.mp4(e.g., 1200x720, H.264).xs.webm: 640x384, 400k bitrate, 20fps (~300-600 KB).md.webm: 960x576, 800k bitrate, 24fps (~600 KB-1.2 MB).lg.webm: 1200x720, 1.5M bitrate, 24fps (~1-2 MB).fallback.mp4: 1200x720, 800k bitrate, H.264 (~1-2 MB)..webm: Two-pass VP9, scaled resolutions, reduced frame rates, no audio, stripped metadata..mp4: Two-pass H.264, CRF 25,veryslowpreset,faststartfor web, BT.709 color.<video autoplay loop muted playsinline disablepictureinpicture>with<source>tags:xs.webm:(max-width: 959px)md.webm:(min-width: 960px) and (max-width: 1263px)lg.webm:(min-width: 1264px)fallback.mp4: Nomediaquery for universal compatibility.Prerequisites
Node.js: v16+ recommended.
FFmpeg: Installed with H.264 and VP9 support.
brew install ffmpegsudo apt-get install ffmpegUsage
Save the Script:
convert-m2w.tsin your project directory.Make Executable (optional, Unix-like systems):
Run the Script:
Replace
input.mp4with your video file (must be.mp4).Example:
Output:
Creates
xs.webm,md.webm,lg.webm, andfallback.mp4in the current directory.Prints a completion message with HTML boilerplate, e.g.:
Integration in Vuetify 2.x
Add to Component:
Use the generated HTML in a Vuetify component, updating paths to match your project:
Testing
Quality:
fallback.mp4in Safari to ensure no freezing (unlike.webm).Responsive Loading:
xs.webmmd.webmlg.webmfallback.mp4(ignoresmediaattributes).Customization
configsinconvert-m2w.tsto change:resolution(e.g.,720:432forxs).bitrate(e.g.,300kfor smallerxs).frameRate(e.g.,15forxs).generateMP4FFmpegCommandsfor different.mp4settings (e.g.,-crf 23).Notes
<source>mediaattributes, using the last non-media<source>(fallback.mp4)..webmfiles may freeze after a few loops in Safari, makingfallback.mp4essential for reliability..mp4is silent and high-quality (e.g., 1200x720)..mp4fallback name (fallback.mp4); adjust if dynamic naming is needed.mediaattribute support.mediaQueryinconfigsif Vuetify breakpoints change.License
MIT License. Feel free to use and modify.
Created for a Pull Request to optimize a homepage animation in a Vuetify 2.x app.