首页 > 编程问答 >ffprobe 不反映 mp4 尺寸编辑

ffprobe 不反映 mp4 尺寸编辑

时间:2024-07-26 06:35:07浏览次数:14  
标签:python mp4 ffprobe exiftool

我正在尝试编辑 mp4 宽度和高度而不缩放

我通过编辑 tkhd 和 stsd 框来做到这一点

  • exiftool 将显示新的宽度和高度,但 ffprobe 不会

之前:

$ exiftool $f | egrep -i 'width|height'
Image Width                     : 100
Image Height                    : 100
Source Image Width              : 100
Source Image Height             : 100
$ ffprobe -v error -select_streams v:0 -show_entries 'stream=width,height' -of 'csv=s=x:p=0' $f
100x100

以下 python 文件输出:

[ftyp] size:32
[mdat] size:196933
[moov] size:2057
- [mvhd] size:108
- [trak] size:1941
- - [tkhd] size:92
     Updated tkhd box: Width: 100 -> 300, Height: 100 -> 400
- - [mdia] size:1841
- - - [mdhd] size:32
- - - [hdlr] size:44
- - - [minf] size:1757
- - - - [vmhd] size:20
- - - - [dinf] size:36
- - - - - [dref] size:28
- - - - [stbl] size:1693
- - - - - [stsd] size:145
           Updated stsd box #1: Width: 100 -> 300, Height: 100 -> 400
- - - - - [stts] size:512
- - - - - [stss] size:56
- - - - - [stsc] size:28
- - - - - [stsz] size:924
- - - - - [stco] size:20

再次运行 exiftool & ffprobe:

$ exiftool $f egrep -i 'width|height'
Image Width                     : 300
Image Height                    : 400
Source Image Width              : 300
Source Image Height             : 400
$ ffprobe -v error -select_streams v:0 -show_entries 'stream=width,height' -of 'csv=s=x:p=0' $f
100x100
import sys, struct

def read_box(f):
    offset = f.tell()
    header = f.read(8)
    if len(header) < 8:
        return None, offset
    size, box_type = struct.unpack(">I4s", header)
    box_type = box_type.decode("ascii")
    if size == 1:
        size = struct.unpack(">Q", f.read(8))[0]
    elif size == 0:
        size = None
    return {"type": box_type, "size": size, "start_offset": offset}, offset

def edit_tkhd_box(f, box_start, new_width, new_height, depth):
    f.seek(box_start + 84, 0)  # Go to the width/height part in tkhd box
    try:
        old_width = struct.unpack('>I', f.read(4))[0] >> 16
        old_height = struct.unpack('>I', f.read(4))[0] >> 16
        f.seek(box_start + 84, 0)  # Go back to write
        f.write(struct.pack('>I', new_width << 16))
        f.write(struct.pack('>I', new_height << 16))
        print(f"{'  ' * depth} Updated tkhd box: Width: {old_width} -> {new_width}, Height: {old_height} -> {new_height}")
    except struct.error:
        print(f"  Error reading or writing width/height to tkhd box")

def edit_stsd_box(f, box_start, new_width, new_height, depth):
    f.seek(box_start + 12, 0)  # Skip to the entry count in stsd box
    try:
        entry_count = struct.unpack('>I', f.read(4))[0]
        for i in range(entry_count):
            entry_start = f.tell()
            f.seek(entry_start + 4, 0)  # Skip the entry size
            format_type = f.read(4).decode("ascii", "ignore")
            if format_type == "avc1":
                f.seek(entry_start + 32, 0)  # Adjust this based on format specifics
                try:
                    old_width = struct.unpack('>H', f.read(2))[0]
                    old_height = struct.unpack('>H', f.read(2))[0]
                    f.seek(entry_start + 32, 0)  # Go back to write
                    f.write(struct.pack('>H', new_width))
                    f.write(struct.pack('>H', new_height))
                    print(f"{'  ' * depth} Updated stsd box #{i + 1}: Width: {old_width} -> {new_width}, Height: {old_height} -> {new_height}")
                except struct.error:
                    print(f"  Error reading or writing dimensions to avc1 format in entry {i + 1}")
            else:
                f.seek(entry_start + 8, 0)  # Skip to the next entry
    except struct.error:
        print(f"  Error reading or writing entries in stsd box")

def parse_and_edit_boxes(f, new_width, new_height, depth=0, parent_size=None):
    while True:
        current_pos = f.tell()
        if parent_size is not None and current_pos >= parent_size:
            break
        box, box_start = read_box(f)
        if not box:
            break
        box_type, box_size = box["type"], box["size"]
        print(f'{"- " * depth}[{box_type}] size:{box_size}')
        
        if box_type == "tkhd":
            edit_tkhd_box(f, box_start, new_width, new_height, depth)
        elif box_type == "stsd":
            edit_stsd_box(f, box_start, new_width, new_height, depth)
        
        # Recursively parse children if it's a container box
        if box_type in ["moov", "trak", "mdia", "minf", "stbl", "dinf", "edts"]:
            parse_and_edit_boxes(f, new_width, new_height, depth + 1, box_start + box_size)
        
        if box_size is None:
            f.seek(0, 2)  # Move to the end of file
        else:
            f.seek(box_start + box_size, 0)

if __name__ == '__main__':
    if len(sys.argv) != 4:
        print("Usage: python script.py <input_file> <new_width> <new_height>")
    else:
        with open(sys.argv[1], 'r+b') as f:
            parse_and_edit_boxes(f, int(sys.argv[2]), int(sys.argv[3]))

这个问题的根源在于你混淆了视频文件中存储的两种不同的“分辨率”:

  1. 存储分辨率: 这是视频实际编码的分辨率,也就是视频帧的像素尺寸。你通过修改 avc1 atom中的宽度和高度值来改变的就是这个分辨率。
  2. 显示分辨率: 这是视频播放时应该显示的分辨率,它不一定与存储分辨率相同。播放器会根据存储分辨率、显示比例、画面裁剪等信息来决定最终的显示分辨率。你修改 tkhd atom中的宽度和高度值影响的是这个分辨率。

ffprobe默认显示的是存储分辨率,而exiftool显示的则是显示分辨率。 这就是为什么你修改了 avc1 atom后,exiftool能识别到变化,而ffprobe没有变化的原因。

为了让ffprobe也显示修改后的分辨率,你需要 重新编码视频 ,使存储分辨率和显示分辨率保持一致。你可以使用以下命令来重新编码视频:

ffmpeg -i input.mp4 -c:v libx264 -vf scale=300:400 output.mp4

这个命令会使用 libx264 编码器将视频重新编码为300x400的分辨率,并将结果保存到 output.mp4 文件中。

另外,还需要注意以下几点:

  • 修改 tkhd atom中的分辨率信息并不会改变视频的实际分辨率,只是改变了播放器对视频显示分辨率的理解。如果显示分辨率与存储分辨率不匹配,播放器可能会对视频进行拉伸或裁剪,导致画面变形。
  • 直接修改视频文件可能会导致文件损坏,建议在修改之前备份原始文件。
  • 如果你只想改变视频的显示比例而不改变实际分辨率,可以使用视频播放器提供的画面缩放功能。

希望这些信息能帮到你!

标签:python,mp4,ffprobe,exiftool
From: 78795606

相关文章

  • 84 我正在使用 Python 开发 selenium 自动化项目。我收到错误 .NoSuchElementExceptio
    场景是这样的,我将打开一个网页,在使用selenium单击该网页后,它会要求位置访问权限,屏幕上会出现一堆按钮,我正在尝试定位其中一个按钮,但即使正确给出了Xpath地址,我得到.NoSuchElementException:错误能够单击目标按钮你正在使用Selenium在Python中开发自动化项目,并遇到......
  • 在Python 3中删除两个指定字符串之间的字符串
    我正在从事一个NLP项目,该项目要求我从一段文本中删除计算机代码。代码包含在标签<pre><code>和</code></pre>之间。现在我可以做一个简单的正则表达式匹配,但我想概括这个函数,以便它可以删除任何两个指定字符串之间的文本,即使它们是嵌套的。例如,如果我有一个......
  • Azure Open AI - Python 和 Java API 之间 gpt4o 的结果截然不同
    我使用Java和PythonAPI对AzureOpenAI进行相同的调用,但收到截然不同的结果:相同的系统提示相同的用户提示适用于Java和Python的azureai包的相同(最新)版本尽管输入的用户和系统提示完全相同,但响应却非常不同-python提示是“正确的”并......
  • leetcode 输出错误? (Python)
    我的VSCode/本地终端给出了[1,4,1,5,1,6]的正确输出,但不知何故leetcode给了我完全不同的输出。我在这里错过了什么吗?这怎么可能?顺便说一下,这是wigglesort2将我的本地代码复制粘贴到leetcode中给出了不同的输出数组很难在没有看到你的代码的情况下......
  • 当 python 窗口的一部分不在屏幕上时,如何让它自己被记录?
    在Windows10中,大多数应用程序窗口都可以使用OBS等程序进行记录。当窗口被拖动以致其部分内容在显示屏上不可见时,通常OBS仍会接收窗口的内容,即使它在屏幕上不可见。但是,在编写python应用程序时,这似乎不以相同的方式工作。我尝试了几种不同的类似GUI的模块......
  • 使用 aws cdk 设置用户池客户端属性以具有读/写访问权限 - Python
    我试图根据属性给予一些自定义属性特定的读/写访问权限。我收到此错误。资源处理程序返回消息:“无效写入创建客户端时指定的属性(服务:CognitoIdentityProvider,状态代码:400,请求ID:<request_id>)”(RequestToken:<request_token>,HandlerErrorCode:InvalidRequest)任何人都可以为......
  • 试图找出此页面的逻辑:存储了大约 ++ 100 个结果 - 并使用 Python 和 BS4 进行了解析
    试图找出此页面背后的逻辑:我们已将一些结果存储在以下数据库中:https://www.raiffeisen.ch/rch/de/ueber-uns/raiffeisen-gruppe/Organization/raiffeisenbanken/deutsche-schweiz.html#accordionitem_18104049731620873397从a到z大约:120个结果或更多:......
  • 如何在 Numpy Python 中将 4 维数组的下三角形复制到上三角形?
    目标是将下三角形复制到上三角形。根据OP中提出的建议,起草了以下代码。importnumpyasnplw_up_pair=np.tril_indices(4,-1)arr=np.zeros((4,4,1,1))arr[1,:1,:,0]=1arr[2,:2,0,0]=2arr[3,:3,0,0]=3arr=arr+arr.T-np.diag(np.diag(arr))但是,它......
  • 如何在 Python 中对多行使用单个 INSERT INTO 语句?
    我目前正在开发一个DiscordPython机器人,我在其中循环遍历ForumTags列表,并为每个对象生成INSERTINTOSQL语句以将数据插入MySQL数据库。但是,我想要通过将所有这些单独的INSERTINTO语句组合到单个查询中来优化我的代码,如下所示:INSERTINTO......
  • 双 for 循环的 Pythonic 方式
    我有以下代码:importnumpyasnpepsilon=np.array([[0.,0.00172667,0.00071437,0.00091779,0.00154501],[0.00128983,0.,0.00028139,0.00215905,0.00094862],[0.00035811,0.00018714,0.,0.00029365,0.00036993......