150 lines
4.7 KiB
Python
150 lines
4.7 KiB
Python
|
|
"""Models for user-uploaded local audio files"""
|
||
|
|
|
||
|
|
from django.db import models
|
||
|
|
from django.contrib.auth import get_user_model
|
||
|
|
import os
|
||
|
|
|
||
|
|
User = get_user_model()
|
||
|
|
|
||
|
|
|
||
|
|
class LocalAudio(models.Model):
|
||
|
|
"""User-uploaded local audio files"""
|
||
|
|
|
||
|
|
owner = models.ForeignKey(
|
||
|
|
User,
|
||
|
|
on_delete=models.CASCADE,
|
||
|
|
related_name='local_audio_files',
|
||
|
|
help_text="User who uploaded this file"
|
||
|
|
)
|
||
|
|
|
||
|
|
# File info
|
||
|
|
title = models.CharField(max_length=500)
|
||
|
|
artist = models.CharField(max_length=200, blank=True)
|
||
|
|
album = models.CharField(max_length=200, blank=True)
|
||
|
|
year = models.IntegerField(null=True, blank=True)
|
||
|
|
genre = models.CharField(max_length=100, blank=True)
|
||
|
|
track_number = models.IntegerField(null=True, blank=True)
|
||
|
|
|
||
|
|
# File details
|
||
|
|
file = models.FileField(upload_to='local_audio/%Y/%m/', max_length=500)
|
||
|
|
file_size = models.BigIntegerField(help_text="File size in bytes")
|
||
|
|
duration = models.IntegerField(null=True, blank=True, help_text="Duration in seconds")
|
||
|
|
|
||
|
|
# Audio properties
|
||
|
|
audio_format = models.CharField(max_length=20, blank=True) # mp3, flac, m4a, etc.
|
||
|
|
bitrate = models.IntegerField(null=True, blank=True, help_text="Bitrate in kbps")
|
||
|
|
sample_rate = models.IntegerField(null=True, blank=True, help_text="Sample rate in Hz")
|
||
|
|
channels = models.IntegerField(null=True, blank=True, help_text="Number of audio channels")
|
||
|
|
|
||
|
|
# Cover art
|
||
|
|
cover_art = models.ImageField(upload_to='local_audio_covers/%Y/%m/', null=True, blank=True)
|
||
|
|
|
||
|
|
# Metadata
|
||
|
|
original_filename = models.CharField(max_length=500)
|
||
|
|
uploaded_date = models.DateTimeField(auto_now_add=True)
|
||
|
|
modified_date = models.DateTimeField(auto_now=True)
|
||
|
|
|
||
|
|
# Playback tracking
|
||
|
|
play_count = models.IntegerField(default=0)
|
||
|
|
last_played = models.DateTimeField(null=True, blank=True)
|
||
|
|
|
||
|
|
# Tags and organization
|
||
|
|
tags = models.JSONField(default=list, blank=True, help_text="User-defined tags")
|
||
|
|
notes = models.TextField(blank=True, help_text="User notes about this file")
|
||
|
|
|
||
|
|
# Favorites
|
||
|
|
is_favorite = models.BooleanField(default=False)
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
ordering = ['-uploaded_date']
|
||
|
|
indexes = [
|
||
|
|
models.Index(fields=['owner', '-uploaded_date']),
|
||
|
|
models.Index(fields=['owner', 'is_favorite']),
|
||
|
|
models.Index(fields=['owner', 'artist']),
|
||
|
|
models.Index(fields=['owner', 'album']),
|
||
|
|
]
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
return f"{self.owner.username} - {self.title}"
|
||
|
|
|
||
|
|
@property
|
||
|
|
def file_size_mb(self):
|
||
|
|
"""Get file size in MB"""
|
||
|
|
return self.file_size / (1024 * 1024)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def duration_formatted(self):
|
||
|
|
"""Get formatted duration (MM:SS)"""
|
||
|
|
if not self.duration:
|
||
|
|
return "00:00"
|
||
|
|
minutes = self.duration // 60
|
||
|
|
seconds = self.duration % 60
|
||
|
|
return f"{minutes:02d}:{seconds:02d}"
|
||
|
|
|
||
|
|
def delete(self, *args, **kwargs):
|
||
|
|
"""Override delete to remove files"""
|
||
|
|
# Delete the audio file
|
||
|
|
if self.file:
|
||
|
|
if os.path.isfile(self.file.path):
|
||
|
|
os.remove(self.file.path)
|
||
|
|
|
||
|
|
# Delete cover art
|
||
|
|
if self.cover_art:
|
||
|
|
if os.path.isfile(self.cover_art.path):
|
||
|
|
os.remove(self.cover_art.path)
|
||
|
|
|
||
|
|
super().delete(*args, **kwargs)
|
||
|
|
|
||
|
|
|
||
|
|
class LocalAudioPlaylist(models.Model):
|
||
|
|
"""Playlists for local audio files"""
|
||
|
|
|
||
|
|
owner = models.ForeignKey(
|
||
|
|
User,
|
||
|
|
on_delete=models.CASCADE,
|
||
|
|
related_name='local_playlists'
|
||
|
|
)
|
||
|
|
|
||
|
|
title = models.CharField(max_length=200)
|
||
|
|
description = models.TextField(blank=True)
|
||
|
|
|
||
|
|
# Playlist image
|
||
|
|
cover_image = models.ImageField(upload_to='local_playlist_covers/', null=True, blank=True)
|
||
|
|
|
||
|
|
# Timestamps
|
||
|
|
created_date = models.DateTimeField(auto_now_add=True)
|
||
|
|
modified_date = models.DateTimeField(auto_now=True)
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
ordering = ['-created_date']
|
||
|
|
indexes = [
|
||
|
|
models.Index(fields=['owner', '-created_date']),
|
||
|
|
]
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
return f"{self.owner.username} - {self.title}"
|
||
|
|
|
||
|
|
|
||
|
|
class LocalAudioPlaylistItem(models.Model):
|
||
|
|
"""Items in local audio playlist"""
|
||
|
|
|
||
|
|
playlist = models.ForeignKey(
|
||
|
|
LocalAudioPlaylist,
|
||
|
|
on_delete=models.CASCADE,
|
||
|
|
related_name='items'
|
||
|
|
)
|
||
|
|
audio = models.ForeignKey(
|
||
|
|
LocalAudio,
|
||
|
|
on_delete=models.CASCADE,
|
||
|
|
related_name='playlist_items'
|
||
|
|
)
|
||
|
|
position = models.IntegerField(default=0)
|
||
|
|
added_date = models.DateTimeField(auto_now_add=True)
|
||
|
|
|
||
|
|
class Meta:
|
||
|
|
ordering = ['position']
|
||
|
|
unique_together = ('playlist', 'audio')
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
return f"{self.playlist.title} - {self.audio.title}"
|