riplex 0.9.9__tar.gz → 1.0.2__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 (241) hide show
  1. {riplex-0.9.9 → riplex-1.0.2}/.github/agents/riplex.agent.md +12 -1
  2. {riplex-0.9.9 → riplex-1.0.2}/.github/copilot-instructions.md +28 -4
  3. {riplex-0.9.9 → riplex-1.0.2}/.github/workflows/release.yml +10 -0
  4. {riplex-0.9.9 → riplex-1.0.2}/.github/workflows/update-contributors.yml +2 -2
  5. {riplex-0.9.9/src/riplex.egg-info → riplex-1.0.2}/PKG-INFO +3 -3
  6. {riplex-0.9.9 → riplex-1.0.2}/README.md +1 -1
  7. riplex-1.0.2/RELEASE_NOTES_v1.0.0.md +139 -0
  8. {riplex-0.9.9 → riplex-1.0.2}/docs/architecture.md +3 -0
  9. {riplex-0.9.9 → riplex-1.0.2}/docs/changelog.md +92 -0
  10. {riplex-0.9.9 → riplex-1.0.2}/docs/cli-guide/orchestrate.md +2 -0
  11. {riplex-0.9.9 → riplex-1.0.2}/docs/cli-guide/organize.md +14 -2
  12. {riplex-0.9.9 → riplex-1.0.2}/docs/cli-guide/workflow.md +3 -1
  13. riplex-1.0.2/docs/development/testing.md +182 -0
  14. {riplex-0.9.9 → riplex-1.0.2}/docs/getting-started/configuration.md +2 -0
  15. {riplex-0.9.9 → riplex-1.0.2}/docs/getting-started/installation.md +13 -1
  16. {riplex-0.9.9 → riplex-1.0.2}/docs/gui-guide/gui-walkthrough.md +10 -0
  17. {riplex-0.9.9 → riplex-1.0.2}/docs/index.md +5 -0
  18. {riplex-0.9.9 → riplex-1.0.2}/docs/troubleshooting.md +12 -0
  19. riplex-1.0.2/issues/one-season-at-a-time.md +95 -0
  20. riplex-1.0.2/issues/organize-preview-match-override.md +87 -0
  21. {riplex-0.9.9 → riplex-1.0.2}/mkdocs.yml +2 -0
  22. {riplex-0.9.9 → riplex-1.0.2}/pyproject.toml +4 -1
  23. riplex-1.0.2/scripts/gen_gui_fixtures.py +393 -0
  24. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/cache.py +51 -0
  25. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/config.py +9 -0
  26. riplex-1.0.2/src/riplex/disc/analysis.py +1588 -0
  27. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/disc/makemkv.py +4 -1
  28. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/disc/provider.py +251 -21
  29. riplex-1.0.2/src/riplex/lookup.py +512 -0
  30. riplex-1.0.2/src/riplex/manifest.py +791 -0
  31. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/matcher.py +106 -0
  32. riplex-1.0.2/src/riplex/metadata/autosearch.py +130 -0
  33. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/metadata/planner.py +29 -7
  34. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/metadata/provider.py +1 -0
  35. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/metadata/sources/tmdb.py +5 -1
  36. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/models.py +79 -0
  37. riplex-1.0.2/src/riplex/organize_by_group.py +364 -0
  38. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/organizer.py +199 -7
  39. riplex-1.0.2/src/riplex/resume.py +337 -0
  40. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/ui.py +140 -4
  41. riplex-1.0.2/src/riplex/updater.py +421 -0
  42. {riplex-0.9.9 → riplex-1.0.2/src/riplex.egg-info}/PKG-INFO +3 -3
  43. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/SOURCES.txt +80 -1
  44. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/requires.txt +1 -1
  45. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/scm_file_list.json +189 -110
  46. riplex-1.0.2/src/riplex.egg-info/scm_version.json +8 -0
  47. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/bug_report.py +5 -1
  48. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/main.py +153 -1
  49. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/disc_detection.py +178 -142
  50. riplex-1.0.2/src/riplex_app/screens/disc_overview.py +1234 -0
  51. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/disc_swap.py +57 -10
  52. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/done.py +7 -1
  53. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/folder_picker.py +221 -32
  54. riplex-1.0.2/src/riplex_app/screens/metadata.py +535 -0
  55. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/orchestrate_done.py +114 -33
  56. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/organize_done.py +7 -2
  57. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/organize_preview.py +166 -44
  58. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/progress.py +85 -9
  59. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/release.py +288 -11
  60. riplex-1.0.2/src/riplex_app/screens/season_select.py +359 -0
  61. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/selection.py +195 -20
  62. riplex-1.0.2/src/riplex_app/screens/update.py +207 -0
  63. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/welcome.py +1 -0
  64. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/lookup.py +18 -0
  65. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/orchestrate.py +185 -76
  66. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/organize.py +274 -65
  67. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/rip.py +59 -3
  68. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/main.py +17 -1
  69. riplex-1.0.2/tests/conftest.py +36 -0
  70. riplex-1.0.2/tests/fixtures/gui/scenarios/2001-a-space-odyssey-1968.json +278 -0
  71. riplex-1.0.2/tests/fixtures/gui/scenarios/akira-1988.json +1351 -0
  72. riplex-1.0.2/tests/fixtures/gui/scenarios/back-to-the-future-1985.json +713 -0
  73. riplex-1.0.2/tests/fixtures/gui/scenarios/back-to-the-future-part-ii-1989.json +764 -0
  74. riplex-1.0.2/tests/fixtures/gui/scenarios/back-to-the-future-part-iii-1990.json +1268 -0
  75. riplex-1.0.2/tests/fixtures/gui/scenarios/blade-1998.json +120 -0
  76. riplex-1.0.2/tests/fixtures/gui/scenarios/blade-runner-2049-2017.json +160 -0
  77. riplex-1.0.2/tests/fixtures/gui/scenarios/chernobyl-2019.json +370 -0
  78. riplex-1.0.2/tests/fixtures/gui/scenarios/d-day-normandy-1944-2014.json +59 -0
  79. riplex-1.0.2/tests/fixtures/gui/scenarios/final-destination-bloodlines-2025.json +98 -0
  80. riplex-1.0.2/tests/fixtures/gui/scenarios/flight-of-the-butterflies-2012.json +277 -0
  81. riplex-1.0.2/tests/fixtures/gui/scenarios/inception-2010.json +449 -0
  82. riplex-1.0.2/tests/fixtures/gui/scenarios/independence-day-1996.json +481 -0
  83. riplex-1.0.2/tests/fixtures/gui/scenarios/journey-to-space-2015.json +272 -0
  84. riplex-1.0.2/tests/fixtures/gui/scenarios/journey-to-the-south-pacific-2013.json +59 -0
  85. riplex-1.0.2/tests/fixtures/gui/scenarios/jurassic-park-1993.json +567 -0
  86. riplex-1.0.2/tests/fixtures/gui/scenarios/king-kong-2005.json +1384 -0
  87. riplex-1.0.2/tests/fixtures/gui/scenarios/mad-max-fury-road-2015.json +125 -0
  88. riplex-1.0.2/tests/fixtures/gui/scenarios/march-of-the-penguins-2005.json +147 -0
  89. riplex-1.0.2/tests/fixtures/gui/scenarios/national-parks-adventure-2016.json +59 -0
  90. riplex-1.0.2/tests/fixtures/gui/scenarios/oceans-our-blue-planet-2018.json +156 -0
  91. riplex-1.0.2/tests/fixtures/gui/scenarios/psych-season-01.json +825 -0
  92. riplex-1.0.2/tests/fixtures/gui/scenarios/psych-season-03.json +1023 -0
  93. riplex-1.0.2/tests/fixtures/gui/scenarios/psych-season-04.json +971 -0
  94. riplex-1.0.2/tests/fixtures/gui/scenarios/psych-the-movie-2017.json +2850 -0
  95. riplex-1.0.2/tests/fixtures/gui/scenarios/space-station-3d-2002.json +59 -0
  96. riplex-1.0.2/tests/fixtures/gui/scenarios/the-godfather-1972.json +1525 -0
  97. riplex-1.0.2/tests/fixtures/gui/scenarios/the-last-reef-cities-beneath-the-sea-2012.json +350 -0
  98. riplex-1.0.2/tests/fixtures/gui/scenarios/the-matrix-1999.json +944 -0
  99. riplex-1.0.2/tests/fixtures/gui/scenarios/the-matrix-reloaded-2003.json +500 -0
  100. riplex-1.0.2/tests/fixtures/gui/scenarios/the-mummy-1999.json +318 -0
  101. riplex-1.0.2/tests/fixtures/gui/scenarios/the-mummy-returns-2001.json +273 -0
  102. riplex-1.0.2/tests/fixtures/gui/scenarios/the-mummy-tomb-of-the-dragon-emperor-2008.json +59 -0
  103. riplex-1.0.2/tests/fixtures/gui/scenarios/the-patriot-2000.json +148 -0
  104. riplex-1.0.2/tests/fixtures/gui/scenarios/the-thing-1982.json +148 -0
  105. riplex-1.0.2/tests/fixtures/gui/scenarios/wonders-of-the-arctic-2014.json +219 -0
  106. riplex-1.0.2/tests/integration/__init__.py +6 -0
  107. riplex-1.0.2/tests/integration/conftest.py +13 -0
  108. riplex-1.0.2/tests/integration/test_cli_lookup.py +90 -0
  109. riplex-1.0.2/tests/integration/test_cli_organize.py +92 -0
  110. riplex-1.0.2/tests/integration/test_cli_parser.py +59 -0
  111. riplex-1.0.2/tests/integration/test_fixture_scenarios.py +73 -0
  112. riplex-1.0.2/tests/integration/test_flow_errors.py +79 -0
  113. riplex-1.0.2/tests/integration/test_flow_media_types.py +123 -0
  114. riplex-1.0.2/tests/integration/test_flow_movie_orchestrate.py +53 -0
  115. riplex-1.0.2/tests/integration/test_flow_organize.py +65 -0
  116. riplex-1.0.2/tests/integration/test_flow_resume_prefill.py +67 -0
  117. riplex-1.0.2/tests/integration/test_flow_tv_orchestrate.py +52 -0
  118. riplex-1.0.2/tests/integration/test_flow_welcome.py +42 -0
  119. riplex-1.0.2/tests/integration/test_screen_build_smoke.py +130 -0
  120. riplex-1.0.2/tests/support/__init__.py +16 -0
  121. riplex-1.0.2/tests/support/cli.py +92 -0
  122. riplex-1.0.2/tests/support/driver.py +99 -0
  123. riplex-1.0.2/tests/support/fake_page.py +155 -0
  124. riplex-1.0.2/tests/support/fixtures.py +417 -0
  125. riplex-1.0.2/tests/support/provider_mocks.py +301 -0
  126. riplex-1.0.2/tests/support/sync_runtime.py +93 -0
  127. {riplex-0.9.9 → riplex-1.0.2}/tests/test_app_main.py +67 -2
  128. riplex-1.0.2/tests/test_autosearch.py +155 -0
  129. {riplex-0.9.9 → riplex-1.0.2}/tests/test_cache.py +50 -0
  130. {riplex-0.9.9 → riplex-1.0.2}/tests/test_cli_utils.py +21 -0
  131. riplex-1.0.2/tests/test_disc_analysis.py +2093 -0
  132. riplex-1.0.2/tests/test_disc_detection_screen.py +287 -0
  133. riplex-1.0.2/tests/test_disc_provider.py +662 -0
  134. riplex-1.0.2/tests/test_folder_picker.py +75 -0
  135. riplex-1.0.2/tests/test_lookup.py +468 -0
  136. riplex-1.0.2/tests/test_lookup_resolve_groups.py +122 -0
  137. riplex-1.0.2/tests/test_manifest_prefill_ids.py +160 -0
  138. riplex-1.0.2/tests/test_manifest_session.py +660 -0
  139. {riplex-0.9.9 → riplex-1.0.2}/tests/test_matcher.py +228 -0
  140. {riplex-0.9.9 → riplex-1.0.2}/tests/test_metadata_screen.py +3 -2
  141. riplex-1.0.2/tests/test_organize_by_group.py +370 -0
  142. riplex-1.0.2/tests/test_organize_cli.py +303 -0
  143. riplex-1.0.2/tests/test_organize_session_fanout.py +224 -0
  144. {riplex-0.9.9 → riplex-1.0.2}/tests/test_organizer.py +458 -0
  145. {riplex-0.9.9 → riplex-1.0.2}/tests/test_planner.py +69 -0
  146. riplex-1.0.2/tests/test_progress.py +223 -0
  147. riplex-1.0.2/tests/test_release_screen.py +273 -0
  148. riplex-1.0.2/tests/test_resume.py +492 -0
  149. riplex-1.0.2/tests/test_season_select_screen.py +509 -0
  150. riplex-1.0.2/tests/test_ui.py +374 -0
  151. riplex-1.0.2/tests/test_updater_selfupdate.py +266 -0
  152. riplex-0.9.9/src/riplex/disc/analysis.py +0 -673
  153. riplex-0.9.9/src/riplex/lookup.py +0 -90
  154. riplex-0.9.9/src/riplex/manifest.py +0 -338
  155. riplex-0.9.9/src/riplex/updater.py +0 -173
  156. riplex-0.9.9/src/riplex.egg-info/scm_version.json +0 -8
  157. riplex-0.9.9/src/riplex_app/screens/disc_overview.py +0 -265
  158. riplex-0.9.9/src/riplex_app/screens/metadata.py +0 -282
  159. riplex-0.9.9/src/riplex_app/screens/update.py +0 -127
  160. riplex-0.9.9/tests/test_disc_analysis.py +0 -602
  161. riplex-0.9.9/tests/test_disc_detection_screen.py +0 -46
  162. riplex-0.9.9/tests/test_disc_provider.py +0 -298
  163. riplex-0.9.9/tests/test_lookup.py +0 -48
  164. riplex-0.9.9/tests/test_release_screen.py +0 -72
  165. riplex-0.9.9/tests/test_ui.py +0 -163
  166. {riplex-0.9.9 → riplex-1.0.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  167. {riplex-0.9.9 → riplex-1.0.2}/.github/ISSUE_TEMPLATE/crash_report.yml +0 -0
  168. {riplex-0.9.9 → riplex-1.0.2}/.github/contributors-overrides.json +0 -0
  169. {riplex-0.9.9 → riplex-1.0.2}/.github/workflows/publish.yml +0 -0
  170. {riplex-0.9.9 → riplex-1.0.2}/.gitignore +0 -0
  171. {riplex-0.9.9 → riplex-1.0.2}/.vscode/settings.json +0 -0
  172. {riplex-0.9.9 → riplex-1.0.2}/CONTRIBUTORS.md +0 -0
  173. {riplex-0.9.9 → riplex-1.0.2}/LICENSE +0 -0
  174. {riplex-0.9.9 → riplex-1.0.2}/docs/cli-guide/lookup.md +0 -0
  175. {riplex-0.9.9 → riplex-1.0.2}/docs/naming-rules.md +0 -0
  176. {riplex-0.9.9 → riplex-1.0.2}/docs/reference/cli.md +0 -0
  177. {riplex-0.9.9 → riplex-1.0.2}/issues/debug-artifacts-consolidation.md +0 -0
  178. {riplex-0.9.9 → riplex-1.0.2}/issues/manual-rip-and-organize-escape-hatch.md +0 -0
  179. {riplex-0.9.9 → riplex-1.0.2}/issues/orchestrate-dvdcompare-fallback.md +0 -0
  180. {riplex-0.9.9 → riplex-1.0.2}/issues/planned-features.md +0 -0
  181. {riplex-0.9.9 → riplex-1.0.2}/screenshots/0_Rip_Flow_BTTF.gif +0 -0
  182. {riplex-0.9.9 → riplex-1.0.2}/screenshots/1_Welcome_Screen.png +0 -0
  183. {riplex-0.9.9 → riplex-1.0.2}/screenshots/2_Disc_Detection_BTTF.png +0 -0
  184. {riplex-0.9.9 → riplex-1.0.2}/screenshots/3_Metadata_Lookup_BTTF.png +0 -0
  185. {riplex-0.9.9 → riplex-1.0.2}/screenshots/4_Disc_Release_BTTF.png +0 -0
  186. {riplex-0.9.9 → riplex-1.0.2}/screenshots/5_Multi_Disc_Overview_BTTF.png +0 -0
  187. {riplex-0.9.9 → riplex-1.0.2}/screenshots/5_Select_Title_to_RIP_BTTF.png +0 -0
  188. {riplex-0.9.9 → riplex-1.0.2}/scripts/__init__.py +0 -0
  189. {riplex-0.9.9 → riplex-1.0.2}/scripts/update_contributors.py +0 -0
  190. {riplex-0.9.9 → riplex-1.0.2}/setup.cfg +0 -0
  191. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/__init__.py +0 -0
  192. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/dedup.py +0 -0
  193. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/detect.py +0 -0
  194. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/disc/__init__.py +0 -0
  195. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/formatter.py +0 -0
  196. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/metadata/__init__.py +0 -0
  197. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/metadata/sources/__init__.py +0 -0
  198. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/normalize.py +0 -0
  199. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/scanner.py +0 -0
  200. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/snapshot.py +0 -0
  201. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/splitter.py +0 -0
  202. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/tagger.py +0 -0
  203. {riplex-0.9.9 → riplex-1.0.2}/src/riplex/title.py +0 -0
  204. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/dependency_links.txt +0 -0
  205. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/entry_points.txt +0 -0
  206. {riplex-0.9.9 → riplex-1.0.2}/src/riplex.egg-info/top_level.txt +0 -0
  207. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/__init__.py +0 -0
  208. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/crash_dump.py +0 -0
  209. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/keep_awake.py +0 -0
  210. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_app/screens/__init__.py +0 -0
  211. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/__init__.py +0 -0
  212. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/__init__.py +0 -0
  213. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/commands/setup.py +0 -0
  214. {riplex-0.9.9 → riplex-1.0.2}/src/riplex_cli/formatting.py +0 -0
  215. {riplex-0.9.9 → riplex-1.0.2}/tests/__init__.py +0 -0
  216. {riplex-0.9.9 → riplex-1.0.2}/tests/fixtures/chernobyl_disc1.json +0 -0
  217. {riplex-0.9.9 → riplex-1.0.2}/tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt +0 -0
  218. {riplex-0.9.9 → riplex-1.0.2}/tests/fixtures/makemkvcon_list.txt +0 -0
  219. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/Batman Begins.snapshot.json +0 -0
  220. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/Blade Runner (Blu-ray 4k).snapshot.json +0 -0
  221. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/Blade Runner The Final Cut.snapshot.json +0 -0
  222. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/Seven Worlds One Planet.snapshot.json +0 -0
  223. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/The Dark Knight Rises.snapshot.json +0 -0
  224. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/The Dark Knight.snapshot.json +0 -0
  225. {riplex-0.9.9 → riplex-1.0.2}/tests/snapshots/Waterworld.snapshot.json +0 -0
  226. {riplex-0.9.9 → riplex-1.0.2}/tests/test_config.py +0 -0
  227. {riplex-0.9.9 → riplex-1.0.2}/tests/test_dedup.py +0 -0
  228. {riplex-0.9.9 → riplex-1.0.2}/tests/test_detect.py +0 -0
  229. {riplex-0.9.9 → riplex-1.0.2}/tests/test_disc_fixtures.py +0 -0
  230. {riplex-0.9.9 → riplex-1.0.2}/tests/test_formatter.py +0 -0
  231. {riplex-0.9.9 → riplex-1.0.2}/tests/test_makemkv.py +0 -0
  232. {riplex-0.9.9 → riplex-1.0.2}/tests/test_normalize.py +0 -0
  233. {riplex-0.9.9 → riplex-1.0.2}/tests/test_rip_guide.py +0 -0
  234. {riplex-0.9.9 → riplex-1.0.2}/tests/test_scanner.py +0 -0
  235. {riplex-0.9.9 → riplex-1.0.2}/tests/test_snapshot.py +0 -0
  236. {riplex-0.9.9 → riplex-1.0.2}/tests/test_splitter.py +0 -0
  237. {riplex-0.9.9 → riplex-1.0.2}/tests/test_tagger.py +0 -0
  238. {riplex-0.9.9 → riplex-1.0.2}/tests/test_tmdb_provider.py +0 -0
  239. {riplex-0.9.9 → riplex-1.0.2}/tests/test_update_contributors.py +0 -0
  240. {riplex-0.9.9 → riplex-1.0.2}/tests/test_updater.py +0 -0
  241. {riplex-0.9.9 → riplex-1.0.2}/tests/test_welcome_screen.py +0 -0
@@ -151,7 +151,18 @@ Do NOT use `py -m riplex` (errors — riplex is a library package). Do NOT use `
151
151
 
152
152
  ## Debugging the GUI
153
153
 
154
- The GUI writes detailed logs to `riplex_app.log` in the working directory (project root when launched from the repo). This log includes screen navigation, disc analysis details, title classification decisions, and rip progress. Check this file first when debugging GUI behavior do NOT try to capture terminal output (Flet's `2>&1` redirect breaks its Flutter-Python IPC).
154
+ **When the user reports a bug they can reproduce locally, ALWAYS check the debug log first** before reading code, before guessing at causes. The log will usually tell you exactly which code path ran and what state it saw. Guessing at fixes without checking the log wastes time and often lands the wrong fix.
155
+
156
+ The GUI writes detailed logs including screen navigation, dvdcompare/TMDb lookups, disc analysis, title classification decisions, group routing, autofill results, and rip progress. Log locations (per `platformdirs`):
157
+
158
+ - **Windows**: `%LOCALAPPDATA%\riplex\riplex\Logs\riplex_app.log`
159
+ (typically `C:\Users\<user>\AppData\Local\riplex\riplex\Logs\riplex_app.log`)
160
+ - **macOS**: `~/Library/Logs/riplex/riplex_app.log`
161
+ - **Linux**: `~/.local/state/riplex/log/riplex_app.log` (or `$XDG_STATE_HOME/riplex/log/`)
162
+
163
+ The log is truncated / rotated on each GUI launch, so grab it before the user restarts the app. Use `Select-String` (PowerShell) or `grep` for keywords tied to the buggy behavior — `Season`, `dvdcompare_film`, `group`, `autofill`, `classify`, etc.
164
+
165
+ Do NOT try to capture terminal output with `2>&1` — Flet's Flutter-Python IPC breaks when stderr is redirected.
155
166
 
156
167
  ## Testing
157
168
 
@@ -1,5 +1,24 @@
1
1
  # Copilot Instructions for riplex
2
2
 
3
+ ## Shared flow logic (CLI/GUI parity)
4
+
5
+ The CLI (`src/riplex_cli/`) and the GUI (`src/riplex_app/`) must not carry
6
+ duplicated multi-step orchestration. Any flow-level logic — anything that
7
+ does more than "render this" or "prompt for that" — lives in `src/riplex/`
8
+ as a plain function that takes inputs and returns a dataclass. Both surfaces
9
+ then become thin callers: the CLI does `prompt_*` / `print(...)`, the GUI
10
+ does `ft.Control` / `state[...] = ...`.
11
+
12
+ Reference example: `src/riplex/resume.py` centralizes the resume-from-session
13
+ adapter. `_fetch_dvdcompare_for_resume` in
14
+ `src/riplex_app/screens/disc_detection.py` and the resume short-circuit in
15
+ `src/riplex_cli/commands/orchestrate.py` are both callers of that one
16
+ function; adding a new field to the resumed lookup only touches the
17
+ adapter (and its tests), never both surfaces.
18
+
19
+ When adding a new feature that both surfaces need, default to this
20
+ pattern before writing any UI code.
21
+
3
22
  ## Documentation changelog
4
23
 
5
24
  When any file under `docs/` is added, modified, or removed, update `docs/changelog.md` with a dated entry describing the change. Follow the [Keep a Changelog](https://keepachangelog.com/) format with sections like Added, Changed, Removed, or Fixed under a date heading.
@@ -108,10 +127,15 @@ Run tests with `pytest` (or `python -m pytest`) from the project root with the v
108
127
 
109
128
  ## Debugging the GUI
110
129
 
111
- The GUI writes detailed logs to `riplex_app.log` in the working directory
112
- (project root when launched from the repo). Check this file first when
113
- debugging GUI behavior. Do NOT try to capture the GUI's terminal output with
114
- a `2>&1` redirect — it breaks Flet's Flutter-Python IPC.
130
+ **When the user reports a bug they can reproduce locally, ALWAYS check the debug log first** — before reading code, before guessing. The log usually tells you exactly which code path ran and what state it saw.
131
+
132
+ Log locations (per `platformdirs`):
133
+
134
+ - **Windows**: `%LOCALAPPDATA%\riplex\riplex\Logs\riplex_app.log`
135
+ - **macOS**: `~/Library/Logs/riplex/riplex_app.log`
136
+ - **Linux**: `~/.local/state/riplex/log/riplex_app.log`
137
+
138
+ The log is rotated on each GUI launch, so grab it before the user restarts the app. Do NOT try to capture terminal output with `2>&1` — it breaks Flet's Flutter-Python IPC.
115
139
 
116
140
  ## Releases and packaging
117
141
 
@@ -192,6 +192,12 @@ jobs:
192
192
  cp artifacts/riplex-windows/riplex-ui.exe release/riplex-ui-windows.exe
193
193
  cp artifacts/riplex-macos/riplex-macos release/
194
194
  cp artifacts/riplex-macos/riplex-ui-macos.zip release/
195
+ # Publish SHA-256 checksums so the app can verify a download before
196
+ # applying an in-place update (see riplex.updater.stage_update).
197
+ cd release
198
+ for f in riplex-windows.exe riplex-ui-windows.exe riplex-macos riplex-ui-macos.zip; do
199
+ sha256sum "$f" > "$f.sha256"
200
+ done
195
201
 
196
202
  - name: Create GitHub Release
197
203
  uses: softprops/action-gh-release@v2
@@ -204,6 +210,10 @@ jobs:
204
210
  body: ${{ steps.tag_notes.outputs.body }}
205
211
  files: |
206
212
  release/riplex-windows.exe
213
+ release/riplex-windows.exe.sha256
207
214
  release/riplex-ui-windows.exe
215
+ release/riplex-ui-windows.exe.sha256
208
216
  release/riplex-macos
217
+ release/riplex-macos.sha256
209
218
  release/riplex-ui-macos.zip
219
+ release/riplex-ui-macos.zip.sha256
@@ -35,8 +35,8 @@ jobs:
35
35
  - name: Create pull request
36
36
  uses: peter-evans/create-pull-request@v6
37
37
  with:
38
- commit-message: chore: refresh CONTRIBUTORS.md
39
- title: chore: refresh CONTRIBUTORS.md
38
+ commit-message: "chore: refresh CONTRIBUTORS.md"
39
+ title: "chore: refresh CONTRIBUTORS.md"
40
40
  body: |
41
41
  Updates the generated Bug Bashers table in CONTRIBUTORS.md from closed issue reports.
42
42
  branch: chore/update-contributors
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: riplex
3
- Version: 0.9.9
3
+ Version: 1.0.2
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
@@ -8,7 +8,7 @@ Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: certifi>=2024.2.2
10
10
  Requires-Dist: httpx>=0.27
11
- Requires-Dist: dvdcompare-scraper>=0.1.16
11
+ Requires-Dist: dvdcompare-scraper>=0.1.18
12
12
  Requires-Dist: platformdirs>=4.0
13
13
  Provides-Extra: dev
14
14
  Requires-Dist: pytest>=8.0; extra == "dev"
@@ -67,7 +67,7 @@ More screenshots:
67
67
  [disc overview](screenshots/5_Multi_Disc_Overview_BTTF.png).
68
68
 
69
69
  > [!NOTE]
70
- > riplex currently handles one film (or one TV show) per session. Multi-film box sets that bundle separate films onto separate discs (e.g. a trilogy in one box) need to be ripped one film at a time.
70
+ > riplex handles multi-work box sets and complete multi-season TV series in a single guided session. It detects the separate films (or films plus a show) inside a release, groups the discs that belong to each work, and routes every one to its own Plex destination — you can stop after any disc and resume the set later.
71
71
 
72
72
  ## Requirements
73
73
 
@@ -47,7 +47,7 @@ More screenshots:
47
47
  [disc overview](screenshots/5_Multi_Disc_Overview_BTTF.png).
48
48
 
49
49
  > [!NOTE]
50
- > riplex currently handles one film (or one TV show) per session. Multi-film box sets that bundle separate films onto separate discs (e.g. a trilogy in one box) need to be ripped one film at a time.
50
+ > riplex handles multi-work box sets and complete multi-season TV series in a single guided session. It detects the separate films (or films plus a show) inside a release, groups the discs that belong to each work, and routes every one to its own Plex destination — you can stop after any disc and resume the set later.
51
51
 
52
52
  ## Requirements
53
53
 
@@ -0,0 +1,139 @@
1
+ # riplex v1.0.0
2
+
3
+ **The first stable release.** v1.0.0 consolidates riplex's box-set and TV-series
4
+ support, hardens episode matching, adds a comprehensive automated test suite so
5
+ the flows that matter stay working as the project grows, and makes upgrades
6
+ painless with one-click in-place updates on Windows.
7
+
8
+ Until now every disc was treated as a standalone rip, and updating meant
9
+ re-downloading the app each release. This release lets riplex understand a whole
10
+ box set at once — detecting the *works* inside a release, grouping the discs that
11
+ belong to each, and routing every work to its correct Plex destination in a
12
+ single guided session — and then keeps itself up to date automatically.
13
+
14
+ ## Why 1.0.0?
15
+
16
+ riplex has been usable for a while; three things make this the right moment to
17
+ call it stable:
18
+
19
+ - **The hard workflows are done.** Multi-work box sets and complete multi-season
20
+ TV series — the cases that used to mean ripping disc-by-disc and sorting by
21
+ hand — now work end to end, in both the desktop app and the CLI.
22
+ - **It's protected against regressions.** A headless integration suite exercises
23
+ the entire GUI wizard and the CLI against real-world scenarios generated from
24
+ archived rips, so the flows you depend on keep working as development continues.
25
+ - **Updates are effortless.** In-place updates mean you install once and never
26
+ have to re-download and re-approve the app for each new version.
27
+
28
+ ## Headline features
29
+
30
+ ### Multi-work box sets
31
+
32
+ A release that contains several distinct works — several films, or films plus a
33
+ TV series — is detected as *multiple works* instead of one. Each work gets its
34
+ own title selection, its own rip grouping, and its own Plex target, so a mixed
35
+ box set is ripped and filed correctly in one pass:
36
+
37
+ • Disc grouping splits on dvdcompare's per-film hyperlinks (`pointer_fid`) rather than a coarse "is this a film" guess, so a bonus-films disc forms its own group and the main features stay separate.
38
+ • The Select Titles screen shows each work's rip output separately, so you can see exactly which files belong to which movie or show.
39
+ • Full CLI parity: `riplex orchestrate` and `riplex organize` route multi-work releases the same way the GUI does, through the shared flow logic.
40
+
41
+ ### Multi-season TV series
42
+
43
+ Complete-series sets that span several seasons are ripped season by season:
44
+
45
+ • A **Season Select** screen (GUI) and season prompt (CLI) assign each disc to its season.
46
+ • TV rips nest automatically under `Season NN/`, and specials land in `Season 00`.
47
+ • Season labels and chips appear throughout the Disc Overview, so a 12-disc set is easy to keep track of.
48
+
49
+ ### Guided resume across a whole set
50
+
51
+ Every session writes a `_riplex_session.json` marker capturing the full plan, so
52
+ you can rip a few discs, come back later, and pick up exactly where you left off:
53
+
54
+ • Resume from *any* disc of a multi-work or multi-season set — not just the next in sequence.
55
+ • `riplex orchestrate` (CLI) resumes from the marker with GUI parity, via a shared `resume.py` adapter.
56
+ • `riplex organize` discovers the marker and fans out across every work in the set.
57
+
58
+ ### Metadata persisted per rip
59
+
60
+ Rip manifests record the exact TMDb and dvdcompare ids you confirmed at rip time.
61
+ When you organize (or resume) a ripped disc, riplex reuses those ids and skips
62
+ the metadata pickers entirely — no re-searching, no risk of picking a different
63
+ release the second time.
64
+
65
+ ### Smarter, more resilient TV episode matching
66
+
67
+ Matching ripped titles to episodes used to be a runtime guessing game that falls
68
+ apart when episodes run within seconds of each other — or when a metadata
69
+ listing is simply wrong. Now:
70
+
71
+ • dvdcompare's episode listing is cross-referenced against the TMDb episode list.
72
+ • When a disc's episode-length titles line up 1:1 with the episode list, they're assigned by **disc position** rather than by runtime, so a single wrong dvdcompare runtime can't orphan an episode or let a same-runtime neighbour steal its slot.
73
+ • Organize honors the rip-time season/episode classification and no longer collides identically-named files across different discs.
74
+ • Over-length titles are labeled **Unmatched content** and stay visible instead of being forced into the wrong slot.
75
+
76
+ ### Seamless in-place updates (Windows)
77
+
78
+ When a new version is available, the update screen offers **Update & Restart**:
79
+ riplex downloads the new build, **verifies its SHA-256 checksum**, swaps the
80
+ running `.exe` in place, and relaunches — no re-downloading and no repeated
81
+ SmartScreen approval. It only runs from a writable install folder (otherwise it
82
+ falls back to the browser download), and every release now publishes `.sha256`
83
+ checksums for its assets.
84
+
85
+ > Note: this applies going forward. Upgrading *to* v1.0.0 from an older build
86
+ > still uses the browser download (the old build doesn't know how to self-update);
87
+ > from v1.0.0 onward, updates are one click.
88
+
89
+ ### Auto-eject after ripping
90
+
91
+ The disc ejects automatically once a rip finishes, so you can swap discs — or
92
+ just know it's done — without reaching for the drive. On by default; set
93
+ `auto_eject = false` in the config to disable.
94
+
95
+ ## Reliability & testing
96
+
97
+ v1.0.0 adds a substantial automated safety net so future changes don't quietly
98
+ break the flows you rely on:
99
+
100
+ • **Headless GUI integration tests** drive the full desktop wizard through mocked disc / TMDb / dvdcompare scenarios, asserting every screen renders and hands off correctly without touching a real drive or the network.
101
+ • **CLI integration tests** run the real argument parser and command dispatch for `organize` and `lookup`.
102
+ • **Real-world fixtures** are generated from archived rips and classified by media type (movie, mini-series, series), so new rips can grow the protected-scenario set over time.
103
+ • Cancelling a rip now returns to the **current** disc's Insert Disc screen (retry / skip / eject) instead of skipping ahead, and doesn't mark the disc as ripped.
104
+ • Duplicate-title extras no longer clobber their episode's destination during organize.
105
+
106
+ ## Minor improvements
107
+
108
+ • "Currently loaded" disc dropdown on the Disc Overview for quickly switching context.
109
+ • "Organize into Library" shortcut appears once every disc in the set has been ripped.
110
+ • Hidden-discs banner explains any discs the plan intentionally skips, and the primary-work slot shows its runtime.
111
+ • "View on dvdcompare.net" link shown once a release has been matched.
112
+ • Interactive title-selection editor at the CLI Proceed prompt, so you can adjust picks before the rip starts.
113
+ • Every `orchestrate` disc routes through an Insert Disc scan-confirm step for consistent behavior.
114
+ • Ctrl-C exits cleanly (exit code 130, no traceback, no orphaned `makemkvcon` process).
115
+
116
+ ## Bug fixes
117
+
118
+ • Duplicate **Quit** buttons on the rip-complete summary and Insert Disc screens.
119
+ • False "multiple films" alert on Select Titles, with a confirmed movie title and clearer section headers.
120
+ • Ctrl-C at a prompt no longer behaves like pressing Enter.
121
+ • Movie picks filter to just the movie disc(s), on both fresh and resumed sessions.
122
+ • Organize fuzzy-matches dvdcompare episode titles against TMDb, and reads title/season from the rip manifest for season-nested output.
123
+ • Organize no longer crashes after a resumed rip.
124
+ • Organize Rips scan-results footer anchors to the bottom, and its Back button walks up the flow instead of dropping to Welcome.
125
+ • `detect_disc_format` recognizes standard-definition DVDs.
126
+ • Linked-film autofill strips dvdcompare format markers, and the main feature is no longer misclassified as a play-all title.
127
+ • The dvdcompare cache auto-invalidates when the scraper version changes, and auto-lookup no longer picks the wrong franchise.
128
+
129
+ ## ⚠️ Breaking change
130
+
131
+ Disc-group ids changed from the old `main_1` / `film_31` scheme to
132
+ `disc_1` / `discs_1_4` / `disc_31`. A session saved by an earlier version will
133
+ not resume — **start a fresh session after upgrading**.
134
+
135
+ ## Full changelog
136
+
137
+ See [docs/changelog.md](https://github.com/AnyCredit5518/riplex/blob/v1.0.0/docs/changelog.md)
138
+ for the per-section breakdown, and the auto-generated commit list below for
139
+ every commit since v0.9.8.
@@ -77,7 +77,10 @@ src/
77
77
  organize_done.py # Organize results summary
78
78
  tests/
79
79
  test_*.py # One test file per source module
80
+ integration/ # Headless GUI flow tests (see Development > Testing)
81
+ support/ # Integration harness: fake page, driver, provider mocks
80
82
  fixtures/ # makemkvcon output samples for parsing tests
83
+ gui/scenarios/ # Committed GUI scenarios (generated from archived rips)
81
84
  snapshots/ # MKV metadata snapshots for offline test replay
82
85
  ```
83
86
 
@@ -4,6 +4,98 @@ 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
+ ## Unreleased
8
+
9
+ ### Fixed
10
+
11
+ - **TV episodes with parenthetical titles (or filed under extras) no longer land in `Other/`.** When organizing a TV rip, the destination is now chosen from the authoritative `SxxEyy` classification recorded in each disc's rip manifest at rip time, taking priority over the re-derived dvdcompare match label. Previously an episode whose title contains a parenthetical (e.g. *Shawn and Gus in Drag (Racing)*), or one that dvdcompare files under a disc's extras (e.g. an *(Extended Version)* listing such as *Romeo and Juliet and Juliet* or *Dual Spires*), was misread as an extra and dropped into `Other/` instead of its season folder — even though orchestrate had already classified it correctly (e.g. Psych S5 E01/E05/E12).
12
+
13
+ ### Added
14
+
15
+ - **Durations on the organize preview.** Each matched file now shows its runtime next to the matched episode/target's expected runtime; a divergence of more than a minute is highlighted so an incorrect match is easy to spot before you execute.
16
+
17
+ ## v1.0.1 — 2026-07-20
18
+
19
+ ### Fixed
20
+
21
+ - **Out-of-order episode matching.** When dvdcompare files an “(Extended Version)” of an episode under a disc's *extras*, TMDb enrichment promotes it to an episode but leaves it positioned after the real episode list, so the episode entries arrive out of broadcast order. Positional episode alignment now sorts episodes by their canonical `SxxEyy` number before matching disc titles, so a disc whose titles are in broadcast order maps correctly. Previously the misordered list could orphan a title to a generic “Episode” label and shift the following episodes by one (e.g. Psych S6 D3).
22
+
23
+ ## v1.0.0 — 2026-07-18
24
+
25
+ Summary: the first stable release. v1.0.0 brings full multi-work and
26
+ multi-season box-set support, hardens TV episode matching, adds a comprehensive
27
+ headless GUI + CLI integration test suite to guard against regressions, and
28
+ introduces one-click in-place updates on Windows so future upgrades are
29
+ seamless. A box set that bundles several films, or a complete multi-season TV
30
+ series, can be ripped and organized in a single guided session, with each work
31
+ routed to its correct Plex destination.
32
+
33
+ ### Highlights
34
+
35
+ - **Multi-work box sets.** A single release containing several distinct films (or films plus a TV series) is now detected as multiple works. Each work gets its own title selection, rip grouping, and Plex target, so a mixed box set no longer has to be ripped one disc at a time and sorted by hand.
36
+ - **Multi-season TV.** Complete-series sets spanning multiple seasons are ripped season by season. A new Season Select screen (GUI) and season prompt (CLI) assign each disc to a season, and rips nest under `Season NN/` automatically.
37
+ - **Guided resume.** A session marker (`_riplex_session.json`) records the whole plan, so you can stop after any disc and resume later — from any disc of the set — without re-picking metadata.
38
+ - **Metadata persisted per rip.** Rip manifests now store the chosen TMDb and dvdcompare ids, so organizing (or resuming) a ripped disc skips the pickers and reuses the exact match you confirmed at rip time.
39
+ - **Smarter TV matching.** dvdcompare episode listings are cross-referenced against the TMDb episode list, and episodes are assigned deterministically from their rip-time classification instead of guessing between near-identical runtimes.
40
+ - **Seamless updates.** On Windows, the app can download, verify, and install its own updates in place and relaunch — no more re-downloading the exe and clearing SmartScreen every release.
41
+ - **Regression-proofed for 1.0.** A headless integration suite drives the full GUI wizard and the CLI through mocked disc/TMDb/dvdcompare scenarios (generated from real archived rips), so the flows that matter stay protected as the project grows.
42
+
43
+ ### ⚠️ Breaking
44
+
45
+ - **Disc-group ids changed** from the old `main_1` / `film_31` scheme to `disc_1` / `discs_1_4` / `disc_31`. A session saved by an earlier version will not resume — start a fresh session after upgrading.
46
+
47
+ ### Added
48
+
49
+ - Multi-work release routing: per-work title selection, rip groups, and Plex destinations, with full CLI parity for `organize` and `orchestrate`.
50
+ - Season Select screen (GUI) and season prompt (CLI) for assigning discs to seasons in multi-season sets.
51
+ - Interactive title-selection editor at the CLI Proceed prompt, so picks can be adjusted before the rip starts.
52
+ - Per-group rip output on the Select Titles screen, showing each work's files separately.
53
+ - Multi-work session marker (`_riplex_session.json`) enabling resume from any disc of a set.
54
+ - Rip manifests now record the confirmed TMDb and dvdcompare ids to skip the pickers on organize and resume.
55
+ - Season labels and chips on the Disc Overview, plus a "Currently loaded" disc dropdown.
56
+ - "Organize into Library" shortcut on the Disc Overview once every disc has been ripped.
57
+ - Hidden-discs banner explaining discs the plan intentionally skips.
58
+ - "View on dvdcompare.net" link when a release has already been matched.
59
+ - **Development > Testing guide** (`development/testing.md`) documenting the headless GUI and CLI integration test suites, the `tests/support/` harness, the `gui` fixture, media-type-targeted flows, and how to generate scenario fixtures from archived rips with `scripts/gen_gui_fixtures.py`.
60
+ - **GUI auto-eject.** The disc is ejected automatically after a rip finishes so you can swap discs (or know it's done) without reaching for the drive. On by default; set `auto_eject = false` in the config to disable.
61
+ - **GUI in-place update (Windows).** The update screen now offers **Update & Restart**: riplex downloads the new build, verifies its SHA-256 checksum, swaps the running `.exe` in place, and relaunches — no manual re-download or repeated SmartScreen approval. Requires a writable install folder; falls back to the browser download otherwise. Releases now publish `.sha256` checksums for every asset.
62
+
63
+ ### Changed
64
+
65
+ - CLI `orchestrate` now resumes from the session marker (GUI parity) via the shared `resume.py` adapter.
66
+ - Disc grouping splits on dvdcompare hyperlinks (`pointer_fid`) rather than `is_film`, so a bonus-films disc forms its own group.
67
+ - `DiscGroup.kind` removed; group labels and ids simplified.
68
+ - TV rips nest under `Season NN/` and cross-reference dvdcompare features against the TMDb episode list.
69
+ - Ctrl-C now exits cleanly (exit code 130, no traceback, no orphaned `makemkvcon` process).
70
+ - Softer disc-mismatch wording, and the release picker shows the matched dvdcompare film.
71
+ - `_plan_show` keeps Season 0 (Specials); the Organize preview shows a title/season source badge.
72
+ - Every `orchestrate` disc now routes through an Insert Disc scan-confirm step.
73
+ - **Architecture** file tree now lists `tests/integration/`, `tests/support/`, and `tests/fixtures/gui/scenarios/`.
74
+ - Home page and docs index/README refreshed for the 1.0.0 feature set (multi-work box sets, multi-season TV, in-place updates, auto-eject).
75
+
76
+ ### Fixed
77
+
78
+ - **TV: a single wrong dvdcompare episode runtime no longer mislabels two episodes.** When a disc's episode-length titles line up 1:1 with the dvdcompare episode list, riplex now assigns them by disc position instead of purely by runtime. Previously a bad listed runtime (e.g. Psych S6 D3 listing "Heeeeere's Lassie" at 43:10 when the disc title is 49:41) orphaned that episode to a generic "Episode" label and let a same-runtime neighbour steal its slot. Positional alignment fixes both; it only applies on an exact 1:1 count match with at most one runtime outlier, and defers to runtime matching on ragged or reordered discs.
79
+ - **GUI: cancelling a rip returns to the current disc, not the next one.** In an orchestrate session, stopping a rip mid-disc took you to the *next* disc's Insert Disc screen. It now returns to the current disc's Insert Disc screen so you can retry, skip, or eject — and the cancelled disc is no longer marked as ripped (no manifest written) or auto-ejected.
80
+ - **GUI: Organize Rips scan-results footer now anchors to the bottom** like every other wizard screen. The results form now scrolls internally while the Back/Next buttons stay pinned at the window edge.
81
+ - **GUI: Organize Rips Back button walks up the flow** instead of dropping to the Welcome screen. On the scan-results view it restores the multi-group picker (when the folder held several seasons or works) or otherwise returns to the folder input; the multi-group picker's Back button now returns to the folder input as well.
82
+ - **Organize: duplicate-title extras no longer clobber their episode's destination.** dvdcompare sometimes lists the same episode name twice (real broadcast episode + a shorter bonus re-edit). The rip-time enrichment already demotes the shorter entry to ``[extra]``, but the organizer was ignoring that tag and fuzzy-routing both files to the same ``s0Xe0Y - Title.mkv`` destination — so the second file silently overwrote the first when organize actually executed. The organizer now respects the ``[extra]`` classification and routes the duplicate to an extras folder instead.
83
+ - Duplicate Quit buttons on the rip-complete summary and Insert Disc screens.
84
+ - False "multiple films" alert on Select Titles, with a confirmed movie title and clearer section headers.
85
+ - Ctrl-C at a prompt no longer behaves like pressing Enter.
86
+ - Movie picks now filter to just the movie disc(s), on both fresh and resumed sessions.
87
+ - The primary-work film slot shows its runtime, and hidden discs are explained.
88
+ - Organize honors the rip-time season/episode classification and no longer collides same-basename files across discs.
89
+ - Organize fuzzy-matches dvdcompare episode titles against TMDb, and reads title/season from the rip manifest for season-nested output.
90
+ - Organize no longer crashes after a resumed rip.
91
+ - TV episodes are assigned sequentially (first-fit); over-length titles are labeled "Unmatched content".
92
+ - The Disc Release screen shows the dvdcompare film title as its heading.
93
+ - `detect_disc_format` recognizes standard-definition DVDs.
94
+ - `riplex organize` discovers the session marker and fans out across all works.
95
+ - Resume works from any disc of a multi-work release, and season chips show on leading discs on resume.
96
+ - Linked-film autofill strips dvdcompare format markers, and the main feature is no longer misclassified as a play-all title.
97
+ - The dvdcompare cache auto-invalidates when the scraper version changes, and auto-lookup no longer picks the wrong franchise.
98
+
7
99
  ## v0.9.2 — 2026-06-24
8
100
 
9
101
  ### Fixed
@@ -29,8 +29,10 @@ In interactive mode, orchestrate presents prompts at each decision point:
29
29
 
30
30
  - **Title confirmation**: auto-detected title shown for confirmation or correction
31
31
  - **TMDb disambiguation**: if multiple matches, pick the correct one
32
+ - **Season selection** (TV multi-season shows only): when the show has more than one season on TMDb, pick which season is on the disc. The picked season is used both to narrow the plan and to bias the dvdcompare query (e.g. `Psych: Season 2`) so it lands on the correct per-season film page. Season 0 (Specials) is never shown in the picker but is always retained in the plan so extras on the disc that match a curated TMDb special can still route to `Season 00/`. Mini-series (only one non-special season, e.g. *Planet Earth II*) skip this prompt entirely.
32
33
  - **dvdcompare release selection**: choose the regional release (default: America)
33
34
  - **Disc selection**: pick which discs to rip (skip standard Blu-ray copies, bonus discs, etc.)
35
+ - **Title selection**: after the per-disc analysis, the `Proceed? [Y/n/e(dit)]` prompt accepts `e` to open an interactive picker that lets you toggle titles by index, range (`3-7`), or list (`1,4,6`); type `all`, `none`, `default` to reset, or `done` / Enter to accept. This is the CLI equivalent of the GUI selection screen.
34
36
  - **Disc swap**: after each disc, prompted to insert the next one
35
37
  - **Archive**: after organize, prompted to archive the rip folder (if `archive_root` is configured)
36
38
 
@@ -34,16 +34,28 @@ riplex organize "path/to/rips/PLANET EARTH II" --type tv --format "Blu-ray 4K"
34
34
 
35
35
  Multi-disc rips in separate folders (e.g. "Planet Earth III - Disc 1", "Planet Earth III - Disc 2") are automatically grouped into a single title.
36
36
 
37
+ ## Multi-work releases (TV series + bonus films)
38
+
39
+ Some releases bundle more than one work — a classic example is *Psych: The Complete Series*, with the eight-season show on discs 1-30 and three standalone TV-movies on disc 31. `organize` detects this and splits the release into groups, planning each group against its own TMDb match:
40
+
41
+ - The main-content discs organize into the TV show folder using the top-level match you searched for.
42
+ - Each film disc's slot gets a best-guess TMDb match auto-filled from its dvdcompare title.
43
+ - In interactive mode you'll be prompted per slot to accept the auto-fill, search for a different match, or skip the slot (its files stay unmatched).
44
+ - In non-interactive mode (piped output, CI) auto-fills stand and any unresolved slot is reported as `SKIPPED (no TMDb match assigned)` in the group summary.
45
+
46
+ The dry-run preview prints one line per group so you can verify the routing before running with `--execute`.
47
+
37
48
  ## Chapter splitting
38
49
 
39
50
  When the scanner detects that a file has chapter markers matching the number of TMDb Season 00 episodes, the tool automatically plans a chapter split instead of a single move. With `--execute`, this uses mkvmerge to split the file by chapters and moves each piece to the correct Season 00 location.
40
51
 
41
52
  ## Regional release selection
42
53
 
43
- dvdcompare lists multiple regional releases. In interactive mode (the default when running in a terminal), you will be presented with all available releases to choose from:
54
+ dvdcompare lists multiple regional releases. In interactive mode (the default when running in a terminal), you will be presented with all available releases to choose from. The line above the picker shows which dvdcompare film page was matched so you can spot a wrong pick (e.g. a season 1 page when the disc is season 2) before choosing a region:
44
55
 
45
56
  ```
46
- Select a dvdcompare release:
57
+ Matched dvdcompare film: Oppenheimer (2023) [film #12345]
58
+ Select a dvdcompare release for Oppenheimer:
47
59
  1. North America (4K Ultra HD) [4 discs] *
48
60
  2. United Kingdom (4K Ultra HD) [4 discs]
49
61
  3. Germany (4K Ultra HD) [3 discs]
@@ -83,7 +83,9 @@ This will:
83
83
  5. Show a disc analysis table with rip recommendations
84
84
  6. Prompt for confirmation, then rip the selected titles
85
85
 
86
- Add `--yes` to skip the final rip confirmation prompt. Use `--auto` to also skip all interactive selection prompts (title, TMDb, release). Use `--titles 1,2,3` to override the auto-selection, or `--all` to rip everything. Add `--organize` to automatically run the organize step after ripping.
86
+ At the final `Proceed? [Y/n/e(dit)]` prompt, press `e` to open an interactive title picker. The picker shows every title on the disc with `[x]`/`[ ]` marks reflecting the current selection and lets you toggle by index (`3`), comma-separated list (`1,4`), or range (`3-7`). Type `all` or `none` to select or clear everything, `default` to restore the analyzer's recommendation, `done` (or Enter) to accept, or `cancel` to abort the edit. This is the CLI equivalent of the GUI selection screen, so you no longer need to abort and re-run with `--titles 1,3,5` to fix a wrong recommendation.
87
+
88
+ Add `--yes` to skip the final rip confirmation prompt (and the edit picker with it). Use `--auto` to also skip all interactive selection prompts (title, TMDb, release, season, edit). Use `--titles 1,2,3` to override the auto-selection non-interactively, or `--all` to rip everything. Add `--organize` to automatically run the organize step after ripping.
87
89
 
88
90
  Repeat for each disc in the set, swapping discs between runs.
89
91
 
@@ -0,0 +1,182 @@
1
+ # Testing
2
+
3
+ riplex uses `pytest`. Run the whole suite from the project root with the venv
4
+ active:
5
+
6
+ ```
7
+ pytest
8
+ ```
9
+
10
+ The suite has two layers:
11
+
12
+ - **Unit tests** (`tests/test_*.py`) — one file per source module, covering
13
+ parsing, planning, matching, and per-screen handler logic in isolation.
14
+ - **Integration tests** (`tests/integration/`) — headless end-to-end flows that
15
+ drive the GUI wizard and the CLI through mocked scenarios to catch regressions
16
+ in how the pieces fit together.
17
+
18
+ To run only the integration flows (or skip them):
19
+
20
+ ```
21
+ pytest -m integration
22
+ pytest -m "not integration"
23
+ ```
24
+
25
+ ## GUI integration tests
26
+
27
+ The integration suite builds a real `RiplexApp` on a fake page and clicks
28
+ through the wizard the way a user would — but with every external system
29
+ (makemkv, TMDb, dvdcompare, config, network) replaced by fakes. This means a
30
+ whole flow (welcome → disc detection → metadata → release → disc overview →
31
+ rip → done) runs in milliseconds with no disc, no network, and no crash dialog.
32
+
33
+ The goal is to catch the most common regression class: a screen that throws
34
+ while rendering because an upstream change altered the shape of the state it
35
+ reads.
36
+
37
+ ### Harness (`tests/support/`)
38
+
39
+ | Module | Responsibility |
40
+ | ------ | -------------- |
41
+ | `fake_page.py` | `FakePage`, a stand-in for `ft.Page` implementing only the surface the screens touch, plus control-tree walking helpers. |
42
+ | `sync_runtime.py` | Makes background threads and `page.run_task` run synchronously so flows are deterministic (no sleeps, no polling loops). |
43
+ | `fixtures.py` | Loads a scenario JSON and reconstructs the **real** riplex dataclasses (`DiscInfo`, `MetadataSearchResult`, `PlannedDisc`, …). |
44
+ | `provider_mocks.py` | Installs scenario-driven fakes for makemkv, TMDb, dvdcompare, config, and network in one call. |
45
+ | `driver.py` | `WizardDriver` — `click(label)`, `has_text(...)`, `current`, `state`, `crashed()`. |
46
+
47
+ The `gui` pytest fixture (in `tests/conftest.py`) wires it all together:
48
+
49
+ ```python
50
+ def test_movie_flow(gui):
51
+ d = gui("the-matrix-1999") # load a scenario, mock everything
52
+ d.click("Rip Disc") # welcome -> disc detection -> metadata
53
+ d.click("Next") # accept the TMDb match
54
+ assert d.current == "disc_overview"
55
+ assert not d.crashed()
56
+ ```
57
+
58
+ Failure injection to prove the net works: adding a `raise` to any screen's
59
+ `build()` fails both the relevant flow test and the screen-build smoke matrix.
60
+
61
+ ### What is covered
62
+
63
+ - **Screen-build smoke matrix** (`test_screen_build_smoke.py`) — builds *every*
64
+ screen with a fully-populated state for one representative scenario per media
65
+ type (plus a no-dvdcompare edge case).
66
+ - **Media-type flow tests** (`test_flow_media_types.py`) — drive *every* fixture
67
+ of a given media type through the core orchestrate path, so the whole fixture
68
+ corpus is exercised through the real screens.
69
+ - **Named flow tests** — movie orchestrate, TV multi-disc, organize,
70
+ resume/prefill fast paths, and error paths (makemkv unavailable, TMDb
71
+ empty/raises, dvdcompare failure, rip failure).
72
+ - **Fixture integrity** — every committed scenario reconstructs into real
73
+ dataclasses and carries a valid media-type category.
74
+
75
+ ### Targeting media types
76
+
77
+ Each scenario is classified into one media-type **category** so a test can run
78
+ against exactly the fixtures it applies to — and automatically pick up new
79
+ fixtures of that type as they're generated:
80
+
81
+ | Category | Meaning |
82
+ | -------- | ------- |
83
+ | `movie` | A theatrical / feature film release. |
84
+ | `tv_miniseries` | A limited, self-contained series (e.g. Chernobyl). |
85
+ | `tv_series` | An ongoing multi-season show — a single-season rip *or* a complete-series set. |
86
+
87
+ A single season of an ongoing show (e.g. a `Season 01` folder) is **series
88
+ content**, so it classifies as `tv_series` and flows through the series tests.
89
+ "Seasonal series" isn't a separate category; use `season_scenarios()` to target
90
+ just the single-season rips.
91
+
92
+ The category is inferred from the archive folder name, dvdcompare release name,
93
+ and TMDb season structure; a scenario can also pin it with a top-level
94
+ `"category"` key. `tests/support/fixtures.py` exposes filter helpers that return
95
+ the matching scenario names for parametrization:
96
+
97
+ ```python
98
+ from tests.support.fixtures import movie_scenarios, series_scenarios, season_scenarios
99
+
100
+ @pytest.mark.parametrize("name", series_scenarios()) # every TV series fixture
101
+ def test_series_flow(gui, name):
102
+ d = gui(name)
103
+ ...
104
+
105
+ @pytest.mark.parametrize("name", season_scenarios()) # just single-season rips
106
+ def test_seasonal_flow(gui, name):
107
+ ...
108
+ ```
109
+
110
+ Adding a new fixture of a category makes it flow through that category's tests
111
+ on the next run — no test edits required.
112
+
113
+ ## CLI integration tests
114
+
115
+ The CLI is exercised end-to-end the same way: `tests/support/cli.py` runs the
116
+ real argparse parser and async command dispatch (`_run`) in-process, so tests
117
+ cover argument parsing, command routing, and command logic together. External
118
+ boundaries (config, TMDb, dvdcompare) are stubbed via monkeypatch.
119
+
120
+ ```python
121
+ from tests.support.cli import install_cli_mocks, run_command
122
+
123
+ def test_lookup(monkeypatch, capsys):
124
+ install_cli_mocks(monkeypatch) # offline config + providers
125
+ ... # mock the lookup seam
126
+ code = run_command(["lookup", "The Matrix"])
127
+ assert code == 0
128
+ assert "The Matrix" in capsys.readouterr().out
129
+ ```
130
+
131
+ Covered: parser/dispatch (help on no command, `--version`, unknown command,
132
+ every subcommand parses), `organize` dry-run against a committed scan snapshot
133
+ plus its guard rails (`--execute` rejected with `--snapshot`, missing snapshot,
134
+ non-directory folder), and `lookup` (rip guide, graceful dvdcompare failure,
135
+ `--json` output). The `organize --snapshot` path replays a real
136
+ `tests/snapshots/*.snapshot.json` so no ffprobe or live files are needed.
137
+
138
+ ## Test fixtures from archived rips
139
+
140
+ Integration scenarios live as committed JSON under
141
+ `tests/fixtures/gui/scenarios/`. They share one schema between hand-authored
142
+ edge cases and scenarios generated from real archived rips. The loader reads
143
+ only the committed JSON, so the test suite never needs the archive and CI stays
144
+ hermetic.
145
+
146
+ ### Generating scenarios
147
+
148
+ Archived rips carry everything a scenario needs — the disc titles makemkv
149
+ reported, the confirmed TMDb match, and the dvdcompare release breakdown — in
150
+ their debug artifacts:
151
+
152
+ - `<Title (Year)>/_riplex/riplex-rip.snapshot.json` — disc titles, TMDb match,
153
+ dvdcompare disc breakdown.
154
+ - `<Title (Year)>/Disc N/_rip_manifest.json` — richer per-title stream info plus
155
+ the exact dvdcompare release name, disc format, and volume label (when
156
+ present).
157
+
158
+ `scripts/gen_gui_fixtures.py` walks an archive root, normalizes each title
159
+ folder into a scenario JSON, and writes it under `tests/fixtures/gui/scenarios/`:
160
+
161
+ ```
162
+ # List archived folders (a * marks ones with a usable snapshot)
163
+ python scripts/gen_gui_fixtures.py --list
164
+
165
+ # Generate all scenarios
166
+ python scripts/gen_gui_fixtures.py
167
+
168
+ # Generate just one, from a custom archive path
169
+ python scripts/gen_gui_fixtures.py --only "Chernobyl (2019)" \
170
+ --archive "/path/to/_archive"
171
+ ```
172
+
173
+ Missing pieces (a TMDb id, per-episode lists on older snapshots, TMDb season
174
+ structure) are synthesized deterministically and listed under a `synthesized`
175
+ key in the output so consumers know which blocks are inferred rather than
176
+ observed. Each generated scenario also carries a `category` (see
177
+ [Targeting media types](#targeting-media-types)), which you can override by
178
+ hand-editing the field.
179
+
180
+ Re-run the generator and commit the resulting JSON whenever you want to refresh
181
+ or add real-world scenarios. The archive itself is never required to run the
182
+ tests.