Skip to content

Commit e1c4980

Browse files
GH-5: Refactor the functions' positions (GH-9)
2 parents dfc135a + f619723 commit e1c4980

File tree

3 files changed

+62
-150
lines changed

3 files changed

+62
-150
lines changed

ffmpeg/__init__.py

Lines changed: 1 addition & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1 @@
1-
# import asyncio
2-
3-
import imageio
4-
from PIL import Image
5-
6-
7-
class Ffmpeg:
8-
9-
def __init__(self, filename):
10-
self.filename = filename
11-
self.reader = imageio.get_reader(filename)
12-
self.meta = self.reader.get_meta_data()
13-
self.duration = self.meta.get("duration")
14-
self.fps = self.meta.get("fps")
15-
16-
def get_frame(self, t):
17-
pos = int(self.fps * t + 0.00001) + 1
18-
return self.reader.get_data(pos)
19-
20-
def get_frame_buffer(self, t=0):
21-
image = self.get_frame(t)
22-
image = image.astype("uint8")
23-
return Image.fromarray(image)
24-
25-
def close(self):
26-
self.reader.close()
27-
28-
def __del__(self):
29-
self.close()
30-
31-
# class Ffmpeg:
32-
#
33-
# def __init__(self, filename):
34-
# self.filename = filename
35-
#
36-
# def get_frame(self, t):
37-
# pos = int(self.fps * t + 0.00001) + 1
38-
# return self.reader.get_data(pos)
39-
#
40-
# def get_frame_buffer(self, t=0):
41-
# # image = await asyncio.to_thread(self.get_frame, t)
42-
# image = self.get_frame(t)
43-
# image = image.astype("uint8")
44-
# return Image.fromarray(image)
45-
#
46-
# async def __aenter__(self):
47-
# self.reader = await asyncio.to_thread(
48-
# imageio.get_reader, self.filename)
49-
# self.meta = self.reader.get_meta_data()
50-
# self.duration = self.meta.get("duration")
51-
# self.fps = self.meta.get("fps")
52-
# return self
53-
#
54-
# async def __aexit__(self, *_):
55-
# self.close()
56-
#
57-
# def close(self):
58-
# self.reader.close()
59-
#
60-
# def __del__(self):
61-
# self.close()
1+
from .ffmpeg import FFMpeg

ffmpeg/ffmpeg.py

Lines changed: 59 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,72 @@
1111
FFMPEG_BINARY = imageio_ffmpeg.get_ffmpeg_exe()
1212

1313

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:
2215
def __init__(self, filename):
23-
24-
self.filename = filename
25-
26-
duration, size = image_meta(filename)
16+
duration, size = self.parse_metadata(filename)
2717
self.area = reduce(int.__mul__, size)
2818
self.bytes = self.area * 4
19+
self.filename = filename
2920
self.duration = duration
3021
self.size = size
3122

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+
3268
@staticmethod
3369
def frame_to_buffer(image):
3470
image = image.astype("uint8")
3571
return Image.fromarray(image)
3672

37-
def get_frame(self, start_time=0):
73+
def get_frame(self, start_time):
3874
if start_time != 0:
3975
offset = min(1, start_time)
4076
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,
4780
]
4881
else:
4982
i_arg = ["-i", self.filename]
@@ -52,32 +85,17 @@ def get_frame(self, start_time=0):
5285
[FFMPEG_BINARY]
5386
+ i_arg
5487
+ [
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", "-",
6894
]
6995
)
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-
)
7896

97+
popen_params = self.cross_platform_popen_params(self.bytes + 100)
7998
process = subprocess.Popen(cmd, **popen_params)
80-
8199
buffer = process.stdout.read(self.bytes)
82100

83101
process.terminate()
@@ -91,49 +109,3 @@ def get_frame(self, start_time=0):
91109
result.shape = (*self.size[::-1], len(buffer) // self.area)
92110

93111
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

thumbnails.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from PIL import Image
66

7-
from ffmpeg.ffmpeg import Ffmpeg
7+
from ffmpeg import FFMpeg
88

99
width, height = 300, 200
1010
interval = 20
@@ -44,7 +44,7 @@ def worker(video):
4444

4545

4646
async def main():
47-
fps = [Ffmpeg(fp) for fp in files]
47+
fps = [FFMpeg(fp) for fp in files]
4848

4949
with concurrent.futures.ProcessPoolExecutor() as executor:
5050
executor.map(worker, fps)

0 commit comments

Comments
 (0)