-
-
Save youthlin/a3b3fc033586bede6046086f3d889322 to your computer and use it in GitHub Desktop.
--[[-- | |
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)() |
按照介绍的方法:
1、将本文件放在 lua/intf/ 文件夹下
2、设置(显示所有)-界面-主界面-扩展界面,填入 luaintf,并勾选 Lua interpreter(勾选会自动填为 lua, 需要手动改成 luaintf)
3、设置(显示所有)-界面-主界面-Lua,在 Lua 界面 字段填入本文件名: lyrics
4、设置-字幕/OSD-确保 "启用 OSD" 勾选,下方可以设置字体
在flac文件标签里的歌词还是不能显示,选可视化的时候,提示未找到歌词,不知道自己哪里弄错了,来请教
按照介绍的方法: 1、将本文件放在 lua/intf/ 文件夹下 2、设置(显示所有)-界面-主界面-扩展界面,填入 luaintf,并勾选 Lua interpreter(勾选会自动填为 lua, 需要手动改成 luaintf) 3、设置(显示所有)-界面-主界面-Lua,在 Lua 界面 字段填入本文件名: lyrics 4、设置-字幕/OSD-确保 "启用 OSD" 勾选,下方可以设置字体 在flac文件标签里的歌词还是不能显示,选可视化的时候,提示未找到歌词,不知道自己哪里弄错了,来请教
音乐标签:安卓版、Windows 版本,小众软件介绍。
另一个 Web 版: xhongc/music-tag-web
@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】,部分中文字体也会导致歌词失效
How do I upload my extension to http://addons.videolan.org