@hed-hog/lms 0.0.365 → 0.0.370

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 (243) hide show
  1. package/dist/certificate/certificate.controller.d.ts +1 -1
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +4 -2
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +50 -0
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +73 -0
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/class-group/class-group.controller.d.ts +1 -0
  10. package/dist/class-group/class-group.controller.d.ts.map +1 -1
  11. package/dist/class-group/class-group.service.d.ts +1 -0
  12. package/dist/class-group/class-group.service.d.ts.map +1 -1
  13. package/dist/course/course-ai-usage.service.d.ts +58 -0
  14. package/dist/course/course-ai-usage.service.d.ts.map +1 -0
  15. package/dist/course/course-ai-usage.service.js +176 -0
  16. package/dist/course/course-ai-usage.service.js.map +1 -0
  17. package/dist/course/course-audio-transcription.service.d.ts +65 -1
  18. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  19. package/dist/course/course-audio-transcription.service.js +381 -29
  20. package/dist/course/course-audio-transcription.service.js.map +1 -1
  21. package/dist/course/course-export-scorm12.service.d.ts +3 -0
  22. package/dist/course/course-export-scorm12.service.d.ts.map +1 -1
  23. package/dist/course/course-export-scorm12.service.js +141 -6
  24. package/dist/course/course-export-scorm12.service.js.map +1 -1
  25. package/dist/course/course-export.service.d.ts.map +1 -1
  26. package/dist/course/course-export.service.js +2 -1
  27. package/dist/course/course-export.service.js.map +1 -1
  28. package/dist/course/course-lesson.controller.d.ts +25 -3
  29. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  30. package/dist/course/course-lesson.controller.js +71 -8
  31. package/dist/course/course-lesson.controller.js.map +1 -1
  32. package/dist/course/course-structure.controller.d.ts +30 -7
  33. package/dist/course/course-structure.controller.d.ts.map +1 -1
  34. package/dist/course/course-structure.controller.js +37 -4
  35. package/dist/course/course-structure.controller.js.map +1 -1
  36. package/dist/course/course-structure.service.d.ts +37 -5
  37. package/dist/course/course-structure.service.d.ts.map +1 -1
  38. package/dist/course/course-structure.service.js +165 -20
  39. package/dist/course/course-structure.service.js.map +1 -1
  40. package/dist/course/course-transcription-translation.service.d.ts +31 -0
  41. package/dist/course/course-transcription-translation.service.d.ts.map +1 -0
  42. package/dist/course/course-transcription-translation.service.js +227 -0
  43. package/dist/course/course-transcription-translation.service.js.map +1 -0
  44. package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
  45. package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
  46. package/dist/course/course-video-agent-pipeline.service.js +398 -0
  47. package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
  48. package/dist/course/course-video-hls.service.d.ts +14 -0
  49. package/dist/course/course-video-hls.service.d.ts.map +1 -1
  50. package/dist/course/course-video-hls.service.js +25 -8
  51. package/dist/course/course-video-hls.service.js.map +1 -1
  52. package/dist/course/course.controller.d.ts +2 -0
  53. package/dist/course/course.controller.d.ts.map +1 -1
  54. package/dist/course/course.module.d.ts.map +1 -1
  55. package/dist/course/course.module.js +9 -0
  56. package/dist/course/course.module.js.map +1 -1
  57. package/dist/course/course.service.d.ts +2 -0
  58. package/dist/course/course.service.d.ts.map +1 -1
  59. package/dist/course/course.service.js +36 -2
  60. package/dist/course/course.service.js.map +1 -1
  61. package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
  62. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
  63. package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
  64. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
  65. package/dist/course/dto/create-course-export.dto.d.ts +1 -0
  66. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -1
  67. package/dist/course/dto/create-course-export.dto.js +6 -0
  68. package/dist/course/dto/create-course-export.dto.js.map +1 -1
  69. package/dist/course/ffmpeg.util.d.ts +10 -0
  70. package/dist/course/ffmpeg.util.d.ts.map +1 -0
  71. package/dist/course/ffmpeg.util.js +79 -0
  72. package/dist/course/ffmpeg.util.js.map +1 -0
  73. package/dist/course/lms-bulk-upload-automation.service.d.ts +3 -1
  74. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
  75. package/dist/course/lms-bulk-upload-automation.service.js +33 -16
  76. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
  77. package/dist/course/lms-bulk-upload.controller.d.ts +3 -0
  78. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  79. package/dist/course/lms-bulk-upload.service.d.ts +3 -0
  80. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  81. package/dist/course/lms-bulk-upload.service.js +48 -29
  82. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  83. package/dist/course/subtitle.util.d.ts +46 -0
  84. package/dist/course/subtitle.util.d.ts.map +1 -0
  85. package/dist/course/subtitle.util.js +206 -0
  86. package/dist/course/subtitle.util.js.map +1 -0
  87. package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
  88. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  89. package/dist/enterprise/training/training-admin.service.d.ts +2 -0
  90. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  91. package/dist/enterprise/training/training-student.service.d.ts +27 -0
  92. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  93. package/dist/enterprise/training/training-student.service.js +197 -10
  94. package/dist/enterprise/training/training-student.service.js.map +1 -1
  95. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +3 -1
  96. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
  97. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +19 -5
  98. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
  99. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -1
  100. package/dist/lesson-xp-map/lesson-xp-map.module.js +2 -1
  101. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -1
  102. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
  103. package/dist/lms.module.d.ts.map +1 -1
  104. package/dist/lms.module.js +14 -0
  105. package/dist/lms.module.js.map +1 -1
  106. package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
  107. package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
  108. package/dist/platforma/dto/heartbeat.dto.js +50 -0
  109. package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
  110. package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
  111. package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
  112. package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
  113. package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
  114. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
  115. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
  116. package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
  117. package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
  118. package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
  119. package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
  120. package/dist/platforma/platforma-heartbeat.service.js +50 -0
  121. package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
  122. package/dist/platforma/platforma-performance.service.d.ts +121 -0
  123. package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
  124. package/dist/platforma/platforma-performance.service.js +500 -0
  125. package/dist/platforma/platforma-performance.service.js.map +1 -0
  126. package/dist/platforma/platforma-search.service.d.ts +21 -0
  127. package/dist/platforma/platforma-search.service.d.ts.map +1 -0
  128. package/dist/platforma/platforma-search.service.js +64 -0
  129. package/dist/platforma/platforma-search.service.js.map +1 -0
  130. package/dist/platforma/platforma-video.service.d.ts +8 -0
  131. package/dist/platforma/platforma-video.service.d.ts.map +1 -1
  132. package/dist/platforma/platforma-video.service.js +45 -2
  133. package/dist/platforma/platforma-video.service.js.map +1 -1
  134. package/dist/platforma/platforma.controller.d.ts +213 -1
  135. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  136. package/dist/platforma/platforma.controller.js +159 -2
  137. package/dist/platforma/platforma.controller.js.map +1 -1
  138. package/dist/realtime/lms-realtime.controller.d.ts +2 -0
  139. package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
  140. package/dist/realtime/lms-realtime.controller.js +31 -0
  141. package/dist/realtime/lms-realtime.controller.js.map +1 -1
  142. package/dist/realtime/lms-realtime.service.d.ts +1 -1
  143. package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
  144. package/dist/realtime/lms-realtime.service.js.map +1 -1
  145. package/dist/training/dto/create-training.dto.d.ts +9 -0
  146. package/dist/training/dto/create-training.dto.d.ts.map +1 -1
  147. package/dist/training/dto/create-training.dto.js +45 -1
  148. package/dist/training/dto/create-training.dto.js.map +1 -1
  149. package/dist/training/training.controller.d.ts +144 -0
  150. package/dist/training/training.controller.d.ts.map +1 -1
  151. package/dist/training/training.service.d.ts +149 -0
  152. package/dist/training/training.service.d.ts.map +1 -1
  153. package/dist/training/training.service.js +332 -167
  154. package/dist/training/training.service.js.map +1 -1
  155. package/hedhog/data/image_type.yaml +10 -0
  156. package/hedhog/data/route.yaml +251 -0
  157. package/hedhog/data/setting_group.yaml +97 -0
  158. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +139 -27
  159. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
  160. package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
  161. package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
  162. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +69 -57
  163. package/hedhog/frontend/app/courses/[id]/_components/CourseIssuedCertificatesCard.tsx.ejs +168 -0
  164. package/hedhog/frontend/app/courses/[id]/structure/_components/course-ai-costs-tab.tsx.ejs +191 -0
  165. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +81 -1
  166. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +12 -0
  167. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +141 -30
  168. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
  169. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +69 -1
  170. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
  171. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +267 -19
  172. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +114 -86
  173. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +239 -31
  174. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +344 -59
  175. package/hedhog/frontend/app/courses/[id]/structure/_components/lesson-video-preview.tsx.ejs +200 -0
  176. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +1 -0
  177. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -0
  178. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
  179. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -7
  180. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -0
  181. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-ai-costs.ts.ejs +40 -0
  182. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -0
  183. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +25 -0
  184. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +148 -0
  185. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +157 -8
  186. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +1 -22
  187. package/hedhog/frontend/app/courses/page.tsx.ejs +66 -13
  188. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
  189. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
  190. package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
  191. package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
  192. package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
  193. package/hedhog/frontend/app/paths/page.tsx.ejs +650 -168
  194. package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
  195. package/hedhog/frontend/messages/en.json +41 -12
  196. package/hedhog/frontend/messages/pt.json +44 -13
  197. package/hedhog/query/triggers.sql +33 -0
  198. package/hedhog/table/course_ai_usage.yaml +46 -0
  199. package/hedhog/table/course_enrollment.yaml +3 -0
  200. package/hedhog/table/course_lesson.yaml +3 -0
  201. package/hedhog/table/course_lesson_answer.yaml +37 -0
  202. package/hedhog/table/course_lesson_transcription_segment.yaml +8 -0
  203. package/hedhog/table/learning_path.yaml +6 -0
  204. package/hedhog/table/learning_path_module.yaml +22 -0
  205. package/hedhog/table/learning_path_step.yaml +9 -6
  206. package/hedhog/table/lesson_view_event.yaml +66 -0
  207. package/package.json +8 -7
  208. package/src/certificate/certificate.controller.ts +2 -0
  209. package/src/certificate/certificate.service.ts +99 -0
  210. package/src/course/course-ai-usage.service.ts +221 -0
  211. package/src/course/course-audio-transcription.service.ts +471 -43
  212. package/src/course/course-export-scorm12.service.ts +149 -5
  213. package/src/course/course-export.service.ts +1 -0
  214. package/src/course/course-lesson.controller.ts +59 -6
  215. package/src/course/course-structure.controller.ts +19 -1
  216. package/src/course/course-structure.service.ts +184 -10
  217. package/src/course/course-transcription-translation.service.ts +293 -0
  218. package/src/course/course-video-agent-pipeline.service.ts +471 -0
  219. package/src/course/course-video-hls.service.ts +30 -10
  220. package/src/course/course.module.ts +9 -0
  221. package/src/course/course.service.ts +46 -1
  222. package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
  223. package/src/course/dto/create-course-export.dto.ts +6 -0
  224. package/src/course/ffmpeg.util.ts +65 -0
  225. package/src/course/lms-bulk-upload-automation.service.ts +33 -8
  226. package/src/course/lms-bulk-upload.service.ts +20 -1
  227. package/src/course/subtitle.util.ts +220 -0
  228. package/src/enterprise/training/training-student.service.ts +224 -4
  229. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +14 -0
  230. package/src/lesson-xp-map/lesson-xp-map.module.ts +2 -1
  231. package/src/lms.module.ts +14 -0
  232. package/src/platforma/dto/heartbeat.dto.ts +30 -0
  233. package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
  234. package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
  235. package/src/platforma/platforma-heartbeat.service.ts +33 -0
  236. package/src/platforma/platforma-performance.service.ts +606 -0
  237. package/src/platforma/platforma-search.service.ts +48 -0
  238. package/src/platforma/platforma-video.service.ts +59 -3
  239. package/src/platforma/platforma.controller.ts +130 -0
  240. package/src/realtime/lms-realtime.controller.ts +27 -1
  241. package/src/realtime/lms-realtime.service.ts +2 -1
  242. package/src/training/dto/create-training.dto.ts +36 -0
  243. package/src/training/training.service.ts +360 -163
@@ -7,6 +7,7 @@ import { promises as fs } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { finished } from 'stream/promises';
9
9
  import { CourseExportService, VideoLessonWithFile } from './course-export.service';
10
+ import { balanceSubtitleLines } from './subtitle.util';
10
11
  import {
11
12
  ADLCP_ROOTV1P2_XSD,
12
13
  IMSCP_ROOTV1P1P2_XSD,
@@ -25,7 +26,7 @@ export type ScormVisualOptions = {
25
26
 
26
27
  export type Scorm12BuildParams = {
27
28
  courseId: number;
28
- settings: { visual?: ScormVisualOptions };
29
+ settings: { visual?: ScormVisualOptions; subtitleLocaleIds?: number[] };
29
30
  workDir: string;
30
31
  onProgress: (pct: number, msg: string) => Promise<void>;
31
32
  };
@@ -35,6 +36,7 @@ type LessonData = {
35
36
  title: string;
36
37
  duration: number | null;
37
38
  src: string;
39
+ subtitles: Array<{ code: string; name: string; vttFile: string }>;
38
40
  };
39
41
 
40
42
  type ModuleData = {
@@ -128,7 +130,39 @@ export class CourseExportScorm12Service {
128
130
  }
129
131
  }
130
132
 
131
- const courseData = this.buildCourseData(courseTitle, lessons, videoSrcMap);
133
+ const subtitleLocaleIds = settings.subtitleLocaleIds ?? [];
134
+ const subtitleMap: Record<number, Array<{ code: string; name: string; vttFile: string }>> = {};
135
+ const vttFileList: string[] = [];
136
+
137
+ if (subtitleLocaleIds.length > 0) {
138
+ await onProgress(72, 'Gerando arquivos de legenda...');
139
+ const locales = await (this.prisma as any).locale.findMany({
140
+ where: { id: { in: subtitleLocaleIds } },
141
+ select: { id: true, code: true, name: true },
142
+ }) as Array<{ id: number; code: string; name: string }>;
143
+ const localeMap = new Map(locales.map((l: { id: number; code: string; name: string }) => [l.id, l]));
144
+
145
+ for (const lesson of lessons) {
146
+ for (const localeId of subtitleLocaleIds) {
147
+ const locale = localeMap.get(localeId);
148
+ if (!locale) continue;
149
+ const segments = await (this.prisma as any).course_lesson_transcription_segment.findMany({
150
+ where: { course_lesson_id: lesson.id, locale_id: localeId },
151
+ select: { start_seconds: true, end_seconds: true, text: true },
152
+ orderBy: { start_seconds: 'asc' },
153
+ }) as Array<{ start_seconds: number; end_seconds: number; text: string }>;
154
+ if (segments.length === 0) continue;
155
+ const vttContent = this.generateVttContent(segments);
156
+ const vttName = `assets/lesson_${lesson.id}_${locale.code}.vtt`;
157
+ archive.append(Buffer.from(vttContent, 'utf-8'), { name: vttName });
158
+ vttFileList.push(vttName);
159
+ if (!subtitleMap[lesson.id]) subtitleMap[lesson.id] = [];
160
+ subtitleMap[lesson.id].push({ code: locale.code, name: locale.name, vttFile: vttName });
161
+ }
162
+ }
163
+ }
164
+
165
+ const courseData = this.buildCourseData(courseTitle, lessons, videoSrcMap, subtitleMap);
132
166
 
133
167
  archive.append(Buffer.from(this.generateScormApiJs()), { name: 'SCORM12API.js' });
134
168
  archive.append(Buffer.from(IMS_XML_XSD), { name: 'ims_xml.xsd' });
@@ -140,7 +174,7 @@ export class CourseExportScorm12Service {
140
174
  );
141
175
  archive.append(Buffer.from(this.generateShellHtml(courseData, visual)), { name: 'index.html' });
142
176
 
143
- const manifestXml = this.generateManifest(courseTitle, embeddedLessonIds);
177
+ const manifestXml = this.generateManifest(courseTitle, embeddedLessonIds, vttFileList);
144
178
  archive.append(Buffer.from(manifestXml), { name: 'imsmanifest.xml' });
145
179
 
146
180
  await onProgress(80, 'Finalizando pacote ZIP...');
@@ -166,6 +200,7 @@ export class CourseExportScorm12Service {
166
200
  courseTitle: string,
167
201
  lessons: VideoLessonWithFile[],
168
202
  videoSrcMap: Record<number, string>,
203
+ subtitleMap: Record<number, Array<{ code: string; name: string; vttFile: string }>>,
169
204
  ): CourseData {
170
205
  const sessionMap = new Map<number, ModuleData>();
171
206
 
@@ -184,6 +219,7 @@ export class CourseExportScorm12Service {
184
219
  title: lesson.title,
185
220
  duration: lesson.duration_seconds,
186
221
  src: videoSrcMap[lesson.id] || '',
222
+ subtitles: subtitleMap[lesson.id] ?? [],
187
223
  });
188
224
  }
189
225
 
@@ -204,10 +240,13 @@ export class CourseExportScorm12Service {
204
240
  private generateManifest(
205
241
  courseTitle: string,
206
242
  embeddedLessonIds: number[],
243
+ vttFiles: string[],
207
244
  ): string {
208
245
  const manifestId = `manifest-${Date.now()}`;
209
- const assetFiles = embeddedLessonIds.length
210
- ? embeddedLessonIds.map((id) => ` <file href="assets/lesson_${id}.mp4"/>`).join('\n') + '\n'
246
+ const videoLines = embeddedLessonIds.map((id) => ` <file href="assets/lesson_${id}.mp4"/>`);
247
+ const vttLines = vttFiles.map((f) => ` <file href="${f}"/>`);
248
+ const assetFiles = [...videoLines, ...vttLines].length
249
+ ? [...videoLines, ...vttLines].join('\n') + '\n'
211
250
  : '';
212
251
 
213
252
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -347,6 +386,10 @@ ${this.generateCss(visual)}
347
386
  <div class="footer-meta" id="footerMeta"></div>
348
387
  </div>
349
388
  <div class="footer-nav">
389
+ <div class="sub-ctrl" id="subCtrl" style="display:none">
390
+ <button class="nav-btn" id="subtitleBtn" title="Legendas">CC</button>
391
+ <div id="subtitleMenu" class="subtitle-menu"></div>
392
+ </div>
350
393
  <button class="nav-btn" id="prevBtn" disabled>&#8592; Anterior</button>
351
394
  <button class="nav-btn primary" id="nextBtn" disabled>Pr&#243;xima &#8594;</button>
352
395
  </div>
@@ -436,6 +479,11 @@ ${this.generateJs(courseJson, visual.progressStyle)}
436
479
  .nav-btn.primary:hover:not(:disabled){filter:brightness(.85)}
437
480
  .menu-toggle{display:none;position:fixed;top:10px;z-index:30;width:34px;height:34px;border-radius:8px;background:rgba(0,0,0,.75);color:#fff;border:none;cursor:pointer;font-size:1rem;align-items:center;justify-content:center}
438
481
  .overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9}
482
+ .sub-ctrl{position:relative}
483
+ .subtitle-menu{display:none;position:absolute;bottom:calc(100% + 6px);right:0;background:#1f2937;border:1px solid #374151;border-radius:6px;min-width:140px;overflow:hidden;z-index:20}
484
+ .sub-item{padding:7px 12px;cursor:pointer;font-size:${fs.xs};color:#f3f4f6;white-space:nowrap}
485
+ .sub-item:hover{background:#374151}
486
+ .sub-item.active-sub{color:${pri};font-weight:600}
439
487
  @media(max-width:768px){
440
488
  .menu-toggle{display:flex}
441
489
  .sidebar{position:fixed;top:0;bottom:0;${isRtl ? 'right:0;left:auto' : 'left:0;right:auto'};transform:${isRtl ? 'translateX(100%)' : 'translateX(-100%)'}}
@@ -453,6 +501,7 @@ ${this.generateJs(courseJson, visual.progressStyle)}
453
501
  var currentIdx=-1;
454
502
  var allLessons=[];
455
503
  var _api=null;
504
+ var currentSubCode=null;
456
505
 
457
506
  COURSE.modules.forEach(function(m){
458
507
  m.lessons.forEach(function(l){l._mod=m.title;allLessons.push(l);});
@@ -554,6 +603,63 @@ ${this.generateJs(courseJson, visual.progressStyle)}
554
603
  });
555
604
  }
556
605
 
606
+ function applySubtitle(video,code){
607
+ var tracks=video.textTracks;
608
+ for(var i=0;i<tracks.length;i++){
609
+ tracks[i].mode=(code&&tracks[i].language===code)?'showing':'hidden';
610
+ }
611
+ var btn=document.getElementById('subtitleBtn');
612
+ if(btn)btn.style.opacity=code?'1':'0.5';
613
+ }
614
+
615
+ function highlightSubMenu(){
616
+ var menu=document.getElementById('subtitleMenu');
617
+ if(!menu)return;
618
+ menu.querySelectorAll('.sub-item').forEach(function(el){
619
+ el.classList.toggle('active-sub',(el.getAttribute('data-code')||null)===currentSubCode);
620
+ });
621
+ }
622
+
623
+ function buildSubMenu(subtitles,video){
624
+ var menu=document.getElementById('subtitleMenu');
625
+ if(!menu)return;
626
+ var html='<div class="sub-item" data-code="">Desligado</div>';
627
+ subtitles.forEach(function(sub){
628
+ html+='<div class="sub-item" data-code="'+sub.code+'">'+esc(sub.name)+'</div>';
629
+ });
630
+ menu.innerHTML=html;
631
+ menu.querySelectorAll('.sub-item').forEach(function(el){
632
+ el.addEventListener('click',function(e){
633
+ e.stopPropagation();
634
+ var code=el.getAttribute('data-code')||null;
635
+ currentSubCode=code;
636
+ applySubtitle(video,currentSubCode);
637
+ menu.style.display='none';
638
+ highlightSubMenu();
639
+ });
640
+ });
641
+ highlightSubMenu();
642
+ }
643
+
644
+ function updateSubtitleTracks(lesson,video){
645
+ var oldTracks=video.querySelectorAll('track');
646
+ oldTracks.forEach(function(t){video.removeChild(t);});
647
+ var subCtrl=document.getElementById('subCtrl');
648
+ if(!subCtrl)return;
649
+ if(!lesson.subtitles||!lesson.subtitles.length){subCtrl.style.display='none';return;}
650
+ subCtrl.style.display='';
651
+ lesson.subtitles.forEach(function(sub){
652
+ var t=document.createElement('track');
653
+ t.kind='subtitles';
654
+ t.src=sub.vttFile;
655
+ t.srclang=sub.code;
656
+ t.label=sub.name;
657
+ video.appendChild(t);
658
+ });
659
+ applySubtitle(video,currentSubCode);
660
+ buildSubMenu(lesson.subtitles,video);
661
+ }
662
+
557
663
  function navigateTo(idx){
558
664
  if(idx<0||idx>=allLessons.length)return;
559
665
  if(currentIdx>=0&&idx!==currentIdx)markCompleted(allLessons[currentIdx].id);
@@ -572,6 +678,7 @@ ${this.generateJs(courseJson, visual.progressStyle)}
572
678
  empty.textContent='Esta aula n\\u00e3o possui v\\u00eddeo dispon\\u00edvel';
573
679
  empty.style.display='block';
574
680
  }
681
+ updateSubtitleTracks(lesson,video);
575
682
  document.getElementById('footerTitle').textContent=lesson.title;
576
683
  var meta=[];
577
684
  if(lesson._mod)meta.push(lesson._mod);
@@ -610,6 +717,21 @@ ${this.generateJs(courseJson, visual.progressStyle)}
610
717
  document.getElementById('prevBtn').addEventListener('click',function(){navigateTo(currentIdx-1);});
611
718
  document.getElementById('nextBtn').addEventListener('click',function(){navigateTo(currentIdx+1);});
612
719
 
720
+ (function(){
721
+ var sbtn=document.getElementById('subtitleBtn');
722
+ var smenu=document.getElementById('subtitleMenu');
723
+ if(sbtn&&smenu){
724
+ sbtn.addEventListener('click',function(e){
725
+ e.stopPropagation();
726
+ smenu.style.display=smenu.style.display==='block'?'none':'block';
727
+ highlightSubMenu();
728
+ });
729
+ }
730
+ document.addEventListener('click',function(){
731
+ if(smenu)smenu.style.display='none';
732
+ });
733
+ })();
734
+
613
735
  (function(){
614
736
  var sidebar=document.getElementById('sidebar');
615
737
  var overlay=document.getElementById('overlay');
@@ -650,6 +772,28 @@ ${this.generateJs(courseJson, visual.progressStyle)}
650
772
  });`;
651
773
  }
652
774
 
775
+ private generateVttContent(
776
+ segments: Array<{ start_seconds: number; end_seconds: number; text: string }>,
777
+ ): string {
778
+ const lines = ['WEBVTT', ''];
779
+ for (const seg of segments) {
780
+ lines.push(
781
+ `${this.secondsToVttTime(seg.start_seconds)} --> ${this.secondsToVttTime(seg.end_seconds)}`,
782
+ balanceSubtitleLines(seg.text),
783
+ '',
784
+ );
785
+ }
786
+ return lines.join('\n');
787
+ }
788
+
789
+ private secondsToVttTime(s: number): string {
790
+ const total = Math.max(0, s);
791
+ const h = Math.floor(total / 3600);
792
+ const m = Math.floor((total % 3600) / 60);
793
+ const sec = total % 60;
794
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${sec.toFixed(3).padStart(6, '0')}`;
795
+ }
796
+
653
797
  private escapeXml(str: string): string {
654
798
  return str
655
799
  .replace(/&/g, '&amp;')
@@ -109,6 +109,7 @@ export class CourseExportService {
109
109
  ) {
110
110
  const settings = {
111
111
  visual: { ...(dto.visualSettings ?? {}) },
112
+ subtitleLocaleIds: dto.subtitleLocaleIds ?? [],
112
113
  };
113
114
 
114
115
  const exportRecord = await this.prisma.course_export.create({
@@ -1,24 +1,65 @@
1
1
  import { Role } from '@hed-hog/api';
2
- import { Body, Controller, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common';
2
+ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common';
3
+ import { PlatformaVideoService } from '../platforma/platforma-video.service';
3
4
  import { CourseStructureService } from './course-structure.service';
4
5
  import { UpdateTranscriptionSegmentsDTO } from './dto/update-transcription-segments.dto';
5
6
 
6
7
  @Role()
7
8
  @Controller('lms/lessons')
8
9
  export class CourseLessonController {
9
- constructor(private readonly courseStructureService: CourseStructureService) {}
10
+ constructor(
11
+ private readonly courseStructureService: CourseStructureService,
12
+ private readonly platformaVideoService: PlatformaVideoService,
13
+ ) {}
10
14
 
11
15
  @Get(':id/transcription-segments')
12
- getTranscriptionSegments(@Param('id', ParseIntPipe) id: number) {
13
- return this.courseStructureService.getTranscriptionSegments(id);
16
+ getTranscriptionSegments(
17
+ @Param('id', ParseIntPipe) id: number,
18
+ @Query('locale_id') localeId?: string,
19
+ ) {
20
+ const parsedLocaleId =
21
+ localeId === 'null' ? null : localeId !== undefined ? parseInt(localeId, 10) : undefined;
22
+ return this.courseStructureService.getTranscriptionSegments(id, parsedLocaleId);
23
+ }
24
+
25
+ @Get(':id/transcription-locales')
26
+ getTranscriptionLocales(@Param('id', ParseIntPipe) id: number) {
27
+ return this.courseStructureService.getAvailableTranscriptionLocales(id);
14
28
  }
15
29
 
16
30
  @Put(':id/transcription-segments')
17
31
  updateTranscriptionSegments(
18
32
  @Param('id', ParseIntPipe) id: number,
19
33
  @Body() dto: UpdateTranscriptionSegmentsDTO,
34
+ @Query('locale_id') localeId?: string,
35
+ ) {
36
+ const parsedLocaleId =
37
+ localeId === 'null' ? null : localeId !== undefined ? parseInt(localeId, 10) : undefined;
38
+ return this.courseStructureService.updateTranscriptionSegments(id, dto, parsedLocaleId);
39
+ }
40
+
41
+ @Delete(':id/transcription-segments')
42
+ deleteTranscriptionLocale(
43
+ @Param('id', ParseIntPipe) id: number,
44
+ @Query('locale_id') localeId?: string,
45
+ ) {
46
+ const parsedLocaleId =
47
+ localeId === 'null' ? null : localeId !== undefined ? parseInt(localeId, 10) : null;
48
+ return this.courseStructureService.deleteTranscriptionLocale(id, parsedLocaleId);
49
+ }
50
+
51
+ @Get(':id/preview/hls-token')
52
+ getAdminHlsToken(@Param('id', ParseIntPipe) id: number) {
53
+ return this.platformaVideoService.generateAdminHlsToken(id);
54
+ }
55
+
56
+ @Get(':id/preview/subtitles-token')
57
+ getAdminSubtitlesToken(
58
+ @Param('id', ParseIntPipe) id: number,
59
+ @Query('locale_id') localeId?: string,
20
60
  ) {
21
- return this.courseStructureService.updateTranscriptionSegments(id, dto);
61
+ const parsedLocaleId = localeId !== undefined ? parseInt(localeId, 10) : undefined;
62
+ return this.platformaVideoService.generateAdminSubtitlesToken(id, parsedLocaleId);
22
63
  }
23
64
 
24
65
  @Get(':id/audio-files')
@@ -30,4 +71,16 @@ export class CourseLessonController {
30
71
  startTranscription(@Param('id', ParseIntPipe) id: number) {
31
72
  return this.courseStructureService.startTranscription(id);
32
73
  }
33
- }
74
+
75
+ @Post(':id/transcription/translate')
76
+ translateTranscription(
77
+ @Param('id', ParseIntPipe) id: number,
78
+ @Body() body: { targetLocaleId: number; sourceLocaleId?: number | null },
79
+ ) {
80
+ return this.courseStructureService.translateTranscription(
81
+ id,
82
+ body.sourceLocaleId ?? null,
83
+ body.targetLocaleId,
84
+ );
85
+ }
86
+ }
@@ -10,6 +10,7 @@ import {
10
10
  Post,
11
11
  } from '@nestjs/common';
12
12
  import { CourseStructureService } from './course-structure.service';
13
+ import { CourseVideoAgentPipelineService } from './course-video-agent-pipeline.service';
13
14
  import { CourseVideoConversionService } from './course-video-conversion.service';
14
15
  import { CourseVideoHlsService } from './course-video-hls.service';
15
16
  import { CreateCourseBulkJobDto } from './dto/create-course-bulk-job.dto';
@@ -35,6 +36,7 @@ export class CourseStructureController {
35
36
  private readonly courseStructureService: CourseStructureService,
36
37
  private readonly courseVideoConversionService: CourseVideoConversionService,
37
38
  private readonly courseVideoHlsService: CourseVideoHlsService,
39
+ private readonly courseVideoAgentPipelineService: CourseVideoAgentPipelineService,
38
40
  ) {}
39
41
 
40
42
  @Get()
@@ -162,7 +164,7 @@ export class CourseStructureController {
162
164
  @Param('lessonId', ParseIntPipe) lessonId: number,
163
165
  @Body() dto: CreateLessonVideoConversionDto,
164
166
  ) {
165
- return this.courseVideoHlsService.enqueueHls({
167
+ return this.courseVideoAgentPipelineService.startProcessing({
166
168
  userId,
167
169
  courseId,
168
170
  sessionId,
@@ -236,6 +238,21 @@ export class CourseStructureController {
236
238
  return this.courseStructureService.getVideoProcessingStats(courseId);
237
239
  }
238
240
 
241
+ @Get('transcription-locales')
242
+ getCourseTranscriptionLocales(@Param('id', ParseIntPipe) courseId: number) {
243
+ return this.courseStructureService.getCourseTranscriptionLocales(courseId);
244
+ }
245
+
246
+ @Delete('transcription-segments')
247
+ deleteAllCourseTranscriptions(@Param('id', ParseIntPipe) courseId: number) {
248
+ return this.courseStructureService.deleteAllCourseTranscriptions(courseId);
249
+ }
250
+
251
+ @Get('ai-costs')
252
+ getCourseAiCosts(@Param('id', ParseIntPipe) courseId: number) {
253
+ return this.courseStructureService.getCourseAiCosts(courseId);
254
+ }
255
+
239
256
  @Post('bulk-jobs')
240
257
  createBulkJobs(
241
258
  @User() user: any,
@@ -247,6 +264,7 @@ export class CourseStructureController {
247
264
  dto.jobType,
248
265
  user?.id ?? 0,
249
266
  dto.reprocessAlreadyProcessed,
267
+ dto.targetLocaleId,
250
268
  );
251
269
  }
252
270
  }