Compare commits
No commits in common. "f3b9075a820fe2754a2132e0c324e3ad0e8a75cb" and "3cdb14fc96f2e5cea7fd5f981dab4a12e4566447" have entirely different histories.
f3b9075a82
...
3cdb14fc96
@ -9,7 +9,6 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/um_cli/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_cli/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/utils/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/utils/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_crypto/ncm/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/um_crypto/ncm/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/um_audio/src" isTestSource="false" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -427,14 +427,6 @@ version = "1.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "um_audio"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "um_cli"
|
name = "um_cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -454,7 +446,6 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"um_audio",
|
|
||||||
"umc_kuwo",
|
"umc_kuwo",
|
||||||
"umc_ncm",
|
"umc_ncm",
|
||||||
"umc_qmc",
|
"umc_qmc",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["um_audio", "um_crypto/*", "um_wasm", "um_cli"]
|
members = ["um_crypto/*", "um_wasm", "um_cli"]
|
||||||
|
|
||||||
[profile.release.package.um_wasm]
|
[profile.release.package.um_wasm]
|
||||||
# Tell `rustc` to optimize for small code size.
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "um_audio"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
thiserror = "1.0.63"
|
|
@ -1,131 +0,0 @@
|
|||||||
mod metadata;
|
|
||||||
|
|
||||||
use std::fmt::Display;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone)]
|
|
||||||
pub enum AudioError {
|
|
||||||
#[error("Require at least {0} bytes of header.")]
|
|
||||||
NeedMoreHeader(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MASK_LOSSLESS: u32 = 0x80000000;
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum AudioType {
|
|
||||||
Unknown = 0,
|
|
||||||
|
|
||||||
// Lossy
|
|
||||||
OGG = 1,
|
|
||||||
AAC = 2,
|
|
||||||
MP3 = 3,
|
|
||||||
M4A = 4,
|
|
||||||
M4B = 5,
|
|
||||||
MP4 = 6,
|
|
||||||
WMA = 7, // While possible, it is rare to find a lossless WMA file.
|
|
||||||
|
|
||||||
// Lossless
|
|
||||||
FLAC = MASK_LOSSLESS | 1,
|
|
||||||
DFF = MASK_LOSSLESS | 2,
|
|
||||||
WAV = MASK_LOSSLESS | 3,
|
|
||||||
APE = MASK_LOSSLESS | 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AudioType {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
AudioType::OGG => "ogg",
|
|
||||||
AudioType::AAC => "aac",
|
|
||||||
AudioType::MP3 => "mp3",
|
|
||||||
AudioType::M4A => "m4a",
|
|
||||||
AudioType::M4B => "m4b",
|
|
||||||
AudioType::MP4 => "mp4",
|
|
||||||
AudioType::WMA => "wma",
|
|
||||||
AudioType::FLAC => "flac",
|
|
||||||
AudioType::DFF => "dff",
|
|
||||||
AudioType::WAV => "wav",
|
|
||||||
AudioType::APE => "ape",
|
|
||||||
|
|
||||||
_ => "bin",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for AudioType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_mp3(magic: u32) -> bool {
|
|
||||||
// Frame sync should have the first 11 bits set to 1.
|
|
||||||
const MP3_AND_MASK: u32 = 0b1111_1111_1110_0000u32 << 16;
|
|
||||||
const MP3_EXPECTED: u32 = 0b1111_1111_1110_0000u32 << 16;
|
|
||||||
|
|
||||||
(magic & MP3_AND_MASK) == MP3_EXPECTED
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_aac(magic: u32) -> bool {
|
|
||||||
// Frame sync should have the first 12 bits set to 1.
|
|
||||||
const AAC_AND_MASK: u32 = 0b1111_1111_1111_0110u32 << 16;
|
|
||||||
const AAC_EXPECTED: u32 = 0b1111_1111_1111_0000u32 << 16;
|
|
||||||
|
|
||||||
(magic & AAC_AND_MASK) == AAC_EXPECTED
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAGIC_FLAC: [u8; 4] = *b"fLaC";
|
|
||||||
const MAGIC_OGG: [u8; 4] = *b"OggS";
|
|
||||||
const MAGIC_DFF: [u8; 4] = *b"FRM8";
|
|
||||||
const MAGIC_WMA: [u8; 4] = *b"\x30\x26\xB2\x75";
|
|
||||||
const MAGIC_WAV: [u8; 4] = *b"RIFF";
|
|
||||||
const MAGIC_APE: [u8; 4] = *b"MAC ";
|
|
||||||
|
|
||||||
pub fn detect_audio_type(buffer: &[u8]) -> Result<AudioType, AudioError> {
|
|
||||||
let offset = metadata::get_header_metadata_size(buffer, 0)?;
|
|
||||||
if buffer.len() < offset + 0x10 {
|
|
||||||
Err(AudioError::NeedMoreHeader(offset + 0x10))?;
|
|
||||||
}
|
|
||||||
let buffer = &buffer[offset..];
|
|
||||||
let mut magic = [0u8; 4];
|
|
||||||
magic.copy_from_slice(&buffer[..4]);
|
|
||||||
match magic {
|
|
||||||
MAGIC_FLAC => return Ok(AudioType::FLAC),
|
|
||||||
MAGIC_OGG => return Ok(AudioType::OGG),
|
|
||||||
MAGIC_DFF => return Ok(AudioType::DFF),
|
|
||||||
MAGIC_WMA => return Ok(AudioType::WMA),
|
|
||||||
MAGIC_WAV => return Ok(AudioType::WAV),
|
|
||||||
MAGIC_APE => return Ok(AudioType::APE),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let magic = u32::from_be_bytes(magic);
|
|
||||||
if is_aac(magic) {
|
|
||||||
return Ok(AudioType::AAC);
|
|
||||||
} else if is_mp3(magic) {
|
|
||||||
return Ok(AudioType::MP3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MP4 Containers
|
|
||||||
if &buffer[0x04..0x08] == b"ftyp" {
|
|
||||||
let mut magic = [0u8; 4];
|
|
||||||
magic.copy_from_slice(&buffer[0x08..0x0c]);
|
|
||||||
match &magic {
|
|
||||||
// MSNV: SonyPSP
|
|
||||||
// isom / iso2: MP4 (Generic?)
|
|
||||||
b"isom" | b"iso2" | b"MSNV" => return Ok(AudioType::MP4),
|
|
||||||
b"NDAS" => return Ok(AudioType::M4A), // Nero Digital AAC Audio
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut magic = [0u8; 3];
|
|
||||||
magic.copy_from_slice(&buffer[0x08..0x0b]);
|
|
||||||
match &magic {
|
|
||||||
b"M4A" => return Ok(AudioType::M4A), // iTunes AAC-LC Audio
|
|
||||||
b"M4B" => return Ok(AudioType::M4B), // iTunes AAC-LC Audio
|
|
||||||
b"mp4" => return Ok(AudioType::MP4), // QQMusic
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(AudioType::Unknown)
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
use crate::AudioError;
|
|
||||||
use byteorder::{ByteOrder, BE, LE};
|
|
||||||
|
|
||||||
fn parse_id3_sync_safe_int(buffer: &[u8]) -> i32 {
|
|
||||||
const UNSAFE_INT_MASK_32: u32 = 0x80808080;
|
|
||||||
const U32_BYTE_MASK_1: u32 = 0xFF000000;
|
|
||||||
const U32_BYTE_MASK_2: u32 = 0x00FF0000;
|
|
||||||
const U32_BYTE_MASK_3: u32 = 0x0000FF00;
|
|
||||||
const U32_BYTE_MASK_4: u32 = 0x000000FF;
|
|
||||||
|
|
||||||
let value = BE::read_u32(buffer);
|
|
||||||
|
|
||||||
// Sync safe int should use only lower 7-bits of each byte.
|
|
||||||
if (value & UNSAFE_INT_MASK_32) != 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = (value & U32_BYTE_MASK_1) >> 3
|
|
||||||
| (value & U32_BYTE_MASK_2) >> 2
|
|
||||||
| (value & U32_BYTE_MASK_3) >> 1
|
|
||||||
| (value & U32_BYTE_MASK_4);
|
|
||||||
|
|
||||||
value as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIN_ID3_HEADER_LEN: usize = 10;
|
|
||||||
|
|
||||||
fn get_id3_header_size(buffer: &[u8], offset: usize) -> Result<usize, AudioError> {
|
|
||||||
if buffer.len() < MIN_ID3_HEADER_LEN {
|
|
||||||
Err(AudioError::NeedMoreHeader(offset + MIN_ID3_HEADER_LEN))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TAG: ID3v1, 128 bytes
|
|
||||||
if buffer.starts_with(b"TAG") {
|
|
||||||
return Ok(128);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID3: ID3v2
|
|
||||||
if buffer.starts_with(b"ID3") {
|
|
||||||
// offset value
|
|
||||||
// 0 header('ID3')
|
|
||||||
// 3 uint8_t(ver_major) uint8_t(ver_minor)
|
|
||||||
// 5 uint8_t(flags)
|
|
||||||
// 6 uint32_t(inner_tag_size)
|
|
||||||
// 10 byte[inner_tag_size] id3v2 data
|
|
||||||
// ?? byte[*] original_file_content
|
|
||||||
let inner_size = parse_id3_sync_safe_int(&buffer[6..10]) as usize;
|
|
||||||
return Ok(10 + inner_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIN_APE_V2_HEADER_LEN: usize = 32;
|
|
||||||
fn get_ape_v2_size(buffer: &[u8], offset: usize) -> Result<usize, AudioError> {
|
|
||||||
if buffer.len() < MIN_APE_V2_HEADER_LEN {
|
|
||||||
Err(AudioError::NeedMoreHeader(offset + MIN_APE_V2_HEADER_LEN))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.starts_with(b"APETAGEX") {
|
|
||||||
let extra_size = LE::read_u32(&buffer[0x0c..0x10]) as usize;
|
|
||||||
return Ok(MIN_APE_V2_HEADER_LEN + extra_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_header_metadata_size(buffer: &[u8], offset: usize) -> Result<usize, AudioError> {
|
|
||||||
let len = get_id3_header_size(buffer, offset)?;
|
|
||||||
if len == 0 {
|
|
||||||
Ok(len)
|
|
||||||
} else {
|
|
||||||
get_ape_v2_size(buffer, offset)
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ impl NCMFile {
|
|||||||
{
|
{
|
||||||
let header = header.as_ref();
|
let header = header.as_ref();
|
||||||
if header.len() < 14 {
|
if header.len() < 14 {
|
||||||
Err(Error::HeaderTooSmall(14))?;
|
Err(Error::HeaderTooSmall(14 - header.len()))?;
|
||||||
}
|
}
|
||||||
if !header.starts_with(b"CTENFDAM") {
|
if !header.starts_with(b"CTENFDAM") {
|
||||||
Err(Error::NotNCMFile)?;
|
Err(Error::NotNCMFile)?;
|
||||||
|
@ -26,7 +26,6 @@ console_error_panic_hook = { version = "0.1.7", optional = true }
|
|||||||
umc_kuwo = { path = "../um_crypto/kuwo" }
|
umc_kuwo = { path = "../um_crypto/kuwo" }
|
||||||
umc_ncm = { path = "../um_crypto/ncm" }
|
umc_ncm = { path = "../um_crypto/ncm" }
|
||||||
umc_qmc = { path = "../um_crypto/qmc" }
|
umc_qmc = { path = "../um_crypto/qmc" }
|
||||||
um_audio = { path = "../um_audio" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.34"
|
wasm-bindgen-test = "0.3.34"
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
use um_audio::{AudioError, AudioType};
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
|
||||||
use wasm_bindgen::JsError;
|
|
||||||
|
|
||||||
/// Detected audio result
|
|
||||||
#[wasm_bindgen(js_name=AudioTypeResult)]
|
|
||||||
pub struct AudioTypeResult {
|
|
||||||
/// When this field is not zero, it means we need to feed this amount of bytes to the detector.
|
|
||||||
#[wasm_bindgen(js_name=needMore)]
|
|
||||||
pub need_more: usize,
|
|
||||||
|
|
||||||
/// Audio extension, without "."
|
|
||||||
/// When is unknown, it will return "bin".
|
|
||||||
#[wasm_bindgen(getter_with_clone,js_name=audioType)]
|
|
||||||
pub audio_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Detect audio type for given file header.
|
|
||||||
/// Recommended a buffer of 1024 bytes.
|
|
||||||
#[wasm_bindgen(js_name=detectAudioType)]
|
|
||||||
pub fn detect_audio_type(buffer: &[u8]) -> Result<AudioTypeResult, JsError> {
|
|
||||||
let (need_more, audio_type) = match um_audio::detect_audio_type(buffer) {
|
|
||||||
Ok(t) => (0, t),
|
|
||||||
Err(AudioError::NeedMoreHeader(n)) => (n, AudioType::Unknown),
|
|
||||||
// Err(err) => Err(JsError::new(err.into()))?,
|
|
||||||
};
|
|
||||||
Ok(AudioTypeResult {
|
|
||||||
need_more,
|
|
||||||
audio_type: audio_type.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
pub mod audio;
|
|
||||||
pub mod kuwo;
|
pub mod kuwo;
|
||||||
pub mod ncm;
|
pub mod ncm;
|
||||||
pub mod qmc;
|
pub mod qmc;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@unlock-music/crypto",
|
"name": "@unlock-music/crypto",
|
||||||
"version": "0.0.0-alpha.7",
|
"version": "0.0.0-alpha.6",
|
||||||
"description": "Project Unlock Music: 加解密支持库",
|
"description": "Project Unlock Music: 加解密支持库",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.js",
|
"build": "node build.js",
|
||||||
|
Loading…
Reference in New Issue
Block a user