python实现ai短剧漫剧生成管理seedance免费提示词版本BfwStoryForge代码

代码语言:python

所属分类:其他

版本3(最新):python实现ai短剧漫剧生成管理seedance免费提示词版本BfwStoryForge代码

版本2(旧版):python+agnesai的api实现免费ai短剧生成系统bfwStoryForge代码

版本1(旧版):python+阿里大模型api实现模拟小云雀ai短剧生成工具BfwStoryForge代码

代码描述:python实现ai短剧漫剧生成管理seedance免费提示词版本BfwStoryForge代码,可以管理多个不同的短剧漫剧,根据故事主题剧本生成提示词给任意聊天大模型免费生成aif结构化的角色场景道具音色及变种,还有所有的seedance2提示词及参考资源库,告别手动管理短剧时代。

代码标签: python ai 短剧 漫剧 生成 管理 seedance 免费 提示词 版本 BfwStoryF

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AI有声视频生成工作流系统 - 纯手动离线版 · AIF
无 shots 模式:分段镜头节奏由 seedance2_prompt 时间戳自由划分;
资产库支持变种(含AI生图提示词一键复制);分段出镜资产可选;@图片N/@音频N 按 asset_refs 顺序编号;
自动注入全局风格与画面比例;多人物分镜强制描述站位。
"""

import os
import re
import json
import ast
import uuid
import base64
from datetime import datetime
from pathlib import Path
from flask import Flask, request, jsonify, send_from_directory

# ============================================================
# 配置与初始化
# ============================================================
WORK_DIR = Path("workspace")
WORK_DIR.mkdir(exist_ok=True)
PROJECTS_FILE = WORK_DIR / "projects.json"

app = Flask(__name__)

KIND_KEY = {"char": "characters", "prop": "props", "scene": "scenes"}

# ============================================================
# 辅助工具
# ============================================================
def _aif_scalar(value):
    value = value.strip()
    if not value:
        return ""
    if value.startswith("`") and value.endswith("`") and len(value) >= 2:
        value = value[1:-1].strip()
    if value in ("null", "NULL", "None", "~"):
        return None
    if value.lower() in ("true", "false"):
        return value.lower() == "true"
    if (len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"')):
        try:
            return ast.literal_eval(value)
        except Exception:
            return value[1:-1]
    if value.startswith("[") and value.endswith("]"):
        inner = value[1:-1].strip()
        if not inner:
            return []
        parts, buf, quote, depth = [], [], None, 0
        for ch in inner:
            if quote:
                buf.append(ch)
                if ch == quote and (len(buf) < 2 or buf[-2] != "\\"):
                    quote = None
            elif ch in ("'", '"'):
                quote = ch
                buf.append(ch)
            elif ch in "[{":
                depth += 1; buf.append(ch)
            elif ch in "]}":
                depth -= 1; buf.append(ch)
            elif ch == "," and depth == 0:
                parts.append("".join(buf).strip()); buf = []
            else:
                buf.append(ch)
        if buf:
            parts.append("".join(buf).strip())
        return [_aif_scalar(x) for x in parts if x != ""]
    try:
        if re.fullmatch(r"-?\d+", value):
            return int(value)
        if re.fullmatch(r"-?(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?", value):
            return float(value)
    except Exception:
        pass
    return value


def _aif_clean_lines(text):
    text = text.replace("\r\n", "\n").replace("\r", "\n").strip()
    text = re.sub(r"^\s*```(?:aif|AIF|ai-flow|text)?\s*\n?", "", text)
    text = re.sub(r"\n?\s*```\s*$", "", text)
    lines = text.split("\n")
    header_idx = None
    for i, line in enumerate(lines):
        if line.strip().lower() in ("aif", "aif 1", "aif/1", "aif 1.0", "aif/1.0"):
            header_idx = i
            break
    if header_idx is not None:
        lines = lines[header_idx + 1:]
    else:
        first = None
        for i, line in enumerate(lines):
            s = line.strip()
            if re.match(r"^[A-Za-z_][\w-]*\s*:", s) or re.match(r"^[A-Za-z_][\w-]*\s*$", s):
                first = i
                break
        if first is not None:
            lines = lines[first:]
    return lines


def parse_aif(text):
    lines = _aif_clean_lines(text)
    while lines and not lines[0].strip():
        lines.pop(0)
    if not lines:
        raise ValueError("AIF 内容为空。")

    rows = []
    for raw in lines:
        if not raw.strip() or raw.lstrip().startswith("#"):
            continue
        indent = len(raw) - len(raw.lstrip(" "))
        rows.append((indent, raw.strip()))

    def split_key(s):
        depth, quote = 0, None
        for i, ch in enumerate(s):
            if quote:
                if ch == quote and (i == 0 or s[i-1] != "\\"):
                    quote = None
            elif ch in ("'", '"'):
                quote = ch
            elif ch in "[{(":
                depth += 1
            elif ch in "]})":
                depth -= 1
            elif ch == ":" and depth == 0:
                key = s[:i].strip()
                val = s[i+1:].strip()
                if key:
                    return key, val
        return None, None

    def parse_block(pos, indent):
        if pos >= len(rows) or rows[pos][0] < indent:
            return {}, pos
        is_list = rows[pos][0] == indent and rows[pos][1].startswith("-")
        obj = [] if is_list else {}

        while pos < len(rows):
            ind, content = rows[pos]
            if ind < indent:
                break
            if ind > indent:
                break

            if is_list:
                if not content.startswith("-"):
                    break
                item = content[1:].strip()
                if not item:
                    if pos + 1 < len(rows) and rows[pos+1][0] > indent:
                        child, pos = parse_block(pos+1, rows[pos+1][0])
                        obj.append(child)
                    else:
                        obj.append(None); pos += 1
                    continue
                k, v = split_key(item)
                if k is not None:
                    d = {}
                    if v == "":
                        if pos + 1 < len(rows) and rows[pos+1][0] > indent:
                            child, nextpos = parse_block(pos+1, rows[pos+1][0])
                            d[k] = child; pos = nextpos
                        else:
                            d[k] = {}; pos += 1
                    elif v == "|":
                        vals=[]; pos += 1
                        while pos < len(rows) and rows[pos][0] > indent:
                            vals.append(rows[pos][1]); pos += 1
                        d[k] = "\n".join(vals)
                    else:
                        d[k] = _aif_scalar(v); pos += 1
                    if pos < len(rows) and rows[pos][0] > indent:
                        child, nextpos = parse_block(pos, rows[pos][0])
                        if isinstance(child, dict):
                            d.update(child)
                            pos = nextpos
                    obj.append(d)
                else:
                    obj.append(_aif_scalar(item)); pos += 1
            else:
                k, v = split_key(content)
                if k is None:
                    k = content.strip()
                    v = ""
                if v == "":
                    if pos + 1 < len(rows) and rows[pos+1][0] > indent:
                        child, pos = parse_block(pos+1, rows[pos+1][0])
                        obj[k] = child
                    else:
                        obj[k] = {}; pos += 1
                elif v == "|":
                    vals=[]; pos += 1
                    while pos < len(rows) and rows[pos][0] > indent:
                        vals.append(rows[pos][1]); pos += 1
                    obj[k] = "\n".join(vals)
                else:
                    obj[k] = _aif_scalar(v); pos += 1

        return obj, pos

    root, pos = parse_block(0, rows[0][0])
    if not isinstance(root, dict):
        raise ValueError("AIF 根节点必须是对象。")
    return root


def extract_aif_from_text(text):
    if not isinstance(text, str) or not text.strip():
        raise ValueError("没有提供 AIF 文本。")
    try:
        return parse_aif(text)
    except Exception as first_error:
        m = re.search(r'(?m)^[ \t]*(?:AIF(?:/1(?:\.0)?)?|title\s*:|script\s*$)', text)
        if m:
            try:
                return parse_aif(text[m.start():])
            except Exception:
                pass
        raise ValueError(f"无法解析 AIF:{first_error}")


def _aif_quote(value):
    if value is None:
        return "null"
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    s = str(value)
    if (not s or s.strip() != s or "\n" in s or ":" in s or s.startswith(("[", "{", "#", "-", "`"))):
        return json.dumps(s, ensure_ascii=False)
    return s


def dump_aif(data, indent=0):
    out = ["AIF 1"]

    def emit_value(key, value, level):
        pad = " " * level
        if isinstance(value, dict):
            out.append(f"{pad}{key}:")
            for k, v in value.items():
                emit_value(k, v, level + 2)
        elif isinstance(value, list):
            if not value:
                out.append(f"{pad}{key}: []")
            else:
                out.append(f"{pad}{key}:")
                for item in value:
                    if isinstance(item, dict):
                        first = True
                        for k, v in item.items():
                            if first:
                                if isinstance(v, (dict, list)):
                                    out.append(" " * (level + 2) + f"- {k}:")
                                    if isinstance(v, dict):
                                        for sk, sv in v.items(): emit_value(sk, sv, level + 6)
                                    else:
                                        emit_value("", v, level + 6)
                                else:
                                    out.append(" " * (level + 2) + f"- {k}: {_aif_quote(v)}")
                                first = False
                            else:
                                emit_value(k, v, level + 4)
                    else:
                        out.append(" " * (level + 2) + f"- {_aif_quote(item)}")
        elif isinstance(value, str) and "\n" in value:
            out.append(f"{pad}{key}: |")
            for line in value.splitlines():
                out.append(" " * (level + 2) + line)
        else:
            out.append(f"{pad}{key}: {_aif_quote(value)}")

    if isinstance(data, dict):
        for k, v in data.items():
            emit_value(k, v, 0)
    return "\n".join(out) + "\n"

# ============================================================
# 资产变种 / 出镜资产 / 编号 相关
# ============================================================
def _ensure_group_variants(group, kind):
    if not isinstance(group, dict):
        return group
    group.setdefault("id", f"{kind}_{uuid.uuid4().hex[:6]}")
    group.setdefault("name", "未命名")
    group.setdefault("description", "")
    vs = group.get("variants")
    if not isinstance(vs, list) or not vs:
        v = {"id": group["id"], "label": "默认", "image_url": group.get("im.........完整代码请登录后点击上方下载按钮下载查看

网友评论0