| import os, sys, random, requests, time, io |
| from gradio_client import Client, handle_file |
| from PIL import Image, ImageOps |
|
|
| prompt = os.environ.get('PROMPT', '').strip() |
| if not prompt: |
| prompt = 'make this image come alive, cinematic motion, smooth animation' |
| else: |
| prompt = f'{prompt}, cinematic motion, smooth animation, high quality' |
|
|
| negative_prompt = '色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺의, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走' |
|
|
| image_url = os.environ.get('IMAGE_URL', '') |
| run_id = os.environ.get('RUN_ID') |
| space_url = os.environ.get('SPACE_URL') |
| github_run_id = os.environ.get('GITHUB_RUN_ID') |
|
|
| |
| try: |
| duration = float(os.environ.get('DURATION', 5.0)) |
| except Exception: |
| duration = 5.0 |
|
|
| |
| def report_failure(error_msg): |
| try: |
| requests.post( |
| f"{space_url}/api/webhook/fail", |
| json={ |
| "run_id": run_id, |
| "error": error_msg, |
| "event_type": "generate-video", |
| "client_payload": { |
| "prompt": prompt, |
| "duration": duration, |
| "image_url": image_url, |
| "run_id": run_id, |
| "space_url": space_url |
| }, |
| "github_run_id": github_run_id |
| }, |
| timeout=15 |
| ) |
| except Exception as e: |
| print(f"Failed to report failure: {e}") |
|
|
| print('1. Downloading and validating input image from Docker Space...') |
|
|
| max_download_attempts = 2 |
| download_success = False |
|
|
| |
| for dl_attempt in range(max_download_attempts): |
| try: |
| req = requests.get(image_url, timeout=30) |
| |
| |
| if req.status_code == 200: |
| download_success = True |
| break |
| else: |
| print(f" -> Download attempt {dl_attempt + 1} failed (Status {req.status_code}).") |
| |
| except Exception as e: |
| print(f" -> Download attempt {dl_attempt + 1} connection failed: {e}") |
| |
| |
| if dl_attempt < max_download_attempts - 1: |
| print(" -> Waiting 2 seconds before retrying download...") |
| time.sleep(2.0) |
|
|
| |
| if not download_success: |
| err_str = f"Download failed after {max_download_attempts} attempts. URL might be invalid or CDN delayed." |
| print(f"CRITICAL ERROR: {err_str}") |
| report_failure(err_str) |
| sys.exit(1) |
| |
| |
| try: |
| img_data = io.BytesIO(req.content) |
| img = Image.open(img_data) |
| img.verify() |
| |
| |
| img_data.seek(0) |
| clean_img = Image.open(img_data).convert('RGB') |
| clean_img.save('input_raw.png', format='PNG') |
| print(' -> Image successfully downloaded, verified, and strictly converted to PNG format.') |
| |
| except Exception as img_err: |
| err_str = f"Downloaded content is corrupted or not a valid image format: {img_err}" |
| print(f"CRITICAL ERROR: {err_str}") |
| report_failure(err_str) |
| sys.exit(1) |
|
|
| print('2. Analyzing image dimensions to prevent stretching...') |
| |
| img = Image.open('input_raw.png') |
| orig_w, orig_h = img.size |
| ratio = orig_w / orig_h |
|
|
| if ratio > 1.2: |
| target_w, target_h = 832, 480 |
| print(' -> Format: Landscape (16:9)') |
| elif ratio < 0.8: |
| target_w, target_h = 480, 832 |
| print(' -> Format: Portrait (9:16)') |
| else: |
| target_w, target_h = 480, 480 |
| print(' -> Format: Square (1:1)') |
|
|
| img_resized = ImageOps.fit(img, (target_w, target_h), Image.Resampling.LANCZOS) |
| img_resized.save('input.png', format='PNG') |
| print(f' -> Image perfectly resized to {target_w}x{target_h}') |
|
|
| print('3. Connecting to AI Video Space...') |
| video_path = None |
|
|
| space_id = 'zerogpu-aoti/wan2-2-fp8da-aoti-faster' |
| print(f' -> Loading Space: {space_id}') |
| try: |
| client = Client(space_id) |
| except Exception as e: |
| err_str = f"Could not load AI space: {e}" |
| print(f'CRITICAL ERROR: {err_str}') |
| report_failure(err_str) |
| sys.exit(1) |
|
|
| max_attempts = 4 |
| for attempt in range(max_attempts): |
| seed = random.randint(1, 2147483647) |
| print(f' -> Attempt {attempt + 1} of {max_attempts} with seed {seed} and duration {duration}s...') |
| |
| try: |
| result = client.predict( |
| handle_file('input.png'), |
| prompt, |
| 6, |
| negative_prompt, |
| duration, |
| 1, |
| 1, |
| seed, |
| True, |
| fn_index=0 |
| ) |
| |
| if isinstance(result, list) and len(result) > 0: |
| video_path = result[0].get('video') or result[0].get('path') if isinstance(result[0], dict) else result[0] |
| elif isinstance(result, tuple) and len(result) > 0: |
| video_path = result[0] |
| elif isinstance(result, dict): |
| video_path = result.get('video') or result.get('path') |
| else: |
| video_path = result |
| |
| if video_path and os.path.exists(str(video_path)): |
| print(' -> Success! Video generated.') |
| break |
| |
| except Exception as e: |
| err_msg = str(e) |
| print(f' -> Failed on attempt {attempt + 1}: {err_msg}') |
| |
| |
| if 'ZeroGPU quota' in err_msg or 'quota' in err_msg.lower(): |
| print(' -> ZeroGPU quota hit. Aborting internal retries to trigger account switch.') |
| report_failure(err_msg) |
| sys.exit(1) |
| |
| time.sleep(5) |
|
|
| if not video_path or not os.path.exists(str(video_path)): |
| err_str = "Failed to generate video after all internal attempts." |
| print(f'CRITICAL ERROR: {err_str}') |
| report_failure(err_str) |
| sys.exit(1) |
|
|
| print('4. Uploading back to your Docker Space...') |
| try: |
| with open(video_path, 'rb') as f: |
| res = requests.post(f'{space_url}/api/webhook/upload', |
| data={'run_id': run_id, 'github_run_id': github_run_id, 'ext': 'mp4'}, |
| files={'file': f}) |
|
|
| if res.status_code == 200: |
| print('5. SUCCESS! Process finished.') |
| else: |
| err_str = f"Upload failed with status {res.status_code}: {res.text}" |
| print(f'CRITICAL ERROR: {err_str}') |
| report_failure(err_str) |
| sys.exit(1) |
| except Exception as e: |
| err_str = f"Upload connection failed: {e}" |
| print(f'CRITICAL ERROR: {err_str}') |
| report_failure(err_str) |
| sys.exit(1) |