mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 11:29:26 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7142991f7d | ||
|
|
a60e557ba2 |
@@ -131,6 +131,8 @@ npm build
|
||||
|
||||
- [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)
|
||||
- [YesPlayMusic](https://github.com/qier222/YesPlayMusic)
|
||||
- [UnblockNeteaseMusic](https://github.com/UnblockNeteaseMusic/server)
|
||||
- [BlurLyric](https://github.com/Project-And-Factory/BlurLyric)
|
||||
- [Vue-mmPlayer](https://github.com/maomao1996/Vue-mmPlayer)
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "splayer",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"author": "imsyy",
|
||||
"home": "https://imsyy.top",
|
||||
"github": "https://github.com/imsyy/SPlayer",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="logo" @click="router.push('/')">
|
||||
<img src="/images/logo/favicon.svg" alt="logo" />
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div :class="site.searchInputActive ? 'controls hidden' : 'controls'">
|
||||
<n-icon size="22" :component="Left" @click="router.go(-1)" />
|
||||
<n-icon size="22" :component="Right" @click="router.go(1)" />
|
||||
</div>
|
||||
@@ -68,13 +68,14 @@ import {
|
||||
SunOne,
|
||||
Moon,
|
||||
} from "@icon-park/vue-next";
|
||||
import { userStore, settingStore } from "@/store";
|
||||
import { userStore, settingStore, siteStore } from "@/store";
|
||||
import { useRouter } from "vue-router";
|
||||
import AboutSite from "@/components/DataModal/AboutSite.vue";
|
||||
import SearchInp from "@/components/SearchInp/index.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const user = userStore();
|
||||
const site = siteStore();
|
||||
const setting = settingStore();
|
||||
const aboutSiteRef = ref(null);
|
||||
const timeOut = ref(null);
|
||||
@@ -361,6 +362,7 @@ watch(
|
||||
|
||||
onMounted(() => {
|
||||
changeUserOptions(user.userLogin);
|
||||
console.log(router);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -402,7 +404,10 @@ nav {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@media (max-width: 520px) {
|
||||
display: none;
|
||||
&.hidden{
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
.n-icon {
|
||||
margin: 0 4px;
|
||||
|
||||
@@ -120,9 +120,9 @@
|
||||
>
|
||||
<n-icon
|
||||
v-if="music.getPlaySongTransl"
|
||||
:class="setting.getShowTransl ? 'open' : ''"
|
||||
:class="setting.showTransl ? 'open' : ''"
|
||||
:component="GTranslateFilled"
|
||||
@click="setting.setShowTransl(!setting.getShowTransl)"
|
||||
@click="setting.setShowTransl(!setting.showTransl)"
|
||||
/>
|
||||
<n-icon
|
||||
class="open"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:style="{ animationPlayState: music.getPlayState ? 'running' : 'paused' }"
|
||||
v-if="
|
||||
remainingPoint <= 2 &&
|
||||
totalDuration > 3 &&
|
||||
totalDuration > 1 &&
|
||||
music.getPlaySongLyric.lrc[0]
|
||||
"
|
||||
>
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
</span>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasTran &&
|
||||
setting.getShowTransl &&
|
||||
music.getPlaySongLyric.hasLrcTran &&
|
||||
setting.showTransl &&
|
||||
item.tran
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||
@@ -59,6 +59,17 @@
|
||||
>
|
||||
{{ item.tran }}</span
|
||||
>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasLrcRoma &&
|
||||
setting.showRoma &&
|
||||
item.roma
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||
class="lyric-roma"
|
||||
>
|
||||
{{ item.roma }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -88,11 +99,11 @@
|
||||
v-for="(v, i) in item.content"
|
||||
:key="i"
|
||||
:style="{
|
||||
'--dur': v.duration - 0.15 + 's',
|
||||
'--dur': `${Math.max(v.duration - 0.15, 0.1)}s`,
|
||||
}"
|
||||
:class="
|
||||
music.getPlaySongLyricIndex == index &&
|
||||
music.getPlaySongTime.currentTime + 0.15 >= v.time
|
||||
music.getPlaySongLyricIndex === index &&
|
||||
music.getPlaySongTime.currentTime + 0.15 > v.time
|
||||
? 'text fill'
|
||||
: 'text'
|
||||
"
|
||||
@@ -102,15 +113,26 @@
|
||||
</div>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasTran &&
|
||||
setting.getShowTransl &&
|
||||
music.getPlaySongLyric.hasYrcTran &&
|
||||
setting.showTransl &&
|
||||
item.tran
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1 + 'vh' }"
|
||||
class="lyric-fy"
|
||||
>
|
||||
{{ item.tran }}</span
|
||||
{{ item.tran }}
|
||||
</span>
|
||||
<span
|
||||
v-show="
|
||||
music.getPlaySongLyric.hasYrcRoma &&
|
||||
setting.showRoma &&
|
||||
item.roma
|
||||
"
|
||||
:style="{ fontSize: setting.lyricsFontSize - 1.5 + 'vh' }"
|
||||
class="lyric-roma"
|
||||
>
|
||||
{{ item.roma }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -268,7 +290,8 @@ const lrcTextClick = (time) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.lyric-fy {
|
||||
.lyric-fy,
|
||||
.lyric-roma {
|
||||
margin-top: 4px;
|
||||
transition: all 0.3s;
|
||||
opacity: 0.6;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="searchInp">
|
||||
<n-input
|
||||
:class="inputActive ? 'input focus' : 'input'"
|
||||
:class="site.searchInputActive ? 'input focus' : 'input'"
|
||||
:input-props="{ autoComplete: false }"
|
||||
ref="searchInpRef"
|
||||
round
|
||||
clearable
|
||||
placeholder="搜索音乐/视频"
|
||||
@@ -14,7 +15,7 @@
|
||||
<template #prefix>
|
||||
<n-icon
|
||||
size="16"
|
||||
:class="inputActive ? 'active' : ''"
|
||||
:class="site.searchInputActive ? 'active' : ''"
|
||||
:component="Search"
|
||||
/>
|
||||
</template>
|
||||
@@ -23,7 +24,7 @@
|
||||
<n-card
|
||||
class="list"
|
||||
v-show="
|
||||
inputActive &&
|
||||
site.searchInputActive &&
|
||||
!inputValue &&
|
||||
(music.getSearchHistory[0] || searchData.hot[0])
|
||||
"
|
||||
@@ -93,7 +94,7 @@
|
||||
<CollapseTransition easing="ease-in-out">
|
||||
<n-card
|
||||
class="list"
|
||||
v-show="inputActive && inputValue && searchData.suggest"
|
||||
v-show="site.searchInputActive && inputValue && searchData.suggest"
|
||||
content-style="padding: 0"
|
||||
>
|
||||
<n-scrollbar>
|
||||
@@ -195,20 +196,21 @@ import {
|
||||
import CollapseTransition from "@ivanv/vue-collapse-transition/src/CollapseTransition.vue";
|
||||
import debounce from "@/utils/debounce";
|
||||
import { useRouter } from "vue-router";
|
||||
import { musicStore, settingStore } from "@/store";
|
||||
import { musicStore, settingStore, siteStore } from "@/store";
|
||||
|
||||
const router = useRouter();
|
||||
const music = musicStore();
|
||||
const setting = settingStore();
|
||||
const site = siteStore();
|
||||
|
||||
// 输入框内容
|
||||
const inputValue = ref(null);
|
||||
|
||||
// 输入框激活状态
|
||||
const inputActive = ref(false);
|
||||
const searchInpRef = ref(null);
|
||||
|
||||
// 输入框激活事件
|
||||
const inputFocus = () => {
|
||||
inputActive.value = true;
|
||||
searchInpRef.value?.focus();
|
||||
site.searchInputActive = true;
|
||||
music.showPlayList = false;
|
||||
getSearchHotData();
|
||||
};
|
||||
@@ -274,7 +276,8 @@ const toSearch = (val, type) => {
|
||||
const inputkeydown = (e) => {
|
||||
if (e.key === "Enter" && inputValue.value != null) {
|
||||
console.log("执行搜索" + inputValue.value.trim());
|
||||
inputActive.value = false;
|
||||
searchInpRef.value?.blur();
|
||||
site.searchInputActive = false;
|
||||
// 写入搜索历史
|
||||
music.setSearchHistory(inputValue.value.trim());
|
||||
router.push({
|
||||
@@ -306,13 +309,15 @@ onMounted(() => {
|
||||
getSearchHotData();
|
||||
// 搜索框失焦
|
||||
document.addEventListener("click", () => {
|
||||
inputActive.value = false;
|
||||
searchInpRef.value?.blur();
|
||||
site.searchInputActive = false;
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", () => {
|
||||
inputActive.value = false;
|
||||
searchInpRef.value?.blur();
|
||||
site.searchInputActive = false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -333,7 +338,10 @@ watch(
|
||||
watch(
|
||||
() => music.showPlayList,
|
||||
(val) => {
|
||||
if (val) inputActive.value = false;
|
||||
if (val) {
|
||||
searchInpRef.value?.blur();
|
||||
site.searchInputActive = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -232,22 +232,22 @@ const useMusicDataStore = defineStore("musicData", {
|
||||
setLikeSong(id, like).then((res) => {
|
||||
if (res.code == 200) {
|
||||
list.push(id);
|
||||
$message.info("成功喜欢歌曲");
|
||||
$message.info("已添加到我喜欢的音乐");
|
||||
} else {
|
||||
$message.error("喜欢歌曲时发生错误");
|
||||
$message.error("喜欢音乐时发生错误");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$message.info("我喜欢的列表中已存在该歌曲");
|
||||
$message.info("我喜欢的音乐中已存在该歌曲");
|
||||
}
|
||||
} else {
|
||||
if (exists) {
|
||||
setLikeSong(id, like).then((res) => {
|
||||
if (res.code == 200) {
|
||||
list.splice(list.indexOf(id), 1);
|
||||
$message.info("成功取消喜欢歌曲");
|
||||
$message.info("已从我喜欢的音乐中移除");
|
||||
} else {
|
||||
$message.error("取消喜欢歌曲时发生错误");
|
||||
$message.error("取消喜欢音乐时发生错误");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,8 @@ const useSettingDataStore = defineStore("settingData", {
|
||||
showYrc: true,
|
||||
// 是否显示歌词翻译
|
||||
showTransl: true,
|
||||
// 是否显示歌词音译
|
||||
showRoma: true,
|
||||
// 歌曲音质
|
||||
songLevel: "exhigh",
|
||||
// 歌词位置
|
||||
@@ -53,10 +55,6 @@ const useSettingDataStore = defineStore("settingData", {
|
||||
getSiteTheme(state) {
|
||||
return state.theme;
|
||||
},
|
||||
// 获取是否开启翻译
|
||||
getShowTransl(state) {
|
||||
return state.showTransl;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 切换明暗模式
|
||||
|
||||
@@ -7,6 +7,8 @@ const useSiteDataStore = defineStore("siteData", {
|
||||
siteTitle: "SPlayer",
|
||||
// 封面主题色
|
||||
songPicColor: "rgb(128,128,128)",
|
||||
// 搜索框激活状态
|
||||
searchInputActive: false,
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* 格式化歌词字符串为时间轴和歌词文本的数组
|
||||
* @param {string} lrc 歌词字符串
|
||||
* @returns {Array<{ time: number, lyric: string }>} 时间轴和歌词文本的数组
|
||||
*/
|
||||
const lyricFormat = (lrc) => {
|
||||
// 匹配时间轴和歌词文本的正则表达式
|
||||
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
||||
// 将歌词字符串按行分割为数组
|
||||
const lines = lrc.split("\n");
|
||||
// 对每一行进行转换
|
||||
const result = lines
|
||||
// 筛选出包含时间轴和歌词文本的行
|
||||
.filter((line) => regex.test(line))
|
||||
// 转换时间轴和歌词文本为对象
|
||||
.map((line) => {
|
||||
const [, time, text] = line.match(regex);
|
||||
const parts = time.split(":");
|
||||
let seconds = parseInt(parts[0]) * 60 + parseFloat(parts[1]);
|
||||
if (parts.length > 2) {
|
||||
seconds += parseFloat(parts[2]) / 1000;
|
||||
}
|
||||
return { time: seconds, lyric: text.trim() };
|
||||
}).filter((element) => element.lyric.trim() !== "");
|
||||
// 检查是否为纯音乐,是则返回空数组
|
||||
if (result.length && /纯音乐,请欣赏/.test(result[0].lyric)) {
|
||||
console.log("该歌曲为纯音乐");
|
||||
return [];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
export default lyricFormat;
|
||||
@@ -1,193 +1,191 @@
|
||||
/**
|
||||
* 将接口数据解析出对应数据
|
||||
* 将歌词接口数据解析出对应数据
|
||||
* @param {string} data 接口数据
|
||||
* @returns {Array} 对应数据
|
||||
*/
|
||||
const parseLyric = (data) => {
|
||||
// 判断是否具有内容
|
||||
const checkLyric = (lyric) => (lyric ? (lyric.lyric ? true : false) : false);
|
||||
// 初始化数据
|
||||
const { lrc, tlyric, romalrc, yrc, ytlrc } = data;
|
||||
const lyrics = lrc ? lrc.lyric : null;
|
||||
const otherLyrics = {
|
||||
tran: tlyric ? tlyric.lyric : null,
|
||||
roma: romalrc ? romalrc.lyric : null,
|
||||
yrc: yrc ? yrc.lyric : null,
|
||||
ytlrc: ytlrc ? ytlrc.lyric : null,
|
||||
const { lrc, tlyric, romalrc, yrc, ytlrc, yromalrc } = data;
|
||||
const lrcData = {
|
||||
lrc: lrc?.lyric || null,
|
||||
tlyric: tlyric?.lyric || null,
|
||||
romalrc: romalrc?.lyric || null,
|
||||
yrc: yrc?.lyric || null,
|
||||
ytlrc: ytlrc?.lyric || null,
|
||||
yromalrc: yromalrc?.lyric || null,
|
||||
};
|
||||
// 初始化输出结果
|
||||
let result = {
|
||||
lrc: [], // 歌词数组 {time:时间,content:歌词}
|
||||
yrc: [], // 逐字歌词数据
|
||||
// 是否具有翻译
|
||||
hasTran: tlyric ? (tlyric.lyric ? true : false) : false,
|
||||
// 是否具有音译
|
||||
hasRoma: romalrc ? (romalrc.lyric ? true : false) : false,
|
||||
const result = {
|
||||
// 是否具有普通翻译
|
||||
hasLrcTran: checkLyric(tlyric),
|
||||
// 是否具有普通音译
|
||||
hasLrcRoma: checkLyric(romalrc),
|
||||
// 是否具有逐字歌词
|
||||
hasYrc: yrc ? (yrc.lyric ? true : false) : false,
|
||||
hasYrc: checkLyric(yrc),
|
||||
// 是否具有逐字翻译
|
||||
hasYrcTran: checkLyric(ytlrc),
|
||||
// 是否具有逐字音译
|
||||
hasYrcRoma: checkLyric(yromalrc),
|
||||
// 普通歌词数组
|
||||
lrc: [],
|
||||
// 逐字歌词数据
|
||||
yrc: [],
|
||||
};
|
||||
// 普通歌词数据
|
||||
let lrcData = Lrcsplit(lyrics);
|
||||
// 翻译歌词数据
|
||||
let tranLrcData = null;
|
||||
// 循环遍历 otherLyrics 参数对象
|
||||
for (let i in otherLyrics) {
|
||||
const element = otherLyrics[i];
|
||||
if (element !== null) {
|
||||
// 若存在逐字歌词
|
||||
if (i == "yrc" && otherLyrics[i] != null) {
|
||||
result[i] = parseYrc(otherLyrics[i]);
|
||||
continue;
|
||||
}
|
||||
// 若存在翻译
|
||||
if (i == "ytlrc" && element != null) {
|
||||
tranLrcData = Lrcsplit(element);
|
||||
for (let num in tranLrcData) {
|
||||
// 翻译文本对齐
|
||||
let objNum = result["yrc"].findIndex(
|
||||
(o) => o.time == tranLrcData[num].time
|
||||
);
|
||||
if (objNum != -1)
|
||||
result["yrc"][objNum]["tran"] = tranLrcData[num].content;
|
||||
}
|
||||
}
|
||||
// 若存在其他翻译
|
||||
tranLrcData = Lrcsplit(element);
|
||||
if (tranLrcData[0]) {
|
||||
console.log(`歌曲存在 ${i} 翻译`, tranLrcData);
|
||||
for (let num in tranLrcData) {
|
||||
// 翻译文本对齐
|
||||
let objNum = lrcData.findIndex(
|
||||
(o) => o.time == tranLrcData[num].time
|
||||
);
|
||||
if (objNum != -1) lrcData[objNum][i] = tranLrcData[num].content;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 普通歌词
|
||||
if (lrcData.lrc) {
|
||||
result.lrc = parseLrc(lrcData.lrc);
|
||||
//判断是否有其他翻译
|
||||
result.lrc = lrcData.tlyric
|
||||
? parseOtherLrc(result.lrc, parseLrc(lrcData.tlyric), "tran")
|
||||
: result.lrc;
|
||||
result.lrc = lrcData.romalrc
|
||||
? parseOtherLrc(result.lrc, parseLrc(lrcData.romalrc), "roma")
|
||||
: result.lrc;
|
||||
}
|
||||
// 逐字歌词
|
||||
if (lrcData.yrc) {
|
||||
result.yrc = parseYrc(lrcData.yrc);
|
||||
//判断是否有其他翻译
|
||||
result.yrc = lrcData.ytlrc
|
||||
? parseOtherLrc(result.yrc, parseLrc(lrcData.ytlrc), "tran")
|
||||
: result.yrc;
|
||||
result.yrc = lrcData.yromalrc
|
||||
? parseOtherLrc(result.yrc, parseLrc(lrcData.yromalrc), "roma")
|
||||
: result.yrc;
|
||||
}
|
||||
// 将歌词按时间排序
|
||||
result.lrc = lrcData.sort((a, b) => {
|
||||
return a.t - b.t;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 将歌词字符串解析为歌词对象数组
|
||||
* 翻译文本对齐
|
||||
* @param {string} lrc 歌词字符串
|
||||
* @param {string} tranLrc 翻译歌词字符串
|
||||
* @returns {Array} 包含翻译的歌词对象数组
|
||||
*/
|
||||
const parseOtherLrc = (lrc, tranLrc, name) => {
|
||||
const lyric = lrc;
|
||||
const tranLyric = tranLrc;
|
||||
if (lyric[0] && tranLyric[0]) {
|
||||
lyric.forEach((v) => {
|
||||
tranLyric.forEach((x) => {
|
||||
if (
|
||||
Number(v.time) === Number(x.time) ||
|
||||
Math.abs(Number(v.time) - Number(x.time)) < 0.6
|
||||
) {
|
||||
v[name] = x.content;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return lyric;
|
||||
};
|
||||
|
||||
/**
|
||||
* 普通歌词解析
|
||||
* @param {string} lyrics 歌词字符串
|
||||
* @returns {Array} 歌词对象数组
|
||||
*/
|
||||
const Lrcsplit = (lrc) => {
|
||||
const lyrics = lrc.split("\n");
|
||||
const lrcData = [];
|
||||
lyrics.forEach((lyric) => {
|
||||
lyric = lyric.replace(/(^\s*)|(\s*$)/g, "");
|
||||
const time = lyric.substring(lyric.indexOf("[") + 1, lyric.indexOf("]"));
|
||||
const timeArr = time.split(":");
|
||||
if (isNaN(parseInt(timeArr[0]))) {
|
||||
for (let i in lyrics) {
|
||||
if (i != "lrc" && i == timeArr[0].toLowerCase()) {
|
||||
lyrics[i] = timeArr[1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const lyricArr = lyric.match(/\[(\d+:.+?)\]/g);
|
||||
let start = 0;
|
||||
for (let k in lyricArr) {
|
||||
start += lyricArr[k].length;
|
||||
}
|
||||
const content = lyric.substring(start);
|
||||
if (content == "") {
|
||||
return false;
|
||||
}
|
||||
lyricArr.forEach((t) => {
|
||||
let time = t.substring(1, t.length - 1);
|
||||
let second = time.split(":");
|
||||
if (
|
||||
(parseFloat(second[0]) * 60 + parseFloat(second[1])).toFixed(3) == 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
lrcData.push({
|
||||
time: (parseFloat(second[0]) * 60 + parseFloat(second[1])).toFixed(3),
|
||||
content: content,
|
||||
});
|
||||
});
|
||||
const parseLrc = (lyrics) => {
|
||||
if (!lyrics) return [];
|
||||
try {
|
||||
// 匹配时间轴和歌词文本的正则表达式
|
||||
const regex = /^\[([^\]]+)\]\s*(.+?)\s*$/;
|
||||
// 将歌词字符串按行分割为数组
|
||||
const lines = lyrics.split("\n");
|
||||
// 对每一行进行转换
|
||||
const parsedLyrics = lines
|
||||
// 筛选出包含时间轴和歌词文本的行
|
||||
.filter((line) => regex.test(line))
|
||||
// 转换时间轴和歌词文本为对象
|
||||
.map((line) => {
|
||||
const [, time, text] = line.match(regex);
|
||||
const parts = time.split(":");
|
||||
const seconds =
|
||||
Number(parts[0]) * 60 +
|
||||
Number(parts[1]) +
|
||||
(parts.length > 2 ? Number(parts[2]) / 1000 : 0);
|
||||
return { time: Number(seconds.toFixed(2)), content: text.trim() };
|
||||
})
|
||||
.filter((c) => c.content.trim() !== "");
|
||||
// 检查是否为纯音乐,是则返回空数组
|
||||
if (parsedLyrics.length && /纯音乐,请欣赏/.test(parsedLyrics[0].content)) {
|
||||
console.log("该歌曲为纯音乐");
|
||||
return [];
|
||||
}
|
||||
});
|
||||
return lrcData;
|
||||
return parsedLyrics;
|
||||
} catch (err) {
|
||||
console.error("普通歌词处理出错:" + err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 逐字歌词解析
|
||||
* @param {string} lyrics 歌词字符串
|
||||
* @param {string} lyrics 逐字歌词字符串
|
||||
* @returns {Array} 歌词对象数组
|
||||
*/
|
||||
const parseYrc = (lyrics) => {
|
||||
// 若无内容,则返回空数组
|
||||
if (lyrics == undefined) {
|
||||
if (!lyrics) return [];
|
||||
try {
|
||||
// 遍历每一行逐字歌词
|
||||
const parsedLyrics = lyrics
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
// 匹配每一行中的时间戳信息
|
||||
const timeReg = /\[(\d+),(\d+)\]/;
|
||||
const timeMatch = line.match(timeReg);
|
||||
if (!timeMatch) {
|
||||
return null;
|
||||
}
|
||||
// 解构出起始时间和结束时间
|
||||
const [_, startTime, endTime] = timeMatch;
|
||||
if (isNaN(startTime) || isNaN(endTime)) {
|
||||
return null;
|
||||
}
|
||||
// 去除当前行中的时间戳信息,得到歌词内容
|
||||
const content = line.replace(timeReg, "");
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
// 对歌词内容中的时间戳和歌词内容分离
|
||||
const contentArray = content
|
||||
.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^\(]*)/g)
|
||||
.filter((c) => c.trim())
|
||||
.map((c) => {
|
||||
// 匹配当前片段中的时间戳信息
|
||||
const timeReg = /\((\d+),(\d+),(\d+)\)/;
|
||||
const timeMatch = c.match(timeReg);
|
||||
if (!timeMatch) {
|
||||
return null;
|
||||
}
|
||||
// 解构出时间戳,持续时间和歌词内容
|
||||
const [_, time, duration] = timeMatch;
|
||||
const content = c.replace(timeReg, "");
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
time: Number(time) / 1000,
|
||||
duration: Number(duration) / 1000,
|
||||
content,
|
||||
};
|
||||
})
|
||||
.filter((c) => c);
|
||||
// 返回当前行解析出的时间信息和歌词内容信息
|
||||
return {
|
||||
time: Number(startTime) / 1000,
|
||||
endTime: Number(endTime) / 1000,
|
||||
content: contentArray,
|
||||
};
|
||||
})
|
||||
.filter((line) => line);
|
||||
return parsedLyrics;
|
||||
} catch (err) {
|
||||
console.error("逐字歌词处理出错:" + err);
|
||||
return [];
|
||||
}
|
||||
let lines = lyrics.split("\n");
|
||||
let parsedLyrics = [];
|
||||
// 解析每一句
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
// 创建一个空对象,用于存放当前句的信息
|
||||
let parsedLine = {
|
||||
time: undefined, // 开始时间
|
||||
endTime: undefined, // 结束时间
|
||||
content: undefined, // 歌词内容
|
||||
};
|
||||
// 分离出时间信息,并转换为秒
|
||||
let timeInfo = line
|
||||
.substring(line.indexOf("[") + 1, line.indexOf("]"))
|
||||
.split(",");
|
||||
// 将开始时间转换为秒
|
||||
parsedLine.time = Number(timeInfo[0]) / 1000;
|
||||
// 将结束时间转换为秒
|
||||
parsedLine.endTime = Number(timeInfo[1]) / 1000;
|
||||
// 若时间信息不合法,将跳过该句
|
||||
if (isNaN(parsedLine.time) || isNaN(parsedLine.endTime)) {
|
||||
continue;
|
||||
}
|
||||
// 寻找歌词内容
|
||||
const lyricArr = line.match(/\[[1-9]\d*,[1-9]\d*]/g);
|
||||
if (!lyricArr) {
|
||||
continue;
|
||||
}
|
||||
// 去除时间信息,获取歌词内容
|
||||
let contentArray = [];
|
||||
// 分离成单个字或词,并解析时间信息
|
||||
let splitContent = line.split(/(\([1-9]\d*,[1-9]\d*,\d*\)[^\(]*)/g);
|
||||
for (let j = 0; j < splitContent.length; j++) {
|
||||
const splitc = splitContent[j];
|
||||
if (splitc == "") {
|
||||
continue;
|
||||
}
|
||||
// 创建一个对象,用于存放当前字或词的信息,并添加到当前句的歌词内容中
|
||||
let contentObj = {
|
||||
time: undefined, // 开始时间
|
||||
duration: undefined, // 持续时间
|
||||
content: "", // 字或词的文本内容
|
||||
};
|
||||
// 提取时间和文本信息,并转换为秒
|
||||
let time = splitc.match(/\([1-9]\d*,[1-9]\d*,\d*\)/);
|
||||
if (!time) {
|
||||
continue;
|
||||
}
|
||||
let timeArray = time[0].slice(1, -1).split(",");
|
||||
// 将开始时间转换为秒
|
||||
contentObj.time = Number(timeArray[0]) / 1000;
|
||||
// 将持续时间转换为秒
|
||||
contentObj.duration = Number(timeArray[1]) / 1000;
|
||||
// 获取字或词的文本内容
|
||||
contentObj.content = splitc.slice(time[0].length);
|
||||
contentArray.push(contentObj);
|
||||
}
|
||||
parsedLine.content = contentArray;
|
||||
parsedLyrics.push(parsedLine);
|
||||
}
|
||||
|
||||
return parsedLyrics;
|
||||
};
|
||||
|
||||
export default parseLyric;
|
||||
|
||||
@@ -44,9 +44,19 @@
|
||||
/>
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">显示歌词翻译</div>
|
||||
<div class="name">
|
||||
显示歌词翻译
|
||||
<span class="tip">是否在具有翻译歌词时显示</span>
|
||||
</div>
|
||||
<n-switch v-model:value="showTransl" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
显示歌词音译
|
||||
<span class="tip">是否在具有音译歌词时显示</span>
|
||||
</div>
|
||||
<n-switch v-model:value="showRoma" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item">
|
||||
<div class="name">
|
||||
显示前奏等待
|
||||
@@ -164,6 +174,7 @@ const {
|
||||
lyricsBlur,
|
||||
lrcMousePause,
|
||||
showYrc,
|
||||
showRoma,
|
||||
useUnmServer,
|
||||
backgroundImageShow,
|
||||
countDownShow,
|
||||
@@ -226,7 +237,7 @@ const changeMusicFrequency = () => {
|
||||
$dialog.warning({
|
||||
class: "s-dialog",
|
||||
title: "实验性功能",
|
||||
content: "确认开启音乐频谱?将于刷新后生效",
|
||||
content: "确认开启音乐频谱?将在重启应用后生效",
|
||||
positiveText: "开启",
|
||||
negativeText: "取消",
|
||||
onMaskClick: () => {
|
||||
@@ -234,7 +245,6 @@ const changeMusicFrequency = () => {
|
||||
},
|
||||
onPositiveClick: () => {
|
||||
musicFrequency.value = true;
|
||||
location.reload();
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
musicFrequency.value = false;
|
||||
|
||||
Reference in New Issue
Block a user