{"id":4875,"date":"2026-01-06T16:16:58","date_gmt":"2026-01-06T08:16:58","guid":{"rendered":"https:\/\/imastudio.com\/?p=4875"},"modified":"2026-01-06T16:17:31","modified_gmt":"2026-01-06T08:17:31","slug":"mp4-to-webp-macos-ffmpeg-script","status":"publish","type":"post","link":"https:\/\/imastudio.com\/fr\/blog\/mp4-to-webp-macos-ffmpeg-script","title":{"rendered":"One-Click macOS Script to Turn MP4 into Looping WebP GIF-Style Animations"},"content":{"rendered":"<p>For client and app development work, I often need to <strong>visualize video content quickly<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>preview animations for product managers<\/li>\n\n\n\n<li>demo clips for docs or PRDs<\/li>\n\n\n\n<li>lightweight motion for social posts or landing pages<\/li>\n<\/ul>\n\n\n\n<p>In these cases, <strong>extracting a few key frames from an MP4 and turning them into a looping WebP animation<\/strong> is usually enough\u2014and much lighter than uploading a full video.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img fetchpriority=\"high\" decoding=\"async\" width=\"800\" height=\"533\" src=\"https:\/\/imastudio.com\/wp-content\/uploads\/2026\/01\/mac-os-video-to-webp-gif.jpg\" alt=\"\" class=\"wp-image-4877\" srcset=\"https:\/\/imastudio.com\/wp-content\/uploads\/2026\/01\/mac-os-video-to-webp-gif.jpg 800w, https:\/\/imastudio.com\/wp-content\/uploads\/2026\/01\/mac-os-video-to-webp-gif-300x200.jpg 300w, https:\/\/imastudio.com\/wp-content\/uploads\/2026\/01\/mac-os-video-to-webp-gif-768x512.jpg 768w, https:\/\/imastudio.com\/wp-content\/uploads\/2026\/01\/mac-os-video-to-webp-gif-18x12.jpg 18w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<p>This article shares a <strong>one-click shell script for macOS<\/strong> that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>extracts 6\u201312 evenly spaced frames from an MP4<\/li>\n\n\n\n<li>resizes them to 720px width<\/li>\n\n\n\n<li>builds a looping WebP animation<\/li>\n\n\n\n<li>and automatically installs <code>ffmpeg<\/code> if it\u2019s missing<\/li>\n<\/ul>\n\n\n\n<p>All of this runs from a single command in Terminal.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. What the script does<\/h2>\n\n\n\n<p>On macOS, the script <code>extract_even_frames_webp.sh<\/code> provides:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Automatic <code>ffmpeg<\/code> detection and installation<\/strong>\n<ul class=\"wp-block-list\">\n<li>Checks whether <code>ffmpeg<\/code> is available<\/li>\n\n\n\n<li>If not, installs Homebrew (if needed) and then installs <code>ffmpeg<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Even frame extraction<\/strong>\n<ul class=\"wp-block-list\">\n<li>Calculates the video duration<\/li>\n\n\n\n<li>Evenly samples <strong>6\u201312 frames<\/strong> across the whole clip<\/li>\n\n\n\n<li>Default is 6 frames, but you can customize the number<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>WebP animation generation<\/strong>\n<ul class=\"wp-block-list\">\n<li>Combines the extracted PNG frames into a <strong>looping WebP<\/strong><\/li>\n\n\n\n<li>Output width is <strong>720px<\/strong> by default (height is scaled proportionally)<\/li>\n\n\n\n<li>Quality is adjustable via <code>-q:v<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Automatic cleanup<\/strong>\n<ul class=\"wp-block-list\">\n<li>Stores intermediate frames in a temporary directory<\/li>\n\n\n\n<li>Deletes the temp directory after the WebP is generated<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Works with both short and long videos<\/strong>\n<ul class=\"wp-block-list\">\n<li>If the video is very short, frame count is automatically reduced<\/li>\n\n\n\n<li>For longer videos, frames are always evenly spaced over the full duration<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. How it works under the hood<\/h2>\n\n\n\n<p>Here\u2019s the basic logic behind the script.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 Check dependencies<\/h3>\n\n\n\n<p>The script first checks whether <code>ffmpeg<\/code> is installed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Uses <code>command -v ffmpeg<\/code><\/li>\n\n\n\n<li>If not found, it checks for <code>brew<\/code>\n<ul class=\"wp-block-list\">\n<li>If Homebrew is missing, it installs Homebrew<\/li>\n\n\n\n<li>Then uses <code>brew install ffmpeg<\/code> to install <code>ffmpeg<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 Retrieve video duration<\/h3>\n\n\n\n<p>Using <code>ffprobe<\/code>, the script reads the <strong>total length of the video in seconds<\/strong>.<br>Based on this and the desired frame count, it calculates the time interval between frames.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.3 Evenly sample frames<\/h3>\n\n\n\n<p>For each frame index, it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Computes the timestamp = <code>index \u00d7 interval<\/code><\/li>\n\n\n\n<li>Uses <code>ffmpeg -ss<\/code> to jump to that time position<\/li>\n\n\n\n<li>Extracts exactly one frame<\/li>\n\n\n\n<li>Scales it with <code>scale=720:-1<\/code> so width is 720px and height is proportional<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.4 Build the WebP animation<\/h3>\n\n\n\n<p>Once all frames are saved as PNGs, the script calls <code>ffmpeg<\/code> again to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Read them in order (<code>%03d.png<\/code>)<\/li>\n\n\n\n<li>Encode them as an animated WebP<\/li>\n\n\n\n<li>Set <code>-loop 0<\/code> so the animation loops indefinitely<\/li>\n\n\n\n<li>Control quality with <code>-q:v 70<\/code> (lower = higher quality, bigger file)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2.5 Clean up<\/h3>\n\n\n\n<p>All intermediate PNG files are stored in a temporary directory created by <code>mktemp -d<\/code>.<br>After the WebP is generated, that directory is removed with <code>rm -rf<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Full shell script: extract_even_frames_webp.sh<\/h2>\n\n\n\n<p>You can save the following script as <code>extract_even_frames_webp.sh<\/code> on your Mac:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n\n# Usage: .\/extract_even_frames_webp.sh input.mp4 output.webp &#91;frames]\n# frames is optional, default 6, maximum 12\n\nINPUT=\"$1\"\nOUTPUT=\"$2\"\nFRAMES=\"${3:-6}\"\n\n# Cap maximum frames at 12\nif &#91; \"$FRAMES\" -gt 12 ]; then\n    FRAMES=12\nfi\n\nif &#91; -z \"$INPUT\" ] || &#91; -z \"$OUTPUT\" ]; then\n    echo \"Usage: $0 input.mp4 output.webp &#91;frames]\"\n    exit 1\nfi\n\n# ---------------------------------\n# 1\ufe0f\u20e3 Check ffmpeg\n# ---------------------------------\nif ! command -v ffmpeg &gt;\/dev\/null 2&gt;&amp;1; then\n    echo \"\u26a0\ufe0f ffmpeg not found, starting installation...\"\n    if ! command -v brew &gt;\/dev\/null 2&gt;&amp;1; then\n        echo \"\u26a0\ufe0f Homebrew not found, installing Homebrew...\"\n        \/bin\/bash -c \"$(curl -fsSL https:\/\/raw.githubusercontent.com\/Homebrew\/install\/HEAD\/install.sh)\"\n        echo \"\u2705 Homebrew installed\"\n    else\n        echo \"\u2705 Homebrew already installed\"\n    fi\n    brew update\n    brew install ffmpeg\n    if ! command -v ffmpeg &gt;\/dev\/null 2&gt;&amp;1; then\n        echo \"\u274c ffmpeg installation failed, please check manually\"\n        exit 1\n    fi\n    echo \"\ud83c\udf89 ffmpeg installed successfully!\"\nelse\n    echo \"\u2705 ffmpeg is already installed\"\nfi\n\n# ---------------------------------\n# 2\ufe0f\u20e3 Get video duration\n# ---------------------------------\nDURATION=$(ffprobe -v error -show_entries format=duration -of csv=p=0 \"$INPUT\")\nif &#91;&#91; -z \"$DURATION\" ]]; then\n    echo \"\u274c Failed to get video duration\"\n    exit 1\nfi\nDURATION=${DURATION%.*}  # integer seconds\n\n# If video is shorter than requested frames, match frame count to duration\nif &#91; \"$DURATION\" -lt \"$FRAMES\" ]; then\n    FRAMES=$DURATION\nfi\n\necho \"\ud83d\udcf9 Video length: ${DURATION}s, extracting $FRAMES frames evenly\"\n\n# ---------------------------------\n# 3\ufe0f\u20e3 Create temporary directory\n# ---------------------------------\nTMPDIR=$(mktemp -d)\n\n# ---------------------------------\n# 4\ufe0f\u20e3 Calculate interval\n# ---------------------------------\nINTERVAL=$(echo \"scale=6; $DURATION\/$FRAMES\" | bc)\necho \"\u23f1 Frame interval: $INTERVAL seconds\"\n\n# ---------------------------------\n# 5\ufe0f\u20e3 Extract frames\n# ---------------------------------\nfor i in $(seq 0 $(($FRAMES-1))); do\n    TIME=$(echo \"$i * $INTERVAL\" | bc)\n    printf -v FILENAME \"%03d.png\" $((i+1))\n    ffmpeg -ss \"$TIME\" -i \"$INPUT\" -frames:v 1 -vf \"scale=720:-1\" \\\n        \"$TMPDIR\/$FILENAME\" -hide_banner -loglevel error\ndone\n\n# ---------------------------------\n# 6\ufe0f\u20e3 Generate animated WebP\n# ---------------------------------\nffmpeg -y -i \"$TMPDIR\/%03d.png\" -vcodec libwebp -lossless 0 -q:v 70 \\\n    -loop 0 -preset picture \"$OUTPUT\" -hide_banner -loglevel error\n\n# ---------------------------------\n# 7\ufe0f\u20e3 Clean up\n# ---------------------------------\nrm -rf \"$TMPDIR\"\n\necho \"\ud83c\udf89 Done! Generated file: $OUTPUT\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. How to use the script on macOS<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Save the script<\/strong> Save the content above as <code>extract_even_frames_webp.sh<\/code>, for example in your home directory.<\/li>\n\n\n\n<li><strong>Give it execute permission<\/strong><\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x extract_even_frames_webp.sh\n<\/code><\/pre>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><strong>Basic usage<\/strong><\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/extract_even_frames_webp.sh input.mp4 output.webp\n<\/code><\/pre>\n\n\n\n<p>This will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Check or install <code>ffmpeg<\/code><\/li>\n\n\n\n<li>Extract 6 evenly spaced frames<\/li>\n\n\n\n<li>Generate <code>output.webp<\/code> at 720px width<\/li>\n<\/ul>\n\n\n\n<ol start=\"4\" class=\"wp-block-list\">\n<li><strong>Specify the number of frames (6\u201312)<\/strong><\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/extract_even_frames_webp.sh input.mp4 output.webp 8\n<\/code><\/pre>\n\n\n\n<p>This will extract 8 frames instead of the default 6.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Why this workflow is useful<\/h2>\n\n\n\n<p>Compared with manually opening a video editor or clicking through a GUI converter, this script has a few advantages:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Zero manual setup<\/strong>\n<ul class=\"wp-block-list\">\n<li>Automatically installs <code>ffmpeg<\/code> (and Homebrew if needed)<\/li>\n\n\n\n<li>No need to search for installers or remember commands<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Smart frame sampling<\/strong>\n<ul class=\"wp-block-list\">\n<li>For short clips, it reduces the frame count automatically<\/li>\n\n\n\n<li>For longer videos, frames are always spaced evenly across the duration<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Quality and size control<\/strong>\n<ul class=\"wp-block-list\">\n<li><code>-q:v<\/code> lets you tune WebP quality vs file size<\/li>\n\n\n\n<li><code>scale=720:-1<\/code> gives a web-friendly width and consistent aspect ratio<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Looping by default<\/strong>\n<ul class=\"wp-block-list\">\n<li><code>-loop 0<\/code> makes the WebP animation loop forever<\/li>\n\n\n\n<li>Great for previews, demos, and subtle motion on docs or sites<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Clean disk usage<\/strong>\n<ul class=\"wp-block-list\">\n<li>Temporary PNG frames are stored in a temp folder and deleted afterwards<\/li>\n\n\n\n<li>No leftover files piling up over time<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Example: turning a 20-second MP4 into a WebP preview<\/h2>\n\n\n\n<p>Imagine you have a <strong>20-second feature demo video<\/strong> and run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/extract_even_frames_webp.sh demo.mp4 demo-preview.webp 6\n<\/code><\/pre>\n\n\n\n<p>The script will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Calculate an interval of about <strong>3.33 seconds<\/strong><\/li>\n\n\n\n<li>Extract 6 frames at <code>0s, 3.33s, 6.66s, 10s, 13.33s, 16.66s<\/code><\/li>\n\n\n\n<li>Resize them to 720px width<\/li>\n\n\n\n<li>Build a <strong>looping WebP<\/strong> animation<\/li>\n<\/ul>\n\n\n\n<p>You can then drop this WebP into:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Product docs or Feishu \/ Notion documents as a <strong>lightweight preview<\/strong><\/li>\n\n\n\n<li>Social posts or landing pages as an <strong>auto-playing motion thumbnail<\/strong><\/li>\n\n\n\n<li>PPT decks and internal demos where a full video would be overkill<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. Ideas for extension<\/h2>\n\n\n\n<p>This script is a starting point\u2014you can easily tweak it for your own workflow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Dynamic frame count<\/strong>\n<ul class=\"wp-block-list\">\n<li>Automatically choose between 6\u201312 frames based on video length<\/li>\n\n\n\n<li>e.g. more frames for longer clips to show more states<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Custom sizes<\/strong>\n<ul class=\"wp-block-list\">\n<li>Change <code>scale=720:-1<\/code> to any width your product team prefers<\/li>\n\n\n\n<li>Or expose width as a parameter: <code>.\/script.sh in.mp4 out.webp 8 1080<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Batch processing<\/strong>\n<ul class=\"wp-block-list\">\n<li>Wrap this script in another loop to process all <code>.mp4<\/code> files in a folder<\/li>\n\n\n\n<li>Useful when you export a batch of feature demos at once<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Author\u2019s note<\/h3>\n\n\n\n<p>This script comes from my real macOS development workflow for turning MP4 videos into lightweight WebP previews. I use it in daily client development to generate demo animations for docs and social posts. GPT was only used to help translate, structure, and polish the English version of this article\u2014the script logic, implementation details, and usage patterns are all from my own project experience.<\/p>","protected":false},"excerpt":{"rendered":"<p>For client and app development work, I often need to visualize video content quickly: In these cases, extracting a few key frames from an MP4 and turning them into a looping WebP animation is usually enough\u2014and much lighter than uploading a full video. This article shares a one-click shell script for macOS that: All of [&hellip;]<\/p>","protected":false},"author":8,"featured_media":4877,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"MP4 to WebP on macOS: One-Click ffmpeg Script Guide","rank_math_description":"Use a one-click macOS shell script with ffmpeg to convert MP4 to looping WebP animations, with auto-install, even frame extraction and clean temp files.","footnotes":""},"categories":[35],"tags":[],"class_list":["post-4875","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-knowledge-hub"],"_links":{"self":[{"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/posts\/4875","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/comments?post=4875"}],"version-history":[{"count":2,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/posts\/4875\/revisions"}],"predecessor-version":[{"id":4878,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/posts\/4875\/revisions\/4878"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/media\/4877"}],"wp:attachment":[{"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/media?parent=4875"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/categories?post=4875"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/imastudio.com\/fr\/wp-json\/wp\/v2\/tags?post=4875"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}