image_service.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """image_service.py — 图片提取和处理服务"""
  2. import base64
  3. import json
  4. from typing import Dict, List
  5. from docx import Document
  6. from docx.enum.text import WD_ALIGN_PARAGRAPH
  7. def extract_images_from_word(doc: Document) -> List[Dict]:
  8. """从 Word 文档提取图片(paragraph_index, data_url, style: 宽高/对齐/段落样式)"""
  9. images = []
  10. # 建立 rel_id -> 图片数据映射
  11. image_parts = {}
  12. for rel in doc.part.rels.values():
  13. if "image" in rel.target_ref:
  14. image_parts[rel.rId] = {
  15. "blob": rel.target_part.blob,
  16. "content_type": rel.target_part.content_type
  17. }
  18. # 遍历段落查找图片
  19. for para_idx, paragraph in enumerate(doc.paragraphs):
  20. for run in paragraph.runs:
  21. # 查找 inline 图片(w:drawing 元素)
  22. for drawing in run._element.findall('.//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}drawing'):
  23. # 提取图片引用
  24. blip = drawing.find('.//{http://schemas.openxmlformats.org/drawingml/2006/main}blip')
  25. if blip is None:
  26. continue
  27. rel_id = blip.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
  28. if not rel_id or rel_id not in image_parts:
  29. continue
  30. # 提取尺寸(EMU 转厘米,1厘米 = 360000 EMU)
  31. extent = drawing.find('.//{http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing}extent')
  32. width_emu = int(extent.get('cx')) if extent is not None else 360000 * 10 # 默认 10cm
  33. height_emu = int(extent.get('cy')) if extent is not None else 360000 * 7 # 默认 7cm
  34. width_cm = round(width_emu / 360000, 2)
  35. height_cm = round(height_emu / 360000, 2)
  36. # 获取对齐方式
  37. align = 'left'
  38. if paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER:
  39. align = 'center'
  40. elif paragraph.alignment == WD_ALIGN_PARAGRAPH.RIGHT:
  41. align = 'right'
  42. # 获取段落样式名称
  43. para_style = paragraph.style.name if paragraph.style else "Normal"
  44. # Base64 编码
  45. image_blob = image_parts[rel_id]['blob']
  46. content_type = image_parts[rel_id]['content_type']
  47. b64_data = base64.b64encode(image_blob).decode('utf-8')
  48. data_url = f"data:{content_type};base64,{b64_data}"
  49. images.append({
  50. "paragraph_index": para_idx,
  51. "data_url": data_url,
  52. "style": {
  53. "width": width_cm,
  54. "height": height_cm,
  55. "align": align,
  56. "unit": "cm",
  57. "para_style": para_style # 新增:段落样式
  58. }
  59. })
  60. return images
  61. def create_image_markdown(data_url: str, style: Dict, alt: str = "图片") -> str:
  62. """生成带样式注释的图片 Markdown(<!-- img-style: {...} --> ![alt](data_url))"""
  63. style_json = json.dumps(style, ensure_ascii=False)
  64. return f'<!-- img-style: {style_json} -->\n![{alt}]({data_url})\n'