offcourse 0.0.2 → 1.0.0

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 (284) hide show
  1. package/.github/workflows/ci.yml +50 -0
  2. package/.husky/commit-msg +2 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.husky/pre-push +3 -0
  5. package/.prettierrc +8 -0
  6. package/.release-it.json +23 -0
  7. package/ARCHITECTURE.md +233 -0
  8. package/CHANGELOG.md +78 -0
  9. package/README.md +255 -20
  10. package/commitlint.config.js +4 -0
  11. package/dist/ai/openRouter.d.ts +47 -0
  12. package/dist/ai/openRouter.d.ts.map +1 -0
  13. package/dist/ai/openRouter.js +116 -0
  14. package/dist/ai/openRouter.js.map +1 -0
  15. package/dist/ai/transcriptPolisher.d.ts +24 -0
  16. package/dist/ai/transcriptPolisher.d.ts.map +1 -0
  17. package/dist/ai/transcriptPolisher.js +89 -0
  18. package/dist/ai/transcriptPolisher.js.map +1 -0
  19. package/dist/cli/commands/config.d.ts +13 -0
  20. package/dist/cli/commands/config.d.ts.map +1 -0
  21. package/dist/cli/commands/config.js +66 -0
  22. package/dist/cli/commands/config.js.map +1 -0
  23. package/dist/cli/commands/enrich.d.ts +14 -0
  24. package/dist/cli/commands/enrich.d.ts.map +1 -0
  25. package/dist/cli/commands/enrich.js +271 -0
  26. package/dist/cli/commands/enrich.js.map +1 -0
  27. package/dist/cli/commands/inspect.d.ts +11 -0
  28. package/dist/cli/commands/inspect.d.ts.map +1 -0
  29. package/dist/cli/commands/inspect.js +365 -0
  30. package/dist/cli/commands/inspect.js.map +1 -0
  31. package/dist/cli/commands/login.d.ts +12 -0
  32. package/dist/cli/commands/login.d.ts.map +1 -0
  33. package/dist/cli/commands/login.js +55 -0
  34. package/dist/cli/commands/login.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +15 -0
  36. package/dist/cli/commands/status.d.ts.map +1 -0
  37. package/dist/cli/commands/status.js +118 -0
  38. package/dist/cli/commands/status.js.map +1 -0
  39. package/dist/cli/commands/sync.d.ts +16 -0
  40. package/dist/cli/commands/sync.d.ts.map +1 -0
  41. package/dist/cli/commands/sync.js +922 -0
  42. package/dist/cli/commands/sync.js.map +1 -0
  43. package/dist/cli/commands/syncGhl.d.ts +20 -0
  44. package/dist/cli/commands/syncGhl.d.ts.map +1 -0
  45. package/dist/cli/commands/syncGhl.js +483 -0
  46. package/dist/cli/commands/syncGhl.js.map +1 -0
  47. package/dist/cli/commands/syncHighLevel.d.ts +24 -0
  48. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  49. package/dist/cli/commands/syncHighLevel.js +483 -0
  50. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  51. package/dist/cli/commands/syncHighLevel.test.d.ts +2 -0
  52. package/dist/cli/commands/syncHighLevel.test.d.ts.map +1 -0
  53. package/dist/cli/commands/syncHighLevel.test.js +102 -0
  54. package/dist/cli/commands/syncHighLevel.test.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +106 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/config/configManager.d.ts +31 -0
  60. package/dist/config/configManager.d.ts.map +1 -0
  61. package/dist/config/configManager.js +64 -0
  62. package/dist/config/configManager.js.map +1 -0
  63. package/dist/config/paths.d.ts +21 -0
  64. package/dist/config/paths.d.ts.map +1 -0
  65. package/dist/config/paths.js +33 -0
  66. package/dist/config/paths.js.map +1 -0
  67. package/dist/config/paths.test.d.ts +2 -0
  68. package/dist/config/paths.test.d.ts.map +1 -0
  69. package/dist/config/paths.test.js +70 -0
  70. package/dist/config/paths.test.js.map +1 -0
  71. package/dist/config/schema.d.ts +60 -0
  72. package/dist/config/schema.d.ts.map +1 -0
  73. package/dist/config/schema.js +50 -0
  74. package/dist/config/schema.js.map +1 -0
  75. package/dist/config/schema.test.d.ts +2 -0
  76. package/dist/config/schema.test.d.ts.map +1 -0
  77. package/dist/config/schema.test.js +151 -0
  78. package/dist/config/schema.test.js.map +1 -0
  79. package/dist/downloader/hlsDownloader.d.ts +58 -0
  80. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  81. package/dist/downloader/hlsDownloader.js +254 -0
  82. package/dist/downloader/hlsDownloader.js.map +1 -0
  83. package/dist/downloader/hlsDownloader.test.d.ts +2 -0
  84. package/dist/downloader/hlsDownloader.test.d.ts.map +1 -0
  85. package/dist/downloader/hlsDownloader.test.js +116 -0
  86. package/dist/downloader/hlsDownloader.test.js.map +1 -0
  87. package/dist/downloader/hlsValidator.d.ts +35 -0
  88. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  89. package/dist/downloader/hlsValidator.js +148 -0
  90. package/dist/downloader/hlsValidator.js.map +1 -0
  91. package/dist/downloader/index.d.ts +26 -0
  92. package/dist/downloader/index.d.ts.map +1 -0
  93. package/dist/downloader/index.js +52 -0
  94. package/dist/downloader/index.js.map +1 -0
  95. package/dist/downloader/loomDownloader.d.ts +56 -0
  96. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  97. package/dist/downloader/loomDownloader.js +559 -0
  98. package/dist/downloader/loomDownloader.js.map +1 -0
  99. package/dist/downloader/loomDownloader.test.d.ts +2 -0
  100. package/dist/downloader/loomDownloader.test.d.ts.map +1 -0
  101. package/dist/downloader/loomDownloader.test.js +36 -0
  102. package/dist/downloader/loomDownloader.test.js.map +1 -0
  103. package/dist/downloader/queue.d.ts +56 -0
  104. package/dist/downloader/queue.d.ts.map +1 -0
  105. package/dist/downloader/queue.js +88 -0
  106. package/dist/downloader/queue.js.map +1 -0
  107. package/dist/downloader/queue.test.d.ts +2 -0
  108. package/dist/downloader/queue.test.d.ts.map +1 -0
  109. package/dist/downloader/queue.test.js +158 -0
  110. package/dist/downloader/queue.test.js.map +1 -0
  111. package/dist/downloader/videoDownloader.d.ts +32 -0
  112. package/dist/downloader/videoDownloader.d.ts.map +1 -0
  113. package/dist/downloader/videoDownloader.js +173 -0
  114. package/dist/downloader/videoDownloader.js.map +1 -0
  115. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  116. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  117. package/dist/downloader/vimeoDownloader.js +565 -0
  118. package/dist/downloader/vimeoDownloader.js.map +1 -0
  119. package/dist/downloader/vimeoDownloader.test.d.ts +2 -0
  120. package/dist/downloader/vimeoDownloader.test.d.ts.map +1 -0
  121. package/dist/downloader/vimeoDownloader.test.js +51 -0
  122. package/dist/downloader/vimeoDownloader.test.js.map +1 -0
  123. package/dist/scraper/auth.d.ts +29 -0
  124. package/dist/scraper/auth.d.ts.map +1 -0
  125. package/dist/scraper/auth.js +115 -0
  126. package/dist/scraper/auth.js.map +1 -0
  127. package/dist/scraper/extractor.d.ts +49 -0
  128. package/dist/scraper/extractor.d.ts.map +1 -0
  129. package/dist/scraper/extractor.js +627 -0
  130. package/dist/scraper/extractor.js.map +1 -0
  131. package/dist/scraper/extractor.test.d.ts +2 -0
  132. package/dist/scraper/extractor.test.d.ts.map +1 -0
  133. package/dist/scraper/extractor.test.js +65 -0
  134. package/dist/scraper/extractor.test.js.map +1 -0
  135. package/dist/scraper/ghl/auth.d.ts +25 -0
  136. package/dist/scraper/ghl/auth.d.ts.map +1 -0
  137. package/dist/scraper/ghl/auth.js +187 -0
  138. package/dist/scraper/ghl/auth.js.map +1 -0
  139. package/dist/scraper/ghl/extractor.d.ts +96 -0
  140. package/dist/scraper/ghl/extractor.d.ts.map +1 -0
  141. package/dist/scraper/ghl/extractor.js +345 -0
  142. package/dist/scraper/ghl/extractor.js.map +1 -0
  143. package/dist/scraper/ghl/index.d.ts +4 -0
  144. package/dist/scraper/ghl/index.d.ts.map +1 -0
  145. package/dist/scraper/ghl/index.js +4 -0
  146. package/dist/scraper/ghl/index.js.map +1 -0
  147. package/dist/scraper/ghl/navigator.d.ts +93 -0
  148. package/dist/scraper/ghl/navigator.d.ts.map +1 -0
  149. package/dist/scraper/ghl/navigator.js +447 -0
  150. package/dist/scraper/ghl/navigator.js.map +1 -0
  151. package/dist/scraper/highlevel/auth.d.ts +25 -0
  152. package/dist/scraper/highlevel/auth.d.ts.map +1 -0
  153. package/dist/scraper/highlevel/auth.js +189 -0
  154. package/dist/scraper/highlevel/auth.js.map +1 -0
  155. package/dist/scraper/highlevel/extractor.d.ts +97 -0
  156. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  157. package/dist/scraper/highlevel/extractor.js +386 -0
  158. package/dist/scraper/highlevel/extractor.js.map +1 -0
  159. package/dist/scraper/highlevel/extractor.test.d.ts +2 -0
  160. package/dist/scraper/highlevel/extractor.test.d.ts.map +1 -0
  161. package/dist/scraper/highlevel/extractor.test.js +101 -0
  162. package/dist/scraper/highlevel/extractor.test.js.map +1 -0
  163. package/dist/scraper/highlevel/index.d.ts +3 -0
  164. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  165. package/dist/scraper/highlevel/index.js +3 -0
  166. package/dist/scraper/highlevel/index.js.map +1 -0
  167. package/dist/scraper/highlevel/navigator.d.ts +93 -0
  168. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  169. package/dist/scraper/highlevel/navigator.js +492 -0
  170. package/dist/scraper/highlevel/navigator.js.map +1 -0
  171. package/dist/scraper/highlevel/navigator.test.d.ts +2 -0
  172. package/dist/scraper/highlevel/navigator.test.d.ts.map +1 -0
  173. package/dist/scraper/highlevel/navigator.test.js +78 -0
  174. package/dist/scraper/highlevel/navigator.test.js.map +1 -0
  175. package/dist/scraper/navigator.d.ts +65 -0
  176. package/dist/scraper/navigator.d.ts.map +1 -0
  177. package/dist/scraper/navigator.js +300 -0
  178. package/dist/scraper/navigator.js.map +1 -0
  179. package/dist/scraper/navigator.test.d.ts +2 -0
  180. package/dist/scraper/navigator.test.d.ts.map +1 -0
  181. package/dist/scraper/navigator.test.js +63 -0
  182. package/dist/scraper/navigator.test.js.map +1 -0
  183. package/dist/scraper/skoolApi.d.ts +17 -0
  184. package/dist/scraper/skoolApi.d.ts.map +1 -0
  185. package/dist/scraper/skoolApi.js +72 -0
  186. package/dist/scraper/skoolApi.js.map +1 -0
  187. package/dist/scraper/videoInterceptor.d.ts +19 -0
  188. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  189. package/dist/scraper/videoInterceptor.js +315 -0
  190. package/dist/scraper/videoInterceptor.js.map +1 -0
  191. package/dist/shared/auth.d.ts +58 -0
  192. package/dist/shared/auth.d.ts.map +1 -0
  193. package/dist/shared/auth.js +211 -0
  194. package/dist/shared/auth.js.map +1 -0
  195. package/dist/shared/fs.d.ts +31 -0
  196. package/dist/shared/fs.d.ts.map +1 -0
  197. package/dist/shared/fs.js +73 -0
  198. package/dist/shared/fs.js.map +1 -0
  199. package/dist/shared/http.d.ts +15 -0
  200. package/dist/shared/http.d.ts.map +1 -0
  201. package/dist/shared/http.js +31 -0
  202. package/dist/shared/http.js.map +1 -0
  203. package/dist/shared/index.d.ts +4 -0
  204. package/dist/shared/index.d.ts.map +1 -0
  205. package/dist/shared/index.js +4 -0
  206. package/dist/shared/index.js.map +1 -0
  207. package/dist/state/database.d.ts +245 -0
  208. package/dist/state/database.d.ts.map +1 -0
  209. package/dist/state/database.js +676 -0
  210. package/dist/state/database.js.map +1 -0
  211. package/dist/state/database.test.d.ts +2 -0
  212. package/dist/state/database.test.d.ts.map +1 -0
  213. package/dist/state/database.test.js +34 -0
  214. package/dist/state/database.test.js.map +1 -0
  215. package/dist/state/index.d.ts +2 -0
  216. package/dist/state/index.d.ts.map +1 -0
  217. package/dist/state/index.js +2 -0
  218. package/dist/state/index.js.map +1 -0
  219. package/dist/storage/fileSystem.d.ts +56 -0
  220. package/dist/storage/fileSystem.d.ts.map +1 -0
  221. package/dist/storage/fileSystem.js +121 -0
  222. package/dist/storage/fileSystem.js.map +1 -0
  223. package/dist/transcription/whisperService.d.ts +27 -0
  224. package/dist/transcription/whisperService.d.ts.map +1 -0
  225. package/dist/transcription/whisperService.js +102 -0
  226. package/dist/transcription/whisperService.js.map +1 -0
  227. package/eslint.config.js +55 -0
  228. package/package.json +68 -11
  229. package/src/__fixtures__/highlevel-post-response.json +68 -0
  230. package/src/__fixtures__/hls-master-playlist.m3u8 +24 -0
  231. package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +38 -0
  232. package/src/cli/commands/config.ts +74 -0
  233. package/src/cli/commands/inspect.ts +441 -0
  234. package/src/cli/commands/login.ts +68 -0
  235. package/src/cli/commands/status.ts +147 -0
  236. package/src/cli/commands/sync.ts +1235 -0
  237. package/src/cli/commands/syncHighLevel.test.ts +144 -0
  238. package/src/cli/commands/syncHighLevel.ts +639 -0
  239. package/src/cli/index.ts +121 -0
  240. package/src/config/configManager.ts +75 -0
  241. package/src/config/paths.test.ts +83 -0
  242. package/src/config/paths.ts +36 -0
  243. package/src/config/schema.test.ts +173 -0
  244. package/src/config/schema.ts +65 -0
  245. package/src/downloader/hlsDownloader.test.ts +148 -0
  246. package/src/downloader/hlsDownloader.ts +327 -0
  247. package/src/downloader/hlsValidator.ts +196 -0
  248. package/src/downloader/index.ts +122 -0
  249. package/src/downloader/loomDownloader.test.ts +43 -0
  250. package/src/downloader/loomDownloader.ts +742 -0
  251. package/src/downloader/queue.test.ts +199 -0
  252. package/src/downloader/queue.ts +118 -0
  253. package/src/downloader/vimeoDownloader.test.ts +62 -0
  254. package/src/downloader/vimeoDownloader.ts +722 -0
  255. package/src/scraper/extractor.test.ts +124 -0
  256. package/src/scraper/extractor.ts +757 -0
  257. package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +41 -0
  258. package/src/scraper/highlevel/extractor.test.ts +134 -0
  259. package/src/scraper/highlevel/extractor.ts +537 -0
  260. package/src/scraper/highlevel/index.ts +2 -0
  261. package/src/scraper/highlevel/navigator.test.ts +110 -0
  262. package/src/scraper/highlevel/navigator.ts +668 -0
  263. package/src/scraper/highlevel/schemas.ts +183 -0
  264. package/src/scraper/navigator.test.ts +122 -0
  265. package/src/scraper/navigator.ts +355 -0
  266. package/src/scraper/schemas.ts +177 -0
  267. package/src/scraper/videoInterceptor.ts +435 -0
  268. package/src/shared/auth.test.ts +58 -0
  269. package/src/shared/auth.ts +251 -0
  270. package/src/shared/firebase.ts +151 -0
  271. package/src/shared/fs.ts +80 -0
  272. package/src/shared/http.ts +34 -0
  273. package/src/shared/index.ts +6 -0
  274. package/src/shared/slug.ts +26 -0
  275. package/src/shared/url.test.ts +122 -0
  276. package/src/shared/url.ts +57 -0
  277. package/src/state/database.test.ts +49 -0
  278. package/src/state/database.ts +919 -0
  279. package/src/state/index.ts +14 -0
  280. package/src/storage/fileSystem.test.ts +64 -0
  281. package/src/storage/fileSystem.ts +175 -0
  282. package/tsconfig.json +28 -0
  283. package/vitest.config.ts +29 -0
  284. package/cli.js +0 -45
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/config/schema.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { DownloadProgress } from "./loomDownloader.js";
2
+ export interface HLSDownloadResult {
3
+ success: boolean;
4
+ error?: string;
5
+ errorCode?: string;
6
+ outputPath?: string;
7
+ duration?: number;
8
+ }
9
+ export interface HLSQuality {
10
+ label: string;
11
+ url: string;
12
+ bandwidth: number;
13
+ width?: number | undefined;
14
+ height?: number | undefined;
15
+ }
16
+ /**
17
+ * Checks if ffmpeg is available on the system.
18
+ */
19
+ export declare function checkFfmpeg(): Promise<boolean>;
20
+ /**
21
+ * Fetches an HLS master playlist and parses quality variants.
22
+ */
23
+ export declare function fetchHLSQualities(masterUrl: string): Promise<HLSQuality[]>;
24
+ /**
25
+ * Parses an HLS master playlist to extract quality variants.
26
+ * Uses hls-parser for robust parsing.
27
+ */
28
+ export declare function parseHLSPlaylist(content: string, baseUrl: string): HLSQuality[];
29
+ /**
30
+ * Gets the best quality URL from a master playlist.
31
+ * @param masterUrl The master playlist URL
32
+ * @param preferredHeight Preferred video height (e.g., 720, 1080)
33
+ */
34
+ export declare function getBestQualityUrl(masterUrl: string, preferredHeight?: number): Promise<string>;
35
+ /**
36
+ * Downloads an HLS stream using ffmpeg.
37
+ * @param hlsUrl The HLS playlist URL (master or media)
38
+ * @param outputPath The output file path (should end in .mp4)
39
+ * @param onProgress Progress callback
40
+ */
41
+ export declare function downloadHLSVideo(hlsUrl: string, outputPath: string, onProgress?: (progress: DownloadProgress) => void): Promise<HLSDownloadResult>;
42
+ /**
43
+ * Downloads a HighLevel HLS video with quality selection.
44
+ * @param masterUrl The master playlist URL (may include token)
45
+ * @param outputPath The output file path
46
+ * @param preferredQuality Preferred quality label (e.g., "720p", "1080p")
47
+ * @param onProgress Progress callback
48
+ */
49
+ export declare function downloadHighLevelVideo(masterUrl: string, outputPath: string, preferredQuality?: string, onProgress?: (progress: DownloadProgress) => void): Promise<HLSDownloadResult>;
50
+ /**
51
+ * Extracts video info from a HighLevel HLS URL.
52
+ */
53
+ export declare function parseHighLevelVideoUrl(url: string): {
54
+ locationId: string;
55
+ videoId: string;
56
+ token?: string | undefined;
57
+ } | null;
58
+ //# sourceMappingURL=hlsDownloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsDownloader.d.ts","sourceRoot":"","sources":["../../src/downloader/hlsDownloader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAOpD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAahF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAuC/E;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAsBjB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAChD,OAAO,CAAC,iBAAiB,CAAC,CA4G5B;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAChD,OAAO,CAAC,iBAAiB,CAAC,CA0B5B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,GAAG,IAAI,CAqBP"}
@@ -0,0 +1,254 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { execa } from "execa";
4
+ import * as HLS from "hls-parser";
5
+ /**
6
+ * Checks if ffmpeg is available on the system.
7
+ */
8
+ export async function checkFfmpeg() {
9
+ try {
10
+ await execa("ffmpeg", ["-version"]);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Fetches an HLS master playlist and parses quality variants.
19
+ */
20
+ export async function fetchHLSQualities(masterUrl) {
21
+ try {
22
+ const response = await fetch(masterUrl);
23
+ if (!response.ok) {
24
+ throw new Error(`Failed to fetch playlist: ${response.status}`);
25
+ }
26
+ const content = await response.text();
27
+ return parseHLSPlaylist(content, masterUrl);
28
+ }
29
+ catch (error) {
30
+ console.error("Failed to fetch HLS qualities:", error);
31
+ return [];
32
+ }
33
+ }
34
+ /**
35
+ * Parses an HLS master playlist to extract quality variants.
36
+ * Uses hls-parser for robust parsing.
37
+ */
38
+ export function parseHLSPlaylist(content, baseUrl) {
39
+ try {
40
+ const playlist = HLS.parse(content);
41
+ // Check if it's a master playlist with variants
42
+ if (!("variants" in playlist) || !playlist.variants) {
43
+ return [];
44
+ }
45
+ const variants = playlist.variants.map((variant) => {
46
+ const bandwidth = variant.bandwidth ?? 0;
47
+ const resolution = variant.resolution;
48
+ const width = resolution?.width;
49
+ const height = resolution?.height;
50
+ // Build absolute URL
51
+ const variantUrl = variant.uri.startsWith("http")
52
+ ? variant.uri
53
+ : new URL(variant.uri, baseUrl).href;
54
+ const label = height ? `${height}p` : `${Math.round(bandwidth / 1000)}k`;
55
+ return {
56
+ label,
57
+ url: variantUrl,
58
+ bandwidth,
59
+ width,
60
+ height,
61
+ };
62
+ });
63
+ // Sort by bandwidth (highest first)
64
+ variants.sort((a, b) => b.bandwidth - a.bandwidth);
65
+ return variants;
66
+ }
67
+ catch {
68
+ // Fallback to empty array on parse error
69
+ return [];
70
+ }
71
+ }
72
+ /**
73
+ * Gets the best quality URL from a master playlist.
74
+ * @param masterUrl The master playlist URL
75
+ * @param preferredHeight Preferred video height (e.g., 720, 1080)
76
+ */
77
+ export async function getBestQualityUrl(masterUrl, preferredHeight) {
78
+ const qualities = await fetchHLSQualities(masterUrl);
79
+ if (qualities.length === 0) {
80
+ // Assume it's a direct media playlist
81
+ return masterUrl;
82
+ }
83
+ if (preferredHeight) {
84
+ // Find closest match to preferred height
85
+ const match = qualities.find((q) => q.height === preferredHeight);
86
+ if (match)
87
+ return match.url;
88
+ // Find closest lower quality
89
+ const lower = qualities.filter((q) => q.height && q.height <= preferredHeight);
90
+ if (lower.length > 0) {
91
+ return lower[0].url;
92
+ }
93
+ }
94
+ // Return highest quality
95
+ return qualities[0]?.url ?? masterUrl;
96
+ }
97
+ /**
98
+ * Downloads an HLS stream using ffmpeg.
99
+ * @param hlsUrl The HLS playlist URL (master or media)
100
+ * @param outputPath The output file path (should end in .mp4)
101
+ * @param onProgress Progress callback
102
+ */
103
+ export async function downloadHLSVideo(hlsUrl, outputPath, onProgress) {
104
+ // Check if ffmpeg is available
105
+ const hasFfmpeg = await checkFfmpeg();
106
+ if (!hasFfmpeg) {
107
+ return {
108
+ success: false,
109
+ error: "ffmpeg is not installed. Please install ffmpeg to download HLS videos.",
110
+ errorCode: "FFMPEG_NOT_FOUND",
111
+ };
112
+ }
113
+ // Ensure output directory exists
114
+ const outputDir = path.dirname(outputPath);
115
+ if (!fs.existsSync(outputDir)) {
116
+ fs.mkdirSync(outputDir, { recursive: true });
117
+ }
118
+ // Build ffmpeg command
119
+ const args = [
120
+ "-y", // Overwrite output
121
+ "-hide_banner",
122
+ "-loglevel",
123
+ "warning",
124
+ "-stats",
125
+ "-i",
126
+ hlsUrl,
127
+ "-c",
128
+ "copy", // Copy streams without re-encoding
129
+ "-bsf:a",
130
+ "aac_adtstoasc", // Fix AAC stream
131
+ outputPath,
132
+ ];
133
+ let duration = 0;
134
+ let currentTime = 0;
135
+ let lastProgressUpdate = 0;
136
+ const updateProgress = () => {
137
+ if (duration > 0 && onProgress) {
138
+ const percent = Math.min((currentTime / duration) * 100, 100);
139
+ const now = Date.now();
140
+ // Throttle progress updates to avoid spam
141
+ if (now - lastProgressUpdate > 200 || percent >= 100) {
142
+ lastProgressUpdate = now;
143
+ onProgress({
144
+ phase: "downloading",
145
+ percent: Math.round(percent),
146
+ currentBytes: currentTime,
147
+ totalBytes: duration,
148
+ });
149
+ }
150
+ }
151
+ };
152
+ try {
153
+ const subprocess = execa("ffmpeg", args);
154
+ // Parse stderr for progress info
155
+ subprocess.stderr?.on("data", (data) => {
156
+ const output = data.toString();
157
+ // Parse duration from input info
158
+ const durationMatch = /Duration:\s*(\d{2}):(\d{2}):(\d{2})\.(\d{2})/.exec(output);
159
+ if (durationMatch && duration === 0) {
160
+ duration =
161
+ parseInt(durationMatch[1], 10) * 3600 +
162
+ parseInt(durationMatch[2], 10) * 60 +
163
+ parseInt(durationMatch[3], 10) +
164
+ parseInt(durationMatch[4], 10) / 100;
165
+ }
166
+ // Parse current time from progress
167
+ const timeMatch = /time=(\d{2}):(\d{2}):(\d{2})\.(\d{2})/.exec(output);
168
+ if (timeMatch) {
169
+ currentTime =
170
+ parseInt(timeMatch[1], 10) * 3600 +
171
+ parseInt(timeMatch[2], 10) * 60 +
172
+ parseInt(timeMatch[3], 10) +
173
+ parseInt(timeMatch[4], 10) / 100;
174
+ updateProgress();
175
+ }
176
+ });
177
+ await subprocess;
178
+ // Final progress update
179
+ if (onProgress) {
180
+ onProgress({
181
+ phase: "complete",
182
+ percent: 100,
183
+ });
184
+ }
185
+ return {
186
+ success: true,
187
+ outputPath,
188
+ duration,
189
+ };
190
+ }
191
+ catch (error) {
192
+ const errorMessage = error instanceof Error ? error.message : String(error);
193
+ return {
194
+ success: false,
195
+ error: `ffmpeg error: ${errorMessage}`,
196
+ errorCode: "FFMPEG_ERROR",
197
+ };
198
+ }
199
+ }
200
+ /**
201
+ * Downloads a HighLevel HLS video with quality selection.
202
+ * @param masterUrl The master playlist URL (may include token)
203
+ * @param outputPath The output file path
204
+ * @param preferredQuality Preferred quality label (e.g., "720p", "1080p")
205
+ * @param onProgress Progress callback
206
+ */
207
+ export async function downloadHighLevelVideo(masterUrl, outputPath, preferredQuality, onProgress) {
208
+ // Report start
209
+ onProgress?.({
210
+ phase: "preparing",
211
+ percent: 0,
212
+ });
213
+ // Parse preferred height from quality string
214
+ let preferredHeight;
215
+ if (preferredQuality) {
216
+ const match = /(\d+)p?/i.exec(preferredQuality);
217
+ if (match) {
218
+ preferredHeight = parseInt(match[1], 10);
219
+ }
220
+ }
221
+ // Get the best quality URL
222
+ let downloadUrl = masterUrl;
223
+ try {
224
+ downloadUrl = await getBestQualityUrl(masterUrl, preferredHeight);
225
+ }
226
+ catch (error) {
227
+ console.warn("Failed to fetch quality options, using master URL:", error);
228
+ }
229
+ // Download using ffmpeg
230
+ return downloadHLSVideo(downloadUrl, outputPath, onProgress);
231
+ }
232
+ /**
233
+ * Extracts video info from a HighLevel HLS URL.
234
+ */
235
+ export function parseHighLevelVideoUrl(url) {
236
+ try {
237
+ const urlObj = new URL(url);
238
+ // Pattern: /hls/v2/memberships/{locationId}/videos/{videoId}/...
239
+ const match = /\/memberships\/([^/]+)\/videos\/([^/,]+)/.exec(urlObj.pathname);
240
+ if (!match) {
241
+ return null;
242
+ }
243
+ const token = urlObj.searchParams.get("token");
244
+ return {
245
+ locationId: match[1],
246
+ videoId: match[2],
247
+ ...(token ? { token } : {}),
248
+ };
249
+ }
250
+ catch {
251
+ return null;
252
+ }
253
+ }
254
+ //# sourceMappingURL=hlsDownloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsDownloader.js","sourceRoot":"","sources":["../../src/downloader/hlsDownloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hlsDownloader.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsDownloader.test.d.ts","sourceRoot":"","sources":["../../src/downloader/hlsDownloader.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseHLSPlaylist, parseHighLevelVideoUrl } from "./hlsDownloader.js";
3
+ describe("parseHLSPlaylist", () => {
4
+ const baseUrl = "https://cdn.example.com/video/";
5
+ it("parses master playlist with multiple qualities", () => {
6
+ const content = `#EXTM3U
7
+ #EXT-X-VERSION:3
8
+ #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
9
+ 360p.m3u8
10
+ #EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=854x480
11
+ 480p.m3u8
12
+ #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
13
+ 720p.m3u8
14
+ #EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
15
+ 1080p.m3u8`;
16
+ const result = parseHLSPlaylist(content, baseUrl);
17
+ expect(result).toHaveLength(4);
18
+ // Should be sorted by bandwidth (highest first)
19
+ expect(result[0].label).toBe("1080p");
20
+ expect(result[0].bandwidth).toBe(5000000);
21
+ expect(result[0].height).toBe(1080);
22
+ expect(result[0].width).toBe(1920);
23
+ expect(result[0].url).toBe("https://cdn.example.com/video/1080p.m3u8");
24
+ expect(result[3].label).toBe("360p");
25
+ expect(result[3].bandwidth).toBe(800000);
26
+ });
27
+ it("handles absolute URLs in playlist", () => {
28
+ const content = `#EXTM3U
29
+ #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
30
+ https://other-cdn.com/video/720p.m3u8`;
31
+ const result = parseHLSPlaylist(content, baseUrl);
32
+ expect(result).toHaveLength(1);
33
+ expect(result[0].url).toBe("https://other-cdn.com/video/720p.m3u8");
34
+ });
35
+ it("handles playlist without resolution", () => {
36
+ const content = `#EXTM3U
37
+ #EXT-X-STREAM-INF:BANDWIDTH=500000
38
+ audio.m3u8`;
39
+ const result = parseHLSPlaylist(content, baseUrl);
40
+ expect(result).toHaveLength(1);
41
+ expect(result[0].label).toBe("500k");
42
+ expect(result[0].height).toBeUndefined();
43
+ expect(result[0].width).toBeUndefined();
44
+ });
45
+ it("handles empty playlist", () => {
46
+ const content = `#EXTM3U
47
+ #EXT-X-VERSION:3`;
48
+ const result = parseHLSPlaylist(content, baseUrl);
49
+ expect(result).toHaveLength(0);
50
+ });
51
+ it("ignores comments and metadata", () => {
52
+ const content = `#EXTM3U
53
+ #EXT-X-VERSION:3
54
+ # This is a comment
55
+ #EXT-X-INDEPENDENT-SEGMENTS
56
+ #EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=1280x720
57
+ 720p.m3u8`;
58
+ const result = parseHLSPlaylist(content, baseUrl);
59
+ expect(result).toHaveLength(1);
60
+ });
61
+ it("handles real-world Vimeo-style playlist", () => {
62
+ const content = `#EXTM3U
63
+ #EXT-X-VERSION:4
64
+ #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=246064,BANDWIDTH=326400,CODECS="avc1.4D401E,mp4a.40.2",RESOLUTION=426x240,FRAME-RATE=24.000
65
+ https://vod.example.com/exp=123/~hmac=abc/240p/prog_index.m3u8
66
+ #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=602416,BANDWIDTH=796800,CODECS="avc1.4D401F,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=24.000
67
+ https://vod.example.com/exp=123/~hmac=abc/360p/prog_index.m3u8
68
+ #EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=1270416,BANDWIDTH=1680000,CODECS="avc1.4D401F,mp4a.40.2",RESOLUTION=854x480,FRAME-RATE=24.000
69
+ https://vod.example.com/exp=123/~hmac=abc/480p/prog_index.m3u8`;
70
+ const result = parseHLSPlaylist(content, baseUrl);
71
+ expect(result).toHaveLength(3);
72
+ expect(result[0].height).toBe(480);
73
+ expect(result[1].height).toBe(360);
74
+ expect(result[2].height).toBe(240);
75
+ });
76
+ });
77
+ describe("parseHighLevelVideoUrl", () => {
78
+ it("parses standard HighLevel HLS URL", () => {
79
+ const url = "https://backend.leadconnectorhq.com/hls/v2/memberships/ABC123/videos/video-id-456/master.m3u8";
80
+ const result = parseHighLevelVideoUrl(url);
81
+ expect(result).toEqual({
82
+ locationId: "ABC123",
83
+ videoId: "video-id-456",
84
+ });
85
+ });
86
+ it("parses URL with token", () => {
87
+ const url = "https://backend.leadconnectorhq.com/hls/v2/memberships/LOC123/videos/VID456/master.m3u8?token=secret-token";
88
+ const result = parseHighLevelVideoUrl(url);
89
+ expect(result).toEqual({
90
+ locationId: "LOC123",
91
+ videoId: "VID456",
92
+ token: "secret-token",
93
+ });
94
+ });
95
+ it("handles complex video IDs", () => {
96
+ const url = "https://cdn.example.com/hls/memberships/location-abc/videos/cts-184162b5f0747fcd,1080p/master.m3u8";
97
+ const result = parseHighLevelVideoUrl(url);
98
+ expect(result).toEqual({
99
+ locationId: "location-abc",
100
+ videoId: "cts-184162b5f0747fcd",
101
+ });
102
+ });
103
+ it("returns null for non-HighLevel URLs", () => {
104
+ expect(parseHighLevelVideoUrl("https://vimeo.com/123456")).toBeNull();
105
+ expect(parseHighLevelVideoUrl("https://youtube.com/watch?v=abc")).toBeNull();
106
+ });
107
+ it("returns null for invalid URLs", () => {
108
+ expect(parseHighLevelVideoUrl("not-a-url")).toBeNull();
109
+ expect(parseHighLevelVideoUrl("")).toBeNull();
110
+ });
111
+ it("returns null for missing video path", () => {
112
+ const url = "https://backend.leadconnectorhq.com/hls/v2/other/path";
113
+ expect(parseHighLevelVideoUrl(url)).toBeNull();
114
+ });
115
+ });
116
+ //# sourceMappingURL=hlsDownloader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsDownloader.test.js","sourceRoot":"","sources":["../../src/downloader/hlsDownloader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * Result of HLS validation.
4
+ */
5
+ export interface HlsValidationResult {
6
+ isValid: boolean;
7
+ hlsUrl: string | null;
8
+ error?: string;
9
+ errorCode?: string;
10
+ details?: string;
11
+ }
12
+ /**
13
+ * Validates that a Loom video has an accessible HLS stream.
14
+ * This should be called during the scanning phase to catch issues early.
15
+ *
16
+ * @param loomUrl - The Loom video URL
17
+ * @param page - Optional Playwright page for network interception fallback
18
+ */
19
+ export declare function validateLoomHls(loomUrl: string, page?: Page): Promise<HlsValidationResult>;
20
+ /**
21
+ * Validates a Vimeo video has accessible streams.
22
+ * @param vimeoUrl - The Vimeo video URL
23
+ * @param page - Optional Playwright page for domain-restricted videos
24
+ * @param lessonUrl - Optional lesson URL for referer-based access
25
+ */
26
+ export declare function validateVimeoVideo(vimeoUrl: string, page?: Page, lessonUrl?: string): Promise<HlsValidationResult>;
27
+ /**
28
+ * Validates HLS availability for a video URL based on its type.
29
+ * @param videoUrl - The video URL to validate
30
+ * @param videoType - The type of video (loom, vimeo, etc.)
31
+ * @param page - Optional Playwright page for network interception fallback
32
+ * @param lessonUrl - Optional lesson URL for referer-based access
33
+ */
34
+ export declare function validateVideoHls(videoUrl: string, videoType: string, page?: Page, lessonUrl?: string): Promise<HlsValidationResult>;
35
+ //# sourceMappingURL=hlsValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsValidator.d.ts","sourceRoot":"","sources":["../../src/downloader/hlsValidator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,IAAI,GACV,OAAO,CAAC,mBAAmB,CAAC,CAgD9B;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,IAAI,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAyD9B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,IAAI,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAgC9B"}
@@ -0,0 +1,148 @@
1
+ import { extractLoomId, getLoomVideoInfoDetailed } from "./loomDownloader.js";
2
+ import { extractVimeoId, getVimeoVideoInfo, getVimeoVideoInfoFromBrowser } from "./vimeoDownloader.js";
3
+ import { captureLoomHls, captureVimeoConfig } from "../scraper/videoInterceptor.js";
4
+ /**
5
+ * Validates that a Loom video has an accessible HLS stream.
6
+ * This should be called during the scanning phase to catch issues early.
7
+ *
8
+ * @param loomUrl - The Loom video URL
9
+ * @param page - Optional Playwright page for network interception fallback
10
+ */
11
+ export async function validateLoomHls(loomUrl, page) {
12
+ const videoId = extractLoomId(loomUrl);
13
+ if (!videoId) {
14
+ return {
15
+ isValid: false,
16
+ hlsUrl: null,
17
+ error: "Invalid Loom URL - could not extract video ID",
18
+ errorCode: "INVALID_URL",
19
+ details: `URL: ${loomUrl}`,
20
+ };
21
+ }
22
+ // First try direct API
23
+ const result = await getLoomVideoInfoDetailed(videoId, 2, 500);
24
+ if (result.success && result.info) {
25
+ return {
26
+ isValid: true,
27
+ hlsUrl: result.info.hlsUrl,
28
+ };
29
+ }
30
+ // If direct API failed and we have a page, try network interception
31
+ if (page && result.errorCode === "HLS_NOT_FOUND") {
32
+ const captured = await captureLoomHls(page, videoId, 15000);
33
+ if (captured.hlsUrl) {
34
+ return {
35
+ isValid: true,
36
+ hlsUrl: captured.hlsUrl,
37
+ details: "Captured via network interception",
38
+ };
39
+ }
40
+ }
41
+ // Return the original error
42
+ const validation = {
43
+ isValid: false,
44
+ hlsUrl: null,
45
+ error: result.error ?? "Failed to fetch Loom video info",
46
+ };
47
+ if (result.errorCode) {
48
+ validation.errorCode = result.errorCode;
49
+ }
50
+ if (result.details) {
51
+ validation.details = result.details;
52
+ }
53
+ return validation;
54
+ }
55
+ /**
56
+ * Validates a Vimeo video has accessible streams.
57
+ * @param vimeoUrl - The Vimeo video URL
58
+ * @param page - Optional Playwright page for domain-restricted videos
59
+ * @param lessonUrl - Optional lesson URL for referer-based access
60
+ */
61
+ export async function validateVimeoVideo(vimeoUrl, page, lessonUrl) {
62
+ const videoId = extractVimeoId(vimeoUrl);
63
+ if (!videoId) {
64
+ return {
65
+ isValid: false,
66
+ hlsUrl: null,
67
+ error: "Invalid Vimeo URL - could not extract video ID",
68
+ errorCode: "INVALID_URL",
69
+ details: `URL: ${vimeoUrl}`,
70
+ };
71
+ }
72
+ // Extract unlisted hash if present
73
+ const hashMatch = (/vimeo\.com\/\d+\/([a-f0-9]+)/.exec(vimeoUrl)) ?? (/[?&]h=([a-f0-9]+)/.exec(vimeoUrl));
74
+ const unlistedHash = hashMatch?.[1] ?? null;
75
+ // First try direct fetch (works for public videos)
76
+ let result = await getVimeoVideoInfo(videoId, unlistedHash, lessonUrl);
77
+ // If video is private/restricted and we have a browser context, try browser-based fetch
78
+ if (!result.success && result.errorCode === "PRIVATE_VIDEO" && page) {
79
+ result = await getVimeoVideoInfoFromBrowser(page, videoId, unlistedHash);
80
+ }
81
+ // If still failing and we have a page, try extracting from the running player
82
+ if (!result.success && result.errorCode === "PRIVATE_VIDEO" && page) {
83
+ const captured = await captureVimeoConfig(page, videoId, 20000);
84
+ if (captured.hlsUrl || captured.progressiveUrl) {
85
+ return {
86
+ isValid: true,
87
+ hlsUrl: captured.hlsUrl ?? captured.progressiveUrl,
88
+ details: "Extracted from running player",
89
+ };
90
+ }
91
+ }
92
+ if (!result.success || !result.info) {
93
+ const validation = {
94
+ isValid: false,
95
+ hlsUrl: null,
96
+ error: result.error ?? "Failed to fetch Vimeo video info",
97
+ };
98
+ if (result.errorCode) {
99
+ validation.errorCode = result.errorCode;
100
+ }
101
+ if (result.details) {
102
+ validation.details = result.details;
103
+ }
104
+ return validation;
105
+ }
106
+ // Return HLS URL if available, or progressive URL as fallback
107
+ return {
108
+ isValid: true,
109
+ hlsUrl: result.info.hlsUrl ?? result.info.progressiveUrl,
110
+ };
111
+ }
112
+ /**
113
+ * Validates HLS availability for a video URL based on its type.
114
+ * @param videoUrl - The video URL to validate
115
+ * @param videoType - The type of video (loom, vimeo, etc.)
116
+ * @param page - Optional Playwright page for network interception fallback
117
+ * @param lessonUrl - Optional lesson URL for referer-based access
118
+ */
119
+ export async function validateVideoHls(videoUrl, videoType, page, lessonUrl) {
120
+ switch (videoType) {
121
+ case "loom":
122
+ return validateLoomHls(videoUrl, page);
123
+ case "vimeo":
124
+ return validateVimeoVideo(videoUrl, page, lessonUrl);
125
+ case "youtube":
126
+ case "wistia":
127
+ // These require yt-dlp - skip validation, will fail at download
128
+ return {
129
+ isValid: true,
130
+ hlsUrl: null,
131
+ details: `${videoType} requires yt-dlp - will attempt download`,
132
+ };
133
+ case "native":
134
+ // Native videos have direct URLs, no HLS needed
135
+ return {
136
+ isValid: true,
137
+ hlsUrl: videoUrl,
138
+ };
139
+ default:
140
+ return {
141
+ isValid: false,
142
+ hlsUrl: null,
143
+ error: `Unknown video type: ${videoType}`,
144
+ errorCode: "UNKNOWN_TYPE",
145
+ };
146
+ }
147
+ }
148
+ //# sourceMappingURL=hlsValidator.js.map