Skip to content

Add the ability to specify ffmpeg path & use Matplotlib's ffmpeg path#39

Open
calw20 wants to merge 12 commits intoali-ramadhan:mainfrom
calw20:main
Open

Add the ability to specify ffmpeg path & use Matplotlib's ffmpeg path#39
calw20 wants to merge 12 commits intoali-ramadhan:mainfrom
calw20:main

Conversation

@calw20
Copy link

@calw20 calw20 commented Nov 28, 2025

Changed default ffmpeg command to use matplotlib.pyplot.rcParams['animation.ffmpeg_path'] and added "ffmpeg_path" as a new argument to the Loom initialiser to allow for each Loom instance to have its own ffmpeg command path (defaults to matplotlib's).

Sorry for the double pull-request - I managed to mangle my git history.

Copy link
Owner

@ali-ramadhan ali-ramadhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @calw20 and thank you for contributing by opening this PR! I didn't know that matplotlib came with an ffmpeg executable and I think using the matplotlib ffmpeg is a great improvement, especially for users on systems with no ffmpeg (or a very old ffmpeg version).

Out of curiosity, do you know if ffmpeg versions are unique to matplotlib versions? E.g. if you have matplotlib version x.y.z then you're guaranteed to have ffmpeg version a.b.c?

I just left a couple of comments/questions to address before we merge. And thanks for tagging v0.9.2!

Comment on lines 12 to 13
def _check_ffmpeg_availability():
"""Check if ffmpeg is available on the system."""
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the user-supplied ffmpeg_path be an input here? I guess it can't be since this is __init__.py. I guess in the instance that ffmpeg is not available on the system but the user-supplied path works this will still produce a warning, but maybe this is fine?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that was my thought as well but then I saw it was passed to __init__.py I couldn't figure out a way to make it stick. The hope was that by using matplotlib's rcPrams if a user updated the rcPrams before importing Loom it would pull the updated value.

This works but only in the context of the main thread, as soon as you parallelise Loom it re-imports matplotlib & its rcPrams seems to be reset to the file-based defaults. I'm yet to test it but in theory if you add an rcPrams file where matplotlib expects it those settings should be maintained in the mutli-threaded/process context.


DEFAULT_FFMPEG_PATH = plt.rcParams['animation.ffmpeg_path']

ACCEPTABLE_EXTENSIONS = ("mp4", "gif")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I could be wrong but I thought Loom should still work if you specify another extension as long as ffmpeg supports it? So I was keeping it open and up to ffmpeg whether it supports the output extension. But do you think we need to limit them using ACCEPTABLE_EXTENSIONS?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the check as a bit of a bodge; it was something I was hopeing to clean up a bit later a work & wanted to seek comments on - I added it as I got an erorr from Loom when trying to create an .avi movie file. From memory the command list wasn't created as the conditional logic block didn't have a default else case that functioned.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that's a format I haven't seen in a while haha.

It's true that we don't really test for any output format besides GIF and MP4. I would say if AVI output does not work, let's open an issue about it and hopefully it'll be an easy fix.

@calw20
Copy link
Author

calw20 commented Nov 28, 2025

As far as I know matplotlib also defaults to trying the ffmpeg command BUT though rcPrams configuration it allows you to point to a specific ffmpeg path as well. I used this method for work as I use an ffmpeg python module (its basically a wrapper to a single binary & a python method to get the path of the binary) as I use uv based virtual environments.

@calw20
Copy link
Author

calw20 commented Nov 28, 2025

I can show some examples later next week once I'm back at work - if it helps as well. I'm super chuffed how easy it was to use Loom so thankyou for the quick speed up in generating video plots!

Copy link
Owner

@ali-ramadhan ali-ramadhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @calw20 sorry I've been meaning to revisit this PR. I left a few more comments. Just let me know if I can help with this PR.

I might make some changes to the rest of the code (nothing too drastic) but if any conflicts show up in this PR I can help resolve them.

Comment on lines 121 to 125
self.ffmpeg_path: str = DEFAULT_FFMPEG_PATH
if ffmpeg_path is not None:
_ffmpeg_path = Path(ffmpeg_path)
if _ffmpeg_path.exists():
self.ffmpeg_path = str(_ffmpeg_path.absolute())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great but if the user provides an ffmpeg_path that doesn't exist, the code will silently fall back to the default. To alert the user and avoid confusing behavior, I think we should either produce an error and exit, or warn the user then switch to the default.


DEFAULT_FFMPEG_PATH = plt.rcParams['animation.ffmpeg_path']

ACCEPTABLE_EXTENSIONS = ("mp4", "gif")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that's a format I haven't seen in a while haha.

It's true that we don't really test for any output format besides GIF and MP4. I would say if AVI output does not work, let's open an issue about it and hopefully it'll be an easy fix.

@calw20
Copy link
Author

calw20 commented Feb 5, 2026

Hey @ali-ramadhan, I also have to apologise, I also have been meaning to continue looking at this PR but got caught up in end-of-year work shenanigans.

I've made a few additional commits, the crux of which are:

  • I agree with your point of having an optional warning be thrown and have added a enable_ffmpeg_path_fallback option (defaulting to True)
  • I've implemented the DEFAULT_FFMPEG_PATH const to be pulled from an environment variable as I ran into a bug where parallelised instances of the Loom object wouldn't get the correct rcPrmas settings from matplotlib if they are in a new process AND the rcPrams where set via assignment (ie only present in the scope of the main process)
  • Added an example of using a custom ffmpeg path to parallel_sine_wave.py

Hope this helps!

@calw20 calw20 requested a review from ali-ramadhan February 5, 2026 00:08
Copy link
Owner

@ali-ramadhan ali-ramadhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@calw20 Thanks for revisiting this PR! Nice work adding the enable_ffmpeg_path_fallback option and the example.

Took another look and I have a few more thoughts:

  1. Good idea to add an example of using a custom ffmpeg path! But I think we should not add it to the parallel_sine_wave.py example. I think each example should be as simple and self-contained as possible. So I would actually suggest a completely new example: custom_ffmpeg_sine_wave.py? Then we can test it in CI and include it in the README and the docs.

  2. I think we should get rid of ACCEPTABLE_EXTENSIONS and aim to support any extension by default (that ffmpeg also supports). If a particular extension causes an issue, we can open a GitHub issue and work on fixing it. I feel like ffmpeg supports a huge range of output file formats, and many of them would probably work out of the box for us here.

  3. I'm a little confused by what _LOOM_DEFAULT_ENVIRON_VAR does and the use case for it.

  4. Can we add some tests for this new functionality? Should be simple to test the logic of ffmpeg_path and enable_ffmpeg_path_fallback. Testing the new example would also be super useful. I'll try to think of others too.

  5. Would also be great if we can add a section to the docs documenting the default path and the different ways you can use a custom ffmpeg path.

Apologies for the many asks! Very happy to work on some of them!

Comment on lines +160 to +161
# In theory this should never fail.
assert isinstance(self.ffmpeg_path, str), "ffmpeg path is not a string?"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I'm thinking we will probably want self.ffmpeg_path to always be a Path object?

elif self.odd_dimension_handling == "pad":
return "pad='if(mod(iw,2),iw+1,iw)':'if(mod(ih,2),ih+1,ih)':0:0:color=white"
else:
raise ValueError("Scale Settings Incorrect!")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to print the value of self.odd_dimension_handling and the possible values so the user can know what is wrong.


# Either the environ var OR matplotlib rcPram *needs* to be set BEFORE
# importing matplotloom
import imageio_ffmpeg # python library that provides ffmpeg binary
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, didn't know about this package!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants