diff --git a/.vscode/settings.json b/.vscode/settings.json index eee8b4c8..d961a26d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "daterange", "dotenv", "finaldir", + "flac", "getpid", "gpac", "httpx", @@ -36,6 +37,7 @@ "muxdelay", "nodesc", "noprogress", + "plexmatch", "postprocessor", "preferredcodec", "preferredquality", diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 3ed9c441..74177928 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -42,9 +42,9 @@ from .Utils import ( encrypt_data, extract_info, get_file, + get_file_sidecar, get_files, get_mime_type, - get_sidecar_subtitles, validate_url, validate_uuid, ) @@ -1550,15 +1550,19 @@ class HttpAPI(Common): ff_info = await ffprobe(realFile) response = { + "title": str(Path(realFile).stem), "ffprobe": ff_info, "mimetype": get_mime_type(ff_info.get("metadata", {}), realFile), - "sidecar": get_sidecar_subtitles(realFile), + "sidecar": get_file_sidecar(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("/") - ) + for key in response["sidecar"]: + for i, f in enumerate(response["sidecar"][key]): + response["sidecar"][key][i]["file"] = ( + str(Path(realFile).with_name(Path(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: diff --git a/app/library/Playlist.py b/app/library/Playlist.py index 316d55ca..563956e1 100644 --- a/app/library/Playlist.py +++ b/app/library/Playlist.py @@ -2,7 +2,7 @@ from pathlib import Path from urllib.parse import quote from .ffprobe import ffprobe -from .Utils import StreamingError, get_sidecar_subtitles +from .Utils import StreamingError, get_file_sidecar class Playlist: @@ -30,9 +30,9 @@ class Playlist: subs = "" duration: float = float(ff.metadata.get("duration")) - for sub_file in get_sidecar_subtitles(file): + for sub_file in get_file_sidecar(file).get("subtitle", []): lang = sub_file["lang"] - item = sub_file["file"] + item = Path(sub_file["file"]) name = sub_file["name"] subs = ',SUBTITLES="subs"' diff --git a/app/library/Utils.py b/app/library/Utils.py index d6a1a3d5..be338ec4 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -26,6 +26,15 @@ YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None ALLOWED_SUBS_EXTENSIONS: tuple[str] = (".srt", ".vtt", ".ass") +FILES_TYPE: list = [ + {"rx": re.compile(r"\.(avi|ts|mkv|mp4|mp3|mpv|ogm|m4v|webm|m4b)$", re.IGNORECASE), "type": "video"}, + {"rx": re.compile(r"\.(mp3|flac|aac|opus|wav|m4a)$", re.IGNORECASE), "type": "audio"}, + {"rx": re.compile(r"\.(srt|ass|ssa|smi|sub|idx)$", re.IGNORECASE), "type": "subtitle"}, + {"rx": re.compile(r"\.(jpg|jpeg|png|gif|bmp|webp)$", re.IGNORECASE), "type": "image"}, + {"rx": re.compile(r"\.(txt|nfo|md|json|yml|yaml|plexmatch)$", re.IGNORECASE), "type": "text"}, + {"rx": re.compile(r"\.(nfo|json|jpg|torrent|\.info\.json)$", re.IGNORECASE), "type": "metadata"}, +] + class StreamingError(Exception): """Raised when an error occurs during streaming.""" @@ -480,7 +489,7 @@ def validate_uuid(uuid_str: str, version: int = 4) -> bool: return False -def get_sidecar_subtitles(file: pathlib.Path) -> list[dict]: +def get_file_sidecar(file: pathlib.Path) -> list[dict]: """ Get sidecar files for the given file. @@ -491,26 +500,60 @@ def get_sidecar_subtitles(file: pathlib.Path) -> list[dict]: list: List of sidecar files. """ - 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 - if f.stat().st_size < 1: continue - lg = re.search(r"\.(?P\w{2,3})\.\w{3}$", f.name) - lang = lg.groupdict().get("lang") if lg else "und" + content_type = "Unknown" + for pattern in FILES_TYPE: + if pattern["rx"].search(f.name): + content_type = pattern["type"] + break - files.append({"file": f, "lang": lang, "name": f"{f.suffix[1:].upper()} ({i}) - {lang}"}) + if content_type == "subtitle": + 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" + content = {"file": f, "lang": lang, "name": f"{f.suffix[1:].upper()} ({i}) - {lang}"} + else: + content = {"file": f} + + if content_type not in files: + files[content_type] = [] + + files[content_type].append(content) + + images = get_possible_images(str(file.parent)) + if len(images) > 0: + if "image" not in files: + files["image"] = [] + + files["image"].extend(images) return files +@lru_cache(maxsize=512) +def get_possible_images(dir: str) -> list[dict]: + images = [] + + path_loc = pathlib.Path(dir, "test.jpg") + + for filename in ["poster", "thumbnail", "artwork", "cover", "fanart"]: + for ext in [".jpg", ".jpeg", ".png", ".webp"]: + f = path_loc.with_stem(filename).with_suffix(ext) + if f.exists(): + images.append({"file": f}) + + return images + + def get_mime_type(metadata: dict, file_path: pathlib.Path) -> str: """ Determine the correct MIME type for a video file based on ffprobe metadata. @@ -738,13 +781,28 @@ def get_files(base_path: str, dir: str | None = None): if file.name.startswith(".") or file.name.startswith("_"): continue + content_type = None + + for pattern in FILES_TYPE: + if pattern["rx"].search(file.name): + content_type = pattern["type"] + break + + if not content_type and file.is_dir(): + content_type = "dir" + + if not content_type: + content_type = "download" + stat = file.stat() contents.append( { "type": "file" if file.is_file() else "dir", + "content_type": content_type, "name": file.name, "path": str(file).replace(base_path, "").strip("/"), "size": stat.st_size, + "mime": get_mime_type({}, file) if file.is_file() else "directory", "mtime": datetime.datetime.fromtimestamp(stat.st_mtime, tz=datetime.UTC).isoformat(), "ctime": datetime.datetime.fromtimestamp(stat.st_ctime, tz=datetime.UTC).isoformat(), "is_dir": file.is_dir(), diff --git a/ui/assets/css/style.css b/ui/assets/css/style.css index 264f0995..0867fd4b 100644 --- a/ui/assets/css/style.css +++ b/ui/assets/css/style.css @@ -281,3 +281,8 @@ hr { background-repeat: no-repeat; background-blend-mode: darken; } + +.is-unbounded-model { + max-height: unset !important; + width: unset !important; +} diff --git a/ui/components/EmbedPlayer.vue b/ui/components/EmbedPlayer.vue index 0b692a4e..ce22b216 100644 --- a/ui/components/EmbedPlayer.vue +++ b/ui/components/EmbedPlayer.vue @@ -1,9 +1,16 @@ +