Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6f37a961c | |||
| 9d4c7d57b8 | |||
| ae6c580d25 | |||
| 1d029fe083 | |||
| 341a568683 | |||
| ad3436d405 | |||
| 2f54a6727b |
21
CHANGELOG.md
21
CHANGELOG.md
@ -41,4 +41,23 @@
|
||||
## [2.3.7] - 2024.11.24
|
||||
### 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
454
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@ -1,24 +1,24 @@
|
||||
[package]
|
||||
name = "ncmmiao"
|
||||
version = "2.3.8"
|
||||
version = "2.6.11"
|
||||
edition = "2021"
|
||||
authors = ["Lkhsss <lkhsss1019@gmail.com>"]
|
||||
description = "A magic tool convert ncm to flac"
|
||||
repository = "https://github.com/lkhsss/ncmmiao"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
aes = "0.8.3"
|
||||
audiotags = "0.5.0"
|
||||
base64 = "0.22.*"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
env_logger = "0.11.1"
|
||||
aes = { version = "0.8.4", default-features = false }
|
||||
audiotags = {version = "0.5.0",default-features = false}
|
||||
base64 = {version = "0.22.*"}
|
||||
chrono = {version = "0.4.*",features = ["clock"],default-features = false}
|
||||
clap = { version = "4.5.*", features = ["derive","std"]}
|
||||
colored = {version = "3.0.0",default-features = false}
|
||||
env_logger = {version = "0.11.7",default-features = false}
|
||||
hex = "0.4.3"
|
||||
image = "0.25.*"
|
||||
indicatif = "0.17.9"
|
||||
|
||||
12
README.md
12
README.md
@ -1,14 +1,16 @@
|
||||
# NcmMiao :tada:
|
||||
[](https://github.com/Lkhsss/NcmMiao/actions/workflows/build.yml)
|
||||
|
||||
一个使用Rust语言编写的ncm文件解密工具(第一!😆)。
|
||||
一个使用Rust语言编写的ncm文件解密工具😆。
|
||||
|
||||
### 功能及特点
|
||||
- 支持单一文件,多文件夹递归批量解密。
|
||||
- 完善的日志功能
|
||||
- Colorful
|
||||
- 编译文件小,解密快
|
||||
- [New!]支持自动添加封面!
|
||||
- 支持自动添加封面!
|
||||
- 自动打开输出文件夹
|
||||
- 简约美观
|
||||
|
||||
## 编译
|
||||
```
|
||||
@ -32,13 +34,15 @@ Options:
|
||||
# TODO :construction:
|
||||
- [x] 多线程支持
|
||||
- [x] 自动添加封面
|
||||
- [ ] 解密进度条
|
||||
- [x] 解密进度条
|
||||
- [x] 命令行解析
|
||||
- [x] 自定义输出文件夹
|
||||
- [x] 计时功能
|
||||
- [ ] 自动覆盖开关
|
||||
- [x] 自动覆盖开关
|
||||
|
||||
---
|
||||
# [Changelog](CHANGELOG.md)
|
||||
---
|
||||
|
||||
# 附 - ncm文件结构
|
||||
|信息|大小|作用|
|
||||
|
||||
15
src/clap.rs
15
src/clap.rs
@ -5,13 +5,22 @@ use clap::Parser;
|
||||
#[command(author = "lkhsss")]
|
||||
#[command(version,about = "一个解密ncm文件的神秘程序 By Lkhsss", long_about = None)]
|
||||
pub struct Cli {
|
||||
/// 并发的最大线程数,默认为4线程
|
||||
/// 并发的最大线程数,默认为8线程
|
||||
#[arg(short, long)]
|
||||
pub workers: Option<usize>,
|
||||
/// 需要解密的文件夹或文件
|
||||
#[arg(short, long, name = "输入文件/文件夹")]
|
||||
#[arg(short, long, name = "输入文件/目录")]
|
||||
pub input: Vec<String>,
|
||||
|
||||
#[arg(short, long, name = "输出文件夹", default_value = "NcmmiaoOutput")]
|
||||
/// 输出目录
|
||||
#[arg(short, long, name = "输出目录", default_value = "NcmmiaoOutput")]
|
||||
pub output: Option<String>,
|
||||
|
||||
/// 强制覆盖保存开关
|
||||
#[arg(short, long, name = "强制覆盖开关")]
|
||||
pub forcesave: bool,
|
||||
|
||||
/// 自动打开输出目录
|
||||
#[arg(short,long,name="自动打开输出目录")]
|
||||
pub autoopen:bool,
|
||||
}
|
||||
|
||||
@ -1,47 +1,51 @@
|
||||
use chrono;
|
||||
use env_logger::Builder;
|
||||
use std::io::Write;
|
||||
use colored::Color::{Blue, Cyan, Green, Red, Yellow};
|
||||
use colored::Colorize;
|
||||
use indicatif::MultiProgress;
|
||||
use log::{LevelFilter, Log, Metadata, Record, SetLoggerError};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Logger {}
|
||||
|
||||
impl Logger {
|
||||
pub fn new() {
|
||||
let mut builder = Builder::new();
|
||||
if cfg!(debug_assertions) {
|
||||
builder.filter(None, log::LevelFilter::Debug);
|
||||
} else {
|
||||
builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
builder.format(move |buf, record| {
|
||||
writeln!(
|
||||
buf,
|
||||
"[{} {}] {}",
|
||||
format!("{}", chrono::Local::now().format("%H:%M:%S")),
|
||||
match record.level() {
|
||||
log::Level::Error => {
|
||||
let style = buf.default_level_style(log::Level::Error);
|
||||
format!("{style}Error{style:#}")
|
||||
}
|
||||
log::Level::Warn => {
|
||||
let style = buf.default_level_style(log::Level::Warn);
|
||||
format!("{style}Warn{style:#}")
|
||||
}
|
||||
log::Level::Info => {
|
||||
let style = buf.default_level_style(log::Level::Info);
|
||||
format!("{style}Info{style:#}")
|
||||
}
|
||||
log::Level::Debug => {
|
||||
let style = buf.default_level_style(log::Level::Debug);
|
||||
format!("{style}Debug{style:#}")
|
||||
}
|
||||
log::Level::Trace => {
|
||||
let style = buf.default_level_style(log::Level::Trace);
|
||||
format!("{style}Trace{style:#}")
|
||||
}
|
||||
},
|
||||
record.args(),
|
||||
)
|
||||
});
|
||||
builder.init(); //初始化logger
|
||||
}
|
||||
// 自定义Logger,将日志发送到MultiProgress
|
||||
pub struct MultiProgressLogger {
|
||||
mp: Arc<MultiProgress>,
|
||||
}
|
||||
|
||||
impl Log for MultiProgressLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let level = match record.level() {
|
||||
log::Level::Error => ("Error").color(Red),
|
||||
log::Level::Warn => ("Warn").color(Yellow),
|
||||
log::Level::Info => ("Info").color(Green),
|
||||
log::Level::Debug => ("Debug").color(Blue),
|
||||
log::Level::Trace => ("Debug").color(Cyan),
|
||||
};
|
||||
let message = format!(
|
||||
"[{}][{}] {}",
|
||||
chrono::Local::now().format("%H:%M:%S"),
|
||||
level,
|
||||
record.args()
|
||||
);
|
||||
self.mp.println(message).expect("Failed to print log");
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
// 初始化日志系统
|
||||
pub fn init_logger() -> Result<(), SetLoggerError> {
|
||||
let logger = MultiProgressLogger {
|
||||
mp: crate::MP.clone(),
|
||||
};
|
||||
log::set_boxed_logger(Box::new(logger))?;
|
||||
if cfg!(debug_assertions) {
|
||||
log::set_max_level(LevelFilter::Debug);
|
||||
} else {
|
||||
log::set_max_level(LevelFilter::Info);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
132
src/main.rs
132
src/main.rs
@ -1,23 +1,33 @@
|
||||
use std::{ops::Add, path::Path, sync::{Arc, Mutex}};
|
||||
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 colored::Colorize;
|
||||
|
||||
use walkdir::WalkDir; //遍历目录
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
mpsc::{self, Sender},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
mod clap;
|
||||
mod logger;
|
||||
mod messager;
|
||||
mod ncmdump;
|
||||
mod threadpool;
|
||||
use ncmdump::Ncmfile;
|
||||
mod pathparse;
|
||||
mod test;
|
||||
mod threadpool;
|
||||
mod opendir;
|
||||
use ncmdump::Ncmfile;
|
||||
|
||||
const DEFAULT_MAXWORKER:usize = 8;
|
||||
|
||||
fn main() {
|
||||
let timer = ncmdump::TimeCompare::new();
|
||||
// 初始化日志系统
|
||||
logger::Logger::new();
|
||||
logger::init_logger().unwrap();
|
||||
|
||||
let cli = clap::Cli::parse();
|
||||
|
||||
@ -30,47 +40,19 @@ fn main() {
|
||||
1
|
||||
}
|
||||
}
|
||||
None => 4,
|
||||
None => DEFAULT_MAXWORKER,
|
||||
};
|
||||
|
||||
let input = cli.input;
|
||||
|
||||
let outputdir = cli.output.unwrap();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let forcesave = cli.forcesave;
|
||||
if forcesave {
|
||||
warn!("文件{}已开启!", "强制覆盖".bright_red())
|
||||
}
|
||||
|
||||
let undumpfile = pathparse::pathparse(input); // 该列表将存入文件的路径
|
||||
|
||||
let taskcount = undumpfile.len();
|
||||
let successful = Arc::new(Mutex::new(0));
|
||||
if taskcount == 0 {
|
||||
@ -78,23 +60,50 @@ fn main() {
|
||||
} else {
|
||||
// 初始化线程池
|
||||
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 {
|
||||
let output = outputdir.clone();
|
||||
let successful = Arc::clone(&successful);
|
||||
pool.execute(move || {
|
||||
match Ncmfile::new(filepath.as_str()) {
|
||||
Ok(mut n) => match n.dump(Path::new(&output)) {
|
||||
Ok(_) => {
|
||||
let mut num = successful.lock().unwrap();
|
||||
*num += 1;},
|
||||
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e),
|
||||
},
|
||||
Err(e) => error!("[{}]解密失败: {}", filepath.yellow(), e),
|
||||
}
|
||||
let sender: Sender<messager::Message> = tx.clone();
|
||||
pool.execute(move || match Ncmfile::new(filepath.as_str()) {
|
||||
Ok(mut n) => match n.dump(Path::new(&output), sender, forcesave) {
|
||||
Ok(_) => {
|
||||
let mut num = successful.lock().unwrap();
|
||||
*num += 1;
|
||||
}
|
||||
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 showtime = || {
|
||||
@ -110,5 +119,16 @@ fn main() {
|
||||
successful.to_string().bright_green(),
|
||||
(taskcount - successful).to_string().bright_red(),
|
||||
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
62
src/messager.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::messager;
|
||||
use aes::cipher::generic_array::typenum::U16;
|
||||
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, KeyInit};
|
||||
use aes::Aes128;
|
||||
@ -8,12 +9,15 @@ use hex::decode;
|
||||
use lazy_static::lazy_static;
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use messager::Signals;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::{self, Value};
|
||||
use std::fmt::Debug;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8;
|
||||
use std::sync::mpsc;
|
||||
use std::vec;
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
@ -144,13 +148,21 @@ impl Ncmfile {
|
||||
|
||||
/// 解密函数
|
||||
#[allow(unused_assignments)]
|
||||
pub fn dump(&mut self, outputdir: &Path) -> Result<(), NcmError> {
|
||||
info!("开始解密[{}]文件", self.fullfilename.yellow());
|
||||
pub fn dump(
|
||||
&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
|
||||
let magic_header = match self.seekread(8) {
|
||||
Ok(header) => header,
|
||||
Err(_e) => {
|
||||
return Err(NcmError::FileReadError);
|
||||
return Err(NcmError::FileReadError); //TODO去除向上传播
|
||||
}
|
||||
};
|
||||
|
||||
@ -186,6 +198,7 @@ impl Ncmfile {
|
||||
//读取meta信息的数据大小
|
||||
trace!("获取meta信息数据大小");
|
||||
let meta_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
|
||||
let _ = messager.send(Signals::GetMetaInfo);
|
||||
|
||||
// 读取meta信息
|
||||
trace!("读取meta信息");
|
||||
@ -211,12 +224,39 @@ impl Ncmfile {
|
||||
Err(_) => return Err(NcmError::CannotReadMetaInfo),
|
||||
};
|
||||
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),
|
||||
}; //解析json数据
|
||||
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个字节的校验码
|
||||
trace!("读取校验码");
|
||||
// let _crc32 = u32::from_le_bytes(self.seekread(4).unwrap().try_into().unwrap()) as u64;
|
||||
@ -226,10 +266,10 @@ impl Ncmfile {
|
||||
trace!("跳过5个字节");
|
||||
self.skip(5)?;
|
||||
|
||||
let _ = messager.send(Signals::GetCover);
|
||||
// 获取图片数据的大小
|
||||
trace!("获取图片数据的大小");
|
||||
let image_data_length =
|
||||
u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
|
||||
let image_data_length = u32::from_le_bytes(self.seekread(4)?.try_into().unwrap()) as u64;
|
||||
|
||||
// 读取图片,并写入文件当中
|
||||
let image_data = self.seekread(image_data_length)?; //读取图片数据
|
||||
@ -280,6 +320,7 @@ impl Ncmfile {
|
||||
|
||||
//解密音乐数据
|
||||
trace!("解密音乐数据");
|
||||
let _ = messager.send(Signals::Decrypt);
|
||||
let mut music_data: Vec<u8> = Vec::new();
|
||||
loop {
|
||||
let mut chunk = self.seekread_no_error(0x8000);
|
||||
@ -325,30 +366,14 @@ impl Ncmfile {
|
||||
|
||||
//退出循环,写入文件
|
||||
|
||||
//处理文件路径
|
||||
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);
|
||||
let _ = messager.send(Signals::Save);
|
||||
self.save(&path, music_data)?;
|
||||
|
||||
{
|
||||
// 保存封面
|
||||
let mut tag = match Tag::new().read_from_path(&path){
|
||||
Ok(o)=>o,
|
||||
Err(_)=>return Err(NcmError::CoverCannotSave)
|
||||
let mut tag = match Tag::new().read_from_path(&path) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return Err(NcmError::CoverCannotSave),
|
||||
};
|
||||
let cover = Picture {
|
||||
mime_type: MimeType::Jpeg,
|
||||
@ -368,19 +393,20 @@ impl Ncmfile {
|
||||
self.fullfilename.yellow(),
|
||||
"解密成功".bright_green()
|
||||
);
|
||||
let _ = messager.send(Signals::End);
|
||||
Ok(())
|
||||
}
|
||||
fn save(&mut self, path: &PathBuf, data: Vec<u8>)->Result<(),NcmError> {
|
||||
let music_file = match File::create(path){
|
||||
Ok(o)=>o,
|
||||
Err(_)=>return Err(NcmError::FileWriteError)
|
||||
fn save(&mut self, path: &PathBuf, data: Vec<u8>) -> Result<(), NcmError> {
|
||||
let music_file = match File::create(path) {
|
||||
Ok(o) => o,
|
||||
Err(_) => return Err(NcmError::FileWriteError),
|
||||
};
|
||||
let mut writer = BufWriter::new(music_file);
|
||||
let _ = writer.write_all(&data);
|
||||
// 关闭文件
|
||||
match writer.flush(){
|
||||
Ok(o)=>o,
|
||||
Err(_)=>return Err(NcmError::FileWriteError)
|
||||
match writer.flush() {
|
||||
Ok(o) => o,
|
||||
Err(_) => return Err(NcmError::FileWriteError),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@ -544,7 +570,8 @@ pub enum NcmError {
|
||||
FileSkipError,
|
||||
FileWriteError,
|
||||
FullFilenameError,
|
||||
FileNotFoundError,
|
||||
FileNotFound,
|
||||
ProtectFile,
|
||||
}
|
||||
|
||||
impl std::error::Error for NcmError {}
|
||||
@ -560,6 +587,10 @@ impl std::fmt::Display for NcmError {
|
||||
Self::FileReadError => write!(f, "读取文件时发生错误"),
|
||||
Self::FileWriteError => write!(f, "写入文件时错误"),
|
||||
Self::FullFilenameError => write!(f, "文件名不符合规范"),
|
||||
Self::ProtectFile => write!(
|
||||
f,
|
||||
"已关闭文件强制覆盖且文件已存在。使用-f或-forcesave开启强制覆盖。"
|
||||
),
|
||||
_ => write!(f, "未知错误"),
|
||||
}
|
||||
}
|
||||
|
||||
34
src/opendir.rs
Normal file
34
src/opendir.rs
Normal 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
40
src/pathparse.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user