Skip to content

Commit ef7c38c

Browse files
committed
Adding evaluate.py and getTranscodeData.py
1 parent ffaf64a commit ef7c38c

File tree

9 files changed

+345
-77
lines changed

9 files changed

+345
-77
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@ optional arguments:
6868
### Dependencies:
6969
* python >= 3.8 at `/usr/local/bin/python3`
7070
* [HandBrakeCLI](https://handbrake.fr/downloads2.php) and [ffmpeg](https://www.ffmpeg.org/download.html) on your `$PATH`
71-
* [numpy](https://pypi.org/project/numpy/)
7271
* [cv2](https://pypi.org/project/opencv-python/)
72+
* [dill](https://pypi.org/project/dill/)
73+
* [imutils](https://pypi.org/project/imutils/)
74+
* [numpy](https://pypi.org/project/numpy/)
75+
* [scikit-image](https://pypi.org/project/scikit-image/)
76+
7377

7478
<br>
7579
<br>

compareEncoding.py

Lines changed: 0 additions & 68 deletions
This file was deleted.

compareTranscode.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/local/bin/python3
2+
3+
import argparse
4+
import cv2
5+
import dill
6+
import imutils
7+
import numpy as np
8+
import os
9+
from skimage.metrics import structural_similarity
10+
import subprocess
11+
import sys
12+
13+
# Verify script is colocated with ./lib/ and import dependencies
14+
sys.path.append(os.path.join(sys.path[0], "lib"))
15+
try:
16+
from common import get_choice_from_menu
17+
except ImportError:
18+
sys.exit("FATAL: failed to import dependencies from ./lib/\n")
19+
20+
# Parse command-line arguments
21+
parser = argparse.ArgumentParser()
22+
source_group = parser.add_mutually_exclusive_group()
23+
source_group.add_argument("--source", help="Source filename to compare that exists in both ./source/ and ./hevc/)")
24+
source_group.add_argument("--all", action="store_true", help="Compare all files which exist in both ./source/ and ./hevc/")
25+
parser.add_argument("--frame", nargs=1, type=int, help="Compare SSIM values for specific frames from previously generated comparisons")
26+
parser.add_argument("--num_frames", nargs="?", default=5, type=int, help="Number of comparison frames to generate")
27+
parser.add_argument("--dill", action="store_true")
28+
args = parser.parse_args()
29+
30+
# Verify we're working from a directory that contains expected subdirectories
31+
if not set(["source", "hevc", "performance"]).issubset(set(os.listdir())):
32+
sys.exit("Invalid working directory, exiting.")
33+
34+
if args.all and args.frame:
35+
sys.exit("Error: --frame must be used with --source, not --all. Exiting.")
36+
elif args.all:
37+
source_files = [filename for filename in os.listdir("source") if os.path.splitext(filename)[1] == ".mp4"]
38+
elif args.source.endswith(".mp4"):
39+
source_files = [args.source]
40+
# if args.frame, fail unless args.source exists
41+
# if args.frame, verify filename exists in "comparison"
42+
else:
43+
sys.exit("Invalid filename, exiting.")
44+
45+
# if comparison directory already exists, exit
46+
47+
print("\nComparison frames:\t{frames}".format(frames=args.num_frames))
48+
49+
for source_file in source_files:
50+
source_file_path = os.path.join("source", source_file)
51+
source_file_size = int(os.path.getsize(source_file_path)/1000000)
52+
source_file_handle = cv2.VideoCapture(source_file_path)
53+
hevc_files = [filename for filename in os.listdir("hevc") if filename.startswith(os.path.splitext(source_file)[0])]
54+
55+
for hevc_file in hevc_files:
56+
evaluate_frames = True
57+
output_directory = os.path.join(os.path.relpath("comparison"), os.path.splitext(os.path.basename(hevc_file))[0])
58+
hevc_file_path = os.path.join("hevc", hevc_file)
59+
hevc_file_handle = cv2.VideoCapture(hevc_file_path)
60+
hevc_file_size = int(os.path.getsize(hevc_file_path)/1000000)
61+
compression_ratio = int(100-(hevc_file_size/source_file_size*100))
62+
total_frames = source_file_handle.get(cv2.CAP_PROP_FRAME_COUNT)
63+
# get other attributes from ./performance/<<>>.log
64+
stride = int(total_frames/(args.num_frames+1))
65+
66+
print("\nFilename:\t\t{filename}".format(filename=hevc_file))
67+
if source_file_handle.get(cv2.CAP_PROP_FRAME_COUNT) != hevc_file_handle.get(cv2.CAP_PROP_FRAME_COUNT):
68+
print("\t\t\t!!! WARNING: Frame counts do not match, screencaps may be time-shifted")
69+
evaluate_frames = False
70+
print("\tSource Size:\t{size} MB".format(size=source_file_size))
71+
print("\tHEVC Size:\t{size} MB".format(size=hevc_file_size))
72+
print("\tReduction:\t{ratio}%\n".format(ratio=compression_ratio))
73+
74+
ssim_total = 0.0
75+
ssim_values = {}
76+
print("\tSSIM:")
77+
if not os.path.exists(output_directory): os.makedirs(output_directory)
78+
#if frame_resolution_differs: # e.g. letterboxing removed -- where does this go?
79+
for frame in range(1, args.num_frames+1):
80+
source_file_handle.set(cv2.CAP_PROP_POS_FRAMES,stride*frame)
81+
hevc_file_handle.set(cv2.CAP_PROP_POS_FRAMES,stride*frame)
82+
ret,source_frame = source_file_handle.read()
83+
ret,hevc_frame = hevc_file_handle.read()
84+
cv2.imwrite(os.path.join(output_directory, "{number}-source.png".format(number=frame)), source_frame, [cv2.IMWRITE_PNG_COMPRESSION, 0])
85+
cv2.imwrite(os.path.join(output_directory, "{number}-x265.png".format(number=frame)), hevc_frame, [cv2.IMWRITE_PNG_COMPRESSION, 0])
86+
if evaluate_frames:
87+
try:
88+
ssim = structural_similarity(cv2.cvtColor(source_frame, cv2.COLOR_BGR2GRAY), cv2.cvtColor(hevc_frame, cv2.COLOR_BGR2GRAY))
89+
except ValueError as error:
90+
print("\tERROR: " +str(error))
91+
evaluate_frames = False
92+
else:
93+
ssim_values[frame] = ssim
94+
ssim_total += ssim
95+
print("\t Frame {frame}:\t{ssim}".format(frame=frame, ssim=ssim))
96+
97+
ssim_average = ssim_total/args.num_frames
98+
print("\tAverage:\t{average}\n".format(average=ssim_average))
99+
with open(os.path.join("performance", hevc_file[:-4] + ".log"), "r") as performance_file:
100+
duration = performance_file.readline().rstrip()
101+
fps = "{:0.2f}".format(float(performance_file.readline().rstrip().split(" ")[0]))
102+
with open(os.path.join(output_directory, "summary.txt"), "w") as summary_file:
103+
summary_file.write("SSIM Avg:\t{average}\nDuration:\t{duration}\nFPS:\t\t{fps}\nCompression:\t{compression}%\n\n".format(average=ssim_average, duration=duration, fps=fps, compression=compression_ratio))
104+
if evaluate_frames:
105+
for iterator in range(1, args.num_frames+1):
106+
summary_file.write("\t{iterator}:\t{ssim}\n".format(iterator=iterator, ssim=ssim_values[iterator]))
107+
108+
hevc_file_handle.release()
109+
110+
source_file_handle.release()
111+
112+
sys.exit("Done.\n")

evaluate.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/local/bin/python3
2+
3+
import argparse
4+
import cv2
5+
import imutils
6+
import os
7+
from skimage.metrics import structural_similarity
8+
import sys
9+
10+
sys.path.append(os.path.join(sys.path[0], "lib"))
11+
try:
12+
from common import get_choice_from_menu
13+
except ImportError:
14+
sys.exit("FATAL: failed to import dependencies from ./lib/\n")
15+
16+
parser = argparse.ArgumentParser()
17+
parser.add_argument("--dir")
18+
parser.add_argument("--frame")
19+
args = parser.parse_args()
20+
21+
if not args.dir:
22+
choices = sorted([file for file in os.listdir("comparison") if os.path.isdir(os.path.join("comparison", file))])
23+
if len(choices) == 0:
24+
sys.exit("\nNo transcode directories to evaluate.\n")
25+
else:
26+
print("\nChoose a transcode to evaluate:")
27+
transcode = choices[get_choice_from_menu(choices)]
28+
else:
29+
if args.dir in os.listdir("comparison"):
30+
transcode = args.dir
31+
else:
32+
sys.exit("Invalid directory.\n")
33+
34+
# TODO: frame arg
35+
36+
screenshots = sorted([file for file in os.listdir(os.path.join("comparison", transcode)) if file.endswith(".png")], key=lambda filename: int(filename.split("-")[0]))
37+
38+
if not (len(screenshots) % 2 == 0):
39+
sys.exit("ERROR: Odd number of screenshots found in {directory}".format(directory=transcode))
40+
else:
41+
num_screenshots = int(len(screenshots)/2)
42+
43+
44+
#TODO: integrate into compareEncoding.py, error out if source/hevc dimenions !=
45+
46+
if os.path.exists(os.path.join("comparison", transcode, "summary.txt")):
47+
sys.exit("\nsummary.txt exists, {transcode} has already been evaluated.\n\nExiting.\n".format(transcode=transcode))
48+
49+
file_info = {"filename": transcode}
50+
with open(os.path.join("performance", transcode + ".log"), "r") as log_file:
51+
file_info["duration"] = log_file.readline().rstrip()
52+
file_info["fps"] = "{:0.2f}".format(float(log_file.readline().rstrip().split(" ")[0]))
53+
file_info["compression"] = log_file.readline().rstrip().split(" ")[0]
54+
for line in log_file:
55+
if "bitrate" in line:
56+
file_info["bitrate"] = int(line.rstrip().split(": ")[2][:-1])
57+
elif "height" in line:
58+
file_info["height"] = line.rstrip().split(": ")[1][:-1]
59+
elif "width" in line:
60+
file_info["width"] = line.rstrip().split(": ")[1][:-2]
61+
elif "encoder_quality" in line:
62+
file_info["encoder_quality"] = line.rstrip().split(": ")[1][:-1]
63+
elif "encoder_preset" in line:
64+
file_info["encoder_preset"] = line.rstrip().split(": ")[1][1:-2]
65+
elif "encoder_options" in line:
66+
file_info["encoder_options"] = line.rstrip().split(": ")[1][1:-2]
67+
68+
print(" Resolution:\t{resolution}".format(resolution=file_info["width"] + "x" + file_info["height"]))
69+
print(" Bitrate:\t{bitrate}".format(bitrate=str(int(file_info["bitrate"] / 1000)) + "kbps"))
70+
print(" Encoder:\t{settings}".format(settings=str("RF" + file_info["encoder_quality"] + " " + file_info["encoder_preset"] + ", " + file_info["encoder_options"])))
71+
print(" Duration:\t{duration}".format(duration=file_info["duration"]))
72+
print(" FPS:\t\t{fps}".format(fps=str(file_info["fps"])))
73+
print(" Compression:\t{ratio}".format(ratio=file_info["compression"]))
74+
75+
print("\n SSIM:")
76+
ssim_total = 0.0
77+
ssim_values = {}
78+
for image_iterator in range(1, num_screenshots+1):
79+
screenshot_pair = sorted([os.path.join("comparison", transcode, screenshot) for screenshot in screenshots if screenshot.split("-")[0] == str(image_iterator)])
80+
ssim = structural_similarity(cv2.cvtColor(cv2.imread(screenshot_pair[0]), cv2.COLOR_BGR2GRAY), cv2.cvtColor(cv2.imread(screenshot_pair[1]), cv2.COLOR_BGR2GRAY))
81+
#(score, diff) = structural_similarity(source_grayscale, hevc_grayscale, full=True)
82+
# What does the full image get me?
83+
ssim_values[image_iterator] = ssim
84+
print(" Frame {image_iterator}:\t{ssim}".format(image_iterator=image_iterator, ssim=ssim))
85+
ssim_total += ssim
86+
87+
ssim_average = ssim_total/num_screenshots
88+
print(" Average:\t{average}\n".format(average=ssim_average))
89+
90+
with open(os.path.join("comparison", transcode, "summary.txt"), "w") as summary_file:
91+
summary_file.write("SSIM Avg:\t{average}\nDuration:\t{duration}\nFPS:\t\t{fps}\nCompression:\t{compression}\n\n".format(average=ssim_average, duration=file_info["duration"], fps=file_info["fps"], compression=file_info["compression"]))
92+
for iterator in range(1, num_screenshots+1):
93+
summary_file.write("\t{iterator}:\t{ssim}\n".format(iterator=iterator, ssim=ssim_values[iterator]))

getTranscodeData.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/local/bin/python3
2+
3+
import argparse
4+
import cv2
5+
from datetime import datetime, timedelta
6+
import dill
7+
import imutils
8+
import numpy as np
9+
import os
10+
from pprint import pprint
11+
from skimage.metrics import structural_similarity
12+
import subprocess
13+
import sys
14+
15+
# Verify script is colocated with ./lib/ and import dependencies
16+
sys.path.append(os.path.join(sys.path[0], "lib"))
17+
try:
18+
from common import get_choice_from_menu
19+
except ImportError:
20+
sys.exit("FATAL: failed to import dependencies from ./lib/\n")
21+
22+
# Verify we're working from a directory that contains expected subdirectories
23+
if not set(["source", "performance"]).issubset(set(os.listdir())):
24+
sys.exit("Invalid working directory, exiting.")
25+
26+
source_files = [filename for filename in os.listdir("source") if os.path.splitext(filename)[1] == ".mp4"]
27+
comparisons = {}
28+
29+
for source_file in source_files:
30+
comparison_directories = [filename for filename in os.listdir("comparison") if filename.startswith(os.path.splitext(source_file)[0])]
31+
movie_name = source_file.split(".")[0]
32+
33+
transcodes = {}
34+
35+
for directory in comparison_directories:
36+
summary_file = os.path.join("comparison", directory, "summary.txt")
37+
transcode_options = directory.split("-")[1].split("_")
38+
transcode_options.pop(0)
39+
40+
if "Baseline" in transcode_options:
41+
quality = "Baseline"
42+
else:
43+
quality = transcode_options[0]
44+
transcode_options.pop(0)
45+
46+
if quality not in transcodes:
47+
transcodes[quality] = {}
48+
49+
with open(summary_file, "r") as file:
50+
data = {"ssim": file.readline().rstrip().split("\t")[1]}
51+
data["duration"] = file.readline().rstrip().split("\t")[1]
52+
data["fps"] = file.readline().rstrip().split("\t")[2]
53+
data["compression"] = file.readline().rstrip().split("\t")[1]
54+
55+
if quality == "Baseline":
56+
transcodes[quality] = data
57+
else:
58+
transcodes[quality]["_".join(transcode_options)] = data
59+
60+
comparisons[movie_name] = transcodes
61+
62+
for movie_name in sorted(comparisons.keys()):
63+
print(movie_name)
64+
for key, value in sorted(comparisons[movie_name].items()):
65+
if key == "Baseline":
66+
print("Baseline", value["ssim"], "-", value["duration"], "-", str(value["compression"]) + "%", "-", value["fps"], sep="\t")
67+
else:
68+
for variant in comparisons[movie_name][key]:
69+
name = key + "_" + variant
70+
ssim_delta = float(value[variant]["ssim"]) - float(comparisons[movie_name]["Baseline"]["ssim"])
71+
#duration_delta = timedelta(seconds = (datetime.strptime(value[variant]["duration"], "%H:%M:%S.%f") - datetime.strptime(comparisons[movie_name]["Baseline"]["duration"], "%H:%M:%S.%f")).total_seconds())
72+
compression_delta = str(int(value[variant]["compression"]) - int(comparisons[movie_name]["Baseline"]["compression"])) + "%"
73+
print(name, value[variant]["ssim"], ssim_delta, value[variant]["duration"], "", str(value[variant]["compression"]) + "%", compression_delta, value[variant]["fps"], sep="\t")
74+
print()
75+
print()
76+
print()

0 commit comments

Comments
 (0)