annextube 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. annextube-0.14.0/.autorc +83 -0
  2. annextube-0.14.0/.gitignore +119 -0
  3. annextube-0.14.0/.jscpd.json +18 -0
  4. annextube-0.14.0/CHANGELOG.md +359 -0
  5. annextube-0.14.0/CLAUDE.md +230 -0
  6. annextube-0.14.0/CONTRIBUTING.md +200 -0
  7. annextube-0.14.0/LICENSE +20 -0
  8. annextube-0.14.0/PKG-INFO +220 -0
  9. annextube-0.14.0/README.md +132 -0
  10. annextube-0.14.0/annextube/__init__.py +0 -0
  11. annextube-0.14.0/annextube/__main__.py +6 -0
  12. annextube-0.14.0/annextube/_version.py +24 -0
  13. annextube-0.14.0/annextube/cli/__init__.py +0 -0
  14. annextube-0.14.0/annextube/cli/__main__.py +113 -0
  15. annextube-0.14.0/annextube/cli/aggregate.py +268 -0
  16. annextube-0.14.0/annextube/cli/backup.py +499 -0
  17. annextube-0.14.0/annextube/cli/build_search_index.py +90 -0
  18. annextube-0.14.0/annextube/cli/check.py +258 -0
  19. annextube-0.14.0/annextube/cli/collection.py +138 -0
  20. annextube-0.14.0/annextube/cli/completion.py +63 -0
  21. annextube-0.14.0/annextube/cli/curate_captions.py +449 -0
  22. annextube-0.14.0/annextube/cli/embed_config.py +116 -0
  23. annextube-0.14.0/annextube/cli/export.py +116 -0
  24. annextube-0.14.0/annextube/cli/generate_web.py +305 -0
  25. annextube-0.14.0/annextube/cli/info.py +130 -0
  26. annextube-0.14.0/annextube/cli/init.py +175 -0
  27. annextube-0.14.0/annextube/cli/init_user_config.py +51 -0
  28. annextube-0.14.0/annextube/cli/prepare_ghpages.py +534 -0
  29. annextube-0.14.0/annextube/cli/serve.py +290 -0
  30. annextube-0.14.0/annextube/cli/unannex.py +75 -0
  31. annextube-0.14.0/annextube/lib/__init__.py +0 -0
  32. annextube-0.14.0/annextube/lib/annex_utils.py +69 -0
  33. annextube-0.14.0/annextube/lib/archive_discovery.py +152 -0
  34. annextube-0.14.0/annextube/lib/cli_options.py +48 -0
  35. annextube-0.14.0/annextube/lib/config.py +900 -0
  36. annextube-0.14.0/annextube/lib/date_utils.py +103 -0
  37. annextube-0.14.0/annextube/lib/error_utils.py +31 -0
  38. annextube-0.14.0/annextube/lib/file_utils.py +84 -0
  39. annextube-0.14.0/annextube/lib/logging_config.py +137 -0
  40. annextube-0.14.0/annextube/lib/process_semaphore.py +142 -0
  41. annextube-0.14.0/annextube/lib/quota_manager.py +223 -0
  42. annextube-0.14.0/annextube/lib/range_server.py +146 -0
  43. annextube-0.14.0/annextube/lib/tsv_utils.py +111 -0
  44. annextube-0.14.0/annextube/lib/ytdlp_ratelimit.py +232 -0
  45. annextube-0.14.0/annextube/models/__init__.py +7 -0
  46. annextube-0.14.0/annextube/models/author.py +50 -0
  47. annextube-0.14.0/annextube/models/channel.py +75 -0
  48. annextube-0.14.0/annextube/models/curation.py +143 -0
  49. annextube-0.14.0/annextube/models/playlist.py +51 -0
  50. annextube-0.14.0/annextube/models/video.py +195 -0
  51. annextube-0.14.0/annextube/schema/extra_metadata.yaml +130 -0
  52. annextube-0.14.0/annextube/schema/models.json +691 -0
  53. annextube-0.14.0/annextube/services/__init__.py +0 -0
  54. annextube-0.14.0/annextube/services/archiver.py +2221 -0
  55. annextube-0.14.0/annextube/services/authors.py +209 -0
  56. annextube-0.14.0/annextube/services/caption_curator.py +837 -0
  57. annextube-0.14.0/annextube/services/collection.py +356 -0
  58. annextube-0.14.0/annextube/services/export.py +783 -0
  59. annextube-0.14.0/annextube/services/git_annex.py +845 -0
  60. annextube-0.14.0/annextube/services/llm_corrector.py +219 -0
  61. annextube-0.14.0/annextube/services/search_index.py +835 -0
  62. annextube-0.14.0/annextube/services/tsv_reader.py +184 -0
  63. annextube-0.14.0/annextube/services/youtube.py +1734 -0
  64. annextube-0.14.0/annextube/services/youtube_api.py +899 -0
  65. annextube-0.14.0/hatch_build.py +75 -0
  66. annextube-0.14.0/pyproject.toml +227 -0
  67. annextube-0.14.0/tests/conftest.py +81 -0
  68. annextube-0.14.0/tests/contract/__init__.py +0 -0
  69. annextube-0.14.0/tests/contract/test_backup_json.py +242 -0
  70. annextube-0.14.0/tests/contract/test_collection_cli.py +238 -0
  71. annextube-0.14.0/tests/e2e/README.md +289 -0
  72. annextube-0.14.0/tests/e2e/test_collection_e2e.py +277 -0
  73. annextube-0.14.0/tests/e2e/test_multi_channel.py +321 -0
  74. annextube-0.14.0/tests/e2e/test_web_ui.py +241 -0
  75. annextube-0.14.0/tests/e2e/verify_archive.py +182 -0
  76. annextube-0.14.0/tests/fixtures/sample_glossary.yaml +37 -0
  77. annextube-0.14.0/tests/fixtures/sample_karaoke.vtt +33 -0
  78. annextube-0.14.0/tests/integration/test_api_enhanced_metadata.py +483 -0
  79. annextube-0.14.0/tests/integration/test_checkpoint_commits.py +371 -0
  80. annextube-0.14.0/tests/integration/test_collection.py +308 -0
  81. annextube-0.14.0/tests/integration/test_comprehensive_backup.py +229 -0
  82. annextube-0.14.0/tests/integration/test_e2e_backup_features.py +251 -0
  83. annextube-0.14.0/tests/integration/test_incremental_backup.py +145 -0
  84. annextube-0.14.0/tests/integration/test_new_video_components.py +120 -0
  85. annextube-0.14.0/tests/integration/test_no_timestamp_commits.py +183 -0
  86. annextube-0.14.0/tests/integration/test_search_index_build.py +177 -0
  87. annextube-0.14.0/tests/integration/test_update_annexed_files.py +144 -0
  88. annextube-0.14.0/tests/test_component_mode_bug.py +79 -0
  89. annextube-0.14.0/tests/test_date_filtering.py +108 -0
  90. annextube-0.14.0/tests/test_tsv_refactoring.py +392 -0
  91. annextube-0.14.0/tests/unit/test_annex_utils.py +105 -0
  92. annextube-0.14.0/tests/unit/test_archive_discovery.py +270 -0
  93. annextube-0.14.0/tests/unit/test_atomic_file_write.py +179 -0
  94. annextube-0.14.0/tests/unit/test_batch_api_optimization.py +222 -0
  95. annextube-0.14.0/tests/unit/test_caption_curator.py +729 -0
  96. annextube-0.14.0/tests/unit/test_collection_config.py +122 -0
  97. annextube-0.14.0/tests/unit/test_collection_service.py +94 -0
  98. annextube-0.14.0/tests/unit/test_completion.py +100 -0
  99. annextube-0.14.0/tests/unit/test_config_precedence.py +127 -0
  100. annextube-0.14.0/tests/unit/test_curation_config.py +132 -0
  101. annextube-0.14.0/tests/unit/test_datalad_init.py +79 -0
  102. annextube-0.14.0/tests/unit/test_deploy_frontend_staleness.py +85 -0
  103. annextube-0.14.0/tests/unit/test_embed_config.py +156 -0
  104. annextube-0.14.0/tests/unit/test_error_reporting.py +89 -0
  105. annextube-0.14.0/tests/unit/test_export_channel_json.py +417 -0
  106. annextube-0.14.0/tests/unit/test_export_descriptions.py +215 -0
  107. annextube-0.14.0/tests/unit/test_git_annex_channel_json_timestamps.py +250 -0
  108. annextube-0.14.0/tests/unit/test_git_annex_metadata.py +156 -0
  109. annextube-0.14.0/tests/unit/test_git_annex_timestamp_filter.py +188 -0
  110. annextube-0.14.0/tests/unit/test_hierarchical_video_paths.py +502 -0
  111. annextube-0.14.0/tests/unit/test_init.py +133 -0
  112. annextube-0.14.0/tests/unit/test_new_video_detection.py +87 -0
  113. annextube-0.14.0/tests/unit/test_playlist_model.py +83 -0
  114. annextube-0.14.0/tests/unit/test_playlist_symlinks.py +619 -0
  115. annextube-0.14.0/tests/unit/test_process_semaphore.py +115 -0
  116. annextube-0.14.0/tests/unit/test_quota_estimator.py +133 -0
  117. annextube-0.14.0/tests/unit/test_quota_manager.py +296 -0
  118. annextube-0.14.0/tests/unit/test_retroactive_curation.py +102 -0
  119. annextube-0.14.0/tests/unit/test_sdist_frontend.py +92 -0
  120. annextube-0.14.0/tests/unit/test_search_index.py +809 -0
  121. annextube-0.14.0/tests/unit/test_tsv_escaping.py +147 -0
  122. annextube-0.14.0/tests/unit/test_unavailable_video_filtering.py +771 -0
  123. annextube-0.14.0/tests/unit/test_video_model.py +90 -0
  124. annextube-0.14.0/tests/unit/test_youtube_api_client.py +428 -0
  125. annextube-0.14.0/tests/unit/test_youtube_api_quota_handling.py +344 -0
  126. annextube-0.14.0/tests/unit/test_youtube_service_playlist.py +220 -0
  127. annextube-0.14.0/tests/unit/test_ytdlp_ratelimit.py +224 -0
  128. annextube-0.14.0/tox.ini +149 -0
  129. annextube-0.14.0/web/assets/index.css +1 -0
  130. annextube-0.14.0/web/assets/index.js +17 -0
  131. annextube-0.14.0/web/index.html +14 -0
@@ -0,0 +1,83 @@
1
+ {
2
+ "baseBranch": "master",
3
+ "noVersionPrefix": false,
4
+ "plugins": [
5
+ "git-tag",
6
+ [
7
+ "exec",
8
+ {
9
+ "afterRelease": "python -m build && twine upload dist/*"
10
+ }
11
+ ],
12
+ "released"
13
+ ],
14
+ "labels": [
15
+ {
16
+ "name": "release-major",
17
+ "releaseType": "major",
18
+ "changelogTitle": "💥 Breaking Change",
19
+ "description": "Increment the major version when merged",
20
+ "color": "C5000B"
21
+ },
22
+ {
23
+ "name": "release-minor",
24
+ "releaseType": "minor",
25
+ "changelogTitle": "🚀 Enhancement",
26
+ "description": "Increment the minor version when merged",
27
+ "color": "F1A60E"
28
+ },
29
+ {
30
+ "name": "release-patch",
31
+ "releaseType": "patch",
32
+ "changelogTitle": "🐛 Bug Fix",
33
+ "description": "Increment the patch version when merged",
34
+ "color": "870048"
35
+ },
36
+ {
37
+ "name": "release",
38
+ "releaseType": "release",
39
+ "description": "Create a release when this PR is merged",
40
+ "color": "16a34a"
41
+ },
42
+ {
43
+ "name": "skip-release",
44
+ "releaseType": "skip",
45
+ "description": "Preserve the current version when merged",
46
+ "color": "bf5416"
47
+ },
48
+ {
49
+ "name": "internal",
50
+ "releaseType": "none",
51
+ "changelogTitle": "🏠 Internal",
52
+ "description": "Changes only affect the internal API",
53
+ "color": "696969"
54
+ },
55
+ {
56
+ "name": "documentation",
57
+ "releaseType": "none",
58
+ "changelogTitle": "📝 Documentation",
59
+ "description": "Changes only affect the documentation",
60
+ "color": "cfd3d7"
61
+ },
62
+ {
63
+ "name": "tests",
64
+ "releaseType": "none",
65
+ "changelogTitle": "🧪 Tests",
66
+ "description": "Add or improve existing tests",
67
+ "color": "e4e669"
68
+ },
69
+ {
70
+ "name": "performance",
71
+ "releaseType": "patch",
72
+ "changelogTitle": "🏎 Performance",
73
+ "description": "Improve performance of an existing feature",
74
+ "color": "f4b2d8"
75
+ },
76
+ {
77
+ "name": "released",
78
+ "releaseType": "none",
79
+ "description": "This PR has been released",
80
+ "color": "84cc16"
81
+ }
82
+ ]
83
+ }
@@ -0,0 +1,119 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ pip-wheel-metadata/
19
+ share/python-wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+ MANIFEST
24
+
25
+ # Virtual environments
26
+ .venv/
27
+ venv/
28
+ ENV/
29
+ env/
30
+ .virtualenv
31
+ uv.lock
32
+
33
+ # Testing
34
+ .tox/
35
+ .nox/
36
+ .coverage
37
+ .coverage.*
38
+ .cache
39
+ nosetests.xml
40
+ coverage.xml
41
+ *.cover
42
+ *.log
43
+ .pytest_cache/
44
+ htmlcov/
45
+
46
+ # Type checking
47
+ .mypy_cache/
48
+ .dmypy.json
49
+ dmypy.json
50
+ .pyre/
51
+ .pytype/
52
+
53
+ # IDEs
54
+ .vscode/
55
+ .idea/
56
+ .vim/
57
+ .viminfo
58
+ *.swp
59
+ *.swo
60
+ *~
61
+ .DS_Store
62
+ Thumbs.db
63
+
64
+ # Project specific
65
+ .annextube/
66
+ test-archive/
67
+ test-archives/
68
+ *.tmp
69
+
70
+ # Node.js (frontend)
71
+ node_modules/
72
+ .npm/
73
+ npm-debug.log*
74
+ yarn-debug.log*
75
+ yarn-error.log*
76
+ .lesshst
77
+
78
+ # Build artifacts
79
+ *.min.js
80
+ *.min.css
81
+ web/
82
+ demo/
83
+ frontend/dist/
84
+ frontend/build/
85
+ pagefind/
86
+
87
+ # Environment files
88
+ .env
89
+ .env.local
90
+ .env.*.local
91
+ *.key
92
+ *.pem
93
+ credentials.json
94
+
95
+ # Documentation
96
+ docs/_build/
97
+ docs/.hugo_build.lock
98
+
99
+ # Shell history and local config
100
+ .bash_history
101
+ .config/ # User config created during testing
102
+ .anaconda/
103
+ .conda/
104
+ .claude/settings.local.json
105
+ annextube/_version.py
106
+ .deno
107
+ .local
108
+ .worktrees
109
+ .config
110
+ .ssh
111
+
112
+ # Various files claude likes to create
113
+ CHANGES_SUMMARY.md
114
+ DEMO_SETUP.md
115
+ EXAMPLE_UNAVAILABLE_FILTERING.md
116
+
117
+ # no duct logs here -- only for tests etc
118
+ .duct
119
+ .claude/worktrees
@@ -0,0 +1,18 @@
1
+ {
2
+ "threshold": 0,
3
+ "reporters": ["console"],
4
+ "ignore": [
5
+ "**/node_modules/**",
6
+ "**/.git/**",
7
+ "**/dist/**",
8
+ "**/.tox/**",
9
+ "**/.venv/**",
10
+ "**/__pycache__/**",
11
+ "**/web/**",
12
+ "**/.specify/**",
13
+ "**/specs/**",
14
+ "**/tools/**"
15
+ ],
16
+ "minTokens": 50,
17
+ "minLines": 5
18
+ }
@@ -0,0 +1,359 @@
1
+ # v0.14.0 (Fri Sep 04 2026)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Searchable video descriptions + Metadata/Full search modes (FR-042d–g) [#6](https://github.com/con/annextube/pull/6) ([@claude](https://github.com/claude) [@yarikoptic](https://github.com/yarikoptic) [@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
6
+ - Flag approximate caption search matches and reorder results [#7](https://github.com/con/annextube/pull/7) ([@claude](https://github.com/claude) [@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
7
+
8
+ #### ⚠️ Pushed to `master`
9
+
10
+ - fix(release): use releaseType "none" for the "released" label ([@yarikoptic](https://github.com/yarikoptic))
11
+
12
+ #### 🏠 Internal
13
+
14
+ - feat(release): introduce intuit/auto for automated releases [#8](https://github.com/con/annextube/pull/8) ([@yarikoptic](https://github.com/yarikoptic))
15
+
16
+ #### Authors: 3
17
+
18
+ - Claude ([@claude](https://github.com/claude))
19
+ - GitMate for @yarikoptic ([@yarikoptic-gitmate](https://github.com/yarikoptic-gitmate))
20
+ - Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic))
21
+
22
+ ---
23
+
24
+ # Changelog
25
+
26
+ <!-- Entries from v0.14.0 onward are auto-generated by intuit/auto. -->
27
+ <!-- Entries prior to v0.14.0 use Keep a Changelog format. -->
28
+
29
+ ## [0.13.0] - 2026-07-13
30
+
31
+ ### Added
32
+
33
+ - **Multi-channel collection management (feature 003)**: DataLad superdataset workflow for archives that aggregate multiple channels — `annextube collection add`, `annextube aggregate`, breadcrumb navigation in the web UI, and the corresponding contract / integration / E2E test suites. Ships alongside a spec artifact under `.specify/specs/003-multi-channel-collections/`
34
+ - **Retroactive caption curation during backup** (FR-062a): backups can now run the caption curation pipeline over existing archived videos, not just newly-fetched ones
35
+ - **`annextube completion` command** (FR-057i, T141): outputs shell completion scripts for bash, zsh, and fish via Click's `ShellComplete.source()`; auto-detects the current shell from `$SHELL`
36
+ - **MKV video playback: browser-specific error messages**: the web player now explains which browsers can natively decode MKV and links users to workarounds. Feature spec `.specify/specs/002-mkv-video-playback/`
37
+ - **GitHub Actions test workflow**: tox-based Python matrix (3.10 – 3.14) plus lint / type / coverage jobs, running on every push and PR
38
+ - **JSON backup output (T030) + batch API optimization (T039)**: completes remaining MVP gaps identified in `speckit.analyze`
39
+
40
+ ### Fixed
41
+
42
+ - **yt-dlp playlist pagination truncation**: older yt-dlp releases silently truncated large YouTube playlists at ~100 entries even though `playlist_count` reported the true size, causing videos to appear-and-disappear across runs. Fixed upstream in yt-dlp/yt-dlp#16948; minimum requirement bumped to `yt-dlp>=2026.07.04`. Layered defence in the archive too: `Archiver._save_playlist_metadata` refuses to persist truncated lists, and when a YouTube Data API key is configured `YouTubeService.get_playlist_metadata` cross-checks against `playlistItems.list` and prefers the API's authoritative list
43
+ - **YouTube API robustness**: HTTP error bodies decoded with `errors='replace'`; mid-pagination quota-exceeded events resume from the current `pageToken` instead of restarting from page 1 (applies to `get_playlist_video_ids` and `fetch_comments`)
44
+ - **`annextube init` on an existing git-annex repo**: skip repo init when the target is already a git-annex working tree, and configure `annex.security.allowed-*` policies so the reused repo doesn't drop content
45
+ - **Single-channel archive detection requires `.annextube/config.toml`**: previously a bare git repo could be misdetected as a single-channel archive
46
+ - **Thumbnail download failures downgraded to warnings**: no longer aborts a backup
47
+
48
+ ### Changed
49
+
50
+ - **Zero-tolerance code duplication**: dedicated jscpd tox env with threshold 0 %; the entire codebase now reports 0 clones after helper extraction (batch prefetch, video-processing loop, symlink writer, unavailable-video recorder, playlist-tab-entry parser, atomic-write prep, shared `--output-dir` Click decorator)
51
+ - **Constitution amended to v1.5.0**: adds Principle XIII "DataLad-Native Operations"
52
+
53
+ ## [0.12.0] - 2026-03-11
54
+
55
+ ### Added
56
+
57
+ - **`annextube init` modular flags**: `--curation`, `--search`, `--enable-all` for selective feature initialization
58
+ - **`[full]` extra**: `pip install annextube[full]` installs all optional dependencies
59
+ - **Auto-build search index after backup**: Pagefind caption index is rebuilt automatically when captions change
60
+ - **Real-pagefind integration tests**: 7 tests exercise the actual pagefind binary with 30s timeout to catch IPC stalls
61
+ - **WCAG 2.1 AA accessibility tests**: Automated contrast-ratio and a11y checks for the web frontend
62
+ - **Zero-tolerance code duplication**: jscpd enforced at threshold 0 across the codebase
63
+
64
+ ### Fixed
65
+
66
+ - **Pagefind IPC deadlock on DataLad subdatasets**: The pagefind binary hangs when its output directory contains git-annex symlinks (read-only targets) from a previous build. Fixed by writing to a clean temp directory, then syncing into the subdataset
67
+ - **Pagefind IPC pipe interlocking**: Separate stderr from stdout, bypass the upstream library's fragile polling task, use direct IPC with 300s timeout for WriteFiles
68
+ - **Color contrast violation**: Fixed insufficient contrast ratio in frontend CSS
69
+
70
+ ### Changed
71
+
72
+ - **DataLad is now a core dependency**: Removed plain git submodule fallback; all subdataset operations use DataLad exclusively
73
+ - **DataLad cfg_text2git for pagefind subdataset**: Use DataLad's built-in text2git configuration instead of manual .gitattributes
74
+ - **yt-dlp[default] co-installs challenge solver**: No longer need separate `yt-dlp-youtube-oauth2` or manual solver setup
75
+ - **Consolidate scripts/ into tools/**: All helper scripts live in `tools/`, tox.ini calls them directly
76
+ - **Spec harmonization**: Aligned specification artifacts with implementation reality
77
+
78
+ ## [0.11.0] - 2026-03-04
79
+
80
+ ### Added
81
+
82
+ - **Full-text caption search via Pagefind**: Search across all video captions with highlighted results and click-to-seek
83
+ - **`build-search-index` CLI command**: Standalone pagefind index generation
84
+ - **DataLad subdataset management for pagefind index**: Automatic subdataset creation with cfg_text2git
85
+ - **Caption search UX polish**: Improved search result display and git submodule for pagefind frontend assets
86
+
87
+ ### Fixed
88
+
89
+ - **Duplicate pagefind/ output at archive root**: Index files now go only to `web/pagefind/`
90
+ - **Pagefind 100ms polling sleep**: Removed upstream library's polling delay causing ~140x slowdown
91
+ - **Curated caption writes on annexed files**: Use AtomicFileWriter to handle symlink-to-readonly targets
92
+ - **Pagefind URL resolution**: Fixed incorrect paths in subdirectory deployments
93
+
94
+ ## [0.10.0] - 2026-03-02
95
+
96
+ ### Added
97
+
98
+ - **Aggregated error reporting**: Record all errors during backup and fail at end with summary instead of aborting on first error
99
+
100
+ ### Fixed
101
+
102
+ - **metadata.json writes on annexed files**: Use UTF-8 encoding and handle annexed file targets correctly
103
+ - **git diff quoting for non-ASCII paths**: Fixed diff parsing for filenames with special characters
104
+ - **Unavailable video handling**: Skip downloads for known-unavailable videos in incremental channel backup
105
+ - **Thumbnail re-download**: Skip unless in force mode
106
+ - **TSV commit failure**: Target specific files and deduplicate commit logic
107
+ - **Playlist-exclusive detection**: Include existing and unavailable video IDs
108
+
109
+ ### Changed
110
+
111
+ - **Stop writing volatile timestamps to channel.json**: Removed last_sync/fetched_at fields that caused unnecessary commits
112
+
113
+ ## [0.9.0] - 2026-02-18
114
+
115
+ ### Added
116
+
117
+ - **`embed-config` command**: Propagate shared TOML settings (e.g. `[curation]` glossary) from a super-dataset config into subdataset configs with comment-preserving deep merge. Supports `--existing keep` (default) and `--existing update` modes. Skips `[[sources]]` (per-subdataset)
118
+ - **Caption curation design spec**: Planning document for the curation pipeline
119
+ - **`[curation]` section in config template**: Generated configs now include documented curation settings (glossary, fuzzy matching, LLM, audio alignment)
120
+ - **Glossary discovery with parent collation**: `glossary_path` + `glossary_collate_parents` in `[curation]` config for multi-channel setups — walk up parent dirs and merge all glossaries found
121
+ - **Caption curation pipeline**: 8-stage ASR correction (`curate-captions` command) — glossary regex, LLM corrections, fuzzy matching, filler removal, ASR artifact fixes, sentence segmentation, cue chunking, timestamp restoration
122
+ - **Clone command panel**: Web UI header button to clone the archive repository
123
+ - **`tomlkit` dependency**: Comment-preserving TOML editing for `embed-config`
124
+ - **`types-PyYAML` dev dependency**: Fix mypy type checking for yaml imports
125
+
126
+ ### Fixed
127
+
128
+ - **`curate-captions` ignoring archive config for VIDEO_PATH**: Direct video directory mode was creating bare `CurationConfig()` defaults instead of loading `.annextube/config.toml`, silently ignoring `glossary_path` and other `[curation]` settings
129
+ - **Transcript language selector reverting**: Fixed CaptionBrowser resetting user language picks on reactive updates
130
+ - **Clone command UI**: Fixed reactive updates, tab switching, and inline layout
131
+ - **Curated captions not default in transcript browser**: CaptionBrowser now auto-selects the curated variant (e.g. `en-curated`) over the base language; `en-curated` displays as "EN (curated)"
132
+
133
+ ### Changed
134
+
135
+ - **Skip already-archived videos during playlist backup**: Incremental playlist backup no longer re-processes existing videos
136
+
137
+ ## [0.8.0] - 2026-02-16
138
+
139
+ ### Added
140
+
141
+ - **yt-dlp rate-limit detection and concurrency limiting**: Detects YouTube 429/rate-limit responses and retries with exponential backoff. Cross-process concurrency limiting via file locks prevents parallel yt-dlp calls from triggering rate limits
142
+ - **`tox -e sdist-check`**: Automated pre-release verification — builds sdist, installs in clean venv, verifies built frontend is included and `generate-web` works
143
+ - **`tox -e full`**: Run all non-network tests with all optional deps (including playwright)
144
+ - **Pre-release sdist verification checklist**: Documented manual tarball inspection steps
145
+
146
+ ### Fixed
147
+
148
+ - **Catastrophically slow sdist build**: `skip-excluded-dirs = true` prevents hatchling from walking into multi-GB cache directories (201s → 0.04s)
149
+ - **Built frontend missing from sdist**: `force-include` and `artifacts` config ensures `web/` is included; dropped frontend source from sdist
150
+ - **Skip known-unavailable videos during playlist backup**: Avoid re-attempting videos that are known to be unavailable
151
+ - **Rate-limit and concurrency implementation gaps**: Fixed three issues in the initial rate-limit implementation
152
+ - **e2e test fixture**: Added pytest-playwright dependency
153
+
154
+ ## [0.7.0] - 2026-02-14
155
+
156
+ ### Added
157
+
158
+ - **Extra metadata with DataCite vocabulary**: User-managed `extra_metadata.json` per video for supplemental metadata (related slides, papers, code repos). LinkML schema defines `RelatedResource` using DataCite v4.6 relationType and resourceTypeGeneral vocabularies. Merged additively into `metadata.json` during export (never overwrites archiver-managed fields)
159
+ - **Related resources display**: Frontend shows linked resources in video details with type badges (`[Presentation slides]`, `[Software]`, etc.)
160
+ - **Human-readable caption labels**: Caption language selector shows readable labels for yt-dlp variant suffixes (e.g. `en-cur1` → `EN (curated)`, `en-orig` → `EN (original)`)
161
+ - **Caption download button**: Download VTT caption files directly from the transcript panel header
162
+ - **Deno as core dependency**: Required for yt-dlp YouTube JS challenge solver
163
+ - **Skip network tests by default**: pytest addopts `-m "not network"` prevents flaky CI from YouTube bot detection. Use `tox -e network` for full network test sweep
164
+
165
+ ### Fixed
166
+
167
+ - **Phased backup_channel() flow**: Refactored into discovery → fetch → store → link phases, fixing O(P^2) TSV regeneration and symlink overhead
168
+ - **Version injection after appVersion refactor**: Fixed placeholder matching in `deploy_frontend()` after Vite inlining changed from `v0.0.0-unknown` to bare `0.0.0-unknown`
169
+ - **TypeError on None playlist titles**: Handle yt-dlp returning None for playlist titles
170
+ - **Empty videos.tsv crash**: Handle missing `videos/` directory gracefully
171
+ - **Frontend build warnings**: A11y labels, version variable reference, video caption attributes
172
+ - **Encoding consistency**: Use `encoding="utf-8"` instead of `text=True` in all subprocess calls
173
+ - **yt-dlp options consistency**: Use `_get_ydl_opts()` for all yt-dlp calls, apply env vars in UserConfig
174
+
175
+ ### Changed
176
+
177
+ - **Header renamed to AnnexTube**: Links to GitHub repo https://github.com/con/annextube
178
+ - **ejs:github enabled by default**: Remote JavaScript components for yt-dlp challenge solver
179
+ - **Test infrastructure**: pytest tmp_path fixture, datalad in test deps, playwright skip when missing, dedicated network test environment
180
+
181
+ ## [0.6.0] - 2026-02-14
182
+
183
+ ### Added
184
+
185
+ - **URL permalink for caption search state**: Shared links now also restore caption search context — query, case-sensitive/regex/filter toggles, and match position. URL params `q`, `cs`, `re`, `filter`, `match` are appended alongside existing player params (e.g. `#/channel/X/video/Y?tab=local&t=90&lang=en&q=neuroimaging&cs=1&re=1&filter=1&match=3`). Match position is restored on first load, then resets normally on new searches.
186
+
187
+ ## [0.5.0] - 2026-02-13
188
+
189
+ ### Added
190
+
191
+ - **URL permalink for video player state**: Shared links now restore the full viewing context — active player tab (archive/youtube), wide mode, transcript visibility, playback position, and caption language. URL params are appended to the hash route (e.g. `#/channel/X/video/Y?tab=youtube&wide=1&t=90&transcript=0&lang=es`). Only non-default values are written, keeping URLs clean. Uses `history.replaceState` to avoid history pollution.
192
+
193
+ ## [0.4.0] - 2026-02-13
194
+
195
+ ### Added
196
+
197
+ - **Interactive caption/transcript browser**: Side-by-side panel next to video player with full transcript display, click-to-seek, and active cue highlighting synced to playback position
198
+ - **Transcript search**: Search within captions with case-sensitive (C), regex (.*), and filter (F) modes. Filter mode hides non-matching cues; dim mode preserves timeline context. Enter/Shift+Enter keyboard navigation between matches with N/M position indicator
199
+ - **WebVTT parser with auto-caption dedup**: Zero-dependency parser handles YouTube auto-caption rolling 3-cue pattern (display→snapshot→carry-over), merging overlapping cues and stripping carry-over lines. Reduces a 1-hour video from ~4000 raw cues to ~1400 clean cues
200
+ - **Wide/theater mode**: Toggle (button or `t` key) to use full browser width for video+transcript layout, persisted in localStorage
201
+ - **Language selector**: Switch between available caption languages in the transcript panel
202
+ - **Auto-scroll with manual override**: Transcript auto-scrolls to active cue during playback, pauses on manual scroll, re-enables after 5s idle or cue click. "Resume auto-scroll" button when disabled
203
+ - **GitHub Pages sharing**: `annextube unannex` command and `prepare-ghpages` workflow for sharing archives via GitHub Pages
204
+ - **Frontend download button**: Download local video files directly from the player
205
+ - **YouTube external links**: "View on YouTube" button with source URL
206
+
207
+ ### Fixed
208
+
209
+ - **Per-playlist videos.tsv**: Use `os.walk` to follow directory symlinks when indexing playlist videos
210
+ - **Playlist symlink indexing**: Fixed per-playlist videos.tsv generation
211
+ - **DataLoader archive root discovery**: Probe ascending paths at runtime instead of assuming fixed `../` relative path, enabling deployment at any URL depth
212
+ - **Click-to-seek scroll glitch**: Set activeCueIndex immediately on cue click to prevent afterUpdate scrolling to the old active cue while the async seek completes
213
+ - **Search regex statefulness**: Removed `g` flag from shared searchRegex — `RegExp.test()` with `g` advances `lastIndex`, breaking match detection in loops across different strings
214
+ - **Wide→normal height revert**: CSS Grid `align-items: start` breaks the circular dependency where `--player-height` was measured from the grid-row height inflated by the caption browser
215
+ - **Search nav button clipping**: Redesigned as two-row layout — search options on row 1, navigation (Filter + Prev/Next) on conditional row 2
216
+ - **Filter→unfilter scroll position**: Scroll to current match when exiting filter mode instead of jumping to top
217
+ - **Caption file path**: Fixed `getCaptionPath` in DataLoader to use `video.{lang}.vtt` naming (was `caption_{lang}.vtt`)
218
+ - **Missing captions in transcript browser**: Reconcile `captions_available` in metadata.json with actual VTT files on disk during export. Fixes videos where captions were downloaded but metadata wasn't re-saved
219
+ - **YouTube customUrl field**: Strip leading `@` from API response
220
+
221
+ ### Changed
222
+
223
+ - **Simplified unannex**: Replaced 632-line implementation with thin git-annex wrapper
224
+ - **Version injection**: Use `0.0.0-unknown` placeholder in frontend build, replaced at deploy time by `deploy_frontend()`
225
+ - **Video+transcript layout**: CSS Grid with `align-items: start` and dynamic `--player-height` variable ensures transcript panel matches video height exactly
226
+
227
+ ## [0.3.0] - 2026-02-11
228
+
229
+ ### Added
230
+
231
+ - **Batch YouTube API calls**: Pre-fetch statistics and metadata in bulk instead of per-video API calls
232
+ - **`--comments-depth` CLI option**: Override comments depth at backup time (-1=unlimited, 0=disabled, N=limit)
233
+ - **DataLad dataset support**: `--datalad` flag for `init` command to create DataLad-managed archives
234
+ - **Version display in web UI**: Show annextube version in the header, injected during `generate-web`
235
+ - **Channel permalink routing**: Direct URLs to channels and per-channel playlist support in web UI
236
+ - **TSV regeneration for multi-channel collections**: `--update=tsv_metadata` works across collections
237
+ - **`serve --regenerate=web`**: Allow creating the web directory if it doesn't exist yet
238
+
239
+ ### Fixed
240
+
241
+ - **Incremental backup discarding new videos**: Social date window filter was incorrectly applied to new video discovery in incremental mode, silently dropping videos published outside the 1-week window
242
+ - **Frontend URL state feedback loop**: Changing any filter caused a hashchange → restoreFromURL → re-render cycle, visibly resetting selections. Fixed with `history.replaceState()` instead of `window.location.hash`
243
+ - **URL defaults pollution**: Default sort params (`sort=date&dir=desc`) no longer appended to clean URLs
244
+ - **Video player paths at subpath deployments**: Absolute `/videos/...` paths replaced with relative `../videos/...` for correct resolution at any deployment subpath
245
+ - **Video player reactivity**: Fixed state not updating when video prop changes
246
+ - **Video availability checking**: Use HEAD requests instead of relying on TSV download_status; include 'tracked' status; centralized checking logic
247
+ - **VideoPlayer thumbnail fallback**: Use YouTube CDN thumbnail when video not downloaded locally
248
+ - **Video metadata/comments loading**: Use file_path from video object instead of reconstructing paths
249
+ - **Browser back/forward navigation**: Fixed multi-channel video metadata loading on history navigation
250
+ - **Page reloads and playlist selection**: Fixed routing to preserve context and support nested routes
251
+ - **TSV field escaping**: Properly escape newlines and special characters in TSV output
252
+ - **Unicode encoding errors**: Fixed encoding issues in channel avatar display
253
+ - **Metadata sync**: Sync metadata.json download_status with actual file system state during TSV generation
254
+
255
+ ### Changed
256
+
257
+ - **Asset filenames**: Vite build now produces clean filenames (`index.js`, `index.css`) without content hashes
258
+ - **FilterPanel initialization**: Uses Svelte `tick()` for clean reactive cycle during mount
259
+
260
+ ## [0.2.2] - 2026-02-09
261
+
262
+ ### Added
263
+
264
+ - **Multi-channel collections**: Hierarchical structure with channels.tsv, aggregate/export/generate-web/serve commands
265
+ - **Auto-generate channel.json**: Automatically created during backup for seamless multi-channel integration
266
+ - **YouTube API channel metadata**: Three-tier extraction (API → yt-dlp → archive) for complete channel information
267
+ - **Channel avatar download**: Automatic download with MIME type detection and git-annex tracking
268
+ - **Timestamp filtering for channel.json**: Prevents unnecessary commits when only timestamps change
269
+ - **Quota exceeded auto-retry**: Automatic wait and retry when YouTube API quota is exceeded
270
+ - **Periodic checkpoint commits**: Configurable intermediate commits during long backup operations with interruption recovery
271
+ - **Configurable playlist symlink patterns**: Use {video_id}, {video_title}, {position} in playlist symlinks
272
+ - **Archive discovery helpers**: Central discovery system for single-channel vs multi-channel detection
273
+
274
+ ### Fixed
275
+
276
+ - **Playlist video reprocessing**: Skip reprocessing existing videos in 'playlists' update mode
277
+ - **Caption download for existing videos**: Properly download captions when adding to existing archive
278
+ - **Incremental playlist optimization**: Skip unavailable videos in incremental mode for faster processing
279
+ - **Path pattern validation**: Fail early on unknown placeholders in video_path_pattern
280
+ - **E2E test environment**: Use 'uv run annextube' to ensure correct environment activation
281
+ - **Type checking**: Complete mypy compliance across all modules
282
+ - **Linting**: Full ruff compliance across all modules
283
+
284
+ ### Changed
285
+
286
+ - **Default include-playlists**: Changed from 'none' to 'all' for better discoverability
287
+ - **Default include-podcasts**: Changed from 'none' to 'all' for consistency
288
+ - **Default playlist path pattern**: Changed from {playlist_id} to {playlist_title} for readability
289
+ - **Config template formatting**: Simplified double-brace escaping for better readability
290
+
291
+ ### Documentation
292
+
293
+ - **Quota handling guide**: Complete documentation for YouTube API quota management
294
+ - **Checkpoint system guide**: Documentation for periodic commits and recovery
295
+ - **Multi-channel collections**: End-to-end guide for creating and managing collections
296
+ - **yt-dlp challenge solver**: Documented dependency issues and solutions
297
+
298
+ ## [0.2.0] - 2026-02-05
299
+
300
+ ### Added
301
+
302
+ - **Incremental social data updates**: Efficient API-optimized updates with statistics gate and early stopping (96% API request reduction)
303
+ - **Containerization support**: Complete Podman/Docker/Singularity recipes with documentation
304
+ - **Progress indicators**: Real-time feedback for long-running playlist/channel fetches
305
+ - **Video file validation**: Magic byte detection to identify HTML error pages disguised as videos
306
+ - **YouTube Data API integration**: Enhanced metadata and comment fetching with yt-dlp fallback
307
+ - **Per-source component configuration**: Override global settings per channel/playlist
308
+ - **User-wide configuration**: Cookies, proxy, network settings in ~/.config/annextube/config.toml
309
+ - **Hierarchical video structure**: Year/month subdirectories for better organization
310
+ - **Range server with annextube serve**: Local playback with seeking support
311
+ - **Comprehensive E2E test suite**: Playwright tests for web UI and complete workflows
312
+ - **Full web frontend**: Search, filter, sort, video player with local/YouTube tabs
313
+
314
+ ### Fixed
315
+
316
+ - **Nested directory bug**: git-annex addurl with relative paths creating nested structures
317
+ - **EJS solver integration**: Support for authenticated access with cookies + deno
318
+ - **Playlist creation bug**: Ensure playlists are created with correct video associations
319
+ - **Download status detection**: Proper differentiation between tracked and downloaded videos
320
+ - **Video player issues**: Blank screen, iframe sizing, seeking with range requests
321
+ - **git-annex addurl**: Remove --no-raw flag to avoid conflicts with --fast
322
+ - **Cookie path handling**: Remove quotes from git-annex cookie configuration
323
+ - **Code quality**: Complete mypy and ruff linting fixes
324
+
325
+ ### Changed
326
+
327
+ - **Test infrastructure**: Dedicated AnnexTube test channel with 10 videos and 5 playlists
328
+ - **E2E browser setup**: Avoid sudo prompts by checking existing installations
329
+ - **Default update mode**: Changed to all-incremental (videos + social data updates)
330
+
331
+ ### Documentation
332
+
333
+ - Complete frontend MVP documentation with phase-by-phase progress
334
+ - Container deployment guide for Podman/Docker/Singularity
335
+ - User configuration documentation with cookie and network settings examples
336
+
337
+ ## [0.1.0] - 2026-01-28
338
+
339
+ Initial release with core YouTube archival functionality:
340
+ - Channel and playlist backup
341
+ - git-annex integration for efficient storage
342
+ - Metadata extraction (JSON + TSV)
343
+ - Caption and thumbnail downloads
344
+ - Comment fetching
345
+ - Basic web UI for browsing archives
346
+
347
+ [0.12.0]: https://github.com/con/annextube/compare/v0.11.0...v0.12.0
348
+ [0.11.0]: https://github.com/con/annextube/compare/v0.10.0...v0.11.0
349
+ [0.10.0]: https://github.com/con/annextube/compare/v0.9.0...v0.10.0
350
+ [0.9.0]: https://github.com/con/annextube/compare/v0.8.0...v0.9.0
351
+ [0.8.0]: https://github.com/con/annextube/compare/v0.7.0...v0.8.0
352
+ [0.7.0]: https://github.com/con/annextube/compare/v0.6.0...v0.7.0
353
+ [0.6.0]: https://github.com/con/annextube/compare/v0.5.0...v0.6.0
354
+ [0.5.0]: https://github.com/con/annextube/compare/v0.4.0...v0.5.0
355
+ [0.4.0]: https://github.com/con/annextube/compare/v0.3.0...v0.4.0
356
+ [0.3.0]: https://github.com/con/annextube/compare/v0.2.2...v0.3.0
357
+ [0.2.2]: https://github.com/con/annextube/compare/v0.2.0...v0.2.2
358
+ [0.2.0]: https://github.com/con/annextube/compare/v0.1.0...v0.2.0
359
+ [0.1.0]: https://github.com/con/annextube/releases/tag/v0.1.0