{"id":"865083ba-d69a-43ab-b9a2-410707190f6c","slug":"map-shorts","kind":"skill","name":"map-shorts","summary":"Open-source agent skill for vertical 3D map-explainer Shorts: NASA globe to real terrain in one shot, narration, word-synced captions. Beta.","owner":"Heenzaa1998","repoUrl":"https://github.com/Heenzaa1998/map-shorts","license":"MIT","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-10-30T09:20:00.000Z","addedAt":"2026-11-01T13:53:47.797Z","logoUrl":"https://avatars.githubusercontent.com/u/124670034?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["design-media","developer-tools","files-documents"],"tags":["claude-code","maps","threejs","video-generation","youtube-shorts"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-11-01T16:11:51.720Z","sample":false,"url":"https://conduits.dev/tools/map-shorts","language":"JavaScript","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# map-shorts\n\n**Vertical 3D map-explainer videos from a JSON file — an open-source skill for AI coding agents.**\n\nYou (or your agent) write a short script and a `spec.json`. map-shorts voices it, times every graphic to the spoken words, flies one continuous camera from a spinning NASA globe down to real satellite terrain, and renders a 1080×1920 MP4 ready for YouTube Shorts, Reels or TikTok.\n\n![status: beta](https://img.shields.io/badge/status-beta-orange) ![license: MIT](https://img.shields.io/badge/license-MIT-blue) ![works with: Claude Code](https://img.shields.io/badge/works%20with-Claude%20Code-8A63D2)\n\n<p align=\"center\">\n  <img src=\"docs/demo.gif\" width=\"270\" alt=\"Demo: the camera dives from the globe to Mount Fuji, a counter counts up to 3,776 m, captions highlight each spoken word\">\n</p>\n\n<p align=\"center\"><img src=\"docs/frames.jpg\" alt=\"Six frames from the Mount Fuji example: globe opener, distance line to Tokyo, height counter, ash-fall rings, info card, closing question\"></p>\n<p align=\"center\"><sub>The <code>examples/fuji</code> clip, rendered with the free draft voice. One shot, no cuts: the globe opener, a line to Tokyo, a height counter, eruption rings, an info card and a closing question.</sub></p>\n\n<p align=\"center\"><img src=\"docs/thai.jpg\" alt=\"Thai example: Thai title on the globe, Chi river drawn on the terrain with a length counter, Thai captions\"></p>\n<p align=\"center\"><sub>Thai works too (<code>examples/chi</code>): a Thai title, word-split Thai captions, a river traced on the relief and a province overlay.</sub></p>\n\n---\n\n## เริ่มต้นใช้งาน (ภาษาไทย)\n\nmap-shorts คือ skill ฟรีสำหรับ Claude Code ใช้ทำคลิปแนวตั้งอธิบายเรื่องราวบนแผนที่ 3D: กล้องดิ่งจากลูกโลกลงไปยังภูมิประเทศจริงในช็อตเดียว มีเสียงพากย์ ซับขึ้นตามคำพูด การ์ดข้อมูล และเส้นแม่น้ำหรือเขตจังหวัดบนแผนที่ ได้ไฟล์ MP4 ขนาด 1080×1920 พร้อมลง Shorts, Reels หรือ TikTok\n\n**สิ่งที่ต้องมี:** [Claude Code](https://claude.com/claude-code) อย่างเดียว ที่เหลือให้ Claude ติดตั้งให้\n\n**ขั้นที่ 1: ติดตั้ง** เปิด Claude Code ในโฟลเดอร์ว่างๆ แล้วคัดลอกข้อความนี้ไปวางทั้งก้อน\n\n```text\nติดตั้ง map-shorts ให้หน่อย: clone https://github.com/Heenzaa1998/map-shorts ลงโฟลเดอร์นี้ แล้วทำตามหัวข้อ Install ใน README.md ให้ครบทุกขั้น ถ้าเครื่องยังไม่มี Python, Node.js หรือ ffmpeg ให้บอกคำสั่งติดตั้งที่เหมาะกับระบบของฉัน และขออนุญาตก่อนติดตั้งทุกครั้ง จากนั้นคัดลอกโฟลเดอร์ skill/map-shorts ไปไว้ที่ ~/.claude/skills/ แล้วรัน check_setup.py จนไม่มีบรรทัด -- เหลือ (บรรทัด opt ข้ามได้) สุดท้ายทำคลิปตัวอย่าง examples/fuji ให้ฉันดู โดยส่งภาพนิ่งให้ดูก่อน และถ้าเครื่องไม่มีการ์ดจอให้บอกก่อนเรนเดอร์เต็ม\n```\n\n**ขั้นที่ 2: สั่งทำคลิป** เช่น\n\n```text\nทำคลิป map-shorts เรื่องทำไมกรุงเทพถึงน้ำท่วมง่าย เป็นภาษาไทย\n```\n\nClaude จะถามคำถามสั้นๆ ก่อน (หัวข้อ คนดู ช่องหารายได้หรือยัง ใช้เสียงอะไร) จากนั้นเสนอมุมเล่า เขียนสคริปต์ให้คุณอนุมัติ แล้วส่งภาพตัวอย่างให้ดูก่อนเรนเดอร์จริง\n\n**ต้องรู้ก่อนใช้**\n- **เสียงพากย์:** เสียงฟรี (Edge TTS) ใช้ทำร่างได้เท่านั้น ห้ามใช้กับช่องที่หารายได้ ถ้าจะลงจริงให้ใช้ ElevenLabs แบบเสียเงิน ([รายละเอียด](#voices))\n- **ความเร็ว:** เครื่องที่มีการ์ดจอ (รวมถึงการ์ดจอในตัวของโน้ตบุ๊กรุ่นใหม่) เรนเดอร์คลิป 30 วินาทีเสร็จในราว 1 นาที ถ้าไม่มีจะช้ามาก (ราว 30 นาที)\n- **เครดิต:** ต้องใส่เครดิตภาพดาวเทียมและข้อมูลความสูงไว้ในคำอธิบายคลิปทุกครั้ง มีข้อความให้คัดลอกในหัวข้อ [Credits](#credits-you-must-put-on-your-videos)\n- **สถานะ:** ยังเป็นรุ่น beta ถ้าเจอปัญหา เปิด issue ได้ที่ GitHub\n\n---\n\n## Contents\n- [เริ่มต้นใช้งาน (ภาษาไทย)](#เริ่มต้นใช้งาน-ภาษาไทย)\n- [What you get](#what-you-get)\n- [How it works](#how-it-works)\n- [Requirements](#requirements)\n- [Install](#install)\n- [Make a clip with your agent](#make-a-clip-with-your-agent)\n- [Make the example by hand](#make-the-example-by-hand)\n- [Writing a spec](#writing-a-spec)\n- [Voices](#voices)\n- [Credits you must put on your videos](#credits-you-must-put-on-your-videos)\n- [Rendering speed and GPUs](#rendering-speed-and-gpus)\n- [Troubleshooting](#troubleshooting)\n- [Known limitations](#known-limitations)\n- [Privacy and safety](#privacy-and-safety)\n- [Project layout](#project-layout)\n- [Contributing](#contributing) · [License](#license)\n\n---\n\n## What you get\n\n**The look**\n- One continuous camera shot: globe → country → close-up over exaggerated real terrain. No stock footage, no cuts.\n- Map overlays: country and province fills, glowing beacons, rings, lines and arcs, and rivers traced on the relief with flowing drops.\n- Screen graphics: titles, glass info cards with icons, counters that count up, labels pinned to places, a flash for big moments.\n- Captions that highlight each word as it is spoken (Thai is split into real words).\n\n**The method** — the skill carries what we learned running a map Shorts channel, so the agent does more than run commands:\n- how to pick topics people watch to the end: viewer stakes, a common wrong belief, and a visual only a 3D map can give;\n- script and hook rules, and pacing (something new on screen every 3–4 s);\n- a QA checklist, fact-checking and honesty rules;\n- the agent asks you a few questions first and gives advice at the right moments (voice quality, licences, render speed, weak topics, unverified numbers, reading your day-3 analytics).\n\n## How it works\n\n```mermaid\nflowchart LR\n  A[Topic + script] --> B[spec.json]\n  M[make_site.py<br/>satellite imagery + terrain] --> D\n  G[make_geo.py<br/>provinces + rivers] --> D\n  B --> C[build.py<br/>voice, word timing, timeline]\n  C --> D[world.html<br/>three.js scene]\n  D --> E[render.mjs<br/>headless Chrome frames]\n  E --> F[encode.py<br/>MP4 1080x1920]\n```\n\nEvery time in the spec can point at a spoken word: `\"@2:Ash\"` means \"when the word *Ash* starts in line 2\". If you change the script, the camera and the graphics move with the new timing automatically.\n\n## Requirements\n\n| Need | Why | Check |\n|---|---|---|\n| Python 3.10+ | build, map data | `python3 --version` |\n| Node.js 18+ | rendering | `node --version` |\n| ffmpeg + ffprobe | audio and video | `ffmpeg -version` (Windows: `winget install --id Gyan.FFmpeg -e`, macOS: `brew install ffmpeg`) |\n| Chrome or Chromium | renders the 3D page | installed by Playwright, or pass `--chrome <path>` |\n| Internet | voices, map tiles, Natural Earth (first run) | — |\n| An AI coding agent (recommended) | writes and tunes the spec with you | e.g. [Claude Code](https://claude.com/claude-code) |\n| A GPU (recommended; a laptop iGPU is enough) | about 30× faster renders (measured) | the render prints `renderer: ...` |\n\n## Install\n\n```bash\n# 1. get the code\ngit clone https://github.com/Heenzaa1998/map-shorts.git\ncd map-shorts\n\n# 2. Python packages (a virtual environment is a good idea)\npython3 -m venv .venv && source .venv/bin/activate      # Windows: .venv\\Scripts\\activate\npip install -r requirements.txt\n\n# 3. the headless browser driver (use playwright-core: it matches the pinned version)\nnpm install\nnpx playwright-core install chromium\n\n# 4. check everything\npython3 skill/map-shorts/scripts/check_setup.py .\n```\n\nCloning creates a `map-shorts/` folder; run the rest inside it.\n\nThe check prints `OK`, `--` (needed, with the fix) or `opt` (optional, e.g. an ElevenLabs key) for each part. It changes nothing.\n\nThe browser goes to Playwright's cache (`~/.cache/ms-playwright`, on Windows `%LOCALAPPDATA%\\ms-playwright`), about 200 MB. Use `npx playwright-core ...`, not `npx playwright ...`: the latter fetches a browser for a different version.\n\n**Or let Claude Code install it:** paste this into Claude Code in an empty folder:\n\n```text\nInstall map-shorts for me: clone https://github.com/Heenzaa1998/map-shorts into this folder and follow every step of the Install section in README.md. If Python, Node.js or ffmpeg is missing, tell me the right install command for my system and ask before installing anything. Then copy skill/map-shorts to ~/.claude/skills/, run check_setup.py until no \"--\" lines remain (\"opt\" lines are optional), and make the examples/fuji clip for me: show me stills first, and if there is no GPU tell me before the full render.\n```\n\n**Add the skill to your agent.** For Claude Code:\n\n```bash\ncp -r skill/map-shorts ~/.claude/skills/\n```\n\nOther agents that read `SKILL.md` folders work the same way: point them at `skill/map-shorts`.\n\n## Make a clip with your agent\n\nOpen your agent in the folder where you ran `npm install` and ask, for example:\n\n> Make a map-shorts clip about why the Dead Sea is so salty.\n\n> ทำคลิป map-shorts เรื่องทำไมกรุงเทพถึงน้ำท่วมง่าย เป็นภาษาไทย\n\nThe agent follows the skill:\n\n1. **Asks first:** topic and audience, whether the channel is monetised (this decides which voices and data are allowed), which voice to use, whether you have a GPU, and whether you have your own music.\n2. **Suggests a stronger angle** for the topic, then writes a 4–7 line script for you to approve.\n3. **Downloads map data** for the area.\n4. **Writes the spec and builds it.**\n5. **Shows you still frames** so framing can be fixed cheaply, before the slow full render.\n6. **Renders and encodes the MP4.** Then it asks you to **listen to the voice** (only a human catches a mispronounced name).\n7. **Drafts the title, description, sources and credits.** It never publishes for you.\n\n## Make the example by hand\n\nUseful to check your setup, or to learn the format.\n\n```bash\nS=skill/map-shorts/scripts\n\n# 1. map data for the area (cached in examples/fuji/.tiles)\npython3 $S/make_site.py examples/fuji --name region --box 26 44 126 146 --zimg 8 --zter 7 --grid 512   # wide view\npython3 $S/make_site.py examples/fuji --box 34.9 36.0 138.0 139.4 --zimg 12 --zter 11                    # close-up\n\n# 2. voice + timing + scene\npython3 $S/build.py examples/fuji\n\n# 3. look at a few stills first (seconds)\nnode $S/render.mjs examples/fuji --times 3,8,15,22\n#    -> examples/fuji/build/frames/t3.jpg ...\n\n# 4. full render (minutes) and encode\nnode $S/render.mjs examples/fuji\npython3 $S/encode.py examples/fuji v1\n#    -> examples/fuji/build/fuji-v1.mp4\n```\n\n`--box` is `south north west east` in degrees. Region patches are typically 15–30° wide; close-ups are 1–2° wide.\n\nThe Thai example also needs provinces and rivers:\n\n```bash\npython3 $S/make_site.py examples/chi --name region --box 5 21 97 106 --zimg 8 --zter 7 --grid 512\npython3 $S/make_geo.py  examples/chi --region \"KhonKaen_UdonThani=Thailand:Khon Kaen,Udon Thani\" --river \"chi=Chi\"\npython3 $S/build.py     examples/chi\n```\n\n`make_geo.py --list-provinces Thailand` prints the province names Natural Earth uses for a country.\n\n## Writing a spec\n\nA small but complete spec:\n\n```json\n{\n  \"voice\": {\"provider\": \"edge\", \"voice\": \"en-US-AriaNeural\"},\n  \"lines\": [\n    \"Mount Fuji is an active volcano, about a hundred kilometers from Tokyo.\",\n    \"It rises 3,776 meters, the tallest mountain in Japan.\"\n  ],\n  \"spoken\": {\"3,776\": \"three thousand seven hundred seventy six\"},\n  \"relief\": {\"ex\": 3, \"k0\": 0.15, \"at\": \"@0:Mount\", \"dur\": 2.6, \"hclamp\": 4000},\n  \"cam\": [\n    [2.0, 137.0, 35.6, 2200, 0, 0, 0],\n    [\"B1-0.2\", 138.7274, 35.3606, 70, 48, 0, 8]\n  ],\n  \"q\":  [{\"type\": \"beacon\", \"lon\": 138.7274, \"lat\": 35.3606, \"color\": \"#FF8A3D\", \"at\": \"@0:Mount\", \"until\": \"B1\"}],\n  \"ui\": [{\"type\": \"counter\", \"y\": 240, \"from_\": 0, \"to\": 3776, \"t0\": \"@1:rises\", \"t1\": \"@1:meters>\", \"suffix\": \" m\", \"label\": \"Mount Fuji\", \"at\": \"@1:rises\", \"until\": \"END+1\"}],\n  \"credits\": \"Facts: Wikipedia (Mount Fuji)\"\n}\n```\n\n**Times**\n\n| Write | Means |\n|---|---|\n| `4.5` | 4.5 s from the start |\n| `\"@2:Ash\"` | when the word \"Ash\" starts in line 2 (lines count from 0) |\n| `\"@2:Edo>\"` | when \"Edo\" ends |\n| `\"@2:Ash-0.2\"` | 0.2 s before \"Ash\" |\n| `\"B3\"`, `\"B3+0.5\"` | start of line 3 |\n| `\"END\"`, `\"END+1\"` | end of the voice |\n\n**Camera keys:** `[time, lon, lat, range_km, tilt, heading, target_height_km]`\n- `range_km`: 21000 is the whole globe, 2000 a country, 50 a mountain.\n- `tilt`: 0 looks straight down; 45–60 is a dramatic oblique view.\n- `target_height_km` is measured in *exaggerated* space: for a 3.8 km peak with `ex: 3`, use about 8.\n\n**Building blocks**\n- Map elements (`q`): `country`, `beacon`, `ring`, `line`, `arc`, `path`.\n- Screen elements (`ui`): `title`, `pill`, `cards`, `counter`, `ring`, `flash`.\n\nEvery field is in [`skill/map-shorts/references/spec.md`](skill/map-shorts/references/spec.md).\n\n**Tips that save the most time**\n- Render stills after every camera change. Change one thing at a time.\n- Keep something changing every 3–4 seconds; hold each card at least 2 seconds so people can read it.\n- Keep text out of the top ~210 px and bottom ~310 px; the platforms cover those areas.\n- Fade country outlines out before close-ups (they get soft below ~500 km).\n\n## Voices\n\n| Provider | Cost | Quality | Use for |\n|---|---|---|---|\n| `edge` (default) | free | robotic, especially in Thai; coarse word timing | **drafts only** |\n| `elevenlabs` | your plan | natural, accurate timing | final videos (check your plan's terms) |\n\n```json\n\"voice\": {\"provider\": \"elevenlabs\", \"voice_id\": \"YOUR_VOICE_ID\", \"model\": \"eleven_v3\", \"lang\": \"th\", \"speed\": 1.06}\n```\n\nSet the key in your shell (`export ELEVENLABS_API_KEY=...`). Never put it in the spec.\n\n> **Do not publish monetised videos with the Edge voice.** It uses Microsoft Edge's read-aloud service through an unofficial library. Microsoft has said commercial use without an Azure subscription may break its terms, and the service can stop working at any time.\n\nVoice lines are cached by their text, so after you change a camera key or a graphic, a rebuild costs nothing. Only changed lines are voiced again.\n\n**Pronunciation:** use `spoken` to tell the voice how to say a word while the captions keep the written form, e.g. `\"spoken\": {\"1707\": \"seventeen oh seven\", \"แมกมา\": \"แม็กม่า\"}`.\n\n## Credits you must put on your videos\n\nThe build already puts a credit line at the bottom of every video. Put the full text in your video description too:\n\n```\nImagery: EOxCloudless https://cloudless.eox.at by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2016), CC BY 4.0\nTerrain: AWS Terrain Tiles (SRTM and others) + the notices for your area: https://github.com/tilezen/joerd/blob/master/docs/attribution.md\nGlobe: NASA Blue Marble · Borders, provinces, rivers: Natural Earth\nTerrain exaggerated for clarity; beams and rings are illustrations.\n```\n\n- `make_site.py` only uses EOX's **2016** layer, which allows commercial use. Their 2018 and later layers are **non-commercial** — do not switch to them for a monetised channel.\n- Details: [`references/licenses.md`](skill/map-shorts/references/licenses.md). This is not legal advice.\n\n## Rendering speed and GPUs\n\n| Setup | Speed | 30 s clip |\n|---|---|---|\n| Software rendering (`--swiftshader`, any machine; also WSL) | ~0.5 frames/s (measured) | ~30 min |\n| GPU through Chrome (Windows 11, AMD Radeon 860M laptop iGPU) | ~16 frames/s (measured) | ~1 min |\n\n- The first line of a render tells you which renderer is in use. `SwiftShader` means software rendering.\n- **Split a long render** across processes:\n  ```bash\n  node render.mjs clip --from 0 --to 450 &\n  node render.mjs clip --from 450 --to 942 &\n  ```\n- **WSL:** Chrome inside WSL has no GPU. Clone and run the project on the Windows side instead (Windows Python + Node); it uses the GPU directly.\n\n## Troubleshooting\n\n| You see | Do this |\n|---|---|\n| `missing .../data_extra/region.json` | Run `make_site.py <clip> --name region --box ...` first. Every clip needs its own map data. |\n| `time ref: 'X' is not in beat N` | The phrase is not in that line exactly as written. Lines count from 0; use the written words, not the `spoken` respelling. |\n| `camera keys must run forward in time` | Two camera keys resolved to the same or a backwards time. Move one to a later word. |\n| `unknown country/region names` | Countries use Natural Earth `ADMIN` names (e.g. `\"United States of America\"`). Regions come from `make_geo.py`. |\n| `playwright-core not found` | Run `npm i playwright-core` in the folder you run `render.mjs` from. |\n| `Executable doesn't exist at ...ms-playwright...` | Install the browser that matches the pinned version: `npx playwright-core install chromium` (not `npx playwright ...`). |\n| `encoded with libopenh264` | Your ffmpeg has no x264. The file is fine; install a full build (see Requirements) for the best quality. |\n| `Edge TTS gave no audio after 6 tries` | The free service is flaky. Wait a minute and run `build.py` again (finished lines are cached). |\n| `renderer: ... SwiftShader` and very slow | No GPU in use. See [Rendering speed](#rendering-speed-and-gpus). |\n| Blurry ground at mid zoom | Make the region patch cover the area the camera flies over, or raise `--zimg` by 1. |\n| A province fill disappears under mountains | Raise its `lift` (30+). |\n| A phrase like `COVID-19` breaks timing | `-19` is read as an offset; reference another word in that line. |\n\n## Known limitations\n\nThis is a **beta**. Please open an issue if something breaks.\n\n- **Tested end to end** on Windows 11 (GPU rendering) and Linux, with both the free Edge voice and the ElevenLabs voice (Thai, `eleven_v3`). Other systems should work too; if something breaks, please open an issue.\n- **Not one-click:** framing takes a few rounds of stills per clip.\n- **Thai with Edge TTS:** captions often show only one or two words at a time, because its word timing is coarse. ElevenLabs gives smoother captions.\n- **Map graphics:** outlines look soft in close-ups; beacons can look like flat bars from some angles.\n- **Not included:** music, sound effects and voices. Bring your own; CC0 sounds are fine. Add them with `\"music\"` and `\"sfx_files\"` in the spec.\n\n## Privacy and safety\n\n- **Where your text goes:** your script is sent to the voice service you choose (Microsoft for Edge, ElevenLabs with your key). Map tiles and Natural Earth files are downloaded from EOX, AWS and Natural Earth.\n- **The renderer** serves only the clip and template folders, on `127.0.0.1`, to a headless Chrome. It keeps the browser sandbox on.\n- **Treat a `spec.json` from someone else as code:** it can reference local files as audio and inject HTML into the page. Read it before you build.\n- **No keys are stored:** `ELEVENLABS_API_KEY` is read from the environment only.\n\n## Project layout\n\n```\nskill/map-shorts/\n  SKILL.md                 instructions the agent follows\n  references/              method, spec, thai, qa, setup, licenses, advice\n  scripts/\n    check_setup.py         dependency check\n    make_site.py           satellite imagery + terrain patches (EOX 2016, AWS Terrain Tiles)\n    make_geo.py            provinces and rivers (Natural Earth)\n    build.py               voice, word timing, timeline, scene\n    render.mjs             headless Chrome frame capture\n    encode.py              MP4 (H.264 + AAC)\n  template/                three.js scene parts, three.js, Kanit font, globe, country borders\nexamples/fuji/             English example\nexamples/chi/              Thai example\ndocs/                      README images\n```\n\n## Contributing\n\nIssues and pull requests are welcome — especially test reports from Windows, macOS and GPU machines.\n\nWhen you open an issue, include:\n- the output of `check_setup.py`;\n- the `renderer:` line from your render;\n- the full error message.\n\n## License\n\n- **Code and docs:** MIT.\n- **Bundled third-party files** keep their own licences (three.js MIT, Kanit OFL, Natural Earth public domain, NASA Blue Marble); see [`NOTICE.md`](NOTICE.md).\n- **Images in `docs/`** contain EOxCloudless 2016 imagery by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2016), CC BY 4.0.\n- **Videos you make** must carry the imagery and terrain credits above.\n"}