From 29fb78b695664febd0ade085875038683d609e99 Mon Sep 17 00:00:00 2001 From: Almamu Date: Mon, 7 Apr 2025 05:09:11 +0200 Subject: [PATCH] feat: add testing tools to run over all backgrounds and getting output data --- .../Application/CApplicationContext.cpp | 4 +- .../Application/CApplicationContext.h | 2 + .../Application/CWallpaperApplication.cpp | 2 +- tools/run-over-all-backgrounds.sh | 12 +++ tools/scripts/process.py | 80 +++++++++++++++++ tools/scripts/run.py | 90 +++++++++++++++++++ 6 files changed, 188 insertions(+), 2 deletions(-) create mode 100755 tools/run-over-all-backgrounds.sh create mode 100644 tools/scripts/process.py create mode 100644 tools/scripts/run.py diff --git a/src/WallpaperEngine/Application/CApplicationContext.cpp b/src/WallpaperEngine/Application/CApplicationContext.cpp index 7adfc23..58b94f5 100644 --- a/src/WallpaperEngine/Application/CApplicationContext.cpp +++ b/src/WallpaperEngine/Application/CApplicationContext.cpp @@ -23,7 +23,8 @@ struct option long_options [] = { {"set-property", required_argument, nullptr, 'o'}, {"noautomute", no_argument, nullptr, 'm'}, {"no-audio-processing", no_argument, nullptr, 'g'}, {"no-fullscreen-pause", no_argument, nullptr, 'n'}, {"disable-mouse", no_argument, nullptr, 'e'}, {"scaling", required_argument, nullptr, 't'}, - {"clamping", required_argument, nullptr, 't'}, {nullptr, 0, nullptr, 0}}; + {"clamping", required_argument, nullptr, 't'}, {"screenshot-delay", required_argument, nullptr, 'y'}, + {nullptr, 0, nullptr, 0}}; /* std::hash::operator() isn't constexpr, so it can't be used to get hash values as compile-time constants * So here is customHash. It skips all spaces, so hashes for " find " and "fi nd" are the same @@ -198,6 +199,7 @@ CApplicationContext::CApplicationContext (int argc, char* argv []) : this->settings.screenshot.take = true; this->settings.screenshot.path = stringPathFixes (optarg); break; + case 'y': this->settings.screenshot.delay = std::min (atoi (optarg), 5); break; case 'm': this->settings.audio.automute = false; break; diff --git a/src/WallpaperEngine/Application/CApplicationContext.h b/src/WallpaperEngine/Application/CApplicationContext.h index a4027bc..06294f7 100644 --- a/src/WallpaperEngine/Application/CApplicationContext.h +++ b/src/WallpaperEngine/Application/CApplicationContext.h @@ -97,6 +97,8 @@ class CApplicationContext { struct { /** If an screenshot should be taken */ bool take; + /** The frames to wait until the screenshot is taken */ + uint32_t delay; /** The path to where the screenshot must be saved */ std::filesystem::path path; } screenshot; diff --git a/src/WallpaperEngine/Application/CWallpaperApplication.cpp b/src/WallpaperEngine/Application/CWallpaperApplication.cpp index 83f4d2a..a666445 100644 --- a/src/WallpaperEngine/Application/CWallpaperApplication.cpp +++ b/src/WallpaperEngine/Application/CWallpaperApplication.cpp @@ -435,7 +435,7 @@ void CWallpaperApplication::show () { this->m_context.state.general.keepRunning = false; } - if (!this->m_context.settings.screenshot.take || m_videoDriver->getFrameCounter () < 5) + if (!this->m_context.settings.screenshot.take || m_videoDriver->getFrameCounter () < this->m_context.settings.screenshot.delay) continue; this->takeScreenshot (this->m_context.settings.screenshot.path); diff --git a/tools/run-over-all-backgrounds.sh b/tools/run-over-all-backgrounds.sh new file mode 100755 index 0000000..521c069 --- /dev/null +++ b/tools/run-over-all-backgrounds.sh @@ -0,0 +1,12 @@ +#!/bin/bash +if [ $# -eq 0 ] + then + echo "Please provide the current build's executable path. You might want to run this script off the same folder." +fi +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# ensure the output and images directory exists +[ -d "output" ] || mkdir output +[ -d "images" ] || mkdir images +python ${SCRIPT_DIR}/scripts/run.py $1 ~/.steam/steam/steamapps/workshop/content/431960/ +python ${SCRIPT_DIR}/scripts/process.py \ No newline at end of file diff --git a/tools/scripts/process.py b/tools/scripts/process.py new file mode 100644 index 0000000..dbd6b45 --- /dev/null +++ b/tools/scripts/process.py @@ -0,0 +1,80 @@ +import os +import re +import base64 +import cv2 +import numpy as np +import shutil +from bs4 import BeautifulSoup + +def extract_base64_image(html_file): + """Extracts base64-encoded image data from an HTML file.""" + with open(html_file, "r", encoding="utf-8") as f: + soup = BeautifulSoup(f, "html.parser") + img_tag = soup.find("img") + if img_tag and 'src' in img_tag.attrs: + match = re.search(r'data:image/png;base64,(.+)', img_tag['src']) + if match: + return match.group(1) + return None + +def categorize_image(image_data): + """Categorizes the image as 'grey', 'no image', or 'content'.""" + if not image_data: + return "no_image" + + img_array = np.frombuffer(base64.b64decode(image_data), dtype=np.uint8) + img = cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE) + + if img is None: + return "no_image" + + mean_value = cv2.mean(img)[0] # Average pixel brightness + std_dev = np.std(img) # Standard deviation of pixel values + + if mean_value > 200 and std_dev < 10: + return "grey" + return "content" + +def process_html_files(input_folder, output_html="gallery.html"): + """Processes all HTML files, categorizes them, and generates a gallery.""" + categories = {"no_image": [], "grey": [], "content": []} + + for file in os.listdir(input_folder): + if file.endswith(".html") and not file.startswith('report'): + file_path = os.path.join(input_folder, file) + image_data = extract_base64_image(file_path) + category = categorize_image(image_data) + categories[category].append((file, image_data)) + + with open(output_html, "w", encoding="utf-8") as f: + f.write(""" + + + Image Gallery + + + +

Image Gallery

+ """) + + for category, files in categories.items(): + f.write(f"

{category.replace('_', ' ').title()}

") + f.write("") + + f.write("") + + print(f"Gallery created at {output_html}") + +if __name__ == "__main__": + input_folder = "./output" # Change to your directory + process_html_files(input_folder, "output/gallery.html") diff --git a/tools/scripts/run.py b/tools/scripts/run.py new file mode 100644 index 0000000..844e670 --- /dev/null +++ b/tools/scripts/run.py @@ -0,0 +1,90 @@ +import subprocess +import os +import time +import html +import base64 +import argparse +import signal + +def run_and_monitor(program, program_args=None, output_file="output.png", wait_time=1, timeout=30, html_report="report.html"): + try: + # Build the full command + full_command = [program] + ([program_args] if program_args else []) + + print(f"Running command: {' '.join(full_command)}") + + # Start the program with shell=True for better compatibility + process = subprocess.Popen( + f"exec {" ".join(full_command)}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, preexec_fn=os.setsid + ) + stdout_data, stderr_data = "", "" + start_time = time.time() + + while time.time() - start_time < timeout: + print("Polling for file {output_file}".format(output_file=output_file)) + # Check if the file is created + if os.path.exists(output_file): + break + time.sleep(wait_time) + else: + print("Timeout reached. File not found.") + + # Capture output + if process.poll() is None: + os.killpg(os.getpgid(process.pid), signal.SIGKILL) + stdout_data, stderr_data = process.communicate() + + # Read the created image file as base64 + image_base64 = "" + if os.path.exists(output_file): + with open(output_file, "rb") as f: + image_base64 = base64.b64encode(f.read()).decode('utf-8') + + # Save results to an HTML file + with open(os.path.join("output", html_report), "w", encoding="utf-8") as report: + report.write(""" + + Program Output Review + +

Generated Image

+ Generated Image +

Standard Output

+
{stdout_data}
+

Error Output

+
{stderr_data}
+ + + """.format( + image_base64=image_base64, + stdout_data=html.escape(stdout_data), + stderr_data=html.escape(stderr_data) + )) + print(f"Report saved to {html_report}") + + if stderr_data: + print("Error output detected:") + print(stderr_data) + + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Run a program, monitor a file, and capture output.") + parser.add_argument("program", help="The program to execute.") + parser.add_argument("directory", help="Directory containing folders to process.") + + args = parser.parse_args() + + if not os.path.isdir(args.directory): + print("Error: Specified directory does not exist.") + exit(1) + + folders = [f for f in os.listdir(args.directory) if os.path.isdir(os.path.join(args.directory, f))] + + for folder in folders: + run_and_monitor( + args.program, + program_args="--screenshot {path}/images/{folder}.png --screenshot-delay 420 {folder}".format(path=os.getcwd(), folder=folder), + output_file="{path}/images/{folder}.png".format(path=os.getcwd(), folder=folder), + html_report="{folder}.html".format(folder=folder) + )