Release v1.0.13: fix TheTVDB match crash and improve stability.
Emit plain tuples across thread boundaries, prevent overlapping match workers, scope episode comparisons by season, and cap API pagination. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+61
-37
@@ -32,7 +32,7 @@ from engine.rules import (
|
||||
CsvMappingRule,
|
||||
)
|
||||
from engine.tvdb_client import TvdbClient, TvdbError, TVDB_API_KEY, SEASON_TYPE_CHOICES, LANGUAGE_CHOICES
|
||||
from engine.episode_match import match_filenames_to_episodes
|
||||
from engine.episode_match import match_filenames_to_episodes, target_to_tuple
|
||||
|
||||
|
||||
class ReplaceRuleWidget(QWidget):
|
||||
@@ -355,19 +355,8 @@ class _TvdbMatchWorker(QThread):
|
||||
try:
|
||||
client = TvdbClient(TVDB_API_KEY)
|
||||
lang = self.language
|
||||
if self.all_seasons:
|
||||
official = client.get_all_episodes(
|
||||
self.series_id, season_type="official", language=lang,
|
||||
)
|
||||
season_filter = 0
|
||||
else:
|
||||
official = client.get_season_episodes(
|
||||
self.series_id,
|
||||
self.season,
|
||||
season_type="official",
|
||||
language=lang,
|
||||
)
|
||||
season_filter = self.season
|
||||
season_filter = 0 if self.all_seasons else self.season
|
||||
need_official = self.multi_episode or self.season_type == "alternate"
|
||||
|
||||
if self.all_seasons:
|
||||
episodes = client.get_all_episodes(
|
||||
@@ -383,8 +372,38 @@ class _TvdbMatchWorker(QThread):
|
||||
language=lang,
|
||||
)
|
||||
|
||||
if self.season_type == "alternate":
|
||||
if self.all_seasons:
|
||||
official = client.get_all_episodes(
|
||||
self.series_id, season_type="official", language=lang,
|
||||
)
|
||||
else:
|
||||
official = client.get_season_episodes(
|
||||
self.series_id,
|
||||
self.season,
|
||||
season_type="official",
|
||||
language=lang,
|
||||
)
|
||||
episodes = official
|
||||
elif need_official:
|
||||
if self.season_type == "official":
|
||||
official = episodes
|
||||
elif self.all_seasons:
|
||||
official = client.get_all_episodes(
|
||||
self.series_id, season_type="official", language=lang,
|
||||
)
|
||||
else:
|
||||
official = client.get_season_episodes(
|
||||
self.series_id,
|
||||
self.season,
|
||||
season_type="official",
|
||||
language=lang,
|
||||
)
|
||||
else:
|
||||
official = episodes
|
||||
|
||||
combined = None
|
||||
if self.multi_episode or self.season_type == "alternate":
|
||||
if need_official:
|
||||
if self.all_seasons:
|
||||
combined = client.get_all_episodes(
|
||||
self.series_id,
|
||||
@@ -399,7 +418,6 @@ class _TvdbMatchWorker(QThread):
|
||||
language=lang,
|
||||
)
|
||||
if not combined and lang:
|
||||
# Combined order may lack translations; fall back to show language.
|
||||
if self.all_seasons:
|
||||
combined = client.get_all_episodes(
|
||||
self.series_id, season_type="alternate",
|
||||
@@ -409,9 +427,6 @@ class _TvdbMatchWorker(QThread):
|
||||
self.series_id, self.season, season_type="alternate",
|
||||
)
|
||||
|
||||
if self.season_type == "alternate":
|
||||
episodes = official
|
||||
|
||||
if not episodes and not combined:
|
||||
label = "all seasons" if self.all_seasons else f"season {self.season}"
|
||||
raise TvdbError(f"No episodes found for {label}")
|
||||
@@ -422,11 +437,13 @@ class _TvdbMatchWorker(QThread):
|
||||
official_episodes=official,
|
||||
combined_episodes=combined,
|
||||
)
|
||||
self.finished.emit(mapping, unmatched, notes)
|
||||
safe_mapping = {path: target_to_tuple(t) for path, t in mapping.items()}
|
||||
self.finished.emit(safe_mapping, unmatched, notes)
|
||||
except TvdbError as e:
|
||||
self.failed.emit(str(e))
|
||||
except Exception as e:
|
||||
self.failed.emit(str(e))
|
||||
import traceback
|
||||
self.failed.emit(f"{e}\n\n{traceback.format_exc()}")
|
||||
|
||||
|
||||
class TvdbEpisodeRenumberRuleWidget(QWidget):
|
||||
@@ -583,6 +600,9 @@ class TvdbEpisodeRenumberRuleWidget(QWidget):
|
||||
series_id = self.series_combo.currentData()
|
||||
if series_id is None:
|
||||
return
|
||||
if self._match_worker is not None and self._match_worker.isRunning():
|
||||
self.status.setText("Matching already in progress…")
|
||||
return
|
||||
self.match_btn.setEnabled(False)
|
||||
all_seasons = self.season.value() == 0
|
||||
self.status.setText(
|
||||
@@ -603,17 +623,22 @@ class TvdbEpisodeRenumberRuleWidget(QWidget):
|
||||
self._match_worker.start()
|
||||
|
||||
def _on_match_finished(self, mapping: dict, unmatched: list, notes: list):
|
||||
self._episode_mapping = mapping
|
||||
self.match_btn.setEnabled(True)
|
||||
matched = len(mapping)
|
||||
total = len(self._file_names)
|
||||
scope = "all seasons" if self.season.value() == 0 else f"season {self.season.value()}"
|
||||
msg = f"Matched {matched} of {total} file(s) ({scope}, {self._order_label()}, {self._language_label()})."
|
||||
if unmatched:
|
||||
msg += f" {len(unmatched)} file(s) unmatched."
|
||||
self.status.setText(msg)
|
||||
self.matchCompleted.emit()
|
||||
self._emit()
|
||||
try:
|
||||
self._episode_mapping = mapping
|
||||
self.match_btn.setEnabled(True)
|
||||
matched = len(mapping)
|
||||
total = len(self._file_names)
|
||||
scope = "all seasons" if self.season.value() == 0 else f"season {self.season.value()}"
|
||||
msg = f"Matched {matched} of {total} file(s) ({scope}, {self._order_label()}, {self._language_label()})."
|
||||
if unmatched:
|
||||
msg += f" {len(unmatched)} file(s) unmatched."
|
||||
self.status.setText(msg)
|
||||
self.matchCompleted.emit()
|
||||
self._emit()
|
||||
except Exception as e:
|
||||
self.match_btn.setEnabled(True)
|
||||
self.status.setText(f"Match error: {e}")
|
||||
QMessageBox.warning(self, "Match error", str(e))
|
||||
|
||||
def _on_match_failed(self, message: str):
|
||||
self.match_btn.setEnabled(True)
|
||||
@@ -622,11 +647,10 @@ class TvdbEpisodeRenumberRuleWidget(QWidget):
|
||||
def getRule(self) -> TvdbEpisodeRenumberRule:
|
||||
mapping: dict = {}
|
||||
for k, v in self._episode_mapping.items():
|
||||
if hasattr(v, "season"):
|
||||
if getattr(v, "episode_end", None) and v.episode_end > v.episode:
|
||||
mapping[k] = (v.season, v.episode, v.episode_end)
|
||||
else:
|
||||
mapping[k] = (v.season, v.episode)
|
||||
if isinstance(v, tuple):
|
||||
mapping[k] = v
|
||||
elif hasattr(v, "season"):
|
||||
mapping[k] = target_to_tuple(v)
|
||||
else:
|
||||
mapping[k] = v
|
||||
r = TvdbEpisodeRenumberRule(
|
||||
|
||||
Reference in New Issue
Block a user