Skip to content

Instantly share code, notes, and snippets.

@youthlin
Last active November 5, 2024 02:15
Show Gist options
  • Save youthlin/a3b3fc033586bede6046086f3d889322 to your computer and use it in GitHub Desktop.
Save youthlin/a3b3fc033586bede6046086f3d889322 to your computer and use it in GitHub Desktop.
show lyrics in vlc
--[[--
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
Copy link

Bigxxi commented Nov 30, 2023

作者,你好
我是VLC 3.02 win10版本
我也是使用这个插件显示未找到歌词
QQ图片20231130172610
QQ图片20231130172622
经排查发现是中文问题,我播放英文歌曲可以正常显示歌词
QQ图片20231130172628
必须要满足mp3和lrc文件名称为英文,同时lrc歌词也要为英文方可正常显示歌词,这应该是编码问题,请问大佬有办法解决吗,非常刚需这个插件,谢谢!
QQ截图20231130173210

@youthlin
Copy link
Author

youthlin commented Dec 2, 2023

@Bigxxi 你好,可以把日志级别调整为debug看下。也可加微信交流:Youth_Lin

@youthlin
Copy link
Author

youthlin commented Dec 4, 2023

问题:在 Windows 环境中,lrc 路径包含中文(Non-Ascii字符),使用 lua 原生的 io.open 会报错 invalid argument
解决:使用 vlc.io.open 即可,vlc 内部封装的这个函数入参为 UTF-8 字符串,支持中文
脚本已更新

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

@youthlin
Copy link
Author

youthlin commented Dec 4, 2023

@Bigxxi
Copy link

Bigxxi commented Dec 4, 2023

顺便补充一下,如果有朋友使用外挂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