riplex 0.7.3__tar.gz → 0.8.0__tar.gz

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.
Files changed (138) hide show
  1. {riplex-0.7.3/src/riplex.egg-info → riplex-0.8.0}/PKG-INFO +1 -1
  2. {riplex-0.7.3 → riplex-0.8.0}/docs/changelog.md +16 -0
  3. riplex-0.8.0/issues/manual-rip-and-organize-escape-hatch.md +106 -0
  4. {riplex-0.7.3 → riplex-0.8.0/src/riplex.egg-info}/PKG-INFO +1 -1
  5. {riplex-0.7.3 → riplex-0.8.0}/src/riplex.egg-info/SOURCES.txt +3 -0
  6. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/done.py +1 -1
  7. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/metadata.py +1 -1
  8. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/orchestrate_done.py +5 -5
  9. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/release.py +12 -1
  10. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/welcome.py +59 -10
  11. riplex-0.8.0/tests/test_release_screen.py +40 -0
  12. riplex-0.8.0/tests/test_welcome_screen.py +42 -0
  13. {riplex-0.7.3 → riplex-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  14. {riplex-0.7.3 → riplex-0.8.0}/.github/ISSUE_TEMPLATE/crash_report.yml +0 -0
  15. {riplex-0.7.3 → riplex-0.8.0}/.github/agents/riplex.agent.md +0 -0
  16. {riplex-0.7.3 → riplex-0.8.0}/.github/copilot-instructions.md +0 -0
  17. {riplex-0.7.3 → riplex-0.8.0}/.github/workflows/publish.yml +0 -0
  18. {riplex-0.7.3 → riplex-0.8.0}/.github/workflows/release.yml +0 -0
  19. {riplex-0.7.3 → riplex-0.8.0}/.gitignore +0 -0
  20. {riplex-0.7.3 → riplex-0.8.0}/.vscode/settings.json +0 -0
  21. {riplex-0.7.3 → riplex-0.8.0}/CONTRIBUTORS.md +0 -0
  22. {riplex-0.7.3 → riplex-0.8.0}/LICENSE +0 -0
  23. {riplex-0.7.3 → riplex-0.8.0}/README.md +0 -0
  24. {riplex-0.7.3 → riplex-0.8.0}/docs/architecture.md +0 -0
  25. {riplex-0.7.3 → riplex-0.8.0}/docs/cli-guide/lookup.md +0 -0
  26. {riplex-0.7.3 → riplex-0.8.0}/docs/cli-guide/orchestrate.md +0 -0
  27. {riplex-0.7.3 → riplex-0.8.0}/docs/cli-guide/organize.md +0 -0
  28. {riplex-0.7.3 → riplex-0.8.0}/docs/cli-guide/workflow.md +0 -0
  29. {riplex-0.7.3 → riplex-0.8.0}/docs/getting-started/configuration.md +0 -0
  30. {riplex-0.7.3 → riplex-0.8.0}/docs/getting-started/installation.md +0 -0
  31. {riplex-0.7.3 → riplex-0.8.0}/docs/gui-guide/gui-walkthrough.md +0 -0
  32. {riplex-0.7.3 → riplex-0.8.0}/docs/index.md +0 -0
  33. {riplex-0.7.3 → riplex-0.8.0}/docs/naming-rules.md +0 -0
  34. {riplex-0.7.3 → riplex-0.8.0}/docs/reference/cli.md +0 -0
  35. {riplex-0.7.3 → riplex-0.8.0}/docs/troubleshooting.md +0 -0
  36. {riplex-0.7.3 → riplex-0.8.0}/issues/debug-artifacts-consolidation.md +0 -0
  37. {riplex-0.7.3 → riplex-0.8.0}/issues/orchestrate-dvdcompare-fallback.md +0 -0
  38. {riplex-0.7.3 → riplex-0.8.0}/issues/planned-features.md +0 -0
  39. {riplex-0.7.3 → riplex-0.8.0}/mkdocs.yml +0 -0
  40. {riplex-0.7.3 → riplex-0.8.0}/pyproject.toml +0 -0
  41. {riplex-0.7.3 → riplex-0.8.0}/screenshots/0_Rip_Flow_BTTF.gif +0 -0
  42. {riplex-0.7.3 → riplex-0.8.0}/screenshots/1_Welcome_Screen.png +0 -0
  43. {riplex-0.7.3 → riplex-0.8.0}/screenshots/2_Disc_Detection_BTTF.png +0 -0
  44. {riplex-0.7.3 → riplex-0.8.0}/screenshots/3_Metadata_Lookup_BTTF.png +0 -0
  45. {riplex-0.7.3 → riplex-0.8.0}/screenshots/4_Disc_Release_BTTF.png +0 -0
  46. {riplex-0.7.3 → riplex-0.8.0}/screenshots/5_Multi_Disc_Overview_BTTF.png +0 -0
  47. {riplex-0.7.3 → riplex-0.8.0}/screenshots/5_Select_Title_to_RIP_BTTF.png +0 -0
  48. {riplex-0.7.3 → riplex-0.8.0}/setup.cfg +0 -0
  49. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/__init__.py +0 -0
  50. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/cache.py +0 -0
  51. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/config.py +0 -0
  52. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/dedup.py +0 -0
  53. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/detect.py +0 -0
  54. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/disc/__init__.py +0 -0
  55. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/disc/analysis.py +0 -0
  56. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/disc/makemkv.py +0 -0
  57. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/disc/provider.py +0 -0
  58. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/formatter.py +0 -0
  59. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/lookup.py +0 -0
  60. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/manifest.py +0 -0
  61. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/matcher.py +0 -0
  62. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/metadata/__init__.py +0 -0
  63. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/metadata/planner.py +0 -0
  64. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/metadata/provider.py +0 -0
  65. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/metadata/sources/__init__.py +0 -0
  66. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/metadata/sources/tmdb.py +0 -0
  67. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/models.py +0 -0
  68. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/normalize.py +0 -0
  69. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/organizer.py +0 -0
  70. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/scanner.py +0 -0
  71. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/snapshot.py +0 -0
  72. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/splitter.py +0 -0
  73. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/tagger.py +0 -0
  74. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/title.py +0 -0
  75. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/ui.py +0 -0
  76. {riplex-0.7.3 → riplex-0.8.0}/src/riplex/updater.py +0 -0
  77. {riplex-0.7.3 → riplex-0.8.0}/src/riplex.egg-info/dependency_links.txt +0 -0
  78. {riplex-0.7.3 → riplex-0.8.0}/src/riplex.egg-info/entry_points.txt +0 -0
  79. {riplex-0.7.3 → riplex-0.8.0}/src/riplex.egg-info/requires.txt +0 -0
  80. {riplex-0.7.3 → riplex-0.8.0}/src/riplex.egg-info/top_level.txt +0 -0
  81. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/__init__.py +0 -0
  82. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/bug_report.py +0 -0
  83. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/crash_dump.py +0 -0
  84. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/keep_awake.py +0 -0
  85. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/main.py +0 -0
  86. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/__init__.py +0 -0
  87. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/disc_detection.py +0 -0
  88. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/disc_overview.py +0 -0
  89. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/disc_swap.py +0 -0
  90. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/folder_picker.py +0 -0
  91. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/organize_done.py +0 -0
  92. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/organize_preview.py +0 -0
  93. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/progress.py +0 -0
  94. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/selection.py +0 -0
  95. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_app/screens/update.py +0 -0
  96. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/__init__.py +0 -0
  97. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/__init__.py +0 -0
  98. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/lookup.py +0 -0
  99. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/orchestrate.py +0 -0
  100. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/organize.py +0 -0
  101. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/rip.py +0 -0
  102. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/commands/setup.py +0 -0
  103. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/formatting.py +0 -0
  104. {riplex-0.7.3 → riplex-0.8.0}/src/riplex_cli/main.py +0 -0
  105. {riplex-0.7.3 → riplex-0.8.0}/tests/__init__.py +0 -0
  106. {riplex-0.7.3 → riplex-0.8.0}/tests/fixtures/chernobyl_disc1.json +0 -0
  107. {riplex-0.7.3 → riplex-0.8.0}/tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt +0 -0
  108. {riplex-0.7.3 → riplex-0.8.0}/tests/fixtures/makemkvcon_list.txt +0 -0
  109. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/Batman Begins.snapshot.json +0 -0
  110. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/Blade Runner (Blu-ray 4k).snapshot.json +0 -0
  111. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/Blade Runner The Final Cut.snapshot.json +0 -0
  112. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/Seven Worlds One Planet.snapshot.json +0 -0
  113. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/The Dark Knight Rises.snapshot.json +0 -0
  114. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/The Dark Knight.snapshot.json +0 -0
  115. {riplex-0.7.3 → riplex-0.8.0}/tests/snapshots/Waterworld.snapshot.json +0 -0
  116. {riplex-0.7.3 → riplex-0.8.0}/tests/test_cache.py +0 -0
  117. {riplex-0.7.3 → riplex-0.8.0}/tests/test_cli_utils.py +0 -0
  118. {riplex-0.7.3 → riplex-0.8.0}/tests/test_config.py +0 -0
  119. {riplex-0.7.3 → riplex-0.8.0}/tests/test_dedup.py +0 -0
  120. {riplex-0.7.3 → riplex-0.8.0}/tests/test_detect.py +0 -0
  121. {riplex-0.7.3 → riplex-0.8.0}/tests/test_disc_analysis.py +0 -0
  122. {riplex-0.7.3 → riplex-0.8.0}/tests/test_disc_detection_screen.py +0 -0
  123. {riplex-0.7.3 → riplex-0.8.0}/tests/test_disc_fixtures.py +0 -0
  124. {riplex-0.7.3 → riplex-0.8.0}/tests/test_disc_provider.py +0 -0
  125. {riplex-0.7.3 → riplex-0.8.0}/tests/test_formatter.py +0 -0
  126. {riplex-0.7.3 → riplex-0.8.0}/tests/test_lookup.py +0 -0
  127. {riplex-0.7.3 → riplex-0.8.0}/tests/test_makemkv.py +0 -0
  128. {riplex-0.7.3 → riplex-0.8.0}/tests/test_matcher.py +0 -0
  129. {riplex-0.7.3 → riplex-0.8.0}/tests/test_normalize.py +0 -0
  130. {riplex-0.7.3 → riplex-0.8.0}/tests/test_organizer.py +0 -0
  131. {riplex-0.7.3 → riplex-0.8.0}/tests/test_planner.py +0 -0
  132. {riplex-0.7.3 → riplex-0.8.0}/tests/test_rip_guide.py +0 -0
  133. {riplex-0.7.3 → riplex-0.8.0}/tests/test_scanner.py +0 -0
  134. {riplex-0.7.3 → riplex-0.8.0}/tests/test_snapshot.py +0 -0
  135. {riplex-0.7.3 → riplex-0.8.0}/tests/test_splitter.py +0 -0
  136. {riplex-0.7.3 → riplex-0.8.0}/tests/test_tagger.py +0 -0
  137. {riplex-0.7.3 → riplex-0.8.0}/tests/test_ui.py +0 -0
  138. {riplex-0.7.3 → riplex-0.8.0}/tests/test_updater.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: riplex
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -4,6 +4,22 @@ All notable changes to the riplex documentation are recorded here.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## v0.8.0 — 2026-06-12
8
+
9
+ ### Added
10
+
11
+ - **GUI: editable settings after first-run setup.** The welcome screen now exposes an **Edit Settings** button after configuration is complete, so users can update the TMDb API key, media library root, MakeMKV rip output folder, and optional archive folder without re-running setup from the command line.
12
+
13
+ ### Changed
14
+
15
+ - **GUI: less Plex-specific library wording.** User-facing destination copy now refers to a general media library while retaining **Plex-compatible naming** where the current folder/file convention is being described.
16
+
17
+ ## v0.7.4 — 2026-06-10
18
+
19
+ ### Fixed
20
+
21
+ - **GUI: empty Disc Overview after continuing without dvdcompare data.** When dvdcompare had no matching release, the orchestrate flow still navigated to the multi-disc overview, which only renders dvdcompare-provided disc rows. The no-dvdcompare fallback now treats the inserted disc as a single-disc rip and jumps directly to title selection, where TMDb runtime heuristics can pick the main feature.
22
+
7
23
  ## v0.7.3 — 2026-06-10
8
24
 
9
25
  ### Fixed
@@ -0,0 +1,106 @@
1
+ # GUI: manual rip and manual organize escape hatches
2
+
3
+ ## Problem
4
+
5
+ When riplex can't classify a disc — no dvdcompare match, ambiguous TMDb
6
+ result, multi-feature compilation, region-coded oddity, etc. — the GUI flow
7
+ dead-ends at the title-selection screen. The user has to leave riplex-ui,
8
+ launch the MakeMKV GUI to rip the disc directly, and then either keep those
9
+ files unorganized or manually rename and move them into Plex layout.
10
+
11
+ We hit this in practice with a BBC Earth Blu-ray containing two unrelated
12
+ features (*Wild Africa* + *Tiny Giants*). The disc isn't on dvdcompare, TMDb
13
+ has only a combined "double feature" entry, and riplex's planner assumes one
14
+ disc → one TMDb item, so the rip flow couldn't proceed at all.
15
+
16
+ ## Why we don't want to model compilations directly
17
+
18
+ Compilation discs that contain multiple unrelated films are rare enough that
19
+ building first-class support (per-title TMDb matching, per-title disc
20
+ metadata, multi-target organize) isn't justified. A generic escape hatch is
21
+ cheaper and covers other niche scenarios as a side benefit (foreign releases
22
+ without metadata, damaged-disc recovery, special workshop discs, etc.).
23
+
24
+ ## Proposed UX
25
+
26
+ Two new entry points in the GUI, both intentionally generic:
27
+
28
+ ### 1. "Manual rip" button on the title-selection screen
29
+
30
+ When riplex can't classify titles confidently (no dvdcompare match **and**
31
+ TMDb selection is ambiguous, or user just wants raw control), surface a
32
+ "Manual rip" button alongside the existing "Rip selected" button.
33
+
34
+ Clicking it drops the user into a flat MakeMKV-style title list:
35
+
36
+ - All titles, sorted by index, with duration / size / chapter count
37
+ - Checkbox per title (default: all titles longer than 2 minutes)
38
+ - Output folder picker (defaults to `<output_root>/_MakeMKV/<disc_label>/`)
39
+ - "Rip" button → calls `makemkvcon mkv all` (or per-title) and shows the
40
+ existing progress UI
41
+
42
+ No metadata lookup, no organize, no Plex naming. The user lands in the "Done"
43
+ screen with a folder of raw MKVs, with a follow-up button: "Organize this
44
+ folder now → opens the manual organize screen pre-pointed at that path."
45
+
46
+ ### 2. "Organize existing folder" entry on the welcome screen
47
+
48
+ A second card on welcome (next to "Rip a disc") titled "Organize existing
49
+ files." Picks up an arbitrary folder of MKVs and walks the user through:
50
+
51
+ 1. **Folder picker** → list MKVs with sizes and ffprobe runtimes.
52
+ 2. **TMDb search** (reuses existing `metadata` screen) → select the canonical
53
+ entry.
54
+ 3. **File assignment** → for each MKV, drop-down: `Main feature`, `Part 1
55
+ (-pt1)`, `Part 2 (-pt2)`, `Extra → behindthescenes`, `Extra → featurette`,
56
+ `Extra → trailer`, `Skip`. Auto-suggest based on duration.
57
+ 4. **Plan preview** → show source → destination paths.
58
+ 5. **Apply** → move + rename into Plex layout, with the existing dry-run
59
+ default and `Execute` toggle.
60
+
61
+ This is essentially the existing `riplex organize` CLI behavior with a GUI in
62
+ front of it. No new business logic — just a thin wrapper that also handles
63
+ the "two parts of one TMDb movie" case via the assignment step.
64
+
65
+ ## Why this works for the BBC Earth case
66
+
67
+ End-to-end flow stays inside riplex-ui:
68
+
69
+ 1. Insert disc → riplex tries to classify, fails to find dvdcompare match.
70
+ 2. User clicks **Manual rip**, picks the two main titles, hits Rip.
71
+ 3. Done screen → click **Organize this folder**.
72
+ 4. TMDb search → "Wild Africa Tiny Giants" → pick `tmdb-1657586`.
73
+ 5. Assign t00 → pt1, t01 → pt2.
74
+ 6. Apply → files land in `Movies\Wild Africa - Tiny Giants (2016)\`.
75
+
76
+ No external tool, no manual `Move-Item`.
77
+
78
+ ## Tradeoffs
79
+
80
+ - **Pro**: covers any disc riplex can't classify, not just compilations.
81
+ - **Pro**: reuses existing rip + organize plumbing; mostly UI work.
82
+ - **Con**: more GUI surface (two screens, file-assignment widget).
83
+ - **Con**: risk of users defaulting to manual mode and bypassing the smart
84
+ classification we built. Mitigation: gate the manual-rip button behind a
85
+ "we couldn't classify this disc" trigger or an "Advanced" reveal, so it's
86
+ not a peer to the normal "Rip selected" button on every disc.
87
+
88
+ ## Out of scope
89
+
90
+ - Per-title TMDb matching (full compilation support).
91
+ - Multi-target organize where one folder of MKVs maps to N different TMDb
92
+ entries. Manual mode handles this by running the organize wizard once per
93
+ target with file selection.
94
+ - Saving manual-mode decisions for repeat use (cache).
95
+
96
+ ## Files likely involved
97
+
98
+ - `src/riplex_app/screens/selection.py` — add "Manual rip" button + branch.
99
+ - `src/riplex_app/screens/manual_rip.py` (new) — flat title list + ripper.
100
+ - `src/riplex_app/screens/welcome.py` — add "Organize existing files" card.
101
+ - `src/riplex_app/screens/manual_organize.py` (new) — folder picker → TMDb →
102
+ assignment → plan preview → apply.
103
+ - `src/riplex/organizer.py` — already has the path-building logic; may need
104
+ a small helper for the assignment data structure.
105
+ - `src/riplex_cli/commands/organize.py` — sanity-check the CLI still works
106
+ after any planner refactor.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: riplex
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
5
5
  License: MIT
6
6
  Requires-Python: >=3.11
@@ -25,6 +25,7 @@ docs/getting-started/installation.md
25
25
  docs/gui-guide/gui-walkthrough.md
26
26
  docs/reference/cli.md
27
27
  issues/debug-artifacts-consolidation.md
28
+ issues/manual-rip-and-organize-escape-hatch.md
28
29
  issues/orchestrate-dvdcompare-fallback.md
29
30
  issues/planned-features.md
30
31
  screenshots/0_Rip_Flow_BTTF.gif
@@ -114,6 +115,7 @@ tests/test_matcher.py
114
115
  tests/test_normalize.py
115
116
  tests/test_organizer.py
116
117
  tests/test_planner.py
118
+ tests/test_release_screen.py
117
119
  tests/test_rip_guide.py
118
120
  tests/test_scanner.py
119
121
  tests/test_snapshot.py
@@ -121,6 +123,7 @@ tests/test_splitter.py
121
123
  tests/test_tagger.py
122
124
  tests/test_ui.py
123
125
  tests/test_updater.py
126
+ tests/test_welcome_screen.py
124
127
  tests/fixtures/chernobyl_disc1.json
125
128
  tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt
126
129
  tests/fixtures/makemkvcon_list.txt
@@ -87,7 +87,7 @@ class DoneScreen:
87
87
 
88
88
  no_metadata_hint = ft.Text(
89
89
  "Ripped without metadata — use 'Organize Rips' from the home screen "
90
- "when TMDb is available to move these files into your Plex library.",
90
+ "when TMDb is available to move these files into your media library.",
91
91
  size=12,
92
92
  color=ft.Colors.ORANGE,
93
93
  ) if tmdb_match is None and len(successful) > 0 else ft.Container()
@@ -149,7 +149,7 @@ class MetadataScreen:
149
149
  [
150
150
  ft.Text("Metadata Lookup", size=24, weight=ft.FontWeight.BOLD),
151
151
  ft.Text(
152
- "TMDb is needed for canonical titles, years, and Plex folder "
152
+ "TMDb is needed for canonical titles, years, and media-library folder "
153
153
  "structure. Without it, you can still rip the disc and organize later.",
154
154
  size=13,
155
155
  color=ft.Colors.GREY_500,
@@ -17,7 +17,7 @@ class OrchestrateDoneScreen:
17
17
  """Shown after all discs in the orchestrate queue have been ripped.
18
18
 
19
19
  Displays a summary of all disc rips and offers to organize the
20
- ripped files into Plex-compatible folder structure.
20
+ ripped files into a Plex-compatible folder structure.
21
21
  """
22
22
 
23
23
  def __init__(self, app):
@@ -108,7 +108,7 @@ class OrchestrateDoneScreen:
108
108
  on_click=self._open_folder,
109
109
  )
110
110
  organize_btn = ft.ElevatedButton(
111
- "Organize into Plex",
111
+ "Organize into Library",
112
112
  icon=ft.Icons.DRIVE_FILE_MOVE,
113
113
  on_click=self._organize,
114
114
  style=ft.ButtonStyle(
@@ -136,8 +136,8 @@ class OrchestrateDoneScreen:
136
136
  ft.Column(disc_rows, spacing=6),
137
137
  ft.Container(height=10),
138
138
  ft.Text(
139
- "All selected discs have been ripped. Click 'Organize into Plex' "
140
- "to match files to metadata and move them into your Plex library.",
139
+ "All selected discs have been ripped. Click 'Organize into Library' "
140
+ "to match files to metadata and move them into your media library.",
141
141
  size=13,
142
142
  color=ft.Colors.GREY_500,
143
143
  ),
@@ -202,7 +202,7 @@ class OrchestrateDoneScreen:
202
202
  self._organize_status.value = f"Error: {exc}"
203
203
  self._organize_status.color = ft.Colors.RED
204
204
  e.control.disabled = False
205
- e.control.text = "Organize into Plex"
205
+ e.control.text = "Organize into Library"
206
206
  self.app.page.update()
207
207
 
208
208
  threading.Thread(target=_do_organize, daemon=True).start()
@@ -31,6 +31,17 @@ class ReleaseScreen:
31
31
  return "disc_overview"
32
32
  return "selection"
33
33
 
34
+ def _skip_next_screen(self) -> str:
35
+ """Screen to use when continuing without dvdcompare data."""
36
+ workflow = self.app.state.get("workflow")
37
+ if workflow == "orchestrate":
38
+ self.app.state["_orchestrate_disc_number"] = 1
39
+ self.app.state["disc_queue"] = [1]
40
+ self.app.state["current_disc_idx"] = 0
41
+ self.app.state["all_rip_results"] = {}
42
+ return "selection"
43
+ return self._next_screen
44
+
34
45
  def _current_search_title(self) -> str:
35
46
  """Title to use for dvdcompare lookup (user override > TMDb > raw title)."""
36
47
  override = self.app.state.get("dvdcompare_title_override")
@@ -499,7 +510,7 @@ class ReleaseScreen:
499
510
  def _skip(self, e):
500
511
  """Proceed without dvdcompare data."""
501
512
  self.app.state["dvdcompare_discs"] = []
502
- self.app.navigate(self._next_screen)
513
+ self.app.navigate(self._skip_next_screen())
503
514
 
504
515
  def _use_release(self, release):
505
516
  """Convert a dvdcompare release to PlannedDiscs and navigate."""
@@ -16,13 +16,30 @@ from riplex.updater import check_for_update, get_current_version
16
16
  log = logging.getLogger(__name__)
17
17
 
18
18
 
19
+ def has_complete_config(config: dict) -> bool:
20
+ """Return True when required GUI setup values are present."""
21
+ return bool(
22
+ config
23
+ and config.get("tmdb_api_key")
24
+ and config.get("output_root")
25
+ and config.get("rip_output")
26
+ )
27
+
28
+
29
+ def should_show_setup(config: dict, state: dict) -> bool:
30
+ """Return True when the welcome screen should show setup fields."""
31
+ return not has_complete_config(config) or bool(state.get("_show_setup"))
32
+
33
+
19
34
  class WelcomeScreen:
20
35
  def __init__(self, app):
21
36
  self.app = app
22
37
 
23
38
  def build(self) -> ft.Control:
24
39
  config = load_config()
25
- has_config = bool(config and config.get("tmdb_api_key"))
40
+ has_api_key = bool(config and config.get("tmdb_api_key"))
41
+ has_config = has_complete_config(config)
42
+ show_setup = should_show_setup(config, self.app.state)
26
43
  has_makemkv = find_makemkvcon() is not None
27
44
  has_ffprobe = find_ffprobe() is not None
28
45
  from riplex.splitter import find_mkvmerge
@@ -31,7 +48,7 @@ class WelcomeScreen:
31
48
  # Status indicators
32
49
  checks = [
33
50
  ("Config file", has_config),
34
- ("TMDb API key", has_config),
51
+ ("TMDb API key", has_api_key),
35
52
  ("makemkvcon", has_makemkv),
36
53
  ("ffprobe", has_ffprobe),
37
54
  ("mkvmerge", has_mkvmerge),
@@ -102,7 +119,7 @@ class WelcomeScreen:
102
119
  can_rip = all(ok for _, ok in checks)
103
120
  can_organize = has_config and has_ffprobe
104
121
 
105
- # Setup fields (shown if config missing)
122
+ # Setup fields (shown if config missing or user chooses to edit)
106
123
  self.api_key_field = ft.TextField(
107
124
  label="TMDb API key",
108
125
  value=config.get("tmdb_api_key", ""),
@@ -111,7 +128,7 @@ class WelcomeScreen:
111
128
  expand=True,
112
129
  )
113
130
  self.output_root_field = ft.TextField(
114
- label="Plex library root",
131
+ label="Media library root",
115
132
  value=config.get("output_root", ""),
116
133
  expand=True,
117
134
  )
@@ -155,8 +172,9 @@ class WelcomeScreen:
155
172
  ft.Container(height=8),
156
173
  _make_browse_row(self.output_root_field),
157
174
  ft.Text(
158
- "Your Plex media library root. Organized files will be placed "
159
- "into Movies/ and TV Shows/ subfolders here.",
175
+ "Your media library root. Organized files will be placed "
176
+ "into Movies/ and TV Shows/ subfolders here using "
177
+ "Plex-compatible naming.",
160
178
  size=11,
161
179
  color=ft.Colors.GREY_600,
162
180
  ),
@@ -177,10 +195,20 @@ class WelcomeScreen:
177
195
  color=ft.Colors.GREY_600,
178
196
  ),
179
197
  ft.Container(height=8),
180
- ft.ElevatedButton("Save Config", on_click=self._save_config),
198
+ ft.Row(
199
+ [
200
+ ft.ElevatedButton("Save Config", on_click=self._save_config),
201
+ ft.TextButton(
202
+ "Cancel",
203
+ on_click=self._hide_setup,
204
+ visible=has_config,
205
+ ),
206
+ ],
207
+ spacing=8,
208
+ ),
181
209
  ],
182
210
  spacing=4,
183
- visible=not has_config,
211
+ visible=show_setup,
184
212
  )
185
213
 
186
214
  # Workflow buttons
@@ -190,7 +218,7 @@ class WelcomeScreen:
190
218
  on_click=self._start_rip,
191
219
  disabled=not can_rip,
192
220
  style=ft.ButtonStyle(padding=ft.Padding(left=30, top=15, right=30, bottom=15)),
193
- tooltip="Detect a disc, look up metadata, rip selected titles, and organize into Plex.",
221
+ tooltip="Detect a disc, look up metadata, rip selected titles, and organize into your media library.",
194
222
  )
195
223
  organize_button = ft.ElevatedButton(
196
224
  "Organize Rips",
@@ -228,7 +256,7 @@ class WelcomeScreen:
228
256
  ),
229
257
  self.update_banner,
230
258
  ft.Text(
231
- "Rip physical discs and organize into Plex-compatible libraries.",
259
+ "Rip physical discs and organize them into a media library with Plex-compatible naming.",
232
260
  size=14,
233
261
  color=ft.Colors.GREY_400,
234
262
  ),
@@ -244,6 +272,16 @@ class WelcomeScreen:
244
272
  ft.Column(status_rows, spacing=4),
245
273
  install_section,
246
274
  ft.Container(height=10),
275
+ ft.Row(
276
+ [
277
+ ft.OutlinedButton(
278
+ "Edit Settings",
279
+ icon=ft.Icons.SETTINGS,
280
+ on_click=self._show_setup,
281
+ visible=has_config and not show_setup,
282
+ ),
283
+ ],
284
+ ),
247
285
  setup_section,
248
286
  ft.Container(expand=True),
249
287
  ft.Text("What would you like to do?", size=16, weight=ft.FontWeight.BOLD),
@@ -645,6 +683,17 @@ class WelcomeScreen:
645
683
  )
646
684
 
647
685
  # Refresh the screen
686
+ self.app.state.pop("_show_setup", None)
687
+ self.app.navigate("welcome")
688
+
689
+ def _show_setup(self, e):
690
+ """Reveal config fields for editing after first-run setup."""
691
+ self.app.state["_show_setup"] = True
692
+ self.app.navigate("welcome")
693
+
694
+ def _hide_setup(self, e):
695
+ """Hide config fields without saving changes."""
696
+ self.app.state.pop("_show_setup", None)
648
697
  self.app.navigate("welcome")
649
698
 
650
699
  def _start_rip(self, e):
@@ -0,0 +1,40 @@
1
+ from riplex_app.screens.release import ReleaseScreen
2
+
3
+
4
+ class _App:
5
+ def __init__(self, state):
6
+ self.state = state
7
+ self.navigated_to = None
8
+
9
+ def navigate(self, screen):
10
+ self.navigated_to = screen
11
+
12
+
13
+ class TestReleaseSkipRouting:
14
+ def test_rip_workflow_continues_to_selection(self):
15
+ app = _App({"workflow": "rip"})
16
+ screen = ReleaseScreen(app)
17
+
18
+ screen._skip(None)
19
+
20
+ assert app.state["dvdcompare_discs"] == []
21
+ assert app.navigated_to == "selection"
22
+
23
+ def test_orchestrate_with_release_uses_disc_overview(self):
24
+ app = _App({"workflow": "orchestrate"})
25
+ screen = ReleaseScreen(app)
26
+
27
+ assert screen._next_screen == "disc_overview"
28
+
29
+ def test_orchestrate_without_dvdcompare_starts_single_disc_selection(self):
30
+ app = _App({"workflow": "orchestrate"})
31
+ screen = ReleaseScreen(app)
32
+
33
+ screen._skip(None)
34
+
35
+ assert app.state["dvdcompare_discs"] == []
36
+ assert app.state["_orchestrate_disc_number"] == 1
37
+ assert app.state["disc_queue"] == [1]
38
+ assert app.state["current_disc_idx"] == 0
39
+ assert app.state["all_rip_results"] == {}
40
+ assert app.navigated_to == "selection"
@@ -0,0 +1,42 @@
1
+ from riplex_app.screens.welcome import has_complete_config, should_show_setup
2
+
3
+
4
+ class TestHasCompleteConfig:
5
+ def test_requires_api_key_output_root_and_rip_output(self):
6
+ assert has_complete_config({}) is False
7
+ assert has_complete_config({"tmdb_api_key": "key"}) is False
8
+ assert has_complete_config({"tmdb_api_key": "key", "output_root": "E:/Media"}) is False
9
+ assert has_complete_config({
10
+ "tmdb_api_key": "key",
11
+ "output_root": "E:/Media",
12
+ "rip_output": "E:/Media/_MakeMKV",
13
+ }) is True
14
+
15
+ def test_archive_root_is_optional(self):
16
+ assert has_complete_config({
17
+ "tmdb_api_key": "key",
18
+ "output_root": "E:/Media",
19
+ "rip_output": "E:/Media/_MakeMKV",
20
+ "archive_root": "",
21
+ }) is True
22
+
23
+
24
+ class TestShouldShowSetup:
25
+ def test_shows_setup_for_incomplete_config(self):
26
+ assert should_show_setup({"tmdb_api_key": "key"}, {}) is True
27
+
28
+ def test_hides_setup_for_complete_config(self):
29
+ config = {
30
+ "tmdb_api_key": "key",
31
+ "output_root": "E:/Media",
32
+ "rip_output": "E:/Media/_MakeMKV",
33
+ }
34
+ assert should_show_setup(config, {}) is False
35
+
36
+ def test_can_force_show_setup_for_editing(self):
37
+ config = {
38
+ "tmdb_api_key": "key",
39
+ "output_root": "E:/Media",
40
+ "rip_output": "E:/Media/_MakeMKV",
41
+ }
42
+ assert should_show_setup(config, {"_show_setup": True}) is True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes