Linux下多层子目录中各类音频文件总时长统计问题
递归统计多层目录下音频/视频总时长问题
问题背景
我有一个包含mp3、ogg、opus、m4a等多种格式音频文件的大型多层目录,目录结构类似K/King, Stephen/The Stand/The Stand.mp3,需要统计所有音频文件的总时长。因为文件总大小达数百GB,无法复制到单一文件夹用mp3info处理;mp3info没有递归搜索选项,mp3info2虽有-R递归选项但资料不足不会用。
尝试过的脚本及报错
我尝试了以下脚本:
tot=0; while read -r i; do tmp=0; tmp=`ffprobe "$i" -show_format 2>/dev/null | grep "^duration" | cut -d '=' -f 2 | cut -d '.' -f 1`; if [ -n "$tmp" ]; then let tot+=$tmp; fi; done < <(find . -type f -iname "*[.mp3,.wav,.m3u,.m4a,.m4b,.mpga,.opus,.opus]"); echo "Total duration: $(($tot/60)) minutes"
出现重复报错:
bash: let: tot+=N/A: division by 0 (error token is "A")
另外尝试soxi -D *.mp3命令时,报错:
soxi FAIL formats: can't open input file `*.mp3': No such file or directory
额外问题
如何在另一目录中对视频文件执行同样的总时长统计操作?
解决方案
1. 修复音频统计脚本(解决N/A报错)
报错原因是部分文件ffprobe无法识别时长,返回N/A导致let命令无法计算,同时原find的后缀匹配规则错误,会匹配包含指定字符的任意文件而非目标格式。
修复后的脚本:
tot=0 while read -r file; do # 直接用ffprobe输出时长,简化处理 duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null) # 过滤有效数字,跳过无法识别的文件 if [[ "$duration" =~ ^[0-9.]+$ ]]; then tot=$(( tot + $(printf "%.0f" "$duration") )) fi done < <(find . -type f \( -iname "*.mp3" -o -iname "*.wav" -o -iname "*.m4a" -o -iname "*.m4b" -o -iname "*.mpga" -o -iname "*.opus" -o -iname "*.ogg" \)) # 转换为小时+分钟的直观格式 hours=$(( tot / 3600 )) minutes=$(( (tot % 3600) / 60 )) echo "总时长: $hours 小时 $minutes 分钟"
2. 解决soxi命令报错问题
soxi -D *.mp3报错是因为当前目录无mp3文件且soxi不支持递归,用以下命令递归统计:
find . -type f \( -iname "*.mp3" -o -iname "*.wav" -o -iname "*.m4a" -o -iname "*.opus" \) -exec soxi -D {} + | awk '{sum += $1} END {print "总时长(秒):", sum, "| 总时长(分钟):", sum/60, "| 总时长(小时):", sum/3600}'
3. 统计指定目录下的视频总时长
只需修改find的路径和视频格式后缀,示例如下:
tot=0 video_dir="/path/to/your/videos" while read -r file; do duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null) if [[ "$duration" =~ ^[0-9.]+$ ]]; then tot=$(( tot + $(printf "%.0f" "$duration") )) fi done < <(find "$video_dir" -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.mov" \)) hours=$(( tot / 3600 )) minutes=$(( (tot % 3600) / 60 )) seconds=$(( tot % 60 )) echo "视频总时长: $hours 小时 $minutes 分钟 $seconds 秒"
内容的提问来源于stack exchange,提问作者ReaderGuy42
相关产品推荐
相关产品推荐

