Rust音频元数据处理库moosicbox_lofty的使用,支持高效读取和编辑MP3、FLAC等格式的ID3标签
Rust音频元数据处理库moosicbox_lofty的使用
moosicbox_lofty是一个用于解析、转换和写入各种音频格式元数据的Rust库,支持高效读取和编辑MP3、FLAC等格式的ID3标签。
特性
- 支持多种音频格式的元数据处理
- 提供读取、写入和删除元数据的功能
- 高性能的元数据处理能力
安装
在Cargo.toml中添加依赖:
moosicbox_lofty = "0.1.0"
示例代码
读取音频标签
use moosicbox_lofty::read_from_path;
fn main() {
// 读取音频文件元数据
let tagged_file = read_from_path("path/to/audio.mp3").unwrap();
// 获取主标签
if let Some(tag) = tagged_file.primary_tag() {
println!("标题: {:?}", tag.title());
println!("艺术家: {:?}", tag.artist());
println!("专辑: {:?}", tag.album());
}
// 获取所有标签
for tag in tagged_file.tags() {
println!("标签类型: {:?}", tag.tag_type());
println!("标题: {:?}", tag.title());
println!("艺术家: {:?}", tag.artist());
}
}
写入音频标签
use moosicbox_lofty::{Tag, TagType, ItemKey};
fn main() {
// 创建一个新标签
let mut tag = Tag::new(TangType::Id3v2);
// 设置基本元数据
tag.insert_text(ItemKey::TrackTitle, "歌曲标题");
tag.insert_text(ItemKey::TrackArtist, "艺术家");
tag.insert_text(ItemKey::AlbumTitle, "专辑名称");
// 设置自定义字段
tag.insert_text(ItemKey::Custom("MY_CUSTOM_FIELD".to_string()), "自定义值");
// 保存到文件
tag.save_to_path("path/to/audio.mp3").unwrap();
}
删除音频标签
use moosicbox_lofty::{read_from_path, TagType};
fn main() {
// 读取文件
let mut tagged_file = read_from_path("path/to/audio.mp3").unwrap();
// 删除特定类型的标签
tagged_file.remove(TagType::Id3v1);
// 保存更改
tagged_file.save_to_path("path/to/audio.mp3").unwrap();
}
支持的格式
moosicbox_lofty支持多种音频格式的元数据处理,包括但不限于:
- MP3 (ID3v1, ID3v2)
- FLAC
- OGG
- WAV
- AIFF
- MP4
- WMA
许可证
moosicbox_lofty采用双重许可:
- Apache License, Version 2.0
- MIT license
您可以根据需要选择其中一种许可证。
完整示例代码
下面是一个完整的音频元数据处理示例,包含读取、修改和保存元数据的完整流程:
use moosicbox_lofty::{read_from_path, Tag, TagType, ItemKey};
fn main() {
// 1. 读取现有音频文件元数据
let audio_path = "test.mp3";
let mut tagged_file = read_from_path(audio_path).unwrap();
println!("=== 原始标签信息 ===");
if let Some(tag) = tagged_file.primary_tag() {
println!("标题: {:?}", tag.title());
println!("艺术家: {:?}", tag.artist());
println!("专辑: {:?}", tag.album());
}
// 2. 创建新标签并添加元数据
let mut new_tag = Tag::new(TagType::Id3v2);
new_tag.insert_text(ItemKey::TrackTitle, "新歌曲标题");
new_tag.insert_text(ItemKey::TrackArtist, "新艺术家");
new_tag.insert_text(ItemKey::AlbumTitle, "新专辑");
new_tag.insert_text(ItemKey::TrackNumber, "1");
// 3. 替换原有标签
tagged_file.insert_tag(new_tag);
// 4. 删除不需要的旧标签
tagged_file.remove(TagType::Id3v1);
// 5. 保存修改后的文件
tagged_file.save_to_path(audio_path).unwrap();
println!("元数据已成功更新并保存");
// 6. 验证修改结果
let updated_file = read_from_path(audio_path).unwrap();
println!("\n=== 更新后的标签信息 ===");
if let Some(tag) = updated_file.primary_tag() {
println!("标题: {}", tag.title().unwrap_or("无"));
println!("艺术家: {}", tag.artist().unwrap_or("无"));
println!("专辑: {}", tag.album().unwrap_or("无"));
}
}
这个完整示例展示了:
- 如何读取现有音频文件的元数据
- 如何创建新的标签并添加各种元数据
- 如何替换和删除标签
- 如何保存修改后的文件
- 如何验证修改结果
您可以根据实际需求调整和扩展这个示例代码。
1 回复
moosicbox_lofty - Rust音频元数据处理库
概述
moosicbox_lofty是一个用于处理音频元数据的Rust库,支持高效读取和编辑多种音频格式的ID3标签和其他元数据。它特别适合需要批量处理音频文件元数据的应用程序开发。
支持格式
- MP3 (ID3v1, ID3v2)
- FLAC (Vorbis注释)
- WAV
- AIFF
- MP4 (M4A, ALAC)
- OGG
- OPUS
- WMA
- APE (Monkey’s Audio)
基本用法
首先在Cargo.toml中添加依赖:
[dependencies]
moosicbox_lofty = "0.10"
示例代码
1. 读取音频文件元数据
use moosicbox_lofty::{read_from_path, TaggedFileExt};
fn main() {
// 读取音频文件
let tagged_file = read_from_path("song.mp3").unwrap();
// 获取主标签(通常是ID3标签)
let tag = tagged_file.primary_tag().unwrap();
// 读取元数据
println!("标题: {:?}", tag.title());
println!("艺术家: {:?}", tag.artist());
println!("专辑: {:?}", tag.album());
println!("年份: {:?}", tag.year());
println!("音轨号: {:?}", tag.track());
println!("流派: {:?}", tag.genre());
}
2. 编辑音频文件元数据
use moosicbox_lofty::{read_from_path, TaggedFileExt, Tag};
fn main() {
// 读取文件
let mut tagged_file = read_from_path("song.mp3").unwrap();
// 获取或创建主标签
let tag = match tagged_file.primary_tag_mut() {
Some(tag) => tag,
None => {
tagged_file.insert_tag(Tag::new(tagged_file.primary_tag_type()));
tagged_file.primary_tag_mut().unwrap()
}
};
// 设置元数据
tag.set_title("新标题");
tag.set_artist("新艺术家");
tag.set_album("新专辑");
tag.set_year(2023);
tag.set_track(1);
tag.set_genre("摇滚");
// 保存更改
tag.save_to_path("song.mp3").unwrap();
}
3. 批量处理音频文件
use std::path::Path;
use moosicbox_lofty::{read_from_path, TaggedFileExt};
fn process_directory(dir: &Path) {
if dir.is_dir() {
for entry in dir.read_dir().unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() {
if let Ok(tagged_file) = read_from_path(&path) {
if let Some(tag) = tagged_file.primary_tag() {
println!("处理文件: {:?}", path);
println!("标题: {:?}", tag.title());
// 这里可以添加更多的处理逻辑
}
}
}
}
}
}
fn main() {
process_directory(Path::new("music_directory"));
}
高级功能
1. 处理图片/封面艺术
use moosicbox_lofty::{read_from_path, TaggedFileExt, Picture};
// 添加封面图片
fn add_cover_art(file_path: &str, image_path: &str) {
let mut tagged_file = read_from_path(file_path).unwrap();
let tag = tagged_file.primary_tag_mut().unwrap();
let picture = Picture::from_path(image_path).unwrap();
tag.push_picture(picture);
tag.save_to_path(file_path).unwrap();
}
// 读取封面图片
fn read_cover_art(file_path: &str) {
let tagged_file = read_from_path(file_path).unwrap();
let tag = tagged_file.primary_tag().unwrap();
for (i, picture) in tag.pictures().enumerate() {
println!("图片 {}: {:?}x{:?}, {:?}字节",
i, picture.width(), picture.height(), picture.data().len());
// 可以将图片数据保存到文件
}
}
2. 处理自定义帧/字段
use moosicbox_lofty::{read_from_path, TaggedFileExt};
fn handle_custom_frames(file_path: &str) {
let mut tagged_file = read_from_path(file_path).unwrap();
let tag = tagged_file.primary_tag_mut().unwrap();
// 添加自定义文本帧
tag.insert_text_frame("TXXX", "自定义字段", "自定义值");
// 读取自定义帧
if let Some(value) = tag.get_text("TXXX") {
println!("自定义字段值: {}", value);
}
tag.save_to_path(file_path).unwrap();
}
性能提示
- 对于批量处理,考虑使用并行处理:
use rayon::prelude::*;
use std::path::Path;
fn process_files_parallel(dir: &Path) {
let files: Vec<_> = dir.read_dir().unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect();
files.par_iter().for_each(|path| {
if let Ok(tagged_file) = read_from_path(path) {
// 处理每个文件
}
});
}
- 对于只读操作,使用
read_from
而不是read_from_path
可以避免文件锁定问题。
注意事项
- 修改文件前最好先备份,以防意外损坏
- 某些音频格式对元数据的支持有限
- 处理大量文件时注意内存使用
moosicbox_lofty提供了强大而灵活的音频元数据处理能力,适合从简单的元数据读写到复杂的音频文件管理应用。
完整示例代码
以下是一个完整的音频元数据管理工具示例,结合了读取、编辑和批量处理功能:
use moosicbox_lofty::{read_from_path, TaggedFileExt, Tag, Picture};
use std::path::{Path, PathBuf};
use std::io::{self, Write};
use rayon::prelude::*;
fn main() {
println!("音频元数据管理工具");
loop {
println!("\n请选择操作:");
println!("1. 查看音频文件元数据");
println!("2. 编辑音频文件元数据");
println!("3. 批量处理目录中的音频文件");
println!("4. 添加/查看封面图片");
println!("5. 退出");
print!("> ");
io::stdout().flush().unwrap();
let mut choice = String::new();
io::stdin().read_line(&mut choice).unwrap();
match choice.trim() {
"1" => view_metadata(),
"2" => edit_metadata(),
"3" => batch_process(),
"4" => handle_cover_art(),
"5" => break,
_ => println!("无效选择"),
}
}
}
fn view_metadata() {
let path = input_path("请输入音频文件路径: ");
match read_from_path(&path) {
Ok(tagged_file) => {
if let Some(tag) = tagged_file.primary_tag() {
println!("\n=== 元数据 ===");
println!("标题: {:?}", tag.title());
println!("艺术家: {:?}", tag.artist());
println!("专辑: {:?}", tag.album());
println!("年份: {:?}", tag.year());
println!("音轨号: {:?}", tag.track());
println!("流派: {:?}", tag.genre());
// 显示自定义帧
println!("\n自定义帧:");
for frame in tag.frames() {
println!("{}: {:?}", frame.id(), frame.content());
}
} else {
println!("文件没有元数据标签");
}
}
Err(e) => println!("读取文件失败: {}", e),
}
}
fn edit_metadata() {
let path = input_path("请输入音频文件路径: ");
let mut tagged_file = match read_from_path(&path) {
Ok(f) => f,
Err(e) => {
println!("读取文件失败: {}", e);
return;
}
};
let tag = match tagged_file.primary_tag_mut() {
Some(t) => t,
None => {
println!("创建新标签");
tagged_file.insert_tag(Tag::new(tagged_file.primary_tag_type()));
tagged_file.primary_tag_mut().unwrap()
}
};
println!("\n当前元数据:");
println!("1. 标题: {:?}", tag.title());
println!("2. 艺术家: {:?}", tag.artist());
println!("3. 专辑: {:?}", tag.album());
println!("4. 年份: {:?}", tag.year());
println!("5. 音轨号: {:?}", tag.track());
println!("6. 流派: {:?}", tag.genre());
loop {
print!("\n选择要编辑的字段(1-6)或输入's'保存并退出,'q'不保存退出: ");
io::stdout().flush().unwrap();
let mut choice = String::new();
io::stdin().read_line(&mut choice).unwrap();
let choice = choice.trim();
match choice {
"1" => {
let title = input_string("输入新标题: ");
tag.set_title(title);
}
"2" => {
let artist = input_string("输入新艺术家: ");
tag.set_artist(artist);
}
"3" => {
let album = input_string("输入新专辑: ");
tag.set_album(album);
}
"4" => {
if let Ok(year) = input_string("输入新年份: ").parse::<u32>() {
tag.set_year(year);
} else {
println!("无效的年份");
}
}
"5" => {
if let Ok(track) = input_string("输入新音轨号: ").parse::<u32>() {
tag.set_track(track);
} else {
println!("无效的音轨号");
}
}
"6" => {
let genre = input_string("输入新流派: ");
tag.set_genre(genre);
}
"s" => {
if let Err(e) = tag.save_to_path(&path) {
println!("保存失败: {}", e);
} else {
println!("保存成功");
}
break;
}
"q" => break,
_ => println!("无效选择"),
}
}
}
fn batch_process() {
let dir_path = input_path("请输入目录路径: ");
let dir = Path::new(&dir_path);
if !dir.is_dir() {
println!("路径不是目录");
return;
}
let files: Vec<PathBuf> = dir.read_dir()
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.collect();
println!("\n找到 {} 个文件", files.len());
// 并行处理
files.par_iter().for_each(|path| {
if let Ok(tagged_file) = read_from_path(path) {
if let Some(tag) = tagged_file.primary_tag() {
println!("处理文件: {:?}", path.file_name().unwrap());
println!(" 标题: {:?}", tag.title());
println!(" 艺术家: {:?}", tag.artist());
// 这里可以添加批量处理逻辑
}
}
});
}
fn handle_cover_art() {
println!("\n封面图片操作:");
println!("1. 添加封面图片");
println!("2. 查看封面图片信息");
println!("3. 提取封面图片");
let mut choice = String::new();
io::stdin().read_line(&mut choice).unwrap();
match choice.trim() {
"1" => {
let audio_path = input_path("请输入音频文件路径: ");
let image_path = input_path("请输入图片文件路径: ");
let mut tagged_file = match read_from_path(&audio_path) {
Ok(f) => f,
Err(e) => {
println!("读取音频文件失败: {}", e);
return;
}
};
let tag = match tagged_file.primary_tag_mut() {
Some(t) => t,
None => {
println!("创建新标签");
tagged_file.insert_tag(Tag::new(tagged_file.primary_tag_type()));
tagged_file.primary_tag_mut().unwrap()
}
};
match Picture::from_path(&image_path) {
Ok(picture) => {
tag.push_picture(picture);
if let Err(e) = tag.save_to_path(&audio_path) {
println!("保存失败: {}", e);
} else {
println!("封面图片添加成功");
}
}
Err(e) => println!("读取图片失败: {}", e),
}
}
"2" => {
let audio_path = input_path("请输入音频文件路径: ");
let tagged_file = match read_from_path(&audio_path) {
Ok(f) => f,
Err(e) => {
println!("读取文件失败: {}", e);
return;
}
};
if let Some(tag) = tagged_file.primary_tag() {
for (i, picture) in tag.pictures().enumerate() {
println!("图片 {}:", i + 1);
println!(" 类型: {:?}", picture.pic_type());
println!(" 尺寸: {}x{}", picture.width(), picture.height());
println!(" 大小: {} 字节", picture.data().len());
println!(" MIME类型: {:?}", picture.mime_type());
}
} else {
println!("文件没有元数据标签");
}
}
"3" => {
let audio_path = input_path("请输入音频文件路径: ");
let output_dir = input_path("请输入输出目录: ");
let tagged_file = match read_from_path(&audio_path) {
Ok(f) => f,
Err(e) => {
println!("读取文件失败: {}", e);
return;
}
};
if let Some(tag) = tagged_file.primary_tag() {
for (i, picture) in tag.pictures().enumerate() {
let output_path = Path::new(&output_dir)
.join(format!("cover_{}.{}", i + 1, picture.extension()));
if let Err(e) = std::fs::write(&output_path, picture.data()) {
println!("保存图片 {} 失败: {}", i + 1, e);
} else {
println!("图片 {} 保存到: {:?}", i + 1, output_path);
}
}
} else {
println!("文件没有元数据标签");
}
}
_ => println!("无效选择"),
}
}
fn input_path(prompt: &str) -> String {
print!("{}", prompt);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
fn input_string(prompt: &str) -> String {
print!("{}", prompt);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
这个完整示例提供了以下功能:
- 查看音频文件元数据
- 编辑音频文件元数据
- 批量处理目录中的音频文件
- 添加/查看/提取封面图片
使用方法:
- 将代码保存为main.rs
- 在Cargo.toml中添加moosicbox_lofty和rayon依赖
- 运行程序并按提示操作