diff --git a/.vscode/launch.json b/.vscode/launch.json index 96b9c850..da500c43 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,8 @@ "NUXT_API_URL": "http://localhost:8081/api/", "NUXT_PUBLIC_WSS": ":8081/", }, - "console": "internalConsole" + "console": "internalConsole", + "outputCapture": "std", }, { "name": "Python: main.py", diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 2479085e..4ffef7b1 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -38,6 +38,7 @@ from .Utils import ( StreamingError, arg_converter, calc_download_path, + get_sidecar_subtitles, get_video_info, validate_url, validate_uuid, @@ -1178,6 +1179,46 @@ class HttpAPI(Common): except Exception as e: return web.json_response(data={"error": str(e)}, status=web.HTTPInternalServerError.status_code) + @route("GET", "api/file/info/{file:.*}") + async def get_file_info(self, request: Request) -> Response: + """ + Get file info + + Args: + request (Request): The request object. + + Returns: + Response: The response object. + + """ + file: str = request.match_info.get("file") + if not file: + return web.json_response(data={"error": "file is required."}, status=web.HTTPBadRequest.status_code) + + try: + realFile: str = calc_download_path(base_path=self.config.download_path, folder=file, create_path=False) + if not os.path.exists(realFile) or not os.path.isfile(realFile): + return web.json_response( + data={"error": f"File '{file}' does not exist."}, status=web.HTTPNotFound.status_code + ) + + realFile = Path(realFile) + + response = { + "ffprobe": await ffprobe(realFile), + "sidecar": get_sidecar_subtitles(realFile), + } + + for i, f in enumerate(response["sidecar"]): + response["sidecar"][i]["file"] = str(Path(realFile).with_name(f["file"].name)).replace( + self.config.download_path, "" + ).strip("/") + + return web.json_response(data=response, status=web.HTTPOk.status_code, dumps=self.encoder.encode) + except Exception as e: + LOG.exception(e) + return web.json_response(data={"error": str(e)}, status=web.HTTPInternalServerError.status_code) + @route("GET", "api/youtube/auth") async def is_authenticated(self, request: Request) -> Response: """ diff --git a/app/library/Playlist.py b/app/library/Playlist.py index a83074ca..befd9193 100644 --- a/app/library/Playlist.py +++ b/app/library/Playlist.py @@ -1,13 +1,10 @@ -import glob -import re from pathlib import Path from urllib.parse import quote from aiohttp.web import HTTPFound, Response from .ffprobe import ffprobe -from .Subtitle import Subtitle -from .Utils import StreamingError, calc_download_path, check_id +from .Utils import StreamingError, calc_download_path, check_id, get_sidecar_subtitles class Playlist: @@ -41,28 +38,19 @@ class Playlist: msg = f"Unable to get '{rFile}' duration." raise StreamingError(msg) - duration: float = float(ff.metadata.get("duration")) - playlist = [] playlist.append("#EXTM3U") subs = "" - index = 0 - for item in self.get_sidecar_files(rFile): - if item.suffix not in Subtitle.allowed_extensions: - continue - - index += 1 - lang: str = "und" - lg = re.search(r"\.(?P\w{2,3})\.\w{3}$", item.name) - if lg: - lang = lg.groupdict().get("lang") + duration: float = float(ff.metadata.get("duration")) + for sub_file in get_sidecar_subtitles(rFile): + lang = sub_file["lang"] + item = sub_file["file"] + name = sub_file["name"] subs = ',SUBTITLES="subs"' - url = f"{self.url}api/player/m3u8/subtitle/{quote(str(Path(file).with_name(item.name)))}.m3u8?duration={duration}" - name = f"{item.suffix[1:].upper()} ({index}) - {lang}" playlist.append( f'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="{name}",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE="{lang}",URI="{url}"' ) @@ -71,20 +59,3 @@ class Playlist: playlist.append(f"{self.url}api/player/m3u8/video/{quote(file)}.m3u8") return "\n".join(playlist) - - def get_sidecar_files(self, file: Path) -> list[Path]: - """ - Get sidecar files for the given file. - - :param file: File to get sidecar files for. - :return: List of sidecar files. - """ - files = [] - - for sub_file in file.parent.glob(f"{glob.escape(file.stem)}.*"): - if sub_file == file or sub_file.is_file() is False or sub_file.stem.startswith("."): - continue - - files.append(sub_file) - - return files diff --git a/app/library/Utils.py b/app/library/Utils.py index b8e3fe7d..bad19221 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -1,4 +1,5 @@ import copy +import glob import ipaddress import json import logging @@ -27,6 +28,8 @@ IGNORED_KEYS: tuple[str] = ( ) YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None +ALLOWED_SUBS_EXTENSIONS: tuple[str] = (".srt", ".vtt", ".ass") + class StreamingError(Exception): """Raised when an error occurs during streaming.""" @@ -524,3 +527,27 @@ def validate_uuid(uuid_str: str, version: int = 4) -> bool: return True except ValueError: return False + + +def get_sidecar_subtitles(file: pathlib.Path) -> list[dict]: + """ + Get sidecar files for the given file. + + :param file: File to get sidecar files for. + :return: List of sidecar files. + """ + files = [] + + for i, f in enumerate(file.parent.glob(f"{glob.escape(file.stem)}.*")): + if f == file or f.is_file() is False or f.stem.startswith("."): + continue + + if f.suffix not in ALLOWED_SUBS_EXTENSIONS: + continue + + lg = re.search(r"\.(?P\w{2,3})\.\w{3}$", f.name) + lang = lg.groupdict().get("lang") if lg else "und" + + files.append({"file": f, "lang": lang, "name": f"{f.suffix[1:].upper()} ({i}) - {lang}"}) + + return files diff --git a/app/library/encoder.py b/app/library/encoder.py index 2a788649..0ed61c57 100644 --- a/app/library/encoder.py +++ b/app/library/encoder.py @@ -1,4 +1,5 @@ import json +from pathlib import Path class Encoder(json.JSONEncoder): @@ -9,10 +10,14 @@ class Encoder(json.JSONEncoder): """ def default(self, o): - if isinstance(o, object) and hasattr(o, "serialize"): - return o.serialize() + if isinstance(o, Path): + return str(o) - if isinstance(o, object) and hasattr(o, "__dict__"): - return o.__dict__ + if isinstance(o, object): + if hasattr(o, "serialize"): + return o.serialize() + + if hasattr(o, "__dict__"): + return o.__dict__ return json.JSONEncoder.default(self, o) diff --git a/ui/components/History.vue b/ui/components/History.vue index b5aab95d..44be0a0f 100644 --- a/ui/components/History.vue +++ b/ui/components/History.vue @@ -235,12 +235,11 @@

-