kryten-webqueue 0.44.4__tar.gz → 0.44.6__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 (199) hide show
  1. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/CHANGELOG.md +24 -0
  2. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/ytpipe/downloader.py +14 -3
  4. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/queue.py +38 -3
  5. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/pyproject.toml +1 -1
  6. kryten_webqueue-0.44.6/tests/test_queue_preview_fallback.py +68 -0
  7. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.github/workflows/gitleaks.yml +0 -0
  8. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.github/workflows/python-publish.yml +0 -0
  9. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.github/workflows/release.yml +0 -0
  10. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.gitignore +0 -0
  11. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.gitleaks.toml +0 -0
  12. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/.pre-commit-config.yaml +0 -0
  13. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/AGENT-WORKFLOW-GUIDE.md +0 -0
  14. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/AGENTS.md +0 -0
  15. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/README.md +0 -0
  16. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/config.example.json +0 -0
  17. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/deploy/kryten-webqueue.service +0 -0
  18. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/deploy/nginx-queue.conf +0 -0
  19. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/IMPLEMENTATION_SPEC.md +0 -0
  20. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/IMPL_API_GATE.md +0 -0
  21. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/IMPL_ECONOMY.md +0 -0
  22. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/IMPL_KRYTEN_PY.md +0 -0
  23. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/IMPL_ROBOT.md +0 -0
  24. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  25. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  26. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/Log for incorrect promo insertion problem.txt +0 -0
  27. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  28. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  29. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/PRE_PLAN_GAPS.md +0 -0
  30. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/PRODUCT_PLAN.md +0 -0
  31. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/PUBLIC_API.md +0 -0
  32. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  33. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  34. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  35. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC_EMOTE_REHOST.md +0 -0
  36. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  37. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  38. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/docs/UX_POLISH_PLAN.md +0 -0
  39. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/__init__.py +0 -0
  40. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/__main__.py +0 -0
  41. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/api_gate/__init__.py +0 -0
  42. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/api_gate/client.py +0 -0
  43. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/app.py +0 -0
  44. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/auth/__init__.py +0 -0
  45. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/auth/device_keys.py +0 -0
  46. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/auth/otp.py +0 -0
  47. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/auth/rate_limit.py +0 -0
  48. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/auth/session.py +0 -0
  49. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/__init__.py +0 -0
  50. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/__init__.py +0 -0
  51. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
  52. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  53. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_connection.py +0 -0
  54. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_devices.py +0 -0
  55. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  56. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  57. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  58. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_people.py +0 -0
  59. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  60. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_queue.py +0 -0
  61. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  62. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  63. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  64. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  65. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  66. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  67. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  68. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  69. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  70. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  71. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  72. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  73. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  74. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  75. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  76. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/images.py +0 -0
  77. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/mediacms.py +0 -0
  78. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/sync.py +0 -0
  79. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  80. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  81. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  82. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  83. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  84. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  85. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  86. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/config.py +0 -0
  87. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/__init__.py +0 -0
  88. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  89. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  90. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
  91. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  92. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  93. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  94. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  95. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  96. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  97. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  98. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/__init__.py +0 -0
  99. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  100. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  101. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/log_capture.py +0 -0
  102. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/manager.py +0 -0
  103. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  104. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/jobs/tasks.py +0 -0
  105. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/logging_config.py +0 -0
  106. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/__init__.py +0 -0
  107. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/bulk_add.py +0 -0
  108. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/fire.py +0 -0
  109. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/importer.py +0 -0
  110. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/ordering.py +0 -0
  111. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/playlists/scheduler.py +0 -0
  112. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/promos/__init__.py +0 -0
  113. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/promos/director.py +0 -0
  114. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/__init__.py +0 -0
  115. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/completion.py +0 -0
  116. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/ordering.py +0 -0
  117. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/poller.py +0 -0
  118. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/presence.py +0 -0
  119. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/race_poller.py +0 -0
  120. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/queue/shadow.py +0 -0
  121. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/__init__.py +0 -0
  122. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_catalog.py +0 -0
  123. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_feedback.py +0 -0
  124. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  125. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_jobs.py +0 -0
  126. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_moderation.py +0 -0
  127. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_playlists.py +0 -0
  128. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_promos.py +0 -0
  129. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_queue.py +0 -0
  130. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/admin_schedules.py +0 -0
  131. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/auth.py +0 -0
  132. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/catalog.py +0 -0
  133. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/devices.py +0 -0
  134. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/feedback.py +0 -0
  135. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/pages.py +0 -0
  136. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/public_api.py +0 -0
  137. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/routes/user.py +0 -0
  138. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/css/main.css +0 -0
  139. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  140. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/admin-promos.js +0 -0
  141. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  142. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  143. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/admin.js +0 -0
  144. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/keybindings.js +0 -0
  145. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/static/js/main.js +0 -0
  146. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/admin/index.html +0 -0
  147. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/admin/playlists.html +0 -0
  148. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/admin/promos.html +0 -0
  149. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  150. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/admin/schedules.html +0 -0
  151. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/auth/login.html +0 -0
  152. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/base.html +0 -0
  153. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/catalog/browse.html +0 -0
  154. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  155. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  156. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  157. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/feedback/index.html +0 -0
  158. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/queue/index.html +0 -0
  159. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/race.html +0 -0
  160. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/user/dashboard.html +0 -0
  161. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/user/link_device.html +0 -0
  162. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/templates/user/my_list.html +0 -0
  163. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/ws/__init__.py +0 -0
  164. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/ws/handler.py +0 -0
  165. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/kryten_webqueue/ws/manager.py +0 -0
  166. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/scripts/AGENTS.md +0 -0
  167. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/scripts/patch_mediacms_bulk_facets.py +0 -0
  168. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/scripts/patch_mediacms_tags.py +0 -0
  169. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/scripts/trigger_enrich.py +0 -0
  170. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/__init__.py +0 -0
  171. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_catalog_blackout.py +0 -0
  172. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_catalog_delete.py +0 -0
  173. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_classify.py +0 -0
  174. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_config_persistence.py +0 -0
  175. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_device_linking.py +0 -0
  176. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_feedback.py +0 -0
  177. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_fetch_queue_pacing.py +0 -0
  178. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_fetchurls_sharepoint.py +0 -0
  179. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_identify_step.py +0 -0
  180. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_job_logs_and_queue_visibility.py +0 -0
  181. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_people_facets.py +0 -0
  182. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_phase1.py +0 -0
  183. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_phase2_jobs.py +0 -0
  184. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_phase3_jobs.py +0 -0
  185. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_phase4_live_fixes.py +0 -0
  186. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_playlist_import.py +0 -0
  187. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_presence_refund.py +0 -0
  188. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_promo_director.py +0 -0
  189. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_promo_pool_exclusion.py +0 -0
  190. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_providers.py +0 -0
  191. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_queue_announce.py +0 -0
  192. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_race_poller.py +0 -0
  193. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_recently_played_hide.py +0 -0
  194. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_save_results_to_playlist.py +0 -0
  195. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_schedule_lock.py +0 -0
  196. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_search_facets.py +0 -0
  197. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_title_normalization.py +0 -0
  198. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_tmdb_index.py +0 -0
  199. {kryten_webqueue-0.44.4 → kryten_webqueue-0.44.6}/tests/test_watchlist.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.44.6] - 2026-08-30
4
+
5
+ ### Fixed
6
+
7
+ - **Tubi (AES-128 HLS) downloads no longer fail ffprobe validation.** When
8
+ `ffprobe` exits non-zero but writes nothing to stderr — the common outcome
9
+ for TS/HLS-merged containers like Tubi movies — the pre-upload check now
10
+ treats it as a soft skip (same behaviour as when `ffprobe` is absent) rather
11
+ than a hard failure. A real error (non-empty stderr) still fails fast. All
12
+ error messages now include the filename so the admin job history is
13
+ actionable.
14
+
15
+ ## [0.44.5] - 2026-08-30
16
+
17
+ ### Fixed
18
+
19
+ - **Queue pricing degrades gracefully when economy is unreachable.** The
20
+ pay-to-play cost preview (used by `POST /queue/add`, `/queue/playnext`, and
21
+ `GET /queue/preview`) is proxied through kryten-api-gate to kryten-economy.
22
+ When economy was down the proxy returned a 5xx and webqueue surfaced an
23
+ unhandled **HTTP 500**. Those calls now go through `_queue_preview_or_503`,
24
+ which maps upstream 5xx and transport errors to a clean **HTTP 503**
25
+ ("Pricing is temporarily unavailable. Please try again in a moment.").
26
+
3
27
  ## [0.44.4] - 2026-08-30
4
28
 
5
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.44.4
3
+ Version: 0.44.6
4
4
  Summary: Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube
5
5
  Author: grobertson
6
6
  License-Expression: MIT
@@ -3101,14 +3101,25 @@ class MediaDownloaderToMediaCMS:
3101
3101
  # ffprobe missing/errored: don't block the upload on our own check.
3102
3102
  print(f"[WARN] skipping pre-upload validation (ffprobe unavailable): {e}")
3103
3103
  return
3104
+ fname = os.path.basename(file_path)
3104
3105
  if proc.returncode != 0:
3106
+ stderr = proc.stderr.strip()
3107
+ if not stderr:
3108
+ # ffprobe exited non-zero but printed nothing to stderr — common
3109
+ # with certain TS/HLS-merged containers (e.g. Tubi AES-128).
3110
+ # Let MediaCMS be the final arbiter rather than blocking the upload.
3111
+ print(
3112
+ f"[WARN] ffprobe exited {proc.returncode} with no error output for"
3113
+ f" {fname}; skipping pre-upload validation"
3114
+ )
3115
+ return
3105
3116
  raise ValueError(
3106
- f"ffprobe could not read the file: {proc.stderr.strip()[:200]}"
3117
+ f"ffprobe could not read the file {fname}: {stderr[:200]}"
3107
3118
  )
3108
3119
  try:
3109
3120
  meta = json.loads(proc.stdout or "{}")
3110
3121
  except ValueError:
3111
- raise ValueError("ffprobe returned unparseable output")
3122
+ raise ValueError(f"ffprobe returned unparseable output for {fname}")
3112
3123
  has_video = any(
3113
3124
  s.get("codec_type") == "video" for s in meta.get("streams") or []
3114
3125
  )
@@ -3118,7 +3129,7 @@ class MediaDownloaderToMediaCMS:
3118
3129
  duration = 0.0
3119
3130
  if not has_video or duration <= 0:
3120
3131
  raise ValueError(
3121
- f"downloaded file is not a valid video (video_stream={has_video}, duration={duration})"
3132
+ f"{fname} is not a valid video (video_stream={has_video}, duration={duration})"
3122
3133
  )
3123
3134
 
3124
3135
  def process_video(
@@ -1,3 +1,6 @@
1
+ import logging
2
+
3
+ import httpx
1
4
  from fastapi import APIRouter, Request, Depends, HTTPException
2
5
  from datetime import datetime, UTC
3
6
 
@@ -6,6 +9,35 @@ from ..queue.ordering import insert_pay_queue, insert_pay_playnext
6
9
 
7
10
  router = APIRouter(prefix="/queue", tags=["queue"])
8
11
 
12
+ logger = logging.getLogger(__name__)
13
+
14
+ _PRICING_UNAVAILABLE = (
15
+ "Pricing is temporarily unavailable. Please try again in a moment."
16
+ )
17
+
18
+
19
+ async def _queue_preview_or_503(
20
+ api_gate, *, username: str, duration_sec: int, tier: str
21
+ ) -> dict:
22
+ """Fetch a cost preview, degrading to HTTP 503 when economy is unreachable.
23
+
24
+ Pricing is proxied through kryten-api-gate to kryten-economy. If economy is
25
+ down the proxy returns a 5xx (or the call fails at the transport layer);
26
+ surface that as a clean 503 instead of an unhandled 500.
27
+ """
28
+ try:
29
+ return await api_gate.queue_preview(
30
+ username=username, duration_sec=duration_sec, tier=tier
31
+ )
32
+ except httpx.HTTPStatusError as exc:
33
+ if exc.response.status_code >= 500:
34
+ logger.warning("queue_preview upstream error: %s", exc)
35
+ raise HTTPException(503, _PRICING_UNAVAILABLE)
36
+ raise
37
+ except httpx.RequestError as exc:
38
+ logger.warning("queue_preview transport error: %s", exc)
39
+ raise HTTPException(503, _PRICING_UNAVAILABLE)
40
+
9
41
 
10
42
  async def _pre_fire_lock_detail(db) -> str:
11
43
  """Build a specific 'pay-to-play closes before [event]' message.
@@ -78,7 +110,8 @@ async def add_to_queue(request: Request, user: dict = Depends(get_current_user))
78
110
 
79
111
  # Preview cost (api-gate _unwrap strips the success envelope; raise_for_status
80
112
  # handles non-2xx, so no success check needed here)
81
- preview = await api_gate.queue_preview(
113
+ preview = await _queue_preview_or_503(
114
+ api_gate,
82
115
  username=user["username"],
83
116
  duration_sec=item["duration_sec"],
84
117
  tier=tier,
@@ -135,7 +168,8 @@ async def play_next(request: Request, user: dict = Depends(get_current_user)):
135
168
 
136
169
  # Preview cost (api-gate _unwrap strips the success envelope; raise_for_status
137
170
  # handles non-2xx, so no success check needed here)
138
- preview = await api_gate.queue_preview(
171
+ preview = await _queue_preview_or_503(
172
+ api_gate,
139
173
  username=user["username"],
140
174
  duration_sec=item["duration_sec"],
141
175
  tier=tier,
@@ -187,7 +221,8 @@ async def cost_preview(
187
221
  if not item:
188
222
  raise HTTPException(404, "Item not found")
189
223
 
190
- preview = await api_gate.queue_preview(
224
+ preview = await _queue_preview_or_503(
225
+ api_gate,
191
226
  username=user["username"],
192
227
  duration_sec=item["duration_sec"],
193
228
  tier=tier,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.44.4"
3
+ version = "0.44.6"
4
4
  description = "Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,68 @@
1
+ """Graceful degradation when economy pricing is unreachable.
2
+
3
+ `_queue_preview_or_503` wraps the api-gate cost preview so that an economy /
4
+ api-gate outage surfaces as a clean HTTP 503 instead of an unhandled 500.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import httpx
10
+ import pytest
11
+ from fastapi import HTTPException
12
+
13
+ from kryten_webqueue.routes.queue import _queue_preview_or_503
14
+
15
+
16
+ class _FakeApiGate:
17
+ def __init__(self, exc: Exception | None = None, result: dict | None = None):
18
+ self._exc = exc
19
+ self._result = result or {"available": True, "cost_z": 100}
20
+
21
+ async def queue_preview(
22
+ self, *, username: str, duration_sec: int, tier: str
23
+ ) -> dict:
24
+ if self._exc is not None:
25
+ raise self._exc
26
+ return self._result
27
+
28
+
29
+ def _status_error(code: int) -> httpx.HTTPStatusError:
30
+ request = httpx.Request("POST", "http://api-gate/economy/queue-preview")
31
+ response = httpx.Response(code, request=request)
32
+ return httpx.HTTPStatusError("upstream", request=request, response=response)
33
+
34
+
35
+ async def test_passthrough_on_success():
36
+ api = _FakeApiGate(result={"available": True, "cost_z": 250})
37
+ out = await _queue_preview_or_503(
38
+ api, username="alice", duration_sec=600, tier="queue"
39
+ )
40
+ assert out["cost_z"] == 250
41
+
42
+
43
+ @pytest.mark.parametrize("code", [500, 502, 503])
44
+ async def test_upstream_5xx_becomes_503(code: int):
45
+ api = _FakeApiGate(exc=_status_error(code))
46
+ with pytest.raises(HTTPException) as excinfo:
47
+ await _queue_preview_or_503(
48
+ api, username="alice", duration_sec=600, tier="queue"
49
+ )
50
+ assert excinfo.value.status_code == 503
51
+
52
+
53
+ async def test_transport_error_becomes_503():
54
+ request = httpx.Request("POST", "http://api-gate/economy/queue-preview")
55
+ api = _FakeApiGate(exc=httpx.ConnectError("refused", request=request))
56
+ with pytest.raises(HTTPException) as excinfo:
57
+ await _queue_preview_or_503(
58
+ api, username="alice", duration_sec=600, tier="queue"
59
+ )
60
+ assert excinfo.value.status_code == 503
61
+
62
+
63
+ async def test_upstream_4xx_propagates():
64
+ api = _FakeApiGate(exc=_status_error(404))
65
+ with pytest.raises(httpx.HTTPStatusError):
66
+ await _queue_preview_or_503(
67
+ api, username="alice", duration_sec=600, tier="queue"
68
+ )