前言
小米监控默认是每分钟上传一次录像的,录像存储在以小时为单位的文件夹中,其中每个文件夹的命名格式为yyyy-MM-dd HH(年月日时,例如2025021022),而这些文件夹内的视频都是以分钟为单位储存的,这些视频的命名格式为mm-M-ss-S_十位数后缀(mm是视频起始分钟数,ss是视频起始秒数),这种存储方式初衷是为了帮助用户节省电量和存储空间,和快速找到和浏览特定时间点的视频。
大概是这样子的:
但有些呼声认为这种以分钟为单位的视频存储方式使得他们在观看整个小时或整天的视频时需要一个个退出并点击那些分钟视频,比较麻烦,而且所有的文件夹都存放在一起,没有按年按月这样的存储结构,整体显得琐碎臃肿,网上也有一些合并视频的方法,但大多数需要各种高级编程语言环境(python、java等)来支持,有些教程甚至需要安装IDEA或vs等特定IDE。
这里我编写了一个bat文件(批处理脚本文件)来实现合并小米视频的功能。
使用批处理文件的好处主要是方便计算机小白的使用和操作简便性,因为不需要使用者在电脑上安装任何其他拓展环境,拿来脚本文件就能用,批处理文件的后缀名通常是.bat。
在实现该功能前需要安装一个工具来实现用批处理脚本合并视频的功能,这个工具是ffmpeg,安装方法十分简单,只需要下载ffmpeg的压缩包并解压到你的任意目录中,复制解压好的目录的路径,将其添加到电脑的环境变量path中即可,如果有不会的可以参考该教程windows电脑FFmpeg安装教程手把手详解,并附带ffmpeg安装包的源码资源:ffmpeg源码下载
为了后续方便你的理解,我将存储录像的以小时为单位的文件夹,命名为原视频存放目录,合并完成的视频所存放的文件夹,命名为目标存储文件夹。
这是原视频存放目录(可以看到bat文件放在原视频存放目录下):
好了,当你配置好ffmpeg环境后就可以进入正文阶段了,为了方便操作,我将所有功能都集中在了一个bat文件中,如果你只想快速实现该功能,请在看完下面一部分后,移至文章目录末尾的总体代码部分,下面开始讲解代码。
一、选择目标存储目录和准备计时功能
- @echo off & setlocal EnableDelayedExpansion
-
- :start
- call :setp 请输入您想存储的目录(该目录下 & call :echox_red 不要
- call :setp 有其他子目录和文件,如有疑问请键入 & call :echox_light "/?" & call :setp ):
- set /p targer_dir=""
-
- if "!targer_dir!"=="/?" (
- call :show_help
- goto start
- ) else (
- call :is_valid_path "!targer_dir!"
- if "!result!"=="0" (
- goto start
- ) else (
- call :setp 您所选的目录为:& call :echox_cyan "!targer_dir!" & call :setp ,是否确认?
- echo.
- )
- )
-
- :choice
- set /p choice=输入选择(Y/N):
-
- if /i "!choice!"=="Y" (
- echo 已确认存储目录,程序开始...
- echo.
- call :md_dir "!targer_dir!"
- if !errorlevel! equ 1 (
- goto start
- )
- ) else (
- if /i "!choice!"=="N" (
- echo 操作已被取消,返回开始...
- goto start
- ) else (
- echo 异常输入,请重新选择。
- goto choice
- )
- )
-
-
- REM 获取开始时间
- for /f "tokens=1-4 delims=/ " %%a in ("%date%") do (
- set "startyear=%%a"
- set "startmonth=%%b"
- set "startday=%%c"
- set "startweek=%%d"
- )
- for /f "tokens=1-4 delims=:.," %%a in ("%time%") do (
- set "startHour=%%a"
- set "startMinute=%%b"
- set "startSecond=%%c"
- set "startHundredths=%%d"
- )
-
这里可以看到使用了call来调用某个函数(批处理脚本不存在函数这一概念,但重用某一代码块这一功能已经有了函数的雏形,为了行文方便,下文都将使用该词)。
像echox_这种格式的函数是单独对指定字符串使用彩色输出,而setp函数是不换行的输出字符,起到文字嵌入作用。这两个函数的实现代码如下:
- :setp
-
set /p "=%~1" - goto :eof
-
- :echox
- call :setp "[38;5;%~1m[48;5;%~2m%~3[0m"
- goto :eof
-
- :echox_test
- call :echox 10 20 "%~1"
- goto :eof
彩色输入部分
这里要提醒:echox函数(标签)内容显示的并不全面,这种未显示的字符是ESC字符,其在csdn中无法直接显示,也无法直接在txt文本中完全显示出来,只会出现一个框,而在notepad++中显示内容如下:

这里附带了可下载的文本链接:ANSI-16Color.txt,下载后可直接复制该文件第四行末尾的这一部分,将对应的位置改成上述代码中echox函数的%~1,%~2,%~3就好啦。如下:

其在记事本中改好的样子:

好了,如果你已经做到这一步后,可以直接跳到最后的总体代码部分去尝试了,有兴趣跟着我讲解代码的同学下面请。
上述ANSI彩色文本部分参考了以下两篇博客,有兴趣的同学可以自行阅读:
Windows cmd (DOS) 命令窗口中 echo 命令 ANSI 转义显示彩色字或背景
上面这三个函数是嵌套结构,setp函数这里是利用了set /p用户输入前的文本提示来显示字符,并使用 将标准输入重定向到 nul 设备,相当于没有输入,避免等待用户输入。
echox_test函数将三个参数都传递给echox函数,echox函数再调用setp函数输出,三者最大程度上避免了代码块重复。
- :md_dir
- set checked_dir=%~1
- if not exist "!checked_dir!" (
- md "!checked_dir!" 2>nul
- if errorlevel 1 (
- echo 无法创建目录: !checked_dir!
- echo 目录名称含有非法字符或符盘不存在,已终止操作
- exit /b 1
- )
- echo 成功创建目录:!checked_dir!
- )
- exit /b 0
-
- :is_valid_path
- set inputPath=%~1
- rem 检查路径是否为空
- if "!inputPath!"=="" (
- echo 输入的路径为空
- set "result=0"
- goto :eof
- )
- rem 检查是否以驱动器字母开头,并包含有效的分隔符
- if not "!inputPath:~1,1!"==":" (
- echo 输入的路径格式不正确(应该以驱动器字母开头,比如 C:)
- set "result=0"
- goto :eof
- )
- rem 检查是否包含反斜杠 "\"
- if "!inputPath!"=="!inputPath:\=!" (
- echo 输入的路径格式不正确(不包含反斜杠)
- set "result=0"
- goto :eof
- )
- set "result=1"
- goto :eof
这两个函数是用来检查输入的目标存储目录的路径是否合法,避免后续因无效路径的原因而报错。
最后的获取开始时间是为了统计执行该代码的所用时长,只能统计30天以内的运行(应该没有人能运行这么久吧),支持跨天、跨月、跨年计时。
二、按小时文件夹合并视频并移动到目标存储目录
- echo.
- call :echox_af 开始执行按小时合并操作:
- echo.
- rem 遍历当前目录下所有文件夹的名称
- for /f "delims=" %%a in ('dir /ad/b') do (
- set "name=%%a"
- set "name=!name:~0,8!_!name:~8,2!"
-
- cd "%%a"
-
- rem 设置目标文件名
- set output_file="!cd!\!name!_part.mp4"
-
- REM 创建一个临时文件列表
- set file_list="!cd!\file.txt"
- if exist "!file_list!" del "!file_list!"
-
- for /f "delims=" %%d in ('dir /b *.mp4') do (
- echo file '%%d' >> "!file_list!"
- )
-
- ffmpeg -loglevel quiet -f concat -i !file_list! -c copy -r 20 !output_file!
-
- del "!file_list!"
-
- call :echox_cf %%a
- call :setp 小时级文件夹中视频已合并为
- call :echox_df !output_file!
- echo.
-
- call :echox_af 准备执行移动操作
- echo.
-
- set "b=%%a"
- set "c=!b:~0,4!"
- set "d=!b:~4,2!"
- set "e=!b:~6,2!"
-
- call :md_dir "!targer_dir!\!c!\!d!\!e!"
- if !errorlevel! equ 1 (
- del "!output_file!"
- echo 已删除文件 !output_file!,撤销成功
- goto start
- )
- move "!output_file!" "!targer_dir!\!c!\!d!\!e!" >nul
-
- call :echox_df !output_file!
- call :setp 成功移动至
- call :echox_cf "!targer_dir!\!c!\!d!\!e!"
- call :setp 文件夹!
- echo.
- echo.
- cd ..
- )
-
前面所说的小时文件夹和分钟视频的命名格式在这里被利用来创建按年份、月份和日的文件夹,准备存放后面合并生成的小时视频。
将同一文件夹中所有的分钟视频合并成小时视频之后,将这些小时视频移动到对应的日期文件夹中,准备接下来的按天合并操作。
值得注意的是该ffmpep代码使用了-loglevel quiet选项,作用是隐藏跟合并视频有关的日志输出,可以直观的看到程序运行到了哪里,但如果你享受命令窗口不断地飞出海量的命令行信息的话,可以把-loglevel quiet给删掉,方便你在朋友面前搞噱头。
ffmpeg -loglevel quiet -f concat -i !file_list! -c copy -r 20 !output_file!
下面代码中的>nul是将标准输出重定向到nul设备,等于吞掉该命令的信息输出。
move "!output_file!" "!targer_dir!\!c!\!d!\!e!" >nul
三、在日期文件夹中将小时文件夹合并成按天存储的视频
- call :echox_af 开始执行按天合并操作:
- echo.
-
- cd /d "!targer_dir!"
-
- for /f %%a in ('dir /ad/b') do (
- set dirnow="!cd!\%%a"
- call :echox_db 准备进入目录:
- call :echox_db !dirnow!
- echo.
- set year=%%a
-
- cd "%%a"
-
- for /f %%b in ('dir /ad/b') do (
- set dirnow="!cd!\%%b"
- call :echox_db 准备进入目录:
- call :echox_db !dirnow!
- echo.
- set month=%%b
-
- cd "%%b"
-
- for /f %%c in ('dir /ad/b') do (
- set dirnow="!cd!\%%c"
- call :echox_db 准备进入目录:
- call :echox_db !dirnow!
- echo.
- set "day=%%c"
-
- cd "%%c"
-
- REM 设置输出文件名
- set "output_file=!year!_!month!_!day!.mp4"
-
- REM 创建一个临时文件列表
- set "file_list=file.txt"
- if exist "!file_list!" del "!file_list!"
-
- for /f "delims=" %%d in ('dir /b *part.mp4') do (
- echo file '%%d' >> "!file_list!"
- )
-
- ffmpeg -loglevel quiet -f concat -i !file_list! -c copy !output_file!
-
- del "!file_list!"
-
- call :echox_cf !dirnow! & call :setp 天级文件夹中视频已合并为 & call :echox_df !output_file!
- echo.
-
- rem 定义要保留的文件名
- set keepFile=!output_file!
-
- rem 遍历当前目录下的所有 .mp4 文件
- for %%f in (*part.mp4) do (
- echo Deleting %%f
- del "%%f"
- )
- cd ..
- )
- cd ..
- )
- cd ..
- )
这段代码是将所有已移动到日期文件夹的同一天不同小时的视频合并为改天的视频,以天为单位。从目标存储目录开始for遍历,从年份文件夹到月份文件夹,最后进入日期文件夹,将该文件夹内所有的视频合并,并删除小时视频,节省空间。
需要注意的是,假设你想合并的分钟视频的总大小是10g,那么你的剩余存储空间必须要有10g+300m以上,这些缓冲空间是给合并的小时视频存放的。
后面的部分就是计算所耗时间的计算过程了,篇幅过长,接下来直接放总代码。
四、总体代码部分
以上就是全部代码了,如果有人逃课没看第一节的彩色输入部分的赶紧点击右侧目录(手机用户则在下方)去补习,那一块必须实现。
使用方法是将该代码在文本文档(windows自带的记事本)中粘贴(注意ESC字符那段必须要从ANSI-16Color.txt中复制替换代码中对应的部分),粘贴完成后在记事本中另存,改后缀名为.bat,设置编码格式为ANSI(为了正常显示中文),并将该bat文件放在原视频存放目录下,和原小时级视频夹放在一起(不是放在原小时级视频夹里面),然后双击运行即可。

如果双击bat文件后窗口如下,并输入后的结果正常显示则已成功运行:

以上代码对于原视频目录里的视频不会造成任何损坏。
我建议目标存储目录最好是一个刚刚创建的空目录,如果输入的目录不存在的话程序会自动创建。虽然代码不会对其他不相关的视频(命名不以_part结尾的视频)造成损害,但避免意外发生,目标存储目标还是不要有其他无关视频,以及任何子目录,减少遍历时间,节省总运行所耗时间,如果某一子目录下存在视频的话程序还会产生其他额外无关目录。
在运行过一次后,如果还要继续合并以后天数的视频,请将后来生成的若干待合并视频文件夹单独(不是就放一个视频文件夹)放在一个文件夹中,再运行bat文件,设置目标存储目录和原来设置的一样。
如果你只是想得到一个小时的合并视频,下载ffm.bat文件放在想要合并的小时文件夹内,双击运行即可得到合并后的视频,不会影响原视频文件:ffm.bat
在第三节末尾有个说明很重要,这里再说一下:
假设你想合并的分钟视频的总大小是10g,那么你的剩余存储空间必须要有10g+300m以上,这些缓冲空间是给合并的小时视频存放的。
以下博客是我一开始找到参考教程,可能会需要不同的环境支持,供各位参考:
本文代码的优点在于可以很好的从年到日分类每一天的视频并创建相应的文件夹来存储这些按天合并好的视频,检索方便。程序运行的界面简化明了,缺点是需要手动运行,并且对于后续新增加的待合并视频文件夹,需要单独放在一个空目录下运行程序(节省时间),还没有实现自动化任务执行操作(对新添加的文件夹内的视频合并为小时视频后实时移动到按天合并的文件夹中,与其中的视频合并)。

由于篇幅关系,关于实现跨月计时的代码实现本文没有提及,但内容值得学习,如果对上述代码有兴趣的同学有不懂的地方可以在评论区留言交流。如有转载请附原文链接,谢谢!
才疏学浅,一些具体方面难免出现纰漏,还请各位斧正。
2025-02-11 14:36:51
——————————分割线
据反馈,发现有时计时功能出问题,明明是只运行了几分钟,结果是二十多天,于是片段调试,发现分秒在08,09的时候才会计时紊乱:

原来是当日期、小时、分钟、秒和厘秒在等于08、09时参与计算时会被cmd当做零来处理,因为cmd将以0开头的数字当作是8进制数字,而08、09在8进制中不存在所以计时才会出错。我们需要对这些可能会报错的数字进行一些处理:
- rem 获取开始时间
- for /f "tokens=1-4 delims=/ " %%a in ("!date!") do (
- set "startyear=%%a"
- set "startmonth=%%b"
- call :decimal %%c startday
- set "startweek=%%d"
- )
- for /f "tokens=1-4 delims=:.," %%a in ("!time!") do (
- call :decimal %%a startHour
- call :decimal %%b startMinute
- call :decimal %%c startSecond
- call :decimal %%d startHundredths
- )
-
- rem 原代码内容
- ……
-
- rem 获取结束时间
- for /f "tokens=1-4 delims=/ " %%a in ("!date!") do (
- set "endyear=%%a"
- set "endmonth=%%b"
- call :decimal %%c endday
- set "endweek=%%d"
- )
-
- for /f "tokens=1-4 delims=:.," %%a in ("!time!") do (
- call :decimal %%a endHour
- call :decimal %%b endMinute
- call :decimal %%c endSecond
- call :decimal %%d endHundredths
- )
-
- rem 原代码内容
- ……
-
- :decimal
- set "num=%~1"
- set /a %~2 =100!num! %% 100
- goto :eof
通过求模操作即可消除数字的前缀0,让cmd将其作为10进制数来计算。

验证结果正常。
评论记录:
回复评论: