plugin-media
| Placement | runtime (out-of-process over gRPC) |
| Source | github.com/opencharly/plugin-media/candy/plugin-media |
| Version | 2026.248.1230 |
| Candy | plugin-media |
This plugin is not listed in charly/charly.yml’s compiled_plugins:. It is not part of the shipped binary: charly builds and loads it out-of-process over gRPC when a plan references one of its words (the coexist path).
Providers
Section titled “Providers”The reserved words this plugin serves:
transcode— verb class
What it does
Section titled “What it does”OUT-OF-TREE charly plugin serving the transcode pipeline check verb — the
single-purpose HOST-SIDE video transcode (source MJPEG → H.264 MP4 via the
host’s ffmpeg), a standalone Go module (go.mod + cmd/serve) served out-of-process
over go-plugin gRPC via the charly plugin SDK (github.com/opencharly/sdk). It is
the ONLY new org repo of the nested-capture cutover A: the capture evidence
pipeline’s transcode: {to: mp4} word dispatches through the provider registry
exactly like a built-in verb (ResolveVerb → grpcProvider → invokeVerbProvider
hands it the full #Op), executing in the evidence phase BEFORE venue teardown.
Host-side by construction: the MJPEG artifact a capture session flushes (e.g.
spice: record) is already host-side, so this provider runs ffmpeg -y -loglevel error -i <mjpeg> -c:v libx264 -pix_fmt yuv420p <out> locally, writes the MP4 to
the input’s artifact (or the derived <source>.mp4 path), and SELF-EVALUATES
the artifact validators + the shared exit/stdout/stderr matchers itself (the
out-of-process path does not run a host-side matcher pipeline). Per the RDD-3
binding (exact hashes of transcoded frames are vacuous — x264 QP noise),
artifact_not_uniform runs on the SOURCE MJPEG frames (pre-encode): the MJPEG
stream is split into its constituent JPEGs, decoded, and compared — identical
frames FAIL the assertion honestly, differing frames PASS. DEPENDENCY: requires
host ffmpeg (with libx264 + yuv420p support).
Parameter schema
Section titled “Parameter schema”The CUE schema below is the authoritative grammar for this plugin’s input. It is the same single source that generates the plugin’s Go parameter types and answers the runtime Describe RPC, so this page cannot disagree with either.
schema/transcode.cue
Section titled “schema/transcode.cue”// The `transcode` plugin's OWN CUE schema — the typed plugin_input for the// `transcode` pipeline check verb (the nested-capture cutover A pipeline word). It// is the SINGLE SOURCE for this plugin's params, used two ways (the same contract// core `spec` and the record/spice plugins use)://// 1. GENERATE the Go param struct — `cue exp gengotypes` (driven by the cue:gen// pipeline, which wraps this with `package params` + `@go(params)`) emits// ../params/cue_types_gen.go, so the provider decodes plugin_input into a// TYPED struct, never a hand-parsed map.// 2. VALIDATE authored input AT RUNTIME — the plugin serves this source over the// Describe channel; the host splices it onto the base (base ++ plugin) and// validates every authored `transcode:` pipeline word's plugin_input against// #TranscodeInput.//// Since the schema-compaction cutover the per-verb fields LEFT core #Op: an// authored `transcode: <scalar>` step (scalar sugar via the declared primary// field `to`) or `transcode: {to: mp4, artifact: …}` (map form) desugars to the// INTERNAL plugin/plugin_input envelope, and every transcode-exclusive modifier// lives HERE. The shared assertion matchers (exit_status/stdout/stderr) and the// general `timeout` stay on core #Op, read off the step Op by the provider and// self-evaluated against the ffmpeg run (R3 — the shared matcher engine).//// SELF-CONTAINED: it references NO base def, so it compiles standalone// (gengotypes + the load-gate compile) AND splices onto the base (base ++ plugin// is a def-name collision check, not a base-reference resolver).#TranscodeInput: { // source_artifact — the HOST path of the source to transcode (the MJPEG the // capture session flushed). The bed-runner evidence phase threads the entry's // primary artifact here; a direct/plan-step dispatch may set it explicitly. // Empty → the provider falls back to the check env's source_artifact. source_artifact?: string @go(SourceArtifact) // to — the CONTAINER FORMAT to transcode the source to. Single-purpose verb: // only "mp4" is supported (the default; an empty/omitted value means mp4). The // transcode is H.264 + yuv420p in an MP4 container, so the artifact plays // everywhere (the ffmpeg argv: -c:v libx264 -pix_fmt yuv420p). to?: string @go(To) // artifact — the HOST path the transcoded file is written to. Empty (the // common case) → derived from the source path by swapping its extension to // ".mp4" (the source MJPEG the capture session flushed is already host-side). artifact?: string @go(Artifact) // artifact_min_bytes — the post-transcode artifact-reality assertion: the // output MP4 must be at least this many bytes (sdk.RunArtifactValidators, // shared implementation). artifact_min_bytes?: int & >=0 @go(ArtifactMinBytes,type=int) // artifact_not_uniform — the SOURCE-frame motion assertion (the RDD-3 binding // of the nested-capture plan): evaluated on the SOURCE MJPEG frames (the // pre-encode JPEGs), NEVER on the transcoded output — exact hashes of // transcoded frames are vacuous (x264 QP noise). The provider splits the // source MJPEG stream into its constituent JPEGs, decodes a stream-wide // sample, and requires at least two distinct frame signatures: identical // frames FAIL honestly (a real capture shows motion), differing frames PASS. artifact_not_uniform?: bool @go(ArtifactNotUniform)}See also the candy reference for this candy’s install surface.