soundwave/backend/audio/id3_service.py.backup

281 lines
10 KiB
Text
Raw Permalink Normal View History

"""ID3 tagging service using mutagen"""
import os
import logging
from mutagen.mp4 import MP4, MP4Cover
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB, TDRC, TRCK, TCON, TXXX
from mutagen.flac import FLAC, Picture
from typing import Optional, Dict, Any
logger = logging.getLogger(__name__)
class ID3TagService:
"""Service for reading and writing ID3 tags"""
@staticmethod
def read_tags(file_path: str) -> Dict[str, Any]:
"""
Read ID3 tags from audio file
Args:
file_path: Path to audio file
Returns:
Dictionary with tag information
"""
try:
if not os.path.exists(file_path):
logger.error(f"File not found: {file_path}")
return {}
ext = os.path.splitext(file_path)[1].lower()
tags = {}
if ext == '.m4a' or ext == '.mp4':
audio = MP4(file_path)
tags = {
'title': audio.get('\xa9nam', [''])[0],
'artist': audio.get('\xa9ART', [''])[0],
'album': audio.get('\xa9alb', [''])[0],
'album_artist': audio.get('aART', [''])[0],
'year': audio.get('\xa9day', [''])[0],
'genre': audio.get('\xa9gen', [''])[0],
'track_number': audio.get('trkn', [(0, 0)])[0][0],
'disc_number': audio.get('disk', [(0, 0)])[0][0],
'duration': audio.info.length if audio.info else 0,
'bitrate': audio.info.bitrate if audio.info else 0,
'has_cover': 'covr' in audio,
}
elif ext == '.mp3':
audio = ID3(file_path)
tags = {
'title': str(audio.get('TIT2', '')),
'artist': str(audio.get('TPE1', '')),
'album': str(audio.get('TALB', '')),
'album_artist': str(audio.get('TPE2', '')),
'year': str(audio.get('TDRC', '')),
'genre': str(audio.get('TCON', '')),
'track_number': str(audio.get('TRCK', '')).split('/')[0] if audio.get('TRCK') else '',
'disc_number': str(audio.get('TPOS', '')).split('/')[0] if audio.get('TPOS') else '',
'has_cover': 'APIC:' in audio or any(k.startswith('APIC') for k in audio.keys()),
}
elif ext == '.flac':
audio = FLAC(file_path)
tags = {
'title': audio.get('title', [''])[0],
'artist': audio.get('artist', [''])[0],
'album': audio.get('album', [''])[0],
'album_artist': audio.get('albumartist', [''])[0],
'year': audio.get('date', [''])[0],
'genre': audio.get('genre', [''])[0],
'track_number': audio.get('tracknumber', [''])[0],
'disc_number': audio.get('discnumber', [''])[0],
'duration': audio.info.length if audio.info else 0,
'has_cover': len(audio.pictures) > 0,
}
return tags
except Exception as e:
logger.error(f"Error reading tags from {file_path}: {e}")
return {}
@staticmethod
def write_tags(file_path: str, tags: Dict[str, Any]) -> bool:
"""
Write ID3 tags to audio file
Args:
file_path: Path to audio file
tags: Dictionary with tag information
Returns:
True if successful, False otherwise
"""
try:
if not os.path.exists(file_path):
logger.error(f"File not found: {file_path}")
return False
ext = os.path.splitext(file_path)[1].lower()
if ext == '.m4a' or ext == '.mp4':
audio = MP4(file_path)
if 'title' in tags:
audio['\xa9nam'] = tags['title']
if 'artist' in tags:
audio['\xa9ART'] = tags['artist']
if 'album' in tags:
audio['\xa9alb'] = tags['album']
if 'album_artist' in tags:
audio['aART'] = tags['album_artist']
if 'year' in tags:
audio['\xa9day'] = str(tags['year'])
if 'genre' in tags:
audio['\xa9gen'] = tags['genre']
if 'track_number' in tags:
audio['trkn'] = [(int(tags['track_number']), 0)]
if 'disc_number' in tags:
audio['disk'] = [(int(tags['disc_number']), 0)]
audio.save()
elif ext == '.mp3':
try:
audio = ID3(file_path)
except:
audio = ID3()
if 'title' in tags:
audio['TIT2'] = TIT2(encoding=3, text=tags['title'])
if 'artist' in tags:
audio['TPE1'] = TPE1(encoding=3, text=tags['artist'])
if 'album' in tags:
audio['TALB'] = TALB(encoding=3, text=tags['album'])
if 'year' in tags:
audio['TDRC'] = TDRC(encoding=3, text=str(tags['year']))
if 'genre' in tags:
audio['TCON'] = TCON(encoding=3, text=tags['genre'])
if 'track_number' in tags:
audio['TRCK'] = TRCK(encoding=3, text=str(tags['track_number']))
audio.save(file_path)
elif ext == '.flac':
audio = FLAC(file_path)
if 'title' in tags:
audio['title'] = tags['title']
if 'artist' in tags:
audio['artist'] = tags['artist']
if 'album' in tags:
audio['album'] = tags['album']
if 'album_artist' in tags:
audio['albumartist'] = tags['album_artist']
if 'year' in tags:
audio['date'] = str(tags['year'])
if 'genre' in tags:
audio['genre'] = tags['genre']
if 'track_number' in tags:
audio['tracknumber'] = str(tags['track_number'])
if 'disc_number' in tags:
audio['discnumber'] = str(tags['disc_number'])
audio.save()
logger.info(f"Successfully wrote tags to {file_path}")
return True
except Exception as e:
logger.error(f"Error writing tags to {file_path}: {e}")
return False
@staticmethod
def embed_cover_art(file_path: str, image_data: bytes, mime_type: str = 'image/jpeg') -> bool:
"""
Embed cover art into audio file
Args:
file_path: Path to audio file
image_data: Image binary data
mime_type: MIME type of image
Returns:
True if successful, False otherwise
"""
try:
if not os.path.exists(file_path):
logger.error(f"File not found: {file_path}")
return False
ext = os.path.splitext(file_path)[1].lower()
if ext == '.m4a' or ext == '.mp4':
audio = MP4(file_path)
if mime_type == 'image/png':
cover_format = MP4Cover.FORMAT_PNG
else:
cover_format = MP4Cover.FORMAT_JPEG
audio['covr'] = [MP4Cover(image_data, imageformat=cover_format)]
audio.save()
elif ext == '.mp3':
try:
audio = ID3(file_path)
except:
audio = ID3()
audio['APIC'] = APIC(
encoding=3,
mime=mime_type,
type=3, # Cover (front)
desc='Cover',
data=image_data
)
audio.save(file_path)
elif ext == '.flac':
audio = FLAC(file_path)
picture = Picture()
picture.type = 3 # Cover (front)
picture.mime = mime_type
picture.desc = 'Cover'
picture.data = image_data
audio.clear_pictures()
audio.add_picture(picture)
audio.save()
logger.info(f"Successfully embedded cover art in {file_path}")
return True
except Exception as e:
logger.error(f"Error embedding cover art in {file_path}: {e}")
return False
@staticmethod
def extract_cover_art(file_path: str) -> Optional[bytes]:
"""
Extract cover art from audio file
Args:
file_path: Path to audio file
Returns:
Image binary data or None
"""
try:
if not os.path.exists(file_path):
return None
ext = os.path.splitext(file_path)[1].lower()
if ext == '.m4a' or ext == '.mp4':
audio = MP4(file_path)
covers = audio.get('covr', [])
if covers:
return bytes(covers[0])
elif ext == '.mp3':
audio = ID3(file_path)
for key in audio.keys():
if key.startswith('APIC'):
return audio[key].data
elif ext == '.flac':
audio = FLAC(file_path)
if audio.pictures:
return audio.pictures[0].data
return None
except Exception as e:
logger.error(f"Error extracting cover art from {file_path}: {e}")
return None