| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- """媒体本地落盘:把图文帖的图片、视频帖的整段 mp4 存到 data/<run_id>/<platform>/<post_id>/。
- 解耦设计:仅当 settings.data_dir 非空时调用;视频落盘在 extract_video 内(复用其内存
- 字节),这里只管图片下载 + 卡片 url 改写为本地 /data 路径。
- """
- from __future__ import annotations
- import datetime
- import logging
- from dataclasses import dataclass
- from pathlib import Path
- from typing import Callable
- from creation_knowledge.integrations.video_extract import _default_download
- from core.models import Post
- logger = logging.getLogger(__name__)
- Downloader = Callable[[str, str], bytes]
- def run_id() -> str:
- """运行批次 id,默认时间戳到秒。"""
- return datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
- @dataclass
- class MediaTarget:
- dir: Path # 本地目录 data/<run_id>/<platform>/<post_id>/
- public_base: str # 浏览器前缀 /data/<run_id>/<platform>/<post_id>
- def media_target(data_dir: str, batch: str, platform: str, post_id: str) -> MediaTarget:
- rel = f"{batch}/{platform}/{post_id}"
- return MediaTarget(dir=Path(data_dir) / rel, public_base=f"/data/{rel}")
- def _ext_from_bytes(b: bytes) -> str:
- """按文件头嗅探扩展名(不依赖 Content-Type,便于注入式测试)。"""
- if b[:4] == b"\x89PNG":
- return ".png"
- if b[:3] == b"GIF":
- return ".gif"
- if b[:4] == b"RIFF" and b[8:12] == b"WEBP":
- return ".webp"
- return ".jpg"
- def save_images(
- post: Post,
- media_dir: Path,
- public_base: str,
- *,
- downloader: Downloader = _default_download,
- ) -> None:
- """下载图文帖每张远程图片 → media_dir/image_<index>.<ext>,并把 card.url 改写为本地路径。
- 单张失败只告警跳过(不影响其余卡片与整条流水线)。
- """
- media_dir = Path(media_dir)
- for card in post.cards:
- if card.kind != "image" or not card.url or not card.url.startswith("http"):
- continue
- try:
- data = downloader(card.url, post.platform)
- except Exception as exc: # 单张图失败不致命
- logger.warning("图片下载失败 idx=%s %s: %s", card.index, card.url, exc)
- continue
- if not data:
- continue
- media_dir.mkdir(parents=True, exist_ok=True)
- fname = f"image_{card.index}{_ext_from_bytes(data)}"
- (media_dir / fname).write_bytes(data)
- card.url = f"{public_base}/{fname}"
|