From 66a1e4128bf9ea399c6ed3d54985aa13ee51bb41 Mon Sep 17 00:00:00 2001 From: phillychi3 Date: Fri, 3 Jan 2025 14:25:39 +0800 Subject: [PATCH] feat: anime not finish --- backend/src/core/syncfile.py | 21 ++-- backend/src/main.py | 2 + backend/src/routers/video.py | 96 +++++++++++++++++-- .../web/src/routes/app/anime/+page.svelte | 31 ++++++ frontend/web/src/routes/app/anime/+page.ts | 10 ++ .../routes/app/anime/[animeid]/+page.svelte | 89 +++++++++++++++++ .../src/routes/app/anime/[animeid]/+page.ts | 10 ++ 7 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 frontend/web/src/routes/app/anime/+page.svelte create mode 100644 frontend/web/src/routes/app/anime/+page.ts create mode 100644 frontend/web/src/routes/app/anime/[animeid]/+page.svelte create mode 100644 frontend/web/src/routes/app/anime/[animeid]/+page.ts diff --git a/backend/src/core/syncfile.py b/backend/src/core/syncfile.py index a50bf7a..f997031 100644 --- a/backend/src/core/syncfile.py +++ b/backend/src/core/syncfile.py @@ -15,7 +15,7 @@ AnimeSeries, AnimeTag, ) -from core.logger import logging +from core.logger import logger def sync_text_file(metadata: dict, db: Session): @@ -43,7 +43,8 @@ def sync_text_file(metadata: dict, db: Session): def sync_video_file(metadata: dict, db: Session): """同步影片檔案到資料庫""" try: - logging.debug(f"Syncing video file: {metadata}") + logger.debug(f"Syncing video file: {metadata}") + logger.info(f"Syncing video file: {metadata}") video_file = VideoFile( filename=metadata.get("filename"), filepath=metadata.get("file_path"), @@ -56,13 +57,13 @@ def sync_video_file(metadata: dict, db: Session): ) anime = None - if metadata.get("isanime") and metadata.get("anime"): + if metadata.get("isanime"): anime = db.exec( - select(AnimeSeries).where(AnimeSeries.title == metadata["anime"]) + select(AnimeSeries).where(AnimeSeries.title == metadata["title"]) ).first() if not anime: anime = AnimeSeries( - title=metadata["anime"], + title=metadata["title"], description=metadata.get("description"), season_number=metadata.get("season_number", 1), release_date=metadata.get("date"), @@ -77,7 +78,9 @@ def sync_video_file(metadata: dict, db: Session): anime.tags.append(anime_tag) video = Video( - title=metadata.get("title") or metadata["filename"], + title=metadata.get("title") or metadata["filename"] + if metadata.get("isanime") + else metadata.get("titme") + " - " + metadata.get("season_number", 1), duration=metadata.get("duration", 0), description=metadata.get("description"), subtitles=metadata.get("subtitles", []), @@ -189,12 +192,12 @@ def sync_dir_file(dir_path: Path) -> list: for file in files: file_path = Path(root) / file if file_path in exist_files: - logging.debug(f"File {file_path} already exists") + logger.debug(f"File {file_path} already exists") continue try: files.append(FileParser().parse_file(file_path)) except Exception as e: - logging.error(f"Error parsing file {file_path} : {str(e)}") + logger.error(f"Error parsing file {file_path} : {str(e)}") pass for file in files: if file.get("file_type") == FileType.MUSIC: @@ -218,7 +221,7 @@ def sync_one_file(file_path: Path): try: file = FileParser().parse_file(file_path) except Exception as e: - logging.error(f"Error parsing file {file_path}: {str(e)}") + logger.error(f"Error parsing file {file_path}: {str(e)}") return if file.get("file_type") == FileType.MUSIC: sync_music_file(metadata=file, db=db) diff --git a/backend/src/main.py b/backend/src/main.py index 790425a..4d857f9 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -13,6 +13,7 @@ from routers.user import user_router from routers.setting import setting_router from routers.playlist import playlist_router +from routers.video import video_router from core.logger import logger from starlette.middleware.cors import CORSMiddleware @@ -41,6 +42,7 @@ def ping(): app.include_router(user_router) app.include_router(setting_router) app.include_router(playlist_router) +app.include_router(video_router) logger.info("Server started") cors = [ diff --git a/backend/src/routers/video.py b/backend/src/routers/video.py index 7bf5e32..e704373 100644 --- a/backend/src/routers/video.py +++ b/backend/src/routers/video.py @@ -1,24 +1,66 @@ -from typing import Annotated +from typing import Annotated, Optional from fastapi import APIRouter, Depends +from pydantic import BaseModel from sqlmodel import Session, select from sqlalchemy.orm import selectinload from db import get_db -from models import AnimeSeries +from models import AnimeSeries, VideoFile +from fastapi import HTTPException video_router = APIRouter(prefix="/video", tags=["video"]) SessionDep = Annotated[Session, Depends(get_db)] -@video_router.get("/anime", response_model=AnimeSeries) +class TagResponse(BaseModel): + id: Optional[int] + name: str + description: Optional[str] + + +class EpisodeResponse(BaseModel): + id: Optional[int] + episode_number: Optional[int] + title: Optional[str] + file: Optional[VideoFile] + description: Optional[str] + subtitles: list[str] + audio_tracks: list[str] + thumbnail: Optional[str] + series_id: Optional[int] + episode_number: Optional[int] + + +class AnimeResponse(BaseModel): + id: Optional[int] + title: str + original_title: Optional[str] + release_date: str + author: Optional[str] + studio: Optional[str] + description: Optional[str] + season_number: Optional[int] + total_episodes: Optional[int] + cover_image: Optional[str] + created_at: Optional[str] + updated_at: Optional[str] + tags: list[TagResponse] = [] + episodes: list[EpisodeResponse] = [] + + class Config: + from_attributes = True + + +@video_router.get("/anime", response_model=list[AnimeSeries]) async def get_anime(session: SessionDep): """獲取動畫列表""" - animes = session.exec(AnimeSeries).all() + statement = select(AnimeSeries) + animes = session.exec(statement).all() return animes -@video_router.get("/anime/{anime_id}", response_model=AnimeSeries) +@video_router.get("/anime/{anime_id}", response_model=AnimeResponse) async def get_anime_by_id(anime_id: int, session: SessionDep): """獲取動畫資訊""" stmt = ( @@ -26,5 +68,45 @@ async def get_anime_by_id(anime_id: int, session: SessionDep): .where(AnimeSeries.id == anime_id) .options(selectinload(AnimeSeries.tags), selectinload(AnimeSeries.episodes)) ) - anime = session.exec(stmt).first() - return anime + + result = session.exec(stmt).first() + + tags = [ + TagResponse(id=tag.id, name=tag.name, description=tag.description) + for tag in result.tags + ] + + # 手動轉換 Video 到 EpisodeResponse + episodes = [ + EpisodeResponse( + id=ep.id, + episode_number=ep.episode_number, + title=ep.title, + file=ep.file, + description=ep.description, + subtitles=ep.subtitles, + audio_tracks=ep.audio_tracks, + thumbnail=ep.thumbnail, + series_id=ep.series_id, + ) + for ep in result.episodes + ] + + if not result: + raise HTTPException(status_code=404, detail=f"找不到 ID 為 {anime_id} 的動畫") + return AnimeResponse( + id=result.id, + title=result.title, + original_title=result.original_title, + release_date=result.release_date, + author=result.author, + studio=result.studio, + description=result.description, + season_number=result.season_number, + total_episodes=result.total_episodes, + cover_image=result.cover_image, + created_at=str(result.created_at) if result.created_at else None, + updated_at=str(result.updated_at) if result.updated_at else None, + tags=tags, + episodes=episodes, + ) diff --git a/frontend/web/src/routes/app/anime/+page.svelte b/frontend/web/src/routes/app/anime/+page.svelte new file mode 100644 index 0000000..430074d --- /dev/null +++ b/frontend/web/src/routes/app/anime/+page.svelte @@ -0,0 +1,31 @@ + + +
+ {#each data.animes as anime} +
+
+ {#if anime.cover_image} + {anime.title} + {/if} +
+
+

{anime.title}

+

{anime.description}

+
+
+ {/each} +
diff --git a/frontend/web/src/routes/app/anime/+page.ts b/frontend/web/src/routes/app/anime/+page.ts new file mode 100644 index 0000000..94ed755 --- /dev/null +++ b/frontend/web/src/routes/app/anime/+page.ts @@ -0,0 +1,10 @@ +import { APIUrl } from '$lib/api'; +import type { PageLoad } from './$types'; + +export const load = (async ({ fetch }) => { + const response = await fetch(`${APIUrl}/video/anime`); + const animes = await response.json(); + return { + animes + }; +}) satisfies PageLoad; \ No newline at end of file diff --git a/frontend/web/src/routes/app/anime/[animeid]/+page.svelte b/frontend/web/src/routes/app/anime/[animeid]/+page.svelte new file mode 100644 index 0000000..a59af3b --- /dev/null +++ b/frontend/web/src/routes/app/anime/[animeid]/+page.svelte @@ -0,0 +1,89 @@ + + +
+
+
+ {#if animes.cover_image} + {animes.title} + {:else} +
+ 無封面 +
+ {/if} +
+ +
+

{animes.title}

+ {#if animes.original_title} +

{animes.original_title}

+ {/if} + +
+

{animes.description || '暫無簡介'}

+
+ +
+
+ 放送日期: + {animes.release_date} +
+ {#if animes.studio} +
+ 製作公司: + {animes.studio} +
+ {/if} + {#if animes.author} +
+ 原作: + {animes.author} +
+ {/if} +
+ + +
+ {#each animes.tags as tag} + + {tag.name} + + {/each} +
+
+
+ + +
+

劇集列表

+
+ {#each animes.episodes as episode} +
+ {#if episode.thumbnail} + {episode.title} + {:else} +
+ {/if} +
+

第 {episode.episode_number} 集

+ {#if episode.title} +

{episode.title}

+ {/if} +
+
+ {/each} +
+
+
diff --git a/frontend/web/src/routes/app/anime/[animeid]/+page.ts b/frontend/web/src/routes/app/anime/[animeid]/+page.ts new file mode 100644 index 0000000..1959cca --- /dev/null +++ b/frontend/web/src/routes/app/anime/[animeid]/+page.ts @@ -0,0 +1,10 @@ +import { APIUrl } from '$lib/api' +import type { PageLoad } from './$types' + +export const load = (async ({ fetch, params }) => { + const response = await fetch(`${APIUrl}/video/anime/${params.animeid}`) + const animes = await response.json() + return { + animes + } +}) satisfies PageLoad