chore: sync upstream/master (6 commits) #250

Merged
yippee0903 merged 11 commits from sync/upstream-merge into master 2026-06-18 19:38:50 +00:00
yippee0903 commented 2026-06-18 18:26:28 +00:00 (Migrated from github.com)

Summary

  • Merges 6 upstream commits from Audionut/Upload-Assistant into the fork's master
  • All conflicts resolved: source files take upstream, README retains fork banner

Upstream changes included

  • readme: development freeze notice + upbrr link
  • fix(edition): ignore sole IMDb edition (imdb_edition_count > 1 guard)
  • fix(tvdb): don't rely on slug — use _series_translation_metadata / _translated_series_name
  • PTP: fix poster rehosting (rehost_poster_to_selected_host)
  • fix(trackers/IS): successful upload detection
  • NBL: fix ignoredupes ('1' instead of 'on')

Conflict resolution

File Decision
README.md Kept fork banner + added upstream content below
src/edition.py Took upstream (bug fix)
src/imdb.py Took upstream (adds edition_count field)
src/metadata_searching.py Took upstream (refactor to _apply_tvdb_series_metadata)
src/prep.py Took upstream (leading-article guard)
src/trackers/NBL.py Took upstream (ignoredupes fix)
src/trackers/PTP.py Took upstream (poster rehosting fix)
src/tvdb.py Took upstream (slug-free series name resolution)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added flexible upload success validation supporting multiple success indicators for tracker uploads.
    • Introduced poster rehosting functionality for PTP with intelligent host selection and fallback support.
    • Enhanced TVDB metadata handling with improved series title and year extraction.
  • Bug Fixes

    • Improved IMDb edition detection logic for more accurate media categorization.
    • Fixed NBL upload payload configuration.
  • Documentation

    • Updated README with reference to related project repository.
## Summary - Merges 6 upstream commits from Audionut/Upload-Assistant into the fork's master - All conflicts resolved: source files take upstream, README retains fork banner ## Upstream changes included - `readme`: development freeze notice + upbrr link - `fix(edition)`: ignore sole IMDb edition (`imdb_edition_count > 1` guard) - `fix(tvdb)`: don't rely on slug — use `_series_translation_metadata` / `_translated_series_name` - `PTP`: fix poster rehosting (`rehost_poster_to_selected_host`) - `fix(trackers/IS)`: successful upload detection - `NBL`: fix ignoredupes (`'1'` instead of `'on'`) ## Conflict resolution | File | Decision | |------|----------| | `README.md` | Kept fork banner + added upstream content below | | `src/edition.py` | Took upstream (bug fix) | | `src/imdb.py` | Took upstream (adds `edition_count` field) | | `src/metadata_searching.py` | Took upstream (refactor to `_apply_tvdb_series_metadata`) | | `src/prep.py` | Took upstream (leading-article guard) | | `src/trackers/NBL.py` | Took upstream (ignoredupes fix) | | `src/trackers/PTP.py` | Took upstream (poster rehosting fix) | | `src/tvdb.py` | Took upstream (slug-free series name resolution) | 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added flexible upload success validation supporting multiple success indicators for tracker uploads. * Introduced poster rehosting functionality for PTP with intelligent host selection and fallback support. * Enhanced TVDB metadata handling with improved series title and year extraction. * **Bug Fixes** * Improved IMDb edition detection logic for more accurate media categorization. * Fixed NBL upload payload configuration. * **Documentation** * Updated README with reference to related project repository. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-18 18:26:37 +00:00 (Migrated from github.com)

Review Change Stack

Warning

Review limit reached

@yippee0903, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 6 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62f1f3ea-bc67-46e3-9314-edb04bac48a1

📥 Commits

Reviewing files that changed from the base of the PR and between 9e52a8b5c3 and b1e631e199.

📒 Files selected for processing (7)
  • src/edition.py
  • src/imdb.py
  • src/metadata_searching.py
  • src/prep.py
  • src/trackers/PTP.py
  • src/tvdb.py
  • tests/test_edition.py
📝 Walkthrough

Walkthrough

The PR centralizes TVDB English title/year resolution into translation-driven helpers, adds edition_count to IMDb info to gate duration-based edition matching, extends CookieAuthUploader with a success_list parameter, overhauls PTP poster handling with a pluggable rehosting pipeline, and adjusts Prep category/title/audio logic. NBL gets a minor ignoredupes fix and raw-disc check reordering.

Changes

TVDB/IMDb Metadata Pipeline Refactor

Layer / File(s) Summary
IMDb edition_count field and get_edition consumer
src/imdb.py, src/edition.py, tests/test_edition.py
ImdbManager sets edition_count = len(edition_list) on the returned dict; get_edition converts it defensively to int, gates the duration-match path on edition_count > 1, and corrects the third return element's type annotation from str to bool; the test fixture adds edition_count=2.
TVDB translation-driven title/year helpers
src/tvdb.py
Introduces YEAR_PATTERN constant and replaces slug/alias selection with a _series_translation_metadata pipeline: helpers for building English alias lists, extracting years from text, and best-effort year from series info; all get_tvdb_by_external_id series-name return paths switch to _translated_series_name.
_apply_tvdb_series_metadata helper and call-site migration
src/metadata_searching.py
Adds _apply_tvdb_series_metadata to set tvdb_series_name and derive tvdb_series_year/search_year from episodes_data with early return for English originals; replaces inline assignments across all_ids, imdb_tmdb_tvdb, imdb_tvdb, get_tv_data (both paths), and get_tvdb_tvmaze_tmdb_episode_data.

Upload Success & PTP Poster Rehosting

Layer / File(s) Summary
CookieAuth success_list and IS tracker usage
src/cookie_auth.py, src/trackers/IS.py
handle_upload and handle_failed_upload gain an optional success_list parameter; validation and response-predicate logic extended to match any list entry; IS.upload switches from success_text to success_list with two accepted success strings.
PTP poster rehosting helpers and upload flow
src/trackers/PTP.py
Adds contextlib import; introduces _selected_poster_host, _poster_already_on_selected_host, _poster_extension, and rehost_poster_to_selected_host methods; expired-session handling deletes the cookie file via contextlib.suppress(OSError); upload cover acquisition uses the new rehosting pipeline with a user-prompt loop enforcing allowed image extensions.
NBL ignoredupes value and raw-disc check ordering
src/trackers/NBL.py
Changes ignoredupes payload value from "on" to "1"; raw-disc early-exit in search_existing is moved to run after language and valid_mi checks.

Prep Metadata Handling

Layer / File(s) Summary
Title article helpers, category logic, TVDB overwrite, and audio unpacking
src/prep.py
Adds _title_without_leading_article and _tvdb_title_drops_existing_leading_article helpers; gather_prep derives category via get_cat or uppercases existing value without manual_category; removes aka similarity-suppression block; TVDB series name overwrite is now conditional on the leading-article heuristic; get_audio_v2 unpacked to 3 values dropping has_audiodesc; episode regex patterns loosened and multi-file TV-pack detection removed from get_cat.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • yippee0903/Upload-Assistant#45: Both PRs modify src/edition.py's get_edition logic—this PR adjusts edition-count gating and fixes the return type, while the retrieved PR adds normalization to preserve "Special Edition" formatting.
  • yippee0903/Upload-Assistant#62: Both PRs modify src/trackers/IS.py's upload success handling via CookieAuthUploader, with this PR extending the mechanism to support multiple success strings via success_list.
  • yippee0903/Upload-Assistant#210: This PR removes the SequenceMatcher-based meta["aka"] suppression block in src/prep.py that the retrieved PR introduced, making them directly conflicting changes.
🚥 Pre-merge checks | 4 | 1

Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title 'chore: sync upstream/master (6 commits)' accurately describes the main change—syncing upstream commits from the master branch into the fork.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/upstream-merge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/yippee0903/Upload-Assistant/pull/250?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- This is an auto-generated comment: rate limited by coderabbit.ai --> > [!WARNING] > ## Review limit reached > > `@yippee0903`, we couldn't start this review because you've reached your PR review rate limit. > > More reviews will be available in 37 minutes and 6 seconds. [Learn how PR review limits work](https://docs.coderabbit.ai/management/plans#fair-usage-limits-policy). > > Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the **review add-on** in the [billing tab](https://app.coderabbit.ai/settings/billing?tab=usage&orgId=eaa0908c-6035-4950-8a28-f8ec6fe501df) to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). > > <details> > <summary>⌛ How to resolve this issue?</summary> > > After more reviews become available, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR. > > To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. > > </details> > > > <details> > <summary>🚦 How do rate limits work?</summary> > > CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. > > For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. > > Please see our [Fair Usage Limits Policy](https://docs.coderabbit.ai/management/plans#fair-usage-limits-policy) for further information. > > </details> > > <details> > <summary>ℹ️ Review info</summary> > > <details> > <summary>⚙️ Run configuration</summary> > > **Configuration used**: Repository UI > > **Review profile**: ASSERTIVE > > **Plan**: Pro > > **Run ID**: `62f1f3ea-bc67-46e3-9314-edb04bac48a1` > > </details> > > <details> > <summary>📥 Commits</summary> > > Reviewing files that changed from the base of the PR and between 9e52a8b5c31777fbb300d3e2ac2cfa71f7cff9af and b1e631e199d4397f205d59be7b089e426579bfce. > > </details> > > <details> > <summary>📒 Files selected for processing (7)</summary> > > * `src/edition.py` > * `src/imdb.py` > * `src/metadata_searching.py` > * `src/prep.py` > * `src/trackers/PTP.py` > * `src/tvdb.py` > * `tests/test_edition.py` > > </details> > > </details> <!-- end of auto-generated comment: rate limited by coderabbit.ai --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough The PR centralizes TVDB English title/year resolution into translation-driven helpers, adds `edition_count` to IMDb info to gate duration-based edition matching, extends `CookieAuthUploader` with a `success_list` parameter, overhauls PTP poster handling with a pluggable rehosting pipeline, and adjusts `Prep` category/title/audio logic. NBL gets a minor `ignoredupes` fix and raw-disc check reordering. ## Changes **TVDB/IMDb Metadata Pipeline Refactor** | Layer / File(s) | Summary | |---|---| | **IMDb edition\_count field and get\_edition consumer** <br> `src/imdb.py`, `src/edition.py`, `tests/test_edition.py` | `ImdbManager` sets `edition_count = len(edition_list)` on the returned dict; `get_edition` converts it defensively to `int`, gates the duration-match path on `edition_count > 1`, and corrects the third return element's type annotation from `str` to `bool`; the test fixture adds `edition_count=2`. | | **TVDB translation-driven title/year helpers** <br> `src/tvdb.py` | Introduces `YEAR_PATTERN` constant and replaces slug/alias selection with a `_series_translation_metadata` pipeline: helpers for building English alias lists, extracting years from text, and best-effort year from series info; all `get_tvdb_by_external_id` series-name return paths switch to `_translated_series_name`. | | **`_apply_tvdb_series_metadata` helper and call-site migration** <br> `src/metadata_searching.py` | Adds `_apply_tvdb_series_metadata` to set `tvdb_series_name` and derive `tvdb_series_year`/`search_year` from `episodes_data` with early return for English originals; replaces inline assignments across `all_ids`, `imdb_tmdb_tvdb`, `imdb_tvdb`, `get_tv_data` (both paths), and `get_tvdb_tvmaze_tmdb_episode_data`. | **Upload Success & PTP Poster Rehosting** | Layer / File(s) | Summary | |---|---| | **CookieAuth `success_list` and IS tracker usage** <br> `src/cookie_auth.py`, `src/trackers/IS.py` | `handle_upload` and `handle_failed_upload` gain an optional `success_list` parameter; validation and response-predicate logic extended to match any list entry; `IS.upload` switches from `success_text` to `success_list` with two accepted success strings. | | **PTP poster rehosting helpers and upload flow** <br> `src/trackers/PTP.py` | Adds `contextlib` import; introduces `_selected_poster_host`, `_poster_already_on_selected_host`, `_poster_extension`, and `rehost_poster_to_selected_host` methods; expired-session handling deletes the cookie file via `contextlib.suppress(OSError)`; upload cover acquisition uses the new rehosting pipeline with a user-prompt loop enforcing allowed image extensions. | | **NBL `ignoredupes` value and raw-disc check ordering** <br> `src/trackers/NBL.py` | Changes `ignoredupes` payload value from `"on"` to `"1"`; raw-disc early-exit in `search_existing` is moved to run after language and `valid_mi` checks. | **Prep Metadata Handling** | Layer / File(s) | Summary | |---|---| | **Title article helpers, category logic, TVDB overwrite, and audio unpacking** <br> `src/prep.py` | Adds `_title_without_leading_article` and `_tvdb_title_drops_existing_leading_article` helpers; `gather_prep` derives category via `get_cat` or uppercases existing value without `manual_category`; removes `aka` similarity-suppression block; TVDB series name overwrite is now conditional on the leading-article heuristic; `get_audio_v2` unpacked to 3 values dropping `has_audiodesc`; episode regex patterns loosened and multi-file TV-pack detection removed from `get_cat`. | ## Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes ## Possibly related PRs - [yippee0903/Upload-Assistant#45](https://github.com/yippee0903/Upload-Assistant/pull/45): Both PRs modify `src/edition.py`'s `get_edition` logic—this PR adjusts edition-count gating and fixes the return type, while the retrieved PR adds normalization to preserve "Special Edition" formatting. - [yippee0903/Upload-Assistant#62](https://github.com/yippee0903/Upload-Assistant/pull/62): Both PRs modify `src/trackers/IS.py`'s upload success handling via `CookieAuthUploader`, with this PR extending the mechanism to support multiple success strings via `success_list`. - [yippee0903/Upload-Assistant#210](https://github.com/yippee0903/Upload-Assistant/pull/210): This PR removes the `SequenceMatcher`-based `meta["aka"]` suppression block in `src/prep.py` that the retrieved PR introduced, making them directly conflicting changes. </details> <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> <details> <summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary> ### ❌ Failed checks (1 warning) | Check name | Status | Explanation | Resolution | | :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | <details> <summary>✅ Passed checks (4 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title check | ✅ Passed | The title 'chore: sync upstream/master (6 commits)' accurately describes the main change—syncing upstream commits from the master branch into the fork. | | Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | | Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. | </details> <sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub> </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `sync/upstream-merge` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=yippee0903/Upload-Assistant&utm_content=250)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. <details> <summary>❤️ Share</summary> - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code) </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end -->
yippee0903 commented 2026-06-18 19:11:04 +00:00 (Migrated from github.com)

@coderabbitai review

@coderabbitai review
coderabbitai[bot] commented 2026-06-18 19:11:11 +00:00 (Migrated from github.com)
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

<!-- This is an auto-generated reply by CodeRabbit --> <!-- CodeRabbit review command invocation: 3c2efc35-f9e1-490c-90cb-6a51231be2e1 --> <details> <summary>✅ Action performed</summary> Review finished. > Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused. </details>
coderabbitai[bot] (Migrated from github.com) requested changes 2026-06-18 19:22:01 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/imdb.py (1)

352-374: ⚠️ Potential issue | 🟠 Major | Quick win

Avoid collapsing IMDb editions that share the same runtime minute.

Line 374 now lets get_edition run when IMDb reports multiple runtimes, but line 371 stores details by minute only. Two editions in the same minute bucket overwrite each other while edition_count still says there are multiple editions, so duration matching can auto-label from whichever entry survived.

Proposed fix
-            for edge in editions:
+            for edition_index, edge in enumerate(editions):
                 node = self.safe_get(edge, ["node"], {})
                 seconds = self.safe_get(node, ["seconds"], 0)
                 minutes = seconds // 60 if seconds else 0
@@
-                    runtime_key = str(minutes)
+                    runtime_key = f"{minutes}:{edition_index}"
                     imdb_info["edition_details"][runtime_key] = {"display_name": displayable_property, "seconds": seconds, "minutes": minutes, "attributes": attribute_texts}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/imdb.py` around lines 352 - 374, The issue is that multiple editions with
the same runtime in minutes are overwriting each other in the
imdb_info["edition_details"] dictionary because the key is based only on the
runtime_key variable (derived from minutes). To fix this, change the storage
mechanism for edition_details to use a list or index-based approach instead of a
dictionary keyed by runtime minutes, so that each edition in the loop (at line
352 where editions are iterated) is stored uniquely without overwriting editions
that share the same minute duration. Ensure all edition entries are preserved in
the imdb_info["edition_details"] structure to match the accurate edition_count
being set at line 374.
🧹 Nitpick comments (2)
tests/test_edition.py (1)

77-103: Quick win

Add the negative edge case for the new edition_count guard.

This fixture forces edition_count = 2, so it only proves the IMDb duration path still works. Add a companion case with edition_count = 1 and matching attributes to lock in the “ignore sole IMDb edition” behavior.

Suggested test coverage
     def test_imdb_special_edition_preserved(self) -> None:
         """Simulate IMDB duration match returning 'Special Edition'.
@@
         edition, _repack, _hybrid = _run(get_edition(video, None, [video], '', meta))
         assert edition == 'SPECIAL EDITION'
+
+    def test_single_imdb_edition_is_ignored(self) -> None:
+        meta = _meta_base(
+            imdb_info={
+                'edition_count': 1,
+                'edition_details': {
+                    'v1': {
+                        'seconds': 7200,
+                        'attributes': ['special', 'edition'],
+                    },
+                },
+            },
+            mediainfo={
+                'media': {
+                    'track': [
+                        {'`@type`': 'General', 'Duration': '7200'},
+                    ],
+                },
+            },
+        )
+        video = 'Aliens.1986.2160p.UHD.BluRay.TrueHD.7.1.DoVi.HDR10.x265-W4NK3R.mkv'
+        edition, _repack, _hybrid = _run(get_edition(video, None, [video], '', meta))
+        assert edition == ''

As per coding guidelines, tests/** should cover edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_edition.py` around lines 77 - 103, Add a new test method to
complement test_imdb_special_edition_preserved that covers the negative edge
case. Create a test that uses the same structure but sets edition_count to 1
instead of 2, with matching IMDB edition attributes (special, edition) and the
same duration. This test should verify that when edition_count equals 1, the
sole IMDB edition is ignored and the Special Edition is NOT returned, thereby
validating the upstream imdb_edition_count > 1 guard behavior that prevents
processing single IMDB editions.

Source: Coding guidelines

src/trackers/PTP.py (1)

551-558: 💤 Low value

Consider cleaning up the temporary poster file after upload.

The downloaded poster file at poster_path is left on disk after upload. While it's in a temp directory that may be cleaned up eventually, explicitly removing it after a successful upload would prevent accumulation of orphaned files.

♻️ Optional cleanup suggestion
             uploaded_images, _ = await self.uploadscreens_manager.upload_screens(meta, 1, 1, 0, 1, [str(poster_path)], {})
         finally:
             if original_imghost is None:
                 meta.pop("imghost", None)
             else:
                 meta["imghost"] = original_imghost
+            with contextlib.suppress(OSError):
+                poster_path.unlink()

         if uploaded_images:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/trackers/PTP.py` around lines 551 - 558, After the poster file is
successfully uploaded (following the write_bytes operation on poster_path),
explicitly delete the temporary file to prevent accumulation of orphaned files
on disk. Use the unlink() method from the Path object (poster_path.unlink()) to
remove the file after the upload completes, ensuring cleanup happens whether the
upload succeeds or fails gracefully through exception handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/edition.py`:
- Line 19: The return type annotation for the get_edition function incorrectly
specifies the third return value as bool when it should be str, since the
function actually returns a string value (either empty string, "Hybrid", or
"Custom"). Update the return type annotation from tuple[str, str, bool] to
tuple[str, str, str] in the function signature.

In `@src/metadata_searching.py`:
- Around line 21-34: In the _apply_tvdb_series_metadata function, when both
series_title and series_year are available and valid, concatenate them together
in the format "title (year)" when setting tvdb_series_name, instead of setting
only the title. Currently the function sets tvdb_series_name to just the
series_title value without the year component, which causes downstream consumers
in LST.py and prep.py to fail to detect the year through their regex-based
string inspection logic. Modify the code where series_title is assigned to
tvdb_series_name to append the validated series_year in parentheses when
series_year is present and valid.

In `@src/prep.py`:
- Line 1172: The unpacking statement for the return value of get_audio_v2 is
missing a fourth variable. The function returns a 4-tuple (str, str, bool, bool)
but the code only unpacks three values into meta["audio"], meta["channels"], and
meta["has_commentary"]. Add a fourth assignment in the unpacking statement to
capture the fourth boolean value returned by get_audio_v2 and assign it to the
appropriate meta key or variable to match the actual return signature of the
function.

In `@src/tvdb.py`:
- Around line 111-121: The year extraction logic only searches through
english_aliases (lines 116-121) but never examines the selected title variable
that was set on line 114, which may already contain a year like "Foo (2020)".
Add a year extraction attempt from the title variable itself before the loop
that searches english_aliases, using _extract_year_from_text on the title. This
ensures that if the chosen title contains a year, it will be captured even when
english_aliases is empty or contains no year information.

---

Outside diff comments:
In `@src/imdb.py`:
- Around line 352-374: The issue is that multiple editions with the same runtime
in minutes are overwriting each other in the imdb_info["edition_details"]
dictionary because the key is based only on the runtime_key variable (derived
from minutes). To fix this, change the storage mechanism for edition_details to
use a list or index-based approach instead of a dictionary keyed by runtime
minutes, so that each edition in the loop (at line 352 where editions are
iterated) is stored uniquely without overwriting editions that share the same
minute duration. Ensure all edition entries are preserved in the
imdb_info["edition_details"] structure to match the accurate edition_count being
set at line 374.

---

Nitpick comments:
In `@src/trackers/PTP.py`:
- Around line 551-558: After the poster file is successfully uploaded (following
the write_bytes operation on poster_path), explicitly delete the temporary file
to prevent accumulation of orphaned files on disk. Use the unlink() method from
the Path object (poster_path.unlink()) to remove the file after the upload
completes, ensuring cleanup happens whether the upload succeeds or fails
gracefully through exception handling.

In `@tests/test_edition.py`:
- Around line 77-103: Add a new test method to complement
test_imdb_special_edition_preserved that covers the negative edge case. Create a
test that uses the same structure but sets edition_count to 1 instead of 2, with
matching IMDB edition attributes (special, edition) and the same duration. This
test should verify that when edition_count equals 1, the sole IMDB edition is
ignored and the Special Edition is NOT returned, thereby validating the upstream
imdb_edition_count > 1 guard behavior that prevents processing single IMDB
editions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40c6a164-026b-4f21-9c8b-f048a910c0b8

📥 Commits

Reviewing files that changed from the base of the PR and between 2e55153a8d and 9e52a8b5c3.

📒 Files selected for processing (11)
  • README.md
  • src/cookie_auth.py
  • src/edition.py
  • src/imdb.py
  • src/metadata_searching.py
  • src/prep.py
  • src/trackers/IS.py
  • src/trackers/NBL.py
  • src/trackers/PTP.py
  • src/tvdb.py
  • tests/test_edition.py
**Actionable comments posted: 4** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to platform limitations. > > > > <details> > <summary>⚠️ Outside diff range comments (1)</summary><blockquote> > > <details> > <summary>src/imdb.py (1)</summary><blockquote> > > `352-374`: _⚠️ Potential issue_ | _🟠 Major_ | _⚡ Quick win_ > > **Avoid collapsing IMDb editions that share the same runtime minute.** > > Line 374 now lets `get_edition` run when IMDb reports multiple runtimes, but line 371 stores details by minute only. Two editions in the same minute bucket overwrite each other while `edition_count` still says there are multiple editions, so duration matching can auto-label from whichever entry survived. > > <details> > <summary>Proposed fix</summary> > > ```diff > - for edge in editions: > + for edition_index, edge in enumerate(editions): > node = self.safe_get(edge, ["node"], {}) > seconds = self.safe_get(node, ["seconds"], 0) > minutes = seconds // 60 if seconds else 0 > @@ > - runtime_key = str(minutes) > + runtime_key = f"{minutes}:{edition_index}" > imdb_info["edition_details"][runtime_key] = {"display_name": displayable_property, "seconds": seconds, "minutes": minutes, "attributes": attribute_texts} > ``` > </details> > > <details> > <summary>🤖 Prompt for AI Agents</summary> > > ``` > Verify each finding against current code. Fix only still-valid issues, skip the > rest with a brief reason, keep changes minimal, and validate. > > In `@src/imdb.py` around lines 352 - 374, The issue is that multiple editions with > the same runtime in minutes are overwriting each other in the > imdb_info["edition_details"] dictionary because the key is based only on the > runtime_key variable (derived from minutes). To fix this, change the storage > mechanism for edition_details to use a list or index-based approach instead of a > dictionary keyed by runtime minutes, so that each edition in the loop (at line > 352 where editions are iterated) is stored uniquely without overwriting editions > that share the same minute duration. Ensure all edition entries are preserved in > the imdb_info["edition_details"] structure to match the accurate edition_count > being set at line 374. > ``` > > </details> > > <!-- cr-comment:v1:9ffa67548793a6f0bbe4dd56 --> > > </blockquote></details> > > </blockquote></details> <details> <summary>🧹 Nitpick comments (2)</summary><blockquote> <details> <summary>tests/test_edition.py (1)</summary><blockquote> `77-103`: _⚡ Quick win_ **Add the negative edge case for the new `edition_count` guard.** This fixture forces `edition_count = 2`, so it only proves the IMDb duration path still works. Add a companion case with `edition_count = 1` and matching attributes to lock in the “ignore sole IMDb edition” behavior. <details> <summary>Suggested test coverage</summary> ```diff def test_imdb_special_edition_preserved(self) -> None: """Simulate IMDB duration match returning 'Special Edition'. @@ edition, _repack, _hybrid = _run(get_edition(video, None, [video], '', meta)) assert edition == 'SPECIAL EDITION' + + def test_single_imdb_edition_is_ignored(self) -> None: + meta = _meta_base( + imdb_info={ + 'edition_count': 1, + 'edition_details': { + 'v1': { + 'seconds': 7200, + 'attributes': ['special', 'edition'], + }, + }, + }, + mediainfo={ + 'media': { + 'track': [ + {'`@type`': 'General', 'Duration': '7200'}, + ], + }, + }, + ) + video = 'Aliens.1986.2160p.UHD.BluRay.TrueHD.7.1.DoVi.HDR10.x265-W4NK3R.mkv' + edition, _repack, _hybrid = _run(get_edition(video, None, [video], '', meta)) + assert edition == '' ``` </details> As per coding guidelines, `tests/**` should cover edge cases. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_edition.py` around lines 77 - 103, Add a new test method to complement test_imdb_special_edition_preserved that covers the negative edge case. Create a test that uses the same structure but sets edition_count to 1 instead of 2, with matching IMDB edition attributes (special, edition) and the same duration. This test should verify that when edition_count equals 1, the sole IMDB edition is ignored and the Special Edition is NOT returned, thereby validating the upstream imdb_edition_count > 1 guard behavior that prevents processing single IMDB editions. ``` </details> <!-- cr-comment:v1:2008b947adaeba904386af2a --> _Source: Coding guidelines_ </blockquote></details> <details> <summary>src/trackers/PTP.py (1)</summary><blockquote> `551-558`: _💤 Low value_ **Consider cleaning up the temporary poster file after upload.** The downloaded poster file at `poster_path` is left on disk after upload. While it's in a temp directory that may be cleaned up eventually, explicitly removing it after a successful upload would prevent accumulation of orphaned files. <details> <summary>♻️ Optional cleanup suggestion</summary> ```diff uploaded_images, _ = await self.uploadscreens_manager.upload_screens(meta, 1, 1, 0, 1, [str(poster_path)], {}) finally: if original_imghost is None: meta.pop("imghost", None) else: meta["imghost"] = original_imghost + with contextlib.suppress(OSError): + poster_path.unlink() if uploaded_images: ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/trackers/PTP.py` around lines 551 - 558, After the poster file is successfully uploaded (following the write_bytes operation on poster_path), explicitly delete the temporary file to prevent accumulation of orphaned files on disk. Use the unlink() method from the Path object (poster_path.unlink()) to remove the file after the upload completes, ensuring cleanup happens whether the upload succeeds or fails gracefully through exception handling. ``` </details> <!-- cr-comment:v1:390e7595b68033528c8eb5a1 --> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Inline comments: In `@src/edition.py`: - Line 19: The return type annotation for the get_edition function incorrectly specifies the third return value as bool when it should be str, since the function actually returns a string value (either empty string, "Hybrid", or "Custom"). Update the return type annotation from tuple[str, str, bool] to tuple[str, str, str] in the function signature. In `@src/metadata_searching.py`: - Around line 21-34: In the _apply_tvdb_series_metadata function, when both series_title and series_year are available and valid, concatenate them together in the format "title (year)" when setting tvdb_series_name, instead of setting only the title. Currently the function sets tvdb_series_name to just the series_title value without the year component, which causes downstream consumers in LST.py and prep.py to fail to detect the year through their regex-based string inspection logic. Modify the code where series_title is assigned to tvdb_series_name to append the validated series_year in parentheses when series_year is present and valid. In `@src/prep.py`: - Line 1172: The unpacking statement for the return value of get_audio_v2 is missing a fourth variable. The function returns a 4-tuple (str, str, bool, bool) but the code only unpacks three values into meta["audio"], meta["channels"], and meta["has_commentary"]. Add a fourth assignment in the unpacking statement to capture the fourth boolean value returned by get_audio_v2 and assign it to the appropriate meta key or variable to match the actual return signature of the function. In `@src/tvdb.py`: - Around line 111-121: The year extraction logic only searches through english_aliases (lines 116-121) but never examines the selected title variable that was set on line 114, which may already contain a year like "Foo (2020)". Add a year extraction attempt from the title variable itself before the loop that searches english_aliases, using _extract_year_from_text on the title. This ensures that if the chosen title contains a year, it will be captured even when english_aliases is empty or contains no year information. --- Outside diff comments: In `@src/imdb.py`: - Around line 352-374: The issue is that multiple editions with the same runtime in minutes are overwriting each other in the imdb_info["edition_details"] dictionary because the key is based only on the runtime_key variable (derived from minutes). To fix this, change the storage mechanism for edition_details to use a list or index-based approach instead of a dictionary keyed by runtime minutes, so that each edition in the loop (at line 352 where editions are iterated) is stored uniquely without overwriting editions that share the same minute duration. Ensure all edition entries are preserved in the imdb_info["edition_details"] structure to match the accurate edition_count being set at line 374. --- Nitpick comments: In `@src/trackers/PTP.py`: - Around line 551-558: After the poster file is successfully uploaded (following the write_bytes operation on poster_path), explicitly delete the temporary file to prevent accumulation of orphaned files on disk. Use the unlink() method from the Path object (poster_path.unlink()) to remove the file after the upload completes, ensuring cleanup happens whether the upload succeeds or fails gracefully through exception handling. In `@tests/test_edition.py`: - Around line 77-103: Add a new test method to complement test_imdb_special_edition_preserved that covers the negative edge case. Create a test that uses the same structure but sets edition_count to 1 instead of 2, with matching IMDB edition attributes (special, edition) and the same duration. This test should verify that when edition_count equals 1, the sole IMDB edition is ignored and the Special Edition is NOT returned, thereby validating the upstream imdb_edition_count > 1 guard behavior that prevents processing single IMDB editions. ``` </details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Path: .coderabbit.yaml **Review profile**: CHILL **Plan**: Pro **Run ID**: `40c6a164-026b-4f21-9c8b-f048a910c0b8` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 2e55153a8dc65936213841dab8e3a7d2c6d78709 and 9e52a8b5c31777fbb300d3e2ac2cfa71f7cff9af. </details> <details> <summary>📒 Files selected for processing (11)</summary> * `README.md` * `src/cookie_auth.py` * `src/edition.py` * `src/imdb.py` * `src/metadata_searching.py` * `src/prep.py` * `src/trackers/IS.py` * `src/trackers/NBL.py` * `src/trackers/PTP.py` * `src/tvdb.py` * `tests/test_edition.py` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
coderabbitai[bot] (Migrated from github.com) commented 2026-06-18 19:21:59 +00:00

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify get_edition's third return value and callers.
rg -n -C3 'async def get_edition|return edition, repack, hybrid|webdv.*get_edition|meta\["webdv"\]' --type=py

Repository: yippee0903/Upload-Assistant

Length of output: 2678


🏁 Script executed:

sed -n '19,417p' src/edition.py | grep -n "hybrid\s*=" | head -20

Repository: yippee0903/Upload-Assistant

Length of output: 193


🏁 Script executed:

sed -n '350,417p' src/edition.py

Repository: yippee0903/Upload-Assistant

Length of output: 2905


Fix the return type annotation to match the actual implementation.

The third return value is always a string ("", "Hybrid", or "Custom"), not a bool. The annotation at line 19 should be tuple[str, str, str].

Proposed fix
-async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, bool]:
+async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, str]:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, str]:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/edition.py` at line 19, The return type annotation for the get_edition
function incorrectly specifies the third return value as bool when it should be
str, since the function actually returns a string value (either empty string,
"Hybrid", or "Custom"). Update the return type annotation from tuple[str, str,
bool] to tuple[str, str, str] in the function signature.

Addressed in commit b1e631e

_⚠️ Potential issue_ | _🟡 Minor_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash # Verify get_edition's third return value and callers. rg -n -C3 'async def get_edition|return edition, repack, hybrid|webdv.*get_edition|meta\["webdv"\]' --type=py ``` Repository: yippee0903/Upload-Assistant Length of output: 2678 --- 🏁 Script executed: ```shell sed -n '19,417p' src/edition.py | grep -n "hybrid\s*=" | head -20 ``` Repository: yippee0903/Upload-Assistant Length of output: 193 --- 🏁 Script executed: ```shell sed -n '350,417p' src/edition.py ``` Repository: yippee0903/Upload-Assistant Length of output: 2905 --- </details> **Fix the return type annotation to match the actual implementation.** The third return value is always a string (`""`, `"Hybrid"`, or `"Custom"`), not a `bool`. The annotation at line 19 should be `tuple[str, str, str]`. <details> <summary>Proposed fix</summary> ```diff -async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, bool]: +async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, str]: ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion async def get_edition(video: str, bdinfo: Optional[dict[str, Any]], filelist: list[str], manual_edition: Union[str, list[str]], meta: dict[str, Any]) -> tuple[str, str, str]: ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/edition.py` at line 19, The return type annotation for the get_edition function incorrectly specifies the third return value as bool when it should be str, since the function actually returns a string value (either empty string, "Hybrid", or "Custom"). Update the return type annotation from tuple[str, str, bool] to tuple[str, str, str] in the function signature. ``` </details> <!-- cr-comment:v1:5c0c1c93b28731b7043475e0 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit b1e631e
@ -18,6 +18,23 @@ def _coerce_int(value: Any) -> Optional[int]:
return None
coderabbitai[bot] (Migrated from github.com) commented 2026-06-18 19:21:59 +00:00

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find consumers that still infer TVDB year/title behavior from tvdb_series_name.
rg -n -C3 'tvdb_series_name|tvdb_series_year|tvdb_has_year' --type=py

Repository: yippee0903/Upload-Assistant

Length of output: 11930


Fix TVDB year handling: include the year in tvdb_series_name or update all consumers to check tvdb_series_year.

The change separates tvdb_series_year into its own field, but downstream code (LST.py and prep.py) still infers year presence by regex-matching against tvdb_series_name. When TVDB returns series_title="Shameless" and series_year="2011", the code now sets tvdb_series_name="Shameless" (no year) and tvdb_series_year="2011" (separate). LST then sees no year in the name string and strips it—even though TVDB provided one. Test cases explicitly expect the year to be present in the tvdb_series_name string (e.g., tvdb_series_name="Shameless (2011)") for year-retention logic to work.

Options:

  • Include the year in tvdb_series_name when available (e.g., "Shameless (2011)")
  • Update LST.py (lines 165–168) and prep.py (lines 1116–1128) to check tvdb_series_year in addition to or instead of string inspection
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/metadata_searching.py` around lines 21 - 34, In the
_apply_tvdb_series_metadata function, when both series_title and series_year are
available and valid, concatenate them together in the format "title (year)" when
setting tvdb_series_name, instead of setting only the title. Currently the
function sets tvdb_series_name to just the series_title value without the year
component, which causes downstream consumers in LST.py and prep.py to fail to
detect the year through their regex-based string inspection logic. Modify the
code where series_title is assigned to tvdb_series_name to append the validated
series_year in parentheses when series_year is present and valid.

Addressed in commit b1e631e

_⚠️ Potential issue_ | _🟠 Major_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash # Find consumers that still infer TVDB year/title behavior from tvdb_series_name. rg -n -C3 'tvdb_series_name|tvdb_series_year|tvdb_has_year' --type=py ``` Repository: yippee0903/Upload-Assistant Length of output: 11930 --- </details> **Fix TVDB year handling: include the year in `tvdb_series_name` or update all consumers to check `tvdb_series_year`.** The change separates `tvdb_series_year` into its own field, but downstream code (LST.py and prep.py) still infers year presence by regex-matching against `tvdb_series_name`. When TVDB returns `series_title="Shameless"` and `series_year="2011"`, the code now sets `tvdb_series_name="Shameless"` (no year) and `tvdb_series_year="2011"` (separate). LST then sees no year in the name string and strips it—even though TVDB provided one. Test cases explicitly expect the year to be present in the `tvdb_series_name` string (e.g., `tvdb_series_name="Shameless (2011)"`) for year-retention logic to work. Options: - Include the year in `tvdb_series_name` when available (e.g., `"Shameless (2011)"`) - Update `LST.py` (lines 165–168) and `prep.py` (lines 1116–1128) to check `tvdb_series_year` in addition to or instead of string inspection <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/metadata_searching.py` around lines 21 - 34, In the _apply_tvdb_series_metadata function, when both series_title and series_year are available and valid, concatenate them together in the format "title (year)" when setting tvdb_series_name, instead of setting only the title. Currently the function sets tvdb_series_name to just the series_title value without the year component, which causes downstream consumers in LST.py and prep.py to fail to detect the year through their regex-based string inspection logic. Modify the code where series_title is assigned to tvdb_series_name to append the validated series_year in parentheses when series_year is present and valid. ``` </details> <!-- cr-comment:v1:5a5d0bb7ebed96ad2bd8c5d5 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit b1e631e
coderabbitai[bot] (Migrated from github.com) commented 2026-06-18 19:21:59 +00:00

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current return type of get_audio_v2
rg -n "def get_audio_v2|async def get_audio_v2|def _get_audio_v2|async def _get_audio_v2" src/audio.py -A 10

# Check the actual return statement in _get_audio_v2
ast-grep --pattern $'async def _get_audio_v2($$$) -> $_:
  $$$
  return $$$
'

Repository: yippee0903/Upload-Assistant

Length of output: 19029


Return value count mismatch causes runtime ValueError.

The code unpacks 3 values from get_audio_v2, but the function returns a 4-tuple (str, str, bool, bool) per src/audio.py:127-133. This will raise ValueError: too many values to unpack (expected 3) at runtime, breaking all uploads.

🐛 Proposed fix to unpack all 4 values
-            meta["audio"], meta["channels"], meta["has_commentary"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo)
+            meta["audio"], meta["channels"], meta["has_commentary"], meta["has_audiodesc"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            meta["audio"], meta["channels"], meta["has_commentary"], meta["has_audiodesc"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prep.py` at line 1172, The unpacking statement for the return value of
get_audio_v2 is missing a fourth variable. The function returns a 4-tuple (str,
str, bool, bool) but the code only unpacks three values into meta["audio"],
meta["channels"], and meta["has_commentary"]. Add a fourth assignment in the
unpacking statement to capture the fourth boolean value returned by get_audio_v2
and assign it to the appropriate meta key or variable to match the actual return
signature of the function.

Addressed in commit b1e631e

_⚠️ Potential issue_ | _🔴 Critical_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash # Verify current return type of get_audio_v2 rg -n "def get_audio_v2|async def get_audio_v2|def _get_audio_v2|async def _get_audio_v2" src/audio.py -A 10 # Check the actual return statement in _get_audio_v2 ast-grep --pattern $'async def _get_audio_v2($$$) -> $_: $$$ return $$$ ' ``` Repository: yippee0903/Upload-Assistant Length of output: 19029 --- </details> **Return value count mismatch causes runtime `ValueError`.** The code unpacks 3 values from `get_audio_v2`, but the function returns a 4-tuple `(str, str, bool, bool)` per `src/audio.py:127-133`. This will raise `ValueError: too many values to unpack (expected 3)` at runtime, breaking all uploads. <details> <summary>🐛 Proposed fix to unpack all 4 values</summary> ```diff - meta["audio"], meta["channels"], meta["has_commentary"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo) + meta["audio"], meta["channels"], meta["has_commentary"], meta["has_audiodesc"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo) ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion meta["audio"], meta["channels"], meta["has_commentary"], meta["has_audiodesc"] = await self.audio_manager.get_audio_v2(mi_data, meta, bdinfo) ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/prep.py` at line 1172, The unpacking statement for the return value of get_audio_v2 is missing a fourth variable. The function returns a 4-tuple (str, str, bool, bool) but the code only unpacks three values into meta["audio"], meta["channels"], and meta["has_commentary"]. Add a fourth assignment in the unpacking statement to capture the fourth boolean value returned by get_audio_v2 and assign it to the appropriate meta key or variable to match the actual return signature of the function. ``` </details> <!-- fingerprinting:phantom:medusa:ocelot --> <!-- cr-comment:v1:e6b85acf56f1f93e19cdadc6 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit b1e631e
coderabbitai[bot] (Migrated from github.com) commented 2026-06-18 19:22:00 +00:00

⚠️ Potential issue | 🟡 Minor | Quick win

Extract the year from the selected translation title before falling back.

Line 114 may choose a title like Foo (2020), but lines 116-121 never inspect that title. When aliases are empty or lack a year, series_year stays unset and callers cannot populate tvdb_series_year/search_year.

Proposed fix
     fallback_title = translation_aliases[-1] if translation_aliases else _pick_eng_alias(aliases, debug=debug)
     title = translation_name or fallback_title
     year = None
-    for alias in english_aliases:
-        year = _extract_year_from_text(alias)
+    year_candidates = [candidate for candidate in (translation_name, fallback_title, *english_aliases) if candidate]
+    for alias in year_candidates:
+        year = _extract_year_from_text(alias)
         if year:
             break
-    if not english_aliases:
+    if not year:
         year = _best_effort_series_year(_series_info)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tvdb.py` around lines 111 - 121, The year extraction logic only searches
through english_aliases (lines 116-121) but never examines the selected title
variable that was set on line 114, which may already contain a year like "Foo
(2020)". Add a year extraction attempt from the title variable itself before the
loop that searches english_aliases, using _extract_year_from_text on the title.
This ensures that if the chosen title contains a year, it will be captured even
when english_aliases is empty or contains no year information.

Addressed in commit b1e631e

_⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_ **Extract the year from the selected translation title before falling back.** Line 114 may choose a title like `Foo (2020)`, but lines 116-121 never inspect that title. When aliases are empty or lack a year, `series_year` stays unset and callers cannot populate `tvdb_series_year`/`search_year`. <details> <summary>Proposed fix</summary> ```diff fallback_title = translation_aliases[-1] if translation_aliases else _pick_eng_alias(aliases, debug=debug) title = translation_name or fallback_title year = None - for alias in english_aliases: - year = _extract_year_from_text(alias) + year_candidates = [candidate for candidate in (translation_name, fallback_title, *english_aliases) if candidate] + for alias in year_candidates: + year = _extract_year_from_text(alias) if year: break - if not english_aliases: + if not year: year = _best_effort_series_year(_series_info) ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tvdb.py` around lines 111 - 121, The year extraction logic only searches through english_aliases (lines 116-121) but never examines the selected title variable that was set on line 114, which may already contain a year like "Foo (2020)". Add a year extraction attempt from the title variable itself before the loop that searches english_aliases, using _extract_year_from_text on the title. This ensures that if the chosen title contains a year, it will be captured even when english_aliases is empty or contains no year information. ``` </details> <!-- cr-comment:v1:7c78dfe40499de7743674521 --> <!-- This is an auto-generated comment by CodeRabbit --> ✅ Addressed in commit b1e631e
coderabbitai[bot] (Migrated from github.com) approved these changes 2026-06-18 19:34:42 +00:00
Sign in to join this conversation.
No description provided.