CQTAI
FFmpeg Media · FFmpeg

Deep QCNEW

📌 What is thisRead-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 useUse 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

UsageMethodPath
Submit taskPOST/v1/ffmpeg/qc
Query ResultGET/v1/ffmpeg/info?id={taskId}
Recommended polling intervalPoll 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

ParamTypeRequiredDescription
inputUrlstringRequiredInput 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
webhookUrlstringCompletion 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.

FieldTypeDescription
schemaVersionintAlways 1; bumped only when thresholds or field semantics change
analyzedVideoboolWhether the video pass completed (completed != nothing found)
analyzedAudioboolWhether the audio pass completed; false when there is no audio track or the pass failed
blackSegmentsobject[]Black-frame segments (blackdetect d=0.5 pix_th=0.10); [] when none
freezeSegmentsobject[]Freeze segments (freezedetect n=-50dB d=2); [] when none
silenceSegmentsobject[]Silence segments (silencedetect n=-45dB d=1.5); [] when none
integratedLufsnumber | nullIntegrated loudness (LUFS, signed, usually negative); null when unavailable
loudnessRangeLunumber | nullLoudness range (LU); null when unavailable
truePeakDbfsnumber | nullTrue peak (dBFS, signed, may be >= 0 when clipping); null when unavailable
errorstring | nullDegradation 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.