253 lines
11 KiB
Python
Executable file
253 lines
11 KiB
Python
Executable file
import os
|
|
import json
|
|
import logging
|
|
from typing import Dict, Any, Set, Tuple, Optional
|
|
from PIL import Image
|
|
from utils.check_helpers import record_skipped_type, prepare_skipped_result
|
|
|
|
def run_check(config):
|
|
"""
|
|
Check that each image in linkingrecord.json matches the specified resolution
|
|
based on (viewtype, imagetype) conditions and an overall MEC-or-BAU pack
|
|
designation. However, if the pack is MEC, certain items will still use the
|
|
BAU resolution map under these conditions:
|
|
1) viewtype in ("exterior", "interior") AND no experienceCondition
|
|
2) imagetype == "powertrain"
|
|
|
|
MEC detection (pack-level): If ANY item has "experienceCondition": "2d-background",
|
|
the entire pack is MEC. Otherwise, the entire pack is BAU.
|
|
|
|
Expected config:
|
|
- working_dir (str): Directory where linkingrecord.json and images reside.
|
|
- linkingrecord_filename (str): Name of the linking record file. Default: linkingrecord.json
|
|
|
|
Returns:
|
|
- "passed" if all checked images match their expected resolution
|
|
- "passed" with skipped_types details if unknown type combinations were encountered
|
|
- "failed" if any present images don't match, listing each that failed
|
|
- "error" if linkingrecord is missing or invalid
|
|
"""
|
|
|
|
working_dir = config.get("working_dir", "working")
|
|
linkingrecord_filename = config.get("linkingrecord_filename", "linkingrecord.json")
|
|
linkingrecord_path = os.path.join(working_dir, linkingrecord_filename)
|
|
|
|
if not os.path.exists(linkingrecord_path):
|
|
return {
|
|
"status": "error",
|
|
"error_message": f"Linking record '{linkingrecord_filename}' not found in {working_dir}."
|
|
}
|
|
|
|
# Load linkingrecord.json
|
|
with open(linkingrecord_path, 'r', encoding='utf-8') as f:
|
|
linkingrecord = json.load(f)
|
|
|
|
if "items" not in linkingrecord or not isinstance(linkingrecord["items"], list):
|
|
return {
|
|
"status": "error",
|
|
"error_message": "Invalid linkingrecord.json structure: 'items' missing or not a list."
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# 1) Determine if ANY item has experienceCondition = "2d-background" => MEC
|
|
# -------------------------------------------------------------------------
|
|
any_mec = any(
|
|
item.get("conditions", {}).get("experienceCondition") == "2d-background"
|
|
for item in linkingrecord["items"]
|
|
)
|
|
pack_experience_type = "MEC" if any_mec else "BAU"
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Define BAU and MEC resolution maps
|
|
# -------------------------------------------------------------------------
|
|
bau_map = {
|
|
("exterior", None): (1600, 900),
|
|
("interior", None): (1600, 900),
|
|
("exterior", "layeroptionext"): (1600, 900),
|
|
("interior", "layeroptionint"): (1600, 900),
|
|
("exterior", "showroom"): (768, 432),
|
|
("lifestyle", None): (1440, 810),
|
|
("inventory", None): (2900, 1280),
|
|
("carousel", "extra"): (678, 381),
|
|
("carousel", "powertrain"): (678, 381),
|
|
("carousel", "colour"): (148, 83),
|
|
("carousel", "bodystyle"): (678, 381),
|
|
("carousel", "series"): (678, 381),
|
|
("carousel", "trim"): (678, 381),
|
|
# Beltline resolution requirements (desktop and mobile variants)
|
|
("vehicleselector", "desktop"): (1920, 842),
|
|
("vehicleselector", "mobile"): (640, 428),
|
|
}
|
|
|
|
mec_map = {
|
|
("exterior", None): (1600, 1600),
|
|
("interior", None): (1600, 1600),
|
|
("exterior", "layeroptionext"): (1600, 1600),
|
|
("interior", "layeroptionint"): (1600, 1600),
|
|
("exterior", "showroom"): (768, 432), # Same as BAU
|
|
("lifestyle", None): (1440, 810), # Same for both BAU and MEC
|
|
("inventory", None): (2900, 1280), # Same for both BAU and MEC
|
|
("carousel", "extra"): (1280, 720),
|
|
("carousel", "powertrain"): (1600, 1600),
|
|
("carousel", "colour"): (148, 83), # Not changed
|
|
("carousel", "bodystyle"): (678, 381),
|
|
("carousel", "series"): (678, 381),
|
|
("carousel", "trim"): (678, 381),
|
|
# Beltline resolution requirements (desktop and mobile variants)
|
|
("vehicleselector", "desktop"): (1920, 842),
|
|
("vehicleselector", "mobile"): (640, 428),
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Helper to decide which resolution map to use for a given item.
|
|
# Even if the entire pack is MEC, we might override to BAU for certain items.
|
|
# -------------------------------------------------------------------------
|
|
def get_resolution_map_for_item(viewtype, imagetype, item_experience_condition, pack_experience_type):
|
|
"""
|
|
Returns (resolution_map, used_experience_type_string).
|
|
|
|
If the pack is MEC, we override to BAU when:
|
|
- viewtype in ["exterior", "interior"] AND no experienceCondition
|
|
OR
|
|
- imagetype == "powertrain"
|
|
Otherwise, use the pack_experience_type's map (MEC or BAU).
|
|
"""
|
|
if pack_experience_type == "MEC":
|
|
# Condition #1: viewtype is exterior/interior & no experienceCondition
|
|
# Condition #2: imagetype is powertrain
|
|
if ((item_experience_condition is None and viewtype in ("exterior", "interior"))
|
|
or imagetype == "powertrain"):
|
|
return bau_map, "BAU"
|
|
else:
|
|
return mec_map, "MEC"
|
|
else:
|
|
# If pack is BAU, always BAU
|
|
return bau_map, "BAU"
|
|
|
|
def get_required_resolution(conditions, pack_experience_type):
|
|
"""
|
|
Determine the required resolution for the item based on the *actual*
|
|
resolution map used, which might be MEC or BAU due to overrides.
|
|
Returns ( (width, height), used_experience_type ).
|
|
"""
|
|
viewtype = conditions.get("viewtype")
|
|
imagetype = conditions.get("imagetype", None)
|
|
variant = conditions.get("variant")
|
|
item_experience_condition = conditions.get("experienceCondition")
|
|
|
|
resolution_map, used_experience_type = get_resolution_map_for_item(
|
|
viewtype,
|
|
imagetype,
|
|
item_experience_condition,
|
|
pack_experience_type
|
|
)
|
|
|
|
key = (viewtype, imagetype)
|
|
|
|
# If the exact key isn't in the map, try a fallback without imagetype
|
|
if key not in resolution_map:
|
|
fallback_key = (viewtype, None)
|
|
if fallback_key in resolution_map:
|
|
return resolution_map[fallback_key], used_experience_type
|
|
else:
|
|
return None, used_experience_type
|
|
|
|
return resolution_map[key], used_experience_type
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Main check logic
|
|
# -------------------------------------------------------------------------
|
|
# We'll keep a dict to deduplicate failed entries by filename
|
|
# Key: filename, Value: single dict with all the fail details
|
|
failed_images_dict = {}
|
|
|
|
# Track skipped viewtype/imagetype combinations
|
|
skipped_types: Dict[str, Set[Tuple[str, Optional[str]]]] = {"image_resolution_check": set()}
|
|
|
|
for item in linkingrecord["items"]:
|
|
conditions = item.get("conditions", {})
|
|
viewtype = conditions.get("viewtype")
|
|
imagetype = conditions.get("imagetype")
|
|
|
|
# Skip items with missing viewtype
|
|
if not viewtype:
|
|
logging.debug(f"Skipping item with missing viewtype: {conditions}")
|
|
continue
|
|
|
|
required_res, used_experience_type = get_required_resolution(conditions, pack_experience_type)
|
|
|
|
if required_res is None:
|
|
# Unknown resolution requirement - skip this item and record it
|
|
record_skipped_type(skipped_types, "image_resolution_check", viewtype, imagetype)
|
|
continue
|
|
|
|
expected_width, expected_height = required_res
|
|
|
|
records = item.get("records", [])
|
|
for record in records:
|
|
assets = record.get("assets", [])
|
|
for asset in assets:
|
|
filename = asset.get("filename")
|
|
if not filename:
|
|
continue
|
|
|
|
image_path = os.path.join(working_dir, filename)
|
|
if not os.path.exists(image_path):
|
|
# Ignore missing files for this check
|
|
continue
|
|
|
|
# Open image and check resolution
|
|
try:
|
|
with Image.open(image_path) as img:
|
|
width, height = img.size
|
|
except Exception:
|
|
# Can't open or read the file => skip
|
|
continue
|
|
|
|
if width != expected_width or height != expected_height:
|
|
# Only add an entry if this filename not yet in the dict
|
|
if filename not in failed_images_dict:
|
|
failed_images_dict[filename] = {
|
|
"filename": filename,
|
|
"viewtype": conditions.get("viewtype"),
|
|
"imagetype": conditions.get("imagetype"),
|
|
"variant": conditions.get("variant"),
|
|
"used_experience_type": used_experience_type,
|
|
"expected_resolution": f"{expected_width}x{expected_height}",
|
|
"actual_resolution": f"{width}x{height}"
|
|
}
|
|
|
|
# Convert dict of failures to a list
|
|
if failed_images_dict:
|
|
result = {
|
|
"status": "failed",
|
|
"details": {
|
|
"message": f"{len(failed_images_dict)} images do not match their expected resolution.",
|
|
"failed_images": list(failed_images_dict.values()),
|
|
"pack_type": pack_experience_type
|
|
}
|
|
}
|
|
|
|
# Add skipped types to the result if any were found
|
|
if skipped_types["image_resolution_check"]:
|
|
result["details"]["skipped_types_message"] = f"{len(skipped_types['image_resolution_check'])} unknown viewtype/imagetype combinations were skipped."
|
|
result["details"]["skipped_types"] = [
|
|
{"viewtype": vt, "imagetype": it if it is not None else "None"}
|
|
for vt, it in skipped_types["image_resolution_check"]
|
|
]
|
|
|
|
return result
|
|
|
|
# If we skipped any types, add that to the successful result
|
|
if skipped_types["image_resolution_check"]:
|
|
result = prepare_skipped_result(skipped_types, "image_resolution_check")
|
|
result["details"]["pack_type"] = pack_experience_type
|
|
return result
|
|
|
|
return {
|
|
"status": "passed",
|
|
"details": {
|
|
"message": "All present images match their required resolution.",
|
|
"pack_type": pack_experience_type
|
|
}
|
|
}
|