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.
- vsview_comp/__init__.py +10 -0
- vsview_comp/_metadata.py +4 -0
- vsview_comp/models.py +204 -0
- vsview_comp/plugin.py +839 -0
- vsview_comp/py.typed +0 -0
- vsview_comp/tmdb_api_key.txt +1 -0
- vsview_comp/tmdb_tooltip.html.jinja +16 -0
- vsview_comp/ui.py +984 -0
- vsview_comp/utils.py +118 -0
- vsview_comp/worker.py +550 -0
- vsview_comp-0.1.0.dist-info/METADATA +61 -0
- vsview_comp-0.1.0.dist-info/RECORD +15 -0
- vsview_comp-0.1.0.dist-info/WHEEL +4 -0
- vsview_comp-0.1.0.dist-info/entry_points.txt +2 -0
- vsview_comp-0.1.0.dist-info/licenses/LICENSE +21 -0
vsview_comp/__init__.py
ADDED
vsview_comp/_metadata.py
ADDED
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
|
+
)
|