doocus first ver
This commit is contained in:
79
doocus/extractors/media.py
Normal file
79
doocus/extractors/media.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Media-family extractor: mp4.
|
||||
|
||||
doocus does not transcribe here. It probes container metadata via ffprobe, grabs
|
||||
a thumbnail via ffmpeg, and records a pointer delegating full transcription to
|
||||
the meetus pipeline (process_meeting.py). Meeting videos are thus discoverable
|
||||
and previewable in doocus while their transcript lives in meetus.
|
||||
"""
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from .base import ExtractionResult
|
||||
|
||||
EXTRACTOR_ID = "media/ffprobe@1"
|
||||
|
||||
|
||||
def _ffprobe(source: Path) -> dict:
|
||||
if not shutil.which("ffprobe"):
|
||||
return {}
|
||||
cmd = ["ffprobe", "-v", "quiet", "-print_format", "json",
|
||||
"-show_format", "-show_streams", str(source)]
|
||||
try:
|
||||
out = subprocess.run(cmd, capture_output=True, text=True, check=True).stdout
|
||||
return json.loads(out)
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def _thumbnail(source: Path, at_seconds: float = 1.0) -> bytes:
|
||||
if not shutil.which("ffmpeg"):
|
||||
return b""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
|
||||
tmp_path = Path(tmp.name)
|
||||
try:
|
||||
cmd = ["ffmpeg", "-v", "quiet", "-y", "-ss", str(at_seconds),
|
||||
"-i", str(source), "-frames:v", "1", "-vf", "scale=640:-1",
|
||||
str(tmp_path)]
|
||||
subprocess.run(cmd, check=True)
|
||||
return tmp_path.read_bytes() if tmp_path.exists() else b""
|
||||
except subprocess.CalledProcessError:
|
||||
return b""
|
||||
finally:
|
||||
tmp_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def extract(source: Path, options: dict) -> ExtractionResult:
|
||||
warnings = []
|
||||
probe = _ffprobe(source)
|
||||
metadata = {"delegate": "meetus", "delegate_hint": "run process_meeting.py for transcript"}
|
||||
|
||||
fmt = probe.get("format", {})
|
||||
if fmt:
|
||||
if "duration" in fmt:
|
||||
metadata["duration_seconds"] = float(fmt["duration"])
|
||||
tags = fmt.get("tags", {})
|
||||
if "creation_time" in tags:
|
||||
metadata["created"] = tags["creation_time"]
|
||||
video_streams = [s for s in probe.get("streams", []) if s.get("codec_type") == "video"]
|
||||
if video_streams:
|
||||
v = video_streams[0]
|
||||
metadata["width"], metadata["height"] = v.get("width"), v.get("height")
|
||||
if not probe:
|
||||
warnings.append("ffprobe unavailable or failed; metadata limited")
|
||||
|
||||
thumb = _thumbnail(source) or None
|
||||
if thumb is None:
|
||||
warnings.append("thumbnail not generated (ffmpeg unavailable or failed)")
|
||||
|
||||
dur = metadata.get("duration_seconds")
|
||||
content = (f"_Video {source.name}"
|
||||
+ (f", {dur:.0f}s" if dur else "")
|
||||
+ ". Transcript is produced by the meetus pipeline "
|
||||
"(`process_meeting.py`), not doocus._\n")
|
||||
|
||||
return ExtractionResult(content=content, metadata=metadata, thumb=thumb,
|
||||
warnings=warnings, extractor=EXTRACTOR_ID)
|
||||
Reference in New Issue
Block a user