diff --git a/package-lock.json b/package-lock.json index 6b16ef48..a312a2a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lx-music-desktop", - "version": "0.3.2", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6243,6 +6243,11 @@ "resolve-dir": "^1.0.1" } }, + "flac-metadata": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/flac-metadata/download/flac-metadata-0.1.1.tgz", + "integrity": "sha1-wC+KBtJL1bad4rhVEsbkW9j5F2w=" + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npm.taobao.org/flat-cache/download/flat-cache-2.0.1.tgz", @@ -7534,7 +7539,6 @@ "version": "0.4.24", "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz", "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -9410,6 +9414,14 @@ "integrity": "sha1-bBUsNFzhHFL0ZcKr2VfoY5zWdN8=", "dev": true }, + "node-id3": { + "version": "0.1.11", + "resolved": "https://registry.npm.taobao.org/node-id3/download/node-id3-0.1.11.tgz", + "integrity": "sha1-ypuX8MVOQPsjRuUptKxMFaDPFio=", + "requires": { + "iconv-lite": "^0.4.15" + } + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npm.taobao.org/node-libs-browser/download/node-libs-browser-2.2.1.tgz", diff --git a/package.json b/package.json index c2dce36c..cb7c8391 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,14 @@ "scripts": { "pack": "node build-config/pack.js && npm run pack:win", "pack:win": "npm run pack:win:setup && npm run pack:win:7z", - "pack:win:setup": "cross-env TARGET=win安装版 ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32", + "pack:win:setup": "cross-env TARGET=win_安装版 ARCH=x64_x86 electron-builder -w=nsis --x64 --ia32", "pack:win:portable": "npm run pack:win:portable:x64_x86 && npm run pack:win:portable:x64 && npm run pack:win:portable:x86", "pack:win:portable:x64_x86": "cross-env TARGET=便携版 ARCH=x64_x86 electron-builder -w=portable --x64 --ia32", "pack:win:portable:x64": "cross-env TARGET=便携版 ARCH=x64 electron-builder -w=portable --x64", "pack:win:portable:x86": "cross-env TARGET=便携版 ARCH=x86 electron-builder -w=portable --ia32", "pack:win:7z": "npm run pack:win:7z:x64 && npm run pack:win:7z:x86", - "pack:win:7z:x64": "cross-env TARGET=win绿色版 ARCH=x64 electron-builder -w=7z --x64", - "pack:win:7z:x86": "cross-env TARGET=win绿色版 ARCH=x86 electron-builder -w=7z --ia32", + "pack:win:7z:x64": "cross-env TARGET=win_绿色版 ARCH=x64 electron-builder -w=7z --x64", + "pack:win:7z:x86": "cross-env TARGET=win_绿色版 ARCH=x86 electron-builder -w=7z --ia32", "publish": "node publish", "publish:gh": "node build-config/pack.js && npm run publish:win", "publish:win": "npm run publish:win:7z && npm run publish:win:setup", @@ -199,9 +199,11 @@ "electron-log": "^3.0.7", "electron-store": "^4.0.0", "electron-updater": "^4.1.2", + "flac-metadata": "^0.1.1", "js-htmlencode": "^0.3.0", "lrc-file-parser": "^0.1.12", "node-downloader-helper": "^1.0.10", + "node-id3": "^0.1.11", "request": "^2.88.0", "vue": "^2.6.10", "vue-electron": "^1.0.6", diff --git a/publish/changeLog.md b/publish/changeLog.md index 9343f067..87269837 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -1,6 +1,8 @@ ### 新增 -- 新增单例应用功能 +- 新增**封面嵌入**(默认开启,可到设置-下载设置关闭) +- 新增**歌词下载**(默认关闭,可到设置-下载设置开启) +- 新增单例应用功能(实现软件单开功能,禁止软件多开) ### 优化 @@ -9,3 +11,6 @@ ### 修复 - 修复歌单无法翻页的问题 +- 修复在某些情况下,添加下载歌曲导致下载列表崩溃的问题 +- 修复版本更新弹窗Bug +- 修复酷狗歌单推荐歌单出现在其他分类中的Bug diff --git a/src/main/events/index.js b/src/main/events/index.js index 306989c1..9318cd26 100644 --- a/src/main/events/index.js +++ b/src/main/events/index.js @@ -1,4 +1,5 @@ require('./request') -require('./appName') +// require('./appName') +require('./musicMeta') diff --git a/src/main/events/musicMeta.js b/src/main/events/musicMeta.js new file mode 100644 index 00000000..795d06b9 --- /dev/null +++ b/src/main/events/musicMeta.js @@ -0,0 +1,6 @@ +const { mainOn } = require('../../common/icp') +const { setMeta } = require('../utils/musicMeta') + +mainOn('setMusicMeta', (event, { filePath, meta }) => { + setMeta(filePath, meta) +}) diff --git a/src/main/utils/flacMeta.js b/src/main/utils/flacMeta.js new file mode 100644 index 00000000..e0150c25 --- /dev/null +++ b/src/main/utils/flacMeta.js @@ -0,0 +1,38 @@ +const fs = require('fs') +const flac = require('flac-metadata') + +module.exports = (filenPath, meta) => { + const reader = fs.createReadStream(filenPath) + const tempPath = filenPath + '.lxmtemp' + const writer = fs.createWriteStream(tempPath) + const processor = new flac.Processor() + if (meta.APIC) delete meta.APIC + + const comments = [] + for (const key in meta) { + comments.push(`${key.toUpperCase()}=${meta[key]}`) + } + const vendor = 'lx-music-desktop' + + processor.on('preprocess', function(mdb) { + // Remove existing VORBIS_COMMENT block, if any. + if (mdb.type === flac.Processor.MDB_TYPE_VORBIS_COMMENT) { + mdb.remove() + } + // Inject new VORBIS_COMMENT block. + if (mdb.removed || mdb.isLast) { + let mdbVorbis = flac.data.MetaDataBlockVorbisComment.create(mdb.isLast, vendor, comments) + this.push(mdbVorbis.publish()) + } + }) + + reader.pipe(processor).pipe(writer).on('finish', () => { + fs.unlink(filenPath, err => { + if (err) return console.log(err.message) + fs.rename(tempPath, filenPath, err => { + if (err) console.log(err.message) + }) + }) + }) +} + diff --git a/src/main/utils/mp3Meta.js b/src/main/utils/mp3Meta.js new file mode 100644 index 00000000..5b5950ea --- /dev/null +++ b/src/main/utils/mp3Meta.js @@ -0,0 +1,17 @@ +const NodeID3 = require('node-id3') +const path = require('path') +const fs = require('fs') +const request = require('request') +const extReg = /^(\.(?:jpe?g|png)).*$/ + +module.exports = (filePath, meta) => { + if (!meta.APIC) return NodeID3.write(meta, filePath) + let picPath = path.join(path.dirname(filePath), `${meta.title}-${meta.artist}${path.extname(meta.APIC).replace(extReg, '$1')}`) + request(meta.APIC).pipe(fs.createWriteStream(picPath)).on('finish', () => { + meta.APIC = picPath + NodeID3.write(meta, filePath) + fs.unlink(picPath, err => { + if (err) console.log(err.message) + }) + }) +} diff --git a/src/main/utils/musicMeta.js b/src/main/utils/musicMeta.js new file mode 100644 index 00000000..018392cb --- /dev/null +++ b/src/main/utils/musicMeta.js @@ -0,0 +1,14 @@ +const path = require('path') +const mp3Meta = require('./mp3Meta') +const flacMeta = require('./flacMeta') + +exports.setMeta = (filePath, meta) => { + switch (path.extname(filePath)) { + case '.mp3': + mp3Meta(filePath, meta) + break + case '.flac': + flacMeta(filePath, meta) + break + } +} diff --git a/src/renderer/components/material/TagList.vue b/src/renderer/components/material/TagList.vue index acb940ae..7194e8a3 100644 --- a/src/renderer/components/material/TagList.vue +++ b/src/renderer/components/material/TagList.vue @@ -2,7 +2,7 @@ div(:class="$style.tagList") div(:class="$style.label" ref="dom_btn" @click="handleShow") {{value.name}} div.scroll(:class="$style.list" @click.stop ref="dom_list" :style="listStyle") - div(:class="$style.tag" @click="handleClick(null)") 全部 + div(:class="$style.tag" @click="handleClick(null)") 默认 dl(v-for="type in list") dt(:class="$style.type") {{type.name}} dd(:class="$style.tag" v-for="tag in type.list" @click="handleClick(tag)") {{tag.name}} @@ -66,7 +66,7 @@ export default { handleClick(item) { if (!item) { item = { - name: '全部', + name: '默认', id: null, } } diff --git a/src/renderer/store/actions.js b/src/renderer/store/actions.js index d1968fd3..c6ac3331 100644 --- a/src/renderer/store/actions.js +++ b/src/renderer/store/actions.js @@ -6,7 +6,7 @@ export default { getVersionInfo() { return new Promise((resolve, reject) => { httpGet(`https://raw.githubusercontent.com/${author.name}/${name}/master/publish/version.json`, (err, resp, body) => { - if (!err) { + if (err) { return resolve({ version: '0.0.0', desc: '

版本信息获取失败

', diff --git a/src/renderer/store/modules/download.js b/src/renderer/store/modules/download.js index c18994e4..5a61b6e5 100644 --- a/src/renderer/store/modules/download.js +++ b/src/renderer/store/modules/download.js @@ -3,6 +3,7 @@ import fs from 'fs' import path from 'path' import music from '../../utils/music' import { getMusicType } from '../../utils/music/utils' +import { setMeta, saveLrc } from '../../utils' // state const state = { @@ -75,6 +76,43 @@ const getUrl = (downloadInfo, isRefresh) => { return url && !isRefresh ? Promise.resolve({ url }) : music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise } +/** + * 设置歌曲meta信息 + * @param {*} downloadInfo + * @param {*} filePath + * @param {*} isEmbedPic + */ +const saveMeta = (downloadInfo, filePath, isEmbedPic) => { + if (downloadInfo.type === 'ape') return + const promise = isEmbedPic + ? downloadInfo.musicInfo.img + ? Promise.resolve(downloadInfo.musicInfo.img) + : music[downloadInfo.musicInfo.source].getPic(downloadInfo.musicInfo).promise + : Promise.resolve() + promise.then(url => { + setMeta(filePath, { + title: downloadInfo.musicInfo.name, + artist: downloadInfo.musicInfo.singer, + album: downloadInfo.musicInfo.albumName, + APIC: url, + }) + }) +} + +/** + * 保存歌词 + * @param {*} downloadInfo + * @param {*} filePath + */ +const downloadLyric = (downloadInfo, filePath) => { + const promise = downloadInfo.musicInfo.lrc + ? Promise.resolve(downloadInfo.musicInfo.lrc) + : music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise + promise.then(lrc => { + if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape)$/, 'lrc'), lrc) + }) +} + // actions const actions = { createDownload({ state, rootState, commit }, { musicInfo, type }) { @@ -133,8 +171,16 @@ const actions = { method: 'get', override: true, onEnd() { + if (downloadInfo.progress.progress != '100.00') { + delete dls[downloadInfo.key] + return this.dispatch('download/startTask', downloadInfo) + } commit('onEnd', downloadInfo) _this.dispatch('download/startTask') + const filePath = path.join(options.path, options.fileName) + + saveMeta(downloadInfo, filePath, rootState.setting.download.isEmbedPic) + if (rootState.setting.download.isDownloadLrc) downloadLyric(downloadInfo, filePath) console.log('on complate') }, onError(err) { diff --git a/src/renderer/store/modules/songList.js b/src/renderer/store/modules/songList.js index ccb4eec2..6f54fba6 100644 --- a/src/renderer/store/modules/songList.js +++ b/src/renderer/store/modules/songList.js @@ -97,7 +97,6 @@ const mutations = { state.selectListInfo = info }, clearListDetail(state) { - console.log('object') state.listDetail.list = [] }, } diff --git a/src/renderer/utils/index.js b/src/renderer/utils/index.js index d6997758..d4660c33 100644 --- a/src/renderer/utils/index.js +++ b/src/renderer/utils/index.js @@ -3,6 +3,7 @@ import { shell, remote, clipboard } from 'electron' import path from 'path' import os from 'os' import crypto from 'crypto' +import { rendererSend } from '../../common/icp' /** * 获取两个数之间的随机整数,大于等于min,小于max @@ -162,7 +163,7 @@ export const isChildren = (parent, children) => { * @param {*} setting */ export const updateSetting = setting => { - const defaultVersion = '1.0.5' + const defaultVersion = '1.0.6' const defaultSetting = { version: defaultVersion, player: { @@ -178,6 +179,8 @@ export const updateSetting = setting => { savePath: path.join(os.homedir(), 'Desktop'), fileName: '歌名 - 歌手', maxDownloadNum: 3, + isDownloadLrc: false, + isEmbedPic: true, }, leaderboard: { source: 'kw', @@ -187,7 +190,7 @@ export const updateSetting = setting => { source: 'kg', sortId: '5', tagInfo: { - name: '全部', + name: '默认', id: null, }, }, @@ -242,3 +245,23 @@ export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex') * @param {*} str */ export const clipboardWriteText = str => clipboard.writeText(str) + +/** + * 设置音频 meta 信息 + * @param {*} filePath + * @param {*} meta + */ +export const setMeta = (filePath, meta) => { + rendererSend('setMusicMeta', { filePath, meta }) +} + +/** + * 保存歌词文件 + * @param {*} filePath + * @param {*} lrc + */ +export const saveLrc = (filePath, lrc) => { + fs.writeFile(filePath, lrc, 'utf8', err => { + if (err) console.log(err) + }) +} diff --git a/src/renderer/utils/music/bd/songList.js b/src/renderer/utils/music/bd/songList.js index b1240d04..c65ec2c5 100644 --- a/src/renderer/utils/music/bd/songList.js +++ b/src/renderer/utils/music/bd/songList.js @@ -89,7 +89,7 @@ export default { }, getListUrl(sortType, tagName, page) { return this.createUrl({ - channelname: tagName || '全部', + channelname: tagName || '默认', from: 'qianqianmini', offset: (page - 1) * this.limit_list, order_type: sortType, diff --git a/src/renderer/utils/music/kg/songList.js b/src/renderer/utils/music/kg/songList.js index fd79249b..dea770e9 100644 --- a/src/renderer/utils/music/kg/songList.js +++ b/src/renderer/utils/music/kg/songList.js @@ -231,7 +231,7 @@ export default { return info }) ) - if (!tagId && page === 1) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表 + if (!tagId && page === 1 && sortId === this.sortList[0].id) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表 return Promise.all(tasks).then(([list, info, recommendList]) => { if (recommendList) list.unshift(...recommendList) return { diff --git a/src/renderer/utils/music/kw/songList.js b/src/renderer/utils/music/kw/songList.js index 0c75b125..979919b1 100644 --- a/src/renderer/utils/music/kw/songList.js +++ b/src/renderer/utils/music/kw/songList.js @@ -23,7 +23,6 @@ export default { tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576', hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576', getListUrl({ sortId, id, type, page }) { - console.log(id, type) if (!id) return `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&&pn=${page}&rn=${this.limit_list}&order=${sortId}` switch (type) { case '10000': return `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&id=${id}&rn=${this.limit_list}` @@ -145,7 +144,6 @@ export default { desc: item.desc, }))) }) - console.log(list) return list }, diff --git a/src/renderer/utils/music/utils.js b/src/renderer/utils/music/utils.js index 433945e9..2b43f645 100644 --- a/src/renderer/utils/music/utils.js +++ b/src/renderer/utils/music/utils.js @@ -15,4 +15,5 @@ export const getMusicType = (info, type) => { for (const type of rangeType) { if (info._types[type]) return type } + return '128k' } diff --git a/src/renderer/utils/request.js b/src/renderer/utils/request.js index 897297f1..ef976b85 100644 --- a/src/renderer/utils/request.js +++ b/src/renderer/utils/request.js @@ -41,8 +41,8 @@ const buildHttpPromose = (url, options) => { const obj = { promise: p, cancelHttp() { - console.log('cancel') if (!requestObj) return + console.log('cancel') cancelHttp(requestObj) cancelFn(new Error(requestMsg.cancelRequest)) requestObj = null diff --git a/src/renderer/views/Download.vue b/src/renderer/views/Download.vue index 0266b9cb..27fa58b6 100644 --- a/src/renderer/views/Download.vue +++ b/src/renderer/views/Download.vue @@ -23,7 +23,7 @@ div(:class="$style.download") td.break(style="width: 28%;") {{item.musicInfo.name}} - {{item.musicInfo.singer}} td.break(style="width: 22%;") {{item.progress.progress}}% td.break(style="width: 15%;") {{item.statusText}} - td.break(style="width: 10%;") {{item.type.toUpperCase()}} + td.break(style="width: 10%;") {{item.type && item.type.toUpperCase()}} td(style="width: 20%; padding-left: 0; padding-right: 0;") material-list-buttons(:index="index" :download-btn="false" :start-btn="!item.isComplate && item.status != downloadStatus.WAITING && (item.status != downloadStatus.RUN)" :pause-btn="!item.isComplate && (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING)" diff --git a/src/renderer/views/Setting.vue b/src/renderer/views/Setting.vue index e62a37b9..c608451a 100644 --- a/src/renderer/views/Setting.vue +++ b/src/renderer/views/Setting.vue @@ -49,6 +49,14 @@ div.scroll(:class="$style.setting") div material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name") + dd(title='封面嵌入') + h3 是否将封面嵌入音频文件中(只支持MP3格式) + div + material-checkbox(id="setting_download_isEmbedPic" v-model="current_setting.download.isEmbedPic" label="是否启用") + dd(title='歌词下载') + h3 是否同时下载歌词文件 + div + material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" label="是否启用") //- dt 列表设置 //- dd(title='播放列表是否显示专辑栏') h3 专辑栏 @@ -146,6 +154,8 @@ export default { download: { savePath: '', fileName: '歌名 - 歌手', + isDownloadLrc: false, + isEmbedPic: true, }, themeId: 0, sourceId: 0, diff --git a/src/renderer/views/SongList.vue b/src/renderer/views/SongList.vue index 237a6e89..179b05d4 100644 --- a/src/renderer/views/SongList.vue +++ b/src/renderer/views/SongList.vue @@ -42,7 +42,7 @@ export default { data() { return { tagInfo: { - name: '全部', + name: '默认', id: null, }, sortId: undefined, @@ -103,7 +103,7 @@ export default { if (o) { this.isToggleSource = true this.tagInfo = { - name: '全部', + name: '默认', id: null, } this.sortId = this.sorts[0] && this.sorts[0].id