#!/usr/bin/env python3 """ DJI Returns Image Viewer -- Generator Run this whenever you add new images to the Images/ folder. New folders are auto-detected. Edit CATEGORY_CONFIG to customise labels. """ import os, json, sys BASE = os.path.dirname(os.path.abspath(__file__)) IMAGES_DIR = os.path.join(BASE, 'Images') OUTPUT_HTML = os.path.join(BASE, 'DJI_Returns_Image_Viewer.html') TEMPLATE_FILE = os.path.join(BASE, 'viewer_template.html') CATEGORY_CONFIG = { 'Acceptable Damage': {'label': 'Acceptable Damage', 'section': 'accept', 'dot': '#2acc94', 'desc': 'Minor wear -- acceptable for resale'}, 'Acceptable Damage/RC': {'label': 'Acceptable -- RC Controllers','section': 'accept', 'dot': '#50f7bd', 'desc': 'Controller scuffs and minor marks'}, 'Excessive Crash Damage': {'label': 'Excessive Crash Damage', 'section': 'reject', 'dot': '#ff4d4d', 'desc': 'Automatic reject'}, 'Excessive Physical Damage': {'label': 'Excessive Physical Damage', 'section': 'reject', 'dot': '#ff6622', 'desc': 'Structural damage'}, 'Fire': {'label': 'Fire / Burn Damage', 'section': 'reject', 'dot': '#ff7700', 'desc': 'Fire or thermal damage'}, 'Mini 3-4Pro Cracked Gimbals': {'label': 'Cracked Gimbals', 'section': 'reject', 'dot': '#ff6b35', 'desc': 'Mini 3 / 4 Pro gimbal damage'}, 'P3 Gimbal Damage': {'label': 'P3 Gimbal Damage', 'section': 'reject', 'dot': '#ff6b35', 'desc': 'Phantom 3 gimbal'}, 'Subtle But Rejectable': {'label': 'Subtle But Rejectable', 'section': 'reject', 'dot': '#ffaa00', 'desc': 'Non-obvious -- still a reject'}, 'Tampering': {'label': 'Tampering / Modified', 'section': 'reject', 'dot': '#cc44ff', 'desc': 'Signs of tampering'}, 'Very Worn': {'label': 'Very Worn', 'section': 'borderline', 'dot': '#ffcc00', 'desc': 'Heavy use -- borderline case'}, 'Water Damage': {'label': 'Water Damage', 'section': 'reject', 'dot': '#3eafff', 'desc': 'Water ingress -- automatic reject'}, 'Strange': {'label': 'Strange / Unusual', 'section': 'reject', 'dot': '#9966cc', 'desc': 'Unusual damage patterns'}, 'Strange/mb_removed': {'label': 'Mainboard Removed', 'section': 'reject', 'dot': '#cc66aa', 'desc': 'Components stripped'}, 'Mavic Mini-4k': {'label': 'Mavic Mini 4K Ref', 'section': 'reference', 'dot': '#4488cc', 'desc': 'Reference images'}, 'Retail Images': {'label': 'All Retail Images', 'section': 'reference', 'dot': '#239aee', 'desc': 'Product retail shots'}, 'Retail Images/Action': {'label': 'Action Cameras', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Action 4 / 5 / 6'}, 'Retail Images/Avata': {'label': 'Avata', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Avata 2 range'}, 'Retail Images/Flip': {'label': 'Flip', 'section': 'reference', 'dot': '#2acc94', 'desc': 'DJI Flip range'}, 'Retail Images/Lito': {'label': 'Lito 1 / X1', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Entry-level 249g drones'}, 'Retail Images/Mini': {'label': 'Mini Series', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Mini 2 / 3 / 4 Pro / 5 Pro'}, 'Retail Images/Neo': {'label': 'Neo', 'section': 'reference', 'dot': '#2acc94', 'desc': 'DJI Neo / Neo 2'}, 'Retail Images/OM': {'label': 'Osmo Mobile', 'section': 'reference', 'dot': '#2acc94', 'desc': 'OM7 / OM7P / OM8 / OM8P'}, 'Retail Images/Osmo 360': {'label': 'Osmo 360', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Osmo 360 camera'}, 'Retail Images/Osmo Nano': {'label': 'Osmo Nano', 'section': 'reference', 'dot': '#2acc94', 'desc': 'Osmo Nano camera'}, } SECTION_ORDER = ['accept','reject','borderline','reference'] IMAGE_EXTS = {'.jpg','.jpeg','.png','.webp','.gif','.bmp'} def scan(): results = [] if not os.path.isdir(IMAGES_DIR): print(f'ERROR: Images folder not found at {IMAGES_DIR}'); sys.exit(1) for dirpath, dirnames, filenames in os.walk(IMAGES_DIR): dirnames.sort() for fname in sorted(filenames): if os.path.splitext(fname)[1].lower() in IMAGE_EXTS: rel = os.path.relpath(os.path.join(dirpath, fname), BASE).replace('\\\\', '/') cat_key = os.path.relpath(dirpath, IMAGES_DIR).replace('\\\\', '/') if cat_key == '.': cat_key = '' results.append({'path': rel, 'cat': cat_key}) return results def build_cat_meta(images): seen = {} for img in images: k = img['cat'] if k not in seen: cfg = CATEGORY_CONFIG.get(k) if cfg: seen[k] = dict(cfg, key=k, count=0) else: label = os.path.basename(k) if k else 'Uncategorised' seen[k] = {'key':k,'label':label,'section':'reference','dot':'#888','desc':'Auto-detected folder','count':0} seen[k]['count'] += 1 def sort_key(c): try: si = SECTION_ORDER.index(c['section']) except ValueError: si = 99 return (si, c['label']) return sorted(seen.values(), key=sort_key) def main(): print('Scanning images...') images = scan() print(f' Found {len(images)} images') cats = build_cat_meta(images) print(f' Found {len(cats)} categories') for c in cats: print(f' [{c["section"]:10}] {c["key"]} ({c["count"]} images)') with open(TEMPLATE_FILE, 'r', encoding='utf-8') as f: template = f.read() html = template.replace('__IMAGE_DATA__', json.dumps(images, indent=2)) html = html.replace('__CAT_DATA__', json.dumps(cats, indent=2)) with open(OUTPUT_HTML, 'w', encoding='utf-8') as f: f.write(html) print(f'\nDone! Saved: {OUTPUT_HTML}') print(f'Total: {len(images)} images across {len(cats)} categories') if __name__ == '__main__': main()