QuickRouter · 人工操作教材 · 非目标产品 UI / 非模型会话日志
Gemini CLI Plan 模式 · 配置与操作材料
plan-request.md
Use /plan first. Plan an offline converter, max side 1600, no upscaling, alpha preserved, separate output, deterministic collision suffixes and dry-run. Do not implement until I approve the plan.
convert.py
import argparse,hashlib,json
from pathlib import Path
from PIL import Image
def convert(source,dest,dry=False):
source=Path(source).resolve();dest=Path(dest).resolve()
if source==dest or dest.is_relative_to(source):raise ValueError('output must be separate from input')
rows=[];reserved=set()
for p in sorted(source.iterdir()):
if p.suffix.lower() not in {'.png','.jpg','.jpeg'}:continue
target=dest/(p.stem+'.webp');n=1
while target.name in reserved or target.exists():target=dest/(p.stem+'-'+str(n)+'.webp');n+=1
reserved.add(target.name)
with Image.open(p) as im:
original=im.size;im=im.convert('RGBA' if 'A' in im.getbands() else 'RGB');im.thumbnail((1600,1600))
if not dry:dest.mkdir(parents=True,exist_ok=True);im.save(target,'WEBP',quality=85)
rows.append({'source':p.name,'output':target.name,'before':original,'after':im.size,'alpha':'A' in im.getbands(),'sourceSha':hashlib.sha256(p.read_bytes()).hexdigest()})
return rows
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--input',default='input');p.add_argument('--output',default='output');p.add_argument('--dry-run',action='store_true');a=p.parse_args();print(json.dumps(convert(a.input,a.output,a.dry_run),ensure_ascii=False,indent=2))