How to Parse FFmpeg VMAF XML or JSON Output

When running video quality assessments using the FFmpeg libvmaf filter, the output is typically saved as an XML or JSON file containing aggregate metrics and per-frame quality scores. This article provides straightforward methods to parse both output formats using Python and standard command-line tools, allowing you to extract overall scores and frame-by-frame data efficiently.


Generating the Output Files

To generate the files, pass the log_fmt and log_path options to the libvmaf filter:

For JSON:

ffmpeg -i distorted.mp4 -i reference.mp4 -filter_complex "libvmaf=log_fmt=json:log_path=vmaf.json" -f null -

For XML:

ffmpeg -i distorted.mp4 -i reference.mp4 -filter_complex "libvmaf=log_fmt=xml:log_path=vmaf.xml" -f null -

Parsing JSON Output

The JSON output contains a pooled_metrics object for aggregated statistics (such as mean, min, and harmonic mean) and a frames array containing per-frame scores.

Using Python

import json

with open("vmaf.json", "r") as f:
    data = json.load(f)

# Extract pooled (overall) VMAF scores
pooled_vmaf = data["pooled_metrics"]["vmaf"]
print(f"Mean VMAF: {pooled_vmaf['mean']}")
print(f"Min VMAF:  {pooled_vmaf['min']}")

# Extract per-frame scores
frame_scores = [frame["metrics"]["vmaf"] for frame in data["frames"]]
print(f"First frame VMAF: {frame_scores[0]}")
print(f"Total evaluated frames: {len(frame_scores)}")

Using the Command Line (jq)

Extract the aggregate mean VMAF score:

jq '.pooled_metrics.vmaf.mean' vmaf.json

Extract a list of per-frame scores:

jq '[.frames[].metrics.vmaf]' vmaf.json

Parsing XML Output

The XML output contains <metric> nodes inside the <pooled_metrics> parent, and individual frame records inside <frames> using <f> elements with metric attributes.

Using Python (xml.etree.ElementTree)

import xml.etree.ElementTree as ET

tree = ET.parse("vmaf.xml")
root = tree.getroot()

# Extract pooled metrics
for metric in root.findall(".//pooled_metrics/metric"):
    if metric.attrib.get("name") == "vmaf":
        print(f"Mean VMAF: {metric.attrib.get('mean')}")
        print(f"Min VMAF:  {metric.attrib.get('min')}")

# Extract per-frame scores
frame_scores = []
for frame in root.findall(".//frames/f"):
    frame_scores.append(float(frame.attrib["vmaf"]))

print(f"First frame VMAF: {frame_scores[0]}")
print(f"Total evaluated frames: {len(frame_scores)}")

Using the Command Line (xmlstarlet)

Extract the aggregate mean VMAF score:

xmlstarlet sel -t -v "//pooled_metrics/metric[@name='vmaf']/@mean" -n vmaf.xml

Extract all per-frame scores separated by newlines:

xmlstarlet sel -t -m "//frames/f" -v "@vmaf" -n vmaf.xml