返回 ViMax
video_generator_doubao_seedance_yunwu_api.py
根目录 / tools / video_generator_doubao_seedance_yunwu_api.py
1 import logging
2 from typing import List, Literal
3 import asyncio
4 import aiohttp
5 from interfaces.video_output import VideoOutput
6 from utils.image import image_path_to_b64
7
8
9 class VideoGeneratorDoubaoSeedanceYunwuAPI:
10 def __init__(
11 self,
12 api_key: str,
13 t2v_model: str = "doubao-seedance-1-0-lite-t2v-250428",
14 ff2v_model: str = "doubao-seedance-1-0-lite-i2v-250428",
15 flf2v_model: str = "doubao-seedance-1-0-lite-i2v-250428",
16 max_create_attempts: int = 3,
17 poll_interval: int = 2,
18 max_poll_attempts: int = 300,
19 ):
20 self.api_key = api_key
21 self.t2v_model = t2v_model
22 self.ff2v_model = ff2v_model
23 self.flf2v_model = flf2v_model
24 self.max_create_attempts = max_create_attempts
25 self.poll_interval = poll_interval
26 self.max_poll_attempts = max_poll_attempts
27
28
29 async def create_video_generation_task(
30 self,
31 prompt: str,
32 reference_image_paths: List[str],
33 resolution: Literal["480p", "720p", "1080p"] = "720p",
34 aspect_ratio: str = "16:9",
35 fps: Literal[16, 24] = 16,
36 duration: Literal[5, 10] = 5,
37 ) -> str:
38 """
39 Create a video generation task and return the task ID.
40
41 Args:
42 prompt: Text prompt for video generation
43 reference_image_paths: List of 1 or 2 reference images
44
45 Returns:
46 Task ID string
47 """
48 if len(reference_image_paths) == 0:
49 model = self.t2v_model
50 elif len(reference_image_paths) == 1:
51 model = self.ff2v_model
52 elif len(reference_image_paths) == 2:
53 model = self.flf2v_model
54 else:
55 raise ValueError("reference_image_paths must contain 1 or 2 images.")
56
57 logging.info(f"Calling {model} to generate video...")
58
59 url = "https://yunwu.ai/volc/v1/contents/generations/tasks"
60
61
62 content = [
63 {
64 "type": "text",
65 "text": prompt + f" --rs {resolution} --rt {aspect_ratio} --dur {duration} --fps {fps} --wm false --seed -1 --cf false"
66 }
67 ]
68 if len(reference_image_paths) >= 1:
69 content.append(
70 {
71 "type": "image_url",
72 "image_url": {
73 "url": image_path_to_b64(reference_image_paths[0])
74 },
75 "role": "first_frame",
76 }
77 )
78 if len(reference_image_paths) >= 2:
79 content.append(
80 {
81 "type": "image_url",
82 "image_url": {
83 "url": image_path_to_b64(reference_image_paths[1])
84 },
85 "role": "last_frame",
86 }
87 )
88
89 payload = {
90 "model": model,
91 "content": content
92 }
93
94 headers = {
95 'Authorization': f'Bearer {self.api_key}',
96 'Content-Type': 'application/json'
97 }
98
99 last_error = None
100 for attempt in range(1, self.max_create_attempts + 1):
101 try:
102 async with aiohttp.ClientSession() as session:
103 async with session.post(url, headers=headers, json=payload) as response:
104 response_json = await response.json()
105 http_status = response.status
106 logging.debug(f"Response: {response_json}")
107 except Exception as e:
108 last_error = e
109 logging.error(f"Error occurred while creating video generation task (attempt {attempt}/{self.max_create_attempts}): {e}")
110 if attempt < self.max_create_attempts:
111 await asyncio.sleep(attempt)
112 continue
113
114 if http_status >= 400:
115 message = f"Video generation task creation failed with HTTP {http_status}: {response_json}"
116 if http_status < 500:
117 raise RuntimeError(message)
118 last_error = RuntimeError(message)
119 logging.error(f"{message} (attempt {attempt}/{self.max_create_attempts})")
120 if attempt < self.max_create_attempts:
121 await asyncio.sleep(attempt)
122 continue
123
124 task_id = response_json.get("id")
125 if not task_id:
126 raise RuntimeError(f"Video generation task creation returned no task id: {response_json}")
127 logging.info(f"Video generation task created successfully. Task ID: {task_id}")
128 return task_id
129
130 raise RuntimeError(f"Failed to create video generation task after {self.max_create_attempts} attempts.") from last_error
131
132 async def query_video_generation_task(
133 self,
134 task_id: str,
135 ) -> str:
136 """
137 Query the video generation task until completion and return the video URL.
138
139 Args:
140 task_id: Task ID to query
141
142 Returns:
143 Video URL string
144 """
145 url = f"https://yunwu.ai/volc/v1/contents/generations/tasks/{task_id}"
146 headers = {
147 'Authorization': f'Bearer {self.api_key}',
148 }
149
150 attempts = 0
151 consecutive_errors = 0
152 while True:
153 if attempts >= self.max_poll_attempts:
154 raise TimeoutError(f"Video generation did not complete after {attempts} polls.")
155 attempts += 1
156
157 try:
158 async with aiohttp.ClientSession() as session:
159 async with session.get(url, headers=headers) as response:
160 response_json = await response.json()
161 http_status = response.status
162 except Exception as e:
163 consecutive_errors += 1
164 if consecutive_errors >= 5:
165 raise RuntimeError(f"Querying video generation task failed {consecutive_errors} times in a row.") from e
166 logging.error(f"Error occurred while querying video generation task: {e}. Retrying in {self.poll_interval} seconds...")
167 await asyncio.sleep(self.poll_interval)
168 continue
169 consecutive_errors = 0
170
171 if http_status >= 400:
172 raise RuntimeError(f"Querying video generation task failed with HTTP {http_status}: {response_json}")
173
174 status = response_json.get("status")
175 if status == "succeeded":
176 video_url = response_json["content"]["video_url"]
177 logging.info(f"Video generation completed successfully. Video URL: {video_url}")
178 return video_url
179 elif status == "failed":
180 logging.error(f"Video generation failed. Response: {response_json}")
181 raise ValueError("Video generation failed.")
182 else:
183 logging.info(f"Video generation is still in progress. Checking again in {self.poll_interval} seconds...")
184 await asyncio.sleep(self.poll_interval)
185
186 async def generate_single_video(
187 self,
188 prompt: str,
189 reference_image_paths: List[str],
190 resolution: Literal["480p", "720p", "1080p"] = "720p",
191 aspect_ratio: str = "16:9",
192 fps: Literal[16, 24] = 16,
193 duration: Literal[5, 10] = 5,
194 **kwargs,
195 ) -> VideoOutput:
196 """
197 Generate a single video by creating a task and waiting for completion.
198
199 Args:
200 prompt: Text prompt for video generation
201 reference_image_paths: List of 1 or 2 reference images
202 resolution: Resolution of the video
203 aspect_ratio: Aspect ratio of the video
204 fps: Frames per second of the video
205 duration: Duration of the video
206 Returns:
207 VideoOutput containing the video URL
208 """
209 task_id = await self.create_video_generation_task(prompt, reference_image_paths, resolution, aspect_ratio, fps, duration)
210 video_url = await self.query_video_generation_task(task_id)
211 return VideoOutput(fmt="url", ext="mp4", data=video_url)
212
213
213 lines PYTHON