3737import shutil
3838import inspect
3939import sys
40+ import tempfile
4041import urllib.error
4142import urllib.parse
4243import urllib.request
@@ -10399,6 +10400,8 @@ class MTMDLoadedMedia:
1039910400 key: str
1040010401 bitmap: Any
1040110402 video_ctx: Optional[Any] = None
10403+ video_temp_path: Optional[Path] = None
10404+ video_callback: Optional[Any] = None
1040210405 video_frame_count: int = 0
1040310406 video_frames_used: int = 0
1040410407
@@ -10593,6 +10596,8 @@ def _create_loaded_media(
1059310596 media_bytes=media_bytes,
1059410597 )
1059510598 )
10599+ if media.kind == "video":
10600+ return self._create_loaded_video_media(media, media_bytes, key)
1059610601 buffer = (ctypes.c_uint8 * len(media_bytes)).from_buffer_copy(media_bytes)
1059710602 wrapper = mtmd_cpp.mtmd_helper_bitmap_init_from_buf_wrapper(
1059810603 self.ctx,
@@ -10618,6 +10623,73 @@ def _create_loaded_media(
1061810623 video_frame_count=video_frame_count,
1061910624 )
1062010625
10626+ @staticmethod
10627+ def _video_temp_suffix(media: MediaInput) -> str:
10628+ extension = (media.format or "mp4").lstrip(".").lower()
10629+ if not extension or any(not char.isalnum() for char in extension):
10630+ extension = "video"
10631+ return f".{extension}"
10632+
10633+ def _create_loaded_video_media(
10634+ self,
10635+ media: MediaInput,
10636+ media_bytes: bytes,
10637+ key: str,
10638+ ) -> MTMDLoadedMedia:
10639+ temp_file = tempfile.NamedTemporaryFile(
10640+ prefix="llama-cpp-python-mtmd-",
10641+ suffix=self._video_temp_suffix(media),
10642+ delete=False,
10643+ )
10644+ temp_path = Path(temp_file.name)
10645+ try:
10646+ with temp_file:
10647+ temp_file.write(media_bytes)
10648+ params = mtmd_cpp.mtmd_helper_video_init_params_default()
10649+ video_ctx = mtmd_cpp.mtmd_helper_video_init(
10650+ self.ctx,
10651+ str(temp_path).encode("utf-8"),
10652+ params,
10653+ )
10654+ if video_ctx is None:
10655+ raise CompletionRequestValidationError("failed to create MTMD video context")
10656+
10657+ def read_next(
10658+ _chunk_index: int,
10659+ _user_data: Any,
10660+ out_bitmap: Any,
10661+ out_text: Any,
10662+ ) -> int:
10663+ return int(mtmd_cpp.mtmd_helper_video_read_next(video_ctx, out_bitmap, out_text))
10664+
10665+ callback = mtmd_cpp.mtmd_bitmap_lazy_callback(read_next)
10666+ bitmap = mtmd_cpp.mtmd_bitmap_init_lazy(
10667+ self.ctx,
10668+ key.encode("utf-8"),
10669+ ctypes.c_void_p(),
10670+ callback,
10671+ )
10672+ if bitmap is None:
10673+ mtmd_cpp.mtmd_helper_video_free(video_ctx)
10674+ raise CompletionRequestValidationError("failed to create MTMD video bitmap")
10675+ video_info = mtmd_cpp.mtmd_helper_video_get_info(video_ctx)
10676+ return MTMDLoadedMedia(
10677+ media=media,
10678+ media_bytes=media_bytes,
10679+ key=key,
10680+ bitmap=bitmap,
10681+ video_ctx=video_ctx,
10682+ video_temp_path=temp_path,
10683+ video_callback=callback,
10684+ video_frame_count=max(0, int(video_info.n_frames)),
10685+ )
10686+ except Exception:
10687+ try:
10688+ temp_path.unlink()
10689+ except OSError:
10690+ pass
10691+ raise
10692+
1062110693 def _media_identity_tokens(
1062210694 self,
1062310695 kind: Literal["image", "audio", "video"],
@@ -10922,6 +10994,11 @@ def _build_prompt_plan_locked(
1092210994 mtmd_cpp.mtmd_bitmap_free(media.bitmap)
1092310995 if media.video_ctx:
1092410996 mtmd_cpp.mtmd_helper_video_free(media.video_ctx)
10997+ if media.video_temp_path is not None:
10998+ try:
10999+ media.video_temp_path.unlink()
11000+ except OSError:
11001+ pass
1092511002
1092611003
1092711004class Model:
0 commit comments