vsview-comp 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ from typing import Any
2
+
3
+ from vsview.api import WidgetPluginBase, hookimpl
4
+
5
+ from .plugin import CompPlugin
6
+
7
+
8
+ @hookimpl
9
+ def vsview_register_toolpanel() -> type[WidgetPluginBase[Any, Any]]:
10
+ return CompPlugin
@@ -0,0 +1,4 @@
1
+ PLUGIN_ID = "jet_vsview_comp"
2
+ PLUGIN_DISPLAY = "Comparison"
3
+ LOGIN_CONTEXT = "slowpicslogin"
4
+ COOKIE_KEY = "cookies.v1"
vsview_comp/models.py ADDED
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from collections.abc import Sequence
5
+ from logging import getLogger
6
+ from pathlib import Path
7
+ from typing import Any, Literal, NamedTuple, Self
8
+
9
+ from jetpytools import classproperty
10
+ from pydantic import BaseModel, ConfigDict, Field, ValidationError
11
+
12
+ logger = getLogger(__name__)
13
+
14
+
15
+ class ComparisonImage(NamedTuple):
16
+ path: Path
17
+ pict_type: str
18
+ frame_no: int
19
+ timestamp: str
20
+
21
+
22
+ class ComparisonSource(NamedTuple):
23
+ name: str
24
+ images: Sequence[ComparisonImage]
25
+
26
+
27
+ class TMDBGenre(BaseModel):
28
+ id: int
29
+ name: str
30
+
31
+
32
+ class TMDBProductionCountry(BaseModel):
33
+ iso_3166_1: str = ""
34
+
35
+
36
+ class LoggedBaseModel(BaseModel):
37
+ @classmethod
38
+ def validate_logged(cls, obj: Any, log_context: str) -> Self | None:
39
+ try:
40
+ return super().model_validate(obj)
41
+ except ValidationError as e:
42
+ logger.warning("Ignoring invalid payload in %s: %s", log_context, e)
43
+ logger.debug("Payload validation traceback", exc_info=True)
44
+ return None
45
+
46
+
47
+ class TMDBTitleData(LoggedBaseModel):
48
+ model_config = ConfigDict(extra="allow")
49
+
50
+ id: int
51
+ name: str = ""
52
+ title: str = ""
53
+ original_name: str | None = None
54
+ original_title: str | None = None
55
+ original_language: str | None = None
56
+ origin_country: list[str] = Field(default_factory=list)
57
+ production_countries: list[TMDBProductionCountry] = Field(default_factory=list)
58
+ first_air_date: str | None = None
59
+ release_date: str | None = None
60
+ poster_path: str | None = None
61
+ overview: str | None = None
62
+ vote_average: float | None = None
63
+ vote_count: int | None = None
64
+ popularity: float | None = None
65
+ genre_ids: list[int] = Field(default_factory=list)
66
+ genres: list[TMDBGenre] = Field(default_factory=list)
67
+
68
+
69
+ class TMDBPayload(LoggedBaseModel):
70
+ results: list[TMDBTitleData] = Field(default_factory=list)
71
+ genres: list[TMDBGenre] = Field(default_factory=list)
72
+
73
+
74
+ class TMDBTooltipData(BaseModel):
75
+ header: str
76
+ original_name: str
77
+ genres: str
78
+ language: str
79
+ country: str
80
+ release_date: str
81
+ rating: str
82
+ popularity: str
83
+ tmdb_id: str
84
+ overview: str
85
+
86
+
87
+ class TMDBTitle(BaseModel):
88
+ model_config = ConfigDict(ignored_types=(classproperty,))
89
+
90
+ data: TMDBTitleData
91
+ media_type: Literal["tv", "movie"]
92
+ genres: list[str] = Field(default_factory=list)
93
+
94
+ @property
95
+ def id(self) -> str:
96
+ """TMDB ID"""
97
+ return str(self.data.id)
98
+
99
+ @property
100
+ def name(self) -> str:
101
+ """Title name"""
102
+ return self.data.name if self.media_type == "tv" else self.data.title
103
+
104
+ @property
105
+ def original_name(self) -> str:
106
+ """Original title name"""
107
+ return self.data.original_name or self.data.original_title or "-"
108
+
109
+ @property
110
+ def year(self) -> str:
111
+ """Release year"""
112
+ release_date = self.data.first_air_date if self.media_type == "tv" else self.data.release_date
113
+ return (release_date or "0000")[:4]
114
+
115
+ @property
116
+ def media_tag(self) -> str:
117
+ """Media tag (TV or Movie)"""
118
+ return self.media_type.upper()
119
+
120
+ @property
121
+ def language(self) -> str:
122
+ """Original language"""
123
+ return (self.data.original_language or "-").upper()
124
+
125
+ @property
126
+ def country(self) -> str:
127
+ """Original country"""
128
+ return (
129
+ ", ".join(
130
+ [c for c in self.data.origin_country if c]
131
+ or [c.iso_3166_1 for c in self.data.production_countries if c.iso_3166_1]
132
+ )
133
+ or "-"
134
+ )
135
+
136
+ @property
137
+ def release_date(self) -> str:
138
+ """Release date"""
139
+ return (self.data.first_air_date or self.data.release_date or "-").strip()
140
+
141
+ @property
142
+ def rating(self) -> str:
143
+ """Rating"""
144
+ if self.data.vote_average is not None and self.data.vote_count is not None and self.data.vote_count > 0:
145
+ return f"{self.data.vote_average:.1f} ({self.data.vote_count} votes)"
146
+ else:
147
+ return "Not enough ratings"
148
+
149
+ @property
150
+ def popularity(self) -> str:
151
+ """Popularity"""
152
+ return f"{self.data.popularity:.2f}" if self.data.popularity is not None else "-"
153
+
154
+ @property
155
+ def overview(self) -> str:
156
+ """Overview"""
157
+ return (self.data.overview or "-").strip()
158
+
159
+ @property
160
+ def tooltip_data(self) -> TMDBTooltipData:
161
+ overview = self.overview
162
+ if len(overview) > 480:
163
+ overview = f"{overview[:477].rstrip()}..."
164
+
165
+ return TMDBTooltipData(
166
+ header=f"{self.name} ({self.year}) [{self.media_tag}]",
167
+ original_name=self.original_name,
168
+ genres=", ".join(self.genres) if self.genres else "-",
169
+ language=self.language,
170
+ country=self.country,
171
+ release_date=self.release_date,
172
+ rating=self.rating,
173
+ popularity=self.popularity,
174
+ tmdb_id=self.id,
175
+ overview=overview,
176
+ )
177
+
178
+ @classproperty
179
+ @classmethod
180
+ def supported_format_fields(cls) -> tuple[str, ...]:
181
+ return ("id", "name", "original_name", "year", "media_tag", "language", "country", "release_date")
182
+
183
+ @classproperty
184
+ @classmethod
185
+ def format_hints(cls) -> dict[str, str]:
186
+ hints = dict[str, str]()
187
+ for field in cls.supported_format_fields:
188
+ if not isinstance((prop := getattr(cls, field, None)), property):
189
+ logger.warning("Field %s is not a property", field)
190
+ continue
191
+
192
+ hints[field] = (prop.fget.__doc__ or "").strip()
193
+ return hints
194
+
195
+ def format_name(self, fmt: str, **extra: str) -> str:
196
+ """Format a name string using available TMDB fields and extra context."""
197
+
198
+ return fmt.format_map(
199
+ defaultdict(
200
+ str,
201
+ **{field: str(getattr(self, field)) for field in self.supported_format_fields},
202
+ **extra,
203
+ )
204
+ )