{
  "format": "mosael-workflow",
  "version": 1,
  "workflow_revision": 1,
  "name": "Product into a narrated short",
  "description": "From a product photo and a few selling points, write a beat-by-beat script (the first beat is the hook, the last the call to action, up to 12 beats), paint one portrait frame per beat with the product in it, lay each on the timeline for that beat's length, voice each beat lined up with its start (your cloned voice if you have one, otherwise a free Edge voice), add the on-screen lines, and export a vertical short. Nobody appears on camera. Only the selling points you provide are used — no invented claims or figures. Where a video model is available, every beat moves: its frame becomes the first frame of a clip, the clip is what goes on the timeline, and every beat is the same length (the start node's beat_seconds, a length that model can make). Cost: one paid image per beat, plus one paid video generation per beat when beats move — about target length ÷ beat_seconds beats, so 30 seconds at 5 seconds a beat is about 6 images and 6 clips. This copy includes “Put this beat in motion”: the start node's beat_seconds is every beat's length, and the video model you pick has to make that length. Without a video model, open the per-beat loop on the canvas after importing, delete “Put this beat in motion”, “File this beat's clip” and “Mute this beat's clip”, then wire the output of “Paint this beat” into the asset of “Lay it down for this beat's length” — every beat then lays a still for beat_seconds. Or add the template from the in-app gallery: without a video model it is built with stills.",
  "graph_hash": "73a64a6b67d66eea072e4a010a58a7a2b9cc14febd7febd5937e0baa5b295ef7",
  "graph": {
    "meta": {
      "template_id": "product_pitch_short",
      "template_version": 7,
      "source": "official"
    },
    "nodes": [
      {
        "id": "start",
        "type": "start",
        "name": "Product and selling points",
        "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": "Pick the product photo",
        "position": {
          "x": 330,
          "y": 260
        },
        "config": {
          "asset_id": ""
        }
      },
      {
        "id": "pitch_script",
        "type": "llm",
        "name": "Write the beat-by-beat script",
        "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": "Check each beat's narration fits",
        "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": "Create the vertical timeline",
        "position": {
          "x": 650,
          "y": 420
        },
        "config": {
          "name": "{{start.product_name}} · Product short",
          "width": "{{start.width}}",
          "height": "{{start.height}}",
          "fps": "{{start.fps}}"
        }
      },
      {
        "id": "shoot_beats",
        "type": "loop_foreach",
        "name": "Paint, animate, voice and place each beat",
        "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": "Paint this beat",
                "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": "File this beat's frame",
                "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": "Put this beat in motion",
                "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": "File this beat's clip",
                "position": {
                  "x": 720,
                  "y": 0
                },
                "config": {
                  "asset_ids": "",
                  "name": "{{input.product_name}} · {{loop.item.caption}} · clip",
                  "project_id": "{{input.project_id}}"
                },
                "inputs": [
                  "asset_ids"
                ]
              },
              {
                "id": "mute_clip",
                "type": "edit_timeline",
                "name": "Mute this beat's clip",
                "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": "Lay it down for this beat's length",
                "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": "Does this beat have narration?",
                "position": {
                  "x": 80,
                  "y": 320
                },
                "config": {
                  "left": "{{loop.item.narration}}",
                  "op": "not_empty"
                }
              },
              {
                "id": "beat_voice",
                "type": "synthesize_speech",
                "name": "Voice this beat",
                "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": "Line the voice up with the beat",
                "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": "Was the voice-over cut short?",
                "position": {
                  "x": 1360,
                  "y": 320
                },
                "config": {
                  "left": "",
                  "op": "gt",
                  "right": "0"
                },
                "inputs": [
                  "left"
                ]
              },
              {
                "id": "overflow_notice",
                "type": "notify",
                "name": "Say the voice-over was cut",
                "position": {
                  "x": 1680,
                  "y": 320
                },
                "config": {
                  "title": "Product short: a beat's voice-over didn't fit",
                  "body": "The voice-over for the “{{loop.item.caption}}” beat is still {{beat_voice_place.trimmed}} s longer than its picture at 1.5× speed, so the rest was cut. To keep the whole line, shorten that beat's narration or raise the start node's beat_seconds (to a length the video model can make) and run again."
                }
              }
            ],
            "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": "Lay each beat's on-screen line",
        "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": "Export the short",
        "position": {
          "x": 1610,
          "y": 260
        },
        "config": {
          "sequence_id": ""
        },
        "inputs": [
          "sequence_id"
        ]
      },
      {
        "id": "done_notice",
        "type": "notify",
        "name": "Short is ready",
        "position": {
          "x": 1930,
          "y": 260
        },
        "config": {
          "title": "Product short exported",
          "body": "The narrated short for {{start.product_name}} is done: {{shoot_beats.count}} beats.\n{{shoot_beats.results.0.beat_voice.voice_note}}\n{{fit_beats.note}}"
        }
      },
      {
        "id": "output",
        "type": "output",
        "name": "Hand over the short and the script",
        "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"
      }
    ]
  }
}
