FFmpeg Media · FFmpeg
Deep QCNEW
📌 What is this:Read-only QC: decodes the finished video twice (one video pass, one audio pass) with detector filters and returns a small JSON report — black-frame, freeze and silence segments plus EBU R128 loudness. It produces no media file and leaves the source untouched.
💡 When to use:Use for automated acceptance before delivery: judge pass / warning / block from the reported segments and loudness values instead of watching the whole cut. Thresholds are fixed server-side (black d=0.5, freeze d=2, silence -45dB/1.5s) and not tunable — tunable thresholds would yield reports that are perfectly valid yet wrong. No parameters beyond inputUrl are accepted; extra keys are ignored.
Endpoints
| Usage | Method | Path |
|---|---|---|
| Submit task | POST | /v1/ffmpeg/qc |
| Query Result | GET | /v1/ffmpeg/info?id={taskId} |
⏱ Recommended polling interval:Poll every 3-5s (response includes progress 0-100) until status = succeeded / failed. status = queued means the task is enqueued and waiting to start (not charged yet) — just keep polling. Single-task timeout is 300s with auto-refund.
Request Parameters
| Param | Type | Required | Description |
|---|---|---|---|
| inputUrl | string | Required | Input media URL on a trusted allowlist domain (platform CDN or a trusted-source CDN); upload first to obtain one, or use an asset URL already on a trusted CDN |
| webhookUrl | string | — | Completion webhook URL (optional) |
Request Example
curl -X POST https://api.cqtai.com/v1/ffmpeg/qc \
-H 'Authorization: Bearer <API_KEY>' \
-H 'Content-Type: application/json' \
-d '{ "inputUrl": "https://cdn.novapi.ai/veo31/1788164205419_de868f9d69d445baaf577d42e87a236f.mp4" }'
# -> { "code":200, "data":"<taskId>" }Response Example
{
"code": 200,
"msg": "success",
"data": {
"taskId": "<taskId>",
"operation": "qc",
"status": "succeeded",
"progress": 100,
"resultUrl": "https://cdn.novapi.ai/ffmpeg/qc/1756800000000_0f1e2d3c4b5a.json",
"costPoints": 10,
"errorMsg": ""
}
}QC Report JSON
resultUrl points to the report JSON (Content-Type application/json, <=1 MiB, a direct public link with no redirects, retained long-term). All 10 top-level fields are always present — nothing is omitted: an empty segment list is [] (not null), and only unavailable loudness values are null. progress reaches 45 after the video pass, 90 after the audio pass, 100 once uploaded.
| Field | Type | Description |
|---|---|---|
| schemaVersion | int | Always 1; bumped only when thresholds or field semantics change |
| analyzedVideo | bool | Whether the video pass completed (completed != nothing found) |
| analyzedAudio | bool | Whether the audio pass completed; false when there is no audio track or the pass failed |
| blackSegments | object[] | Black-frame segments (blackdetect d=0.5 pix_th=0.10); [] when none |
| freezeSegments | object[] | Freeze segments (freezedetect n=-50dB d=2); [] when none |
| silenceSegments | object[] | Silence segments (silencedetect n=-45dB d=1.5); [] when none |
| integratedLufs | number | null | Integrated loudness (LUFS, signed, usually negative); null when unavailable |
| loudnessRangeLu | number | null | Loudness range (LU); null when unavailable |
| truePeakDbfs | number | null | True peak (dBFS, signed, may be >= 0 when clipping); null when unavailable |
| error | string | null | Degradation notes (English, joined by "; "). Note: a non-null error does NOT mean the task failed |
| 段落形状 | { startMs, endMs, durationMs } | Shared shape for all three segment kinds: integer milliseconds on the clip’s own timeline starting at 0. Note: for a segment still open at EOF, both endMs and durationMs are null (not 0, and the segment is never dropped) — this is what keeps a long trailing silence from being ignored as noise. Segments may touch or overlap; the server does not merge ranges, that call is yours |
{
"schemaVersion": 1,
"analyzedVideo": true,
"analyzedAudio": true,
"blackSegments": [
{ "startMs": 121500, "endMs": 122510, "durationMs": 1010 }
],
"freezeSegments": [],
"silenceSegments": [
{ "startMs": 121480, "endMs": 124500, "durationMs": 3020 },
{ "startMs": 331000, "endMs": null, "durationMs": null }
],
"integratedLufs": -18.4,
"loudnessRangeLu": 7.2,
"truePeakDbfs": -1.1,
"error": null
}Partial failure still succeeds: the video and audio passes run and fail independently. If only one fails the task is still succeeded, the failed side contributes [] segments with its analyzed* set to false, and error names the reason (e.g. input has no audio track / video analysis failed: ...) — half a true report beats a fabricated whole one. Only when both passes fail is the task failed and refunded.
Limits: at most 500 segments per kind — beyond that they are truncated and error notes <kind>Segments truncated to 500; if the report exceeds 1 MiB all segments are dropped while analyzed* and loudness are kept, with report exceeded 1048576 bytes; all segments dropped appended to error.
Timeouts: each pass gets the full budget on its own (300s by default, not shared). One pass timing out is treated as a partial failure; only both timing out fails the task with a refund.
Billing & Credits
Factor 0.3 (same tier as trim: decode only, no encode), billed by source length; 6 min 1080P ~ 10 credits, 30s 720P = 1 credit (x your user rate). Auto-refunded on failure.