Spaces:
Running
Running
Download apps/api/dist/queue/content.processor.js from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 12.6 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/queue/content.processor.js
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/dist/queue/content.processor.js
-
curl -L -o content.processor.js https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/dist/queue/content.processor.js
12.6 kB
| ; | |
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | |
| var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | |
| if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | |
| 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; | |
| return c > 3 && r && Object.defineProperty(target, key, r), r; | |
| }; | |
| var __metadata = (this && this.__metadata) || function (k, v) { | |
| if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | |
| }; | |
| var ContentProcessor_1; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.ContentProcessor = void 0; | |
| const bullmq_1 = require("@nestjs/bullmq"); | |
| const common_1 = require("@nestjs/common"); | |
| const bullmq_2 = require("bullmq"); | |
| const prisma_service_1 = require("../prisma/prisma.service"); | |
| const ai_service_1 = require("../ai/ai.service"); | |
| const export_renderer_service_1 = require("../common/export/export-renderer.service"); | |
| let ContentProcessor = ContentProcessor_1 = class ContentProcessor extends bullmq_1.WorkerHost { | |
| prisma; | |
| aiService; | |
| exportRenderer; | |
| logger = new common_1.Logger(ContentProcessor_1.name); | |
| constructor(prisma, aiService, exportRenderer) { | |
| super(); | |
| this.prisma = prisma; | |
| this.aiService = aiService; | |
| this.exportRenderer = exportRenderer; | |
| } | |
| async process(job) { | |
| this.logger.log(`Processing content job ${job.id} of type ${job.name}`); | |
| switch (job.name) { | |
| case "transcribe": | |
| return this.handleTranscription(job.data, job); | |
| case "generate-clips": | |
| return this.handleClipGeneration(job.data, job); | |
| case "export": | |
| return this.handleExport(job.data, job); | |
| default: | |
| this.logger.warn(`Unknown job type: ${job.name}`); | |
| } | |
| } | |
| async handleTranscription(data, job) { | |
| this.logger.log(`Starting transcription for episode ${data.episodeId}`); | |
| await job.updateProgress(10); | |
| const episode = await this.prisma.episode.findUnique({ | |
| where: { id: data.episodeId }, | |
| include: { | |
| podcast: { | |
| select: { workspaceId: true } | |
| } | |
| } | |
| }); | |
| if (!episode) { | |
| throw new Error(`Episode with ID ${data.episodeId} not found`); | |
| } | |
| const workspaceId = episode.podcast.workspaceId; | |
| await job.updateProgress(30); | |
| let audioBuffer; | |
| try { | |
| if (data.audioUrl && data.audioUrl.startsWith("http")) { | |
| const audioResponse = await fetch(data.audioUrl); | |
| if (!audioResponse.ok) { | |
| throw new Error(`Failed to download audio: ${audioResponse.statusText}`); | |
| } | |
| const arrayBuffer = await audioResponse.arrayBuffer(); | |
| audioBuffer = Buffer.from(arrayBuffer); | |
| } | |
| else { | |
| audioBuffer = Buffer.from("placeholder audio data"); | |
| } | |
| } | |
| catch (downloadError) { | |
| this.logger.warn(`Could not download audio from ${data.audioUrl}, using placeholder buffer. Error: ${downloadError instanceof Error ? downloadError.message : String(downloadError)}`); | |
| audioBuffer = Buffer.from("placeholder audio data"); | |
| } | |
| await job.updateProgress(50); | |
| const filename = data.audioUrl ? data.audioUrl.split("/").pop() || "audio.wav" : "audio.wav"; | |
| const result = await this.aiService.transcribeAudio(workspaceId, audioBuffer, filename, { | |
| language: data.language || "en", | |
| }); | |
| await job.updateProgress(80); | |
| const transcript = await this.prisma.transcript.create({ | |
| data: { | |
| episodeId: data.episodeId, | |
| fullText: result.text, | |
| segments: (result.segments || []), | |
| speakerDiarization: {}, | |
| language: result.language || data.language || "en", | |
| confidenceScore: null, | |
| processingDuration: null, | |
| }, | |
| }); | |
| await job.updateProgress(100); | |
| this.logger.log(`Transcription completed and saved for episode ${data.episodeId}`); | |
| return { | |
| transcriptId: transcript.id, | |
| text: result.text, | |
| duration: result.duration || 3600, | |
| }; | |
| } | |
| async handleClipGeneration(data, job) { | |
| this.logger.log(`Generating clips for episode ${data.episodeId}`); | |
| const candidates = await this.prisma.clip.findMany({ | |
| where: { episodeId: data.episodeId }, | |
| select: { id: true, startSeconds: true, endSeconds: true }, | |
| }); | |
| const clipIds = []; | |
| for (let i = 0; i < data.clips.length; i++) { | |
| const want = data.clips[i]; | |
| const progress = Math.round(((i + 1) / data.clips.length) * 100); | |
| await job.updateProgress(progress); | |
| const matched = candidates.find((c) => Math.abs(c.startSeconds - want.startSeconds) < 0.5 && | |
| Math.abs(c.endSeconds - want.endSeconds) < 0.5); | |
| if (matched) { | |
| clipIds.push(matched.id); | |
| } | |
| else { | |
| this.logger.warn(`No pre-existing Clip row matched AI-detected range ` + | |
| `${want.startSeconds}s-${want.endSeconds}s for episode ${data.episodeId}`); | |
| } | |
| } | |
| if (clipIds.length === 0) { | |
| this.logger.warn(`Clip generation produced no linked rows for episode ${data.episodeId}. ` + | |
| `The AI may have detected ranges that did not match the persisted clips.`); | |
| } | |
| return { clipIds }; | |
| } | |
| async handleExport(data, job) { | |
| this.logger.log(`Exporting clip ${data.clipId} for ${data.platform}`); | |
| await job.updateProgress(5); | |
| const exportJob = data.exportJobId | |
| ? await this.prisma.exportJob.findUnique({ | |
| where: { id: data.exportJobId }, | |
| select: { id: true, status: true }, | |
| }) | |
| : await this.prisma.exportJob.findFirst({ | |
| where: { | |
| clipId: data.clipId, | |
| platform: data.platform, | |
| }, | |
| select: { id: true, status: true }, | |
| orderBy: { createdAt: "desc" }, | |
| }); | |
| if (!exportJob) { | |
| throw new Error(`ExportJob not found for clip=${data.clipId} platform=${data.platform}`); | |
| } | |
| await this.prisma.exportJob.update({ | |
| where: { id: exportJob.id }, | |
| data: { status: "rendering", startedAt: new Date() }, | |
| }); | |
| const clip = await this.prisma.clip.findUnique({ | |
| where: { id: data.clipId }, | |
| select: { | |
| id: true, | |
| episodeId: true, | |
| startSeconds: true, | |
| endSeconds: true, | |
| suggestedHook: true, | |
| suggestedCaption: true, | |
| episode: { | |
| select: { | |
| recordingSession: { | |
| select: { | |
| tracks: { | |
| select: { trackUrl: true }, | |
| where: { status: "completed" }, | |
| orderBy: { createdAt: "desc" }, | |
| take: 1, | |
| }, | |
| }, | |
| }, | |
| podcast: { | |
| select: { workspaceId: true }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }); | |
| if (!clip) { | |
| throw new Error(`Clip ${data.clipId} not found for export`); | |
| } | |
| const workspaceId = data.workspaceId || clip.episode?.podcast?.workspaceId; | |
| if (!workspaceId) { | |
| throw new Error(`Cannot resolve workspaceId for export job ${exportJob.id} ` + | |
| `(clip=${data.clipId}, platform=${data.platform})`); | |
| } | |
| const trackUrl = clip.episode?.recordingSession?.tracks?.[0]?.trackUrl ?? null; | |
| const sourceUrl = data.sourceUrl ?? trackUrl ?? null; | |
| if (!sourceUrl) { | |
| this.logger.error(`Clip ${data.clipId} has no sourceUrl and no recording track URL. Export cannot proceed.`); | |
| await this.prisma.exportJob.update({ | |
| where: { id: exportJob.id }, | |
| data: { | |
| status: "failed", | |
| errorMessage: "No source audio URL available. Ensure the recording is completed before exporting.", | |
| }, | |
| }); | |
| throw new Error(`Clip ${data.clipId} has no source media URL. ` + | |
| `Complete the recording and processing pipeline before exporting.`); | |
| } | |
| await job.updateProgress(10); | |
| const caption = data.caption || clip.suggestedCaption || undefined; | |
| const hook = data.hook || clip.suggestedHook || undefined; | |
| try { | |
| const result = await this.exportRenderer.renderAndUpload({ | |
| exportJobId: exportJob.id, | |
| clipId: data.clipId, | |
| platform: data.platform, | |
| sourceUrl, | |
| caption, | |
| hook, | |
| renderingParams: data.renderingParams || {}, | |
| workspaceId, | |
| episodeId: data.episodeId || clip.episodeId, | |
| }, sourceUrl, clip.startSeconds, clip.endSeconds, (progress) => job.updateProgress(10 + Math.round(progress * 0.85))); | |
| if (!result) { | |
| throw new Error(`Export rendering returned null for clip ${data.clipId}`); | |
| } | |
| await this.prisma.exportJob.update({ | |
| where: { id: exportJob.id }, | |
| data: { | |
| outputUrl: result.cdnUrl, | |
| status: "completed", | |
| completedAt: new Date(), | |
| }, | |
| }); | |
| await job.updateProgress(100); | |
| this.logger.log(`Export completed for clip ${data.clipId} (${data.platform}): ${result.cdnUrl} (${result.fileSizeBytes} bytes)`); | |
| return { url: result.cdnUrl, r2Key: result.r2Key }; | |
| } | |
| catch (err) { | |
| const errorMessage = err instanceof Error ? err.message : String(err); | |
| this.logger.error(`Export failed for clip ${data.clipId} (${data.platform}): ${errorMessage}`); | |
| await this.prisma.exportJob.update({ | |
| where: { id: exportJob.id }, | |
| data: { | |
| status: "failed", | |
| errorMessage, | |
| }, | |
| }); | |
| throw err; | |
| } | |
| } | |
| onCompleted(job) { | |
| this.logger.log(`Content job ${job.id} completed successfully`); | |
| } | |
| onFailed(job, error) { | |
| this.logger.error(`Content job ${job.id} failed: ${error.message}`, error.stack); | |
| } | |
| onProgress(job, progress) { | |
| this.logger.debug(`Content job ${job.id} progress: ${progress}%`); | |
| } | |
| }; | |
| exports.ContentProcessor = ContentProcessor; | |
| __decorate([ | |
| (0, bullmq_1.OnWorkerEvent)("completed"), | |
| __metadata("design:type", Function), | |
| __metadata("design:paramtypes", [bullmq_2.Job]), | |
| __metadata("design:returntype", void 0) | |
| ], ContentProcessor.prototype, "onCompleted", null); | |
| __decorate([ | |
| (0, bullmq_1.OnWorkerEvent)("failed"), | |
| __metadata("design:type", Function), | |
| __metadata("design:paramtypes", [bullmq_2.Job, Error]), | |
| __metadata("design:returntype", void 0) | |
| ], ContentProcessor.prototype, "onFailed", null); | |
| __decorate([ | |
| (0, bullmq_1.OnWorkerEvent)("progress"), | |
| __metadata("design:type", Function), | |
| __metadata("design:paramtypes", [bullmq_2.Job, Number]), | |
| __metadata("design:returntype", void 0) | |
| ], ContentProcessor.prototype, "onProgress", null); | |
| exports.ContentProcessor = ContentProcessor = ContentProcessor_1 = __decorate([ | |
| (0, bullmq_1.Processor)("content", { | |
| concurrency: 2, | |
| }), | |
| __metadata("design:paramtypes", [prisma_service_1.PrismaService, | |
| ai_service_1.AIService, | |
| export_renderer_service_1.ExportRendererService]) | |
| ], ContentProcessor); | |
| //# sourceMappingURL=content.processor.js.map |