Skip to content

Commit

Permalink
ffmpeg 5 and cambi support
Browse files Browse the repository at this point in the history
  • Loading branch information
gdavila committed May 24, 2022
1 parent 3e88582 commit 31c59a4
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 26 deletions.
16 changes: 13 additions & 3 deletions FFmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def __init__(self, main, ref, loglevel="info"):
self.vmafFilter = []
self.invertedSrc = False
self.vmafpath = None
self.vmaf_cambi_heatmap_path = None

def _commit(self):
"""build the final cmd to run"""
Expand Down Expand Up @@ -159,7 +160,7 @@ def getPsnr(self, stats_file=False):
psnr = [s for s in stdout if "average" in s][0].split(":")[1]
return float(psnr)

def getVmaf(self, log_path=None, model='HD', subsample=1, output_fmt='json', threads=0, print_progress=False, end_sync=False, features = None):
def getVmaf(self, log_path=None, model='HD', subsample=1, output_fmt='json', threads=0, print_progress=False, end_sync=False, features = None, cambi_heatmap = False):
main = self.main.lastOutputID
ref = self.ref.lastOutputID
if output_fmt == 'xml':
Expand All @@ -173,6 +174,11 @@ def getVmaf(self, log_path=None, model='HD', subsample=1, output_fmt='json', thr
log_path = os.path.splitext(self.main.videoSrc)[
0] + '_vmaf.json'
self.vmafpath = log_path

self.vmaf_cambi_heatmap_path = os.path.splitext(self.main.videoSrc)[0] + '_cambi_heatmap'



if model == 'HD':
model_hd = f'version={HD_MODEL_VERSION}\\\\:name={HD_MODEL_NAME}|version={HD_NEG_MODEL_VERSION}\\\\:name={HD_NEG_MODEL_NAME}|version={HD_PHONE_MODEL_VERSION}\\\\:name={HD_PHONE_MODEL_NAME}\\\\:enable_transform=true'
model = model_hd
Expand All @@ -186,11 +192,15 @@ def getVmaf(self, log_path=None, model='HD', subsample=1, output_fmt='json', thr
else:
shortest = 0

if features == None:
if not features:
self.vmafFilter = [f'[{main}][{ref}]libvmaf=log_fmt={log_fmt}:model={model}:n_subsample={subsample}:log_path={log_path}:n_threads={threads}:shortest={shortest}']
else:

elif features and not cambi_heatmap:
self.vmafFilter = [f'[{main}][{ref}]libvmaf=log_fmt={log_fmt}:model={model}:n_subsample={subsample}:log_path={log_path}:n_threads={threads}:shortest={shortest}:feature={features}']

elif features and cambi_heatmap:
self.vmafFilter = [f'[{main}][{ref}]libvmaf=log_fmt={log_fmt}:model={model}:n_subsample={subsample}:log_path={log_path}:n_threads={threads}:shortest={shortest}:feature={features}\\\\:heatmaps_path={self.vmaf_cambi_heatmap_path}']


self._commit()
if self.loglevel == "verbose":
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ Details about **How it Works** can be found [here](https://ottverse.com/vmaf-eas

## Updates

* Cambi features support
Since `easyVmaf` `2.0` only FFmpeg versions >= `5.0` will be supported. For using `easyVmaf` with FFmpeg < `5.0`, please consider rollingback to `easyVmaf` `1.3`.

* Added support for ffmpeg 5.0 and their built-in vmaf models
New feaures and updates:

* Progress indicator added `-progress`. It shows the progress while doing vmaf computations.
* [Cambi feature](https://github.com/Netflix/vmaf/blob/master/resource/doc/cambi.md#options) - Netflix banding detector, supported.

* Added the option to explicilty set the number of threads to run `-threads (int)`
* Command line ussage updated according to [libvmaf docs](https://ffmpeg.org/ffmpeg-filters.html#libvmaf)

* [Cambi heatmap](https://github.com/Netflix/vmaf/issues/936) support added. The outputs may be visualized with [ffplay](https://github.com/Netflix/vmaf/issues/1016#issuecomment-1099591977)

* Built-in VMAF models are only supported since they are included in FFmpeg >= `v5.0`.

* Docker image - better handling of dependencies and built instruccions

* 'HD Neg' and 'HD phone' models are computed by default

## Requirements

Expand Down
10 changes: 3 additions & 7 deletions Vmaf.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def __init__(self, mainSrc, refSrc, output_fmt, model="HD", phone=False, logleve
self.end_sync = end_sync
self.cambi_heatmap = cambi_heatmap


def _initResolutions(self):
"""
initialization of resolutions for each vmaf model
Expand Down Expand Up @@ -400,12 +401,7 @@ def getVmaf(self, autoSync=False):
"""Apply Offset filters, if offset =0 nothing happens """
self.setOffset()

if self.cambi_heatmap:
heatmap_path = os.path.splitext(self.main.videoSrc)[0] + '_cambi_heatmap'
self.features = f'name=psnr|name=cambi\\\\:full_ref=true\\\\:enc_width={self.main.streamInfo["width"]}\\\\:enc_height={self.main.streamInfo["height"]}\\\\:src_width={self.ref.streamInfo["width"]}\\\\:src_height={self.ref.streamInfo["height"]}\\\\:heatmaps_path={heatmap_path}'

else: self.features = f'name=psnr|name=cambi\\\\:full_ref=true\\\\:enc_width={self.main.streamInfo["width"]}\\\\:enc_height={self.main.streamInfo["height"]}\\\\:src_width={self.ref.streamInfo["width"]}\\\\:src_height={self.ref.streamInfo["height"]}'

self.features = f'name=psnr|name=cambi\\\\:full_ref=true\\\\:enc_width={self.main.streamInfo["width"]}\\\\:enc_height={self.main.streamInfo["height"]}\\\\:src_width={self.ref.streamInfo["width"]}\\\\:src_height={self.ref.streamInfo["height"]}'


print("\n\n=======================================", flush=True)
Expand All @@ -425,7 +421,7 @@ def getVmaf(self, autoSync=False):


vmafProcess = self.ffmpegQos.getVmaf(model=self.model, subsample=self.subsample,
output_fmt=self.output_fmt, threads=self.threads, print_progress=self.print_progress, end_sync=self.end_sync, features=self.features)
output_fmt=self.output_fmt, threads=self.threads, print_progress=self.print_progress, end_sync=self.end_sync, features=self.features, cambi_heatmap = self.cambi_heatmap)
return vmafProcess


Expand Down
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
SOFTWARE.
"""

ffmpeg = '/Users/gabriel/Downloads/ffmpeg'
ffmpeg = '/usr/local/bin/ffmpeg'
ffprobe = '/usr/local/bin/ffprobe'
36 changes: 25 additions & 11 deletions easyVmaf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
import glob
import xml.etree.ElementTree as ET

from FFmpeg import HD_MODEL_NAME
from FFmpeg import _4K_MODEL_NAME
from FFmpeg import HD_MODEL_NAME, HD_NEG_MODEL_NAME, HD_PHONE_MODEL_NAME ,_4K_MODEL_NAME, HD_PHONE_MODEL_VERSION


from statistics import mean, harmonic_mean
Expand Down Expand Up @@ -150,9 +149,6 @@ def error(self, message):
main_pattern, flush=True)
sys.exit(1)

if model == 'HD': vmaf_metric_name = HD_MODEL_NAME
elif model == '4K': vmaf_metric_name = _4K_MODEL_NAME

for main in mainFiles:
myVmaf = vmaf(main, reference, loglevel=loglevel, subsample=n_subsample, model=model,
output_fmt=output_fmt, threads=threads, print_progress=print_progress, end_sync=end_sync, manual_fps=fps, cambi_heatmap = cambi_heatmap)
Expand All @@ -171,26 +167,44 @@ def error(self, message):
vmafProcess = myVmaf.getVmaf()
vmafpath = myVmaf.ffmpegQos.vmafpath
vmafScore = []
vmafNegScore = []
vmafPhoneScore = []

if output_fmt == 'json':
with open(vmafpath) as jsonFile:
jsonData = json.load(jsonFile)
for frame in jsonData['frames']:
vmafScore.append(frame["metrics"][vmaf_metric_name])
if model == 'HD':
vmafScore.append(frame["metrics"][HD_MODEL_NAME])
vmafNegScore.append(frame["metrics"][HD_NEG_MODEL_NAME])
vmafPhoneScore.append(frame["metrics"][HD_PHONE_MODEL_NAME])
if model == '4K':
vmafScore.append(frame["metrics"][_4K_MODEL_NAME])

elif output_fmt == 'xml':
tree = ET.parse(vmafpath)
root = tree.getroot()
for frame in root.findall('frames/frame'):
value = frame.get(vmaf_metric_name)
vmafScore.append(float(value))
if model == 'HD':
vmafScore.append(frame["metrics"][HD_MODEL_NAME])
vmafNegScore.append(frame["metrics"][HD_NEG_MODEL_NAME])
vmafPhoneScore.append(frame["metrics"][HD_PHONE_MODEL_NAME])
if model == '4K':
vmafScore.append(frame["metrics"][_4K_MODEL_NAME])

print("\n \n \n \n \n ")
print("=======================================", flush=True)
print("VMAF computed", flush=True)
print("=======================================", flush=True)
print("offset: ", offset, " | psnr: ", psnr)
print("VMAF score (arithmetic mean): ", mean(vmafScore))
print("VMAF score (harmonic mean): ", harmonic_mean(vmafScore))
print("VMAF output File Path: ", myVmaf.ffmpegQos.vmafpath)
if model == 'HD':
print("VMAF HD: ", mean(vmafScore))
print("VMAF Neg: ", mean(vmafNegScore))
print("VMAF Phone: ", mean(vmafPhoneScore))
if model == '4K':
print("VMAF 4K: ", mean(vmafScore))
print("VMAF output file path: ", myVmaf.ffmpegQos.vmafpath)
if cambi_heatmap:
print("CAMBI Heatmap output path: ", myVmaf.ffmpegQos.vmaf_cambi_heatmap_path)

print("\n \n \n \n \n ")

0 comments on commit 31c59a4

Please sign in to comment.