diff --git a/ImageDiffwithPython/config.py b/ImageDiffwithPython/config.py new file mode 100644 index 0000000..d5d624a --- /dev/null +++ b/ImageDiffwithPython/config.py @@ -0,0 +1,3 @@ +import os + +SCREENSHOTS_DIR = os.environ.get("SCREENSHOTS_DIR", "./screenshots/") diff --git a/ImageDiffwithPython/imagediff.py b/ImageDiffwithPython/imagediff.py new file mode 100644 index 0000000..8609ab1 --- /dev/null +++ b/ImageDiffwithPython/imagediff.py @@ -0,0 +1,101 @@ +from PIL import Image, ImageChops +import os +import base64 +from io import BytesIO +from config import SCREENSHOTS_DIR + +def encode_image(image): + """Encode an image to a base64 string.""" + buffered = BytesIO() + image.save(buffered, format="PNG") + return base64.b64encode(buffered.getvalue()).decode('utf-8') + +def image_diff(src_img_path, cmp_img_path): + """ + Compute the difference between two images and return the results. + """ + try: + src_img = Image.open(src_img_path) + cmp_img = Image.open(cmp_img_path) + diff_img = ImageChops.difference(src_img, cmp_img).convert('RGB') + + has_diff = diff_img.getbbox() is not None + + return { + 'src_img_data': encode_image(src_img), + 'cmp_img_data': encode_image(cmp_img), + 'diff_img_data': encode_image(diff_img) if has_diff else None, + 'has_diff': has_diff + } + except IOError: + return {} + +def movie_diff(src_build, cmp_build, target, movie): + """ + Compare all frames of a movie between two builds and determine if there are any differences. + + Args: + src_build (str): The source build name + cmp_build (str): The comparison build name + target (str): The target name + movie (str): The movie name + + Returns: + bool: True if any frame has differences, False otherwise + """ + src_build_path = os.path.join(SCREENSHOTS_DIR, target, src_build) + cmp_build_path = os.path.join(SCREENSHOTS_DIR, target, cmp_build) + + # Ensure both build paths exist + if not os.path.exists(src_build_path) or not os.path.exists(cmp_build_path): + return False + + # Get all frames for this movie in both builds + src_frames = [f for f in os.listdir(src_build_path) + if os.path.isfile(os.path.join(src_build_path, f)) and f.startswith(f"{movie}-")] + + cmp_frames = [f for f in os.listdir(cmp_build_path) + if os.path.isfile(os.path.join(cmp_build_path, f)) and f.startswith(f"{movie}-")] + + # If frame counts differ, there's definitely a difference + if len(src_frames) != len(cmp_frames): + return True + + # Helper function to extract frame number from filename + def get_frame_number(filename): + parts = filename.split('-') + if len(parts) > 1: + try: + return int(parts[1].split('.')[0]) + except ValueError: + return 0 + return 0 + + src_frame_map = {get_frame_number(f): f for f in src_frames} + cmp_frame_map = {get_frame_number(f): f for f in cmp_frames} + + all_frame_numbers = sorted(set(list(src_frame_map.keys()) + list(cmp_frame_map.keys()))) + + if set(src_frame_map.keys()) != set(cmp_frame_map.keys()): + return True + + for frame_num in all_frame_numbers: + src_frame = src_frame_map.get(frame_num) + cmp_frame = cmp_frame_map.get(frame_num) + + if src_frame and cmp_frame: + src_img_path = os.path.join(src_build_path, src_frame) + cmp_img_path = os.path.join(cmp_build_path, cmp_frame) + + try: + diff_result = image_diff(src_img_path, cmp_img_path) + + if diff_result.get('has_diff', False): + return True + except Exception as e: + print(f"Error comparing frames {src_frame} and {cmp_frame}: {e}") + return True + else: + return True + + return False \ No newline at end of file diff --git a/ImageDiffwithPython/main.py b/ImageDiffwithPython/main.py new file mode 100644 index 0000000..6a07607 --- /dev/null +++ b/ImageDiffwithPython/main.py @@ -0,0 +1,545 @@ +import os + +from PIL import Image +from flask import Flask, render_template, jsonify, url_for, send_from_directory + +from config import SCREENSHOTS_DIR +from imagediff import image_diff, encode_image, movie_diff + +app = Flask(__name__) + +def get_frame_number(filename): + """Extract frame number from filename.""" + parts = filename.split('-') + if len(parts) > 1: + try: + return int(parts[1].split('.')[0]) + except ValueError: + return 0 + return 0 + +def get_sorted_builds(target_path, reverse=True): + """Get sorted list of builds for a target.""" + builds = [b for b in os.listdir(target_path) + if os.path.isdir(os.path.join(target_path, b))] + return sorted(builds, reverse=reverse) + +def collect_movie_frames(target_path, builds): + """Collect all movie frames information for all builds.""" + all_movies = set() + build_movie_frames = {} + build_files = {} + + # Pre-collect file information to avoid multiple directory reads + for build in builds: + build_path = os.path.join(target_path, build) + build_files[build] = os.listdir(build_path) + + # Process movie and frame data + for build in builds: + build_movie_frames[build] = {} + + for file in build_files[build]: + if not os.path.isfile(os.path.join(target_path, build, file)): + continue + + if "-" in file: + parts = file.split("-") + movie_name = parts[0] + frame_num = parts[1].split('.')[0] + + if movie_name not in build_movie_frames[build]: + build_movie_frames[build][movie_name] = [] + + build_movie_frames[build][movie_name].append(frame_num) + all_movies.add(movie_name) + + return all_movies, build_movie_frames, build_files + +def find_first_build_for_movies(all_movies, builds_ascending, build_movie_frames): + """Find the first build where each movie appears.""" + first_build_for_movie = {} + for movie in all_movies: + for build in builds_ascending: + if movie in build_movie_frames.get(build, {}): + first_build_for_movie[movie] = build + break + return first_build_for_movie + +def calculate_reference_builds(all_movies, builds, build_movie_frames): + """Calculate reference builds for each movie in each build.""" + movie_reference_builds = {} + for movie in all_movies: + movie_reference_builds[movie] = {} + for i, current_build in enumerate(builds): + has_in_current = movie in build_movie_frames.get(current_build, {}) + + if not has_in_current: + reference_build = None + for j in range(i+1, len(builds)): + if movie in build_movie_frames.get(builds[j], {}): + reference_build = builds[j] + break + movie_reference_builds[movie][current_build] = reference_build + return movie_reference_builds + +def get_movie_frames(build_path, movie_prefix=None): + """Get all frames for a movie in a build path.""" + all_files = [f for f in os.listdir(build_path) + if os.path.isfile(os.path.join(build_path, f))] + + if movie_prefix: + # Filter files for specific movie + movie_files = [f for f in all_files if f.startswith(f"{movie_prefix}-")] + else: + # Get all movie files + movie_files = [f for f in all_files if "-" in f] + + return sorted(movie_files, key=get_frame_number) + +def extract_movie_names(files): + """Extract unique movie names from a list of files.""" + movies = set() + for file in files: + if "-" in file: + movie_name = file.split("-")[0] + movies.add(movie_name) + return movies + +def create_frame_map(frames): + """Create a map of frame numbers to filenames.""" + return {get_frame_number(f): f for f in frames} + +@app.route('/') +def index(): + targets = [d for d in os.listdir(SCREENSHOTS_DIR) + if os.path.isdir(os.path.join(SCREENSHOTS_DIR, d))] + + return render_template('index.html', targets=targets) + +@app.route('/target/') +def target_detail(target): + target_path = os.path.join(SCREENSHOTS_DIR, target) + + if not os.path.exists(target_path) or not os.path.isdir(target_path): + return "Target not found", 404 + + # Only get the build list, other time-consuming operations moved to API + builds = get_sorted_builds(target_path) + + # Only return the page framework with build list but without table data + return render_template('target.html', + target=target, + builds=builds) + +# Handle time-consuming data calculations +@app.route('/api/target_data/') +def target_data_api(target): + target_path = os.path.join(SCREENSHOTS_DIR, target) + + if not os.path.exists(target_path) or not os.path.isdir(target_path): + return jsonify({"error": "Target not found"}), 404 + + builds = get_sorted_builds(target_path) + builds_ascending = get_sorted_builds(target_path, reverse=False) + + # Collect movie frame data + all_movies, build_movie_frames, _ = collect_movie_frames(target_path, builds) + + # Calculate first build for each movie + first_build_for_movie = find_first_build_for_movies( + all_movies, builds_ascending, build_movie_frames) + + # Calculate reference builds + movie_reference_builds = {} + for movie in all_movies: + movie_reference_builds[movie] = {} + for i, current_build in enumerate(builds): + has_in_current = movie in build_movie_frames.get(current_build, {}) + current_frames = build_movie_frames.get(current_build, {}).get(movie, []) + + if not has_in_current: + # Reference build needs to have the movie + reference_build = None + for j in range(i+1, len(builds)): + next_build = builds[j] + if movie in build_movie_frames.get(next_build, {}): + reference_build = next_build + break + movie_reference_builds[movie][current_build] = { + 'build': reference_build, + 'frames': [] # Empty since current build doesn't have the movie + } + else: + # Current build has the movie, find a reference build with matching frames + reference_data = { + 'build': None, + 'frames': current_frames + } + + for j in range(i+1, len(builds)): + next_build = builds[j] + if movie in build_movie_frames.get(next_build, {}): + next_frames = build_movie_frames.get(next_build, {}).get(movie, []) + + # Check if current frames exist in the next build + common_frames = set(current_frames).intersection(set(next_frames)) + if common_frames: + reference_data = { + 'build': next_build, + 'frames': list(common_frames) + } + break + + movie_reference_builds[movie][current_build] = reference_data + + # Pre-calculate image difference results + image_diff_cache = {} + def get_image_diff(current_build, prev_build, movie, frame): + cache_key = (current_build, prev_build, movie, frame) + if cache_key not in image_diff_cache: + current_frame_path = os.path.join(target_path, current_build, f"{movie}-{frame}.png") + prev_frame_path = os.path.join(target_path, prev_build, f"{movie}-{frame}.png") + + if os.path.exists(current_frame_path) and os.path.exists(prev_frame_path): + try: + image_diff_cache[cache_key] = image_diff(current_frame_path, prev_frame_path) + except Exception as e: + print(f"Error comparing frames {movie}-{frame}: {e}") + image_diff_cache[cache_key] = {'has_diff': True} + else: + image_diff_cache[cache_key] = {'has_diff': True} + + return image_diff_cache[cache_key] + + # Modified movie difference function to only compare specified frames + def get_movie_diff_for_frames(current_build, reference_build, target, movie, frames_to_compare): + if not frames_to_compare: + return False # No frames to compare + + has_any_diff = False + for frame in frames_to_compare: + diff_result = get_image_diff(current_build, reference_build, movie, frame) + if diff_result.get('has_diff', False): + has_any_diff = True + break + + return has_any_diff + + # Pre-calculate movie difference results + movie_diff_cache = {} + def get_movie_diff_cached(current_build, prev_build, target, movie): + cache_key = (current_build, prev_build, target, movie) + if cache_key not in movie_diff_cache: + movie_diff_cache[cache_key] = movie_diff(current_build, prev_build, target, movie) + return movie_diff_cache[cache_key] + + movies = sorted(list(all_movies)) + continuous_bars = {} + + # Create continuous bars for visualization with updated skip logic + for movie in movies: + continuous_bars[movie] = [] + + for i, current_build in enumerate(builds): + prev_build = builds[i+1] if i < len(builds)-1 else None + + has_in_current = movie in build_movie_frames.get(current_build, {}) + current_frames = build_movie_frames.get(current_build, {}).get(movie, []) + + prev_frames = [] + if prev_build: + prev_frames = build_movie_frames.get(prev_build, {}).get(movie, []) + + has_in_prev = len(prev_frames) > 0 if prev_build else False + is_first_build = (current_build == first_build_for_movie.get(movie)) + + # Build entry information + if is_first_build: + continuous_bars[movie].append({ + 'build': current_build, + 'type': 'first' + }) + elif not has_in_current and prev_build: + # Modified skip build logic + reference_data = movie_reference_builds[movie].get(current_build, {'build': None, 'frames': []}) + reference_build = reference_data['build'] + + if reference_build: + continuous_bars[movie].append({ + 'build': current_build, + 'reference_build': reference_build, + 'type': 'diff', + 'has_diff': False, # No diff since current build doesn't have the movie + 'is_skipped': True, + 'compare_with': reference_build + }) + else: + continuous_bars[movie].append({ + 'build': current_build, + 'type': 'missing' + }) + elif has_in_current and prev_build: + if has_in_prev: + common_frames = set(current_frames).intersection(set(prev_frames)) + + if len(current_frames) < len(prev_frames): + # End difference check loop early + has_any_diff = False + for frame in common_frames: + diff_result = get_image_diff(current_build, prev_build, movie, frame) + if diff_result.get('has_diff', False): + has_any_diff = True + break + + continuous_bars[movie].append({ + 'build': current_build, + 'has_diff': has_any_diff, + 'compare_with': prev_build, + 'type': 'diff', + 'is_partial': True + }) + else: + has_diff = get_movie_diff_cached(current_build, prev_build, target, movie) + + continuous_bars[movie].append({ + 'build': current_build, + 'has_diff': has_diff, + 'compare_with': prev_build, + 'type': 'diff' + }) + else: + # Modified readded build logic + reference_data = movie_reference_builds[movie].get(current_build, {'build': None, 'frames': []}) + reference_build = reference_data['build'] + comparable_frames = reference_data['frames'] + + has_diff = False + if reference_build and comparable_frames: + has_diff = get_movie_diff_for_frames(current_build, reference_build, target, movie, comparable_frames) + + continuous_bars[movie].append({ + 'build': current_build, + 'type': 'readded', + 'compare_with': reference_build, + 'common_frames': comparable_frames, + 'has_diff': has_diff, + 'no_reference': reference_build is None + }) + elif not prev_build: + continuous_bars[movie].append({ + 'build': current_build, + 'type': 'unknown' + }) + + # Generate URL templates needed by frontend + urls = { + 'movie_url': url_for('movie', movie='MOVIE_PLACEHOLDER'), + 'compare_url': url_for('compare', + build1='BUILD1_PLACEHOLDER', + build2='BUILD2_PLACEHOLDER', + target='TARGET_PLACEHOLDER', + movie='MOVIE_PLACEHOLDER'), + 'single_build_url': url_for('view_single_build', + build='BUILD_PLACEHOLDER', + target='TARGET_PLACEHOLDER', + movie='MOVIE_PLACEHOLDER') + } + + # Return all data to frontend + data = { + 'target': target, + 'builds': builds, + 'movies': movies, + 'continuous_bars': continuous_bars, + 'urls': urls + } + + return jsonify(data) + +@app.route('/movie/') +def movie(movie): + target_builds = {} + all_builds = set() + diff_matrix = {} + + # Scan through all targets + for target in os.listdir(SCREENSHOTS_DIR): + target_path = os.path.join(SCREENSHOTS_DIR, target) + if os.path.isdir(target_path): + # Get sorted builds + builds = get_sorted_builds(target_path) + + # Find builds containing this movie + builds_with_movie = [] + for build in builds: + build_path = os.path.join(target_path, build) + movie_files = get_movie_frames(build_path, movie) + + if movie_files: + builds_with_movie.append(build) + all_builds.add(build) + + # Calculate diffs between consecutive builds + for i in range(len(builds_with_movie) - 1): + current_build = builds_with_movie[i] + prev_build = builds_with_movie[i + 1] + + has_diff = movie_diff(current_build, prev_build, target, movie) + + if target not in diff_matrix: + diff_matrix[target] = {} + + diff_matrix[target][(current_build, prev_build)] = has_diff + + if builds_with_movie: + target_builds[target] = builds_with_movie + + all_builds = sorted(list(all_builds), reverse=True) + + return render_template('movie.html', + movie=movie, + target_builds=target_builds, + all_builds=all_builds, + diff_matrix=diff_matrix) + +@app.route('/build/') +def build(build): + target_info = {} + + for target in os.listdir(SCREENSHOTS_DIR): + target_path = os.path.join(SCREENSHOTS_DIR, target) + if not os.path.isdir(target_path): + continue + + # Get sorted builds + builds = get_sorted_builds(target_path) + + # Check if current build exists in this target + if build not in builds: + continue + + # Find the previous build + build_index = builds.index(build) + prev_build = builds[build_index + 1] if build_index < len(builds) - 1 else None + + # Find all movies in this build + build_path = os.path.join(target_path, build) + movie_files = get_movie_frames(build_path) + movies = extract_movie_names(movie_files) + + # Calculate differences for each movie + movie_diffs = {} + if prev_build: + for movie in movies: + has_diff = movie_diff(build, prev_build, target, movie) + movie_diffs[movie] = has_diff + + # Store target information + target_info[target] = { + 'prev_build': prev_build, + 'movies': sorted(list(movies)), + 'movie_diffs': movie_diffs + } + + return render_template('build.html', + build=build, + target_info=target_info) + +@app.route('/compare////') +def compare(build1, build2, target, movie): + build1_path = os.path.join(SCREENSHOTS_DIR, target, build1) + build2_path = os.path.join(SCREENSHOTS_DIR, target, build2) + + # Get all frames for this movie in both builds + build1_frames = get_movie_frames(build1_path, movie) + build2_frames = get_movie_frames(build2_path, movie) + + # Map frame numbers to filenames for easy lookup + build1_frame_map = create_frame_map(build1_frames) + build2_frame_map = create_frame_map(build2_frames) + + # Find only common frames between the two builds + common_frame_numbers = sorted(set(build1_frame_map.keys()).intersection(set(build2_frame_map.keys()))) + + # For each common frame number, create a comparison entry + frame_comparisons = [] + + # Process only common frames + for frame_num in common_frame_numbers: + build1_frame = build1_frame_map.get(frame_num) + build2_frame = build2_frame_map.get(frame_num) + + comparison = { + 'frame_number': frame_num, + 'build1_frame': build1_frame, + 'build2_frame': build2_frame, + 'has_diff': False, + 'diff_data': None + } + + # Calculate diff + img1_path = os.path.join(build1_path, build1_frame) + img2_path = os.path.join(build2_path, build2_frame) + + try: + diff_result = image_diff(img1_path, img2_path) + comparison['has_diff'] = diff_result.get('has_diff', False) + comparison['diff_data'] = diff_result + except Exception as e: + print(f"Error comparing images: {e}") + + frame_comparisons.append(comparison) + + # Calculate summary statistics + stats = { + 'total_common_frames': len(common_frame_numbers), + 'different_frames': sum(1 for comp in frame_comparisons if comp.get('has_diff', False)) + } + + return render_template('compare.html', + build1=build1, + build2=build2, + target=target, + movie=movie, + comparisons=frame_comparisons, + stats=stats) + +@app.route('/view///') +def view_single_build(target, build, movie): + """ + Display all frames of a movie from a single build without comparison. + """ + build_path = os.path.join(SCREENSHOTS_DIR, target, build) + frames = get_movie_frames(build_path, movie) + + # Prepare frame data for the template + frame_data = [] + for frame in frames: + frame_path = os.path.join(build_path, frame) + frame_num = get_frame_number(frame) + + try: + img = Image.open(frame_path) + img_data = encode_image(img) + + frame_data.append({ + 'frame_number': frame_num, + 'filename': frame, + 'img_data': img_data + }) + except Exception as e: + print(f"Error processing frame {frame}: {e}") + + return render_template('view.html', + target=target, + build=build, + movie=movie, + frames=frame_data) + +@app.route('/screenshots/') +def screenshots(filename): + return send_from_directory(SCREENSHOTS_DIR, filename) + +if __name__ == '__main__': + app.run(debug=True, port=5001) \ No newline at end of file diff --git a/ImageDiffwithPython/screenshots/sample-target.rar b/ImageDiffwithPython/screenshots/sample-target.rar new file mode 100644 index 0000000..f54514f Binary files /dev/null and b/ImageDiffwithPython/screenshots/sample-target.rar differ diff --git a/ImageDiffwithPython/static/css/style.css b/ImageDiffwithPython/static/css/style.css new file mode 100644 index 0000000..410eea8 --- /dev/null +++ b/ImageDiffwithPython/static/css/style.css @@ -0,0 +1,131 @@ +h1, h2, h3{ + font-weight: normal; +} + +body{ + padding: 0 24px; +} + +header{ + text-align: center; + + a{ + color: unset; + text-decoration: none; + } +} + +table{ + white-space: nowrap; +} + +a { + text-decoration: none; + color: #337ab7; +} + +a:hover { + text-decoration: underline; +} + +.quote{ + margin: 18px 0; + padding: 12px 24px; + + border: 1px solid #dcdcdc; + border-radius: 6px; + background: #f5f5f5; + + overflow-x: auto; +} + +.code{ + padding: 3px 6px; + + font-weight: normal; + border-radius: 6px; + background: #f7f7f7; +} + +.alert { + padding: .75rem 1.25rem; + margin-bottom: 1rem; + + color: #004085; + background-color: #cce5ff; + border: 1px solid #b8daff; + border-radius: 6px; +} + +.result-table{ + border-collapse:collapse; + + text-align: center; +} + +.result-table.horizontal{ + padding-bottom: 24px; + + thead>tr{ + height: 50px; + } + + tbody{ + th{ + padding-right: 12px; + } + + td{ + text-align: center; + padding-left: 5px; + padding-right: 5px; + border: 1px solid black; + border-left: none; + font-size: 80%; + } + + td:nth-of-type(1) { + border-left: 1px solid black; + } + + td.no-data { + border: none; + } + + td.no-diff { + border-right: none; + } + + td.success, td.first-build { + background-color: #90ee90; + } + } + + .result-table-build{ + width: 30px; + transform: translate(-12px, 10px) rotate(315deg); + a{ + border-bottom: 1px solid #ccc; + } + } +} + +.comparison-table { + width: 100%; + border-collapse: collapse; + margin-bottom: 30px; + + thead{ + height: 50px; + } + + th, td{ + text-align: center; + width: 33.33%; + } + + .frame-image { + max-width: 300px; + max-height: 300px; + } +} \ No newline at end of file diff --git a/ImageDiffwithPython/templates/build.html b/ImageDiffwithPython/templates/build.html new file mode 100644 index 0000000..37adc04 --- /dev/null +++ b/ImageDiffwithPython/templates/build.html @@ -0,0 +1,113 @@ + + + + Build: {{ build }} + + + + +
+

ImageDiff

+
+ +

Information on {{ build }}

+ +{% if target_info %} + + +
+

Test Results

+
+ + + + + + + + + + {% for target, info in target_info.items() %} + {% for movie in info.movies %} + + {% if loop.first %} + + {% endif %} + + + + + + {% endfor %} + {% endfor %} + +
TargetMovieResult
+ {{ target }} + + {{ movie }} + + {% if info.prev_build %} + {% if info.movie_diffs.get(movie, False) %} + + + DIFF + + + {% else %} + No Diff + {% endif %} + {% else %} + First build (no comparison available) + {% endif %} +
+
+
+ +{% else %} + +

This build was not found in any target.

+ +{% endif %} + + \ No newline at end of file diff --git a/ImageDiffwithPython/templates/compare.html b/ImageDiffwithPython/templates/compare.html new file mode 100644 index 0000000..9b16b28 --- /dev/null +++ b/ImageDiffwithPython/templates/compare.html @@ -0,0 +1,77 @@ + + + + {{ build1 }} vs. {{ build2 }}/ {{movie}}/ {{target}} + + + + +
+

ImageDiff

+
+ +

{{ build1 }} vs. {{ build2 }} / {{ movie }} / {{ target }}

+ +{% if comparisons %} +
+ Different Frames: {{ stats.different_frames }} +
+ + + + + + + + + + + {% for comp in comparisons %} + + + + + + {% endfor %} + +
{{ build1 }}Diff{{ build2 }}
+ {% if comp.diff_data and comp.diff_data.src_img_data %} + Frame {{ comp.frame_number }} in {{ build1 }} + {% else %} +
Image data not available
+ {% endif %} +
+ {% if comp.has_diff %} + {% if comp.diff_data and comp.diff_data.diff_img_data %} + Difference for frame {{ comp.frame_number }} + {% else %} + DIFFERENT + {% endif %} + {% else %} + No difference + {% endif %} + + {% if comp.diff_data and comp.diff_data.cmp_img_data %} + Frame {{ comp.frame_number }} in {{ build2 }} + {% else %} +
Image data not available
+ {% endif %} +
+{% else %} +
+

No frames found for comparison.

+
+{% endif %} + + \ No newline at end of file diff --git a/ImageDiffwithPython/templates/index.html b/ImageDiffwithPython/templates/index.html new file mode 100644 index 0000000..932b9ca --- /dev/null +++ b/ImageDiffwithPython/templates/index.html @@ -0,0 +1,50 @@ + + + + + + ImageDiff + + + + +
+

ImageDiff

+
+ +
+

Director builds

+ +
+ {% if targets %} + {% for target in targets %} + + {% endfor %} + {% else %} +

No targets found. Please check your screenshots directory.

+ {% endif %} +
+
+ + \ No newline at end of file diff --git a/ImageDiffwithPython/templates/movie.html b/ImageDiffwithPython/templates/movie.html new file mode 100644 index 0000000..53e403c --- /dev/null +++ b/ImageDiffwithPython/templates/movie.html @@ -0,0 +1,90 @@ + + + + Movie: {{ movie }} + + + +
+

ImageDiff

+
+ +

{{ movie }}

+ +
+

Description

+
+ {% if target_builds %} + + + + + + + + + {% for target, builds in target_builds.items() %} + + + + + {% endfor %} + +
TargetBuilds
+ {{ target }} + + {{ builds|length }} builds +
+ {% endif %} +
+
+ +
+

Recent results

+
+ + + + + {% for build in all_builds %} + + {% endfor %} + + + + {% for target, builds in target_builds.items() %} + + + {% for build in all_builds %} + {% if build in builds %} + {% set build_index = builds.index(build) %} + {% if build_index < (builds|length - 1) %} + {% set next_build = builds[build_index + 1] %} + {% if diff_matrix.get(target, {}).get((build, next_build), False) %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
+ {{ build }} +
{{ target }} + 🔍 + + + 🔍 + +
+
+
+ + \ No newline at end of file diff --git a/ImageDiffwithPython/templates/target.html b/ImageDiffwithPython/templates/target.html new file mode 100644 index 0000000..92bcfdb --- /dev/null +++ b/ImageDiffwithPython/templates/target.html @@ -0,0 +1,283 @@ + + + + + + Target: {{ target }} - ImageDiff + + + + + +
+
+
Loading image diff data...
+
This may take a while. Please be patient.
+
+ +
+

ImageDiff

+
+ +

Target: {{ target }}

+ +
+

Recent Results

+ +
+ + + + + {% for build in builds %} + + {% endfor %} + + + + + + + +
+ {{ build }} +
+ Loading... +
+
+
+ + + + \ No newline at end of file diff --git a/ImageDiffwithPython/templates/view.html b/ImageDiffwithPython/templates/view.html new file mode 100644 index 0000000..0eec42b --- /dev/null +++ b/ImageDiffwithPython/templates/view.html @@ -0,0 +1,52 @@ + + + + {{ build }} / {{ movie }} / {{ target }} + + + + +
+

ImageDiff

+
+ +

{{ build }} / {{ movie }} / {{ target }}

+ +

This is the first build that contains this movie, so there's no previous build to compare with.

+ +{% if frames %} + + + + + + + + + + {% for frame in frames %} + + + + + + {% endfor %} + +
{{ build }}
+ Frame {{ frame.frame_number }} in {{ build }} + + No comparison available + + No previous build +
+{% else %} +

No frames found for this movie.

+{% endif %} + + \ No newline at end of file