技术经验谈 技术经验谈
首页
  • 最佳实践

    • 抓包
    • 数据库操作
  • ui

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 总纲
  • 整体开发框架
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

hss01248

一号线程序员
首页
  • 最佳实践

    • 抓包
    • 数据库操作
  • ui

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • 《Vue》
    • 《React》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • TypeScript
    • JS设计模式总结
  • 总纲
  • 整体开发框架
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • apps

  • app

    • 图片压缩

      • 360全景图的展示
      • 360全景图的压缩
      • motion photo的压缩
        • 文件格式标准:
        • by xmp
        • by byte:
    • 天下(anyTimeAnyWhere)
  • 作品
  • app
  • 图片压缩
hss01248
2025-10-10
目录

motion photo的压缩

# motion photo/ live photo的压缩

# 判断是否为动态图

# 文件格式标准:

https://developer.android.com/media/platform/motion-photo-format?hl=zh-cn

# by xmp

按谷歌的标准,判断xmp里是否有关键标签,有了之后,提取其中关键的字段:视频文件的大小

            String androidXmp = "xmlns:GCamera=";
            String iosXmp = "iosxxx";
            if (!xmp.contains(androidXmp) && !xmp.contains(iosXmp)) {
                return false;
            }
            if (xmp.contains(androidXmp)) {
                LogUtils.d("读取xmp",xmp);
                    String regex0 = "GCamera:MicroVideoOffset=\"(\\d+)\"";
                    // 创建 Pattern 对象
                    Pattern pattern0 = Pattern.compile(regex0);
                    // 创建 matcher 对象
                    Matcher matcher0 = pattern0.matcher(xmp);
                    // 查找并提取数字
                    if (matcher0.find()) {
                        String number = matcher0.group(1);
                        int length = Integer.parseInt(number);
                        System.out.println("提取到的数字是: " + number);

                        if (wholeFileLength <= length) {
                            System.out.println("文件大小小于视频文件大小, xmp显示是动态图,但实际不是 " + fileOrUriPath);
                            return false;
                        }
                        //提取视频文件
                        if (extractVideo) {
                            extractMp4FromMotionPhoto(fileOrUriPath, motion.mp4CacheFile(fileOrUriPath), wholeFileLength - length, length);
                        }

                        return true;
                    } else {
                        String regex = "<Container:Item\\s+Item:Mime=\"video/mp4\"\\s+Item:Semantic=\"MotionPhoto\"\\s+Item:Length=\"(\\d+)\"\\s+Item:Padding=\"(\\d+)\"\\s*/>";
                        Pattern pattern = Pattern.compile(regex);
                        Matcher matcher = pattern.matcher(xmp);
                        if (matcher.find()) {
                            String length = matcher.group(1);
                            String padding = matcher.group(2);
                            System.out.println("length: " + length + ", padding:" + padding);
                            int len = Integer.parseInt(length);
                            int pad = Integer.parseInt(padding);
                            if (wholeFileLength <= len + pad) {
                                System.out.println("文件大小小于视频文件大小2, xmp显示是动态图,但实际不是 " + fileOrUriPath);
                                return false;
                            }
                            //提取视频文件
                            if (extractVideo) {
                                extractMp4FromMotionPhoto(fileOrUriPath, motion.mp4CacheFile(fileOrUriPath), wholeFileLength - (len + pad), len);
                            }
                            return true;

                        } else {
                            LogUtils.w("没有找到xmp里匹配的模式。");
                        }
                }
            }else {
                LogUtils.w("没有找到xmp里匹配的模式2");
            }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# by byte:

最准确,且不受于不同版本的协议差异影响

但是需要逐个比对字节,io耗时较多

image-20251010144116557

文件结尾:

image-20251010152015500

mp4文件头格式:

image-20251010171419304

# 压缩前后:

分别对图片和视频都做了压缩,rotation/orientation不为0的,还对原图和原视频做了旋转处理并将rotation//orientation置为0

xmp为空的,将视频文件大小写入到motion photo模板中:

public static final String xmpTmplate = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.1.0-jc003\">" +
            "  <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">" +
            "<rdf:Description rdf:about=\"\"" +
            "xmlns:GCamera=\"http://ns.google.com/photos/1.0/camera/\"" +
            "xmlns:MiCamera=\"http://ns.xiaomi.com/photos/1.0/camera/\"" +
            "GCamera:MicroVideoVersion=\"1\"" +
            "GCamera:MicroVideo=\"1\"" +
            "GCamera:MicroVideoOffset=\"xxxxxx\"" +
            "GCamera:MicroVideoPresentationTimestampUs=\"900999\"" +
            "MiCamera:XMPMeta=\"&lt;?xml version='1.0' encoding='UTF-8' standalone='yes' ?&gt;\"/>" +
            " </rdf:RDF>" +
            "</x:xmpmeta>";
1
2
3
4
5
6
7
8
9
10
11
12

Xnip2025-10-11_10-57-35

原图:

Xnip2025-10-11_10-58-16

Xnip2025-10-11_11-03-39

压缩后:

Xnip2025-10-11_10-59-26

Xnip2025-10-11_11-02-39

# 兼容性

荣耀手机没有按谷歌的motion photo协议来,是自己瞎搞的.压缩后,系统相册无法识别图片为动态图. 小米相册没有这个问题.

Xnip2025-10-11_11-09-20

将荣耀手机拍的动态图,无xmp,只能通过字节比对来判断

移除图片中的视频,荣耀相册依然识别为动态图,可见荣耀相册是在数据库增加一个字段的方式记录一张图片是否为动态图,而非根据xmp或字节规律来判断.他们的这种方式是比较初级的,鲁棒性不够,没有任何兼容性.

# 代码

编辑 (opens new window)
360全景图的压缩
天下(anyTimeAnyWhere)

← 360全景图的压缩 天下(anyTimeAnyWhere)→

最近更新
01
360全景图的压缩
10-10
02
360全景图的展示
10-10
03
webview里blob的下载和保存
09-11
更多文章>
Theme by Vdoing | Copyright © 2020-2025 | 粤ICP备20041795号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式