from pathlib import Path
from zipfile import ZipFile
from lxml import etree as E
from PIL import Image,ImageOps,ImageDraw
import json,posixpath,io
ROOT=Path(__file__).resolve().parents[1]
SRC=Path('/Users/jack/Downloads/标准产品材料 202609/PPT/政企版智慧食堂解决方案-一平台 3 系统 20260913.pptx')
NS={'a':'http://schemas.openxmlformats.org/drawingml/2006/main','p':'http://schemas.openxmlformats.org/presentationml/2006/main','r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}
rows=[]
with ZipFile(SRC) as z:
 for num in [23,25,27,28,29,30,31,32,33,34,35,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53]:
  rels={r.get('Id'):posixpath.normpath(posixpath.join('ppt/slides',r.get('Target'))) for r in E.fromstring(z.read(f'ppt/slides/_rels/slide{num}.xml.rels')) if r.get('Type','').endswith('/image')}
  xml=E.fromstring(z.read(f'ppt/slides/slide{num}.xml'))
  for j,pic in enumerate(xml.findall('.//p:pic',NS)):
   b=pic.find('.//a:blip',NS)
   if b is None:continue
   target=rels.get(b.get('{'+NS['r']+'}embed'))
   if not target:continue
   try:
    raw=z.read(target);im=Image.open(io.BytesIO(raw))
   except:continue
   if im.width<100 or im.height<60:continue
   dest=ROOT/'assets'/f's{num:03d}-p{j:02d}{Path(target).suffix}'
   dest.write_bytes(raw)
   off=pic.find('.//a:xfrm/a:off',NS);ext=pic.find('.//a:xfrm/a:ext',NS)
   crop=pic.find('.//a:srcRect',NS)
   rows.append(dict(slide=num,pic=j,path=str(dest.relative_to(ROOT)),zip_member=target,width=im.width,height=im.height,source_crop=dict(crop.attrib) if crop is not None else {},source_x=int(off.get('x'))/914400 if off is not None else None,source_y=int(off.get('y'))/914400 if off is not None else None))
# Phone screenshots can be stored in shape fills, not only p:pic.
with ZipFile(SRC) as z:
 for num in [47,49]:
  for r in E.fromstring(z.read(f'ppt/slides/_rels/slide{num}.xml.rels')):
   if not r.get('Type','').endswith('/image'):continue
   target=posixpath.normpath(posixpath.join('ppt/slides',r.get('Target')))
   try:
    raw=z.read(target);im=Image.open(io.BytesIO(raw))
   except:continue
   dest=ROOT/'assets'/f's{num}-r{r.get("Id")}{Path(target).suffix}'
   dest.write_bytes(raw)
   rows.append(dict(slide=num,pic=r.get('Id'),path=str(dest.relative_to(ROOT)),zip_member=target,width=im.width,height=im.height,source_crop={},source_x=None,source_y=None))
(ROOT/'asset-manifest.json').write_text(json.dumps(rows,ensure_ascii=False,indent=2))
for k in range(0,len(rows),35):
 batch=rows[k:k+35];sheet=Image.new('RGB',(1500,180*7),(239,239,239));d=ImageDraw.Draw(sheet)
 for j,r in enumerate(batch):
  im=Image.open(ROOT/r['path']).convert('RGBA');bg=Image.new('RGBA',im.size,'white');bg.alpha_composite(im);im=bg.convert('RGB');im.thumbnail((288,145))
  x=(j%5)*300;y=(j//5)*180;sheet.paste(im,(x+(300-im.width)//2,y+22));d.text((x+8,y+4),f"S{r['slide']} P{r['pic']} {r['width']}x{r['height']}",fill='black')
 sheet.save(ROOT/'preview'/f'assets-{k//35+1}.jpg')
print('extracted',len(rows),'images')
