Files
Tools/generate-viewer/generate_viewer.py
T

95 lines
6.2 KiB
Python

#!/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()