11
11
FFMPEG_BINARY = imageio_ffmpeg .get_ffmpeg_exe ()
12
12
13
13
14
- def cross_platform_popen_params (popen_params ):
15
- if os .name == "nt" :
16
- popen_params ["creationflags" ] = 0x08000000
17
- return popen_params
18
-
19
-
20
- class Ffmpeg :
21
-
14
+ class FFMpeg :
22
15
def __init__ (self , filename ):
23
-
24
- self .filename = filename
25
-
26
- duration , size = image_meta (filename )
16
+ duration , size = self .parse_metadata (filename )
27
17
self .area = reduce (int .__mul__ , size )
28
18
self .bytes = self .area * 4
19
+ self .filename = filename
29
20
self .duration = duration
30
21
self .size = size
31
22
23
+ @staticmethod
24
+ def cross_platform_popen_params (bufsize = 100000 ):
25
+ popen_params = {
26
+ "bufsize" : bufsize ,
27
+ "stdout" : subprocess .PIPE ,
28
+ "stderr" : subprocess .PIPE ,
29
+ "stdin" : subprocess .DEVNULL ,
30
+ }
31
+ if os .name == "nt" :
32
+ popen_params ["creationflags" ] = 0x08000000
33
+ return popen_params
34
+
35
+ @staticmethod
36
+ def _parse_duration (stdout ):
37
+ duration_regex = r"duration[^\n]+([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])"
38
+ time = re .search (duration_regex , stdout , re .M | re .I ).group (1 )
39
+ time = [float (part .replace ("," , "." )) for part in time .split (":" )]
40
+ return sum (mult * part for mult , part in zip ((3600 , 60 , 1 ), time ))
41
+
42
+ @staticmethod
43
+ def _parse_size (stdout ):
44
+ size_regex = r"\s(\d+)x(\d+)[,\s]"
45
+ match_size = re .search (size_regex , stdout , re .M )
46
+ return tuple (map (int , match_size .groups ()))
47
+
48
+ def parse_metadata (self , filename ):
49
+ meta = immeta (filename )
50
+ duration , size = meta .get ("duration" ), meta .get ("size" )
51
+
52
+ if not all ([duration , size ]):
53
+ cmd = [FFMPEG_BINARY , "-hide_banner" , "-i" , filename ]
54
+
55
+ popen_params = self .cross_platform_popen_params ()
56
+ process = subprocess .Popen (cmd , ** popen_params )
57
+ _ , stderr = process .communicate ()
58
+ stdout = stderr .decode ("utf8" , errors = "ignore" )
59
+
60
+ process .terminate ()
61
+ del process
62
+
63
+ duration = self ._parse_duration (stdout )
64
+ size = self ._parse_size (stdout )
65
+
66
+ return duration , size
67
+
32
68
@staticmethod
33
69
def frame_to_buffer (image ):
34
70
image = image .astype ("uint8" )
35
71
return Image .fromarray (image )
36
72
37
- def get_frame (self , start_time = 0 ):
73
+ def get_frame (self , start_time ):
38
74
if start_time != 0 :
39
75
offset = min (1 , start_time )
40
76
i_arg = [
41
- "-ss" ,
42
- "%.06f" % (start_time - offset ),
43
- "-i" ,
44
- self .filename ,
45
- "-ss" ,
46
- "%.06f" % offset ,
77
+ "-ss" , "%.06f" % (start_time - offset ),
78
+ "-i" , self .filename ,
79
+ "-ss" , "%.06f" % offset ,
47
80
]
48
81
else :
49
82
i_arg = ["-i" , self .filename ]
@@ -52,32 +85,17 @@ def get_frame(self, start_time=0):
52
85
[FFMPEG_BINARY ]
53
86
+ i_arg
54
87
+ [
55
- "-loglevel" ,
56
- "error" ,
57
- "-f" ,
58
- "image2pipe" ,
59
- "-vf" ,
60
- "scale=%d:%d" % tuple (self .size ),
61
- "-sws_flags" ,
62
- "bicubic" ,
63
- "-pix_fmt" ,
64
- "rgba" ,
65
- "-vcodec" ,
66
- "rawvideo" ,
67
- "-" ,
88
+ "-loglevel" , "error" ,
89
+ "-f" , "image2pipe" ,
90
+ "-vf" , "scale=%d:%d" % tuple (self .size ),
91
+ "-sws_flags" , "bicubic" ,
92
+ "-pix_fmt" , "rgba" ,
93
+ "-vcodec" , "rawvideo" , "-" ,
68
94
]
69
95
)
70
- popen_params = cross_platform_popen_params (
71
- {
72
- "bufsize" : self .bytes + 100 ,
73
- "stdout" : subprocess .PIPE ,
74
- "stderr" : subprocess .PIPE ,
75
- "stdin" : subprocess .DEVNULL ,
76
- }
77
- )
78
96
97
+ popen_params = self .cross_platform_popen_params (self .bytes + 100 )
79
98
process = subprocess .Popen (cmd , ** popen_params )
80
-
81
99
buffer = process .stdout .read (self .bytes )
82
100
83
101
process .terminate ()
@@ -91,49 +109,3 @@ def get_frame(self, start_time=0):
91
109
result .shape = (* self .size [::- 1 ], len (buffer ) // self .area )
92
110
93
111
return result
94
-
95
-
96
- def convert_to_seconds (time ):
97
- time = [float (part .replace ("," , "." )) for part in time .split (":" )]
98
- return sum (mult * part for mult , part in zip ((3600 , 60 , 1 ), time ))
99
-
100
-
101
- def parse_duration (stdout ):
102
- duration_regex = r"duration[^\n]+([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])"
103
- match_duration = re .search (duration_regex , stdout , re .M | re .I )
104
- return convert_to_seconds (match_duration .group (1 ))
105
-
106
-
107
- def parse_size (stdout ):
108
- size_regex = r"\s(\d+)x(\d+)[,\s]"
109
- match_size = re .search (size_regex , stdout , re .M )
110
- return tuple (map (int , match_size .groups ()))
111
-
112
-
113
- def image_meta (filename ):
114
- meta = immeta (filename )
115
- duration , size = meta .get ("duration" ), meta .get ("size" )
116
-
117
- if not all ([duration , size ]):
118
- cmd = [FFMPEG_BINARY , "-hide_banner" , "-i" , filename ]
119
-
120
- popen_params = cross_platform_popen_params (
121
- {
122
- "bufsize" : 10 ** 5 ,
123
- "stdout" : subprocess .PIPE ,
124
- "stderr" : subprocess .PIPE ,
125
- "stdin" : subprocess .DEVNULL ,
126
- }
127
- )
128
-
129
- process = subprocess .Popen (cmd , ** popen_params )
130
- _ , stderr = process .communicate ()
131
- stdout = stderr .decode ("utf8" , errors = "ignore" )
132
-
133
- process .terminate ()
134
- del process
135
-
136
- duration = parse_duration (stdout )
137
- size = parse_size (stdout )
138
-
139
- return duration , size
0 commit comments