Skip to content

Commit 152d6e7

Browse files
committed
Create GUI window
1 parent 6c9b704 commit 152d6e7

1 file changed

Lines changed: 233 additions & 1 deletion

File tree

src/HwCodecDetect/run_tests.py

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
import shutil
88
import tempfile
99
import argparse
10+
import threading
11+
import tkinter as tk
1012
from collections import defaultdict
1113
from .install_ffmpeg_if_needed import install_ffmpeg_if_needed
1214
from colorama import init, Fore, Style
1315
from concurrent.futures import ThreadPoolExecutor, as_completed
1416
from tqdm import tqdm
17+
from tkinter import ttk
18+
1519
try:
1620
import psutil
1721
PSUTIL_AVAILABLE = True
@@ -586,6 +590,222 @@ def get_resource_path(relative_path):
586590
return os.path.join(sys._MEIPASS, relative_path)
587591
return os.path.join(os.path.abspath("."), relative_path)
588592

593+
class HwCodecGUI:
594+
def __init__(self, root, args):
595+
self.root = root
596+
self.args = args
597+
self.stop_requested = False
598+
self.running = False
599+
600+
frame = ttk.LabelFrame(root, text="Settings")
601+
frame.pack(fill="x", padx=10, pady=10)
602+
603+
ttk.Label(frame, text="Encoder Multi-process Count (1-8):").grid(row=0, column=0, padx=5, pady=5)
604+
self.encoder_var = tk.StringVar(value=str(args.encoder_count))
605+
ttk.Entry(frame, textvariable=self.encoder_var, width=10).grid(row=0, column=1)
606+
607+
ttk.Label(frame, text="Decoder Multi-process Count (1-8):").grid(row=0, column=2, padx=5, pady=5)
608+
self.decoder_var = tk.StringVar(value=str(args.decoder_count))
609+
ttk.Entry(frame, textvariable=self.decoder_var, width=10).grid(row=0, column=3)
610+
611+
self.start_button = ttk.Button(frame, text="Start Test", command=self.start_or_stop)
612+
self.start_button.grid(row=0, column=4, columnspan=4, pady=10)
613+
614+
self.progress = ttk.Progressbar(root, mode="determinate", maximum=100)
615+
self.progress.pack(fill="x", padx=10, pady=10)
616+
617+
self.notebook = ttk.Notebook(root)
618+
self.tab_dec = ttk.Frame(self.notebook)
619+
self.tab_enc = ttk.Frame(self.notebook)
620+
self.notebook.add(self.tab_dec, text="Decoders")
621+
self.notebook.add(self.tab_enc, text="Encoders")
622+
self.notebook.pack(expand=True, fill="both", padx=10, pady=10, ipady=200)
623+
624+
self.table_dec = self.create_summary_table(self.tab_dec)
625+
self.table_enc = self.create_summary_table(self.tab_enc)
626+
627+
def create_summary_table(self, parent):
628+
container = ttk.Frame(parent, height=500)
629+
container.pack(expand=True, fill="both")
630+
container.pack_propagate(False)
631+
632+
columns = ["Codec"] + list(RESOLUTIONS.keys())
633+
634+
tree = ttk.Treeview(container, columns=columns, show="headings")
635+
for col in columns:
636+
tree.heading(col, text=col)
637+
width = 380 if col == "Codec" else 80
638+
tree.column(col, width=width, anchor="center")
639+
640+
vsb = ttk.Scrollbar(container, orient="vertical", command=tree.yview)
641+
hsb = ttk.Scrollbar(container, orient="horizontal", command=tree.xview)
642+
tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
643+
644+
tree.grid(row=0, column=0, sticky="nsew")
645+
vsb.grid(row=0, column=1, sticky="ns")
646+
hsb.grid(row=1, column=0, sticky="ew")
647+
648+
container.rowconfigure(0, weight=1)
649+
container.columnconfigure(0, weight=1)
650+
651+
return tree
652+
653+
def start_or_stop(self):
654+
if not self.running:
655+
self.start_test()
656+
else:
657+
self.stop_requested = True
658+
self.start_button.config(text="Start Test")
659+
self._clear_tables()
660+
661+
def start_test(self):
662+
enc = self._safe_count(self.encoder_var, default=self.args.encoder_count)
663+
dec = self._safe_count(self.decoder_var, default=self.args.decoder_count)
664+
self.args.encoder_count = enc
665+
self.args.decoder_count = dec
666+
667+
self.stop_requested = False
668+
self.running = True
669+
self.start_button.config(text="Stop")
670+
self.progress["value"] = 0
671+
self._clear_tables()
672+
673+
t = threading.Thread(target=self.run_tests_thread, daemon=True)
674+
t.start()
675+
676+
def _safe_count(self, var, default):
677+
try:
678+
v = int(var.get())
679+
except Exception:
680+
v = default
681+
if v < 1:
682+
v = 1
683+
if v > 8:
684+
v = 8
685+
var.set(str(v))
686+
return v
687+
688+
def run_tests_thread(self):
689+
temp_dir = os.path.join(tempfile.gettempdir(), "HwCodecDetect_GUI")
690+
if os.path.exists(temp_dir):
691+
shutil.rmtree(temp_dir)
692+
os.makedirs(temp_dir, exist_ok=True)
693+
694+
encoder_results = defaultdict(dict)
695+
decoder_results = defaultdict(dict)
696+
697+
total_enc_tasks = sum(
698+
len(info["hw_encoders"]) * len(RESOLUTIONS)
699+
for info in ENCODERS.values()
700+
)
701+
total_dec_tasks = sum(
702+
len(info["hw_decoders"]) * len(RESOLUTIONS)
703+
for info in DECODERS.values()
704+
)
705+
total_tasks = total_enc_tasks + total_dec_tasks
706+
done_tasks = 0
707+
708+
enc_tasks = []
709+
for codec, info in ENCODERS.items():
710+
for encoder in info["hw_encoders"]:
711+
for res_name, res_size in RESOLUTIONS.items():
712+
enc_tasks.append((codec, encoder, res_name, res_size, temp_dir, False))
713+
714+
with ThreadPoolExecutor(max_workers=self.args.encoder_count) as executor:
715+
futures = [executor.submit(_run_encoder_test_single, t) for t in enc_tasks]
716+
for f in as_completed(futures):
717+
if self.stop_requested:
718+
executor.shutdown(wait=False, cancel_futures=True)
719+
shutil.rmtree(temp_dir, ignore_errors=True)
720+
self._finish_run(cancelled=True)
721+
return
722+
title, res_name, status = f.result()
723+
encoder_results[title][res_name] = status
724+
done_tasks += 1
725+
self._update_progress(int(done_tasks * 100 / total_tasks))
726+
727+
dec_tasks = []
728+
for codec, info in DECODERS.items():
729+
for hw_decoder in info["hw_decoders"]:
730+
for res_name, res_size in RESOLUTIONS.items():
731+
dec_tasks.append((codec, hw_decoder, res_name, res_size, temp_dir, False))
732+
733+
with ThreadPoolExecutor(max_workers=self.args.decoder_count) as executor:
734+
futures = [executor.submit(_run_decoder_test_single, t) for t in dec_tasks]
735+
for f in as_completed(futures):
736+
if self.stop_requested:
737+
executor.shutdown(wait=False, cancel_futures=True)
738+
shutil.rmtree(temp_dir, ignore_errors=True)
739+
self._finish_run(cancelled=True)
740+
return
741+
title, res_name, status = f.result()
742+
decoder_results[title][res_name] = status
743+
done_tasks += 1
744+
self._update_progress(int(done_tasks * 100 / total_tasks))
745+
746+
shutil.rmtree(temp_dir, ignore_errors=True)
747+
748+
if not self.stop_requested:
749+
self._update_summary_table(self.table_enc, encoder_results, kind="Encoder")
750+
self._update_summary_table(self.table_dec, decoder_results, kind="Decoder")
751+
self._update_progress(100)
752+
753+
self._finish_run(cancelled=self.stop_requested)
754+
755+
def _finish_run(self, cancelled=False):
756+
def _inner():
757+
self.running = False
758+
self.start_button.config(text="Start Test")
759+
if cancelled:
760+
self.progress["value"] = 0
761+
self._clear_tables()
762+
self.root.after(0, _inner)
763+
764+
def _update_progress(self, value):
765+
def _inner():
766+
self.progress["value"] = value
767+
self.root.after(0, _inner)
768+
769+
def _clear_tables(self):
770+
for table in (self.table_dec, self.table_enc):
771+
for row in table.get_children():
772+
table.delete(row)
773+
774+
def _update_summary_table(self, table, results, kind):
775+
resolutions = list(RESOLUTIONS.keys())
776+
777+
def _inner():
778+
for row in table.get_children():
779+
table.delete(row)
780+
781+
filtered_titles = sorted(
782+
[t for t in results.keys() if kind in t]
783+
) or sorted(results.keys())
784+
785+
for title in filtered_titles:
786+
row = [title]
787+
for res in resolutions:
788+
status = results.get(title, {}).get(res, "skipped")
789+
if status == "succeeded":
790+
symbol = "✅" # green cell-like symbol
791+
elif status == "failed":
792+
symbol = "❌" # red cell-like symbol
793+
else:
794+
symbol = "-"
795+
row.append(symbol)
796+
table.insert("", "end", values=row)
797+
798+
self.root.after(0, _inner)
799+
800+
801+
def launch_gui(args):
802+
root = tk.Tk()
803+
root.title("HwCodecDetect - Hardware Video Codec Detection Tool")
804+
805+
app = HwCodecGUI(root, args)
806+
root.mainloop()
807+
808+
589809
def main():
590810
"""Parses arguments and runs the test suite."""
591811

@@ -641,11 +861,23 @@ def main():
641861

642862
parser.add_argument(
643863
'--verbose',
644-
action='store_true',
864+
action='store_true',
645865
help='Print detailed information for each test'
646866
)
647867

868+
parser.add_argument(
869+
"-ui", "--ui",
870+
action="store_true",
871+
default=True,
872+
help="Launch GUI"
873+
)
874+
648875
args = parser.parse_args()
876+
877+
if args.ui:
878+
launch_gui(args)
879+
return
880+
649881
run_all_tests(args)
650882

651883
if __name__ == "__main__":

0 commit comments

Comments
 (0)