Last updated: February 23, 2025

<span aria-hidden="true" id="concatenate-videos-in-scala-with-ffmpeg"></span>

# Concatenate videos in Scala with FFmpeg

![Tim Koschützki](/assets/images/teammates/avatar-tim-kos-1.jpg?dpl=dpl_C6YH6XrtnwbHLJcm1LDKywQn4CB3)

#### Tim Koschützki

Co-founder · Berlin, Germany · Show bio

[](https://x.com/tim%5Fkos)[](https://github.com/tim-kos)

Video concatenation is a common requirement in media processing applications, whether you're building a video editor, creating compilations, or automating video workflows. In this guide, we'll explore how to concatenate videos efficiently using Scala and FFmpeg, covering different methods and best practices.

<span aria-hidden="true" id="set-up-your-environment"></span>

## Set up your environment

<span aria-hidden="true" id="install-scala"></span>

### Install Scala

The recommended way to install Scala is using Coursier (cs setup):

**macOS**:

```bash
brew install coursier/formulas/coursier && cs setup

```

**Linux**:

```bash
curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs && chmod +x cs && ./cs setup

```

**Windows**: Download and execute[the Scala installer for Windows⁠](https://github.com/coursier/coursier/releases/latest/download/cs-x86%5F64-pc-win32.zip)

<span aria-hidden="true" id="install-ffmpeg"></span>

### Install FFmpeg

**macOS**:

```bash
brew install ffmpeg

```

**Linux (Ubuntu/Debian)**:

```bash
sudo apt update && sudo apt install ffmpeg

```

**Windows**: Download from the[official FFmpeg website⁠](https://ffmpeg.org/download.html#build-windows)

<span aria-hidden="true" id="video-concatenation-methods"></span>

## Video concatenation methods

FFmpeg offers two main methods for concatenating videos:

1. Concat demuxer: Ideal for videos with the same codec and format
2. Concat filter: More flexible but requires re-encoding

Let's implement both methods in Scala:

```scala
import scala.sys.process.*
import scala.util.{Try, Success, Failure}
import java.io.{File, PrintWriter}
import java.nio.file.{Files, Paths}

enum ConcatMethod:
  case Demuxer, Filter

case class VideoFile(path: String):
  def exists: Boolean = Files.exists(Paths.get(path))
  def extension: String = path.split("\\.").lastOption.getOrElse("")

object VideoConcatenator:
  def createConcatFile(videos: Seq[VideoFile], tempFile: String): Try[Unit] = Try {
    val writer = PrintWriter(File(tempFile))
    try
      videos.foreach(video => writer.println(s"file '${video.path}'"))
    finally
      writer.close()
  }

  def concatenateVideos(
    videos: Seq[VideoFile],
    output: String,
    method: ConcatMethod = ConcatMethod.Demuxer
  ): Try[Boolean] = Try {
    if videos.isEmpty then throw IllegalArgumentException("No videos provided")
    if !videos.forall(_.exists) then throw IllegalArgumentException("One or more input videos not found")

    method match
      case ConcatMethod.Demuxer =>
        val tempFile = "concat_list.txt"
        createConcatFile(videos, tempFile).get

        val cmd = Seq(
          "ffmpeg",
          "-f", "concat",
          "-safe", "0",
          "-i", tempFile,
          "-c", "copy",
          "-y",
          output
        )

        val result = cmd.! == 0
        Files.deleteIfExists(Paths.get(tempFile))
        result

      case ConcatMethod.Filter =>
        val inputs = videos.flatMap(v => Seq("-i", v.path))
        val filter = s"concat=n=${videos.length}:v=1:a=1[outv][outa]"

        val cmd = Seq("ffmpeg") ++ inputs ++ Seq(
          "-filter_complex", filter,
          "-map", "[outv]",
          "-map", "[outa]",
          "-y",
          output
        )

        cmd.! == 0
  }

  @main def run(args: String*): Unit =
    if args.length < 3 then
      println("Usage: VideoConcatenator <output_file> <input_file1> <input_file2> [input_file3...] [--filter]")
      sys.exit(1)

    val useFilter = args.contains("--filter")
    val output = args(0)
    val videos = args.slice(1, args.length).filterNot(_ == "--filter").map(VideoFile.apply)
    val method = if useFilter then ConcatMethod.Filter else ConcatMethod.Demuxer

    concatenateVideos(videos, output, method) match
      case Success(true) => println(s"Successfully concatenated videos to $output")
      case Success(false) => println("Failed to concatenate videos: FFmpeg returned non-zero exit code")
      case Failure(e) => println(s"Error concatenating videos: ${e.getMessage}")

```

<span aria-hidden="true" id="understanding-the-methods"></span>

## Understanding the methods

<span aria-hidden="true" id="concat-demuxer"></span>

### Concat demuxer

The concat demuxer is faster as it doesn't require re-encoding. It works by:

1. Creating a text file listing input videos
2. Using FFmpeg's concat demuxer to combine them
3. Copying streams directly to the output

This method is ideal when your videos share the same codec and format.

<span aria-hidden="true" id="concat-filter"></span>

### Concat filter

The concat filter is more flexible but slower as it requires re-encoding. Use this method when:

* Videos have different codecs
* You need to add transitions
* You're working with different formats or resolutions

<span aria-hidden="true" id="error-handling-and-optimization"></span>

## Error handling and optimization

Our implementation includes several error handling features:

1. Input validation
2. File existence checks
3. Proper resource cleanup
4. Detailed error reporting

For better performance:

* Use the demuxer method when possible
* Ensure input videos are in the same format
* Consider using hardware acceleration with `-hwaccel`
* Clean up temporary files

<span aria-hidden="true" id="advanced-techniques"></span>

## Advanced techniques

<span aria-hidden="true" id="adding-transitions"></span>

### Adding transitions

To add crossfade transitions between videos:

```scala
def concatenateWithTransitions(
  videos: Seq[VideoFile],
  output: String,
  transitionDuration: Double = 1.0
): Try[Boolean] = Try {
  val inputs = videos.flatMap(v => Seq("-i", v.path))
  val filters = videos.indices.map(i =>
    if i == 0 then s"[$i:v]setpts=PTS-STARTPTS[v$i]"
    else s"[$i:v]setpts=PTS-STARTPTS[v$i];[v${i-1}][v$i]xfade=transition=fade:duration=$transitionDuration:offset=${i*5}[v${i+1}]"
  )

  val cmd = Seq("ffmpeg") ++ inputs ++ Seq(
    "-filter_complex", filters.mkString(";"),
    "-map", s"[v${videos.length}]",
    "-y",
    output
  )

  cmd.! == 0
}

```

<span aria-hidden="true" id="handling-different-formats"></span>

### Handling different formats

When working with different formats, you might need to normalize them first:

```scala
def normalizeVideo(input: VideoFile, output: String): Try[Boolean] = Try {
  val cmd = Seq(
    "ffmpeg",
    "-i", input.path,
    "-c:v", "libx264",
    "-c:a", "aac",
    "-y",
    output
  )

  cmd.! == 0
}

```

<span aria-hidden="true" id="conclusion"></span>

## Conclusion

Video concatenation in Scala using FFmpeg offers a powerful way to combine video files programmatically. The choice between the concat demuxer and filter methods depends on your specific requirements for speed versus flexibility.

For more advanced video processing capabilities, including automated video concatenation at scale, check out Transloadit's Video Encoding service.

\#scala#ffmpeg#video-processing#open-source#command-line#video-encoding-service

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
