v2.0.0
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
|
||||
import { formatPlayTime } from '@common/utils/common'
|
||||
|
||||
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
||||
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
|
||||
(await checkPath(musicInfo.metadata.filePath) || await checkPath(joinPath(savePath, musicInfo.metadata.fileName)))
|
||||
}
|
||||
|
||||
export const checkLocalFileAvailable = async(musicInfo: LX.Music.MusicInfoLocal): Promise<boolean> => {
|
||||
return await checkPath(musicInfo.meta.filePath)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查音乐文件是否存在
|
||||
* @param musicInfo
|
||||
* @param savePath
|
||||
*/
|
||||
export const checkMusicFileAvailable = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return await checkDownloadFileAvailable(musicInfo, savePath)
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return await checkLocalFileAvailable(musicInfo)
|
||||
} else return true
|
||||
}
|
||||
|
||||
export const getDownloadFilePath = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<string> => {
|
||||
if (musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName)) {
|
||||
if (await checkPath(musicInfo.metadata.filePath)) return musicInfo.metadata.filePath
|
||||
const path = joinPath(savePath, musicInfo.metadata.fileName)
|
||||
if (await checkPath(path)) return path
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const getLocalFilePath = async(musicInfo: LX.Music.MusicInfoLocal): Promise<string> => {
|
||||
return await checkPath(musicInfo.meta.filePath) ? musicInfo.meta.filePath : ''
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取音乐文件路径
|
||||
* @param musicInfo
|
||||
* @param savePath
|
||||
* @returns
|
||||
*/
|
||||
export const getMusicFilePath = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, savePath: string): Promise<string> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return await getDownloadFilePath(musicInfo, savePath)
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return await getLocalFilePath(musicInfo)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建本地音乐信息对象
|
||||
* @param path 文件路径
|
||||
* @returns
|
||||
*/
|
||||
export const createLocalMusicInfo = async(path: string): Promise<LX.Music.MusicInfoLocal | null> => {
|
||||
if (!await checkPath(path)) return null
|
||||
const { parseFile } = await import('music-metadata')
|
||||
|
||||
let metadata
|
||||
try {
|
||||
metadata = await parseFile(path)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
|
||||
// console.log(metadata)
|
||||
let ext = extname(path)
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
let name = (metadata.common.title || basename(path, ext)).trim()
|
||||
let singer = metadata.common.artists?.length ? metadata.common.artists.map(a => a.trim()).join('、') : ''
|
||||
let interval = metadata.format.duration ? formatPlayTime(metadata.format.duration) : ''
|
||||
let albumName = metadata.common.album?.trim() ?? ''
|
||||
|
||||
return {
|
||||
id: path,
|
||||
name,
|
||||
singer,
|
||||
source: 'local',
|
||||
interval,
|
||||
meta: {
|
||||
albumName,
|
||||
filePath: path,
|
||||
songId: path,
|
||||
picUrl: '',
|
||||
ext: ext.replace(/^\./, ''),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let prevFileInfo: {
|
||||
path: string
|
||||
promise: Promise<LX.MusicMetadataModule.IAudioMetadata | null>
|
||||
} = {
|
||||
path: '',
|
||||
promise: Promise.resolve(null),
|
||||
}
|
||||
const getFileMetadata = async(path: string) => {
|
||||
if (prevFileInfo.path == path) return prevFileInfo.promise
|
||||
prevFileInfo.path = path
|
||||
return prevFileInfo.promise = checkPath(path).then(async(isExist) => {
|
||||
return isExist ? import('music-metadata').then(async({ parseFile }) => parseFile(path)) : null
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取歌曲文件封面图片
|
||||
* @param path 路径
|
||||
*/
|
||||
export const getLocalMusicFilePic = async(path: string) => {
|
||||
const metadata = await getFileMetadata(path)
|
||||
if (!metadata) return null
|
||||
const { selectCover } = await import('music-metadata')
|
||||
return selectCover(metadata.common.picture)
|
||||
}
|
||||
|
||||
const timeExp = /^\[([\d:.]*)\]{1}/
|
||||
/**
|
||||
* 解析歌词文件,分离可能存在的翻译、罗马音歌词
|
||||
* @param lrc 歌词内容
|
||||
* @returns
|
||||
*/
|
||||
export const parseLyric = (lrc: string): LX.Music.LyricInfo => {
|
||||
const lines = lrc.split(/\r\n|\r|\n/)
|
||||
const lyrics: string[][] = []
|
||||
const map = new Map<string, number>()
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim()
|
||||
let result = timeExp.exec(line)
|
||||
if (result) {
|
||||
const index = map.get(result[1]) ?? 0
|
||||
if (!lyrics[index]) lyrics[index] = []
|
||||
lyrics[index].push(line)
|
||||
map.set(result[1], index + 1)
|
||||
} else {
|
||||
if (!lyrics[0]) lyrics[0] = []
|
||||
lyrics[0].push(line)
|
||||
}
|
||||
}
|
||||
const lyricInfo: LX.Music.LyricInfo = {
|
||||
lyric: lyrics[0].join('\n'),
|
||||
tlyric: '',
|
||||
}
|
||||
if (lyrics[1]) lyricInfo.tlyric = lyrics[1].join('\n')
|
||||
if (lyrics[2]) lyricInfo.rlyric = lyrics[2].join('\n')
|
||||
|
||||
return lyricInfo
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取歌曲文件歌词
|
||||
* @param path 路径
|
||||
*/
|
||||
export const getLocalMusicFileLyric = async(path: string): Promise<string | null> => {
|
||||
// 尝试读取同目录下的同名lrc文件
|
||||
const lrcPath = path.replace(new RegExp('\\' + extname(path) + '$'), '.lrc')
|
||||
const stats = await getFileStats(lrcPath)
|
||||
// console.log(lrcPath, stats)
|
||||
if (stats && stats.size < 1024 * 1024 * 10) {
|
||||
const lrcBuf = await readFile(lrcPath)
|
||||
const { detect } = await import('jschardet')
|
||||
const { confidence, encoding } = detect(lrcBuf)
|
||||
console.log('lrc file encoding', confidence, encoding)
|
||||
if (confidence > 0.8) {
|
||||
const iconv = await import('iconv-lite')
|
||||
if (iconv.encodingExists(encoding)) {
|
||||
const lrc = iconv.decode(lrcBuf, encoding)
|
||||
if (lrc) return lrc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试读取文件内歌词
|
||||
const metadata = await getFileMetadata(path)
|
||||
if (!metadata) return null
|
||||
if (metadata.common.lyrics?.length && metadata.common.lyrics[0].length > 10) {
|
||||
return metadata.common.lyrics[0]
|
||||
}
|
||||
// console.log(metadata)
|
||||
for (const info of Object.values(metadata.native)) {
|
||||
const ust = info.find(i => i.id == 'USLT')
|
||||
if (ust && ust.value.text.length > 10) return ust.value.text
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user