pom文件
<dependency><groupId>org.mp4parser</groupId><artifactId>isoparser</artifactId><version>1.9.36</version>
</dependency>
<dependency><groupId>org.mp4parser</groupId><artifactId>muxer</artifactId><version>1.9.36</version>
</dependency>
实现代码
package com.test.vodio;import org.mp4parser.BasicContainer;
import org.mp4parser.Container;
import org.mp4parser.IsoFile;
import org.mp4parser.boxes.iso14496.part12.CompositionTimeToSample;
import org.mp4parser.boxes.iso14496.part12.SampleDependencyTypeBox;
import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox;
import org.mp4parser.boxes.iso14496.part12.SubSampleInformationBox;
import org.mp4parser.boxes.sampleentry.SampleEntry;
import org.mp4parser.boxes.samplegrouping.GroupEntry;
import org.mp4parser.muxer.*;
import org.mp4parser.muxer.builder.DefaultMp4Builder;
import org.mp4parser.muxer.container.mp4.MovieCreator;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.stream.Collectors;/*** @author JR* @version 1.0* @description:* @date 2025年09月11日 17:01*/
public class Test {// 4.3G的字节数 (1G = 1024^3字节)private static final long MAX_FILE_SIZE_BYTES = (long) (4.3 * 1024 * 1024 * 1024);/*** 按时长切割MP4文件,大于4.3G时自动计算切割次数* @param inputFilePath 输入文件路径* @param outputDir 输出目录* @throws IOException 处理过程中可能抛出的IO异常*/public void splitMP4ByDuration(String inputFilePath, String outputDir) throws IOException {File outputDirectory = new File(outputDir);if (!outputDirectory.exists()) {outputDirectory.mkdirs();}File inputFile = new File(inputFilePath);long fileSize = inputFile.length();// 转换文件大小为GBdouble fileSizeGB = (double) fileSize / (1024 * 1024 * 1024);System.out.printf("文件大小: %.2f GB%n", fileSizeGB);// 如果文件小于等于4.3G,不处理if (fileSize <= MAX_FILE_SIZE_BYTES) {System.out.println("文件小于等于4.3G,不需要切割");return;}// 计算需要切割的次数(向上取整)int splitCount = (int) Math.ceil(fileSizeGB / 4.3);System.out.printf("文件大于4.3G,需要切割为 %d 个片段%n", splitCount);// 读取原始视频Movie movie = MovieCreator.build(inputFilePath);List<Track> tracks = movie.getTracks();// 获取视频总时长(微秒)- 修正:使用1.9.41版本支持的方式long totalDurationUs = getTotalDurationUs(tracks);// 计算每个片段的时长(微秒)long segmentDurationUs = totalDurationUs / splitCount;// 切割并保存每个片段for (int i = 0; i < splitCount; i++) {long startTimeUs = i * segmentDurationUs;// 最后一个片段包含剩余的所有时长long endTimeUs = (i == splitCount - 1) ? totalDurationUs : (i + 1) * segmentDurationUs;List<Track> segmentTracks = new ArrayList<>();for (Track track : tracks) {Track segmentTrack = new CroppedTrack(track, startTimeUs, endTimeUs);segmentTracks.add(segmentTrack);}Movie segmentMovie = new Movie();for (Track track : segmentTracks) {segmentMovie.addTrack(track);}DefaultMp4Builder builder = new DefaultMp4Builder();BasicContainer build = (BasicContainer) builder.build(segmentMovie);String outputFilePath = outputDir + File.separator +getFileNameWithoutExtension(inputFile) + "_part" + (i + 1) + ".mp4";try (FileOutputStream fos = new FileOutputStream(outputFilePath);FileChannel fc = fos.getChannel()) {build.writeContainer(fc);}System.out.printf("已生成片段 %d: %s%n", i + 1, outputFilePath);}}/*** 修正:计算视频总时长(微秒),使用1.9.41版本的TrackMetaData*/private long getTotalDurationUs(List<Track> tracks) {long maxDuration = 0;for (Track track : tracks) {// 1.9.41版本通过TrackMetaData获取时间信息TrackMetaData trackMetaData = track.getTrackMetaData();if (trackMetaData != null) {// 时长 = 轨道持续时间 / 时间尺度 * 1000000(转换为微秒)long duration = (long)(track.getDuration() * 1000000.0 / trackMetaData.getTimescale());if (duration > maxDuration) {maxDuration = duration;}}}return maxDuration;}private String getFileNameWithoutExtension(File file) {String fileName = file.getName();int lastDotIndex = fileName.lastIndexOf('.');if (lastDotIndex > 0) {return fileName.substring(0, lastDotIndex);}return fileName;}/*** 完整实现Track接口的裁剪轨道类*/private static class CroppedTrack implements Track {private final Track source;private final long startTimeUs;private final long endTimeUs;private List<Sample> samples;private TrackMetaData trackMetaData;private List<Long> sampleDurationsList;private long[] sampleDurations;private List<CompositionTimeToSample.Entry> compositionTimeEntries;private long[] syncSamples;private List<SampleDependencyTypeBox.Entry> sampleDependencies;private SubSampleInformationBox subSampleInformationBox;private String name;private List<Edit> edits;private Map<GroupEntry, long[]> sampleGroups;public CroppedTrack(Track source, long startTimeUs, long endTimeUs) {this.source = source;this.startTimeUs = startTimeUs;this.endTimeUs = endTimeUs;this.trackMetaData = source.getTrackMetaData();this.sampleDurationsList = new ArrayList<>();calculateTrackData();}/*** 计算裁剪后轨道的所有核心数据*/private void calculateTrackData() {samples = new ArrayList<>();sampleDurationsList.clear();long currentTimeUs = 0;long timeScale = trackMetaData.getTimescale();List<Sample> sourceSamples = source.getSamples();List<Long> sourceSampleDurationsList = Arrays.stream(source.getSampleDurations()).boxed().collect(Collectors.toList());// 1. 筛选样本并记录时长for (int i = 0; i < sourceSamples.size(); i++) {Sample sample = sourceSamples.get(i);long sampleDuration = sourceSampleDurationsList.get(i);long sampleDurationUs = (long)(sampleDuration * 1000000.0 / timeScale);if (currentTimeUs + sampleDurationUs > startTimeUs && currentTimeUs < endTimeUs) {samples.add(sample);sampleDurationsList.add(sampleDuration);}currentTimeUs += sampleDurationUs;if (currentTimeUs >= endTimeUs) break;}// 2. 转换样本时长为数组sampleDurations = sampleDurationsList.stream().mapToLong(Long::longValue).toArray();// 3. 处理合成时间条目compositionTimeEntries = source.getCompositionTimeEntries();// 4. 筛选同步样本(关键帧等)syncSamples = filterSyncSamples(source.getSyncSamples());sampleDependencies = source.getSampleDependencies();// 6. 子样本信息subSampleInformationBox = source.getSubsampleInformationBox();// 7. 轨道名称name = source.getName();// 8. 编辑信息edits = source.getEdits();// 9. 样本组信息sampleGroups = source.getSampleGroups();}/*** 筛选出在裁剪样本范围内的同步样本索引*/private long[] filterSyncSamples(long[] sourceSyncSamples) {if (sourceSyncSamples == null || sourceSyncSamples.length == 0) {return new long[0];}List<Long> filtered = new ArrayList<>();int sourceSampleCount = source.getSamples().size();int targetSampleCount = samples.size();// 只保留在裁剪后样本范围内的同步点for (long syncSample : sourceSyncSamples) {if (syncSample > 0 && syncSample <= sourceSampleCount &&syncSample <= targetSampleCount) {filtered.add(syncSample);}}return filtered.stream().mapToLong(Long::longValue).toArray();}// === 实现Track接口的所有方法 ===@Overridepublic List<SampleEntry> getSampleEntries() {return source.getSampleEntries();}@Overridepublic long[] getSampleDurations() {return sampleDurations;}@Overridepublic List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {return compositionTimeEntries;}@Overridepublic long[] getSyncSamples() {return syncSamples;}@Overridepublic List<SampleDependencyTypeBox.Entry> getSampleDependencies() {return sampleDependencies;}@Overridepublic SubSampleInformationBox getSubsampleInformationBox() {return subSampleInformationBox;}@Overridepublic String getName() {return name;}@Overridepublic List<Edit> getEdits() {return edits;}@Overridepublic Map<GroupEntry, long[]> getSampleGroups() {return sampleGroups;}@Overridepublic List<Sample> getSamples() {return samples;}@Overridepublic TrackMetaData getTrackMetaData() {return trackMetaData;}@Overridepublic String getHandler() {return source.getHandler();}@Overridepublic long getDuration() {return (long)((endTimeUs - startTimeUs) * trackMetaData.getTimescale() / 1000000.0);}@Overridepublic void close() throws IOException {source.close();}}public static void main(String[] args) {Test splitter = new Test();try {splitter.splitMP4ByDuration("C:\\Users\\Administrator\\Desktop\\魔童闹海.mp4", "output_segments");System.out.println("切割完成");} catch (IOException e) {e.printStackTrace();}}
}