{"id":50967301,"url":"https://github.com/dymoo/media-understanding","last_synced_at":"2026-06-18T22:00:29.762Z","repository":{"id":344021308,"uuid":"1179876740","full_name":"dymoo/media-understanding","owner":"dymoo","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-07T16:16:33.000Z","size":453,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T06:27:27.767Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dymoo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-12T13:25:17.000Z","updated_at":"2026-04-07T16:15:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dymoo/media-understanding","commit_stats":null,"previous_names":["dymoo/media-understanding"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dymoo/media-understanding","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Fmedia-understanding","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Fmedia-understanding/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Fmedia-understanding/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Fmedia-understanding/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dymoo","download_url":"https://codeload.github.com/dymoo/media-understanding/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Fmedia-understanding/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34508867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-18T22:00:19.567Z","updated_at":"2026-06-18T22:00:29.731Z","avatar_url":"https://github.com/dymoo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @dymoo/media-understanding\n\nTurn audio, video, and images into the two modalities LLMs actually accept: **text** and **images**.\n\n| Input                       | Output                                             |\n| --------------------------- | -------------------------------------------------- |\n| Audio (mp3, wav, m4a, ...)  | Timestamped transcript text                        |\n| Video (mp4, mkv, mov, ...)  | Transcript text + timestamped keyframe grid images |\n| Image (png, jpg, webp, ...) | Compressed JPEG (base64) + metadata                |\n\n## Agent Setup\n\n\u003cdetails\u003e\n\u003csummary\u003eOpenClaw\u003c/summary\u003e\n\nOpenClaw supports two integration points that work together:\n\n1. **MCP server** — gives the agent 5 media analysis tools in the reply pipeline\n2. **Media model stub** — pre-digests inbound media at conversation start (~50ms probe, no heavy work)\n\n```json5\n{\n  // 1. MCP server — 5 tools for media analysis in the reply pipeline\n  mcpServers: {\n    \"media-understanding\": {\n      command: \"media-understanding-mcp\",\n    },\n  },\n\n  // 2. Media model — pre-digests inbound media before the reply pipeline.\n  //    Runs a quick probe (~50ms) and tells the agent to use MCP tools\n  //    for full analysis. No transcription, no heavy work at pre-digest time.\n  tools: {\n    media: {\n      models: [\n        {\n          type: \"cli\",\n          command: \"media-understanding\",\n          args: [\"{{MediaPath}}\"],\n          capabilities: [\"audio\", \"video\", \"image\"],\n          maxBytes: 10737418240, // 10 GB — let the MCP tools handle size limits\n          timeoutSeconds: 10, // probe is fast\n        },\n      ],\n    },\n  },\n}\n```\n\n**Why this works well:** The media model stub is instant (~50ms header read) and gives the agent a metadata summary plus a nudge to use MCP tools. The agent then has full iterative control — it chooses which tools to call based on the media type and its current task. No API keys needed, works offline, no wasted compute on pre-digestion.\n\n**What the agent sees from the media model stub:**\n\n```\n[Video] 2m15s, 1920x1080, h264/aac, 48.2 MB\n\nThis file has been detected by the media-understanding CLI.\nUse your media-understanding MCP tools for full analysis:\n  - probe_media: batch metadata scanning\n  - understand_media: full analysis (transcript + keyframe grids)\n  - get_transcript: speech content with format options (text/srt/json)\n  - get_video_grids: visual keyframe inspection\n  - get_frames: exact frame extraction at specific timestamps\n```\n\nThe agent then calls MCP tools for the actual work, with full iterative capability.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eOpenCode\u003c/summary\u003e\n\nAdd to `~/.config/opencode/opencode.json`:\n\n```json\n{\n  \"mcp\": {\n    \"media-understanding\": {\n      \"type\": \"local\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"-p\", \"@dymoo/media-understanding\", \"media-understanding-mcp\"],\n      \"env\": {\n        \"MEDIA_UNDERSTANDING_MODEL\": \"base.en-q5_1\"\n      }\n    }\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eClaude Desktop\u003c/summary\u003e\n\nAdd to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"media-understanding\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"-p\", \"@dymoo/media-understanding\", \"media-understanding-mcp\"],\n      \"env\": {\n        \"MEDIA_UNDERSTANDING_MODEL\": \"base.en-q5_1\"\n      }\n    }\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCursor\u003c/summary\u003e\n\nAdd to `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:\n\n```json\n{\n  \"mcpServers\": {\n    \"media-understanding\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"-p\", \"@dymoo/media-understanding\", \"media-understanding-mcp\"],\n      \"env\": {\n        \"MEDIA_UNDERSTANDING_MODEL\": \"base.en-q5_1\"\n      }\n    }\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWindsurf\u003c/summary\u003e\n\nAdd to `~/.codeium/windsurf/mcp_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"media-understanding\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"-p\", \"@dymoo/media-understanding\", \"media-understanding-mcp\"],\n      \"env\": {\n        \"MEDIA_UNDERSTANDING_MODEL\": \"base.en-q5_1\"\n      }\n    }\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCline / Roo Code\u003c/summary\u003e\n\nAdd a new MCP server:\n\n- command: `npx`\n- args: `-y -p @dymoo/media-understanding media-understanding-mcp`\n- env: `MEDIA_UNDERSTANDING_MODEL=base.en-q5_1`\n\n\u003c/details\u003e\n\n## Quick Start\n\n```bash\nnpm install @dymoo/media-understanding\n```\n\nRequirements:\n\n- Node \u003e= 22\n- FFmpeg is handled automatically via `node-av`\n- The default Whisper model (`base.en-q5_1`, ~57 MB) auto-downloads on first transcription; set `SKIP_MODEL_DOWNLOAD=1` to defer\n\n## Docker Quick Start\n\nThe npm package runs the MCP server over **stdio**. The official Docker image wraps that same server with [`supergateway`](https://github.com/supercorp-ai/supergateway) so HTTP-capable MCP clients can connect directly.\n\n```bash\ndocker run --rm -p 8000:8000 ghcr.io/dymoo/media-understanding:1.1.0\n```\n\nEndpoints:\n\n- MCP: `http://localhost:8000/mcp`\n- Health: `http://localhost:8000/healthz`\n\nThe official Docker image includes:\n\n- the built `media-understanding-mcp` server\n- `supergateway@3.4.3` for Streamable HTTP transport\n- the default Whisper model (`base.en-q5_1`)\n- `yt-dlp`, so URL support is enabled out of the box\n\nBind-mount media into the container when you want to analyze local files:\n\n```bash\ndocker run --rm -p 8000:8000 -v \"$PWD/testdata:/media:ro\" ghcr.io/dymoo/media-understanding:1.1.0\n```\n\n### Optional: URL support via yt-dlp\n\nIf [yt-dlp](https://github.com/yt-dlp/yt-dlp) is installed on your system, **all tools** gain the ability to accept URLs in place of file paths. A dedicated `fetch_ytdlp` tool is also registered for fine-grained download control.\n\nyt-dlp is **not bundled by the npm package** — you must install it yourself for local stdio usage. The official Docker image includes yt-dlp so URL support works out of the box.\n\nAs always, URL support does not change your responsibility to comply with local law, content rights, and target-site terms.\n\n```bash\n# macOS\nbrew install yt-dlp\n\n# Linux\npip install yt-dlp     # or: sudo apt install yt-dlp\n\n# Windows\nwinget install yt-dlp   # or: pip install yt-dlp\n```\n\n**Supported platforms** (1800+ total):\nYouTube, **Instagram** (Reels \u0026 posts), Vimeo, **Loom**, Twitch, Dailymotion, TikTok, X/Twitter, Facebook, LinkedIn, Reddit, SoundCloud, Dropbox, Google Drive, BBC, CNN, and [many more](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md).\n\nWhen yt-dlp is detected at startup, the server logs `yt-dlp detected — URL support enabled` to stderr. Without it, tools only accept local file paths.\n\n## How It Works\n\nThe server exposes 5 core tools (+ 1 optional) organized around a three-step workflow: **discover, analyze, iterate**. When yt-dlp is installed, all tools also accept URLs.\n\n```\nStep 1: DISCOVER (cheap, batch-safe)\n  probe_media  →  metadata for 1-200 files (~5-50ms each, header reads only)\n\nStep 2: ANALYZE (expensive, one file at a time)\n  understand_media  →  full analysis: metadata + transcript + keyframe grids\n  get_transcript    →  timestamped speech text (text, SRT, or JSON format)\n  get_video_grids   →  visual keyframe contact sheets\n  get_frames        →  exact frames at specific timestamps\n\nStep 3: ITERATE (use output from one tool to target another)\n  transcript timestamps → get_video_grids with start_sec/end_sec\n  grid timestamps → get_frames with exact seconds\n\nOptional (requires yt-dlp):\n  fetch_ytdlp    →  download + cache media from URLs with fine-grained control\n```\n\nThe cost boundary between cheap and expensive operations is encoded in the tool names. `probe_media` is safe to call on dozens of files. The analysis tools process one file per call and enforce payload budgets so an LLM does not accidentally blow up its context.\n\n## MCP Tools\n\n### `probe_media` — discover and triage\n\nScan files for metadata before committing to heavy analysis. Returns type, duration, resolution, codecs, file size. No decoding, no transcription, no images.\n\nAccepts a `paths` parameter: a string or array of strings. Each string can be a literal file path or a glob pattern. Default limit: 50 files (max 200).\n\n```json\n{ \"paths\": \"/path/to/video.mp4\" }\n{ \"paths\": [\"recordings/*.mp4\", \"specific/file.mp3\"] }\n{ \"paths\": \"media/**/*.{mp4,mp3,wav}\", \"max_files\": 100 }\n```\n\n### `understand_media` — full single-file analysis\n\nReturns metadata + transcript + keyframe grids for one file. For video, transcript segments are interleaved with their corresponding grid images — each grid is preceded by the transcript chunk covering that time window, so the LLM sees speech and visuals together in chronological order.\n\nBest for images, short audio, and short-to-medium video. For files over 2 hours, use the focused tools below instead.\n\n```json\n{ \"file_path\": \"/path/to/video.mp4\" }\n{ \"file_path\": \"/path/to/podcast.mp3\", \"model\": \"base.en\" }\n{ \"file_path\": \"/path/to/clip.mp4\", \"start_sec\": 60, \"end_sec\": 120, \"max_grids\": 3 }\n```\n\nKey options: `model`, `max_chars`, `max_total_chars`, `max_grids`, `start_sec`, `end_sec`, `sampling_strategy`, `seconds_per_frame`, `seconds_per_grid`, `cols`, `rows`, `thumb_width`, `aspect_mode`.\n\n### `get_transcript` — speech content with format options\n\nTimestamped transcript with three output formats:\n\n- **`text`** (default) — `[start-end] text` per segment, compact and scannable\n- **`srt`** — standard SRT subtitle format with `HH:MM:SS,mmm` timestamps\n- **`json`** — machine-readable `{ segments: [{ start, end, text }] }` with millisecond precision\n\nOptional time windowing with `start_sec`/`end_sec` filters the output to a specific range (the full file is still transcribed once, then cached).\n\n```json\n{ \"file_path\": \"/path/to/podcast.mp3\" }\n{ \"file_path\": \"/path/to/meeting.mp4\", \"format\": \"srt\" }\n{ \"file_path\": \"/path/to/episode.mp3\", \"format\": \"json\", \"start_sec\": 60, \"end_sec\": 120 }\n```\n\n**Two-step workflow for long media:**\n\n1. **Overview** — `get_transcript(file, { format: \"srt\" })` to scan for topics and transitions\n2. **Detail** — `get_transcript(file, { format: \"json\", start_sec: 120, end_sec: 300 })` for precise segment data in a specific range\n\nThis avoids dumping the entire transcript into context at once.\n\n### `get_video_grids` — visual keyframe sampling\n\nJPEG contact sheets of thumbnails. Every tile has an exact timestamp overlay. Budget-aware: omit `max_grids` and the server auto-fits as many grids as possible within `max_total_chars`.\n\n```json\n{ \"file_path\": \"/path/to/video.mp4\" }\n{ \"file_path\": \"/path/to/movie.mkv\", \"start_sec\": 300, \"end_sec\": 600, \"max_grids\": 2, \"seconds_per_frame\": 8 }\n{ \"file_path\": \"/path/to/lecture.mp4\", \"sampling_strategy\": \"scene\", \"frame_interval\": 150 }\n```\n\n**Example output** — 4x4 grid from a 16:9 landscape video (`thumb_width: 320`):\n\n![Example keyframe grid from get_video_grids — 4x4 contact sheet covering 30-90s with timestamp overlays on each tile](docs/assets/example-grid.jpg)\n\n### `get_frames` — exact moments\n\nOne JPEG per requested timestamp. Each frame includes a timestamp overlay.\n\n```json\n{ \"file_path\": \"/path/to/video.mp4\", \"timestamps\": [0, 30, 60] }\n{ \"file_path\": \"/path/to/clip.mp4\", \"timestamps\": [83.5] }\n```\n\n**Example output** — single frame at t=60s:\n\n![Example frame from get_frames — single frame extracted at 00:01:00.000 with filename and timestamp overlay](docs/assets/example-frame.jpg)\n\n### `fetch_ytdlp` — URL download control (requires yt-dlp)\n\nOnly available when yt-dlp is installed. Fine-grained control over what to download from a URL: subtitles (default, instant), video (for frame analysis), audio (for ASR), thumbnail. All downloads are cached.\n\nFor most workflows, passing a URL directly to `get_transcript` or `understand_media` is simpler — use `fetch_ytdlp` when you need selective downloads.\n\n```json\n{ \"url\": \"https://youtube.com/watch?v=dQw4w9WgXcQ\" }\n{ \"url\": \"https://www.instagram.com/reel/ABC123/\", \"include_audio\": true }\n{ \"url\": \"https://www.loom.com/share/abc123\", \"include_video\": true }\n{ \"url\": \"https://vimeo.com/123456\", \"include_audio\": true }\n```\n\n## Recommended LLM Workflow\n\n### Per-file type guidance\n\n| Media type         | Recommended first tool                                              |\n| ------------------ | ------------------------------------------------------------------- |\n| Image              | `understand_media`                                                  |\n| Short audio (\u003c30m) | `understand_media`                                                  |\n| Long audio         | `get_transcript`                                                    |\n| Short video (\u003c10m) | `understand_media`                                                  |\n| Long video         | `get_transcript` (speech-first) or `get_video_grids` (visual-first) |\n\n### Iteration pattern\n\n1. Start with `understand_media` or `get_transcript` for a first-pass understanding\n2. Use transcript timestamps to target `get_video_grids` on a narrow window\n3. Use grid timestamps to target `get_frames` for exact moments\n\nNarrow the window — don't widen it. Each follow-up call should be more targeted than the last.\n\n### Podcast / long audio workflow\n\nFor analyzing podcasts or long audio files:\n\n1. **Overview pass** — `get_transcript(file, { format: \"srt\" })` to get a timestamped overview of the full episode. Scan the SRT to identify interesting segments, topic transitions, or key moments.\n2. **Detail pass** — `get_transcript(file, { format: \"json\", start_sec: 120, end_sec: 300 })` to get precise segment data for a specific time range. Use for quoting, summarizing, or extracting specific portions.\n\nThe SRT overview is compact and scannable; the JSON detail pass gives exact data for the segments that matter.\n\n## Processing Multiple Files\n\nFor a single file, call `understand_media` directly. For multiple files, follow the three-tier workflow.\n\n### The three-tier approach\n\n```\n1. probe_media({ \"paths\": \"media/**/*.mp4\" })\n   → metadata for all files (cheap, fast)\n\n2. Pick files that matter based on probe results\n\n3. Call analysis tools one file at a time:\n   understand_media({ \"file_path\": \"...\" })\n   get_transcript({ \"file_path\": \"...\" })\n```\n\n### Subagent orchestration (recommended for many files)\n\nWhen processing many files, launch **subagents** — context-isolated workers each with MCP access — instead of accumulating raw media analysis in a single context.\n\n**Why:** Each `understand_media` call returns transcript text and base64 images. Accumulating these for 10+ files floods the context window, causing compression and lost details. Subagents prevent this.\n\n**How:**\n\n1. Probe everything first: `probe_media` with a glob (cheap triage)\n2. Launch one subagent per file (or per small group)\n3. Pass each subagent a clear **intention**: _\"Analyze this podcast episode. Extract the main topics discussed and any action items mentioned.\"_\n4. Each subagent calls `understand_media` / `get_transcript` / etc., then returns a **distilled summary**\n5. The orchestrator synthesizes summaries without ever accumulating raw media context\n\nSubagents are **sacrificial** — their full analysis context is discarded after they return their distilled result. This is the key insight: the orchestrator works with summaries, not raw transcripts and images.\n\n**When NOT to use subagents:**\n\n- Single file — just call `understand_media` directly\n- Quick metadata checks — `probe_media` is sufficient\n- 2-3 small files — serial `understand_media` calls are fine\n\nThis is a **guideline**, not enforcement. The MCP server does not prevent serial `understand_media` calls in one context. But analysis quality degrades as context fills up with raw media data.\n\n## Safety and Budgets\n\nThe server is conservative by default:\n\n- **Payload budgets:** Every response is capped at `max_total_chars` (default 48,000). If a response would exceed the budget, the server returns a natural-language error explaining how to adjust (narrow the window, request fewer images, etc.), including the overage ratio and resolution-aware suggestions for reducing image sizes. Budget guidance is context-aware: grid errors suggest `cols`/`rows`/`thumb_width`, while frame errors suggest fewer timestamps or splitting requests.\n- **Portrait-aware grid defaults:** When grid parameters (`cols`, `rows`, `thumb_width`) are omitted, portrait video (height \u003e width) defaults to `3x3` grids at `120px` thumbnail width instead of the standard `4x4` at `480px`. This prevents portrait video grids from immediately exceeding the default budget. Explicit values always override these defaults.\n- **Preflight checks:** Heavy tools reject obviously problematic requests before doing expensive work:\n  - `understand_media`: files over 2 hours (transcription bottleneck)\n  - `get_transcript`: files over 4 hours\n  - All heavy tools: files over 10 GB\n- **Bounded transcript cache:** In-memory LRU cache (max 32 entries, skips transcripts over 500K chars). Prevents unbounded memory growth.\n- **Concurrency limits:** Heavy operations (transcription + frame extraction) are capped at 2 concurrent in-process jobs to control memory peaks.\n\nWhen the server rejects a request, it explains _why_ and suggests _what to do instead_ — it teaches the calling model to recover.\n\n## Programmatic API\n\n```ts\nimport {\n  extractFrameGridImages,\n  extractFrameImage,\n  probeMedia,\n  transcribeAudio,\n  understandMedia,\n} from \"@dymoo/media-understanding\";\n\nconst info = await probeMedia(\"/path/to/video.mp4\");\nconst segments = await transcribeAudio(\"/path/to/audio.mp3\");\nconst frame = await extractFrameImage(\"/path/to/video.mp4\", 30);\nconst grids = await extractFrameGridImages(\"/path/to/video.mp4\", { maxGrids: 1 });\nconst result = await understandMedia(\"/path/to/video.mp4\");\n```\n\n`understandMedia()` returns `{ info, segments, transcript, grids, gridImages }`.\n\n## Environment Variables\n\n| Variable                         | Default        | Description                                |\n| -------------------------------- | -------------- | ------------------------------------------ |\n| `MEDIA_UNDERSTANDING_MODEL`      | `base.en-q5_1` | Default Whisper model name                 |\n| `MEDIA_UNDERSTANDING_MAX_CHARS`  | `32000`        | Max transcript characters                  |\n| `MEDIA_UNDERSTANDING_MAX_GRIDS`  | `6`            | Max grid images per video call             |\n| `MEDIA_UNDERSTANDING_DISABLE_HW` | (unset)        | Set to `1` to force software decode/encode |\n\n## ASR Model\n\nUses Whisper via `node-av`. Default model: `base.en-q5_1` (English, quantized GGML, ~57 MB). The default model is cached on first transcription call, or prewarmed during Docker image build.\n\nModels are cached at `~/.cache/media-understanding/models/` (or `$XDG_CACHE_HOME/media-understanding/models/`).\n\n## Credits\n\nThanks to [Simon Willison](https://simonwillison.net/) for the inspiration around feeding timestamped video frames to LLMs. This library borrows that spirit — multiple separate images per turn, uniform time-distributed sampling, visible timestamps on every frame — and packages it into a conservative MCP workflow.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdymoo%2Fmedia-understanding","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdymoo%2Fmedia-understanding","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdymoo%2Fmedia-understanding/lists"}