python+html实现手动版ai短剧生成工具BfwStoryForge代码

代码语言:python

所属分类:其他

代码描述:python+html实现手动版ai短剧生成工具BfwStoryForge代码,从剧本分镜图片及视频等生成全程提供每一步的提示词和参考图片,去给免费的ai大模型生成需要的数据,粘贴进去一步一步生成ai短剧,图片和视频也是通过ai提示词复制生成,最后合并在一起,就是一个ai无限时长的短剧。注意,此版本无 api,完全通过复制提示词来操作,方便管理。

代码标签: python html 手动版 ai 短剧 生成 工具 BfwStoryForge 代码

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AI有声视频生成工作流系统 - 纯手动离线版
精简版:去除造型与变体,增强构图与位置描述,自动注入【图X】参考标记
"""

import os
import re
import json
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__)

# ============================================================
# 辅助工具
# ============================================================
def extract_json_from_text(text):
    """从 LLM 返回的文本中提取 JSON(去掉 markdown 标记等)"""
    text = text.strip()
    try:
        return json.loads(text)
    except:
        pass
    match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
    if match:
        try:
            return json.loads(match.group(1))
        except:
            pass
    raise ValueError("无法从提供的文本中解析出合法的 JSON 格式,请检查大模型的回复。如果大模型输出未完成(被截断),请让它继续输出完毕。")

def _normalize_scene_characters(scene: dict):
    chars = scene.get("characters")
    char_ids = scene.get("character_ids")
    if isinstance(chars, list) and chars:
        scene["character_ids"] = chars[:]
    elif isinstance(char_ids, list) and char_ids:
        scene["characters"] = char_ids[:]
    elif scene.get("speaking_character"):
        scene["characters"] = [scene.get("speaking_character")]
        scene["character_ids"] = [scene.get("speaking_character")]
    else:
        scene["characters"] = []
        scene["character_ids"] = []
    
    for key in["props", "prop_ids", "scenes", "scene_ids"]:
        if not isinstance(scene.get(key), list):
            scene[key] =[]
    scene.setdefault("transition_note", "")
    scene.setdefault("continuity_note", "")

def _normalize_script_structure(script: dict):
    for key in ["characters", "props", "scenes", "segments"]:
        script.setdefault(key,[])
    
    if "segment_outlines" in script and not script["segments"]:
        script["segments"] = script.pop("segment_outlines")
        
    return script

def save_base64_media(b64_str, output_path: Path):
    if "," in b64_str:
        b64_str = b64_str.split(",")[1]
    with open(output_path, "wb") as f:
        f.write(base64.b64decode(b64_str))
    return str(output_path)

def _merge_with_ffmpeg(video_clips, output_path):
    import subprocess
    list_file = WORK_DIR / f"concat_{uuid.uuid4().hex[:8]}.txt"
    with open(str(list_file), 'w', encoding='utf-8') as f:
        for clip in video_clips:
            f.write(f"file '{os.path.abspath(clip)}'\n")
    cmd =['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', str(list_file), '-c', 'copy', output_path]
    result = subprocess.run(cmd, capture_output=True, timeout=120)
    if result.returncode != 0:
        cmd =['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', str(list_file),
               '-c:v', 'libx264', '-preset', 'fast', '-pix_fmt', 'yuv420p',
               '-c:a', 'aac', '-b:a', '128k', output_path]
        subprocess.run(cmd, capture_output=True, timeout=120)
    list_file.unlink(missing_ok=True)
    return output_path

# ============================================================
# 项目状态管理
# ============================================================
projects = {}

def load_projects():
    global projects
    if PROJECTS_FILE.exists():
        try:
            with open(PROJECTS_FILE, "r", encoding="utf-8") as f:
                projects = json.load(f)
        except Exception as e:
            print("Failed to load projects:", e)
            projects = {}

def save_projects():
    with open(PROJECTS_FILE, "w", encoding="utf-8") as f:
        json.dump(projects, f, ensure_ascii=False, indent=2)

def get_project_dir(project_id):
    d = WORK_DIR / project_id
    d.mkdir(exist_ok=True)
    return d

def get_project(project_id):
    if project_id not in projects:
        projects[project_id] = {
            "id": project_id, "theme": "", "aspect_ratio": "16:9", "visual_style": "realistic",
            "total_duration": 300, "script": None, "characters": [], "props": [], "scenes": [],
            "segments":[], "final_video_url": None, "status": "init",
            "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        save_projects()
    return projects[project_id]

# ============================================================
# Flask 路由
# ============================================================
@app.route("/")
def index():
    return HTML_PAGE

@app.route("/workspace/<project_id>/<path:filename>")
def serve_project_file(project_id, filename):
    return send_from_directory(str(WORK_DIR / project_id), filename)

@app.route("/api/projects", methods=["GET"])
def api_list_projects():
    return jsonify(list(projects.values()))

@app.route("/api/project/<project_id>", methods=["GET"])
def api_get_project(project_id):
    if project_id in projects:
        return jsonify(projects[project_id])
    return jsonify({"error": "Project not found"}), 404

@app.route("/api/save_manual_script", methods=["POST"])
def api_save_manual_script():
    data = request.json
    project_id = data.get("project_id") or str(uuid.uuid4().hex[:8])
    json_text = data.get("json_text", "")
    
    try:
        script = extract_json_from_text(json_text)
    except Exception as e:
        return jsonify({"error": str(e)}), 400

    project = get_project(project_id)
    project["theme"] = data.get("theme", "")
    project["aspect_ratio"] = data.get("aspect_ratio", "16:9")
    project["visual_style"] = data.get("style", "realistic")
    project["total_duration"] = data.get("duration", 300)
    
    script = _normalize_script_structure(script)
    project["script"] = script
    project["characters"] = script.get("characters", [])
    project["props"] = script.get("props",[])
    project["scenes"] = script.get("scenes", [])

    segments =[]
    raw_segments = script.get("segments", [])
    for i, seg in enumerate(raw_segments):
        shots =[]
        for shot in seg.get("shots",[]):
            _normalize_scene_characters(shot)
            shots.append(shot)
            
        segments.append({
            "segment_id": seg.get("segment_id", i + 1),
            "segment_title": seg.get("segment_title", f"片段{i + 1}"),
            &qu.........完整代码请登录后点击上方下载按钮下载查看

网友评论0