diff --git a/docker/ffmpeg-7.1/Dockerfile b/docker/ffmpeg-7.1/Dockerfile index 379d0ef..2237db2 100644 --- a/docker/ffmpeg-7.1/Dockerfile +++ b/docker/ffmpeg-7.1/Dockerfile @@ -653,18 +653,6 @@ RUN \ make qt-faststart && cp qt-faststart ${PREFIX}/bin/ -RUN \ - echo "Downloading ACES." && \ - DIR=/opt/aces && \ - mkdir -p ${DIR} && \ - cd ${DIR} && \ - curl -sLO https://github.com/colour-science/OpenColorIO-Configs/archive/refs/tags/v1.2.tar.gz && \ - tar zxvf v1.2.tar.gz OpenColorIO-Configs-1.2/aces_1.2 && \ - rm v1.2.tar.gz && \ - mkdir -p /usr/share/ocio/aces_1.2 && \ - cp -vr ${DIR}/OpenColorIO-Configs-1.2/aces_1.2 /usr/share/ocio && \ - rm -rf ${DIR} - RUN \ ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib64/ && \ for lib in /usr/local/lib64/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \ @@ -690,7 +678,7 @@ RUN \ # Need to add /opt/rh/httpd24/root/usr/lib64/ to get git clone to work. ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib:/opt/rh/httpd24/root/usr/lib64/ \ - OCIO=/usr/share/ocio/aces_1.2/config.ocio \ + OCIO=ocio://studio-config-v1.0.0_aces-v1.3_ocio-v2.1 \ VMAF_MODEL=${VMAF_LIB_DIR} RUN pip install OpenTimelineIO diff --git a/docker/rocky-ffmpeg-7.0/Dockerfile b/docker/rocky-ffmpeg-7.0/Dockerfile index e34258a..c61783a 100644 --- a/docker/rocky-ffmpeg-7.0/Dockerfile +++ b/docker/rocky-ffmpeg-7.0/Dockerfile @@ -645,7 +645,7 @@ RUN \ --disable-doc \ # --enable-cuda-nvcc \ --enable-libnpp \ - --disable-ffplay \ + # --disable-ffplay \ --enable-fontconfig \ --enable-cuda-nvcc \ --enable-gpl \ diff --git a/enctests/README.md b/enctests/README.md index 1ea91dc..f56ff05 100644 --- a/enctests/README.md +++ b/enctests/README.md @@ -224,7 +224,7 @@ rm v1.2.tar.gz export OCIO=$PWD/OpenColorIO-Configs-1.2/aces_1.2/config.ocio # Set VMAF_Models NOTE, should probably change this. -export VMAF_MODEL=/opt/homebrew/Cellar/libvmaf/2.3.1/share/libvmaf/model/ +export VMAF_MODEL_DIR=/opt/homebrew/Cellar/libvmaf/3.0.0/share/libvmaf/model/ # Run tests (for now) .venv/bin/python main.py diff --git a/enctests/bin/generic_encodewrap.csh b/enctests/bin/generic_encodewrap.csh new file mode 100755 index 0000000..4a9949a --- /dev/null +++ b/enctests/bin/generic_encodewrap.csh @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 + + +import os, sys +import argparse +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from functools import partial +import subprocess + +# This allows you to compress the file with the compressor of your choice, and then wrap the result in a quicktime file +# So we can do the comparison with vmaf. +# We assume that what ever the intermediate compressor is doing that the resulting intermediate file is a png file. + +def process_file(args, tmpoutputdir, ext, file_path): + input_path = Path(file_path) + output_path = Path(os.path.join(tmpoutputdir, os.path.basename(str(input_path)))).with_suffix(ext) + if os.path.exists(str(output_path)): + print("Removing file:", output_path) + os.remove(str(output_path)) + try: + # Your processing here + # Example: copy file with new extension + cmd = args[:] + cmd.extend(['-i', file_path]) + + cmd.extend(["-o", str(output_path)]) + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + return True, input_path, output_path + except Exception as e: + print("ERROR:", e) + return False, input_path, str(e) + +def main(): + print("Root:", os.path.dirname(os.path.abspath(__file__))) + os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + ":" + os.environ['PATH'] + + parser = argparse.ArgumentParser() + + parser.add_argument( + '-i', '--input', + dest="input", + action='store', + default=[""], + help='Provide an input file.' + ) + parser.add_argument( + '-start_number', + dest="start_frame", + default="0", + help='Start frame for input file.' + ) + parser.add_argument( + '-r', + dest="framerate", + default="25", + help='Frame rate' + ) + + parser.add_argument( + '--extension', + dest="extension", + default="png", + help='Intermediate extension' + ) + + args, otherargs = parser.parse_known_args() + ext = args.extension + print("EXT:", ext) + outputfile = otherargs[-1] + otherargs = otherargs[:-1] #Strip of the last argument. + + # Configure number of workers + max_workers = 4 + + outputdir = os.path.dirname(outputfile) + tmpoutputdir = os.path.join(outputdir, "tmppngfiles", os.path.basename(outputfile)) + if not os.path.exists(tmpoutputdir): + os.makedirs(tmpoutputdir) + + # Get list of files + inputfile = args.input + print("Processing input file spec:", inputfile) + files = [] + if "%" in inputfile: + startframe = int(args.start_frame) + infile = inputfile % startframe + while os.path.exists(infile): + files.append(infile) + startframe = startframe + 1 + infile = inputfile % startframe + print("Checking:", infile) + else: + files.append(inputfile) + # files = list(Path('directory').glob('*.txt')) + + # Process files with progress bar + with ThreadPoolExecutor(max_workers=max_workers) as executor: + results = executor.map(partial(process_file, otherargs, str(tmpoutputdir), "." + ext), files) + + # Report results + successes = [r for r in results if r[0]] + failures = [r for r in results if not r[0]] + + print(f"\nProcessed {len(successes)} files successfully") + if failures: + print(f"Failed to process {len(failures)} files:") + for _, input_path, error in failures: + print(f" {input_path}: {error}") + else: + intputfilej2k = str(Path(os.path.join(tmpoutputdir, os.path.basename(inputfile))).with_suffix("." + ext)) + cmd = ['ffmpeg', '-f', 'image2', '-r', args.framerate, + '-start_number', args.start_frame, '-i', intputfilej2k, '-vcodec', 'copy', + '-color_range', '1', '-colorspace', '1', '-color_primaries', '1', + '-color_trc', '2', '-y', outputfile] + print(" ".join(cmd)) + subprocess.run(cmd) + # Now we remove the intermediate files. + + + + +if __name__ == '__main__': + main() + diff --git a/enctests/bin/htj2k_encodewrap.csh b/enctests/bin/htj2k_encodewrap.csh new file mode 100755 index 0000000..8be92c9 --- /dev/null +++ b/enctests/bin/htj2k_encodewrap.csh @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + + +import os +import argparse +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from functools import partial +import subprocess + +# This allows you to do the encoding of a j2h file in a different app, and then wrap the result into a movie file with ffmpeg. + +def process_file(args, tmpoutputdir, file_path): + input_path = Path(file_path) + output_path = Path(os.path.join(tmpoutputdir, os.path.basename(str(input_path)))).with_suffix('.j2c') + if os.path.exists(str(output_path)): + print("Removing file:", output_path) + os.remove(str(output_path)) + try: + # Your processing here + # Example: copy file with new extension + cmd = [f.replace("--reversible", "-reversible") for f in args[:]] + cmd.extend(['-i', file_path, "-o", str(output_path)]) + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + return True, input_path, output_path + except Exception as e: + print("ERROR:", e) + return False, input_path, str(e) + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '-i', '--input', + dest="input", + action='store', + default=[""], + help='Provide an input file.' + ) + parser.add_argument( + '-start_number', + dest="start_frame", + default="0", + help='Start frame for input file.' + ) + parser.add_argument( + '-r', + dest="framerate", + default="25", + help='Frame rate' + ) + + args, otherargs = parser.parse_known_args() + outputfile = otherargs[-1] + otherargs = otherargs[:-1] #Strip of the last argument. + + # Need to look for arguments ending in "=" so we know to merge with next arg. + # so ["Qfactor=", "40", "Clevels=", "5"] would become ['Qfactor=40', 'Clevels=5'] + newotherargs = [] + while otherargs: + print("OTHERARGS:", otherargs) + arg = otherargs.pop(0) + if arg.endswith("="): + nextarg = otherargs.pop(0) + arg = arg[:-1] + "=" + nextarg + newotherargs.append(arg) + otherargs = newotherargs + + # Configure number of workers + max_workers = 4 + + outputdir = os.path.dirname(outputfile) + tmpoutputdir = os.path.join(outputdir, "tmpj2kfiles", os.path.basename(outputfile)) + if not os.path.exists(tmpoutputdir): + os.makedirs(tmpoutputdir) + + # Get list of files + inputfile = args.input + print("Processing input file spec:", inputfile) + files = [] + if "%" in inputfile: + startframe = int(args.start_frame) + infile = inputfile % startframe + while os.path.exists(infile): + files.append(infile) + startframe = startframe + 1 + infile = inputfile % startframe + print("Checking:", infile) + else: + files.append(inputfile) + # files = list(Path('directory').glob('*.txt')) + + # Process files with progress bar + with ThreadPoolExecutor(max_workers=max_workers) as executor: + results = executor.map(partial(process_file, otherargs, str(tmpoutputdir)), files) + + # Report results + successes = [r for r in results if r[0]] + failures = [r for r in results if not r[0]] + + print(f"\nProcessed {len(successes)} files successfully") + if failures: + print(f"Failed to process {len(failures)} files:") + for _, input_path, error in failures: + print(f" {input_path}: {error}") + else: + intputfilej2k = str(Path(os.path.join(tmpoutputdir, os.path.basename(inputfile))).with_suffix('.j2c')) + cmd = ['ffmpeg', '-f', 'image2', '-r', args.framerate, + '-start_number', args.start_frame, '-i', intputfilej2k, '-vcodec', 'copy', + '-color_range', '1', '-colorspace', '1', '-color_primaries', '1', + '-color_trc', '2', '-y', outputfile] + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + # Now we remove the intermediate files. + + + + +if __name__ == '__main__': + main() + diff --git a/enctests/bin/htj2k_encodewrap_oiio.csh b/enctests/bin/htj2k_encodewrap_oiio.csh new file mode 100755 index 0000000..ea9cc25 --- /dev/null +++ b/enctests/bin/htj2k_encodewrap_oiio.csh @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + + +import os +import argparse +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from functools import partial +import subprocess + + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '-i', '--input', + dest="input", + action='store', + default=[""], + help='Provide an input file.' + ) + parser.add_argument( + '-start_number', + dest="start_frame", + default="0", + help='Start frame for input file.' + ) + parser.add_argument( + '-r', + dest="framerate", + default="25", + help='Frame rate' + ) + + args, otherargs = parser.parse_known_args() + file_path = args.input + outputfile = otherargs[-1] + otherargs = otherargs[:-1] #Strip of the last argument. + + input_path = Path(file_path) + + outputdir = os.path.dirname(outputfile) + tmpoutputdir = os.path.join(outputdir, "tmpj2kfiles", os.path.basename(outputfile)) +) + output_path = Path(os.path.join(tmpoutputdir, os.path.basename(str(input_path)))).with_suffix('.j2c') + + if not os.path.exists(tmpoutputdir): + os.makedirs(tmpoutputdir) + + # Get list of files + inputfile = args.input + + oiio_args = [] + launch_arg = otherargs.pop(0) # Get rid of the app to run. + arg = otherargs.pop(0) + while otherargs: + if arg.startswith("-jph:"): + oiio_args.append("--attrib") + oiio_args.append(arg.replace("-jph:", "jph:")) + arg = otherargs.pop(0) + oiio_args.append(arg) + else: + print("Misc arg:", arg) + oiio_args.append(arg) + if not otherargs: + break + arg = otherargs.pop(0) + + endframe = int(args.start_frame) + file = str(input_path) % endframe + print("FILE:", file, " from :", input_path) + while(os.path.exists(str(input_path) % endframe)): + endframe += 1 + endframe -= 1 + + oiio_args = [launch_arg, '-v', '-i', str(input_path), "--parallel-frames", "--frames", "%s-%d" % (args.start_frame, endframe)]+oiio_args+['-o', str(output_path)] + print(" ".join(oiio_args)) + subprocess.run(oiio_args) + + + intputfilej2k = str(Path(os.path.join(tmpoutputdir, os.path.basename(inputfile))).with_suffix('.j2c')) + cmd = ['ffmpeg', '-f', 'image2', '-r', args.framerate, + '-start_number', args.start_frame, '-i', intputfilej2k, '-vcodec', 'copy', + '-color_range', '1', '-colorspace', '1', '-color_primaries', '1', + '-color_trc', '2', '-y', outputfile] + subprocess.run(cmd) + # Now we remove the intermediate files. + + + + +if __name__ == '__main__': + main() + diff --git a/enctests/bin/todwab.csh b/enctests/bin/todwab.csh new file mode 100755 index 0000000..5c3d1b1 --- /dev/null +++ b/enctests/bin/todwab.csh @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + + +import os, sys +import argparse +from pathlib import Path +import subprocess + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '-i', '--input', + dest="input", + action='store', + default="", + help='Provide an input file.' + ) + + parser.add_argument( + '-o', '--output', + dest="output", + action='store', + default="", + help='Provide an output file.' + ) + + print("ARGV:", sys.argv) + + args, otherargs = parser.parse_known_args(sys.argv[1:]) + newotherargs = [] + print("Other args:", otherargs) + print("ARGS:", args) + + + input_path = Path(args.input) + output_path = Path(args.output) + intermediate_path = output_path.with_suffix(".exr") + + cmd = ["oiiotool", "-i", str(input_path)] + cmd.extend(otherargs) + cmd.extend(["-o", str(intermediate_path)]) + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + cmd = ['oiiotool', '-i', str(intermediate_path), "-o", str(output_path) ] + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/enctests/bin/tofloatj2k.csh b/enctests/bin/tofloatj2k.csh new file mode 100755 index 0000000..34f6eb1 --- /dev/null +++ b/enctests/bin/tofloatj2k.csh @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + + +import os +import argparse +from pathlib import Path +import subprocess + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '-i', '--input', + dest="input", + action='store', + default="", + help='Provide an input file.' + ) + + parser.add_argument( + '-o', '--output', + dest="output", + action='store', + default="", + help='Provide an output file.' + ) + + args, otherargs = parser.parse_known_args() + + input_path = Path(args.input) + output_path = Path(args.output) + intermediate_path = output_path.with_suffix(".exr") + # Initial conversion to exr, to ensure the ojph_compress is using exr and its half float. + cmd = ['oiiotool', '-i', str(input_path), "-o", str(intermediate_path) ] + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + # Now we do the j2c compression + j2k_intermediate_path = output_path.with_suffix(".j2c") + cmd = ["/Users/sam/git/OpenJphExr/build/src/apps/ojph_compress/ojph_compress", "-i", str(intermediate_path)] + cmd.extend(otherargs) + cmd.extend(["-o", str(j2k_intermediate_path)]) + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + + # Now we do the decompression using the same decompression code + exr_intermediate_path2 = str(output_path).replace(".png", "2.exr") + cmd = ["/Users/sam/git/OpenJphExr/build/src/apps/ojph_expand/ojph_expand", "-i", str(j2k_intermediate_path), "-o", str(exr_intermediate_path2)] + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + + # Finally we convert the result back to png + cmd = ['oiiotool', '-i', str(exr_intermediate_path2), "-o", str(output_path) ] + print("Running:", " ".join(cmd)) + subprocess.run(cmd) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/enctests/playback/codec_test.c b/enctests/playback/codec_test.c index 6cf1a48..629d107 100644 --- a/enctests/playback/codec_test.c +++ b/enctests/playback/codec_test.c @@ -8,9 +8,9 @@ #include #include -#define ADDITIONAL_FRAMES 30 +#define ADDITIONAL_FRAMES 100 -double get_time() { +double get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; @@ -36,7 +36,7 @@ int decode_frames_with_library(const char* filename, const char* decoder_library return -1; } - for (int i = 0; i < format_ctx->nb_streams; i++) { + for (unsigned int i = 0; i < format_ctx->nb_streams; i++) { if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; @@ -82,9 +82,9 @@ int decode_frames_with_library(const char* filename, const char* decoder_library } // Set threading options codec_ctx->thread_count = 0; // Let FFmpeg decide the number of threads - codec_ctx->thread_type = FF_THREAD_FRAME | FF_THREAD_SLICE; // Use both frame and slice threading - codec_ctx->flags2 |= AV_CODEC_FLAG2_FAST; - codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; + // codec_ctx->thread_type = FF_THREAD_FRAME; // | FF_THREAD_SLICE; // Use both frame and slice threading + // codec_ctx->flags2 |= AV_CODEC_FLAG2_FAST; + // codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; if (avcodec_open2(codec_ctx, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); @@ -116,6 +116,8 @@ int decode_frames_with_library(const char* filename, const char* decoder_library double total_decoding_time = 0.0; double first_frame_time = 0.0; clock_t start_time = clock(); + double seek_start = get_time(); + if (av_seek_frame(format_ctx, video_stream_index, seek_timestamp, AVSEEK_FLAG_BACKWARD) < 0) { fprintf(stderr, "Error while seeking\n"); @@ -128,12 +130,16 @@ int decode_frames_with_library(const char* filename, const char* decoder_library avcodec_flush_buffers(codec_ctx); + double seek_end = get_time(); + printf("SeekTime: %.3f\n", seek_end - seek_start); + clock_t end_time = clock(); double frame_time = ((double) (end_time - start_time)) / CLOCKS_PER_SEC; fprintf(stderr, "Decoder seek: %s\nFirstFrame: %.6f\n", decoder_library, frame_time); start_time = clock(); struct timespec start_time2; - clock_gettime(CLOCK_MONOTONIC, &start_time2); + clock_gettime(CLOCK_MONOTONIC, &start_time2); + double start_decode = get_time(); while (av_read_frame(format_ctx, packet) >= 0 && frames_decoded <= ADDITIONAL_FRAMES) { @@ -171,13 +177,15 @@ int decode_frames_with_library(const char* filename, const char* decoder_library clock_gettime(CLOCK_MONOTONIC, &end_time2); double elapsed_seconds = (end_time2.tv_sec - start_time2.tv_sec) + (end_time2.tv_nsec - start_time2.tv_nsec) / 1e9; - fprintf(stderr, "Decoder: %s Frame: %02d Duration:%.6f ReceiveDuration:%.6f Duration2:%.6f\n", + fprintf(stderr, "Decoder: %s Frame: %02d Duration:%.6f ReceiveDuration:%.6f Elapsed:%.6f\n", decoder_library, frames_decoded, frame_time, receive_frame_time, elapsed_seconds); total_decoding_time += frame_time; if (frames_decoded == 0) { + double first_frame_end = get_time(); + printf("FirstFrame: %.6f\n", first_frame_end - seek_start); first_frame_time = frame_time; - printf("Decoder: %s\nFirstFrame: %.6f\n", + printf("Decoder: %s\nFirstFrameClock: %.6f\n", decoder_library, frame_time); total_decoding_time = 0; start_time = clock(); // We reset, since we dont want the following times to include the first time. @@ -196,9 +204,17 @@ int decode_frames_with_library(const char* filename, const char* decoder_library } end: + + if (frames_decoded > 1) { + double end_decode = get_time(); + double decode_duration = end_decode - start_decode; + printf("totalDecodeTime: %.6f\n", decode_duration); + printf("Average: %.6f\n", decode_duration/frames_decoded); + printf("FPS: %.6f\n", frames_decoded/decode_duration); + double avg_subsequent_frame_time = (total_decoding_time - first_frame_time) / (frames_decoded - 1); - printf("Average: %.6f\n", + printf("AverageClock: %.6f\n", avg_subsequent_frame_time); } @@ -223,4 +239,4 @@ int main(int argc, char* argv[]) { decode_frames_with_library(input_file, decoder_library, seek_frame); return 0; -} \ No newline at end of file +} diff --git a/enctests/playback/ffmpegplayback.py b/enctests/playback/ffmpegplayback.py new file mode 100644 index 0000000..b33cf62 --- /dev/null +++ b/enctests/playback/ffmpegplayback.py @@ -0,0 +1,190 @@ +import subprocess +import time +import json +import os +import re +from datetime import datetime + +testdirs = ["../wedge_results/ffmpeg_version_7.1/linux-x86_64/intraframe_tests-encode", + "../wedge_results/ffmpeg_version_7.1/linux-x86_64/codec_tests-encode", + "../wedge_results/ffmpeg_version_7.1/linux-x86_64/htj2k_options_tests-encode"] + +def run_decode_test(input_file, iterations=3): + """ + Run decode performance test on a video file + """ + results = [] + + for i in range(iterations): + start_time = time.time() + + # Run ffmpeg with detailed stats + cmd = [ + 'ffmpeg', + '-hide_banner', + '-benchmark', + '-i', input_file, + '-f', 'null', + '-' + ] + print("Running:", " ".join(cmd)) + + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True + ) + + _, stderr = process.communicate() + + # Parse the output + duration = time.time() - start_time + + # Extract benchmark info from ffmpeg output + fps_line = [line for line in stderr.split('\n') if 'fps=' in line] + print("Got FPS line:", fps_line[-1]) + # Parse something like "frame= 200 fps= 67 q=-0.0 Lsize=N/A time=00:00:08.00 bitrate=N/A speed=2.67x" + fps_pattern = re.compile(r'fps=\s*(\d+\.?\d*)') + match = fps_pattern.search(fps_line[-1]) + if match: + fps = float(match.group(1)) + else: + fps = "Undefined" + speed_pattern = re.compile(r'speed=\s*(\d+\.?\d*)x') + match = speed_pattern.search(fps_line[-1]) + if match: + speed = float(match.group(1)) + else: + speed = "Undefined" + + print("SPeed:", speed, " fps:", fps) + + # Extract benchmark info from ffmpeg output + bench_line = [line for line in stderr.split('\n') if 'bench' in line] + # Parse something like "bench: utime=2.124s stime=0.069s rtime=2.204s" + parts = bench_line[0].split() + times = {} + for part in parts[1:]: # Skip 'bench:' + key, value = part.split('=') + times[key] = float(value.rstrip('s')) + + results.append({ + 'iteration': i + 1, + 'fps': fps, + 'speed': speed, + 'duration': times.get('rtime', 0), + 'user_time': times.get('utime', 0), + 'system_time': times.get('stime', 0), + 'real_time': times.get('rtime', 0) + }) + + # Small delay between iterations + time.sleep(1) + + return results + +def test_multiple_codecs(input_files): + """ + Test decode performance for multiple input files + """ + results = {} + + for input_file in input_files: + print(f"\nTesting {input_file}...") + codec_info = get_codec_info(input_file) + results[input_file] = { + 'codec_info': codec_info, + 'decode_tests': run_decode_test(input_file) + } + + return results + +def get_codec_info(input_file): + """ + Get codec information for the input file + """ + cmd = [ + 'ffprobe', + '-v', 'quiet', + '-print_format', 'json', + '-show_streams', + input_file + ] + + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True + ) + + stdout, _ = process.communicate() + return json.loads(stdout) + +def generate_report(results): + """ + Generate a formatted report of the results + """ + report = ["Codec Decode Performance Report", "=" * 30, ""] + + for file_name, data in results.items(): + report.append(f"\nFile: {file_name}") + report.append("-" * 50) + + # Add codec info + if 'codec_info' in data and 'streams' in data['codec_info']: + for stream in data['codec_info']['streams']: + if stream.get('codec_type') == 'video': + report.append(f"Codec: {stream.get('codec_name', 'Unknown')}") + report.append(f"Resolution: {stream.get('width', '?')}x{stream.get('height', '?')}") + report.append(f"Framerate: {stream.get('r_frame_rate', 'Unknown')}") + + # Add decode test results + report.append("\nDecode Test Results:") + report.append("Iteration | Duration (s) | Speed | FPS") + report.append("-" * 40) + + avg_duration = 0 + avg_speed = 0 + avg_fps = 0 + tests = data['decode_tests'] + + for test in tests: + report.append(f"{test['iteration']:^9} | {test['duration']:^11.3f} | {test['speed']:^5.2f}x | {test['fps']:^5.2f}") + avg_duration += test['duration'] + avg_fps += test['fps'] + if test['speed']: + avg_speed += test['speed'] + + avg_duration /= len(tests) + avg_speed /= len(tests) + avg_fps /= len(tests) + + report.append("-" * 40) + report.append(f"Average | {avg_duration:^11.3f} | {avg_speed:^5.2f}x | {avg_fps:^5.2f}") + report.append("") + + return "\n".join(report) + +def main(): + # Example usage + input_files = [] + for testdir in testdirs: + input_files.extend([os.path.join(testdir, f) for f in os.listdir(testdir) if f.endswith(".mp4") or f.endswith(".mov")or f.endswith(".mxf")]) + + results = test_multiple_codecs(input_files) + report = generate_report(results) + + # Save report + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + report_file = f'codec_benchmark_report_{timestamp}.txt' + + with open(report_file, 'w') as f: + f.write(report) + + print(report) + print(f"\nReport saved to: {report_file}") + +if __name__ == "__main__": + main() diff --git a/enctests/playback/playbacktest.py b/enctests/playback/playbacktest.py index 3c93dd2..b471aae 100644 --- a/enctests/playback/playbacktest.py +++ b/enctests/playback/playbacktest.py @@ -9,8 +9,9 @@ testdirs = ["../wedge_results/ffmpeg_version_7.0.1/darwin-arm64/intraframe_tests-encode", "../wedge_results/ffmpeg_version_7.0/darwin-arm64/codec_tests-encode"] -testdirs = ["../wedge_results/ffmpeg_version_7.0/linux-x86_64/intraframe_tests-encode", - "../wedge_results/ffmpeg_version_7.0/linux-x86_64/codec_tests-encode"] +testdirs = ["../wedge_results/ffmpeg_version_7.1/linux-x86_64/intraframe_tests-encode", + "../wedge_results/ffmpeg_version_7.1/linux-x86_64/codec_tests-encode", + "../wedge_results/ffmpeg_version_7.1/linux-x86_64/htj2k_options_tests-encode"] def get_video_codec(filename): @@ -79,7 +80,7 @@ def estimate_gop_size(filename): files.extend([os.path.join(testdir, f) for f in os.listdir(testdir) if f.endswith(".mp4") or f.endswith(".mov")]) -f = open("playback_results2.html", "w") +f = open("playback_results3.html", "w") print(""" @@ -103,6 +104,7 @@ def estimate_gop_size(filename): 'cfhd': ['cfhd'], 'prores': ['prores'], 'mjpeg': ['mjpeg'], + 'jpeg2000': ['jpeg2000'], } fields=['basename', 'Decoder', 'filesize', 'gopsize', 'FirstFrame', 'FirstFrame%30', 'FirstFrame%60', 'Average','Average%30', 'Average%60'] diff --git a/enctests/sources/enc_sources/chimera_coaster/chimera_coaster.%06d.tif.yml b/enctests/sources/enc_sources/chimera_coaster/chimera_coaster.%06d.tif.yml new file mode 100644 index 0000000..d9ec849 --- /dev/null +++ b/enctests/sources/enc_sources/chimera_coaster/chimera_coaster.%06d.tif.yml @@ -0,0 +1,8 @@ +images: true +path: Chimera_DCI4k5994p_HDR_P3PQ_%06d.tif +width: 4096 +height: 2160 +pix_fmt: rgb48be +in: 44200 +duration: 200 +rate: 60.0 diff --git a/enctests/sources/enc_sources/chimera_fountains/chimera_fountains.%05d.tif.yml b/enctests/sources/enc_sources/chimera_fountains/chimera_fountains.%05d.tif.yml new file mode 100644 index 0000000..7ab0254 --- /dev/null +++ b/enctests/sources/enc_sources/chimera_fountains/chimera_fountains.%05d.tif.yml @@ -0,0 +1,8 @@ +images: true +path: Chimera_DCI4k5994p_HDR_P3PQ_%06d.tif +width: 4096 +height: 2160 +pix_fmt: rgb48be +in: 5400 +duration: 200 +rate: 60.0 diff --git a/enctests/sources/enc_sources/download_media.sh b/enctests/sources/enc_sources/download_media.sh index 279bd86..27e7998 100755 --- a/enctests/sources/enc_sources/download_media.sh +++ b/enctests/sources/enc_sources/download_media.sh @@ -47,7 +47,7 @@ if [ ! -f chimera_wind_srgb/chimera_wind_srgb.66600.png ] then mkdir chimera_wind_srgb echo Building chimera_wind png - oiiotool -v --framepadding 6 --frames 66600-667199 -i chimera_wind/Chimera_DCI4k5994p_HDR_P3PQ_@@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_wind_srgb/chimera_wind_srgb.#.png + oiiotool -v --framepadding 6 --parallel-frames --frames 66600-66799 -i chimera_wind/Chimera_DCI4k5994p_HDR_P3PQ_@@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_wind_srgb/chimera_wind_srgb.#.png # rm -rf chimera_wind fi @@ -55,7 +55,7 @@ if [ ! -f chimera_coaster_srgb/chimera_coaster_srgb.44200.png ] then mkdir chimera_coaster_srgb echo Building chimera_coaster_srgb png - echo oiiotool -v --framepadding 6 --frames 44200-44399 -i chimera_coaster/Chimera_DCI4k5994p_HDR_P3PQ_@@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_coaster_srgb/chimera_coaster_srgb.#.png + oiiotool -v --framepadding 6 --parallel-frames --frames 44200-44399 -i chimera_coaster/Chimera_DCI4k5994p_HDR_P3PQ_@@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_coaster_srgb/chimera_coaster_srgb.#.png #rm -rf chimera_coaster fi @@ -64,7 +64,7 @@ if [ ! -f chimera_cars_srgb/chimera_cars_srgb.02500.png ] then mkdir chimera_cars_srgb echo Building chimera_cars png - echo oiiotool -v --framepadding 5 --frames 2500-2699 -i chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_cars_srgb/chimera_cars_srgb.#.png + oiiotool -v --framepadding 5 --parallel-frames --frames 2500-2699 -i chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_cars_srgb/chimera_cars_srgb.#.png #rm -rf chimera_cars fi @@ -73,6 +73,10 @@ if [ ! -f chimera_fountains_srgb/chimera_fountains_srgb.05400.png ] then mkdir chimera_fountains_srgb echo Building chimera_fountains png - echo oiiotool -v --framepadding 5 --frames 5400-5599 -i chimera_fountains/Chimera_DCI4k2398p_HDR_P3PQ_@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_fountains_srgb/chimera_fountains_srgb.#.png + oiiotool -v --framepadding 5 --parallel-frames --frames 5400-5599 -i chimera_fountains/Chimera_DCI4k2398p_HDR_P3PQ_@@@@@.tif --resize 1920x1080 --powc 2 -d uint16 -o chimera_fountains_srgb/chimera_fountains_srgb.#.png #rm -rf chimera_fountains fi + +#oiiotool -v --frames 2500-2699 --parallel-frames -i chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_%05d.tif --ociodisplay:from=ACEScg:inverse=1 'ST2084-P3-D65 - Display' 'ACES 1.1 - HDR Video (1000 nits & P3 lim)' --mulc 0.15 -o chimera_cars_ACEScg_exr/chimera_cars_ACEScg_exr.%05d.exr +#oiiotool -v --frames 044200-44399 --parallel-frames -i chimera_coaster/Chimera_DCI4k5994p_HDR_P3PQ_%06d.tif --ociodisplay:from=ACEScg:inverse=1 'ST2084-P3-D65 - Display' 'ACES 1.1 - HDR Video (1000 nits & P3 lim)' -o exr/coaster.%06d.exr +#oiiotool -v --framepadding 5 --parallel-frames --frames 5400-5599 -i chimera_fountains/Chimera_DCI4k2398p_HDR_P3PQ_@@@@@.tif --ociodisplay:from=ACEScg:inverse=1 'ST2084-P3-D65 - Display' 'ACES 1.1 - HDR Video (1000 nits & P3 lim)' -o chimera_fountains/chimera_fountains.%06d.exr diff --git a/enctests/sources/enc_sources/htj2k_prep.sh b/enctests/sources/enc_sources/htj2k_prep.sh new file mode 100755 index 0000000..18f62d7 --- /dev/null +++ b/enctests/sources/enc_sources/htj2k_prep.sh @@ -0,0 +1,27 @@ +#!/bin/bash +ext=dpx +bitdepth=16 +ops="-d uint${bitdepth}" +#ops=-pix_fmt rgb30 -compression_level 0 +mkdir chimera_coaster_srgb_${ext}_${bitdepth} +sed "s/.png/.$ext/" < chimera_coaster_srgb/chimera_coaster_srgb.%06d.png.yml > chimera_coaster_srgb_${ext}_${bitdepth}/chimera_coaster_srgb.%06d.$ext.yml +#mogrify -path chimera_coaster_srgb_${ext}_${bitdepth} -format $ext -depth $bitdepth chimera_coaster_srgb/*.png + +#ffmpeg -f image2 -framerate 1 -start_number 44200 -i chimera_coaster_srgb/chimera_coaster_srgb.%06d.png ${ops} chimera_coaster_srgb_${ext}_${bitdepth}/chimera_coaster_srgb.%06d.$ext +echo oiiotool -v -framepadding 6 --parallel-frames --frames 44200-44399 -i chimera_coaster_srgb/chimera_coaster_srgb.#.png ${ops} -o chimera_coaster_srgb_${ext}_${bitdepth}/chimera_coaster_srgb.#.$ext +oiiotool -v -framepadding 6 --parallel-frames --frames 44200-44399 -i chimera_coaster_srgb/chimera_coaster_srgb.#.png ${ops} -o chimera_coaster_srgb_${ext}_${bitdepth}/chimera_coaster_srgb.#.$ext + +mkdir chimera_fountains_srgb_${ext}_${bitdepth} +sed "s/.png/.$ext/" < chimera_fountains_srgb/chimera_fountains_srgb.%05d.png.yml > chimera_fountains_srgb_${ext}_${bitdepth}/chimera_fountains_srgb.%05d.$ext.yml +#mogrify -path chimera_fountains_srgb_${ext}_${bitdepth} -format $ext -depth $bitdepth chimera_fountains_srgb/*.png +oiiotool -v --framepadding 5 --parallel-frames --frames 5400-5599 -i chimera_fountains_srgb/chimera_fountains_srgb.#.png ${ops} -o chimera_fountains_srgb_${ext}_${bitdepth}/chimera_fountains.#.$ext + +mkdir chimera_wind_srgb_${ext}_${bitdepth} +sed "s/.png/.$ext/" < chimera_wind_srgb/chimera_wind_srgb.%06d.png.yml > chimera_wind_srgb_${ext}_${bitdepth}/chimera_wind_srgb.%06d.$ext.yml +#mogrify -path chimera_wind_srgb_${ext}_${bitdepth} -format $ext -depth $bitdepth chimera_wind_srgb/*.png +oiiotool -v --framepadding 6 --parallel-frames --frames 66600-66799 -i chimera_wind_srgb/chimera_wind_srgb.#.png ${ops} -o chimera_wind_srgb_${ext}_${bitdepth}/chimera_wind_srgb.#.$ext + +mkdir chimera_cars_srgb_${ext}_${bitdepth} +sed "s/.png/.$ext/" < chimera_cars_srgb/chimera_cars_srgb.%05d.png.yml > chimera_cars_srgb_${ext}_${bitdepth}/chimera_cars_srgb.%05d.$ext.yml +#mogrify -path chimera_cars_srgb_${ext}_${bitdepth} -format $ext -depth $bitdepth chimera_cars_srgb/*.png +oiiotool -v --framepadding 5 --parallel-frames --frames 2500-2699 -i chimera_cars_srgb/chimera_cars_srgb.#.png ${ops} -o chimera_cars_srgb_${ext}_${bitdepth}/chimera_cars_srgb.#.$ext diff --git a/enctests/sources/hdr_sources/sparks/SPARKS_ACES_%05d.exr.yml b/enctests/sources/hdr_sources/sparks/SPARKS_ACES_%05d.exr.yml new file mode 100644 index 0000000..d9351cc --- /dev/null +++ b/enctests/sources/hdr_sources/sparks/SPARKS_ACES_%05d.exr.yml @@ -0,0 +1,8 @@ +images: true +path: SPARKS_ACES_%05d.exr +width: 3840 +height: 2160 +pix_fmt: rgb48be +in: 6700 +duration: 200 +rate: 25.0 diff --git a/enctests/sources/hdr_sources/sparks2/SPARKS_ACES_%05d.exr.yml b/enctests/sources/hdr_sources/sparks2/SPARKS_ACES_%05d.exr.yml new file mode 100644 index 0000000..d9351cc --- /dev/null +++ b/enctests/sources/hdr_sources/sparks2/SPARKS_ACES_%05d.exr.yml @@ -0,0 +1,8 @@ +images: true +path: SPARKS_ACES_%05d.exr +width: 3840 +height: 2160 +pix_fmt: rgb48be +in: 6700 +duration: 200 +rate: 25.0 diff --git a/enctests/test_wedge_configs/dwab_compress1_tests.yml b/enctests/test_wedge_configs/dwab_compress1_tests.yml new file mode 100644 index 0000000..e10caab --- /dev/null +++ b/enctests/test_wedge_configs/dwab_compress1_tests.yml @@ -0,0 +1,131 @@ +test_dwab1: + name: test_dwab1 + description: variations of openexr dwab compress for still frame + app: ffmpeg + suffix: .mp4 + encoding_template: 'bin/generic_encodewrap.csh todwab.csh --extension png {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_02500.tif + #- sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + #- sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + - sources/hdr_sources/sparks/SPARKS_ACES_06100.exr + - sources/hdr_sources/sparks2/SPARKS_ACES_06700.exr + - sources/hdr_sources/dry_orchard_meadow_4k.exr + wedges: + dwab001: &base_args + --compression: dwab:001 + --caption: 1 + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + dwab020: + << : *base_args + --compression: dwab:020 + --caption: 20 + + dwab040: + << : *base_args + --compression: dwab:040 + --caption: 40 + + dwab060: + << : *base_args + --compression: dwab:060 + --caption: 60 + + dwab075: + << : *base_args + --compression: dwab:080 + --caption: 80 + + dwab100: + << : *base_args + --compression: dwab:100 + --caption: 100 + + dwab150: + << : *base_args + --compression: dwab:150 + --caption: 150 + + dwab250: + << : *base_args + --compression: dwab:250 + --caption: 250 + + dwab300: + << : *base_args + --compression: dwab:300 + --caption: 300 + + dwab350: + << : *base_args + --compression: dwab:350 + --caption: 350 + + dwab400: + << : *base_args + --compression: dwab:400 + --caption: 400 + + dwab450: + << : *base_args + --compression: dwab:450 + --caption: 450 + + dwab600: + << : *base_args + --compression: dwab:600 + --caption: 600 + + +--- + +reports: + graphs: + - args: + color: media + height: 400 + x: --caption + y: psnr_y_harmonic_mean + markers: True + name: psnr_y_harmonic_mean.png + sortby: --caption + - args: + color: media + height: 400 + x: --caption + y: vmaf_harmonic_mean + markers: True + name: vmaf_harmonic_mean.png + sortby: --caption + - args: + color: media + height: 400 + x: --caption + y: encode_time + range_y: [0, 500] + markers: True + name: encode_time_zoom.png + sortby: --caption + - args: + color: media + height: 400 + x: --caption + y: encode_time + markers: True + name: encode_time.png + sortby: --caption + - args: + color: media + height: 400 + x: --caption + y: filesize + markers: True + name: filesize.png + sortby: --caption + name: dwab-compression-test + title: Comparing dwab compression settings. + description: Comparing different dwab values. + directory: dwab-encode + templatefile: basicmovie.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k4k_options_tests.yml b/enctests/test_wedge_configs/htj2k4k_options_tests.yml new file mode 100644 index 0000000..8cdc9d5 --- /dev/null +++ b/enctests/test_wedge_configs/htj2k4k_options_tests.yml @@ -0,0 +1,100 @@ +test_options4k: + name: test_options4k + description: variations of HTJ2k options + app: ffmpeg + suffix: .mp4 + encoding_template: 'bin/htj2k_encodewrap.csh ojph_compress {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_coaster/chimera_coaster.%06d.tif.yml + #- sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + #- sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + #- sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + #- sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + block64: &base_args + -qstep: 0.025 + -num_decomps: 5 + -block_size: '{64,64}' + -precincts: '{128,128},{256,256}' + -prog_order: CPRL + -colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + block32: + << : *base_args + -block_size: '{32,32}' + + precincts128: + << : *base_args + -precincts: '{128,128}' + + precincts256: + << : *base_args + -precincts: '{256,256}' + + num_decomps3: + << : *base_args + -num_decomps: 3 + + num_decomps3b32: + << : *base_args + -jph:num_decomps: 3 + -jph:block_size: '32,32' + + prog_orderLRCP: + << : *base_args + -prog_order: LRCP + + bit_depth10: + << : *base_args + -bit_depth: 10 + + bit_depth12: + << : *base_args + -bit_depth: 12 + +--- + +reports: + graphs: + - args: + color: wedge + height: 400 + barmode: group + x: media + y: psnr_y_harmonic_mean + name: psnr_y_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + barmode: group + x: media + y: vmaf_harmonic_mean + range_y: + - 90 + - 100 + name: vmaf_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: encode_time + name: encode_time.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: filesize + name: filesize.png + type: bar + name: htj2k-options-test + title: Comparing htj2k compression options + description: Comparing different htj2k compression options + directory: htj2k4k-options-encode + templatefile: basicmovie.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_options_tests.yml b/enctests/test_wedge_configs/htj2k_options_tests.yml new file mode 100644 index 0000000..3bee14d --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_options_tests.yml @@ -0,0 +1,99 @@ +test_options: + name: test_options + description: variations of HTJ2k options + app: ffmpeg + suffix: .mp4 + encoding_template: 'bin/htj2k_encodewrap.csh ojph_compress {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + - sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + - sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + #- sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + block64: &base_args + -qstep: 0.025 + -num_decomps: 5 + -block_size: '{64,64}' + -precincts: '{128,128},{256,256}' + -prog_order: CPRL + -colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + block32: + << : *base_args + -block_size: '{32,32}' + + precincts128: + << : *base_args + -precincts: '{128,128}' + + precincts256: + << : *base_args + -precincts: '{256,256}' + + num_decomps3: + << : *base_args + -num_decomps: 3 + + num_decomps3b32: + << : *base_args + -num_decomps: 3 + -block_size: '{32,32}' + + prog_orderLRCP: + << : *base_args + -prog_order: LRCP + + bit_depth10: + << : *base_args + -bit_depth: 10 + + bit_depth12: + << : *base_args + -bit_depth: 12 + +--- + +reports: + graphs: + - args: + color: wedge + height: 400 + barmode: group + x: media + y: psnr_y_harmonic_mean + name: psnr_y_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + barmode: group + x: media + y: vmaf_harmonic_mean + range_y: + - 90 + - 100 + name: vmaf_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: encode_time + name: encode_time.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: filesize + name: filesize.png + type: bar + name: htj2k-options-test + title: Comparing htj2k compression options + description: Comparing different htj2k compression options + directory: htj2k-options-encode + templatefile: basicmovie.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_qstep1_tests.yml b/enctests/test_wedge_configs/htj2k_qstep1_tests.yml new file mode 100644 index 0000000..ea8a6a3 --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_qstep1_tests.yml @@ -0,0 +1,104 @@ +test_qstep1: + name: test_qstep1 + description: variations of HTJ2k qstep + app: ffmpeg + comparisontest: + - testtype: vmaf3 + description: Slightly different conversion to keep it as 16-bit RGB. + ext: -png.mp4 + ffmpegflags: ' -c:v png' + suffix: .mp4 + encoding_template: 'bin/htj2k_encodewrap.csh /Users/sam/git/OpenJphExr/build/src/apps/ojph_compress/ojph_compress {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_02500.tif + #- sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + #- sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + - sources/hdr_sources/sparks_cct_tif/SPARKS_ACES_CCT.06100.tif + - sources/hdr_sources/sparks2_cct_tif/SPARKS_ACES_CCT.06700.tif + - sources/hdr_sources/dry_orchard_meadow_4k_cct.tif + wedges: + qstep0.001: &base_args + -qstep: 0.001 + -num_decomps: 5 + -block_size: '{64,64}' + -precincts: '{128,128},{256,256}' + -prog_order: CPRL + -colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + qstep0.025: + << : *base_args + -qstep: 0.025 + + qstep0.050: + << : *base_args + -qstep: 0.05 + + qstep0.005: + << : *base_args + -qstep: 0.005 + + qstep0.010: + << : *base_args + -qstep: 0.01 + + qstep0.015: + << : *base_args + -qstep: 0.015 + + qstep0.035: + << : *base_args + -qstep: 0.035 + + +--- + +reports: + graphs: + - args: + color: media + height: 400 + x: -qstep + y: psnr_y_harmonic_mean + markers: True + name: psnr_y_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: vmaf_harmonic_mean + markers: True + name: vmaf_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + range_y: [0, 500] + markers: True + name: encode_time_zoom.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + markers: True + name: encode_time.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: filesize + markers: True + name: filesize.png + sortby: -qstep + name: htj2k-qstep-test + title: Comparing qstep values for ojph for 10-bit input files. + description: Comparing different qstep values. + directory: htj2k-qstep-10bit-encode + templatefile: basicmovie2.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_qstep_tests.yml b/enctests/test_wedge_configs/htj2k_qstep_tests.yml new file mode 100644 index 0000000..a47b957 --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_qstep_tests.yml @@ -0,0 +1,102 @@ +test_qstep10: + name: test_qstep10 + description: variations of HTJ2k qstep + app: ffmpeg + comparisontest: + - testtype: vmaf3 + description: Slightly different conversion to keep it as 16-bit RGB. + ext: -png.mp4 + ffmpegflags: ' -c:v png' + suffix: .mp4 + encoding_template: 'bin/htj2k_encodewrap.csh ojph_compress {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + - sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + - sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + # - sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + qstep0.001: &base_args + -qstep: 0.001 + -num_decomps: 5 + -block_size: '{64,64}' + -precincts: '{128,128},{256,256}' + -prog_order: CPRL + -colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + qstep0.025: + << : *base_args + -qstep: 0.025 + + qstep0.003: + << : *base_args + -qstep: 0.003 + + qstep0.005: + << : *base_args + -qstep: 0.005 + + qstep0.01: + << : *base_args + -qstep: 0.01 + + qstep0.015: + << : *base_args + -qstep: 0.015 + + qstep0.035: + << : *base_args + -qstep: 0.035 + + +--- + +reports: + graphs: + - args: + color: media + height: 400 + x: -qstep + y: psnr_y_harmonic_mean + markers: True + name: psnr_y_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: vmaf_harmonic_mean + markers: True + name: vmaf_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + range_y: [0, 500] + markers: True + name: encode_time_zoom.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + markers: True + name: encode_time.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: filesize + markers: True + name: filesize.png + sortby: -qstep + name: htj2k-qstep-test + title: Comparing qstep values for ojph for 10-bit input files. + description: Comparing different qstep values. + directory: htj2k-qstep-10bit-encode + templatefile: basicmovie2.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_qstepfloat1_tests.yml b/enctests/test_wedge_configs/htj2k_qstepfloat1_tests.yml new file mode 100644 index 0000000..35821de --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_qstepfloat1_tests.yml @@ -0,0 +1,114 @@ +test_float1: + name: test_float1 + description: variations of HTJ2k qstep with floating point. + app: ffmpeg + comparisontest: + - testtype: vmaf3 + description: Slightly different conversion to keep it as 16-bit RGB. + ext: -png.mp4 + suffix: .mp4 + encoding_template: 'bin/generic_encodewrap.csh tofloatj2k.csh --extension png {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars/Chimera_DCI4k2398p_HDR_P3PQ_02500.tif + - sources/enc_sources/chimera_coaster_ACEScg_exr/chimera_coaster_ACEScg_exr.044200.exr + #- sources/enc_sources/chimera_coaster_ACEScg_exr/chimera_coaster_ACEScg_exr.%06d.exr.yml + #- sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + - sources/hdr_sources/sparks/SPARKS_ACES_06100.exr + - sources/hdr_sources/sparks2/SPARKS_ACES_06700.exr + - sources/hdr_sources/dry_orchard_meadow_4k_half.exr + wedges: + qstep0.00001: &base_args + -qstep: 0.00001 + #-num_decomps: 5 + #-block_size: '{64,64}' + #-precincts: '{128,128},{256,256}' + #-prog_order: CPRL + #-colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + qstep0.00005: + << : *base_args + -qstep: 0.00005 + + qstep0.00010: + << : *base_args + -qstep: 0.0001 + + qstep0.00025: + << : *base_args + -qstep: 0.00025 + + qstep0.00050: + << : *base_args + -qstep: 0.0005 + + + qstep0.00075: + << : *base_args + -qstep: 0.00075 + + qstep0.00100: + << : *base_args + -qstep: 0.001 + + qstep0.00125: + << : *base_args + -qstep: 0.00125 + + qstep0.00150: + << : *base_args + -qstep: 0.0015 + + + +--- + +reports: + graphs: + - args: + color: media + height: 400 + x: -qstep + y: psnr_y_harmonic_mean + markers: True + name: psnr_y_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: vmaf_harmonic_mean + markers: True + name: vmaf_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + range_y: [0, 500] + markers: True + name: encode_time_zoom.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + markers: True + name: encode_time.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: filesize + markers: True + name: filesize.png + sortby: -qstep + name: htj2k-qstep-test + title: Comparing qstep values for ojph for exr input files. + description: Comparing different qstep values for floating point input files. + directory: htj2k-qstep-10bit-encode + templatefile: basicmovie.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_qstepfloat_tests.yml b/enctests/test_wedge_configs/htj2k_qstepfloat_tests.yml new file mode 100644 index 0000000..d0f52a1 --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_qstepfloat_tests.yml @@ -0,0 +1,98 @@ +test_float: + name: test_float + description: variations of HTJ2k qstep with floating point. + app: ffmpeg + suffix: .mp4 + encoding_template: 'bin/generic_encodewrap.csh tofloatj2k.csh --extension png {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + #- sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + #- sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + #- sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + qstep0.00001: &base_args + -qstep: 0.00001 + #-num_decomps: 5 + #-block_size: '{64,64}' + #-precincts: '{128,128},{256,256}' + #-prog_order: CPRL + #-colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + + qstep0.00005: + << : *base_args + -qstep: 0.00005 + + qstep0.0001: + << : *base_args + -qstep: 0.0001 + + qstep0.00025: + << : *base_args + -qstep: 0.00025 + + qstep0.0005: + << : *base_args + -qstep: 0.0005 + + qstep0.001: + << : *base_args + -qstep: 0.001 + + qstep0.0015: + << : *base_args + -qstep: 0.0015 + + + +--- + +reports: + graphs: + - args: + color: media + height: 400 + x: -qstep + y: psnr_y_harmonic_mean + markers: True + name: psnr_y_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: vmaf_harmonic_mean + markers: True + name: vmaf_harmonic_mean.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + range_y: [0, 500] + markers: True + name: encode_time_zoom.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: encode_time + markers: True + name: encode_time.png + sortby: -qstep + - args: + color: media + height: 400 + x: -qstep + y: filesize + markers: True + name: filesize.png + sortby: -qstep + name: htj2k-qstep-test + title: Comparing qstep values for ojph for 10-bit input files. + description: Comparing different qstep values. + directory: htj2k-qstep-10bit-encode + templatefile: basicmovie.html.jinja + diff --git a/enctests/test_wedge_configs/htj2k_uncompressed_tests.yml b/enctests/test_wedge_configs/htj2k_uncompressed_tests.yml new file mode 100644 index 0000000..209ac35 --- /dev/null +++ b/enctests/test_wedge_configs/htj2k_uncompressed_tests.yml @@ -0,0 +1,107 @@ +test_htj2k: + name: test_htj2k + description: variations of HTJ2k qstep + app: ffmpeg + comparisontest: + - testtype: vmaf3 + description: Slightly different conversion to keep it as 16-bit RGB. + ext: -png.mp4 + suffix: .mov + encoding_template: 'bin/htj2k_encodewrap.csh ojph_compress {input_args} -i "{source}" {encoding_args} "{outfile}"' + sources: + - sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + - sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + - sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + #- sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + htj2k-uncompressed: &base_args + --reversible: 'true' + -num_decomps: 5 + -block_size: '{64,64}' + -precincts: '{128,128},{256,256}' + -prog_order: CPRL + -colour_trans: 'true' + # Note we are assuming -color_range 1 -colorspace 1 -color_primaries 1 -color_trc 2 + +test_mov: + name: test_htj2k + description: variations of HTJ2k qstep + app: ffmpeg + comparisontest: + - testtype: vmaf3 + description: Slightly different conversion to keep it as 16-bit RGB. + ext: -png.mp4 + suffix: .mov + encoding_template: 'ffmpeg {input_args} -i "{source}" -vframes {duration} {encoding_args} -y "{outfile}"' + sources: + - sources/enc_sources/chimera_cars_srgb_dpx_16/chimera_cars_srgb.%05d.dpx.yml + - sources/enc_sources/chimera_coaster_srgb_dpx_16/chimera_coaster_srgb.%06d.dpx.yml + - sources/enc_sources/chimera_fountains_srgb_dpx_16/chimera_fountains_srgb.%05d.dpx.yml + #- sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml + wedges: + libx265-12bit-ultrafast: + -c:v: libx265 + -pix_fmt: gbrp12le + -preset: ultrafast + -x265-params: lossless=1 + -color_range: 1 + -colorspace: rgb + -color_primaries: 1 + -color_trc: 2 + + libx265-12bit-ultrafast: + -c:v: libx265 + -pix_fmt: gbrp12le + -preset: ultrafast + -x265-params: lossless=1 + -color_range: 1 + -colorspace: rgb + -color_primaries: 1 + -color_trc: 2 + + +--- + +reports: + graphs: + - args: + color: wedge + height: 400 + barmode: group + x: media + y: psnr_y_harmonic_mean + name: psnr_y_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + barmode: group + x: media + y: vmaf_harmonic_mean + range_y: + - 90 + - 100 + name: vmaf_harmonic_mean.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: encode_time + name: encode_time.png + type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: filesize + name: filesize.png + type: bar + name: htj2k-qstep-test + title: Comparing qstep values for ojph + description: Comparing different qstep values. + directory: htj2k-qstep-encode + templatefile: basicmovie2.html.jinja + diff --git a/enctests/test_wedge_configs/osx_prores_tests.yml b/enctests/test_wedge_configs/osx_prores_tests.yml index 49c1193..b67f36c 100644 --- a/enctests/test_wedge_configs/osx_prores_tests.yml +++ b/enctests/test_wedge_configs/osx_prores_tests.yml @@ -2,6 +2,8 @@ test_osx-prores: name: test_prores_quality description: variations of prores_quality app: ffmpeg + comparisontest: + - testtype: vmaf3 suffix: .mov encoding_template: '{ffmpeg_bin} {input_args} -i "{source}" -vframes {duration} {encoding_args} -y "{outfile}"' sources: @@ -10,73 +12,67 @@ test_osx-prores: - sources/enc_sources/chimera_fountains_srgb/chimera_fountains_srgb.%05d.png.yml - sources/hdr_sources/sparks_srgb/sparks_srgb.%05d.png.yml wedges: - prores_ks: &base_args + prores_ks_4444: &base_args -c:v: prores_ks - -profile:v: 3 + -profile:v: 4444 # -qscale:v: 16 - -pix_fmt: yuv422p10le + -pix_fmt: yuv444p10le -color_range: tv -colorspace: bt709 -color_primaries: bt709 # -color_trc: iec61966-2-1 - prores_videotoolbox: + prores_ks_xq: << : *base_args - -c:v: prores_videotoolbox - -pix_fmt: p210le - - h264_videotoolbox_q50: - << : *base_args - -c:v: h264_videotoolbox - -pix_fmt: yuv420p - -profile: high - -q:v: 50 + -c:v: prores_ks + -profile: 4444xq - h264_videotoolbox_q100: + prores_ks_hq: << : *base_args - -c:v: h264_videotoolbox - -pix_fmt: yuv420p - -profile: high - -q:v: 100 + -c:v: prores_ks + -profile: hq - h264_videotoolbox_q90: + prores_videotoolbox_p210_hq: << : *base_args - -c:v: h264_videotoolbox - -pix_fmt: yuv420p - -profile: high - -q:v: 90 + -c:v: prores_videotoolbox + -profile: hq + -pix_fmt: p210le - hevc_videotoolbox_q90: + prores_videotoolbox_yuv444p10le: << : *base_args - -c:v: hevc_videotoolbox - -pix_fmt: yuv420p - -profile:v: main - -q:v: 90 + -c:v: prores_videotoolbox + -profile:v: 4444 + -pix_fmt: yuv444p10le - hevc_videotoolbox_q80: + prores_videotoolbox_yuv444p10le: << : *base_args - -c:v: hevc_videotoolbox - -pix_fmt: yuv420p - -profile:v: main - -q:v: 80 + -c:v: prores_videotoolbox + -profile:v: xq + -pix_fmt: yuv444p10le - hevc_videotoolbox_q70: + prores_videotoolbox_yuv444p12le: << : *base_args - -c:v: hevc_videotoolbox - -pix_fmt: yuv420p - -profile:v: main - -q:v: 70 + -c:v: prores_videotoolbox + -profile:v: xq + -pix_fmt: yuv444p12le - hevc_videotoolbox_q60: + prores_videotoolbox_bgra: << : *base_args - -c:v: hevc_videotoolbox - -pix_fmt: yuv420p - -profile:v: main - -q:v: 60 + -c:v: prores_videotoolbox + -profile:v: xq + -pix_fmt: bgra --- reports: graphs: + - args: + color: wedge + height: 400 + barmode: group + x: media + y: psnr_y_harmonic_mean + name: psnr_y_harmonic_mean.png + type: bar - args: color: wedge height: 400 @@ -104,9 +100,17 @@ reports: y: filesize name: filesize.png type: bar + - args: + color: wedge + height: 400 + x: media + barmode: group + y: psnr_y_harmonic_mean + name: psnr_y_harmonic_mean.png + type: bar name: osx-videotoolbox title: OSX Videotoolbox Comparison description: This is a comparison of different videotoolbox encodes. directory: osx-prores-encode - templatefile: basic.html.jinja + templatefile: basic2.html.jinja diff --git a/enctests/testframework/encoders/__init__.py b/enctests/testframework/encoders/__init__.py index 73deb76..87d6291 100644 --- a/enctests/testframework/encoders/__init__.py +++ b/enctests/testframework/encoders/__init__.py @@ -16,6 +16,10 @@ def destination_from_config( destination: pathlib.Path, results_folder: pathlib.Path ): + """ + :param destination: The OTIO file + :param results_folder: The top level results folder (e.g. results or wedge_results) + """ encoder_cls = encoder_map.get(test_config.get('app')) encoder = encoder_cls(None, test_config, destination) diff --git a/enctests/testframework/encoders/ffmpeg_encoder.py b/enctests/testframework/encoders/ffmpeg_encoder.py index 2a10ff5..051e834 100644 --- a/enctests/testframework/encoders/ffmpeg_encoder.py +++ b/enctests/testframework/encoders/ffmpeg_encoder.py @@ -41,7 +41,7 @@ def __init__( destination ) - def run_wedges(self) -> dict: + def run_wedges(self, verbose: bool) -> dict: # The results dictionary is passed to source clip's list of available # media references. Key is test name and value is media reference results = {} @@ -72,6 +72,8 @@ def run_wedges(self) -> dict: # Do the encoding log_file = Path(out_file.parent, out_file.stem + ".log") with open(log_file, "w") as log_file_object: + if verbose: + print(f'ffmpeg command: {cmd}') print(f'ffmpeg command: {cmd}', file=log_file_object) log_file_object.flush() process = subprocess.Popen( diff --git a/enctests/testframework/main.py b/enctests/testframework/main.py index e5bf46e..d88583c 100644 --- a/enctests/testframework/main.py +++ b/enctests/testframework/main.py @@ -95,6 +95,14 @@ def parse_args(): 'in encoding tests' ) + + parser.add_argument( + '--verbose', + action='store_true', + default=False, + help='Be verbose in the progress.' + ) + parser.add_argument( '--force', action='store_true', @@ -268,8 +276,8 @@ def vmaf_compare(source_clip, test_ref, testname, comparisontestinfo, source_pat [distorted][reference]\ libvmaf=log_fmt=json:\ log_path=compare_log.json:\ -feature="name=psnr":\ -model=path={vmaf_model}\" \ +feature="name=psnr":feature="name=cambi":feature="name=vif":\ +model=path={vmaf_model}:n_threads=4\" \ -f null -\ ' @@ -343,6 +351,137 @@ def vmaf_compare(source_clip, test_ref, testname, comparisontestinfo, source_pat enc_meta = get_test_metadata_dict(test_ref) enc_meta['results'].update(results) +def vmaf3_compare(source_clip, test_ref, testname, comparisontestinfo, source_path, distorted, log_file_object): + """ + Compare the sourceclip to the test_ref using vmaf. This is slightly different to the vmaf_compare + since we convert the source clip into a y4m format. + source_clip -- The original clip that the new media is based on. + test_ref -- The generated movie we are testing. + testname -- The testname we are running. + comparisontestinfo -- other parameters that can be used to configure the test. + + This creates a results dictionary with the following parameters: + vmaf - the vmaf comparison metric. + psnr - the PSNR dictionary. + psnr_y - The PSNR lumanance value. + psnr_cb, psnr_cr - the PNSR chrominance. + result - Was the test able to run (Completed = Yes) + success - Boolean, was the test a success. + """ + + vmaf_cmd = '\ +{ffmpeg_bin} \ +{reference} \ +-i "{distorted}" \ +-vframes {duration} \ +-lavfi \ +\"[0:v]setpts=PTS-STARTPTS[reference]; \ +[1:v]setpts=PTS-STARTPTS[distorted]; \ +[distorted][reference]\ +libvmaf=log_fmt=json:\ +log_path={compare_log}:\ +feature="name=psnr|name=cambi|name=vif|name=float_ms_ssim":\ +model=path={vmaf_model}:n_threads=6\" \ +-f null -\ +' + + if not distorted.exists(): + results = {'success': False, + 'testresult': "No movie generated." + } + + enc_meta = get_test_metadata_dict(test_ref) + enc_meta['results'].update(results) + return + + # Get settings from metadata used as basis for encoded media + source_meta = get_source_metadata_dict(source_clip) + input_args = '' + framerange='' + if source_meta.get('images'): + input_args = f"-start_number {source_meta.get('in')}" + endframe = int(source_meta.get('in'))+int(source_meta.get('duration')) - 1 + framerange = f".[{source_meta.get('in')}-{endframe}]" + + reference = f'{input_args} -i "{source_path}" ' + + env = os.environ + + y4m_source = f"{source_path}{framerange}{comparisontestinfo.get('ext', '.y4m')}" + y4m_convert_cmd = '{ffmpeg_bin} {reference} {ffmpegflags} -y {y4m_source}' + y4mcmd = y4m_convert_cmd.format( + ffmpeg_bin=FFMPEG_BIN, + reference=reference.replace("\\", "/"), + ffmpegflags=comparisontestinfo.get("ffmpegflags", "-strict -1 -pix_fmt yuv444p16 "), + y4m_source=y4m_source.replace("\\", "/")) + if not os.path.exists(y4m_source) or os.path.getsize(y4m_source) == 0: + print(f'Creating y4m file command: {y4mcmd}', file=log_file_object) + log_file_object.flush() # Need to flush it to make sure its before the subprocess logging. + process = subprocess.Popen( + shlex.split(y4mcmd), + stdout=log_file_object, + stderr=log_file_object, + universal_newlines=True, + env=env + ) + process.wait() + + # Assuming all encoded files are video files for now + reference = f"-i {y4m_source}" + compare_log = Path(f"{distorted.as_posix()}-compare_log.json") + + cmd = vmaf_cmd.format( + ffmpeg_bin=FFMPEG_BIN, + reference=reference.replace("\\", "/"), + compare_log=str(compare_log), + distorted=distorted.as_posix(), + duration=source_meta.get('duration'), + vmaf_model=get_nearest_model(int(source_meta.get('width', 1920))) + ) + print(f'VMAF command: {cmd}', file=log_file_object) + log_file_object.flush() # Need to flush it to make sure its before the subprocess logging. + + if compare_log.exists(): + # Make sure we remove the old one, so that we know one is generated. + compare_log.unlink() + process = subprocess.Popen( + shlex.split(cmd), + stdout=log_file_object, + stderr=log_file_object, + universal_newlines=True, + env=env + ) + process.wait() + if not compare_log.exists(): + results = {'result': 'Failed to run'} + enc_meta = get_test_metadata_dict(test_ref) + enc_meta['results'].update(results) + print(f"\tFailed to generate {compare_log.name}") + print(f"Failed to generate {compare_log.name}", file=log_file_object) + return + + with compare_log.open(mode='rb') as f: + raw_results = json.load(f) + + results = { + 'vmaf': raw_results['pooled_metrics'].get('vmaf'), + 'cambi': raw_results['pooled_metrics'].get('cambi'), + 'float_ms_ssim': raw_results['pooled_metrics'].get('float_ms_ssim'), + 'psnr': raw_results['pooled_metrics'].get('psnr'), # FFmpeg < 5.1 + 'testresult': "Completed" + } + + # TODO Do this as a pretty print. + print(f"--- VMAF output\n {raw_results['pooled_metrics']}", file=log_file_object) + + # FFmpeg >= 5.1 have split psnr results + if not results['psnr']: + for key in ['psnr_y', 'psnr_cb', 'psnr_cr']: + results[key] = raw_results['pooled_metrics'].get(key) + + enc_meta = get_test_metadata_dict(test_ref) + enc_meta['results'].update(results) + def identity_compare(source_dict, source_clip, test_ref, testname, comparisontestinfo, source_path, distorted, log_file_object): """ Compare the sourceclip to the test_ref using ffmpeg identity. @@ -654,7 +793,7 @@ def run_tests(args, config_data, timeline): ) # Run tests and get a dict of resulting media references - results = encoder.run_wedges() + results = encoder.run_wedges(args.verbose) comparisontests = test_config.get("comparisontest", [{'testtype': 'vmaf'}]) @@ -688,6 +827,8 @@ def run_tests(args, config_data, timeline): identity_compare(identity_distort_clips, source_clip, test_ref, test_name, test, source_path, distorted, log_file_object) elif testtype == "vmaf": vmaf_compare(source_clip, test_ref, test_name, test, source_path, distorted, log_file_object) + elif testtype == "vmaf3": + vmaf3_compare(source_clip, test_ref, test_name, test, source_path, distorted, log_file_object) elif testtype == "idiff": idiff_compare(source_clip, test_ref, test_name, test, source_path, distorted, log_file_object) elif testtype == "assertresults": diff --git a/enctests/testframework/otio2html.py b/enctests/testframework/otio2html.py index 2b5a730..ec70f2b 100644 --- a/enctests/testframework/otio2html.py +++ b/enctests/testframework/otio2html.py @@ -82,6 +82,7 @@ def otio2htmlmain(): continue print("Outputfile:", output_file) + print("destination_folder:", destination_folder) timeline = otio.adapters.read_from_file(str(output_file)) # Create an encoder instance, since this will configure the destination folder. diff --git a/enctests/testframework/templates/basic.html.jinja b/enctests/testframework/templates/basic.html.jinja index 160b487..daccbad 100644 --- a/enctests/testframework/templates/basic.html.jinja +++ b/enctests/testframework/templates/basic.html.jinja @@ -51,7 +51,7 @@ {{ enc_info.name|e }} Test {{ enc_info.testresult}} {{ enc_info.encode_time}} - {{ enc_info.filesize}} + {{ "{:,}".format(enc_info.filesize) }} {{enc_info.vmaf_harmonic_mean |e }} {{enc_info.vmaf_mean |e }} {{enc_info.vmaf_min |e }} diff --git a/enctests/testframework/templates/basic2.html.jinja b/enctests/testframework/templates/basic2.html.jinja new file mode 100644 index 0000000..2572989 --- /dev/null +++ b/enctests/testframework/templates/basic2.html.jinja @@ -0,0 +1,66 @@ + + + + + {{config.title}} + + + + + + +

{{config.title}}

+

{{config.description}}

+ + + +
+ + Testing with ffmpeg version: {{ testinfo.ffmpeg_version}} + + {% for media_name, test in tests.items() %} + +

{{ media_name }} Results

+ + + + + + + + + + + + + +
File Path{{test.source_info.path}}
Resolution{{test.source_info.width}} x {{test.source_info.height}}
Source Frame Rate{{test.source_info.rate}}
Frame Range{{test.source_info.in}} - {{test.source_info.in + test.source_info.duration}}
+ + + + + + + + + + + + + {% for enc_info in test.results %} + + + + + + + + + + + + {% endfor %} +
Test NameTestEncode DurationFile SizeVMAF Harmonic MeanPSNR-Y Harmonic MeanCAMBI Harmonic MeanMS-SSIM Harmonic MeanEncode Arguments
{{ enc_info.name|e }}Test {{ enc_info.testresult}} {{ enc_info.encode_time}}{{ "{:,}".format(enc_info.filesize) }}{{enc_info.vmaf_harmonic_mean |e }}{{enc_info.psnr_y_harmonic_mean |e }}{{enc_info.cambi_harmonic_mean |e }}{{enc_info.float_ms_ssim_harmonic_mean |e }}{{enc_info.encode_arguments | e}}
+ {% endfor %} + + \ No newline at end of file diff --git a/enctests/testframework/templates/basicmovie2.html.jinja b/enctests/testframework/templates/basicmovie2.html.jinja new file mode 100644 index 0000000..3f4814c --- /dev/null +++ b/enctests/testframework/templates/basicmovie2.html.jinja @@ -0,0 +1,102 @@ + + + + + {{config.title}} + + + + + + +

{{config.title}}

+

{{config.description}}

+ + + +
+ + Testing with ffmpeg version: {{ testinfo.ffmpeg_version}} + + {% for media_name, test in tests.items() %} + +

{{ media_name }} Results

+ + + + + + + + + + + + + +
File Path{{test.source_info.path}}
Resolution{{test.source_info.width}} x {{test.source_info.height}}
Source Frame Rate{{test.source_info.rate}}
Frame Range{{test.source_info.in}} - {{test.source_info.in + test.source_info.duration}}
+ + + + + + + + + + + + + + {% for enc_info in test.results %} + + + + + + + + + + + + + {% endfor %} +
Test NameTestEncode DurationFile SizeVMAF Harmonic MeanVMAF MinPSNR-Y Harmonic MeanCAMBI Harmonic MeanMS-SSIM Harmonic MeanEncode Arguments
{{ enc_info.name|e }}Result - {{ enc_info.testresult }} {{ enc_info.encode_time}}{{ "{:,}".format(enc_info.filesize) }}{{enc_info.vmaf_harmonic_mean |e }}{{enc_info.vmaf_min |e }}{{enc_info.psnr_y_harmonic_mean |e }}{{enc_info.cambi_harmonic_mean |e }}{{enc_info.float_ms_ssim_harmonic_mean |e }}{{enc_info.encode_arguments | e}}
+ {% endfor %} + +

Pictures

+ {% for media_name, test in tests.items() %} +

{{ media_name }} Results

+ + + + + + + + + + + + + +
File Path{{test.source_info.path}}
Resolution{{test.source_info.width}} x {{test.source_info.height}}
Source Frame Rate{{test.source_info.rate}}
Frame Range{{test.source_info.in}} - {{test.source_info.in + test.source_info.duration}}
+ + + + + + + {% for enc_info in test.results %} + + + + + + {% endfor %} +
Test NamemovieEncode Arguments
{{ enc_info.name|e }}{{enc_info.encode_arguments | e}}
+ {% endfor %} + + + diff --git a/enctests/testframework/utils/outputTemplate.py b/enctests/testframework/utils/outputTemplate.py index b77ccde..6f77dd1 100644 --- a/enctests/testframework/utils/outputTemplate.py +++ b/enctests/testframework/utils/outputTemplate.py @@ -98,12 +98,17 @@ def processTemplate(config, timeline): merge_test_info['vmaf_mean'] = float(merge_test_info['vmaf']['mean']) merge_test_info['vmaf_harmonic_mean'] = float(merge_test_info['vmaf']['harmonic_mean']) merge_test_info['psnr_y_harmonic_mean'] = float(merge_test_info['psnr_y']['harmonic_mean']) + if "cambi" in merge_test_info: + merge_test_info['cambi_harmonic_mean'] = float(merge_test_info['cambi']['harmonic_mean']) + merge_test_info['float_ms_ssim_harmonic_mean'] = float(merge_test_info['float_ms_ssim']['harmonic_mean']) else: merge_test_info['psnr_y'] = {} merge_test_info['psnr_cr'] = {} merge_test_info['psnr_cb'] = {} merge_test_info['vmaf_harmonic_mean'] = -1 merge_test_info['psnr_y_harmonic_mean'] = -1 + merge_test_info['cambi_harmonic_mean'] = -1 + merge_test_info['float_ms_ssim_harmonic_mean'] = -1 merge_test_info['filesize'] = merge_test_info['filesize'] if 'host_config' in test_info.metadata['aswf_enctests']: