全部產品
Search
文件中心

Platform For AI:Wan視頻產生最佳實務

更新時間:Nov 27, 2025

Wan是一個開源的AI視頻產生大模型,支援T2V(文本產生視頻)和I2V(映像產生視頻)功能。在ComfyUI中,PAI提供了定製化的JSON工作流程和API調用方式,協助您使用Wan模型產生高品質的視頻內容。本文將以I2V(映像產生視頻)為例,介紹如何部署ComfyUI服務,並使用Wan產生視頻。

部署標準版ComfyUI(單使用者使用)

部署配置

使用自訂部署方式部署標準版ComfyUI鏡像服務,具體操作步驟如下:

  1. 登入PAI控制台,在頁面上方選擇目標地區,並在右側選擇目標工作空間,然後單擊進入EAS

  2. 單擊部署服務,然後在自訂模型部署地區,單擊自訂部署

  3. 自訂部署頁面,配置以下參數。

    1. 環境資訊地區,配置以下參數。

      參數

      描述

      鏡像配置

      官方鏡像列表中選擇鏡像comfyui > comfyui:1.9

      說明

      其中1.9為鏡像版本,由於版本迭代迅速,部署時鏡像版本選擇最高版本即可。

      儲存掛載

      為EAS服務掛載外部儲存資料來源(如OSS、NAS等),後續調用服務產生的視頻,將自動儲存到相應的資料來源中。以Object Storage Service為例,單擊OSS,並配置以下參數:

      • Uri:選擇OSS Bucket源地址目錄。關於如何建立儲存空間和目錄,請參見控制台快速入門。請確保建立的儲存空間與EAS服務位於同一地區。

      • 掛載路徑:即掛載到服務執行個體中的目標路徑。例如/code/data-oss

      運行命令

      選擇鏡像後,系統會自動設定運行命令。

      當您進行模型配置後,您需要在運行命令中設定 --data-dir 掛載目錄,並確保掛載目錄與模型配置中的掛載路徑一致。

      對於1.9版本的鏡像,--data-dir 已預先配置,您只需將其更新為模型配置中的掛載路徑即可。例如python main.py --listen --port 8000 --data-dir /code/data-oss

    2. 資源資訊地區,選擇資源規格。

      參數

      描述

      資源類型

      選擇公用資源

      部署資源

      選擇資源規格。因視頻產生相比圖片產生需要更大的GPU顯存,建議您選擇單卡顯存不低於48 GB的資源規格。推薦使用GU60機型(例如ml.gu8is.c16m128.1-gu60)。

    3. 網路資訊地區,配置具有公網訪問能力的專用網路,詳情請參見EAS訪問公網或內網資源

      說明

      EAS服務預設不通公網,因I2V(映像產生視頻)功能需要串連公網下載圖片,所以需為EAS配置具有公網訪問能力的專用網路。

  4. 參數配置完成後,單擊部署

WebUI使用

服務部署成功後,您可以在WebUI頁面構建工作流程,完成服務調用。具體操作步驟如下:

  1. 單擊目標服務名稱進入概覽頁面,在右上方單擊Web應用

  2. 在WebUI頁面左上方,選擇工作流程 > 開啟,選擇JSON工作流程檔案,然後單擊開啟

    PAI已在ComfyUI中整合了各類加速演算法,速度與效果較好的ComfyUI JSON工作流程樣本如下:

    • Image to Video(直接上傳圖片):wanvideo_720P_I2V.json

      工作流程載入成功後,您可以在WebUI頁面的載入映像地區,通過單擊選擇檔案上傳,手動上傳並更新圖片檔案。image

    • Image to Video(通過圖片URL載入圖片):wanvideo_720P_I2V_URL.json

      工作流程載入成功後,您可以在WebUI頁面的Load Image By URL地區,配置圖片URL地址,以更新載入的圖片。image

  3. 單擊頁面下方的運行按鈕,產生視頻。

    大約執行20分鐘左右,結果會顯示在右方的合并為視頻地區中。image

API同步調用

標準版服務僅支援同步調用,即直接請求推理執行個體,不使用EAS的佇列服務。具體操作步驟如下:

  1. 匯出工作流程JSON檔案。

    ComfyUI的API請求體取決於工作流程配置。您需要先在已部署的標準版服務的WebUI版面設定工作流程,然後在WebUI頁面左上方,選擇工作流程 > 匯出(API),通過儲存API格式擷取工作流程對應的JSON檔案。

    image

  2. 查看調用資訊。

    1. 在服務列表中,單擊服務名稱,然後在基本資料地區,單擊查看調用資訊

    2. 調用資訊對話方塊,擷取訪問地址和Token。

      說明
      • 使用公網調用地址:調用用戶端需支援訪問公網。

      • 使用VPC調用地址:調用用戶端必須與服務位於同一個專用網路內。

      image

  3. 調用服務。

    完整調用和擷取結果的程式碼範例如下,您可以在最終結果中通過data[prompt_id]["outputs"]["fullpath"],擷取輸出映像的OSS完整路徑。

    範例程式碼通過環境變數擷取EAS服務訪問地址和Token,您可以在終端中執行以下命令添加臨時性環境變數(僅在當前會話中生效):

    # 分別配置為服務訪問地址和Token。 
    
    export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/"
    export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="

    完整I2V調用代碼

    from time import sleep
    
    import os
    import json
    import requests
    
    service_url     = os.getenv("SERVICE_URL")
    token           = os.getenv("TOKEN")
    image_url       = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png"
    prompt          = "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。"
    negative_prompt = "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走"
    height          = 720
    width           = 1280
    steps           = 40
    num_frames      = 81
    
    if service_url[-1] == "/":
        service_url = service_url[:-1]
    
    prompt_url = f"{service_url}/prompt"
    
    # 請將payload中的prompt的值配置為工作流程對應的JSON檔案內容。
    payload = """
    {
        "prompt":
        {
            "11": {
                "inputs": {
                "model_name": "umt5-xxl-enc-bf16.safetensors",
                "precision": "bf16",
                "load_device": "offload_device",
                "quantization": "disabled"
                },
                "class_type": "LoadWanVideoT5TextEncoder",
                "_meta": {
                "title": "Load WanVideo T5 TextEncoder"
                }
            },
            "16": {
                "inputs": {
                "positive_prompt": "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。",
                "negative_prompt": "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走",
                "force_offload": true,
                "speak_and_recognation": {
                    "__value__": [
                    false,
                    true
                    ]
                },
                "t5": [
                    "11",
                    0
                ],
                "model_to_offload": [
                    "22",
                    0
                ]
                },
                "class_type": "WanVideoTextEncode",
                "_meta": {
                "title": "WanVideo TextEncode"
                }
            },
            "22": {
                "inputs": {
                "model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors",
                "base_precision": "fp16",
                "quantization": "fp8_e4m3fn",
                "load_device": "offload_device",
                "attention_mode": "sageattn",
                "compile_args": [
                    "35",
                    0
                ]
                },
                "class_type": "WanVideoModelLoader",
                "_meta": {
                "title": "WanVideo Model Loader"
                }
            },
            "27": {
                "inputs": {
                "steps": 40,
                "cfg": 6,
                "shift": 5,
                "seed": 1057359483639287,
                "force_offload": true,
                "scheduler": "unipc",
                "riflex_freq_index": 0,
                "denoise_strength": 1,
                "batched_cfg": "",
                "rope_function": "comfy",
                "nocfg_begin": 0.7500000000000001,
                "nocfg_end": 1,
                "model": [
                    "22",
                    0
                ],
                "text_embeds": [
                    "16",
                    0
                ],
                "image_embeds": [
                    "63",
                    0
                ],
                "teacache_args": [
                    "52",
                    0
                ]
                },
                "class_type": "WanVideoSampler",
                "_meta": {
                "title": "WanVideo Sampler"
                }
            },
            "28": {
                "inputs": {
                "enable_vae_tiling": false,
                "tile_x": 272,
                "tile_y": 272,
                "tile_stride_x": 144,
                "tile_stride_y": 128,
                "vae": [
                    "38",
                    0
                ],
                "samples": [
                    "27",
                    0
                ]
                },
                "class_type": "WanVideoDecode",
                "_meta": {
                "title": "WanVideo Decode"
                }
            },
            "30": {
                "inputs": {
                "frame_rate": 16,
                "loop_count": 0,
                "filename_prefix": "WanVideoWrapper_I2V",
                "format": "video/h264-mp4",
                "pix_fmt": "yuv420p",
                "crf": 19,
                "save_metadata": true,
                "trim_to_audio": false,
                "pingpong": false,
                "save_output": true,
                "images": [
                    "28",
                    0
                ]
                },
                "class_type": "VHS_VideoCombine",
                "_meta": {
                "title": "合并為視頻"
                }
            },
            "35": {
                "inputs": {
                "backend": "inductor",
                "fullgraph": false,
                "mode": "default",
                "dynamic": false,
                "dynamo_cache_size_limit": 64,
                "compile_transformer_blocks_only": true
                },
                "class_type": "WanVideoTorchCompileSettings",
                "_meta": {
                "title": "WanVideo Torch Compile Settings"
                }
            },
            "38": {
                "inputs": {
                "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
                "precision": "bf16"
                },
                "class_type": "WanVideoVAELoader",
                "_meta": {
                "title": "WanVideo VAE Loader"
                }
            },
            "52": {
                "inputs": {
                "rel_l1_thresh": 0.25,
                "start_step": 1,
                "end_step": -1,
                "cache_device": "offload_device",
                "use_coefficients": "true"
                },
                "class_type": "WanVideoTeaCache",
                "_meta": {
                "title": "WanVideo TeaCache"
                }
            },
            "59": {
                "inputs": {
                "clip_name": "wanx_clip_vision_h.safetensors"
                },
                "class_type": "CLIPVisionLoader",
                "_meta": {
                "title": "CLIP視覺載入器"
                }
            },
            "63": {
                "inputs": {
                "width": [
                    "66",
                    1
                ],
                "height": [
                    "66",
                    2
                ],
                "num_frames": 81,
                "noise_aug_strength": 0.030000000000000006,
                "start_latent_strength": 1,
                "end_latent_strength": 1,
                "force_offload": true,
                "start_image": [
                    "66",
                    0
                ],
                "vae": [
                    "38",
                    0
                ],
                "clip_embeds": [
                    "65",
                    0
                ]
                },
                "class_type": "WanVideoImageToVideoEncode",
                "_meta": {
                "title": "WanVideo ImageToVideo Encode"
                }
            },
            "65": {
                "inputs": {
                "strength_1": 1,
                "strength_2": 1,
                "crop": "center",
                "combine_embeds": "average",
                "force_offload": true,
                "tiles": 4,
                "ratio": 0.20000000000000004,
                "clip_vision": [
                    "59",
                    0
                ],
                "image_1": [
                    "66",
                    0
                ]
                },
                "class_type": "WanVideoClipVisionEncode",
                "_meta": {
                "title": "WanVideo ClipVision Encode"
                }
            },
            "66": {
                "inputs": {
                "width": 832,
                "height": 480,
                "upscale_method": "lanczos",
                "keep_proportion": false,
                "divisible_by": 16,
                "crop": "disabled",
                "image": [
                    "68",
                    0
                ]
                },
                "class_type": "ImageResizeKJ",
                "_meta": {
                "title": "映像縮放(KJ)"
                }
            },
            "68": {
                "inputs": {
                "url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png",
                "cache": true
                },
                "class_type": "LoadImageByUrl //Browser",
                "_meta": {
                "title": "Load Image By URL"
                }
            }
        }
    }
    """
    
    session = requests.session()
    session.headers.update({"Authorization":token})
    
    payload = json.loads(payload)
    payload["prompt"]["16"]["inputs"]["positive_prompt"] = prompt
    payload["prompt"]["16"]["inputs"]["negative_prompt"] = negative_prompt
    payload["prompt"]["27"]["inputs"]["steps"] = steps
    payload["prompt"]["66"]["inputs"]["height"] = height
    payload["prompt"]["66"]["inputs"]["width"] = width
    payload["prompt"]["63"]["inputs"]["num_frames"] = num_frames
    payload["prompt"]["68"]["inputs"]["url"] = image_url
    
    response = session.post(url=f'{prompt_url}', json=payload)
    if response.status_code != 200:
        raise Exception(response.content)
    
    data = response.json()
    prompt_id = data["prompt_id"]
    print(data)
    
    while 1:
        url = f"{service_url}/history/{prompt_id}"
    
        response = session.get(url=f'{url}')
    
        if response.status_code != 200:
            raise Exception(response.content)
       
        data = response.json()
        if len(data) != 0:
            print(data[prompt_id]["outputs"])
            if len(data[prompt_id]["outputs"]) == 0:
                print("Find no outputs key in output json, the process may be failed, please check the log")
            break
        else:
            sleep(1)
    

    分步解析上述代碼

    • 發送POST請求,從返回結果中擷取Prompt ID。

      import requests
      import os
      
      service_url = os.getenv("SERVICE_URL")
      token = os.getenv("TOKEN")
      url = f"{service_url}/prompt"
      
      payload = {
          "prompt":
          請求體...省略
      }
      
      session = requests.session()
      session.headers.update({"Authorization":token})
      
      
      response = session.post(url=f'{url}', json=payload)
      if response.status_code != 200:
          raise Exception(response.content)
      
      data = response.json()
      print(data)

      其中payload需配置為請求體,prompt索引值是上述匯出(API)獲得的請求JSON,在Python請求中,請求體中的布爾值(True和False)首字母需要大寫。

      首次請求的返回結果樣本如下:

      {
          "prompt_id": "021ebc5b-e245-4e37-8bd3-00f7b949****",
          "number": 5,
          "node_errors": {}
      }
    • 根據請求的Prompt ID擷取最終預測結果。

      import requests
      import os
      # 構造請求URL。
      service_url = os.getenv("SERVICE_URL")
      token = os.getenv("TOKEN")
      url = f"{service_url}history/<prompt_id>"
      
      session = requests.session()
      session.headers.update({"Authorization":f'{token}'})
      
      response = session.get(url=f'{url}')
      
      if response.status_code != 200:
          raise Exception(response.content)
      
      data = response.json()
      print(data)

      其中<prompt_id>需替換為上一步中擷取的prompt_id。

      返回結果樣本如下:

      {
          "130bcd6b-5bb5-496c-9c8c-3a1359a0****": {
              "prompt": ...省略,
              "outputs": {
                  "30": {
                    'gifs': [
                      {
                        'filename': 'WanVideo2_1_T2V_00002.mp4', 
                        'subfolder': '', 
                        'type': 'output', 
                        'format': 'video/h264-mp4', 
                        'frame_rate': 16.0, 'workflow': 
                        'WanVideo2_1_T2V_00002.png', 'fullpath': 
                        '/code/data-oss/output/WanVideo2_1_T2V_00002.mp4'
                      }
                    ]
                  }
              },
              "status": {
                  "status_str": "success",
                  "completed": true,
                  "messages": ...省略,
              }
          }
      }

部署API版ComfyUI(高並發情境)

部署配置

說明

如果您已建立標準版ComfyUI服務,希望將其改成API版本,建議刪除原有服務後重新建立API版本。

使用自訂部署方式部署API版ComfyUI鏡像服務,具體操作步驟如下:

  1. 登入PAI控制台,在頁面上方選擇目標地區,並在右側選擇目標工作空間,然後單擊進入EAS

  2. 單擊部署服務,然後在自訂模型部署地區,單擊自訂部署

  3. 自訂部署頁面,配置以下參數。

    1. 環境資訊地區,配置以下參數。

      參數

      描述

      鏡像配置

      官方鏡像列表中選擇鏡像comfyui > comfyui:1.9-api

      說明

      其中1.9為鏡像版本,由於版本迭代迅速,部署時鏡像版本選擇最高版本即可。

      儲存掛載

      為EAS服務掛載外部儲存資料來源(如OSS、NAS等),後續調用服務產生的視頻,將自動儲存到相應的資料來源中。以Object Storage Service為例,單擊OSS,並配置以下參數:

      • Uri:選擇OSS Bucket源地址目錄。關於如何建立儲存空間和目錄,請參見控制台快速入門。請確保建立的儲存空間與EAS服務位於同一地區。

      • 掛載路徑:即掛載到服務執行個體中的目標路徑。例如/code/data-oss

      運行命令

      選擇鏡像後,系統會自動設定運行命令。

      當您進行模型配置後,您需要在運行命令中設定 --data-dir 掛載目錄,並確保掛載目錄與模型配置中的掛載路徑一致。

      對於1.9版本的鏡像,--data-dir 已預先配置,您只需將其更新為模型配置中的掛載路徑即可。例如python main.py --listen --port 8000 --api --data-dir /code/data-oss

    2. 資源資訊地區,選擇資源規格。

      參數

      描述

      資源類型

      選擇公用資源

      部署資源

      選擇資源規格。因視頻產生相比圖片產生需要更大的GPU顯存,建議您選擇單卡顯存不低於48 GB的資源規格。推薦使用GU60機型(例如ml.gu8is.c16m128.1-gu60)。

    3. 非同步隊列地區,配置單一輸入請求最巨量資料單一輸出返回最巨量資料,標準值為1024 KB。

      說明

      建議合理設定隊列資料大小,以避免因超出限制而導致請求被拒絕、樣本丟失、響應失敗或隊列阻塞等問題。

    4. 網路資訊地區,配置具有公網訪問能力的專用網路,詳情請參見EAS訪問公網或內網資源

      說明

      EAS服務預設不通公網,因I2V(映像產生視頻)功能需要串連公網下載圖片,所以需為EAS配置具有公網訪問能力的專用網路。

  4. 參數配置完成後,單擊部署

API非同步呼叫

API版僅支援非同步呼叫,且僅支援api_prompt路徑。非同步呼叫是指使用EAS的佇列服務,向輸入隊列發送請求,以訂閱的方式獲得結果推送。具體操作步驟如下:

  1. 推理服務頁簽,單擊目標服務名稱進入概覽頁面,在基本資料地區單擊查看調用資訊。在調用資訊對話方塊的非同步呼叫頁簽,查看服務訪問地址和Token。

    image

    說明
    • 使用公網調用地址:調用用戶端需支援訪問公網。

    • 使用VPC調用地址:調用用戶端必須與服務位於同一個專用網路內。

  2. 在終端中執行安裝eas_prediction SDK命令。

    pip install eas_prediction  --user
  3. 調用服務。

    完整調用的程式碼範例如下,您可以在最終結果中通過json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"],擷取輸出映像的OSS完整路徑。

    範例程式碼通過環境變數擷取EAS服務訪問地址和Token,您可以在終端中執行以下命令添加臨時性環境變數(僅在當前會話中生效):

    # 分別配置為服務訪問地址和Token。 
    
    export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/"
    export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="

    完整I2V調用代碼

    import json
    import os
    import requests
    from urllib.parse import urlparse, urlunparse
    from eas_prediction import QueueClient
    
    service_url     = os.getenv("SERVICE_URL")
    token           = os.getenv("TOKEN")
    
    image_url       = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png"
    prompt          = "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。"
    negative_prompt = "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走"
    height          = 720
    width           = 1280
    steps           = 40
    num_frames      = 81
    
    if service_url[-1] == "/":
        service_url = service_url[:-1]
    
    
    def parse_service_url(service_url):
        parsed = urlparse(service_url)
        service_domain = f"{parsed.scheme}://{parsed.netloc}"
        path_parts = [p for p in parsed.path.strip('/').split('/') if p]
        service_name = path_parts[-1]
        return service_domain, service_name
    
    
    service_domain, service_name = parse_service_url(service_url)
    print(f"service_domain: {service_domain}, service_name: {service_name}.")
    
    # 請將payload配置為工作流程對應的JSON檔案內容。
    payload = """
    {
        "11": {
            "inputs": {
            "model_name": "umt5-xxl-enc-bf16.safetensors",
            "precision": "bf16",
            "load_device": "offload_device",
            "quantization": "disabled"
            },
            "class_type": "LoadWanVideoT5TextEncoder",
            "_meta": {
            "title": "Load WanVideo T5 TextEncoder"
            }
        },
        "16": {
            "inputs": {
            "positive_prompt": "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。",
            "negative_prompt": "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走",
            "force_offload": true,
            "speak_and_recognation": {
                "__value__": [
                false,
                true
                ]
            },
            "t5": [
                "11",
                0
            ],
            "model_to_offload": [
                "22",
                0
            ]
            },
            "class_type": "WanVideoTextEncode",
            "_meta": {
            "title": "WanVideo TextEncode"
            }
        },
        "22": {
            "inputs": {
            "model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors",
            "base_precision": "fp16",
            "quantization": "fp8_e4m3fn",
            "load_device": "offload_device",
            "attention_mode": "sageattn",
            "compile_args": [
                "35",
                0
            ]
            },
            "class_type": "WanVideoModelLoader",
            "_meta": {
            "title": "WanVideo Model Loader"
            }
        },
        "27": {
            "inputs": {
            "steps": 40,
            "cfg": 6,
            "shift": 5,
            "seed": 1057359483639287,
            "force_offload": true,
            "scheduler": "unipc",
            "riflex_freq_index": 0,
            "denoise_strength": 1,
            "batched_cfg": "",
            "rope_function": "comfy",
            "nocfg_begin": 0.7500000000000001,
            "nocfg_end": 1,
            "model": [
                "22",
                0
            ],
            "text_embeds": [
                "16",
                0
            ],
            "image_embeds": [
                "63",
                0
            ],
            "teacache_args": [
                "52",
                0
            ]
            },
            "class_type": "WanVideoSampler",
            "_meta": {
            "title": "WanVideo Sampler"
            }
        },
        "28": {
            "inputs": {
            "enable_vae_tiling": false,
            "tile_x": 272,
            "tile_y": 272,
            "tile_stride_x": 144,
            "tile_stride_y": 128,
            "vae": [
                "38",
                0
            ],
            "samples": [
                "27",
                0
            ]
            },
            "class_type": "WanVideoDecode",
            "_meta": {
            "title": "WanVideo Decode"
            }
        },
        "30": {
            "inputs": {
            "frame_rate": 16,
            "loop_count": 0,
            "filename_prefix": "WanVideoWrapper_I2V",
            "format": "video/h264-mp4",
            "pix_fmt": "yuv420p",
            "crf": 19,
            "save_metadata": true,
            "trim_to_audio": false,
            "pingpong": false,
            "save_output": true,
            "images": [
                "28",
                0
            ]
            },
            "class_type": "VHS_VideoCombine",
            "_meta": {
            "title": "合并為視頻"
            }
        },
        "35": {
            "inputs": {
            "backend": "inductor",
            "fullgraph": false,
            "mode": "default",
            "dynamic": false,
            "dynamo_cache_size_limit": 64,
            "compile_transformer_blocks_only": true
            },
            "class_type": "WanVideoTorchCompileSettings",
            "_meta": {
            "title": "WanVideo Torch Compile Settings"
            }
        },
        "38": {
            "inputs": {
            "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
            "precision": "bf16"
            },
            "class_type": "WanVideoVAELoader",
            "_meta": {
            "title": "WanVideo VAE Loader"
            }
        },
        "52": {
            "inputs": {
            "rel_l1_thresh": 0.25,
            "start_step": 1,
            "end_step": -1,
            "cache_device": "offload_device",
            "use_coefficients": "true"
            },
            "class_type": "WanVideoTeaCache",
            "_meta": {
            "title": "WanVideo TeaCache"
            }
        },
        "59": {
            "inputs": {
            "clip_name": "wanx_clip_vision_h.safetensors"
            },
            "class_type": "CLIPVisionLoader",
            "_meta": {
            "title": "CLIP視覺載入器"
            }
        },
        "63": {
            "inputs": {
            "width": [
                "66",
                1
            ],
            "height": [
                "66",
                2
            ],
            "num_frames": 81,
            "noise_aug_strength": 0.030000000000000006,
            "start_latent_strength": 1,
            "end_latent_strength": 1,
            "force_offload": true,
            "start_image": [
                "66",
                0
            ],
            "vae": [
                "38",
                0
            ],
            "clip_embeds": [
                "65",
                0
            ]
            },
            "class_type": "WanVideoImageToVideoEncode",
            "_meta": {
            "title": "WanVideo ImageToVideo Encode"
            }
        },
        "65": {
            "inputs": {
            "strength_1": 1,
            "strength_2": 1,
            "crop": "center",
            "combine_embeds": "average",
            "force_offload": true,
            "tiles": 4,
            "ratio": 0.20000000000000004,
            "clip_vision": [
                "59",
                0
            ],
            "image_1": [
                "66",
                0
            ]
            },
            "class_type": "WanVideoClipVisionEncode",
            "_meta": {
            "title": "WanVideo ClipVision Encode"
            }
        },
        "66": {
            "inputs": {
            "width": 832,
            "height": 480,
            "upscale_method": "lanczos",
            "keep_proportion": false,
            "divisible_by": 16,
            "crop": "disabled",
            "image": [
                "68",
                0
            ]
            },
            "class_type": "ImageResizeKJ",
            "_meta": {
            "title": "映像縮放(KJ)"
            }
        },
        "68": {
            "inputs": {
            "url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png",
            "cache": true
            },
            "class_type": "LoadImageByUrl //Browser",
            "_meta": {
            "title": "Load Image By URL"
            }
        }
    }
    """
    
    
    session = requests.session()
    session.headers.update({"Authorization":token})
    
    payload = json.loads(payload)
    payload["16"]["inputs"]["positive_prompt"] = prompt
    payload["16"]["inputs"]["negative_prompt"] = negative_prompt
    payload["27"]["inputs"]["steps"] = steps
    payload["66"]["inputs"]["height"] = height
    payload["66"]["inputs"]["width"] = width
    payload["63"]["inputs"]["num_frames"] = num_frames
    payload["68"]["inputs"]["url"] = image_url
    
    response = session.post(url=f'{service_url}/api_prompt?task_id=txt2img', json=payload)
    if response.status_code != 200:
        raise Exception(response.content)
    
    data = response.json()
    sink_queue = QueueClient(service_domain, f'{service_name}/sink')
    sink_queue.set_token(token)
    sink_queue.init()
    
    watcher = sink_queue.watch(0, 1, auto_commit=False)
    for x in watcher.run():
        if 'task_id' in x.tags:
            print('index {} task_id is {}'.format(x.index, x.tags['task_id']))
        print(f'index {x.index} data is {x.data}')
        print(json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"])
        sink_queue.commit(x.index)

    分步解析上述代碼

    • 發送請求,程式碼範例如下:

      import requests, io, base64, os
      from PIL import Image, PngImagePlugin
      
      url = os.getenv("SERVICE_URL")
      token = os.getenv("TOKEN")
      session = requests.session()
      session.headers.update({"Authorization": token})
      
      work_flow = {
          '3':
          ...省略
      }  # 與標準版不同的是沒有prompt key
      
      for i in range(1):
          payload = work_flow
          response = session.post(url=f'{url}/api_prompt?task_id=txt2img_{i}', json=payload)
          if response.status_code != 200:
            exit(f"send request error:{response.content}")
          else:
            print(f"send {i} success, index is {response.content}")
      

      其中work_flow需配置為請求體,即上述匯出(API)獲得的請求JSON,在Python請求中,請求體中的布爾值(True和False)首字母需要大寫。

    • 訂閱結果,程式碼範例如下:

      from eas_prediction import QueueClient
      import os
      
      token = os.getenv("TOKEN")
      sink_queue = QueueClient('<service_domain>', '<service_name>/sink')
      sink_queue.set_token(token)
      sink_queue.init()
      
      watcher = sink_queue.watch(0, 1, auto_commit=False)
      for x in watcher.run():
          if 'task_id' in x.tags:
              print('index {} task_id is {}'.format(x.index, x.tags['task_id']))
          print(f'index {x.index} data is {x.data}')
          sink_queue.commit(x.index)
      

      其中關鍵配置項說明如下:

      配置項

      描述

      <service_domain>

      請替換為已查詢的服務訪問地址中的調用資訊。例如139699392458****.cn-hangzhou.pai-eas.aliyuncs.com

      <service_name>

      請替換為EAS服務名稱。

      返回結果樣本如下:

      index 2 task_id is txt2img
      index 2 data is b'[{"status_code": 200}, {"type": "executed", "data": {"node": "30", "display_node": "30", "output": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}, "prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c"}}, {"e20b1cb0-fb48-4ddd-92e5-3c783b064a2c": {"prompt": [1, "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", {"11": {"inputs": {"model_name": "umt5-xxl-enc-bf16.safetensors", "precision": "bf16", "load_device": "offload_device", "quantization": "disabled"}, "class_type": "LoadWanVideoT5TextEncoder", "_meta": {"title": "Load WanVideo T5 TextEncoder"}}, "16": {"inputs": {"positive_prompt": "\\u4e00\\u4f4d\\u91d1\\u53d1\\u5973\\u5b50\\uff0c\\u5979\\u4ef0\\u5934\\u95ed\\u773c\\uff0c\\u8868\\u60c5\\u5b81\\u9759\\u800c\\u68a6\\u5e7b\\u3002\\u5979\\u7684\\u5934\\u53d1\\u975e\\u5e38\\u957f\\u4e14\\u84ec\\u677e\\uff0c\\u5448\\u73b0\\u51fa\\u81ea\\u7136\\u7684\\u6ce2\\u6d6a\\u72b6\\uff0c\\u4eff\\u4f5b\\u88ab\\u98ce\\u5439\\u62c2\\u3002\\u80cc\\u666f\\u4e2d\\u6709\\u4e00\\u4e9b\\u6a21\\u7cca\\u7684\\u82b1\\u6735\\u98d8\\u843d\\uff0c\\u8425\\u9020\\u51fa\\u4e00\\u79cd\\u6d6a\\u6f2b\\u548c\\u68a6\\u5e7b\\u7684\\u6c1b\\u56f4\\u3002\\u5979\\u7a7f\\u7740\\u4e00\\u4ef6\\u5e26\\u6709\\u857e\\u4e1d\\u88c5\\u9970\\u7684\\u4e0a\\u8863\\uff0c\\u8863\\u670d\\u7684\\u989c\\u8272\\u4e0e\\u80cc\\u666f\\u76f8\\u534f\\u8c03\\uff0c\\u6574\\u4f53\\u8272\\u8c03\\u67d4\\u548c\\u3002\\u5149\\u7ebf\\u4ece\\u4e0a\\u65b9\\u7167\\u5c04\\u4e0b\\u6765\\uff0c\\u7167\\u4eae\\u4e86\\u5979\\u7684\\u8138\\u5e9e\\u548c\\u5934\\u53d1\\uff0c\\u4f7f\\u6574\\u4e2a\\u753b\\u9762\\u663e\\u5f97\\u975e\\u5e38\\u67d4\\u548c\\u548c\\u6e29\\u6696\\u3002", "negative_prompt": "\\u8272\\u8c03\\u8273\\u4e3d\\uff0c\\u8fc7\\u66dd\\uff0c\\u9759\\u6001\\uff0c\\u7ec6\\u8282\\u6a21\\u7cca\\u4e0d\\u6e05\\uff0c\\u5b57\\u5e55\\uff0c\\u98ce\\u683c\\uff0c\\u4f5c\\u54c1\\uff0c\\u753b\\u4f5c\\uff0c\\u753b\\u9762\\uff0c\\u9759\\u6b62\\uff0c\\u6574\\u4f53\\u53d1\\u7070\\uff0c\\u6700\\u5dee\\u8d28\\u91cf\\uff0c\\u4f4e\\u8d28\\u91cf\\uff0cJPEG\\u538b\\u7f29\\u6b8b\\u7559\\uff0c\\u4e11\\u964b\\u7684\\uff0c\\u6b8b\\u7f3a\\u7684\\uff0c\\u591a\\u4f59\\u7684\\u624b\\u6307\\uff0c\\u753b\\u5f97\\u4e0d\\u597d\\u7684\\u624b\\u90e8\\uff0c\\u753b\\u5f97\\u4e0d\\u597d\\u7684\\u8138\\u90e8\\uff0c\\u7578\\u5f62\\u7684\\uff0c\\u6bc1\\u5bb9\\u7684\\uff0c\\u5f62\\u6001\\u7578\\u5f62\\u7684\\u80a2\\u4f53\\uff0c\\u624b\\u6307\\u878d\\u5408\\uff0c\\u9759\\u6b62\\u4e0d\\u52a8\\u7684\\u753b\\u9762\\uff0c\\u6742\\u4e71\\u7684\\u80cc\\u666f\\uff0c\\u4e09\\u6761\\u817f\\uff0c\\u80cc\\u666f\\u4eba\\u5f88\\u591a\\uff0c\\u5012\\u7740\\u8d70", "force_offload": true, "speak_and_recognation": {"__value__": [false, true]}, "t5": ["11", 0], "model_to_offload": ["22", 0]}, "class_type": "WanVideoTextEncode", "_meta": {"title": "WanVideo TextEncode"}}, "22": {"inputs": {"model": "WanVideo/wan2.1_i2v_720p_14B_bf16.safetensors", "base_precision": "fp16", "quantization": "fp8_e4m3fn", "load_device": "offload_device", "attention_mode": "sageattn", "compile_args": ["35", 0]}, "class_type": "WanVideoModelLoader", "_meta": {"title": "WanVideo Model Loader"}}, "27": {"inputs": {"steps": 40, "cfg": 6.0, "shift": 5.0, "seed": 1057359483639287, "force_offload": true, "scheduler": "unipc", "riflex_freq_index": 0, "denoise_strength": 1.0, "batched_cfg": false, "rope_function": "comfy", "nocfg_begin": 0.7500000000000001, "nocfg_end": 1.0, "model": ["22", 0], "text_embeds": ["16", 0], "image_embeds": ["63", 0], "teacache_args": ["52", 0]}, "class_type": "WanVideoSampler", "_meta": {"title": "WanVideo Sampler"}}, "28": {"inputs": {"enable_vae_tiling": false, "tile_x": 272, "tile_y": 272, "tile_stride_x": 144, "tile_stride_y": 128, "vae": ["38", 0], "samples": ["27", 0]}, "class_type": "WanVideoDecode", "_meta": {"title": "WanVideo Decode"}}, "30": {"inputs": {"frame_rate": 16.0, "loop_count": 0, "filename_prefix": "WanVideoWrapper_I2V", "format": "video/h264-mp4", "pix_fmt": "yuv420p", "crf": 19, "save_metadata": true, "trim_to_audio": false, "pingpong": false, "save_output": true, "images": ["28", 0]}, "class_type": "VHS_VideoCombine", "_meta": {"title": "\\u5408\\u5e76\\u4e3a\\u89c6\\u9891"}}, "35": {"inputs": {"backend": "inductor", "fullgraph": false, "mode": "default", "dynamic": false, "dynamo_cache_size_limit": 64, "compile_transformer_blocks_only": true}, "class_type": "WanVideoTorchCompileSettings", "_meta": {"title": "WanVideo Torch Compile Settings"}}, "38": {"inputs": {"model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors", "precision": "bf16"}, "class_type": "WanVideoVAELoader", "_meta": {"title": "WanVideo VAE Loader"}}, "52": {"inputs": {"rel_l1_thresh": 0.25, "start_step": 1, "end_step": -1, "cache_device": "offload_device", "use_coefficients": true}, "class_type": "WanVideoTeaCache", "_meta": {"title": "WanVideo TeaCache"}}, "59": {"inputs": {"clip_name": "wanx_clip_vision_h.safetensors"}, "class_type": "CLIPVisionLoader", "_meta": {"title": "CLIP\\u89c6\\u89c9\\u52a0\\u8f7d\\u5668"}}, "63": {"inputs": {"width": ["66", 1], "height": ["66", 2], "num_frames": 81, "noise_aug_strength": 0.030000000000000006, "start_latent_strength": 1.0, "end_latent_strength": 1.0, "force_offload": true, "start_image": ["66", 0], "vae": ["38", 0], "clip_embeds": ["65", 0]}, "class_type": "WanVideoImageToVideoEncode", "_meta": {"title": "WanVideo ImageToVideo Encode"}}, "65": {"inputs": {"strength_1": 1.0, "strength_2": 1.0, "crop": "center", "combine_embeds": "average", "force_offload": true, "tiles": 4, "ratio": 0.20000000000000004, "clip_vision": ["59", 0], "image_1": ["66", 0]}, "class_type": "WanVideoClipVisionEncode", "_meta": {"title": "WanVideo ClipVision Encode"}}, "66": {"inputs": {"width": 1280, "height": 720, "upscale_method": "lanczos", "keep_proportion": false, "divisible_by": 16, "crop": "disabled", "image": ["68", 0]}, "class_type": "ImageResizeKJ", "_meta": {"title": "\\u56fe\\u50cf\\u7f29\\u653e\\uff08KJ\\uff09"}}, "68": {"inputs": {"url": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset/3.png", "cache": true}, "class_type": "LoadImageByUrl //Browser", "_meta": {"title": "Load Image By URL"}}}, {"client_id": "unknown"}, ["30"]], "outputs": {"30": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}}, "status": {"status_str": "success", "completed": true, "messages": [["execution_start", {"prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702895}], ["execution_cached", {"nodes": ["11", "16", "22", "27", "28", "30", "35", "38", "52", "59", "63", "65", "66", "68"], "prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702899}], ["execution_success", {"prompt_id": "e20b1cb0-fb48-4ddd-92e5-3c783b064a2c", "timestamp": 1746512702900}]]}, "meta": {"30": {"node_id": "30", "display_node": "30", "parent_node": null, "real_node_id": "30"}}}}, {"30": {"gifs": [{"filename": "WanVideoWrapper_I2V_00001.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16.0, "workflow": "WanVideoWrapper_I2V_00001.png", "fullpath": "/code/data-oss/output/WanVideoWrapper_I2V_00001.mp4"}]}}]'

附錄:更多樣本

T2V(文本產生視頻)和I2V(映像產生視頻)的使用流程一致,參考上述步驟部署和調用服務即可。但T2V不依賴公網串連功能,因此在部署EAS服務時,無需配置專用網路。

您可以通過樣本工作流程檔案(wanvideo_720P_T2V.json)體驗WebUI調用流程。參考WebUI使用,在WebUI頁面載入工作流程,然後在WanVideo TextEncode輸入框中輸入文本提示詞,並單擊運行,則會開始執行流程。image如果需要通過API調用,完整的程式碼範例如下:

範例程式碼通過環境變數擷取EAS服務訪問地址和Token,您可以在終端中執行以下命令添加臨時性環境變數(僅在當前會話中生效):

# 分別配置為服務訪問地址和Token。 

export SERVICE_URL="http://test****.115770327099****.cn-beijing.pai-eas.aliyuncs.com/"
export TOKEN="MzJlMDNjMmU3YzQ0ZDJ*****************TMxZA=="

API同步調用

完整T2V調用代碼

from time import sleep

import os
import json
import requests

service_url     = os.getenv("SERVICE_URL")
token           = os.getenv("TOKEN")
prompt          = "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。"
negative_prompt = "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走"
height          = 720
width           = 1280
steps           = 40
num_frames      = 81

if service_url[-1] == "/":
    service_url = service_url[:-1]

prompt_url = f"{service_url}/prompt"

# 請將payload中的prompt的值配置為工作流程對應的JSON檔案內容。
payload = """
{
    "prompt":
    {
        "11": {
            "inputs": {
            "model_name": "umt5-xxl-enc-bf16.safetensors",
            "precision": "bf16",
            "load_device": "offload_device",
            "quantization": "disabled"
            },
            "class_type": "LoadWanVideoT5TextEncoder",
            "_meta": {
            "title": "Load WanVideo T5 TextEncoder"
            }
        },
        "16": {
            "inputs": {
            "positive_prompt": "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。",
            "negative_prompt": "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走",
            "force_offload": true,
            "speak_and_recognation": {
                "__value__": [
                false,
                true
                ]
            },
            "t5": [
                "11",
                0
            ]
            },
            "class_type": "WanVideoTextEncode",
            "_meta": {
            "title": "WanVideo TextEncode"
            }
        },
        "22": {
            "inputs": {
            "model": "WanVideo/wan2.1_t2v_14B_bf16.safetensors",
            "base_precision": "fp16",
            "quantization": "fp8_e4m3fn",
            "load_device": "offload_device",
            "attention_mode": "sageattn",
            "compile_args": [
                "35",
                0
            ]
            },
            "class_type": "WanVideoModelLoader",
            "_meta": {
            "title": "WanVideo Model Loader"
            }
        },
        "27": {
            "inputs": {
            "steps": 40,
            "cfg": 6.000000000000002,
            "shift": 5.000000000000001,
            "seed": 1057359483639287,
            "force_offload": true,
            "scheduler": "unipc",
            "riflex_freq_index": 0,
            "denoise_strength": 1,
            "batched_cfg": false,
            "rope_function": "default",
            "nocfg_begin": 0.7500000000000001,
            "nocfg_end": 1,
            "model": [
                "22",
                0
            ],
            "text_embeds": [
                "16",
                0
            ],
            "image_embeds": [
                "37",
                0
            ],
            "teacache_args": [
                "52",
                0
            ]
            },
            "class_type": "WanVideoSampler",
            "_meta": {
            "title": "WanVideo Sampler"
            }
        },
        "28": {
            "inputs": {
            "enable_vae_tiling": true,
            "tile_x": 272,
            "tile_y": 272,
            "tile_stride_x": 144,
            "tile_stride_y": 128,
            "vae": [
                "38",
                0
            ],
            "samples": [
                "27",
                0
            ]
            },
            "class_type": "WanVideoDecode",
            "_meta": {
            "title": "WanVideo Decode"
            }
        },
        "30": {
            "inputs": {
            "frame_rate": 16,
            "loop_count": 0,
            "filename_prefix": "WanVideo2_1_T2V",
            "format": "video/h264-mp4",
            "pix_fmt": "yuv420p",
            "crf": 19,
            "save_metadata": true,
            "trim_to_audio": false,
            "pingpong": false,
            "save_output": true,
            "images": [
                "28",
                0
            ]
            },
            "class_type": "VHS_VideoCombine",
            "_meta": {
            "title": "合并為視頻"
            }
        },
        "35": {
            "inputs": {
            "backend": "inductor",
            "fullgraph": false,
            "mode": "default",
            "dynamic": false,
            "dynamo_cache_size_limit": 64,
            "compile_transformer_blocks_only": true
            },
            "class_type": "WanVideoTorchCompileSettings",
            "_meta": {
            "title": "WanVideo Torch Compile Settings"
            }
        },
        "37": {
            "inputs": {
            "width": 832,
            "height": 480,
            "num_frames": 81
            },
            "class_type": "WanVideoEmptyEmbeds",
            "_meta": {
            "title": "WanVideo Empty Embeds"
            }
        },
        "38": {
            "inputs": {
            "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
            "precision": "bf16"
            },
            "class_type": "WanVideoVAELoader",
            "_meta": {
            "title": "WanVideo VAE Loader"
            }
        },
        "52": {
            "inputs": {
            "rel_l1_thresh": 0.25000000000000006,
            "start_step": 1,
            "end_step": -1,
            "cache_device": "offload_device",
            "use_coefficients": "true"
            },
            "class_type": "WanVideoTeaCache",
            "_meta": {
            "title": "WanVideo TeaCache"
            }
        }
    }
}
"""

session = requests.session()
session.headers.update({"Authorization":token})

payload = json.loads(payload)
payload["prompt"]["16"]["inputs"]["positive_prompt"] = prompt
payload["prompt"]["16"]["inputs"]["negative_prompt"] = negative_prompt
payload["prompt"]["27"]["inputs"]["steps"] = steps
payload["prompt"]["37"]["inputs"]["height"] = height
payload["prompt"]["37"]["inputs"]["width"] = width
payload["prompt"]["37"]["inputs"]["num_frames"] = num_frames

response = session.post(url=f'{prompt_url}', json=payload)
if response.status_code != 200:
    raise Exception(response.content)

data = response.json()
prompt_id = data["prompt_id"]
print(data)

while 1:
    url = f"{service_url}/history/{prompt_id}"

    response = session.get(url=f'{url}')

    if response.status_code != 200:
        raise Exception(response.content)
    
    data = response.json()
    if len(data) != 0:
        print(data[prompt_id]["outputs"])
        if len(data[prompt_id]["outputs"]) == 0:
            print("Find no outputs key in output json, the process may be failed, please check the log")
        break
    else:
        sleep(1)

API非同步呼叫

完整T2V調用代碼

import json
import os
import requests
from urllib.parse import urlparse, urlunparse
from eas_prediction import QueueClient

service_url     = os.getenv("SERVICE_URL")
token           = os.getenv("TOKEN")

prompt          = "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。"
negative_prompt = "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走"
height          = 720
width           = 1280
steps           = 40
num_frames      = 81

if service_url[-1] == "/":
    service_url = service_url[:-1]


def parse_service_url(service_url):
    parsed = urlparse(service_url)
    service_domain = f"{parsed.scheme}://{parsed.netloc}"
    path_parts = [p for p in parsed.path.strip('/').split('/') if p]
    service_name = path_parts[-1]
    return service_domain, service_name


service_domain, service_name = parse_service_url(service_url)
print(f"service_domain: {service_domain}, service_name: {service_name}.")

# 請將payload配置為工作流程對應的JSON檔案內容。
payload = """
{
    "11": {
        "inputs": {
        "model_name": "umt5-xxl-enc-bf16.safetensors",
        "precision": "bf16",
        "load_device": "offload_device",
        "quantization": "disabled"
        },
        "class_type": "LoadWanVideoT5TextEncoder",
        "_meta": {
        "title": "Load WanVideo T5 TextEncoder"
        }
    },
    "16": {
        "inputs": {
        "positive_prompt": "一位金髮女子,她仰頭閉眼,表情寧靜而夢幻。她的頭髮非常長且蓬鬆,呈現出自然的波浪狀,彷彿被風吹拂。背景中有一些模糊的花朵飄落,營造出一種浪漫和夢幻的氛圍。她穿著一件帶有蕾絲裝飾的上衣,衣服的顏色與背景相協調,整體色調柔和。光線從上方照射下來,照亮了她的臉龐和頭髮,使整個畫面顯得非常柔和和溫暖。",
        "negative_prompt": "色調豔麗,過曝,靜態,細節模糊不清,字幕,風格,作品,畫作,畫面,靜止,整體發灰,最差品質,低品質,JPEG壓縮殘留,醜陋的,殘缺的,多餘的手指,畫得不好的手部,畫得不好的臉部,畸形的,毀容的,形態畸形的肢體,手指融合,靜止不動的畫面,雜亂的背景,三條腿,背景人很多,倒著走",
        "force_offload": true,
        "speak_and_recognation": {
            "__value__": [
            false,
            true
            ]
        },
        "t5": [
            "11",
            0
        ]
        },
        "class_type": "WanVideoTextEncode",
        "_meta": {
        "title": "WanVideo TextEncode"
        }
    },
    "22": {
        "inputs": {
        "model": "WanVideo/wan2.1_t2v_14B_bf16.safetensors",
        "base_precision": "fp16",
        "quantization": "fp8_e4m3fn",
        "load_device": "offload_device",
        "attention_mode": "sageattn",
        "compile_args": [
            "35",
            0
        ]
        },
        "class_type": "WanVideoModelLoader",
        "_meta": {
        "title": "WanVideo Model Loader"
        }
    },
    "27": {
        "inputs": {
        "steps": 40,
        "cfg": 6.000000000000002,
        "shift": 5.000000000000001,
        "seed": 1057359483639287,
        "force_offload": true,
        "scheduler": "unipc",
        "riflex_freq_index": 0,
        "denoise_strength": 1,
        "batched_cfg": false,
        "rope_function": "default",
        "nocfg_begin": 0.7500000000000001,
        "nocfg_end": 1,
        "model": [
            "22",
            0
        ],
        "text_embeds": [
            "16",
            0
        ],
        "image_embeds": [
            "37",
            0
        ],
        "teacache_args": [
            "52",
            0
        ]
        },
        "class_type": "WanVideoSampler",
        "_meta": {
        "title": "WanVideo Sampler"
        }
    },
    "28": {
        "inputs": {
        "enable_vae_tiling": true,
        "tile_x": 272,
        "tile_y": 272,
        "tile_stride_x": 144,
        "tile_stride_y": 128,
        "vae": [
            "38",
            0
        ],
        "samples": [
            "27",
            0
        ]
        },
        "class_type": "WanVideoDecode",
        "_meta": {
        "title": "WanVideo Decode"
        }
    },
    "30": {
        "inputs": {
        "frame_rate": 16,
        "loop_count": 0,
        "filename_prefix": "WanVideo2_1_T2V",
        "format": "video/h264-mp4",
        "pix_fmt": "yuv420p",
        "crf": 19,
        "save_metadata": true,
        "trim_to_audio": false,
        "pingpong": false,
        "save_output": true,
        "images": [
            "28",
            0
        ]
        },
        "class_type": "VHS_VideoCombine",
        "_meta": {
        "title": "合并為視頻"
        }
    },
    "35": {
        "inputs": {
        "backend": "inductor",
        "fullgraph": false,
        "mode": "default",
        "dynamic": false,
        "dynamo_cache_size_limit": 64,
        "compile_transformer_blocks_only": true
        },
        "class_type": "WanVideoTorchCompileSettings",
        "_meta": {
        "title": "WanVideo Torch Compile Settings"
        }
    },
    "37": {
        "inputs": {
        "width": 832,
        "height": 480,
        "num_frames": 81
        },
        "class_type": "WanVideoEmptyEmbeds",
        "_meta": {
        "title": "WanVideo Empty Embeds"
        }
    },
    "38": {
        "inputs": {
        "model_name": "WanVideo/Wan2_1_VAE_bf16.safetensors",
        "precision": "bf16"
        },
        "class_type": "WanVideoVAELoader",
        "_meta": {
        "title": "WanVideo VAE Loader"
        }
    },
    "52": {
        "inputs": {
        "rel_l1_thresh": 0.25000000000000006,
        "start_step": 1,
        "end_step": -1,
        "cache_device": "offload_device",
        "use_coefficients": "true"
        },
        "class_type": "WanVideoTeaCache",
        "_meta": {
        "title": "WanVideo TeaCache"
        }
    }
}
"""

session = requests.session()
session.headers.update({"Authorization":token})

payload = json.loads(payload)
payload["16"]["inputs"]["positive_prompt"] = prompt
payload["16"]["inputs"]["negative_prompt"] = negative_prompt
payload["27"]["inputs"]["steps"] = steps
payload["37"]["inputs"]["height"] = height
payload["37"]["inputs"]["width"] = width
payload["37"]["inputs"]["num_frames"] = num_frames

response = session.post(url=f'{service_url}/api_prompt?task_id=txt2img', json=payload)
if response.status_code != 200:
    raise Exception(response.content)

data = response.json()
sink_queue = QueueClient(service_domain, f'{service_name}/sink')
sink_queue.set_token(token)
sink_queue.init()

watcher = sink_queue.watch(0, 1, auto_commit=False)
for x in watcher.run():
    if 'task_id' in x.tags:
        print('index {} task_id is {}'.format(x.index, x.tags['task_id']))
    print(f'index {x.index} data is {x.data}')
    print(json.loads(x.data.decode('utf-8'))[1]["data"]["output"]["gifs"][0]["fullpath"])
    sink_queue.commit(x.index)

相關文檔

如需更全面的瞭解ComfyUI的部署與功能使用,包括載入自訂模型、整合ComfyUI外掛程式,以及常見問題等內容,請參閱AI視頻產生-ComfyUI部署