mirror of
				https://git.unlock-music.dev/um/web.git
				synced 2025-11-04 15:33:28 +08:00 
			
		
		
		
	Reconstruct
This commit is contained in:
		
							parent
							
								
									bc138c4078
								
							
						
					
					
						commit
						bddde78fcd
					
				
							
								
								
									
										37
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								src/App.vue
									
									
									
									
									
								
							@ -85,10 +85,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const NcmDecrypt = require("./plugins/ncm");
 | 
					    const dec = require("./decrypt/common");
 | 
				
			||||||
    const QmcDecrypt = require("./plugins/qmc");
 | 
					 | 
				
			||||||
    const RawDecrypt = require("./plugins/raw");
 | 
					 | 
				
			||||||
    const MFlacDecrypt = require("./plugins/mflac");
 | 
					 | 
				
			||||||
    export default {
 | 
					    export default {
 | 
				
			||||||
        name: 'app',
 | 
					        name: 'app',
 | 
				
			||||||
        components: {},
 | 
					        components: {},
 | 
				
			||||||
@ -119,33 +116,9 @@
 | 
				
			|||||||
                });
 | 
					                });
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            handleFile(file) {
 | 
					            handleFile(file) {
 | 
				
			||||||
                let ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
 | 
					
 | 
				
			||||||
                (async () => {
 | 
					                (async () => {
 | 
				
			||||||
                    let data = null;
 | 
					                    let data =await dec.CommonDecrypt(file);
 | 
				
			||||||
                    switch (ext) {
 | 
					 | 
				
			||||||
                        case "ncm":
 | 
					 | 
				
			||||||
                            data = await NcmDecrypt.Decrypt(file.raw);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        case "mp3":
 | 
					 | 
				
			||||||
                        case "flac":
 | 
					 | 
				
			||||||
                            data = await RawDecrypt.Decrypt(file.raw);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        case "qmc3":
 | 
					 | 
				
			||||||
                        case "qmc0":
 | 
					 | 
				
			||||||
                        case "qmcflac":
 | 
					 | 
				
			||||||
                        case "qmcogg":
 | 
					 | 
				
			||||||
                            data = await QmcDecrypt.Decrypt(file.raw);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        case "mflac":
 | 
					 | 
				
			||||||
                            data = await MFlacDecrypt.Decrypt(file.raw);
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                        default:
 | 
					 | 
				
			||||||
                            data = {
 | 
					 | 
				
			||||||
                                status: false,
 | 
					 | 
				
			||||||
                                message: "不支持此文件格式",
 | 
					 | 
				
			||||||
                            };
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (data.status) {
 | 
					                    if (data.status) {
 | 
				
			||||||
                        this.tableData.push(data);
 | 
					                        this.tableData.push(data);
 | 
				
			||||||
                        this.$notify.success({
 | 
					                        this.$notify.success({
 | 
				
			||||||
@ -154,8 +127,7 @@
 | 
				
			|||||||
                            duration: 3000
 | 
					                            duration: 3000
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                        let _rp_data = [data.title, data.artist, data.album];
 | 
					                        let _rp_data = [data.title, data.artist, data.album];
 | 
				
			||||||
                        console.log(data);
 | 
					                        window._paq.push(["trackEvent", "Unlock", data.rawExt + "," + data.mime, JSON.stringify(_rp_data)]);
 | 
				
			||||||
                        window._paq.push(["trackEvent", "Unlock", ext + "," + data.mime, JSON.stringify(_rp_data)]);
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        this.$notify.error({
 | 
					                        this.$notify.error({
 | 
				
			||||||
                            title: '出现问题',
 | 
					                            title: '出现问题',
 | 
				
			||||||
@ -173,7 +145,6 @@
 | 
				
			|||||||
                this.playing_auto = true;
 | 
					                this.playing_auto = true;
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            handleDelete(index, row) {
 | 
					            handleDelete(index, row) {
 | 
				
			||||||
                console.log(index);
 | 
					 | 
				
			||||||
                URL.revokeObjectURL(row.file);
 | 
					                URL.revokeObjectURL(row.file);
 | 
				
			||||||
                URL.revokeObjectURL(row.picture);
 | 
					                URL.revokeObjectURL(row.picture);
 | 
				
			||||||
                this.tableData.splice(index, 1);
 | 
					                this.tableData.splice(index, 1);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										44
									
								
								src/decrypt/common.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/decrypt/common.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					const NcmDecrypt = require("./ncm");
 | 
				
			||||||
 | 
					const QmcDecrypt = require("./qmc");
 | 
				
			||||||
 | 
					const RawDecrypt = require("./raw");
 | 
				
			||||||
 | 
					const MFlacDecrypt = require("./mflac");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {CommonDecrypt}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function CommonDecrypt(file) {
 | 
				
			||||||
 | 
					    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 rt_data;
 | 
				
			||||||
 | 
					    switch (raw_ext) {
 | 
				
			||||||
 | 
					        case "ncm":// Netease Mp3/Flac
 | 
				
			||||||
 | 
					            rt_data = await NcmDecrypt.Decrypt(file.raw);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "mp3":// Raw Mp3
 | 
				
			||||||
 | 
					        case "flac"://Raw Flac
 | 
				
			||||||
 | 
					        case "m4a":// todo: Raw M4A
 | 
				
			||||||
 | 
					        case "tm0":// QQ Music IOS Mp3
 | 
				
			||||||
 | 
					        case "tm3":// QQ Music IOS Mp3
 | 
				
			||||||
 | 
					            rt_data = await RawDecrypt.Decrypt(file.raw, raw_filename, raw_ext);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "qmc3"://QQ Music Android Mp3
 | 
				
			||||||
 | 
					        case "qmc0"://QQ Music Android Mp3
 | 
				
			||||||
 | 
					        case "qmcflac"://QQ Music Android Flac
 | 
				
			||||||
 | 
					        case "qmcogg"://QQ Music Android Ogg
 | 
				
			||||||
 | 
					            rt_data = await QmcDecrypt.Decrypt(file.raw, raw_filename, raw_ext);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "mflac"://QQ Music Desktop Flac
 | 
				
			||||||
 | 
					            rt_data = await MFlacDecrypt.Decrypt(file.raw, raw_filename, raw_ext);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "tm2":// todo: QQ Music IOS M4A
 | 
				
			||||||
 | 
					        case "tm6":// todo: QQ Music IOS M4A
 | 
				
			||||||
 | 
					            debugger;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            rt_data = {status: false, message: "不支持此文件格式",}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (rt_data.status) {
 | 
				
			||||||
 | 
					        rt_data.rawExt = raw_ext;
 | 
				
			||||||
 | 
					        rt_data.rawFilename = raw_filename;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return rt_data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,21 +1,15 @@
 | 
				
			|||||||
const musicMetadata = require("music-metadata-browser");
 | 
					const musicMetadata = require("music-metadata-browser");
 | 
				
			||||||
 | 
					const util = require("./util");
 | 
				
			||||||
export {Decrypt}
 | 
					export {Decrypt}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function Decrypt(file) {
 | 
					async function Decrypt(file, raw_filename, raw_ext) {
 | 
				
			||||||
    // 获取扩展名
 | 
					    // 获取扩展名
 | 
				
			||||||
    let filename_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
 | 
					    if (raw_ext !== "mflac") return {
 | 
				
			||||||
    if (filename_ext !== "mflac") return {
 | 
					 | 
				
			||||||
        status: false,
 | 
					        status: false,
 | 
				
			||||||
        message: "File type is incorrect!",
 | 
					        message: "File type is incorrect!",
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    // 读取文件
 | 
					    // 读取文件
 | 
				
			||||||
    const fileBuffer = await new Promise(resolve => {
 | 
					    const fileBuffer = await util.GetArrayBuffer(file);
 | 
				
			||||||
        const reader = new FileReader();
 | 
					 | 
				
			||||||
        reader.onload = (e) => {
 | 
					 | 
				
			||||||
            resolve(e.target.result);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const audioData = new Uint8Array(fileBuffer.slice(0, -0x170));
 | 
					    const audioData = new Uint8Array(fileBuffer.slice(0, -0x170));
 | 
				
			||||||
    const audioDataLen = audioData.length;
 | 
					    const audioDataLen = audioData.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,35 +28,16 @@ async function Decrypt(file) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // 读取Meta
 | 
					    // 读取Meta
 | 
				
			||||||
    let tag = await musicMetadata.parseBlob(musicData);
 | 
					    let tag = await musicMetadata.parseBlob(musicData);
 | 
				
			||||||
 | 
					    const info = util.GetFileInfo(tag.common.artist, tag.common.title, raw_filename, "flac");
 | 
				
			||||||
    // 处理无标题歌手
 | 
					    let picUrl = util.GetCoverURL(tag);
 | 
				
			||||||
    let filename_array = file.name.substring(0, file.name.lastIndexOf(".")).split("-");
 | 
					    // 返回
 | 
				
			||||||
    let title = tag.common.title;
 | 
					 | 
				
			||||||
    let artist = tag.common.artist;
 | 
					 | 
				
			||||||
    if (filename_array.length > 1) {
 | 
					 | 
				
			||||||
        if (artist === undefined) artist = filename_array[0].trim();
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[1].trim();
 | 
					 | 
				
			||||||
    } else if (filename_array.length === 1) {
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[0].trim();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const filename = artist + " - " + title + ".flac";
 | 
					 | 
				
			||||||
    // 处理无封面
 | 
					 | 
				
			||||||
    let pic_url = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (tag.common.picture !== undefined && tag.common.picture.length >= 1) {
 | 
					 | 
				
			||||||
        const picture = tag.common.picture[0];
 | 
					 | 
				
			||||||
        const blobPic = new Blob([picture.data], {type: picture.format});
 | 
					 | 
				
			||||||
        pic_url = URL.createObjectURL(blobPic);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 返回*/
 | 
					 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        status: true,
 | 
					        status: true,
 | 
				
			||||||
        message: "",
 | 
					        filename: info.filename,
 | 
				
			||||||
        filename: filename,
 | 
					        title: info.title,
 | 
				
			||||||
        title: title,
 | 
					        artist: info.artist,
 | 
				
			||||||
        artist: artist,
 | 
					 | 
				
			||||||
        album: tag.common.album,
 | 
					        album: tag.common.album,
 | 
				
			||||||
        picture: pic_url,
 | 
					        picture: picUrl,
 | 
				
			||||||
        file: musicUrl,
 | 
					        file: musicUrl,
 | 
				
			||||||
        mime: "audio/flac"
 | 
					        mime: "audio/flac"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
							
								
								
									
										171
									
								
								src/decrypt/ncm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/decrypt/ncm.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,171 @@
 | 
				
			|||||||
 | 
					const CryptoJS = require("crypto-js");
 | 
				
			||||||
 | 
					const ID3Writer = require("browser-id3-writer");
 | 
				
			||||||
 | 
					const util = require("./util");
 | 
				
			||||||
 | 
					const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857");
 | 
				
			||||||
 | 
					const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {Decrypt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function Decrypt(file) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fileBuffer = await util.GetArrayBuffer(file);
 | 
				
			||||||
 | 
					    const dataView = new DataView(fileBuffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (dataView.getUint32(0, true) !== 0x4e455443 ||
 | 
				
			||||||
 | 
					        dataView.getUint32(4, true) !== 0x4d414446)
 | 
				
			||||||
 | 
					        return {status: false, message: "此ncm文件已损坏"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const keyDataObj = getKeyData(dataView, fileBuffer, 10);
 | 
				
			||||||
 | 
					    const keyBox = getKeyBox(keyDataObj.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const musicMetaObj = getMetaData(dataView, fileBuffer, keyDataObj.offset);
 | 
				
			||||||
 | 
					    const musicMeta = musicMetaObj.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let audioOffset = musicMetaObj.offset + dataView.getUint32(musicMetaObj.offset + 5, true) + 13;
 | 
				
			||||||
 | 
					    let audioData = new Uint8Array(fileBuffer, audioOffset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let cur = 0; cur < audioData.length; ++cur) {
 | 
				
			||||||
 | 
					        audioData[cur] ^= keyBox[cur & 0xff];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 = util.AudioMimeType[musicMeta.format];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const artists = [];
 | 
				
			||||||
 | 
					    musicMeta.artist.forEach(arr => {
 | 
				
			||||||
 | 
					        artists.push(arr[0]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (musicMeta.format === "mp3") {
 | 
				
			||||||
 | 
					        audioData = await writeID3(audioData, artists, musicMeta.musicName, musicMeta.album, musicMeta.albumPic)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const musicData = new Blob([audioData], {type: mime});
 | 
				
			||||||
 | 
					    const musicUrl = URL.createObjectURL(musicData);
 | 
				
			||||||
 | 
					    const filename = artists.join(" & ") + " - " + musicMeta.musicName + "." + musicMeta.format;
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        status: true,
 | 
				
			||||||
 | 
					        filename: filename,
 | 
				
			||||||
 | 
					        title: musicMeta.musicName,
 | 
				
			||||||
 | 
					        artist: artists.join(" & "),
 | 
				
			||||||
 | 
					        album: musicMeta.album,
 | 
				
			||||||
 | 
					        picture: musicMeta.albumPic,
 | 
				
			||||||
 | 
					        file: musicUrl,
 | 
				
			||||||
 | 
					        mime: mime
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function writeID3(audioData, artistList, title, album, picture) {
 | 
				
			||||||
 | 
					    const writer = new ID3Writer(audioData);
 | 
				
			||||||
 | 
					    writer.setFrame("TPE1", artistList)
 | 
				
			||||||
 | 
					        .setFrame("TIT2", title)
 | 
				
			||||||
 | 
					        .setFrame("TALB", album);
 | 
				
			||||||
 | 
					    if (picture !== "") {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const img = await (await fetch(picture)).arrayBuffer();
 | 
				
			||||||
 | 
					            writer.setFrame('APIC', {
 | 
				
			||||||
 | 
					                type: 3,
 | 
				
			||||||
 | 
					                data: img,
 | 
				
			||||||
 | 
					                description: 'Cover'
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            console.log("Fail to write cover image!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    writer.addTag();
 | 
				
			||||||
 | 
					    return writer.arrayBuffer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getKeyData(dataView, fileBuffer, offset) {
 | 
				
			||||||
 | 
					    const keyLen = dataView.getUint32(offset, true);
 | 
				
			||||||
 | 
					    offset += 4;
 | 
				
			||||||
 | 
					    const cipherText = new Uint8Array(fileBuffer, offset, keyLen).map(
 | 
				
			||||||
 | 
					        uint8 => uint8 ^ 0x64
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    offset += keyLen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const plainText = CryptoJS.AES.decrypt(
 | 
				
			||||||
 | 
					        {ciphertext: CryptoJS.lib.WordArray.create(cipherText)},
 | 
				
			||||||
 | 
					        CORE_KEY,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mode: CryptoJS.mode.ECB,
 | 
				
			||||||
 | 
					            padding: CryptoJS.pad.Pkcs7
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = new Uint8Array(plainText.sigBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const words = plainText.words;
 | 
				
			||||||
 | 
					    const sigBytes = plainText.sigBytes;
 | 
				
			||||||
 | 
					    for (let i = 0; i < sigBytes; i++) {
 | 
				
			||||||
 | 
					        result[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {offset: offset, data: result.slice(17)};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getKeyBox(keyData) {
 | 
				
			||||||
 | 
					    const box = new Uint8Array(Array(256).keys());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const keyDataLen = keyData.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let j = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < 256; i++) {
 | 
				
			||||||
 | 
					        j = (box[i] + j + keyData[i % keyDataLen]) & 0xff;
 | 
				
			||||||
 | 
					        [box[i], box[j]] = [box[j], box[i]];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return box.map((_, i, arr) => {
 | 
				
			||||||
 | 
					        i = (i + 1) & 0xff;
 | 
				
			||||||
 | 
					        const si = arr[i];
 | 
				
			||||||
 | 
					        const sj = arr[(i + si) & 0xff];
 | 
				
			||||||
 | 
					        return arr[(si + sj) & 0xff];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @typedef {Object} MusicMetaType
 | 
				
			||||||
 | 
					 * @property {Number} musicId
 | 
				
			||||||
 | 
					 * @property {String} musicName
 | 
				
			||||||
 | 
					 * @property {[[String, Number]]} artist
 | 
				
			||||||
 | 
					 * @property {String} album
 | 
				
			||||||
 | 
					 * @property {"flac"|"mp3"} format
 | 
				
			||||||
 | 
					 * @property {String} albumPic
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getMetaData(dataView, fileBuffer, offset) {
 | 
				
			||||||
 | 
					    const metaDataLen = dataView.getUint32(offset, true);
 | 
				
			||||||
 | 
					    offset += 4;
 | 
				
			||||||
 | 
					    if (metaDataLen === 0) {
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cipherText = new Uint8Array(fileBuffer, offset, metaDataLen).map(
 | 
				
			||||||
 | 
					        data => data ^ 0x63
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    offset += metaDataLen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const plainText = CryptoJS.AES.decrypt({
 | 
				
			||||||
 | 
					            ciphertext: CryptoJS.enc.Base64.parse(
 | 
				
			||||||
 | 
					                CryptoJS.lib.WordArray.create(cipherText.slice(22)).toString(CryptoJS.enc.Utf8)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        META_KEY,
 | 
				
			||||||
 | 
					        {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = JSON.parse(plainText.toString(CryptoJS.enc.Utf8).slice(6));
 | 
				
			||||||
 | 
					    result.albumPic = result.albumPic.replace("http:", "https:");
 | 
				
			||||||
 | 
					    return {data: result, offset: offset};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
const musicMetadata = require("music-metadata-browser");
 | 
					const musicMetadata = require("music-metadata-browser");
 | 
				
			||||||
 | 
					const util = require("./util");
 | 
				
			||||||
export {Decrypt}
 | 
					export {Decrypt}
 | 
				
			||||||
const SEED_MAP = [
 | 
					const SEED_MAP = [
 | 
				
			||||||
    [0x4a, 0xd6, 0xca, 0x90, 0x67, 0xf7, 0x52],
 | 
					    [0x4a, 0xd6, 0xca, 0x90, 0x67, 0xf7, 0x52],
 | 
				
			||||||
@ -9,17 +10,12 @@ const SEED_MAP = [
 | 
				
			|||||||
    [0x1d, 0x95, 0xde, 0x9f, 0x84, 0x11, 0xf4],
 | 
					    [0x1d, 0x95, 0xde, 0x9f, 0x84, 0x11, 0xf4],
 | 
				
			||||||
    [0x0e, 0x74, 0xbb, 0x90, 0xbc, 0x3f, 0x92],
 | 
					    [0x0e, 0x74, 0xbb, 0x90, 0xbc, 0x3f, 0x92],
 | 
				
			||||||
    [0x00, 0x09, 0x5b, 0x9f, 0x62, 0x66, 0xa1]];
 | 
					    [0x00, 0x09, 0x5b, 0x9f, 0x62, 0x66, 0xa1]];
 | 
				
			||||||
const audio_mime_type = {
 | 
					 | 
				
			||||||
    mp3: "audio/mpeg",
 | 
					 | 
				
			||||||
    flac: "audio/flac",
 | 
					 | 
				
			||||||
    ogg: "audio/ogg"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function Decrypt(file) {
 | 
					
 | 
				
			||||||
 | 
					async function Decrypt(file, raw_filename, raw_ext) {
 | 
				
			||||||
    // 获取扩展名
 | 
					    // 获取扩展名
 | 
				
			||||||
    let filename_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
 | 
					 | 
				
			||||||
    let new_ext;
 | 
					    let new_ext;
 | 
				
			||||||
    switch (filename_ext) {
 | 
					    switch (raw_ext) {
 | 
				
			||||||
        case "qmc0":
 | 
					        case "qmc0":
 | 
				
			||||||
        case "qmc3":
 | 
					        case "qmc3":
 | 
				
			||||||
            new_ext = "mp3";
 | 
					            new_ext = "mp3";
 | 
				
			||||||
@ -31,62 +27,33 @@ async function Decrypt(file) {
 | 
				
			|||||||
            new_ext = "flac";
 | 
					            new_ext = "flac";
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        default:
 | 
					        default:
 | 
				
			||||||
            return {
 | 
					            return {status: false, message: "File type is incorrect!"}
 | 
				
			||||||
                status: false,
 | 
					 | 
				
			||||||
                message: "File type is incorrect!",
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const mime = audio_mime_type[new_ext];
 | 
					    const mime = util.AudioMimeType[new_ext];
 | 
				
			||||||
    // 读取文件
 | 
					    // 读取文件
 | 
				
			||||||
    const fileBuffer = await new Promise(resolve => {
 | 
					    const fileBuffer = await util.GetArrayBuffer(file);
 | 
				
			||||||
        const reader = new FileReader();
 | 
					 | 
				
			||||||
        reader.onload = (e) => {
 | 
					 | 
				
			||||||
            resolve(e.target.result);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const audioData = new Uint8Array(fileBuffer);
 | 
					    const audioData = new Uint8Array(fileBuffer);
 | 
				
			||||||
    const audioDataLen = audioData.length;
 | 
					 | 
				
			||||||
    // 转换数据
 | 
					    // 转换数据
 | 
				
			||||||
    const seed = new Mask();
 | 
					    const seed = new Mask();
 | 
				
			||||||
    for (let cur = 0; cur < audioDataLen; ++cur) {
 | 
					    for (let cur = 0; cur < audioData.length; ++cur) {
 | 
				
			||||||
        audioData[cur] ^= seed.NextMask();
 | 
					        audioData[cur] ^= seed.NextMask();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // 导出
 | 
					    // 导出
 | 
				
			||||||
    const musicData = new Blob([audioData], {
 | 
					    const musicData = new Blob([audioData], {type: mime});
 | 
				
			||||||
        type: mime
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const musicUrl = URL.createObjectURL(musicData);
 | 
					    const musicUrl = URL.createObjectURL(musicData);
 | 
				
			||||||
    // 读取Meta
 | 
					    // 读取Meta
 | 
				
			||||||
    let tag = await musicMetadata.parseBlob(musicData);
 | 
					    let tag = await musicMetadata.parseBlob(musicData);
 | 
				
			||||||
 | 
					    const info = util.GetFileInfo(tag.common.artist, tag.common.title, raw_filename, raw_ext);
 | 
				
			||||||
 | 
					    let picUrl = util.GetCoverURL(tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 处理无标题歌手
 | 
					 | 
				
			||||||
    let filename_array = file.name.substring(0, file.name.lastIndexOf(".")).split("-");
 | 
					 | 
				
			||||||
    let title = tag.common.title;
 | 
					 | 
				
			||||||
    let artist = tag.common.artist;
 | 
					 | 
				
			||||||
    if (filename_array.length > 1) {
 | 
					 | 
				
			||||||
        if (artist === undefined) artist = filename_array[0].trim();
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[1].trim();
 | 
					 | 
				
			||||||
    } else if (filename_array.length === 1) {
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[0].trim();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const filename = artist + " - " + title + "." + new_ext;
 | 
					 | 
				
			||||||
    // 处理无封面
 | 
					 | 
				
			||||||
    let pic_url = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (tag.common.picture !== undefined && tag.common.picture.length >= 1) {
 | 
					 | 
				
			||||||
        const picture = tag.common.picture[0];
 | 
					 | 
				
			||||||
        const blobPic = new Blob([picture.data], {type: picture.format});
 | 
					 | 
				
			||||||
        pic_url = URL.createObjectURL(blobPic);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // 返回
 | 
					    // 返回
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        status: true,
 | 
					        status: true,
 | 
				
			||||||
        filename: filename,
 | 
					        filename: info.filename,
 | 
				
			||||||
        title: title,
 | 
					        title: info.title,
 | 
				
			||||||
        artist: artist,
 | 
					        artist: info.artist,
 | 
				
			||||||
        album: tag.common.album,
 | 
					        album: tag.common.album,
 | 
				
			||||||
        picture: pic_url,
 | 
					        picture: picUrl,
 | 
				
			||||||
        file: musicUrl,
 | 
					        file: musicUrl,
 | 
				
			||||||
        mime: mime
 | 
					        mime: mime
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/decrypt/raw.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/decrypt/raw.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					const musicMetadata = require("music-metadata-browser");
 | 
				
			||||||
 | 
					const util = require("./util");
 | 
				
			||||||
 | 
					export {Decrypt}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function Decrypt(file, raw_filename, raw_ext) {
 | 
				
			||||||
 | 
					    let tag = await musicMetadata.parseBlob(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let fileUrl = URL.createObjectURL(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const picUrl = util.GetCoverURL(tag);
 | 
				
			||||||
 | 
					    const mime = util.AudioMimeType[raw_ext];
 | 
				
			||||||
 | 
					    const info = util.GetFileInfo(tag.common.artist, tag.common.title, raw_filename, raw_ext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        status: true,
 | 
				
			||||||
 | 
					        filename: info.filename,
 | 
				
			||||||
 | 
					        title: info.title,
 | 
				
			||||||
 | 
					        artist: info.artist,
 | 
				
			||||||
 | 
					        album: tag.common.album,
 | 
				
			||||||
 | 
					        picture: picUrl,
 | 
				
			||||||
 | 
					        file: fileUrl,
 | 
				
			||||||
 | 
					        mime: mime
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/decrypt/util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/decrypt/util.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					export {GetArrayBuffer, GetFileInfo, GetCoverURL, AudioMimeType}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Also a new draft API: blob.arrayBuffer()
 | 
				
			||||||
 | 
					async function GetArrayBuffer(blobObject) {
 | 
				
			||||||
 | 
					    return await new Promise(resolve => {
 | 
				
			||||||
 | 
					        const reader = new FileReader();
 | 
				
			||||||
 | 
					        reader.onload = (e) => {
 | 
				
			||||||
 | 
					            resolve(e.target.result);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        reader.readAsArrayBuffer(blobObject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AudioMimeType = {
 | 
				
			||||||
 | 
					    mp3: "audio/mpeg",
 | 
				
			||||||
 | 
					    flac: "audio/flac",
 | 
				
			||||||
 | 
					    m4a: "audio/mp4",
 | 
				
			||||||
 | 
					    ogg: "audio/ogg"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function GetFileInfo(artist, title, filenameNoExt, ext) {
 | 
				
			||||||
 | 
					    let newArtist = "", newTitle = "";
 | 
				
			||||||
 | 
					    let filenameArray = filenameNoExt.split("-");
 | 
				
			||||||
 | 
					    if (filenameArray.length > 1) {
 | 
				
			||||||
 | 
					        newArtist = filenameArray[0].trim();
 | 
				
			||||||
 | 
					        newTitle = filenameArray[1].trim();
 | 
				
			||||||
 | 
					    } else if (filenameArray.length === 1) {
 | 
				
			||||||
 | 
					        newTitle = filenameArray[0].trim();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof artist == "string" && artist !== "") {
 | 
				
			||||||
 | 
					        newArtist = artist;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof title == "string" && title !== "") {
 | 
				
			||||||
 | 
					        newTitle = title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let newFilename = newArtist + " - " + newTitle + "." + ext;
 | 
				
			||||||
 | 
					    return {artist: newArtist, title: newTitle, filename: newFilename};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @return {string}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function GetCoverURL(metadata) {
 | 
				
			||||||
 | 
					    let pic_url = "";
 | 
				
			||||||
 | 
					    if (metadata.common.picture !== undefined && metadata.common.picture.length > 0) {
 | 
				
			||||||
 | 
					        let pic = new Blob([metadata.common.picture[0].data], {type: metadata.common.picture[0].format});
 | 
				
			||||||
 | 
					        pic_url = URL.createObjectURL(pic);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return pic_url;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,183 +0,0 @@
 | 
				
			|||||||
const CryptoJS = require("crypto-js");
 | 
					 | 
				
			||||||
const ID3Writer = require("browser-id3-writer");
 | 
					 | 
				
			||||||
const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857");
 | 
					 | 
				
			||||||
const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const audio_mime_type = {
 | 
					 | 
				
			||||||
    mp3: "audio/mpeg",
 | 
					 | 
				
			||||||
    flac: "audio/flac"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export {Decrypt};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function Decrypt(file) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const fileBuffer = await new Promise(reslove => {
 | 
					 | 
				
			||||||
        const reader = new FileReader();
 | 
					 | 
				
			||||||
        reader.onload = (e) => {
 | 
					 | 
				
			||||||
            reslove(e.target.result);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dataView = new DataView(fileBuffer);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (dataView.getUint32(0, true) !== 0x4e455443 ||
 | 
					 | 
				
			||||||
        dataView.getUint32(4, true) !== 0x4d414446
 | 
					 | 
				
			||||||
    ) return {
 | 
					 | 
				
			||||||
        status: false,
 | 
					 | 
				
			||||||
        message: "此ncm文件已损坏",
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let offset = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const keyData = (() => {
 | 
					 | 
				
			||||||
        const keyLen = dataView.getUint32(offset, true);
 | 
					 | 
				
			||||||
        offset += 4;
 | 
					 | 
				
			||||||
        const cipherText = new Uint8Array(fileBuffer, offset, keyLen).map(
 | 
					 | 
				
			||||||
            uint8 => uint8 ^ 0x64
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        offset += keyLen;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const plainText = CryptoJS.AES.decrypt(
 | 
					 | 
				
			||||||
            {ciphertext: CryptoJS.lib.WordArray.create(cipherText)},
 | 
					 | 
				
			||||||
            CORE_KEY,
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                mode: CryptoJS.mode.ECB,
 | 
					 | 
				
			||||||
                padding: CryptoJS.pad.Pkcs7
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const result = new Uint8Array(plainText.sigBytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const words = plainText.words;
 | 
					 | 
				
			||||||
        const sigBytes = plainText.sigBytes;
 | 
					 | 
				
			||||||
        for (let i = 0; i < sigBytes; i++) {
 | 
					 | 
				
			||||||
            result[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return result.slice(17);
 | 
					 | 
				
			||||||
    })();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const keyBox = (() => {
 | 
					 | 
				
			||||||
        const box = new Uint8Array(Array(256).keys());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const keyDataLen = keyData.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let j = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (let i = 0; i < 256; i++) {
 | 
					 | 
				
			||||||
            j = (box[i] + j + keyData[i % keyDataLen]) & 0xff;
 | 
					 | 
				
			||||||
            [box[i], box[j]] = [box[j], box[i]];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return box.map((_, i, arr) => {
 | 
					 | 
				
			||||||
            i = (i + 1) & 0xff;
 | 
					 | 
				
			||||||
            const si = arr[i];
 | 
					 | 
				
			||||||
            const sj = arr[(i + si) & 0xff];
 | 
					 | 
				
			||||||
            return arr[(si + sj) & 0xff];
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    })();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @typedef {Object} MusicMetaType
 | 
					 | 
				
			||||||
     * @property {Number} musicId
 | 
					 | 
				
			||||||
     * @property {String} musicName
 | 
					 | 
				
			||||||
     * @property {[[String, Number]]} artist
 | 
					 | 
				
			||||||
     * @property {String} album
 | 
					 | 
				
			||||||
     * @property {"flac"|"mp3"} format
 | 
					 | 
				
			||||||
     * @property {String} albumPic
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** @type {MusicMetaType|undefined} */
 | 
					 | 
				
			||||||
    const musicMeta = (() => {
 | 
					 | 
				
			||||||
        const metaDataLen = dataView.getUint32(offset, true);
 | 
					 | 
				
			||||||
        offset += 4;
 | 
					 | 
				
			||||||
        if (metaDataLen === 0) {
 | 
					 | 
				
			||||||
            return {};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const cipherText = new Uint8Array(fileBuffer, offset, metaDataLen).map(
 | 
					 | 
				
			||||||
            data => data ^ 0x63
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        offset += metaDataLen;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const plainText = CryptoJS.AES.decrypt(
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ciphertext: CryptoJS.enc.Base64.parse(
 | 
					 | 
				
			||||||
                    CryptoJS.lib.WordArray.create(cipherText.slice(22)).toString(CryptoJS.enc.Utf8)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            META_KEY,
 | 
					 | 
				
			||||||
            {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const result = JSON.parse(plainText.toString(CryptoJS.enc.Utf8).slice(6));
 | 
					 | 
				
			||||||
        result.albumPic = result.albumPic.replace("http:", "https:");
 | 
					 | 
				
			||||||
        return result;
 | 
					 | 
				
			||||||
    })();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    offset += dataView.getUint32(offset + 5, true) + 13;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let audioData = new Uint8Array(fileBuffer, offset);
 | 
					 | 
				
			||||||
    let audioDataLen = audioData.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (let cur = 0; cur < audioDataLen; ++cur) {
 | 
					 | 
				
			||||||
        audioData[cur] ^= keyBox[cur & 0xff];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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 = audio_mime_type[musicMeta.format];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const artists = [];
 | 
					 | 
				
			||||||
    musicMeta.artist.forEach(arr => {
 | 
					 | 
				
			||||||
        artists.push(arr[0]);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (musicMeta.format === "mp3") {
 | 
					 | 
				
			||||||
        const writer = new ID3Writer(audioData);
 | 
					 | 
				
			||||||
        writer.setFrame("TPE1", artists)
 | 
					 | 
				
			||||||
            .setFrame("TIT2", musicMeta.musicName)
 | 
					 | 
				
			||||||
            .setFrame("TALB", musicMeta.album);
 | 
					 | 
				
			||||||
        if (musicMeta.albumPic !== "") {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                const img = await (await fetch(musicMeta.albumPic)).arrayBuffer();
 | 
					 | 
				
			||||||
                writer.setFrame('APIC', {
 | 
					 | 
				
			||||||
                    type: 3,
 | 
					 | 
				
			||||||
                    data: img,
 | 
					 | 
				
			||||||
                    description: 'Cover'
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            } catch (e) {
 | 
					 | 
				
			||||||
                console.log("Fail to write cover image!");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        writer.addTag();
 | 
					 | 
				
			||||||
        audioData = writer.arrayBuffer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const musicData = new Blob([audioData], {
 | 
					 | 
				
			||||||
        type: mime
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const musicUrl = URL.createObjectURL(musicData);
 | 
					 | 
				
			||||||
    const filename = artists.join(" & ") + " - " + musicMeta.musicName + "." + musicMeta.format;
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        status: true,
 | 
					 | 
				
			||||||
        filename: filename,
 | 
					 | 
				
			||||||
        title: musicMeta.musicName,
 | 
					 | 
				
			||||||
        artist: artists.join(" & "),
 | 
					 | 
				
			||||||
        album: musicMeta.album,
 | 
					 | 
				
			||||||
        picture: musicMeta.albumPic,
 | 
					 | 
				
			||||||
        file: musicUrl,
 | 
					 | 
				
			||||||
        mime: mime
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -1,44 +0,0 @@
 | 
				
			|||||||
const musicMetadata = require("music-metadata-browser");
 | 
					 | 
				
			||||||
export {Decrypt}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const audio_mime_type = {
 | 
					 | 
				
			||||||
    mp3: "audio/mpeg",
 | 
					 | 
				
			||||||
    flac: "audio/flac"
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function Decrypt(file) {
 | 
					 | 
				
			||||||
    let tag = await musicMetadata.parseBlob(file);
 | 
					 | 
				
			||||||
    let pic_url = "";
 | 
					 | 
				
			||||||
    if (tag.common.picture !== undefined && tag.common.picture.length > 0) {
 | 
					 | 
				
			||||||
        let pic = new Blob([tag.common.picture[0].data], {type: tag.common.picture[0].format});
 | 
					 | 
				
			||||||
        pic_url = URL.createObjectURL(pic);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let file_url = URL.createObjectURL(file);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let filename_no_ext = file.name.substring(0, file.name.lastIndexOf("."));
 | 
					 | 
				
			||||||
    let filename_array = filename_no_ext.split("-");
 | 
					 | 
				
			||||||
    let filename_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
 | 
					 | 
				
			||||||
    const mime = audio_mime_type[filename_ext];
 | 
					 | 
				
			||||||
    let title = tag.common.title;
 | 
					 | 
				
			||||||
    let artist = tag.common.artist;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (filename_array.length > 1) {
 | 
					 | 
				
			||||||
        if (artist === undefined) artist = filename_array[0].trim();
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[1].trim();
 | 
					 | 
				
			||||||
    } else if (filename_array.length === 1) {
 | 
					 | 
				
			||||||
        if (title === undefined) title = filename_array[0].trim();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const filename = artist + " - " + title + "." + filename_ext;
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        status:true,
 | 
					 | 
				
			||||||
        filename: filename,
 | 
					 | 
				
			||||||
        title: title,
 | 
					 | 
				
			||||||
        artist: artist,
 | 
					 | 
				
			||||||
        album: tag.common.album,
 | 
					 | 
				
			||||||
        picture: pic_url,
 | 
					 | 
				
			||||||
        file: file_url,
 | 
					 | 
				
			||||||
        mime: mime
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user