pynetv 0.4.1__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 (217) hide show
  1. pynetv-0.4.1/.dockerignore +39 -0
  2. pynetv-0.4.1/.github/actionlint.yaml +5 -0
  3. pynetv-0.4.1/.github/workflows/ai-upscale.yml +155 -0
  4. pynetv-0.4.1/.github/workflows/ci.yml +109 -0
  5. pynetv-0.4.1/.github/workflows/ffmpeg-base.yml +155 -0
  6. pynetv-0.4.1/.github/workflows/publish-pypi.yml +50 -0
  7. pynetv-0.4.1/.github/workflows/release.yml +75 -0
  8. pynetv-0.4.1/.gitignore +39 -0
  9. pynetv-0.4.1/.pre-commit-config.yaml +421 -0
  10. pynetv-0.4.1/.python-version +1 -0
  11. pynetv-0.4.1/Dockerfile +81 -0
  12. pynetv-0.4.1/Dockerfile.ai_upscale +112 -0
  13. pynetv-0.4.1/Dockerfile.ffmpeg +301 -0
  14. pynetv-0.4.1/Dockerfile.gateway +33 -0
  15. pynetv-0.4.1/LICENSE +190 -0
  16. pynetv-0.4.1/PKG-INFO +916 -0
  17. pynetv-0.4.1/README.md +883 -0
  18. pynetv-0.4.1/__init__.py +0 -0
  19. pynetv-0.4.1/apple/.env.example +2 -0
  20. pynetv-0.4.1/apple/.gitignore +5 -0
  21. pynetv-0.4.1/apple/AppStoreScreenshots/AppleTV/01-live-guide.png +0 -0
  22. pynetv-0.4.1/apple/AppStoreScreenshots/AppleTV/02-channel-browsing.png +0 -0
  23. pynetv-0.4.1/apple/AppStoreScreenshots/AppleTV/03-full-screen.png +0 -0
  24. pynetv-0.4.1/apple/AppStoreScreenshots/Attribution.txt +48 -0
  25. pynetv-0.4.1/apple/AppStoreScreenshots/Mac/01-live-guide.png +0 -0
  26. pynetv-0.4.1/apple/AppStoreScreenshots/Mac/02-channel-browsing.png +0 -0
  27. pynetv-0.4.1/apple/AppStoreScreenshots/Mac/03-full-screen.png +0 -0
  28. pynetv-0.4.1/apple/AppStoreScreenshots/contact-sheet.jpg +0 -0
  29. pynetv-0.4.1/apple/AppStoreScreenshots/iPad/01-live-guide.png +0 -0
  30. pynetv-0.4.1/apple/AppStoreScreenshots/iPad/02-channel-browsing.png +0 -0
  31. pynetv-0.4.1/apple/AppStoreScreenshots/iPad/03-full-screen.png +0 -0
  32. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone/01-live-guide.png +0 -0
  33. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone/02-channel-browsing.png +0 -0
  34. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone/03-full-screen.png +0 -0
  35. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone-6.5-inch/01-live-guide.png +0 -0
  36. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone-6.5-inch/02-channel-browsing.png +0 -0
  37. pynetv-0.4.1/apple/AppStoreScreenshots/iPhone-6.5-inch/03-full-screen.png +0 -0
  38. pynetv-0.4.1/apple/Config.xcconfig +3 -0
  39. pynetv-0.4.1/apple/PrivacyInfo.xcprivacy +74 -0
  40. pynetv-0.4.1/apple/README.md +284 -0
  41. pynetv-0.4.1/apple/Resources/PrivacyPolicy.txt +88 -0
  42. pynetv-0.4.1/apple/Resources/Support.txt +44 -0
  43. pynetv-0.4.1/apple/Shared/APIClient.swift +492 -0
  44. pynetv-0.4.1/apple/Shared/AppInformationView.swift +103 -0
  45. pynetv-0.4.1/apple/Shared/AppModel.swift +512 -0
  46. pynetv-0.4.1/apple/Shared/GuideView.swift +926 -0
  47. pynetv-0.4.1/apple/Shared/LoginView.swift +73 -0
  48. pynetv-0.4.1/apple/Shared/MainView.swift +191 -0
  49. pynetv-0.4.1/apple/Shared/Models.swift +447 -0
  50. pynetv-0.4.1/apple/Shared/PlayerView.swift +1859 -0
  51. pynetv-0.4.1/apple/Shared/RootView.swift +50 -0
  52. pynetv-0.4.1/apple/Shared/SettingsView.swift +35 -0
  53. pynetv-0.4.1/apple/Shared/Theme.swift +65 -0
  54. pynetv-0.4.1/apple/Shared/WatchView.swift +594 -0
  55. pynetv-0.4.1/apple/Shared/neTVApp.swift +26 -0
  56. pynetv-0.4.1/apple/Tests/ArchiveAPIChecks.swift +204 -0
  57. pynetv-0.4.1/apple/Tests/GuideDataChecks.swift +173 -0
  58. pynetv-0.4.1/apple/Tests/PlaybackStartChecks.swift +469 -0
  59. pynetv-0.4.1/apple/archive-testflight.sh +48 -0
  60. pynetv-0.4.1/apple/iOS/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png +0 -0
  61. pynetv-0.4.1/apple/iOS/Info.plist +57 -0
  62. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-128@1x.png +0 -0
  63. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png +0 -0
  64. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-16@1x.png +0 -0
  65. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png +0 -0
  66. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-256@1x.png +0 -0
  67. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png +0 -0
  68. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-32@1x.png +0 -0
  69. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png +0 -0
  70. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-512@1x.png +0 -0
  71. pynetv-0.4.1/apple/macOS/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png +0 -0
  72. pynetv-0.4.1/apple/macOS/Info.plist +44 -0
  73. pynetv-0.4.1/apple/macOS/neTV.entitlements +10 -0
  74. pynetv-0.4.1/apple/neTV.xcodeproj/project.pbxproj +675 -0
  75. pynetv-0.4.1/apple/neTV.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  76. pynetv-0.4.1/apple/neTV.xcodeproj/xcshareddata/xcschemes/neTV-iOS.xcscheme +87 -0
  77. pynetv-0.4.1/apple/neTV.xcodeproj/xcshareddata/xcschemes/neTV-macOS.xcscheme +87 -0
  78. pynetv-0.4.1/apple/neTV.xcodeproj/xcshareddata/xcschemes/neTV-tvOS.xcscheme +87 -0
  79. pynetv-0.4.1/apple/project.yml +117 -0
  80. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Background.imagestacklayer/Content.imageset/icon@1x.png +0 -0
  81. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Background.imagestacklayer/Content.imageset/icon@2x.png +0 -0
  82. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Foreground.imagestacklayer/Content.imageset/icon@1x.png +0 -0
  83. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Foreground.imagestacklayer/Content.imageset/icon@2x.png +0 -0
  84. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Store Icon.imagestack/Background.imagestacklayer/Content.imageset/icon@1x.png +0 -0
  85. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Store Icon.imagestack/Foreground.imagestacklayer/Content.imageset/icon@1x.png +0 -0
  86. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Wide.imageset/image@1x.png +0 -0
  87. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Wide.imageset/image@2x.png +0 -0
  88. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf.imageset/image@1x.png +0 -0
  89. pynetv-0.4.1/apple/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf.imageset/image@2x.png +0 -0
  90. pynetv-0.4.1/apple/tvOS/Info.plist +31 -0
  91. pynetv-0.4.1/auth.py +370 -0
  92. pynetv-0.4.1/auth_test.py +331 -0
  93. pynetv-0.4.1/cache.py +963 -0
  94. pynetv-0.4.1/cache_test.py +1093 -0
  95. pynetv-0.4.1/catchup.py +149 -0
  96. pynetv-0.4.1/catchup_test.py +144 -0
  97. pynetv-0.4.1/chromecast.py +389 -0
  98. pynetv-0.4.1/chromecast_test.py +423 -0
  99. pynetv-0.4.1/collector_tests/cast.test.cjs +299 -0
  100. pynetv-0.4.1/collector_tests/live-playback.test.cjs +324 -0
  101. pynetv-0.4.1/collector_tests/player-live-integration.test.cjs +883 -0
  102. pynetv-0.4.1/collector_tests/player-quality.test.cjs +89 -0
  103. pynetv-0.4.1/collector_tests/virtual-guide.test.cjs +52 -0
  104. pynetv-0.4.1/dataclasses_test.py +105 -0
  105. pynetv-0.4.1/docker-compose.cast.yml +14 -0
  106. pynetv-0.4.1/docker-compose.yml +125 -0
  107. pynetv-0.4.1/entrypoint-ai_upscale.sh +98 -0
  108. pynetv-0.4.1/entrypoint.sh +68 -0
  109. pynetv-0.4.1/epg.py +582 -0
  110. pynetv-0.4.1/epg_test.py +729 -0
  111. pynetv-0.4.1/fast_start.py +354 -0
  112. pynetv-0.4.1/fast_start_test.py +861 -0
  113. pynetv-0.4.1/ffmpeg_command.py +1782 -0
  114. pynetv-0.4.1/ffmpeg_command_test.py +2102 -0
  115. pynetv-0.4.1/ffmpeg_session.py +2646 -0
  116. pynetv-0.4.1/ffmpeg_session_coverage_test.py +2120 -0
  117. pynetv-0.4.1/ffmpeg_session_lifecycle_test.py +205 -0
  118. pynetv-0.4.1/ffmpeg_session_test.py +1358 -0
  119. pynetv-0.4.1/gateway.py +703 -0
  120. pynetv-0.4.1/gateway_catalog.py +354 -0
  121. pynetv-0.4.1/gateway_catalog_test.py +475 -0
  122. pynetv-0.4.1/gateway_epg.py +189 -0
  123. pynetv-0.4.1/gateway_epg_test.py +428 -0
  124. pynetv-0.4.1/gateway_test.py +1360 -0
  125. pynetv-0.4.1/handoff.py +489 -0
  126. pynetv-0.4.1/handoff_test.py +391 -0
  127. pynetv-0.4.1/live_recovery_test.py +375 -0
  128. pynetv-0.4.1/m3u.py +567 -0
  129. pynetv-0.4.1/m3u_test.py +692 -0
  130. pynetv-0.4.1/main.py +4580 -0
  131. pynetv-0.4.1/main_core_test.py +1031 -0
  132. pynetv-0.4.1/main_test.py +2655 -0
  133. pynetv-0.4.1/main_views_test.py +1529 -0
  134. pynetv-0.4.1/playback_policy.py +181 -0
  135. pynetv-0.4.1/playback_policy_test.py +232 -0
  136. pynetv-0.4.1/playback_session_test.py +237 -0
  137. pynetv-0.4.1/playlists.py +533 -0
  138. pynetv-0.4.1/playlists_test.py +318 -0
  139. pynetv-0.4.1/pyproject.toml +402 -0
  140. pynetv-0.4.1/screenshots/ai-upscale_batman_disabled.png +0 -0
  141. pynetv-0.4.1/screenshots/ai-upscale_batman_enabled.png +0 -0
  142. pynetv-0.4.1/screenshots/ai-upscale_cleopatra_disabled.png +0 -0
  143. pynetv-0.4.1/screenshots/ai-upscale_cleopatra_enabled.png +0 -0
  144. pynetv-0.4.1/screenshots/ai-upscale_macgyver_disabled.png +0 -0
  145. pynetv-0.4.1/screenshots/ai-upscale_macgyver_enabled.png +0 -0
  146. pynetv-0.4.1/screenshots/ai-upscale_price-is-right_disabled.png +0 -0
  147. pynetv-0.4.1/screenshots/ai-upscale_price-is-right_enabled.png +0 -0
  148. pynetv-0.4.1/screenshots/epg.png +0 -0
  149. pynetv-0.4.1/screenshots/player.png +0 -0
  150. pynetv-0.4.1/screenshots/series.png +0 -0
  151. pynetv-0.4.1/screenshots/settings.png +0 -0
  152. pynetv-0.4.1/screenshots/vod.png +0 -0
  153. pynetv-0.4.1/static/icons/android-chrome-192x192.png +0 -0
  154. pynetv-0.4.1/static/icons/android-chrome-512x512.png +0 -0
  155. pynetv-0.4.1/static/icons/apple-touch-icon.png +0 -0
  156. pynetv-0.4.1/static/icons/favicon-16x16.png +0 -0
  157. pynetv-0.4.1/static/icons/favicon-32x32.png +0 -0
  158. pynetv-0.4.1/static/icons/mstile-150x150.png +0 -0
  159. pynetv-0.4.1/static/icons/site.webmanifest +20 -0
  160. pynetv-0.4.1/static/js/app.js +245 -0
  161. pynetv-0.4.1/static/js/cast.js +325 -0
  162. pynetv-0.4.1/static/js/favorites-grid.js +225 -0
  163. pynetv-0.4.1/static/js/handoff-card.js +54 -0
  164. pynetv-0.4.1/static/js/live-playback.js +227 -0
  165. pynetv-0.4.1/static/js/player-quality.js +41 -0
  166. pynetv-0.4.1/static/js/player.js +1925 -0
  167. pynetv-0.4.1/static/js/settings.js +944 -0
  168. pynetv-0.4.1/static/js/virtual-guide.js +711 -0
  169. pynetv-0.4.1/templates/base.html +87 -0
  170. pynetv-0.4.1/templates/error.html +14 -0
  171. pynetv-0.4.1/templates/guide.html +592 -0
  172. pynetv-0.4.1/templates/icons.html +5 -0
  173. pynetv-0.4.1/templates/login.html +35 -0
  174. pynetv-0.4.1/templates/movie_detail.html +101 -0
  175. pynetv-0.4.1/templates/player.html +286 -0
  176. pynetv-0.4.1/templates/playlists.html +452 -0
  177. pynetv-0.4.1/templates/search.html +207 -0
  178. pynetv-0.4.1/templates/series.html +110 -0
  179. pynetv-0.4.1/templates/series_detail.html +111 -0
  180. pynetv-0.4.1/templates/settings.html +1007 -0
  181. pynetv-0.4.1/templates/setup.html +46 -0
  182. pynetv-0.4.1/templates/vod.html +111 -0
  183. pynetv-0.4.1/testing.py +31 -0
  184. pynetv-0.4.1/testing_test.py +42 -0
  185. pynetv-0.4.1/tools/alignm3u.py +310 -0
  186. pynetv-0.4.1/tools/alignm3u_test.py +195 -0
  187. pynetv-0.4.1/tools/benchmark-nomos.py +270 -0
  188. pynetv-0.4.1/tools/benchmark-nomos_test.py +310 -0
  189. pynetv-0.4.1/tools/build-nomos-benchmark.sh +18 -0
  190. pynetv-0.4.1/tools/debug_remap.py +79 -0
  191. pynetv-0.4.1/tools/debug_remap_test.py +117 -0
  192. pynetv-0.4.1/tools/export-tensorrt.py +579 -0
  193. pynetv-0.4.1/tools/export-tensorrt_test.py +757 -0
  194. pynetv-0.4.1/tools/install-ai_upscale.sh +152 -0
  195. pynetv-0.4.1/tools/install-ffmpeg.sh +1801 -0
  196. pynetv-0.4.1/tools/install-letsencrypt.sh +46 -0
  197. pynetv-0.4.1/tools/install-netv.sh +143 -0
  198. pynetv-0.4.1/tools/install-prereqs.sh +30 -0
  199. pynetv-0.4.1/tools/manual_remap.py +121 -0
  200. pynetv-0.4.1/tools/manual_remap_test.py +131 -0
  201. pynetv-0.4.1/tools/patches/dnn_backend_tensorrt.cpp +1844 -0
  202. pynetv-0.4.1/tools/patches/dnn_backend_torch.cpp +1006 -0
  203. pynetv-0.4.1/tools/patches/dnn_cuda_kernels.cu +424 -0
  204. pynetv-0.4.1/tools/patches/dnn_cuda_kernels.h +49 -0
  205. pynetv-0.4.1/tools/patches/vf_dnn_processing.c +491 -0
  206. pynetv-0.4.1/tools/remap_epg.py +915 -0
  207. pynetv-0.4.1/tools/remap_epg_test.py +348 -0
  208. pynetv-0.4.1/tools/uninstall-netv.sh +40 -0
  209. pynetv-0.4.1/tools/xtream2m3u.py +671 -0
  210. pynetv-0.4.1/tools/xtream2m3u_test.py +495 -0
  211. pynetv-0.4.1/tools/zap2xml.py +733 -0
  212. pynetv-0.4.1/tools/zap2xml_test.py +419 -0
  213. pynetv-0.4.1/util.py +232 -0
  214. pynetv-0.4.1/util_test.py +253 -0
  215. pynetv-0.4.1/uv.lock +3449 -0
  216. pynetv-0.4.1/xtream.py +161 -0
  217. pynetv-0.4.1/xtream_test.py +410 -0
@@ -0,0 +1,39 @@
1
+ # Git
2
+ .git/
3
+ .gitignore
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ .venv/
9
+ .ruff_cache/
10
+ .pytest_cache/
11
+ uv.lock
12
+
13
+ # App data (user-specific)
14
+ cache/
15
+ .cache/
16
+ settings.json
17
+ *.pem
18
+
19
+ # Tools (not needed in container, except specific scripts)
20
+ tools/
21
+ !tools/install-ffmpeg.sh
22
+ !tools/patches/
23
+ !tools/install-ai_upscale.sh
24
+ !tools/export-tensorrt.py
25
+
26
+ # Tests
27
+ *_test.py
28
+ conftest.py
29
+
30
+ # Docs
31
+ *.md
32
+ !README.md
33
+ screenshots/
34
+ LICENSE
35
+
36
+ # Docker
37
+ Dockerfile
38
+ docker-compose.yml
39
+ .dockerignore
@@ -0,0 +1,5 @@
1
+ self-hosted-runner:
2
+ # GitHub-hosted since 2026-09-17; actionlint v1.7.12 predates them.
3
+ labels:
4
+ - ubuntu-26.04
5
+ - ubuntu-26.04-arm
@@ -0,0 +1,155 @@
1
+ name: AI Upscale Image
2
+
3
+ on:
4
+ # Run manually when an FFmpeg base-image update should be published immediately.
5
+ workflow_dispatch:
6
+ push:
7
+ branches: [main]
8
+ paths:
9
+ - "Dockerfile.ai_upscale"
10
+ - "entrypoint-ai_upscale.sh"
11
+ - "*.py"
12
+ - "pyproject.toml"
13
+ - "templates/**"
14
+ - "static/**"
15
+ - "tools/**"
16
+ - ".github/workflows/ai-upscale.yml"
17
+
18
+ env:
19
+ REGISTRY: ghcr.io
20
+ IMAGE_NAME: ${{ github.repository }}-ai-upscale
21
+ DEFAULT_BASE: ubuntu:26.04
22
+
23
+ jobs:
24
+ build:
25
+ runs-on: ubuntu-26.04
26
+ permissions:
27
+ contents: read
28
+ packages: write
29
+ strategy:
30
+ # One failing combination must not cancel the others.
31
+ fail-fast: false
32
+ matrix:
33
+ # Mirrors ffmpeg-base.yml: each image builds on the ffmpeg image of the same CUDA and OS.
34
+ nvidia: ['cuda:12.4', 'cuda:12.6', 'cuda:12.8', 'cuda:13.0']
35
+ base_image: ['ubuntu:22.04', 'ubuntu:24.04', 'ubuntu:26.04']
36
+
37
+ steps:
38
+ - name: Free disk space
39
+ run: |
40
+ # Remove large unnecessary packages to free up space for torch/tensorrt
41
+ sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
42
+ sudo apt-get clean
43
+ df -h
44
+
45
+ - uses: actions/checkout@v7
46
+
47
+ - name: Set up Docker Buildx
48
+ uses: docker/setup-buildx-action@v4
49
+
50
+ - name: Log in to Container Registry
51
+ uses: docker/login-action@v4
52
+ with:
53
+ registry: ${{ env.REGISTRY }}
54
+ username: ${{ github.actor }}
55
+ password: ${{ secrets.GITHUB_TOKEN }}
56
+
57
+ - name: Parse CUDA version
58
+ id: cuda
59
+ run: |
60
+ # Extract "12.4" from "cuda:12.4"
61
+ CUDA_VER="${{ matrix.nvidia }}"
62
+ CUDA_VER="${CUDA_VER#cuda:}"
63
+ echo "version=$CUDA_VER" >> $GITHUB_OUTPUT
64
+ # "ubuntu:26.04" -> "ubuntu26.04"
65
+ OS="${{ matrix.base_image }}"
66
+ echo "os=${OS/:/}" >> $GITHUB_OUTPUT
67
+
68
+ - name: Extract metadata
69
+ id: meta
70
+ uses: docker/metadata-action@v6
71
+ with:
72
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
73
+ tags: |
74
+ type=raw,value=cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }}
75
+ type=raw,value=cuda${{ steps.cuda.outputs.version }},enable=${{ matrix.base_image == env.DEFAULT_BASE }}
76
+ type=raw,value=latest,enable=${{ matrix.base_image == env.DEFAULT_BASE && steps.cuda.outputs.version == '13.0' }}
77
+
78
+ - name: Build and push
79
+ uses: docker/build-push-action@v7
80
+ with:
81
+ context: .
82
+ file: Dockerfile.ai_upscale
83
+ push: true
84
+ tags: ${{ steps.meta.outputs.tags }}
85
+ labels: ${{ steps.meta.outputs.labels }}
86
+ build-args: |
87
+ FFMPEG_IMAGE=ghcr.io/${{ github.repository }}-ffmpeg:cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }}
88
+ NVIDIA=${{ matrix.nvidia }}
89
+ BASE_IMAGE=${{ matrix.base_image }}
90
+ no-cache: true
91
+
92
+ - name: Verify image
93
+ run: |
94
+ # Free space: BuildKit uses separate storage, prune it before pulling
95
+ docker buildx prune -af || true
96
+ docker system prune -af || true
97
+ df -h
98
+ IMG=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }}
99
+ docker pull $IMG
100
+ # Verify everything that should be enabled is actually linked
101
+ # Note: --entrypoint bypasses the default entrypoint which tries to build TensorRT engines (requires GPU)
102
+ docker run --rm --entrypoint sh "$IMG" -c "
103
+ echo '=== Verifying AI Upscale build ==='
104
+ LDD_OUTPUT=\$(ldd /usr/local/bin/ffmpeg)
105
+
106
+ # Verify ffmpeg binaries exist
107
+ for bin in ffmpeg ffprobe ffplay; do
108
+ test -x /usr/local/bin/\$bin || { echo \"ERROR: \$bin not found\"; exit 1; }
109
+ echo \"OK: \$bin exists\"
110
+ done
111
+
112
+ # Verify non-NVIDIA dependencies are satisfied
113
+ # Note: All NVIDIA libraries (libnvinfer, libcuda, libcudart) are loaded via dlopen,
114
+ # so ffmpeg works even without NVIDIA GPU/drivers installed
115
+ MISSING=\$(echo \"\$LDD_OUTPUT\" | grep 'not found' | grep -v -E 'libnvinfer|libnvonnxparser|libcudart|libcuda' || true)
116
+ if [ -n \"\$MISSING\" ]; then
117
+ echo 'ERROR: Missing non-NVIDIA libraries:'
118
+ echo \"\$MISSING\"
119
+ exit 1
120
+ fi
121
+ echo 'OK: All non-NVIDIA dependencies satisfied'
122
+
123
+ # Verify ffmpeg has NO hard CUDA dependency (all CUDA/TensorRT loaded via dlopen)
124
+ if echo \"\$LDD_OUTPUT\" | grep -q 'libcudart'; then
125
+ echo 'ERROR: libcudart linked at compile time (should use dlopen)'
126
+ exit 1
127
+ fi
128
+ echo 'OK: No libcudart dependency (CUDA Driver API via dlopen)'
129
+ echo 'OK: libnvinfer loaded via dlopen (not in ldd output)'
130
+
131
+ # Verify Python AI packages installed (use pip show, not import - import needs CUDA runtime)
132
+ echo '=== Verifying Python packages ==='
133
+ pip3 show torch >/dev/null 2>&1 || { echo 'ERROR: torch not installed'; exit 1; }
134
+ echo 'OK: torch installed'
135
+ pip3 show onnx >/dev/null 2>&1 || { echo 'ERROR: onnx not installed'; exit 1; }
136
+ echo 'OK: onnx installed'
137
+ pip3 show tensorrt >/dev/null 2>&1 || { echo 'ERROR: tensorrt not installed'; exit 1; }
138
+ echo 'OK: tensorrt installed'
139
+
140
+ # Verify AI upscale scripts exist
141
+ echo '=== Verifying AI upscale scripts ==='
142
+ test -x /app/tools/install-ai_upscale.sh || { echo 'ERROR: install-ai_upscale.sh not found'; exit 1; }
143
+ echo 'OK: install-ai_upscale.sh exists'
144
+ test -f /app/tools/export-tensorrt.py || { echo 'ERROR: export-tensorrt.py not found'; exit 1; }
145
+ echo 'OK: export-tensorrt.py exists'
146
+ python3 /app/tools/export-tensorrt.py --list | grep -q '2x-nomosuni-compact' || { echo 'ERROR: Nomos model unavailable'; exit 1; }
147
+ if python3 /app/tools/export-tensorrt.py --help | grep -q -- '--model'; then
148
+ echo 'ERROR: Legacy model selection is still available'
149
+ exit 1
150
+ fi
151
+ echo 'OK: Nomos is the sole supported model'
152
+
153
+ echo ''
154
+ echo '=== All verifications passed ==='
155
+ "
@@ -0,0 +1,109 @@
1
+ name: CI
2
+
3
+ on:
4
+ # Run manually when an FFmpeg base-image update should be published immediately.
5
+ workflow_dispatch:
6
+ push:
7
+ branches: [main]
8
+ paths:
9
+ # Only trigger on files used by main Dockerfile
10
+ - "Dockerfile"
11
+ - "*.py"
12
+ - "pyproject.toml"
13
+ - "uv.lock"
14
+ - "templates/**"
15
+ - "static/**"
16
+ - "entrypoint.sh"
17
+ - ".github/workflows/ci.yml"
18
+ pull_request:
19
+ branches: [main]
20
+
21
+ env:
22
+ REGISTRY: ghcr.io
23
+ IMAGE_NAME: ${{ github.repository }}
24
+ FFMPEG_IMAGE: ghcr.io/${{ github.repository }}-ffmpeg:latest
25
+
26
+ jobs:
27
+ test:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v7
31
+
32
+ - name: Set up Python
33
+ uses: actions/setup-python@v7
34
+ with:
35
+ python-version: "3.14.10"
36
+
37
+ - name: Install uv
38
+ uses: astral-sh/setup-uv@v7
39
+ with:
40
+ python-version: "3.14.10"
41
+
42
+ - name: Install FFmpeg
43
+ run: |
44
+ sudo apt-get update
45
+ sudo apt-get install --yes --no-install-recommends ffmpeg
46
+
47
+ - name: Install dependencies
48
+ run: uv sync --frozen --group pre-commit
49
+
50
+ - name: Lint with ruff
51
+ run: uv --quiet run --frozen --group pre-commit ruff check .
52
+
53
+ - name: Type check with basedpyright
54
+ run: uv --quiet run --frozen --group pre-commit basedpyright
55
+
56
+ - name: Run tests
57
+ run: uv --quiet run --frozen --group pre-commit pytest
58
+
59
+ build:
60
+ runs-on: ubuntu-latest
61
+ needs: test
62
+ permissions:
63
+ contents: read
64
+ packages: write
65
+
66
+ steps:
67
+ - uses: actions/checkout@v7
68
+
69
+ - name: Set up Docker Buildx
70
+ uses: docker/setup-buildx-action@v4
71
+
72
+ - name: Log in to Container Registry
73
+ if: github.event_name != 'pull_request'
74
+ uses: docker/login-action@v4
75
+ with:
76
+ registry: ${{ env.REGISTRY }}
77
+ username: ${{ github.actor }}
78
+ password: ${{ secrets.GITHUB_TOKEN }}
79
+
80
+ - name: Verify base image exists
81
+ run: |
82
+ if ! docker manifest inspect ${{ env.FFMPEG_IMAGE }} > /dev/null 2>&1; then
83
+ echo "ERROR: Base image ${{ env.FFMPEG_IMAGE }} not found"
84
+ echo "Run the 'FFmpeg Base Image' workflow first"
85
+ exit 1
86
+ fi
87
+ echo "Base image verified: ${{ env.FFMPEG_IMAGE }}"
88
+
89
+ - name: Extract metadata
90
+ id: meta
91
+ uses: docker/metadata-action@v6
92
+ with:
93
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
94
+ tags: |
95
+ type=raw,value=latest,enable={{is_default_branch}}
96
+ type=ref,event=branch
97
+ type=sha,prefix=
98
+
99
+ - name: Build and push
100
+ uses: docker/build-push-action@v7
101
+ with:
102
+ context: .
103
+ push: ${{ github.event_name != 'pull_request' }}
104
+ tags: ${{ steps.meta.outputs.tags }}
105
+ labels: ${{ steps.meta.outputs.labels }}
106
+ build-args: |
107
+ FFMPEG_IMAGE=${{ env.FFMPEG_IMAGE }}
108
+ cache-from: type=gha
109
+ cache-to: type=gha,mode=max
@@ -0,0 +1,155 @@
1
+ name: FFmpeg Base Image
2
+
3
+ on:
4
+ schedule:
5
+ # Build daily at 3 AM UTC
6
+ # Application images are rebuilt separately on code changes or manual dispatch.
7
+ - cron: "0 3 * * *"
8
+ push:
9
+ branches: [main]
10
+ paths:
11
+ - "Dockerfile.ffmpeg"
12
+ - "tools/install-ffmpeg.sh"
13
+ - ".github/workflows/ffmpeg-base.yml"
14
+ workflow_dispatch:
15
+ inputs:
16
+ ffmpeg_version:
17
+ description: "FFmpeg version (e.g., 7.1 or snapshot)"
18
+ required: false
19
+ default: "snapshot"
20
+
21
+ env:
22
+ REGISTRY: ghcr.io
23
+ IMAGE_NAME: ${{ github.repository }}-ffmpeg
24
+ DEFAULT_BASE: ubuntu:26.04
25
+
26
+ jobs:
27
+ build:
28
+ runs-on: ubuntu-26.04
29
+ permissions:
30
+ contents: read
31
+ packages: write
32
+ strategy:
33
+ # One failing combination must not cancel the others.
34
+ fail-fast: false
35
+ matrix:
36
+ # Every CUDA on every supported LTS; older CUDA serves older drivers/GPUs.
37
+ # The newest LTS owns the unsuffixed cudaX.Y and latest tags (see DEFAULT_BASE).
38
+ nvidia: ['cuda:12.4', 'cuda:12.6', 'cuda:12.8', 'cuda:13.0']
39
+ base_image: ['ubuntu:22.04', 'ubuntu:24.04', 'ubuntu:26.04']
40
+
41
+ steps:
42
+ - uses: actions/checkout@v7
43
+
44
+ - name: Set up Docker Buildx
45
+ uses: docker/setup-buildx-action@v4
46
+
47
+ - name: Log in to Container Registry
48
+ uses: docker/login-action@v4
49
+ with:
50
+ registry: ${{ env.REGISTRY }}
51
+ username: ${{ github.actor }}
52
+ password: ${{ secrets.GITHUB_TOKEN }}
53
+
54
+ - name: Parse CUDA version
55
+ id: cuda
56
+ run: |
57
+ # Extract "12.4" from "cuda:12.4"
58
+ CUDA_VER="${{ matrix.nvidia }}"
59
+ CUDA_VER="${CUDA_VER#cuda:}"
60
+ echo "version=$CUDA_VER" >> $GITHUB_OUTPUT
61
+ # "ubuntu:26.04" -> "ubuntu26.04"
62
+ OS="${{ matrix.base_image }}"
63
+ echo "os=${OS/:/}" >> $GITHUB_OUTPUT
64
+
65
+ - name: Generate build date
66
+ id: date
67
+ run: |
68
+ echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
69
+ echo "datetime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
70
+
71
+ - name: Determine FFmpeg version
72
+ id: version
73
+ run: |
74
+ VERSION="${{ github.event.inputs.ffmpeg_version || 'snapshot' }}"
75
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
76
+ if [ "$VERSION" = "snapshot" ]; then
77
+ echo "tag=${{ steps.date.outputs.date }}" >> $GITHUB_OUTPUT
78
+ else
79
+ echo "tag=$VERSION" >> $GITHUB_OUTPUT
80
+ fi
81
+
82
+ - name: Extract metadata
83
+ id: meta
84
+ uses: docker/metadata-action@v6
85
+ with:
86
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
87
+ tags: |
88
+ type=raw,value=cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }}
89
+ type=raw,value=cuda${{ steps.cuda.outputs.version }},enable=${{ matrix.base_image == env.DEFAULT_BASE }}
90
+ type=raw,value=latest,enable=${{ matrix.base_image == env.DEFAULT_BASE && steps.cuda.outputs.version == '13.0' }}
91
+ type=raw,value=${{ steps.date.outputs.date }}-cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }},enable=${{ steps.version.outputs.version == 'snapshot' }}
92
+
93
+ - name: Build and push
94
+ uses: docker/build-push-action@v7
95
+ with:
96
+ context: .
97
+ file: Dockerfile.ffmpeg
98
+ push: true
99
+ tags: ${{ steps.meta.outputs.tags }}
100
+ labels: ${{ steps.meta.outputs.labels }}
101
+ build-args: |
102
+ BUILD_DATE=${{ steps.date.outputs.datetime }}
103
+ FFMPEG_VERSION=${{ steps.version.outputs.version }}
104
+ NVIDIA=${{ matrix.nvidia }}
105
+ FFMPEG_BASE_IMAGE=${{ matrix.base_image }}
106
+ no-cache: true
107
+
108
+ - name: Verify image
109
+ run: |
110
+ IMG=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cuda${{ steps.cuda.outputs.version }}-${{ steps.cuda.outputs.os }}
111
+ docker pull $IMG
112
+ # Verify ffmpeg build - check binaries and shared library dependencies
113
+ # Note: Static libraries (libx264, libx265, etc.) are compiled into the binary
114
+ # and won't appear in ldd output. If they were missing, the build would have failed.
115
+ docker run --rm $IMG sh -c "
116
+ echo '=== Verifying ffmpeg build ==='
117
+
118
+ # Verify all binaries exist
119
+ for bin in ffmpeg ffprobe ffplay; do
120
+ test -x /usr/local/bin/\$bin || { echo \"ERROR: \$bin not found\"; exit 1; }
121
+ echo \"OK: \$bin exists\"
122
+ done
123
+
124
+ # Get ldd output for shared library checks
125
+ LDD_OUTPUT=\$(ldd /usr/local/bin/ffmpeg)
126
+
127
+ # Verify non-NVIDIA shared dependencies are satisfied
128
+ MISSING=\$(echo \"\$LDD_OUTPUT\" | grep 'not found' | grep -v -E 'libnvinfer|libnvonnxparser|libcudart|libcuda' || true)
129
+ if [ -n \"\$MISSING\" ]; then
130
+ echo 'ERROR: Missing non-NVIDIA libraries:'
131
+ echo \"\$MISSING\"
132
+ exit 1
133
+ fi
134
+ echo 'OK: All non-NVIDIA shared dependencies satisfied'
135
+
136
+ # Verify NVIDIA libraries (all loaded via dlopen - no hard dependencies)
137
+ # - CUDA Driver API (libcuda): loaded via dlopen when TensorRT backend used
138
+ # - TensorRT (libnvinfer): loaded via dlopen when TensorRT backend used
139
+ echo '=== Verifying NVIDIA libraries ==='
140
+ # Verify ffmpeg has NO hard CUDA dependency
141
+ if echo \"\$LDD_OUTPUT\" | grep -q 'libcudart'; then
142
+ echo 'ERROR: libcudart linked at compile time (should use dlopen)'
143
+ exit 1
144
+ fi
145
+ echo 'OK: No libcudart dependency (uses CUDA Driver API via dlopen)'
146
+ ls /usr/lib/x86_64-linux-gnu/libnvinfer.so* >/dev/null 2>&1 || { echo 'ERROR: TensorRT (libnvinfer) not installed'; exit 1; }
147
+ echo 'OK: libnvinfer installed (loaded via dlopen)'
148
+
149
+ # Verify libva is linked (we build it as shared for runtime)
150
+ echo \"\$LDD_OUTPUT\" | grep -q libva || { echo 'ERROR: libva not linked'; exit 1; }
151
+ echo 'OK: libva linked'
152
+
153
+ echo ''
154
+ echo '=== All verifications passed ==='
155
+ "
@@ -0,0 +1,50 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ - uses: astral-sh/setup-uv@v7
18
+ with:
19
+ python-version: "3.12"
20
+ - name: Build distributions
21
+ run: uv build
22
+ - name: Check package metadata
23
+ run: uvx --from twine twine check --strict dist/*
24
+ - name: Smoke test installed commands
25
+ env:
26
+ NETV_DATA_DIR: ${{ runner.temp }}/netv-data
27
+ run: |
28
+ uvx --isolated --from "$PWD"/dist/*.whl netv --help
29
+ uvx --isolated --from "$PWD"/dist/*.whl netv-gateway --help
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: distributions
33
+ path: dist/*
34
+ if-no-files-found: error
35
+
36
+ publish:
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ environment:
40
+ name: pypi
41
+ url: https://pypi.org/p/pynetv
42
+ permissions:
43
+ id-token: write
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: distributions
48
+ path: dist/
49
+ - name: Publish to PyPI
50
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,75 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ env:
9
+ REGISTRY: ghcr.io
10
+ IMAGE_NAME: ${{ github.repository }}
11
+ FFMPEG_IMAGE: ghcr.io/${{ github.repository }}-ffmpeg:latest
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-26.04
16
+ permissions:
17
+ contents: write
18
+ packages: write
19
+
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+ with:
23
+ fetch-depth: 0 # full history and tags for the changelog
24
+
25
+ - name: Set up Docker Buildx
26
+ uses: docker/setup-buildx-action@v4
27
+
28
+ - name: Log in to Container Registry
29
+ uses: docker/login-action@v4
30
+ with:
31
+ registry: ${{ env.REGISTRY }}
32
+ username: ${{ github.actor }}
33
+ password: ${{ secrets.GITHUB_TOKEN }}
34
+
35
+ - name: Extract metadata
36
+ id: meta
37
+ uses: docker/metadata-action@v6
38
+ with:
39
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
40
+ tags: |
41
+ type=semver,pattern={{version}}
42
+ type=semver,pattern={{major}}.{{minor}}
43
+ type=semver,pattern={{major}}
44
+ type=raw,value=latest
45
+
46
+ - name: Build and push
47
+ uses: docker/build-push-action@v7
48
+ with:
49
+ context: .
50
+ push: true
51
+ tags: ${{ steps.meta.outputs.tags }}
52
+ labels: ${{ steps.meta.outputs.labels }}
53
+ build-args: |
54
+ FFMPEG_IMAGE=${{ env.FFMPEG_IMAGE }}
55
+ cache-from: type=gha
56
+ cache-to: type=gha,mode=max
57
+
58
+ - name: Generate release notes
59
+ id: notes
60
+ run: |
61
+ echo "## Docker Image" >> notes.md
62
+ echo "" >> notes.md
63
+ echo "Pull the image:" >> notes.md
64
+ echo "\`\`\`bash" >> notes.md
65
+ echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}" >> notes.md
66
+ echo "\`\`\`" >> notes.md
67
+ echo "" >> notes.md
68
+ echo "## What's Changed" >> notes.md
69
+ git log $(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || git rev-list --max-parents=0 HEAD)..HEAD --pretty=format:"- %s" | grep -v '^- Release v' >> notes.md || true
70
+
71
+ - name: Create GitHub Release
72
+ uses: softprops/action-gh-release@v3
73
+ with:
74
+ body_path: notes.md
75
+ generate_release_notes: true
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+
8
+ # Debugging
9
+ **/*.out
10
+ **/*.log
11
+
12
+ # App data
13
+ .cache/
14
+ cache/
15
+
16
+ # Certificates
17
+ *.pem
18
+ /apple/.env
19
+
20
+ # Tools - user data and generated files
21
+ **/*.gz
22
+ **/*.json
23
+ **/*.m3u
24
+ **/*.xml
25
+ tools/.zap2xml/
26
+
27
+ # macOS
28
+ .DS_Store
29
+
30
+ # Local App Store source material
31
+ /apple/AppStoreScreenshots/**/*.pdf
32
+
33
+ .testmondata
34
+ .codegraph
35
+ .coverage
36
+ .testmondata*
37
+ .codegraph/
38
+ .coverage.*
39
+ .coverage-*