diff --git a/README.rst b/README.rst
index cf6dd3c1c3..b6536a7a77 100644
--- a/README.rst
+++ b/README.rst
@@ -90,6 +90,7 @@ Optional Dependencies
* `scikit-posthocs `_ Using interquartile range or generalized Extreme Studentized Deviate quality control tests
* `icartt `_ icartt is an ICARTT file format reader and writer for Python
* `PySP2 `_ PySP2 is a python package for reading and processing Single Particle Soot Photometer (SP2) datasets.
+* `MoviePy `_ MoviePy is a python package for creating movies from images
Installation
~~~~~~~~~~~~
diff --git a/act/tests/utils/test_io_utils.py b/act/tests/utils/test_io_utils.py
index ffe259974e..8f37ce44c4 100644
--- a/act/tests/utils/test_io_utils.py
+++ b/act/tests/utils/test_io_utils.py
@@ -1,12 +1,21 @@
import tempfile
from pathlib import Path
-from os import PathLike
+from os import PathLike, getcwd
from string import ascii_letters
import random
+import glob
+import pytest
+import os
import act
from act.tests import sample_files
+try:
+ import moviepy.video.io.ImageSequenceClip
+ MOVIEPY_AVAILABLE = True
+except ImportError:
+ MOVIEPY_AVAILABLE = False
+
def test_read_netcdf_gztarfiles():
with tempfile.TemporaryDirectory() as tmpdirname:
@@ -203,3 +212,15 @@ def test_gunzip():
assert file.endswith('.nc')
assert Path(unpack_filename).is_file() is False
+
+
+@pytest.mark.skipif(not MOVIEPY_AVAILABLE, reason='MoviePy is not installed.')
+def test_generate_movie():
+ files = [
+ 'https://github.com/ARM-DOE/ACT/blob/main/act/tests/plotting/baseline/test_contour.png?raw=true',
+ 'https://github.com/ARM-DOE/ACT/blob/main/act/tests/plotting/baseline/test_contour2.png?raw=true',
+ 'https://github.com/ARM-DOE/ACT/blob/main/act/tests/plotting/baseline/test_contourf.png?raw=true',
+ 'https://github.com/ARM-DOE/ACT/blob/main/act/tests/plotting/baseline/test_contourf2.png?raw=true'
+ ]
+ result = act.utils.generate_movie(files, fps=5)
+ assert str(result) == 'movie.mp4'
diff --git a/act/utils/__init__.py b/act/utils/__init__.py
index 867c0d8690..035bf99de3 100644
--- a/act/utils/__init__.py
+++ b/act/utils/__init__.py
@@ -49,7 +49,8 @@
'cleanup_files',
'is_gunzip_file',
'pack_gzip',
- 'unpack_gzip'
+ 'unpack_gzip',
+ 'generate_movie'
],
},
)
diff --git a/act/utils/io_utils.py b/act/utils/io_utils.py
index c7b4dcfe7a..8da106a19c 100644
--- a/act/utils/io_utils.py
+++ b/act/utils/io_utils.py
@@ -8,6 +8,12 @@
import shutil
import tempfile
+try:
+ import moviepy.video.io.ImageSequenceClip
+ MOVIEPY_AVAILABLE = True
+except ImportError:
+ MOVIEPY_AVAILABLE = False
+
def pack_tar(filenames, write_filename=None, write_directory=None, remove=False):
"""
@@ -276,3 +282,55 @@ def unpack_gzip(filename, write_directory=None, remove=False):
Path(filename).unlink()
return str(write_filename)
+
+
+def generate_movie(images, write_directory=None, write_filename=None, fps=10, codec=None, threads=None):
+ """
+ Creates a movie from a list of images
+
+ ...
+
+ Parameters
+ ----------
+ images : list
+ List of images in the correct order to make into a movie
+ write_directory : str, pahtlib.Path, list, None
+ Path to directory to place newly created gunzip file.
+ write_filename : str, pathlib.Path, None
+ Movie output filename
+ fps: int
+ Frames per second
+ codec: int
+ Codec to use for image encoding
+ threads: int
+ Number of threads to use for ffmpeg
+
+
+ Returns
+ -------
+ write_filename : str
+ Full path name of created gunzip file
+
+ """
+ if not MOVIEPY_AVAILABLE:
+ raise ImportError(
+ 'MoviePy needs to be installed on your system to make movies.'
+ )
+
+ if write_filename is None:
+ write_filename = 'movie.mp4'
+
+ if write_directory is not None:
+ write_directory = Path(write_directory)
+ write_directory.mkdir(parents=True, exist_ok=True)
+ write_filename = Path(write_filename).name
+ elif Path(write_filename).parent != Path('.'):
+ write_directory = Path(write_filename).parent
+ else:
+ write_directory = Path('.')
+
+ full_path = write_directory / write_filename
+ clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(images, fps=fps)
+ clip.write_videofile(str(full_path), codec=codec, threads=threads)
+
+ return full_path
diff --git a/continuous_integration/environment_actions.yml b/continuous_integration/environment_actions.yml
index 900525e6a0..0443eb0502 100644
--- a/continuous_integration/environment_actions.yml
+++ b/continuous_integration/environment_actions.yml
@@ -28,6 +28,7 @@ dependencies:
- lazy_loader
- cmweather
- arm-test-data
+ - moviepy
- pip:
- mpl2nc
- metpy
diff --git a/docs/environment_docs.yml b/docs/environment_docs.yml
index 867230ad92..1effcf3da0 100644
--- a/docs/environment_docs.yml
+++ b/docs/environment_docs.yml
@@ -19,6 +19,7 @@ dependencies:
- pip
- shapely<1.8.3
- arm-test-data
+ - moviepy
- pip:
- mpl2nc
- lazy_loader