Skip to content

fix: title scrobble reliability and local history rewatch#175

Open
itskavin wants to merge 1 commit into
mainfrom
fix-scrobble
Open

fix: title scrobble reliability and local history rewatch#175
itskavin wants to merge 1 commit into
mainfrom
fix-scrobble

Conversation

@itskavin

@itskavin itskavin commented Jul 3, 2026

Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings July 3, 2026 11:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces robust filename parsing, rate-limiting for Simkl file searches, and the ability to remove items from local watch history in the viewer. It also improves media identification by trying normalized file-search variants and validating cached entries. The review feedback highlights three key improvement opportunities: safely converting season and episode values to integers in _file_search_variant_candidates to prevent potential crashes, and using platform-independent path splitting in _year_from_path_context and _identify_media_from_filepath to correctly handle Windows paths on Linux environments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +963 to +1005
def _file_search_variant_candidates(self, filepath, guessit_info=None, include_original=True):
expected = self._expected_media_info_from_filepath(filepath, guessit_info)
title = expected.get("title")
episode = expected.get("episode")
season = expected.get("season")
year = expected.get("year")

candidates = []
if filepath and include_original:
candidates.append(filepath)
if filepath:
basename = os.path.basename(filepath)
if basename and basename != filepath:
candidates.append(basename)

if title:
if episode is not None:
if season is not None:
if year:
candidates.append(f"{title}.{year}.S{int(season):02d}E{int(episode):02d}.mkv")
candidates.append(f"{title} {year} S{int(season):02d}E{int(episode):02d}.mkv")
candidates.append(f"{title}.{year}.{int(season)}x{int(episode):02d}.mkv")
candidates.append(f"{title}.S{int(season):02d}E{int(episode):02d}.mkv")
candidates.append(f"{title} S{int(season):02d}E{int(episode):02d}.mkv")
candidates.append(f"{title}.{int(season)}x{int(episode):02d}.mkv")
else:
candidates.append(f"{title}.{int(episode)}.mkv")
candidates.append(f"{title} E{int(episode):02d}.mkv")
else:
year = expected.get("year")
if year:
candidates.append(f"{title}.{year}.mkv")
candidates.append(f"{title} {year}.mkv")
candidates.append(f"{title}.mkv")

unique_candidates = []
seen = set()
for candidate in candidates:
key = candidate.lower()
if key not in seen:
seen.add(key)
unique_candidates.append(candidate)
return unique_candidates

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The _file_search_variant_candidates method directly calls int(season) and int(episode). If season or episode is a list (which guessit commonly returns for multi-episode files) or a non-integer string, this will raise a TypeError or ValueError and crash the scrobbler thread. We should safely convert these values to integers, handling lists and non-integer strings gracefully.

    def _file_search_variant_candidates(self, filepath, guessit_info=None, include_original=True):
        expected = self._expected_media_info_from_filepath(filepath, guessit_info)
        title = expected.get("title")
        episode = expected.get("episode")
        season = expected.get("season")
        year = expected.get("year")

        def to_int(val):
            if isinstance(val, list):
                val = val[0] if val else None
            try:
                return int(val) if val is not None else None
            except (ValueError, TypeError):
                return None

        season_int = to_int(season)
        episode_int = to_int(episode)

        candidates = []
        if filepath and include_original:
            candidates.append(filepath)
        if filepath:
            basename = os.path.basename(filepath)
            if basename and basename != filepath:
                candidates.append(basename)

        if title:
            if episode_int is not None:
                if season_int is not None:
                    if year:
                        candidates.append(f"{title}.{year}.S{season_int:02d}E{episode_int:02d}.mkv")
                        candidates.append(f"{title} {year} S{season_int:02d}E{episode_int:02d}.mkv")
                        candidates.append(f"{title}.{year}.{season_int}x{episode_int:02d}.mkv")
                    candidates.append(f"{title}.S{season_int:02d}E{episode_int:02d}.mkv")
                    candidates.append(f"{title} S{season_int:02d}E{episode_int:02d}.mkv")
                    candidates.append(f"{title}.{season_int}x{episode_int:02d}.mkv")
                else:
                    candidates.append(f"{title}.{episode_int}.mkv")
                    candidates.append(f"{title} E{episode_int:02d}.mkv")
            else:
                year = expected.get("year")
                if year:
                    candidates.append(f"{title}.{year}.mkv")
                    candidates.append(f"{title} {year}.mkv")
                candidates.append(f"{title}.mkv")

        unique_candidates = []
        seen = set()
        for candidate in candidates:
            key = candidate.lower()
            if key not in seen:
                seen.add(key)
                unique_candidates.append(candidate)
        return unique_candidates

Comment on lines +755 to +756
for part in reversed(pathlib.Path(filepath).parts[:-1]):
for match in re.finditer(r'\b(19\d{2}|20\d{2})\b', part):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using pathlib.Path(filepath).parts is platform-dependent. If the scrobbler runs on Linux (e.g., in Docker) but receives a Windows path from a remote player, pathlib.Path will treat the entire Windows path as a single filename because backslash is not a path separator on Linux. We should split the path string using a regular expression that matches both forward and backward slashes to ensure platform independence.

Suggested change
for part in reversed(pathlib.Path(filepath).parts[:-1]):
for match in re.finditer(r'\b(19\d{2}|20\d{2})\b', part):
current_year = datetime.now().year
parts = [p for p in re.split(r'[/\\]', str(filepath)) if p]
for part in reversed(parts[:-1]):

Comment on lines +1568 to +1570
basename = os.path.basename(filepath)
basename_result = None
if basename and basename != filepath:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using os.path.basename(filepath) is platform-dependent. If the scrobbler runs on Linux (e.g., in Docker) but receives a Windows path from a remote player, os.path.basename will fail to extract the filename because backslash is not recognized as a path separator on Linux. We should use a platform-independent split to safely extract the filename.

Suggested change
basename = os.path.basename(filepath)
basename_result = None
if basename and basename != filepath:
basename = re.split(r'[/\\]', str(filepath))[-1] if filepath else ""
basename_result = None
if basename and basename != filepath:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants