From 398973387c477d16b384ed9cca21fa2a414c24a7 Mon Sep 17 00:00:00 2001 From: teith Date: Thu, 24 Jul 2025 20:08:30 +0200 Subject: [PATCH 1/4] refactor --- docs/CODE_OF_CONDUCT.md => CODE_OF_CONDUCT.md | 0 docs/CONTRIBUTING.md => CONTRIBUTING.md | 0 docs/LICENSE.md | 21 - docs/controlnets.md | 87 - docs/editing.md | 63 - docs/examples/example1.md | 0 docs/examples/example2.md | 0 docs/getting_started.md | 113 -- docs/image_to_image.md | 0 docs/text_to_image.md | 129 -- docs/utilities.md | 27 - docs/utilities_image_to_text.md | 52 - docs/utilities_image_upload.md | 51 - docs/utilities_prompt_enhancer.md | 76 - examples/01_basic_image_generation.py | 130 ++ examples/02_advanced_image_generation.py | 226 +++ examples/03_image_editing.py | 259 +++ examples/04_video_generation.py | 315 ++++ examples/05_ace_plus_plus.py | 206 +++ examples/06_photo_maker.py | 344 ++++ examples/07_prompt_enhancement.py | 323 ++++ examples/08_model_management.py | 332 ++++ examples/09_image_to_image_inpainting.py | 417 +++++ examples/ace++/local_local_1.py | 36 - examples/ace++/logo_paste.py | 40 - examples/ace++/movie_poster_1.py | 40 - examples/ace++/photo_editing_1.py | 39 - examples/ace++/portrait_human_1.py | 36 - examples/ace++/subject_subject_1.py | 35 - examples/dalmatian.jpg | Bin 724970 -> 0 bytes .../image_background_removal_withModel.py | 38 - .../image_background_removal_with_settings.py | 51 - examples/image_caption.py | 46 - examples/image_upscale.py | 52 - examples/parallel_image_inference.py | 124 -- examples/prompt_enhance.py | 53 - examples/retriever.jpg | Bin 211220 -> 0 bytes examples/video/kling.py | 33 - examples/video/minimax.py | 41 - examples/video/pixverse.py | 49 - examples/video/seedance.py | 40 - examples/video/veo.py | 40 - examples/video/vidu.py | 37 - requirements.txt | 2 +- runware/__init__.py | 179 +- runware/async_retry.py | 96 - runware/base.py | 1564 ----------------- runware/client.py | 699 ++++++++ runware/connection/__init__.py | 11 + runware/connection/manager.py | 435 +++++ runware/connection/types.py | 14 + runware/core/__init__.py | 15 + runware/core/cpu_bound.py | 81 + runware/core/error_context.py | 56 + runware/core/types.py | 69 + runware/exceptions.py | 127 ++ runware/logging_config.py | 194 +- runware/messaging/__init__.py | 22 + runware/messaging/router.py | 323 ++++ runware/messaging/types.py | 38 + runware/operations/__init__.py | 25 + runware/operations/base.py | 593 +++++++ .../operations/image_background_removal.py | 90 + runware/operations/image_caption.py | 75 + runware/operations/image_inference.py | 400 +++++ runware/operations/image_upscale.py | 89 + runware/operations/manager.py | 286 +++ runware/operations/model_search.py | 143 ++ runware/operations/model_upload.py | 135 ++ runware/operations/photo_maker.py | 137 ++ runware/operations/prompt_enhance.py | 81 + runware/operations/video_inference.py | 466 +++++ runware/server.py | 298 ---- runware/types.py | 161 +- runware/utils.py | 728 +------- setup.py | 6 +- tests/__init__.py | 0 tests/test_base.py | 0 tests/test_server.py | 0 tests/test_types.py | 221 --- tests/test_utils.py | 297 ---- 81 files changed, 7362 insertions(+), 4795 deletions(-) rename docs/CODE_OF_CONDUCT.md => CODE_OF_CONDUCT.md (100%) rename docs/CONTRIBUTING.md => CONTRIBUTING.md (100%) delete mode 100644 docs/LICENSE.md delete mode 100644 docs/controlnets.md delete mode 100644 docs/editing.md delete mode 100644 docs/examples/example1.md delete mode 100644 docs/examples/example2.md delete mode 100644 docs/getting_started.md delete mode 100644 docs/image_to_image.md delete mode 100644 docs/text_to_image.md delete mode 100644 docs/utilities.md delete mode 100644 docs/utilities_image_to_text.md delete mode 100644 docs/utilities_image_upload.md delete mode 100644 docs/utilities_prompt_enhancer.md create mode 100644 examples/01_basic_image_generation.py create mode 100644 examples/02_advanced_image_generation.py create mode 100644 examples/03_image_editing.py create mode 100644 examples/04_video_generation.py create mode 100644 examples/05_ace_plus_plus.py create mode 100644 examples/06_photo_maker.py create mode 100644 examples/07_prompt_enhancement.py create mode 100644 examples/08_model_management.py create mode 100644 examples/09_image_to_image_inpainting.py delete mode 100644 examples/ace++/local_local_1.py delete mode 100644 examples/ace++/logo_paste.py delete mode 100644 examples/ace++/movie_poster_1.py delete mode 100644 examples/ace++/photo_editing_1.py delete mode 100644 examples/ace++/portrait_human_1.py delete mode 100644 examples/ace++/subject_subject_1.py delete mode 100644 examples/dalmatian.jpg delete mode 100644 examples/image_background_removal_withModel.py delete mode 100644 examples/image_background_removal_with_settings.py delete mode 100644 examples/image_caption.py delete mode 100644 examples/image_upscale.py delete mode 100644 examples/parallel_image_inference.py delete mode 100644 examples/prompt_enhance.py delete mode 100644 examples/retriever.jpg delete mode 100644 examples/video/kling.py delete mode 100644 examples/video/minimax.py delete mode 100644 examples/video/pixverse.py delete mode 100644 examples/video/seedance.py delete mode 100644 examples/video/veo.py delete mode 100644 examples/video/vidu.py delete mode 100644 runware/async_retry.py delete mode 100644 runware/base.py create mode 100644 runware/client.py create mode 100644 runware/connection/__init__.py create mode 100644 runware/connection/manager.py create mode 100644 runware/connection/types.py create mode 100644 runware/core/__init__.py create mode 100644 runware/core/cpu_bound.py create mode 100644 runware/core/error_context.py create mode 100644 runware/core/types.py create mode 100644 runware/exceptions.py create mode 100644 runware/messaging/__init__.py create mode 100644 runware/messaging/router.py create mode 100644 runware/messaging/types.py create mode 100644 runware/operations/__init__.py create mode 100644 runware/operations/base.py create mode 100644 runware/operations/image_background_removal.py create mode 100644 runware/operations/image_caption.py create mode 100644 runware/operations/image_inference.py create mode 100644 runware/operations/image_upscale.py create mode 100644 runware/operations/manager.py create mode 100644 runware/operations/model_search.py create mode 100644 runware/operations/model_upload.py create mode 100644 runware/operations/photo_maker.py create mode 100644 runware/operations/prompt_enhance.py create mode 100644 runware/operations/video_inference.py delete mode 100644 runware/server.py delete mode 100644 tests/__init__.py delete mode 100644 tests/test_base.py delete mode 100644 tests/test_server.py delete mode 100644 tests/test_types.py delete mode 100644 tests/test_utils.py diff --git a/docs/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to CODE_OF_CONDUCT.md diff --git a/docs/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from docs/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/docs/LICENSE.md b/docs/LICENSE.md deleted file mode 100644 index 6734491..0000000 --- a/docs/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -# MIT License - -Copyright (c) 2024 Runware Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/docs/controlnets.md b/docs/controlnets.md deleted file mode 100644 index 3315d13..0000000 --- a/docs/controlnets.md +++ /dev/null @@ -1,87 +0,0 @@ -# ControlNet Guide - -ControlNet offers advanced capabilities for precise image processing through the use of guide images in specific formats, known as preprocessed images. This powerful tool enhances the control and customization of image generation, enabling users to achieve desired artistic styles and detailed adjustments effectively. - -Using ControlNet via our API simplifies the integration of guide images into your workflow. By leveraging the API, you can seamlessly incorporate preprocessed images and specify various parameters to tailor the image generation process to your exact requirements. - -## Request - -Our API always accepts an array of objects as input, where each object represents a specific task to be performed. The structure of the object varies depending on the type of the task. For this section, we will focus on the parameters related to the ControlNet preprocessing task. - -The following JSON snippet shows the basic structure of a request object. All properties are explained in detail in the next section: - -```json -[ - { - "taskType": "imageControlNetPreProcess", - "taskUUID": "3303f1be-b3dc-41a2-94df-ead00498db57", - "inputImage": "ff1d9a0b-b80f-4665-ae07-8055b99f4aea", - "preProcessorType": "canny", - "height": 512, - "width": 512 - } -] -``` - -### Parameters - -| Parameter | Type | Description | -|-------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------| -| taskType | string | Must be set to "imageControlNetPreProcess" for this operation. | -| taskUUID | UUIDv4 string | Unique identifier for the task, used to match async responses. | -| inputImage | UUIDv4 string | The UUID of the image to be preprocessed. Use the Image Upload functionality to obtain this. | -| preProcessorType | string | The type of preprocessor to use. See list of available options below. | -| width | integer | Optional. Will resize the image to this width. | -| height | integer | Optional. Will resize the image to this height. | -| lowThresholdCanny | integer | Optional. Available only for 'canny' preprocessor. Defines the lower threshold. Recommended value is 100. | -| highThresholdCanny | integer | Optional. Available only for 'canny' preprocessor. Defines the high threshold. Recommended value is 200. | -| includeHandsAndFaceOpenPose | boolean | Optional. Available only for 'openpose' preprocessor. Includes hands and face in the pose outline. Defaults to false. | - -### Preprocessor Types - -Available preprocessor types are: - -```json -canny -depth -mlsd -normalbae -openpose -tile -seg -lineart -lineart_anime -shuffle -scribble -softedge -``` - -## Response - -The response to the ControlNet preprocessing request will have the following format: - -```json -{ - "data": [ - { - "taskType": "imageControlNetPreProcess", - "taskUUID": "3303f1be-b3dc-41a2-94df-ead00498db57", - "guideImageUUID": "b6a06b3b-ce32-4884-ad93-c5eca7937ba0", - "inputImageUUID": "ff1d9a0b-b80f-4665-ae07-8055b99f4aea", - "guideImageURL": "https://im.runware.ai/image/ws/0.5/ii/b6a06b3b-ce32-4884-ad93-c5eca7937ba0.jpg", - "cost": 0.0006 - } - ] -} -``` - -### Response Parameters - -| Parameter | Type | Description | -|----------------|---------------|------------------------------------------------------------------------------------------------| -| taskType | string | The type of task, in this case "imageControlNetPreProcess". | -| taskUUID | UUIDv4 string | The unique identifier matching the original request. | -| guideImageUUID | UUIDv4 string | Unique identifier for the preprocessed guide image. | -| inputImageUUID | UUIDv4 string | The UUID of the original input image. | -| guideImageURL | string | The URL of the preprocessed guide image. Can be used to visualize or display the image in UIs. | -| cost | number | The cost of the operation (if includeCost was set to true in the request). | diff --git a/docs/editing.md b/docs/editing.md deleted file mode 100644 index de58d83..0000000 --- a/docs/editing.md +++ /dev/null @@ -1,63 +0,0 @@ -# Image Upscaling - -Enhance the resolution and quality of your images using Runware's advanced upscaling API. Transform low-resolution images into sharp, high-definition visuals. -Upscaling refers to the process of enhancing the resolution and overall quality of images. This technique is particularly useful for improving the visual clarity and detail of lower-resolution images, making them suitable for various high-definition applications. - -## Request - -To upscale an image, send a request in the following format: - -```json -[ - { - "taskType": "imageUpscale", - "taskUUID": "19abad0d-6ec5-40a6-b7af-203775fa5b7f", - "inputImage": "fd613011-3872-4f37-b4aa-0d343c051a27", - "outputType": "URL", - "outputFormat": "JPG", - "upscaleFactor": 2 - } -] -``` - -### Parameters - -| Parameter | Type | Description | -|---------------|---------------|---------------------------------------------------------------------------------------------------------------| -| taskType | string | Must be set to "imageUpscale" for this operation. | -| taskUUID | UUIDv4 string | Unique identifier for the task, used to match async responses. | -| inputImage | UUIDv4 string | The UUID of the image to be upscaled. Can be from a previously uploaded or generated image. | -| upscaleFactor | integer | The level of upscaling to be performed. Can be 2, 3, or 4. Each will increase the image size by that factor. | -| outputFormat | string | Specifies the format of the output image. Supported formats are: PNG, JPG and WEBP. | -| includeCost | boolean | Optional. If set to true, the response will include the cost of the operation. | - -## Response - -Responses will be delivered in the following format: - -```json -{ - "data": [ - { - "taskType": "imageUpscale", - "taskUUID": "19abad0d-6ec5-40a6-b7af-203775fa5b7f", - "imageUUID": "e0b6ed2b-311d-4abc-aa01-8f3fdbdb8860", - "inputImageUUID": "fd613011-3872-4f37-b4aa-0d343c051a27", - "imageURL": "https://im.runware.ai/image/ws/0.5/ii/e0b6ed2b-311d-4abc-aa01-8f3fdbdb8860.jpg", - "cost": 0 - } - ] -} -``` - -### Response Parameters - -| Parameter | Type | Description | -|--------------|---------------|------------------------------------------------------------------------------------------------| -| taskType | string | The type of task, in this case "imageUpscale". | -| taskUUID | UUIDv4 string | The unique identifier matching the original request. | -| imageUUID | UUIDv4 string | The UUID of the upscaled image. | -| imageURL | string | The URL where the upscaled image can be downloaded from. | -| cost | number | The cost of the operation (included if `includeCost` was set to true). | - -Note: The NSFW filter occasionally returns false positives and very rarely false negatives. \ No newline at end of file diff --git a/docs/examples/example1.md b/docs/examples/example1.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/examples/example2.md b/docs/examples/example2.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/getting_started.md b/docs/getting_started.md deleted file mode 100644 index aec50f8..0000000 --- a/docs/getting_started.md +++ /dev/null @@ -1,113 +0,0 @@ -# WebSockets Endpoint, API Key, and Connections - -This guide explains how to authenticate, connect to, and interact with the Runware WebSocket API. - -## Authentication - -To interact with the Runware API, you need to authenticate your requests using an API key. This key is unique to your account and identifies you when making requests. - -- You can create multiple keys for different projects or environments (development, production, staging). -- Keys can have descriptions and can be revoked at any time. -- With the new teams feature, you can share keys with your team members. - -To create an API key: - -1. Sign up on Runware -2. Visit the "API Keys" page -3. Click "Create Key" -4. Fill in the details for your new key - -## WebSockets - -We currently support WebSocket connections as they are more efficient, faster, and less resource-intensive. Our WebSocket connections are designed to be easy to work with, as each response contains the request ID, allowing for easy matching of requests to responses. - -- The API uses a bidirectional protocol that encodes all messages as JSON objects. -- You can connect using one of our provided SDKs (Python, JavaScript, Go) or manually. -- If connecting manually, the endpoint URL is `wss://ws-api.runware.ai/v1`. - -## New Connections - -WebSocket connections are point-to-point, so there's no need for each request to contain an authentication header. Instead, the first request must always be an authentication request that includes the API key. - -### Authentication Request - -```json -[ - { - "taskType": "authentication", - "apiKey": "" - } -] -``` - -### Authentication Response - -On successful authentication, you'll receive a response with a `connectionSessionUUID`: - -```json -{ - "data": [ - { - "taskType": "authentication", - "connectionSessionUUID": "f40c2aeb-f8a7-4af7-a1ab-7594c9bf778f" - } - ] -} -``` - -In case of an error, you'll receive an object with an error message: - -```json -{ - "error": true, - "errorMessageContentId": 1212, - "errorId": 19, - "errorMessage": "Invalid api key" -} -``` - -## Keeping Connection Alive - -The WebSocket connection is kept open for 120 seconds from the last message exchanged. If you don't send any messages for 120 seconds, the connection will be closed automatically. - -To keep the connection active, you can send a `ping` message: - -```json -[ - { - "taskType": "ping", - "ping": true - } -] -``` - -The server will respond with a `pong`: - -```json -{ - "data": [ - { - "taskType": "ping", - "pong": true - } - ] -} -``` - -## Resuming Connections - -If any service, server, or network becomes unresponsive, all undelivered images or tasks are kept in a buffer memory for 120 seconds. You can reconnect and receive these messages by including the `connectionSessionUUID` in the authentication request: - -```json -[ - { - "taskType": "authentication", - "apiKey": "", - "connectionSessionUUID": "f40c2aeb-f8a7-4af7-a1ab-7594c9bf778f" - } -] -``` - -This means you don't need to resend the initial request; it will be delivered when reconnecting. SDK libraries handle reconnections automatically. - -After establishing a connection, you can send various tasks to the API, such as text-to-image, image-to-image, inpainting, upscaling, image-to-text, image upload, etc. \ No newline at end of file diff --git a/docs/image_to_image.md b/docs/image_to_image.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/text_to_image.md b/docs/text_to_image.md deleted file mode 100644 index 599c782..0000000 --- a/docs/text_to_image.md +++ /dev/null @@ -1,129 +0,0 @@ -# Image Inference API - -Generate images from text prompts or transform existing ones using Runware's API. This powerful feature allows you to create high-quality visuals, bringing creative ideas to life or enhancing existing images with new styles or subjects. - -## Introduction - -Image inference enables you to: - -1. **Text-to-Image**: Generate images from descriptive text prompts. -2. **Image-to-Image**: Transform existing images, controlling the strength of the transformation. -3. **Inpainting**: Replace parts of an image with new content. -4. **Outpainting**: Extend the boundaries of an image with new content. - -Advanced features include: - -- **ControlNet**: Precise control over image generation using additional input conditions. -- **LoRA**: Adapt models to specific styles or tasks. - -Our API is optimized for speed and efficiency, powered by our Sonic Inference Engine. - -## Request - -Requests are sent as an array of objects, each representing a specific task. Here's the basic structure for an image inference task: - -```json -[ - { - "taskType": "imageInference", - "taskUUID": "string", - "outputType": "string", - "outputFormat": "string", - "positivePrompt": "string", - "negativePrompt": "string", - "height": int, - "width": int, - "model": "string", - "steps": int, - "CFGScale": float, - "numberResults": int - } -] -``` - -### Parameters - -| Parameter | Type | Required | Description | -|------------------|-------------------------------|----------|------------------------------------------------------------------------------------------------| -| taskType | string | Yes | Must be set to "imageInference" for this task. | -| taskUUID | string (UUID v4) | Yes | Unique identifier for the task, used to match async responses. | -| outputType | string | No | Specifies the output format: "base64Data", "dataURI", or "URL" (default). | -| outputFormat | string | No | Specifies the image format: "JPG" (default), "PNG", or "WEBP". | -| positivePrompt | string | Yes | Text instruction guiding the image generation (4-2000 characters). | -| negativePrompt | string | No | Text instruction to avoid certain elements in the image (4-2000 characters). | -| height | integer | Yes | Height of the generated image (512-2048, must be divisible by 64). | -| width | integer | Yes | Width of the generated image (512-2048, must be divisible by 64). | -| model | string | Yes | AIR identifier of the model to use. | -| steps | integer | No | Number of inference steps (1-100, default 20). | -| CFGScale | float | No | Guidance scale for prompt adherence (0-30, default 7). | -| numberResults | integer | No | Number of images to generate (default 1). | - -Additional parameters: - -| Parameter | Type | Required | Description | -|---------------------|---------|----------|---------------------------------------------------------------------------------| -| uploadEndpoint | string | No | URL to upload the generated image using HTTP PUT. | -| checkNSFW | boolean | No | Enable NSFW content check (adds 0.1s to inference time). | -| includeCost | boolean | No | Include the cost of the operation in the response. | -| seedImage | string | No* | Image to use as a starting point (required for Image-to-Image, In/Outpainting). | -| maskImage | string | No* | Mask image for Inpainting/Outpainting (required for these operations). | -| strength | float | No | Influence of the seed image (0-1, default 0.8). | -| scheduler | string | No | Specify a different scheduler (default is model's own scheduler). | -| seed | integer | No | Seed for reproducible results (1-9223372036854776000). | -| clipSkip | integer | No | Number of CLIP layers to skip (0-2, default 0). | -| usePromptWeighting | boolean | No | Enable advanced prompt weighting (adds 0.2s to inference time). | - -### ControlNet - -To use ControlNet, include a `controlNet` array in your request with objects containing: - -| Parameter | Type | Required | Description | -|-----------|---------|----------|-----------------------------------------------------------| -| model | string | Yes | AIR identifier of the ControlNet model. | -| guideImage| string | Yes | Preprocessed guide image (UUID, data URI, base64, or URL).| -| weight | float | No | Weight of this ControlNet model (0-1). | -| startStep | integer | No | Step to start applying ControlNet. | -| endStep | integer | No | Step to stop applying ControlNet. | -| controlMode| string | No | "prompt", "controlnet", or "balanced". | - -### LoRA - -To use LoRA, include a `lora` array in your request with objects containing: - -| Parameter | Type | Required | Description | -|-----------|--------|----------|----------------------------------------| -| model | string | Yes | AIR identifier of the LoRA model. | -| weight | float | No | Weight of this LoRA model (default 1). | - -## Response - -The API returns results in the following format: - -```json -{ - "data": [ - { - "taskType": "imageInference", - "taskUUID": "a770f077-f413-47de-9dac-be0b26a35da6", - "imageUUID": "77da2d99-a6d3-44d9-b8c0-ae9fb06b6200", - "imageURL": "https://im.runware.ai/image/ws/0.5/ii/a770f077-f413-47de-9dac-be0b26a35da6.jpg", - "cost": 0.0013 - } - ] -} -``` - -### Response Parameters - -| Parameter | Type | Description | -|----------------|------------------|---------------------------------------------------------------------| -| taskType | string | Type of the task ("imageInference"). | -| taskUUID | string (UUID v4) | Unique identifier matching the original request. | -| imageUUID | string (UUID v4) | Unique identifier of the generated image. | -| imageURL | string | URL to download the image (if outputType is "URL"). | -| imageBase64Data| string | Base64-encoded image data (if outputType is "base64Data"). | -| imageDataURI | string | Data URI of the image (if outputType is "dataURI"). | -| NSFWContent | boolean | Indicates if the image was flagged as NSFW (if checkNSFW was true). | -| cost | float | Cost of the operation in USD (if includeCost was true). | - -Note: The API may return multiple images per message, as they are generated in parallel. diff --git a/docs/utilities.md b/docs/utilities.md deleted file mode 100644 index 7f712e7..0000000 --- a/docs/utilities.md +++ /dev/null @@ -1,27 +0,0 @@ -# Utilities - -The Runware SDK provides several utility functions to enhance your workflow and simplify common tasks. This section provides an overview of the available utilities. - -## Image Upload - -Images can be uploaded to be used as seed to reverse prompts and get image to text results. - -For detailed information on how to use the Image Upload utility, see the [Image Upload documentation](utilities_image_upload.md). - -## Image to Text - -Image to text allows you to upload an image and generate the prompt used to create similar images. - -To learn more about the Image to Text utility and how to use it in your code, refer to the [Image to Text documentation](utilities_image_to_text.md). - -## Prompt Enhancer - -Prompt enhancer can be used to add keywords to prompts that are meant to increase the quality or variety of results. - -For a comprehensive guide on using the Prompt Enhancer utility, see the [Prompt Enhancer documentation](utilities_prompt_enhancer.md). - ---- - -These utilities are designed to streamline your workflow and provide additional functionality to the core features of the Runware SDK. Each utility comes with its own detailed documentation, explaining how to use it effectively in your projects. - -If you have any questions or need further assistance with the utilities, please don't hesitate to [reach out](https://github.com/runware/sdk-python/issues) to our support team. diff --git a/docs/utilities_image_to_text.md b/docs/utilities_image_to_text.md deleted file mode 100644 index f84b623..0000000 --- a/docs/utilities_image_to_text.md +++ /dev/null @@ -1,52 +0,0 @@ -# Image to Text - -Image to text, also known as image captioning, allows you to obtain descriptive text prompts based on uploaded or previously generated images. This process is instrumental in generating textual descriptions that can be used to create additional images or provide detailed insights into visual content. - -## Request - -Image to text requests must have the following format: - -```json -[ - { - "taskType": "imageCaption", - "taskUUID": "f0a5574f-d653-47f1-ab42-e2c1631f1a47", - "inputImage": "5788104a-1ca7-4b7e-8a16-b27b57e86f87" - } -] -``` - -### Parameters - -| Parameter | Type | Description | -|-------------|--------------|---------------------------------------------------------------------------------------| -| taskType | string | Must be set to "imageCaption" for this operation. | -| taskUUID | UUIDv4 string | Unique identifier for the task, used to match async responses. | -| inputImage | UUIDv4 string | The UUID of the image to be analyzed. Can be from an uploaded or generated image. | -| includeCost | boolean | Optional. If set to true, the response will include the cost of the operation. | - -## Response - -Results will be delivered in the following format: - -```json -{ - "data": [ - { - "taskType": "imageCaption", - "taskUUID": "f0a5574f-d653-47f1-ab42-e2c1631f1a47", - "text": "arafed troll in the jungle with a backpack and a stick, cgi animation, cinematic movie image, gremlin, pixie character, nvidia promotional image, park background, with lots of scumbling, hollywood promotional image, on island, chesley, green fog, post-nuclear", - "cost": 0 - } - ] -} -``` - -### Response Parameters - -| Parameter | Type | Description | -|-----------|---------------|-----------------------------------------------------------------------| -| taskType | string | The type of task, in this case "imageCaption". | -| taskUUID | UUIDv4 string | The unique identifier matching the original request. | -| text | string | The resulting text prompt from analyzing the image. | -| cost | number | The cost of the operation (included if `includeCost` was set to true).| \ No newline at end of file diff --git a/docs/utilities_image_upload.md b/docs/utilities_image_upload.md deleted file mode 100644 index 630d59e..0000000 --- a/docs/utilities_image_upload.md +++ /dev/null @@ -1,51 +0,0 @@ -# Image Upload - -Image upload is necessary for using images as seeds for new image generation, or to run image-to-text operations and obtain prompts that would generate similar images. - -## Request - -Image upload requests must have the following format: - -```json -[ - { - "taskType": "imageUpload", - "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", - "image": "data:image/png;base64,iVBORw0KGgo..." - } -] -``` - -### Parameters - -| Parameter | Type | Description | -|-----------|--------------|---------------------------------------------------------------------------------------| -| taskType | string | Must be set to "imageUpload" for this operation. | -| taskUUID | UUIDv4 string | Unique identifier for the task, used to match async responses. | -| image | string | The image file in base64 format. Supported formats are: PNG, JPG, WEBP. | - -## Response - -The response to the image upload request will have the following format: - -```json -{ - "data": [ - { - "taskType": "imageUpload", - "taskUUID": "50836053-a0ee-4cf5-b9d6-ae7c5d140ada", - "imageUUID": "989ba605-1449-4e1e-b462-cd83ec9c1a67", - "imageURL": "https://im.runware.ai/image/ws/0.5/ii/989ba605-1449-4e1e-b462-cd83ec9c1a67.jpg" - } - ] -} -``` - -### Response Parameters - -| Parameter | Type | Description | -|-----------|---------------|------------------------------------------------------------------------------------------------| -| taskType | string | The type of task, in this case "imageUpload". | -| taskUUID | UUIDv4 string | The unique identifier matching the original request. | -| imageUUID | UUIDv4 string | Unique identifier for the uploaded image. Use this for referencing in other operations. | -| imageURL | string | The URL of the uploaded image. Can be used to visualize or display the image in UIs. | diff --git a/docs/utilities_prompt_enhancer.md b/docs/utilities_prompt_enhancer.md deleted file mode 100644 index 69bc557..0000000 --- a/docs/utilities_prompt_enhancer.md +++ /dev/null @@ -1,76 +0,0 @@ -# Prompt Enhancer (Magic Prompt) - -Prompt enhancing can be used to generate different or potentially improved results for a particular topic. It works by adding keywords to a given prompt. Note that enhancing a prompt does not always preserve the intended subject and does not guarantee improved results over the original prompt. - -## Request - -Prompt enhancing requests must have the following format: - -```json -[ - { - "taskType": "promptEnhance", - "taskUUID": "9da1a4ad-c3de-4470-905d-5be5c042f98a", - "prompt": "dog", - "promptMaxLength": 64, - "promptVersions": 4 - } -] -``` - -### Parameters - -| Parameter | Type | Description | -|-----------------|---------------|-----------------------------------------------------------------------------------------------------| -| taskType | string | Must be set to "promptEnhance" for this operation. | -| taskUUID | UUIDv4 string | Unique identifier for the task, used to match async responses. | -| prompt | string | The original prompt you want to enhance. | -| promptMaxLength | integer | Maximum length of the enhanced prompt. Value between 4 and 400. | -| promptVersions | integer | Number of enhanced prompt versions to generate. Value between 1 and 5. | -| includeCost | boolean | Optional. If set to true, the response will include the cost of the operation. | - -## Response - -Results will be delivered in the following format: - -```json -{ - "data": [ - { - "taskType": "promptEnhance", - "taskUUID": "9da1a4ad-c3de-4470-905d-5be5c042f98a", - "text": "dog, ilya kuvshinov, gaston bussiere, craig mullins, simon bisley, arthur rackham", - "cost": 0 - }, - { - "taskType": "promptEnhance", - "taskUUID": "9da1a4ad-c3de-4470-905d-5be5c042f98a", - "text": "dog, ilya kuvshinov, artgerm", - "cost": 0 - }, - { - "taskType": "promptEnhance", - "taskUUID": "9da1a4ad-c3de-4470-905d-5be5c042f98a", - "text": "dog, ilya kuvshinov, gaston bussiere, craig mullins, simon bisley", - "cost": 0 - }, - { - "taskType": "promptEnhance", - "taskUUID": "9da1a4ad-c3de-4470-905d-5be5c042f98a", - "text": "dog, ilya kuvshinov, artgerm, krenz cushart, greg rutkowski, pixiv. cinematic dramatic atmosphere, sharp focus, volumetric lighting, cinematic lighting, studio quality", - "cost": 0 - } - ] -} -``` - -The response contains an array of objects in the "data" field. The number of objects corresponds to the `promptVersions` requested. Each object represents an enhanced prompt suggestion. - -### Response Parameters - -| Parameter | Type | Description | -|-----------|---------------|-----------------------------------------------------------------------| -| taskType | string | The type of task, in this case "promptEnhance". | -| taskUUID | UUIDv4 string | The unique identifier matching the original request. | -| text | string | The enhanced prompt text. | -| cost | number | The cost of the operation (included if `includeCost` was set to true).| diff --git a/examples/01_basic_image_generation.py b/examples/01_basic_image_generation.py new file mode 100644 index 0000000..216b460 --- /dev/null +++ b/examples/01_basic_image_generation.py @@ -0,0 +1,130 @@ +""" +Basic Image Generation Example + +This example demonstrates how to generate images using the Runware SDK +with various configuration options and proper error handling. +""" + +import asyncio +import os +from typing import List + +from runware import IImage, IImageInference, Runware, RunwareError + + +async def basic_image_generation(): + """Generate a simple image with basic parameters.""" + + # Initialize client with API key from environment + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + # Connect to the Runware service + await runware.connect() + + # Create image generation request + request = IImageInference( + positivePrompt="A majestic mountain landscape at sunset with vibrant colors", + model="civitai:4384@128713", + numberResults=1, + height=1024, + width=1024, + negativePrompt="blurry, low quality, distorted", + steps=30, + CFGScale=7.5, + seed=42, + ) + + # Generate images + images: List[IImage] = await runware.imageInference(requestImage=request) + + # Process results + for i, image in enumerate(images): + print(f"Generated image {i + 1}:") + print(f" URL: {image.imageURL}") + print(f" UUID: {image.imageUUID}") + if image.seed: + print(f" Seed: {image.seed}") + if image.cost: + print(f" Cost: ${image.cost}") + + except RunwareError as e: + print(f"Runware API Error: {e}") + if hasattr(e, "code"): + print(f"Error Code: {e.code}") + except Exception as e: + print(f"Unexpected error: {e}") + finally: + # Always disconnect when done + await runware.disconnect() + + +async def batch_image_generation(): + """Generate multiple images with different configurations.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Generate multiple images with different aspect ratios + requests = [ + IImageInference( + positivePrompt="Portrait of a wise old wizard with a long beard", + model="civitai:4384@128713", + numberResults=1, + height=1024, + width=768, # Portrait orientation + negativePrompt="young, modern clothing", + ), + IImageInference( + positivePrompt="Futuristic cityscape with flying cars and neon lights", + model="civitai:4384@128713", + numberResults=1, + height=768, + width=1024, # Landscape orientation + negativePrompt="old, vintage, medieval", + ), + IImageInference( + positivePrompt="Cute cartoon animal in a magical forest", + model="civitai:4384@128713", + numberResults=2, + height=1024, + width=1024, # Square format + negativePrompt="realistic, dark, scary", + ), + ] + + # Process all requests concurrently + tasks = [runware.imageInference(requestImage=req) for req in requests] + results = await asyncio.gather(*tasks, return_exceptions=True) + + # Process results + for i, result in enumerate(results): + if isinstance(result, Exception): + print(f"Request {i + 1} failed: {result}") + else: + images: List[IImage] = result + print(f"Request {i + 1} completed with {len(images)} images:") + for image in images: + print(f" Image URL: {image.imageURL}") + + except RunwareError as e: + print(f"Runware API Error: {e}") + except Exception as e: + print(f"Unexpected error: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all basic image generation examples.""" + print("=== Basic Image Generation ===") + await basic_image_generation() + + print("\n=== Batch Image Generation ===") + await batch_image_generation() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/02_advanced_image_generation.py b/examples/02_advanced_image_generation.py new file mode 100644 index 0000000..068607c --- /dev/null +++ b/examples/02_advanced_image_generation.py @@ -0,0 +1,226 @@ +""" +Advanced Image Generation Example + +This example demonstrates advanced image generation features including: +- LoRA models for style enhancement +- ControlNet for guided generation +- Refiner models for quality improvement +- Accelerator options for faster inference +- Progress callbacks for real-time updates +""" + +import asyncio +import os +from typing import List + +from runware import ( + EControlMode, + IAcceleratorOptions, + IControlNetGeneral, + IImage, + IImageInference, + ILora, + IRefiner, + ProgressUpdate, + Runware, + RunwareError, +) +import time + + +def progress_callback(progress: ProgressUpdate): + """Handle progress updates during image generation.""" + print(f"Operation {progress.operation_id}: {progress.progress:.1%} complete") + if progress.message: + print(f" Status: {progress.message}") + if progress.partial_results: + print(f" Received {len(progress.partial_results)} partial results") + + +async def lora_enhanced_generation(): + """Generate images using LoRA models for enhanced styling.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Define LoRA models for style enhancement + lora_models = [ + ILora(model="civitai:58390@62833", weight=0.8), + ILora(model="civitai:42903@232848", weight=0.6), + ] + + request = IImageInference( + positivePrompt="masterpiece, best quality, 1girl, elegant dress, garden background, soft lighting", + model="civitai:36520@76907", + lora=lora_models, + numberResults=3, + height=1024, + width=768, + negativePrompt="worst quality, blurry, nsfw", + steps=35, + CFGScale=8.0, + outputFormat="PNG", + includeCost=True, + ) + + images: List[IImage] = await runware.imageInference( + requestImage=request, progress_callback=progress_callback + ) + + print("LoRA-enhanced images generated:") + for i, image in enumerate(images): + print(f" Image {i + 1}: {image.imageURL}") + if image.cost: + print(f" Cost: ${image.cost}") + + except RunwareError as e: + print(f"Error in LoRA generation: {e}") + finally: + await runware.disconnect() + + +async def controlnet_guided_generation(): + """Generate images using ControlNet for precise control.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Define ControlNet configuration + controlnet = IControlNetGeneral( + model="civitai:38784@44716", + guideImage="https://huggingface.co/datasets/mishig/sample_images/resolve/main/canny-edge.jpg", + weight=0.8, + startStep=0, + endStep=15, + controlMode=EControlMode.BALANCED, + ) + + request = IImageInference( + positivePrompt="beautiful anime character, detailed eyes, colorful hair", + model="civitai:4384@128713", + controlNet=[controlnet], + numberResults=1, + height=768, + width=768, + steps=30, + CFGScale=7.0, + seed=12345, + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("ControlNet-guided images:") + for image in images: + print(f" URL: {image.imageURL}") + print(f" Generated with seed: {image.seed}") + + except RunwareError as e: + print(f"Error in ControlNet generation: {e}") + finally: + await runware.disconnect() + + +async def refiner_enhanced_generation(): + """Generate images with refiner model for quality enhancement.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure refiner model + refiner = IRefiner( + model="civitai:101055@128080", # SDXL refiner + startStep=25, # Start refining after 25 steps + ) + + request = IImageInference( + positivePrompt="hyperrealistic portrait of a astronaut in space, detailed helmet reflection", + model="civitai:101055@128078", + refiner=refiner, + numberResults=1, + height=1024, + width=1024, + steps=40, # More steps for better quality with refiner + CFGScale=7.5, + outputFormat="PNG", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Refiner-enhanced images:") + for image in images: + print(f" High-quality URL: {image.imageURL}") + + except RunwareError as e: + print(f"Error in refiner generation: {e}") + finally: + await runware.disconnect() + + +async def fast_generation_with_accelerators(): + """Generate images quickly using accelerator options.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure accelerator options for speed + accelerator_options = IAcceleratorOptions( + teaCache=True, + teaCacheDistance=0.4, + cacheStartStep=5, + cacheStopStep=25, + ) + + request = IImageInference( + positivePrompt="vibrant fantasy landscape with magical creatures", + model="runware:100@1", # Flux model that supports acceleration + acceleratorOptions=accelerator_options, + numberResults=1, + height=1024, + width=1024, + steps=28, + CFGScale=3.5, + ) + + start_time = time.time() + + images: List[IImage] = await runware.imageInference( + requestImage=request, progress_callback=progress_callback + ) + + generation_time = time.time() - start_time + + print(f"Fast generation completed in {generation_time:.2f} seconds:") + for image in images: + print(f" URL: {image.imageURL}") + + except RunwareError as e: + print(f"Error in fast generation: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all advanced image generation examples.""" + print("=== LoRA Enhanced Generation ===") + await lora_enhanced_generation() + + print("\n=== ControlNet Guided Generation ===") + await controlnet_guided_generation() + + print("\n=== Refiner Enhanced Generation ===") + await refiner_enhanced_generation() + + print("\n=== Fast Generation with Accelerators ===") + await fast_generation_with_accelerators() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/03_image_editing.py b/examples/03_image_editing.py new file mode 100644 index 0000000..775edbe --- /dev/null +++ b/examples/03_image_editing.py @@ -0,0 +1,259 @@ +""" +Image Editing Operations Example + +This example demonstrates various image editing capabilities: +- Image captioning for description generation +- Background removal with custom settings +- Image upscaling for resolution enhancement +- File upload and processing workflows +""" + +import asyncio +import os +from typing import List + +from runware import ( + IBackgroundRemovalSettings, + IImage, + IImageBackgroundRemoval, + IImageCaption, + IImageToText, + IImageUpscale, + Runware, + RunwareError, +) + + +async def image_captioning(): + """Generate descriptive captions for images.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Test image URL + image_url = "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg" + + # Create captioning request + caption_request = IImageCaption(inputImage=image_url, includeCost=True) + + # Generate caption + result: IImageToText = await runware.imageCaption( + requestImageToText=caption_request + ) + + print("Image Caption Analysis:") + print(f" Image: {image_url}") + print(f" Description: {result.text}") + if result.cost: + print(f" Processing cost: ${result.cost}") + + except RunwareError as e: + print(f"Error in image captioning: {e}") + finally: + await runware.disconnect() + + +async def background_removal_basic(): + """Remove background from image using default settings.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Image with clear subject for background removal + image_url = "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/headphones.jpeg" + + # Basic background removal request + removal_request = IImageBackgroundRemoval( + inputImage=image_url, outputType="URL", outputFormat="PNG", includeCost=True + ) + + # Process image + processed_images: List[IImage] = await runware.imageBackgroundRemoval( + removeImageBackgroundPayload=removal_request + ) + + print("Basic Background Removal:") + for i, image in enumerate(processed_images): + print(f" Result {i + 1}: {image.imageURL}") + if image.cost: + print(f" Cost: ${image.cost}") + + except RunwareError as e: + print(f"Error in background removal: {e}") + finally: + await runware.disconnect() + + +async def background_removal_advanced(): + """Remove background with advanced settings and custom model.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + image_url = "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/headphones.jpeg" + + # Advanced background removal settings + advanced_settings = IBackgroundRemovalSettings( + rgba=[255, 255, 255, 0], # White transparent background + alphaMatting=True, # Better edge quality + postProcessMask=True, # Refine mask edges + returnOnlyMask=False, # Return processed image, not just mask + alphaMattingErodeSize=10, + alphaMattingForegroundThreshold=240, + alphaMattingBackgroundThreshold=10, + ) + + # Request with custom settings + removal_request = IImageBackgroundRemoval( + inputImage=image_url, + settings=advanced_settings, + outputType="URL", + outputFormat="PNG", + outputQuality=95, + includeCost=True, + ) + + processed_images: List[IImage] = await runware.imageBackgroundRemoval( + removeImageBackgroundPayload=removal_request + ) + + print("Advanced Background Removal:") + for image in processed_images: + print(f" High-quality result: {image.imageURL}") + if image.cost: + print(f" Cost: ${image.cost}") + + except RunwareError as e: + print(f"Error in advanced background removal: {e}") + finally: + await runware.disconnect() + + +async def image_upscaling(): + """Enhance image resolution using upscaling.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Lower resolution image for upscaling demonstration + image_url = "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg" + + # Test different upscale factors + upscale_factors = [2, 4] + + for factor in upscale_factors: + upscale_request = IImageUpscale( + inputImage=image_url, + upscaleFactor=factor, + outputType="URL", + outputFormat="PNG", + includeCost=True, + ) + + upscaled_images: List[IImage] = await runware.imageUpscale( + upscaleGanPayload=upscale_request + ) + + print(f"Upscaling {factor}x:") + for image in upscaled_images: + print(f" Enhanced image: {image.imageURL}") + if image.cost: + print(f" Processing cost: ${image.cost}") + + except RunwareError as e: + print(f"Error in image upscaling: {e}") + finally: + await runware.disconnect() + + +async def complete_editing_workflow(): + """Demonstrate a complete image editing workflow.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + original_image = "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/headphones.jpeg" + + print("Starting complete editing workflow...") + + # Step 1: Generate caption for the original image + print("Step 1: Analyzing image content...") + caption_request = IImageCaption(inputImage=original_image) + caption_result: IImageToText = await runware.imageCaption( + requestImageToText=caption_request + ) + print(f" Original image description: {caption_result.text}") + + # Step 2: Remove background + print("Step 2: Removing background...") + bg_removal_request = IImageBackgroundRemoval( + inputImage=original_image, outputType="URL", outputFormat="PNG" + ) + bg_removed_images: List[IImage] = await runware.imageBackgroundRemoval( + removeImageBackgroundPayload=bg_removal_request + ) + background_removed_url = bg_removed_images[0].imageURL + print(f" Background removed: {background_removed_url}") + + # Step 3: Upscale the result + print("Step 3: Enhancing resolution...") + upscale_request = IImageUpscale( + inputImage=background_removed_url, + upscaleFactor=2, + outputType="URL", + outputFormat="PNG", + ) + upscaled_images: List[IImage] = await runware.imageUpscale( + upscaleGanPayload=upscale_request + ) + final_image_url = upscaled_images[0].imageURL + print(f" Final enhanced image: {final_image_url}") + + # Step 4: Generate caption for final result + print("Step 4: Analyzing final result...") + final_caption_request = IImageCaption(inputImage=final_image_url) + final_caption: IImageToText = await runware.imageCaption( + requestImageToText=final_caption_request + ) + print(f" Final image description: {final_caption.text}") + + print("\nWorkflow completed successfully!") + print(f"Original: {original_image}") + print(f"Final: {final_image_url}") + + except RunwareError as e: + print(f"Error in editing workflow: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all image editing examples.""" + print("=== Image Captioning ===") + await image_captioning() + + print("\n=== Basic Background Removal ===") + await background_removal_basic() + + print("\n=== Advanced Background Removal ===") + await background_removal_advanced() + + print("\n=== Image Upscaling ===") + await image_upscaling() + + print("\n=== Complete Editing Workflow ===") + await complete_editing_workflow() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/04_video_generation.py b/examples/04_video_generation.py new file mode 100644 index 0000000..e0e6a2b --- /dev/null +++ b/examples/04_video_generation.py @@ -0,0 +1,315 @@ +""" +Video Generation Example + +This example demonstrates video generation using different AI providers: +- Google Veo for high-quality cinematic videos +- Kling AI for creative video generation +- Minimax for text-to-video and image-to-video +- Bytedance for professional video content +- Pixverse for stylized video generation +- Vidu for anime-style videos +""" + +import asyncio +import os +from typing import List + +from runware import ( + IBytedanceProviderSettings, + IFrameImage, + IGoogleProviderSettings, + IMinimaxProviderSettings, + IPixverseProviderSettings, + IVideo, + IVideoInference, + IViduProviderSettings, + Runware, + RunwareError, +) + + +async def google_veo_generation(): + """Generate videos using Google's Veo model with advanced features.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Text-to-video generation + print("Generating text-to-video with Google Veo...") + text_to_video_request = IVideoInference( + positivePrompt="A majestic eagle soaring through mountain peaks at golden hour, cinematic cinematography", + model="google:3@0", + width=1280, + height=720, + duration=8, + numberResults=1, + seed=42, + includeCost=True, + providerSettings=IGoogleProviderSettings( + generateAudio=True, enhancePrompt=True + ), + ) + + videos: List[IVideo] = await runware.videoInference( + requestVideo=text_to_video_request + ) + + for video in videos: + print(f" Generated video: {video.videoURL}") + if video.cost: + print(f" Cost: ${video.cost}") + print(f" Seed used: {video.seed}") + + # Image-to-video generation + print("\nGenerating image-to-video with Google Veo...") + image_to_video_request = IVideoInference( + positivePrompt="The galaxy slowly rotates with sparkling stars", + model="google:2@0", + width=1280, + height=720, + duration=5, + numberResults=1, + frameImages=[ + IFrameImage( + inputImage="https://github.com/adilentiq/test-images/blob/main/common/image_15_mb.jpg?raw=true" + ) + ], + includeCost=True, + ) + + videos = await runware.videoInference(requestVideo=image_to_video_request) + + for video in videos: + print(f" I2V result: {video.videoURL}") + if video.cost: + print(f" Cost: ${video.cost}") + + except RunwareError as e: + print(f"Error with Google Veo: {e}") + finally: + await runware.disconnect() + + +async def kling_ai_generation(): + """Generate videos using Kling AI""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + request = IVideoInference( + positivePrompt="A beautiful woman portrait", + model="klingai:1@2", + width=1280, + height=720, + duration=5, + numberResults=1, + CFGScale=1.0, + includeCost=True, + frameImages=[ + IFrameImage( + inputImage="https://huggingface.co/ntc-ai/SDXL-LoRA-slider.Studio-Ghibli-style/resolve/main/images/Studio%20Ghibli%20style_17_-1.5.png", + frame="first", + ) + ], + ) + + videos: List[IVideo] = await runware.videoInference(requestVideo=request) + + print("Kling AI video:") + for video in videos: + print(f" Video URL: {video.videoURL}") + print(f" Status: {video.status}") + if video.cost: + print(f" Cost: ${video.cost}") + + except RunwareError as e: + print(f"Error with Kling AI: {e}") + finally: + await runware.disconnect() + + +async def minimax_generation(): + """Generate videos using Minimax with prompt optimization.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + request = IVideoInference( + positivePrompt="A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage", + model="minimax:1@1", + width=1366, + height=768, + duration=6, + numberResults=1, + seed=12345, + includeCost=True, + providerSettings=IMinimaxProviderSettings( + promptOptimizer=True # Enhance prompt automatically + ), + ) + + videos: List[IVideo] = await runware.videoInference(requestVideo=request) + + print("Minimax video generation:") + for video in videos: + print(f" Video URL: {video.videoURL}") + if video.cost: + print(f" Cost: ${video.cost}") + print(f" Generated with seed: {video.seed}") + + except RunwareError as e: + print(f"Error with Minimax: {e}") + finally: + await runware.disconnect() + + +async def bytedance_generation(): + """Generate videos using Bytedance with professional settings.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + request = IVideoInference( + positivePrompt="A couple in formal evening attire walking in heavy rain with an umbrella, cinematic lighting", + model="bytedance:1@1", + height=1504, + width=640, + duration=5, + numberResults=1, + seed=98765, + includeCost=True, + providerSettings=IBytedanceProviderSettings( + cameraFixed=False, # Allow camera movement + ), + ) + + videos: List[IVideo] = await runware.videoInference(requestVideo=request) + + print("Seedance 1.0 Lite video:") + for video in videos: + print(f" Video URL: {video.videoURL}") + if video.cost: + print(f" Cost: ${video.cost}") + + except RunwareError as e: + print(f"Error with Bytedance: {e}") + finally: + await runware.disconnect() + + +async def pixverse_stylized_generation(): + """Generate stylized videos using Pixverse with effects and styles.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + request = IVideoInference( + positivePrompt="A magical transformation sequence with sparkles and light effects", + negativePrompt="blurry, low quality", + model="pixverse:1@2", + width=1280, + height=720, + duration=5, + fps=24, + numberResults=1, + seed=55555, + includeCost=True, + frameImages=[ + IFrameImage( + inputImage="https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/man_beard.jpg" + ) + ], + providerSettings=IPixverseProviderSettings( + effect="boom drop", # Special effect + style="anime", # Anime art style + motionMode="normal", # Motion intensity + ), + ) + + videos: List[IVideo] = await runware.videoInference(requestVideo=request) + + print("Pixverse stylized video:") + for video in videos: + print(f" Anime-style video: {video.videoURL}") + print(f" Status: {video.status}") + if video.cost: + print(f" Cost: ${video.cost}") + + except RunwareError as e: + print(f"Error with Pixverse: {e}") + finally: + await runware.disconnect() + + +async def vidu_anime_generation(): + """Generate anime-style videos using Vidu.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + request = IVideoInference( + positivePrompt="A red fox moves stealthily through autumn woods, hunting for prey", + model="vidu:1@5", + width=1920, + height=1080, + duration=4, + numberResults=1, + seed=77777, + includeCost=True, + providerSettings=IViduProviderSettings( + style="anime", # Anime art style + movementAmplitude="auto", # Automatic movement detection + bgm=True, # Add background music + ), + ) + + videos: List[IVideo] = await runware.videoInference(requestVideo=request) + + print("Vidu anime-style video:") + for video in videos: + print(f" Anime video with BGM: {video.videoURL}") + print(f" Status: {video.status}") + if video.cost: + print(f" Cost: ${video.cost}") + + except RunwareError as e: + print(f"Error with Vidu: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run video generation examples for different providers.""" + print("=== Google Veo Video Generation ===") + await google_veo_generation() + + print("\n=== Kling AI ===") + await kling_ai_generation() + + print("\n=== Minimax with Prompt Optimization ===") + await minimax_generation() + + print("\n=== Bytedance Video ===") + await bytedance_generation() + + print("\n=== Pixverse Stylized Video ===") + await pixverse_stylized_generation() + # + print("\n=== Vidu Anime-Style Video ===") + await vidu_anime_generation() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/05_ace_plus_plus.py b/examples/05_ace_plus_plus.py new file mode 100644 index 0000000..62bb2f9 --- /dev/null +++ b/examples/05_ace_plus_plus.py @@ -0,0 +1,206 @@ +""" +ACE++ Advanced Character Editing Example + +ACE++ (Advanced Character Edit) enables character-consistent image generation +and editing while preserving identity. This example demonstrates: +- Portrait editing with identity preservation +- Subject integration and replacement +- Local editing with masks +- Logo and object placement +- Movie poster style editing +""" + +import asyncio +import os +from typing import List + +from runware import IAcePlusPlus, IImage, IImageInference, Runware, RunwareError + + +async def logo_placement(): + """Place logos and branding elements on products using masks.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Assets for logo placement + reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_ref.png" + mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_1_m.png" + init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_1_edit.png" + + request = IImageInference( + positivePrompt="The logo is printed on the headphones with high quality and proper lighting.", + model="runware:102@1", + height=1024, + width=1024, + numberResults=1, + steps=28, + CFGScale=50.0, + referenceImages=[reference_image], # Logo reference + acePlusPlus=IAcePlusPlus( + inputImages=[init_image], # Product image + inputMasks=[mask_image], # Mask for logo placement area + repaintingScale=1.0, # Full prompt adherence for placement + taskType="subject", # Subject placement task + ), + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Logo Placement:") + for image in images: + print(f" Product with logo: {image.imageURL}") + print(f" Logo professionally integrated using mask guidance") + + except RunwareError as e: + print(f"Error in logo placement: {e}") + finally: + await runware.disconnect() + + +async def local_region_editing(): + """Edit specific regions of images using local editing masks.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Assets for local editing + mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/local/local_1_m.webp" + init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/local/local_1.webp" + + request = IImageInference( + positivePrompt='By referencing the mask, restore a partial image from the doodle that aligns with the textual explanation: "1 white old owl".', + model="runware:102@1", + height=1024, + width=1024, + numberResults=1, + steps=28, + CFGScale=50.0, + acePlusPlus=IAcePlusPlus( + inputImages=[init_image], # Image to edit + inputMasks=[mask_image], # Local region mask + repaintingScale=0.5, # Balanced editing + taskType="local_editing", # Local editing mode + ), + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Local Region Editing:") + for image in images: + print(f" Locally edited image: {image.imageURL}") + print(f" Specific region refined while preserving surrounding areas") + + except RunwareError as e: + print(f"Error in local editing: {e}") + finally: + await runware.disconnect() + + +async def movie_poster_editing(): + """Create movie poster style edits with character replacement.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Movie poster editing assets + reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_ref.png" + mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_1_m.png" + init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_1_edit.png" + + request = IImageInference( + positivePrompt="The man is facing the camera and is smiling with confidence and charisma, perfect for a movie poster.", + model="runware:102@1", + height=768, + width=1024, + numberResults=1, + steps=28, + CFGScale=50.0, + referenceImages=[reference_image], # Character reference + acePlusPlus=IAcePlusPlus( + inputImages=[init_image], # Poster template + inputMasks=[mask_image], # Character replacement area + repaintingScale=1.0, # Full creative freedom in masked area + taskType="portrait", # Portrait-aware processing + ), + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Movie Poster Editing:") + for image in images: + print(f" Movie poster: {image.imageURL}") + print(f" Character seamlessly integrated into poster design") + + except RunwareError as e: + print(f"Error in movie poster editing: {e}") + finally: + await runware.disconnect() + + +async def photo_editing_workflow(): + """Demonstrate a complex photo editing workflow using ACE++.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Professional photo editing assets + init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_1_edit.png" + mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_1_m.png" + reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_ref.png" + + request = IImageInference( + positivePrompt="The item is put on the ground with proper lighting and realistic shadows, professional product photography.", + model="runware:102@1", + height=1024, + width=1024, + numberResults=1, + steps=28, + CFGScale=50.0, + referenceImages=[reference_image], # Product reference + acePlusPlus=IAcePlusPlus( + inputImages=[init_image], # Scene to edit + inputMasks=[mask_image], # Product placement area + repaintingScale=1.0, # Full control over placement + taskType="subject", # Subject placement + ), + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Professional Photo Editing:") + for image in images: + print(f" Edited photo: {image.imageURL}") + print(f" Product professionally integrated with realistic lighting") + + except RunwareError as e: + print(f"Error in photo editing: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all ACE++ advanced editing examples.""" + print("\n=== Logo Placement ===") + await logo_placement() + + print("\n=== Local Region Editing ===") + await local_region_editing() + + print("\n=== Movie Poster Editing ===") + await movie_poster_editing() + + print("\n=== Professional Photo Editing ===") + await photo_editing_workflow() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/06_photo_maker.py b/examples/06_photo_maker.py new file mode 100644 index 0000000..559a8d4 --- /dev/null +++ b/examples/06_photo_maker.py @@ -0,0 +1,344 @@ +""" +PhotoMaker Example + +PhotoMaker enables identity-consistent photo generation by combining +multiple input photos to create new images while preserving identity. +This example demonstrates various PhotoMaker use cases and styles. +""" + +import asyncio +import os +from typing import List + +from runware import IImage, IPhotoMaker, Runware, RunwareError, UploadImageType + + +async def basic_photo_maker(): + """Generate photos using PhotoMaker with multiple input images.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Sample input images for identity reference + input_images = [ + "https://im.runware.ai/image/ws/0.5/ii/74723926-22f6-417c-befb-f2058fc88c13.webp", + "https://im.runware.ai/image/ws/0.5/ii/64acee31-100d-4aa1-a47e-6f8b432e7188.webp", + "https://im.runware.ai/image/ws/0.5/ii/1b39b0e0-6bf7-4c9a-8134-c0251b5ede01.webp", + "https://im.runware.ai/image/ws/0.5/ii/f4b4cec3-66d9-4c02-97c5-506b8813182a.webp", + ] + + request = IPhotoMaker( + model="civitai:139562@344487", # PhotoMaker compatible model + positivePrompt="img of a beautiful lady in a peaceful forest setting, natural lighting", + steps=35, + numberResults=2, + height=768, + width=512, + style="No style", # Natural style + strength=40, + outputFormat="WEBP", + includeCost=True, + inputImages=input_images, + ) + + photos: List[IImage] = await runware.photoMaker(requestPhotoMaker=request) + + print("Basic PhotoMaker Results:") + for i, photo in enumerate(photos): + print(f" Photo {i + 1}: {photo.imageURL}") + if photo.cost: + print(f" Cost: ${photo.cost}") + if photo.seed: + print(f" Seed: {photo.seed}") + + except RunwareError as e: + print(f"Error in basic PhotoMaker: {e}") + finally: + await runware.disconnect() + + +async def styled_photo_generation(): + """Generate photos with different artistic styles.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + input_images = [ + "https://im.runware.ai/image/ws/0.5/ii/74723926-22f6-417c-befb-f2058fc88c13.webp", + "https://im.runware.ai/image/ws/0.5/ii/64acee31-100d-4aa1-a47e-6f8b432e7188.webp", + ] + + # Test different artistic styles + styles = [ + ( + "Cinematic", + "img of a person in a dramatic movie scene with cinematic lighting", + ), + ( + "Digital Art", + "img of a person as a digital art character with vibrant colors", + ), + ( + "Fantasy art", + "img of a person as a fantasy character in a magical realm", + ), + ("Comic book", "img of a person in comic book art style with bold lines"), + ] + + for style_name, prompt in styles: + print(f"Generating {style_name} style...") + + request = IPhotoMaker( + model="civitai:139562@344487", + positivePrompt=prompt, + steps=30, + numberResults=1, + height=1024, + width=768, + style=style_name, + strength=50, + outputFormat="PNG", + inputImages=input_images, + ) + + photos: List[IImage] = await runware.photoMaker(requestPhotoMaker=request) + + for photo in photos: + print(f" {style_name} result: {photo.imageURL}") + + except RunwareError as e: + print(f"Error in styled photo generation: {e}") + finally: + await runware.disconnect() + + +async def professional_portraits(): + """Generate professional portrait photos with various settings.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + input_images = [ + "https://im.runware.ai/image/ws/0.5/ii/74723926-22f6-417c-befb-f2058fc88c13.webp", + "https://im.runware.ai/image/ws/0.5/ii/64acee31-100d-4aa1-a47e-6f8b432e7188.webp", + "https://im.runware.ai/image/ws/0.5/ii/1b39b0e0-6bf7-4c9a-8134-c0251b5ede01.webp", + ] + + # Professional portrait scenarios + scenarios = [ + { + "name": "Business Portrait", + "prompt": ( + "img of a professional person in business attire, office background, confident expression" + ), + "strength": 30, + }, + { + "name": "Casual Portrait", + "prompt": ( + "img of a person in casual clothing, natural outdoor setting, relaxed smile" + ), + "strength": 35, + }, + { + "name": "Artistic Portrait", + "prompt": ( + "img of a person with dramatic lighting, artistic composition, professional photography" + ), + "strength": 45, + }, + ] + + for scenario in scenarios: + print(f"Creating {scenario['name']}...") + + request = IPhotoMaker( + model="civitai:139562@344487", + positivePrompt=scenario["prompt"], + steps=40, # Higher steps for quality + numberResults=1, + height=1024, + width=768, + style="Photographic", # Realistic style + strength=scenario["strength"], + outputFormat="PNG", + includeCost=True, + inputImages=input_images, + ) + + photos: List[IImage] = await runware.photoMaker(requestPhotoMaker=request) + + for photo in photos: + print(f" {scenario['name']}: {photo.imageURL}") + if photo.cost: + print(f" Processing cost: ${photo.cost}") + + except RunwareError as e: + print(f"Error in professional portraits: {e}") + finally: + await runware.disconnect() + + +async def creative_photo_scenarios(): + """Generate creative photo scenarios with thematic elements.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + input_images = [ + "https://im.runware.ai/image/ws/0.5/ii/74723926-22f6-417c-befb-f2058fc88c13.webp", + "https://im.runware.ai/image/ws/0.5/ii/64acee31-100d-4aa1-a47e-6f8b432e7188.webp", + ] + + # Creative scenarios with specific themes + creative_themes = [ + { + "theme": "Vintage Portrait", + "prompt": ( + "img of a person in vintage 1920s clothing, sepia tones, classic photography style" + ), + "style": "Enhance", + "dimensions": (768, 1024), + }, + { + "theme": "Superhero Style", + "prompt": ( + "img of a person as a superhero character, dynamic pose, comic book style" + ), + "style": "Comic book", + "dimensions": (512, 768), + }, + { + "theme": "Fantasy Character", + "prompt": ( + "img of a person as an elegant elf character in a mystical forest" + ), + "style": "Fantasy art", + "dimensions": (768, 1024), + }, + { + "theme": "Futuristic Portrait", + "prompt": ( + "img of a person in futuristic sci-fi setting with neon lighting" + ), + "style": "Digital Art", + "dimensions": (1024, 768), + }, + ] + + for theme_config in creative_themes: + print(f"Creating {theme_config['theme']}...") + + width, height = theme_config["dimensions"] + + request = IPhotoMaker( + model="civitai:139562@344487", + positivePrompt=theme_config["prompt"], + steps=35, + numberResults=1, + height=height, + width=width, + style=theme_config["style"], + strength=50, # Higher strength for creative themes + outputFormat="WEBP", + inputImages=input_images, + ) + + photos: List[IImage] = await runware.photoMaker(requestPhotoMaker=request) + + for photo in photos: + print(f" {theme_config['theme']}: {photo.imageURL}") + print(f" Style: {theme_config['style']}") + print(f" Dimensions: {width}x{height}") + + except RunwareError as e: + print(f"Error in creative scenarios: {e}") + finally: + await runware.disconnect() + + +async def upload_and_generate(): + """Upload custom images and use them with PhotoMaker.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Example of uploading images (URLs in this case, but could be local files) + image_urls = [ + "https://im.runware.ai/image/ws/0.5/ii/74723926-22f6-417c-befb-f2058fc88c13.webp", + "https://im.runware.ai/image/ws/0.5/ii/64acee31-100d-4aa1-a47e-6f8b432e7188.webp", + ] + + # Upload images and get UUIDs + uploaded_images = [] + for i, url in enumerate(image_urls): + print(f"Uploading image {i + 1}...") + uploaded: UploadImageType = await runware.uploadImage(url) + if uploaded and uploaded.imageUUID: + uploaded_images.append(uploaded.imageUUID) + print(f" Uploaded successfully: {uploaded.imageUUID}") + else: + print(f" Failed to upload image {i + 1}") + + if len(uploaded_images) >= 2: + # Use uploaded images for PhotoMaker + request = IPhotoMaker( + model="civitai:139562@344487", + positivePrompt="img of a person in a beautiful garden setting, golden hour lighting, professional photography", + steps=32, + numberResults=1, + height=1024, + width=768, + style="Photographic", + strength=40, + outputFormat="PNG", + includeCost=True, + inputImages=uploaded_images, # Use uploaded UUIDs + ) + + photos: List[IImage] = await runware.photoMaker(requestPhotoMaker=request) + + print("PhotoMaker with uploaded images:") + for photo in photos: + print(f" Generated photo: {photo.imageURL}") + if photo.cost: + print(f" Cost: ${photo.cost}") + else: + print("Not enough images uploaded successfully") + + except RunwareError as e: + print(f"Error with upload and generate: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all PhotoMaker examples.""" + print("=== Basic PhotoMaker ===") + await basic_photo_maker() + + print("\n=== Styled Photo Generation ===") + await styled_photo_generation() + + print("\n=== Professional Portraits ===") + await professional_portraits() + + print("\n=== Creative Photo Scenarios ===") + await creative_photo_scenarios() + + print("\n=== Upload and Generate ===") + await upload_and_generate() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/07_prompt_enhancement.py b/examples/07_prompt_enhancement.py new file mode 100644 index 0000000..7812f5a --- /dev/null +++ b/examples/07_prompt_enhancement.py @@ -0,0 +1,323 @@ +""" +Prompt Enhancement Example + +This example demonstrates how to use Runware's prompt enhancement feature +to automatically improve and expand prompts for better image generation results. +The enhanced prompts include more descriptive language, artistic terms, and +technical specifications that lead to higher quality outputs. +""" + +import asyncio +import os +from typing import List + +from runware import ( + IEnhancedPrompt, + IImage, + IImageInference, + IPromptEnhance, + Runware, + RunwareError, +) + + +async def basic_prompt_enhancement(): + """Enhance simple prompts to create more detailed descriptions.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Simple prompts to enhance + simple_prompts = [ + "a cat", + "sunset over mountains", + "beautiful woman", + "futuristic city", + ] + + for original_prompt in simple_prompts: + print(f"\nOriginal prompt: '{original_prompt}'") + + enhancer = IPromptEnhance( + prompt=original_prompt, + promptVersions=3, # Generate 3 different enhanced versions + promptMaxLength=200, # Maximum length for enhanced prompts + includeCost=True, + ) + + enhanced_prompts: List[IEnhancedPrompt] = await runware.promptEnhance( + promptEnhancer=enhancer + ) + + print("Enhanced versions:") + for i, enhanced in enumerate(enhanced_prompts, 1): + print(f" {i}. {enhanced.text}") + if enhanced.cost: + print(f" Cost: ${enhanced.cost}") + + except RunwareError as e: + print(f"Error in basic prompt enhancement: {e}") + finally: + await runware.disconnect() + + +async def artistic_prompt_enhancement(): + """Enhance prompts specifically for artistic and creative image generation.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Artistic prompts that can benefit from enhancement + artistic_prompts = [ + "abstract painting", + "portrait in renaissance style", + "cyberpunk street scene", + "magical forest", + ] + + for prompt in artistic_prompts: + print(f"\nArtistic prompt: '{prompt}'") + + enhancer = IPromptEnhance( + prompt=prompt, + promptVersions=2, + promptMaxLength=300, # Longer prompts for artistic detail + includeCost=True, + ) + + enhanced_prompts: List[IEnhancedPrompt] = await runware.promptEnhance( + promptEnhancer=enhancer + ) + + print("Enhanced artistic descriptions:") + for i, enhanced in enumerate(enhanced_prompts, 1): + print(f" Version {i}:") + print(f" {enhanced.text}") + if enhanced.cost: + print(f" Processing cost: ${enhanced.cost}") + + except RunwareError as e: + print(f"Error in artistic prompt enhancement: {e}") + finally: + await runware.disconnect() + + +async def photography_prompt_enhancement(): + """Enhance prompts for photorealistic image generation with technical terms.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Photography-focused prompts + photography_prompts = [ + "professional headshot", + "landscape photography", + "macro flower photo", + "street photography at night", + ] + + for prompt in photography_prompts: + print(f"\nPhotography prompt: '{prompt}'") + + enhancer = IPromptEnhance( + prompt=prompt, promptVersions=2, promptMaxLength=250, includeCost=True + ) + + enhanced_prompts: List[IEnhancedPrompt] = await runware.promptEnhance( + promptEnhancer=enhancer + ) + + print("Enhanced with photography terms:") + for i, enhanced in enumerate(enhanced_prompts, 1): + print(f" Version {i}: {enhanced.text}") + + except RunwareError as e: + print(f"Error in photography prompt enhancement: {e}") + finally: + await runware.disconnect() + + +async def compare_original_vs_enhanced(): + """Compare image generation results using original vs enhanced prompts.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + original_prompt = "a dragon in a castle" + print(f"Comparing results for: '{original_prompt}'") + + # First, enhance the prompt + enhancer = IPromptEnhance( + prompt=original_prompt, + promptVersions=1, # Just one enhanced version for comparison + promptMaxLength=180, + ) + + enhanced_prompts: List[IEnhancedPrompt] = await runware.promptEnhance( + promptEnhancer=enhancer + ) + + enhanced_prompt = enhanced_prompts[0].text + print(f"Enhanced to: '{enhanced_prompt}'") + + # Generate image with original prompt + print("\nGenerating with original prompt...") + original_request = IImageInference( + positivePrompt=original_prompt, + model="civitai:4384@128713", + numberResults=1, + height=768, + width=768, + seed=42, # Fixed seed for fair comparison + ) + + original_images: List[IImage] = await runware.imageInference( + requestImage=original_request + ) + + # Generate image with enhanced prompt + print("Generating with enhanced prompt...") + enhanced_request = IImageInference( + positivePrompt=enhanced_prompt, + model="civitai:4384@128713", + numberResults=1, + height=768, + width=768, + seed=42, # Same seed for comparison + ) + + enhanced_images: List[IImage] = await runware.imageInference( + requestImage=enhanced_request + ) + + # Display results + print("\nComparison Results:") + print(f"Original prompt result: {original_images[0].imageURL}") + print(f"Enhanced prompt result: {enhanced_images[0].imageURL}") + print( + "\nThe enhanced prompt should produce more detailed and visually appealing results!" + ) + + except RunwareError as e: + print(f"Error in comparison: {e}") + finally: + await runware.disconnect() + + +async def batch_prompt_enhancement(): + """Enhance multiple prompts efficiently in batch.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Batch of prompts to enhance + prompt_batch = [ + "cozy coffee shop", + "space exploration", + "underwater scene", + "medieval knight", + "tropical paradise", + ] + + print("Batch enhancing multiple prompts...") + + # Create enhancement tasks + enhancement_tasks = [] + for prompt in prompt_batch: + enhancer = IPromptEnhance( + prompt=prompt, promptVersions=1, promptMaxLength=150 + ) + task = runware.promptEnhance(promptEnhancer=enhancer) + enhancement_tasks.append((prompt, task)) + + # Execute all enhancements concurrently + results = await asyncio.gather( + *[task for _, task in enhancement_tasks], return_exceptions=True + ) + + # Process results + print("\nBatch Enhancement Results:") + for i, (original_prompt, result) in enumerate(zip(prompt_batch, results)): + print(f"\n{i + 1}. Original: '{original_prompt}'") + + if isinstance(result, Exception): + print(f" Error: {result}") + else: + enhanced_prompts: List[IEnhancedPrompt] = result + if enhanced_prompts: + print(f" Enhanced: '{enhanced_prompts[0].text}'") + + except RunwareError as e: + print(f"Error in batch enhancement: {e}") + finally: + await runware.disconnect() + + +async def length_variation_testing(): + """Test prompt enhancement with different maximum length settings.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + base_prompt = "magical wizard casting spells" + lengths = [50, 100, 200, 300] + + print(f"Testing different enhancement lengths for: '{base_prompt}'") + + for max_length in lengths: + print(f"\nMax length: {max_length} characters") + + enhancer = IPromptEnhance( + prompt=base_prompt, promptVersions=1, promptMaxLength=max_length + ) + + enhanced_prompts: List[IEnhancedPrompt] = await runware.promptEnhance( + promptEnhancer=enhancer + ) + + if enhanced_prompts: + enhanced_text = enhanced_prompts[0].text + actual_length = len(enhanced_text) + print(f" Result ({actual_length} chars): {enhanced_text}") + + except RunwareError as e: + print(f"Error in length variation testing: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all prompt enhancement examples.""" + print("=== Basic Prompt Enhancement ===") + await basic_prompt_enhancement() + + print("\n=== Artistic Prompt Enhancement ===") + await artistic_prompt_enhancement() + + print("\n=== Photography Prompt Enhancement ===") + await photography_prompt_enhancement() + + print("\n=== Original vs Enhanced Comparison ===") + await compare_original_vs_enhanced() + + print("\n=== Batch Prompt Enhancement ===") + await batch_prompt_enhancement() + + print("\n=== Length Variation Testing ===") + await length_variation_testing() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/08_model_management.py b/examples/08_model_management.py new file mode 100644 index 0000000..caaa3db --- /dev/null +++ b/examples/08_model_management.py @@ -0,0 +1,332 @@ +""" +Model Management Example + +This example demonstrates model management capabilities including: +- Searching for models in the repository +- Uploading custom models (checkpoints, LoRA, ControlNet) +- Managing model metadata and configurations +- Working with different model architectures +""" + +import asyncio +import os + +from runware import ( + EModelArchitecture, + IModelSearch, + IModelSearchResponse, + IUploadModelCheckPoint, + IUploadModelControlNet, + IUploadModelLora, + IUploadModelResponse, + Runware, + RunwareError, +) + + +async def search_models_basic(): + """Search for models using basic criteria.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Basic model search + search_request = IModelSearch( + search="fantasy art", # Search term + limit=5, # Limit results + offset=0, # Starting position + ) + + results: IModelSearchResponse = await runware.modelSearch( + payload=search_request + ) + + print("Basic Model Search Results:") + print(f"Total models found: {results.totalResults}") + print(f"Showing first {len(results.results)} models:") + + for i, model in enumerate(results.results, 1): + print(f"\n{i}. {model.name} (v{model.version})") + print(f" Category: {model.category}") + print(f" Architecture: {model.architecture}") + print(f" AIR: {model.air}") + print(f" Tags: {', '.join(model.tags) if model.tags else 'None'}") + if model.comment: + print(f" Description: {model.comment}") + + # Access additional fields that may have been provided by API + if hasattr(model, "downloadURL"): + print(f" Download URL: {model.downloadURL}") + if hasattr(model, "shortDescription"): + print(f" Short Description: {model.shortDescription[:100]}...") + + except RunwareError as e: + print(f"Error in basic model search: {e}") + finally: + await runware.disconnect() + + +async def search_models_filtered(): + """Search for models with specific filters and categories.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Search for SDXL checkpoints + print("Searching for SDXL checkpoint models...") + checkpoint_search = IModelSearch( + category="checkpoint", + architecture=EModelArchitecture.SDXL, + visibility="public", + limit=3, + ) + + checkpoint_results: IModelSearchResponse = await runware.modelSearch( + payload=checkpoint_search + ) + + print(f"Found {checkpoint_results.totalResults} SDXL checkpoints:") + for model in checkpoint_results.results: + print(f" - {model.name}: {model.air}") + if model.defaultSteps: + print(f" Default steps: {model.defaultSteps}") + if model.defaultCFG: + print(f" Default CFG: {model.defaultCFG}") + + # Search for LoRA models + print("\nSearching for LoRA models...") + lora_search = IModelSearch(category="lora", tags=["anime", "style"], limit=3) + + lora_results: IModelSearchResponse = await runware.modelSearch( + payload=lora_search + ) + + print(f"Found {lora_results.totalResults} LoRA models:") + for model in lora_results.results: + print(f" - {model.name}: {model.air}") + if hasattr(model, "defaultWeight") and model.defaultWeight: + print(f" Default weight: {model.defaultWeight}") + if model.positiveTriggerWords: + print(f" Trigger words: {model.positiveTriggerWords}") + + # Search for ControlNet models + print("\nSearching for ControlNet models...") + controlnet_search = IModelSearch( + category="controlnet", architecture=EModelArchitecture.SDXL, limit=3 + ) + + controlnet_results: IModelSearchResponse = await runware.modelSearch( + payload=controlnet_search + ) + + print(f"Found {controlnet_results.totalResults} ControlNet models:") + for model in controlnet_results.results: + print(f" - {model.name}: {model.air}") + if hasattr(model, "conditioning") and model.conditioning: + print(f" Conditioning: {model.conditioning}") + + except RunwareError as e: + print(f"Error in filtered model search: {e}") + finally: + await runware.disconnect() + + +async def upload_checkpoint_model(): + """Upload a custom checkpoint model.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure checkpoint model upload + checkpoint_payload = IUploadModelCheckPoint( + air="runware:68487@0862923414", # Unique AIR identifier + name="SDXL Model", + heroImageURL="https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/01.png?download=true", + downloadURL="https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors?download=true", + uniqueIdentifier="unique_checkpoint_id_12345678901234567890123456789012", + version="1.0", + tags=["realistic", "portrait", "photography"], + architecture="sdxl", + type="base", # Required for checkpoints + defaultWeight=1.0, + format="safetensors", + positiveTriggerWords="masterpiece, best quality", + shortDescription="High-quality realistic portrait model", + private=False, + defaultScheduler="DPM++ 2M Karras", # Required for checkpoints + defaultSteps=30, + defaultGuidanceScale=7.5, + comment="Custom trained model for realistic portraits", + ) + + print("Uploading checkpoint model...") + upload_result: IUploadModelResponse = await runware.modelUpload( + requestModel=checkpoint_payload + ) + + if upload_result: + print(f"Checkpoint upload successful!") + print(f" AIR: {upload_result.air}") + print(f" Task UUID: {upload_result.taskUUID}") + print(f" Task Type: {upload_result.taskType}") + else: + print("Checkpoint upload failed") + + except RunwareError as e: + print(f"Error uploading checkpoint model: {e}") + finally: + await runware.disconnect() + + +async def upload_lora_model(): + """Upload a custom LoRA model.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure LoRA model upload + lora_payload = IUploadModelLora( + air="runware:68487@08629", + name="Ghibli lora", + heroImageURL="https://huggingface.co/ntc-ai/SDXL-LoRA-slider.Studio-Ghibli-style/resolve/main/images/Studio%20Ghibli%20style_17_3.0.png", + downloadURL="https://huggingface.co/ntc-ai/SDXL-LoRA-slider.Studio-Ghibli-style/resolve/main/Studio%20Ghibli%20style.safetensors?download=true", + uniqueIdentifier="unique_lora_id_abcdefghijklmnopqrstuvwxyz123456789012", + version="2.0", + tags=["anime", "style", "cartoon"], + architecture="sdxl", + format="safetensors", + defaultWeight=0.8, # Typical LoRA weight + positiveTriggerWords="anime style, cel shading", + shortDescription="Anime art style enhancement LoRA", + private=False, + comment="Trained on high-quality anime artwork", + ) + + print("Uploading LoRA model...") + upload_result: IUploadModelResponse = await runware.modelUpload( + requestModel=lora_payload + ) + + if upload_result: + print(f"LoRA upload successful!") + print(f" AIR: {upload_result.air}") + print(f" Task UUID: {upload_result.taskUUID}") + else: + print("LoRA upload failed") + + except RunwareError as e: + print(f"Error uploading LoRA model: {e}") + finally: + await runware.disconnect() + + +async def upload_controlnet_model(): + """Upload a custom ControlNet model.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure ControlNet model upload + controlnet_payload = IUploadModelControlNet( + air="runware:68487@08629112", + name="Custom Canny ControlNet", + downloadURL="https://huggingface.co/diffusers/controlnet-canny-sdxl-1.0-small/resolve/main/diffusion_pytorch_model.safetensors?download=true", + uniqueIdentifier="unique_controlnet_id_987654321098765432109876543210", + version="1.5", + tags=["controlnet", "canny", "edge"], + architecture="sdxl", + format="safetensors", + conditioning="canny", # Required for ControlNet + shortDescription="Canny edge detection ControlNet for SDXL", + private=False, + comment="Fine-tuned for better edge detection accuracy", + ) + + print("Uploading ControlNet model...") + upload_result: IUploadModelResponse = await runware.modelUpload( + requestModel=controlnet_payload + ) + + if upload_result: + print(f"ControlNet upload successful!") + print(f" AIR: {upload_result.air}") + print(f" Task UUID: {upload_result.taskUUID}") + else: + print("ControlNet upload failed") + + except RunwareError as e: + print(f"Error uploading ControlNet model: {e}") + finally: + await runware.disconnect() + + +async def search_runware_models(): + """Search for runware's private models.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Search for runware's private models + private_search = IModelSearch( + visibility="private", limit=10 # Only private models + ) + + results: IModelSearchResponse = await runware.modelSearch( + payload=private_search + ) + + print("runware's Private Models:") + print(f"Total private models: {results.totalResults}") + + if results.results: + for i, model in enumerate(results.results, 1): + print(f"\n{i}. {model.name} (v{model.version})") + print(f" AIR: {model.air}") + print(f" Category: {model.category}") + print(f" Private: {model.private}") + if model.comment: + print(f" Notes: {model.comment}") + else: + print("No private models found") + + except RunwareError as e: + print(f"Error searching runware models: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all model management examples.""" + print("=== Basic Model Search ===") + await search_models_basic() + + print("\n=== Filtered Model Search ===") + await search_models_filtered() + + print("\n=== Upload Checkpoint Model ===") + await upload_checkpoint_model() + + print("\n=== Upload LoRA Model ===") + await upload_lora_model() + + print("\n=== Upload ControlNet Model ===") + await upload_controlnet_model() + + print("\n=== Search runware Models ===") + await search_runware_models() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/09_image_to_image_inpainting.py b/examples/09_image_to_image_inpainting.py new file mode 100644 index 0000000..298bb2d --- /dev/null +++ b/examples/09_image_to_image_inpainting.py @@ -0,0 +1,417 @@ +""" +Image-to-Image and Inpainting Example + +This example demonstrates advanced image-to-image generation techniques: +- Seed image transformations with different strengths +- Inpainting with mask-based editing +- Outpainting for image extension +- InstantID for identity preservation +- IP Adapters for style transfer +- Reference image guidance +""" + +import asyncio +import os +from typing import List + +from runware import ( + IEmbedding, + IImage, + IImageInference, + IIpAdapter, + IOutpaint, + Runware, + RunwareError, +) + + +async def basic_image_to_image(): + """Transform existing images using seed images with different strengths.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Source image for transformation + seed_image = "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg" + + # Test different transformation strengths + strengths = [0.3, 0.5, 0.7, 0.9] + + for strength in strengths: + print(f"Transforming with strength {strength}...") + + request = IImageInference( + positivePrompt="vibrant digital art, neon colors, cyberpunk aesthetic, highly detailed", + model="civitai:4384@128713", + seedImage=seed_image, + strength=strength, # How much to transform the original + numberResults=1, + height=768, + width=768, + negativePrompt="blurry, low quality, monochrome", + steps=30, + CFGScale=7.5, + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + for image in images: + print(f" Strength {strength}: {image.imageURL}") + print(f" Seed used: {image.seed}") + + except RunwareError as e: + print(f"Error in image-to-image transformation: {e}") + finally: + await runware.disconnect() + + +async def inpainting_with_masks(): + """Perform selective editing using mask-based inpainting.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Base image and mask for inpainting + base_image = "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/background.jpg" + + # Create a simple inpainting scenario + print("Performing inpainting operation...") + + # For this example, we'll use the base image and create targeted edits + request = IImageInference( + positivePrompt="beautiful garden with colorful flowers, vibrant blooms, natural lighting", + model="civitai:4384@128713", + seedImage=base_image, + strength=0.8, # High strength for significant changes + numberResults=1, + height=1024, + width=1024, + steps=40, # More steps for better inpainting quality + CFGScale=8.0, + maskMargin=32, # Blend mask edges smoothly + negativePrompt="dead plants, withered, dark, gloomy", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Inpainting Results:") + for image in images: + print(f" Inpainted image: {image.imageURL}") + print(f" Original base: {base_image}") + + except RunwareError as e: + print(f"Error in inpainting: {e}") + finally: + await runware.disconnect() + + +async def outpainting_extension(): + """Extend images beyond their borders using outpainting.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Image to extend + source_image = "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg" + + # Configure outpainting extension + outpaint_config = IOutpaint( + top=64, # Extend 64px upward + right=128, # Extend 128px to the right + bottom=64, # Extend 64px downward + left=128, # Extend 128px to the left + blur=8, # Blur radius for seamless blending + ) + + print("Extending image with outpainting...") + + request = IImageInference( + positivePrompt="seamless natural extension, consistent lighting and style, photorealistic", + model="civitai:4384@128713", + seedImage=source_image, + outpaint=outpaint_config, + width=1024, + height=640, + strength=0.6, + numberResults=1, + steps=35, + CFGScale=7.0, + negativePrompt="seams, inconsistent lighting, artifacts", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Outpainting Results:") + for image in images: + print(f" Extended image: {image.imageURL}") + print( + f" Extensions: top={outpaint_config.top}, right={outpaint_config.right}" + ) + print( + f" Extensions: bottom={outpaint_config.bottom}, left={outpaint_config.left}" + ) + + except RunwareError as e: + print(f"Error in outpainting: {e}") + finally: + await runware.disconnect() + + +async def ip_adapter_style_transfer(): + """Apply style transfer using IP Adapters.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Style reference images + style_images = [ + "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/background.jpg", + "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg", + ] + + # Configure IP Adapters + ip_adapters = [] + for i, style_img in enumerate(style_images): + ip_adapter = IIpAdapter( + model="runware:55@1", + guideImage=style_img, + weight=0.6, + ) + ip_adapters.append(ip_adapter) + + print("Applying style transfer with IP Adapters...") + + request = IImageInference( + positivePrompt="beautiful landscape painting, artistic composition, masterpiece quality", + model="civitai:288584@324619", + ipAdapters=ip_adapters, + numberResults=1, + height=1024, + width=1024, + steps=35, + CFGScale=8.0, + negativePrompt="low quality, blurry, distorted", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("IP Adapter Style Transfer Results:") + for image in images: + print(f" Style-transferred image: {image.imageURL}") + print(f" Applied {len(ip_adapters)} style references") + + except RunwareError as e: + print(f"Error with IP Adapter: {e}") + finally: + await runware.disconnect() + + +async def reference_guided_generation(): + """Generate images guided by reference images.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Multiple reference images for guidance + reference_images = [ # right now it supports only 1 image in a list + "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/background.jpg" + ] + print("Generating with reference image guidance...") + + request = IImageInference( + positivePrompt="epic fantasy landscape, magical atmosphere, vibrant colors, cinematic composition", + model="civitai:4384@128713", + referenceImages=reference_images, + numberResults=2, + height=1024, + width=1024, + steps=30, + CFGScale=7.5, + seed=98765, + negativePrompt="dark, gloomy, low quality, blurry", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Reference-guided generation results:") + for i, image in enumerate(images, 1): + print(f" Generated image {i}: {image.imageURL}") + print(f" Guided by {len(reference_images)} reference images") + print(f" Seed: {image.seed}") + + except RunwareError as e: + print(f"Error in reference-guided generation: {e}") + finally: + await runware.disconnect() + + +async def embedding_enhanced_generation(): + """Generate images using textual embeddings for enhanced control.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + # Configure textual embeddings + embeddings = [ + IEmbedding(model="civitai:7808@9208"), + IEmbedding(model="civitai:4629@5637"), + ] + + print("Generating with textual embeddings...") + + request = IImageInference( + positivePrompt="award-winning photography, professional composition, perfect lighting", + model="civitai:4384@128713", + embeddings=embeddings, + numberResults=1, + height=1024, + width=1024, + steps=35, + CFGScale=8.0, + negativePrompt="amateur, poor lighting, distorted", + ) + + images: List[IImage] = await runware.imageInference(requestImage=request) + + print("Embedding-enhanced results:") + for image in images: + print(f" Enhanced image: {image.imageURL}") + print(f" Used {len(embeddings)} textual embeddings") + + except RunwareError as e: + print(f"Error with embeddings: {e}") + finally: + await runware.disconnect() + + +async def comprehensive_editing_workflow(): + """Demonstrate a comprehensive image editing workflow combining multiple techniques.""" + + runware = Runware(api_key=os.getenv("RUNWARE_API_KEY")) + + try: + await runware.connect() + + print("=== Comprehensive Image Editing Workflow ===") + + # Starting image + original_image = "https://raw.githubusercontent.com/adilentiq/test-images/refs/heads/main/common/background.jpg" + + # Step 1: Style transformation + print("\n1. Applying artistic style transformation...") + style_request = IImageInference( + positivePrompt="impressionist painting style, soft brushstrokes, warm colors, artistic masterpiece", + model="civitai:4384@128713", + seedImage=original_image, + strength=0.6, + numberResults=1, + height=1024, + width=1024, + steps=30, + CFGScale=7.5, + ) + + style_images: List[IImage] = await runware.imageInference( + requestImage=style_request + ) + styled_image_url = style_images[0].imageURL + print(f" Style applied: {styled_image_url}") + + # Step 2: Extend the styled image with outpainting + print("\n2. Extending image with outpainting...") + outpaint_config = IOutpaint(top=64, right=64, bottom=64, left=64, blur=8) + + extend_request = IImageInference( + positivePrompt="seamless extension, consistent artistic style, harmonious composition", + model="civitai:4384@128713", + seedImage=styled_image_url, + outpaint=outpaint_config, + width=1280, + height=640, + strength=0.5, + numberResults=1, + steps=35, + CFGScale=7.0, + ) + + extended_images: List[IImage] = await runware.imageInference( + requestImage=extend_request + ) + extended_image_url = extended_images[0].imageURL + print(f" Extended image: {extended_image_url}") + + # Step 3: Final enhancement with reference guidance + print("\n3. Final enhancement with reference guidance...") + reference_image = [ # right now it supports only 1 image in a list + "https://img.freepik.com/free-photo/macro-picture-red-leaf-lights-against-black-background_181624-32636.jpg" + ] + + enhance_request = IImageInference( + positivePrompt="masterpiece quality, enhanced details, perfect composition, museum-worthy art", + model="civitai:4384@128713", + seedImage=extended_image_url, + referenceImages=reference_image, + width=1280, + height=640, + strength=0.3, # Light enhancement + numberResults=1, + steps=40, + CFGScale=8.0, + ) + + final_images: List[IImage] = await runware.imageInference( + requestImage=enhance_request + ) + final_image_url = final_images[0].imageURL + + print("\n=== Workflow Complete ===") + print(f"Original: {original_image}") + print(f"Styled: {styled_image_url}") + print(f"Extended: {extended_image_url}") + print(f"Final: {final_image_url}") + print("\nThe image has been transformed through multiple editing stages!") + + except RunwareError as e: + print(f"Error in comprehensive workflow: {e}") + finally: + await runware.disconnect() + + +async def main(): + """Run all image-to-image and inpainting examples.""" + print("=== Basic Image-to-Image Transformation ===") + await basic_image_to_image() + + print("\n=== Inpainting with Masks ===") + await inpainting_with_masks() + + print("\n=== Outpainting Extension ===") + await outpainting_extension() + + print("\n=== IP Adapter Style Transfer ===") + await ip_adapter_style_transfer() + + print("\n=== Reference-Guided Generation ===") + await reference_guided_generation() + + print("\n=== Embedding-Enhanced Generation ===") + await embedding_enhanced_generation() + + print("\n=== Comprehensive Editing Workflow ===") + await comprehensive_editing_workflow() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/ace++/local_local_1.py b/examples/ace++/local_local_1.py deleted file mode 100644 index 5daabec..0000000 --- a/examples/ace++/local_local_1.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/local/local_1_m.webp" - init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/local/local_1.webp" - request_image = IImageInference( - positivePrompt="By referencing the mask, restore a partial image from the doodle {image} that aligns with the textual explanation: \"1 white old owl\".", - model="runware:102@1", - height=1024, - width=1024, - numberResults=1, - steps=28, - CFGScale=50.0, - acePlusPlus=IAcePlusPlus( - inputImages=[init_image], - inputMasks=[mask_image], - repaintingScale=0.5, - taskType="local_editing" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/examples/ace++/logo_paste.py b/examples/ace++/logo_paste.py deleted file mode 100644 index 7138e78..0000000 --- a/examples/ace++/logo_paste.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - - reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_ref.png" - mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_1_m.png" - init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/logo_paste/1_1_edit.png" - request_image = IImageInference( - positivePrompt="The logo is printed on the headphones.", - model="runware:102@1", - taskUUID="68020b8f-bbcf-4779-ba51-4f3bb00aef6a", - height=1024, - width=1024, - numberResults=1, - steps=28, - CFGScale=50.0, - referenceImages=[reference_image], - acePlusPlus=IAcePlusPlus( - inputImages=[init_image], - inputMasks=[mask_image], - repaintingScale=1.0, - taskType="subject" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/examples/ace++/movie_poster_1.py b/examples/ace++/movie_poster_1.py deleted file mode 100644 index 6515264..0000000 --- a/examples/ace++/movie_poster_1.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - - reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_ref.png" - mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_1_m.png" - init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/movie_poster/1_1_edit.png" - request_image = IImageInference( - positivePrompt="The man is facing the camera and is smiling.", - model="runware:102@1", - taskUUID="68020b8f-bbcf-4779-ba51-4f3bb00aef6a", - height=768, - width=1024, - numberResults=1, - steps=28, - CFGScale=50.0, - referenceImages=[reference_image], - acePlusPlus=IAcePlusPlus( - inputImages=[init_image], - inputMasks=[mask_image], - repaintingScale=1.0, - taskType="portrait" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/examples/ace++/photo_editing_1.py b/examples/ace++/photo_editing_1.py deleted file mode 100644 index 3f33943..0000000 --- a/examples/ace++/photo_editing_1.py +++ /dev/null @@ -1,39 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - - init_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_1_edit.png" - mask_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_1_m.png" - reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/application/photo_editing/1_ref.png" - request_image = IImageInference( - positivePrompt="The item is put on the ground.", - model="runware:102@1", - height=1024, - width=1024, - numberResults=1, - steps=28, - CFGScale=50.0, - referenceImages=[reference_image], - acePlusPlus=IAcePlusPlus( - inputImages=[init_image], - inputMasks=[mask_image], - repaintingScale=1.0, - taskType="subject" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/examples/ace++/portrait_human_1.py b/examples/ace++/portrait_human_1.py deleted file mode 100644 index b216438..0000000 --- a/examples/ace++/portrait_human_1.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - - reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/portrait/human_1.jpg" - request_image = IImageInference( - positivePrompt="Maintain the facial features, A girl is wearing a neat police uniform and sporting a badge. She is smiling with a friendly and confident demeanor. The background is blurred, featuring a cartoon logo.", - model="runware:102@1", - height=1024, - width=1024, - seed=4194866942, - numberResults=1, - steps=28, - CFGScale=50.0, - referenceImages=[reference_image], - acePlusPlus=IAcePlusPlus( - repaintingScale=0.5, - taskType="portrait" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/examples/ace++/subject_subject_1.py b/examples/ace++/subject_subject_1.py deleted file mode 100644 index 930e706..0000000 --- a/examples/ace++/subject_subject_1.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -from runware import Runware, IImageInference, IAcePlusPlus - - -async def main() -> None: - runware = Runware( - api_key=os.getenv("RUNWARE_API_KEY"), - ) - await runware.connect() - - reference_image = "https://raw.githubusercontent.com/ali-vilab/ACE_plus/refs/heads/main/assets/samples/subject/subject_1.jpg" - request_image = IImageInference( - positivePrompt="Display the logo in a minimalist style printed in white on a matte black ceramic coffee mug, alongside a steaming cup of coffee on a cozy cafe table.", - model="runware:102@1", - height=1024, - width=1024, - seed=2935362780, - numberResults=1, - steps=28, - CFGScale=50.0, - referenceImages=[reference_image], - acePlusPlus=IAcePlusPlus( - repaintingScale=1, - taskType="subject" - ), - ) - images = await runware.imageInference(requestImage=request_image) - for image in images: - print(f"Image URL: {image.imageURL}") - - -if __name__ == "__main__": - import asyncio - asyncio.run(main()) diff --git a/examples/dalmatian.jpg b/examples/dalmatian.jpg deleted file mode 100644 index d07bd210af32202baff192e060009d9e310c08f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 724970 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%0ATU0?jE!-KN?GH|uZjboLvHx$r*BtL^HXF*) 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