You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Xuggler中写入视频旋转元数据?缩放后元数据丢失问题

解决Xuggler视频缩放后丢失旋转元数据的问题

我明白你遇到的困扰:用Xuggler处理竖屏手机视频时,虽然尺寸缩放正确,但视频的旋转元数据(Rotation)丢失了,导致播放器无法正确识别竖屏显示模式。结合你提供的MediaInfo信息和代码,我来帮你解决这个问题。

问题根源

你的代码目前只处理了视频尺寸的缩放,但没有将原视频的旋转元数据传递到输出视频中。Xuggler的IMediaWriter不会自动复制容器或流的元数据,需要我们手动提取并设置到输出文件里。

解决方案步骤

我们需要分三步操作:

  1. 从原视频中提取旋转角度元数据
  2. 将旋转元数据写入输出视频的容器元数据中
  3. 确保视频缩放尺寸与旋转角度匹配(保证显示比例正确)

修改后的完整代码

1. 主类 MediaConverter 修改

package test.video;
import com.xuggle.mediatool.IMediaReader;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IMetaData;
import com.xuggle.xuggler.IStream;
import java.io.IOException;

public class MediaConverter {
    private static final String INPUT_FILE = "/home/user/input_video.mp4";
    private static final String OUTPUT_FILE = "/home/user/output_video.mp4";

    public static void main(String[] args) throws IOException {
        // 原视频旋转90°后实际是1080x1920(9:16比例),所以目标尺寸设为360x640保持竖屏比例
        convertVideo(INPUT_FILE, OUTPUT_FILE, 360, 640);
    }

    private static void convertVideo(String inputFile, String outputFile, int width, int height) {
        // 第一步:提取原视频的旋转元数据
        int rotation = getVideoRotation(inputFile);

        // 初始化Reader
        IMediaReader reader = ToolFactory.makeReader(inputFile);
        Resizer resizer = new Resizer(width, height);
        reader.addListener(resizer);

        // 初始化Writer并设置旋转元数据
        IMediaWriter writer = ToolFactory.makeWriter(outputFile, reader);
        setRotationMetadata(writer, rotation);

        resizer.addListener(writer);

        // 处理视频数据包
        while (reader.readPacket() == null) {
            // 空循环,让reader自动处理所有数据包
        }

        // 释放资源
        writer.flush();
        writer.close();
        reader.close();
    }

    // 从原视频中提取旋转角度(兼容容器/流两种元数据存储方式)
    private static int getVideoRotation(String inputFile) {
        IContainer container = IContainer.make();
        int rotation = 0;
        try {
            if (container.open(inputFile, IContainer.Type.READ, null) < 0) {
                throw new IllegalArgumentException("无法打开输入文件: " + inputFile);
            }

            // 先尝试从容器元数据获取旋转信息
            IMetaData containerMetaData = container.getMetaData();
            if (containerMetaData != null) {
                String rotationStr = containerMetaData.getValue("rotation");
                if (rotationStr != null) {
                    rotation = Integer.parseInt(rotationStr);
                }
            }

            // 如果容器元数据没有,尝试从视频流元数据获取
            if (rotation == 0) {
                IStream videoStream = container.getStream(0);
                if (videoStream != null) {
                    IMetaData streamMetaData = videoStream.getMetaData();
                    if (streamMetaData != null) {
                        String rotationStr = streamMetaData.getValue("rotation");
                        if (rotationStr != null) {
                            rotation = Integer.parseInt(rotationStr);
                        }
                    }
                }
            }
        } finally {
            if (container.isOpen()) {
                container.close();
            }
        }
        return rotation;
    }

    // 将旋转元数据设置到输出视频容器
    private static void setRotationMetadata(IMediaWriter writer, int rotation) {
        IMetaData outputMetaData = writer.getContainer().getMetaData();
        if (outputMetaData == null) {
            outputMetaData = IMetaData.make();
            writer.getContainer().setMetaData(outputMetaData);
        }
        outputMetaData.setValue("rotation", String.valueOf(rotation));
    }
}

2. Resizer 类修改

移除onAddStream中无效的元数据操作,只保留视频重采样核心逻辑:

package test.video;
import com.xuggle.mediatool.MediaToolAdapter;
import com.xuggle.mediatool.event.IVideoPictureEvent;
import com.xuggle.mediatool.event.VideoPictureEvent;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.IVideoResampler;

public class Resizer extends MediaToolAdapter {
    private Integer width;
    private Integer height;
    private IVideoResampler videoResampler = null;

    public Resizer(Integer aWidth, Integer aHeight) {
        this.width = aWidth;
        this.height = aHeight;
    }

    @Override
    public void onVideoPicture(IVideoPictureEvent event) {
        IVideoPicture pic = event.getPicture();
        if (videoResampler == null) {
            // 初始化视频重采样器,匹配原视频像素格式
            videoResampler = IVideoResampler.make(width, height, pic.getPixelType(),
                    pic.getWidth(), pic.getHeight(), pic.getPixelType());
        }

        // 创建输出视频帧并完成重采样
        IVideoPicture out = IVideoPicture.make(pic.getPixelType(), width, height);
        videoResampler.resample(out, pic);

        // 将处理后的帧传递给Writer
        super.onVideoPicture(new VideoPictureEvent(event.getSource(), out, event.getStreamIndex()));

        // 释放资源
        out.delete();
    }
}

关键说明

  1. 旋转元数据的兼容性:有些视频的旋转信息存在容器元数据中,有些存在视频流元数据中,所以我们两种方式都尝试获取,确保不会遗漏。
  2. 尺寸比例匹配:原视频是1920x1080旋转90°,实际显示比例为9:16(竖屏),所以缩放时目标尺寸设为360x640(保持相同比例),避免旋转后显示变形。
  3. 资源管理:添加了reader.close()和writer.close(),避免Xuggler的资源泄漏问题。

验证方法

修改完成后,用MediaInfo查看输出视频的元数据,应该能看到Rotation : 90°字段,然后在手机和电脑播放器中打开视频,会正常以竖屏模式显示。

内容的提问来源于stack exchange,提问作者ulanBator

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:46:59