{
  "format": "mosael-workflow",
  "version": 1,
  "workflow_revision": 1,
  "name": "商品 → 带货口播短视频",
  "description": "给一张商品图和几条卖点,写一条分拍的口播脚本(第一拍是钩子、最后一拍是行动号召,最多 12 拍),每一拍出一张带商品的竖幅画面、按这一拍的时长铺上时间线,念这一拍的画外音、对齐这一拍的开头(有克隆音色优先用,没有就用免费的 Edge 音色),再铺上屏幕短句,导出竖屏成片。没有人出镜。卖点只用你给的那几条,不编功效和数据。视频模型可用时每一拍动起来:这一拍的画面当首帧出一段视频,铺上时间线的是视频,每拍一样长(开始节点的 beat_seconds,建图时取这个模型出得了的那一档)。花费量级:每一拍一次付费出图,动起来时每一拍再加一次付费视频生成 —— 拍数约为目标时长 ÷ beat_seconds,默认 30 秒、每拍 5 秒约 6 拍,就是 6 张图加 6 段视频。这一份带着「把这一拍动起来」:开始节点的 beat_seconds 是每一拍几秒,挑的视频模型要出得了这个时长。没有视频模型的话,导入后在画布上打开逐拍的那个循环,删掉「把这一拍动起来」「归档这一拍的视频」「这一拍的视频不出声」三个节点,再把「出这一拍的画面」的结果连到「按这一拍的时长铺上去」的素材那一格 —— 每一拍就按 beat_seconds 铺一张静图。也可以在应用的模板库里直接添加:没有视频模型时建出来的就是静图版。",
  "graph_hash": "a3bbc95b99ca1668068eb181e022abf406bd89de83c495fc6ce82544d8e308b8",
  "graph": {
    "meta": {
      "template_id": "product_pitch_short",
      "template_version": 7,
      "source": "official"
    },
    "nodes": [
      {
        "id": "start",
        "type": "start",
        "name": "填商品与卖点",
        "position": {
          "x": 40,
          "y": 260
        },
        "config": {
          "params": {
            "product_name": "",
            "selling_points": "",
            "audience": "刷竖屏短视频的年轻观众",
            "target_duration_seconds": 30,
            "beat_seconds": 5,
            "width": 1080,
            "height": 1920,
            "fps": 30
          },
          "required_params": [
            "product_name",
            "selling_points"
          ]
        }
      },
      {
        "id": "product_photo",
        "type": "asset",
        "name": "选择商品图",
        "position": {
          "x": 330,
          "y": 260
        },
        "config": {
          "asset_id": ""
        }
      },
      {
        "id": "pitch_script",
        "type": "llm",
        "name": "写分拍口播脚本",
        "position": {
          "x": 650,
          "y": 120
        },
        "config": {
          "profile_id": "",
          "model": "",
          "preset": "creative",
          "system": "你是带货短视频的编导。用户给你一件商品和几条卖点，你要写一条约 {{start.target_duration_seconds}} 秒、\n能直接拍的口播脚本，按\"拍\"拆开。\n\n硬性要求：\n- 前三秒必须给出观看理由，不要从\"大家好\"开始。\n- 每一拍正好 {{start.beat_seconds}} 秒（视频模型一段就出这么长），每一拍的 narration 念出来不能超过它：中文按每秒约 4 个字估，宁短勿长。\n- visual_prompt 用英文写这一拍的第一帧画面（这一拍的视频从它开始动），**商品必须出现在画面里**；不要写机位参数，不要在画面里\n  生成文字、logo 或水印；也不要把画面写成「手机屏幕里的画面」、不要加设备边框或 App 界面 ——\n  成片本来就在手机上看。\n- motion_prompt 用英文写这 {{start.beat_seconds}} 秒里画面怎么动：镜头缓慢推近、平移或环绕，光线在商品表面\n  流动，场景里轻微的动作；动作要小、在这几秒内完成，商品始终完整、清楚、不变形、不换样子。\n- 卖点只能来自用户给的那几条，不要编造功效、成分、资质或数据。\n- caption 是屏幕上的短句，不是把 narration 原样抄一遍。\n- 最多 12 拍：每一拍都要出一张画面、再出一段视频，是两次付费生成。\n\n只输出符合 JSON Schema 的对象。\n\n这一条没有人出镜:第一拍的 narration 就是开场钩子,最后一拍的 narration 是行动号召 —— 整条片子就是这几拍,\n首尾不另加。",
          "prompt": "商品名称：{{start.product_name}}\n卖点（只能用这些）：\n{{start.selling_points}}\n目标观众：{{start.audience}}\n成片目标时长：{{start.target_duration_seconds}} 秒\n\n请写出分拍脚本。每拍 {{start.beat_seconds}} 秒，拍数 × 每拍秒数就是成片时长：拍数取目标时长 ÷ 每拍秒数（四舍五入，至少 2 拍）。",
          "response_format": "json_schema",
          "json_schema_name": "product_pitch_script",
          "json_schema": {
            "type": "object",
            "properties": {
              "beats": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "narration": {
                      "type": "string",
                      "description": "这一拍要念的口播原文,中文;念出来不超过一拍的时长"
                    },
                    "visual_prompt": {
                      "type": "string",
                      "description": "这一拍的第一帧画面,英文;商品必须在画面里"
                    },
                    "motion_prompt": {
                      "type": "string",
                      "description": "这一拍的画面怎么动,英文:镜头的缓慢运动、光线在商品上的变化、轻微的动作"
                    },
                    "caption": {
                      "type": "string",
                      "description": "屏幕上的短句,不超过 14 字"
                    }
                  },
                  "required": [
                    "narration",
                    "visual_prompt",
                    "motion_prompt",
                    "caption"
                  ],
                  "additionalProperties": false
                },
                "minItems": 2,
                "maxItems": 12,
                "description": "按时间顺序的各拍:第一拍的 narration 就是前三秒的钩子,最后一拍的 narration 是行动号召"
              }
            },
            "required": [
              "beats"
            ],
            "additionalProperties": false
          },
          "json_schema_strict": "true",
          "temperature": 0.6,
          "max_tokens": 6000
        }
      },
      {
        "id": "fit_beats",
        "type": "fit_narration",
        "name": "量一遍每拍的口播念不念得完",
        "position": {
          "x": 970,
          "y": 120
        },
        "config": {
          "items": "{{pitch_script.json.beats}}",
          "text_field": "narration",
          "seconds": "{{start.beat_seconds}}",
          "max_total_seconds": "{{start.target_duration_seconds}}",
          "profile_id": "",
          "model": ""
        }
      },
      {
        "id": "pitch_project",
        "type": "project_sequence_create",
        "name": "建立竖屏成片时间线",
        "position": {
          "x": 650,
          "y": 420
        },
        "config": {
          "name": "{{start.product_name}} · 带货短片",
          "width": "{{start.width}}",
          "height": "{{start.height}}",
          "fps": "{{start.fps}}"
        }
      },
      {
        "id": "shoot_beats",
        "type": "loop_foreach",
        "name": "逐拍出画面、动起来、配音并上时间线",
        "position": {
          "x": 970,
          "y": 260
        },
        "config": {
          "items": "",
          "inputs": {
            "product_asset_id": "{{product_photo.asset_id}}",
            "product_name": "{{start.product_name}}",
            "project_id": "{{pitch_project.project_id}}",
            "sequence_id": "{{pitch_project.sequence_id}}",
            "video_track_id": "{{pitch_project.video_track_id}}",
            "audio_track_id": "{{pitch_project.audio_track_id}}",
            "beat_seconds": "{{start.beat_seconds}}",
            "aspect_ratio": "9:16",
            "resolution": "720p"
          },
          "body": {
            "nodes": [
              {
                "id": "beat_frame",
                "type": "ai_generate",
                "name": "出这一拍的画面",
                "position": {
                  "x": 80,
                  "y": 140
                },
                "config": {
                  "provider": "",
                  "provider_profile_id": "",
                  "model": "",
                  "kind": "image",
                  "prompt": "{{loop.item.visual_prompt}} The product in the reference image must be reproduced EXACTLY: identical cut, silhouette, colour, fabric texture, weave, print scale and placement, stitching, buttons, trims and any logo or label. Do not redesign, restyle, recolour, resize the print, add or remove details. Only the wearer, pose, setting and lighting may change. Vertical 9:16 portrait-orientation photograph, full-bleed: the scene fills the frame edge to edge. Photorealistic, no text, no logo overlay, no watermark.",
                  "negative_prompt": "different garment, altered pattern, changed colour, distorted print, redesigned collar or sleeves, extra logos, text, watermark, deformed hands, extra limbs, phone, smartphone, device frame, screen bezel, mockup, app interface",
                  "parameters": {},
                  "source_assets": [
                    "{{input.product_asset_id}}:reference_image"
                  ]
                }
              },
              {
                "id": "file_frame",
                "type": "asset_update",
                "name": "归档这一拍的画面",
                "position": {
                  "x": 400,
                  "y": 0
                },
                "config": {
                  "asset_ids": "",
                  "name": "{{input.product_name}} · {{loop.item.caption}}",
                  "project_id": "{{input.project_id}}"
                },
                "inputs": [
                  "asset_ids"
                ]
              },
              {
                "id": "beat_clip",
                "type": "ai_generate",
                "name": "把这一拍动起来",
                "position": {
                  "x": 400,
                  "y": 140
                },
                "config": {
                  "provider": "",
                  "provider_profile_id": "",
                  "model": "",
                  "kind": "video",
                  "prompt": "{{loop.item.motion_prompt}} The product in the reference image must be reproduced EXACTLY: identical cut, silhouette, colour, fabric texture, weave, print scale and placement, stitching, buttons, trims and any logo or label. Do not redesign, restyle, recolour, resize the print, add or remove details. Only the wearer, pose, setting and lighting may change. Subtle, natural motion only; the product stays whole, undistorted and readable throughout. No text, no logo overlay, no watermark.",
                  "negative_prompt": "different garment, altered pattern, changed colour, distorted print, redesigned collar or sleeves, extra logos, text, watermark, deformed hands, extra limbs, phone, smartphone, device frame, screen bezel, mockup, app interface",
                  "parameters": {
                    "duration_seconds": "{{input.beat_seconds}}",
                    "aspect_ratio": "{{input.aspect_ratio}}",
                    "resolution": "{{input.resolution}}",
                    "generate_audio": false
                  },
                  "source_assets": [
                    "{{beat_frame.asset_id}}:first_frame"
                  ]
                }
              },
              {
                "id": "file_clip",
                "type": "asset_update",
                "name": "归档这一拍的视频",
                "position": {
                  "x": 720,
                  "y": 0
                },
                "config": {
                  "asset_ids": "",
                  "name": "{{input.product_name}} · {{loop.item.caption}} · 视频",
                  "project_id": "{{input.project_id}}"
                },
                "inputs": [
                  "asset_ids"
                ]
              },
              {
                "id": "mute_clip",
                "type": "edit_timeline",
                "name": "这一拍的视频不出声",
                "position": {
                  "x": 1040,
                  "y": 140
                },
                "config": {
                  "sequence_id": "{{input.sequence_id}}",
                  "operations": "[{\"kind\": \"set_clip_gain\", \"clip_id\": \"{{beat_on_timeline.clip_id}}\", \"muted\": true}]"
                }
              },
              {
                "id": "beat_on_timeline",
                "type": "timeline_append",
                "name": "按这一拍的时长铺上去",
                "position": {
                  "x": 720,
                  "y": 140
                },
                "config": {
                  "sequence_id": "{{input.sequence_id}}",
                  "asset_id": "",
                  "track_id": "{{input.video_track_id}}",
                  "start": 0,
                  "end": "{{input.beat_seconds}}"
                },
                "inputs": [
                  "asset_id"
                ]
              },
              {
                "id": "has_narration",
                "type": "condition",
                "name": "这一拍有画外音吗",
                "position": {
                  "x": 80,
                  "y": 320
                },
                "config": {
                  "left": "{{loop.item.narration}}",
                  "op": "not_empty"
                }
              },
              {
                "id": "beat_voice",
                "type": "synthesize_speech",
                "name": "念这一拍的画外音",
                "position": {
                  "x": 400,
                  "y": 320
                },
                "config": {
                  "text": "{{loop.item.narration}}",
                  "engine": "builtin:edge",
                  "voice": "zh-CN-XiaoxiaoNeural"
                }
              },
              {
                "id": "beat_voice_place",
                "type": "timeline_append",
                "name": "画外音对齐这一拍开头",
                "position": {
                  "x": 1040,
                  "y": 320
                },
                "config": {
                  "sequence_id": "{{input.sequence_id}}",
                  "asset_id": "",
                  "track_id": "{{input.audio_track_id}}",
                  "at": "",
                  "max_duration": "",
                  "trim_overflow": "yes"
                },
                "inputs": [
                  "asset_id",
                  "at",
                  "max_duration"
                ]
              },
              {
                "id": "voice_overflow",
                "type": "condition",
                "name": "画外音裁掉了尾巴吗",
                "position": {
                  "x": 1360,
                  "y": 320
                },
                "config": {
                  "left": "",
                  "op": "gt",
                  "right": "0"
                },
                "inputs": [
                  "left"
                ]
              },
              {
                "id": "overflow_notice",
                "type": "notify",
                "name": "说一声画外音被裁了",
                "position": {
                  "x": 1680,
                  "y": 320
                },
                "config": {
                  "title": "带货短片:有一拍的画外音念不完",
                  "body": "「{{loop.item.caption}}」那一拍的画外音加速到 1.5 倍仍比画面长 {{beat_voice_place.trimmed}} 秒,超出的部分已裁掉。想保住整句,把这一拍的口播改短或把开始节点的 beat_seconds(每拍几秒)改长(视频模型要出得了那个时长)再运行一次。"
                }
              }
            ],
            "edges": [
              {
                "id": "file_then_clip",
                "source": "file_frame",
                "target": "beat_clip"
              },
              {
                "id": "clip_file",
                "source": "beat_clip",
                "target": "file_clip"
              },
              {
                "id": "clip_place",
                "source": "beat_clip",
                "target": "beat_on_timeline"
              },
              {
                "id": "place_mute",
                "source": "beat_on_timeline",
                "target": "mute_clip"
              },
              {
                "id": "frame_file",
                "source": "beat_frame",
                "target": "file_frame"
              },
              {
                "id": "place_voice",
                "source": "beat_on_timeline",
                "target": "beat_voice_place"
              },
              {
                "id": "voice_voice_place",
                "source": "beat_voice",
                "target": "beat_voice_place"
              },
              {
                "id": "narration_voice",
                "source": "has_narration",
                "target": "beat_voice",
                "source_handle": "true"
              },
              {
                "id": "narration_voice_place",
                "source": "has_narration",
                "target": "beat_voice_place",
                "source_handle": "true"
              },
              {
                "id": "voice_overflow_check",
                "source": "beat_voice_place",
                "target": "voice_overflow"
              },
              {
                "id": "overflow_notify",
                "source": "voice_overflow",
                "target": "overflow_notice",
                "source_handle": "true"
              },
              {
                "id": "d-beat_frame-asset_id-file_frame-asset_ids",
                "source": "beat_frame",
                "target": "file_frame",
                "kind": "data",
                "source_output": "asset_id",
                "target_input": "asset_ids"
              },
              {
                "id": "d-beat_clip-asset_id-file_clip-asset_ids",
                "source": "beat_clip",
                "target": "file_clip",
                "kind": "data",
                "source_output": "asset_id",
                "target_input": "asset_ids"
              },
              {
                "id": "d-beat_clip-asset_id-beat_on_timeline-asset_id",
                "source": "beat_clip",
                "target": "beat_on_timeline",
                "kind": "data",
                "source_output": "asset_id",
                "target_input": "asset_id"
              },
              {
                "id": "d-beat_voice-asset_id-beat_voice_place-asset_id",
                "source": "beat_voice",
                "target": "beat_voice_place",
                "kind": "data",
                "source_output": "asset_id",
                "target_input": "asset_id"
              },
              {
                "id": "d-beat_on_timeline-timeline_start-beat_voice_place-at",
                "source": "beat_on_timeline",
                "target": "beat_voice_place",
                "kind": "data",
                "source_output": "timeline_start",
                "target_input": "at"
              },
              {
                "id": "d-beat_on_timeline-duration-beat_voice_place-max_duration",
                "source": "beat_on_timeline",
                "target": "beat_voice_place",
                "kind": "data",
                "source_output": "duration",
                "target_input": "max_duration"
              },
              {
                "id": "d-beat_voice_place-trimmed-voice_overflow-left",
                "source": "beat_voice_place",
                "target": "voice_overflow",
                "kind": "data",
                "source_output": "trimmed",
                "target_input": "left"
              }
            ]
          },
          "concurrency": 1,
          "output": ""
        },
        "inputs": [
          "items"
        ]
      },
      {
        "id": "beat_captions",
        "type": "generate_subtitles",
        "name": "按每一拍的落点铺屏幕短句",
        "position": {
          "x": 1290,
          "y": 260
        },
        "config": {
          "sequence_id": "",
          "segments": "",
          "start_field": "beat_on_timeline.timeline_start",
          "end_field": "beat_on_timeline.timeline_end",
          "text_field": "loop.item.caption"
        },
        "inputs": [
          "sequence_id",
          "segments"
        ]
      },
      {
        "id": "export_short",
        "type": "export_sequence",
        "name": "导出成片",
        "position": {
          "x": 1610,
          "y": 260
        },
        "config": {
          "sequence_id": ""
        },
        "inputs": [
          "sequence_id"
        ]
      },
      {
        "id": "done_notice",
        "type": "notify",
        "name": "成片完成通知",
        "position": {
          "x": 1930,
          "y": 260
        },
        "config": {
          "title": "带货短片已导出",
          "body": "{{start.product_name}} 的口播短片已完成,共 {{shoot_beats.count}} 拍。\n{{shoot_beats.results.0.beat_voice.voice_note}}\n{{fit_beats.note}}"
        }
      },
      {
        "id": "output",
        "type": "output",
        "name": "交付成片与脚本",
        "position": {
          "x": 2250,
          "y": 260
        },
        "config": {
          "values": {
            "final_asset_id": "{{export_short.asset_id}}",
            "script": "{{pitch_script.json}}",
            "beat_count": "{{shoot_beats.count}}",
            "caption_count": "{{beat_captions.count}}",
            "sequence_id": "{{pitch_project.sequence_id}}",
            "voice": "{{shoot_beats.results.0.beat_voice.voice_note}}"
          }
        }
      }
    ],
    "edges": [
      {
        "id": "start_photo",
        "source": "start",
        "target": "product_photo"
      },
      {
        "id": "start_script",
        "source": "start",
        "target": "pitch_script"
      },
      {
        "id": "script_project",
        "source": "pitch_script",
        "target": "pitch_project"
      },
      {
        "id": "script_fit",
        "source": "pitch_script",
        "target": "fit_beats"
      },
      {
        "id": "fit_shoot",
        "source": "fit_beats",
        "target": "shoot_beats"
      },
      {
        "id": "project_shoot",
        "source": "pitch_project",
        "target": "shoot_beats"
      },
      {
        "id": "photo_shoot",
        "source": "product_photo",
        "target": "shoot_beats"
      },
      {
        "id": "shoot_captions",
        "source": "shoot_beats",
        "target": "beat_captions"
      },
      {
        "id": "captions_export",
        "source": "beat_captions",
        "target": "export_short"
      },
      {
        "id": "export_notice",
        "source": "export_short",
        "target": "done_notice"
      },
      {
        "id": "notice_output",
        "source": "done_notice",
        "target": "output"
      },
      {
        "id": "d-fit_beats-items-shoot_beats-items",
        "source": "fit_beats",
        "target": "shoot_beats",
        "kind": "data",
        "source_output": "items",
        "target_input": "items"
      },
      {
        "id": "d-pitch_project-sequence_id-beat_captions-sequence_id",
        "source": "pitch_project",
        "target": "beat_captions",
        "kind": "data",
        "source_output": "sequence_id",
        "target_input": "sequence_id"
      },
      {
        "id": "d-shoot_beats-results-beat_captions-segments",
        "source": "shoot_beats",
        "target": "beat_captions",
        "kind": "data",
        "source_output": "results",
        "target_input": "segments"
      },
      {
        "id": "d-pitch_project-sequence_id-export_short-sequence_id",
        "source": "pitch_project",
        "target": "export_short",
        "kind": "data",
        "source_output": "sequence_id",
        "target_input": "sequence_id"
      }
    ]
  }
}
