{"id":"00754f8b-9fc0-4a65-aba0-8572d06bfccc","shortId":"FDuGFC","kind":"skill","title":"video-to-gif","tagline":"Convert an MP4 (e.g. a HyperFrames render or a screen recording) to a high-quality GIF using FFmpeg's two-pass palette method. Picks the right preset for the target surface — Slack/DMs/Twitter/email/Reddit/GitHub — so you don't ship a 40MB GIF to a 5MB inbox.","description":"# Video to GIF\n\nConvert a video (MP4, MOV, WebM) to a GIF that's **actually small** and **actually looks good** on the surface you're posting to. Pairs naturally with `/tiktok-launch-video`, `/reddit-launch-video`, and `/linkedin-launch-video` for sharing renders in places where MP4 isn't supported (Slack threads, X cards, GitHub READMEs, transactional emails).\n\nGIF is a terrible video format — but it's everywhere, so we use FFmpeg's two-pass `palettegen` / `paletteuse` flow to make it look as good as the format allows.\n\n## Usage\n\n`/video-to-gif launch.mp4` — interactive, asks the target\n`/video-to-gif launch.mp4 slack` — Slack-tuned\n`/video-to-gif launch.mp4 reddit` — Reddit-comment-tuned\n`/video-to-gif launch.mp4 github` — GitHub README–tuned\n`/video-to-gif launch.mp4 twitter` — X / Twitter–tuned\n`/video-to-gif launch.mp4 email` — transactional / newsletter–tuned\n\n## Prerequisites\n\n```bash\nffmpeg -version | head -1   # must exist\nffprobe -version | head -1\n```\n\nIf FFmpeg isn't installed:\n\n```bash\n# macOS\nbrew install ffmpeg\n# Debian/Ubuntu\nsudo apt install ffmpeg\n```\n\n## Steps\n\n### 1. Inspect the source\n\nRun `ffprobe` to get the source's duration, dimensions, and frame rate. You need these to pick the right preset and to warn the user before producing something that won't fit.\n\n```bash\nffprobe -v error -show_entries stream=width,height,r_frame_rate -show_entries format=duration -of default=noprint_wrappers=1 <input>\n```\n\nCapture: source width, source height, source duration (seconds), source fps.\n\n### 2. Pick the preset\n\nIf the user passed a target, use the matching preset. Otherwise ask. Each preset is tuned to the surface's actual size limits and aesthetic norms:\n\n| Preset | Width | FPS | Loop | Why |\n|--------|------:|----:|------|-----|\n| **slack** | 480 | 12 | yes | 50MB upload cap; threads scroll fast; readability > smoothness |\n| **twitter** | 480 | 15 | yes | 15MB GIF cap; auto-converted to MP4 above the cap, kills the loop |\n| **reddit** | 600 | 15 | yes | 100MB cap but image-host sidebar dies past 20MB |\n| **github** | 720 | 15 | yes | 10MB README cap; over the cap GitHub silently drops the file |\n| **email** | 480 | 10 | yes | 5MB Gmail clip threshold; many ESPs strip animation past 1MB |\n| **web** | 800 | 18 | yes | Generic fallback for landing pages and product blogs |\n\n**Rules of thumb:**\n\n- If source is < 6 seconds, you can usually bump fps by ~3 without breaking the size budget.\n- If source is > 12 seconds, drop fps by 2–3 and consider trimming.\n- Never upscale — if source width is below the preset width, keep source width.\n- Strip audio (GIF can't carry it) — saves a step and removes accidental noise from screen captures.\n\n### 3. Two-pass palette render (this is the only correct way)\n\nA single-pass GIF render produces banded, dithered, oversized garbage. The right approach is two passes:\n\n1. Generate an optimal 256-color palette from the source.\n2. Apply that palette with Floyd-Steinberg dithering.\n\nThe pipeline is FFmpeg's `palettegen` filter feeding `paletteuse`. Do it as a single command using filter splits:\n\n```bash\nffmpeg -y -i \"<input>\" \\\n  -vf \"fps=<FPS>,scale=<WIDTH>:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg\" \\\n  -loop 0 \\\n  \"<output.gif>\"\n```\n\nNotes on the filter chain:\n\n- `fps=<FPS>` first — so palette is generated from the actual frames you'll keep.\n- `scale=<WIDTH>:-1` keeps aspect; `-1` rounds the height. Use `-2` for codecs that need even dimensions (not needed for GIF, but harmless).\n- `flags=lanczos` is the best general-purpose downscaler.\n- `stats_mode=diff` weights moving regions higher when picking palette colors — sharper text, cleaner UI, less banding.\n- `dither=floyd_steinberg` is the best default. For UI / sharp edges with no gradients, try `dither=bayer:bayer_scale=3` instead — slightly larger files but no dither noise on flat colors.\n- `-loop 0` = infinite loop. Use `-loop 1` to play once. (For Slack and Twitter, infinite loop is what users expect.)\n\n### 4. Render and verify\n\nRun the command. Then verify:\n\n```bash\n# Check the file size\nls -lh \"<output.gif>\"\n\n# Check the GIF's actual dimensions and frame count\nffprobe -v error -count_frames -select_streams v:0 \\\n  -show_entries stream=width,height,nb_read_frames \\\n  -of default=noprint_wrappers=1 \"<output.gif>\"\n```\n\nIf the file is larger than the preset's surface cap (see table above), do **one** of:\n\n1. Drop FPS by 2–3 and re-render (best quality preserved).\n2. Drop width by ~80px and re-render (text may get harder to read).\n3. Trim the source first (`-ss <start> -t <duration>`) and re-render.\n\nDon't mix all three — change one variable at a time so you know what helped.\n\n### 5. (Optional) Trim before converting\n\nIf the source is long, trim *before* the palette pass — the palette will be more accurate and the file smaller.\n\n```bash\n# Take 8 seconds starting at 2.5s in\nffmpeg -y -ss 2.5 -t 8 -i \"<input>\" -c copy \"<input>.trimmed.mp4\"\n# then run the two-pass palette command on .trimmed.mp4\n```\n\n`-ss` *before* `-i` is keyframe-fast but slightly imprecise. For frame-accurate trims, put `-ss` *after* `-i` (slower).\n\n### 6. Hand the file back\n\nPrint:\n\n1. Path to the output GIF\n2. Final size on disk\n3. Final dimensions and frame count\n4. Surface cap status: \"fits Slack (50MB) ✓\" / \"exceeds GitHub README cap (10MB), retry with fps=12 ✗\"\n\n## One-liners (for when the user knows what they want)\n\n```bash\n# Slack-tuned (480px / 12fps)\nffmpeg -y -i in.mp4 -vf \"fps=12,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg\" -loop 0 out.gif\n\n# Twitter-tuned (480px / 15fps)\nffmpeg -y -i in.mp4 -vf \"fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg\" -loop 0 out.gif\n\n# GitHub README-tuned (720px / 15fps)\nffmpeg -y -i in.mp4 -vf \"fps=15,scale=720:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg\" -loop 0 out.gif\n\n# Email-tuned (480px / 10fps, single loop)\nffmpeg -y -i in.mp4 -vf \"fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=floyd_steinberg\" -loop 0 out.gif\n```\n\n## Common mistakes\n\n1. **Single-pass GIF render** — `ffmpeg -i in.mp4 out.gif` produces ugly, oversized output. Always use the palette pipeline.\n2. **Upscaling** — never scale GIFs *up*; they go from \"bad\" to \"extremely bad\". Match or downscale only.\n3. **High FPS for talking-head content** — anything above 18fps is wasted on a GIF; drop to 12–15fps and put the bytes into resolution instead.\n4. **Including audio** — GIF can't carry it. If you need audio, the surface supports MP4 and you should ship MP4 instead.\n5. **Long GIFs (> 15s)** — if you need 30+ seconds, you probably need MP4 or WebP, not GIF. Ask the user if a different format works.\n6. **Sending a 40MB GIF to email** — most ESPs strip the animation past 1MB, leaving the recipient with a static first frame. Use the email preset or fall back to a static PNG hero with a link.\n\n## Related\n\n- `/tiktok-launch-video`, `/reddit-launch-video`, `/linkedin-launch-video` — produce the source MP4 this skill converts\n- `/welcome-series`, `/winback-engine` — places where a small embedded GIF lifts engagement","tags":["video","gif","claude","code","marketing","skills","cognyai","agent-skills","ai-agents","claude-code","claude-skills","cluade-mcp"],"capabilities":["skill","source-cognyai","skill-video-to-gif","topic-agent-skills","topic-ai-agents","topic-claude-code","topic-claude-skills","topic-cluade-mcp","topic-cursor","topic-geo","topic-growth-hacking","topic-llm","topic-marketing","topic-mcp","topic-seo"],"categories":["claude-code-marketing-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cognyai/claude-code-marketing-skills/video-to-gif","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cognyai/claude-code-marketing-skills","source_repo":"https://github.com/cognyai/claude-code-marketing-skills","install_from":"skills.sh"}},"qualityScore":"0.474","qualityRationale":"deterministic score 0.47 from registry signals: · indexed on github topic:agent-skills · 48 github stars · SKILL.md body (7,330 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:58:08.217Z","embedding":null,"createdAt":"2026-04-25T12:56:04.325Z","updatedAt":"2026-05-18T18:58:08.217Z","lastSeenAt":"2026-05-18T18:58:08.217Z","tsv":"'-1':177,183,523,562,565,918,953,989,1026 '-2':570 '/linkedin-launch-video':84,1199 '/reddit-launch-video':82,1198 '/tiktok-launch-video':81,1197 '/video-to-gif':135,141,147,154,160,166 '/welcome-series':1207 '/winback-engine':1208 '0':542,641,693,937,972,1008,1045 '1':200,256,479,646,706,724,859,1049 '10':363,1023 '100mb':336 '10fps':1014 '10mb':350,887 '12':304,410,891,915,1103 '12fps':908 '15':316,334,348,950,986 '15fps':943,979,1104 '15mb':318 '15s':1137 '18':377 '18fps':1095 '1mb':374,1172 '2':267,415,489,728,737,865,1068 '2.5':810,816 '20mb':345 '256':483 '3':401,416,450,628,729,752,870,1085 '30':1141 '4':660,876,1112 '40mb':45,1162 '480':303,315,362,917,952,1025 '480px':907,942,1013 '5':779,1134 '50mb':306,882 '5mb':49,365 '6':393,853,1159 '600':333 '720':347,988 '720px':978 '8':806,818 '800':376 '80px':741 'accident':445 'accur':799,846 'actual':65,68,291,556,680 'aesthet':295 'allow':133 'alway':1063 'anim':372,1170 'anyth':1093 'appli':490 'approach':475 'apt':196 'ask':138,282,1151 'aspect':564 'audio':434,1114,1123 'auto':322 'auto-convert':321 'back':857,1187 'bad':1077,1080 'band':469,608 'bash':173,189,236,516,669,804,903 'bayer':625,626 'best':587,614,734 'blog':386 'break':403 'brew':191 'budget':406 'bump':398 'byte':1108 'c':820 'cap':308,320,328,337,352,355,717,878,886 'captur':257,449 'card':98 'carri':438,1118 'chain':547 'chang':768 'check':670,676 'cleaner':605 'clip':367 'codec':572 'color':484,602,639 'command':512,666,830 'comment':152 'common':1047 'consid':418 'content':1092 'convert':5,54,323,783,1206 'copi':821 'correct':460 'count':684,688,875 'debian/ubuntu':194 'default':253,615,703 'die':343 'diff':533,594,928,963,999,1036 'differ':1156 'dimens':212,576,681,872 'disk':869 'dither':470,497,538,609,624,635,933,968,1004,1041 'downscal':591,1083 'drop':358,412,725,738,1101 'durat':211,251,263 'e.g':8 'edg':619 'email':102,168,361,1011,1165,1183 'email-tun':1010 'embed':1213 'engag':1216 'entri':241,249,695 'error':239,687 'esp':370,1167 'even':575 'everywher':112 'exceed':883 'exist':179 'expect':659 'extrem':1079 'fall':1186 'fallback':380 'fast':311,839 'feed':505 'ffmpeg':23,116,174,185,193,198,501,517,813,909,944,980,1017,1055 'ffprobe':180,205,237,685 'file':360,632,672,709,802,856 'filter':504,514,546 'final':866,871 'first':549,756,1179 'fit':235,880 'flag':524,583,919,954,990,1027 'flat':638 'flow':123 'floyd':495,539,610,934,969,1005,1042 'floyd-steinberg':494 'format':108,132,250,1157 'fps':266,299,399,413,521,548,726,890,914,949,985,1022,1087 'frame':214,246,557,683,689,701,845,874,1180 'frame-accur':844 'garbag':472 'general':589 'general-purpos':588 'generat':480,553 'generic':379 'get':207,748 'gif':4,21,46,53,62,103,319,435,466,580,678,864,1053,1072,1100,1115,1136,1150,1163,1214 'github':99,156,157,346,356,884,974 'gmail':366 'go':1075 'good':70,129 'gradient':622 'hand':854 'harder':749 'harmless':582 'head':176,182,1091 'height':244,261,568,698 'help':778 'hero':1192 'high':19,1086 'high-qual':18 'higher':598 'host':341 'hyperfram':10 'imag':340 'image-host':339 'imprecis':842 'in.mp4':912,947,983,1020,1057 'inbox':50 'includ':1113 'infinit':642,654 'inspect':201 'instal':188,192,197 'instead':629,1111,1133 'interact':137 'isn':92,186 'keep':430,560,563 'keyfram':838 'keyframe-fast':837 'kill':329 'know':776,899 'lanczo':525,584,920,955,991,1028 'land':382 'larger':631,711 'launch.mp4':136,142,148,155,161,167 'leav':1173 'less':607 'lh':675 'lift':1215 'limit':293 'liner':894 'link':1195 'll':559 'long':788,1135 'look':69,127 'loop':300,331,541,640,643,645,655,936,971,1007,1016,1044 'ls':674 'maco':190 'make':125 'mani':369 'match':279,1081 'may':747 'method':29 'mistak':1048 'mix':765 'mode':532,593,927,962,998,1035 'mov':58 'move':596 'mp4':7,57,91,325,1127,1132,1146,1203 'must':178 'natur':79 'nb':699 'need':217,574,578,1122,1140,1145 'never':420,1070 'newslett':170 'nois':446,636 'noprint':254,704 'norm':296 'note':543 'one':722,769,893 'one-lin':892 'optim':482 'option':780 'otherwis':281 'out.gif':938,973,1009,1046,1058 'output':863,1062 'overs':471,1061 'p':534,536,929,931,964,966,1000,1002,1037,1039 'page':383 'pair':78 'palett':28,454,485,492,551,601,792,795,829,1066 'palettegen':121,503,530,925,960,996,1033 'paletteus':122,506,537,932,967,1003,1040 'pass':27,120,274,453,465,478,793,828,1052 'past':344,373,1171 'path':860 'pick':30,220,268,600 'pipelin':499,1067 'place':89,1209 'play':648 'png':1191 'post':76 'prerequisit':172 'preserv':736 'preset':33,223,270,280,284,297,428,714,1184 'print':858 'probabl':1144 'produc':230,468,1059,1200 'product':385 'purpos':590 'put':848,1106 'qualiti':20,735 'r':245 'rate':215,247 're':75,732,744,761 're-rend':731,743,760 'read':700,751 'readabl':312 'readm':100,158,351,885,976 'readme-tun':975 'recipi':1175 'record':15 'reddit':149,151,332 'reddit-comment-tun':150 'region':597 'relat':1196 'remov':444 'render':11,87,455,467,661,733,745,762,1054 'resolut':1110 'retri':888 'right':32,222,474 'round':566 'rule':387 'run':204,664,824 's0':527,529,922,924,957,959,993,995,1030,1032 's1':528,535,923,930,958,965,994,1001,1031,1038 'save':440 'scale':522,561,627,916,951,987,1024,1071 'screen':14,448 'scroll':310 'second':264,394,411,807,1142 'see':718 'select':690 'send':1160 'share':86 'sharp':618 'sharper':603 'ship':43,1131 'show':240,248,694 'sidebar':342 'silent':357 'singl':464,511,1015,1051 'single-pass':463,1050 'size':292,405,673,867 'skill':1205 'skill-video-to-gif' 'slack':95,143,145,302,651,881,905 'slack-tun':144,904 'slack/dms/twitter/email/reddit/github':38 'slight':630,841 'slower':852 'small':66,1212 'smaller':803 'smooth':313 'someth':231 'sourc':203,209,258,260,262,265,391,408,423,431,488,755,786,1202 'source-cognyai' 'split':515,526,921,956,992,1029 'ss':757,815,833,849 'start':808 'stat':531,592,926,961,997,1034 'static':1178,1190 'status':879 'steinberg':496,540,611,935,970,1006,1043 'step':199,442 'stream':242,691,696 'strip':371,433,1168 'sudo':195 'support':94,1126 'surfac':37,73,289,716,877,1125 'tabl':719 'take':805 'talk':1090 'talking-head':1089 'target':36,140,276 'terribl':106 'text':604,746 'thread':96,309 'three':767 'threshold':368 'thumb':389 'time':773 'topic-agent-skills' 'topic-ai-agents' 'topic-claude-code' 'topic-claude-skills' 'topic-cluade-mcp' 'topic-cursor' 'topic-geo' 'topic-growth-hacking' 'topic-llm' 'topic-marketing' 'topic-mcp' 'topic-seo' 'transact':101,169 'tri':623 'trim':419,753,781,789,847 'trimmed.mp4':822,832 'tune':146,153,159,165,171,286,906,941,977,1012 'twitter':162,164,314,653,940 'twitter-tun':939 'two':26,119,452,477,827 'two-pass':25,118,451,826 'ugli':1060 'ui':606,617 'upload':307 'upscal':421,1069 'usag':134 'use':22,115,277,513,569,644,1064,1181 'user':228,273,658,898,1153 'usual':397 'v':238,686,692 'variabl':770 'verifi':663,668 'version':175,181 'vf':520,913,948,984,1021 'video':2,51,56,107 'video-to-gif':1 'want':902 'warn':226 'wast':1097 'way':461 'web':375 'webm':59 'webp':1148 'weight':595 'width':243,259,298,424,429,432,697,739 'without':402 'won':233 'work':1158 'wrapper':255,705 'x':97,163 'y':518,814,910,945,981,1018 'yes':305,317,335,349,364,378","prices":[{"id":"05b2bd5d-1b83-476e-9ae3-b959821b88b1","listingId":"00754f8b-9fc0-4a65-aba0-8572d06bfccc","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cognyai","category":"claude-code-marketing-skills","install_from":"skills.sh"},"createdAt":"2026-04-25T12:56:04.325Z"}],"sources":[{"listingId":"00754f8b-9fc0-4a65-aba0-8572d06bfccc","source":"github","sourceId":"cognyai/claude-code-marketing-skills/video-to-gif","sourceUrl":"https://github.com/cognyai/claude-code-marketing-skills/tree/main/skills/video-to-gif","isPrimary":false,"firstSeenAt":"2026-04-25T12:56:04.325Z","lastSeenAt":"2026-05-18T18:58:08.217Z"}],"details":{"listingId":"00754f8b-9fc0-4a65-aba0-8572d06bfccc","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cognyai","slug":"video-to-gif","github":{"repo":"cognyai/claude-code-marketing-skills","stars":48,"topics":["agent-skills","ai-agents","claude-code","claude-skills","cluade-mcp","cursor","geo","growth-hacking","llm","marketing","mcp","seo","vibe","windsurf"],"license":null,"html_url":"https://github.com/cognyai/claude-code-marketing-skills","pushed_at":"2026-05-07T20:57:53Z","description":"Marketing skills for Claude Code — SEO audits and implementation, ad analysis, ad optimization. Free skills need no account. $9/mo for live Search Console, Bing & LinkedIn data.","skill_md_sha":"68018326095d4ab3d1795b9b2ca848fd57ec5f72","skill_md_path":"skills/video-to-gif/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cognyai/claude-code-marketing-skills/tree/main/skills/video-to-gif"},"layout":"multi","source":"github","category":"claude-code-marketing-skills","frontmatter":{"name":"video-to-gif","description":"Convert an MP4 (e.g. a HyperFrames render or a screen recording) to a high-quality GIF using FFmpeg's two-pass palette method. Picks the right preset for the target surface — Slack/DMs/Twitter/email/Reddit/GitHub — so you don't ship a 40MB GIF to a 5MB inbox."},"skills_sh_url":"https://skills.sh/cognyai/claude-code-marketing-skills/video-to-gif"},"updatedAt":"2026-05-18T18:58:08.217Z"}}