如何在R中通过reticulate调用guano-py完成WAV的GUANO元数据写入?
批量处理相机陷阱MP4:提取WAV音频并嵌入GUANO元数据
需求概述
- 批量处理数千个相机陷阱生成的
.MP4文件 - 提取音频为
WAV格式(适配Kaleidoscope lite等声学分析软件) - 生成GUANO标准元数据并嵌入WAV,核心是提取MP4的创建日期作为元数据字段
- 优先用R实现自动化,Python方案也可
已尝试的操作
- FFmpeg批量提取音频:用命令复制元数据,但存在编码适配隐患
system(sprintf("ffmpeg -i ./data/input/my_file.MP4 -map_metadata 0 -acodec copy -y ./data/output/my_file.wav"))
- R的guano包:仅支持读取含GUANO元数据的文件,无编辑/写入能力
read.guano('my_file.WAV')
- R中通过reticulate调用guano-py:可读取并编辑GUANO对象,但无法完成写入操作。Python中
gfile.write()可正常保存,但R中尝试py_run_string("gfile.write()")和guano$write("gfile")均报错。
解决方案
1. 修复R中reticulate调用guano-py的写入问题
问题出在reticulate的对象引用方式——不能用字符串引用Python对象,要直接调用R中已创建的GUANO对象的方法。具体步骤:
第一步:安装依赖
- R中安装文件操作和Python交互工具:
install.packages(c("reticulate", "fs"))
- 通过reticulate安装Python的
guano包:
reticulate::py_install("guano")
第二步:正确的读取/编辑/写入代码
library(reticulate) library(fs) # 导入Python的guano模块 guano <- import("guano") # 读取已提取的WAV文件 wav_path <- "./data/output/my_file.wav" gfile <- guano$GuanoFile(wav_path) # 编辑元数据(示例:添加备注) gfile$__setitem__("Note", "Camera trap audio extract") # 关键:直接调用对象的write方法保存修改 gfile$write()
提示:如果不想覆盖原文件,可传入新路径:
gfile$write("./data/output/my_file_guano.wav")
2. 完整自动化流程(含MP4创建日期提取)
步骤1:FFmpeg正确提取WAV音频
原命令中-acodec copy可能导致编码不兼容(MP4音频多为AAC,WAV需要PCM编码),替换为标准PCM参数:
# 单文件示例 input_mp4 <- "./data/input/my_file.MP4" output_wav <- "./data/output/my_file.wav" system(sprintf( "ffmpeg -i %s -vn -acodec pcm_s16le -ar 44100 -ac 2 -map_metadata 0 -y %s", shQuote(input_mp4), shQuote(output_wav) ))
参数说明:
-vn:忽略视频流-acodec pcm_s16le:指定WAV标准的PCM 16位小端编码-ar 44100:采样率44.1kHz(可按需调整)-ac 2:双声道(单声道用-ac 1)
步骤2:提取MP4的创建日期
优先用exiftool提取MP4原始创建时间(比系统文件时间更准确):
# 确保系统已安装exiftool,然后提取日期 creation_date <- system(sprintf('exiftool -CreateDate -d "%%Y-%%m-%%dT%%H:%%M:%%SZ" -s3 %s', shQuote(input_mp4)), intern = TRUE)
步骤3:嵌入GUANO元数据
将提取的日期写入GUANO标准字段Recording Start:
# 读取WAV的GUANO对象 gfile <- guano$GuanoFile(output_wav) # 设置GUANO标准元数据 gfile$__setitem__("Recording Start", creation_date) gfile$__setitem__("Device Type", "Camera Trap") gfile$__setitem__("Source File", path_file(input_mp4)) # 保存修改 gfile$write()
步骤4:批量处理脚本
将上述步骤封装为函数,批量处理整个文件夹的MP4:
library(reticulate) library(fs) # 初始化Python环境 guano <- import("guano") # 批量处理函数 process_camera_trap_mp4 <- function(input_dir, output_dir) { # 创建输出目录 dir_create(output_dir, recurse = TRUE) # 获取所有MP4文件(忽略大小写) mp4_files <- dir_ls(input_dir, regexp = "\\.MP4$", ignore.case = TRUE) for (mp4_path in mp4_files) { # 生成输出WAV路径 wav_filename <- path_ext_set(path_file(mp4_path), "wav") wav_path <- path(output_dir, wav_filename) # 1. FFmpeg提取WAV system(sprintf( "ffmpeg -i %s -vn -acodec pcm_s16le -ar 44100 -ac 2 -map_metadata 0 -y %s", shQuote(mp4_path), shQuote(wav_path) )) # 2. 提取MP4创建日期 creation_date <- system(sprintf('exiftool -CreateDate -d "%%Y-%%m-%%dT%%H:%%M:%%SZ" -s3 %s', shQuote(mp4_path)), intern = TRUE) # 3. 嵌入GUANO元数据 gfile <- guano$GuanoFile(wav_path) gfile$__setitem__("Recording Start", creation_date) gfile$__setitem__("Device Type", "Camera Trap") gfile$__setitem__("Source File", path_file(mp4_path)) gfile$write() cat(sprintf("处理完成:%s\n", path_file(mp4_path))) } } # 调用函数 process_camera_trap_mp4("./data/input", "./data/output")
备选Python方案
如果R的reticulate调用仍有问题,可直接用Python实现完整流程:
import os import guano import subprocess def process_mp4(input_path, output_dir): # 生成WAV路径 wav_filename = os.path.splitext(os.path.basename(input_path))[0] + ".wav" wav_path = os.path.join(output_dir, wav_filename) # FFmpeg提取WAV ffmpeg_cmd = [ "ffmpeg", "-i", input_path, "-vn", "-acodec", "pcm_s16le", "-ar", "44100", "-ac", "2", "-map_metadata", "0", "-y", wav_path ] subprocess.run(ffmpeg_cmd, check=True) # 提取创建日期 exif_cmd = [ "exiftool", "-CreateDate", "-d", "%Y-%m-%dT%H:%M:%SZ", "-s3", input_path ] creation_date = subprocess.run(exif_cmd, capture_output=True, text=True).stdout.strip() # 嵌入GUANO元数据 with guano.GuanoFile(wav_path, mode="r+") as gfile: gfile["Recording Start"] = creation_date gfile["Device Type"] = "Camera Trap" gfile["Source File"] = os.path.basename(input_path) # 批量处理 input_dir = "./data/input" output_dir = "./data/output" os.makedirs(output_dir, exist_ok=True) for filename in os.listdir(input_dir): if filename.lower().endswith(".mp4"): input_path = os.path.join(input_dir, filename) process_mp4(input_path, output_dir) print(f"处理完成:{filename}")
内容的提问来源于stack exchange,提问作者Quentin Dubois
相关产品推荐
相关产品推荐

