返回 AiToEarn
aideo.service.ts
1 import type {
2 GetAideoTaskResultRequest,
3 GetAideoTaskResultResponse,
4 MultiInput,
5 SkillParams,
6 SubmitAideoTaskAsyncRequest,
7 SubmitAideoTaskAsyncResponse,
8 VideoInput,
9 VideoStreamInput,
10 VideoUrlInput,
11 } from '../volcengine.interface'
12 import { Injectable } from '@nestjs/common'
13 import { VolcengineConfig } from '../volcengine.config'
14 import {
15 SkillType,
16 VCreativeOutputJsonSuccess,
17 VCreativeParamJson,
18 VCreativeResult,
19 VCreativeStatus,
20 } from '../volcengine.interface'
21 import { BaseService } from './base.service'
22 import { UploadService } from './upload.service'
23
24 /**
25 * Volcengine Aideo 服务
26 * 负责智能视频处理任务:提交、查询
27 */
28 @Injectable()
29 export class AideoService extends BaseService {
30 constructor(
31 config: VolcengineConfig,
32 private readonly uploadService: UploadService,
33 ) {
34 super(config)
35 }
36
37 /**
38 * 提交异步智能视频处理任务
39 * POST https://vod.volcengineapi.com?Action=SubmitAideoTaskAsync&Version=2025-03-03
40 * 支持两种调用方式(类型系统保证互斥):
41 * 1. 自然语言驱动:使用 Prompt 参数
42 * 2. 指定技能驱动:使用 SkillType + SkillParams 参数
43 */
44 async submitAideoTaskAsync(
45 request:
46 | {
47 SpaceName?: string
48 MultiInputs: MultiInput[]
49 Prompt: string
50 }
51 | {
52 SpaceName?: string
53 MultiInputs: MultiInput[]
54 SkillType: SkillType
55 /** 技能参数对象(会自动序列化为 JSON 字符串) */
56 SkillParams?: SkillParams
57 },
58 ): Promise<SubmitAideoTaskAsyncResponse> {
59 const api = this.vodService.createAPI<
60 SubmitAideoTaskAsyncRequest,
61 SubmitAideoTaskAsyncResponse
62 >('SubmitAideoTaskAsync', {
63 Version: '2025-03-03',
64 method: 'POST',
65 contentType: 'json',
66 })
67
68 // 准备请求数据(根据类型系统保证的互斥性构建)
69 let requestData: SubmitAideoTaskAsyncRequest
70
71 if ('Prompt' in request) {
72 // 自然语言驱动
73 requestData = {
74 SpaceName: request.SpaceName || this.config.spaceName,
75 MultiInputs: request.MultiInputs,
76 Prompt: request.Prompt,
77 }
78 }
79 else {
80 // 指定技能驱动
81 requestData = {
82 SpaceName: request.SpaceName || this.config.spaceName,
83 MultiInputs: request.MultiInputs,
84 SkillType: request.SkillType,
85 ...(request.SkillParams && {
86 SkillParams: JSON.stringify(request.SkillParams),
87 }),
88 }
89 }
90
91 // 调用 API
92 const response = await api(requestData)
93
94 // 调试:记录火山引擎提交任务的原始响应
95 this.logger.debug({
96 hasResult: !!response.Result,
97 taskId: response.Result?.TaskId,
98 requestId: response.ResponseMetadata?.RequestId,
99 spaceName: requestData.SpaceName,
100 }, '火山引擎Aideo任务提交API响应')
101
102 // 简洁日志,便于快速排查(单行、易搜索)
103 this.logger.debug({
104 taskId: response.Result?.TaskId,
105 requestId: response.ResponseMetadata?.RequestId,
106 spaceName: requestData.SpaceName,
107 skillType: 'SkillType' in requestData ? requestData.SkillType : undefined,
108 inputVids: (requestData.MultiInputs || []).map(i => i.Vid).filter(Boolean),
109 }, 'VOLCENGINE Submit summary')
110
111 // 检查响应中的错误
112 this.checkApiResponseError(response, 'Aideo task submission', requestData)
113
114 return response.Result
115 }
116
117 /**
118 * 获取异步智能视频处理任务结果
119 * Get https://vod.volcengineapi.com?Action=GetAideoTaskResult&Version=2025-03-03
120 */
121 async getAideoTaskResult(
122 request: GetAideoTaskResultRequest,
123 ): Promise<GetAideoTaskResultResponse> {
124 const api = this.vodService.createAPI<
125 GetAideoTaskResultRequest,
126 GetAideoTaskResultResponse
127 >('GetAideoTaskResult', {
128 Version: '2025-03-03',
129 method: 'GET',
130 contentType: 'json',
131 })
132
133 const requestData: GetAideoTaskResultRequest = {
134 ...request,
135 SpaceName: request.SpaceName || this.config.spaceName,
136 }
137
138 const response = await api(requestData)
139
140 // 检查响应中的错误
141 this.checkApiResponseError(response, 'Get Aideo task result', requestData)
142
143 const result = response.Result
144
145 // 解析 VCreative 的 ParamJson 和 OutputJson
146 if (result.ApiResponses) {
147 for (const apiResponse of result.ApiResponses) {
148 if (apiResponse.VodTaskType === SkillType.VCreative && apiResponse.VCreative) {
149 const vCreative = apiResponse.VCreative
150 try {
151 const paramJsonStr = typeof vCreative.ParamJson === 'string' ? vCreative.ParamJson : JSON.stringify(vCreative.ParamJson)
152 const paramJson = JSON.parse(paramJsonStr) as VCreativeParamJson
153 if (vCreative.Status === VCreativeStatus.Success) {
154 const outputJsonStr = typeof vCreative.OutputJson === 'string' ? vCreative.OutputJson : JSON.stringify(vCreative.OutputJson)
155 const outputJson = JSON.parse(outputJsonStr) as VCreativeOutputJsonSuccess
156 apiResponse.VCreative = {
157 ...vCreative,
158 Status: VCreativeStatus.Success,
159 ParamJson: paramJson,
160 OutputJson: outputJson,
161 } as VCreativeResult
162 }
163 else {
164 apiResponse.VCreative = {
165 ...vCreative,
166 ParamJson: paramJson,
167 } as VCreativeResult
168 }
169 }
170 catch (error) {
171 this.logger.warn({ error, vCreative }, '解析 VCreative ParamJson 或 OutputJson 失败')
172 }
173 }
174 }
175 }
176
177 return result
178 }
179
180 /**
181 * 智能视频上传并提交异步智能视频处理任务
182 * 自动处理视频 URL 或文件流上传,获取 vid 后调用 submitAideoTaskAsync
183 */
184 async submitAideoTaskAsyncWithUpload(
185 request:
186 | {
187 SpaceName?: string
188 MultiInputs: VideoInput[]
189 Prompt: string
190 }
191 | {
192 SpaceName?: string
193 MultiInputs: VideoInput[]
194 SkillType: SkillType
195 SkillParams?: SkillParams
196 },
197 ): Promise<SubmitAideoTaskAsyncResponse & { vids: string[] }> {
198 const urlInputs: Array<{ url: string, options?: VideoUrlInput, index: number }> = []
199 const streamInputs: Array<{ input: VideoStreamInput, index: number }> = []
200
201 request.MultiInputs.forEach((input, index) => {
202 if (typeof input === 'string') {
203 urlInputs.push({ url: input, index })
204 }
205 else if ('type' in input) {
206 if (input.type === 'url') {
207 urlInputs.push({ url: input.url, options: input, index })
208 }
209 else if (input.type === 'stream') {
210 streamInputs.push({ input, index })
211 }
212 }
213 })
214
215 const urlVids = urlInputs.length > 0
216 ? await this.uploadService.batchUploadUrlsAndGetVids(urlInputs)
217 : []
218
219 const streamVids = streamInputs.length > 0
220 ? await Promise.all(
221 streamInputs.map(({ input }) => this.uploadService.uploadStreamAndGetVid(input)),
222 )
223 : []
224
225 const allVids: string[] = Array.from({ length: request.MultiInputs.length })
226 urlInputs.forEach(({ index }, i) => {
227 allVids[index] = urlVids[i]
228 })
229 streamInputs.forEach(({ index }, i) => {
230 allVids[index] = streamVids[i]
231 })
232
233 const multiInputs: MultiInput[] = allVids.map(vid => ({
234 Type: 'Vid',
235 Vid: vid,
236 }))
237
238 const result = await this.submitAideoTaskAsync({
239 ...request,
240 MultiInputs: multiInputs,
241 } as Parameters<typeof this.submitAideoTaskAsync>[0])
242
243 return {
244 ...result,
245 vids: allVids,
246 }
247 }
248 }
249
249 lines TYPESCRIPT