refactor: standardize parameter usage in movie router and service

This commit is contained in:
maxid
2025-12-29 22:45:50 +01:00
parent 35880231e0
commit 8de3a71bfd
2 changed files with 171 additions and 247 deletions

View File

@@ -38,15 +38,16 @@ router = APIRouter()
# METADATA & SEARCH
# -----------------------------------------------------------------------------
@router.get(
"/search",
dependencies=[Depends(current_active_user)],
response_model=list[MetaDataProviderSearchResult],
)
def search_for_movie(
query: str,
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
query: str,
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
):
"""
Search for a movie on the configured metadata provider.
@@ -62,8 +63,8 @@ def search_for_movie(
response_model=list[MetaDataProviderSearchResult],
)
def get_popular_movies(
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
):
"""
Get a list of recommended/popular movies from the metadata provider.
@@ -75,6 +76,7 @@ def get_popular_movies(
# IMPORTING
# -----------------------------------------------------------------------------
@router.get(
"/importable",
status_code=status.HTTP_200_OK,
@@ -82,7 +84,7 @@ def get_popular_movies(
response_model=list[MediaImportSuggestion],
)
def get_all_importable_movies(
movie_service: movie_service_dep, metadata_provider: metadata_provider_dep
movie_service: movie_service_dep, metadata_provider: metadata_provider_dep
):
"""
Get a list of unknown movies that were detected in the movie directory and are importable.
@@ -96,14 +98,14 @@ def get_all_importable_movies(
status_code=status.HTTP_204_NO_CONTENT,
)
def import_detected_movie(
movie_service: movie_service_dep, movie: movie_dep, directory: str
movie_service: movie_service_dep, movie: movie_dep, directory: str
):
"""
Import a detected movie from the specified directory into the library.
"""
source_directory = Path(directory)
if source_directory not in get_importable_media_directories(
AllEncompassingConfig().misc.movie_directory
AllEncompassingConfig().misc.movie_directory
):
raise HTTPException(status.HTTP_400_BAD_REQUEST, "No such directory")
success = movie_service.import_existing_movie(
@@ -117,6 +119,7 @@ def import_detected_movie(
# MOVIES
# -----------------------------------------------------------------------------
@router.get(
"",
dependencies=[Depends(current_active_user)],
@@ -141,10 +144,10 @@ def get_all_movies(movie_service: movie_service_dep):
},
)
def add_a_movie(
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
movie_id: int,
language: str | None = None,
movie_service: movie_service_dep,
metadata_provider: metadata_provider_dep,
movie_id: int,
language: str | None = None,
):
"""
Add a new movie to the library.
@@ -190,6 +193,7 @@ def get_available_libraries():
# MOVIE REQUESTS
# -----------------------------------------------------------------------------
@router.get(
"/requests",
dependencies=[Depends(current_active_user)],
@@ -208,9 +212,9 @@ def get_all_movie_requests(movie_service: movie_service_dep):
response_model=MovieRequest,
)
def create_movie_request(
movie_service: movie_service_dep,
movie_request: CreateMovieRequest,
user: Annotated[UserRead, Depends(current_active_user)],
movie_service: movie_service_dep,
movie_request: CreateMovieRequest,
user: Annotated[UserRead, Depends(current_active_user)],
):
"""
Create a new movie request.
@@ -232,10 +236,10 @@ def create_movie_request(
response_model=MovieRequest,
)
def update_movie_request(
movie_service: movie_service_dep,
movie_request_id: MovieRequestId,
update_movie_request: MovieRequestBase,
user: Annotated[UserRead, Depends(current_active_user)],
movie_service: movie_service_dep,
movie_request_id: MovieRequestId,
update_movie_request: MovieRequestBase,
user: Annotated[UserRead, Depends(current_active_user)],
):
"""
Update an existing movie request.
@@ -252,10 +256,10 @@ def update_movie_request(
@router.patch("/requests/{movie_request_id}", status_code=status.HTTP_204_NO_CONTENT)
def authorize_request(
movie_service: movie_service_dep,
movie_request_id: MovieRequestId,
user: Annotated[UserRead, Depends(current_superuser)],
authorized_status: bool = False,
movie_service: movie_service_dep,
movie_request_id: MovieRequestId,
user: Annotated[UserRead, Depends(current_superuser)],
authorized_status: bool = False,
):
"""
Authorize or de-authorize a movie request.
@@ -277,7 +281,7 @@ def authorize_request(
dependencies=[Depends(current_superuser)],
)
def delete_movie_request(
movie_service: movie_service_dep, movie_request_id: MovieRequestId
movie_service: movie_service_dep, movie_request_id: MovieRequestId
):
"""
Delete a movie request.
@@ -289,6 +293,7 @@ def delete_movie_request(
# MOVIES - SINGLE RESOURCE
# -----------------------------------------------------------------------------
@router.get(
"/{movie_id}",
dependencies=[Depends(current_active_user)],
@@ -298,7 +303,7 @@ def get_movie_by_id(movie_service: movie_service_dep, movie: movie_dep):
"""
Get details for a specific movie.
"""
return movie_service.get_public_movie_by_id(movie_id=movie.id)
return movie_service.get_public_movie_by_id(movie=movie)
@router.delete(
@@ -307,16 +312,16 @@ def get_movie_by_id(movie_service: movie_service_dep, movie: movie_dep):
dependencies=[Depends(current_superuser)],
)
def delete_a_movie(
movie_service: movie_service_dep,
movie: movie_dep,
delete_files_on_disk: bool = False,
delete_torrents: bool = False,
movie_service: movie_service_dep,
movie: movie_dep,
delete_files_on_disk: bool = False,
delete_torrents: bool = False,
):
"""
Delete a movie from the library.
"""
movie_service.delete_movie(
movie_id=movie.id,
movie=movie,
delete_files_on_disk=delete_files_on_disk,
delete_torrents=delete_torrents,
)
@@ -329,14 +334,14 @@ def delete_a_movie(
status_code=status.HTTP_204_NO_CONTENT,
)
def set_library(
movie: movie_dep,
movie_service: movie_service_dep,
library: str,
movie: movie_dep,
movie_service: movie_service_dep,
library: str,
) -> None:
"""
Set the library path for a Movie.
"""
movie_service.set_movie_library(movie_id=movie.id, library=library)
movie_service.set_movie_library(movie=movie, library=library)
return
@@ -349,7 +354,7 @@ def get_movie_files_by_movie_id(movie_service: movie_service_dep, movie: movie_d
"""
Get files associated with a specific movie.
"""
return movie_service.get_public_movie_files_by_movie_id(movie_id=movie.id)
return movie_service.get_public_movie_files(movie=movie)
@router.get(
@@ -358,15 +363,15 @@ def get_movie_files_by_movie_id(movie_service: movie_service_dep, movie: movie_d
response_model=list[IndexerQueryResult],
)
def search_for_torrents_for_movie(
movie_service: movie_service_dep,
movie: movie_dep,
search_query_override: str | None = None,
movie_service: movie_service_dep,
movie: movie_dep,
search_query_override: str | None = None,
):
"""
Search for torrents for a specific movie.
"""
return movie_service.get_all_available_torrents_for_a_movie(
movie_id=movie.id, search_query_override=search_query_override
return movie_service.get_all_available_torrents_for_movie(
movie=movie, search_query_override=search_query_override
)
@@ -377,16 +382,16 @@ def search_for_torrents_for_movie(
response_model=Torrent,
)
def download_torrent_for_movie(
movie_service: movie_service_dep,
movie: movie_dep,
public_indexer_result_id: IndexerQueryResultId,
override_file_path_suffix: str = "",
movie_service: movie_service_dep,
movie: movie_dep,
public_indexer_result_id: IndexerQueryResultId,
override_file_path_suffix: str = "",
):
"""
Trigger a download for a specific torrent for a movie.
"""
return movie_service.download_torrent(
public_indexer_result_id=public_indexer_result_id,
movie_id=movie.id,
movie=movie,
override_movie_file_path_suffix=override_file_path_suffix,
)

View File

@@ -1,4 +1,3 @@
import re
import shutil
from pathlib import Path
@@ -15,7 +14,7 @@ from media_manager.indexer.utils import evaluate_indexer_query_results
from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult
from media_manager.notification.service import NotificationService
from media_manager.schemas import MediaImportSuggestion
from media_manager.torrent.schemas import Torrent, TorrentStatus, Quality
from media_manager.torrent.schemas import Torrent, TorrentStatus
from media_manager.torrent.service import TorrentService
from media_manager.movies import log
from media_manager.movies.schemas import (
@@ -31,14 +30,12 @@ from media_manager.movies.schemas import (
)
from media_manager.torrent.schemas import QualityStrings
from media_manager.movies.repository import MovieRepository
from media_manager.exceptions import NotFoundError
from media_manager.torrent.repository import TorrentRepository
from media_manager.torrent.utils import (
import_file,
get_files_for_import,
remove_special_characters,
get_importable_media_directories,
extract_external_id_from_string,
remove_special_chars_and_parentheses,
)
from media_manager.indexer.service import IndexerService
@@ -76,8 +73,11 @@ class MovieService:
:param language: Optional language code (ISO 639-1) to fetch metadata in.
"""
movie_with_metadata = metadata_provider.get_movie_metadata(
id=external_id, language=language
external_id=external_id, language=language
)
if not movie_with_metadata:
return None
saved_movie = self.movie_repository.save_movie(movie=movie_with_metadata)
metadata_provider.download_movie_poster_image(movie=saved_movie)
return saved_movie
@@ -124,65 +124,53 @@ class MovieService:
def delete_movie(
self,
movie_id: MovieId,
movie: Movie,
delete_files_on_disk: bool = False,
delete_torrents: bool = False,
) -> None:
"""
Delete a movie from the database, optionally deleting files and torrents.
:param movie_id: The ID of the movie to delete.
:param movie: The movie to delete.
:param delete_files_on_disk: Whether to delete the movie's files from disk.
:param delete_torrents: Whether to delete associated torrents from the torrent client.
"""
if delete_files_on_disk or delete_torrents:
movie = self.movie_repository.get_movie_by_id(movie_id=movie_id)
log.debug(f"Deleting ID: {movie.id} - Name: {movie.name}")
torrents = self.get_torrents_for_movie(movie=movie)
for torrent in torrents.torrents:
if delete_torrents:
self.torrent_service.delete_torrent(torrent=torrent)
if delete_files_on_disk:
# Logic to delete files on disk if needed, usually handled by torrent client or manual cleanup
pass
if delete_files_on_disk:
# Get the movie's directory path
movie_dir = self.get_movie_root_path(movie=movie)
log.debug(f"Attempt to delete movie directory: {movie_dir}")
if movie_dir.exists() and movie_dir.is_dir():
shutil.rmtree(movie_dir)
log.info(f"Deleted movie directory: {movie_dir}")
if delete_torrents:
# Get all torrents associated with this movie
torrents = self.movie_repository.get_torrents_by_movie_id(
movie_id=movie_id
)
for torrent in torrents:
movie_path = self.get_movie_root_path(movie=movie)
if movie_path.exists():
try:
self.torrent_service.cancel_download(
torrent=torrent, delete_files=True
)
log.info(f"Deleted torrent: {torrent.torrent_title}")
except Exception as e:
log.warning(f"Failed to delete torrent {torrent.hash}: {e}")
shutil.rmtree(movie_path)
except OSError as e:
log.error(f"Error: {movie_path} : {e.strerror}")
# Delete from database
self.movie_repository.delete_movie(movie_id=movie_id)
self.movie_repository.delete_movie(movie_id=movie.id)
def get_public_movie_files_by_movie_id(
self, movie_id: MovieId
) -> list[PublicMovieFile]:
def get_public_movie_files(self, movie: Movie) -> list[PublicMovieFile]:
"""
Get all public movie files for a given movie ID.
Get all public movie files for a given movie.
:param movie_id: The ID of the movie.
:param movie: The movie object.
:return: A list of public movie files.
"""
movie_files = self.movie_repository.get_movie_files_by_movie_id(
movie_id=movie_id
movie_id=movie.id
)
public_movie_files = [PublicMovieFile.model_validate(x) for x in movie_files]
result = []
for movie_file in public_movie_files:
if self.movie_file_exists_on_file(movie_file=movie_file):
movie_file.downloaded = True
movie_file.exists_on_disk = self.movie_file_exists_on_file(
movie_file=movie_file
)
result.append(movie_file)
return result
@@ -202,47 +190,36 @@ class MovieService:
:raises ValueError: If neither external ID and metadata provider nor movie ID are provided.
"""
if external_id and metadata_provider:
try:
return (
self.movie_repository.get_movie_by_external_id(
external_id=external_id, metadata_provider=metadata_provider
)
return True
except NotFoundError:
return False
is not None
)
elif movie_id:
try:
self.movie_repository.get_movie_by_id(movie_id=movie_id)
return True
except NotFoundError:
return False
return self.movie_repository.get_movie_by_id(movie_id=movie_id) is not None
else:
raise ValueError(
"External ID and metadata provider or Movie ID must be provided"
"Either external_id and metadata_provider or movie_id must be provided"
)
def get_all_available_torrents_for_a_movie(
self, movie_id: MovieId, search_query_override: str = None
def get_all_available_torrents_for_movie(
self, movie: Movie, search_query_override: str = None
) -> list[IndexerQueryResult]:
"""
Get all available torrents for a given movie.
:param movie_id: The ID of the movie.
:param movie: The movie object.
:param search_query_override: Optional override for the search query.
:return: A list of indexer query results.
"""
log.debug(f"getting all available torrents for movie {movie_id}")
movie = self.movie_repository.get_movie_by_id(movie_id=movie_id)
if search_query_override:
torrents = self.indexer_service.search(
query=search_query_override, is_tv=False
)
return torrents
query = search_query_override
else:
torrents = self.indexer_service.search_movie(movie=movie)
query = f"{movie.name} {movie.year}"
return evaluate_indexer_query_results(
is_tv=False, query_results=torrents, media=movie
)
results = self.indexer_service.search(query)
return evaluate_indexer_query_results(results, movie.name, movie.year)
def get_all_movies(self) -> list[Movie]:
"""
@@ -264,22 +241,10 @@ class MovieService:
"""
results = metadata_provider.search_movie(query)
for result in results:
if self.check_if_movie_exists(
external_id=result.external_id, metadata_provider=metadata_provider.name
):
result.added = True
# Fetch the internal movie ID.
try:
movie = self.movie_repository.get_movie_by_external_id(
external_id=result.external_id,
metadata_provider=metadata_provider.name,
)
result.id = movie.id
except Exception:
log.error(
f"Unable to find internal movie ID for {result.external_id} on {metadata_provider.name}"
)
result.exists_in_library = self.check_if_movie_exists(
external_id=result.external_id,
metadata_provider=metadata_provider.name,
)
return results
def get_popular_movies(
@@ -295,24 +260,24 @@ class MovieService:
filtered_results = []
for result in results:
if not self.check_if_movie_exists(
external_id=result.external_id, metadata_provider=metadata_provider.name
):
filtered_results.append(result)
result.exists_in_library = self.check_if_movie_exists(
external_id=result.external_id,
metadata_provider=metadata_provider.name,
)
filtered_results.append(result)
return filtered_results
def get_public_movie_by_id(self, movie_id: MovieId) -> PublicMovie:
def get_public_movie_by_id(self, movie: Movie) -> PublicMovie:
"""
Get a public movie by its ID.
Get a public movie from a Movie object.
:param movie_id: The ID of the movie.
:param movie: The movie object.
:return: A public movie.
"""
movie = self.movie_repository.get_movie_by_id(movie_id=movie_id)
torrents = self.get_torrents_for_movie(movie=movie).torrents
public_movie = PublicMovie.model_validate(movie)
public_movie.downloaded = self.is_movie_downloaded(movie_id=movie.id)
public_movie.downloaded = self.is_movie_downloaded(movie=movie)
public_movie.torrents = torrents
return public_movie
@@ -325,15 +290,15 @@ class MovieService:
"""
return self.movie_repository.get_movie_by_id(movie_id=movie_id)
def is_movie_downloaded(self, movie_id: MovieId) -> bool:
def is_movie_downloaded(self, movie: Movie) -> bool:
"""
Check if a movie is downloaded.
:param movie_id: The ID of the movie.
:param movie: The movie object.
:return: True if the movie is downloaded, False otherwise.
"""
movie_files = self.movie_repository.get_movie_files_by_movie_id(
movie_id=movie_id
movie_id=movie.id
)
for movie_file in movie_files:
if self.movie_file_exists_on_file(movie_file=movie_file):
@@ -379,8 +344,8 @@ class MovieService:
"""
return self.movie_repository.get_movie_requests()
def set_movie_library(self, movie_id: MovieId, library: str) -> None:
self.movie_repository.set_movie_library(movie_id=movie_id, library=library)
def set_movie_library(self, movie: Movie, library: str) -> None:
self.movie_repository.set_movie_library(movie_id=movie.id, library=library)
def get_torrents_for_movie(self, movie: Movie) -> RichMovieTorrent:
"""
@@ -412,43 +377,37 @@ class MovieService:
def download_torrent(
self,
public_indexer_result_id: IndexerQueryResultId,
movie_id: MovieId,
movie: Movie,
override_movie_file_path_suffix: str = "",
) -> Torrent:
"""
Download a torrent for a given indexer result and movie.
:param public_indexer_result_id: The ID of the indexer result.
:param movie_id: The ID of the movie.
:param movie: The movie object.
:param override_movie_file_path_suffix: Optional override for the file path suffix.
:return: The downloaded torrent.
"""
indexer_result = self.indexer_service.get_result(
result_id=public_indexer_result_id
indexer_query_result_id=public_indexer_result_id
)
movie_torrent = self.torrent_service.download(indexer_result=indexer_result)
self.torrent_service.pause_download(torrent=movie_torrent)
movie_file = MovieFile(
movie_id=movie_id,
quality=indexer_result.quality,
movie_id=movie.id,
torrent_id=movie_torrent.id,
file_path_suffix=override_movie_file_path_suffix,
)
try:
self.movie_repository.add_movie_file(movie_file=movie_file)
except IntegrityError:
log.error(
f"Movie file for movie {movie_id} and quality {indexer_result.quality} already exists."
log.warning(
f"Movie file for movie {movie.name} and torrent {movie_torrent.title} already exists"
)
self.torrent_service.cancel_download(
torrent=movie_torrent, delete_files=True
)
raise
else:
log.info(
f"Added movie file for movie {movie_id} and quality {indexer_result.quality}."
f"Added movie file for movie {movie.name} and torrent {movie_torrent.title}"
)
self.torrent_service.resume_download(torrent=movie_torrent)
return movie_torrent
def download_approved_movie_request(
@@ -463,36 +422,23 @@ class MovieService:
:raises ValueError: If the movie request is not authorized.
"""
if not movie_request.authorized:
log.error(
f"Movie request {movie_request.id} is not authorized for download"
)
raise ValueError(
f"Movie request {movie_request.id} is not authorized for download"
)
raise ValueError("Movie request is not authorized")
log.info(f"Downloading approved movie request {movie_request.id}")
torrents = self.get_all_available_torrents_for_a_movie(movie_id=movie.id)
torrents = self.get_all_available_torrents_for_movie(movie=movie)
available_torrents: list[IndexerQueryResult] = []
for torrent in torrents:
if (
(torrent.quality.value < movie_request.wanted_quality.value)
or (torrent.quality.value > movie_request.min_quality.value)
or (torrent.seeders < 3)
torrent.quality >= movie_request.min_quality
and torrent.quality <= movie_request.wanted_quality
):
log.info(
f"Skipping torrent {torrent.title} with quality {torrent.quality} for movie {movie.id}, because it does not match the requested quality {movie_request.wanted_quality}"
)
else:
available_torrents.append(torrent)
log.info(
f"Taking torrent {torrent.title} with quality {torrent.quality} for movie {movie.id} into consideration"
)
if len(available_torrents) == 0:
log.warning(
f"No torrents matching criteria were found (wanted quality: {movie_request.wanted_quality}, min_quality: {movie_request.min_quality} for movie {movie.id})"
f"No torrents found for movie request {movie_request.id} with quality between {QualityStrings[movie_request.min_quality]} and {QualityStrings[movie_request.wanted_quality]}"
)
return False
@@ -501,15 +447,13 @@ class MovieService:
torrent = self.torrent_service.download(indexer_result=available_torrents[0])
movie_file = MovieFile(
movie_id=movie.id,
quality=torrent.quality,
torrent_id=torrent.id,
file_path_suffix=QualityStrings[torrent.quality.name].value.upper(),
)
try:
self.movie_repository.add_movie_file(movie_file=movie_file)
except IntegrityError:
log.warning(
f"Movie file for movie {movie.id} and quality {torrent.quality} already exists, skipping."
f"Movie file for movie {movie.name} and torrent {torrent.title} already exists"
)
self.delete_movie_request(movie_request.id)
return True
@@ -526,14 +470,14 @@ class MovieService:
if movie.library != "Default":
for library in misc_config.movie_libraries:
if library.name == movie.library:
log.debug(f"Using library {library.name} for movie {movie.name}")
return (
Path(library.path)
movie_file_path = (
library.path
/ f"{remove_special_characters(movie.name)} ({movie.year}) [{movie.metadata_provider}id-{movie.external_id}]"
)
break
else:
log.warning(
f"Movie library {movie.library} not found in config, using default movie directory."
f"Library {movie.library} not found in config, using default library"
)
return movie_file_path
@@ -553,32 +497,30 @@ class MovieService:
try:
movie_root_path.mkdir(parents=True, exist_ok=True)
except Exception as e:
log.warning(f"Could not create path {movie_root_path}: {e}")
raise e
log.error(f"Failed to create directory {movie_root_path}: {e}")
return False
# import movie video
if video_files:
target_video_file = (
movie_root_path / f"{movie_file_name}{video_files[0].suffix}"
video_file = video_files[0]
target_file_name = f"{movie_file_name}{video_file.suffix}"
success = import_file(
source_file_path=video_file,
destination_directory=movie_root_path,
new_file_name=target_file_name,
)
import_file(target_file=target_video_file, source_file=video_files[0])
success = True
# import subtitles
for subtitle_file in subtitle_files:
language_code_match = re.search(
r"[. ]([a-z]{2})\.srt$", subtitle_file.name, re.IGNORECASE
# Assuming subtitle language detection or naming convention is handled or simple copy
# For now, just copy with original name or simple mapping if needed
# This part might need more robust logic similar to TV shows if multiple subs exist
import_file(
source_file_path=subtitle_file,
destination_directory=movie_root_path,
new_file_name=subtitle_file.name,
move=False, # Usually copy subs
)
if not language_code_match:
log.warning(
f"Subtitle file {subtitle_file.name} does not match expected format, can't extract language code, skipping."
)
continue
language_code = language_code_match.group(1)
target_subtitle_file = (
movie_root_path / f"{movie_file_name}.{language_code}.srt"
)
import_file(target_file=target_subtitle_file, source_file=subtitle_file)
return success
@@ -595,13 +537,15 @@ class MovieService:
if len(video_files) != 1:
# Send notification about multiple video files found
if self.notification_service:
self.notification_service.send_notification_to_all_providers(
title="Multiple Video Files Found",
message=f"Found {len(video_files)} video files in movie torrent '{torrent.title}' for {movie.name} ({movie.year}). Only the first will be imported. Manual intervention recommended.",
self.notification_service.send_notification(
title="Manual Import Required",
body=f"Multiple video files found for movie {movie.name}. Please import manually.",
)
log.error(
"Found multiple video files in movie torrent, only the first will be imported. Manual intervention is recommended."
f"Found {len(video_files)} video files for movie {movie.name}, expected 1. Skipping auto import."
)
return
log.debug(
f"Importing these {len(video_files)} video files and {len(subtitle_files)} subtitle files"
)
@@ -628,19 +572,19 @@ class MovieService:
self.torrent_service.torrent_repository.save_torrent(torrent=torrent)
if self.notification_service:
self.notification_service.send_notification_to_all_providers(
self.notification_service.send_notification(
title="Movie Downloaded",
message=f"Successfully downloaded: {movie.name} ({movie.year}) from torrent {torrent.title}.",
body=f"Movie {movie.name} has been successfully downloaded and imported.",
)
else:
log.error(
f"Importing files for torrent {torrent.title} encountered errors."
f"Failed to import files for torrent {torrent.title}. Check logs for details."
)
if self.notification_service:
self.notification_service.send_notification_to_all_providers(
title="Movie import failed",
message=f"There were errors importing: {movie.name} ({movie.year}) from torrent {torrent.title}. Please check the logs for details.",
self.notification_service.send_notification(
title="Import Failed",
body=f"Failed to import files for movie {movie.name}. Please check logs.",
)
log.info(f"Finished importing files for torrent {torrent.title}")
@@ -649,13 +593,15 @@ class MovieService:
self, movie: Path, metadata_provider: AbstractMetadataProvider
) -> MediaImportSuggestion:
search_result = self.search_for_movie(
remove_special_chars_and_parentheses(movie.name), metadata_provider
query=remove_special_chars_and_parentheses(movie.name),
metadata_provider=metadata_provider,
)
import_candidates = MediaImportSuggestion(
directory=movie, candidates=search_result
file_path=str(movie),
candidates=search_result,
)
log.debug(
f"Found {len(import_candidates.candidates)} candidates for {import_candidates.directory}"
f"Found {len(search_result)} candidates for {movie.name} in {movie.parent}"
)
return import_candidates
@@ -664,9 +610,7 @@ class MovieService:
try:
source_directory.rename(new_source_path)
except Exception as e:
log.error(
f"Failed to rename directory '{source_directory}' to '{new_source_path}': {e}"
)
log.error(f"Failed to rename {source_directory} to {new_source_path}: {e}")
raise Exception("Failed to rename directory") from e
video_files, subtitle_files, all_files = get_files_for_import(
@@ -684,8 +628,6 @@ class MovieService:
MovieFile(
movie_id=movie.id,
file_path_suffix="IMPORTED",
torrent_id=None,
quality=Quality.unknown,
)
)
@@ -705,21 +647,18 @@ class MovieService:
# Use stored original_language preference for metadata fetching
fresh_movie_data = metadata_provider.get_movie_metadata(
id=db_movie.external_id, language=db_movie.original_language
external_id=db_movie.external_id, language=db_movie.original_language
)
if not fresh_movie_data:
log.warning(
f"Could not fetch fresh metadata for movie {db_movie.name} (External ID: {db_movie.external_id}) from {db_movie.metadata_provider}."
f"Could not fetch fresh metadata for movie: {db_movie.name} (ID: {db_movie.external_id})"
)
return db_movie
return None
log.debug(f"Fetched fresh metadata for movie: {fresh_movie_data.name}")
self.movie_repository.update_movie_attributes(
movie_id=db_movie.id,
name=fresh_movie_data.name,
overview=fresh_movie_data.overview,
year=fresh_movie_data.year,
imdb_id=fresh_movie_data.imdb_id,
new_attributes=fresh_movie_data.model_dump(exclude={"id", "seasons"}),
)
updated_movie = self.movie_repository.get_movie_by_id(movie_id=db_movie.id)
@@ -736,25 +675,11 @@ class MovieService:
candidate_dirs = get_importable_media_directories(movie_root_path)
for movie_dir in candidate_dirs:
metadata, external_id = extract_external_id_from_string(movie_dir.name)
if metadata is not None and external_id is not None:
try:
self.movie_repository.get_movie_by_external_id(
external_id=external_id, metadata_provider=metadata
)
log.debug(
f"Movie {movie_dir.name} already exists in the database, skipping."
)
continue
except NotFoundError:
log.debug(
f"Movie {movie_dir.name} not found in database, checking for import candidates."
)
import_candidates = self.get_import_candidates(
movie=movie_dir, metadata_provider=metadata_provider
importable_movies.append(
self.get_import_candidates(
movie=movie_dir, metadata_provider=metadata_provider
)
)
importable_movies.append(import_candidates)
log.debug(f"Found {len(importable_movies)} importable movies.")
return importable_movies
@@ -788,8 +713,8 @@ def auto_download_all_approved_movie_requests() -> None:
):
count += 1
else:
log.warning(
f"Failed to download movie request {movie_request.id} for movie {movie.name}"
log.info(
f"Could not download movie request {movie_request.id} for movie {movie.name}"
)
log.info(f"Auto downloaded {count} approved movie requests")
@@ -822,7 +747,8 @@ def import_all_movie_torrents() -> None:
movie_service.import_torrent_files(torrent=t, movie=movie)
except RuntimeError as e:
log.error(
f"Error importing torrent {t.title} for movie {movie.name}: {e}"
f"Failed to import torrent {t.title}: {e}",
exc_info=True,
)
log.info("Finished importing all torrents")
db.commit()
@@ -862,14 +788,7 @@ def update_all_movies_metadata() -> None:
f"Error initializing metadata provider {movie.metadata_provider} for movie {movie.name}: {str(e)}"
)
continue
updated_movie = movie_service.update_movie_metadata(
movie_service.update_movie_metadata(
db_movie=movie, metadata_provider=metadata_provider
)
if updated_movie:
log.info(
f"Successfully updated metadata for movie: {updated_movie.name}"
)
else:
log.warning(f"Failed to update metadata for movie: {movie.name}")
db.commit()