Wednesday 17 May 2017

Gambas Video Reverser

I recently needed to reverse a video clip, to make the action run backwards.


I couldn't find a Linux video editor with this feature built in, so I ran a series of command line instructions...


...then I decided it would be useful to wrap these instructions into a GUI application.

Reverse video is one of those gimmicks that can add some humour to a video. Especially a family video where your grandad slips over, then magical returns to his feet.

"How's it done?"  {...shout the crowd}


The magic is performed in a few simple steps by command line applications;
  1. Extract a sequence of jpeg images from the video clip, frame by frame
  2. If the video also contains an audio track, extract to a .wav file and then reverse it
  3. Rebuild the video from the jpegs in reverse order, and add the reversed audio track
Initially I struggled with the commands I had found on the net. They basically worked, but the quality of my reversed video was not great and there was a tendancy for it to "twitch" on playback.

But after checking the ffmpeg wiki and reading through the ls command manual, I made some changes and managed to get good results.

For step 1 we use ffmpeg via the Gambas Exec instruction:-

Exec ["ffmpeg", "-i", {target file}, "-an", "-q", "1", {temp dir} & "/%05d.jpg"] Wait

This action can generate a lot of jpegs (basically video duration x frames per second), so its best to save them in a temporary directory and use Wait for process to complete. I will only ever reverse very short video clips, so %05d (which allows for up to 99,999 jpegs, or over 16min of 100fps video) should be more than enough. Jpeg quality is set to best by: q 1 and "-an" means no audio.

For step 2 we again use ffmpeg to extract the audio track;

Exec ["ffmpeg", "-i", {target file}, "-vn", {temp dir} & "/audio.wav"] Wait

...where -vn means no video. Now use Sox to reverse the audio;

Exec ["sox", {temp dir} & "/audio.wav", {temp dir} & "/revAudio.wav", "reverse"] Wait

Then we use cat, ls & ffmpeg to build the complete, reversed video file via the Gambas Shell command;

Shell "cat $(ls -r " & {temp dir} & "/*.jpg) | ffmpeg -f image2pipe -vcodec mjpeg -r " FrameRate " -i - -i " {temp dir} & "/reversed.wav -vcodec libx264 -preset slower -crf 20 -threads 0 -acodec flac " NameOfRevVideoFile Wait

Note that we use Shell in this case because Exec does not support re-direction operators like pipe "|"

the Gambas GUI


As my application uses a file chooser, I can easily select a target file, and use the chooser reference in my code. I also need to determine the target file frame rate, so I use mplayer in a command like this via Exec;

Exec ["mplayer", "-vo", "null", "-ao", "null", "-identify", "-frames", "0", fileCh.SelectedPath] Wait To strProps

...and display file properties in a text area, and key properties in a list box.

I use a simple routine to pull-out data from the mplayer output;

Const VIDEO_BITRATE As String = "ID_VIDEO_BITRATE"
Const FRAME_RATE As String = "ID_VIDEO_FPS"

....

Public Function FindParameters(strParam As String, strProperties As String) As String

  If InStr(StrProperties, strParam) > 0 Then
    strParam = Mid(StrProperties, InStr(StrProperties, strParam))
    strParam = Mid(strParam, InStr(strParam, "="))
    strParam = Mid(strParam, 2, InStr(strParam, gb.Lf) - 2)   'exclude "=" and /n
    Return strParam
  Endif
End


Its a good idea to create a folder to hold the temporary files, and then clean up by deleting it when the job is done. If fact I check if the folder already exists and delete the temp files and folder before creating a new one;

Public Function DeleteWorkingDir() As Boolean
Dim file As String
 
  If Exist(fileCh.Dir & TEMP_DIR) Then
    For Each file In RDir(fileCh.Dir & TEMP_DIR, "", gb.File)
      Kill fileCh.Dir & TEMP_DIR & "/" & file
    Next
    Rmdir fileCh.Dir & TEMP_DIR
  Endif
  Return True
Catch
  Me.Text = "Error!"
  lblStatus.Text = Error.Text
End

....
      If DeleteWorkingDir() Then
        Mkdir fileCh.Dir & TEMP_DIR
        Wait 1

.....

From the examples given in the ffmpeg wiki, creating an .mkv video file looks quite straightforward (Matroska/mkv is a Russian open source video standard). So I've used this file type for my output file.

The command also includes a preset option which controls the final quality/speed of encoding. Options include: veryslow (very good quality), ultrafast (lowest quality) and the rather curious Placebo option. I've settled on "slower" but this could obviously be made configurable in the Gambas program.

The CRF (constant rate factor) can also be varied over the range 0 to 51 and influences the compression level and the bitrate. A lower value gives a better quality, and the suggested sensible range is 18 to 28. Again I may make this a variable in my program, but at the moment it is fixed at 18.

conclusion


This little Gambas app is very easy and quick to use. I have added the ability to test/view the final video, which plays in a simple mplayer window. The program could certainly do with a bit more attention, but as it is now functional, I am just too busy using it!

I've already used it quite a bit by creating several reverse video clips. These were then easy to drag into Shotcut and edit as required into a complete video. Although the reversed video is .mkv, the final output from Shotcut is encoded into whatever format is required.

As already mentioned, my use for this is generally for short video clips, probably no more than a minute or two at most. So the speed of encoding is not a problem with the settings I have chosen.

The full Gambas code for this project is here on GitHub.

No comments:

Post a Comment