diff --git a/src/decrypt/common.js b/src/decrypt/common.js index b8f1bb4..c059263 100644 --- a/src/decrypt/common.js +++ b/src/decrypt/common.js @@ -1,5 +1,6 @@ const NcmDecrypt = require("./ncm"); const KwmDecrypt = require("./kwm"); +const XmDecrypt = require("./xm"); const QmcDecrypt = require("./qmc"); const RawDecrypt = require("./raw"); const TmDecrypt = require("./tm"); @@ -12,12 +13,16 @@ export async function CommonDecrypt(file) { case "ncm":// Netease Mp3/Flac rt_data = await NcmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); break; - case "kwm"://Kuwo Mp3/Flac + case "kwm":// Kuwo Mp3/Flac rt_data = await KwmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); break - case "mp3":// Raw Mp3 - case "flac"://Raw Flac - case "m4a":// Raw M4a + case "xm": // Xiami Wav/M4a/Mp3/Flac + case "wav":// Xiami/Raw Wav + case "mp3":// Xiami/Raw Mp3 + case "flac":// Xiami/Raw Flac + case "m4a":// Xiami/Raw M4a + rt_data = await XmDecrypt.Decrypt(file.raw, raw_filename, raw_ext); + break; case "ogg":// Raw Ogg rt_data = await RawDecrypt.Decrypt(file.raw, raw_filename, raw_ext); break; diff --git a/src/decrypt/util.js b/src/decrypt/util.js index 12fb426..df3aaeb 100644 --- a/src/decrypt/util.js +++ b/src/decrypt/util.js @@ -7,11 +7,14 @@ export const WMA_HEADER = [ 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C, ] +export const WAV_HEADER = [0x52, 0x49, 0x46, 0x46] export const AudioMimeType = { mp3: "audio/mpeg", flac: "audio/flac", m4a: "audio/mp4", - ogg: "audio/ogg" + ogg: "audio/ogg", + wma: "audio/x-ms-wma", + wav: "audio/x-wav" }; // Also a new draft API: blob.arrayBuffer() @@ -25,9 +28,9 @@ export async function GetArrayBuffer(blobObject) { }); } -export function GetFileInfo(artist, title, filenameNoExt) { +export function GetFileInfo(artist, title, filenameNoExt, separator = "-") { let newArtist = "", newTitle = ""; - let filenameArray = filenameNoExt.split("-"); + let filenameArray = filenameNoExt.split(separator); if (filenameArray.length > 1) { newArtist = filenameArray[0].trim(); newTitle = filenameArray[1].trim(); @@ -68,6 +71,7 @@ export function DetectAudioExt(data, fallbackExt) { if (IsBytesEqual(OGG_HEADER, data.slice(0, OGG_HEADER.length))) return "ogg"; if (IsBytesEqual(M4A_HEADER, data.slice(4, 4 + M4A_HEADER.length))) return "m4a"; if (IsBytesEqual(WMA_HEADER, data.slice(0, WMA_HEADER.length))) return "wma"; + if (IsBytesEqual(WAV_HEADER, data.slice(0, WAV_HEADER.length))) return "wav"; return fallbackExt; } diff --git a/src/decrypt/xm.js b/src/decrypt/xm.js new file mode 100644 index 0000000..de27b21 --- /dev/null +++ b/src/decrypt/xm.js @@ -0,0 +1,72 @@ +import { + AudioMimeType, + DetectAudioExt, + GetArrayBuffer, + GetFileInfo, + GetMetaCoverURL, + IsBytesEqual +} from "./util"; + +import {Decrypt as RawDecrypt} from "./raw"; + +const musicMetadata = require("music-metadata-browser"); +const MagicHeader = [0x69, 0x66, 0x6D, 0x74] +const MagicHeader2 = [0xfe, 0xfe, 0xfe, 0xfe] +const FileTypeMap = { + " WAV": ".wav", + "FLAC": ".flac", + " MP3": ".mp3", + " A4M": ".m4a", +} + +export async function Decrypt(file, raw_filename, raw_ext) { + const oriData = new Uint8Array(await GetArrayBuffer(file)); + if (!IsBytesEqual(MagicHeader, oriData.slice(0, 4)) || + !IsBytesEqual(MagicHeader2, oriData.slice(8, 12))) { + if (raw_ext === "xm") { + return {status: false, message: "Not a valid xm file!"} + } else { + return await RawDecrypt(file, raw_filename, raw_ext, true) + } + } + + let typeText = (new TextDecoder()).decode(oriData.slice(4, 8)) + if (!FileTypeMap.hasOwnProperty(typeText)) { + return {status: false, message: "New Xiami file category!"} + } + + let key = oriData[0xf] + let dataOffset = oriData[0xc] | oriData[0xd] << 8 + let audioData = oriData.slice(0x10); + let lenAudioData = audioData.length; + for (let cur = dataOffset; cur < lenAudioData; ++cur) + audioData[cur] = (audioData[cur] - key) ^ 0xff; + + const ext = DetectAudioExt(audioData, "mp3"); + const mime = AudioMimeType[ext]; + let musicBlob = new Blob([audioData], {type: mime}); + + const musicMeta = await musicMetadata.parseBlob(musicBlob); + if (ext === "wav") { + //todo:未知的编码方式 + musicMeta.common.album = ""; + musicMeta.common.artist = ""; + musicMeta.common.title = ""; + } + + const info = GetFileInfo(musicMeta.common.artist, musicMeta.common.title, raw_filename, "_"); + + const imgUrl = GetMetaCoverURL(musicMeta); + + return { + status: true, + title: info.title, + artist: info.artist, + ext: ext, + album: musicMeta.common.album, + picture: imgUrl, + file: URL.createObjectURL(musicBlob), + mime: mime + } +} +