WIP: Vue 3 迁移 #15
10
.drone.yml
10
.drone.yml
|
@ -20,7 +20,7 @@ steps:
|
|||
- mv *.zip sha256sum.txt upload/
|
||||
|
||||
- name: upload
|
||||
image: "plugins/s3"
|
||||
image: 'plugins/s3'
|
||||
settings:
|
||||
path_style: true
|
||||
endpoint:
|
||||
|
@ -31,7 +31,7 @@ steps:
|
|||
from_secret: S3_SECRET_KEY
|
||||
bucket:
|
||||
from_secret: S3_BUCKET
|
||||
region: "auto"
|
||||
source: "upload/*"
|
||||
strip_prefix: "upload/"
|
||||
target: "${DRONE_REPO}/${DRONE_BUILD_NUMBER}/"
|
||||
region: 'auto'
|
||||
source: 'upload/*'
|
||||
strip_prefix: 'upload/'
|
||||
target: '${DRONE_REPO}/${DRONE_BUILD_NUMBER}/'
|
||||
|
|
|
@ -6,7 +6,6 @@ cache:
|
|||
stages:
|
||||
- build
|
||||
|
||||
|
||||
build-job:
|
||||
stage: build
|
||||
script: |
|
||||
|
@ -37,7 +36,7 @@ build-job:
|
|||
sha256sum *.tar.gz *.zip > sha256sum.txt
|
||||
|
||||
artifacts:
|
||||
name: "$CI_JOB_NAME"
|
||||
name: '$CI_JOB_NAME'
|
||||
paths:
|
||||
- legacy.zip
|
||||
- legacy.tar.gz
|
||||
|
|
|
@ -4,36 +4,31 @@ about: 报告Bug以帮助改进程序
|
|||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
* 请按照此模板填写,否则可能立即被关闭
|
||||
- 请按照此模板填写,否则可能立即被关闭
|
||||
|
||||
- [x] 我确认已经搜索过Issue不存并确认相同的Issue
|
||||
- [x] 我有证据表明这是程序导致的问题(如不确认,可以在[Discussions](https://github.com/ix64/unlock-music/discussions)内提出)
|
||||
* [x] 我确认已经搜索过 Issue 不存并确认相同的 Issue
|
||||
* [x] 我有证据表明这是程序导致的问题(如不确认,可以在[Discussions](https://github.com/ix64/unlock-music/discussions)内提出)
|
||||
|
||||
**Bug 描述**
|
||||
|
||||
**Bug描述**
|
||||
|
||||
简要地复述你遇到的Bug
|
||||
简要地复述你遇到的 Bug
|
||||
|
||||
**复现方法**
|
||||
|
||||
描述复现方法,必要时请提供样本文件
|
||||
|
||||
**程序截图或者Console报错信息**
|
||||
**程序截图或者 Console 报错信息**
|
||||
|
||||
如果可以请提供二者之一
|
||||
|
||||
|
||||
**环境信息:**
|
||||
|
||||
- 操作系统和浏览器:
|
||||
- 程序版本:
|
||||
- 获取音乐文件所使用的客户端及其版本信息:
|
||||
|
||||
- 操作系统和浏览器:
|
||||
- 程序版本:
|
||||
- 获取音乐文件所使用的客户端及其版本信息:
|
||||
|
||||
**附加信息**
|
||||
|
||||
其他能够帮助确认问题的信息
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ about: 对于程序新的想法或建议
|
|||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- 请按照此模板填写,否则可能立即被关闭
|
||||
|
@ -13,14 +12,11 @@ assignees: ''
|
|||
|
||||
简要说明产生此想法的背景和此想法的具体内容
|
||||
|
||||
|
||||
**实现途径**
|
||||
|
||||
- 如果没有设计方案,请简要描述实现思路
|
||||
- 如果你没有任何的实现思路,请通过[Discussions](https://github.com/ix64/unlock-music/discussions)或者Telegram进行讨论
|
||||
|
||||
- 如果你没有任何的实现思路,请通过[Discussions](https://github.com/ix64/unlock-music/discussions)或者 Telegram 进行讨论
|
||||
|
||||
**附加信息**
|
||||
|
||||
更多你想要表达的内容
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
build/
|
||||
dist/
|
||||
src/QmcWasm/
|
||||
src/KgmWasm/
|
13
README.md
13
README.md
|
@ -44,7 +44,7 @@
|
|||
|
||||
### 自行构建
|
||||
|
||||
#### JS部分
|
||||
#### JS 部分
|
||||
|
||||
- 环境要求
|
||||
- nodejs (v16.x)
|
||||
|
@ -71,14 +71,15 @@
|
|||
npm run make-extension
|
||||
```
|
||||
|
||||
#### WASM部分
|
||||
#### WASM 部分
|
||||
|
||||
- 环境要求
|
||||
|
||||
- Linux
|
||||
- python3
|
||||
|
||||
- 运行此目录下的build-wasm
|
||||
- 运行此目录下的 build-wasm
|
||||
|
||||
```sh
|
||||
./scripts/build-wasm.sh
|
||||
```
|
||||
```sh
|
||||
./scripts/build-wasm.sh
|
||||
```
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
||||
export {};
|
||||
declare global {}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app',
|
||||
'@babel/preset-typescript'
|
||||
presets: ['@vue/app', '@babel/preset-typescript'],
|
||||
plugins: [
|
||||
[
|
||||
'component',
|
||||
{
|
||||
libraryName: 'element-ui',
|
||||
styleLibraryName: 'theme-chalk',
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
["component", {
|
||||
"libraryName": "element-ui",
|
||||
"styleLibraryName": "theme-chalk"
|
||||
}]
|
||||
]
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
import '@vue/runtime-core';
|
||||
|
||||
export {}
|
||||
export {};
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElFooter: typeof import('element-plus/es')['ElFooter']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
IEpUpload: typeof import('~icons/ep/upload')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton'];
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'];
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer'];
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog'];
|
||||
ElFooter: typeof import('element-plus/es')['ElFooter'];
|
||||
ElForm: typeof import('element-plus/es')['ElForm'];
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem'];
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon'];
|
||||
ElImage: typeof import('element-plus/es')['ElImage'];
|
||||
ElInput: typeof import('element-plus/es')['ElInput'];
|
||||
ElMain: typeof import('element-plus/es')['ElMain'];
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress'];
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio'];
|
||||
ElRow: typeof import('element-plus/es')['ElRow'];
|
||||
ElTable: typeof import('element-plus/es')['ElTable'];
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'];
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip'];
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload'];
|
||||
IEpUpload: typeof import('~icons/ep/upload')['default'];
|
||||
}
|
||||
}
|
||||
|
|
33
index.html
33
index.html
|
@ -6,14 +6,8 @@
|
|||
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
|
||||
<meta content="width=device-width,initial-scale=1.0" name="viewport" />
|
||||
<title>音乐解锁</title>
|
||||
<meta
|
||||
content="音乐,解锁,ncm,qmc,mgg,mflac,qq音乐,网易云音乐,加密"
|
||||
name="keywords"
|
||||
/>
|
||||
<meta
|
||||
content="音乐解锁 - 在任何设备上解锁已购的加密音乐!"
|
||||
name="description"
|
||||
/>
|
||||
<meta content="音乐,解锁,ncm,qmc,mgg,mflac,qq音乐,网易云音乐,加密" name="keywords" />
|
||||
<meta content="音乐解锁 - 在任何设备上解锁已购的加密音乐!" name="description" />
|
||||
<script src="./src/ixarea-stats.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -29,10 +23,7 @@
|
|||
</noscript>
|
||||
<h3 id="loader-source">请勿直接运行源代码!</h3>
|
||||
<div id="loader-tips-outdated" hidden>
|
||||
<h2>
|
||||
您可能在使用不受支持的<span style="color: #f00">过时</span
|
||||
>浏览器,这可能导致此应用无法正常工作。
|
||||
</h2>
|
||||
<h2>您可能在使用不受支持的<span style="color: #f00">过时</span>浏览器,这可能导致此应用无法正常工作。</h2>
|
||||
<h3>
|
||||
如果您使用双核浏览器,您可以尝试切换到
|
||||
<span style="color: #f00">“极速模式”</span> 解决此问题。
|
||||
|
@ -41,21 +32,11 @@
|
|||
</div>
|
||||
<h3 id="loader-tips-timeout" hidden>
|
||||
音乐解锁采用了一些新特性!建议使用
|
||||
<a href="https://www.microsoft.com/zh-cn/edge" target="_blank"
|
||||
>Microsoft Edge Chromium</a
|
||||
>
|
||||
<a href="https://www.google.cn/chrome/" target="_blank"
|
||||
>Google Chrome</a
|
||||
>
|
||||
<a href="https://www.firefox.com.cn/" target="_blank"
|
||||
>Mozilla Firefox</a
|
||||
>
|
||||
<a href="https://www.microsoft.com/zh-cn/edge" target="_blank">Microsoft Edge Chromium</a>
|
||||
<a href="https://www.google.cn/chrome/" target="_blank">Google Chrome</a>
|
||||
<a href="https://www.firefox.com.cn/" target="_blank">Mozilla Firefox</a>
|
||||
|
|
||||
<a
|
||||
href="https://github.com/ix64/unlock-music/wiki/使用提示"
|
||||
target="_blank"
|
||||
>使用提示</a
|
||||
>
|
||||
<a href="https://github.com/ix64/unlock-music/wiki/使用提示" target="_blank">使用提示</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const src = __dirname + "/src/extension/"
|
||||
const dst = __dirname + "/dist"
|
||||
fs.readdirSync(src).forEach(file => {
|
||||
let srcPath = path.join(src, file)
|
||||
let dstPath = path.join(dst, file)
|
||||
fs.copyFileSync(srcPath, dstPath)
|
||||
console.log(`Copy: ${srcPath} => ${dstPath}`)
|
||||
})
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const src = __dirname + '/src/extension/';
|
||||
const dst = __dirname + '/dist';
|
||||
fs.readdirSync(src).forEach((file) => {
|
||||
let srcPath = path.join(src, file);
|
||||
let dstPath = path.join(dst, file);
|
||||
fs.copyFileSync(srcPath, dstPath);
|
||||
console.log(`Copy: ${srcPath} => ${dstPath}`);
|
||||
});
|
||||
|
||||
const manifestRaw = fs.readFileSync(__dirname + "/extension-manifest.json", "utf-8")
|
||||
const manifest = JSON.parse(manifestRaw)
|
||||
const manifestRaw = fs.readFileSync(__dirname + '/extension-manifest.json', 'utf-8');
|
||||
const manifest = JSON.parse(manifestRaw);
|
||||
|
||||
const pkgRaw = fs.readFileSync(__dirname + "/package.json", "utf-8")
|
||||
const pkg = JSON.parse(pkgRaw)
|
||||
const pkgRaw = fs.readFileSync(__dirname + '/package.json', 'utf-8');
|
||||
const pkg = JSON.parse(pkgRaw);
|
||||
|
||||
verExt = pkg["version"]
|
||||
if (verExt.startsWith("v")) verExt = verExt.slice(1)
|
||||
if (verExt.includes("-")) verExt = verExt.split("-")[0]
|
||||
manifest["version"] = `${verExt}.${pkg["ext_build"]}`
|
||||
manifest["version_name"] = pkg["version"]
|
||||
verExt = pkg['version'];
|
||||
if (verExt.startsWith('v')) verExt = verExt.slice(1);
|
||||
if (verExt.includes('-')) verExt = verExt.split('-')[0];
|
||||
manifest['version'] = `${verExt}.${pkg['ext_build']}`;
|
||||
manifest['version_name'] = pkg['version'];
|
||||
|
||||
fs.writeFileSync(__dirname + "/dist/manifest.json", JSON.stringify(manifest), "utf-8")
|
||||
console.log("Write: manifest.json")
|
||||
fs.writeFileSync(__dirname + '/dist/manifest.json', JSON.stringify(manifest), 'utf-8');
|
||||
console.log('Write: manifest.json');
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
"url": "https://github.com/ix64/unlock-music"
|
||||
},
|
||||
"private": true,
|
||||
"prettier": {
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
"serve": "vite",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
|
33
src/App.vue
33
src/App.vue
|
@ -5,35 +5,19 @@
|
|||
</el-main>
|
||||
<el-footer id="app-footer">
|
||||
<el-row>
|
||||
<a href="https://github.com/ix64/unlock-music" target="_blank"
|
||||
>音乐解锁</a
|
||||
>({{ version }}) :移除已购音乐的加密保护。
|
||||
<a
|
||||
href="https://github.com/ix64/unlock-music/wiki/使用提示"
|
||||
target="_blank"
|
||||
>使用提示</a
|
||||
>
|
||||
<a href="https://github.com/ix64/unlock-music" target="_blank">音乐解锁</a>({{ version }})
|
||||
:移除已购音乐的加密保护。
|
||||
<a href="https://github.com/ix64/unlock-music/wiki/使用提示" target="_blank">使用提示</a>
|
||||
</el-row>
|
||||
<el-row>
|
||||
目前支持 网易云音乐(ncm), QQ音乐(qmc, mflac, mgg), 酷狗音乐(kgm),
|
||||
虾米音乐(xm), 酷我音乐(.kwm)
|
||||
<a
|
||||
href="https://github.com/ix64/unlock-music/blob/master/README.md"
|
||||
target="_blank"
|
||||
>更多</a
|
||||
>。
|
||||
目前支持 网易云音乐(ncm), QQ音乐(qmc, mflac, mgg), 酷狗音乐(kgm), 虾米音乐(xm), 酷我音乐(.kwm)
|
||||
<a href="https://github.com/ix64/unlock-music/blob/master/README.md" target="_blank">更多</a>。
|
||||
</el-row>
|
||||
<el-row>
|
||||
<!--如果进行二次开发,此行版权信息不得移除且应明显地标注于页面上-->
|
||||
<span
|
||||
>Copyright © 2019 - {{ new Date().getFullYear() }} MengYX</span
|
||||
>
|
||||
<span>Copyright © 2019 - {{ new Date().getFullYear() }} MengYX</span>
|
||||
音乐解锁使用
|
||||
<a
|
||||
href="https://github.com/ix64/unlock-music/blob/master/LICENSE"
|
||||
target="_blank"
|
||||
>MIT许可协议</a
|
||||
>
|
||||
<a href="https://github.com/ix64/unlock-music/blob/master/LICENSE" target="_blank">MIT许可协议</a>
|
||||
开放源代码
|
||||
</el-row>
|
||||
</el-footer>
|
||||
|
@ -78,8 +62,7 @@ export default defineComponent({
|
|||
if (
|
||||
updateInfo &&
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
(updateInfo.HttpsFound ||
|
||||
(updateInfo.Found && window.location.protocol !== 'https:'))
|
||||
(updateInfo.HttpsFound || (updateInfo.Found && window.location.protocol !== 'https:'))
|
||||
) {
|
||||
this.$notify.warning({
|
||||
title: '发现更新',
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
// Polyfill for node.
|
||||
global.Blob = global.Blob || require("node:buffer").Blob;
|
||||
global.Blob = global.Blob || require('node:buffer').Blob;
|
||||
|
|
|
@ -26,20 +26,8 @@ form >>> input {
|
|||
</style>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
@close="cancel()"
|
||||
title="解密设定"
|
||||
v-model="internalShow"
|
||||
class="um-config-dialog"
|
||||
center
|
||||
>
|
||||
<el-form
|
||||
ref="form"
|
||||
:rules="rules"
|
||||
status-icon
|
||||
:model="form"
|
||||
label-width="0"
|
||||
>
|
||||
<el-dialog @close="cancel()" title="解密设定" v-model="internalShow" class="um-config-dialog" center>
|
||||
<el-form ref="form" :rules="rules" status-icon :model="form" label-width="0">
|
||||
<section>
|
||||
<label>
|
||||
<span>
|
||||
|
@ -47,14 +35,7 @@ form >>> input {
|
|||
<Ruby caption="Unique Device Identifier">设备唯一识别码</Ruby>
|
||||
</span>
|
||||
<el-form-item prop="jooxUUID">
|
||||
<el-input
|
||||
type="text"
|
||||
v-model="form.jooxUUID"
|
||||
clearable
|
||||
maxlength="32"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
<el-input type="text" v-model="form.jooxUUID" clearable maxlength="32" show-word-limit> </el-input>
|
||||
</el-form-item>
|
||||
</label>
|
||||
|
||||
|
@ -62,9 +43,7 @@ form >>> input {
|
|||
下载该加密文件的 JOOX 应用所记录的设备唯一识别码。
|
||||
<br />
|
||||
参见:
|
||||
<a
|
||||
href="https://github.com/unlock-music/joox-crypto/wiki/%E8%8E%B7%E5%8F%96%E8%AE%BE%E5%A4%87-UUID"
|
||||
>
|
||||
<a href="https://github.com/unlock-music/joox-crypto/wiki/%E8%8E%B7%E5%8F%96%E8%AE%BE%E5%A4%87-UUID">
|
||||
获取设备 UUID · unlock-music/joox-crypto Wiki</a
|
||||
>。
|
||||
</p>
|
||||
|
@ -72,9 +51,7 @@ form >>> input {
|
|||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" :loading="saving" @click="emitConfirm()">
|
||||
确 定
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="emitConfirm()"> 确 定 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
|
|
@ -26,23 +26,11 @@ form >>> input {
|
|||
</style>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
@close="cancel()"
|
||||
title="音乐标签编辑"
|
||||
v-model="internalShow"
|
||||
class="um-edit-dialog"
|
||||
center
|
||||
>
|
||||
<el-dialog @close="cancel()" title="音乐标签编辑" v-model="internalShow" class="um-edit-dialog" center>
|
||||
<el-form ref="form" status-icon :model="form" label-width="0">
|
||||
<section>
|
||||
<el-image
|
||||
v-show="!editPicture"
|
||||
:src="imgFile.url || picture"
|
||||
style="width: 100px; height: 100px"
|
||||
>
|
||||
<template #error class="image-slot el-image__error">
|
||||
暂无封面
|
||||
</template>
|
||||
<el-image v-show="!editPicture" :src="imgFile.url || picture" style="width: 100px; height: 100px">
|
||||
<template #error class="image-slot el-image__error"> 暂无封面 </template>
|
||||
</el-image>
|
||||
<el-upload
|
||||
v-show="editPicture"
|
||||
|
@ -56,12 +44,8 @@ form >>> input {
|
|||
drag
|
||||
>
|
||||
<el-icon><UploadFilled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将新图片拖到此处,或<em>点击选择</em><br />以替换自动匹配的图片
|
||||
</div>
|
||||
<template #tip class="el-upload__tip">
|
||||
新拖到此处的图片将覆盖原始图片
|
||||
</template>
|
||||
<div class="el-upload__text">将新图片拖到此处,或<em>点击选择</em><br />以替换自动匹配的图片</div>
|
||||
<template #tip class="el-upload__tip"> 新拖到此处的图片将覆盖原始图片 </template>
|
||||
</el-upload>
|
||||
|
||||
<i
|
||||
|
@ -75,26 +59,17 @@ form >>> input {
|
|||
标题:
|
||||
<span v-show="!editTitle">{{ title }}</span>
|
||||
<!-- <el-input v-show="editTitle" v-model="title"></el-input> -->
|
||||
<i
|
||||
:class="{ 'el-icon-edit': !editTitle, 'el-icon-check': editTitle }"
|
||||
@click="editTitle = !editTitle"
|
||||
></i
|
||||
<i :class="{ 'el-icon-edit': !editTitle, 'el-icon-check': editTitle }" @click="editTitle = !editTitle"></i
|
||||
><br />
|
||||
艺术家:
|
||||
<span v-show="!editArtist">{{ artist }}</span>
|
||||
<!-- <el-input v-show="editArtist" v-model="artist"></el-input> -->
|
||||
<i
|
||||
:class="{ 'el-icon-edit': !editArtist, 'el-icon-check': editArtist }"
|
||||
@click="editArtist = !editArtist"
|
||||
></i
|
||||
<i :class="{ 'el-icon-edit': !editArtist, 'el-icon-check': editArtist }" @click="editArtist = !editArtist"></i
|
||||
><br />
|
||||
专辑:
|
||||
<span v-show="!editAlbum">{{ album }}</span>
|
||||
<!-- <el-input v-show="editAlbum" v-model="album"></el-input> -->
|
||||
<i
|
||||
:class="{ 'el-icon-edit': !editAlbum, 'el-icon-check': editAlbum }"
|
||||
@click="editAlbum = !editAlbum"
|
||||
></i
|
||||
<i :class="{ 'el-icon-edit': !editAlbum, 'el-icon-check': editAlbum }" @click="editAlbum = !editAlbum"></i
|
||||
><br />
|
||||
专辑艺术家:
|
||||
<span v-show="!editAlbumartist">{{ albumartist }}</span>
|
||||
|
@ -110,10 +85,7 @@ form >>> input {
|
|||
风格:
|
||||
<span v-show="!editGenre">{{ genre }}</span>
|
||||
<!-- <el-input v-show="editGenre" v-model="genre"></el-input> -->
|
||||
<i
|
||||
:class="{ 'el-icon-edit': !editGenre, 'el-icon-check': editGenre }"
|
||||
@click="editGenre = !editGenre"
|
||||
></i
|
||||
<i :class="{ 'el-icon-edit': !editGenre, 'el-icon-check': editGenre }" @click="editGenre = !editGenre"></i
|
||||
><br />
|
||||
|
||||
<p class="item-desc">
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
<template>
|
||||
<el-upload
|
||||
:auto-upload="false"
|
||||
:on-change="addFile"
|
||||
:show-file-list="false"
|
||||
action=""
|
||||
drag
|
||||
multiple
|
||||
>
|
||||
<el-upload :auto-upload="false" :on-change="addFile" :show-file-list="false" action="" drag multiple>
|
||||
<el-icon size="80"><UploadFilled /></el-icon>
|
||||
<div>将文件拖到此处,或 <em>点击选择</em></div>
|
||||
<template #tip>
|
||||
<div>
|
||||
仅在浏览器内对文件进行解锁,无需消耗流量
|
||||
<el-tooltip effect="dark" placement="top-start">
|
||||
<template #content>
|
||||
算法在源代码中已经提供,所有运算都发生在本地
|
||||
</template>
|
||||
<template #content> 算法在源代码中已经提供,所有运算都发生在本地 </template>
|
||||
<el-icon size="12">
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
|
@ -73,16 +64,9 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
if (
|
||||
window.Worker &&
|
||||
window.location.protocol !== 'file:' &&
|
||||
process.env.NODE_ENV === 'production'
|
||||
) {
|
||||
if (window.Worker && window.location.protocol !== 'file:' && process.env.NODE_ENV === 'production') {
|
||||
console.log('Using Worker Pool');
|
||||
this.queue = Pool(
|
||||
() => spawn(new Worker('@/utils/worker.ts')),
|
||||
navigator.hardwareConcurrency || 1
|
||||
);
|
||||
this.queue = Pool(() => spawn(new Worker('@/utils/worker.ts')), navigator.hardwareConcurrency || 1);
|
||||
this.parallel = true;
|
||||
} else {
|
||||
console.log('Using Queue in Main Thread');
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
<el-table-column label="封面">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.picture" style="width: 100px; height: 100px">
|
||||
<template #error class="image-slot el-image__error">
|
||||
暂无封面
|
||||
</template>
|
||||
<template #error class="image-slot el-image__error"> 暂无封面 </template>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -26,11 +24,7 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
circle
|
||||
type="success"
|
||||
@click="handlePlay(scope.$index, scope.row)"
|
||||
>
|
||||
<el-button circle type="success" @click="handlePlay(scope.$index, scope.row)">
|
||||
<el-icon size="20" style="vertical-align: middle">
|
||||
<VideoPlay />
|
||||
</el-icon>
|
||||
|
@ -43,11 +37,7 @@
|
|||
<el-button circle @click="handleEdit(scope.row)">
|
||||
<el-icon size="20" style="vertical-align: middle"><Edit /></el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
circle
|
||||
type="danger"
|
||||
@click="handleDelete(scope.$index, scope.row)"
|
||||
>
|
||||
<el-button circle type="danger" @click="handleDelete(scope.$index, scope.row)">
|
||||
<el-icon size="20" style="vertical-align: middle">
|
||||
<Delete />
|
||||
</el-icon>
|
||||
|
|
|
@ -63,7 +63,11 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string)
|
|||
const mime = AudioMimeType[ext];
|
||||
let musicBlob = new Blob([musicDecoded], { type: mime });
|
||||
const musicMeta = await metaParseBlob(musicBlob);
|
||||
const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, String(musicMeta.common.artists || musicMeta.common.artist || ""));
|
||||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
musicMeta.common.title,
|
||||
String(musicMeta.common.artists || musicMeta.common.artist || ''),
|
||||
);
|
||||
return {
|
||||
album: musicMeta.common.album,
|
||||
picture: GetCoverFromFile(musicMeta),
|
||||
|
|
|
@ -16,10 +16,7 @@ export interface KGMDecryptionResult {
|
|||
* 如果检测并解密成功,返回解密后的 Uint8Array 数据。
|
||||
* @param {ArrayBuffer} kgmBlob 读入的文件 Blob
|
||||
*/
|
||||
export async function DecryptKgmWasm(
|
||||
kgmBlob: ArrayBuffer,
|
||||
ext: string
|
||||
): Promise<KGMDecryptionResult> {
|
||||
export async function DecryptKgmWasm(kgmBlob: ArrayBuffer, ext: string): Promise<KGMDecryptionResult> {
|
||||
const result: KGMDecryptionResult = {
|
||||
success: false,
|
||||
data: new Uint8Array(),
|
||||
|
|
|
@ -38,7 +38,11 @@ export async function Decrypt(file: File, raw_filename: string, _: string): Prom
|
|||
let musicBlob = new Blob([audioData], { type: mime });
|
||||
|
||||
const musicMeta = await metaParseBlob(musicBlob);
|
||||
const { title, artist } = GetMetaFromFile(raw_filename, musicMeta.common.title, String(musicMeta.common.artists || musicMeta.common.artist || ""));
|
||||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
musicMeta.common.title,
|
||||
String(musicMeta.common.artists || musicMeta.common.artist || ''),
|
||||
);
|
||||
return {
|
||||
album: musicMeta.common.album,
|
||||
picture: GetCoverFromFile(musicMeta),
|
||||
|
|
|
@ -5,11 +5,11 @@ import { DecryptResult } from '@/decrypt/entity';
|
|||
const segmentSize = 0x20;
|
||||
|
||||
function isPrintableAsciiChar(ch: number) {
|
||||
return ch >= 0x20 && ch <= 0x7E;
|
||||
return ch >= 0x20 && ch <= 0x7e;
|
||||
}
|
||||
|
||||
function isUpperHexChar(ch: number) {
|
||||
return (ch >= 0x30 && ch <= 0x39) || (ch >= 0x41 && ch <= 0x46);
|
||||
return (ch >= 0x30 && ch <= 0x39) || (ch >= 0x41 && ch <= 0x46);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,10 +19,10 @@ function isUpperHexChar(ch: number) {
|
|||
* @returns Buffer
|
||||
*/
|
||||
function decryptSegment(data: Uint8Array, key: Uint8Array) {
|
||||
for (let i = 0; i < data.byteLength; i++) {
|
||||
data[i] -= key[i % segmentSize];
|
||||
}
|
||||
return Buffer.from(data);
|
||||
for (let i = 0; i < data.byteLength; i++) {
|
||||
data[i] -= key[i % segmentSize];
|
||||
}
|
||||
return Buffer.from(data);
|
||||
}
|
||||
|
||||
export async function Decrypt(file: File, raw_filename: string): Promise<DecryptResult> {
|
||||
|
@ -35,29 +35,29 @@ export async function Decrypt(file: File, raw_filename: string): Promise<Decrypt
|
|||
const possibleKeys = [];
|
||||
|
||||
for (let i = segmentSize; i < segmentSize * 20; i += segmentSize) {
|
||||
const possibleKey = buf.slice(i, i + segmentSize);
|
||||
if (!possibleKey.every(isUpperHexChar)) continue;
|
||||
const possibleKey = buf.slice(i, i + segmentSize);
|
||||
if (!possibleKey.every(isUpperHexChar)) continue;
|
||||
|
||||
const tempHeader = decryptSegment(header, possibleKey);
|
||||
if (tempHeader.slice(0, 4).compare(bytesRIFF)) continue;
|
||||
if (tempHeader.slice(8, 16).compare(bytesWaveFormat)) continue;
|
||||
const tempHeader = decryptSegment(header, possibleKey);
|
||||
if (tempHeader.slice(0, 4).compare(bytesRIFF)) continue;
|
||||
if (tempHeader.slice(8, 16).compare(bytesWaveFormat)) continue;
|
||||
|
||||
// fmt chunk 大小可以是 16 / 18 / 40。
|
||||
const fmtChunkSize = tempHeader.readUInt32LE(0x10);
|
||||
if (![16, 18, 40].includes(fmtChunkSize)) continue;
|
||||
// fmt chunk 大小可以是 16 / 18 / 40。
|
||||
const fmtChunkSize = tempHeader.readUInt32LE(0x10);
|
||||
if (![16, 18, 40].includes(fmtChunkSize)) continue;
|
||||
|
||||
// 下一个 chunk
|
||||
const firstDataChunkOffset = 0x14 + fmtChunkSize;
|
||||
const chunkName = tempHeader.slice(firstDataChunkOffset, firstDataChunkOffset + 4);
|
||||
if (!chunkName.every(isPrintableAsciiChar)) continue;
|
||||
// 下一个 chunk
|
||||
const firstDataChunkOffset = 0x14 + fmtChunkSize;
|
||||
const chunkName = tempHeader.slice(firstDataChunkOffset, firstDataChunkOffset + 4);
|
||||
if (!chunkName.every(isPrintableAsciiChar)) continue;
|
||||
|
||||
const secondDataChunkOffset = firstDataChunkOffset + 8 + tempHeader.readUInt32LE(firstDataChunkOffset + 4);
|
||||
if (secondDataChunkOffset <= header.byteLength) {
|
||||
const secondChunkName = tempHeader.slice(secondDataChunkOffset, secondDataChunkOffset + 4);
|
||||
if (!secondChunkName.every(isPrintableAsciiChar)) continue;
|
||||
}
|
||||
const secondDataChunkOffset = firstDataChunkOffset + 8 + tempHeader.readUInt32LE(firstDataChunkOffset + 4);
|
||||
if (secondDataChunkOffset <= header.byteLength) {
|
||||
const secondChunkName = tempHeader.slice(secondDataChunkOffset, secondDataChunkOffset + 4);
|
||||
if (!secondChunkName.every(isPrintableAsciiChar)) continue;
|
||||
}
|
||||
|
||||
possibleKeys.push(Buffer.from(possibleKey).toString('ascii'));
|
||||
possibleKeys.push(Buffer.from(possibleKey).toString('ascii'));
|
||||
}
|
||||
|
||||
if (possibleKeys.length <= 0) {
|
||||
|
|
|
@ -26,11 +26,7 @@ const CORE_KEY = EncHex.parse('687a4852416d736f356b496e62617857');
|
|||
const META_KEY = EncHex.parse('2331346C6A6B5F215C5D2630553C2728');
|
||||
const MagicHeader = [0x43, 0x54, 0x45, 0x4e, 0x46, 0x44, 0x41, 0x4d];
|
||||
|
||||
export async function Decrypt(
|
||||
file: File,
|
||||
raw_filename: string,
|
||||
_: string
|
||||
): Promise<DecryptResult> {
|
||||
export async function Decrypt(file: File, raw_filename: string, _: string): Promise<DecryptResult> {
|
||||
return new NcmDecrypt(await GetArrayBuffer(file), raw_filename).decrypt();
|
||||
}
|
||||
|
||||
|
@ -72,16 +68,14 @@ class NcmDecrypt {
|
|||
_getKeyData(): Uint8Array {
|
||||
const keyLen = this.view.getUint32(this.offset, true);
|
||||
this.offset += 4;
|
||||
const cipherText = new Uint8Array(this.raw, this.offset, keyLen).map(
|
||||
(uint8) => uint8 ^ 0x64
|
||||
);
|
||||
const cipherText = new Uint8Array(this.raw, this.offset, keyLen).map((uint8) => uint8 ^ 0x64);
|
||||
this.offset += keyLen;
|
||||
|
||||
const plainText = AES.decrypt(
|
||||
// @ts-ignore
|
||||
{ ciphertext: WordArray.create(cipherText) },
|
||||
CORE_KEY,
|
||||
{ mode: ModeECB, padding: PKCS7 }
|
||||
{ mode: ModeECB, padding: PKCS7 },
|
||||
);
|
||||
|
||||
const result = new Uint8Array(plainText.sigBytes);
|
||||
|
@ -121,9 +115,7 @@ class NcmDecrypt {
|
|||
this.offset += 4;
|
||||
if (metaDataLen === 0) return {};
|
||||
|
||||
const cipherText = new Uint8Array(this.raw, this.offset, metaDataLen).map(
|
||||
(data) => data ^ 0x63
|
||||
);
|
||||
const cipherText = new Uint8Array(this.raw, this.offset, metaDataLen).map((data) => data ^ 0x63);
|
||||
this.offset += metaDataLen;
|
||||
|
||||
WordArray.create();
|
||||
|
@ -132,11 +124,11 @@ class NcmDecrypt {
|
|||
{
|
||||
ciphertext: Base64.parse(
|
||||
// @ts-ignore
|
||||
WordArray.create(cipherText.slice(22)).toString(EncUTF8)
|
||||
WordArray.create(cipherText.slice(22)).toString(EncUTF8),
|
||||
),
|
||||
},
|
||||
META_KEY,
|
||||
{ mode: ModeECB, padding: PKCS7 }
|
||||
{ mode: ModeECB, padding: PKCS7 },
|
||||
).toString(EncUTF8);
|
||||
|
||||
const labelIndex = plainText.indexOf(':');
|
||||
|
@ -148,8 +140,7 @@ class NcmDecrypt {
|
|||
result = JSON.parse(plainText.slice(labelIndex + 1));
|
||||
}
|
||||
if (!!result.albumPic) {
|
||||
result.albumPic =
|
||||
result.albumPic.replace('http://', 'https://') + '?param=500y500';
|
||||
result.albumPic = result.albumPic.replace('http://', 'https://') + '?param=500y500';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -158,8 +149,7 @@ class NcmDecrypt {
|
|||
this.offset += this.view.getUint32(this.offset + 5, true) + 13;
|
||||
const audioData = new Uint8Array(this.raw, this.offset);
|
||||
let lenAudioData = audioData.length;
|
||||
for (let cur = 0; cur < lenAudioData; ++cur)
|
||||
audioData[cur] ^= keyBox[cur & 0xff];
|
||||
for (let cur = 0; cur < lenAudioData; ++cur) audioData[cur] ^= keyBox[cur & 0xff];
|
||||
return audioData;
|
||||
}
|
||||
|
||||
|
@ -207,21 +197,14 @@ class NcmDecrypt {
|
|||
if (!this.blob) this.blob = new Blob([this.audio], { type: this.mime });
|
||||
const ori = await metaParseBlob(this.blob);
|
||||
|
||||
let shouldWrite =
|
||||
!ori.common.album && !ori.common.artists && !ori.common.title;
|
||||
let shouldWrite = !ori.common.album && !ori.common.artists && !ori.common.title;
|
||||
if (shouldWrite || this.newMeta.picture) {
|
||||
if (this.format === 'mp3') {
|
||||
this.audio = WriteMetaToMp3(Buffer.from(this.audio), this.newMeta, ori);
|
||||
} else if (this.format === 'flac') {
|
||||
this.audio = WriteMetaToFlac(
|
||||
Buffer.from(this.audio),
|
||||
this.newMeta,
|
||||
ori
|
||||
);
|
||||
this.audio = WriteMetaToFlac(Buffer.from(this.audio), this.newMeta, ori);
|
||||
} else {
|
||||
console.info(
|
||||
`writing meta for ${this.format} is not being supported for now`
|
||||
);
|
||||
console.info(`writing meta for ${this.format} is not being supported for now`);
|
||||
return;
|
||||
}
|
||||
this.blob = new Blob([this.audio], { type: this.mime });
|
||||
|
|
|
@ -13,7 +13,11 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string)
|
|||
const ext = SniffAudioExt(buffer, raw_ext);
|
||||
if (ext !== raw_ext) file = new Blob([buffer], { type: AudioMimeType[ext] });
|
||||
const tag = await metaParseBlob(file);
|
||||
const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ""));
|
||||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
tag.common.title,
|
||||
String(tag.common.artists || tag.common.artist || ''),
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
QmcMapCipher,
|
||||
QmcRC4Cipher,
|
||||
QmcStaticCipher,
|
||||
QmcStreamCipher,
|
||||
} from './qmc_cipher';
|
||||
import { QmcMapCipher, QmcRC4Cipher, QmcStaticCipher, QmcStreamCipher } from './qmc_cipher';
|
||||
import { AudioMimeType, GetArrayBuffer, SniffAudioExt } from '@/decrypt/utils';
|
||||
|
||||
import { DecryptResult } from '@/decrypt/entity';
|
||||
|
@ -42,11 +37,7 @@ export const HandlerMap: { [key: string]: Handler } = {
|
|||
'776176': { ext: 'wav', version: 1 },
|
||||
};
|
||||
|
||||
export async function Decrypt(
|
||||
file: Blob,
|
||||
raw_filename: string,
|
||||
raw_ext: string
|
||||
): Promise<DecryptResult> {
|
||||
export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
|
||||
if (!(raw_ext in HandlerMap)) throw `Qmc cannot handle type: ${raw_ext}`;
|
||||
const handler = HandlerMap[raw_ext];
|
||||
let { version } = handler;
|
||||
|
@ -65,10 +56,7 @@ export async function Decrypt(
|
|||
musicID = v2Decrypted.songId;
|
||||
console.log('qmc wasm decoder suceeded');
|
||||
} else {
|
||||
console.warn(
|
||||
'QmcWasm failed with error %s',
|
||||
v2Decrypted.error || '(unknown error)'
|
||||
);
|
||||
console.warn('QmcWasm failed with error %s', v2Decrypted.error || '(unknown error)');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +75,7 @@ export async function Decrypt(
|
|||
new Blob([musicDecoded], { type: mime }),
|
||||
raw_filename,
|
||||
ext,
|
||||
musicID
|
||||
musicID,
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -34,11 +34,14 @@ export function simpleMakeKey(salt: number, length: number): number[] {
|
|||
return keyBuf;
|
||||
}
|
||||
|
||||
const mixKey1: Uint8Array = new Uint8Array([ 0x33, 0x38, 0x36, 0x5A, 0x4A, 0x59, 0x21, 0x40, 0x23, 0x2A, 0x24, 0x25, 0x5E, 0x26, 0x29, 0x28 ])
|
||||
const mixKey2: Uint8Array = new Uint8Array([ 0x2A, 0x2A, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5E, 0x61, 0x31, 0x63, 0x5A, 0x2C, 0x54 ])
|
||||
const mixKey1: Uint8Array = new Uint8Array([
|
||||
0x33, 0x38, 0x36, 0x5a, 0x4a, 0x59, 0x21, 0x40, 0x23, 0x2a, 0x24, 0x25, 0x5e, 0x26, 0x29, 0x28,
|
||||
]);
|
||||
const mixKey2: Uint8Array = new Uint8Array([
|
||||
0x2a, 0x2a, 0x23, 0x21, 0x28, 0x23, 0x24, 0x25, 0x26, 0x5e, 0x61, 0x31, 0x63, 0x5a, 0x2c, 0x54,
|
||||
]);
|
||||
|
||||
function decryptV2Key(key: Buffer): Buffer
|
||||
{
|
||||
function decryptV2Key(key: Buffer): Buffer {
|
||||
const textEnc = new TextDecoder();
|
||||
if (key.length < 18 || textEnc.decode(key.slice(0, 18)) !== 'QQMusic EncV2,Key:') {
|
||||
return key;
|
||||
|
|
|
@ -17,10 +17,7 @@ export interface QMCDecryptionResult {
|
|||
* 如果检测并解密成功,返回解密后的 Uint8Array 数据。
|
||||
* @param {ArrayBuffer} qmcBlob 读入的文件 Blob
|
||||
*/
|
||||
export async function DecryptQmcWasm(
|
||||
qmcBlob: ArrayBuffer,
|
||||
ext: string
|
||||
): Promise<QMCDecryptionResult> {
|
||||
export async function DecryptQmcWasm(qmcBlob: ArrayBuffer, ext: string): Promise<QMCDecryptionResult> {
|
||||
const result: QMCDecryptionResult = {
|
||||
success: false,
|
||||
data: new Uint8Array(),
|
||||
|
@ -67,12 +64,7 @@ export async function DecryptQmcWasm(
|
|||
// 解密一些片段
|
||||
const blockData = new Uint8Array(qmcBuf.slice(offset, offset + blockSize));
|
||||
QmcCrypto.writeArrayToMemory(blockData, pQmcBuf);
|
||||
decryptedParts.push(
|
||||
QmcCrypto.HEAPU8.slice(
|
||||
pQmcBuf,
|
||||
pQmcBuf + QmcCrypto.decBlob(pQmcBuf, blockSize, offset)
|
||||
)
|
||||
);
|
||||
decryptedParts.push(QmcCrypto.HEAPU8.slice(pQmcBuf, pQmcBuf + QmcCrypto.decBlob(pQmcBuf, blockSize, offset)));
|
||||
|
||||
offset += blockSize;
|
||||
bytesToDecrypt -= blockSize;
|
||||
|
|
|
@ -52,7 +52,11 @@ export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string)
|
|||
throw '不支持的QQ音乐缓存格式';
|
||||
}
|
||||
const tag = await metaParseBlob(audioBlob);
|
||||
const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ""));
|
||||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
tag.common.title,
|
||||
String(tag.common.artists || tag.common.artist || ''),
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
|
|
|
@ -17,7 +17,11 @@ export async function Decrypt(
|
|||
if (ext !== raw_ext) file = new Blob([buffer], { type: AudioMimeType[ext] });
|
||||
}
|
||||
const tag = await metaParseBlob(file);
|
||||
const { title, artist } = GetMetaFromFile(raw_filename, tag.common.title, String(tag.common.artists || tag.common.artist || ''));
|
||||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
tag.common.title,
|
||||
String(tag.common.artists || tag.common.artist || ''),
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
|
|
|
@ -94,7 +94,8 @@ export function GetMetaFromFile(
|
|||
const items = filename.split(separator);
|
||||
if (items.length > 1) {
|
||||
//由文件名和原metadata共同决定歌手tag(有时从文件名看有多个歌手,而metadata只有一个)
|
||||
if (!meta.artist || meta.artist.split(split_regex).length < items[0].trim().split(split_regex).length) meta.artist = items[0].trim();
|
||||
if (!meta.artist || meta.artist.split(split_regex).length < items[0].trim().split(split_regex).length)
|
||||
meta.artist = items[0].trim();
|
||||
if (!meta.title) meta.title = items[1].trim();
|
||||
} else if (items.length === 1) {
|
||||
if (!meta.title) meta.title = items[0].trim();
|
||||
|
@ -182,11 +183,12 @@ export function RewriteMetaToMp3(audioData: Buffer, info: IMusicMeta, original:
|
|||
// preserve original data
|
||||
const frames = original.native['ID3v2.4'] || original.native['ID3v2.3'] || original.native['ID3v2.2'] || [];
|
||||
frames.forEach((frame) => {
|
||||
if (frame.id !== 'TPE1'
|
||||
&& frame.id !== 'TIT2'
|
||||
&& frame.id !== 'TALB'
|
||||
&& frame.id !== 'TPE2'
|
||||
&& frame.id !== 'TCON'
|
||||
if (
|
||||
frame.id !== 'TPE1' &&
|
||||
frame.id !== 'TIT2' &&
|
||||
frame.id !== 'TALB' &&
|
||||
frame.id !== 'TPE2' &&
|
||||
frame.id !== 'TCON'
|
||||
) {
|
||||
try {
|
||||
writer.setFrame(frame.id, frame.value);
|
||||
|
|
|
@ -49,7 +49,7 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string)
|
|||
const { title, artist } = GetMetaFromFile(
|
||||
raw_filename,
|
||||
musicMeta.common.title,
|
||||
String(musicMeta.common.artists || musicMeta.common.artist || ""),
|
||||
String(musicMeta.common.artists || musicMeta.common.artist || ''),
|
||||
raw_filename.indexOf('_') === -1 ? '-' : '_',
|
||||
);
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<script src="./popup.js"></script>
|
||||
<a href="./index.html" target="_blank">
|
||||
<button>立即使用</button>
|
||||
</a>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="./popup.js"></script>
|
||||
<a href="./index.html" target="_blank">
|
||||
<button>立即使用</button>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
var _paq = window._paq || [];
|
||||
_paq.push(["setRequestMethod", "POST"], ["trackPageView"], ["enableLinkTracking"],
|
||||
["setSiteId", "2"], ["setTrackerUrl", "https://stats.ixarea.com/ixarea-stats/report"]);
|
||||
_paq.push(
|
||||
['setRequestMethod', 'POST'],
|
||||
['trackPageView'],
|
||||
['enableLinkTracking'],
|
||||
['setSiteId', '2'],
|
||||
['setTrackerUrl', 'https://stats.ixarea.com/ixarea-stats/report'],
|
||||
);
|
||||
|
||||
var tag = document.createElement('script');
|
||||
tag.type = 'text/javascript';
|
||||
|
|
|
@ -3,85 +3,83 @@
|
|||
*/
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#app{
|
||||
#app {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
body{
|
||||
body {
|
||||
background-color: $dark-bg;
|
||||
}
|
||||
|
||||
// FORM
|
||||
.el-radio{
|
||||
&__label{
|
||||
.el-radio {
|
||||
&__label {
|
||||
color: $dark-text-main;
|
||||
}
|
||||
&__input{
|
||||
&__input {
|
||||
color: $dark-text-info;
|
||||
.el-radio__inner{
|
||||
.el-radio__inner {
|
||||
border-color: $dark-border;
|
||||
background-color: $dark-btn-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked{
|
||||
.el-radio__inner{
|
||||
&.is-checked {
|
||||
.el-radio__inner {
|
||||
background-color: $blue;
|
||||
}
|
||||
.el-radio__label{
|
||||
.el-radio__label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox.is-bordered{
|
||||
.el-checkbox.is-bordered {
|
||||
border-color: $dark-border;
|
||||
color: $dark-text-main;
|
||||
background-color: $dark-btn-bg;
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__inner {
|
||||
background-color: $dark-btn-bg-highlight;
|
||||
border-color: $dark-border-highlight;
|
||||
}
|
||||
&:hover{
|
||||
&:hover {
|
||||
border-color: $dark-border-highlight;
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__inner {
|
||||
background-color: $dark-btn-bg-highlight;
|
||||
border-color: $dark-border-highlight;
|
||||
}
|
||||
.el-checkbox__label{
|
||||
.el-checkbox__label {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
}
|
||||
&.is-checked{
|
||||
&.is-checked {
|
||||
background-color: $blue;
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__inner {
|
||||
border-color: white;
|
||||
}
|
||||
.el-checkbox__label{
|
||||
.el-checkbox__label {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
&:hover{
|
||||
&:hover {
|
||||
border-color: $blue;
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__inner {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// BUTTON
|
||||
.el-button{
|
||||
.el-button {
|
||||
background-color: $dark-btn-bg;
|
||||
border-color: $dark-border;
|
||||
color: $dark-text-main;
|
||||
|
||||
&:active{
|
||||
&:active {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
&--default{
|
||||
&--default {
|
||||
&.is-plain {
|
||||
background-color: $dark-btn-bg;
|
||||
&:hover {
|
||||
|
@ -101,7 +99,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&--success{
|
||||
&--success {
|
||||
&.is-plain {
|
||||
background-color: $dark-btn-bg;
|
||||
&:hover {
|
||||
|
@ -120,11 +118,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
&--danger{
|
||||
&.is-plain{
|
||||
&--danger {
|
||||
&.is-plain {
|
||||
border-color: $dark-border;
|
||||
background-color: $dark-btn-bg;
|
||||
&:hover{
|
||||
&:hover {
|
||||
background-color: $red;
|
||||
border-color: $red;
|
||||
}
|
||||
|
@ -142,44 +140,45 @@
|
|||
}
|
||||
|
||||
// 文件拖放区
|
||||
.el-upload__tip{
|
||||
.el-upload__tip {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
.el-upload-dragger{
|
||||
.el-upload-dragger {
|
||||
background-color: $dark-uploader-bg;
|
||||
border-color: $dark-border;
|
||||
.el-upload__text{
|
||||
.el-upload__text {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
&:hover{
|
||||
&:hover {
|
||||
background: $dark-uploader-bg-highlight;
|
||||
border-color: $dark-border-highlight;
|
||||
}
|
||||
}
|
||||
|
||||
// TABLE
|
||||
.el-table{
|
||||
.el-table {
|
||||
background-color: $dark-bg-td;
|
||||
&:before{ // 去除表格末尾的横线
|
||||
&:before {
|
||||
// 去除表格末尾的横线
|
||||
content: none;
|
||||
}
|
||||
&__header{
|
||||
th{
|
||||
&__header {
|
||||
th {
|
||||
border-bottom-color: $dark-border !important;
|
||||
}
|
||||
}
|
||||
th.el-table__cell{
|
||||
th.el-table__cell {
|
||||
background-color: $dark-bg-th;
|
||||
color: $dark-text-info;
|
||||
}
|
||||
td{
|
||||
td {
|
||||
border-bottom-color: $dark-border !important;
|
||||
}
|
||||
tr{
|
||||
tr {
|
||||
background-color: $dark-bg-td;
|
||||
color: $dark-text-main;
|
||||
&:hover{
|
||||
td{
|
||||
&:hover {
|
||||
td {
|
||||
background-color: $dark-bg-th !important;
|
||||
}
|
||||
}
|
||||
|
@ -187,68 +186,66 @@
|
|||
}
|
||||
|
||||
// LINKS
|
||||
a{
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: darken($dark-color-link, 15%);
|
||||
&:hover{
|
||||
&:hover {
|
||||
color: $dark-color-link;
|
||||
}
|
||||
}
|
||||
|
||||
// ALERT
|
||||
.el-notification{
|
||||
.el-notification {
|
||||
background-color: $dark-btn-bg-highlight;
|
||||
border-color: $dark-border;
|
||||
&__title{
|
||||
&__title {
|
||||
color: white;
|
||||
}
|
||||
&__content{
|
||||
&__content {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
}
|
||||
|
||||
// DIALOG
|
||||
.el-dialog{
|
||||
.el-dialog {
|
||||
background-color: $dark-dialog-bg;
|
||||
.el-dialog__header{
|
||||
.el-dialog__title{
|
||||
.el-dialog__header {
|
||||
.el-dialog__title {
|
||||
color: $dark-text-main;
|
||||
}
|
||||
}
|
||||
.el-dialog__body{
|
||||
.el-dialog__body {
|
||||
color: $dark-text-main;
|
||||
.el-input{
|
||||
.el-input__inner{
|
||||
.el-input {
|
||||
.el-input__inner {
|
||||
color: $dark-text-main;
|
||||
background-color: $dark-btn-bg;
|
||||
}
|
||||
.el-input__suffix{
|
||||
.el-input__suffix-inner{
|
||||
.el-input__suffix {
|
||||
.el-input__suffix-inner {
|
||||
}
|
||||
}
|
||||
.el-input__count{
|
||||
.el-input__count-inner{
|
||||
.el-input__count {
|
||||
.el-input__count-inner {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.item-desc{
|
||||
.item-desc {
|
||||
color: $dark-text-info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 自定义样式
|
||||
// 首页弹窗提示信息的 更新信息 面板
|
||||
.update-info{
|
||||
.update-info {
|
||||
border: 1px solid $dark-btn-bg !important;
|
||||
.update-title{
|
||||
.update-title {
|
||||
color: $dark-text-main;
|
||||
background-color: $dark-btn-bg !important;
|
||||
}
|
||||
.update-content{
|
||||
.update-content {
|
||||
color: $dark-text-info;
|
||||
padding: 10px;
|
||||
}
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
$color-checkbox: $blue;
|
||||
$color-border-el: #DCDFE6;
|
||||
$color-border-el: #dcdfe6;
|
||||
|
||||
$btn-radius: 6px;
|
||||
|
||||
/* FORM */
|
||||
// checkbox
|
||||
.el-checkbox.is-bordered{
|
||||
@include border-radius($btn-radius) ;
|
||||
&:hover{
|
||||
.el-checkbox.is-bordered {
|
||||
@include border-radius($btn-radius);
|
||||
&:hover {
|
||||
border-color: $color-checkbox;
|
||||
.el-checkbox__label{
|
||||
.el-checkbox__label {
|
||||
color: $color-checkbox;
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-focus{
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__input.is-focus {
|
||||
.el-checkbox__inner {
|
||||
border-color: $color-border-el;
|
||||
}
|
||||
}
|
||||
&.is-checked{
|
||||
&.is-checked {
|
||||
background-color: $color-checkbox;
|
||||
.el-checkbox__label{
|
||||
.el-checkbox__label {
|
||||
color: white;
|
||||
}
|
||||
.el-checkbox__inner{
|
||||
.el-checkbox__inner {
|
||||
border-color: white;
|
||||
background-color: white;
|
||||
&:after{
|
||||
&:after {
|
||||
border-color: $color-checkbox;
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,6 @@ $btn-radius: 6px;
|
|||
}
|
||||
|
||||
// el-button
|
||||
.el-button{
|
||||
@include border-radius($btn-radius) ;
|
||||
.el-button {
|
||||
@include border-radius($btn-radius);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,35 @@
|
|||
|
||||
$gap: 5px;
|
||||
@for $item from 0 through 8 {
|
||||
.mt-#{$item} { margin-top : $gap * $item !important;}
|
||||
.mb-#{$item} { margin-bottom : $gap * $item !important;}
|
||||
.ml-#{$item} { margin-left : $gap * $item !important;}
|
||||
.mr-#{$item} { margin-right : $gap * $item !important;}
|
||||
.m-#{$item} { margin : $gap * $item !important;}
|
||||
.mt-#{$item} {
|
||||
margin-top: $gap * $item !important;
|
||||
}
|
||||
.mb-#{$item} {
|
||||
margin-bottom: $gap * $item !important;
|
||||
}
|
||||
.ml-#{$item} {
|
||||
margin-left: $gap * $item !important;
|
||||
}
|
||||
.mr-#{$item} {
|
||||
margin-right: $gap * $item !important;
|
||||
}
|
||||
.m-#{$item} {
|
||||
margin: $gap * $item !important;
|
||||
}
|
||||
|
||||
.pt-#{$item} { padding-top : $gap * $item !important;}
|
||||
.pb-#{$item} { padding-bottom : $gap * $item !important;}
|
||||
.pl-#{$item} { padding-left : $gap * $item !important;}
|
||||
.pr-#{$item} { padding-right : $gap * $item !important;}
|
||||
.p-#{$item} { padding : $gap * $item !important;}
|
||||
.pt-#{$item} {
|
||||
padding-top: $gap * $item !important;
|
||||
}
|
||||
.pb-#{$item} {
|
||||
padding-bottom: $gap * $item !important;
|
||||
}
|
||||
.pl-#{$item} {
|
||||
padding-left: $gap * $item !important;
|
||||
}
|
||||
.pr-#{$item} {
|
||||
padding-right: $gap * $item !important;
|
||||
}
|
||||
.p-#{$item} {
|
||||
padding: $gap * $item !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
body{
|
||||
body {
|
||||
font-family: $font-family;
|
||||
font-size: $fz-main;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@ -26,13 +26,13 @@ body{
|
|||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
audio{
|
||||
audio {
|
||||
margin-bottom: 15px; // 播放控件与表格间隔
|
||||
}
|
||||
|
||||
a{
|
||||
a {
|
||||
color: darken($color-link, 15%);
|
||||
&:hover{
|
||||
&:hover {
|
||||
color: $color-link;
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
// box-shadow
|
||||
@mixin box-shadow($value...){
|
||||
-webkit-box-shadow: $value;
|
||||
-moz-box-shadow: $value;
|
||||
box-shadow: $value;
|
||||
@mixin box-shadow($value...) {
|
||||
-webkit-box-shadow: $value;
|
||||
-moz-box-shadow: $value;
|
||||
box-shadow: $value;
|
||||
}
|
||||
|
||||
// border-radius
|
||||
@mixin border-radius($corner...){
|
||||
@mixin border-radius($corner...) {
|
||||
-webkit-border-radius: $corner;
|
||||
-moz-border-radius: $corner;
|
||||
border-radius: $corner;
|
||||
}
|
||||
|
||||
@mixin clearfix(){
|
||||
&:after{
|
||||
@mixin clearfix() {
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
clear: both;
|
||||
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin transform($value){
|
||||
@mixin transform($value) {
|
||||
-webkit-transform: $value;
|
||||
-moz-transform: $value;
|
||||
-ms-transform: $value;
|
||||
|
@ -29,7 +29,7 @@
|
|||
transform: $value;
|
||||
}
|
||||
|
||||
@mixin transition($value...){
|
||||
@mixin transition($value...) {
|
||||
-webkit-transition: $value;
|
||||
-moz-transition: $value;
|
||||
-ms-transition: $value;
|
||||
|
@ -37,23 +37,22 @@
|
|||
transition: $value;
|
||||
}
|
||||
|
||||
@mixin animation($value){
|
||||
@mixin animation($value) {
|
||||
animation: $value;
|
||||
-webkit-animation: $value;
|
||||
}
|
||||
|
||||
@mixin linear-gradient($direct, $colors){
|
||||
@mixin linear-gradient($direct, $colors) {
|
||||
background: linear-gradient($direct, $colors);
|
||||
background: -webkit-linear-gradient($direct, $colors);
|
||||
background: -moz-linear-gradient($direct, $colors);
|
||||
}
|
||||
|
||||
@mixin backdrop-filter($value){
|
||||
backdrop-filter: $value ;
|
||||
@mixin backdrop-filter($value) {
|
||||
backdrop-filter: $value;
|
||||
-webkit-backdrop-filter: $value;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Extension
|
||||
*/
|
||||
|
@ -65,8 +64,8 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.btn-like{
|
||||
&:active{
|
||||
@include transform(translateY(2px))
|
||||
.btn-like {
|
||||
&:active {
|
||||
@include transform(translateY(2px));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
// COLORS
|
||||
$blue : #409EFF;
|
||||
$red : #F56C6C;
|
||||
$green : #85ce61;
|
||||
$blue: #409eff;
|
||||
$red: #f56c6c;
|
||||
$green: #85ce61;
|
||||
|
||||
// TEXT
|
||||
$text-main : #2C3E50;
|
||||
$text-main: #2c3e50;
|
||||
$color-link: $blue;
|
||||
|
||||
$fz-main: 14px;
|
||||
$fz-mini-title: 13px;
|
||||
$fz-mini-content: 12px;
|
||||
|
||||
$font-family: "Helvetica Neue", Helvetica, "PingFang SC",
|
||||
"Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
|
||||
$font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial,
|
||||
sans-serif;
|
||||
|
||||
// DARK MODE
|
||||
$dark-border : lighten(black, 25%);
|
||||
$dark-border-highlight : lighten(black, 55%);
|
||||
$dark-bg : lighten(black, 10%);
|
||||
$dark-text-main : lighten(black, 90%);
|
||||
$dark-text-info : lighten(black, 60%);
|
||||
$dark-uploader-bg : lighten(black, 13%);
|
||||
$dark-dialog-bg : lighten(black, 15%);
|
||||
$dark-uploader-bg-highlight : lighten(black, 18%);
|
||||
$dark-btn-bg : lighten(black, 20%);
|
||||
$dark-btn-bg-highlight : lighten(black, 30%);
|
||||
$dark-bg-th : lighten(black, 18%);
|
||||
$dark-bg-td : lighten(black, 13%);
|
||||
$dark-color-link : $green;
|
||||
$dark-border: lighten(black, 25%);
|
||||
$dark-border-highlight: lighten(black, 55%);
|
||||
$dark-bg: lighten(black, 10%);
|
||||
$dark-text-main: lighten(black, 90%);
|
||||
$dark-text-info: lighten(black, 60%);
|
||||
$dark-uploader-bg: lighten(black, 13%);
|
||||
$dark-dialog-bg: lighten(black, 15%);
|
||||
$dark-uploader-bg-highlight: lighten(black, 18%);
|
||||
$dark-btn-bg: lighten(black, 20%);
|
||||
$dark-btn-bg-highlight: lighten(black, 30%);
|
||||
$dark-bg-th: lighten(black, 18%);
|
||||
$dark-bg-td: lighten(black, 13%);
|
||||
$dark-color-link: $green;
|
||||
|
||||
$dark-blue : darken(desaturate($blue, 40%), 30%);
|
||||
$dark-red : darken(desaturate($red, 50%), 30%);
|
||||
$dark-green : darken(desaturate($green, 30%), 30%);
|
||||
$dark-blue: darken(desaturate($blue, 40%), 30%);
|
||||
$dark-red: darken(desaturate($red, 50%), 30%);
|
||||
$dark-green: darken(desaturate($green, 30%), 30%);
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
@import "variables";
|
||||
@import "utility";
|
||||
@import "gaps";
|
||||
@import "element-ui-overrite";
|
||||
|
||||
@import "normal";
|
||||
@import "dark-mode"; // dark-mode 放在 normal 后面,以获得更高优先级
|
||||
@import 'variables';
|
||||
@import 'utility';
|
||||
@import 'gaps';
|
||||
@import 'element-ui-overrite';
|
||||
|
||||
@import 'normal';
|
||||
@import 'dark-mode'; // dark-mode 放在 normal 后面,以获得更高优先级
|
||||
|
||||
// 首页弹窗提示信息的 更新信息 面板
|
||||
.update-info{
|
||||
.update-info {
|
||||
@include border-radius(8px);
|
||||
overflow: hidden;
|
||||
border: 1px solid $color-border-el;
|
||||
margin: 10px 0;
|
||||
.update-title{
|
||||
.update-title {
|
||||
font-size: $fz-mini-title;
|
||||
padding: 5px 10px;
|
||||
background-color: $color-border-el;
|
||||
}
|
||||
.update-content{
|
||||
.update-content {
|
||||
font-size: $fz-mini-content;
|
||||
line-height: 1.5;
|
||||
padding: 10px;
|
||||
|
|
|
@ -44,8 +44,7 @@ export async function extractQQMusicMeta(
|
|||
musicMeta.common.artist = '';
|
||||
if (!musicMeta.common.artists) {
|
||||
musicMeta.common.artist = fromGBK(musicMeta.common.artist);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
musicMeta.common.artist = musicMeta.common.artists.map(fromGBK).join();
|
||||
}
|
||||
musicMeta.common.title = fromGBK(musicMeta.common.title);
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
<div id="app-control">
|
||||
<el-row class="mb-3">
|
||||
<span>歌曲命名格式:</span>
|
||||
<el-radio
|
||||
v-for="k in FilenamePolicies"
|
||||
:key="k.key"
|
||||
v-model="filename_policy"
|
||||
:label="k.key"
|
||||
>
|
||||
<el-radio v-for="k in FilenamePolicies" :key="k.key" v-model="filename_policy" :label="k.key">
|
||||
{{ k.text }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
|
@ -26,10 +21,7 @@
|
|||
@cancel="showEditDialog = false"
|
||||
@ok="handleEdit"
|
||||
></edit-dialog>
|
||||
<config-dialog
|
||||
:show="showConfigDialog"
|
||||
@done="showConfigDialog = false"
|
||||
></config-dialog>
|
||||
<config-dialog :show="showConfigDialog" @done="showConfigDialog = false"></config-dialog>
|
||||
<el-tooltip class="item" effect="dark" placement="top">
|
||||
<template #content>
|
||||
<span> 部分解密方案需要设定解密参数。 </span>
|
||||
|
@ -47,22 +39,13 @@
|
|||
|
||||
<el-tooltip class="item" effect="dark" placement="top-start">
|
||||
<template #content>
|
||||
<span v-if="instant_save">
|
||||
工作模式: {{ dir ? '写入本地文件系统' : '调用浏览器下载' }}
|
||||
</span>
|
||||
<span v-if="instant_save"> 工作模式: {{ dir ? '写入本地文件系统' : '调用浏览器下载' }} </span>
|
||||
<span v-else>
|
||||
当您使用此工具进行大量文件解锁的时候,建议开启此选项。<br />
|
||||
开启后,解锁结果将不会存留于浏览器中,防止内存不足。
|
||||
</span>
|
||||
</template>
|
||||
<el-checkbox
|
||||
v-model="instant_save"
|
||||
type="success"
|
||||
border
|
||||
class="ml-2"
|
||||
>
|
||||
立即保存
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="instant_save" type="success" border class="ml-2"> 立即保存 </el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-row>
|
||||
</div>
|
||||
|
@ -86,20 +69,8 @@ import PreviewTable from '@/component/PreviewTable';
|
|||
import ConfigDialog from '@/component/ConfigDialog';
|
||||
import EditDialog from '@/component/EditDialog';
|
||||
|
||||
import {
|
||||
DownloadBlobMusic,
|
||||
FilenamePolicy,
|
||||
FilenamePolicies,
|
||||
RemoveBlobMusic,
|
||||
DirectlyWriteFile,
|
||||
} from '@/utils/utils';
|
||||
import {
|
||||
GetImageFromURL,
|
||||
RewriteMetaToMp3,
|
||||
RewriteMetaToFlac,
|
||||
AudioMimeType,
|
||||
split_regex,
|
||||
} from '@/decrypt/utils';
|
||||
import { DownloadBlobMusic, FilenamePolicy, FilenamePolicies, RemoveBlobMusic, DirectlyWriteFile } from '@/utils/utils';
|
||||
import { GetImageFromURL, RewriteMetaToMp3, RewriteMetaToFlac, AudioMimeType, split_regex } from '@/decrypt/utils';
|
||||
import { parseBlob as metaParseBlob } from 'music-metadata-browser';
|
||||
|
||||
export default {
|
||||
|
@ -152,12 +123,7 @@ export default {
|
|||
}
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
let _rp_data = [data.title, data.artist, data.album];
|
||||
window._paq.push([
|
||||
'trackEvent',
|
||||
'Unlock',
|
||||
data.rawExt + ',' + data.mime,
|
||||
JSON.stringify(_rp_data),
|
||||
]);
|
||||
window._paq.push(['trackEvent', 'Unlock', data.rawExt + ',' + data.mime, JSON.stringify(_rp_data)]);
|
||||
}
|
||||
},
|
||||
showFail(errInfo, filename) {
|
||||
|
@ -214,9 +180,7 @@ export default {
|
|||
let writeSuccess = true;
|
||||
let notifyMsg = '成功修改 ' + this.editing_data.title;
|
||||
try {
|
||||
const musicMeta = await metaParseBlob(
|
||||
new Blob([this.editing_data.blob], { type: mime })
|
||||
);
|
||||
const musicMeta = await metaParseBlob(new Blob([this.editing_data.blob], { type: mime }));
|
||||
let imageInfo = undefined;
|
||||
if (this.editing_data.picture !== '') {
|
||||
imageInfo = await GetImageFromURL(this.editing_data.picture);
|
||||
|
@ -235,26 +199,16 @@ export default {
|
|||
const buffer = Buffer.from(await this.editing_data.blob.arrayBuffer());
|
||||
const mime = AudioMimeType[this.editing_data.ext] || AudioMimeType.mp3;
|
||||
if (this.editing_data.ext === 'mp3') {
|
||||
this.editing_data.blob = new Blob(
|
||||
[RewriteMetaToMp3(buffer, newMeta, musicMeta)],
|
||||
{ type: mime }
|
||||
);
|
||||
this.editing_data.blob = new Blob([RewriteMetaToMp3(buffer, newMeta, musicMeta)], { type: mime });
|
||||
} else if (this.editing_data.ext === 'flac') {
|
||||
this.editing_data.blob = new Blob(
|
||||
[RewriteMetaToFlac(buffer, newMeta, musicMeta)],
|
||||
{ type: mime }
|
||||
);
|
||||
this.editing_data.blob = new Blob([RewriteMetaToFlac(buffer, newMeta, musicMeta)], { type: mime });
|
||||
} else {
|
||||
writeSuccess = undefined;
|
||||
notifyMsg = this.editing_data.ext + '类型文件暂时不支持修改音乐标签';
|
||||
}
|
||||
} catch (e) {
|
||||
writeSuccess = false;
|
||||
notifyMsg =
|
||||
'修改' +
|
||||
this.editing_data.title +
|
||||
'未能完成。在写入新的元数据时发生错误:' +
|
||||
e;
|
||||
notifyMsg = '修改' + this.editing_data.title + '未能完成。在写入新的元数据时发生错误:' + e;
|
||||
}
|
||||
this.editing_data.file = URL.createObjectURL(this.editing_data.blob);
|
||||
if (writeSuccess === true) {
|
||||
|
@ -305,16 +259,12 @@ export default {
|
|||
async showDirectlySave() {
|
||||
if (!window.showDirectoryPicker) return;
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'您的浏览器支持文件直接保存到磁盘,是否使用?',
|
||||
'新特性提示',
|
||||
{
|
||||
confirmButtonText: '使用',
|
||||
cancelButtonText: '不使用',
|
||||
type: 'warning',
|
||||
center: true,
|
||||
}
|
||||
);
|
||||
await ElMessageBox.confirm('您的浏览器支持文件直接保存到磁盘,是否使用?', '新特性提示', {
|
||||
confirmButtonText: '使用',
|
||||
cancelButtonText: '不使用',
|
||||
type: 'warning',
|
||||
center: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return;
|
||||
|
|
|
@ -13,31 +13,13 @@
|
|||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"jest"
|
||||
],
|
||||
"types": ["webpack-env", "jest"],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
],
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
const ThreadsPlugin = require('threads-plugin');
|
||||
module.exports = {
|
||||
publicPath: '',
|
||||
productionSourceMap: false,
|
||||
pwa: {
|
||||
manifestPath: "web-manifest.json",
|
||||
name: "音乐解锁",
|
||||
themeColor: "#4DBA87",
|
||||
msTileColor: "#000000",
|
||||
manifestOptions: {
|
||||
start_url: "./index.html",
|
||||
description: "在任何设备上解锁已购的加密音乐!",
|
||||
icons: [
|
||||
{
|
||||
'src': './img/icons/android-chrome-192x192.png',
|
||||
'sizes': '192x192',
|
||||
'type': 'image/png'
|
||||
},
|
||||
{
|
||||
'src': './img/icons/android-chrome-512x512.png',
|
||||
'sizes': '512x512',
|
||||
'type': 'image/png'
|
||||
}
|
||||
]
|
||||
publicPath: '',
|
||||
productionSourceMap: false,
|
||||
pwa: {
|
||||
manifestPath: 'web-manifest.json',
|
||||
name: '音乐解锁',
|
||||
themeColor: '#4DBA87',
|
||||
msTileColor: '#000000',
|
||||
manifestOptions: {
|
||||
start_url: './index.html',
|
||||
description: '在任何设备上解锁已购的加密音乐!',
|
||||
icons: [
|
||||
{
|
||||
src: './img/icons/android-chrome-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
appleMobileWebAppCapable: 'yes',
|
||||
iconPaths: {
|
||||
faviconSVG: './img/icons/safari-pinned-tab.svg',
|
||||
favicon32: './img/icons/favicon-32x32.png',
|
||||
favicon16: './img/icons/favicon-16x16.png',
|
||||
appleTouchIcon: './img/icons/apple-touch-icon-152x152.png',
|
||||
maskIcon: './img/icons/safari-pinned-tab.svg',
|
||||
msTileImage: './img/icons/msapplication-icon-144x144.png'
|
||||
{
|
||||
src: './img/icons/android-chrome-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
},
|
||||
workboxPluginMode: "GenerateSW",
|
||||
workboxOptions: {
|
||||
skipWaiting: true
|
||||
}
|
||||
],
|
||||
},
|
||||
configureWebpack: {
|
||||
plugins: [new ThreadsPlugin()]
|
||||
}
|
||||
appleMobileWebAppCapable: 'yes',
|
||||
iconPaths: {
|
||||
faviconSVG: './img/icons/safari-pinned-tab.svg',
|
||||
favicon32: './img/icons/favicon-32x32.png',
|
||||
favicon16: './img/icons/favicon-16x16.png',
|
||||
appleTouchIcon: './img/icons/apple-touch-icon-152x152.png',
|
||||
maskIcon: './img/icons/safari-pinned-tab.svg',
|
||||
msTileImage: './img/icons/msapplication-icon-144x144.png',
|
||||
},
|
||||
workboxPluginMode: 'GenerateSW',
|
||||
workboxOptions: {
|
||||
skipWaiting: true,
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
plugins: [new ThreadsPlugin()],
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue