Converting videos to GIFs using FFMPEG on Android

FFMPEG is a CLI tool that can be used out-of-the-box on desktop operating systems such as macOS, Linux, and Windows. However, on a mobile OS, such as Android, we have to use a mobile FFMPEG wrapper to abstract low-level implementations and complexities that come with interacting with the FFMPEG core library (written in C).

In this article, our goal is to convert a video file into a GIF using FFMPEG on Android. We will learn how to request media content from Android Storage Providers in the process. To effectively follow through, you will need a basic understanding of Kotlin and Android development and be ready to learn about FFMPEG. Let’s get started.

Jump ahead:

What is FFMPEG?

FFMPEG is a suite of CLI tools for manipulating media files. It has a rich set of libraries for performing basic to advance operations on media files. One common use case of FFMPEG is converting video files from one format to another, such as files from MOV to AVI video containers. Technically, this process is referred to as transcoding and may occur for a variety of reasons. Some of the reasons include the following:

  • Better compatibility: Due to different devices and platforms, one video format does not suit them all. Converting video files to other formats ensures we target these different devices and platforms with the supported video formats. For example, WebM is designed primarily for the web and works best with web browsers
  • Reduced file size: Some video formats take up less storage capacity while still preserving the video quality
  • Easy to share: Some video formats are easily shareable, like GIFs. They are small and have become widely adopted on social media platforms

FFMPEG’s capabilities extend far beyond simple video conversion. It also offers a range of options for trimming videos, such as selecting specific time ranges, cutting out sections, or splitting videos into multiple parts. In addition to these features, FFMPEG can also be used for other tasks, such as extracting audio from videos, creating GIFs, adding watermarks, custom video filters, and more. Its flexibility and wide range of capabilities have made it an essential tool for developers and video editors.

How to use FFMPEG on Android

FFmpeg Kit is a toolkit or wrapper to use FFMPEG in applications. It supports multiple mobile development environments like Android, iOS, Flutter, and React Native. The beauty of this toolkit is its unified API. You don’t have to learn a new syntax when switching between different environments. The API names are designed to be consistent across all platforms.

Set up a new project

Create a new Android Studio project and add the FFmpeg Kit library as a dependency into your app-level build.gradle file:

implementation 'com.arthenica:ffmpeg-kit-full:5.1'

In this step, we will access the user’s shared storage to select any video file. As per Android’s permission rules, the user grants permission to the request at runtime. To request this permission, specify this in the Android AndroidManifest.xml file like so:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Remember that the code block above alone does not satisfy the permission guidelines for storage. In addition to the permission defined in the manifest file, we’ll make a request at runtime. See the code block below:

val permission =  Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissionCheck = ContextCompat.checkSelfPermission(this, permission)

if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, arrayOf(permission), PERMISSION_CODE)
} else {
    // Your code to select file from the external storage
}

In the code above, we first check if Manifest.permission.WRITE_EXTERNAL_STORAGE has been granted by the user. We request permission to access the external storage if that has not been granted. If the if expression is false, we will go ahead and launch the documents browser.

Select a video from the device

The traditional approach for starting an activity for results has been removed in favor of the AndroidX Activity Result API. The improved API is type-safe and has good abstraction, making it easy to test. Below is a classic example of how to use this API:

val contract = ActivityResultContracts.GetContent()
val callback = ActivityResultCallback<Uri?> { uri -> // do something here }
val launcher = registerForActivityResult(contract, callback)
launcher.launch("video/*")

In only four simple and easy-to-read lines of code, we implemented "launch activity for result".

In line one, we define the kind of intent with GetContent() contract. The expected behavior of this contract is for the user to pick a piece of content and return the result to the activity that launched the intent. ActivityResultCallback is triggered whenever the result from the user selection is available.

The registerForActivityResult() is used to register the request to launch an activity for a result. You can register multiple contracts and callbacks. The last piece of this code is launching the intent by calling launch with "video/*" as an argument for the mime type.

Build the FFMPEG command

FFMPEG commands have varying degrees of complexity depending on the level of control you want over the result. It spans from basic to advanced settings. In most cases, the basic commands can serve well because FFMPEG cleverly chooses its default settings. Usually, it automatically selects the correct codecs and container without any complex configurations.

The basic command to convert a video to a GIF is this:

ffmpeg -i input.mp4 output.gif //this is a command line FFMPEG command

For now, the most important parts are the input and output paths. The input path is extracted from the content URI returned within the callback we defined earlier. The input path will be the absolute path of the user’s selection. Similarly, for the output path, we need to specify where the file path will come from. See the code block below:

val num = Random.nextInt(4600)
val outputFile = File(getExternalFilesDir(null), "output$num.gif")
val outPutFilePath = outputFile.absolutePath

We create a file in the external storage directory that is specific to our application, and then we’ll get the absolute path of the same file. Your command should look like this:

val ffmpegCommand = "-i $inputFilePath $outPutFilePath"

Execute the FFMPEG command

As explained earlier, we are not working with the CLI FFPEG. We are using a mobile wrapper to execute FFMPEG commands. The code below executes the FFMPEG:

FFmpegKit.executeAsync(ffmpegCommand) { session ->
    if (ReturnCode.isSuccess(session.returnCode)) {
        // SUCCESS
    } else {
        // FAILURE
    }
}

The code above is non-blocking and does not block the UI thread. Then, in the lambda callback, we check the status of the operation. Viola! We’ve successfully converted a video file into a GIF. Congrats on making it this far 👏. To see the output result, check your application’s root directory. In my case, it is:

Android/data/user/com.enyason.ffmpegkitdemo/files/output{num}.gif

Tweak the output GIF quality

In the command we used to convert a video to a GIF file, we relied on the default FFMPEG settings. Usually, this is fine. However, if we want fine-grain control over the output, we have to specify the settings ourselves. See the updated command below:

val ffmpegCommand = "-i $inputFilePath -t 10 -r 24 $outPutFilePath"

-t 10 tells FFMPEG to trim the output to only 10 seconds while -r 24 is the frame rate for the output GIF. Higher frame rates produce smoother GIFs. However, higher framerates significantly increase a GIF’s file size. Another setting that is not captured in the command above is frame size. By default, it takes the dimension of the original video. To scale the video, use -s 480x320.

Share the output GIF

What are GIFs sitting on our gallery for? Let’s share them with friends 😊. The code block below shares the output file with supported applications:

private fun shareGif(file: File) {
    val gifUri = FileProvider.getUriForFile(this, "com.example.fileprovider", file)
    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "image/gif"
        putExtra(Intent.EXTRA_STREAM, gifUri)
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    startActivity(shareIntent)
}

One thing to note is that the code block above is how we’ve constructed the content URI for the file that would be shared. Using the FileProvider returns the content URI (instead of file URI) that is managed by the Android system. This way, Android protects user privacy and security so that external apps cannot access the file system through File URIs.

This was the last piece of this article. If you have successfully followed through, well done! To view the complete project, check out the GitHub repo here.

Conclusion

The capabilities of FFMPEG are numerous and robust. It has become the backbone for many applications like VLC media player, HandBrake, Audacity, WhatsApp, and more. The list goes on, but the examples are proof of how successful the tool has been.

FFMPEG provides a library suite that serves many use cases, especially for building video editing software. You may want to explore this technology if you are excited about multi-media for mobile. Cheers!

LogRocket: Instantly recreate issues in your Android apps.

LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket’s product analytics features surface the reasons why users don’t complete a particular flow or don’t adopt a new feature.

Start proactively monitoring your Android apps — .


Source link