Integrate FFMPEG and Video Encoding with Maya

Integrate FFMPEG and Video Encoding with Maya

In this post, I will talk about integrating FFMPEG encoder to your Maya pipeline.

Preface

Xwift is a shelf that contains specialized scripts when I was producing my animated film. I want to write some blogs to document them, in case there’s anyone out there that needs some inspiration.

Everything is developed and tested using Maya 2022.

Swift is not (well, not yet) open-source and therefore I will not share the whole script in my post. However, after reading my posts I believe you can implement your own, given some time and effort.

Introduction

When I was first learning about everything about Maya and animation, I was like wow this thing is coooool! Then someone told me Maya is so good that it is literally the “Industry Standard” (Which, is still mocked by Blender artists these days).

Well, there’s one thing that Maya is terribly bad at, that is, video encoding.

When animating, animators constantly need to playblast their animation for feedbacks. And oh man, those playblasts (which can only be generated in .avi format on Windows) is bulky. A ten second animation can easily exceed 1GB (like…what???), and for my little water pipe internet speed, SyncSketch just doesn’t cut it for me.

A naive thought would be, why can’t we just encode our playblasts into .mp4? Turns out, you just can’t. What the actual fu*k.

But I don’t believe it, with FFMPEG encoding being widely used everywhere, I think it will work. So I started my months long of research. While this search for a better solution eventually becomes a much, much bigger project, I am going to cover only the .avi to .mp4 process in this post. There is no way I can get through a entire automated playblast and iteration tool in one post…

With that said, let’s get started, shall we?

Failures

It is worth mentioning a couple different methods that I tried and failed, so you can feel my pain.

Honorable mention candidates include:

  1. Try install pip inside mayapy.exe and use pip to install ffmpeg-python.
  2. Use a binary version of pip and force that to install inside mayapy.
  3. Writing a separate program that does one and only thing: take in an avi and spit out a mp4.
  4. Attempt to create a python environment outside of maya.

All of them failed, so meh.

Also, I will use Windows as example in this post, it is just too time consuming to write another one for macOS, nobody use macOS for 3D anyway (ha!).

Tool Integration

In order to integrate the encoding into everything seamlessly, I developed a couple other tools to work together and I will introduce the critical functions briefly:

  1. A custom playblast tool that can be called from other scripts to playblast avi and encode mp4 using FFMPEG.
  2. A local binary of ffmpeg.exe (for Windows) or ffmpeg (macOS and Linux).
  3. A iteration tool that will save the current maya scene, and call the playblast tool to automatically playblast to save along with the scene.
  4. An automatic install script (mentioned in one of the earlier post) that also copies the binary to a location where it is easier for my scripts to find.

Get the FFMPEG Binary

Before everything, let’s get one thing straight. What is FFMPEG? It is a simple video encoder that can convert videos to other formats (It is much more powerful than this, but all we are using for this post is this functionality).

First, let’s get the binary for Windows. Some nice people already built the binaries, so I just grabbed it from gyan.dev, since it is a recommended source from FFMPEG’s official website.

After you downloaded the newest version (at the moment of writing, 4.4, but other versions don’t matter), you will get the following:

|ffmpeg-4.4-full_build.7z
    |bin
        |ffmpeg.exe
        |ffplay.exe
        |ffprobe.exe
    |doc
    |...


Get the ffmpeg.exe. That is the only binary executable file you will need.

Installing the ffmpeg.exe Binary

Obviously, I am the creator of Xwift, I make the choice to place anything anywhere I want, you don’t have to follow me. But you will eventually need to place a ffmpeg.exe to a place where your script can find it. Here’s my design for where to place ffmpeg.exe. In my repository:

|xwift
    |ffmpeg
        |ffmpeg.exe
        |ffmpeg_version.txt (Just to keep track of which version I am using)
    |icons
    |plug-ins
    |scripts
    |install_xwift_windows.py


My script install_xwift_windows.py, after some modification, will copy ffmpeg.exe to a dedicated location in MAYA_APP_DIR.

|Documents
    |maya
        |2022
            |ffmpeg
                |ffmpeg.exe
            |scripts
            |maya.env
            |...


The implementation is also easy. Recall in the install_xwift_windows.py, I wrote a function, install_element(), to copy things from a place to another. I will include a simplified version here.

def install_element(element_name, target_folder, maya_path):
    target_path = os.path.join(maya_path, element_name)
    setup_file = xwift_FOLDER + target_folder + element_name
    shutil.copy(setup_file, target_path)


I can take advantage of this function, let’s first get the before and after path of ffmpeg.exe:

# Get FFMPEG_FOLDER
MAYA_FOLDER = "{0}/Documents/maya/".format(os.getenv("USERPROFILE").replace("/", "\\"))
MAYA_VERSION = get_latest_version(MAYA_FOLDER)  # returns e.g. "2022`.
xwift_FOLDER = os.path.dirname(os.path.abspath(__file__))  # before
FFMPEG_FOLDER = "{0}{1}/ffmpeg/".format(MAYA_FOLDER,MAYA_VERSION).replace("/", "\\")  # after


and just add the following in the main function:

install_element("ffmpeg.exe", "\\ffmpeg\\", FFMPEG_FOLDER)


And that will do the trick. Or, since it is just a one time job, you can also do it by hand. :)

Algorithm Design

Step 1 - Create a ./temp folder and make a playblast to ./temp/scene_playblast.avi.

|temp
    |scene_playblast.avi


Step 2 - Use the ./temp/scene_playblast.avi, call ffmpeg.exe, and encode a new video into ./temp/scene_playblast.mp4.

|temp
    |scene_playblast.avi
    |scene_playblast.mp4


Step 3 - Copy ./temp/scene_playblast.mp4 and paste to ./temp/scene_playblast.mp4.

|scene_playblast.mp4
|temp
    |scene_playblast.avi
    |scene_playblast.mp4


Step 4 - Recursively delete /temp.

|scene_playblast.mp4


Calling ffmpeg.exe from Maya

Fast forward a couple hundred lines. Say now we successfully completed the playblast file. We have a playblast file named foo\bar\scene_playblast.avi created on disk. We also have a variable pb_actual_path = "foo\bar\scene_playblast"

We also need to get the exact location of ffmpeg.exe. Using code from a couple blocks above that gets the path for FFMPEG_FOLDER, we can write a simple function:

def get_ffmpeg_path():
    # TODO: Get FFMPEG_FOLDER using USER, MAYA_FOLDER, MAYA_VERSION.
    ffmpeg_path = FFMPEG_FOLDER + "ffmpeg.exe"
    return ffmpeg_path
avi_input = (pb_actual_path + ".avi").replace("\\", "/")
mp4_output = (pb_actual_path + ".mp4").replace("\\", "/")


We call FFMPEG using the following method:

command = ('start /b /w \"\" \"' + get_ffmpeg_path() + '\" -y -i {0} {1}').format(avi_input, mp4_output)
try:
    subprocess.call(command, shell=True)
except:
    print("Failed to playblast during command call.")


Some might argue that subprocess.call() is set to deprecated soon and I should use this better method instead:

subprocess.run(["powershell", "-Command", command], capture_output=True)


Which is correct. But with the majority of Maya running on Python 2 and subprocess.call() still working in 2022, it is best to use the good old way - until one day it doesn’t work anymore.

Finishing Up

With the majority of heavy lifting done, let’s do the copy work and complete the algorithm. Let’s wrap this process in a try/except structure to catch a possible IOError, making the script more robust.

# Move it to playblast directory
try:
    mp4_target_location = (mp4_output.replace("/temp", ""))
    shutil.copyfile(mp4_output, mp4_target_location)
except:
    print("Task failed during copying files.")


And finally, recursively delete /temp folder.

# Get rid of that /temp/ folder. 
temp_folder_location = (current_dir +  "/temp/").replace('\\', '/')
shutil.rmtree(temp_folder_location)


Conclusion

That’s all for converting .avi to .mp4 using ffmpeg. I hope this is helpful.

If you want to quickly say hi just shoot me a message using the contact portal.

Integrate FFMPEG and Video Encoding with Maya
Older post

Zoetrope - A custom Renderer in Maya

Newer post

Picker for Pokemon Ash Rig

Integrate FFMPEG and Video Encoding with Maya