add generate-viewer - helper for work
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user