Files
MeowOS-For-R3P2/patch_v2/sdk/meow_lyric.c
T

315 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* meow_lyric.c — SDK 通用歌词组件实现(见 meow_lyric.h 的契约说明)。
*
* 本 TU 为 ncm_app/app/logic/lyric.c(解析/清空/查询)+ player.c 的 lyr_wrap_lines(折行)的
* 通用化上收:去掉 ncm 特有命名(NCM_LYRIC_LINES / LRC_MAX_TEXT / lyrical 前缀),改用
* meow_lyric_* 前缀与 SDK 命名;行上限由编译期宏变为「静态硬上限 + 运行时可配置」;
* 时间戳存储用 int64_t(跨 32/64 位宿主一致);宽测量由使用方注入回调(不解耦渲染层)。
* 逻辑(LRC 时间标签解析、时间升序排序、翻译按 timestamp 合并、字面 \n 当换行、
* 流式有界、超宽折行中西文混排)与 ncm 逐行保持一致;改动仅限命名/接口/上限可配。
*
* 线程安全:单把 pthread 互斥锁(函数级,写侧整段持锁、读侧每次持锁,无嵌套;
* 内部清空/解析用 nolock 私有版避免自锁死)。与 ncm lyric.c 的机制完全相同。
*
* 内存纪律:静态存储(行数组 + 文本缓冲)零 malloc;仅 meow_lyric_parse 排序时在**堆**上
* 申请瞬态索引/拷贝数组(ncm 实测:600 行×192 字节的临时数组若放栈会占 ~227KB,超出后台
* 线程 128KB 栈 → 栈溢出 SIGSEGV;放堆按 s_n 计、用后即 free)。任一 malloc 失败保留未排序
* (不崩,查询仍可用)。
*/
#include "meow_lyric.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/* ---- 运行时状态(静态,解析/查询全程零 malloc;仅排序时瞬态堆)---- */
static int64_t s_t[MEOW_LYRIC_LINES_MAX]; /* 每行时间戳(ms) */
static char s_txt[MEOW_LYRIC_LINES_MAX][MEOW_LYRIC_TEXT_MAX]; /* 原文 */
static char s_tl[MEOW_LYRIC_LINES_MAX][MEOW_LYRIC_TEXT_MAX]; /* 翻译 */
static int s_n = 0; /* 有效行数 */
static int g_max_lines = MEOW_LYRIC_LINES_MAX; /* 运行时可配行上限(默认 600) */
static pthread_mutex_t g_lyric_mu = PTHREAD_MUTEX_INITIALIZER;
/* 清空(nolock,内部用;调用方须已持锁)。 */
static void meow_lyric_clear_nolock(void)
{
s_n = 0;
memset(s_t, 0, sizeof(s_t));
memset(s_txt, 0, sizeof(s_txt));
memset(s_tl, 0, sizeof(s_tl));
}
/* 复制一行歌词正文 n 字节到 dst,把「字面反斜杠 n/r」序列(服务器用 \n 把多行文本塞进一行,
* 字面 \n 渲染成豆腐块/凌乱)替换为空格;顺带把 \r\n / \n\r 折叠为单空格。有界 ≤max。
* 语义与 ncm copy_lrc_text 一致。 */
static void meow_lyric_copy_text(char *dst, const char *src, size_t n, size_t max)
{
size_t oi = 0, si = 0;
while (si < n && oi < max - 1) {
if (src[si] == '\\' && si + 1 < n && (src[si + 1] == 'n' || src[si + 1] == 'r')) {
dst[oi++] = ' ';
si += 2;
/* \r\n / \n\r 成对 → 只留一个空格(跳过紧随的互补转义,防双空格) */
if (si + 1 < n && src[si] == '\\' &&
((src[si + 1] == 'r' && src[si - 1] == 'n') ||
(src[si + 1] == 'n' && src[si - 1] == 'r')))
si += 2;
} else {
dst[oi++] = src[si++];
}
}
dst[oi] = 0;
}
/* 解析单个 [mm:ss.ff] 时间标签为毫秒。返回 1=合法时间标签(*out_ms、*next=']' 之后);
* 0=非时间标签(*next=']' 之后;NULL=无闭合 ']',调用方按残缺处理)。
* 小数位自适应:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1,
* 网易云 LRC 毫秒)、>3 位截断到毫秒。 */
static int meow_lyric_parse_tag(const char *p, int64_t *out_ms, const char **next)
{
const char *q = p + 1; /* 跳过 '[' */
int m = 0, md = 0, s = 0, sd = 0;
while (*q >= '0' && *q <= '9') { m = m * 10 + (*q - '0'); md++; q++; }
if (md == 0 || *q != ':') goto notime;
q++;
while (*q >= '0' && *q <= '9') { s = s * 10 + (*q - '0'); sd++; q++; }
if (sd == 0) goto notime;
long frac = 0; int fd = 0; long scale = 10;
if (*q == '.') {
q++;
while (*q >= '0' && *q <= '9') { frac = frac * 10 + (*q - '0'); fd++; q++; }
if (fd == 1) scale = 100;
else if (fd == 2) scale = 10;
else if (fd >= 3) { scale = 1; while (fd > 3) { frac /= 10; fd--; } } /* >3 位截断到 ms */
else scale = 0; /* 孤立 '.' 无数字:忽略小数 */
}
if (*q != ']') goto notime;
*out_ms = (int64_t)m * 60000L + (int64_t)s * 1000L + frac * scale;
*next = q + 1;
return 1;
notime:
{
const char *r = strchr(p, ']');
*next = r ? r + 1 : NULL;
return 0;
}
}
/* 解析一批 LRC 行(原文 is_trans=0 / 翻译 is_trans=1)。翻译按 first_ms 匹配到已有原文行合并。
* 行上限取 g_max_lines;超出的原文行丢弃。不持锁(调用方已持锁)。 */
static void meow_lyric_parse_lines(const char *lrc, int is_trans)
{
if (!lrc) return;
const char *p = lrc;
while (*p) {
while (*p && *p != '[') p++;
if (!*p) break;
/* 解析一个或多个时间标签 */
int have = 0;
int64_t first_ms = 0;
while (*p == '[') {
int64_t ms;
const char *after;
if (meow_lyric_parse_tag(p, &ms, &after)) {
if (!have) { first_ms = ms; have = 1; }
p = after;
} else {
/* 非时间标签([ti:..] 等)——跳到行尾 */
if (!after) return; /* 无闭合 ']':残缺文件 */
p = after;
break;
}
}
if (have) {
/* 正文 = 到行尾(去 \r\n) */
const char *e = p;
while (*e && *e != '\n' && *e != '\r') e++;
size_t len = (size_t)(e - p);
if (len > MEOW_LYRIC_TEXT_MAX - 1) len = MEOW_LYRIC_TEXT_MAX - 1;
if (is_trans) {
/* 翻译行:按 first_ms 匹配到已有原文行 */
for (int i = 0; i < s_n; i++) {
if (s_t[i] == first_ms) {
meow_lyric_copy_text(s_tl[i], p, len, MEOW_LYRIC_TEXT_MAX);
break;
}
}
} else {
if (s_n < g_max_lines) {
s_t[s_n] = first_ms;
meow_lyric_copy_text(s_txt[s_n], p, len, MEOW_LYRIC_TEXT_MAX);
s_tl[s_n][0] = 0;
s_n++;
}
}
}
while (*p && *p != '\n') p++; /* 跳到下一行 */
if (*p == '\n') p++;
}
}
/* qsort 比较:按时间戳升序(用索引数组。s_t 为静态数组,比较器直接读它)。 */
static int meow_lyric_cmp(const void *a, const void *b)
{
const int *ia = (const int *)a, *ib = (const int *)b;
if (s_t[*ia] < s_t[*ib]) return -1;
if (s_t[*ia] > s_t[*ib]) return 1;
return 0;
}
/* 折行宽测量的兜底:字节数近似(仅当使用方未注入 width 时;非像素精确)。
* 保证 meow_lyric_wrap 在无渲染引擎下仍行为确定(maxw 作字节预算,仍按中西文断行)。 */
static int meow_lyric_default_width(const char *u, int size, void *ctx)
{
(void)size; (void)ctx;
return (int)strlen(u);
}
void meow_lyric_clear(void)
{
pthread_mutex_lock(&g_lyric_mu);
meow_lyric_clear_nolock();
pthread_mutex_unlock(&g_lyric_mu);
}
int meow_lyric_parse(const char *lrc, const char *tlrc)
{
pthread_mutex_lock(&g_lyric_mu);
meow_lyric_clear_nolock();
meow_lyric_parse_lines(lrc, 0);
meow_lyric_parse_lines(tlrc, 1);
/* 按时间排序(用索引数组)。临时数组放**堆**上:`char [600][192]`×2 + `int64[600]`
* 若放栈会占 ~227KB,超过后台线程 128KB 栈 → 栈溢出 SIGSEGV。堆上按 s_n 计
* (≤600 行),峰值 ~230KB 瞬态、用后即 free。 */
if (s_n > 0) {
int *idx = (int *)malloc(sizeof(int) * (size_t)s_n);
int64_t *ttmp = (int64_t *)malloc(sizeof(int64_t) * (size_t)s_n);
char (*txt)[MEOW_LYRIC_TEXT_MAX] =
(char (*)[MEOW_LYRIC_TEXT_MAX])malloc((size_t)MEOW_LYRIC_TEXT_MAX * (size_t)s_n);
char (*tl)[MEOW_LYRIC_TEXT_MAX] =
(char (*)[MEOW_LYRIC_TEXT_MAX])malloc((size_t)MEOW_LYRIC_TEXT_MAX * (size_t)s_n);
if (idx && ttmp && txt && tl) {
for (int i = 0; i < s_n; i++) idx[i] = i;
qsort(idx, (size_t)s_n, sizeof(int), meow_lyric_cmp);
memcpy(ttmp, s_t, sizeof(int64_t) * (size_t)s_n);
memcpy(txt, s_txt, (size_t)MEOW_LYRIC_TEXT_MAX * (size_t)s_n);
memcpy(tl, s_tl, (size_t)MEOW_LYRIC_TEXT_MAX * (size_t)s_n);
for (int i = 0; i < s_n; i++) {
s_t[i] = ttmp[idx[i]];
memcpy(s_txt[i], txt[idx[i]], (size_t)MEOW_LYRIC_TEXT_MAX);
memcpy(s_tl[i], tl[idx[i]], (size_t)MEOW_LYRIC_TEXT_MAX);
}
}
free(idx); free(ttmp); free(txt); free(tl); /* 任一 malloc 失败:保留未排序(不崩) */
}
int result = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return result;
}
int meow_lyric_count(void)
{
pthread_mutex_lock(&g_lyric_mu);
int n = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return n;
}
int meow_lyric_line_at(int64_t ms)
{
pthread_mutex_lock(&g_lyric_mu);
int line = 0;
for (int i = 0; i < s_n; i++) {
if (s_t[i] <= ms) line = i;
else break;
}
pthread_mutex_unlock(&g_lyric_mu);
return line;
}
const char *meow_lyric_line_text(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_txt[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
const char *meow_lyric_line_tl(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_tl[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
int64_t meow_lyric_line_time(int i)
{
pthread_mutex_lock(&g_lyric_mu);
int64_t t = (i < 0 || i >= s_n) ? -1 : s_t[i];
pthread_mutex_unlock(&g_lyric_mu);
return t;
}
void meow_lyric_set_max_lines(int max)
{
pthread_mutex_lock(&g_lyric_mu);
if (max < 1) max = 1;
if (max > MEOW_LYRIC_LINES_MAX) max = MEOW_LYRIC_LINES_MAX;
g_max_lines = max;
pthread_mutex_unlock(&g_lyric_mu);
}
int meow_lyric_wrap(const char *txt, int size, int maxw,
char seg[][MEOW_LYRIC_SEG_SZ], int max_rows,
meow_lyric_width_fn width, void *ctx)
{
meow_lyric_width_fn w = width ? width : meow_lyric_default_width;
if (!txt || !txt[0] || !seg || max_rows <= 0) {
if (seg) seg[0][0] = 0;
return 1;
}
int n = 0;
size_t len = strlen(txt), pos = 0;
while (pos < len && n < max_rows) {
size_t start = pos;
int last_ws = -1; /* 本行最后一个空格(相对 start 的字节偏移) */
int overflow = 0;
while (pos < len) {
unsigned char c = (unsigned char)txt[pos];
int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
if (pos + (size_t)ch > len) ch = (int)(len - pos);
size_t cand = (pos + (size_t)ch) - start;
size_t cp = cand < MEOW_LYRIC_SEG_SZ - 1 ? cand : MEOW_LYRIC_SEG_SZ - 1;
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
if (w(seg[n], size, ctx) > maxw && cand > 0) { overflow = 1; break; }
if (txt[pos] == ' ' || txt[pos] == '\t') last_ws = (int)(pos - start);
pos += (size_t)ch;
}
if (overflow) {
size_t linelen = (last_ws > 0) ? (size_t)last_ws : (pos - start);
if (linelen == 0) { /* 单字符超宽(罕见):至少放一个字符防死循环 */
unsigned char c = (unsigned char)txt[start];
int ch = (c < 0x80) ? 1 : (c < 0xE0) ? 2 : (c < 0xF0) ? 3 : 4;
linelen = (size_t)ch;
}
size_t cp = linelen < MEOW_LYRIC_SEG_SZ - 1 ? linelen : MEOW_LYRIC_SEG_SZ - 1;
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
pos = start + linelen;
if (last_ws > 0) {
size_t sp = start + (size_t)last_ws + 1;
if (sp > pos) pos = sp; /* 跳过断点后的一个空格 */
}
n++;
} else {
size_t cp = (len - start) < MEOW_LYRIC_SEG_SZ - 1 ? (len - start) : MEOW_LYRIC_SEG_SZ - 1;
memcpy(seg[n], txt + start, cp); seg[n][cp] = 0;
n++;
break;
}
}
if (n == 0) { seg[0][0] = 0; n = 1; }
return n;
}