diff --git a/backend/src/auth/users.py b/backend/src/auth/users.py index eee3754..cc133ea 100644 --- a/backend/src/auth/users.py +++ b/backend/src/auth/users.py @@ -85,3 +85,4 @@ cookie_auth_backend = AuthenticationBackend( fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [bearer_auth_backend, cookie_auth_backend]) current_active_user = fastapi_users.current_user(active=True) +current_superuser = fastapi_users.current_user(active=True, superuser=True) diff --git a/backend/src/config.py b/backend/src/config.py index 55d301e..1de6718 100644 --- a/backend/src/config.py +++ b/backend/src/config.py @@ -4,7 +4,7 @@ from pydantic_settings import BaseSettings class BasicConfig(BaseSettings): - storage_directory: Path = "./data" + image_directory: Path = "./data" tv_directory: Path = "./tv" movie_directory: Path = "./movie" torrent_directory: Path = "./torrent" diff --git a/backend/src/main.py b/backend/src/main.py index 40af85b..791f864 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -62,7 +62,7 @@ if basic_config.DEVELOPMENT: basic_config.torrent_directory.mkdir(parents=True, exist_ok=True) basic_config.tv_directory.mkdir(parents=True, exist_ok=True) basic_config.movie_directory.mkdir(parents=True, exist_ok=True) - basic_config.storage_directory.mkdir(parents=True, exist_ok=True) + basic_config.image_directory.mkdir(parents=True, exist_ok=True) log.warning("Development Mode activated!") else: log.info("Development Mode not activated!") diff --git a/backend/src/metadataProvider/abstractMetaDataProvider.py b/backend/src/metadataProvider/abstractMetaDataProvider.py index 6cbc40b..a6c4998 100644 --- a/backend/src/metadataProvider/abstractMetaDataProvider.py +++ b/backend/src/metadataProvider/abstractMetaDataProvider.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__) class AbstractMetadataProvider(ABC): - storage_path = config.BasicConfig().storage_directory + storage_path = config.BasicConfig().image_directory @property @abstractmethod def name(self) -> str: diff --git a/backend/src/torrent/router.py b/backend/src/torrent/router.py index 0b00c27..900c9fb 100644 --- a/backend/src/torrent/router.py +++ b/backend/src/torrent/router.py @@ -2,13 +2,18 @@ from fastapi import APIRouter from fastapi import status from fastapi.params import Depends -from auth.users import current_active_user +from auth.users import current_active_user, current_superuser from torrent.dependencies import TorrentServiceDependency from torrent.schemas import TorrentId, Torrent router = APIRouter() +@router.get("/{torrent_id}", status_code=status.HTTP_200_OK, response_model=Torrent) +def get_torrent(service: TorrentServiceDependency, torrent_id: TorrentId): + return service.get_torrent_by_id(id=torrent_id) + + @router.get("/", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)], response_model=list[Torrent]) def get_all_torrents(service: TorrentServiceDependency, ): @@ -27,11 +32,6 @@ def import_all_torrents(service: TorrentServiceDependency): return service.import_all_torrents() -@router.get("/{torrent_id}", status_code=status.HTTP_200_OK, response_model=Torrent) -def get_torrent(service: TorrentServiceDependency, torrent_id: TorrentId): - return service.get_torrent_by_id(id=torrent_id) - - -@router.delete("/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)]) +@router.delete("/{torrent_id}", status_code=status.HTTP_200_OK, dependencies=[Depends(current_superuser)]) def delete_torrent(service: TorrentServiceDependency, torrent_id: TorrentId): service.delete_torrent(torrent_id=torrent_id) diff --git a/backend/src/torrent/service.py b/backend/src/torrent/service.py index 92187f4..85db38b 100644 --- a/backend/src/torrent/service.py +++ b/backend/src/torrent/service.py @@ -19,7 +19,7 @@ from config import BasicConfig from indexer import IndexerQueryResult from torrent.repository import get_seasons_files_of_torrent, get_show_of_torrent, save_torrent from torrent.schemas import Torrent, TorrentStatus, TorrentId -from torrent.utils import list_files_recursively, get_torrent_filepath +from torrent.utils import list_files_recursively, get_torrent_filepath, import_file from tv.schemas import SeasonFile, Show log = logging.getLogger(__name__) @@ -139,21 +139,25 @@ class TorrentService: self.api_client.torrents_resume(torrent_hashes=torrent.hash) return self.get_torrent_status(torrent=torrent) - # TODO: add function to differentiate between .srt files and stuff + # TODO: add function to import .srt files def import_torrent(self, torrent: Torrent) -> Torrent: log.info(f"importing torrent {torrent}") all_files = list_files_recursively(path=get_torrent_filepath(torrent=torrent)) log.debug(f"Found {len(all_files)} files downloaded by the torrent") files = [] + subtitle_files = [] for file in all_files: file_type = mimetypes.guess_file_type(file) if file_type[0] is not None: if file_type[0].startswith("video"): files.append(file) log.debug(f"File is a video, it will be imported: {file}") + elif file_type[0].startswith("text") and file.suffix == ".srt": + subtitle_files.append(file) + log.debug(f"File is a subtitle, it will be imported: {file}") else: log.debug(f"File is not a video, will not be imported: {file}") - log.debug(f"Importing these {len(files)} files:\n" + pprint.pformat(files)) + log.info(f"Importing these {len(files)} files:\n" + pprint.pformat(files)) show: Show = get_show_of_torrent(db=self.db, torrent_id=torrent.id) show_file_path = BasicConfig().tv_directory / f"{show.name} ({show.year}) [{show.metadata_provider}id-{show.external_id}]" @@ -162,26 +166,42 @@ class TorrentService: for season_file in season_files: season = tv.service.get_season(db=self.db, season_id=season_file.season_id) season_path = show_file_path / Path(f"Season {season.number}") + try: season_path.mkdir(parents=True) except FileExistsError: log.warning(f"Path already exists: {season_path}") + for episode in season.episodes: episode_file_name = f"{show.name} S{season.number:02d}E{episode.number:02d}" if season_file.file_path_suffix != "": episode_file_name += f" - {season_file.file_path_suffix}" - target_file = season_path / episode_file_name + + pattern = r'.*[.]S0?' + str(season.number) + r'E0?' + str(episode.number) + r"[.].*" + subtitle_pattern = pattern + r"[.]([A-Za-z]{2})[.]srt" + target_file_name = season_path / episode_file_name + + # import subtitles + for subtitle_file in subtitle_files: + log.debug(f"Searching for pattern {subtitle_pattern} in subtitle file: {subtitle_file.name}") + regex_result = re.search(subtitle_pattern, subtitle_file.name) + if regex_result: + language_code = regex_result.group(1) + log.debug( + f"Found matching pattern: {subtitle_pattern} in subtitle file: {subtitle_file.name}," + + f" extracted language code: {language_code}") + target_subtitle_file = target_file_name.with_suffix(f".{language_code}.srt") + import_file(target_file=target_subtitle_file, source_file=subtitle_file) + else: + log.debug(f"Didn't find any pattern {subtitle_pattern} in subtitle file: {subtitle_file.name}") + + # import episode videos for file in files: - pattern = r'.*[.]S0?' + str(season.number) + r'E0?' + str(episode.number) + r"[.].*" - # NOTE: irgendwos passt mit file.name glauwi ned???? - log.debug(f"Searching for pattern {pattern} in file: {file.name}") + log.debug(f"Searching for pattern {pattern} in video file: {file.name}") if re.search(pattern, file.name): log.debug(f"Found matching pattern: {pattern} in file {file.name}") - target_file = target_file.with_suffix(file.suffix) - if target_file.exists(): - target_file.unlink() - - target_file.hardlink_to(file) + target_video_file = target_file_name.with_suffix(file.suffix) + import_file(target_file=target_video_file, source_file=file) break else: log.warning(f"S{season.number}E{episode.number} in Torrent {torrent.title}'s files not found.") diff --git a/backend/src/torrent/utils.py b/backend/src/torrent/utils.py index c3a2a42..76d303d 100644 --- a/backend/src/torrent/utils.py +++ b/backend/src/torrent/utils.py @@ -22,3 +22,9 @@ def list_files_recursively(path: Path = Path(".")) -> list[Path]: def get_torrent_filepath(torrent: Torrent): return BasicConfig().torrent_directory / torrent.title + + +def import_file(target_file: Path, source_file: Path): + if target_file.exists(): + target_file.unlink() + target_file.hardlink_to(source_file) diff --git a/backend/src/tv/router.py b/backend/src/tv/router.py index a4f4d0d..9aec19c 100644 --- a/backend/src/tv/router.py +++ b/backend/src/tv/router.py @@ -3,7 +3,7 @@ from fastapi.responses import JSONResponse import metadataProvider import tv.service -from auth.users import current_active_user +from auth.users import current_active_user, current_superuser from database import DbSessionDependency from indexer.schemas import PublicIndexerQueryResult, IndexerQueryResultId from tv.exceptions import MediaAlreadyExists @@ -87,12 +87,14 @@ def unrequest_season(db: DbSessionDependency, request: SeasonRequest): # 1 is the default for season_number because it returns multi season torrents @router.get("/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)], response_model=list[PublicIndexerQueryResult]) -def get_torrents_for_a_season(db: DbSessionDependency, show_id: ShowId, season_number: int = 1): - return tv.service.get_all_available_torrents_for_a_season(db=db, season_number=season_number, show_id=show_id) +def get_torrents_for_a_season(db: DbSessionDependency, show_id: ShowId, season_number: int = 1, + search_query_override: str = None): + return tv.service.get_all_available_torrents_for_a_season(db=db, season_number=season_number, show_id=show_id, + search_query_override=search_query_override) # download a torrent -@router.post("/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)]) +@router.post("/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_superuser)]) def download_a_torrent(db: DbSessionDependency, public_indexer_result_id: IndexerQueryResultId, show_id: ShowId, override_file_path_suffix: str = ""): return tv.service.download_torrent(db=db, public_indexer_result_id=public_indexer_result_id, show_id=show_id, diff --git a/backend/src/tv/service.py b/backend/src/tv/service.py index 5138658..a2764ba 100644 --- a/backend/src/tv/service.py +++ b/backend/src/tv/service.py @@ -48,11 +48,16 @@ def check_if_show_exists(db: Session, raise ValueError("External ID and metadata provider or Show ID must be provided") -def get_all_available_torrents_for_a_season(db: Session, season_number: int, show_id: ShowId) -> list[ +def get_all_available_torrents_for_a_season(db: Session, season_number: int, show_id: ShowId, + search_query_override: str = None) -> list[ IndexerQueryResult]: log.debug(f"getting all available torrents for season {season_number} for show {show_id}") show = tv.repository.get_show(show_id=show_id, db=db) - torrents: list[IndexerQueryResult] = indexer.service.search(query=show.name + " S" + str(season_number), db=db) + if search_query_override is not None: + search_query = search_query_override + else: + search_query = show.name + " Season " + str(season_number) + torrents: list[IndexerQueryResult] = indexer.service.search(query=search_query, db=db) result: list[IndexerQueryResult] = [] for torrent in torrents: if season_number in torrent.season: diff --git a/docker-compose.yml b/docker-compose.yml index 3dd0591..e22b317 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,4 +37,4 @@ services: - 6881:6881/udp restart: unless-stopped volumes: - - ./torrent/:/torrent/:rw \ No newline at end of file + - ./torrent/:/download/:rw \ No newline at end of file