kryten-webqueue 0.6.5__tar.gz → 0.6.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 (67) hide show
  1. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/CHANGELOG.md +6 -0
  2. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/queue/shadow.py +14 -0
  4. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/static/css/main.css +27 -13
  5. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/queue/index.html +12 -2
  6. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/pyproject.toml +1 -1
  7. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/.github/workflows/python-publish.yml +0 -0
  8. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/.github/workflows/release.yml +0 -0
  9. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/.gitignore +0 -0
  10. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/README.md +0 -0
  11. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/config.example.json +0 -0
  12. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/deploy/kryten-webqueue.service +0 -0
  13. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/deploy/nginx-queue.conf +0 -0
  14. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/IMPLEMENTATION_SPEC.md +0 -0
  15. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/IMPL_API_GATE.md +0 -0
  16. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/IMPL_ECONOMY.md +0 -0
  17. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/IMPL_KRYTEN_PY.md +0 -0
  18. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/IMPL_ROBOT.md +0 -0
  19. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/PRE_PLAN_GAPS.md +0 -0
  20. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/docs/PRODUCT_PLAN.md +0 -0
  21. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/__init__.py +0 -0
  22. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/__main__.py +0 -0
  23. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/api_gate/__init__.py +0 -0
  24. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/api_gate/client.py +0 -0
  25. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/app.py +0 -0
  26. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/auth/__init__.py +0 -0
  27. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/auth/otp.py +0 -0
  28. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/auth/rate_limit.py +0 -0
  29. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/auth/session.py +0 -0
  30. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/catalog/__init__.py +0 -0
  31. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/catalog/db.py +0 -0
  32. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/catalog/images.py +0 -0
  33. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/catalog/sync.py +0 -0
  34. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/config.py +0 -0
  35. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/jobs/__init__.py +0 -0
  36. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/jobs/manager.py +0 -0
  37. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/playlists/__init__.py +0 -0
  38. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/playlists/fire.py +0 -0
  39. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/playlists/importer.py +0 -0
  40. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/playlists/scheduler.py +0 -0
  41. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/queue/__init__.py +0 -0
  42. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/queue/ordering.py +0 -0
  43. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/queue/poller.py +0 -0
  44. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/__init__.py +0 -0
  45. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/admin_jobs.py +0 -0
  46. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/admin_playlists.py +0 -0
  47. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/admin_queue.py +0 -0
  48. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/admin_schedules.py +0 -0
  49. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/auth.py +0 -0
  50. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/catalog.py +0 -0
  51. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/pages.py +0 -0
  52. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/queue.py +0 -0
  53. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/routes/user.py +0 -0
  54. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/static/js/main.js +0 -0
  55. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/admin/index.html +0 -0
  56. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/admin/playlists.html +0 -0
  57. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  58. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/admin/schedules.html +0 -0
  59. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/auth/login.html +0 -0
  60. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/base.html +0 -0
  61. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/catalog/browse.html +0 -0
  62. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  63. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  64. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/templates/user/dashboard.html +0 -0
  65. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/ws/__init__.py +0 -0
  66. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/ws/handler.py +0 -0
  67. {kryten_webqueue-0.6.5 → kryten_webqueue-0.6.6}/kryten_webqueue/ws/manager.py +0 -0
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ ## [0.6.6] - 2026-06-08
8
+
9
+ ### Changed
10
+
11
+ - **Queue view polish.** Three refinements to the Queue page: (1) **Predicted start times** now render reliably in the viewer's local timezone — the frontend defensively treats timezone-less timestamps as UTC before converting, so ETAs no longer risk being misread as server time. (2) **Now Playing card** is larger and easier to read — bigger cover art (128px), a larger title that wraps cleanly, and the elapsed/remaining times no longer wrap. (3) **The currently-playing item is now highlighted** in the queue list with an accent tint and ring. The now-playing playlist `uid` is resolved server-side (matching media id/type against the shadow playlist when CyTube's `changeMedia` payload omits it) so the matching queue item is identified reliably.
12
+
7
13
  ## [0.6.5] - 2026-06-08
8
14
 
9
15
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryten-webqueue
3
- Version: 0.6.5
3
+ Version: 0.6.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
@@ -203,6 +203,20 @@ class QueueShadow:
203
203
  np.setdefault("thumbnail_url", meta.get("thumbnail_url"))
204
204
  if not np.get("friendly_token"):
205
205
  np["friendly_token"] = meta.get("friendly_token")
206
+ # Ensure now-playing carries a playlist uid so the frontend can
207
+ # highlight the matching queue item. CyTube's changeMedia payload
208
+ # lacks a uid; recover it by matching media id/type against the
209
+ # shadow playlist (whose items do carry uids).
210
+ if np.get("uid") is None:
211
+ np_id = np.get("id")
212
+ np_type = np.get("type")
213
+ if np_id is not None:
214
+ for it in enriched_items:
215
+ if it.get("media_id") == np_id and (
216
+ np_type is None or it.get("media_type") == np_type
217
+ ):
218
+ np["uid"] = it.get("uid")
219
+ break
206
220
  state["now_playing"] = np
207
221
 
208
222
  return state
@@ -249,10 +249,10 @@ a:hover {
249
249
  .now-playing-card {
250
250
  background: var(--bg-card);
251
251
  border-radius: var(--radius);
252
- padding: 1.5rem;
252
+ padding: 2rem;
253
253
  display: flex;
254
- gap: 1rem;
255
- align-items: flex-start;
254
+ gap: 1.5rem;
255
+ align-items: center;
256
256
  /* Highlight the currently-playing item: subtle accent ring + glow. */
257
257
  border: 1px solid var(--accent);
258
258
  box-shadow: 0 0 0 1px rgba(108, 92, 231, 0.25), 0 6px 18px rgba(108, 92, 231, 0.18);
@@ -263,13 +263,16 @@ a:hover {
263
263
  }
264
264
  .np-info h3 {
265
265
  margin-bottom: 0.5rem;
266
+ font-size: 1.4rem;
267
+ line-height: 1.25;
268
+ overflow-wrap: anywhere;
266
269
  }
267
270
  .np-progress {
268
- height: 4px;
271
+ height: 6px;
269
272
  background: var(--border);
270
- border-radius: 2px;
273
+ border-radius: 3px;
271
274
  overflow: hidden;
272
- margin: 0.5rem 0;
275
+ margin: 0.75rem 0;
273
276
  }
274
277
  .progress-bar {
275
278
  height: 100%;
@@ -277,7 +280,7 @@ a:hover {
277
280
  transition: width 1s linear;
278
281
  }
279
282
  .np-time {
280
- font-size: 0.8rem;
283
+ font-size: 0.9rem;
281
284
  color: var(--text-secondary);
282
285
  }
283
286
 
@@ -304,6 +307,15 @@ a:hover {
304
307
  .queue-item-paid {
305
308
  border-left-color: var(--accent);
306
309
  }
310
+ .queue-item-now-playing {
311
+ border-left-color: var(--accent);
312
+ background: linear-gradient(90deg, rgba(108, 92, 231, 0.18), var(--bg-card) 60%);
313
+ box-shadow: inset 0 0 0 1px rgba(108, 92, 231, 0.35);
314
+ }
315
+ .queue-item-now-playing .qi-title {
316
+ color: var(--accent);
317
+ font-weight: 600;
318
+ }
307
319
  .qi-pos {
308
320
  font-size: 0.75rem;
309
321
  color: var(--text-secondary);
@@ -332,10 +344,10 @@ a:hover {
332
344
  display: block;
333
345
  }
334
346
  .np-cover {
335
- width: 96px;
336
- height: 96px;
347
+ width: 128px;
348
+ height: 128px;
337
349
  flex-shrink: 0;
338
- border-radius: 6px;
350
+ border-radius: 8px;
339
351
  overflow: hidden;
340
352
  background: var(--bg-elevated, #222);
341
353
  }
@@ -397,15 +409,17 @@ a:hover {
397
409
  color: var(--text-secondary);
398
410
  }
399
411
  .np-meta {
400
- font-size: 0.8rem;
412
+ font-size: 0.9rem;
401
413
  color: var(--text-secondary);
402
414
  margin: 0.25rem 0;
403
415
  }
404
416
  .np-times {
405
417
  display: flex;
406
418
  justify-content: space-between;
407
- font-size: 0.8rem;
408
- margin-top: 0.25rem;
419
+ gap: 1rem;
420
+ font-size: 0.9rem;
421
+ margin-top: 0.5rem;
422
+ white-space: nowrap;
409
423
  }
410
424
  .np-remaining {
411
425
  color: var(--text-secondary);
@@ -110,8 +110,10 @@ function renderQueue(state) {
110
110
  const totalDuration = state.items.reduce((sum, i) => sum + (i.duration_sec || 0), 0);
111
111
  sumEl.innerHTML = `<span>${state.items.length} items · ${formatTime(totalDuration)} total</span>`;
112
112
 
113
+ const nowUid = state.now_playing && state.now_playing.uid != null
114
+ ? String(state.now_playing.uid) : null;
113
115
  qlEl.innerHTML = state.items.map((item, i) => `
114
- <div class="queue-item ${item.is_pay ? 'queue-item-paid' : ''}" data-uid="${item.uid}">
116
+ <div class="queue-item ${item.is_pay ? 'queue-item-paid' : ''} ${nowUid != null && String(item.uid) === nowUid ? 'queue-item-now-playing' : ''}" data-uid="${item.uid}">
115
117
  <span class="qi-drag" title="Drag to reorder">☰</span>
116
118
  <span class="qi-pos">${i + 1}</span>
117
119
  <div class="qi-cover">${coverHtml(item)}</div>
@@ -166,7 +168,15 @@ function coverHtml(item) {
166
168
  }
167
169
 
168
170
  function formatEta(isoStr) {
169
- const d = new Date(isoStr);
171
+ // Server emits UTC ISO timestamps. If a value arrives without an explicit
172
+ // timezone designator, treat it as UTC so the browser converts it to the
173
+ // viewer's local timezone rather than misreading it as local time.
174
+ let s = String(isoStr);
175
+ if (!/[zZ]|[+-]\d{2}:?\d{2}$/.test(s)) {
176
+ s += 'Z';
177
+ }
178
+ const d = new Date(s);
179
+ if (isNaN(d.getTime())) return '';
170
180
  return d.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
171
181
  }
172
182
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.6.5"
3
+ version = "0.6.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"