python+ffmpeg实现类似剪映的可视化剪辑软件代码bfwcutmaster
代码语言:python
所属分类:其他
版本1(最新):python+ffmpeg实现类似剪映的可视化剪辑软件代码bfwcutmaster
代码描述:python+ffmpeg实现类似剪映的可视化剪辑软件代码bfwcutmaster,采用html作为界面,ffmpeg实现最后的导出渲染,支持视频、音频、贴图、字幕多轨道,支持预览。
代码标签: python ffmpeg 类似 剪映 可视化 剪辑 软件 代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Python + FFmpeg 可视化时间轴视频剪辑器(增强版)
运行: python video_editor.py
依赖: pip install flask ; 系统需安装 ffmpeg / ffprobe 并加入 PATH
"""
import os
import re
import json
import uuid
import base64
import shutil
import subprocess
import threading
import time
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory, Response
# ------------------------------------------------------------------
# 目录初始化
# ------------------------------------------------------------------
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.join(BASE_DIR, "workspace")
PROJECTS_DIR = os.path.join(WORKSPACE, "projects")
MEDIA_DIR = os.path.join(WORKSPACE, "media")
EXPORTS_DIR = os.path.join(WORKSPACE, "exports")
THUMBS_DIR = os.path.join(WORKSPACE, "thumbnails")
for d in (PROJECTS_DIR, MEDIA_DIR, EXPORTS_DIR, THUMBS_DIR):
os.makedirs(d, exist_ok=True)
app = Flask(__name__)
EXPORT_STATUS = {} # project_id -> {status, progress, output/message}
ALLOWED_VIDEO = {'.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv', '.m4v'}
ALLOWED_AUDIO = {'.mp3', '.wav', '.aac', '.flac', '.m4a', '.ogg'}
ALLOWED_IMAGE = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
# ==================================================================
# FFmpeg 工具函数
# ==================================================================
def run_cmd(cmd, timeout=None):
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout)
def ffprobe_info(path):
cmd = ["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", path]
try:
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
data = json.loads(out.decode("utf-8", errors="ignore"))
except Exception:
return {"duration": 0, "width": 0, "height": 0, "has_audio": False, "has_video": False}
duration = float(data.get("format", {}).get("duration", 0) or 0)
width = height = 0
has_audio = has_video = False
for s in data.get("streams", []):
if s.get("codec_type") == "video" and not has_video:
width = int(s.get("width") or 0)
height = int(s.get("height") or 0)
has_video = True
if not duration:
duration = float(s.get("duration", 0) or 0)
if s.get("codec_type") == "audio":
has_audio = True
return {"duration": duration, "width": width, "height": height,
"has_audio": has_audio, "has_video": has_video}
def detect_media_type(filename):
ext = os.path.splitext(filename)[1].lower()
if ext in ALLOWED_VIDEO:
return "video"
if ext in ALLOWED_AUDIO:
return "audio"
if ext in ALLOWED_IMAGE:
return "image"
return "unknown"
def generate_thumbnail(src_path, out_path, media_type, duration=0):
try:
if media_type == "video":
t = min(1.0, duration / 2) if duration else 0.3
run_cmd(["ffmpeg", "-y", "-ss", str(t), "-i", src_path,
"-frames:v", "1", "-vf", "scale=160:-1", out_path], timeout=30)
elif media_type == "image":
run_cmd(["ffmpeg", "-y", "-i", src_path,
"-vf", "scale=160:-1", "-frames:v", "1", out_path], timeout=30)
except Exception as e:
print("生成缩略图失败:", e)
# ==================================================================
# 项目管理
# ==================================================================
def project_path(pid):
return os.path.join(PROJECTS_DIR, f"{pid}.json")
def load_project(pid):
p = project_path(pid)
if not os.path.exists(p):
return None
with open(p, "r", encoding="utf-8") as f:
return json.load(f)
def save_project(pid, data):
with open(project_path(pid), "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def list_projects():
result = []
for fn in os.listdir(PROJECTS_DIR):
if fn.endswith(".json"):
try:
with open(os.path.join(PROJECTS_DIR, fn), "r", encoding="utf-8") as f:
d = json.load(f)
result.append({
"id": d.get("id"),
"name": d.get("name"),
"created": d.get("created"),
"modified": d.get("modified"),
"resolution": d.get("resolution"),
})
except Exception:
continue
result.sort(key=lambda x: x.get("modified", ""), reverse=True)
return result
def default_project(name):
pid = uuid.uuid4().hex[:12]
now = datetime.now().isoformat()
return {
"id": pid,
"name": name,
"created": now,
"modified": now,
"resolution": {"width": 1280, "height": 720},
"fps": 30,
"tracks": [
{"id": uuid.uuid4().hex[:8], "type": "video", "name": "视频轨道1",
"clips": [], "muted": False, "hidden": False},
{"id": uuid.uuid4().hex[:8], "type": "audio", "name": "音频轨道1",
"clips": [], "muted": False, "hidden": False},
{"id": uuid.uuid4().hex[:8], "type": "subtitle", "name": "字幕轨道1",
"clips": [], "muted": False, "hidden": False},
],
"media": []
}
# ==================================================================
# 导出引擎(构建 ffmpeg filter_complex 并执行)
# ==================================================================
def escape_drawtext(text):
text = text.replace("\\", "\\\\")
text = text.replace(":", "\\:")
text = text.replace("'", "\u2019")
text = text.replace("\n", " ")
return text
def escape_fontpath(path):
"""drawtext 的 fontfile 参数值里冒号和反斜杠需要转义"""
path = path.replace("\\", "/") # 统一用正斜杠,规避 Windows 反斜杠被当作滤镜分隔符
path = path.replace(":", "\\:") # 转义盘符冒号 C:
return path
_CACHED_FONT = None
def find_system_font():
"""自动探测一个可用的中文字体文件,找不到则返回 None"""
global _CACHED_FONT
if _CACHED_FONT is not None:
return _CACHED_FONT
candidates = []
if os.name == "nt":
win_fonts = os.path.join(os.environ.get("WINDIR", "C:/Windows"), "Fonts")
candidates += [
os.path.join(win_fonts, "msyh.ttc"), # 微软雅黑
os.path.join(win_fonts, "msyhbd.ttc"),
os.path.join(win_fonts, "simhei.ttf"), # 黑体
os.path.join(win_fonts, "simsun.ttc"), # 宋体
os.path.join(win_fonts, "arial.ttf"),
]
elif "darwin" in os.sys.platform:
candidates += [
"/System/Library/Fonts/PingFang.ttc",
"/System/Library/Fonts/STHeiti Light.ttc",
"/Library/Fonts/Arial Unicode.ttf",
]
else: # linux
candidates += [
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
]
for c in candidates:
if os.path.exists(c):
_CACHED_FONT = c
return.........完整代码请登录后点击上方下载按钮下载查看














网友评论0