Add desc & sidecar to item data
This commit is contained in:
parent
816fd55397
commit
27893bcbf9
4 changed files with 91 additions and 12 deletions
|
|
@ -465,6 +465,7 @@ class DownloadQueue(metaclass=Singleton):
|
|||
dl = ItemDTO(
|
||||
id=str(entry.get("id")),
|
||||
title=str(entry.get("title")),
|
||||
description=str(entry.get("description", "")),
|
||||
url=str(entry.get("webpage_url") or entry.get("url")),
|
||||
preset=item.preset,
|
||||
folder=item.folder,
|
||||
|
|
@ -982,6 +983,7 @@ class DownloadQueue(metaclass=Singleton):
|
|||
items["queue"][k] = self._active[k].info if k in self._active else v
|
||||
|
||||
for k, v in self.done.saved_items():
|
||||
v.get_file_sidecar()
|
||||
items["done"][k] = v
|
||||
|
||||
for k, v in self.queue.items():
|
||||
|
|
@ -989,8 +991,11 @@ class DownloadQueue(metaclass=Singleton):
|
|||
items["queue"][k] = self._active[k].info if k in self._active else v
|
||||
|
||||
for k, v in self.done.items():
|
||||
if k not in items["done"]:
|
||||
items["done"][k] = v.info
|
||||
if k in items["done"]:
|
||||
continue
|
||||
|
||||
v.info.get_file_sidecar()
|
||||
items["done"][k] = v.info
|
||||
|
||||
return items
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@ from pathlib import Path
|
|||
from typing import Any
|
||||
|
||||
from app.library.encoder import Encoder
|
||||
from app.library.Utils import archive_add, archive_delete, archive_read, clean_item, get_archive_id, get_file
|
||||
from app.library.Utils import (
|
||||
archive_add,
|
||||
archive_delete,
|
||||
archive_read,
|
||||
clean_item,
|
||||
get_archive_id,
|
||||
get_file,
|
||||
get_file_sidecar,
|
||||
)
|
||||
from app.library.YTDLPOpts import YTDLPOpts
|
||||
|
||||
LOG: logging.Logger = logging.getLogger("ItemDTO")
|
||||
|
|
@ -305,6 +313,8 @@ class ItemDTO:
|
|||
""" The ID of the item yt-dlp """
|
||||
title: str
|
||||
""" The title of the item. """
|
||||
description: str = ""
|
||||
""" The description of the item. """
|
||||
url: str
|
||||
""" The URL of the item. """
|
||||
preset: str = "default"
|
||||
|
|
@ -347,6 +357,8 @@ class ItemDTO:
|
|||
""" If the item has been archived. """
|
||||
archive_id: str | None = None
|
||||
""" The archive ID of the item. """
|
||||
sidecar: dict = field(default_factory=dict)
|
||||
""" Sidecar data associated with the item. """
|
||||
|
||||
# yt-dlp injected fields.
|
||||
tmpfilename: str | None = None
|
||||
|
|
@ -370,8 +382,11 @@ class ItemDTO:
|
|||
dict: The serialized item.
|
||||
|
||||
"""
|
||||
if "finished" == self.status and not self._recomputed:
|
||||
self.archive_status()
|
||||
if "finished" == self.status:
|
||||
if not self._recomputed:
|
||||
self.archive_status()
|
||||
|
||||
self.get_file_sidecar()
|
||||
|
||||
item, _ = clean_item(self.__dict__.copy(), ItemDTO.removed_fields())
|
||||
return item
|
||||
|
|
@ -576,6 +591,19 @@ class ItemDTO:
|
|||
|
||||
return True
|
||||
|
||||
def get_file_sidecar(self) -> dict:
|
||||
"""
|
||||
Get sidecar files associated with the item.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary with sidecar files categorized by type.
|
||||
|
||||
"""
|
||||
if filename := self.get_file():
|
||||
self.sidecar = get_file_sidecar(filename)
|
||||
|
||||
return self.sidecar
|
||||
|
||||
@staticmethod
|
||||
def removed_fields() -> tuple:
|
||||
"""
|
||||
|
|
@ -606,3 +634,4 @@ class ItemDTO:
|
|||
self.get_archive_id()
|
||||
self.get_archive_file()
|
||||
self.archive_status()
|
||||
self.get_file_sidecar()
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ def timed_lru_cache(ttl_seconds: int, max_size: int = 128):
|
|||
# Limit cache size
|
||||
if len(cache) > max_size:
|
||||
# Remove oldest entries
|
||||
oldest_keys = sorted(cache_expiry.keys(), key=lambda k: cache_expiry[k])[:len(cache) - max_size]
|
||||
oldest_keys = sorted(cache_expiry.keys(), key=lambda k: cache_expiry[k])[: len(cache) - max_size]
|
||||
for old_key in oldest_keys:
|
||||
cache.pop(old_key, None)
|
||||
cache_expiry.pop(old_key, None)
|
||||
|
|
@ -153,6 +153,7 @@ def timed_lru_cache(ttl_seconds: int, max_size: int = 128):
|
|||
def cache_info():
|
||||
# Use functools._CacheInfo for compatibility with standard lru_cache
|
||||
from functools import _CacheInfo
|
||||
|
||||
return _CacheInfo(hits=0, misses=len(cache), maxsize=max_size, currsize=len(cache))
|
||||
|
||||
async_wrapper.cache_clear = cache_clear
|
||||
|
|
@ -626,19 +627,22 @@ def validate_uuid(uuid_str: str, version: int = 4) -> bool:
|
|||
|
||||
|
||||
@timed_lru_cache(ttl_seconds=60, max_size=256)
|
||||
def get_file_sidecar(file: Path) -> list[dict]:
|
||||
def get_file_sidecar(file: Path | None = None) -> dict[dict]:
|
||||
"""
|
||||
Get sidecar files for the given file.
|
||||
|
||||
Args:
|
||||
file (Path): The video file.
|
||||
file (Path|None): The video file.
|
||||
|
||||
Returns:
|
||||
list: List of sidecar files.
|
||||
dict: A dictionary with sidecar files categorized by type.
|
||||
|
||||
"""
|
||||
files: dict = {}
|
||||
|
||||
if not file:
|
||||
return 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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -168,9 +169,6 @@ class TestItemDTO:
|
|||
|
||||
def test_get_file_method(self):
|
||||
"""Test ItemDTO get_file method returns correct path."""
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
# Create ItemDTO with filename but no folder
|
||||
dto = ItemDTO(
|
||||
id="test-id",
|
||||
|
|
@ -238,3 +236,46 @@ class TestItemDTO:
|
|||
|
||||
result = dto.get_file(download_path=Path("/custom"))
|
||||
assert result == Path("/custom/test_video.mp4")
|
||||
|
||||
def test_get_file_sidecar_populates_from_utils(self):
|
||||
with patch.object(ItemDTO, "__post_init__", lambda _: None):
|
||||
dto = ItemDTO(id="sidecar", title="Title", url="u", folder="f")
|
||||
|
||||
expected_sidecar = {
|
||||
"subtitle": [
|
||||
{
|
||||
"file": Path("/downloads/video.en.srt"),
|
||||
"lang": "en",
|
||||
"name": "SRT (0) - en",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
patch("app.library.ItemDTO.ItemDTO.get_file", autospec=True, return_value=Path("/downloads/video.mp4")) as mock_get_file,
|
||||
patch("app.library.ItemDTO.get_file_sidecar", return_value=expected_sidecar) as mock_utils_sidecar,
|
||||
):
|
||||
result = dto.get_file_sidecar()
|
||||
|
||||
mock_get_file.assert_called_once_with(dto)
|
||||
mock_utils_sidecar.assert_called_once_with(Path("/downloads/video.mp4"))
|
||||
assert result is expected_sidecar
|
||||
assert dto.sidecar is expected_sidecar
|
||||
|
||||
def test_get_file_sidecar_returns_existing_when_no_file(self):
|
||||
with patch.object(ItemDTO, "__post_init__", lambda _: None):
|
||||
dto = ItemDTO(id="sidecar-none", title="Title", url="u", folder="f")
|
||||
|
||||
existing = {"existing": []}
|
||||
dto.sidecar = existing
|
||||
|
||||
with (
|
||||
patch("app.library.ItemDTO.ItemDTO.get_file", autospec=True, return_value=None) as mock_get_file,
|
||||
patch("app.library.ItemDTO.get_file_sidecar") as mock_utils_sidecar,
|
||||
):
|
||||
result = dto.get_file_sidecar()
|
||||
|
||||
mock_get_file.assert_called_once_with(dto)
|
||||
mock_utils_sidecar.assert_not_called()
|
||||
assert result is existing
|
||||
assert dto.sidecar is existing
|
||||
|
|
|
|||
Loading…
Reference in a new issue