Last active
November 5, 2024 02:15
-
-
Save youthlin/a3b3fc033586bede6046086f3d889322 to your computer and use it in GitHub Desktop.
show lyrics in vlc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[-- | |
show lyrics on visualization 在可视化界面显示歌词 | |
url 项目地址: https://gist.github.com/youthlin/a3b3fc033586bede6046086f3d889322 | |
author 作者: youthlin | |
author url 作者博客: https://youthlin.com | |
How to install: | |
1. put this file on lua/intf/ | |
2. enable extra interface: luaintf (Settings[Show all] -> Interface -> Main Interfaces -> extract modules='luaintf'[make the 'Lua interpreter' checked]) | |
(important: extraintf=luaintf not lua) | |
3. set lua interface to this file name: All Settings -> Interface -> Main Interface -> Lua -> Lua interface = lyrics | |
4. Settings - subtitle/osd - make "enable osd" checked. | |
如何启用: | |
1、将本文件放在 lua/intf/ 文件夹下 | |
2、设置(显示所有)-界面-主界面-扩展界面,填入 luaintf,并勾选 Lua interpreter(勾选会自动填为 lua, 需要手动改成 luaintf) | |
3、设置(显示所有)-界面-主界面-Lua,在 Lua 界面 字段填入本文件名: lyrics | |
4、设置-字幕/OSD-确保 "启用 OSD" 勾选,下方可以设置字体 | |
INSTALLATION directory (\lua\intf\): 安装文件夹在哪里: | |
* Windows (all users): %ProgramFiles%\VideoLAN\VLC\lua\intf\ | |
* Windows (current user): %APPDATA%\VLC\lua\intf\ | |
* Linux (all users): /usr/lib/vlc/lua/intf/ | |
* Linux (current user): ~/.local/share/vlc/lua/intf/ | |
* Mac OS X (all users): /Applications/VLC.app/Contents/MacOS/share/lua/intf/ | |
* Mac OS X (current user): /Users/%your_name%/Library/Application Support/org.videolan.vlc/lua/intf/ | |
Create directory if it does not exist! | |
links: | |
lrcview extension: http://eadmaster.altervista.org/pub/prj/lrcview.lua | |
Times v3.2: https://addons.videolan.org/p/1154032/ | |
vlc lua document: https://github.com/videolan/vlc/tree/master/share/lua | |
document: https://github.com/verghost/vlc-lua-docs/blob/master/index.md | |
VLC forum / Scripting VLC in lua: https://forum.videolan.org/viewforum.php?f=29 | |
--]] -- | |
-- [[ entrance ]] -- | |
(function() -- 立即执行的函数: 入口 | |
local lrc_config = { -- 配置项 | |
supports = {"mp3", "wav", "flac", "ape", "aif", "m4a", "ogg"}, -- 后缀名 file extension | |
pre = { -- 上一行歌词 | |
show = false, | |
prefix = "", | |
suffix = " ♪\n" | |
}, | |
current = { -- 当前歌词 | |
show = true, | |
prefix = "", | |
suffix = " ♪\n" | |
}, | |
next = { -- 下一行歌词 | |
show = true, | |
prefix = "", | |
suffix = " ♪\n" | |
}, | |
lyrics_not_found = "未找到歌词", -- 未找到歌词时显示的文本 | |
position = "top-right" -- 显示位置 | |
} | |
if vlc == nil then | |
vlc = {} -- 消除警告 | |
end | |
local function info(lm) -- Info | |
vlc.msg.info("[lyrics] " .. lm) | |
end | |
local function logerr(lm) -- Error | |
vlc.msg.err("[lyrics] " .. lm) | |
end | |
local function debug(lm) -- debug | |
vlc.msg.dbg("[lyrics] " .. lm) | |
end | |
info("lyrics started! 歌词插件已启动") | |
local function sleep(st) -- 休眠的秒数 seconds | |
vlc.misc.mwait(vlc.misc.mdate() + st * 1000000) | |
end | |
local function dump(name, object, level) -- 显示变量 | |
if level == nil then | |
level = 0 | |
end | |
local prefix = string.rep(" ", level * 2) | |
if type(object) ~= "table" then -- 不是 table | |
info(prefix .. "> " .. name .. " type=" .. type(object) .. " tostring = " .. tostring(object)) | |
return false | |
end | |
info(prefix .. "> " .. name .. " tostring = " .. tostring(object)) | |
for k, v in pairs(object) do | |
k = name .. "." .. k | |
info(prefix .. "> " .. k .. " => " .. type(v) .. " = " .. tostring(v)) | |
if type(v) == "table" then | |
dump(k, v, level + 1) | |
end | |
end | |
return true | |
end | |
dump("vlc", vlc) | |
dump("lrc_config", lrc_config) | |
local function is_win() | |
return vlc.win ~= nil | |
end | |
info("is windows == " .. tostring(is_win())) | |
local curi = nil -- current uri | |
local is_audio = false | |
local seconds_to_lrc = nil -- seconds to lyrics | |
local VLC_tc = 1 -- time corrector | |
if tonumber(string.sub(vlc.misc.version(), 1, 1)) > 2 then | |
VLC_tc = 1000000 | |
end -- VLC3 | |
debug("VLC_tc=" .. tostring(VLC_tc)) | |
local function reset() -- 停止播放时重置变量 | |
info("reset") | |
curi = nil | |
is_audio = false | |
seconds_to_lrc = nil | |
end | |
local function get_lrc() -- 从 lrc 文件获取歌词 | |
if curi == nil or curi == "" then | |
return "" | |
end | |
local file = curi | |
if is_win() then | |
file = string.gsub(file, "file:///", "") -- file:///c:/xxx -> c:/xxx | |
else | |
file = string.gsub(file, "file://", "") -- file:///Users/xxx -> /Users/xxx | |
end | |
local dotIndex = -1 | |
local dot = string.byte(".") | |
for i = #file, 0, -1 do | |
local ch = string.sub(file, i, i) | |
local ch_byte = string.byte(ch) | |
if ch_byte < 128 then | |
if ch_byte == dot then | |
dotIndex = i | |
break | |
end | |
else | |
break | |
end | |
end | |
file = string.sub(file, 1, dotIndex) .. "lrc" | |
if string.match(file, "^http") then | |
info("从网络 lrc 文件读取歌词: " .. file .. " dotIndex=" .. dotIndex) | |
local fd = vlc.stream(file) | |
if not fd then | |
logerr("未获取到网络歌词") | |
return "" | |
end | |
dump("fd", fd) | |
local result = fd:read(65653) | |
fd = nil | |
info('网络读取歌词 ok') | |
return result | |
end | |
file = vlc.strings.decode_uri(file) | |
info("从本地 lrc 文件读取歌词: " .. file .. " dotIndex=" .. dotIndex) | |
-- 使用 vlc.io.open 而不是原生 io.open 可以兼容 Windows 下无法打开中文路径的问题 | |
-- https://github.com/verghost/vlc-lua-docs/blob/master/m/io/index.md | |
local f, err = vlc.io.open(file, "r") | |
if f == nil then | |
logerr("找不到 lrc 歌词文件: " .. file .. " err: " .. err) | |
return "" | |
end | |
local result = f:read("*all") | |
f:close() | |
info('本地 lrc 文件读取歌词 ok') | |
return result | |
end | |
local function get_lyrics() -- 获取当前歌曲的歌词 | |
debug("get_lyrics") | |
local item = vlc.input.item() | |
if item == nil then | |
return "" | |
end | |
local metas = item:metas() | |
dump("metas", metas) | |
if metas["LYRICS"] then | |
info("从歌曲 Tag 中获取歌词") | |
return metas["LYRICS"] | |
end | |
return get_lrc() | |
end | |
local function extract_time(line) -- 获取一行歌词的开始时间 seconds | |
if (line == nil) then | |
return (-1) | |
end | |
local min, sec, mil = line:match('%[(%d%d):(%d%d)%.(%d%d)%]') | |
if (min == nil or sec == nil or mil == nil) then | |
return (-1) | |
end | |
return (tonumber(min) * 60 + tonumber(sec) + tonumber(mil) / 100) | |
end | |
local function build_lrc_table() -- 构造歌词表 | |
debug("build_lrc_table") | |
local lrc = {} | |
local lyrics = get_lyrics() | |
local i = 1 | |
for line in lyrics:gmatch("[^\n]+") do | |
line = string.gsub(line, "\n", "") -- remove newlines | |
line = string.gsub(line, "\r", "") -- remove newlines | |
local time = extract_time(line) | |
if time >= 0 then | |
lrc[i] = { | |
time = time, | |
lrc = string.gsub(line, "^%[.-%]", "") -- 去掉 [xxx] 时间 | |
} | |
i = i + 1 | |
end | |
end | |
return lrc | |
end | |
local function uri_changed(uri) -- 读取当前歌曲的歌词 | |
info("uri_changed to " .. uri) | |
curi = uri | |
local support = false | |
for _, value in pairs(lrc_config.supports) do | |
if string.match(uri, value .. "$") then | |
support = true | |
break | |
end | |
end | |
is_audio = support | |
info("is_audio=" .. tostring(support)) | |
if not support then | |
return | |
end | |
seconds_to_lrc = build_lrc_table() | |
dump("seconds_to_lrc", seconds_to_lrc) | |
if #seconds_to_lrc == 0 then | |
info("没有歌词") | |
end | |
local input = vlc.object.input() | |
dump("input", input) | |
end | |
local function get_show_lyrics() -- 获取要显示的歌词 | |
local total = #seconds_to_lrc | |
-- 没有歌词 | |
if #seconds_to_lrc == 0 then | |
-- debug("没有歌词") | |
return lrc_config.lyrics_not_found | |
end | |
-- 有歌词 | |
local input = vlc.object.input() | |
local current_second = vlc.var.get(input, "time") / VLC_tc | |
local prefix, current, next = "", "", "" | |
debug("get_show_lyrics current_second = " .. current_second) | |
local index = 1 | |
for i = 1, total do | |
local item = seconds_to_lrc[i] | |
local second, value = item.time, item.lrc | |
if second <= current_second then | |
current = value | |
index = i | |
end | |
end | |
if index > 1 then | |
prefix = seconds_to_lrc[index - 1].lrc | |
end | |
if index < total then | |
next = seconds_to_lrc[index + 1].lrc | |
end | |
info("get_show_lyrics = " .. prefix .. " / " .. current .. " / " .. next) | |
local result = "" | |
if lrc_config.pre.show then | |
result = result .. lrc_config.pre.prefix .. prefix .. lrc_config.pre.suffix | |
end | |
if lrc_config.current.show then | |
result = result .. lrc_config.current.prefix .. current .. lrc_config.current.suffix | |
end | |
if lrc_config.next.show then | |
result = result .. lrc_config.next.prefix .. next .. lrc_config.next.suffix | |
end | |
return result | |
end | |
local function do_task() -- 播放时持续调用的函数(每0.1s) | |
--[[ | |
local vout = vlc.object.vout() | |
if vout == nil then | |
-- info("可视化未开启") | |
return | |
end | |
--]] | |
if not is_audio then | |
return | |
end | |
vlc.osd.message(get_show_lyrics(), nil, lrc_config.position) | |
end | |
-- 主循环 | |
while true do | |
if vlc.volume.get() == -256 then -- 当进程被杀时 | |
break | |
end -- inspired by syncplay.lua; kills vlc.exe process in Task Manager | |
if vlc.playlist.status() == "stopped" then -- no input or stopped input | |
if curi then -- input stopped | |
info("stopped") | |
reset() | |
end | |
sleep(1) | |
else -- playing, paused | |
local uri = nil | |
if vlc.input.item() then | |
uri = vlc.input.item():uri() | |
end | |
if not uri then --- WTF (VLC 2.1+): status playing with nil input? Stopping? O.K. in VLC 2.0.x | |
info("WTF??? " .. vlc.playlist.status()) | |
sleep(0.1) | |
elseif not curi or curi ~= uri then -- new input (first input or changed input) | |
uri_changed(uri) | |
else -- current input | |
do_task() | |
if vlc.playlist.status() == "playing" then | |
-- info("playing") | |
elseif vlc.playlist.status() == "paused" then | |
-- info("paused") | |
sleep(0.3) | |
else -- ? | |
info("unknown play status") | |
sleep(1) | |
end | |
sleep(0.1) -- 每 0.1 秒调用一次 task | |
end | |
end | |
end | |
end)() |
@Bigxxi 你好,可以把日志级别调整为debug看下。也可加微信交流:Youth_Lin
问题:在 Windows 环境中,lrc 路径包含中文(Non-Ascii字符),使用 lua 原生的 io.open
会报错 invalid argument
解决:使用 vlc.io.open
即可,vlc 内部封装的这个函数入参为 UTF-8 字符串,支持中文
脚本已更新
- 通过本文确认 io.open 在 Windows 上无法打开中文路径:LUA 编码 CODEPAGE CP936 UNICODE UTF8 及 相关库LIBRARY 整理
- 通过本项目搜到 vlc 内部有 io 库:VLC Lua Docs, IO Module
vlc 内部如何支持非 ASCII 字符的:
// vlc.io.open 的 C 实现【1】/modules/lua/libs/io.c
static int vlclua_io_open( lua_State *L )
// vlclua_io_open 调用 vlc_fopen【2】/src/text/filesystem.c
FILE *vlc_fopen (const char *filename, const char *mode)
// vlc_fopen 调用 vlc_open【3】/src/win32/filesystem.c 【4】
int vlc_open (const char *filename, int flags, ...)
// 调用同文件 widen_path 函数【5】/src/win32/filesystem.c
static wchar_t *widen_path (const char *path)
// 【6】include/vlc_charset.h
VLC_USED static inline wchar_t *ToWide (const char *utf8)
// 【7】是 windows 提供的 api
MultiByteToWideChar
顺便补充一下,如果有朋友使用外挂lrc文件在windows上无法显示中文歌词,需要将lrc文件改为utf-8编码,并且要在设置-字幕/OSD-字体改为【Microsoft YaHei UI】,部分中文字体也会导致歌词失效
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
作者,你好
我是VLC 3.02 win10版本
我也是使用这个插件显示未找到歌词
经排查发现是中文问题,我播放英文歌曲可以正常显示歌词
必须要满足mp3和lrc文件名称为英文,同时lrc歌词也要为英文方可正常显示歌词,这应该是编码问题,请问大佬有办法解决吗,非常刚需这个插件,谢谢!