Browse Source

Add Detect Media Type by File

20230320
MengYX 5 years ago
parent
commit
c314a251c2
No known key found for this signature in database GPG Key ID: E63F9C7303E8F604
  1. 1
      src/decrypt/common.js
  2. 23
      src/decrypt/ncm.js
  3. 11
      src/decrypt/qmc.js
  4. 5
      src/decrypt/qmcMask.js
  5. 14
      src/decrypt/raw.js
  6. 2
      src/decrypt/tm.js
  7. 17
      src/decrypt/util.js

1
src/decrypt/common.js

@ -3,7 +3,6 @@ const QmcDecrypt = require("./qmc");
const RawDecrypt = require("./raw"); const RawDecrypt = require("./raw");
const TmDecrypt = require("./tm"); const TmDecrypt = require("./tm");
export async function CommonDecrypt(file) { export async function CommonDecrypt(file) {
let raw_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase(); let raw_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
let raw_filename = file.name.substring(0, file.name.lastIndexOf(".")); let raw_filename = file.name.substring(0, file.name.lastIndexOf("."));

23
src/decrypt/ncm.js

@ -2,7 +2,7 @@ const CryptoJS = require("crypto-js");
const ID3Writer = require("browser-id3-writer"); const ID3Writer = require("browser-id3-writer");
const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857"); const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857");
const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728"); const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728");
import {AudioMimeType, GetArrayBuffer} from "./util" import {AudioMimeType, DetectAudioExt, GetArrayBuffer} from "./util"
export async function Decrypt(file) { export async function Decrypt(file) {
@ -23,27 +23,16 @@ export async function Decrypt(file) {
let audioOffset = musicMetaObj.offset + dataView.getUint32(musicMetaObj.offset + 5, true) + 13; let audioOffset = musicMetaObj.offset + dataView.getUint32(musicMetaObj.offset + 5, true) + 13;
let audioData = new Uint8Array(fileBuffer, audioOffset); let audioData = new Uint8Array(fileBuffer, audioOffset);
for (let cur = 0; cur < audioData.length; ++cur) { for (let cur = 0; cur < audioData.length; ++cur) audioData[cur] ^= keyBox[cur & 0xff];
audioData[cur] ^= keyBox[cur & 0xff];
} if (musicMeta.format === undefined) musicMeta.format = DetectAudioExt(audioData, "mp3");
if (musicMeta.format === undefined) {
const [f, L, a, C] = audioData;
if (f === 0x66 && L === 0x4c && a === 0x61 && C === 0x43) {
musicMeta.format = "flac";
} else {
musicMeta.format = "mp3";
}
}
const mime = AudioMimeType[musicMeta.format]; const mime = AudioMimeType[musicMeta.format];
const artists = []; const artists = [];
musicMeta.artist.forEach(arr => { musicMeta.artist.forEach(arr => artists.push(arr[0]));
artists.push(arr[0]); if (musicMeta.format === "mp3")
});
if (musicMeta.format === "mp3") {
audioData = await writeID3(audioData, artists, musicMeta.musicName, musicMeta.album, musicMeta.albumPic) audioData = await writeID3(audioData, artists, musicMeta.musicName, musicMeta.album, musicMeta.albumPic)
}
const musicData = new Blob([audioData], {type: mime}); const musicData = new Blob([audioData], {type: mime});
return { return {

11
src/decrypt/qmc.js

@ -1,4 +1,4 @@
import {AudioMimeType, GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util"; import {AudioMimeType, DetectAudioExt, GetArrayBuffer, GetCoverURL, GetFileInfo} from "./util";
import {QmcMaskCreate58, QmcMaskGetDefault, QmcMaskDetectMgg, QmcMaskDetectMflac} from "./qmcMask"; import {QmcMaskCreate58, QmcMaskGetDefault, QmcMaskDetectMgg, QmcMaskDetectMflac} from "./qmcMask";
const musicMetadata = require("music-metadata-browser"); const musicMetadata = require("music-metadata-browser");
@ -15,7 +15,6 @@ const HandlerMap = {
"tkm": {handler: QmcMaskGetDefault, ext: "m4a", detect: false} "tkm": {handler: QmcMaskGetDefault, ext: "m4a", detect: false}
}; };
//todo: use header to detect media type
export async function Decrypt(file, raw_filename, raw_ext) { export async function Decrypt(file, raw_filename, raw_ext) {
if (!(raw_ext in HandlerMap)) return {status: false, message: "File type is incorrect!"}; if (!(raw_ext in HandlerMap)) return {status: false, message: "File type is incorrect!"};
const handler = HandlerMap[raw_ext]; const handler = HandlerMap[raw_ext];
@ -27,14 +26,16 @@ export async function Decrypt(file, raw_filename, raw_ext) {
seed = handler.handler(audioData); seed = handler.handler(audioData);
keyData = fileData.slice(-0x170); keyData = fileData.slice(-0x170);
if (seed === undefined) seed = await queryKeyInfo(keyData, raw_filename, raw_ext); if (seed === undefined) seed = await queryKeyInfo(keyData, raw_filename, raw_ext);
if (seed === undefined) return {status: false, message: "格式仅提供实验性支持!"}; if (seed === undefined) return {status: false, message: raw_ext + "格式仅提供实验性支持!"};
} else { } else {
audioData = fileData; audioData = fileData;
seed = handler.handler(audioData); seed = handler.handler(audioData);
} }
const dec = seed.Decrypt(audioData); const dec = seed.Decrypt(audioData);
const mime = AudioMimeType[handler.ext]; const ext = DetectAudioExt(dec, handler.ext);
const mime = AudioMimeType[ext];
const musicData = new Blob([dec], {type: mime}); const musicData = new Blob([dec], {type: mime});
const tag = await musicMetadata.parseBlob(musicData); const tag = await musicMetadata.parseBlob(musicData);
@ -45,7 +46,7 @@ export async function Decrypt(file, raw_filename, raw_ext) {
status: true, status: true,
title: info.title, title: info.title,
artist: info.artist, artist: info.artist,
ext: handler.ext, ext: ext,
album: tag.common.album, album: tag.common.album,
picture: GetCoverURL(tag), picture: GetCoverURL(tag),
file: URL.createObjectURL(musicData), file: URL.createObjectURL(musicData),

5
src/decrypt/qmcMask.js

@ -110,10 +110,7 @@ export function QmcMaskDetectMflac(data) {
for (let block_idx = 0; block_idx < search_len; block_idx += 128) { for (let block_idx = 0; block_idx < search_len; block_idx += 128) {
try { try {
mask = new QmcMask(data.slice(block_idx, block_idx + 128)); mask = new QmcMask(data.slice(block_idx, block_idx + 128));
if (!mask.Decrypt(data.slice(0, FLAC_HEADER.length)) if (!IsBytesEqual(FLAC_HEADER, mask.Decrypt(data.slice(0, FLAC_HEADER.length)))) break;
.every((val, idx) => {
return val === FLAC_HEADER[idx];
})) break;
} catch (e) { } catch (e) {
} }
} }

14
src/decrypt/raw.js

@ -1,17 +1,23 @@
const musicMetadata = require("music-metadata-browser"); const musicMetadata = require("music-metadata-browser");
import {GetCoverURL, GetFileInfo, AudioMimeType} from "./util"; import {GetCoverURL, GetFileInfo, AudioMimeType, DetectAudioExt, GetArrayBuffer} from "./util";
export async function Decrypt(file, raw_filename, raw_ext) { export async function Decrypt(file, raw_filename, raw_ext, detect = true) {
let ext = raw_ext;
if (detect) {
const buffer = new Uint8Array(await GetArrayBuffer(file));
ext = DetectAudioExt(buffer, raw_ext);
if (ext !== raw_ext) file = new Blob([buffer], {type: AudioMimeType[ext]})
}
const tag = await musicMetadata.parseBlob(file); const tag = await musicMetadata.parseBlob(file);
const info = GetFileInfo(tag.common.artist, tag.common.title, raw_filename); const info = GetFileInfo(tag.common.artist, tag.common.title, raw_filename);
return { return {
status: true, status: true,
title: info.title, title: info.title,
artist: info.artist, artist: info.artist,
ext: raw_ext, ext: ext,
album: tag.common.album, album: tag.common.album,
picture: GetCoverURL(tag), picture: GetCoverURL(tag),
file: URL.createObjectURL(file), file: URL.createObjectURL(file),
mime: AudioMimeType[raw_ext] mime: AudioMimeType[ext]
} }
} }

2
src/decrypt/tm.js

@ -10,5 +10,5 @@ export async function Decrypt(file, raw_filename) {
audioData[cur] = TM_HEADER[cur]; audioData[cur] = TM_HEADER[cur];
} }
const musicData = new Blob([audioData], {type: "audio/mp4"}); const musicData = new Blob([audioData], {type: "audio/mp4"});
return await RawDecrypt(musicData, raw_filename, "m4a") return await RawDecrypt(musicData, raw_filename, "m4a", false)
} }

17
src/decrypt/util.js

@ -1,4 +1,7 @@
export const FLAC_HEADER = [0x66, 0x4C, 0x61, 0x43, 0x00]; export const FLAC_HEADER = [0x66, 0x4C, 0x61, 0x43];
export const MP3_HEADER = [0x49, 0x44, 0x33];
export const OGG_HEADER = [0x4F, 0x67, 0x67, 0x53];
export const M4A_HEADER = [0x66, 0x74, 0x79, 0x70];
export const AudioMimeType = { export const AudioMimeType = {
mp3: "audio/mpeg", mp3: "audio/mpeg",
flac: "audio/flac", flac: "audio/flac",
@ -45,7 +48,19 @@ export function GetCoverURL(metadata) {
} }
export function IsBytesEqual(first, second) { export function IsBytesEqual(first, second) {
// if want wholly check, should length first>=second
return first.every((val, idx) => { return first.every((val, idx) => {
return val === second[idx]; return val === second[idx];
}) })
} }
/**
* @return {string}
*/
export function DetectAudioExt(data, fallbackExt) {
if (IsBytesEqual(MP3_HEADER, data.slice(0, MP3_HEADER.length))) return "mp3";
if (IsBytesEqual(FLAC_HEADER, data.slice(0, FLAC_HEADER.length))) return "flac";
if (IsBytesEqual(OGG_HEADER, data.slice(0, OGG_HEADER.length))) return "ogg";
if (IsBytesEqual(M4A_HEADER, data.slice(4, 8))) return "m4a";
return fallbackExt;
}

Loading…
Cancel
Save