返回 AiToEarn
volcengine.service.ts
根目录 / project / aitoearn-backend / apps / aitoearn-ai / src / core / ai / libs / volcengine / volcengine.service.ts
1 import type { OpenApiResponse } from '@volcengine/openapi/lib/base/types'
2 import type { VodService } from '@volcengine/openapi/lib/services/vod'
3 import type {
4 VodCommitUploadInfoResult,
5 VodGetMediaInfosRequest,
6 VodGetMediaInfosResult,
7 VodGetPlayInfoRequest,
8 VodGetPlayInfoResult,
9 VodQueryUploadTaskInfoRequest,
10 VodQueryUploadTaskInfoResult,
11 VodUploadMaterialRequest,
12 VodUploadMediaByUrlRequest,
13 VodUploadMediaByUrlResult,
14 } from '@volcengine/openapi/lib/services/vod/types'
15 import type {
16 AsyncVCreativeTaskRequest,
17 AsyncVCreativeTaskResponse,
18 CreateDramaRecapTaskRequest,
19 CreateDramaRecapTaskResponse,
20 CreateVideoGenerationTaskRequest,
21 GetAideoTaskResultRequest,
22 GetAideoTaskResultResponse,
23 GetDirectEditResultItem,
24 GetDirectEditResultRequest,
25 GetVCreativeTaskResultRequest,
26 GetVCreativeTaskResultResponse,
27 GetVideoGenerationTaskResponse,
28 MultiInput,
29 QueryDramaRecapTaskRequest,
30 QueryDramaRecapTaskResponse,
31 SkillParams,
32 SubmitAideoTaskAsyncResponse,
33 SubmitDirectEditTaskAsyncRequest,
34 SubmitDirectEditTaskAsyncResponse,
35 VideoInput,
36 VideoUrlInput,
37 } from './volcengine.interface'
38 import { Injectable, Logger } from '@nestjs/common'
39 import { vodOpenapi } from '@volcengine/openapi'
40 import { AppException, ResponseCode } from '@yikart/common'
41 import { AiAvailabilityService } from '../../../ai-availability'
42 import { AideoService } from './services/aideo.service'
43 import { DirectEditService } from './services/direct-edit.service'
44 import { DramaRecapService } from './services/drama-recap.service'
45 import { MediaService } from './services/media.service'
46 import { UploadService } from './services/upload.service'
47 import { VCreativeService } from './services/vcreative.service'
48 import { VideoGenService } from './services/video-gen.service'
49 import { VolcengineConfig } from './volcengine.config'
50 import { SkillType } from './volcengine.interface'
51
52 /**
53 * Volcengine 服务 Facade
54 * 提供统一的 API 访问入口,委托到各专门服务
55 * 保持向后兼容:所有原有方法签名保持不变
56 */
57 @Injectable()
58 export class VolcengineService {
59 private readonly logger = new Logger(VolcengineService.name)
60 private readonly vodService: VodService
61
62 constructor(
63 private readonly config: VolcengineConfig,
64 private readonly uploadService: UploadService,
65 private readonly mediaService: MediaService,
66 private readonly videoGenService: VideoGenService,
67 private readonly aideoService: AideoService,
68 private readonly vCreativeService: VCreativeService,
69 private readonly directEditService: DirectEditService,
70 private readonly dramaRecapService: DramaRecapService,
71 private readonly aiAvailability: AiAvailabilityService,
72 ) {
73 this.vodService = this.createVodService()
74 }
75
76 private async withAvailability<T>(operation: string, fn: () => Promise<T>, model?: string): Promise<T> {
77 return this.aiAvailability.execute(
78 { provider: 'volcengine', operation, model },
79 fn,
80 )
81 }
82
83 // ========== 配置方法 ==========
84
85 /**
86 * 获取播放基础 URL
87 */
88 getPlaybackBaseUrl(): string {
89 return this.config.playbackBaseUrl
90 }
91
92 /**
93 * 获取空间名称
94 */
95 getSpaceName(): string {
96 return this.config.spaceName
97 }
98
99 // ========== 上传服务 (委托到 VolcengineUploadService) ==========
100
101 /**
102 * URL 批量拉取上传
103 */
104 async uploadMediaByUrl(
105 request: VodUploadMediaByUrlRequest,
106 ): Promise<VodUploadMediaByUrlResult> {
107 return this.withAvailability('uploadMediaByUrl', async () => this.uploadService.uploadMediaByUrl(request))
108 }
109
110 /**
111 * 流式上传
112 */
113 async uploadMaterial(
114 request: VodUploadMaterialRequest,
115 ): Promise<VodCommitUploadInfoResult> {
116 return this.withAvailability('uploadMaterial', async () => this.uploadService.uploadMaterial(request))
117 }
118
119 /**
120 * 查询上传任务状态
121 * @deprecated 使用 getUploadTaskInfo 代替
122 */
123 async queryUploadTaskInfo(
124 request: VodQueryUploadTaskInfoRequest,
125 ): Promise<VodQueryUploadTaskInfoResult> {
126 return this.withAvailability('queryUploadTaskInfo', async () => this.uploadService.getUploadTaskInfo(request))
127 }
128
129 /**
130 * 获取上传任务状态
131 */
132 async getUploadTaskInfo(
133 request: VodQueryUploadTaskInfoRequest,
134 ): Promise<VodQueryUploadTaskInfoResult> {
135 return this.withAvailability('getUploadTaskInfo', async () => this.uploadService.getUploadTaskInfo(request))
136 }
137
138 /**
139 * 下载 URL 并使用 stream 方式上传
140 */
141 async downloadUrlAndUploadAsStream(
142 url: string,
143 options?: VideoUrlInput,
144 ): Promise<string> {
145 return this.withAvailability('downloadUrlAndUploadAsStream', async () => this.uploadService.downloadUrlAndUploadAsStream(url, options))
146 }
147
148 // ========== 媒资服务 (委托到 VolcengineMediaService) ==========
149
150 /**
151 * 获取媒资信息
152 */
153 async getMediaInfos(
154 request: VodGetMediaInfosRequest,
155 ): Promise<VodGetMediaInfosResult> {
156 return this.withAvailability('getMediaInfos', async () => this.mediaService.getMediaInfos(request))
157 }
158
159 /**
160 * 获取播放信息
161 */
162 async getPlayInfo(
163 request: VodGetPlayInfoRequest,
164 ): Promise<VodGetPlayInfoResult> {
165 return this.withAvailability('getPlayInfo', async () => this.mediaService.getPlayInfo(request))
166 }
167
168 /**
169 * 构建带鉴权的播放URL
170 */
171 async buildAuthenticatedPlayUrl(uri: string): Promise<string> {
172 return this.withAvailability('buildAuthenticatedPlayUrl', async () => this.mediaService.buildAuthenticatedPlayUrl(uri))
173 }
174
175 // ========== 视频生成服务 (委托到 VolcengineVideoGenService) ==========
176
177 /**
178 * 创建视频生成任务
179 */
180 async createVideoGenerationTask(
181 request: CreateVideoGenerationTaskRequest,
182 ) {
183 return this.withAvailability('createVideoGenerationTask', async () => this.videoGenService.createVideoGenerationTask(request))
184 }
185
186 /**
187 * 查询视频生成任务
188 */
189 async getVideoGenerationTask(
190 taskId: string,
191 ): Promise<GetVideoGenerationTaskResponse> {
192 return this.withAvailability('getVideoGenerationTask', async () => this.videoGenService.getVideoGenerationTask(taskId))
193 }
194
195 /**
196 * 取消或删除视频生成任务
197 */
198 async deleteVideoGenerationTask(
199 taskId: string,
200 ) {
201 return this.withAvailability('deleteVideoGenerationTask', async () => this.videoGenService.deleteVideoGenerationTask(taskId))
202 }
203
204 // ========== Aideo 服务 (委托到 VolcengineAideoService) ==========
205
206 /**
207 * 提交异步智能视频处理任务
208 */
209 async submitAideoTaskAsync(
210 request:
211 | {
212 SpaceName?: string
213 MultiInputs: MultiInput[]
214 Prompt: string
215 }
216 | {
217 SpaceName?: string
218 MultiInputs: MultiInput[]
219 SkillType: SkillType
220 SkillParams?: SkillParams
221 },
222 ): Promise<SubmitAideoTaskAsyncResponse> {
223 return this.withAvailability('submitAideoTaskAsync', async () => this.aideoService.submitAideoTaskAsync(request))
224 }
225
226 /**
227 * 获取异步智能视频处理任务结果
228 */
229 async getAideoTaskResult(
230 request: GetAideoTaskResultRequest,
231 ): Promise<GetAideoTaskResultResponse> {
232 return this.withAvailability('getAideoTaskResult', async () => this.aideoService.getAideoTaskResult(request))
233 }
234
235 /**
236 * 智能视频上传并提交异步智能视频处理任务
237 */
238 async submitAideoTaskAsyncWithUpload(
239 request:
240 | {
241 SpaceName?: string
242 MultiInputs: VideoInput[]
243 Prompt: string
244 }
245 | {
246 SpaceName?: string
247 MultiInputs: VideoInput[]
248 SkillType: SkillType
249 SkillParams?: SkillParams
250 },
251 ): Promise<SubmitAideoTaskAsyncResponse & { vids: string[] }> {
252 return this.withAvailability('submitAideoTaskAsyncWithUpload', async () => this.aideoService.submitAideoTaskAsyncWithUpload(request))
253 }
254
255 // ========== VCreative 服务 (委托到 VolcengineVCreativeService) ==========
256
257 /**
258 * 提交 AI 漫剧转绘任务
259 */
260 async asyncVCreativeTask(
261 request: AsyncVCreativeTaskRequest,
262 ): Promise<AsyncVCreativeTaskResponse> {
263 return this.withAvailability('asyncVCreativeTask', async () => this.vCreativeService.asyncVCreativeTask(request))
264 }
265
266 /**
267 * 获取 AI 漫剧转绘任务结果
268 */
269 async getVCreativeTaskResult(
270 request: GetVCreativeTaskResultRequest,
271 ): Promise<GetVCreativeTaskResultResponse> {
272 return this.withAvailability('getVCreativeTaskResult', async () => this.vCreativeService.getVCreativeTaskResult(request))
273 }
274
275 // ========== DirectEdit 服务 (委托到 VolcengineDirectEditService) ==========
276
277 /**
278 * 提交异步剪辑任务
279 */
280 async submitDirectEditTaskAsync(
281 request: SubmitDirectEditTaskAsyncRequest,
282 ): Promise<SubmitDirectEditTaskAsyncResponse> {
283 return this.withAvailability('submitDirectEditTaskAsync', async () => this.directEditService.submitDirectEditTaskAsync(request))
284 }
285
286 /**
287 * 获取剪辑任务结果
288 */
289 async getDirectEditResult(
290 request: GetDirectEditResultRequest,
291 ): Promise<GetDirectEditResultItem> {
292 return this.withAvailability('getDirectEditResult', async () => this.directEditService.getDirectEditResult(request))
293 }
294
295 // ========== DramaRecap 服务 (委托到 VolcengineDramaRecapService) ==========
296
297 /**
298 * 创建短剧解说任务
299 */
300 async createDramaRecapTask(
301 request: CreateDramaRecapTaskRequest,
302 ): Promise<CreateDramaRecapTaskResponse> {
303 return this.withAvailability('createDramaRecapTask', async () => this.dramaRecapService.createDramaRecapTask(request))
304 }
305
306 /**
307 * 查询短剧解说任务结果
308 * @deprecated 使用 getDramaRecapTask 代替
309 */
310 async queryDramaRecapTask(
311 request: QueryDramaRecapTaskRequest,
312 ): Promise<QueryDramaRecapTaskResponse> {
313 return this.withAvailability('queryDramaRecapTask', async () => this.dramaRecapService.getDramaRecapTask(request))
314 }
315
316 /**
317 * 获取短剧解说任务结果
318 */
319 async getDramaRecapTask(
320 request: QueryDramaRecapTaskRequest,
321 ): Promise<QueryDramaRecapTaskResponse> {
322 return this.withAvailability('getDramaRecapTask', async () => this.dramaRecapService.getDramaRecapTask(request))
323 }
324
325 // ========== 内部辅助方法 ==========
326
327 /**
328 * 创建视频点播服务实例
329 */
330 private createVodService(): VodService {
331 return new vodOpenapi.VodService({
332 accessKeyId: this.config.accessKeyId,
333 secretKey: this.config.secretAccessKey,
334 serviceName: 'vod',
335 })
336 }
337
338 /**
339 * 检查 API 响应中的错误并抛出异常(内部方法)
340 */
341 private checkApiResponseError<T>(
342 response: OpenApiResponse<T>,
343 operation: string,
344 requestData?: unknown,
345 ): asserts response is OpenApiResponse<T> & { Result: T } {
346 if (response.ResponseMetadata?.Error) {
347 const error = response.ResponseMetadata.Error
348 this.logger.error(
349 { error, requestData },
350 `${operation} failed: ${error.Code || 'Unknown'} - ${error.Message}`,
351 )
352 throw new AppException(ResponseCode.AiCallFailed, {
353 code: error.Code || 'Unknown',
354 message: error.Message,
355 })
356 }
357
358 if (!response.Result) {
359 this.logger.error({ requestData }, `${operation} returned no result`)
360 throw new AppException(ResponseCode.AiCallFailed, {
361 message: typeof response === 'string' ? response : 'No result returned',
362 })
363 }
364 }
365 }
366
366 lines TYPESCRIPT