实现原生lrc歌词同步之超级简化版
本帖最后由 马黑黑 于 2024-6-18 12:21 编辑 <br /><br /><style>.art p { font: normal 18px/24px sans-serif; margin: 10px 0; }
.mum { position: relative; margin: 20px 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thin solid lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>
<div class="art">
<p>先看原生lrc歌词的解析,我创建了一个函数,全程仅用10行代码将歌词转换成花潮格式的lrc歌词数组:</p>
<div class='mum'>
<cl-cd data-idx="1"><span class="tBlue">var</span> lrcAr = [];</cl-cd>
<cl-cd data-idx="2"> </cl-cd>
<cl-cd data-idx="3"><span class="tBlue">var</span> getLrcAr = (text) => {</cl-cd>
<cl-cd data-idx="4"> <span class="tBlue">var</span> ar = text.trim().split(<span class="tMagenta">'\n'</span>);</cl-cd>
<cl-cd data-idx="5"> ar.sort();</cl-cd>
<cl-cd data-idx="6"> <span class="tBlue">var</span> reg = /\[(\d+)[.:](\d+)[.:](\d+)\](.*)/;</cl-cd>
<cl-cd data-idx="7"> ar.forEach(item => {</cl-cd>
<cl-cd data-idx="8"> <span class="tBlue">let</span> result = item.match(reg);</cl-cd>
<cl-cd data-idx="9"> <span class="tBlue">let</span> tmsg = parseInt(result) * 60 + parseInt(result) + parseInt(result) / 1000;</cl-cd>
<cl-cd data-idx="10"> lrcAr.push(.trim()]);</cl-cd>
<cl-cd data-idx="11"> });</cl-cd>
<cl-cd data-idx="12">};</cl-cd>
</div>
<p>函数所需参数 text 是原生lrc歌词字符串。函数先将歌词去除头尾空白后按行拆分为数组并进行排序,然后逐行按正则进行解析、提取所需信息再加入事先声明的全局数组变量 lrcAr。正则表达式的设计是简化处理机制的关键,代码中 reg 变量值是一个正则,它分成四个提取组,匹配成功后将拿到5个数组元素的结果,后四个对应于分、秒、毫秒和歌词。</p>
<p>传参执行函数,拿到的结果是花潮格式的lrcAr数组,这意味着众多歌词同步的插件可以派上用场了。</p>
<p>需要注意的是:</p>
<p>一、支持的原生lrc歌词格式</p>
<blockquote>
① 歌词<br>
② 歌词<br>
③ 歌词<br>
④ 歌词
</blockquote>
<p>前两种较为常见,后两种偶尔会见到。这些格式的原生lrc歌词,可以混合出现在歌词中,函数都能处理。</p>
<p>二、不支持复合时间信息原生lrc歌词格式,例如下面这两种:</p>
<blockquote>
① 歌词<br>
② 歌词<br>
</blockquote>
<p>如果碰上这类结果的复合时间信息的歌词,请手动将其分开(歌词相同),顺序不用管。</p>
<p>三、原生歌词的写法</p>
<p>可以分行写,一行一句;也可以使用分隔符 \n 衔接各句歌词。例如:</p>
<blockquote>
</blockquote>
<p>最后给出一个应用实例,无插件依赖。代码:</p>
<div class='mum'>
<cl-cd data-idx="1"><<span class="tDarkRed">style</span>></cl-cd>
<cl-cd data-idx="2">#tz { <span class="tBlue">margin:</span> 20px auto; <span class="tBlue">width:</span> 740px; <span class="tBlue">height:</span> 300px; <span class="tBlue">border:</span> 1px solid gray; <span class="tBlue">position:</span> relative; }</cl-cd>
<cl-cd data-idx="3">#tz::before { <span class="tBlue">content:</span> attr(data-lrc); <span class="tBlue">position:</span> absolute; <span class="tBlue">bottom:</span> 0; <span class="tBlue">width:</span> 100%; <span class="tBlue">height:</span> 60px; <span class="tBlue">text-align:</span> center; <span class="tBlue">font:</span> normal 24px/60px sans-serif; <span class="tBlue">color:</span> transparent; <span class="tBlue">background:</span> repeating-linear-gradient(to right, red, green, blue, green, red) 50%/200px 60px; <span class="tBlue">-webkit-background-clip:</span> text; }</cl-cd>
<cl-cd data-idx="4">#player { <span class="tBlue">position:</span> absolute; <span class="tBlue">top:</span> 30px; <span class="tBlue">left:</span> calc(50% - 61px); <span class="tBlue">cursor:</span> pointer; <span class="tBlue">animation:</span> rot 6s linear infinite <span class="tBlue">var</span>(--state); }</cl-cd>
<cl-cd data-idx="5">@keyframes rot { to { <span class="tBlue">transform:</span> rotate(360deg); } }</cl-cd>
<cl-cd data-idx="6"><<span class="tDarkRed">/style</span>></cl-cd>
<cl-cd data-idx="7"> </cl-cd>
<cl-cd data-idx="8"><<span class="tDarkRed">div</span> <span class="tRed">id</span>=<span class="tMagenta">"tz"</span> data-lrc=<span class="tMagenta">"HUACHAO"</span>></cl-cd>
<cl-cd data-idx="9"> <<span class="tDarkRed">audio</span> <span class="tRed">id</span>=<span class="tMagenta">"aud"</span> src=<span class="tMagenta">"https://music.163.com/song/media/outer/url?<span class="tRed">id</span>=1429962105"</span> autoplay loop><<span class="tDarkRed">/audio</span>></cl-cd>
<cl-cd data-idx="10"> <<span class="tDarkRed">img</span> <span class="tRed">id</span>=<span class="tMagenta">"player"</span> src=<span class="tMagenta">"https://638183.freep.cn/638183/t23/btn/plum.png"</span> alt=<span class="tMagenta">""</span> title=<span class="tMagenta">"播放/暂停"</span> /></cl-cd>
<cl-cd data-idx="11"><<span class="tDarkRed">/div</span>></cl-cd>
<cl-cd data-idx="12"> </cl-cd>
<cl-cd data-idx="13"><<span class="tDarkRed">script</span>></cl-cd>
<cl-cd data-idx="14"><span class="tBlue">var</span> curkey = 0, lrcAr = [];</cl-cd>
<cl-cd data-idx="15"><span class="tBlue">var</span> getAr = (text) => {</cl-cd>
<cl-cd data-idx="16"> <span class="tBlue">var</span> ar = text.trim().split(<span class="tMagenta">'\n'</span>);</cl-cd>
<cl-cd data-idx="17"> ar.sort();</cl-cd>
<cl-cd data-idx="18"> <span class="tBlue">var</span> reg = /\[(\d+)[.:](\d+)[.:](\d+)\](.*)/;</cl-cd>
<cl-cd data-idx="19"> ar.forEach(item => {</cl-cd>
<cl-cd data-idx="20"> <span class="tBlue">let</span> result = item.match(reg);</cl-cd>
<cl-cd data-idx="21"> <span class="tBlue">let</span> tmsg = parseInt(result) * 60 + parseInt(result) + parseInt(result) / 1000;</cl-cd>
<cl-cd data-idx="22"> lrcAr.push(.trim()]);</cl-cd>
<cl-cd data-idx="23"> });</cl-cd>
<cl-cd data-idx="24">};</cl-cd>
<cl-cd data-idx="25"><span class="tBlue">var</span> mState = () => {</cl-cd>
<cl-cd data-idx="26"> tz.style.setProperty(<span class="tMagenta">'--state'</span>, [<span class="tMagenta">'running'</span>,<span class="tMagenta">'paused'</span>][+aud.paused]);</cl-cd>
<cl-cd data-idx="27"> player.title = [<span class="tMagenta">'暂停'</span>,<span class="tMagenta">'播放'</span>][+aud.paused];</cl-cd>
<cl-cd data-idx="28"> <span class="tGreen">//其它控制代码</span></cl-cd>
<cl-cd data-idx="29">};</cl-cd>
<cl-cd data-idx="30">aud.onseeked = () => curkey = 0;</cl-cd>
<cl-cd data-idx="31">aud.onplaying = aud.onpause = () => mState();</cl-cd>
<cl-cd data-idx="32">aud.ontimeupdate = () => {</cl-cd>
<cl-cd data-idx="33"> <span class="tBlue">if</span>(curkey > lrcAr.length - 1) <span class="tBlue">return</span>;</cl-cd>
<cl-cd data-idx="34"> <span class="tBlue">if</span>(aud.currentTime >= lrcAr) {</cl-cd>
<cl-cd data-idx="35"> tz.dataset.lrc = lrcAr;</cl-cd>
<cl-cd data-idx="36"> curkey ++;</cl-cd>
<cl-cd data-idx="37"> }</cl-cd>
<cl-cd data-idx="38">};</cl-cd>
<cl-cd data-idx="39">player.onclick = () => aud.paused ? aud.play() : aud.pause();</cl-cd>
<cl-cd data-idx="40"><span class="tBlue">var</span> lrc = `</cl-cd>
<cl-cd data-idx="41">张子铭 - 一生最爱</cl-cd>
<cl-cd data-idx="42">向雪怀作词,伍思凯作曲</cl-cd>
<cl-cd data-idx="43">吉他:白毛</cl-cd>
<cl-cd data-idx="44">如果痴痴的等某日终于可等到一生中最爱</cl-cd>
<cl-cd data-idx="45">谁介意你我这段情每每碰上了意外不清楚未来</cl-cd>
<cl-cd data-idx="46">何曾愿意 我心中所爱</cl-cd>
<cl-cd data-idx="47">每天要孤单看海</cl-cd>
<cl-cd data-idx="48">宁愿一生都不说话都不想讲假说话欺骗你</cl-cd>
<cl-cd data-idx="49">留意到你我这段情你会发觉间隔着一点点距离</cl-cd>
<cl-cd data-idx="50">无言地爱 我偏不敢说</cl-cd>
<cl-cd data-idx="51">说一句想跟你一起</cl-cd>
<cl-cd data-idx="52">OOOH OOH</cl-cd>
<cl-cd data-idx="53">如真 如假 如可分身饰演自己</cl-cd>
<cl-cd data-idx="54">会将心中的温柔献出给你唯有的知己</cl-cd>
<cl-cd data-idx="55">如痴 如醉 还盼你懂珍惜自己</cl-cd>
<cl-cd data-idx="56">有天即使分离我都想你我 真的想你</cl-cd>
<cl-cd data-idx="57">宁愿一生都不说话都不想讲假说话欺骗你</cl-cd>
<cl-cd data-idx="58">留意到你我这段情你会发觉间隔着一点点距离</cl-cd>
<cl-cd data-idx="59">无言地爱 我偏不敢说</cl-cd>
<cl-cd data-idx="60">说一句想跟你一起</cl-cd>
<cl-cd data-idx="61">OOOH OOH</cl-cd>
<cl-cd data-idx="62">如真 如假 如可分身饰演自己</cl-cd>
<cl-cd data-idx="63">会将心中的温柔献出给你唯有的知己</cl-cd>
<cl-cd data-idx="64">如痴 如醉 还盼你懂珍惜自己</cl-cd>
<cl-cd data-idx="65">有天即使分离我都想你我 真的想你</cl-cd>
<cl-cd data-idx="66">如真 如假 如可分身饰演自己</cl-cd>
<cl-cd data-idx="67">会将心中的温柔献出给你唯有的知己</cl-cd>
<cl-cd data-idx="68">如痴 如醉 还盼你懂珍惜自己</cl-cd>
<cl-cd data-idx="69">有天即使分离我都想你我 真的想你</cl-cd>
<cl-cd data-idx="70">如果痴痴的等某日终于可等到一生中最爱</cl-cd>
<cl-cd data-idx="71">`;</cl-cd>
<cl-cd data-idx="72">getAr(lrc);</cl-cd>
<cl-cd data-idx="73"><<span class="tDarkRed">/script</span>></cl-cd>
</div>
</div> <style>
#tz { margin: 20px auto; width: 740px; height: 300px; border: 1px solid gray; position: relative; }
#tz::before { content: attr(data-lrc); position: absolute; bottom: 0; width: 100%; height: 60px; text-align: center; font: normal 24px/60px sans-serif; color: transparent; background: repeating-linear-gradient(to right, red, green, blue, green, red) 50%/200px 60px; -webkit-background-clip: text; }
#player { position: absolute; top: 30px; left: calc(50% - 61px); cursor: pointer; animation: rot 6s linear infinite var(--state); }
@keyframes rot { to { transform: rotate(360deg); } }
</style>
<div id="tz" data-lrc="HUACHAO">
<audio id="aud" src="https://music.163.com/song/media/outer/url?id=1429962105" autoplay loop></audio>
<img id="player" src="https://638183.freep.cn/638183/t23/btn/plum.png" alt="" title="播放/暂停" />
</div>
<script>
var curkey = 0, lrcAr = [];
var getAr = (text) => {
var ar = text.trim().split('\n');
ar.sort();
var reg = /\[(\d+)[.:](\d+)[.:](\d+)\](.*)/;
ar.forEach(item => {
let result = item.match(reg);
let tmsg = parseInt(result) * 60 + parseInt(result) + parseInt(result) / 1000;
lrcAr.push(.trim()]);
});
};
var mState = () => {
tz.style.setProperty('--state', ['running','paused'][+aud.paused]);
player.title = ['暂停','播放'][+aud.paused];
//其它控制代码
};
aud.onseeked = () => curkey = 0;
aud.onplaying = aud.onpause = () => mState();
aud.ontimeupdate = () => {
if(curkey > lrcAr.length - 1) return;
if(aud.currentTime >= lrcAr) {
tz.dataset.lrc = lrcAr;
curkey ++;
}
};
player.onclick = () => aud.paused ? aud.play() : aud.pause();
var lrc = `
张子铭 - 一生最爱
向雪怀作词,伍思凯作曲
吉他:白毛
如果痴痴的等某日终于可等到一生中最爱
谁介意你我这段情每每碰上了意外不清楚未来
何曾愿意 我心中所爱
每天要孤单看海
宁愿一生都不说话都不想讲假说话欺骗你
留意到你我这段情你会发觉间隔着一点点距离
无言地爱 我偏不敢说
说一句想跟你一起
OOOH OOH
如真 如假 如可分身饰演自己
会将心中的温柔献出给你唯有的知己
如痴 如醉 还盼你懂珍惜自己
有天即使分离我都想你我 真的想你
宁愿一生都不说话都不想讲假说话欺骗你
留意到你我这段情你会发觉间隔着一点点距离
无言地爱 我偏不敢说
说一句想跟你一起
OOOH OOH
如真 如假 如可分身饰演自己
会将心中的温柔献出给你唯有的知己
如痴 如醉 还盼你懂珍惜自己
有天即使分离我都想你我 真的想你
如真 如假 如可分身饰演自己
会将心中的温柔献出给你唯有的知己
如痴 如醉 还盼你懂珍惜自己
有天即使分离我都想你我 真的想你
如果痴痴的等某日终于可等到一生中最爱
`;
getAr(lrc);
</script>
这个这么好用,跟了个速成贴,老师去瞧瞧。。{:4_170:}
用这个需要找到原生歌词。。现在网不行, 晚上回家找~~ 这首歌挺深情,歌词好看,歌好听。。 “请手动将其分开(歌词相同),顺序不用管。”
这法子好,我以前以为需要根据它的时间,按顺序插入到应该的位置。看到这个,明白了,本来就是根据时间出现的歌词,放的位置并不影响它出现的时候呢{:4_199:} 用这个例子故意去打乱顺序,果然不影响呢。{:4_173:} “可以分行写,一行一句;也可以使用分隔符 \n 衔接各句歌词”
太好了,从网易云里找到的歌此就是并列的,这样找到歌词就能直接使用了。幸福{:4_205:} 从网易云上找了个深深的《匆匆那年》
{"sgc":false,"sfy":false,"qfy":false,"lrc":{"version":29,"lyric":"匆匆那年我们究竟说了几遍再见之后再拖延\n可惜谁也没有爱过不是一场七情上面的雄辩\n匆匆那年我们一时匆忙撂下难以承受的诺言\n只有等别人兑现\n不怪那吻痕还没积累成茧\n拥抱着冬眠也没能羽化再成仙\n不怪这一段情没空反复再排练\n是岁月宽容恩赐反悔的时间\n如果再见不能红着眼是否还能红着脸\n就像那年匆促刻下永远一起那样美丽的谣言\n如果过去还值得眷恋别太快冰释前嫌\n谁甘心就这样彼此无挂也无牵\n我们要互相亏欠要不然凭何怀缅\n匆匆那年我们见过太少世面只爱看同一张脸\n那么莫名其妙那么讨人欢喜闹起来又太讨厌\n相爱那年活该匆匆因为我们不懂顽固的诺言\n只是分手的前言\n不怪那天太冷泪滴水成冰\n春风也一样没吹进凝固的照片\n不怪每一个人没能完整爱一遍\n是岁月善意落下残缺的悬念\n如果再见不能红着眼是否还能红着脸\n就像那年匆促刻下永远一起那样美丽的谣言\n如果过去还值得眷恋别太快冰释前嫌\n谁甘心就这样彼此无挂也无牵\n如果再见不能红着眼是否还能红着脸\n就像那年匆促刻下永远一起那样美丽的谣言\n如果过去还值得眷恋别太快冰释前嫌\n谁甘心就这样彼此无挂也无牵\n我们要互相亏欠\n我们要藕断丝连\n"},"code":200} 把上面带颜色的那部分放进去十分完美地出现,哈哈,太省力了{:4_205:}
唯一的缺憾,若原歌词有错,只能跟着错了。
不过仔细点的话,可以自己听一遍,错的地方稍微改变一下就好,包括歌词前面的内容也可以自己加上演唱者等内容。
错误太多的话,就自己做吧,改的话花的功夫不会比自己做的少{:4_173:} 黑黑带来的全是好东西啊,这个又忍不住要置顶了{:4_173:} 再来看一眼,老师自创的函数太先进了。。。
全网又是独一份。。{:4_191:} 红影 发表于 2024-6-18 14:06
用这个例子故意去打乱顺序,果然不影响呢。
看第五行代码,
ar.sort();
这是排序代码,不过它只在很纯洁的环境下能正常工作 南无月 发表于 2024-6-18 17:54
再来看一眼,老师自创的函数太先进了。。。
全网又是独一份。。
这个,歌词转换程序早就能完成这个工作的,只是它处于牢靠,算法比这个要复杂的多 红影 发表于 2024-6-18 14:25
黑黑带来的全是好东西啊,这个又忍不住要置顶了
闹得俺一下子找不到帖子 红影 发表于 2024-6-18 14:23
把上面带颜色的那部分放进去十分完美地出现,哈哈,太省力了
唯一的缺憾,若原歌词有错,只能跟 ...
一般来说,lrc歌词不会有错,最多是时间信息有误差,若此,可以考虑加一个误差变量就行,这之前有过介绍的 红影 发表于 2024-6-18 14:20
从网易云上找了个深深的《匆匆那年》
{"sgc":false,"sfy":false,"qfy":false,"lrc":{"version":29,"lyri ...
对的,辛苦了。一般建议把收尾多余的去掉,然后在歌词开头加一句歌词,毫秒级别的开始时间就好 正则方法用得很巧妙 马黑黑 发表于 2024-6-18 18:14
这个,歌词转换程序早就能完成这个工作的,只是它处于牢靠,算法比这个要复杂的多
歌词转换在歌词编辑器里呀,说实话我只用制作和微调,好象没用过转换。。。这么说来到是可以回头再试试。。 南无月 发表于 2024-6-18 22:05
歌词转换在歌词编辑器里呀,说实话我只用制作和微调,好象没用过转换。。。这么说来到是可以回头再试试。 ...
歌词编辑器有多重模式:制作、转换、微调 马黑黑 发表于 2024-6-18 18:13
看第五行代码,
ar.sort();
原来是这个起作用的,嗯嗯,谢谢黑黑讲解{:4_187:}