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(
|
dl = ItemDTO(
|
||||||
id=str(entry.get("id")),
|
id=str(entry.get("id")),
|
||||||
title=str(entry.get("title")),
|
title=str(entry.get("title")),
|
||||||
|
description=str(entry.get("description", "")),
|
||||||
url=str(entry.get("webpage_url") or entry.get("url")),
|
url=str(entry.get("webpage_url") or entry.get("url")),
|
||||||
preset=item.preset,
|
preset=item.preset,
|
||||||
folder=item.folder,
|
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
|
items["queue"][k] = self._active[k].info if k in self._active else v
|
||||||
|
|
||||||
for k, v in self.done.saved_items():
|
for k, v in self.done.saved_items():
|
||||||
|
v.get_file_sidecar()
|
||||||
items["done"][k] = v
|
items["done"][k] = v
|
||||||
|
|
||||||
for k, v in self.queue.items():
|
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
|
items["queue"][k] = self._active[k].info if k in self._active else v
|
||||||
|
|
||||||
for k, v in self.done.items():
|
for k, v in self.done.items():
|
||||||
if k not in items["done"]:
|
if k in items["done"]:
|
||||||
items["done"][k] = v.info
|
continue
|
||||||
|
|
||||||
|
v.info.get_file_sidecar()
|
||||||
|
items["done"][k] = v.info
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,15 @@ from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from app.library.encoder import Encoder
|
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
|
from app.library.YTDLPOpts import YTDLPOpts
|
||||||
|
|
||||||
LOG: logging.Logger = logging.getLogger("ItemDTO")
|
LOG: logging.Logger = logging.getLogger("ItemDTO")
|
||||||
|
|
@ -305,6 +313,8 @@ class ItemDTO:
|
||||||
""" The ID of the item yt-dlp """
|
""" The ID of the item yt-dlp """
|
||||||
title: str
|
title: str
|
||||||
""" The title of the item. """
|
""" The title of the item. """
|
||||||
|
description: str = ""
|
||||||
|
""" The description of the item. """
|
||||||
url: str
|
url: str
|
||||||
""" The URL of the item. """
|
""" The URL of the item. """
|
||||||
preset: str = "default"
|
preset: str = "default"
|
||||||
|
|
@ -347,6 +357,8 @@ class ItemDTO:
|
||||||
""" If the item has been archived. """
|
""" If the item has been archived. """
|
||||||
archive_id: str | None = None
|
archive_id: str | None = None
|
||||||
""" The archive ID of the item. """
|
""" The archive ID of the item. """
|
||||||
|
sidecar: dict = field(default_factory=dict)
|
||||||
|
""" Sidecar data associated with the item. """
|
||||||
|
|
||||||
# yt-dlp injected fields.
|
# yt-dlp injected fields.
|
||||||
tmpfilename: str | None = None
|
tmpfilename: str | None = None
|
||||||
|
|
@ -370,8 +382,11 @@ class ItemDTO:
|
||||||
dict: The serialized item.
|
dict: The serialized item.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "finished" == self.status and not self._recomputed:
|
if "finished" == self.status:
|
||||||
self.archive_status()
|
if not self._recomputed:
|
||||||
|
self.archive_status()
|
||||||
|
|
||||||
|
self.get_file_sidecar()
|
||||||
|
|
||||||
item, _ = clean_item(self.__dict__.copy(), ItemDTO.removed_fields())
|
item, _ = clean_item(self.__dict__.copy(), ItemDTO.removed_fields())
|
||||||
return item
|
return item
|
||||||
|
|
@ -576,6 +591,19 @@ class ItemDTO:
|
||||||
|
|
||||||
return True
|
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
|
@staticmethod
|
||||||
def removed_fields() -> tuple:
|
def removed_fields() -> tuple:
|
||||||
"""
|
"""
|
||||||
|
|
@ -606,3 +634,4 @@ class ItemDTO:
|
||||||
self.get_archive_id()
|
self.get_archive_id()
|
||||||
self.get_archive_file()
|
self.get_archive_file()
|
||||||
self.archive_status()
|
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
|
# Limit cache size
|
||||||
if len(cache) > max_size:
|
if len(cache) > max_size:
|
||||||
# Remove oldest entries
|
# 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:
|
for old_key in oldest_keys:
|
||||||
cache.pop(old_key, None)
|
cache.pop(old_key, None)
|
||||||
cache_expiry.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():
|
def cache_info():
|
||||||
# Use functools._CacheInfo for compatibility with standard lru_cache
|
# Use functools._CacheInfo for compatibility with standard lru_cache
|
||||||
from functools import _CacheInfo
|
from functools import _CacheInfo
|
||||||
|
|
||||||
return _CacheInfo(hits=0, misses=len(cache), maxsize=max_size, currsize=len(cache))
|
return _CacheInfo(hits=0, misses=len(cache), maxsize=max_size, currsize=len(cache))
|
||||||
|
|
||||||
async_wrapper.cache_clear = cache_clear
|
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)
|
@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.
|
Get sidecar files for the given file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): The video file.
|
file (Path|None): The video file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: List of sidecar files.
|
dict: A dictionary with sidecar files categorized by type.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
files: dict = {}
|
files: dict = {}
|
||||||
|
|
||||||
|
if not file:
|
||||||
|
return files
|
||||||
|
|
||||||
for i, f in enumerate(file.parent.glob(f"{glob.escape(file.stem)}.*")):
|
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("."):
|
if f == file or f.is_file() is False or f.stem.startswith("."):
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -168,9 +169,6 @@ class TestItemDTO:
|
||||||
|
|
||||||
def test_get_file_method(self):
|
def test_get_file_method(self):
|
||||||
"""Test ItemDTO get_file method returns correct path."""
|
"""Test ItemDTO get_file method returns correct path."""
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
# Create ItemDTO with filename but no folder
|
# Create ItemDTO with filename but no folder
|
||||||
dto = ItemDTO(
|
dto = ItemDTO(
|
||||||
id="test-id",
|
id="test-id",
|
||||||
|
|
@ -238,3 +236,46 @@ class TestItemDTO:
|
||||||
|
|
||||||
result = dto.get_file(download_path=Path("/custom"))
|
result = dto.get_file(download_path=Path("/custom"))
|
||||||
assert result == Path("/custom/test_video.mp4")
|
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