Skip to content

Commit 364dbc8

Browse files
committed
fix: tts/media
1 parent 0ed04b4 commit 364dbc8

File tree

5 files changed

+122
-61
lines changed

5 files changed

+122
-61
lines changed

src/media/imagen_controller.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ async def run_generation(self, check_client_disconnected: Callable):
140140
await self.page.keyboard.press('Escape')
141141
await asyncio.sleep(0.15)
142142
run_btn = self.page.locator(IMAGEN_RUN_BUTTON_SELECTOR)
143-
await expect_async(run_btn).to_be_visible(timeout=5000)
144-
await expect_async(run_btn).to_be_enabled(timeout=5000)
143+
await expect_async(run_btn).to_be_visible(timeout=10000)
144+
await expect_async(run_btn).to_be_enabled(timeout=10000)
145145
if not await safe_click(run_btn, 'Run 按钮', self.req_id):
146146
if attempt < max_retries:
147147
continue
@@ -154,7 +154,7 @@ async def run_generation(self, check_client_disconnected: Callable):
154154
raise
155155
self.logger.warning(f'[{self.req_id}] 点击 Run 失败 (尝试 {attempt}): {e}')
156156
if attempt < max_retries:
157-
await asyncio.sleep(0.15)
157+
await asyncio.sleep(0.5)
158158
raise Exception('点击 Run 按钮失败')
159159

160160
async def wait_for_images(self, expected_count: int, check_client_disconnected: Callable, timeout_seconds: int = 180) -> List[GeneratedImage]:

src/media/media_processor.py

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,65 @@ async def process_nano_request(
1919
req_id: str,
2020
check_client_disconnected: Callable
2121
) -> Dict[str, Any]:
22-
controller = NanoController(page, logger, req_id)
23-
24-
await controller.navigate_to_nano_page(config.model, check_client_disconnected)
25-
26-
if config.aspect_ratio and config.aspect_ratio != '1:1':
27-
await controller.set_aspect_ratio(config.aspect_ratio, check_client_disconnected)
28-
29-
if config.image_bytes:
30-
await controller.upload_image(
31-
config.image_bytes,
32-
config.image_mime_type or 'image/png',
33-
check_client_disconnected
34-
)
35-
36-
await controller.fill_prompt(config.prompt, check_client_disconnected)
37-
await controller.run_generation(check_client_disconnected)
38-
39-
content = await controller.wait_for_content(check_client_disconnected)
40-
41-
response_parts = []
42-
43-
if content.text:
44-
response_parts.append({
45-
'text': content.text
46-
})
47-
48-
for img in content.images:
49-
response_parts.append({
50-
'inlineData': {
51-
'mimeType': img.mime_type,
52-
'data': base64.b64encode(img.image_bytes).decode('utf-8')
22+
max_retries = 3
23+
last_error = None
24+
25+
for attempt in range(1, max_retries + 1):
26+
try:
27+
controller = NanoController(page, logger, req_id)
28+
29+
await controller.navigate_to_nano_page(config.model, check_client_disconnected)
30+
31+
if config.aspect_ratio and config.aspect_ratio != '1:1':
32+
await controller.set_aspect_ratio(config.aspect_ratio, check_client_disconnected)
33+
34+
if config.image_bytes:
35+
await controller.upload_image(
36+
config.image_bytes,
37+
config.image_mime_type or 'image/png',
38+
check_client_disconnected
39+
)
40+
41+
await controller.fill_prompt(config.prompt, check_client_disconnected)
42+
await controller.run_generation(check_client_disconnected)
43+
44+
content = await controller.wait_for_content(check_client_disconnected)
45+
46+
response_parts = []
47+
48+
if content.text:
49+
response_parts.append({
50+
'text': content.text
51+
})
52+
53+
for img in content.images:
54+
response_parts.append({
55+
'inlineData': {
56+
'mimeType': img.mime_type,
57+
'data': base64.b64encode(img.image_bytes).decode('utf-8')
58+
}
59+
})
60+
61+
return {
62+
'candidates': [{
63+
'content': {
64+
'parts': response_parts,
65+
'role': 'model'
66+
},
67+
'finishReason': 'STOP'
68+
}],
69+
'modelVersion': config.model
5370
}
54-
})
55-
56-
return {
57-
'candidates': [{
58-
'content': {
59-
'parts': response_parts,
60-
'role': 'model'
61-
},
62-
'finishReason': 'STOP'
63-
}],
64-
'modelVersion': config.model
65-
}
71+
except Exception as e:
72+
last_error = e
73+
if 'ClientDisconnected' in str(type(e).__name__):
74+
raise
75+
logger.warning(f'[{req_id}] Nano 请求失败 (尝试 {attempt}/{max_retries}): {e}')
76+
if attempt < max_retries:
77+
import asyncio
78+
await asyncio.sleep(1)
79+
80+
raise last_error if last_error else Exception('Nano 请求失败')
6681

6782

6883
async def process_image_request(

src/media/nano_controller.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ async def run_generation(self, check_client_disconnected: Callable):
146146
await self.page.keyboard.press('Escape')
147147
await asyncio.sleep(0.15)
148148
run_btn = self.page.locator(SUBMIT_BUTTON_SELECTOR)
149-
await expect_async(run_btn).to_be_visible(timeout=5000)
150-
await expect_async(run_btn).to_be_enabled(timeout=5000)
149+
await expect_async(run_btn).to_be_visible(timeout=10000)
150+
await expect_async(run_btn).to_be_enabled(timeout=10000)
151151
if not await safe_click(run_btn, 'Run 按钮', self.req_id):
152152
if attempt < max_retries:
153153
continue
@@ -160,7 +160,7 @@ async def run_generation(self, check_client_disconnected: Callable):
160160
raise
161161
self.logger.warning(f'[{self.req_id}] 点击 Run 失败 (尝试 {attempt}): {e}')
162162
if attempt < max_retries:
163-
await asyncio.sleep(0.15)
163+
await asyncio.sleep(0.5)
164164
raise Exception('点击 Run 按钮失败')
165165

166166
async def wait_for_content(self, check_client_disconnected: Callable, timeout_seconds: int = 120) -> GeneratedContent:
@@ -172,6 +172,7 @@ async def wait_for_content(self, check_client_disconnected: Callable, timeout_se
172172
result = GeneratedContent()
173173
last_chunk_count = 0
174174
stable_count = 0
175+
no_progress_count = 0
175176

176177
while True:
177178
elapsed = asyncio.get_event_loop().time() - start_time
@@ -183,6 +184,10 @@ async def wait_for_content(self, check_client_disconnected: Callable, timeout_se
183184
await self._check_disconnect(check_client_disconnected, f'等待内容 ({int(elapsed)}s)')
184185

185186
try:
187+
error_detected = await self._check_for_error()
188+
if error_detected:
189+
self.logger.error(f'[{self.req_id}] ❌ 检测到生成错误: {error_detected}')
190+
raise Exception(f'生成失败: {error_detected}')
186191
chunk_count = await image_chunk_locator.count()
187192
self.logger.info(f'[{self.req_id}] 检测到图片块数量: {chunk_count}')
188193

@@ -225,12 +230,41 @@ async def wait_for_content(self, check_client_disconnected: Callable, timeout_se
225230
else:
226231
stable_count = 0
227232
last_chunk_count = chunk_count
233+
elif not is_generating and chunk_count == 0 and not result.text:
234+
no_progress_count += 1
235+
if no_progress_count >= 40:
236+
self.logger.warning(f'[{self.req_id}] ⚠️ 长时间无进展,可能生成失败')
237+
raise Exception('生成无响应,可能失败')
238+
else:
239+
no_progress_count = 0
228240

229241
except Exception as e:
242+
if 'ClientDisconnected' in str(type(e).__name__):
243+
raise
244+
if '生成失败' in str(e) or '生成无响应' in str(e):
245+
raise
230246
self.logger.warning(f'[{self.req_id}] 检查内容时出错: {e}')
231247

232248
await asyncio.sleep(0.25)
233249

250+
async def _check_for_error(self):
251+
error_selectors = [
252+
'mat-snack-bar-container .mdc-snackbar__label',
253+
'.error-toast span.content-text',
254+
'ms-callout[severity="error"] .content-container',
255+
'.mat-mdc-snack-bar-label'
256+
]
257+
for sel in error_selectors:
258+
try:
259+
locator = self.page.locator(sel)
260+
if await locator.count() > 0 and await locator.first.is_visible():
261+
text = await locator.first.inner_text(timeout=1000)
262+
if text and text.strip():
263+
return text.strip()
264+
except:
265+
pass
266+
return None
267+
234268
async def _extract_images_via_download(self, count: int) -> List[GeneratedImage]:
235269
import tempfile
236270
import os

src/media/veo_controller.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,17 @@ async def upload_image(self, image_bytes: bytes, mime_type: str, check_client_di
165165
'mimeType': mime_type,
166166
'buffer': image_bytes
167167
})
168-
await asyncio.sleep(0.8)
168+
await asyncio.sleep(0.3)
169169
await self.page.keyboard.press('Escape')
170-
await asyncio.sleep(0.25)
170+
await asyncio.sleep(0.15)
171171
self.logger.info(f'[{self.req_id}] ✅ 图片已上传')
172172
return
173173
except Exception as e:
174174
if isinstance(e, ClientDisconnectedError):
175175
raise
176176
self.logger.warning(f'[{self.req_id}] 上传图片失败 (尝试 {attempt}): {e}')
177177
if attempt < max_retries:
178-
await asyncio.sleep(0.25)
178+
await asyncio.sleep(0.15)
179179

180180
async def fill_prompt(self, prompt: str, check_client_disconnected: Callable):
181181
self.logger.info(f'[{self.req_id}] 填充提示词 ({len(prompt)} chars)')
@@ -206,8 +206,8 @@ async def run_generation(self, check_client_disconnected: Callable):
206206
await self.page.keyboard.press('Escape')
207207
await asyncio.sleep(0.15)
208208
run_btn = self.page.locator(VEO_RUN_BUTTON_SELECTOR)
209-
await expect_async(run_btn).to_be_visible(timeout=5000)
210-
await expect_async(run_btn).to_be_enabled(timeout=5000)
209+
await expect_async(run_btn).to_be_visible(timeout=10000)
210+
await expect_async(run_btn).to_be_enabled(timeout=10000)
211211
if not await safe_click(run_btn, 'Run 按钮', self.req_id):
212212
if attempt < max_retries:
213213
continue
@@ -220,7 +220,7 @@ async def run_generation(self, check_client_disconnected: Callable):
220220
raise
221221
self.logger.warning(f'[{self.req_id}] 点击 Run 失败 (尝试 {attempt}): {e}')
222222
if attempt < max_retries:
223-
await asyncio.sleep(0.15)
223+
await asyncio.sleep(0.5)
224224
raise Exception('点击 Run 按钮失败')
225225

226226
async def wait_for_videos(self, expected_count: int, check_client_disconnected: Callable, timeout_seconds: int = 300) -> List[GeneratedVideo]:

src/tts/tts_controller.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,30 @@ async def set_voice(self, voice_name: str, speaker_index: int = 0, check_client_
111111
continue
112112
await asyncio.sleep(0.15)
113113
option = self.page.locator(f'{TTS_SETTINGS_VOICE_OPTION_SELECTOR}:has-text("{voice_name}")')
114-
if await option.count() > 0:
115-
if await safe_click(option.first, f'语音选项 {voice_name}', self.req_id):
116-
self.logger.info(f'[{self.req_id}] ✅ 语音已设置: {voice_name}')
117-
return
118-
else:
119-
self.logger.warning(f'[{self.req_id}] 未找到语音选项: {voice_name}')
114+
try:
115+
await expect_async(option.first).to_be_visible(timeout=3000)
116+
except PlaywrightTimeoutError:
117+
self.logger.warning(f'[{self.req_id}] 语音选项 {voice_name} 未出现 (尝试 {attempt})')
120118
await self.page.keyboard.press('Escape')
119+
await asyncio.sleep(0.2)
120+
continue
121+
if await safe_click(option.first, f'语音选项 {voice_name}', self.req_id):
122+
await asyncio.sleep(0.15)
123+
self.logger.info(f'[{self.req_id}] ✅ 语音已设置: {voice_name}')
121124
return
125+
else:
126+
self.logger.warning(f'[{self.req_id}] 语音选项点击失败 (尝试 {attempt})')
127+
await self.page.keyboard.press('Escape')
128+
await asyncio.sleep(0.2)
129+
continue
122130
except Exception as e:
123131
if isinstance(e, ClientDisconnectedError):
124132
raise
125133
self.logger.warning(f'[{self.req_id}] 设置语音失败 (尝试 {attempt}): {e}')
134+
try:
135+
await self.page.keyboard.press('Escape')
136+
except:
137+
pass
126138
if attempt < max_retries:
127139
await asyncio.sleep(0.15)
128140

0 commit comments

Comments
 (0)