|
|
| """
|
| Jimeng Image Generator Script
|
| Generates images using the local Jimeng API and downloads them to the /pic folder.
|
| """
|
|
|
| import os
|
| import sys
|
| import argparse
|
| import requests
|
| from pathlib import Path
|
| from datetime import datetime
|
| from io import BytesIO
|
|
|
| try:
|
| from PIL import Image
|
| PIL_AVAILABLE = True
|
| except ImportError:
|
| PIL_AVAILABLE = False
|
| print("⚠️ Warning: Pillow not installed. WebP images will be saved as-is.")
|
| print(" Install with: pip install Pillow")
|
|
|
|
|
| def generate_text_to_image(
|
| prompt: str,
|
| session_id: str,
|
| model: str = "jimeng-4.0",
|
| ratio: str = "1:1",
|
| resolution: str = "2k",
|
| intelligent_ratio: bool = False,
|
| negative_prompt: str = None,
|
| sample_strength: float = None,
|
| api_url: str = "http://localhost:5100",
|
| output_dir: str = None
|
| ):
|
| """
|
| Generate images from text using the Jimeng API (文生图).
|
|
|
| Args:
|
| prompt: The text prompt for image generation
|
| session_id: Jimeng session ID (with region prefix if needed)
|
| model: Model to use (jimeng-4.0, jimeng-3.1, etc.)
|
| ratio: Aspect ratio (1:1, 16:9, etc.)
|
| resolution: Resolution level (1k, 2k, 4k)
|
| intelligent_ratio: Enable automatic ratio detection
|
| negative_prompt: Negative prompt (elements to avoid)
|
| sample_strength: Sampling strength (0.0-1.0)
|
| api_url: Jimeng API base URL
|
| output_dir: Output directory for downloaded images
|
|
|
| Returns:
|
| List of downloaded image file paths
|
| """
|
| endpoint = f"{api_url}/v1/images/generations"
|
|
|
|
|
| payload = {
|
| "model": model,
|
| "prompt": prompt,
|
| "ratio": ratio,
|
| "resolution": resolution,
|
| "intelligent_ratio": intelligent_ratio
|
| }
|
|
|
| if negative_prompt:
|
| payload["negative_prompt"] = negative_prompt
|
|
|
| if sample_strength is not None:
|
| payload["sample_strength"] = sample_strength
|
|
|
| headers = {
|
| "Content-Type": "application/json",
|
| "Authorization": f"Bearer {session_id}"
|
| }
|
|
|
| print(f"🎨 Generating image(s) with prompt: {prompt[:60]}...")
|
| print(f"📐 Model: {model}, Ratio: {ratio}, Resolution: {resolution}")
|
|
|
|
|
| try:
|
| response = requests.post(endpoint, json=payload, headers=headers, timeout=300)
|
| response.raise_for_status()
|
| api_response = response.json()
|
| except requests.exceptions.RequestException as e:
|
| print(f"❌ Error calling API: {e}")
|
| if hasattr(e, 'response') and e.response is not None:
|
| print(f" Response: {e.response.text}")
|
| sys.exit(1)
|
|
|
|
|
| return download_images(api_response, output_dir, "text")
|
|
|
|
|
| def generate_image_to_image(
|
| prompt: str,
|
| session_id: str,
|
| images: list,
|
| model: str = "jimeng-4.0",
|
| ratio: str = "1:1",
|
| resolution: str = "2k",
|
| intelligent_ratio: bool = False,
|
| negative_prompt: str = None,
|
| sample_strength: float = None,
|
| api_url: str = "http://localhost:5100",
|
| output_dir: str = None
|
| ):
|
| """
|
| Generate images from input images using the Jimeng API (图生图).
|
|
|
| Args:
|
| prompt: The text prompt for image generation
|
| session_id: Jimeng session ID
|
| images: List of image paths or URLs (1-10 images)
|
| model: Model to use
|
| ratio: Aspect ratio
|
| resolution: Resolution level
|
| intelligent_ratio: Enable automatic ratio detection
|
| negative_prompt: Negative prompt
|
| sample_strength: Sampling strength
|
| api_url: Jimeng API base URL
|
| output_dir: Output directory for downloaded images
|
|
|
| Returns:
|
| List of downloaded image file paths
|
| """
|
| endpoint = f"{api_url}/v1/images/compositions"
|
|
|
| headers = {
|
| "Authorization": f"Bearer {session_id}"
|
| }
|
|
|
|
|
| has_local_files = any(os.path.exists(img) for img in images)
|
|
|
| print(f"🎨 Generating image composition with prompt: {prompt[:60]}...")
|
| print(f"📐 Input images: {len(images)}, Model: {model}")
|
|
|
| try:
|
| if has_local_files:
|
|
|
| files = []
|
| data = {
|
| "prompt": prompt,
|
| "model": model,
|
| "ratio": ratio,
|
| "resolution": resolution
|
| }
|
|
|
| if intelligent_ratio:
|
| data["intelligent_ratio"] = "true"
|
|
|
| if negative_prompt:
|
| data["negative_prompt"] = negative_prompt
|
|
|
| if sample_strength is not None:
|
| data["sample_strength"] = str(sample_strength)
|
|
|
|
|
| for img_path in images:
|
| if os.path.exists(img_path):
|
| files.append(('images', open(img_path, 'rb')))
|
| else:
|
|
|
| if 'images' not in data:
|
| data['images'] = []
|
| data['images'].append(img_path)
|
|
|
| response = requests.post(endpoint, data=data, files=files, headers=headers, timeout=300)
|
|
|
|
|
| for _, file_obj in files:
|
| file_obj.close()
|
| else:
|
|
|
| headers["Content-Type"] = "application/json"
|
| payload = {
|
| "model": model,
|
| "prompt": prompt,
|
| "images": images,
|
| "ratio": ratio,
|
| "resolution": resolution,
|
| "intelligent_ratio": intelligent_ratio
|
| }
|
|
|
| if negative_prompt:
|
| payload["negative_prompt"] = negative_prompt
|
|
|
| if sample_strength is not None:
|
| payload["sample_strength"] = sample_strength
|
|
|
| response = requests.post(endpoint, json=payload, headers=headers, timeout=300)
|
|
|
| response.raise_for_status()
|
| api_response = response.json()
|
|
|
| except requests.exceptions.RequestException as e:
|
| print(f"❌ Error calling API: {e}")
|
| if hasattr(e, 'response') and e.response is not None:
|
| print(f" Response: {e.response.text}")
|
| sys.exit(1)
|
|
|
|
|
| return download_images(api_response, output_dir, "composition")
|
|
|
|
|
| def download_images(api_response: dict, output_dir: str, mode: str):
|
| """
|
| Download images from API response URLs.
|
|
|
| Args:
|
| api_response: JSON response from Jimeng API
|
| output_dir: Output directory path
|
| mode: Generation mode ("text" or "composition")
|
|
|
| Returns:
|
| List of downloaded file paths
|
| """
|
|
|
| if output_dir is None:
|
|
|
| current_dir = Path.cwd()
|
| project_root = current_dir
|
|
|
|
|
| while project_root.parent != project_root:
|
| if any((project_root / marker).exists() for marker in
|
| ['.git', '.claude', 'package.json', 'pyproject.toml', 'requirements.txt']):
|
| break
|
| project_root = project_root.parent
|
|
|
| output_dir = project_root / "pic"
|
| else:
|
| output_dir = Path(output_dir)
|
|
|
|
|
| output_dir.mkdir(parents=True, exist_ok=True)
|
| print(f"📁 Output directory: {output_dir}")
|
|
|
|
|
| downloaded_files = []
|
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
| image_data_list = api_response.get("data", [])
|
|
|
| if not image_data_list:
|
| print("⚠️ No images in API response")
|
| return downloaded_files
|
|
|
| for idx, image_data in enumerate(image_data_list):
|
| image_url = image_data.get("url")
|
|
|
| if not image_url:
|
| print(f"⚠️ No URL for image {idx + 1}")
|
| continue
|
|
|
| print(f"🔗 Image {idx + 1}/{len(image_data_list)}: {image_url[:80]}...")
|
|
|
|
|
| try:
|
| img_response = requests.get(image_url, timeout=60)
|
| img_response.raise_for_status()
|
|
|
|
|
| is_webp = ("format=.webp" in image_url or
|
| image_url.endswith(".webp") or
|
| img_response.content[:4] == b'RIFF')
|
|
|
|
|
| filename = f"jimeng_{timestamp}_{idx + 1}.png"
|
| file_path = output_dir / filename
|
|
|
|
|
| if is_webp and PIL_AVAILABLE:
|
| try:
|
|
|
| img = Image.open(BytesIO(img_response.content))
|
|
|
|
|
| if img.mode in ('RGBA', 'LA', 'P'):
|
|
|
| img.save(file_path, 'PNG', optimize=True)
|
| else:
|
| img.save(file_path, 'PNG', optimize=True)
|
|
|
| print(f"✅ Downloaded and converted (WebP→PNG): {file_path}")
|
| except Exception as e:
|
| print(f"⚠️ WebP conversion failed, saving original: {e}")
|
|
|
| with open(file_path.with_suffix('.webp'), 'wb') as f:
|
| f.write(img_response.content)
|
| file_path = file_path.with_suffix('.webp')
|
| else:
|
|
|
| if is_webp and not PIL_AVAILABLE:
|
|
|
| file_path = file_path.with_suffix('.webp')
|
| print(f"⚠️ Saving as WebP (Pillow not available)")
|
|
|
| with open(file_path, 'wb') as f:
|
| f.write(img_response.content)
|
|
|
| print(f"✅ Downloaded: {file_path}")
|
|
|
| downloaded_files.append(str(file_path))
|
|
|
| except requests.exceptions.RequestException as e:
|
| print(f"❌ Error downloading image {idx + 1}: {e}")
|
|
|
|
|
| print(f"\n📊 Summary:")
|
| print(f" Created at: {api_response.get('created', 'N/A')}")
|
| print(f" Downloaded: {len(downloaded_files)}/{len(image_data_list)} images")
|
|
|
| if mode == "composition" and "input_images" in api_response:
|
| print(f" Input images: {api_response['input_images']}")
|
| print(f" Composition type: {api_response.get('composition_type', 'N/A')}")
|
|
|
| return downloaded_files
|
|
|
|
|
| def main():
|
| parser = argparse.ArgumentParser(
|
| description="Generate images using local Jimeng API",
|
| formatter_class=argparse.RawDescriptionHelpFormatter
|
| )
|
|
|
| subparsers = parser.add_subparsers(dest="mode", help="Generation mode")
|
|
|
|
|
| text_parser = subparsers.add_parser("text", help="Text-to-image generation (文生图)")
|
| text_parser.add_argument("prompt", type=str, help="Text prompt for image generation")
|
|
|
|
|
| image_parser = subparsers.add_parser("image", help="Image-to-image generation (图生图)")
|
| image_parser.add_argument("prompt", type=str, help="Text prompt for image transformation")
|
| image_parser.add_argument(
|
| "--images",
|
| nargs="+",
|
| required=True,
|
| help="Input image paths or URLs (1-10 images)"
|
| )
|
|
|
|
|
| for subparser in [text_parser, image_parser]:
|
| subparser.add_argument(
|
| "--session-id",
|
| type=str,
|
| required=True,
|
| help="Jimeng session ID (with region prefix if needed: us-/hk-/jp-/sg-)"
|
| )
|
| subparser.add_argument(
|
| "--model",
|
| type=str,
|
| default="jimeng-4.5",
|
| choices=["jimeng-5.0", "jimeng-4.6", "jimeng-4.5", "jimeng-4.1", "jimeng-4.0", "jimeng-3.1", "jimeng-3.0", "nanobanana"],
|
| help="Model to use (default: jimeng-4.5)"
|
| )
|
| subparser.add_argument(
|
| "--ratio",
|
| type=str,
|
| default="1:1",
|
| choices=["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "21:9"],
|
| help="Aspect ratio (default: 1:1)"
|
| )
|
| subparser.add_argument(
|
| "--resolution",
|
| type=str,
|
| default="2k",
|
| choices=["1k", "2k", "4k"],
|
| help="Resolution level (default: 2k)"
|
| )
|
| subparser.add_argument(
|
| "--intelligent-ratio",
|
| action="store_true",
|
| help="Enable automatic ratio detection based on prompt"
|
| )
|
| subparser.add_argument(
|
| "--negative-prompt",
|
| type=str,
|
| help="Negative prompt (elements to avoid)"
|
| )
|
| subparser.add_argument(
|
| "--sample-strength",
|
| type=float,
|
| help="Sampling strength (0.0-1.0)"
|
| )
|
| subparser.add_argument(
|
| "--api-url",
|
| type=str,
|
| default="http://localhost:5100",
|
| help="Jimeng API base URL (default: http://localhost:5100)"
|
| )
|
| subparser.add_argument(
|
| "--output-dir",
|
| type=str,
|
| help="Output directory for images (defaults to project_root/pic)"
|
| )
|
|
|
| args = parser.parse_args()
|
|
|
|
|
| if not args.mode:
|
| parser.print_help()
|
| sys.exit(1)
|
|
|
|
|
| if args.sample_strength is not None:
|
| if args.sample_strength < 0.0 or args.sample_strength > 1.0:
|
| print("❌ Error: sample_strength must be between 0.0 and 1.0")
|
| sys.exit(1)
|
|
|
|
|
| try:
|
| if args.mode == "text":
|
| downloaded_files = generate_text_to_image(
|
| prompt=args.prompt,
|
| session_id=args.session_id,
|
| model=args.model,
|
| ratio=args.ratio,
|
| resolution=args.resolution,
|
| intelligent_ratio=args.intelligent_ratio,
|
| negative_prompt=args.negative_prompt,
|
| sample_strength=args.sample_strength,
|
| api_url=args.api_url,
|
| output_dir=args.output_dir
|
| )
|
| else:
|
|
|
| if len(args.images) < 1 or len(args.images) > 10:
|
| print("❌ Error: Number of images must be between 1 and 10")
|
| sys.exit(1)
|
|
|
| downloaded_files = generate_image_to_image(
|
| prompt=args.prompt,
|
| session_id=args.session_id,
|
| images=args.images,
|
| model=args.model,
|
| ratio=args.ratio,
|
| resolution=args.resolution,
|
| intelligent_ratio=args.intelligent_ratio,
|
| negative_prompt=args.negative_prompt,
|
| sample_strength=args.sample_strength,
|
| api_url=args.api_url,
|
| output_dir=args.output_dir
|
| )
|
|
|
| print(f"\n✨ Successfully generated and downloaded {len(downloaded_files)} image(s)!")
|
|
|
| except KeyboardInterrupt:
|
| print("\n\n⚠️ Generation cancelled by user")
|
| sys.exit(1)
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|