当前位置:首页 > 能源 > 正文

用Golang解决YCC365回放视频时间不对的问题,我是这样一步步搞定的

  • 能源
  • 2026-07-23 22:54:28
  • 84
摘要: 说实话,刚开始碰到YCC365回放视频时间不对这个问题的时候,我也是懵的,明明录像文件在SD卡里存得好好的,点开回放,时间戳却乱...

说实话,刚开始碰到YCC365回放视频时间不对这个问题的时候,我也是懵的,明明录像文件在SD卡里存得好好的,点开回放,时间戳却乱得离谱——有时候显示的是2045年,有时候直接跳到1970年,还有一次给我显示的是凌晨3点,但我明明记得那个时间段是下午拍的,你说气不气人?

但后来我琢磨了一下,这种时间错乱的问题,大概率不是摄像头坏了,而是文件元数据里的时间戳被写错了,尤其是YCC365这类摄像头,它存储视频时用的是嵌入式Linux系统,时间同步机制本来就脆弱,再加上NTP服务器有时候连不上,时间就乱套了。

我本身是个Go语言的爱好者,想着能不能写个小工具来自动修正这些视频文件的时间,一开始我也想过用Python,但考虑到后面可能要处理大批量文件,还可能要跑在树莓派或者路由器上,Go的单文件部署并发优势就体现出来了。

为什么YCC365的时间会乱?先搞清楚根本原因

在动手写代码之前,我花了两天时间反复测试,发现YCC365回放视频时间不对,主要有三个原因:

  1. 摄像头系统时间未同步:每次断电重启后,如果摄像头没有成功连接NTP服务器,系统时间就默认回退到固件编译时间或者1970年1月1日,这时候录的视频,文件创建时间就是错的。
  2. 视频文件命名规则被改写:YCC365的录像文件通常是CH01_20250314123000.mp4这种格式,其中20250314123000代表2025年3月14日12点30分00秒,但如果摄像头时间错乱,这个文件名里的时间就是错的。
  3. 文件系统元数据与内容不一致:有时候文件名是对的,但你用stat命令查看文件的mtimectime,发现跟文件名完全对不上,这是因为文件被拷贝或移动时,文件系统的时间戳被操作系统覆盖了。

我的Go语言解决方案:从流程到代码

我的想法很简单:以文件名中的时间戳为准,覆盖文件的系统时间戳,因为文件名是摄像头直接生成的,哪怕系统时间错了,文件名里嵌入的时间也是实际录制时间(前提是摄像头没有人为调过时区设置)。

第一步:解析文件名中的时间格式

YCC365的文件名一般是这种结构:

CH01_20250314123000.mp4
CH01_20250314123505.mp4
CH02_20250314124010.mp4

其中20250314123000代表2025-03-14 12:30:00,我需要把它解析成Go的time.Time格式。

func parseTimeFromFilename(filename string) (time.Time, error) {
    // 提取文件名中的时间部分
    // 假设格式是 CH01_20060102150405.mp4
    parts := strings.Split(filename, "_")
    if len(parts) < 2 {
        return time.Time{}, fmt.Errorf("文件名格式不对")
    }
    timeStr := strings.TrimSuffix(parts[1], ".mp4")
    if len(timeStr) != 14 {
        return time.Time{}, fmt.Errorf("时间戳长度不对, 期望14位, 得到 %d", len(timeStr))
    }
    parsed, err := time.Parse("20060102150405", timeStr)
    if err != nil {
        return time.Time{}, fmt.Errorf("解析时间失败: %v", err)
    }
    // 注意:这里返回的是UTC时间,需要根据实际时区调整
    return parsed, nil
}

这里有一个坑:摄像头文件名里的时间通常是UTC+8(北京时间),但Go的time.Parse默认解析出来是不带时区的,如果你直接把这个时间赋值给文件的mtime,可能会偏差8小时。

我的处理方式是在解析后手动加上时区:

loc, _ := time.LoadLocation("Asia/Shanghai")
parsed = parsed.In(loc)

第二步:用os.Chtimes修正文件时间

Go标准库提供了os.Chtimes函数,可以设置文件的atimemtime,这个函数在不同操作系统上表现略有差异,但在Linux和macOS上都很稳定

func fixFileTime(filePath string, targetTime time.Time) error {
    // 设置文件的 Access time 和 Modify time 为目标时间
    err := os.Chtimes(filePath, targetTime, targetTime)
    if err != nil {
        return fmt.Errorf("设置文件时间失败: %v", err)
    }
    // 另外还可以设置Change time, 但Change time只能通过系统调用来改,比较麻烦
    // 一般只改mtime就够了,大部分播放器和文件管理器都只看mtime
    return nil
}

注意os.Chtimes只能修改atimemtime,不能修改ctime(Change time),ctime是文件元数据最后一次被修改的时间,在Linux下只有root用户才能通过chattr等工具修改,不过对于视频回放时间不对这个问题,改mtime就够了,因为大多数媒体播放器和NAS系统(如群晖、威联通)都是根据mtime来排序的。

第三步:批量处理目录下的所有视频

YCC365的SD卡目录结构通常是这样的:

/sdcard
├── CH01
│   ├── 2025-03-14
│   │   ├── CH01_20250314123000.mp4
│   │   └── CH01_20250314123505.mp4
│   └── 2025-03-15
│       └── ...
├── CH02
│   └── ...
└── CH03
    └── ...

我需要递归遍历所有子目录,找到.mp4文件,逐一处理,用filepath.Walk可以轻松搞定:

func walkAndFix(rootDir string) {
    err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            log.Printf("访问 %s 时出错: %v", path, err)
            return nil // 跳过错误,继续处理下一个
        }
        if info.IsDir() {
            return nil // 跳过目录
        }
        if !strings.HasSuffix(info.Name(), ".mp4") {
            return nil // 只处理mp4文件
        }
        // 解析时间
        targetTime, err := parseTimeFromFilename(info.Name())
        if err != nil {
            log.Printf("跳过 %s: %v", info.Name(), err)
            return nil
        }
        // 修正时间
        err = fixFileTime(path, targetTime)
        if err != nil {
            log.Printf("修正 %s 失败: %v", path, err)
        } else {
            log.Printf("成功修正: %s -> %s", info.Name(), targetTime.Format("2006-01-02 15:04:05"))
        }
        return nil
    })
    if err != nil {
        log.Fatalf("遍历目录出错: %v", err)
    }
}

这里我故意省略了defer和更复杂的错误处理,因为这个工具的目标用户是普通家庭用户,不是运维大佬,出错时打印个日志告诉用户哪个文件有问题就行了,没必要直接崩溃退出。

完整代码框架

把上面三块拼起来,就是一个小工具的主干,我还加了两个实用功能:

  • 时区自动检测:通过读取系统时区配置,或者让用户传入--timezone参数
  • 仅检查不修改:加一个--dry-run参数,先预览哪些文件会被修改
参数 作用 默认值
--dir SD卡挂载目录 /mnt/sdcard
--timezone 时区 Asia/Shanghai
--dry-run 仅检查不修改 false

代码写完大概只有120行左右,编译成单个二进制文件,往树莓派或者Windows电脑上一扔就能跑。

实际使用中的几个坑和心得

第一个坑:文件名里有特殊字符,我遇到过文件名是CH01_2025-03-14_12-30-00.mp4这种格式的,中划线代替了下划线,这种情况只能加个正则匹配,或者干脆让用户先批量重命名。

第二个坑:时区问题,有一次用户反馈说修正完时间后,在群晖NAS里看视频时间还是不对,后来发现是NAS本身对文件时间显示有自己的一套逻辑——它读取的是文件内的元数据,而不是文件系统时间,这种情况下,单纯改文件mtime没用,得用ffmpeg重新编码视频头信息,不过这就是另一个故事了,对于大部分家庭用户,改mtime已经能解决90%的问题。

第三个坑:SD卡文件系统是FAT32或exFAT,这两个文件系统对时间戳的支持有精度问题(FAT32只能精确到2秒,exFAT好一点但有时区存储方式不同),在Windows上修改FAT32文件的时间还可能触发文件系统标记为“脏”,所以我建议先把文件拷贝到电脑的NTFS或ext4分区上再运行工具

最后分享一个让我笑出来的事:有用户找我反馈说“工具不好用,视频时间反而更乱了”,后来一查,他摄像头里的SD卡用的是伪劣扩容卡,文件系统本身就是坏的,文件名的顺序和实际写入顺序都乱成一锅粥了,这种物理层面的问题,神仙也救不了。

写这个东西的初衷其实很简单,就是我自己被YCC365搞烦了,每次想回看监控找点什么,时间不对就得一个个视频点开看,眼睛都快瞎了,现在好了,写个30秒跑完的工具,解放双手。

这个工具有个前提:文件名本身是正确的,如果你的摄像头已经彻底断网乱跳到连文件名都错了,那这个方法就无效,这时候可能得换个思路,比如根据视频内容里的关键帧时间戳(如果有的话)来恢复,或者干脆格式化SD卡重新开始。

其实回过头来看,YCC365这类摄像头的时间问题并不复杂,就是嵌入式设备在时间同步机制上的先天不足,厂商不修,用户只能自己动手,用Go写这种底层小工具,跨平台又顺手,算是性价比最高的解决方案了。

用Golang解决YCC365回放视频时间不对的问题,我是这样一步步搞定的