Skip to content

Commit 41730e0

Browse files
authored
Fix Invalid Argument and Broken Pipe warnings, and more (#36)
* allow ffmpeg_timeout to be None * tests check that no warnings are produces * forgot to add * add a test, put some test-snippets in there * lint * Fix Invalid Argument and Broken Pipe warnings * reduce amount of downloads during tests * verbose tests
1 parent 466921b commit 41730e0

File tree

8 files changed

+199
-58
lines changed

8 files changed

+199
-58
lines changed

imageio_ffmpeg/_io.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,11 @@ def read_frames(
238238
# So let's do similar to what communicate does, but without
239239
# reading stdout (which may block). It looks like only closing
240240
# stdout is enough (tried Windows+Linux), but let's play safe.
241-
p.stdin.write(b"q")
242-
p.stdin.close()
241+
# Found that writing to stdin can cause "Invalid argument" on
242+
# Windows # and "Broken Pipe" on Unix.
243+
# p.stdin.write(b"q") # commented out in v0.4.1
243244
p.stdout.close()
245+
p.stdin.close()
244246
except Exception as err: # pragma: no cover
245247
logger.warning("Error while attempting stop ffmpeg (r): " + str(err))
246248

@@ -271,7 +273,7 @@ def write_frames(
271273
codec=None,
272274
macro_block_size=16,
273275
ffmpeg_log_level="warning",
274-
ffmpeg_timeout=0,
276+
ffmpeg_timeout=None,
275277
input_params=None,
276278
output_params=None,
277279
):
@@ -306,7 +308,7 @@ def write_frames(
306308
to 1 to avoid block alignment, though this is not recommended.
307309
ffmpeg_log_level (str): The ffmpeg logging level. Default "warning".
308310
ffmpeg_timeout (float): Timeout in seconds to wait for ffmpeg process
309-
to finish. Value of 0 will wait forever (default). The time that
311+
to finish. Value of 0 or None will wait forever (default). The time that
310312
ffmpeg needs depends on CPU speed, compression, and frame size.
311313
input_params (list): Additional ffmpeg input command line parameters.
312314
output_params (list): Additional ffmpeg output command line parameters.
@@ -335,6 +337,7 @@ def write_frames(
335337
ffmpeg_log_level = ffmpeg_log_level or "warning"
336338
input_params = input_params or []
337339
output_params = output_params or []
340+
ffmpeg_timeout = ffmpeg_timeout or 0
338341

339342
floatish = float, int
340343
if isinstance(size, (tuple, list)):

tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
def test(ctx, cover=False):
2828
"""Perform unit tests. Use --cover to open a webbrowser to show coverage.
2929
"""
30-
cmd = [sys.executable, "-m", "pytest", "tests"]
30+
cmd = [sys.executable, "-m", "pytest", "tests", "-v"]
3131
cmd += ["--cov=" + LIBNAME, "--cov-report=term", "--cov-report=html"]
3232
ret_code = subprocess.call(cmd, cwd=ROOT_DIR)
3333
if ret_code:

tests/snippets.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Snippets of code that are hard to bring under test, but that can be
3+
used to manually test the behavior of imageip-ffmpeg in certain
4+
use-cases. Some may depend on imageio.
5+
"""
6+
7+
# %% Write a series of large frames
8+
9+
# In earlier versions of imageio-ffmpeg, the ffmpeg process was given a timeout
10+
# to complete, but this timeout must be longer for longer movies. The default
11+
# is now to wait for ffmpeg.
12+
13+
import os
14+
import numpy as np
15+
import imageio_ffmpeg
16+
17+
ims = [
18+
np.random.uniform(0, 255, size=(1000, 1000, 3)).astype(np.uint8) for i in range(10)
19+
]
20+
21+
filename = os.path.expanduser("~/Desktop/foo.mp4")
22+
w = imageio_ffmpeg.write_frames(filename, (1000, 1000), ffmpeg_timeout=0)
23+
w.send(None)
24+
for i in range(200):
25+
w.send(ims[i % 10])
26+
print(i)
27+
w.close()
28+
29+
30+
# %% Behavior of KeyboardInterrupt / Ctrl+C
31+
32+
import os
33+
import imageio_ffmpeg
34+
35+
filename = os.path.expanduser("~/.imageio/images/cockatoo.mp4")
36+
reader = imageio_ffmpeg.read_frames(filename)
37+
38+
meta = reader.__next__()
39+
40+
try:
41+
input("Do a manual KeyboardInterrupt now [Ctrl]+[c]")
42+
# Note: Raising an error with code won't trigger the original error.
43+
except BaseException as err:
44+
print(err)
45+
print("out1", len(reader.__next__()))
46+
print("out2", len(reader.__next__()))
47+
48+
print("closing")
49+
reader.close()
50+
print("closed")

tests/test_io.py

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,37 @@
1-
import os
1+
"""
2+
The main tests for the public API.
3+
"""
4+
5+
import time
26
import types
37
import tempfile
4-
import time
5-
from urllib.request import urlopen
6-
7-
from pytest import skip, raises
88

99
import imageio_ffmpeg
1010

11-
12-
if os.getenv("TRAVIS_OS_NAME") == "windows":
13-
skip(
14-
"Skip this on the Travis Windows run for now, see #408", allow_module_level=True
15-
)
16-
17-
18-
test_dir = tempfile.gettempdir()
19-
test_url1 = "https://raw.githubusercontent.com/imageio/imageio-binaries/master/images/cockatoo.mp4"
20-
test_url2 = "https://raw.githubusercontent.com/imageio/imageio-binaries/master/images/realshort.mp4"
21-
test_file1 = os.path.join(test_dir, "cockatoo.mp4")
22-
test_file2 = os.path.join(test_dir, "test.mp4")
23-
test_file3 = os.path.join(test_dir, "realshort.mp4")
11+
from pytest import skip, raises
12+
from testutils import no_warnings_allowed
13+
from testutils import ensure_test_files, test_dir, test_file1, test_file2, test_file3
2414

2515

2616
def setup_module():
27-
bb = urlopen(test_url1, timeout=5).read()
28-
with open(test_file1, "wb") as f:
29-
f.write(bb)
30-
bb = urlopen(test_url2, timeout=5).read()
31-
with open(test_file3, "wb") as f:
32-
f.write(bb)
17+
ensure_test_files()
3318

3419

20+
@no_warnings_allowed
3521
def test_ffmpeg_version():
3622
version = imageio_ffmpeg.get_ffmpeg_version()
3723
print("ffmpeg version", version)
3824
assert version > "3.0"
3925

4026

27+
@no_warnings_allowed
4128
def test_read_nframes():
4229
nframes, nsecs = imageio_ffmpeg.count_frames_and_secs(test_file1)
4330
assert nframes == 280
4431
assert 13.80 < nsecs < 13.99
4532

4633

34+
@no_warnings_allowed
4735
def test_reading1():
4836

4937
# Calling returns a generator
@@ -67,6 +55,7 @@ def test_reading1():
6755
assert count == 280
6856

6957

58+
@no_warnings_allowed
7059
def test_reading2():
7160
# Same as 1, but using other pixel format
7261

@@ -83,6 +72,7 @@ def test_reading2():
8372
assert count == 280
8473

8574

75+
@no_warnings_allowed
8676
def test_reading3():
8777
# Same as 1, but using other fps
8878

@@ -99,6 +89,7 @@ def test_reading3():
9989
assert 50 < count < 100 # because smaller fps, same duration
10090

10191

92+
@no_warnings_allowed
10293
def test_reading4():
10394
# Same as 1, but wrong, using an insane bpp, to invoke eof halfway a frame
10495

@@ -113,6 +104,7 @@ def test_reading4():
113104
assert "ffmpeg version" in msg # The log is included
114105

115106

107+
@no_warnings_allowed
116108
def test_reading5():
117109
# Same as 1, but using other pixel format and bits_per_pixel
118110
bits_per_pixel = 12
@@ -137,6 +129,7 @@ def test_reading5():
137129
assert count == 36
138130

139131

132+
@no_warnings_allowed
140133
def test_reading_invalid_video():
141134
"""
142135
Check whether invalid video is
@@ -156,6 +149,7 @@ def test_reading_invalid_video():
156149
assert end - start < 1, "Metadata extraction hangs"
157150

158151

152+
@no_warnings_allowed
159153
def test_write1():
160154

161155
for n in (1, 9, 14, 279, 280, 281):
@@ -184,6 +178,7 @@ def test_write1():
184178
assert count == n
185179

186180

181+
@no_warnings_allowed
187182
def test_write_pix_fmt_in():
188183

189184
sizes = []
@@ -204,6 +199,7 @@ def test_write_pix_fmt_in():
204199
assert sizes[0] <= sizes[1] <= sizes[2]
205200

206201

202+
@no_warnings_allowed
207203
def test_write_pix_fmt_out():
208204

209205
sizes = []
@@ -224,6 +220,7 @@ def test_write_pix_fmt_out():
224220
assert sizes[0] < sizes[1]
225221

226222

223+
@no_warnings_allowed
227224
def test_write_wmv():
228225
# Switch to MS friendly codec when writing .wmv files
229226

@@ -240,6 +237,7 @@ def test_write_wmv():
240237
assert meta["codec"].startswith(codec)
241238

242239

240+
@no_warnings_allowed
243241
def test_write_quality():
244242

245243
sizes = []
@@ -260,6 +258,7 @@ def test_write_quality():
260258
assert sizes[0] < sizes[1] < sizes[2]
261259

262260

261+
@no_warnings_allowed
263262
def test_write_bitrate():
264263

265264
# Mind that we send uniform images, so the difference is marginal
@@ -282,6 +281,7 @@ def test_write_bitrate():
282281
assert sizes[0] < sizes[1] < sizes[2]
283282

284283

284+
# @no_warnings_allowed --> will generate warnings abiut macro block size
285285
def test_write_macro_block_size():
286286

287287
frame_sizes = []
@@ -304,6 +304,7 @@ def test_write_macro_block_size():
304304
assert frame_sizes[1] == (40, 50)
305305

306306

307+
# @no_warnings_allowed --> Will generate a warning about killing ffmpeg
307308
def test_write_big_frames():
308309
"""Test that we give ffmpeg enough time to finish."""
309310
try:
@@ -343,6 +344,10 @@ def _write_frames(pixfmt, bpp, tout):
343344
nframes, nsecs = imageio_ffmpeg.count_frames_and_secs(test_file2)
344345
assert nframes == n
345346

347+
_write_frames("rgba", 4, None) # the default os to wait (since v0.4.0)
348+
nframes, nsecs = imageio_ffmpeg.count_frames_and_secs(test_file2)
349+
assert nframes == n
350+
346351

347352
if __name__ == "__main__":
348353
setup_module()

tests/test_parsing.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
# styletest: ignore E501
2-
""" Tests specific to parsing ffmpeg header.
2+
"""
3+
Tests specific to parsing ffmpeg header.
34
"""
45

5-
import os
6-
from pytest import skip
76
from imageio_ffmpeg._parsing import cvsecs, limit_lines, parse_ffmpeg_header
87

98

10-
if os.getenv("TRAVIS_OS_NAME") == "windows":
11-
skip(
12-
"Skip this on the Travis Windows run for now, see #408", allow_module_level=True
13-
)
14-
15-
169
def dedent(text, dedent=8):
1710
lines = [line[dedent:] for line in text.splitlines()]
1811
text = "\n".join(lines)

tests/test_special.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
""" Tests more special use-cases.
2+
"""
3+
4+
import gc
5+
import queue
6+
import threading
7+
8+
import imageio_ffmpeg
9+
10+
from testutils import ensure_test_files, test_file1
11+
12+
13+
def setup_module():
14+
ensure_test_files()
15+
16+
17+
def test_threading():
18+
# See issue #20
19+
20+
num_threads = 16
21+
num_frames = 5
22+
23+
def make_iterator(q, n):
24+
for i in range(n):
25+
gen = imageio_ffmpeg.read_frames(test_file1)
26+
gen.__next__() # meta data
27+
q.put(gen.__next__()) # first frame
28+
29+
q = queue.Queue()
30+
threads = []
31+
for i in range(num_threads):
32+
t = threading.Thread(target=make_iterator, args=(q, num_frames))
33+
t.daemon = True
34+
t.start()
35+
threads.append(t)
36+
37+
for i in range(num_threads * num_frames):
38+
print(i, end=" ")
39+
q.get()
40+
gc.collect() # this seems to help invoke the segfault earlier
41+
42+
43+
if __name__ == "__main__":
44+
# setup_module()
45+
test_threading()

0 commit comments

Comments
 (0)