soundwave/backend/audio/models_lyrics.py

102 lines
3.3 KiB
Python
Raw Permalink Normal View History

"""Lyrics models for audio tracks"""
from django.db import models
from audio.models import Audio
class Lyrics(models.Model):
"""Store lyrics for audio tracks"""
audio = models.OneToOneField(
Audio,
on_delete=models.CASCADE,
related_name='lyrics',
primary_key=True
)
# Synced lyrics in LRC format [mm:ss.xx]text
synced_lyrics = models.TextField(blank=True, default='')
# Plain text lyrics without timestamps
plain_lyrics = models.TextField(blank=True, default='')
# Track is instrumental (no lyrics)
is_instrumental = models.BooleanField(default=False)
# Lyrics source (lrclib, genius, manual, etc.)
source = models.CharField(max_length=50, default='lrclib')
# Language code (en, es, fr, etc.)
language = models.CharField(max_length=10, blank=True, default='')
# When lyrics were fetched
fetched_date = models.DateTimeField(auto_now=True)
# Whether fetch was attempted (to avoid repeated failures)
fetch_attempted = models.BooleanField(default=False)
# Number of fetch attempts
fetch_attempts = models.IntegerField(default=0)
# Last fetch error message
last_error = models.TextField(blank=True, default='')
class Meta:
verbose_name_plural = "Lyrics"
ordering = ['-fetched_date']
def __str__(self):
return f"Lyrics for {self.audio.title}"
@property
def has_lyrics(self):
"""Check if lyrics are available"""
return bool(self.synced_lyrics or self.plain_lyrics)
@property
def is_synced(self):
"""Check if lyrics are synchronized"""
return bool(self.synced_lyrics)
def get_display_lyrics(self):
"""Get lyrics for display (prefer synced over plain)"""
if self.is_instrumental:
return "[Instrumental]"
return self.synced_lyrics or self.plain_lyrics or ""
class LyricsCache(models.Model):
"""Cache for LRCLIB API responses to avoid duplicate requests"""
# Composite key: title + artist + album + duration
title = models.CharField(max_length=500)
artist_name = models.CharField(max_length=500)
album_name = models.CharField(max_length=500, blank=True, default='')
duration = models.IntegerField() # Duration in seconds
# Cached response
synced_lyrics = models.TextField(blank=True, default='')
plain_lyrics = models.TextField(blank=True, default='')
is_instrumental = models.BooleanField(default=False)
# Metadata
language = models.CharField(max_length=10, blank=True, default='')
source = models.CharField(max_length=50, default='lrclib')
# Cache management
cached_date = models.DateTimeField(auto_now_add=True)
last_accessed = models.DateTimeField(auto_now=True)
access_count = models.IntegerField(default=0)
# Whether this is a "not found" cache entry
not_found = models.BooleanField(default=False)
class Meta:
indexes = [
models.Index(fields=['title', 'artist_name', 'duration']),
models.Index(fields=['cached_date']),
]
unique_together = [['title', 'artist_name', 'album_name', 'duration']]
def __str__(self):
return f"{self.title} - {self.artist_name}"