7 Commits

Author SHA1 Message Date
a6f37a961c 更新自动打开文件夹功能
Some checks failed
Release / Publish to Github Releases (, macos-latest, aarch64-apple-darwin, true) (push) Has been cancelled
Release / Publish to Github Releases (, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, aarch64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, arm-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, armv7-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, i686-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, x86_64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, aarch64-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, i686-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / Publish to crates.io (push) Has been cancelled
2025-03-15 17:09:57 +08:00
9d4c7d57b8 更新版本号
Some checks failed
Release / Publish to Github Releases (, macos-latest, aarch64-apple-darwin, true) (push) Has been cancelled
Release / Publish to Github Releases (, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, aarch64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, arm-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, armv7-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, i686-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, x86_64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, aarch64-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, i686-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / Publish to crates.io (push) Has been cancelled
2025-03-15 16:33:10 +08:00
ae6c580d25 修正依赖版本号 2025-03-15 16:31:24 +08:00
1d029fe083 微调设置
Some checks failed
Release / Publish to Github Releases (, macos-latest, aarch64-apple-darwin, true) (push) Has been cancelled
Release / Publish to Github Releases (, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, aarch64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, arm-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, armv7-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, i686-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, x86_64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, aarch64-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, i686-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / Publish to crates.io (push) Has been cancelled
2025-03-15 16:17:33 +08:00
341a568683 大量重构。重写日志输出 2025-03-15 16:10:11 +08:00
ad3436d405 更新进度条。更新覆盖保存开关。
Some checks failed
Release / Publish to Github Releases (, macos-latest, aarch64-apple-darwin, true) (push) Has been cancelled
Release / Publish to Github Releases (, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, aarch64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, arm-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, armv7-unknown-linux-musleabihf, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, i686-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, ubuntu-latest, x86_64-unknown-linux-musl, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, aarch64-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, i686-pc-windows-msvc, true) (push) Has been cancelled
Release / Publish to Github Releases (, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release / Publish to crates.io (push) Has been cancelled
2025-01-07 00:34:16 +08:00
2f54a6727b 更新依赖 2025-01-06 00:23:45 +08:00
11 changed files with 544 additions and 437 deletions

View File

@ -41,4 +41,23 @@
## [2.3.7] - 2024.11.24 ## [2.3.7] - 2024.11.24
### Refactoring ### Refactoring
- 优化读取逻辑 - 优化读取逻辑
- :hammer: 重构代码大量减少panic! - :hammer: 重构代码大量减少panic!
## [2.5.8] - 2025.1.7
### Features :sparkles:
- 增加进度条支持(虽然很丑)
- 增加覆盖保存开关
### Refactoring
- :hammer: 重构代码使用mpsc进行线程通讯。
## [2.5.11] - 2025.3.15
- 修正依赖版本号
### Features :sparkles:
- 重新进度条支持!美观了不少啊
### Refactoring
- :hammer: 重构大量代码!
## [2.6.11] - 2025.3.15
### Features :sparkles:
- 更新自动打开文件夹选项,当解密结束后自动调用文件管理器打开输出目录

454
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,24 @@
[package] [package]
name = "ncmmiao" name = "ncmmiao"
version = "2.3.8" version = "2.6.11"
edition = "2021" edition = "2021"
authors = ["Lkhsss <lkhsss1019@gmail.com>"] authors = ["Lkhsss <lkhsss1019@gmail.com>"]
description = "A magic tool convert ncm to flac" description = "A magic tool convert ncm to flac"
repository = "https://github.com/lkhsss/ncmmiao" repository = "https://github.com/lkhsss/ncmmiao"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
keys = ["ncm","flac","neteasemusic"] keywords = ["ncm","flac","neteasemusic"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
aes = "0.8.3" aes = { version = "0.8.4", default-features = false }
audiotags = "0.5.0" audiotags = {version = "0.5.0",default-features = false}
base64 = "0.22.*" base64 = {version = "0.22.*"}
chrono = "0.4.38" chrono = {version = "0.4.*",features = ["clock"],default-features = false}
clap = { version = "4.5.9", features = ["derive"] } clap = { version = "4.5.*", features = ["derive","std"]}
colored = "2.1.0" colored = {version = "3.0.0",default-features = false}
env_logger = "0.11.1" env_logger = {version = "0.11.7",default-features = false}
hex = "0.4.3" hex = "0.4.3"
image = "0.25.*" image = "0.25.*"
indicatif = "0.17.9" indicatif = "0.17.9"

View File

@ -1,14 +1,16 @@
# NcmMiao :tada: # NcmMiao :tada:
[![build](https://github.com/Lkhsss/NcmMiao/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/Lkhsss/NcmMiao/actions/workflows/build.yml) [![build](https://github.com/Lkhsss/NcmMiao/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/Lkhsss/NcmMiao/actions/workflows/build.yml)
一个使用Rust语言编写的ncm文件解密工具(第一!😆) 一个使用Rust语言编写的ncm文件解密工具😆
### 功能及特点 ### 功能及特点
- 支持单一文件,多文件夹递归批量解密。 - 支持单一文件,多文件夹递归批量解密。
- 完善的日志功能 - 完善的日志功能
- Colorful - Colorful
- 编译文件小,解密快 - 编译文件小,解密快
- [New!]支持自动添加封面! - 支持自动添加封面!
- 自动打开输出文件夹
- 简约美观
## 编译 ## 编译
``` ```
@ -32,13 +34,15 @@ Options:
# TODO :construction: # TODO :construction:
- [x] 多线程支持 - [x] 多线程支持
- [x] 自动添加封面 - [x] 自动添加封面
- [ ] 解密进度条 - [x] 解密进度条
- [x] 命令行解析 - [x] 命令行解析
- [x] 自定义输出文件夹 - [x] 自定义输出文件夹
- [x] 计时功能 - [x] 计时功能
- [ ] 自动覆盖开关 - [x] 自动覆盖开关
--- ---
# [Changelog](CHANGELOG.md)
---
# 附 - ncm文件结构 # 附 - ncm文件结构
|信息|大小|作用| |信息|大小|作用|

View File

@ -5,13 +5,22 @@ use clap::Parser;
#[command(author = "lkhsss")] #[command(author = "lkhsss")]
#[command(version,about = "一个解密ncm文件的神秘程序 By Lkhsss", long_about = None)] #[command(version,about = "一个解密ncm文件的神秘程序 By Lkhsss", long_about = None)]
pub struct Cli { pub struct Cli {
/// 并发的最大线程数,默认为4线程 /// 并发的最大线程数,默认为8线程
#[arg(short, long)] #[arg(short, long)]
pub workers: Option<usize>, pub workers: Option<usize>,
/// 需要解密的文件夹或文件 /// 需要解密的文件夹或文件
#[arg(short, long, name = "输入文件/文件夹")] #[arg(short, long, name = "输入文件/目录")]
pub input: Vec<String>, pub input: Vec<String>,
#[arg(short, long, name = "输出文件夹", default_value = "NcmmiaoOutput")] /// 输出目录
#[arg(short, long, name = "输出目录", default_value = "NcmmiaoOutput")]
pub output: Option<String>, pub output: Option<String>,
/// 强制覆盖保存开关
#[arg(short, long, name = "强制覆盖开关")]
pub forcesave: bool,
/// 自动打开输出目录
#[arg(short,long,name="自动打开输出目录")]
pub autoopen:bool,
} }

View File

@ -1,47 +1,51 @@
use chrono; use colored::Color::{Blue, Cyan, Green, Red, Yellow};
use env_logger::Builder; use colored::Colorize;
use std::io::Write; use indicatif::MultiProgress;
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
use std::sync::Arc;
pub struct Logger {} // 自定义Logger将日志发送到MultiProgress
pub struct MultiProgressLogger {
impl Logger { mp: Arc<MultiProgress>,
pub fn new() { }
let mut builder = Builder::new();
if cfg!(debug_assertions) { impl Log for MultiProgressLogger {
builder.filter(None, log::LevelFilter::Debug); fn enabled(&self, _metadata: &Metadata) -> bool {
} else { true
builder.filter(None, log::LevelFilter::Info); }
}
builder.format(move |buf, record| { fn log(&self, record: &Record) {
writeln!( if self.enabled(record.metadata()) {
buf, let level = match record.level() {
"[{} {}] {}", log::Level::Error => ("Error").color(Red),
format!("{}", chrono::Local::now().format("%H:%M:%S")), log::Level::Warn => ("Warn").color(Yellow),
match record.level() { log::Level::Info => ("Info").color(Green),
log::Level::Error => { log::Level::Debug => ("Debug").color(Blue),
let style = buf.default_level_style(log::Level::Error); log::Level::Trace => ("Debug").color(Cyan),
format!("{style}Error{style:#}") };
} let message = format!(
log::Level::Warn => { "[{}][{}] {}",
let style = buf.default_level_style(log::Level::Warn); chrono::Local::now().format("%H:%M:%S"),
format!("{style}Warn{style:#}") level,
} record.args()
log::Level::Info => { );
let style = buf.default_level_style(log::Level::Info); self.mp.println(message).expect("Failed to print log");
format!("{style}Info{style:#}") }
} }
log::Level::Debug => {
let style = buf.default_level_style(log::Level::Debug); fn flush(&self) {}
format!("{style}Debug{style:#}") }
}
log::Level::Trace => { // 初始化日志系统
let style = buf.default_level_style(log::Level::Trace); pub fn init_logger() -> Result<(), SetLoggerError> {
format!("{style}Trace{style:#}") let logger = MultiProgressLogger {
} mp: crate::MP.clone(),
}, };
record.args(), log::set_boxed_logger(Box::new(logger))?;
) if cfg!(debug_assertions) {
}); log::set_max_level(LevelFilter::Debug);
builder.init(); //初始化logger } else {
} log::set_max_level(LevelFilter::Info);
}
Ok(())
} }

View File

@ -1,23 +1,33 @@
use std::{ops::Add, path::Path, sync::{Arc, Mutex}};
use ::clap::Parser; use ::clap::Parser;
#[allow(unused_imports)] use colored::{Color, Colorize};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use lazy_static::lazy_static;
use log::{error, info, warn}; use log::{error, info, warn};
use std::time::Duration;
use colored::Colorize; use std::{
path::Path,
use walkdir::WalkDir; //遍历目录 sync::{
mpsc::{self, Sender},
Arc, Mutex,
},
};
mod clap; mod clap;
mod logger; mod logger;
mod messager;
mod ncmdump; mod ncmdump;
mod threadpool; mod pathparse;
use ncmdump::Ncmfile;
mod test; mod test;
mod threadpool;
mod opendir;
use ncmdump::Ncmfile;
const DEFAULT_MAXWORKER:usize = 8;
fn main() { fn main() {
let timer = ncmdump::TimeCompare::new(); let timer = ncmdump::TimeCompare::new();
// 初始化日志系统 // 初始化日志系统
logger::Logger::new(); logger::init_logger().unwrap();
let cli = clap::Cli::parse(); let cli = clap::Cli::parse();
@ -30,47 +40,19 @@ fn main() {
1 1
} }
} }
None => 4, None => DEFAULT_MAXWORKER,
}; };
let input = cli.input; let input = cli.input;
let outputdir = cli.output.unwrap(); let outputdir = cli.output.unwrap();
let forcesave = cli.forcesave;
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径 if forcesave {
warn!("文件{}已开启!", "强制覆盖".bright_red())
for arg in input {
//解析传入的每一个路径文件or文件夹
let path = Path::new(&arg);
if path.is_file() {
// 当后缀符合为ncm时才加入列表
match path.extension() {
Some(extension) => {
if extension == "ncm" {
let _ = &mut undumpfile.push(arg.to_owned());
}
}
None => {}
}
} else if path.is_dir() {
for entry in WalkDir::new(path) {
let new_entry = entry.unwrap().clone();
let filepath = new_entry.into_path();
// 当后缀符合为ncm时才加入列表
match filepath.extension() {
Some(extension) => {
if extension == "ncm" {
let _ = &mut undumpfile.push(String::from(filepath.to_str().unwrap()));
}
}
None => {
continue;
}
}
}
}
} }
let undumpfile = pathparse::pathparse(input); // 该列表将存入文件的路径
let taskcount = undumpfile.len(); let taskcount = undumpfile.len();
let successful = Arc::new(Mutex::new(0)); let successful = Arc::new(Mutex::new(0));
if taskcount == 0 { if taskcount == 0 {
@ -78,23 +60,50 @@ fn main() {
} else { } else {
// 初始化线程池 // 初始化线程池
let pool = threadpool::Pool::new(max_workers); let pool = threadpool::Pool::new(max_workers);
info!("启用{}线程", max_workers); info!(
"将启用{}线程",
max_workers.to_string().color(Color::BrightGreen)
);
// 初始化通讯
let (tx, rx) = mpsc::channel();
// 循环开始
for filepath in undumpfile { for filepath in undumpfile {
let output = outputdir.clone(); let output = outputdir.clone();
let successful = Arc::clone(&successful); let successful = Arc::clone(&successful);
pool.execute(move || { let sender: Sender<messager::Message> = tx.clone();
match Ncmfile::new(filepath.as_str()) { pool.execute(move || match Ncmfile::new(filepath.as_str()) {
Ok(mut n) => match n.dump(Path::new(&output)) { Ok(mut n) => match n.dump(Path::new(&output), sender, forcesave) {
Ok(_) => { Ok(_) => {
let mut num = successful.lock().unwrap(); let mut num = successful.lock().unwrap();
*num += 1;}, *num += 1;
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e), }
}, Err(e) => error!("[{}] 解密失败: {}", filepath.yellow(), e),
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e), },
} Err(e) => error!("[{}] 解密失败: {}", filepath.yellow(), e),
}); });
} }
//循环到此结束
//进度条
let pb = ProgressBar::new((taskcount * 6) as u64) //长度乘积取决于Signal的数量
.with_elapsed(Duration::from_millis(50))
.with_style(
ProgressStyle::default_bar()
.progress_chars("#>-")
.template("{spinner:.green} [{wide_bar:.cyan/blue}] {percent_precise}% ({eta})")
.unwrap(),
)
.with_message("解密中");
let progressbar = MP.add(pb);
//接受消息
for messages in rx {
progressbar.inc(1);
messages.log(); //发送log
}
progressbar.finish_and_clear();
} }
let timecount = timer.compare(); let timecount = timer.compare();
let showtime = || { let showtime = || {
@ -110,5 +119,16 @@ fn main() {
successful.to_string().bright_green(), successful.to_string().bright_green(),
(taskcount - successful).to_string().bright_red(), (taskcount - successful).to_string().bright_red(),
showtime() showtime()
) );
// 自动打开输出文件夹
if cli.autoopen{
opendir::opendir(outputdir.into());
};
}
lazy_static! {
static ref MP: Arc<MultiProgress> = Arc::new(MultiProgress::new());
} }

62
src/messager.rs Normal file
View File

@ -0,0 +1,62 @@
use colored::Colorize;
use log::info;
use crate::messager;
use std::fmt::Debug;
use std::sync::mpsc;
pub struct Messager {
name: String,
sender: mpsc::Sender<messager::Message>,
}
pub struct Message {
pub name: String,
pub signal: Signals,
}
impl Message {
// 定义一个公共方法 log用于记录不同信号状态下的日志信息
pub fn log(&self) {
let loginfo = match &self.signal {
Signals::Start => "读取文件",
Signals::GetMetaInfo => "解密歌曲元信息",
Signals::GetCover => "解密封面图片数据",
Signals::Decrypt => "解密歌曲信息",
Signals::Save => "保存文件",
Signals::End => "成功!",
};
info!("[{}] {}", self.name.cyan(), loginfo)
}
}
pub enum Signals {
Start,
GetMetaInfo,
GetCover,
Decrypt,
Save,
End,
}
impl Messager {
pub fn new(name: String, sender: mpsc::Sender<messager::Message>) -> Self {
Self { name, sender }
}
pub fn send(&self, s: Signals) -> Result<(), std::sync::mpsc::SendError<messager::Message>> {
self.sender.send(Message {
name: self.name.clone(),
signal: s,
})
}
}
impl Debug for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = match &self.signal {
Signals::Start => "开始破解",
Signals::Decrypt => "开始解密",
Signals::Save => "保存文件",
Signals::End => "破解完成",
Signals::GetMetaInfo => "获取元数据",
Signals::GetCover => "获取封面",
};
write!(f, "[{}] {}", self.name, message)
}
}

View File

@ -1,3 +1,4 @@
use crate::messager;
use aes::cipher::generic_array::typenum::U16; use aes::cipher::generic_array::typenum::U16;
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit}; use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
use aes::Aes128; use aes::Aes128;
@ -8,12 +9,15 @@ use hex::decode;
use lazy_static::lazy_static; use lazy_static::lazy_static;
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use messager::Signals;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::{self, Value}; use serde_json::{self, Value};
use std::fmt::Debug;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}; use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8; use std::str::from_utf8;
use std::sync::mpsc;
use std::vec; use std::vec;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@ -144,13 +148,21 @@ impl Ncmfile {
/// 解密函数 /// 解密函数
#[allow(unused_assignments)] #[allow(unused_assignments)]
pub fn dump(&mut self, outputdir: &Path) -> Result<(), NcmError> { pub fn dump(
info!("开始解密[{}]文件", self.fullfilename.yellow()); &mut self,
outputdir: &Path,
tx: mpsc::Sender<messager::Message>,
force_save: bool,
) -> Result<(), NcmError> {
let messager = messager::Messager::new(self.fullfilename.clone(), tx);
let _ = messager.send(Signals::Start);
//TODO 通讯合法化
// info!("开始解密[{}]文件", self.fullfilename.yellow());
// 获取magic header 。应为CTENFDAM // 获取magic header 。应为CTENFDAM
let magic_header = match self.seekread(8) { let magic_header = match self.seekread(8) {
Ok(header) => header, Ok(header) => header,
Err(_e) => { Err(_e) => {
return Err(NcmError::FileReadError); return Err(NcmError::FileReadError); //TODO去除向上传播
} }
}; };
@ -186,6 +198,7 @@ impl Ncmfile {
//读取meta信息的数据大小 //读取meta信息的数据大小
trace!("获取meta信息数据大小"); trace!("获取meta信息数据大小");
let meta_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64; let meta_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
let _ = messager.send(Signals::GetMetaInfo);
// 读取meta信息 // 读取meta信息
trace!("读取meta信息"); trace!("读取meta信息");
@ -211,12 +224,39 @@ impl Ncmfile {
Err(_) => return Err(NcmError::CannotReadMetaInfo), Err(_) => return Err(NcmError::CannotReadMetaInfo),
}; };
debug!("json_data: {}", json_data); debug!("json_data: {}", json_data);
let data: Value = match serde_json::from_str(&json_data[..]){Ok(o) => o, let data: Value = match serde_json::from_str(&json_data[..]) {
Ok(o) => o,
Err(_) => return Err(NcmError::CannotReadMetaInfo), Err(_) => return Err(NcmError::CannotReadMetaInfo),
}; //解析json数据 }; //解析json数据
data data
}; };
//处理文件路径
trace!("拼接文件路径");
let path = {
let filename = format!(
"{}.{}",
self.filename,
meta_data.get("format").unwrap().as_str().unwrap()
);
// let filename = standardize_filename(filename);
debug!("文件名:{}", filename.yellow());
//链级创建输出目录
match fs::create_dir_all(outputdir) {
Err(_) => return Err(NcmError::FileWriteError),
_ => (),
};
outputdir.join(filename)
};
debug!("文件路径: {:?}", path);
// 先检查是否存在
if !force_save && Path::new(&path).exists() {
return Err(NcmError::ProtectFile);
}
// 跳过4个字节的校验码 // 跳过4个字节的校验码
trace!("读取校验码"); trace!("读取校验码");
// let _crc32 = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64; // let _crc32 = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64;
@ -226,10 +266,10 @@ impl Ncmfile {
trace!("跳过5个字节"); trace!("跳过5个字节");
self.skip(5)?; self.skip(5)?;
let _ = messager.send(Signals::GetCover);
// 获取图片数据的大小 // 获取图片数据的大小
trace!("获取图片数据的大小"); trace!("获取图片数据的大小");
let image_data_length = let image_data_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
// 读取图片,并写入文件当中 // 读取图片,并写入文件当中
let image_data = self.seekread(image_data_length)?; //读取图片数据 let image_data = self.seekread(image_data_length)?; //读取图片数据
@ -280,6 +320,7 @@ impl Ncmfile {
//解密音乐数据 //解密音乐数据
trace!("解密音乐数据"); trace!("解密音乐数据");
let _ = messager.send(Signals::Decrypt);
let mut music_data: Vec<u8> = Vec::new(); let mut music_data: Vec<u8> = Vec::new();
loop { loop {
let mut chunk = self.seekread_no_error(0x8000); let mut chunk = self.seekread_no_error(0x8000);
@ -325,30 +366,14 @@ impl Ncmfile {
//退出循环,写入文件 //退出循环,写入文件
//处理文件路径 let _ = messager.send(Signals::Save);
trace!("拼接文件路径");
let path = {
let filename = format!(
"{}.{}",
self.filename,
meta_data.get("format").unwrap().as_str().unwrap()
);
// let filename = standardize_filename(filename);
debug!("文件名:{}", filename.yellow());
//链级创建输出目录
match fs::create_dir_all(outputdir){Err(_)=>return Err(NcmError::FileWriteError),_=>()};
outputdir.join(filename)
};
debug!("文件路径: {:?}", path);
self.save(&path, music_data)?; self.save(&path, music_data)?;
{ {
// 保存封面 // 保存封面
let mut tag = match Tag::new().read_from_path(&path){ let mut tag = match Tag::new().read_from_path(&path) {
Ok(o)=>o, Ok(o) => o,
Err(_)=>return Err(NcmError::CoverCannotSave) Err(_) => return Err(NcmError::CoverCannotSave),
}; };
let cover = Picture { let cover = Picture {
mime_type: MimeType::Jpeg, mime_type: MimeType::Jpeg,
@ -368,19 +393,20 @@ impl Ncmfile {
self.fullfilename.yellow(), self.fullfilename.yellow(),
"解密成功".bright_green() "解密成功".bright_green()
); );
let _ = messager.send(Signals::End);
Ok(()) Ok(())
} }
fn save(&mut self, path: &PathBuf, data: Vec<u8>)->Result<(),NcmError> { fn save(&mut self, path: &PathBuf, data: Vec<u8>) -> Result<(), NcmError> {
let music_file = match File::create(path){ let music_file = match File::create(path) {
Ok(o)=>o, Ok(o) => o,
Err(_)=>return Err(NcmError::FileWriteError) Err(_) => return Err(NcmError::FileWriteError),
}; };
let mut writer = BufWriter::new(music_file); let mut writer = BufWriter::new(music_file);
let _ = writer.write_all(&data); let _ = writer.write_all(&data);
// 关闭文件 // 关闭文件
match writer.flush(){ match writer.flush() {
Ok(o)=>o, Ok(o) => o,
Err(_)=>return Err(NcmError::FileWriteError) Err(_) => return Err(NcmError::FileWriteError),
}; };
Ok(()) Ok(())
} }
@ -544,7 +570,8 @@ pub enum NcmError {
FileSkipError, FileSkipError,
FileWriteError, FileWriteError,
FullFilenameError, FullFilenameError,
FileNotFoundError, FileNotFound,
ProtectFile,
} }
impl std::error::Error for NcmError {} impl std::error::Error for NcmError {}
@ -560,6 +587,10 @@ impl std::fmt::Display for NcmError {
Self::FileReadError => write!(f, "读取文件时发生错误"), Self::FileReadError => write!(f, "读取文件时发生错误"),
Self::FileWriteError => write!(f, "写入文件时错误"), Self::FileWriteError => write!(f, "写入文件时错误"),
Self::FullFilenameError => write!(f, "文件名不符合规范"), Self::FullFilenameError => write!(f, "文件名不符合规范"),
Self::ProtectFile => write!(
f,
"已关闭文件强制覆盖且文件已存在。使用-f或-forcesave开启强制覆盖。"
),
_ => write!(f, "未知错误"), _ => write!(f, "未知错误"),
} }
} }

34
src/opendir.rs Normal file
View File

@ -0,0 +1,34 @@
use log::error;
use std::{path::PathBuf, process::Command};
#[cfg(target_os = "windows")]
pub fn opendir(dir: PathBuf) {
match Command::new("explorer")
.arg(&dir) // <- Specify the directory you'd like to open.
.spawn()
{
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
_ => (),
}
}
#[cfg(target_os = "linux")]
pub fn opendir(dir: PathBuf) {
match Command::new("open")
.arg(&dir) // <- Specify the directory you'd like to open.
.spawn()
{
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
_ => (),
}
}
#[cfg(target_os = "macos")]
pub fn opendir(dir: PathBuf) {
match Command::new("open")
.arg(&dir) // <- Specify the directory you'd like to open.
.spawn()
{
Err(_) => error!("无法打开输出文件夹:[{}]", dir.display()),
_ => (),
}
}

40
src/pathparse.rs Normal file
View File

@ -0,0 +1,40 @@
use std::path::Path;
use walkdir::WalkDir;
pub fn pathparse(input: Vec<String>) -> Vec<String> {
let mut undumpfile = Vec::new(); // 该列表将存入文件的路径
// 遍历输入的每一个路径参数
for arg in input {
//解析传入的每一个路径文件or文件夹
let path = Path::new(&arg);
if path.is_file() {
// 当后缀符合为ncm时才加入列表
match path.extension() {
Some(extension) => {
if extension == "ncm" {
let _ = &mut undumpfile.push(arg.to_owned());
}
}
None => {}
}
} else if path.is_dir() {
for entry in WalkDir::new(path) {
let new_entry = entry.unwrap().clone();
let filepath = new_entry.into_path();
// 当后缀符合为ncm时才加入列表
match filepath.extension() {
Some(extension) => {
if extension == "ncm" {
let _ = &mut undumpfile.push(String::from(filepath.to_str().unwrap()));
}
}
None => {
continue;
}
}
}
}
}
undumpfile
}