Repository: Zulko/moviepy

Issue:
Ability to set BGR of VideoFileClip
Currently the pix_fmt for ffmpeg is set in VideoFileClip.py via
```
pix_fmt = "rgba" if has_mask else "rgb24"
```
It would be great if VideoFileClip could accept an optional "pix_fmt" arg so that "bgr24" could be used to be consistent with OpenCV.


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/31520546/badge)](https://coveralls.io/builds/31520546)

Coverage decreased (-0.05%) to 67.512% when pulling **92de78c607d9a76647779f1c551bb654e7c6f592 on RicardoHE97:BGR_option** into **74f08ea4df03e36247b78e2d4743c5aad6901611 on Zulko:master**.

tburrows13: Thank you so much for this, it looks great! I've made a couple of tiny changes (I could have left the suggestions as a comment, but they were so trivial I just went ahead and did them myself in the web editor).

I wouldn't worry about tests for this PR. I don't really have a clue where to begin writing tests for `FFMPEG_VideoReader` beyond the basics like making sure that an output file exists and it has seemingly correct properties.

When you're happy with it, I'll merge it into master and it will be available in the next v2.0 dev release.
RicardoHE97: I think I have nothing more to add.

Feel free to make any suggestion or comments :)
RicardoHE97: I think I have nothing more to add to this PR. So I think we can close it 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83e9504..ba8ac03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Important Announcements
 
 ### Added <!-- for new features -->
+- New `pix_fmt` parameter in `VideoFileClip`, `VideoClip.write_videofile()`, `VideoClip.write_gif()` that allows passing a custom `pix_fmt` parameter such as `"bgr24"` to FFmpeg [#1237]
 
 ### Changed <!-- for changes in existing functionality -->
 - `FFMPEG_AudioReader.close_proc()` -> `FFMPEG_AudioReader.close()` for consistency with `FFMPEG_VideoReader` [#1220]
diff --git a/moviepy/video/VideoClip.py b/moviepy/video/VideoClip.py
index ebd2549..d8506c3 100644
--- a/moviepy/video/VideoClip.py
+++ b/moviepy/video/VideoClip.py
@@ -168,6 +168,7 @@ class VideoClip(Clip):
         threads=None,
         ffmpeg_params=None,
         logger="bar",
+        pix_fmt=None,
     ):
         """Write the clip to a videofile.
 
@@ -271,6 +272,9 @@ class VideoClip(Clip):
 
         logger
           Either "bar" for progress bar or None or any Proglog logger.
+        
+        pix_fmt
+          Pixel format for the output video file.
 
         Examples
         ========
@@ -348,6 +352,7 @@ class VideoClip(Clip):
             threads=threads,
             ffmpeg_params=ffmpeg_params,
             logger=logger,
+            pix_fmt=pix_fmt,
         )
 
         if remove_temp and make_audio:
@@ -424,6 +429,7 @@ class VideoClip(Clip):
         colors=None,
         tempfiles=False,
         logger="bar",
+        pix_fmt=None,
     ):
         """ Write the VideoClip to a GIF file.
 
@@ -463,6 +469,12 @@ class VideoClip(Clip):
         progress_bar
           If True, displays a progress bar
 
+        pix_fmt
+          Pixel format for the output gif file. If is not specified
+          'rgb24' will be used as the default format unless ``clip.mask``
+          exist, then 'rgba' will be used. This option is only going to
+          be accepted if ``program=ffmpeg`` or when ``tempfiles=True``
+
 
         Notes
         -----
@@ -503,6 +515,7 @@ class VideoClip(Clip):
                 dispose=dispose,
                 colors=colors,
                 logger=logger,
+                pix_fmt=pix_fmt,
             )
         else:
             # convert imageio opt variable to something that can be used with
@@ -519,6 +532,7 @@ class VideoClip(Clip):
                 dispose=dispose,
                 colors=colors,
                 logger=logger,
+                pix_fmt=pix_fmt,
             )
 
     # -----------------------------------------------------------------
diff --git a/moviepy/video/io/VideoFileClip.py b/moviepy/video/io/VideoFileClip.py
index 5a331a8..28024c1 100644
--- a/moviepy/video/io/VideoFileClip.py
+++ b/moviepy/video/io/VideoFileClip.py
@@ -51,6 +51,11 @@ class VideoFileClip(VideoClip):
       can be set to 'fps', which may be helpful if importing slow-motion videos
       that get messed up otherwise.
 
+    pix_fmt
+      Optional: Pixel format for the video to read. If is not specified
+      'rgb24' will be used as the default format unless ``has_mask`` is set
+      as ``True``, then 'rgba' will be used.
+
 
     Attributes
     -----------
@@ -86,12 +91,14 @@ class VideoFileClip(VideoClip):
         audio_fps=44100,
         audio_nbytes=2,
         fps_source="tbr",
+        pix_fmt=None,
     ):
 
         VideoClip.__init__(self)
 
         # Make a reader
-        pix_fmt = "rgba" if has_mask else "rgb24"
+        if not pix_fmt:
+            pix_fmt = "rgba" if has_mask else "rgb24"
         self.reader = FFMPEG_VideoReader(
             filename,
             pix_fmt=pix_fmt,
diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py
index 1dabe60..a8db08e 100644
--- a/moviepy/video/io/ffmpeg_reader.py
+++ b/moviepy/video/io/ffmpeg_reader.py
@@ -58,7 +58,9 @@ class FFMPEG_VideoReader:
         self.infos = infos
 
         self.pix_fmt = pix_fmt
-        self.depth = 4 if pix_fmt == "rgba" else 3
+        self.depth = 4 if pix_fmt[-1] == "a" else 3
+        # 'a' represents 'alpha' which means that each pixel has 4 values instead of 3.
+        # See https://github.com/Zulko/moviepy/issues/1070#issuecomment-644457274
 
         if bufsize is None:
             w, h = self.size
@@ -218,7 +220,7 @@ class FFMPEG_VideoReader:
         self.close()
 
 
-def ffmpeg_read_image(filename, with_mask=True):
+def ffmpeg_read_image(filename, with_mask=True, pix_fmt=None):
     """ Read an image file (PNG, BMP, JPEG...).
 
     Wraps FFMPEG_Videoreader to read just one image.
@@ -237,8 +239,14 @@ def ffmpeg_read_image(filename, with_mask=True):
       If the image has a transparency layer, ``with_mask=true`` will save
       this layer as the mask of the returned ImageClip
 
+    pix_fmt
+      Optional: Pixel format for the image to read. If is not specified
+      'rgb24' will be used as the default format unless ``with_mask`` is set
+      as ``True``, then 'rgba' will be used.
+
     """
-    pix_fmt = "rgba" if with_mask else "rgb24"
+    if not pix_fmt:
+        pix_fmt = "rgba" if with_mask else "rgb24"
     reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt, check_duration=False)
     im = reader.lastread
     del reader
diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py
index ae7f3b8..7f402e6 100644
--- a/moviepy/video/io/ffmpeg_writer.py
+++ b/moviepy/video/io/ffmpeg_writer.py
@@ -62,6 +62,11 @@ class FFMPEG_VideoWriter:
       Boolean. Set to ``True`` if there is a mask in the video to be
       encoded.
 
+    pix_fmt
+      Optional: Pixel format for the output video file. If is not specified
+      'rgb24' will be used as the default format unless ``withmask`` is set
+      as ``True``, then 'rgba' will be used.
+
     """
 
     def __init__(
@@ -77,6 +82,7 @@ class FFMPEG_VideoWriter:
         logfile=None,
         threads=None,
         ffmpeg_params=None,
+        pix_fmt=None,
     ):
         if logfile is None:
             logfile = sp.PIPE
@@ -84,6 +90,8 @@ class FFMPEG_VideoWriter:
         self.filename = filename
         self.codec = codec
         self.ext = self.filename.split(".")[-1]
+        if not pix_fmt:
+            pix_fmt = "rgba" if withmask else "rgb24"
 
         # order is important
         cmd = [
@@ -98,7 +106,7 @@ class FFMPEG_VideoWriter:
             "-s",
             "%dx%d" % (size[0], size[1]),
             "-pix_fmt",
-            "rgba" if withmask else "rgb24",
+            pix_fmt,
             "-r",
             "%.02f" % fps,
             "-an",
@@ -218,6 +226,7 @@ def ffmpeg_write_video(
     threads=None,
     ffmpeg_params=None,
     logger="bar",
+    pix_fmt=None,
 ):
     """ Write the clip to a videofile. See VideoClip.write_videofile for details
     on the parameters.
@@ -229,6 +238,8 @@ def ffmpeg_write_video(
     else:
         logfile = None
     logger(message="Moviepy - Writing video %s\n" % filename)
+    if not pix_fmt:
+        pix_fmt = "rgba" if withmask else "rgb24"
     with FFMPEG_VideoWriter(
         filename,
         clip.size,
@@ -240,6 +251,7 @@ def ffmpeg_write_video(
         audiofile=audiofile,
         threads=threads,
         ffmpeg_params=ffmpeg_params,
+        pix_fmt=pix_fmt,
     ) as writer:
         for t, frame in clip.iter_frames(
             logger=logger, with_times=True, fps=fps, dtype="uint8"
@@ -257,12 +269,14 @@ def ffmpeg_write_video(
     logger(message="Moviepy - Done !")
 
 
-def ffmpeg_write_image(filename, image, logfile=False):
+def ffmpeg_write_image(filename, image, logfile=False, pix_fmt=None):
     """ Writes an image (HxWx3 or HxWx4 numpy array) to a file, using
         ffmpeg. """
 
     if image.dtype != "uint8":
         image = image.astype("uint8")
+    if not pix_fmt:
+        pix_fmt = "rgba" if (image.shape[2] == 4) else "rgb24"
 
     cmd = [
         FFMPEG_BINARY,
@@ -272,7 +286,7 @@ def ffmpeg_write_image(filename, image, logfile=False):
         "-f",
         "rawvideo",
         "-pix_fmt",
-        "rgba" if (image.shape[2] == 4) else "rgb24",
+        pix_fmt,
         "-i",
         "-",
         filename,
diff --git a/moviepy/video/io/gif_writers.py b/moviepy/video/io/gif_writers.py
index dce984e..bfd5121 100644
--- a/moviepy/video/io/gif_writers.py
+++ b/moviepy/video/io/gif_writers.py
@@ -29,6 +29,7 @@ def write_gif_with_tempfiles(
     dispose=True,
     colors=None,
     logger="bar",
+    pix_fmt=None,
 ):
     """ Write the VideoClip to a GIF file.
 
@@ -56,7 +57,13 @@ def write_gif_with_tempfiles(
 
     delay = int(100.0 / fps)
 
+    if clip.mask is None:
+        withmask = False
+
     if program == "ImageMagick":
+        if not pix_fmt:
+            pix_fmt = "RGBA" if withmask else "RGB"
+
         logger(message="MoviePy - - Optimizing GIF with ImageMagick...")
         cmd = (
             [
@@ -73,12 +80,17 @@ def write_gif_with_tempfiles(
                 "%02d" % fuzz + "%",
                 "-layers",
                 "%s" % opt,
+                "-set",
+                "colorspace",
+                pix_fmt,
             ]
             + (["-colors", "%d" % colors] if colors is not None else [])
             + [filename]
         )
 
     elif program == "ffmpeg":
+        if not pix_fmt:
+            pix_fmt = "rgba" if withmask else "rgb24"
 
         cmd = [
             FFMPEG_BINARY,
@@ -92,6 +104,8 @@ def write_gif_with_tempfiles(
             "-r",
             str(fps),
             filename,
+            "-pix_fmt",
+            (pix_fmt),
         ]
 
     try:
@@ -133,6 +147,7 @@ def write_gif(
     dispose=True,
     colors=None,
     logger="bar",
+    pix_fmt=None,
 ):
     """ Write the VideoClip to a GIF file, without temporary files.
 
@@ -164,6 +179,12 @@ def write_gif(
       the colors that are less than fuzz% different are in fact
       the same.
 
+    pix_fmt
+      Pixel format for the output gif file. If is not specified
+      'rgb24' will be used as the default format unless ``clip.mask``
+      exist, then 'rgba' will be used. This option is going to
+      be ignored if ``program=ImageMagick``.
+
 
     Notes
     -----
@@ -195,6 +216,8 @@ def write_gif(
     logger = proglog.default_bar_logger(logger)
     if clip.mask is None:
         withmask = False
+    if not pix_fmt:
+        pix_fmt = "rgba" if withmask else "rgb24"
 
     cmd1 = [
         FFMPEG_BINARY,
@@ -210,7 +233,7 @@ def write_gif(
         "-s",
         "%dx%d" % (clip.w, clip.h),
         "-pix_fmt",
-        ("rgba" if withmask else "rgb24"),
+        (pix_fmt),
         "-i",
         "-",
     ]
@@ -225,14 +248,7 @@ def write_gif(
         popen_params["stdout"] = sp.DEVNULL
 
         proc1 = sp.Popen(
-            cmd1
-            + [
-                "-pix_fmt",
-                ("rgba" if withmask else "rgb24"),
-                "-r",
-                "%.02f" % fps,
-                filename,
-            ],
+            cmd1 + ["-pix_fmt", (pix_fmt), "-r", "%.02f" % fps, filename,],
             **popen_params,
         )
     else:


Test patch:
diff --git a/tests/test_VideoClip.py b/tests/test_VideoClip.py
index 9ef5051..41facde 100644
--- a/tests/test_VideoClip.py
+++ b/tests/test_VideoClip.py
@@ -103,6 +103,14 @@ def test_write_gif_ffmpeg():
     close_all_clips(locals())
 
 
+def test_write_gif_ffmpeg_pix_fmt():
+    clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.4)
+    location = os.path.join(TMP_DIR, "ffmpeg_gif.gif")
+    clip.write_gif(location, program="ffmpeg", pix_fmt="bgr24")
+    assert os.path.isfile(location)
+    close_all_clips(locals())
+
+
 def test_write_gif_ffmpeg_tmpfiles():
     clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5)
     location = os.path.join(TMP_DIR, "ffmpeg_tmpfiles_gif.gif")
@@ -111,6 +119,14 @@ def test_write_gif_ffmpeg_tmpfiles():
     close_all_clips(locals())
 
 
+def test_write_gif_ffmpeg_tmpfiles_pix_fmt():
+    clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5)
+    location = os.path.join(TMP_DIR, "ffmpeg_tmpfiles_gif.gif")
+    clip.write_gif(location, program="ffmpeg", tempfiles=True, pix_fmt="bgr24")
+    assert os.path.isfile(location)
+    close_all_clips(locals())
+
+
 def test_write_gif_ImageMagick():
     clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5)
     location = os.path.join(TMP_DIR, "imagemagick_gif.gif")
@@ -128,6 +144,14 @@ def test_write_gif_ImageMagick_tmpfiles():
     close_all_clips(locals())
 
 
+def test_write_gif_ImageMagick_tmpfiles_pix_fmt():
+    clip = VideoFileClip("media/big_buck_bunny_432_433.webm").subclip(0.2, 0.5)
+    location = os.path.join(TMP_DIR, "imagemagick_tmpfiles_gif.gif")
+    clip.write_gif(location, program="ImageMagick", tempfiles=True, pix_fmt="SGI")
+    assert os.path.isfile(location)
+    close_all_clips(locals())
+
+
 def test_subfx():
     clip = VideoFileClip("media/big_buck_bunny_0_30.webm").subclip(0, 1)
     transform = lambda c: speedx(c, 0.5)