<?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: Alex Shev</title>
    <description>The latest articles on DEV Community by Alex Shev (@alexshev).</description>
    <link>https://dev.to/alexshev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3812101%2Fa5dfb3f9-d006-41a1-a868-995b7f219269.png</url>
      <title>DEV Community: Alex Shev</title>
      <link>https://dev.to/alexshev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexshev"/>
    <language>en</language>
    <item>
      <title>MCP Is Not the Product — Reusable Skills Are</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Tue, 24 Mar 2026 22:20:07 +0000</pubDate>
      <link>https://dev.to/alexshev/mcp-is-not-the-product-reusable-skills-are-27fp</link>
      <guid>https://dev.to/alexshev/mcp-is-not-the-product-reusable-skills-are-27fp</guid>
      <description>&lt;p&gt;Right now, a lot of people are talking about MCP.&lt;/p&gt;

&lt;p&gt;And I get why.&lt;/p&gt;

&lt;p&gt;It’s a clean idea: connect an AI agent to tools, data, and actions through a standard interface, and suddenly the model can actually do things.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;But I think a lot of people are still confusing the plumbing with the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP is useful. MCP is not the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The product is what happens after the connection exists.&lt;/p&gt;

&lt;p&gt;The product is a reusable skill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools are not enough
&lt;/h2&gt;

&lt;p&gt;Giving an agent access to tools sounds powerful.&lt;/p&gt;

&lt;p&gt;But in practice, raw tool access is messy.&lt;/p&gt;

&lt;p&gt;If you just hand an agent ten tools, you usually get one of these outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it uses the wrong one&lt;/li&gt;
&lt;li&gt;it uses the right one in the wrong order&lt;/li&gt;
&lt;li&gt;it calls the same thing three times with weak assumptions&lt;/li&gt;
&lt;li&gt;it produces output that technically worked, but isn’t reusable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s because tools are still too low-level.&lt;/p&gt;

&lt;p&gt;A tool is a capability.&lt;br&gt;
A skill is a workflow.&lt;/p&gt;

&lt;p&gt;That difference is everything.&lt;/p&gt;


&lt;h2&gt;
  
  
  A simple example
&lt;/h2&gt;

&lt;p&gt;I’ve seen this in boring, practical work more than once.&lt;/p&gt;

&lt;p&gt;An agent with raw shell access and FFmpeg access can absolutely process a video.&lt;/p&gt;

&lt;p&gt;But “can process a video” is not the same as “can reliably produce the same short-form output every time.”&lt;/p&gt;

&lt;p&gt;The raw-tool version tends to drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong trim point&lt;/li&gt;
&lt;li&gt;audio forgotten&lt;/li&gt;
&lt;li&gt;watermark missing&lt;/li&gt;
&lt;li&gt;output naming inconsistent&lt;/li&gt;
&lt;li&gt;thumbnail skipped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skill version is much narrower, but much more useful.&lt;/p&gt;

&lt;p&gt;It knows the sequence.&lt;br&gt;
It knows the defaults.&lt;br&gt;
It knows what “done” looks like.&lt;/p&gt;

&lt;p&gt;That’s the part people actually want in production.&lt;/p&gt;

&lt;p&gt;Not possibility.&lt;br&gt;
Reliability.&lt;/p&gt;


&lt;h2&gt;
  
  
  What a skill actually is
&lt;/h2&gt;

&lt;p&gt;A skill is not just “the agent can run a command.”&lt;/p&gt;

&lt;p&gt;A useful skill packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the goal&lt;/li&gt;
&lt;li&gt;the right sequence of actions&lt;/li&gt;
&lt;li&gt;defaults&lt;/li&gt;
&lt;li&gt;constraints&lt;/li&gt;
&lt;li&gt;output expectations&lt;/li&gt;
&lt;li&gt;failure handling&lt;/li&gt;
&lt;li&gt;context about when to use it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare these two ideas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool: FFmpeg is available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skill: Turn a raw clip into a 1080x1920 short with trimmed intro, normalized audio, watermark, and thumbnail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second one is what people actually want.&lt;/p&gt;

&lt;p&gt;No one wakes up thinking:&lt;br&gt;
“I hope my agent has access to a media binary.”&lt;/p&gt;

&lt;p&gt;They want the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters for AI agents
&lt;/h2&gt;

&lt;p&gt;The current wave of agent demos still over-indexes on tool access.&lt;/p&gt;

&lt;p&gt;You see a lot of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database access&lt;/li&gt;
&lt;li&gt;filesystem access&lt;/li&gt;
&lt;li&gt;browser access&lt;/li&gt;
&lt;li&gt;shell access&lt;/li&gt;
&lt;li&gt;API access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all necessary.&lt;br&gt;
But it’s still not enough.&lt;/p&gt;

&lt;p&gt;Because once the novelty wears off, teams ask a more practical question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this do the same useful task reliably more than once?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s where most agents still fall apart.&lt;/p&gt;

&lt;p&gt;They can improvise.&lt;br&gt;
They can explore.&lt;br&gt;
They can sometimes solve a task.&lt;/p&gt;

&lt;p&gt;But reliable repeated execution comes from skills.&lt;/p&gt;

&lt;p&gt;Skills are what turn “the agent figured it out once” into “the agent can do this whenever I need it.”&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP helps. Skills deliver.
&lt;/h2&gt;

&lt;p&gt;This is why I think the framing matters.&lt;/p&gt;

&lt;p&gt;MCP is important because it standardizes access.&lt;/p&gt;

&lt;p&gt;That’s good.&lt;/p&gt;

&lt;p&gt;But once access is standardized, the real differentiation shifts somewhere else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which workflows are packaged well&lt;/li&gt;
&lt;li&gt;which tasks are reusable&lt;/li&gt;
&lt;li&gt;which skills are trustworthy&lt;/li&gt;
&lt;li&gt;which outputs are predictable&lt;/li&gt;
&lt;li&gt;which agent behaviors are actually worth repeating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP gives agents hands. Skills give them habits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And habits are what make a system valuable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The more useful pattern
&lt;/h2&gt;

&lt;p&gt;The better pattern is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use MCP to expose capabilities&lt;/li&gt;
&lt;li&gt;use skills to package outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That way the agent is not just connected.&lt;br&gt;
It is directed.&lt;/p&gt;

&lt;p&gt;And that is a much more practical way to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I care about this
&lt;/h2&gt;

&lt;p&gt;This is exactly why I’m bullish on skill-based systems.&lt;/p&gt;

&lt;p&gt;Because once you start packaging repeatable terminal workflows into reusable skills, a lot of noisy AI hype suddenly becomes much simpler.&lt;/p&gt;

&lt;p&gt;You stop asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what tools can this model access?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And start asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what useful work can this system repeat reliably?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better question.&lt;/p&gt;

&lt;p&gt;And in my experience, it leads to much better products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools unlock possibility. Skills unlock reliability.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And reliability is what people actually pay for.&lt;/p&gt;

&lt;p&gt;If you want to see how we think about packaging repeatable terminal workflows into reusable skills, that’s exactly what we’re building at &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;Terminal Skills&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>product</category>
    </item>
    <item>
      <title>How I Built a CLI Skill to Batch-Process YouTube Shorts</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Wed, 18 Mar 2026 04:16:33 +0000</pubDate>
      <link>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-1i68</link>
      <guid>https://dev.to/alexshev/how-i-built-a-cli-skill-to-batch-process-youtube-shorts-1i68</guid>
      <description>&lt;p&gt;Last month, I had to process 16 YouTube Shorts.&lt;/p&gt;

&lt;p&gt;Trim intros. Normalize audio. Add watermarks. Export multiple formats. Generate thumbnails.&lt;/p&gt;

&lt;p&gt;Doing that manually in Premiere would have taken me most of an afternoon.&lt;/p&gt;

&lt;p&gt;So I built a CLI skill instead.&lt;/p&gt;

&lt;p&gt;It took about 2 hours to put together. On my machine, the batch itself finished in under 3 minutes once everything was set up.&lt;/p&gt;

&lt;p&gt;Here’s the exact structure I used.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I mean by a CLI skill
&lt;/h2&gt;

&lt;p&gt;For me, a CLI skill is a reusable shell workflow with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input validation&lt;/li&gt;
&lt;li&gt;sensible defaults&lt;/li&gt;
&lt;li&gt;predictable output&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;lightweight docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of retyping a long FFmpeg command every time, I run one script and get the same result every time.&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;# Instead of this:&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:02 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:00:35 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1080:1920"&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast output.mp4

&lt;span class="c"&gt;# I run this:&lt;/span&gt;
./process-short.sh input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That difference sounds small, but it removes the part that always breaks in real work: remembering the flags, the order, and the output steps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: List the manual steps first
&lt;/h2&gt;

&lt;p&gt;Before I wrote anything, I wrote down the full workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;trim the first 2 seconds&lt;/li&gt;
&lt;li&gt;cut after 35 seconds&lt;/li&gt;
&lt;li&gt;scale to 1080×1920&lt;/li&gt;
&lt;li&gt;normalize audio to -14 LUFS&lt;/li&gt;
&lt;li&gt;add watermark&lt;/li&gt;
&lt;li&gt;export MP4&lt;/li&gt;
&lt;li&gt;export WebM&lt;/li&gt;
&lt;li&gt;generate a thumbnail&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gave me a real pipeline instead of a vague automation idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Build one script that does the boring work
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;TRIM_START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:02"&lt;/span&gt;
&lt;span class="nv"&gt;TRIM_END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:35"&lt;/span&gt;
&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1080:1920"&lt;/span&gt;
&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-14"&lt;/span&gt;
&lt;span class="nv"&gt;WATERMARK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./assets/watermark.png"&lt;/span&gt;
&lt;span class="nv"&gt;THUMB_TIME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"00:00:05"&lt;/span&gt;

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; process-short.sh &amp;lt;input.mp4&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: File '&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;' not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .mp4&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-to&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRIM_END&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;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESOLUTION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUDIO_TARGET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;-preset&lt;/span&gt; fast &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WATERMARK&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;"overlay=W-w-20:H-h-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libvpx-vp9 &lt;span class="nt"&gt;-crf&lt;/span&gt; 30 &lt;span class="nt"&gt;-b&lt;/span&gt;:v 0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.webm"&lt;/span&gt;

ffmpeg &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_final.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-ss&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$THUMB_TIME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASENAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_thumb.jpg"&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trimmed.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few choices mattered a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;set -euo pipefail&lt;/code&gt; so failures don’t get ignored&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-y&lt;/code&gt; because this is a pipeline, not an interactive tool&lt;/li&gt;
&lt;li&gt;temp file cleanup so output folders stay usable&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Make it batch-capable
&lt;/h2&gt;

&lt;p&gt;One file is a demo. A directory is the real use case.&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;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;INPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?Usage:&lt;span class="p"&gt; batch-process.sh &amp;lt;directory&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROCESSED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;FAILED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;continue
    if&lt;/span&gt; ./process-short.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;PROCESSED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAILED: &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;FAILED++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Batch complete: &lt;/span&gt;&lt;span class="nv"&gt;$PROCESSED&lt;/span&gt;&lt;span class="s2"&gt; processed, &lt;/span&gt;&lt;span class="nv"&gt;$FAILED&lt;/span&gt;&lt;span class="s2"&gt; failed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where it actually became useful. I didn’t want a cool script. I wanted to stop babysitting repetitive exports.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Add docs, even if it’s just for yourself
&lt;/h2&gt;

&lt;p&gt;I also added a tiny &lt;code&gt;SKILL.md&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the script does&lt;/li&gt;
&lt;li&gt;requirements&lt;/li&gt;
&lt;li&gt;usage&lt;/li&gt;
&lt;li&gt;config variables&lt;/li&gt;
&lt;li&gt;output files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds boring, but it matters. A script without docs becomes archaeology in two weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Test annoying edge cases
&lt;/h2&gt;

&lt;p&gt;This was the part that caught real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;empty files&lt;/li&gt;
&lt;li&gt;videos without audio&lt;/li&gt;
&lt;li&gt;short clips&lt;/li&gt;
&lt;li&gt;weird filenames with spaces and brackets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That testing found multiple issues immediately. If I had skipped it, the batch version would have failed silently later.&lt;/p&gt;

&lt;p&gt;One of the first things I ran into was how quickly “works on one file” falls apart on real footage. A weird filename, a silent clip, or a shorter-than-expected video is enough to break the whole flow if you never test for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; most of an afternoon for 16 videos&lt;br&gt;&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; one command, then the machine does the repetitive part&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./batch-process.sh ./raw-videos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s still less flexible than Premiere. If I want one-off polish, I’ll still do it manually.&lt;/p&gt;

&lt;p&gt;But for repeatable batch work, the tradeoff is absolutely worth it.&lt;/p&gt;

&lt;p&gt;That’s why I like building CLI skills.&lt;/p&gt;

&lt;p&gt;Not because they’re clever. Because they turn something fragile and repetitive into something boring and reliable.&lt;/p&gt;

&lt;p&gt;If you build little terminal workflows like this too, I’d genuinely love to hear what you’ve automated. I keep more reusable terminal workflow patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;Terminal Skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you automate first?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cli</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>8 FFmpeg Recipes I Use Every Week (That Most Developers Don't Know Exist)</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Wed, 11 Mar 2026 15:21:39 +0000</pubDate>
      <link>https://dev.to/alexshev/8-ffmpeg-recipes-i-use-every-week-that-most-developers-dont-know-exist-5458</link>
      <guid>https://dev.to/alexshev/8-ffmpeg-recipes-i-use-every-week-that-most-developers-dont-know-exist-5458</guid>
      <description>&lt;p&gt;I've been using FFmpeg almost every day for the past year.&lt;/p&gt;

&lt;p&gt;Mostly for boring real work: cutting Shorts, cleaning voice tracks, exporting web versions, generating thumbnails, and fixing weird media issues at the last minute when something breaks five minutes before publish.&lt;/p&gt;

&lt;p&gt;Most FFmpeg tutorials cover the basics and stop there. These are the commands I actually come back to in production.&lt;/p&gt;

&lt;p&gt;Here are 8 recipes I use constantly. Copy-paste ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Extract Audio from Video (and Clean It Up)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract audio only&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-acodec&lt;/span&gt; libmp3lame &lt;span class="nt"&gt;-q&lt;/span&gt;:a 2 audio.mp3

&lt;span class="c"&gt;# Extract + normalize loudness to broadcast standard&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-16:TP=-1.5:LRA=11"&lt;/span&gt; &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 clean_audio.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Podcast edits, voiceover cleanup, and those annoying cases where a track sounds fine in headphones but way too quiet after upload. The &lt;code&gt;loudnorm&lt;/code&gt; filter saved me from re-exporting more than once.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Create a GIF from a Video Clip (That Doesn't Look Terrible)
&lt;/h2&gt;

&lt;p&gt;Most GIF conversions look like they were made in 2004. The trick is a two-pass approach with a custom palette.&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;# Generate optimized palette first&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=15,scale=480:-1:flags=lanczos,palettegen"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    palette.png

&lt;span class="c"&gt;# Then use that palette for the GIF&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; palette.png &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    output.gif

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;palette.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Single-pass GIFs use a generic 256-color palette. The two-pass method generates a palette optimized for your specific clip.&lt;/p&gt;

&lt;p&gt;I learned this the hard way after making a product GIF that looked fine in preview and terrible after export. Washed-out gradients, ugly banding, weird skin tones. Two-pass fixed it immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Batch Convert an Entire Folder
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert all MKV files to MP4 (preserving quality)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.mkv&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="p"&gt;%.mkv&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Convert all WAV to MP3 at 192kbps&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.wav&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-codec&lt;/span&gt;:a libmp3lame &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="p"&gt;%.wav&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp3"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; &lt;code&gt;${f%.ext}&lt;/code&gt; strips the original extension. This is bash string manipulation, not FFmpeg — but it's the glue that makes FFmpeg scriptable.&lt;/p&gt;

&lt;p&gt;This one saved me the most time in practice. Converting one file is nothing. Converting 40 files before lunch is where FFmpeg starts earning its keep.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Split a Video into Equal Chunks
&lt;/h2&gt;

&lt;p&gt;Perfect for breaking long recordings into social-media-sized pieces.&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;# Split into 60-second chunks&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; long_video.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-map&lt;/span&gt; 0 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-segment_time&lt;/span&gt; 60 &lt;span class="nt"&gt;-f&lt;/span&gt; segment &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-reset_timestamps&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
    chunk_%03d.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; &lt;code&gt;chunk_000.mp4&lt;/code&gt;, &lt;code&gt;chunk_001.mp4&lt;/code&gt;, &lt;code&gt;chunk_002.mp4&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-c copy&lt;/code&gt; flag means no re-encoding — it's almost instant regardless of file size. The split happens at keyframes, so chunks might be slightly longer or shorter than 60 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Add Subtitles (Burned In)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From an SRT file&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"subtitles=captions.srt:force_style='FontSize=24,FontName=Arial,PrimaryColour=&amp;amp;HFFFFFF,OutlineColour=&amp;amp;H000000,Outline=2'"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Quick one-liner subtitle (no SRT file needed)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"drawtext=text='Hello World':fontsize=36:fontcolor=white:x=(w-text_w)/2:y=h-th-40:box=1:boxcolor=black@0.6:boxborderw=8"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; The &lt;code&gt;force_style&lt;/code&gt; parameter in the SRT method lets you override subtitle styling without editing the SRT file. Useful when you get captions from auto-transcription services and need them to look consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Picture-in-Picture (Two Videos Overlaid)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Main video with small webcam overlay in bottom-right&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; main.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; webcam.mp4 &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]scale=320:240[pip];[0:v][pip]overlay=W-w-20:H-h-20"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
    pip_output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Variations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top-left: &lt;code&gt;overlay=20:20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Center: &lt;code&gt;overlay=(W-w)/2:(H-h)/2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Animated (slide in): &lt;code&gt;overlay='if(lt(t,1),W,W-w-20)':H-h-20&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use this for tutorial videos — screen recording as the main video, webcam as the PiP.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Speed Up / Slow Down Video
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2x speed (video + audio)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]"&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;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; fast.mp4

&lt;span class="c"&gt;# 0.5x speed (slow motion)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=2.0*PTS[v];[0:a]atempo=0.5[a]"&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;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; slow.mp4

&lt;span class="c"&gt;# 4x speed (atempo max is 2.0, so chain them)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=0.25*PTS[v];[0:a]atempo=2.0,atempo=2.0[a]"&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;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; 4x.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The gotcha:&lt;/strong&gt; &lt;code&gt;setpts&lt;/code&gt; changes video speed, &lt;code&gt;atempo&lt;/code&gt; changes audio speed. They're separate filters. If you only use &lt;code&gt;setpts&lt;/code&gt;, you get a fast video with normal-speed audio — which is funny exactly once.&lt;/p&gt;

&lt;p&gt;Also: &lt;code&gt;atempo&lt;/code&gt; only accepts values between 0.5 and 2.0. For 4x, you chain two &lt;code&gt;atempo=2.0&lt;/code&gt; filters. I still have to look that up sometimes because it’s one of those FFmpeg details that never stays in my head.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Generate a Video from Images (Slideshow / Timelapse)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From numbered images (img001.png, img002.png, etc.)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 24 &lt;span class="nt"&gt;-i&lt;/span&gt; img%03d.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p slideshow.mp4

&lt;span class="c"&gt;# From a folder of images (simple slideshow)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1/3 &lt;span class="nt"&gt;-pattern_type&lt;/span&gt; glob &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'*.jpg'&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;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=25,format=yuv420p"&lt;/span&gt; &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
    slideshow.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I use it:&lt;/strong&gt; Generating timelapse videos from screenshot sequences. Also useful for turning design mockups into a quick presentation video.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-framerate 1/3&lt;/code&gt; means each image shows for 3 seconds. Change the denominator to control display time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Key flags&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extract audio&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-vn&lt;/code&gt; (no video)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No re-encode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-c copy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-crf 18&lt;/code&gt; (lower = better, 0-51)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normalize audio&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-af "loudnorm=I=-16"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale video&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-vf "scale=1920:1080"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trim&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-ss START -to END&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overwrite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-y&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want more of these, I keep a small collection of terminal-first FFmpeg patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;terminalskills.io&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your go-to FFmpeg recipe?&lt;/strong&gt; Mine is still the two-pass GIF trick — not glamorous, but I use it constantly because bad GIF exports are way more common than they should be. If you have a better one, drop it in the comments 👇&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Ditched GUI Tools and My Productivity Doubled — Here Are the 5 Terminal Tools That Did It</title>
      <dc:creator>Alex Shev</dc:creator>
      <pubDate>Sat, 07 Mar 2026 22:03:59 +0000</pubDate>
      <link>https://dev.to/alexshev/i-ditched-gui-tools-and-my-productivity-doubled-here-are-the-5-terminal-tools-that-did-it-4ja2</link>
      <guid>https://dev.to/alexshev/i-ditched-gui-tools-and-my-productivity-doubled-here-are-the-5-terminal-tools-that-did-it-4ja2</guid>
      <description>&lt;p&gt;I used to be a GUI person. Premiere for video. Tower for git. Finder for files.&lt;/p&gt;

&lt;p&gt;Then last year, I started building automation pipelines — video production, data processing, server deploys — and I kept hitting the same wall: GUI tools don't scale. You can't loop a drag-and-drop. You can't pipe a button click.&lt;/p&gt;

&lt;p&gt;So I went terminal-first. And I'm not going back.&lt;/p&gt;

&lt;p&gt;Here are the 5 tools that made the difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;strong&gt;ripgrep (rg)&lt;/strong&gt; — Search That Actually Respects Your Time
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; works. &lt;code&gt;ripgrep&lt;/code&gt; works &lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It respects &lt;code&gt;.gitignore&lt;/code&gt; by default, searches recursively, and handles Unicode. On a 50GB monorepo, the difference isn't seconds — it's minutes.&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;# Search for "TODO" across your entire project, skip node_modules automatically&lt;/span&gt;
rg &lt;span class="s2"&gt;"TODO"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; ts

&lt;span class="c"&gt;# Case-insensitive search with context lines&lt;/span&gt;
rg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"deprecated"&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters in 2026:&lt;/strong&gt; Codebases are bigger than ever. AI-generated code means more files, more boilerplate, more things to search through. You need a search tool that scales.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install ripgrep&lt;/code&gt; / &lt;code&gt;cargo install ripgrep&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;strong&gt;jq&lt;/strong&gt; — JSON Surgery from the Command Line
&lt;/h2&gt;

&lt;p&gt;Every API returns JSON. Every config file is JSON. Every log pipeline outputs JSON.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; lets you slice, filter, and transform it without opening a browser tab or writing a Python script.&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;# Extract all error messages from an API response&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; api.example.com/logs | jq &lt;span class="s1"&gt;'.entries[] | select(.level == "error") | .message'&lt;/span&gt;

&lt;span class="c"&gt;# Reshape data — pull specific fields into a new structure&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;data.json | jq &lt;span class="s1"&gt;'[.users[] | {name: .name, email: .contact.email}]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The real power:&lt;/strong&gt; Piping. Chain &lt;code&gt;curl | jq | xargs&lt;/code&gt; and you've built an API automation pipeline in one line. No dependencies. No virtual environments. No "let me spin up a quick script."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install jq&lt;/code&gt; / &lt;code&gt;apt install jq&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;strong&gt;FFmpeg&lt;/strong&gt; — The Swiss Army Knife Nobody Teaches You
&lt;/h2&gt;

&lt;p&gt;Most developers know FFmpeg exists. Few know it can replace an entire video editing suite.&lt;/p&gt;

&lt;p&gt;I recently produced a 49-second educational short — compositing multiple video layers, syncing audio, adding animated subtitles — entirely from the terminal. No Premiere. No DaVinci Resolve.&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;# Trim a video with frame-accurate precision&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:30 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:02:45 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy trimmed.mp4

&lt;span class="c"&gt;# Overlay a watermark with position control&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; logo.png &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"overlay=W-w-10:H-h-10"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Generate a thumbnail from a specific timestamp&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:15 &lt;span class="nt"&gt;-frames&lt;/span&gt;:v 1 thumbnail.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The unlock:&lt;/strong&gt; Once you can script video processing, you can automate content pipelines. Batch process 100 videos. Auto-generate thumbnails. Convert formats at scale. I've been collecting FFmpeg patterns on &lt;a href="https://terminalskills.io" rel="noopener noreferrer"&gt;terminalskills.io&lt;/a&gt; because the documentation alone could fill a textbook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install ffmpeg&lt;/code&gt; / &lt;code&gt;apt install ffmpeg&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;strong&gt;lazygit&lt;/strong&gt; — Git, But You Can Actually See What's Happening
&lt;/h2&gt;

&lt;p&gt;I love git. I hate remembering whether it's &lt;code&gt;git rebase -i HEAD~3&lt;/code&gt; or &lt;code&gt;git rebase -i HEAD~4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;lazygit gives you a terminal UI for git — staging hunks, interactive rebasing, cherry-picking — without leaving the terminal or touching a mouse.&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;# Just run it in any git repo&lt;/span&gt;
lazygit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changed for me:&lt;/strong&gt; I stopped being afraid of interactive rebases. The visual diff panel means you SEE what you're about to do before you do it. Conflict resolution went from "let me open VS Code" to "done in 10 seconds."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Hit &lt;code&gt;?&lt;/code&gt; inside lazygit for the full keybinding list. &lt;code&gt;space&lt;/code&gt; to stage, &lt;code&gt;c&lt;/code&gt; to commit, &lt;code&gt;p&lt;/code&gt; to push. That's 90% of your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install lazygit&lt;/code&gt; / &lt;code&gt;go install github.com/jesseduffield/lazygit@latest&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. &lt;strong&gt;zoxide&lt;/strong&gt; — cd, But It Remembers Where You've Been
&lt;/h2&gt;

&lt;p&gt;This is the smallest tool on the list and the one I'd miss the most.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zoxide&lt;/code&gt; replaces &lt;code&gt;cd&lt;/code&gt; with a smart directory jumper. It learns which directories you visit most and lets you jump to them with partial names.&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;# Instead of: cd ~/projects/company/backend/services/auth&lt;/span&gt;
&lt;span class="c"&gt;# Just type:&lt;/span&gt;
z auth

&lt;span class="c"&gt;# It knows you mean that specific auth directory&lt;/span&gt;
&lt;span class="c"&gt;# because you've been there 50 times this week&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's not trivial:&lt;/strong&gt; I timed it. On an average workday, I &lt;code&gt;cd&lt;/code&gt; about 80 times. Each full path takes 3-5 seconds to type. zoxide cuts that to under 1 second. That's 5-6 minutes saved daily. Over a year, that's nearly a full workday of just... not typing paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install zoxide&lt;/code&gt; / &lt;code&gt;curl -sSfL https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;None of these tools are new. But together, they changed how I think about work.&lt;/p&gt;

&lt;p&gt;GUI tools are great for one-off tasks. But the moment you need to do something twice — the terminal wins. Every time.&lt;/p&gt;

&lt;p&gt;I went from "let me open the app" to "let me write the command" and my throughput doubled. Not because I'm faster at typing. Because scripts don't forget steps, don't get tired, and don't need a mouse.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your terminal setup look like?&lt;/strong&gt; I'm always looking for tools I've missed — I bet someone here has a workflow that would blow my mind. Drop it in the comments 👇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Some parts of this article were refined with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
