@@ -274,3 +274,74 @@ def video_from(
274274 return VideoPart (frames = [frame .image for frame in frames ], fps = fps , mime = mime )
275275 finally :
276276 vs .cleanup ()
277+
278+
279+ # ---------------------------------------------------------------------------
280+ # Bare-path / URL coercion
281+ # ---------------------------------------------------------------------------
282+
283+ # Extension → media kind, for the common cases where mimetypes can't help
284+ # (or guesses differently across platforms).
285+ _AUDIO_EXTS = frozenset ({
286+ ".mp3" , ".wav" , ".flac" , ".ogg" , ".oga" , ".m4a" , ".aac" , ".opus" , ".webm" , ".weba" ,
287+ })
288+ _VIDEO_EXTS = frozenset ({
289+ ".mp4" , ".mov" , ".avi" , ".mkv" , ".webm" , ".m4v" , ".mpeg" , ".mpg" , ".wmv" , ".flv" ,
290+ })
291+ _IMAGE_EXTS = frozenset ({
292+ ".png" , ".jpg" , ".jpeg" , ".gif" , ".webp" , ".bmp" , ".tiff" , ".tif" , ".heic" , ".heif" ,
293+ })
294+
295+
296+ def _media_kind_from_name (name : str ) -> str | None :
297+ """Best-effort image/audio/video classification from a path or URL string.
298+
299+ Returns ``"image"``, ``"audio"``, ``"video"`` or ``None`` (unknown).
300+ """
301+ # Strip any URL query/fragment before looking at the extension.
302+ base = name .split ("?" , 1 )[0 ].split ("#" , 1 )[0 ]
303+ ext = os .path .splitext (base )[1 ].lower ()
304+ # ``.webm`` is overloaded (audio or video); the extension maps are checked
305+ # video-first so a bare ``clip.webm`` becomes a VideoPart.
306+ if ext in _VIDEO_EXTS :
307+ return "video"
308+ if ext in _AUDIO_EXTS :
309+ return "audio"
310+ if ext in _IMAGE_EXTS :
311+ return "image"
312+ guessed , _ = mimetypes .guess_type (base )
313+ if guessed :
314+ top = guessed .split ("/" , 1 )[0 ]
315+ if top in ("image" , "audio" , "video" ):
316+ return top
317+ return None
318+
319+
320+ def part_from (source : str | Path ) -> ImagePart | AudioPart | VideoPart :
321+ """Wrap a bare file path or URL as the matching multimodal ContentPart.
322+
323+ Detects image/audio/video from the file extension (falling back to the
324+ MIME type) and delegates to :func:`image_from`, :func:`audio_from`, or
325+ :func:`video_from`. Use this when you have a path/URL and don't want to pick
326+ the specific helper yourself; :meth:`Agent.run` applies it automatically to
327+ bare paths passed in ``inputs=``.
328+
329+ Raises:
330+ InvalidMultimodalContent: if the media kind can't be determined from the
331+ name — pass ``image_from(...)`` / ``audio_from(...)`` /
332+ ``video_from(...)`` explicitly in that case.
333+ """
334+ name = str (source )
335+ kind = _media_kind_from_name (name )
336+ if kind == "image" :
337+ return image_from (source )
338+ if kind == "audio" :
339+ return audio_from (source )
340+ if kind == "video" :
341+ return video_from (source )
342+ raise InvalidMultimodalContent (
343+ "source" ,
344+ f"Could not infer the media type of { name !r} from its extension. "
345+ "Wrap it explicitly with image_from(...), audio_from(...), or "
346+ "video_from(...) (importable from effgen)." ,
347+ )
0 commit comments