Java mp4parser 实现视频mp4 切割

pom文件

XML 复制代码
<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>

实现代码

java 复制代码
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();

        // 转换文件大小为GB
        double 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接口的所有方法 ===

        @Override
        public List<SampleEntry> getSampleEntries() {
            return source.getSampleEntries();
        }

        @Override
        public long[] getSampleDurations() {
            return sampleDurations;
        }

        @Override
        public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
            return compositionTimeEntries;
        }

        @Override
        public long[] getSyncSamples() {
            return syncSamples;
        }

        @Override
        public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
            return sampleDependencies;
        }

        @Override
        public SubSampleInformationBox getSubsampleInformationBox() {
            return subSampleInformationBox;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public List<Edit> getEdits() {
            return edits;
        }

        @Override
        public Map<GroupEntry, long[]> getSampleGroups() {
            return sampleGroups;
        }

        @Override
        public List<Sample> getSamples() {
            return samples;
        }

        @Override
        public TrackMetaData getTrackMetaData() {
            return trackMetaData;
        }

        @Override
        public String getHandler() {
            return source.getHandler();
        }

        @Override
        public long getDuration() {
            return (long)((endTimeUs - startTimeUs) * trackMetaData.getTimescale() / 1000000.0);
        }

        @Override
        public 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();
        }
    }
}
相关推荐
AI视觉网奇3 小时前
rknn yolo11 推理
前端·人工智能·python
fly-phantomWing3 小时前
Maven的安装与配置的详细步骤
java·后端·maven·intellij-idea
AI数据皮皮侠4 小时前
中国各省森林覆盖率等数据(2000-2023年)
大数据·人工智能·python·深度学习·机器学习
西柚小萌新5 小时前
【深入浅出PyTorch】--3.1.PyTorch组成模块1
人工智能·pytorch·python
2401_841495646 小时前
【数据结构】红黑树的基本操作
java·数据结构·c++·python·算法·红黑树·二叉搜索树
西猫雷婶6 小时前
random.shuffle()函数随机打乱数据
开发语言·pytorch·python·学习·算法·线性回归·numpy
学编程的小鬼6 小时前
SpringBoot 自动装配原理剖析
java·spring boot·后端
♛小小小让让7 小时前
RabbitMQ (二)参数
笔记·python·rabbitmq
fly-phantomWing7 小时前
在命令提示符页面中用pip命令行安装Python第三方库的详细步骤
开发语言·python·pip
@@神农8 小时前
maven的概述以及在mac安装配置
java·macos·maven