feat: 新增去水印失败自动回退配置、优化批量删除逻辑及错误处理机制

This commit is contained in:
TheSmallHanCat
2026-01-24 19:19:33 +08:00
parent d300f94683
commit ef49e3e670
5 changed files with 131 additions and 59 deletions

View File

@@ -154,6 +154,7 @@ class UpdateWatermarkFreeConfigRequest(BaseModel):
parse_method: Optional[str] = "third_party" # "third_party" or "custom"
custom_parse_url: Optional[str] = None
custom_parse_token: Optional[str] = None
fallback_on_failure: Optional[bool] = True # Auto fallback to watermarked video on failure
class UpdateCallLogicConfigRequest(BaseModel):
call_mode: Optional[str] = None # "default" or "polling"
@@ -430,14 +431,16 @@ async def batch_enable_all(request: BatchDisableRequest = None, token: str = Dep
@router.post("/api/tokens/batch/delete-disabled")
async def batch_delete_disabled(request: BatchDisableRequest = None, token: str = Depends(verify_admin_token)):
"""Delete selected tokens or all disabled tokens"""
"""Delete selected disabled tokens or all disabled tokens"""
try:
if request and request.token_ids:
# Delete only selected tokens
# Delete only selected tokens that are disabled
deleted_count = 0
for token_id in request.token_ids:
await token_manager.delete_token(token_id)
deleted_count += 1
token_obj = await db.get_token(token_id)
if token_obj and not token_obj.is_active:
await token_manager.delete_token(token_id)
deleted_count += 1
else:
# Delete all disabled tokens (backward compatibility)
tokens = await db.get_all_tokens()
@@ -449,7 +452,7 @@ async def batch_delete_disabled(request: BatchDisableRequest = None, token: str
return {
"success": True,
"message": f"已删除 {deleted_count} 个Token",
"message": f"已删除 {deleted_count}禁用Token",
"deleted_count": deleted_count
}
except Exception as e:
@@ -472,6 +475,23 @@ async def batch_disable_selected(request: BatchDisableRequest, token: str = Depe
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/api/tokens/batch/delete-selected")
async def batch_delete_selected(request: BatchDisableRequest, token: str = Depends(verify_admin_token)):
"""Delete selected tokens (regardless of their status)"""
try:
deleted_count = 0
for token_id in request.token_ids:
await token_manager.delete_token(token_id)
deleted_count += 1
return {
"success": True,
"message": f"已删除 {deleted_count} 个Token",
"deleted_count": deleted_count
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/api/tokens/batch/update-proxy")
async def batch_update_proxy(request: BatchUpdateProxyRequest, token: str = Depends(verify_admin_token)):
"""Batch update proxy for selected tokens"""
@@ -864,7 +884,8 @@ async def get_watermark_free_config(token: str = Depends(verify_admin_token)) ->
"watermark_free_enabled": config_obj.watermark_free_enabled,
"parse_method": config_obj.parse_method,
"custom_parse_url": config_obj.custom_parse_url,
"custom_parse_token": config_obj.custom_parse_token
"custom_parse_token": config_obj.custom_parse_token,
"fallback_on_failure": config_obj.fallback_on_failure
}
@router.post("/api/watermark-free/config")
@@ -878,7 +899,8 @@ async def update_watermark_free_config(
request.watermark_free_enabled,
request.parse_method,
request.custom_parse_url,
request.custom_parse_token
request.custom_parse_token,
request.fallback_on_failure
)
# Update in-memory config

View File

@@ -105,6 +105,7 @@ class Database:
parse_method = "third_party"
custom_parse_url = None
custom_parse_token = None
fallback_on_failure = True # Default to True
if config_dict:
watermark_config = config_dict.get("watermark_free", {})
@@ -112,15 +113,16 @@ class Database:
parse_method = watermark_config.get("parse_method", "third_party")
custom_parse_url = watermark_config.get("custom_parse_url", "")
custom_parse_token = watermark_config.get("custom_parse_token", "")
fallback_on_failure = watermark_config.get("fallback_on_failure", True)
# Convert empty strings to None
custom_parse_url = custom_parse_url if custom_parse_url else None
custom_parse_token = custom_parse_token if custom_parse_token else None
await db.execute("""
INSERT INTO watermark_free_config (id, watermark_free_enabled, parse_method, custom_parse_url, custom_parse_token)
VALUES (1, ?, ?, ?, ?)
""", (watermark_free_enabled, parse_method, custom_parse_url, custom_parse_token))
INSERT INTO watermark_free_config (id, watermark_free_enabled, parse_method, custom_parse_url, custom_parse_token, fallback_on_failure)
VALUES (1, ?, ?, ?, ?, ?)
""", (watermark_free_enabled, parse_method, custom_parse_url, custom_parse_token, fallback_on_failure))
# Ensure cache_config has a row
cursor = await db.execute("SELECT COUNT(*) FROM cache_config")
@@ -251,6 +253,7 @@ class Database:
("parse_method", "TEXT DEFAULT 'third_party'"),
("custom_parse_url", "TEXT"),
("custom_parse_token", "TEXT"),
("fallback_on_failure", "BOOLEAN DEFAULT 1"),
]
for col_name, col_type in columns_to_add:
@@ -406,6 +409,7 @@ class Database:
parse_method TEXT DEFAULT 'third_party',
custom_parse_url TEXT,
custom_parse_token TEXT,
fallback_on_failure BOOLEAN DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
@@ -1048,10 +1052,11 @@ class Database:
return WatermarkFreeConfig(watermark_free_enabled=False, parse_method="third_party")
async def update_watermark_free_config(self, enabled: bool, parse_method: str = None,
custom_parse_url: str = None, custom_parse_token: str = None):
custom_parse_url: str = None, custom_parse_token: str = None,
fallback_on_failure: bool = None):
"""Update watermark-free configuration"""
async with aiosqlite.connect(self.db_path) as db:
if parse_method is None and custom_parse_url is None and custom_parse_token is None:
if parse_method is None and custom_parse_url is None and custom_parse_token is None and fallback_on_failure is None:
# Only update enabled status
await db.execute("""
UPDATE watermark_free_config
@@ -1063,9 +1068,10 @@ class Database:
await db.execute("""
UPDATE watermark_free_config
SET watermark_free_enabled = ?, parse_method = ?, custom_parse_url = ?,
custom_parse_token = ?, updated_at = CURRENT_TIMESTAMP
custom_parse_token = ?, fallback_on_failure = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = 1
""", (enabled, parse_method or "third_party", custom_parse_url, custom_parse_token))
""", (enabled, parse_method or "third_party", custom_parse_url, custom_parse_token,
fallback_on_failure if fallback_on_failure is not None else True))
await db.commit()
# Cache config operations

View File

@@ -110,6 +110,7 @@ class WatermarkFreeConfig(BaseModel):
parse_method: str # Read from database, initialized from setting.toml on first startup
custom_parse_url: Optional[str] = None # Read from database, initialized from setting.toml on first startup
custom_parse_token: Optional[str] = None # Read from database, initialized from setting.toml on first startup
fallback_on_failure: bool = True # Auto fallback to watermarked video on failure, default True
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None

View File

@@ -986,11 +986,15 @@ class GenerationHandler:
watermark_free_config = await self.db.get_watermark_free_config()
watermark_free_enabled = watermark_free_config.watermark_free_enabled
# Initialize variables
local_url = None
watermark_free_failed = False
if watermark_free_enabled:
# Watermark-free mode: post video and get watermark-free URL
debug_logger.log_info(f"Entering watermark-free mode for task {task_id}")
debug_logger.log_info(f"[Watermark-Free] Entering watermark-free mode for task {task_id}")
generation_id = item.get("id")
debug_logger.log_info(f"Generation ID: {generation_id}")
debug_logger.log_info(f"[Watermark-Free] Generation ID: {generation_id}")
if not generation_id:
raise Exception("Generation ID not found in video draft")
@@ -1090,60 +1094,80 @@ class GenerationHandler:
)
except Exception as publish_error:
# Fallback to normal mode if publish fails
# Watermark-free mode failed
watermark_free_failed = True
import traceback
error_traceback = traceback.format_exc()
debug_logger.log_error(
error_message=f"Watermark-free mode failed: {str(publish_error)}",
error_message=f"[Watermark-Free] ❌ FAILED - Error: {str(publish_error)}",
status_code=500,
response_text=str(publish_error)
response_text=f"{str(publish_error)}\n\nTraceback:\n{error_traceback}"
)
if stream:
yield self._format_stream_chunk(
reasoning_content=f"Warning: Failed to get watermark-free version - {str(publish_error)}\nFalling back to normal video...\n"
)
# Use downloadable_url instead of url
url = item.get("downloadable_url") or item.get("url")
if not url:
raise Exception("Video URL not found")
if config.cache_enabled:
try:
cached_filename = await self.file_cache.download_and_cache(url, "video", token_id=token_id)
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
except Exception as cache_error:
local_url = url
else:
local_url = url
else:
# Normal mode: use downloadable_url instead of url
url = item.get("downloadable_url") or item.get("url")
if url:
# Cache video file (if cache enabled)
if config.cache_enabled:
# Check if fallback is enabled
if watermark_config.fallback_on_failure:
debug_logger.log_info(f"[Watermark-Free] Fallback enabled, falling back to normal mode (original URL)")
if stream:
yield self._format_stream_chunk(
reasoning_content="**Video Generation Completed**\n\nVideo generation successful. Now caching the video file...\n"
reasoning_content=f"⚠️ Warning: Failed to get watermark-free version - {str(publish_error)}\nFalling back to normal video...\n"
)
else:
# Fallback disabled, mark task as failed
debug_logger.log_error(
error_message=f"[Watermark-Free] Fallback disabled, marking task as failed",
status_code=500,
response_text=str(publish_error)
)
if stream:
yield self._format_stream_chunk(
reasoning_content=f"❌ Error: Failed to get watermark-free version - {str(publish_error)}\nFallback is disabled. Task marked as failed.\n"
)
# Re-raise the exception to mark task as failed
raise
try:
cached_filename = await self.file_cache.download_and_cache(url, "video", token_id=token_id)
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
if stream:
# If watermark-free mode is disabled or failed (with fallback enabled), use normal mode
if not watermark_free_enabled or (watermark_free_failed and watermark_config.fallback_on_failure):
# Normal mode: use downloadable_url instead of url
url = item.get("downloadable_url") or item.get("url")
if not url:
raise Exception("Video URL not found in draft")
debug_logger.log_info(f"Using original URL from draft: {url[:100]}...")
if config.cache_enabled:
# Show appropriate message based on mode
if stream and not watermark_free_failed:
# Normal mode (watermark-free disabled)
yield self._format_stream_chunk(
reasoning_content="**Video Generation Completed**\n\nVideo generation successful. Now caching the video file...\n"
)
try:
cached_filename = await self.file_cache.download_and_cache(url, "video", token_id=token_id)
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
if stream:
if watermark_free_failed:
yield self._format_stream_chunk(
reasoning_content="Video file cached successfully (fallback mode). Preparing final response...\n"
)
else:
yield self._format_stream_chunk(
reasoning_content="Video file cached successfully. Preparing final response...\n"
)
except Exception as cache_error:
# Fallback to original URL if caching fails
local_url = url
if stream:
yield self._format_stream_chunk(
reasoning_content=f"Warning: Failed to cache file - {str(cache_error)}\nUsing original URL instead...\n"
)
else:
# Cache disabled: use original URL directly
except Exception as cache_error:
local_url = url
if stream:
yield self._format_stream_chunk(
reasoning_content="**Video Generation Completed**\n\nCache is disabled. Using original URL directly...\n"
reasoning_content=f"Warning: Failed to cache file - {str(cache_error)}\nUsing original URL instead...\n"
)
else:
# Cache disabled
local_url = url
if stream and not watermark_free_failed:
# Normal mode (watermark-free disabled)
yield self._format_stream_chunk(
reasoning_content="**Video Generation Completed**\n\nCache is disabled. Using original URL directly...\n"
)
# Task completed
await self.db.update_task(