Letras (.lyr)¶
Índice¶
- Resumen
- Formato del archivo
- Campos de cabecera
- Líneas de letra con timestamp
- Ejemplo completo
- Constantes del motor
- Funciones del motor
- Flujo de carga
- Notas y consejos
Resumen¶
El motor de letras de la barra AGS lee archivos con extensión .lyr almacenados en ~/lyrics/. Cada archivo contiene los metadatos de una canción y sus letras con timestamps de inicio y fin por línea. El motor indexa todos los archivos al arrancar y los mantiene en caché en memoria para minimizar lecturas de disco.
Las letras se muestran en la píldora derecha de la barra de música (ver AGS Bar → LyricsViewer).
Formato del archivo¶
Los archivos .lyr son texto plano. Tienen dos secciones:
- Cabecera: tags
name:"..."/author:"..."(+ opcionalisinstrumental:). - Letra: líneas en formato LRC
[MM:SS.xx] texto.
Las líneas que empiezan con # son comentarios y se ignoran. Las líneas vacías también.
# Comentario opcional
name:"Nombre oficial de la canción"
author:"Artista principal"
[00:10.00] Primera línea de letra
[00:14.50] Segunda línea
[00:18.75] Con precisión de centésimas
Campos de cabecera¶
| Campo | Requerido | Descripción |
|---|---|---|
name:"..." |
Recomendado | Título de la canción. Acepta alias separados por \|\|\|: el primero es el principal, el resto matchean títulos alternativos. |
author:"..." |
Recomendado | Artista principal. También acepta alias con \|\|\| (feat / karaoke / remix, distintos nombres del artista). |
isinstrumental: |
Opcional | true → la píldora muestra "Instrumental" y se ignoran las líneas de letra. Acepta también "isinstrumental": true o is_instrumental. |
other: y author_other: fueron eliminados
Los tags other: / author_other: (separados por comas) ya no existen. Ahora los alias van dentro de name: / author: separados por ||| (solo la subcadena exacta ||| separa — un | o || suelto no divide).
Ejemplo de alias con |||¶
Permite que el archivo matchee aunque el reproductor muestre "Miles C" o "Flowers (Official)".
Líneas de letra con timestamp¶
Cada línea usa el formato LRC (un solo timestamp de inicio):
MM:SS.xx: minutos:segundos con fracción opcional. El motor lo convierte a microsegundos para sincronía exacta.- El texto va sin comillas después del
]. - El fin de cada línea se infiere como el inicio de la siguiente (la última recibe una cola sintética de +8 s) — no se escribe.
- Una línea con timestamp y texto vacío es una pausa (se muestra
♫ ... ♫). - Las líneas se ordenan por tiempo al parsear, así que el orden en el archivo no importa.
Ejemplo completo¶
# flowers.lyr
name:"Flowers|||Flowers (Official)"
author:"Miley Cyrus|||Miles C"
[00:00.00]
[00:18.00] We were good, we were gold
[00:22.00] Kinda dream that can't be sold
[00:26.00] We were right till we weren't
[00:30.00] Built a home and watched it burn
[00:34.00]
[00:42.00] I didn't want to leave you
[00:46.00] I didn't want to lie
Ejemplo instrumental (guardado en ~/lyrics/instrumental/):
Nombre del archivo
El nombre del archivo (sin extensión) también se usa como clave de búsqueda. flowers.lyr permite encontrar la canción buscando "flowers".
Constantes del motor¶
Definidas en ~/.config/ags/widget/Bar/MusicBar.tsx:
| Constante | Valor | Descripción |
|---|---|---|
LYRICS_DIR |
~/lyrics |
Directorio donde se buscan los .lyr |
INSTRUMENTAL_DIR |
~/lyrics/instrumental |
Donde se guardan los .lyr marcados isinstrumental: true |
MAX_CACHE |
200 |
Máximo de entradas en caché de memoria |
DECAY_λ |
0.001 |
Factor de decaimiento para puntuación de caché |
NO_TRACK |
/org/mpris/.../NoTrack |
ID MPRIS de "sin pista activa" |
Funciones del motor¶
normalizeSongStr(s)¶
Normaliza una cadena para comparación fuzzy:
- Convierte a minúsculas.
- Elimina paréntesis comunes: (Remastered), (Live), (feat. X), (Explicit), etc.
- Elimina caracteres no alfanuméricos.
- Colapsa espacios múltiples.
normalizeSongStr("Flowers (Remastered 2023)") // → "flowers"
normalizeSongStr("Lo Que Siento (feat. Cuco)") // → "lo que siento"
parseTimeMicros(raw)¶
Convierte un string de timestamp "MM:SS.ffffff" a un número de microsegundos.
parseTimeMicros("1:23") // → 83_000_000
parseTimeMicros("1:23.5") // → 83_500_000
parseTimeMicros("0:18.500000") // → 18_500_000
parseLyrFile(path, content)¶
Parsea el contenido completo de un archivo .lyr y devuelve un objeto LyricFile:
interface LyricFile {
path: string // Ruta absoluta del archivo
names: string[] // name: → alias por |||; [0] = principal
authors: string[] // author: → alias por |||; [0] = principal
lines: LyricLine[] // Líneas de letra (LRC)
isInstrumental: boolean // tag isinstrumental: true → píldora muestra "Instrumental"
}
interface LyricLine {
start: number // microsegundos
end: number // microsegundos
text: string
}
El parser recorre el archivo línea a línea:
- Ignora líneas vacías y comentarios (
#). - Detecta cabecera con regex:
name:,author:(alias por|||),isinstrumental:. - Detecta líneas de letra con el patrón LRC
[MM:SS.xx] texto. - Ordena las líneas por tiempo e infiere el
endde cada una como elstartde la siguiente (la última recibe +8 s).
buildIndex()¶
Escanea ~/lyrics/ y ~/lyrics/instrumental/ (helper indexDir) y construye lyricsIndex, un Map<string, LyricFile[]> donde las claves son:
- El nombre del archivo (sin
.lyr). - Cada alias de
name:(separados por|||).
Todo en minúsculas. Múltiples archivos pueden compartir una clave.
watchLyricsDir()¶
Usa Gio.FileMonitor para vigilar el directorio ~/lyrics/. Cuando se detecta un cambio (archivo añadido, modificado o eliminado), llama a buildIndex() después de un debounce de 400 ms.
Esto significa que puedes añadir o editar archivos .lyr sin reiniciar AGS.
lookupOnDisk(title, artist)¶
Busca el mejor archivo .lyr para una canción dada. Algoritmo:
- Normaliza
titleyartist. - Busca en
lyricsIndextodas las claves que contengan o estén contenidas en el título normalizado. - Para cada candidato, calcula una puntuación con
scoreOf(). - Devuelve el candidato con la mayor puntuación (≥ 0).
Puntuación scoreOf(lf)¶
| Condición | Puntuación |
|---|---|
Ningún alias de authors coincide con el artista |
-1 (descartado) |
El nombre principal (names[0]) coincide exactamente con el título |
3 |
names[0] contiene o es contenido por el título |
2 |
Algún alias de names coincide exactamente |
2 |
Algún alias de names contiene o es contenido por el título |
1 |
| Sin metadatos de nombre | 0 |
cacheLookup(songId, title, artist)¶
Wrapper sobre lookupOnDisk con caché en memoria (lyricCache). La caché:
- Identifica canciones por
songId(basado entrackidMPRIS oartista|títulonormalizado). - Incrementa un contador de reproducciones y actualiza
lastPlayedAt. - Si la caché supera
MAX_CACHEentradas, expulsa la de menor puntuación usando decaimiento exponencial:
findActiveLine(lines, posMicros)¶
Dado un array de LyricLine[] y la posición actual de reproducción en microsegundos, devuelve el índice de la línea activa: aquella cuyo start <= pos < end. Retorna -1 si ninguna línea está activa en ese momento.
Flujo de carga¶
AGS arranque
└── buildIndex() — lee ~/lyrics/, llena lyricsIndex
└── watchLyricsDir() — inicia monitor de cambios
Track cambia (MPRIS event)
└── buildSongId() — genera ID único para la pista
└── cacheLookupWithApiFallback()
└── lyricCache hit? → retorna inmediatamente
└── on-disk (.lyr / instrumental) → si hay, retorna
└── fetchLrclib() → lrclib.net (1 request; retry con título limpio)
└── encuentra letra → escribe un .lyr nuevo en ~/lyrics/ (reindexa)
└── instrumental → guarda en ~/lyrics/instrumental/
└── nada → neg-cache 30 días (no vuelve a pedir)
└── LyricsViewer actualiza líneas
Cada LYRIC_SYNC_MS (default 5 ms; low-end: 300 ms)
└── posición interpolada → microsegundos
└── findActiveLine() → índice de línea activa
└── LyricsViewer resalta la línea activa
lrclib.net + logs
Si no hay .lyr local, el motor (Helpers/Lyrics.ts) busca en lrclib.net y cachea el resultado como .lyr para la próxima vez. Los eventos (REQ, LYR, MISS/CACHE, MISS/NOLYR, NET ERR) se ven en el Dashboard → Logs → lrclib.net. La prioridad local-vs-online se ajusta en Settings → MUSIC BAR (lyricsPriority).
Notas y consejos¶
Cómo añadir letras
- Crea
~/lyrics/nombre-cancion.lyr - Añade la cabecera con
name:yauthor: - Añade las líneas con timestamps
- AGS detecta el archivo automáticamente (sin reiniciar)
Canciones con múltiples artistas o títulos
Usa alias con ||| en author: (o name:) para cubrir casos donde la misma canción aparece bajo distintos nombres. Ej: author:"Artista|||Nombre alternativo". Tantos alias como quieras; el fin de cada línea se infiere, no hay que preocuparse por timestamps de fin.
Herramientas útiles
- LRC Maker online — para crear letras con timestamps exportables
- Las líneas
.lyrya usan el formato de línea LRC ([MM:SS.xx] texto): tomá un.lrc, quitá los tags tipo[ti:]/[ar:], agregáname:"..."/author:"..."arriba, guardá como.lyr - Si la canción no tiene letra pero es instrumental, marcala con
isinstrumental: true(se guarda en~/lyrics/instrumental/)