javafilemp4filechannel

Java - MP4 files appear corrupt after split


I followed this tutorial detailing how to split a large file into smaller files based on maximum allowed file size. It works - for example, it successfully splits a 84.4MB mp4 file into 30MB, 30MB and 24.4MB mp4 files. However, I found that the smaller files that are created cannot be played or are corrupted for some reason.

Output:

Smaller files created from original file

Windows Media Player reports:

Windows Media Player cannot play the file. The Player might not support the file type or might not support the codec that was used to compress the file.

Similarly in another media application:

Can't play
This file isn't playable. That might be because the file type is unsupported, the file extension is incorrect, or the file is corrupt.

VideoSplit.java:

import java.util.List;
import java.util.ArrayList;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.UUID;

public class VideoSplit
{
    
    public static void main(String args[])
    {
        
        try {
            splitFile("C:\\Users\\xxxxxxxx\\Desktop\\JavaTest\\Vid\\DJI_0079.mp4", 30);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // version 3.0
private static final String dir = "C:\\Users\\xxxxxxxx\\Desktop\\JavaTest\\Vid\\";
private static final String suffix = ".mp4";

/**
 * Split a file into multiples files.
 *
 * @param fileName   Name of file to be split.
 * @param mBperSplit maximum number of MB per file.
 * @throws IOException
 */
public static void splitFile(final String fileName, final int mBperSplit) throws IOException {

    if (mBperSplit <= 0) {
        throw new IllegalArgumentException("mBperSplit must be more than zero");
    }

    List<Path> partFiles = new ArrayList<>();
    final long sourceSize = Files.size(Paths.get(fileName));
    final long bytesPerSplit = 1024L * 1024L * mBperSplit;
    final long numSplits = sourceSize / bytesPerSplit;
    final long remainingBytes = sourceSize % bytesPerSplit;
    int position = 0;

    try (RandomAccessFile sourceFile = new RandomAccessFile(fileName, "r");
         FileChannel sourceChannel = sourceFile.getChannel()) {

        for (; position < numSplits; position++) {
            //write multipart files.
            writePartToFile(bytesPerSplit, position * bytesPerSplit, sourceChannel, partFiles);
        }

        if (remainingBytes > 0) {
            writePartToFile(remainingBytes, position * bytesPerSplit, sourceChannel, partFiles);
        }
    }
    //return partFiles;
}

private static void writePartToFile(long byteSize, long position, FileChannel sourceChannel, List<Path> partFiles) throws IOException {
    Path fileName = Paths.get(dir + UUID.randomUUID() + suffix);
    try (RandomAccessFile toFile = new RandomAccessFile(fileName.toFile(), "rw");
         FileChannel toChannel = toFile.getChannel()) {
        sourceChannel.position(position);
        toChannel.transferFrom(sourceChannel, 0, byteSize);
    }
    partFiles.add(fileName);
}
}

What could be going wrong here?


Solution

  • A video file contains a block of metadata, followed by the video content data.

    What your method does is not splitting video files. It's just splitting files blindly, i.e. without any regard to the type of file. The big difference is that if you blindly split a file, only one of the files will contain the metadata. The other files will just have raw data without any description of what they contain.

    To split video files properly, you'll need to use a library that understands the structure of mp4 files. This is too complicated to describe in text here, so your best bet is to search the internet for programmer utilities for handling mp4 data.