Skip to content

Commit 365e21b

Browse files
authored
Animated webp support (#39)
* add animated_webp_support decorator * fix mypy * revise * pre-commit * revise * add test case * fix format * update snapshot * update snapshot * update snapshot * fix snapshot * support uri as http url * update snapshot * manual fix snapshot * fix snapshot * fix * check webpmux installed before call webpmux cmd * format * rm webpmux check * refactor * format * update snapshot
1 parent af049be commit 365e21b

File tree

6 files changed

+213
-1
lines changed

6 files changed

+213
-1
lines changed

src/ffmpeg_media_type/tests/__snapshots__/6.0/test_info.ambr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@
109109
'width': 4716,
110110
})
111111
# ---
112+
# name: test_detect[lionic_vp8x_1109.webp][lionic_vp8x_1109.webp]
113+
dict({
114+
'duration': 0.0,
115+
'format': 'webp_pipe',
116+
'height': 520,
117+
'size': 344360,
118+
'suggest_ext': 'webp',
119+
'type': 'image',
120+
'width': 1218,
121+
})
122+
# ---
112123
# name: test_detect[maxresdefault.jpg-sdafdeagwdsadf][maxresdefault.jpg-sdafdeagwdsadf]
113124
dict({
114125
'duration': 0.0,

src/ffmpeg_media_type/utils/ffmpeg.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import json
22
import os
33
import re
4-
from functools import lru_cache
4+
import tempfile
5+
from collections.abc import Callable
6+
from functools import lru_cache, wraps
57
from pathlib import Path
68
from typing import Any, Literal
79

10+
import requests
811
from pydantic import BaseModel, Field
912

1013
from .shell import call
@@ -87,6 +90,7 @@ def _parse_muxer_info(content: str) -> dict[str, Any]:
8790

8891

8992
def _get_muxer_info(version: str, flag: str, codec: str, description: str) -> FFMpegSupport:
93+
9094
muxer_info = {
9195
"common_exts": [],
9296
"mime_type": "",
@@ -239,7 +243,30 @@ def get_ffmpeg_version(mode: Literal["major", "minor", "patch"] = "patch") -> st
239243
raise RuntimeError(f"FFmpeg version not found {result}") from e
240244

241245

246+
def animated_webp_support(func: Callable[[str], FFProbeInfo]) -> Callable[[str], FFProbeInfo]:
247+
@wraps(func)
248+
def wrapper(uri: str) -> FFProbeInfo:
249+
probe_info = func(uri)
250+
if probe_info.streams[0].height == 0 and probe_info.streams[0].width == 0 and probe_info.format.format_name == "webp_pipe":
251+
if uri.startswith("http"):
252+
response = requests.get(uri)
253+
response.raise_for_status()
254+
with tempfile.NamedTemporaryFile(mode="wb", suffix=".webp", delete=False) as f:
255+
f.write(response.content)
256+
uri = f.name
257+
258+
assert os.path.exists(uri)
259+
webpmux_command = ["webpmux", "-get", "frame", "1", uri, "-o", uri]
260+
call(webpmux_command)
261+
return func(uri)
262+
return probe_info
263+
264+
return wrapper
265+
266+
267+
@animated_webp_support
242268
def ffprobe(input_url: str) -> FFProbeInfo:
269+
243270
# Construct the FFprobe command with JSON output format
244271
ffprobe_cmd = get_ffprobe() + [
245272
"-v",

src/ffmpeg_media_type/utils/tests/__snapshots__/4.0/test_ffmpeg.ambr

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,60 @@
428428
]),
429429
})
430430
# ---
431+
# name: test_ffprobe_file[lionic_vp8x_1109.webp][lionic_vp8x_1109.webp]
432+
dict({
433+
'format': dict({
434+
'duration': None,
435+
'format_long_name': None,
436+
'format_name': 'webp_pipe',
437+
'nb_programs': 0,
438+
'nb_streams': 1,
439+
'probe_score': 99,
440+
'size': 344360,
441+
'start_time': None,
442+
}),
443+
'streams': list([
444+
dict({
445+
'avg_frame_rate': '0/0',
446+
'codec_long_name': 'unknown',
447+
'codec_name': 'webp',
448+
'codec_tag': '0x0000',
449+
'codec_tag_string': '[0][0][0][0]',
450+
'codec_time_base': '0/1',
451+
'codec_type': 'video',
452+
'coded_height': 520,
453+
'coded_width': 1218,
454+
'disposition': dict({
455+
'attached_pic': 0,
456+
'clean_effects': 0,
457+
'comment': 0,
458+
'default': 0,
459+
'dub': 0,
460+
'forced': 0,
461+
'hearing_impaired': 0,
462+
'karaoke': 0,
463+
'lyrics': 0,
464+
'original': 0,
465+
'timed_thumbnails': 0,
466+
'visual_impaired': 0,
467+
}),
468+
'has_b_frames': 0,
469+
'height': 520,
470+
'index': 0,
471+
'level': -99,
472+
'pix_fmt': 'argb',
473+
'profile': None,
474+
'r_frame_rate': '25/1',
475+
'refs': 1,
476+
'tags': dict({
477+
'rotate': 0,
478+
}),
479+
'time_base': '1/25',
480+
'width': 1218,
481+
}),
482+
]),
483+
})
484+
# ---
431485
# name: test_ffprobe_file[Cle9pQ5.jpg][Cle9pQ5.jpg]
432486
dict({
433487
'format': dict({

src/ffmpeg_media_type/utils/tests/__snapshots__/5.0/test_ffmpeg.ambr

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,66 @@
497497
]),
498498
})
499499
# ---
500+
# name: test_ffprobe_file[lionic_vp8x_1109.webp][lionic_vp8x_1109.webp]
501+
dict({
502+
'format': dict({
503+
'duration': None,
504+
'format_long_name': None,
505+
'format_name': 'webp_pipe',
506+
'nb_programs': 0,
507+
'nb_streams': 1,
508+
'probe_score': 99,
509+
'size': 344360,
510+
'start_time': None,
511+
}),
512+
'streams': list([
513+
dict({
514+
'avg_frame_rate': '25/1',
515+
'closed_captions': 0,
516+
'codec_long_name': 'unknown',
517+
'codec_name': 'webp',
518+
'codec_tag': '0x0000',
519+
'codec_tag_string': '[0][0][0][0]',
520+
'codec_type': 'video',
521+
'coded_height': 520,
522+
'coded_width': 1218,
523+
'disposition': dict({
524+
'attached_pic': 0,
525+
'captions': 0,
526+
'clean_effects': 0,
527+
'comment': 0,
528+
'default': 0,
529+
'dependent': 0,
530+
'descriptions': 0,
531+
'dub': 0,
532+
'forced': 0,
533+
'hearing_impaired': 0,
534+
'karaoke': 0,
535+
'lyrics': 0,
536+
'metadata': 0,
537+
'original': 0,
538+
'still_image': 0,
539+
'timed_thumbnails': 0,
540+
'visual_impaired': 0,
541+
}),
542+
'film_grain': 0,
543+
'has_b_frames': 0,
544+
'height': 520,
545+
'index': 0,
546+
'level': -99,
547+
'pix_fmt': 'argb',
548+
'profile': None,
549+
'r_frame_rate': '25/1',
550+
'refs': 1,
551+
'tags': dict({
552+
'rotate': 0,
553+
}),
554+
'time_base': '1/25',
555+
'width': 1218,
556+
}),
557+
]),
558+
})
559+
# ---
500560
# name: test_ffprobe_file[Cle9pQ5.jpg][Cle9pQ5.jpg]
501561
dict({
502562
'format': dict({

src/ffmpeg_media_type/utils/tests/__snapshots__/6.0/test_ffmpeg.ambr

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,66 @@
840840
]),
841841
})
842842
# ---
843+
# name: test_ffprobe_file[lionic_vp8x_1109.webp][lionic_vp8x_1109.webp]
844+
dict({
845+
'format': dict({
846+
'duration': None,
847+
'format_long_name': None,
848+
'format_name': 'webp_pipe',
849+
'nb_programs': 0,
850+
'nb_streams': 1,
851+
'probe_score': 99,
852+
'size': 344360,
853+
'start_time': None,
854+
}),
855+
'streams': list([
856+
dict({
857+
'avg_frame_rate': '25/1',
858+
'closed_captions': 0,
859+
'codec_long_name': 'unknown',
860+
'codec_name': 'webp',
861+
'codec_tag': '0x0000',
862+
'codec_tag_string': '[0][0][0][0]',
863+
'codec_type': 'video',
864+
'coded_height': 520,
865+
'coded_width': 1218,
866+
'disposition': dict({
867+
'attached_pic': 0,
868+
'captions': 0,
869+
'clean_effects': 0,
870+
'comment': 0,
871+
'default': 0,
872+
'dependent': 0,
873+
'descriptions': 0,
874+
'dub': 0,
875+
'forced': 0,
876+
'hearing_impaired': 0,
877+
'karaoke': 0,
878+
'lyrics': 0,
879+
'metadata': 0,
880+
'original': 0,
881+
'still_image': 0,
882+
'timed_thumbnails': 0,
883+
'visual_impaired': 0,
884+
}),
885+
'film_grain': 0,
886+
'has_b_frames': 0,
887+
'height': 520,
888+
'index': 0,
889+
'level': -99,
890+
'pix_fmt': 'argb',
891+
'profile': None,
892+
'r_frame_rate': '25/1',
893+
'refs': 1,
894+
'tags': dict({
895+
'rotate': 0,
896+
}),
897+
'time_base': '1/25',
898+
'width': 1218,
899+
}),
900+
]),
901+
})
902+
# ---
843903
# name: test_ffprobe_file[maxresdefault.jpg-sdafdeagwdsadf][maxresdefault.jpg-sdafdeagwdsadf]
844904
dict({
845905
'format': dict({
Binary file not shown.

0 commit comments

Comments
 (0)