Rust解析mp3文件及工作目录下多个项目维护示例

本篇介绍Rust解析mp3文件,获取mp3文件的歌曲名,作者,专辑、封面图片等信息,同时记录在工作目录下同时维护多个项目的结构展示。

1.显示mp3文件的基本信息

首先创建工作目录analyzermp3,在该工作目录下新建Cargo.toml文件,内容如下:

复制代码
[workspace]
resolver = "3"

# 统一依赖版本  
[workspace.dependencies]
id3 = "1.16.4"        # ID3标签处理

切换到该目录下,新建工程cargo new mp3demo,这个时候在[workspace]项目中自动添加mp3demo项目。

复制代码
PS F:\rustproject\learndemo\analyzermp3> cargo new mp3demo
    Creating binary (application) `mp3demo` package
      Adding `mp3demo` as member of workspace at `F:\rustproject\learndemo\analyzermp3`
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
PS F:\rustproject\learndemo\analyzermp3>

在mp3demo项目配置文件中添加依赖:

mp3demo/Cargo.toml

复制代码
[package]
name = "mp3demo"
version = "0.1.0"
edition = "2024"

[dependencies]
id3.workspace = true

编译代码 mp3demo/src/main.rs

复制代码
//使用 id3 crate获取基本信息
use id3::{Tag, TagLike};

fn read_id3_metadata(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let tag = Tag::read_from_path(file_path)?;
    
    println!("ID3 标签信息: ");
    println!("标题: {:?}", tag.title());
    println!("艺术家: {:?}", tag.artist());
    println!("专辑: {:?}", tag.album());
    println!("年份: {:?}", tag.year());
    println!("曲目: {:?}", tag.track());
    println!("流派: {:?}", tag.genre());
    println!("version: {:?}", tag.version());  //version: Id3v23 v2.3标签
    println!("是否有封面:{:?}", tag.pictures().next().is_some()); //是否有封面
    // 读取所有帧
    // for frame in tag.frames() {
    //     println!("帧 ID: {}, 内容: {:?}", frame.id(), frame.content());
    // }
    
    Ok(())
}

fn main() {
    let mp3_list = vec!["F:/rustproject/mp3/01shangxintaipingyang.mp3",
                        "F:/rustproject/mp3/id3v24.mp3",
                        "F:/rustproject/mp3/test.mp3",
                        "F:/rustproject/mp3/visualization.raw"];
    for i in 0..mp3_list.len() {
        println!("{} = {}", i, mp3_list[i]);
        let result = read_id3_metadata(mp3_list[i]);
        println!("result = {:?}", result);
    }
    
    println!("Hello Rust");
}

编译运行:

复制代码
PS F:\rustproject\learndemo\analyzermp3> cargo run
   Compiling cfg-if v1.0.4
   Compiling simd-adler32 v0.3.8
   Compiling adler2 v2.0.1
   Compiling bitflags v2.11.0
   Compiling byteorder v1.5.0
   Compiling crc32fast v1.5.0
   Compiling miniz_oxide v0.8.9
   Compiling flate2 v1.1.9
   Compiling id3 v1.16.4
   Compiling mp3demo v0.1.0 (F:\rustproject\learndemo\analyzermp3\mp3demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.82s
     Running `target\debug\mp3demo.exe`
0 = F:/rustproject/mp3/01shangxintaipingyang.mp3
ID3 标签信息:
标题: Some("伤心太平洋.mp3")
艺术家: Some("焕焕")
专辑: Some("焕焕一直在")
年份: None
曲目: Some(1)
流派: None
version: Id3v23
是否有封面:true
result = Ok(())
1 = F:/rustproject/mp3/id3v24.mp3
ID3 标签信息:
标题: Some("This is a wonderful title isn't it?")
艺术家: Some("Someone/Someone else")
专辑: Some("éൣø§")
年份: None
曲目: Some(1)
流派: Some("(46)")
version: Id3v24
是否有封面:false
result = Ok(())
2 = F:/rustproject/mp3/test.mp3
result = Err(NoTag: reader does not contain an id3 tag)
3 = F:/rustproject/mp3/visualization.raw
result = Err(NoTag: reader does not contain an id3 tag)
Hello Rust
PS F:\rustproject\learndemo\analyzermp3>

2.把基本信息封装到结构体。

添加第2个项目 cargo new mp3demo2,

Cargo.toml文件:

复制代码
[workspace]
resolver = "3"
# 列出所有成员   创建项目时 cargo new xxxx 自动添加
members = ["mp3demo", "mp3demo2"]

# 统一依赖版本  
[workspace.dependencies]
id3 = "1.16.4"        # ID3标签处理

mp3demo2的配置文件mp3demo2/Cargo.toml

复制代码
[package]
name = "mp3demo2"
version = "0.1.0"
edition = "2024"

[dependencies]
id3 = "1.16.4"        # ID3标签处理  id3.workspace = true 这样写也可以

编写代码mp3demo2/src/main.rs

复制代码
//返回结构体信息
use id3::{Tag, TagLike};
use std::path::Path;

fn main() {
    let file_path = "F:/rustproject/mp3/07zhoutianya.mp3";

    match get_mp3_info(file_path) {
        Ok(info) => {
            println!("MP3文件信息:");
            println!("歌曲名: {}", info.title);
            println!("艺术家: {}", info.artist);
            println!("专辑: {}", info.album);
            println!("年份: {}", info.year);
            println!("音轨: {}", info.track);
            println!("流派: {}", info.genre);
            println!("专辑封面: {}", info.has_picture);
        }
        Err(e) => eprintln!("读取MP3信息失败: {}", e),
    }
}

#[derive(Debug, Default)]
struct Mp3Info {
    title: String,
    artist: String,
    album: String,
    year: String,
    track: String,
    genre: String,
    has_picture: bool,
}

fn get_mp3_info<P: AsRef<Path>>(path: P) -> Result<Mp3Info, Box<dyn std::error::Error>> {
    let tag = Tag::read_from_path(path)?;
    let mut info = Mp3Info::default();

    // 获取基本信息
    info.title = tag.title().unwrap_or("未知").to_string();
    info.artist = tag.artist().unwrap_or("未知").to_string();
    info.album = tag.album().unwrap_or("未知").to_string();
    info.year = tag.year()
        .map(|y| y.to_string())
        .unwrap_or_else(|| "未知".to_string());

    // 获取音轨信息
    info.track = tag.track()
        .map(|t| t.to_string())
        .unwrap_or_else(|| "未知".to_string());

    // 获取流派
    info.genre = tag.genre()
        .map(|g| g.to_string())
        .unwrap_or_else(|| "未知".to_string());

    // 检查是否有专辑封面
    info.has_picture = tag.pictures().next().is_some();

    Ok(info)
}

编译运行:

这个时候就不能再执行cargo run命令运行了,因为现在已经有两个项目了,必须指定要运行的项目,cargo build是编译所有项目,cargo run -p xxx是指定运行项目

复制代码
PS F:\rustproject\learndemo\analyzermp3> cargo build
   Compiling mp3demo2 v0.1.0 (F:\rustproject\learndemo\analyzermp3\mp3demo2)
   Compiling mp3demo v0.1.0 (F:\rustproject\learndemo\analyzermp3\mp3demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
PS F:\rustproject\learndemo\analyzermp3> cargo run -p mp3demo2
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target\debug\mp3demo2.exe`
MP3文件信息:
歌曲名: 走天涯
艺术家: 卓雅
专辑: 走天涯
年份: 未知
音轨: 1
流派: 未知
专辑封面: true
PS F:\rustproject\learndemo\analyzermp3>

3.提取mp3文件的封面图片

创建第3个项目cargo new mp3demo3

复制代码
PS F:\rustproject\learndemo\analyzermp3> cargo new mp3demo3
    Creating binary (application) `mp3demo3` package
      Adding `mp3demo3` as member of workspace at `F:\rustproject\learndemo\analyzermp3`
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
PS F:\rustproject\learndemo\analyzermp3>

根目录下的完整配置文件如下:

Cargo.toml

复制代码
[workspace]
resolver = "3"
# 列出所有成员   创建项目时 cargo new xxxx 自动添加
members = ["mp3demo", "mp3demo2", "mp3demo3"]
 
#default-members = ["mp3demo"]  # 默认只编译这个

# 统一依赖版本  
[workspace.dependencies]
id3 = "1.16.4"        # ID3标签处理

#serde = { version = "1.0", features = ["derive"] }
#tokio = { version = "1.0", features = ["full"] }

# 子 crate 中引用
#[dependencies]
#serde.workspace = true
#tokio.workspace = true

# 在工作空间根目录运行
#cargo clean            #清理
#cargo build          # 编译所有 crate
#cargo build -p xxxx  # 只编译特定 crate
#cargo run -p xxxx    # 运行特定项目

#在工作空间根目录运行 
# cargo test --workspace   #运行工作空间中所有 crate 的测试
# cargo test -p xxx         #运行特定测试

mp3demo3的配置文件mp3demo3/Cargo.toml

复制代码
[package]
name = "mp3demo3"
version = "0.1.0"
edition = "2024"

[dependencies]
id3 = "1.16.4"
image = "0.24"
mp3-metadata = "0.3"

编写代码,提取mp3文件的封面

复制代码
//提取封面  
use id3::Tag;
use std::env;
use std::fs::File;
use std::io::Write;
//use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 获取命令行参数:第一个是 MP3 文件路径,第二个(可选)是输出图片路径
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        eprintln!("用法: {} <MP3文件> [输出图片路径]", args[0]);
        eprintln!("如果未指定输出路径,则默认保存为 cover.jpg");
        return Ok(());
    }

    let mp3_path = &args[1];
    let output_path = if args.len() >= 3 {
        args[2].clone()
    } else {
        "cover.jpg".to_string()
    };

    // 读取 MP3 文件的 ID3 标签
    let tag = Tag::read_from_path(mp3_path)?;

    // 获取所有图片帧
    let mut pictures = tag.pictures();
    let picture = pictures.next().ok_or("该 MP3 文件中没有找到封面图片")?;

    // 将图片数据写入文件
    let mut file = File::create(&output_path)?;
    file.write_all(&picture.data)?;

    println!("封面图片已成功提取到: {}", output_path);
    println!("图片类型: {:?}", picture.picture_type);
    println!("MIME 类型: {:?}", picture.mime_type);
    println!("图片大小: {} 字节", picture.data.len());

    Ok(())
}
//cargo run -p mp3demo3 "F:/rustproject/mp3/07zhoutianya.mp3" cover.jpg

cargo build编译工作目录下的所有工程

编译成功后都生成在target/debug目录下:

节省编译过程中产生的临时文件目录。

提取封面执行时加上两个参数,文件路径和封面路径

cargo run -p mp3demo3 "F:/rustproject/mp3/07zhoutianya.mp3" cover.jpg

复制代码
PS F:\rustproject\learndemo\analyzermp3> cargo run -p mp3demo3 "F:/rustproject/mp3/07zhoutianya.mp3" cover.jpg
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     Running `target\debug\mp3demo3.exe F:/rustproject/mp3/07zhoutianya.mp3 cover.jpg`
封面图片已成功提取到: cover.jpg
图片类型: Media
MIME 类型: "image/jpeg"
图片大小: 540407 字节
PS F:\rustproject\learndemo\analyzermp3>

工作目录结构:

以上就是Rust多项目开发实践。

相关推荐
sunny_13 小时前
构建工具的第三次革命:从 Rollup 到 Rust Bundler,我是如何设计 robuild 的
前端·rust·前端工程化
用户02350873731218 小时前
第02篇:5分钟上手 blockcell —— 从安装到第一次对话
rust
badmonster021 小时前
我写了一个超轻量MCP,让编码Agent真正理解你的代码——Token消耗减少70%,1分钟接入
rust·ai编程
RoyLin21 小时前
10美元硬件中可运行的隐私 LLM 推理引擎
人工智能·rust·agent
Source.Liu1 天前
【egui】[特殊字符] 窗口配置小抄:eframe::NativeOptions
rust·egui
Hello.Reader1 天前
Tauri 项目结构前端壳 + Rust 内核,怎么协作、怎么构建、怎么扩展
开发语言·前端·rust
Source.Liu1 天前
【egui】[特殊字符]简单、快速、跨平台的 Rust GUI 库
rust·egui
Source.Liu2 天前
【rust-i18n】Cargo.toml 配置文件解析
rust
班公湖里洗过脚2 天前
Rust操作Josn数据及工作目录下多个应用程序维护示例
rust