Files
media-player-server/media_server/services/metadata_service.py
T
alexei.dolgolyov 5439af1955
Lint & Test / test (push) Failing after 9s
Release / create-release (push) Successful in 1s
Release / build-windows (push) Successful in 59s
Add CI/CD pipelines, NSIS installer, ES module bundling, and ruff linting
- Add Gitea Actions workflows: test.yml (lint + test on push/PR) and
  release.yml (build + NSIS installer + upload on v* tags)
- Add NSIS installer with optional desktop shortcut and auto-start
- Add esbuild bundler: ES module migration with IIFE bundle output
- Add build-dist-windows.sh for cross-building Windows distribution
- Fix all ruff lint errors (import sorting, unused imports, line length)
- Remove redundant scripts (start-server.bat, stop-server.bat,
  start-server-background.vbs)
- Update CLAUDE.md with CI/CD and release documentation
2026-03-23 02:01:28 +03:00

204 lines
7.1 KiB
Python

"""Metadata extraction service for media files."""
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
class MetadataService:
"""Service for extracting metadata from media files."""
@staticmethod
def extract_audio_metadata(file_path: Path) -> dict:
"""Extract metadata from an audio file.
Args:
file_path: Path to the audio file.
Returns:
Dictionary with audio metadata.
"""
try:
from mutagen import File as MutagenFile
audio = MutagenFile(str(file_path), easy=True)
if audio is None:
return {"error": "Unable to read audio file"}
try:
metadata = {
"type": "audio",
"filename": file_path.name,
"path": str(file_path),
}
# Extract duration
if hasattr(audio.info, "length"):
metadata["duration"] = round(audio.info.length, 2)
# Extract bitrate
if hasattr(audio.info, "bitrate"):
metadata["bitrate"] = audio.info.bitrate
# Extract sample rate
if hasattr(audio.info, "sample_rate"):
metadata["sample_rate"] = audio.info.sample_rate
elif hasattr(audio.info, "samplerate"):
metadata["sample_rate"] = audio.info.samplerate
# Extract channels
if hasattr(audio.info, "channels"):
metadata["channels"] = audio.info.channels
# Extract tags (use easy=True for consistent tag names)
if audio is not None and hasattr(audio, "tags") and audio.tags:
# Easy tags provide lists, so we take the first item
tags = audio.tags
if "title" in tags:
metadata["title"] = tags["title"][0] if isinstance(tags["title"], list) else tags["title"]
if "artist" in tags:
metadata["artist"] = tags["artist"][0] if isinstance(tags["artist"], list) else tags["artist"]
if "album" in tags:
metadata["album"] = tags["album"][0] if isinstance(tags["album"], list) else tags["album"]
if "albumartist" in tags:
metadata["album_artist"] = (
tags["albumartist"][0] if isinstance(tags["albumartist"], list) else tags["albumartist"]
)
if "date" in tags:
metadata["date"] = tags["date"][0] if isinstance(tags["date"], list) else tags["date"]
if "genre" in tags:
metadata["genre"] = tags["genre"][0] if isinstance(tags["genre"], list) else tags["genre"]
if "tracknumber" in tags:
metadata["track_number"] = (
tags["tracknumber"][0] if isinstance(tags["tracknumber"], list) else tags["tracknumber"]
)
# If no title tag, use filename
if "title" not in metadata:
metadata["title"] = file_path.stem
return metadata
finally:
if hasattr(audio, 'close'):
audio.close()
except ImportError:
logger.error("mutagen library not installed, cannot extract metadata")
return {"error": "mutagen library not installed"}
except Exception as e:
logger.error(f"Error extracting audio metadata from {file_path}: {e}")
return {
"error": str(e),
"filename": file_path.name,
"title": file_path.stem,
}
@staticmethod
def extract_video_metadata(file_path: Path) -> dict:
"""Extract basic metadata from a video file.
Args:
file_path: Path to the video file.
Returns:
Dictionary with video metadata.
"""
try:
from mutagen import File as MutagenFile
video = MutagenFile(str(file_path))
if video is None:
return {
"type": "video",
"filename": file_path.name,
"title": file_path.stem,
}
try:
metadata = {
"type": "video",
"filename": file_path.name,
"path": str(file_path),
}
# Extract duration
if hasattr(video.info, "length"):
metadata["duration"] = round(video.info.length, 2)
# Extract bitrate
if hasattr(video.info, "bitrate"):
metadata["bitrate"] = video.info.bitrate
# Extract video-specific properties if available
if hasattr(video.info, "width"):
metadata["width"] = video.info.width
if hasattr(video.info, "height"):
metadata["height"] = video.info.height
# Try to extract title from tags
if hasattr(video, "tags") and video.tags:
tags = video.tags
if hasattr(tags, "get"):
title = tags.get("title") or tags.get("TITLE") or tags.get("\xa9nam")
if title:
metadata["title"] = title[0] if isinstance(title, list) else str(title)
# If no title tag, use filename
if "title" not in metadata:
metadata["title"] = file_path.stem
return metadata
finally:
if hasattr(video, 'close'):
video.close()
except ImportError:
logger.error("mutagen library not installed, cannot extract metadata")
return {
"error": "mutagen library not installed",
"type": "video",
"filename": file_path.name,
"title": file_path.stem,
}
except Exception as e:
logger.debug(f"Error extracting video metadata from {file_path}: {e}")
# Return basic metadata
return {
"type": "video",
"filename": file_path.name,
"title": file_path.stem,
}
@staticmethod
def extract_metadata(file_path: Path) -> dict:
"""Extract metadata from a media file (auto-detect type).
Args:
file_path: Path to the media file.
Returns:
Dictionary with media metadata.
"""
from .browser_service import AUDIO_EXTENSIONS, VIDEO_EXTENSIONS
suffix = file_path.suffix.lower()
if suffix in AUDIO_EXTENSIONS:
return MetadataService.extract_audio_metadata(file_path)
elif suffix in VIDEO_EXTENSIONS:
return MetadataService.extract_video_metadata(file_path)
else:
return {
"error": "Unsupported file type",
"filename": file_path.name,
}