Skip to content

Commit 75f3c1f

Browse files
committed
Fix export_video() for odd-sized videos
1 parent a7b1974 commit 75f3c1f

File tree

2 files changed

+30
-8
lines changed

2 files changed

+30
-8
lines changed

optimap/video/_export.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,9 @@ def export_video(
110110
cmap = "gray",
111111
vmin : float = None,
112112
vmax : float = None,
113-
ffmpeg_encoder : str = None,
114113
progress_bar : bool = True,
114+
ffmpeg_encoder : str = None,
115+
pad_mode : str = "edge",
115116
):
116117
"""Export a video numpy array to a video file (e.g. ``.mp4``) using `ffmpeg <https://www.ffmpeg.org>`_.
117118
@@ -145,11 +146,14 @@ def export_video(
145146
The minimum value for the colormap, by default None
146147
vmax : float, optional
147148
The maximum value for the colormap, by default None
149+
progress_bar : bool, optional
150+
Whether to show a progress bar, by default True
148151
ffmpeg_encoder : str, optional
149152
The ffmpeg encoder to use, by default ``'libx264'``. See :func:`set_default_ffmpeg_encoder` and
150153
:func:`set_ffmpeg_defaults` for more information.
151-
progress_bar : bool, optional
152-
Whether to show a progress bar, by default True
154+
pad_mode : str, optional
155+
Odd-sized videos need to be padded to even dimensions, otherwise ffmpeg will error. The padding mode to use,
156+
by default "edge". See :func:`numpy.pad` for more information.
153157
"""
154158
if isinstance(video, (str, os.PathLike)):
155159
filename, video = video, filename
@@ -178,6 +182,13 @@ def export_video(
178182

179183
if frame.dtype != np.uint8:
180184
frame = (frame * 255).astype(np.uint8)
185+
186+
# pad odd-sized frames to even dimension, otherwise ffmpeg will error
187+
if frame.shape[0] % 2 == 1:
188+
frame = np.pad(frame, ((0, 1), (0, 0), (0, 0)), mode=pad_mode)
189+
if frame.shape[1] % 2 == 1:
190+
frame = np.pad(frame, ((0, 0), (0, 1), (0, 0)), mode=pad_mode)
191+
181192
writer.writeFrame(frame)
182193
writer.close()
183194
print(f"video exported to {filename}")

tests/test_video_io.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,24 +148,35 @@ def test_export_video(tmpdir):
148148
assert mimetypes.guess_type(filename)[0] == "video/mp4"
149149

150150

151+
@pytest.mark.skipif(skvideo._HAS_FFMPEG == 0, reason="ffmpeg not installed")
152+
def test_export_video_uneven(tmpdir):
153+
om.video.set_default_ffmpeg_encoder("libx264")
154+
video = np.random.random((10, 15, 21)).astype(np.float32)
155+
filename = Path(tmpdir / "test.mp4")
156+
om.export_video(filename, video, vmin=0, vmax=1)
157+
assert filename.is_file() and filename.stat().st_size > 0
158+
assert mimetypes.guess_type(filename)[0] == "video/mp4"
159+
160+
151161
@pytest.mark.skipif(skvideo._HAS_FFMPEG == 0, reason="ffmpeg not installed")
152162
def test_export_video_overlay(tmpdir):
153163
video = np.random.random((10, 4, 4)).astype(np.float32)
154164
overlay = np.random.random((10, 4, 4)).astype(np.float32)
155-
filename = tmpdir / "test.mp4"
165+
filename = Path(tmpdir / "test.mp4")
156166
om.video.export_video_with_overlay(filename, video, overlay=overlay, vmin_base=0, vmax_base=1)
157-
assert Path(filename).is_file()
167+
assert filename.is_file() and filename.stat().st_size > 0
158168
assert mimetypes.guess_type(filename)[0] == "video/mp4"
159169

160170
om.video.export_video_with_overlay(filename, video, overlay=overlay, alpha=overlay)
161-
assert Path(filename).is_file()
171+
assert filename.is_file() and filename.stat().st_size > 0
162172
assert mimetypes.guess_type(filename)[0] == "video/mp4"
163173

174+
164175
@pytest.mark.skipif(skvideo._HAS_FFMPEG == 0, reason="ffmpeg not installed")
165176
def test_export_video_collage(tmpdir):
166177
videos = [np.random.random((10, 4, 4)).astype(np.float32) for _ in range(4)]
167-
filename = tmpdir / "test.mp4"
178+
filename = Path(tmpdir / "test.mp4")
168179

169180
om.video.export_video_collage(filename, videos, vmins=0, vmaxs=1, padding=10, ncols=2, padding_color="white")
170-
assert Path(filename).is_file()
181+
assert filename.is_file() and filename.stat().st_size > 0
171182
assert mimetypes.guess_type(filename)[0] == "video/mp4"

0 commit comments

Comments
 (0)