<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: sergei argutin</title>
    <description>The latest articles on DEV Community by sergei argutin (@sergeiargutin).</description>
    <link>https://dev.to/sergeiargutin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008328%2F7849e95c-c862-48cb-8bf5-c83d277ebe29.png</url>
      <title>DEV Community: sergei argutin</title>
      <link>https://dev.to/sergeiargutin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sergeiargutin"/>
    <language>en</language>
    <item>
      <title>Exercise animations in a fitness app: transparent WebP or MP4? I measured both</title>
      <dc:creator>sergei argutin</dc:creator>
      <pubDate>Wed, 12 Aug 2026 09:56:16 +0000</pubDate>
      <link>https://dev.to/sergeiargutin/exercise-animations-in-a-fitness-app-transparent-webp-or-mp4-i-measured-both-4481</link>
      <guid>https://dev.to/sergeiargutin/exercise-animations-in-a-fitness-app-transparent-webp-or-mp4-i-measured-both-4481</guid>
      <description>&lt;p&gt;Animated WebP or MP4 for short looping animations? Most write-ups end with "it&lt;br&gt;
depends, measure it", so I measured it on the 442 exercise clips I ship, and&lt;br&gt;
the useful answer turned out not to be a winner but an order of operations.&lt;/p&gt;

&lt;p&gt;The asymmetry decides it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;From a transparent clip you can produce anything&lt;/strong&gt; — composite any
background, export MP4, resize per platform, re-theme next quarter. One
build command, at the bottom of this post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;From an opaque clip you cannot get the background back.&lt;/strong&gt; Resize and
re-encode still work, but the panel colour is in every pixel: no new theme,
no round container, no clean transparency without re-rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the transparent WebP is the master, and MP4 is a delivery format you&lt;br&gt;
generate from it. Here is what each costs.&lt;/p&gt;
&lt;h2&gt;
  
  
  The measured comparison
&lt;/h2&gt;

&lt;p&gt;Six clips spanning the size range of the catalogue. Every variant re-encoded&lt;br&gt;
from the same source with the same settings (quality 90, method 4, exact&lt;br&gt;
alpha); MP4 is H.264 crf 23, &lt;code&gt;medium&lt;/code&gt; preset, composited on &lt;code&gt;#111827&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clip&lt;/th&gt;
&lt;th&gt;WebP 960, transparent&lt;/th&gt;
&lt;th&gt;Same, composited (opaque)&lt;/th&gt;
&lt;th&gt;MP4 crf 23&lt;/th&gt;
&lt;th&gt;MP4 crf 28&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single Leg Glute Bridge&lt;/td&gt;
&lt;td&gt;1232 KB&lt;/td&gt;
&lt;td&gt;474 KB&lt;/td&gt;
&lt;td&gt;71 KB&lt;/td&gt;
&lt;td&gt;43 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mountain Climbers&lt;/td&gt;
&lt;td&gt;1316 KB&lt;/td&gt;
&lt;td&gt;522 KB&lt;/td&gt;
&lt;td&gt;100 KB&lt;/td&gt;
&lt;td&gt;61 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side-Lying Lateral Raise&lt;/td&gt;
&lt;td&gt;2186 KB&lt;/td&gt;
&lt;td&gt;1049 KB&lt;/td&gt;
&lt;td&gt;94 KB&lt;/td&gt;
&lt;td&gt;51 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incline Dumbbell Curl&lt;/td&gt;
&lt;td&gt;3387 KB&lt;/td&gt;
&lt;td&gt;1381 KB&lt;/td&gt;
&lt;td&gt;128 KB&lt;/td&gt;
&lt;td&gt;65 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One Arm Kettlebell Push Press&lt;/td&gt;
&lt;td&gt;4093 KB&lt;/td&gt;
&lt;td&gt;1435 KB&lt;/td&gt;
&lt;td&gt;152 KB&lt;/td&gt;
&lt;td&gt;86 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battle Ropes&lt;/td&gt;
&lt;td&gt;5022 KB&lt;/td&gt;
&lt;td&gt;1841 KB&lt;/td&gt;
&lt;td&gt;419 KB&lt;/td&gt;
&lt;td&gt;214 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read it left to right: one master and two things you can make from it, not&lt;br&gt;
three products to choose between.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing the background saves ~2.5x.&lt;/strong&gt; Same format, same settings, alpha gone —&lt;br&gt;
the median file drops to 39%. That is what the alpha channel costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encoding as video saves another ~8x&lt;/strong&gt;, because a video codec has interframe&lt;br&gt;
prediction and an animated image format does not. End to end: 3387 KB → 128 KB&lt;br&gt;
for one clip, from a file you already have.&lt;/p&gt;

&lt;p&gt;The master is the biggest file in the table. That is what a master is for.&lt;/p&gt;
&lt;h2&gt;
  
  
  What transparency buys
&lt;/h2&gt;

&lt;p&gt;Not aesthetics — options you would otherwise have to buy again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Themes as a feature.&lt;/strong&gt; If users pick or purchase a theme, every asset has
to look right on all of them. A transparent figure composites onto whatever
the theme sets; an opaque one needs an export per theme, from whoever owns
the source artwork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;White-label.&lt;/strong&gt; The same catalogue in a client's palette, without re-cutting
a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dark mode&lt;/strong&gt;, which is the same problem and catches most teams after launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any container&lt;/strong&gt; — round avatars, rounded cards, overlays on a photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MP4 on demand.&lt;/strong&gt; Pick the background, run the recipe below, ship video
where it helps, regenerate it at the next redesign.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same movement, both ways, on two surfaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3p3045ccgg3vkkw1y8gk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3p3045ccgg3vkkw1y8gk.png" alt="The same animation as a transparent WebP and as an MP4 with a dark panel baked in, each shown on a dark panel and a light card. The MP4 is a hard dark rectangle on the light card." width="760" height="876"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The video is a fraction of the bytes and completely correct — on the panel it&lt;br&gt;
was exported for. Move it to a light card and it is simply wrong. Fine, as long&lt;br&gt;
as it is a derivative you can regenerate rather than the only copy you own.&lt;/p&gt;
&lt;h2&gt;
  
  
  What MP4 costs you back
&lt;/h2&gt;

&lt;p&gt;A twelve-card grid, three ways, every tile at 240 CSS px, Chrome on a desktop&lt;br&gt;
over localhost:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Grid of 12&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Load&lt;/th&gt;
&lt;th&gt;Sustained frame rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;480px stills&lt;/td&gt;
&lt;td&gt;0.26 MB&lt;/td&gt;
&lt;td&gt;54 ms&lt;/td&gt;
&lt;td&gt;56 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;960px animated WebP&lt;/td&gt;
&lt;td&gt;37.92 MB&lt;/td&gt;
&lt;td&gt;83 ms&lt;/td&gt;
&lt;td&gt;56 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MP4, background baked in&lt;/td&gt;
&lt;td&gt;1.97 MB&lt;/td&gt;
&lt;td&gt;296 ms&lt;/td&gt;
&lt;td&gt;28 fps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MP4 grid moved 20x fewer bytes and ran at half the frame rate: twelve&lt;br&gt;
&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements decoding and compositing at once is real work. Pausing&lt;br&gt;
eleven put it back to 56 fps; resuming them dropped it to 28 again.&lt;/p&gt;
&lt;h3&gt;
  
  
  The WebP row needs a warning label
&lt;/h3&gt;

&lt;p&gt;That 56 fps is the &lt;em&gt;page's&lt;/em&gt; frame rate from &lt;code&gt;requestAnimationFrame&lt;/code&gt;. An&lt;br&gt;
animated image decodes and rasters outside the page's animation loop, so a clip&lt;br&gt;
can drop half its frames while the page reports a calm 56 — and no browser API&lt;br&gt;
exposes an image's real playback rate. Drawing it to a canvas returns a frozen&lt;br&gt;
frame, not the current one.&lt;/p&gt;

&lt;p&gt;The decode work can be measured, with the browser's own &lt;code&gt;ImageDecoder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ImageDecoder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedTrack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frameCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;frameIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ms per frame&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four clips, every frame, best of three passes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clip&lt;/th&gt;
&lt;th&gt;Frames&lt;/th&gt;
&lt;th&gt;960 px&lt;/th&gt;
&lt;th&gt;480 px&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mountain Climbers&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;4.48 ms/frame&lt;/td&gt;
&lt;td&gt;0.82 ms/frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battle Ropes&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;4.61 ms/frame&lt;/td&gt;
&lt;td&gt;0.95 ms/frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incline Dumbbell Curl&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;5.16 ms/frame&lt;/td&gt;
&lt;td&gt;1.09 ms/frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side-Lying Lateral Raise&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;5.41 ms/frame&lt;/td&gt;
&lt;td&gt;1.07 ms/frame&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the clips' own 20 fps that is &lt;strong&gt;90–108 ms of decode per second of playback&lt;/strong&gt;&lt;br&gt;
for a 960px clip — a tenth of a core, per clip, on a fast desktop. At 480px,&lt;br&gt;
16–22 ms.&lt;/p&gt;

&lt;p&gt;Twelve of them need ~1.1 s of decode per second of wall clock. Nothing plays&lt;br&gt;
correctly under that. The page stayed at 56 fps because the animations, not the&lt;br&gt;
page, absorbed the shortfall — where the metric could not see it.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MP4&lt;/strong&gt; is far cheaper to download, rides the video pipeline, and gets
expensive in bulk — visibly, in the page's frame rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animated WebP&lt;/strong&gt; is heavier to download, costs ~10% of a core per master
clip to play, and degrades &lt;em&gt;invisibly&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A still&lt;/strong&gt; beats both wherever nobody is studying the motion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever you pick, play one clip at a time. That single rule outperforms the&lt;br&gt;
format choice.&lt;/p&gt;
&lt;h2&gt;
  
  
  Converting: the recipe that actually works
&lt;/h2&gt;

&lt;p&gt;This is where the standard advice fails. The obvious command —&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# does NOT work on these clips&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; animation.webp ... output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;— fails on FFmpeg 8.1 with &lt;code&gt;image data not found&lt;/code&gt;. FFmpeg only learned to&lt;br&gt;
decode animated WebP in 7.1, and its decoder still chokes on clips whose frames&lt;br&gt;
are stored as blended sub-rectangles rather than full canvases, which is what a&lt;br&gt;
size-optimised encoder produces. Every clip I tried failed the same way.&lt;/p&gt;

&lt;p&gt;Extract the frames with libwebp's &lt;code&gt;anim_dump&lt;/code&gt; first — it composites each frame&lt;br&gt;
onto the full canvas — then encode the PNG sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CLIP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;animation.webp

&lt;span class="c"&gt;# 1. frames out (libwebp ships anim_dump alongside cwebp)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; frames
anim_dump &lt;span class="nt"&gt;-folder&lt;/span&gt; frames &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 2. the clip's own rate rather than a guess: frames / total duration&lt;/span&gt;
&lt;span class="nv"&gt;FPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;webpinfo &lt;span class="nt"&gt;-summary&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/Duration/ {n++; ms += $2} END {printf "%.0f", n / (ms / 1000)}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. composite on the panel colour and encode&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-framerate&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FPS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"frames/dump_%04d.png"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; lavfi &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"color=c=#111827:s=960x960:r=&lt;/span&gt;&lt;span class="nv"&gt;$FPS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[1:v][0:v]overlay=shortest=1:format=auto,format=yuv420p[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things that bite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frame rate.&lt;/strong&gt; The colour source dictates the output rate through the
overlay, so both &lt;code&gt;-framerate&lt;/code&gt; and &lt;code&gt;r=&lt;/code&gt; must match the clip. Assume 25 and you
ship duplicated frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha edges.&lt;/strong&gt; Check the result on a light &lt;em&gt;and&lt;/em&gt; a dark background.
Incorrect compositing shows up as a pale fringe around thin objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crf.&lt;/strong&gt; 23 is a safe default; 28 roughly halved the files again with no
obvious loss at card size — flat illustration compresses well.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choosing between them
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Asset must sit on light, dark, and brand surfaces&lt;/td&gt;
&lt;td&gt;Animated WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One fixed background across the product&lt;/td&gt;
&lt;td&gt;MP4 derivative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display with a plain &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Animated WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Play, pause, seek, visibility control&lt;/td&gt;
&lt;td&gt;MP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several clips visible at once&lt;/td&gt;
&lt;td&gt;Neither — stills, one active clip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bandwidth-critical, single active clip&lt;/td&gt;
&lt;td&gt;MP4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A master you can re-theme without a transcode step&lt;/td&gt;
&lt;td&gt;Animated WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;Six clips from the current build, 1.3–5.1 MB, covering the size range of 442&lt;br&gt;
shipped animations. WebP variants re-encoded at quality 90, method 4,&lt;br&gt;
&lt;code&gt;exact=True&lt;/code&gt;, disposal 2. MP4 encoded with FFmpeg 8.1, libx264, crf 23/28,&lt;br&gt;
&lt;code&gt;medium&lt;/code&gt; preset. Grid test: Chrome on a desktop, twelve cards at 240 CSS px in&lt;br&gt;
a four-column grid, served over localhost, frame rate sampled over three&lt;br&gt;
seconds via &lt;code&gt;requestAnimationFrame&lt;/code&gt;. Localhost removes the network, which is&lt;br&gt;
why the byte column deserves more weight than the load column. Decode cost:&lt;br&gt;
Chrome's &lt;code&gt;ImageDecoder&lt;/code&gt;, file already in memory, so it isolates decode from&lt;br&gt;
network and layout.&lt;/p&gt;

&lt;p&gt;Reading an animated image's &lt;em&gt;actual&lt;/em&gt; on-screen frame rate is not possible from&lt;br&gt;
page JavaScript. Where this post says an animation stutters, that is an&lt;br&gt;
observation; the decode budget is the measurement that explains it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclosure
&lt;/h2&gt;

&lt;p&gt;The clips are from &lt;a href="https://repdb.co/?utm_source=devto" rel="noopener noreferrer"&gt;RepDB&lt;/a&gt;, an exercise dataset I build and sell. It ships the thing this post argues for: looping exercise animations at 960px &lt;strong&gt;on transparent backgrounds&lt;/strong&gt;, plus stills in two styles, as files you own. That is why both columns of the table came from one source — I had the master, so the opaque and MP4 variants were an export rather than a re-render. Every other illustrated exercise set I looked at ships opaque frames — GIF or photography — with the background painted in, which is the one property you cannot get back.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://repdb.co/blog/animated-webp-vs-mp4-exercise-animations/?utm_source=devto" rel="noopener noreferrer"&gt;original post&lt;/a&gt; has the same measurements with the figures running live, and the &lt;a href="https://repdb.co/preview/?utm_source=devto" rel="noopener noreferrer"&gt;free preview bundle&lt;/a&gt; is there if you would rather benchmark the actual files than take my table for it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>javascript</category>
      <category>fitness</category>
    </item>
    <item>
      <title>ExerciseDB vs WorkoutX vs free-exercise-db vs RepDB (2026)</title>
      <dc:creator>sergei argutin</dc:creator>
      <pubDate>Sat, 18 Jul 2026 19:17:16 +0000</pubDate>
      <link>https://dev.to/sergeiargutin/exercisedb-vs-workoutx-vs-free-exercise-db-vs-repdb-2026-3aj3</link>
      <guid>https://dev.to/sergeiargutin/exercisedb-vs-workoutx-vs-free-exercise-db-vs-repdb-2026-3aj3</guid>
      <description>&lt;p&gt;If you're building a fitness app, the text layer is rarely the hard part. The&lt;br&gt;
real decision is whether the exercise source also solves images, licensing,&lt;br&gt;
local/offline delivery, and the data shape your UI needs.&lt;/p&gt;

&lt;p&gt;I compared four options I considered while building fitness-app prototypes:&lt;br&gt;
ExerciseDB, WorkoutX, free-exercise-db, and RepDB. I now build RepDB, so treat&lt;br&gt;
this as a technical comparison with a disclosed bias and verify the vendor&lt;br&gt;
details before committing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ExerciseDB&lt;/th&gt;
&lt;th&gt;WorkoutX&lt;/th&gt;
&lt;th&gt;free-exercise-db&lt;/th&gt;
&lt;th&gt;RepDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delivery&lt;/td&gt;
&lt;td&gt;Downloadable files&lt;/td&gt;
&lt;td&gt;Hosted API&lt;/td&gt;
&lt;td&gt;GitHub files&lt;/td&gt;
&lt;td&gt;Downloadable files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;Opaque GIF&lt;/td&gt;
&lt;td&gt;Illustrated GIFs; sample 0551 visually matched ExerciseDB&lt;/td&gt;
&lt;td&gt;Mixed gym JPGs&lt;/td&gt;
&lt;td&gt;Flat WebP + classic WebP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transparent background&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Not consistently&lt;/td&gt;
&lt;td&gt;Yes, on classic Standard assets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;English&lt;/td&gt;
&lt;td&gt;English, German, Spanish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline/self-hosted&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial terms&lt;/td&gt;
&lt;td&gt;Paid commercial license&lt;/td&gt;
&lt;td&gt;API terms&lt;/td&gt;
&lt;td&gt;Data is Unlicense; inspect image provenance separately&lt;/td&gt;
&lt;td&gt;Free attribution tier + paid commercial tiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing model&lt;/td&gt;
&lt;td&gt;One-time bundle&lt;/td&gt;
&lt;td&gt;Monthly/metered API&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free tier or one-time bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Figures and product terms change. This comparison reflects the public products&lt;br&gt;
and files I inspected in July 2026.&lt;/p&gt;
&lt;h2&gt;
  
  
  The images side by side
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9g211me6fo2eq6lnqny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv9g211me6fo2eq6lnqny.png" alt="ExerciseDB and WorkoutX, free-exercise-db, RepDB classic, and RepDB flat compared side by side" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The practical UI difference is visible before you read any schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ExerciseDB / the WorkoutX sample I inspected:&lt;/strong&gt; illustrated GIF on an opaque near-white background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;free-exercise-db:&lt;/strong&gt; real gym JPG photography with mixed framing and dimensions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RepDB classic:&lt;/strong&gt; consistent WebP artwork with a transparent background on Standard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RepDB flat:&lt;/strong&gt; consistent full-color WebP illustrations; this is also the style included in the 250-exercise free tier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Motion comparison
&lt;/h3&gt;

&lt;p&gt;DEV's image proxy does not render the 960px transparent animated WebP reliably. To avoid showing a degraded or ghosted version here, the live animation comparison stays on the canonical RepDB article:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://repdb.co/blog/exercisedb-alternatives-2026/#the-images-decide-it" rel="noopener noreferrer"&gt;Open the live Turkish get-up comparison →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the files I inspected in July 2026, the WorkoutX sample was a 360×360, 54-frame opaque GIF with a watermark. The RepDB version is delivered as a transparent 960px animated WebP; the complex-motion source contains 241 frames. These are file-level observations, not claims about ownership or every asset in either catalog.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why RepDB is different
&lt;/h2&gt;

&lt;p&gt;The main difference is not the exercise names. It is whether the visual assets can ship in a real product UI without a redesign or a manual cleanup pass.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebP for both illustration styles; animated WebP on Standard.&lt;/strong&gt; RepDB uses full-color WebP instead of opaque GIF/JPG assets. Standard also includes seamless looping WebP animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent classic assets on Standard.&lt;/strong&gt; They sit cleanly on dark mode, gradients, brand colors, and circular avatars instead of showing a white rectangle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two consistent illustration styles.&lt;/strong&gt; Classic gives you a more realistic look; flat gives you a friendly illustrated UI. The free tier includes 250 flat 512×512 images, while paid bundles include both styles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted files instead of a runtime API dependency.&lt;/strong&gt; JSON/SQLite and WebP assets run locally, with no API key, request quota, or network dependency after download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EN/DE/ES text and product-ready metadata.&lt;/strong&gt; Instructions, muscles, equipment, goals, tags, relations, and MET values are included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-time commercial license.&lt;/strong&gt; Paid tiers are a one-time purchase; the free tier can be used inside commercial apps with visible attribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8r8flyw6vg8vks8mf79.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8r8flyw6vg8vks8mf79.png" alt="The same transparent RepDB classic image on dark, light, gradient, and circular UI surfaces" width="800" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Transparency is deliberately scoped here: the free tier contains the flat style, while transparent classic assets and looping animations are Standard-tier features.&lt;/p&gt;
&lt;h2&gt;
  
  
  ExerciseDB
&lt;/h2&gt;

&lt;p&gt;ExerciseDB is the straightforward choice when the biggest possible raw catalog&lt;br&gt;
matters more than deduplication or theming. It is delivered as files under a&lt;br&gt;
commercial license, so there is no runtime API dependency.&lt;/p&gt;

&lt;p&gt;The main UI tradeoff is the artwork format. The supplied animations are GIFs&lt;br&gt;
with an opaque near-white background. That works in a light card, but becomes a&lt;br&gt;
visible square on dark mode, branded surfaces, gradients, or circular masks.&lt;br&gt;
GIF also limits the palette to 256 colors.&lt;/p&gt;

&lt;p&gt;Choose it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;English-only data is enough;&lt;/li&gt;
&lt;li&gt;a very large raw entry count is the priority;&lt;/li&gt;
&lt;li&gt;opaque GIF artwork fits your UI;&lt;/li&gt;
&lt;li&gt;you prefer a one-time download over an API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  WorkoutX
&lt;/h2&gt;

&lt;p&gt;WorkoutX is convenient when you want a hosted API and do not want to manage&lt;br&gt;
files yourself. In the July 2026 public sample I inspected, exercise &lt;code&gt;0551&lt;/code&gt; used the same&lt;br&gt;
54-frame artwork as ExerciseDB's &lt;code&gt;0551&lt;/code&gt; sample, with WorkoutX serving an&lt;br&gt;
upscaled, watermarked version. That is an observation about those downloaded&lt;br&gt;
samples, not a claim about ownership or the rest of either catalog.&lt;/p&gt;

&lt;p&gt;That changes the operational model, not the visual layer. Every screen that&lt;br&gt;
loads exercise data or imagery depends on a third-party service, quota, and&lt;br&gt;
network availability. Caching and asset use are governed by the API terms.&lt;/p&gt;

&lt;p&gt;Choose it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speed of prototyping matters more than ownership of the files;&lt;/li&gt;
&lt;li&gt;a hosted API is preferable to local JSON/SQLite;&lt;/li&gt;
&lt;li&gt;request limits and a recurring bill are acceptable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  free-exercise-db
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/yuhonas/free-exercise-db" rel="noopener noreferrer"&gt;free-exercise-db&lt;/a&gt; is a useful&lt;br&gt;
choice for hobby projects and portfolios. It contains hundreds of exercises,&lt;br&gt;
is easy to clone, and uses real gym photography rather than illustrations.&lt;/p&gt;

&lt;p&gt;The code and JSON are published under the Unlicense. For a commercial product,&lt;br&gt;
I would inspect the image provenance separately: a repository license and the&lt;br&gt;
rights to every bundled photograph are two different questions. The photos also&lt;br&gt;
vary in framing, size, background, and orientation, which creates UI work.&lt;/p&gt;

&lt;p&gt;Choose it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;free is the overriding constraint;&lt;/li&gt;
&lt;li&gt;real gym photography fits the product;&lt;/li&gt;
&lt;li&gt;you are comfortable reviewing the image provenance;&lt;/li&gt;
&lt;li&gt;consistent illustration style and multilingual text are not requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  RepDB
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://repdb.co/" rel="noopener noreferrer"&gt;RepDB&lt;/a&gt; is built around self-hosted&lt;br&gt;
files and consistent product UI. The paid dataset includes two illustration&lt;br&gt;
styles, English/German/Spanish text, exercise relations, MET values, and a&lt;br&gt;
commercial license. Standard adds transparent classic assets, SQLite,&lt;br&gt;
TypeScript types, embeddings, and looping WebP animations.&lt;/p&gt;

&lt;p&gt;The free tier now contains 250 exercises with flat 512×512 WebP images, full&lt;br&gt;
EN/DE/ES text, muscles, equipment, MET values, goals, and tags. It can be used&lt;br&gt;
inside commercial applications with visible attribution, without an account or&lt;br&gt;
API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://raw.githubusercontent.com/RepDB/exercise-dataset/main/exercises.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 250&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exercises&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app needs a coherent visual system, including dark/brand surfaces;&lt;/li&gt;
&lt;li&gt;EN/DE/ES coverage saves meaningful localization work;&lt;/li&gt;
&lt;li&gt;local JSON/SQLite is preferable to a runtime API;&lt;/li&gt;
&lt;li&gt;you want to prototype against a large free subset before buying assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The decision I would make
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Largest raw catalog, one-time files:&lt;/strong&gt; ExerciseDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosted API for a quick prototype:&lt;/strong&gt; WorkoutX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-budget hobby project with real photos:&lt;/strong&gt; free-exercise-db.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commercial product where images, theming, languages, and local ownership
matter:&lt;/strong&gt; RepDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best way to compare data products is to inspect the actual files. RepDB's&lt;br&gt;
free tier is available as &lt;a href="https://github.com/RepDB/exercise-dataset" rel="noopener noreferrer"&gt;GitHub files&lt;/a&gt;,&lt;br&gt;
a &lt;a href="https://exercise-dataset.com/" rel="noopener noreferrer"&gt;live viewer&lt;/a&gt;. To evaluate the paid assets, &lt;a href="https://repdb.co/#preview-download" rel="noopener noreferrer"&gt;grab the free preview&lt;/a&gt; bundle or &lt;a href="https://repdb.co/preview/" rel="noopener noreferrer"&gt;browse it live&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology and disclosure
&lt;/h2&gt;

&lt;p&gt;I compared public sample files, catalog metadata, image formats, and vendor&lt;br&gt;
terms in July 2026. ExerciseDB and WorkoutX can change their catalogs, pricing,&lt;br&gt;
or terms; check their current sites before purchasing. I build and sell RepDB.&lt;/p&gt;

&lt;p&gt;Third-party product names, marks, and sample imagery are shown only for identification and comparative commentary. They remain the property of their respective owners.&lt;/p&gt;

</description>
      <category>data</category>
      <category>webdev</category>
      <category>api</category>
      <category>fitness</category>
    </item>
    <item>
      <title>Free exercise data for your fitness app (JSON + images, EN/DE/ES)</title>
      <dc:creator>sergei argutin</dc:creator>
      <pubDate>Mon, 29 Jun 2026 14:43:40 +0000</pubDate>
      <link>https://dev.to/sergeiargutin/free-exercise-data-for-your-fitness-app-json-images-endees-5d9f</link>
      <guid>https://dev.to/sergeiargutin/free-exercise-data-for-your-fitness-app-json-images-endees-5d9f</guid>
      <description>&lt;p&gt;If you're building a fitness app and need real exercise data — not a bare name list — here's a free, illustrated, multilingual dataset you can drop straight into an app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RepDB Free Tier&lt;/strong&gt; — free for commercial use inside apps (with attribution):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;250 exercises&lt;/strong&gt; as clean JSON (a dated snapshot of the full, growing RepDB catalog).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flat-style illustrations at 512 px&lt;/strong&gt; (WebP, start/peak poses — or a single pose for stretches).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target muscles, equipment, category, MET, goals, tags&lt;/strong&gt; per exercise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step-by-step instructions in English, German and Spanish.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A few &lt;strong&gt;looping animation previews&lt;/strong&gt; bundled in — the same clips you see on repdb.co — so you can judge the paid-tier motion quality (these previews are evaluation-only).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; the free tier is &lt;strong&gt;free for personal and commercial use inside applications&lt;/strong&gt;, with a visible attribution link ("Exercise data by RepDB — repdb.co"). You may not repackage or redistribute it as a standalone dataset/API — in-app use only. (It's not CC-BY-NC; you &lt;em&gt;can&lt;/em&gt; ship it in a commercial app.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/RepDB/exercise-dataset" rel="noopener noreferrer"&gt;https://github.com/RepDB/exercise-dataset&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browse the exercises live: &lt;a href="https://exercise-dataset.com/" rel="noopener noreferrer"&gt;https://exercise-dataset.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Raw JSON: &lt;a href="https://raw.githubusercontent.com/RepDB/exercise-dataset/main/exercises.json" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/RepDB/exercise-dataset/main/exercises.json&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Need more?&lt;/strong&gt; The full RepDB dataset keeps growing (518+ today) and adds a second 1024 px classic illustration style, transparent backgrounds, and looping animations for most exercises, under a commercial license → &lt;a href="https://repdb.co" rel="noopener noreferrer"&gt;https://repdb.co&lt;/a&gt; / &lt;a href="https://repdb.co/pricing" rel="noopener noreferrer"&gt;https://repdb.co/pricing&lt;/a&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>webdev</category>
      <category>fitness</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
