video-convert
Use when converting a video/audio file's FORMAT, CODEC, or CONTAINER in media-studio (mp4/mkv/mov/webm/gif, h264/h265/vp9/av1, or audio-only extraction to mp3/m4a/wav/flac/opus) — single file or batch, or normalizing a non-Chromium-playable source for playback (remux vs h264-720p proxy). Covers the real ffmpeg argv builder, codec/CRF/bitrate/pix_fmt choices, faststart, container<->codec legality, and the progress/seek/timebase pitfalls that actually bite. DO NOT use this skill for: cutting/trimming/splitting clips (use reframe-video-cutting), reframing 16:9->9:16 (use reframe), burning subtitles/captions, watermark/logo removal (use video-watermark-removal), or pure thumbnail/frame extraction. Also NOT a general 'run any ffmpeg command' helper — it documents media-studio's specific, frozen convert.* / media.* contracts.
- License 许可
- MIT
- 最近更新
- 2026-11-02
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- video-convert
- description
- Use when converting a video/audio file's FORMAT, CODEC, or CONTAINER in media-studio (mp4/mkv/mov/webm/gif, h264/h265/vp9/av1, or audio-only extraction to mp3/m4a/wav/flac/opus) — single file or batch, or normalizing a non-Chromium-playable source for playback (remux vs h264-720p proxy). Covers the real ffmpeg argv builder, codec/CRF/bitrate/pix_fmt choices, faststart, container<->codec legality, and the progress/seek/timebase pitfalls that actually bite. DO NOT use this skill for: cutting/trimming/splitting clips (use reframe-video-cutting), reframing 16:9->9:16 (use reframe), burning subtitles/captions, watermark/logo removal (use video-watermark-removal), or pure thumbnail/frame extraction. Also NOT a general 'run any ffmpeg command' helper — it documents media-studio's specific, frozen convert.* / media.* contracts.
03 SKILL.md 正文
Video Convert (format / codec / container) — media-studio
Convert format/codec/container, extract audio, batch-convert, and produce a Chromium-playable derivative (remux or h264-720p proxy). This skill documents the real media-studio implementation — every command below is what the code actually emits. No invented flags.
Source modules (read these for ground truth)
C:/Users/Prekzursil/Documents/GitHub/Reframe/sidecar/media_studio/ffmpeg.py— the argv builder (build_convert_argv),build_probe_argv,ffprobe_duration, and the progress-streamingrun(). This is where the convert command is actually assembled.C:/Users/Prekzursil/Documents/GitHub/Reframe/sidecar/media_studio/features/convert.py—convert.start/convert.batchfeature layer: pure output-path derivation (output_path), audio-ext map,convert_one/convert_batch, job handlers.C:/Users/Prekzursil/Documents/GitHub/Reframe/sidecar/media_studio/features/media_compat.py—media.playable/media.proxy.start: codec-driven playabilityclassify,build_remux_argv,build_proxy_argv, proxy cache.C:/Users/Prekzursil/Documents/GitHub/Reframe/sidecar/media_studio/features/export_presets.py—exportPresets.*catalog (platform export targets; aspect + 20-60s window, NOT codec).
When to use which path (pick fast)
- User wants a different format/codec/container, or audio extraction ->
convert.start/convert.batch(this is "convert"). Builder =ffmpeg.build_convert_argv(convert.py:182). - User just wants a file to PLAY in the app and it won't (HEVC, WMV, MPEG-2, or h264-in-MKV) ->
media.playablethenmedia.proxy.start. The verdict is codec-driven, not extension-driven (media_compat.py:162classify):- all streams Chromium-playable AND container playable ->
playable(play original, build nothing). - all streams playable but container isn't (e.g. h264-in-MKV) ->
remux(-c copyinto mp4, cheap). - ANY stream unplayable (HEVC even inside MKV) ->
proxy(h264 720p transcode). Playable video codecs:h264, vp8, vp9, av1; audio:aac, mp3, opus, vorbis, flac, pcm_*(media_compat.py:59-61). Anything else => proxy.
- all streams Chromium-playable AND container playable ->
The convert option contract (frozen — CONTRACTS.md §2)
convert.start({videoId|path, options, out?}) / convert.batch({items:[...]}). options keys (convert.py:23-25, consumed in ffmpeg.py:122-169):
| key | effect in argv | notes |
|---|---|---|
container |
output extension only (mp4/mkv/mov/webm/gif/...) | ffmpeg picks the muxer from the out file extension. NOT validated against codec. |
vcodec |
-c:v <vcodec> |
e.g. libx264, libx265, libvpx-vp9, libaom-av1. Omit => ffmpeg default for the container. |
acodec |
-c:a <acodec> |
e.g. aac, libmp3lame, libopus. |
crf |
-crf <crf> |
video path only. NOT clamped/validated. Encoder-specific scale. |
scale |
-vf scale=W:H |
accepts "1280:720" OR "1280x720" (x -> :, ffmpeg.py:159). |
fps |
-r <fps> |
output frame rate. |
audioOnly |
adds -vn, drops all video flags |
extract audio. Only acodec applies. |
audioFormat |
drives the output extension (not a codec flag) | mapped to ext in convert.py:61-84. |
There is no bitrate, no preset, no pix_fmt, no -map, no faststart key. If you need those, the convert path does not provide them — see Pitfalls 1, 2, 5.
The real convert command
ffmpeg.build_convert_argv(in, out, options, settings) produces exactly (ffmpeg.py:137-169):
ffmpeg -hide_banner -nostdin -y -i <in>
[ -vn ] # audioOnly
[ -c:v <vcodec> ] # video path only
[ -c:a <acodec> ]
[ -crf <crf> ] # video path only
[ -vf scale=<W>:<H> ] # video path only; "WxH" normalized to "W:H"
[ -r <fps> ] # video path only
-progress pipe:1 -nostats <out>
- Always argv list, never
shell=True(paths with spaces are one element — safe). The binary is resolved settings.ffmpegPath ->MEDIA_STUDIO_FFMPEGenv -> bundledresources/bin/-> PATH (ffmpeg.py:48-92). -progress pipe:1 -nostatsmakes stdout akey=valuestream;run()(ffmpeg.py:224) turnsout_time*into a 0-100 pct against the ffprobe duration.
Output path derivation (convert.py:94-125 output_path)
- explicit
out-> used verbatim (caller owns the name). - else
<in stem>.<ext>in the source's own directory, whereext=audioFormatext whenaudioOnly, elsecontainerext. - if the derived dest equals the source (same dir+stem+ext, e.g. re-encoding
a.mp4-> mp4 in place) it becomes<stem>.converted.<ext>so it never clobbers the original. - audioFormat->ext map (convert.py:61-76):
mp3/libmp3lame->mp3,aac/m4a/alac->m4a,wav/pcm->wav,flac->flac,opus/libopus->opus,vorbis/libvorbis/ogg->ogg,ac3->ac3. Unknown value used verbatim as the extension (default videomp4, default audiom4a).
Worked examples
1. Re-encode an MKV to a web-friendly mp4 (h264 + aac, CRF 20, 720p):
// convert.start params
{ "path": "C:/clips/raw recording.mkv",
"options": { "container": "mp4", "vcodec": "libx264", "acodec": "aac",
"crf": 20, "scale": "1280x720" } }
Emitted argv (note the space in the path is one safe element):
ffmpeg -hide_banner -nostdin -y -i "C:/clips/raw recording.mkv" \
-c:v libx264 -c:a aac -crf 20 -vf scale=1280:720 \
-progress pipe:1 -nostats "C:/clips/raw recording.mp4"
Add
pix_fmt yuv420p+ faststart yourself if this must stream/play everywhere — the convert builder does NOT (Pitfalls 1 & 2). If you can, prefer routing such "make it play" cases throughmedia.proxy.start, which DOES set both.
2. Extract audio to mp3:
{ "path": "C:/clips/talk.mp4", "options": { "audioOnly": true, "audioFormat": "mp3", "acodec": "libmp3lame" } }
-> ffmpeg -hide_banner -nostdin -y -i talk.mp4 -vn -c:a libmp3lame -progress pipe:1 -nostats talk.mp3
3. Batch convert a folder to mp4: convert.batch({items:[{path,options},...]}). Progress is spread evenly: item i of n reports (i*100 + per_item_pct)/n (convert.py:219-225), so a 4-item batch climbs 0->25->50->... Cancellation is honored between items and within an item.
4. Make a non-playable source playable (the proxy path): call media.playable({videoId}); if {playable:false, reason:"video codec not Chromium-playable: hevc"}, call media.proxy.start({videoId}). It runs the proxy transcode (media_compat.py:243-281):
ffmpeg -hide_banner -nostdin -y -i <in> -map 0:v:0 -map 0:a:0? \
-c:v libx264 -preset veryfast -crf 23 -vf scale=-2:720 -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart -progress pipe:1 -nostats <out>
For h264-in-MKV it instead remuxes (media_compat.py:210-240): -map 0 -map -0:s -map -0:d -map -0:t -c copy -movflags +faststart (drops subtitle/data/attachment streams that aren't mp4-legal under stream copy). Result is cached at %APPDATA%/media-studio/proxies/<videoId>-<mtime_ns>.mp4; the mtime IS the cache invalidation (re-edit the source -> new path; stale siblings evicted). The original is always kept for convert/export — the proxy is playback-only.
Pitfalls (the ones that actually bite)
-
No
pix_fmtin convert -> may not play in Chromium/QuickTime.build_convert_argvnever sets-pix_fmt. x264 will happily emityuv444por 10-bit for some inputs/options, which the Electron renderer (and Safari/QuickTime) refuse to decode. The proxy path forcesyuv420pfor exactly this reason (media_compat.py:269). If a converted mp4 "won't play", suspect chroma/bit-depth first. Workaround: pass it throughmedia.proxy.start, or add-pix_fmt yuv420pif you build the argv directly. -
No
-movflags +faststartin convert -> mp4 not progressively streamable. convert output keeps themoovatom at the END, so a<video>tag can't begin until the whole file is fetched. Remux and proxy add+faststart(media_compat.py:235, 275); plain convert does not. Fine for local files; bad for serve-over-HTTP. -
Container/codec legality is NOT validated.
containeronly sets the output extension; the muxer is chosen from it.container:"webm"+vcodec:"libx264"makes ffmpeg fail ("Only VP8/VP9/AV1... supported for WebM");container:"mp4"+vcodec:"libvpx-vp9"is also illegal. The failure surfaces as a non-zero exit ->RuntimeError(convert.py:189). Match codec to container: mp4/mov -> h264/h265/av1 + aac; webm -> vp8/vp9/av1 + opus/vorbis; mkv -> almost anything. -
GIF needs special handling the convert builder doesn't give you.
container:"gif"just changes the extension; without a palette filter ffmpeg uses a generic 256-color quantization -> banding/dither artifacts, and audio flags are meaningless. For a quality GIF you need a two-passpalettegen/paletteusefiltergraph, which this contract can't express. Treat GIF export as out-of-scope for plain convert. -
-progress pipe:1puts the progress stream on STDOUT — nevercapture_outputwithout draining stderr.run()(ffmpeg.py:246-272) captures stderr=PIPE and drains it on a daemon thread. ffmpeg chats continuously on stderr even with-nostats(banner, stream info, font logs); if you ever rebuild this and forget to drain, the ~64KB stderr pipe fills and the encode blocks forever — this was the real "stuck at clip 1/5 for 29 min" bug (ffmpeg.py:254-258). On non-zero exit the last ~8 stderr lines are logged for diagnosis. -
out_time_msis actually MICROSECONDS. ffmpeg's-progressfield is historically mislabeled; the code divides by1_000_000, not1000(ffmpeg.py:202-218). If you parse progress yourself, don't trust the field name. -
A failed/zero duration probe degrades progress to coarse, it does NOT fail the job.
convert_oneswallows a probe exception and setstotal_sec=0(convert.py:176-180); with no total,run()can't compute a percentage and only emits the final100.0 / "done"onprogress=end. Long convert with a "frozen at 0%" bar usually means ffprobe couldn't read a duration (streaming source, no container duration) — the encode is still progressing. -
media.playableis codec-driven; extension lies. A.mp4containing HEVC isproxy, and a.mkvcontaining h264 isremux, notplayable. Also: ffprobe reports BOTH.webmand.mkvasmatroska,webm(one demuxer), so classify disambiguates by file extension (media_compat.py:148-159) — a real.webmwhose streams pass is playable,.mkvgoes to remux. Never decide playability from the extension. -
Re-encoding in place is auto-renamed, not blocked.
a.mp4-> mp4 in the same dir becomesa.converted.mp4(convert.py:122-124). If you expected to overwrite, pass an explicitout. Conversely an explicitoutis verbatim with-y-> it WILL overwrite whatever is there. -
No
-mapon convert -> default-stream selection. convert relies on ffmpeg's implicit stream mapping (best video + best audio of the chosen codecs). Multi-audio / multi-subtitle sources lose the non-default tracks. The remux path is the only one that explicitly maps (-map 0minus subs/data/attachments). If track preservation matters, convert is the wrong tool. -
exportPresetsare platform/aspect targets, NOT codec settings.export_presets.pystores{aspect, minSec, maxSec, count, captionStyle, reframeEngine}with the window hard-clamped to 20-60s (export_presets.py:54-55, 137-138) and aspect guarded to the curated social set (9:16/1:1/4:5). They feed the short-maker/reframe pipeline, notconvert.*. Don't conflate "export preset" with "convert options" — they share no fields.
GPU / HF-model notes
- No GPU, no HF model is involved. Convert, remux, and proxy are pure CPU ffmpeg (
libx264 -preset veryfastfor the proxy). There is no NVENC/QSV/VAAPI hardware-encode path in the contract; adding-c:v h264_nvencwould require routing it throughvcodecand a matching ffmpeg build, and it is unvalidated. For batch throughput, parallelism is the lever, not hardware encode — but noteconvert_batchruns items sequentially within one job (convert.py:215-238); concurrency would be at the job-registry level.
Confidence
HIGH — every command, option key, codec table, cache path, and pitfall is read directly from the four cited source files (ffmpeg.py, convert.py, media_compat.py, export_presets.py). The convert builder, proxy/remux argvs, ext maps, window clamp, and the stderr-drain/microsecond/faststart/pix_fmt gaps are verbatim from the code. Behavior of ffmpeg muxers (container/codec legality, GIF palette) is standard ffmpeg, noted as such where the media-studio contract does not enforce it.
04 同仓库更多 Skills
clarify-requirements
Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.
openmm-md
Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein / protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.
visual-review
Take screenshots of web pages and UI using Playwright for visual review and iteration
status
Diagnostic status report — shows metaswarm installation state, project setup, and potential issues
start
Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work
setup
Interactive project setup — detects your project, configures metaswarm, writes project-local files