@hed-hog/lms 0.0.365 → 0.0.366
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.
- package/dist/class-group/class-group.controller.d.ts +1 -0
- package/dist/class-group/class-group.controller.d.ts.map +1 -1
- package/dist/class-group/class-group.service.d.ts +1 -0
- package/dist/class-group/class-group.service.d.ts.map +1 -1
- package/dist/course/course-structure.controller.d.ts +4 -2
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +6 -3
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
- package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
- package/dist/course/course-video-agent-pipeline.service.js +398 -0
- package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
- package/dist/course/course-video-hls.service.d.ts +14 -0
- package/dist/course/course-video-hls.service.d.ts.map +1 -1
- package/dist/course/course-video-hls.service.js +25 -8
- package/dist/course/course-video-hls.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +2 -0
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +5 -0
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +2 -0
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +36 -2
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/ffmpeg.util.d.ts +10 -0
- package/dist/course/ffmpeg.util.d.ts.map +1 -0
- package/dist/course/ffmpeg.util.js +79 -0
- package/dist/course/ffmpeg.util.js.map +1 -0
- package/dist/course/lms-bulk-upload-automation.service.d.ts +3 -1
- package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload-automation.service.js +7 -3
- package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +2 -0
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +10 -0
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
- package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
- package/dist/platforma/dto/heartbeat.dto.js +50 -0
- package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
- package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
- package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
- package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
- package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
- package/dist/platforma/platforma-heartbeat.service.js +50 -0
- package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
- package/dist/platforma/platforma-performance.service.d.ts +121 -0
- package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
- package/dist/platforma/platforma-performance.service.js +500 -0
- package/dist/platforma/platforma-performance.service.js.map +1 -0
- package/dist/platforma/platforma-search.service.d.ts +21 -0
- package/dist/platforma/platforma-search.service.d.ts.map +1 -0
- package/dist/platforma/platforma-search.service.js +64 -0
- package/dist/platforma/platforma-search.service.js.map +1 -0
- package/dist/platforma/platforma.controller.d.ts +115 -1
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +50 -2
- package/dist/platforma/platforma.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.controller.d.ts +2 -0
- package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.controller.js +31 -0
- package/dist/realtime/lms-realtime.controller.js.map +1 -1
- package/dist/realtime/lms-realtime.service.d.ts +1 -1
- package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
- package/dist/realtime/lms-realtime.service.js.map +1 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
- package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
- package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +141 -30
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +40 -9
- package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
- package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
- package/hedhog/frontend/app/paths/page.tsx.ejs +38 -4
- package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
- package/hedhog/frontend/messages/en.json +18 -0
- package/hedhog/frontend/messages/pt.json +21 -1
- package/hedhog/table/course_enrollment.yaml +3 -0
- package/hedhog/table/lesson_view_event.yaml +66 -0
- package/package.json +9 -8
- package/src/course/course-structure.controller.ts +3 -1
- package/src/course/course-video-agent-pipeline.service.ts +471 -0
- package/src/course/course-video-hls.service.ts +30 -10
- package/src/course/course.module.ts +5 -0
- package/src/course/course.service.ts +46 -1
- package/src/course/ffmpeg.util.ts +65 -0
- package/src/course/lms-bulk-upload-automation.service.ts +4 -1
- package/src/lms.module.ts +10 -0
- package/src/platforma/dto/heartbeat.dto.ts +30 -0
- package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
- package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
- package/src/platforma/platforma-heartbeat.service.ts +33 -0
- package/src/platforma/platforma-performance.service.ts +606 -0
- package/src/platforma/platforma-search.service.ts +48 -0
- package/src/platforma/platforma.controller.ts +42 -0
- package/src/realtime/lms-realtime.controller.ts +27 -1
- package/src/realtime/lms-realtime.service.ts +2 -1
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
var CourseVideoAgentPipelineService_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.CourseVideoAgentPipelineService = exports.LMS_TRANSCRIPTION_SAVE_JOB = exports.LMS_AUDIO_TRANSCRIBE_CHUNKS_JOB = exports.LMS_AUDIO_SPLIT_JOB = exports.LMS_AUDIO_EXTRACT_JOB = exports.VIDEO_PROCESSING_AGENT_SLUG = void 0;
|
|
20
|
+
const api_prisma_1 = require("@hed-hog/api-prisma");
|
|
21
|
+
const core_1 = require("@hed-hog/core");
|
|
22
|
+
const queue_1 = require("@hed-hog/queue");
|
|
23
|
+
const agent_1 = require("@hed-hog/agent");
|
|
24
|
+
const common_1 = require("@nestjs/common");
|
|
25
|
+
const axios_1 = __importDefault(require("axios"));
|
|
26
|
+
const child_process_1 = require("child_process");
|
|
27
|
+
const fs_1 = require("fs");
|
|
28
|
+
const os_1 = require("os");
|
|
29
|
+
const path_1 = require("path");
|
|
30
|
+
const util_1 = require("util");
|
|
31
|
+
const course_video_hls_service_1 = require("./course-video-hls.service");
|
|
32
|
+
const ffmpeg_util_1 = require("./ffmpeg.util");
|
|
33
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
34
|
+
exports.VIDEO_PROCESSING_AGENT_SLUG = 'lms-video-processing';
|
|
35
|
+
exports.LMS_AUDIO_EXTRACT_JOB = 'lms.audio.extract';
|
|
36
|
+
exports.LMS_AUDIO_SPLIT_JOB = 'lms.audio.split';
|
|
37
|
+
exports.LMS_AUDIO_TRANSCRIBE_CHUNKS_JOB = 'lms.audio.transcribe.chunks';
|
|
38
|
+
exports.LMS_TRANSCRIPTION_SAVE_JOB = 'lms.transcription.save';
|
|
39
|
+
const CHUNK_SECONDS = 60;
|
|
40
|
+
/**
|
|
41
|
+
* Decomposed, queue-backed steps of the LMS video transcription pipeline, orchestrated by the
|
|
42
|
+
* seeded `lms-video-processing` agent flow via `tool.queue_dispatch` (dispatch-and-wait).
|
|
43
|
+
*
|
|
44
|
+
* Each step is its own job so it is visible both in the queue dashboard and the agent run
|
|
45
|
+
* timeline. Intermediates are persisted (audio + chunk + transcript files) and only small
|
|
46
|
+
* file-id references travel through the agent state between steps. The existing monolithic
|
|
47
|
+
* `lms.audio.transcribe` job and legacy paths are left untouched.
|
|
48
|
+
*/
|
|
49
|
+
let CourseVideoAgentPipelineService = CourseVideoAgentPipelineService_1 = class CourseVideoAgentPipelineService {
|
|
50
|
+
constructor(prisma, fileService, settingService, registry, dbQueue, agentRuntime, hlsService) {
|
|
51
|
+
this.prisma = prisma;
|
|
52
|
+
this.fileService = fileService;
|
|
53
|
+
this.settingService = settingService;
|
|
54
|
+
this.registry = registry;
|
|
55
|
+
this.dbQueue = dbQueue;
|
|
56
|
+
this.agentRuntime = agentRuntime;
|
|
57
|
+
this.hlsService = hlsService;
|
|
58
|
+
this.logger = new common_1.Logger(CourseVideoAgentPipelineService_1.name);
|
|
59
|
+
}
|
|
60
|
+
get db() {
|
|
61
|
+
return this.prisma;
|
|
62
|
+
}
|
|
63
|
+
onModuleInit() {
|
|
64
|
+
for (const type of [
|
|
65
|
+
exports.LMS_AUDIO_EXTRACT_JOB,
|
|
66
|
+
exports.LMS_AUDIO_SPLIT_JOB,
|
|
67
|
+
exports.LMS_AUDIO_TRANSCRIBE_CHUNKS_JOB,
|
|
68
|
+
exports.LMS_TRANSCRIPTION_SAVE_JOB,
|
|
69
|
+
]) {
|
|
70
|
+
this.registry.register(type, this);
|
|
71
|
+
}
|
|
72
|
+
this.logger.log(`Registered handlers for ${exports.LMS_AUDIO_EXTRACT_JOB}, ${exports.LMS_AUDIO_SPLIT_JOB}, ${exports.LMS_AUDIO_TRANSCRIBE_CHUNKS_JOB}, ${exports.LMS_TRANSCRIPTION_SAVE_JOB}`);
|
|
73
|
+
}
|
|
74
|
+
// ───────────────────────────── orchestration entry ─────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Validates the lesson/original, registers the `video_original` file, and starts the seeded
|
|
77
|
+
* `lms-video-processing` agent run (async). Falls back to the legacy monolithic HLS pipeline
|
|
78
|
+
* when the agent flow is not seeded yet, so the feature degrades gracefully during rollout.
|
|
79
|
+
*/
|
|
80
|
+
async startProcessing(params) {
|
|
81
|
+
var _a, _b, _c;
|
|
82
|
+
const agent = await this.db.agent.findUnique({
|
|
83
|
+
where: { slug: exports.VIDEO_PROCESSING_AGENT_SLUG },
|
|
84
|
+
select: { id: true, status: true },
|
|
85
|
+
});
|
|
86
|
+
if (!agent || agent.status === 'archived') {
|
|
87
|
+
this.logger.warn(`Agent "${exports.VIDEO_PROCESSING_AGENT_SLUG}" unavailable — falling back to legacy enqueueHls.`);
|
|
88
|
+
const legacy = await this.hlsService.enqueueHls(params);
|
|
89
|
+
return { agentRunId: null, status: legacy.status };
|
|
90
|
+
}
|
|
91
|
+
await this.hlsService.prepareLessonForProcessing({
|
|
92
|
+
courseId: params.courseId,
|
|
93
|
+
sessionId: params.sessionId,
|
|
94
|
+
lessonId: params.lessonId,
|
|
95
|
+
originalFileId: params.originalFileId,
|
|
96
|
+
});
|
|
97
|
+
const settings = await this.settingService.getSettingValues([
|
|
98
|
+
'lms-audio-transcription-enabled',
|
|
99
|
+
]);
|
|
100
|
+
const transcriptionEnabled = settings['lms-audio-transcription-enabled'] !== false;
|
|
101
|
+
const course = await this.db.course.findUnique({
|
|
102
|
+
where: { id: params.courseId },
|
|
103
|
+
include: { locale: true },
|
|
104
|
+
});
|
|
105
|
+
const localeCode = (_b = (_a = course === null || course === void 0 ? void 0 : course.locale) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : 'pt';
|
|
106
|
+
const run = await this.agentRuntime.startManualRun(agent.id, {
|
|
107
|
+
course_id: params.courseId,
|
|
108
|
+
session_id: params.sessionId,
|
|
109
|
+
lesson_id: params.lessonId,
|
|
110
|
+
original_file_id: params.originalFileId,
|
|
111
|
+
user_id: params.userId,
|
|
112
|
+
transcription_enabled: transcriptionEnabled,
|
|
113
|
+
locale_code: localeCode,
|
|
114
|
+
}, params.userId);
|
|
115
|
+
this.logger.log(`Started agent run ${run === null || run === void 0 ? void 0 : run.id} for lesson ${params.lessonId} (transcription=${transcriptionEnabled}).`);
|
|
116
|
+
return { agentRunId: (_c = run === null || run === void 0 ? void 0 : run.id) !== null && _c !== void 0 ? _c : null, status: 'started' };
|
|
117
|
+
}
|
|
118
|
+
// ───────────────────────────── job dispatch ─────────────────────────────
|
|
119
|
+
async handle(job) {
|
|
120
|
+
switch (job.type) {
|
|
121
|
+
case exports.LMS_AUDIO_EXTRACT_JOB:
|
|
122
|
+
return this.handleExtract(job);
|
|
123
|
+
case exports.LMS_AUDIO_SPLIT_JOB:
|
|
124
|
+
return this.handleSplit(job);
|
|
125
|
+
case exports.LMS_AUDIO_TRANSCRIBE_CHUNKS_JOB:
|
|
126
|
+
return this.handleTranscribeChunks(job);
|
|
127
|
+
case exports.LMS_TRANSCRIPTION_SAVE_JOB:
|
|
128
|
+
return this.handleSave(job);
|
|
129
|
+
default:
|
|
130
|
+
throw new queue_1.NonRetryableError(`Unsupported job type "${job.type}"`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Step: extract a 16 kHz mono mp3 from the original video and persist it as lesson_audio. */
|
|
134
|
+
async handleExtract(job) {
|
|
135
|
+
var _a, _b, _c, _d, _e;
|
|
136
|
+
const courseId = Number((_a = job.payload) === null || _a === void 0 ? void 0 : _a.courseId);
|
|
137
|
+
const lessonId = Number((_b = job.payload) === null || _b === void 0 ? void 0 : _b.lessonId);
|
|
138
|
+
const originalFileId = Number((_c = job.payload) === null || _c === void 0 ? void 0 : _c.originalFileId);
|
|
139
|
+
if (!lessonId || !originalFileId) {
|
|
140
|
+
throw new queue_1.NonRetryableError('lms.audio.extract: lessonId and originalFileId are required.');
|
|
141
|
+
}
|
|
142
|
+
const workDir = await fs_1.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), `lms-extract-${job.id}-`));
|
|
143
|
+
const inputPath = (0, path_1.join)(workDir, `original-${originalFileId}`);
|
|
144
|
+
const mp3Path = (0, path_1.join)(workDir, `lesson_${lessonId}_audio.mp3`);
|
|
145
|
+
try {
|
|
146
|
+
await this.fileService.downloadToPath(originalFileId, inputPath);
|
|
147
|
+
await execFileAsync((0, ffmpeg_util_1.getFfmpegCommand)(), ['-y', '-i', inputPath, '-vn', '-ar', '16000', '-ac', '1', '-c:a', 'libmp3lame', '-b:a', '32k', mp3Path], { maxBuffer: 1024 * 1024 * 20, windowsHide: true });
|
|
148
|
+
const course = await this.db.course.findUnique({
|
|
149
|
+
where: { id: courseId },
|
|
150
|
+
select: { locale_id: true },
|
|
151
|
+
});
|
|
152
|
+
const uploaded = await this.fileService.uploadFromPath('lms/lessons/audio', mp3Path, {
|
|
153
|
+
originalname: (0, path_1.basename)(mp3Path),
|
|
154
|
+
mimetype: 'audio/mp3',
|
|
155
|
+
});
|
|
156
|
+
const defaultLocale = await this.db.locale.findFirst({
|
|
157
|
+
where: { OR: [{ code: 'pt-BR' }, { code: 'pt' }] },
|
|
158
|
+
select: { id: true },
|
|
159
|
+
orderBy: { id: 'asc' },
|
|
160
|
+
});
|
|
161
|
+
const resolvedLocaleId = (_e = (_d = course === null || course === void 0 ? void 0 : course.locale_id) !== null && _d !== void 0 ? _d : defaultLocale === null || defaultLocale === void 0 ? void 0 : defaultLocale.id) !== null && _e !== void 0 ? _e : null;
|
|
162
|
+
const existing = await this.db.course_lesson_file.findFirst({
|
|
163
|
+
where: { course_lesson_id: lessonId, type: 'lesson_audio' },
|
|
164
|
+
select: { id: true },
|
|
165
|
+
});
|
|
166
|
+
if (existing) {
|
|
167
|
+
await this.db.course_lesson_file.update({
|
|
168
|
+
where: { id: existing.id },
|
|
169
|
+
data: { file_id: uploaded.id, locale_id: resolvedLocaleId, title: 'Audio Original.mp3', type: 'lesson_audio', is_public: false },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await this.db.course_lesson_file.create({
|
|
174
|
+
data: { course_lesson_id: lessonId, file_id: uploaded.id, title: 'Audio Original.mp3', type: 'lesson_audio', is_public: false, locale_id: resolvedLocaleId },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
this.logger.log(`[extract job=${job.id}] lesson ${lessonId} audio fileId=${uploaded.id}`);
|
|
178
|
+
return { audioFileId: uploaded.id };
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Step: split the lesson audio into 60s chunks, uploaded to a staging prefix. */
|
|
185
|
+
async handleSplit(job) {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
const lessonId = Number((_a = job.payload) === null || _a === void 0 ? void 0 : _a.lessonId);
|
|
188
|
+
const audioFileId = Number((_b = job.payload) === null || _b === void 0 ? void 0 : _b.audioFileId);
|
|
189
|
+
if (!lessonId || !audioFileId) {
|
|
190
|
+
throw new queue_1.NonRetryableError('lms.audio.split: lessonId and audioFileId are required.');
|
|
191
|
+
}
|
|
192
|
+
const workDir = await fs_1.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), `lms-split-${job.id}-`));
|
|
193
|
+
const sourceAudioPath = (0, path_1.join)(workDir, `audio_${lessonId}.source`);
|
|
194
|
+
const normalizedAudioPath = (0, path_1.join)(workDir, `audio_${lessonId}.normalized.mp3`);
|
|
195
|
+
try {
|
|
196
|
+
await this.fileService.downloadToPath(audioFileId, sourceAudioPath);
|
|
197
|
+
await execFileAsync((0, ffmpeg_util_1.getFfmpegCommand)(), ['-y', '-i', sourceAudioPath, '-ar', '16000', '-ac', '1', '-c:a', 'libmp3lame', '-b:a', '32k', normalizedAudioPath], { maxBuffer: 1024 * 1024 * 20, windowsHide: true });
|
|
198
|
+
const chunkPattern = (0, path_1.join)(workDir, 'chunk_%04d.mp3');
|
|
199
|
+
await execFileAsync((0, ffmpeg_util_1.getFfmpegCommand)(), ['-y', '-i', normalizedAudioPath, '-f', 'segment', '-segment_time', String(CHUNK_SECONDS), '-ar', '16000', '-ac', '1', '-c:a', 'libmp3lame', '-b:a', '32k', chunkPattern], { maxBuffer: 1024 * 1024 * 20, windowsHide: true });
|
|
200
|
+
const chunkNames = (await fs_1.promises.readdir(workDir))
|
|
201
|
+
.filter((n) => n.startsWith('chunk_') && n.endsWith('.mp3'))
|
|
202
|
+
.sort((a, b) => a.localeCompare(b));
|
|
203
|
+
const chunkFileIds = [];
|
|
204
|
+
for (const name of chunkNames) {
|
|
205
|
+
const uploaded = await this.fileService.uploadFromPath(`lms/lessons/transcribe-chunks/${lessonId}`, (0, path_1.join)(workDir, name), { originalname: name, mimetype: 'audio/mpeg' });
|
|
206
|
+
chunkFileIds.push(uploaded.id);
|
|
207
|
+
}
|
|
208
|
+
this.logger.log(`[split job=${job.id}] lesson ${lessonId} → ${chunkFileIds.length} chunk(s)`);
|
|
209
|
+
return { chunkFileIds, chunkCount: chunkFileIds.length };
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Step: transcribe each chunk via OpenAI Whisper and persist the merged transcript JSON. */
|
|
216
|
+
async handleTranscribeChunks(job) {
|
|
217
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
218
|
+
const courseId = Number((_a = job.payload) === null || _a === void 0 ? void 0 : _a.courseId);
|
|
219
|
+
const lessonId = Number((_b = job.payload) === null || _b === void 0 ? void 0 : _b.lessonId);
|
|
220
|
+
const chunkFileIds = this.parseIdArray((_c = job.payload) === null || _c === void 0 ? void 0 : _c.chunkFileIds);
|
|
221
|
+
if (!lessonId || chunkFileIds.length === 0) {
|
|
222
|
+
throw new queue_1.NonRetryableError('lms.audio.transcribe.chunks: lessonId and chunkFileIds are required.');
|
|
223
|
+
}
|
|
224
|
+
const apiKey = await this.resolveOpenAiKey();
|
|
225
|
+
const course = await this.db.course.findUnique({
|
|
226
|
+
where: { id: courseId },
|
|
227
|
+
include: { locale: true },
|
|
228
|
+
});
|
|
229
|
+
const language = (_e = (_d = course === null || course === void 0 ? void 0 : course.locale) === null || _d === void 0 ? void 0 : _d.code) !== null && _e !== void 0 ? _e : 'pt';
|
|
230
|
+
const workDir = await fs_1.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), `lms-transcribe-${job.id}-`));
|
|
231
|
+
try {
|
|
232
|
+
const segments = [];
|
|
233
|
+
for (let i = 0; i < chunkFileIds.length; i += 1) {
|
|
234
|
+
const chunkPath = (0, path_1.join)(workDir, `chunk_${i}.mp3`);
|
|
235
|
+
await this.fileService.downloadToPath(chunkFileIds[i], chunkPath);
|
|
236
|
+
const offsetSeconds = i * CHUNK_SECONDS;
|
|
237
|
+
const buffer = await fs_1.promises.readFile(chunkPath);
|
|
238
|
+
const formData = new FormData();
|
|
239
|
+
formData.append('file', new Blob([buffer], { type: 'audio/mpeg' }), `chunk_${i}.mp3`);
|
|
240
|
+
formData.append('model', 'whisper-1');
|
|
241
|
+
formData.append('response_format', 'verbose_json');
|
|
242
|
+
formData.append('language', language);
|
|
243
|
+
const headers = { Authorization: `Bearer ${apiKey}` };
|
|
244
|
+
const maybeHeaders = (_g = (_f = formData).getHeaders) === null || _g === void 0 ? void 0 : _g.call(_f);
|
|
245
|
+
if (maybeHeaders && typeof maybeHeaders === 'object')
|
|
246
|
+
Object.assign(headers, maybeHeaders);
|
|
247
|
+
const response = await axios_1.default.post('https://api.openai.com/v1/audio/transcriptions', formData, { headers });
|
|
248
|
+
for (const segment of (_j = (_h = response.data) === null || _h === void 0 ? void 0 : _h.segments) !== null && _j !== void 0 ? _j : []) {
|
|
249
|
+
const text = String((_k = segment === null || segment === void 0 ? void 0 : segment.text) !== null && _k !== void 0 ? _k : '').trim();
|
|
250
|
+
if (!text)
|
|
251
|
+
continue;
|
|
252
|
+
segments.push({
|
|
253
|
+
course_lesson_id: lessonId,
|
|
254
|
+
start_seconds: offsetSeconds + Number((_l = segment === null || segment === void 0 ? void 0 : segment.start) !== null && _l !== void 0 ? _l : 0),
|
|
255
|
+
end_seconds: offsetSeconds + Number((_m = segment === null || segment === void 0 ? void 0 : segment.end) !== null && _m !== void 0 ? _m : 0),
|
|
256
|
+
text,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const transcriptPath = (0, path_1.join)(workDir, `transcript_${lessonId}.json`);
|
|
261
|
+
await fs_1.promises.writeFile(transcriptPath, JSON.stringify({ lessonId, segments }), 'utf8');
|
|
262
|
+
const uploaded = await this.fileService.uploadFromPath(`lms/lessons/transcripts/${lessonId}`, transcriptPath, { originalname: `transcript_${lessonId}.json`, mimetype: 'application/json' });
|
|
263
|
+
this.logger.log(`[transcribe job=${job.id}] lesson ${lessonId} → ${segments.length} segment(s)`);
|
|
264
|
+
return { transcriptFileId: uploaded.id, segmentCount: segments.length };
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
await fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Step: join + persist the transcript segments and trigger XP calculation. */
|
|
271
|
+
async handleSave(job) {
|
|
272
|
+
var _a, _b, _c;
|
|
273
|
+
const lessonId = Number((_a = job.payload) === null || _a === void 0 ? void 0 : _a.lessonId);
|
|
274
|
+
const transcriptFileId = Number((_b = job.payload) === null || _b === void 0 ? void 0 : _b.transcriptFileId);
|
|
275
|
+
const userId = Number((_c = job.payload) === null || _c === void 0 ? void 0 : _c.userId) || 0;
|
|
276
|
+
if (!lessonId || !transcriptFileId) {
|
|
277
|
+
throw new queue_1.NonRetryableError('lms.transcription.save: lessonId and transcriptFileId are required.');
|
|
278
|
+
}
|
|
279
|
+
const workDir = await fs_1.promises.mkdtemp((0, path_1.join)((0, os_1.tmpdir)(), `lms-tsave-${job.id}-`));
|
|
280
|
+
try {
|
|
281
|
+
const transcriptPath = (0, path_1.join)(workDir, 'transcript.json');
|
|
282
|
+
await this.fileService.downloadToPath(transcriptFileId, transcriptPath);
|
|
283
|
+
const parsed = JSON.parse(await fs_1.promises.readFile(transcriptPath, 'utf8'));
|
|
284
|
+
const segments = Array.isArray(parsed === null || parsed === void 0 ? void 0 : parsed.segments)
|
|
285
|
+
? parsed.segments
|
|
286
|
+
: [];
|
|
287
|
+
await this.prisma.$transaction([
|
|
288
|
+
this.db.course_lesson_transcription_segment.deleteMany({
|
|
289
|
+
where: { course_lesson_id: lessonId },
|
|
290
|
+
}),
|
|
291
|
+
this.db.course_lesson_transcription_segment.createMany({
|
|
292
|
+
data: segments.map((s) => {
|
|
293
|
+
var _a, _b, _c;
|
|
294
|
+
return ({
|
|
295
|
+
course_lesson_id: lessonId,
|
|
296
|
+
start_seconds: Number((_a = s === null || s === void 0 ? void 0 : s.start_seconds) !== null && _a !== void 0 ? _a : 0),
|
|
297
|
+
end_seconds: Number((_b = s === null || s === void 0 ? void 0 : s.end_seconds) !== null && _b !== void 0 ? _b : 0),
|
|
298
|
+
text: String((_c = s === null || s === void 0 ? void 0 : s.text) !== null && _c !== void 0 ? _c : ''),
|
|
299
|
+
});
|
|
300
|
+
}),
|
|
301
|
+
}),
|
|
302
|
+
]);
|
|
303
|
+
this.logger.log(`[save job=${job.id}] lesson ${lessonId} saved ${segments.length} segment(s)`);
|
|
304
|
+
await this.triggerXpCalculation(lessonId, userId);
|
|
305
|
+
return { saved: segments.length };
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
await fs_1.promises.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// ───────────────────────────── helpers ─────────────────────────────
|
|
312
|
+
parseIdArray(raw) {
|
|
313
|
+
let arr = raw;
|
|
314
|
+
if (typeof raw === 'string') {
|
|
315
|
+
try {
|
|
316
|
+
arr = JSON.parse(raw);
|
|
317
|
+
}
|
|
318
|
+
catch (_a) {
|
|
319
|
+
arr = [];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!Array.isArray(arr))
|
|
323
|
+
return [];
|
|
324
|
+
return arr.map((x) => Number(x)).filter((n) => Number.isFinite(n) && n > 0);
|
|
325
|
+
}
|
|
326
|
+
async resolveOpenAiKey() {
|
|
327
|
+
const settings = await this.settingService.getSettingValues(['ai-openai-profile-id']);
|
|
328
|
+
const profileId = Number(settings['ai-openai-profile-id']);
|
|
329
|
+
let apiKey = '';
|
|
330
|
+
if (profileId) {
|
|
331
|
+
const profile = await this.db.integration_profile.findUnique({
|
|
332
|
+
where: { id: profileId },
|
|
333
|
+
include: { integration_provider: { select: { slug: true } } },
|
|
334
|
+
});
|
|
335
|
+
if (profile) {
|
|
336
|
+
apiKey = (0, core_1.buildAiConfigFromIntegration)(profile.integration_provider.slug, profile.config).apiKey;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (!apiKey) {
|
|
340
|
+
throw new queue_1.NonRetryableError('Transcrição de áudio requer um perfil OpenAI configurado (Settings → LMS → ai-openai-profile-id).');
|
|
341
|
+
}
|
|
342
|
+
return apiKey;
|
|
343
|
+
}
|
|
344
|
+
async triggerXpCalculation(lessonId, userId) {
|
|
345
|
+
try {
|
|
346
|
+
const existingMap = await this.db.lesson_xp_map.findUnique({
|
|
347
|
+
where: { course_lesson_id: lessonId },
|
|
348
|
+
select: { id: true },
|
|
349
|
+
});
|
|
350
|
+
let mapId;
|
|
351
|
+
if (existingMap) {
|
|
352
|
+
mapId = existingMap.id;
|
|
353
|
+
await this.prisma.$executeRawUnsafe(`UPDATE lesson_xp_map
|
|
354
|
+
SET status = 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum,
|
|
355
|
+
processing_error = NULL,
|
|
356
|
+
updated_at = NOW()
|
|
357
|
+
WHERE id = $1`, mapId);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
const rows = await this.prisma.$queryRawUnsafe(`INSERT INTO lesson_xp_map (course_lesson_id, version, total_xp, status, created_at, updated_at)
|
|
361
|
+
VALUES ($1, 1, 0, 'processing'::lesson_xp_map_status_d4e5f6a7b8_enum, NOW(), NOW())
|
|
362
|
+
RETURNING id`, lessonId);
|
|
363
|
+
mapId = rows[0].id;
|
|
364
|
+
}
|
|
365
|
+
await this.dbQueue.enqueue({
|
|
366
|
+
type: 'lms.lesson.xp.calculate',
|
|
367
|
+
queueName: 'lms.lesson.xp.calculate',
|
|
368
|
+
payload: { lessonId, mapId, userId },
|
|
369
|
+
maxAttempts: 3,
|
|
370
|
+
sourceModule: 'lms',
|
|
371
|
+
sourceEntity: 'course_lesson',
|
|
372
|
+
sourceEntityId: String(lessonId),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
this.logger.warn(`Failed to enqueue XP calculation for lesson ${lessonId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
exports.CourseVideoAgentPipelineService = CourseVideoAgentPipelineService;
|
|
381
|
+
exports.CourseVideoAgentPipelineService = CourseVideoAgentPipelineService = CourseVideoAgentPipelineService_1 = __decorate([
|
|
382
|
+
(0, common_1.Injectable)(),
|
|
383
|
+
__param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_prisma_1.PrismaService))),
|
|
384
|
+
__param(1, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.FileService))),
|
|
385
|
+
__param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.SettingService))),
|
|
386
|
+
__param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => queue_1.QueueHandlerRegistry))),
|
|
387
|
+
__param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => queue_1.DatabaseQueueProvider))),
|
|
388
|
+
__param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() => agent_1.AgentRuntimeService))),
|
|
389
|
+
__param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() => course_video_hls_service_1.CourseVideoHlsService))),
|
|
390
|
+
__metadata("design:paramtypes", [api_prisma_1.PrismaService,
|
|
391
|
+
core_1.FileService,
|
|
392
|
+
core_1.SettingService,
|
|
393
|
+
queue_1.QueueHandlerRegistry,
|
|
394
|
+
queue_1.DatabaseQueueProvider,
|
|
395
|
+
agent_1.AgentRuntimeService,
|
|
396
|
+
course_video_hls_service_1.CourseVideoHlsService])
|
|
397
|
+
], CourseVideoAgentPipelineService);
|
|
398
|
+
//# sourceMappingURL=course-video-agent-pipeline.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-video-agent-pipeline.service.js","sourceRoot":"","sources":["../../src/course/course-video-agent-pipeline.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,20 @@ export declare class CourseVideoHlsService implements OnModuleInit, IJobHandler
|
|
|
13
13
|
private readonly logger;
|
|
14
14
|
constructor(prisma: PrismaService, fileService: FileService, settingService: SettingService, notificationService: NotificationService, registry: QueueHandlerRegistry, queueJob: QueueJobService);
|
|
15
15
|
onModuleInit(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Validates a lesson is an eligible file-storage video and registers the uploaded original
|
|
18
|
+
* as its `video_original` file. Shared by the legacy `enqueueHls` path and the new
|
|
19
|
+
* agent-orchestrated pipeline (CourseVideoAgentPipelineService.startProcessing).
|
|
20
|
+
*/
|
|
21
|
+
prepareLessonForProcessing(params: {
|
|
22
|
+
courseId: number;
|
|
23
|
+
sessionId: number;
|
|
24
|
+
lessonId: number;
|
|
25
|
+
originalFileId: number;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
content: Record<string, unknown> | null;
|
|
28
|
+
original: any;
|
|
29
|
+
}>;
|
|
16
30
|
enqueueHls(params: {
|
|
17
31
|
userId: number;
|
|
18
32
|
courseId: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-video-hls.service.d.ts","sourceRoot":"","sources":["../../src/course/course-video-hls.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAML,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAOxB,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AA8BjD,qBACa,qBAAsB,YAAW,YAAY,EAAE,WAAW;IAKnE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAEpC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAd3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;gBAI9C,MAAM,EAAE,aAAa,EAErB,WAAW,EAAE,WAAW,EAExB,cAAc,EAAE,cAAc,EAE9B,mBAAmB,EAAE,mBAAmB,EAExC,QAAQ,EAAE,oBAAoB,EAE9B,QAAQ,EAAE,eAAe;IAG5C,YAAY;
|
|
1
|
+
{"version":3,"file":"course-video-hls.service.d.ts","sourceRoot":"","sources":["../../src/course/course-video-hls.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAML,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAOxB,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AA8BjD,qBACa,qBAAsB,YAAW,YAAY,EAAE,WAAW;IAKnE,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAEpC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAd3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;gBAI9C,MAAM,EAAE,aAAa,EAErB,WAAW,EAAE,WAAW,EAExB,cAAc,EAAE,cAAc,EAE9B,mBAAmB,EAAE,mBAAmB,EAExC,QAAQ,EAAE,oBAAoB,EAE9B,QAAQ,EAAE,eAAe;IAG5C,YAAY;IAKZ;;;;OAIG;IACG,0BAA0B,CAAC,MAAM,EAAE;QACvC,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC;IA6CjE,UAAU,CAAC,MAAM,EAAE;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB;;;;IA+DK,MAAM,CAAC,GAAG,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAClC,GAAG,OAAO,CAAC,GAAG,CAAC;YA0QF,WAAW;YAqEX,kBAAkB;IA8EhC,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,gBAAgB;YAmBV,gBAAgB;YAehB,oBAAoB;YAmBpB,4BAA4B;YAgE5B,2BAA2B;YAyE3B,gBAAgB;YAyBhB,mBAAmB;IAoBjC,OAAO,CAAC,2BAA2B;YAerB,uBAAuB;IAYrC,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,2BAA2B;IAKnC,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,iBAAiB;CA2B1B"}
|
|
@@ -44,7 +44,12 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
44
44
|
this.registry.register(exports.LMS_VIDEO_HLS_JOB, this);
|
|
45
45
|
this.logger.log(`Registered handler for "${exports.LMS_VIDEO_HLS_JOB}"`);
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Validates a lesson is an eligible file-storage video and registers the uploaded original
|
|
49
|
+
* as its `video_original` file. Shared by the legacy `enqueueHls` path and the new
|
|
50
|
+
* agent-orchestrated pipeline (CourseVideoAgentPipelineService.startProcessing).
|
|
51
|
+
*/
|
|
52
|
+
async prepareLessonForProcessing(params) {
|
|
48
53
|
var _a, _b, _c;
|
|
49
54
|
const lesson = await this.prisma.course_lesson.findFirst({
|
|
50
55
|
where: {
|
|
@@ -81,6 +86,10 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
81
86
|
public: false,
|
|
82
87
|
overwrite: true,
|
|
83
88
|
});
|
|
89
|
+
return { content, original };
|
|
90
|
+
}
|
|
91
|
+
async enqueueHls(params) {
|
|
92
|
+
const { content } = await this.prepareLessonForProcessing(params);
|
|
84
93
|
const asyncNotification = await this.notificationService.create({
|
|
85
94
|
user_id: params.userId,
|
|
86
95
|
title: 'Geração de HLS em andamento',
|
|
@@ -133,10 +142,18 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
133
142
|
return { queueJobId: job.id, status: 'queued' };
|
|
134
143
|
}
|
|
135
144
|
async handle(job) {
|
|
136
|
-
var _a, _b, _c, _d;
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
145
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
146
|
+
// Ids are coerced because agent-dispatched payloads template values as strings.
|
|
147
|
+
const courseId = Number((_a = job.payload) === null || _a === void 0 ? void 0 : _a.courseId);
|
|
148
|
+
const sessionId = Number((_b = job.payload) === null || _b === void 0 ? void 0 : _b.sessionId);
|
|
149
|
+
const lessonId = Number((_c = job.payload) === null || _c === void 0 ? void 0 : _c.lessonId);
|
|
150
|
+
const originalFileId = Number((_d = job.payload) === null || _d === void 0 ? void 0 : _d.originalFileId);
|
|
151
|
+
// Set by the agent pipeline: HLS-only, since audio extraction + transcription are
|
|
152
|
+
// dispatched as their own visible steps. Legacy callers omit it (full behaviour).
|
|
153
|
+
const skipAudioTranscription = ((_e = job.payload) === null || _e === void 0 ? void 0 : _e.skipAudioTranscription) === true ||
|
|
154
|
+
((_f = job.payload) === null || _f === void 0 ? void 0 : _f.skipAudioTranscription) === 'true';
|
|
155
|
+
const notificationContext = Number.isInteger(Number((_g = job.payload) === null || _g === void 0 ? void 0 : _g.notificationId)) &&
|
|
156
|
+
Number.isInteger(Number((_h = job.payload) === null || _h === void 0 ? void 0 : _h.notificationUserId))
|
|
140
157
|
? { notificationId: Number(job.payload.notificationId), userId: Number(job.payload.notificationUserId) }
|
|
141
158
|
: undefined;
|
|
142
159
|
const emitProgress = async (message, metadata) => {
|
|
@@ -147,7 +164,7 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
147
164
|
}
|
|
148
165
|
this.logger.debug(`[HLS job=${job.id}] payload OK — courseId=${courseId} lessonId=${lessonId} originalFileId=${originalFileId} attempt=${job.attempts}/${job.max_attempts}`);
|
|
149
166
|
const maxInputBytes = this.getPositiveIntegerEnv('LMS_VIDEO_MAX_INPUT_BYTES');
|
|
150
|
-
const ffmpegTimeoutMs = (
|
|
167
|
+
const ffmpegTimeoutMs = (_j = this.getPositiveIntegerEnv('LMS_VIDEO_FFMPEG_TIMEOUT_MS')) !== null && _j !== void 0 ? _j : 1000 * 60 * 60 * 2;
|
|
151
168
|
this.logger.debug(`[HLS job=${job.id}] loading settings...`);
|
|
152
169
|
const settings = await this.settingService.getSettingValues([
|
|
153
170
|
'lms-hls-resolutions',
|
|
@@ -193,7 +210,7 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
212
|
catch (err) {
|
|
196
|
-
this.logger.warn(`Queue job ${job.id}: failed to probe duration: ${(
|
|
213
|
+
this.logger.warn(`Queue job ${job.id}: failed to probe duration: ${(_k = err === null || err === void 0 ? void 0 : err.message) !== null && _k !== void 0 ? _k : err}`);
|
|
197
214
|
}
|
|
198
215
|
this.logger.debug(`[HLS job=${job.id}] probing source height...`);
|
|
199
216
|
const sourceHeight = await this.probeVideoHeight(inputPath);
|
|
@@ -266,7 +283,7 @@ let CourseVideoHlsService = CourseVideoHlsService_1 = class CourseVideoHlsServic
|
|
|
266
283
|
else {
|
|
267
284
|
this.logger.debug(`[HLS job=${job.id}] frame extraction disabled — skipping`);
|
|
268
285
|
}
|
|
269
|
-
if (transcriptionEnabled) {
|
|
286
|
+
if (transcriptionEnabled && !skipAudioTranscription) {
|
|
270
287
|
this.logger.debug(`[HLS job=${job.id}] starting audio extraction for transcription`);
|
|
271
288
|
await emitProgress('Extraindo áudio do vídeo...', { phase: 'extract_audio', lessonId });
|
|
272
289
|
const audioFileId = await this.extractAndUploadLessonAudio({
|