kryten-webqueue 0.44.2__tar.gz → 0.44.4__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 (198) hide show
  1. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/CHANGELOG.md +24 -0
  2. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/PKG-INFO +2 -1
  3. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/ytpipe/downloader.py +55 -0
  4. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/pyproject.toml +4 -1
  5. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.github/workflows/gitleaks.yml +0 -0
  6. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.github/workflows/python-publish.yml +0 -0
  7. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.github/workflows/release.yml +0 -0
  8. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.gitignore +0 -0
  9. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.gitleaks.toml +0 -0
  10. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/.pre-commit-config.yaml +0 -0
  11. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/AGENT-WORKFLOW-GUIDE.md +0 -0
  12. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/AGENTS.md +0 -0
  13. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/README.md +0 -0
  14. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/config.example.json +0 -0
  15. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/deploy/kryten-webqueue.service +0 -0
  16. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/deploy/nginx-queue.conf +0 -0
  17. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/IMPLEMENTATION_SPEC.md +0 -0
  18. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/IMPL_API_GATE.md +0 -0
  19. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/IMPL_ECONOMY.md +0 -0
  20. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/IMPL_KRYTEN_PY.md +0 -0
  21. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/IMPL_ROBOT.md +0 -0
  22. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  23. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  24. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/Log for incorrect promo insertion problem.txt +0 -0
  25. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  26. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  27. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/PRE_PLAN_GAPS.md +0 -0
  28. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/PRODUCT_PLAN.md +0 -0
  29. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/PUBLIC_API.md +0 -0
  30. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  31. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  32. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  33. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC_EMOTE_REHOST.md +0 -0
  34. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  35. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  36. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/docs/UX_POLISH_PLAN.md +0 -0
  37. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/__init__.py +0 -0
  38. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/__main__.py +0 -0
  39. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/api_gate/__init__.py +0 -0
  40. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/api_gate/client.py +0 -0
  41. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/app.py +0 -0
  42. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/auth/__init__.py +0 -0
  43. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/auth/device_keys.py +0 -0
  44. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/auth/otp.py +0 -0
  45. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/auth/rate_limit.py +0 -0
  46. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/auth/session.py +0 -0
  47. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/__init__.py +0 -0
  48. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/__init__.py +0 -0
  49. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
  50. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  51. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_connection.py +0 -0
  52. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_devices.py +0 -0
  53. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  54. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  55. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  56. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_people.py +0 -0
  57. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  58. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_queue.py +0 -0
  59. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  60. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  61. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  62. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  63. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  64. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  65. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  66. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  67. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  68. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  69. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  70. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  71. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  72. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  73. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  74. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/images.py +0 -0
  75. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/mediacms.py +0 -0
  76. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/sync.py +0 -0
  77. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  78. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  79. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  80. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  81. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  82. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  83. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  84. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/config.py +0 -0
  85. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/__init__.py +0 -0
  86. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  87. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  88. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
  89. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  90. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  91. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  92. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  93. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  94. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  95. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  96. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/__init__.py +0 -0
  97. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  98. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  99. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/log_capture.py +0 -0
  100. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/manager.py +0 -0
  101. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  102. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/jobs/tasks.py +0 -0
  103. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/logging_config.py +0 -0
  104. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/__init__.py +0 -0
  105. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/bulk_add.py +0 -0
  106. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/fire.py +0 -0
  107. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/importer.py +0 -0
  108. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/ordering.py +0 -0
  109. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/playlists/scheduler.py +0 -0
  110. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/promos/__init__.py +0 -0
  111. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/promos/director.py +0 -0
  112. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/__init__.py +0 -0
  113. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/completion.py +0 -0
  114. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/ordering.py +0 -0
  115. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/poller.py +0 -0
  116. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/presence.py +0 -0
  117. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/race_poller.py +0 -0
  118. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/queue/shadow.py +0 -0
  119. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/__init__.py +0 -0
  120. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_catalog.py +0 -0
  121. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_feedback.py +0 -0
  122. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  123. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_jobs.py +0 -0
  124. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_moderation.py +0 -0
  125. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_playlists.py +0 -0
  126. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_promos.py +0 -0
  127. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_queue.py +0 -0
  128. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/admin_schedules.py +0 -0
  129. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/auth.py +0 -0
  130. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/catalog.py +0 -0
  131. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/devices.py +0 -0
  132. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/feedback.py +0 -0
  133. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/pages.py +0 -0
  134. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/public_api.py +0 -0
  135. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/queue.py +0 -0
  136. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/routes/user.py +0 -0
  137. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/css/main.css +0 -0
  138. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  139. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/admin-promos.js +0 -0
  140. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  141. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  142. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/admin.js +0 -0
  143. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/keybindings.js +0 -0
  144. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/static/js/main.js +0 -0
  145. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/admin/index.html +0 -0
  146. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/admin/playlists.html +0 -0
  147. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/admin/promos.html +0 -0
  148. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  149. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/admin/schedules.html +0 -0
  150. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/auth/login.html +0 -0
  151. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/base.html +0 -0
  152. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/catalog/browse.html +0 -0
  153. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  154. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  155. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  156. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/feedback/index.html +0 -0
  157. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/queue/index.html +0 -0
  158. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/race.html +0 -0
  159. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/user/dashboard.html +0 -0
  160. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/user/link_device.html +0 -0
  161. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/templates/user/my_list.html +0 -0
  162. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/ws/__init__.py +0 -0
  163. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/ws/handler.py +0 -0
  164. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/kryten_webqueue/ws/manager.py +0 -0
  165. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/scripts/AGENTS.md +0 -0
  166. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/scripts/patch_mediacms_bulk_facets.py +0 -0
  167. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/scripts/patch_mediacms_tags.py +0 -0
  168. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/scripts/trigger_enrich.py +0 -0
  169. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/__init__.py +0 -0
  170. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_catalog_blackout.py +0 -0
  171. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_catalog_delete.py +0 -0
  172. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_classify.py +0 -0
  173. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_config_persistence.py +0 -0
  174. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_device_linking.py +0 -0
  175. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_feedback.py +0 -0
  176. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_fetch_queue_pacing.py +0 -0
  177. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_fetchurls_sharepoint.py +0 -0
  178. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_identify_step.py +0 -0
  179. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_job_logs_and_queue_visibility.py +0 -0
  180. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_people_facets.py +0 -0
  181. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_phase1.py +0 -0
  182. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_phase2_jobs.py +0 -0
  183. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_phase3_jobs.py +0 -0
  184. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_phase4_live_fixes.py +0 -0
  185. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_playlist_import.py +0 -0
  186. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_presence_refund.py +0 -0
  187. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_promo_director.py +0 -0
  188. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_promo_pool_exclusion.py +0 -0
  189. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_providers.py +0 -0
  190. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_queue_announce.py +0 -0
  191. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_race_poller.py +0 -0
  192. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_recently_played_hide.py +0 -0
  193. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_save_results_to_playlist.py +0 -0
  194. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_schedule_lock.py +0 -0
  195. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_search_facets.py +0 -0
  196. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_title_normalization.py +0 -0
  197. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_tmdb_index.py +0 -0
  198. {kryten_webqueue-0.44.2 → kryten_webqueue-0.44.4}/tests/test_watchlist.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.44.4] - 2026-08-30
4
+
5
+ ### Added
6
+
7
+ - **Pre-upload video validation.** After download and before uploading to
8
+ MediaCMS, the fetcher now runs `ffprobe` on the file and rejects it if it has
9
+ no video stream or zero duration. MediaCMS deletes any upload it can't identify
10
+ as video (its `media_init` removes the original when `ffprobe` fails), which
11
+ otherwise showed up as a successful `201` followed by a silently vanished item.
12
+ A bad download now fails fast with a clear reason instead. If `ffprobe` is
13
+ unavailable the check is skipped (non-fatal).
14
+
15
+ ## [0.44.3] - 2026-08-29
16
+
17
+ ### Fixed
18
+
19
+ - **Tubi (and other AES-128 HLS) downloads no longer crash ffmpeg.** Added
20
+ `pycryptodomex` so yt-dlp decrypts AES-128-encrypted HLS streams **in-process**.
21
+ Without it, yt-dlp logged `The stream has AES-128 encryption and pycryptodomex
22
+ is not available; extraction will be delegated to ffmpeg`, and the static ffmpeg
23
+ build then segfaulted (`ffmpeg exited with code -11`), failing the download.
24
+ This is a separate failure mode from the source-throttling read-timeouts
25
+ addressed in 0.44.2.
26
+
3
27
  ## [0.44.2] - 2026-08-29
4
28
 
5
29
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.44.2
3
+ Version: 0.44.4
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
@@ -13,6 +13,7 @@ Requires-Dist: jinja2>=3.1
13
13
  Requires-Dist: msal>=1.28
14
14
  Requires-Dist: openpyxl>=3.1
15
15
  Requires-Dist: pillow>=10.0
16
+ Requires-Dist: pycryptodomex>=3.20
16
17
  Requires-Dist: pydantic>=2.0
17
18
  Requires-Dist: pyjwt>=2.8
18
19
  Requires-Dist: python-dateutil>=2.8
@@ -22,6 +22,7 @@ import argparse
22
22
  import json
23
23
  import os
24
24
  import re
25
+ import subprocess
25
26
  import sys
26
27
  import time
27
28
  from pathlib import Path
@@ -3069,6 +3070,57 @@ class MediaDownloaderToMediaCMS:
3069
3070
 
3070
3071
  return results
3071
3072
 
3073
+ def _validate_downloaded_video(self, file_path: str) -> None:
3074
+ """Reject an unreadable download before uploading.
3075
+
3076
+ MediaCMS runs ffprobe on every upload and *deletes* anything it can't
3077
+ identify as video, so a truncated/invalid file would upload with a 201
3078
+ and then silently vanish. Fail here instead, with a clear reason.
3079
+ """
3080
+ if not os.path.exists(file_path) or os.path.getsize(file_path) == 0:
3081
+ raise ValueError(f"downloaded file missing or empty: {file_path}")
3082
+ try:
3083
+ proc = subprocess.run(
3084
+ [
3085
+ "ffprobe",
3086
+ "-v",
3087
+ "error",
3088
+ "-select_streams",
3089
+ "v:0",
3090
+ "-show_entries",
3091
+ "stream=codec_type:format=duration",
3092
+ "-of",
3093
+ "json",
3094
+ file_path,
3095
+ ],
3096
+ capture_output=True,
3097
+ text=True,
3098
+ timeout=120,
3099
+ )
3100
+ except (OSError, subprocess.SubprocessError) as e:
3101
+ # ffprobe missing/errored: don't block the upload on our own check.
3102
+ print(f"[WARN] skipping pre-upload validation (ffprobe unavailable): {e}")
3103
+ return
3104
+ if proc.returncode != 0:
3105
+ raise ValueError(
3106
+ f"ffprobe could not read the file: {proc.stderr.strip()[:200]}"
3107
+ )
3108
+ try:
3109
+ meta = json.loads(proc.stdout or "{}")
3110
+ except ValueError:
3111
+ raise ValueError("ffprobe returned unparseable output")
3112
+ has_video = any(
3113
+ s.get("codec_type") == "video" for s in meta.get("streams") or []
3114
+ )
3115
+ try:
3116
+ duration = float((meta.get("format") or {}).get("duration") or 0)
3117
+ except (TypeError, ValueError):
3118
+ duration = 0.0
3119
+ if not has_video or duration <= 0:
3120
+ raise ValueError(
3121
+ f"downloaded file is not a valid video (video_stream={has_video}, duration={duration})"
3122
+ )
3123
+
3072
3124
  def process_video(
3073
3125
  self,
3074
3126
  url: str,
@@ -3132,6 +3184,9 @@ class MediaDownloaderToMediaCMS:
3132
3184
  print(f"Downloaded: {file_path}")
3133
3185
 
3134
3186
  try:
3187
+ # Reject unreadable downloads before upload (MediaCMS deletes files
3188
+ # ffprobe can't parse, which otherwise looks like a vanished upload).
3189
+ self._validate_downloaded_video(file_path)
3135
3190
  # Upload to MediaCMS
3136
3191
  if not playlist_info:
3137
3192
  print("Uploading to MediaCMS...")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.44.2"
3
+ version = "0.44.4"
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"
@@ -21,6 +21,9 @@ dependencies = [
21
21
  "pydantic>=2.0",
22
22
  "python-dateutil>=2.8",
23
23
  "yt-dlp>=2024.1",
24
+ # yt-dlp needs this to decrypt AES-128 HLS in-process (e.g. Tubi); without it
25
+ # it delegates to ffmpeg, whose static build segfaults on that path.
26
+ "pycryptodomex>=3.20",
24
27
  "openpyxl>=3.1",
25
28
  "requests>=2.31",
26
29
  "pyyaml>=6.0",