v2.0.0
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { setMeta } from '@common/utils/musicMeta'
|
||||
|
||||
export const writeMeta = (filePath: string, meta: LX.Music.MusicFileMeta) => {
|
||||
setMeta(filePath, meta)
|
||||
}
|
||||
|
||||
export { saveLrc } from './utils'
|
||||
@@ -0,0 +1,288 @@
|
||||
import { createDownload, DownloaderType, Options as DownloadOptions } from '@common/utils/download'
|
||||
// import music from '@renderer/utils/musicSdk'
|
||||
import { createDownloadInfo } from './utils'
|
||||
// import {
|
||||
// filterFileName,
|
||||
// } from '@common/utils/common'
|
||||
// import {
|
||||
// assertApiSupport,
|
||||
// getExt,
|
||||
// } from '..'
|
||||
import { checkPath, removeFile } from '@common/utils/nodejs'
|
||||
import { DOWNLOAD_STATUS } from '@common/constants'
|
||||
// import { download as eventDownloadNames } from '@renderer/event/names'
|
||||
|
||||
// window.downloadList = []
|
||||
// window.downloadListFull = []
|
||||
// window.downloadListFullMap = new Map()
|
||||
|
||||
const dls = new Map<string, DownloaderType>()
|
||||
const tryNum = new Map<string, number>()
|
||||
const taskActions = new Map<string, (action: LX.Download.DownloadTaskActions) => void>()
|
||||
const tasks = new Map<string, LX.Download.ListItem>()
|
||||
|
||||
export const checkList = (list: LX.Download.ListItem[], musicInfo: LX.Music.MusicInfo, quality: LX.Quality, ext: string): boolean => {
|
||||
return list.some(s => s.id === musicInfo.id && (s.metadata.quality === quality || s.metadata.ext === ext))
|
||||
}
|
||||
|
||||
// const removeTask = (id: string) => {
|
||||
// dls.delete(id)
|
||||
// tryNum.delete(id)
|
||||
// taskActions.delete(id)
|
||||
// tasks.delete(id)
|
||||
// }
|
||||
const sendAction = (id: string, action: LX.Download.DownloadTaskActions) => {
|
||||
const callback = taskActions.get(id)
|
||||
if (!callback) return
|
||||
callback(action)
|
||||
}
|
||||
|
||||
export const createDownloadTasks = (
|
||||
list: LX.Music.MusicInfoOnline[],
|
||||
quality: LX.Quality,
|
||||
savePath: string,
|
||||
fileNameFormat: string,
|
||||
qualityList: LX.QualityList,
|
||||
): LX.Download.ListItem[] => {
|
||||
return list.map(musicInfo => {
|
||||
return createDownloadInfo(musicInfo, quality, fileNameFormat, savePath, qualityList)
|
||||
}).filter(task => task)
|
||||
// commit('addTasks', { list: taskList, addMusicLocationType: rootState.setting.list.addMusicLocationType })
|
||||
// let result = getStartTask(downloadList, DOWNLOAD_STATUS, rootState.setting.download.maxDownloadNum)
|
||||
// while (result) {
|
||||
// dispatch('startTask', result)
|
||||
// result = getStartTask(downloadList, DOWNLOAD_STATUS, rootState.setting.download.maxDownloadNum)
|
||||
// }
|
||||
}
|
||||
|
||||
const createTask = async(downloadInfo: LX.Download.ListItem, savePath: string) => {
|
||||
// console.log('createTask', downloadInfo, savePath)
|
||||
// 开始任务
|
||||
/* commit('onStart', downloadInfo)
|
||||
commit('setStatusText', { downloadInfo, text: '任务初始化中' }) */
|
||||
if (!await checkPath(savePath)) {
|
||||
sendAction(downloadInfo.id, {
|
||||
action: 'error',
|
||||
data: {
|
||||
error: 'download_status_error_check_path',
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!tasks.has(downloadInfo.id)) return
|
||||
|
||||
// TODO check file exists
|
||||
|
||||
const downloadOptions: DownloadOptions = {
|
||||
url: downloadInfo.metadata.url ?? '',
|
||||
path: savePath,
|
||||
fileName: downloadInfo.metadata.fileName,
|
||||
method: 'get',
|
||||
onCompleted() {
|
||||
// if (downloadInfo.progress.progress != '100.00') {
|
||||
// delete.get(downloadInfo.id)?
|
||||
// return dispatch('startTask', downloadInfo)
|
||||
// }
|
||||
downloadInfo.isComplate = true
|
||||
downloadInfo.status = DOWNLOAD_STATUS.COMPLETED
|
||||
sendAction(downloadInfo.id, { action: 'complete' })
|
||||
console.log('on complate')
|
||||
},
|
||||
onError(err: any) {
|
||||
console.log(err)
|
||||
if (err.code == 'EPERM') {
|
||||
sendAction(downloadInfo.id, {
|
||||
action: 'error',
|
||||
data: {
|
||||
error: 'download_status_error_write',
|
||||
message: err.message,
|
||||
},
|
||||
// data: `歌曲保存位置被占用或没有写入权限,请尝试更改歌曲保存目录或重启软件或重启电脑,错误详情:${err.message as string}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
// console.log(tryNum[downloadInfo.id])
|
||||
let retryNum = tryNum.get(downloadInfo.id) ?? 0
|
||||
tryNum.set(downloadInfo.id, ++retryNum)
|
||||
if (retryNum > 2) {
|
||||
sendAction(downloadInfo.id, {
|
||||
action: 'error',
|
||||
data: {
|
||||
message: err.message,
|
||||
},
|
||||
})
|
||||
// dispatch('startTask')
|
||||
return
|
||||
}
|
||||
if (err.message?.startsWith('Resume failed')) {
|
||||
removeFile(downloadInfo.metadata.filePath).catch(err => {
|
||||
console.log('删除不匹配的文件失败:', err.message)
|
||||
// commit('onError', { downloadInfo, errorMsg: '删除不匹配的文件失败:' + err.message })
|
||||
}).finally(() => {
|
||||
console.log('正在重试')
|
||||
void dls.get(downloadInfo.id)?.start()
|
||||
// sendAction(downloadInfo.id, {
|
||||
// action: 'statusText',
|
||||
// data: 'download_status_error_retrying',
|
||||
// })
|
||||
})
|
||||
return
|
||||
}
|
||||
if (err.code == 'ENOTFOUND') {
|
||||
sendAction(downloadInfo.id, { action: 'refreshUrl' })
|
||||
} else {
|
||||
console.log('Download failed, Attempting Retry')
|
||||
void dls.get(downloadInfo.id)?.start()
|
||||
console.log('正在重试')
|
||||
}
|
||||
},
|
||||
onFail(response) {
|
||||
let retryNum = tryNum.get(downloadInfo.id) ?? 0
|
||||
tryNum.set(downloadInfo.id, ++retryNum)
|
||||
if (retryNum > 2) {
|
||||
sendAction(downloadInfo.id, {
|
||||
action: 'error',
|
||||
data: {},
|
||||
})
|
||||
return
|
||||
}
|
||||
switch (response.statusCode) {
|
||||
case 401:
|
||||
case 403:
|
||||
case 410:
|
||||
sendAction(downloadInfo.id, { action: 'refreshUrl' })
|
||||
// commit('onError', { downloadInfo, errorMsg: '链接失效' })
|
||||
// refreshUrl.call(_this, commit, downloadInfo, rootState.setting.download.isUseOtherSource)
|
||||
break
|
||||
default:
|
||||
void dls.get(downloadInfo.id)?.start()
|
||||
console.log('正在重试')
|
||||
// commit('setStatusText', { downloadInfo, text: '正在重试' })
|
||||
break
|
||||
}
|
||||
},
|
||||
onStart() {
|
||||
// sendAction(downloadInfo.id, { action: 'start' })
|
||||
console.log('on start')
|
||||
},
|
||||
onProgress(status) {
|
||||
downloadInfo.total = status.total
|
||||
downloadInfo.downloaded = status.downloaded
|
||||
downloadInfo.progress = status.progress
|
||||
downloadInfo.speed = status.speed
|
||||
sendAction(downloadInfo.id, { action: 'progress', data: status })
|
||||
// console.log(status)
|
||||
},
|
||||
onStop() {
|
||||
console.log('on stop')
|
||||
// sendAction(downloadInfo.id, { action: 'pause' })
|
||||
// commit('pauseTask', downloadInfo)
|
||||
// dispatch('startTask')
|
||||
},
|
||||
}
|
||||
// commit('setStatusText', { downloadInfo, text: '获取URL中...' })
|
||||
|
||||
tryNum.set(downloadInfo.id, 0)
|
||||
dls.set(downloadInfo.id, createDownload(downloadOptions))
|
||||
}
|
||||
|
||||
export const updateUrl = (id: string, url: string) => {
|
||||
const task = tasks.get(id)
|
||||
if (!task) return
|
||||
task.metadata.url = url
|
||||
// commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||
const dl = dls.get(id)
|
||||
if (!dl) return
|
||||
dl.refreshUrl(url)
|
||||
dl.start().catch(err => {
|
||||
sendAction(id, {
|
||||
action: 'error',
|
||||
data: {
|
||||
message: err.message,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const startTask = async(downloadInfo: LX.Download.ListItem, savePath: string, callback: (action: LX.Download.DownloadTaskActions) => void) => {
|
||||
await pauseTask(downloadInfo.id)
|
||||
|
||||
tasks.set(downloadInfo.id, downloadInfo)
|
||||
taskActions.set(downloadInfo.id, callback)
|
||||
// 检查是否可以开始任务
|
||||
// if (!downloadInfo.isComplate && downloadInfo.status != DOWNLOAD_STATUS.RUN) {
|
||||
// const result = getStartTask(downloadList, DOWNLOAD_STATUS, rootState.setting.download.maxDownloadNum)
|
||||
// if (result === false) {
|
||||
// commit('setStatus', { downloadInfo, status: DOWNLOAD_STATUS.WAITING })
|
||||
// return
|
||||
// }
|
||||
// } else {
|
||||
// const result = getStartTask(downloadList, DOWNLOAD_STATUS, rootState.setting.download.maxDownloadNum)
|
||||
// if (!result) return
|
||||
// downloadInfo = result
|
||||
// }
|
||||
// commit('setStatus', { downloadInfo, status: DOWNLOAD_STATUS.RUN })
|
||||
|
||||
let dl = dls.get(downloadInfo.id)
|
||||
if (dl) {
|
||||
// commit('updateFilePath', {
|
||||
// downloadInfo,
|
||||
// filePath: path.join(rootState.setting.download.savePath, downloadInfo.metadata.fileName),
|
||||
// })
|
||||
dl.updateSaveInfo(savePath, downloadInfo.metadata.fileName)
|
||||
if (tryNum.has(downloadInfo.id)) tryNum.set(downloadInfo.id, 0)
|
||||
try {
|
||||
await dl.start()
|
||||
} catch (error) {
|
||||
// commit('onError', { downloadInfo, errorMsg: error.message })
|
||||
// commit('setStatusText', error.message)
|
||||
// await dispatch('startTask')
|
||||
}
|
||||
} else {
|
||||
await createTask(downloadInfo, savePath)
|
||||
// await dispatch('handleStartTask', downloadInfo)
|
||||
}
|
||||
}
|
||||
|
||||
export const pauseTask = async(id: string) => {
|
||||
const dl = dls.get(id)
|
||||
if (dl) {
|
||||
dls.delete(id)
|
||||
tasks.delete(id)
|
||||
taskActions.delete(id)
|
||||
tryNum.delete(id)
|
||||
|
||||
try {
|
||||
await dl.stop()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
// commit('setStatus', { downloadInfo: downloadInfo, status: DOWNLOAD_STATUS.PAUSE })
|
||||
}
|
||||
|
||||
export const removeTask = async(id: string) => {
|
||||
const dl = dls.get(id)
|
||||
const downloadInfo = tasks.get(id)
|
||||
if (dl) {
|
||||
dls.delete(id)
|
||||
tasks.delete(id)
|
||||
taskActions.delete(id)
|
||||
tryNum.delete(id)
|
||||
|
||||
try {
|
||||
await dl.stop()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadInfo) {
|
||||
// 没有未完成、已下载大于1k
|
||||
if (!downloadInfo.isComplate && downloadInfo.total && downloadInfo.downloaded > 1024) {
|
||||
try {
|
||||
await removeFile(downloadInfo.metadata.filePath)
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { exposeWorker } from '../utils/worker'
|
||||
|
||||
import * as common from './common'
|
||||
import * as download from './download'
|
||||
|
||||
|
||||
console.log('hello download worker')
|
||||
|
||||
|
||||
exposeWorker(Object.assign({}, common, download))
|
||||
|
||||
export declare type workerDownloadTypes = typeof common &
|
||||
typeof download
|
||||
@@ -0,0 +1,103 @@
|
||||
import { DOWNLOAD_STATUS, QUALITYS } from '@common/constants'
|
||||
import { filterFileName } from '@common/utils/common'
|
||||
import { joinPath } from '@common/utils/nodejs'
|
||||
import fs from 'fs'
|
||||
|
||||
/**
|
||||
* 保存歌词文件
|
||||
* @param {*} filePath
|
||||
* @param {*} lrc
|
||||
* @param {*} format
|
||||
*/
|
||||
export const saveLrc = async(filePath: string, lrc: string, format: LX.LyricFormat) => {
|
||||
const iconv = await import('iconv-lite')
|
||||
switch (format) {
|
||||
case 'gbk':
|
||||
fs.writeFile(filePath, iconv.encode(lrc, 'gbk', { addBOM: true }), err => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
break
|
||||
case 'utf8':
|
||||
default:
|
||||
fs.writeFile(filePath, lrc, 'utf8', err => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export const getExt = (type: string): LX.Download.FileExt => {
|
||||
switch (type) {
|
||||
case 'ape':
|
||||
return 'ape'
|
||||
case 'flac':
|
||||
case 'flac24bit':
|
||||
return 'flac'
|
||||
case 'wav':
|
||||
return 'wav'
|
||||
case '128k':
|
||||
case '192k':
|
||||
case '320k':
|
||||
default:
|
||||
return 'mp3'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音乐音质
|
||||
* @param musicInfo
|
||||
* @param type
|
||||
* @param qualityList
|
||||
*/
|
||||
export const getMusicType = (musicInfo: LX.Music.MusicInfoOnline, type: LX.Quality, qualityList: LX.QualityList): LX.Quality => {
|
||||
let list = qualityList[musicInfo.source]
|
||||
if (!list) return '128k'
|
||||
if (!list.includes(type)) type = list[list.length - 1]
|
||||
const rangeType = QUALITYS.slice(QUALITYS.indexOf(type))
|
||||
for (const type of rangeType) {
|
||||
if (musicInfo.meta._qualitys[type]) return type
|
||||
}
|
||||
return '128k'
|
||||
}
|
||||
|
||||
// const checkExistList = (list: LX.Download.ListItem[], musicInfo: LX.Music.MusicInfo, type: LX.Quality, ext: string): boolean => {
|
||||
// return list.some(s => s.id === musicInfo.id && (s.metadata.type === type || s.metadata.ext === ext))
|
||||
// }
|
||||
|
||||
export const createDownloadInfo = (musicInfo: LX.Music.MusicInfoOnline, type: LX.Quality, fileName: string, savePath: string, qualityList: LX.QualityList) => {
|
||||
type = getMusicType(musicInfo, type, qualityList)
|
||||
let ext = getExt(type)
|
||||
const key = `${musicInfo.id}_${type}_${ext}`
|
||||
// if (checkExistList(list, musicInfo, type, ext)) return null
|
||||
const downloadInfo: LX.Download.ListItem = {
|
||||
id: key,
|
||||
isComplate: false,
|
||||
status: DOWNLOAD_STATUS.WAITING,
|
||||
statusText: '待下载',
|
||||
downloaded: 0,
|
||||
total: 0,
|
||||
progress: 0,
|
||||
speed: '',
|
||||
metadata: {
|
||||
musicInfo,
|
||||
url: null,
|
||||
quality: type,
|
||||
ext,
|
||||
filePath: '',
|
||||
fileName: filterFileName(`${fileName
|
||||
.replace('歌名', musicInfo.name)
|
||||
.replace('歌手', musicInfo.singer)}.${ext}`),
|
||||
},
|
||||
}
|
||||
downloadInfo.metadata.filePath = joinPath(savePath, downloadInfo.metadata.fileName)
|
||||
// commit('addTask', downloadInfo)
|
||||
|
||||
// 删除同路径下的同名文件
|
||||
// TODO
|
||||
// deleteFile(downloadInfo.metadata.filePath)
|
||||
// .catch(err => {
|
||||
// if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
|
||||
// })
|
||||
|
||||
return downloadInfo
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createMainWorker, createDownloadWorker } from './utils'
|
||||
|
||||
|
||||
export default () => {
|
||||
return {
|
||||
main: createMainWorker(),
|
||||
download: createDownloadWorker(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { tranditionalize } from '@renderer/utils/simplify-chinese-main'
|
||||
|
||||
export const langS2t = (textBase64: string): string => {
|
||||
const text = tranditionalize(Buffer.from(textBase64, 'base64').toString())
|
||||
return Buffer.from(text).toString('base64')
|
||||
}
|
||||
|
||||
export {
|
||||
saveLxConfigFile,
|
||||
readLxConfigFile,
|
||||
saveStrToFile,
|
||||
} from '@common/utils/nodejs'
|
||||
@@ -0,0 +1,15 @@
|
||||
import { exposeWorker } from '../utils/worker'
|
||||
|
||||
import * as common from './common'
|
||||
import * as list from './list'
|
||||
import * as music from './music'
|
||||
|
||||
|
||||
console.log('hello main worker')
|
||||
|
||||
|
||||
exposeWorker(Object.assign({}, common, list, music))
|
||||
|
||||
export declare type workerMainTypes = typeof common
|
||||
& typeof list
|
||||
& typeof music
|
||||
@@ -0,0 +1,289 @@
|
||||
// import { throttle } from '@common/utils'
|
||||
|
||||
import { filterFileName, sortInsert, similar } from '@common/utils/common'
|
||||
import { joinPath, saveStrToFile } from '@common/utils/nodejs'
|
||||
import { createLocalMusicInfo } from '@renderer/utils/music'
|
||||
|
||||
|
||||
/**
|
||||
* 过滤列表中已播放的歌曲
|
||||
*/
|
||||
export const filterMusicList = async({ playedList, listId, list, savePath, playerMusicInfo }: {
|
||||
/**
|
||||
* 已播放列表
|
||||
*/
|
||||
playedList: LX.Player.PlayMusicInfo[]
|
||||
/**
|
||||
* 列表id
|
||||
*/
|
||||
listId: string
|
||||
/**
|
||||
* 播放列表
|
||||
*/
|
||||
list: Array<LX.Music.MusicInfo | LX.Download.ListItem>
|
||||
/**
|
||||
* 下载目录
|
||||
*/
|
||||
savePath: string
|
||||
/**
|
||||
* 播放器内当前歌曲(`playInfo.playerPlayIndex`指向的歌曲)
|
||||
*/
|
||||
playerMusicInfo?: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
}) => {
|
||||
let playerIndex = -1
|
||||
|
||||
let canPlayList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = []
|
||||
const filteredPlayedList = playedList.filter(pmInfo => pmInfo.listId == listId && !pmInfo.isTempPlay).map(({ musicInfo }) => musicInfo)
|
||||
|
||||
const filteredList: Array<LX.Music.MusicInfo | LX.Download.ListItem> = list.filter(s => {
|
||||
// if (!assertApiSupport(s.source)) return false
|
||||
if ('progress' in s && !s.isComplate) return false
|
||||
|
||||
canPlayList.push(s)
|
||||
|
||||
let index = filteredPlayedList.findIndex(m => m.id == s.id)
|
||||
if (index > -1) {
|
||||
filteredPlayedList.splice(index, 1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if (playerMusicInfo) {
|
||||
playerIndex = (filteredList.length ? filteredList : canPlayList).findIndex(m => m.id == playerMusicInfo.id)
|
||||
}
|
||||
return {
|
||||
filteredList,
|
||||
canPlayList,
|
||||
playerIndex,
|
||||
}
|
||||
}
|
||||
|
||||
const getIntv = (musicInfo: LX.Music.MusicInfo) => {
|
||||
if (!musicInfo.interval) return 0
|
||||
// if (musicInfo._interval) return musicInfo._interval
|
||||
let intvArr = musicInfo.interval.split(':')
|
||||
let intv = 0
|
||||
let unit = 1
|
||||
while (intvArr.length) {
|
||||
intv += parseInt(intvArr.pop() as string) * unit
|
||||
unit *= 60
|
||||
}
|
||||
return intv
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序歌曲
|
||||
* @param list 歌曲列表
|
||||
* @param sortType 排序类型
|
||||
* @param fieldName 排序字段
|
||||
* @param localeId 排序语言
|
||||
* @returns
|
||||
*/
|
||||
export const sortListMusicInfo = async(list: LX.Music.MusicInfo[], sortType: 'up' | 'down', fieldName: 'name' | 'singer' | 'albumName' | 'interval' | 'source', localeId: string) => {
|
||||
// console.log(sortType, fieldName, localeId)
|
||||
// const locale = new Intl.Locale(localeId)
|
||||
if (sortType == 'up') {
|
||||
if (fieldName == 'interval') {
|
||||
list.sort((a, b) => {
|
||||
if (a.interval == null) {
|
||||
return b.interval == null ? 0 : -1
|
||||
} else return b.interval == null ? 1 : getIntv(a) - getIntv(b)
|
||||
})
|
||||
} else {
|
||||
switch (fieldName) {
|
||||
case 'name':
|
||||
case 'singer':
|
||||
case 'source':
|
||||
list.sort((a, b) => {
|
||||
if (a[fieldName] == null) {
|
||||
return b[fieldName] == null ? 0 : -1
|
||||
} else return b[fieldName] == null ? 1 : a[fieldName].localeCompare(b[fieldName], localeId)
|
||||
})
|
||||
break
|
||||
case 'albumName':
|
||||
list.sort((a, b) => {
|
||||
if (a.meta.albumName == null) {
|
||||
return b.meta.albumName == null ? 0 : -1
|
||||
} else return b.meta.albumName == null ? 1 : a.meta.albumName.localeCompare(b.meta.albumName, localeId)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (fieldName == 'interval') {
|
||||
list.sort((a, b) => {
|
||||
if (a.interval == null) {
|
||||
return b.interval == null ? 0 : 1
|
||||
} else return b.interval == null ? -1 : getIntv(b) - getIntv(a)
|
||||
})
|
||||
} else {
|
||||
switch (fieldName) {
|
||||
case 'name':
|
||||
case 'singer':
|
||||
case 'source':
|
||||
list.sort((a, b) => {
|
||||
if (a[fieldName] == null) {
|
||||
return b[fieldName] == null ? 0 : 1
|
||||
} else return b[fieldName] == null ? -1 : b[fieldName].localeCompare(a[fieldName], localeId)
|
||||
})
|
||||
break
|
||||
case 'albumName':
|
||||
list.sort((a, b) => {
|
||||
if (a.meta.albumName == null) {
|
||||
return b.meta.albumName == null ? 0 : 1
|
||||
} else return b.meta.albumName == null ? -1 : b.meta.albumName.localeCompare(a.meta.albumName, localeId)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
const variantRxp = /(\(|().+(\)|))/g
|
||||
const variantRxp2 = /\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[/g
|
||||
/**
|
||||
* 过滤列表内重复的歌曲
|
||||
* @param list 歌曲列表
|
||||
* @param isFilterVariant 是否过滤 Live Explicit 等歌曲名
|
||||
* @returns
|
||||
*/
|
||||
export const filterDuplicateMusic = async(list: LX.Music.MusicInfo[], isFilterVariant: boolean = true) => {
|
||||
type ListMapValue = Array<{ index: number, musicInfo: LX.Music.MusicInfo }>
|
||||
const listMap = new Map<string, ListMapValue>()
|
||||
const duplicateList = new Set<string>()
|
||||
const handleFilter = (name: string, index: number, musicInfo: LX.Music.MusicInfo) => {
|
||||
if (listMap.has(name)) {
|
||||
const targetMusicInfo = listMap.get(name)
|
||||
targetMusicInfo!.push({
|
||||
index,
|
||||
musicInfo,
|
||||
})
|
||||
duplicateList.add(name)
|
||||
} else {
|
||||
listMap.set(name, [{
|
||||
index,
|
||||
musicInfo,
|
||||
}])
|
||||
}
|
||||
}
|
||||
if (isFilterVariant) {
|
||||
list.forEach((musicInfo, index) => {
|
||||
let musicInfoName = musicInfo.name.toLowerCase().replace(variantRxp, '').replace(variantRxp2, '')
|
||||
if (!musicInfoName) musicInfoName = musicInfo.name.toLowerCase().replace(/\s+/g, '')
|
||||
handleFilter(musicInfoName, index, musicInfo)
|
||||
})
|
||||
} else {
|
||||
list.forEach((musicInfo, index) => {
|
||||
const musicInfoName = musicInfo.name.toLowerCase().trim()
|
||||
handleFilter(musicInfoName, index, musicInfo)
|
||||
})
|
||||
}
|
||||
// console.log(duplicateList)
|
||||
const duplicateNames = Array.from(duplicateList)
|
||||
duplicateNames.sort((a, b) => a.localeCompare(b))
|
||||
return duplicateNames.map(name => listMap.get(name) as ListMapValue).flat()
|
||||
}
|
||||
|
||||
export const searchListMusic = (list: LX.Music.MusicInfo[], text: string) => {
|
||||
let result: LX.Music.MusicInfo[] = []
|
||||
let rxp = new RegExp(text.split('').map(s => s.replace(/[.*+?^${}()|[\]\\]/, '\\$&')).join('.*') + '.*', 'i')
|
||||
for (const mInfo of list) {
|
||||
const str = `${mInfo.name}${mInfo.singer}${mInfo.meta.albumName ? mInfo.meta.albumName : ''}`
|
||||
if (rxp.test(str)) result.push(mInfo)
|
||||
}
|
||||
|
||||
const sortedList: any[] = []
|
||||
|
||||
for (const mInfo of result) {
|
||||
sortInsert(sortedList, {
|
||||
num: similar(text, `${mInfo.name}${mInfo.singer}${mInfo.meta.albumName ? mInfo.meta.albumName : ''}`),
|
||||
data: mInfo,
|
||||
})
|
||||
}
|
||||
return sortedList.map(item => item.data).reverse()
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建排序后的列表
|
||||
* @param list 原始列表
|
||||
* @param position 新位置
|
||||
* @param ids 要调整顺序的歌曲id
|
||||
* @returns
|
||||
*/
|
||||
export const createSortedList = (list: LX.Music.MusicInfo[], position: number, ids: string[]) => {
|
||||
const infos = Array(ids.length)
|
||||
for (let i = list.length; i--;) {
|
||||
const item = list[i]
|
||||
const index = ids.indexOf(item.id)
|
||||
if (index < 0) continue
|
||||
infos.splice(index, 1, list.splice(i, 1)[0])
|
||||
}
|
||||
list.splice(Math.min(position, list.length), 0, ...infos)
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建本地列表音乐信息
|
||||
* @param filePaths 文件路径
|
||||
*/
|
||||
export const createLocalMusicInfos = async(filePaths: string[]): Promise<LX.Music.MusicInfoLocal[]> => {
|
||||
const list: LX.Music.MusicInfoLocal[] = []
|
||||
for await (const path of filePaths) {
|
||||
const musicInfo = await createLocalMusicInfo(path)
|
||||
if (!musicInfo) continue
|
||||
list.push(musicInfo)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出列表到txt文件
|
||||
* @param savePath 保存路径
|
||||
* @param lists 列表数据
|
||||
* @param isMerge 是否合并
|
||||
*/
|
||||
export const exportPlayListToText = async(savePath: string, lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>, isMerge: boolean) => {
|
||||
const iconv = await import('iconv-lite')
|
||||
|
||||
if (isMerge) {
|
||||
await saveStrToFile(savePath,
|
||||
iconv.encode(lists.map(l => l.list.map(m => `${m.name} ${m.singer} ${m.meta.albumName ?? ''}`).join('\n')).join('\n\n'), 'utf8', { addBOM: true }))
|
||||
} else {
|
||||
for await (const list of lists) {
|
||||
await saveStrToFile(joinPath(savePath, `lx_list_${filterFileName(list.name)}.txt`),
|
||||
iconv.encode(list.list.map(m => `${m.name} ${m.singer} ${m.meta.albumName ?? ''}`).join('\n'), 'utf8', { addBOM: true }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出列表到csv文件
|
||||
* @param savePath 保存路径
|
||||
* @param lists 列表数据
|
||||
* @param isMerge 是否合并
|
||||
* @param header 表头名称
|
||||
*/
|
||||
export const exportPlayListToCSV = async(savePath: string,
|
||||
lists: Array<LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull | LX.List.UserListInfoFull>,
|
||||
isMerge: boolean,
|
||||
header: string) => {
|
||||
const iconv = await import('iconv-lite')
|
||||
|
||||
const filterStr = (str: string) => {
|
||||
if (!str) return ''
|
||||
str = str.replace(/"/g, '""')
|
||||
if (str.includes(',')) str = `"${str}"`
|
||||
return str
|
||||
}
|
||||
|
||||
if (isMerge) {
|
||||
await saveStrToFile(savePath, iconv.encode(header + lists.map(l => l.list.map(m => `${filterStr(m.name)},${filterStr(m.singer)},${filterStr(m.meta.albumName ?? '')}`).join('\n')).join('\n'), 'utf8', { addBOM: true }))
|
||||
} else {
|
||||
for await (const list of lists) {
|
||||
await saveStrToFile(joinPath(savePath, `lx_list_${filterFileName(list.name)}.csv`), iconv.encode(header + list.list.map(m => `${filterStr(m.name)},${filterStr(m.singer)},${filterStr(m.meta.albumName ?? '')}`).join('\n'), 'utf8', { addBOM: true }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getLocalMusicFileLyric, getLocalMusicFilePic, parseLyric } from '@renderer/utils/music'
|
||||
|
||||
|
||||
export const getMusicFilePic = async(filePath: string) => {
|
||||
const picture = await getLocalMusicFilePic(filePath)
|
||||
if (!picture) return ''
|
||||
return `data:${picture.format};base64,${picture.data.toString('base64')}`
|
||||
}
|
||||
|
||||
export const getMusicFileLyric = async(filePath: string): Promise<LX.Music.LyricInfo | null> => {
|
||||
const lyric = await getLocalMusicFileLyric(filePath)
|
||||
if (!lyric) return null
|
||||
return parseLyric(lyric)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as Comlink from 'comlink'
|
||||
|
||||
export declare type MainTypes = Comlink.Remote<LX.WorkerMainTypes>
|
||||
|
||||
export const createMainWorker = () => {
|
||||
const worker: Worker = new Worker(new URL(
|
||||
/* webpackChunkName: 'renderer.main.worker' */
|
||||
'../main',
|
||||
import.meta.url,
|
||||
))
|
||||
return Comlink.wrap<LX.WorkerMainTypes>(worker)
|
||||
}
|
||||
|
||||
// export const createWorker = <T>(url: string): Comlink.Remote<T> => {
|
||||
// // @ts-expect-error
|
||||
// const worker: Worker = new Worker(new URL(url, import.meta.url))
|
||||
// return Comlink.wrap<T>(worker)
|
||||
// // worker.addEventListener('message', (event: MessageEvent) => {
|
||||
|
||||
// // })
|
||||
// }
|
||||
|
||||
export declare type DownloadTypes = Comlink.Remote<LX.WorkerDownloadTypes>
|
||||
export const createDownloadWorker = () => {
|
||||
const worker: Worker = new Worker(new URL(
|
||||
/* webpackChunkName: 'renderer.download.worker' */
|
||||
'../download',
|
||||
import.meta.url,
|
||||
))
|
||||
return Comlink.wrap<LX.WorkerDownloadTypes>(worker)
|
||||
}
|
||||
|
||||
export const proxyCallback = <Args extends any[]>(callback: (...T: Args) => void) => {
|
||||
return Comlink.proxy(callback)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import * as Comlink from 'comlink'
|
||||
|
||||
|
||||
export const exposeWorker = (obj: any) => {
|
||||
Comlink.expose(obj)
|
||||
}
|
||||
Reference in New Issue
Block a user