Splitting and Processing Large Files

Welcome back! In our previous lesson, we explored how to use FFmpeg and its ffprobe component to analyze media files within Java applications. Today, we will focus on processing large audio and video files by splitting them into smaller, manageable segments using FFmpeg from Java. This approach is essential for efficiently handling large files, ensuring that subsequent processing tasks — such as transcription or analysis — can be performed smoothly and reliably. By leveraging FFmpeg's capabilities from Java, you will be able to automate the splitting of large media files into smaller chunks, making your applications more robust and scalable.

Understanding the Challenge of Large File Processing

Many multimedia processing tasks, such as transcription or analysis, require files to be below a certain size threshold for optimal performance and compatibility with various services. When dealing with large audio or video files, it becomes necessary to divide them into smaller segments that can be processed sequentially. In this lesson, we will use FFmpeg to split large files into chunks of a specified maximum size, all from within a Java program. This ensures that your Java applications can efficiently handle large media files, maintain content quality, and avoid issues related to file size limitations.

Using FFmpeg to Split Media Files: Extracting Media Duration

Before splitting a media file, we need to determine its total duration. This information allows us to calculate how to divide the file into appropriately sized chunks. In Java, we can use the ProcessBuilder class to execute the ffprobe command and capture its output.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class MediaUtils {
    /**
     * Get the duration of a media file using ffprobe.
     * @param filePath Path to the media file.
     * @return Duration in seconds, or -1 if not found.
     */
    public static double getMediaDuration(String filePath) {
        String[] cmd = {
            "ffprobe",
            "-v", "quiet",
            "-show_entries", "format=duration",
            "-of", "default=noprint_wrappers=1:nokey=1",
            filePath
        };
        try {
            ProcessBuilder pb = new ProcessBuilder(cmd);
            Process process = pb.start();
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream())
            );
            String line = reader.readLine();
            process.waitFor();
            if (line != null) {
                return Double.parseDouble(line.trim());
            }
        } catch (Exception e) {
            System.err.println("Error getting media duration: " + e.getMessage());
        }
        return -1;
    }
}

Explanation:
This Java method executes the ffprobe command to retrieve the duration of a media file. It reads the output from the process and parses the duration as a double. If the duration cannot be determined, it returns -1.

Using FFmpeg to Split Media Files: Streaming FFmpeg's Output

Splitting large media files can take time, and FFmpeg will produce logs as it processes the file. To monitor progress in real time, we can stream FFmpeg's output to the Java console using standard input/output handling.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class CommandRunner {
    /**
     * Run a command and stream its output in real time.
     * @param cmd The command to execute.
     * @param desc Optional description to print before running.
     * @throws IOException
     * @throws InterruptedException
     */
    public static void runCommandWithOutput(String[] cmd, String desc) throws IOException, InterruptedException {
        if (desc != null && !desc.isEmpty()) {
            System.out.println("\n" + desc);
        }
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.redirectErrorStream(true);
        Process process = pb.start();
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getInputStream())
        );
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("Command failed with exit code " + exitCode);
        }
    }
}

Explanation:
This helper method runs a command (such as FFmpeg) and streams its output to the console in real time. It uses Java's ProcessBuilder and BufferedReader to read and print each line of output as it becomes available.

Using FFmpeg to Split Media Files: Helper Methods for Chunk Extraction

Now let's create the utility methods that will help us split media files. We'll start with helper methods for extracting file extensions and processing individual chunks.

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class MediaSplitter {
    // Helper to get file extension (including dot), or default to ".tmp"
    private static String getFileExtension(File file) {
        String name = file.getName();
        int lastDot = name.lastIndexOf('.');
        if (lastDot > 0 && lastDot < name.length() - 1) {
            return name.substring(lastDot);
        }
        return ".tmp";
    }

    /**
     * Extract a single chunk from a media file using FFmpeg.
     * @param filePath Path to the original media file.
     * @param chunkIndex Index of the chunk (0-based).
     * @param chunkDuration Duration of each chunk in seconds.
     * @param fileExtension File extension to use for the chunk file.
     * @param numChunks Total number of chunks (for progress display).
     * @return Path to the created chunk file.
     * @throws IOException
     * @throws InterruptedException
     */
    private static String extractChunk(String filePath, int chunkIndex, double chunkDuration, 
                                     String fileExtension, int numChunks) throws IOException, InterruptedException {
        double startTime = chunkIndex * chunkDuration;
        File tempFile = Files.createTempFile("chunk_" + chunkIndex + "_", fileExtension).toFile();

        String[] cmd = {
            "ffmpeg",
            "-i", filePath,
            "-ss", String.valueOf(startTime),
            "-t", String.valueOf(chunkDuration),
            "-c", "copy",
            "-y",
            tempFile.getAbsolutePath()
        };

        CommandRunner.runCommandWithOutput(cmd, "Extracting chunk " + (chunkIndex + 1) + "/" + numChunks);
        return tempFile.getAbsolutePath();
    }
}

Explanation:
The extractChunk method handles the creation of a single media chunk from the original file. It calculates the start time based on the chunk index and duration, creates a temporary file with the appropriate extension, constructs the FFmpeg command to extract the specific time segment, and executes the command while displaying progress information.

Why Use Temporary Files:
We write the output to temporary files using Files.createTempFile() for several important reasons:

  • Unique naming: Temporary files are automatically assigned unique names, preventing conflicts if multiple splitting operations run simultaneously
  • System cleanup: The operating system can automatically clean up temporary files if the application crashes or doesn't properly remove them
  • Isolation: Temporary files are kept separate from user data and application files, reducing clutter in working directories
  • Optimal storage location: The system chooses an appropriate temporary directory (often with faster I/O or more available space)
  • Standard practice: Using temporary files for intermediate processing is a well-established pattern that makes the code more maintainable and predictable

The method returns the path to the newly created chunk file, allowing the main splitMedia method to focus on the overall coordination and chunk management logic.

Using FFmpeg to Split Media Files: Main Splitting Logic

Now we'll implement the main method that orchestrates the entire splitting process by calculating chunk parameters and coordinating the extraction of individual segments.

import java.util.ArrayList;
import java.util.List;

public class MediaSplitter {
    // ... (previous helper methods remain the same)

    /**
     * Split a media file into chunks smaller than the specified size (in MB).
     * @param filePath Path to the media file.
     * @param chunkSizeMB Maximum size of each chunk in megabytes.
     * @return List of chunk file paths.
     * @throws IOException
     * @throws InterruptedException
     */
    public static List<String> splitMedia(String filePath, int chunkSizeMB) throws IOException, InterruptedException {
        System.out.println("\nSplitting media into chunks...");

        // Using the function that we created earlier to extract the duration of the media file
        double duration = MediaUtils.getMediaDuration(filePath);
        if (duration <= 0) {
            throw new IOException("Could not determine media duration");
        }

        File inputFile = new File(filePath);
        // Get the total file size in bytes
        long fileSizeBytes = inputFile.length();
        // Convert desired chunk size from MB to bytes
        long chunkSizeBytes = chunkSizeMB * 1024L * 1024L;
        // Calculate chunk duration based on size proportion (smaller file size = shorter duration)
        double chunkDuration = duration * ((double) chunkSizeBytes / fileSizeBytes);
        // Calculate total number of chunks needed, rounding up
        int numChunks = (int) Math.ceil(duration / chunkDuration);

        List<String> chunks = new ArrayList<>();
        String fileExtension = getFileExtension(inputFile);

        for (int i = 0; i < numChunks; i++) {
            String chunkPath = extractChunk(filePath, i, chunkDuration, fileExtension, numChunks);
            chunks.add(chunkPath);
        }
        System.out.println("Split media into " + chunks.size() + " chunk(s): " + chunks);
        return chunks;
    }
}

Code Explanation:

  1. Initialize Variables:

    • The method retrieves the media file's duration using MediaUtils.getMediaDuration. We need this to calculate how to divide the timeline into appropriately sized chunks.
    • The file size is obtained to calculate the appropriate chunk duration for the specified chunk size in megabytes.
  2. Calculate Chunks:

    • chunkDuration is computed based on the ratio of the desired chunk size to the total file size, multiplied by the total duration.
    • numChunks is the total number of chunks, rounded up. Rounding up prevents losing content if the final chunk would be shorter than the calculated duration.
  3. Create Each Chunk:

    • The method iterates through each chunk index and calls extractChunk to create individual segments. We process chunks sequentially to avoid overwhelming system resources and maintain predictable output.
    • Each chunk file path is added to the list, which is returned at the end.
Checking Yourself: Executing the Media File Split

To test the splitting functionality, you can invoke the method as follows:

public class Main {
    public static void main(String[] args) {
        try {
            // Example: Split a 2MB video file into 1MB chunks
            List<String> chunks = MediaSplitter.splitMedia("resources/sample_video.mp4", 1);
            // Output will be similar to:
            // Splitting media into chunks...
            //
            // Extracting chunk 1/2
            // <ffmpeg output for chunk 1>
            //
            // Extracting chunk 2/2
            // <ffmpeg output for chunk 2>
            //
            // Split media into 2 chunk(s): [/tmp/chunk_0_12345.mp4, /tmp/chunk_1_67890.mp4]
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If your sample_video.mp4 file is around 2MB, splitting it into 1MB chunks will produce two separate files, each containing a segment of the original video. The output will display the progress and the paths to the generated chunk files.

Lesson Summary

Congratulations! You have learned how to split large media files into smaller, manageable chunks using FFmpeg from Java. By integrating FFmpeg commands into your Java applications, you can efficiently process large audio and video files, reduce memory overhead, and enable parallel or sequential processing for improved performance — all while maintaining the quality of your content. You are now equipped to handle large-scale multimedia tasks with confidence and precision in your Java projects!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal