【原创】基于视觉大模型gemma-3-4b实现短视频自动识别内容并生成解说文案


📦 一、整体功能定位

这是一个用于从原始视频自动生成短视频解说内容的自动化工具,包含:

  1. 视频抽帧(可基于画面变化提取关键帧)

  2. 多模态图像识别(每帧图片理解)

  3. 文案生成(大模型生成口语化解说)

  4. TTS语音合成(将文案变为解说音频)

  5. 视频合成(图片 + 音频 or 原视频音频)

  6. 日志记录 + 文案保存

适合用于内容创作、短视频自动剪辑辅助、图文转视频等场景。


🧱 二、代码架构总览

project/
├── main.py               # 主流程逻辑入口
├── config.ini            # 配置文件(模型URL、key等)
├── doubaoTTS.py          # 语音合成模块(豆包接口)
├── output/               # 输出目录(帧图、文案、音频、视频)
│   ├── frames/
│   ├── narration.mp3
│   ├── frame_descriptions.txt
│   ├── narration_script.txt
│   └── final_output_with_original_audio.mp4

⚙️ 三、主要模块说明

1. extract_keyframes_by_diff() – 拆帧模块(图像变化判断)

  • 从视频中按设定帧率提取帧

  • 对比相邻帧内容,过滤重复或近似帧

  • 降低识图调用频率,提高信息效率


 

2. describe_image() – 图像识别模块

  • 使用 OLLAMA 的多模态模型(如 gemma3:4b

  • 将每帧图像转 base64,提交模型识别

  • 输出自然语言描述(中文)


3. generate_script() – 文案生成模块

  • 使用 OpenRouter 接口,调用如 deepseek 模型

  • 以所有帧描述为输入,生成整体解说词

  • 提示词优化为:短视频风格 + 口语化 + 幽默


4. synthesize_audio() – 音频合成模块

  • 调用 doubaoTTS 实现 TTS 中文语音合成

  • 输出 .mp3 格式音频文件


5. compose_video_with_audio() – 视频合成模块

  • 将图像序列按顺序合成为视频

  • 配上:

    • 合成解说音频,或

    • 原视频音频(当前采用)


6. main() – 主流程协调函数

执行顺序如下:

原始视频↓
图像抽帧(基于变化)↓
每帧图像识别 → 收集所有描述↓
文案生成(整体解说)↓
保存描述 + 文案↓
语音合成↓
合成视频( 原视频+音频)

🛠 四、配置文件说明(config.ini

[ollama]
url = http://localhost:11434/api/generate
model = gemma3:4b[openrouter]
url = https://openrouter.ai/api/v1/chat/completions
api_key = sk-xxx
model = deepseek-chat

🔁 五、可扩展方向建议

功能实现思路
加字幕用 MoviePy 在视频帧上叠加文案
多语言支持替换 TTS 模型或调用多语种 GPT 模型
语音识别加入 whisper 抽取原视频语音作为额外提示输入
图像字幕识别加 OCR 模块识别画面内字幕,辅助理解
批量处理视频使用 CLI 参数或脚本批量遍历目录
Web 界面化用 Gradio / Streamlit 构建可视化上传和预览界面

 

直接上代码:

import os
import requests
import json
import base64
import logging
from PIL import Image
from moviepy.editor import VideoFileClip, ImageSequenceClip, AudioFileClip
import doubaoTTS
import configparser
from PIL import Image, ImageChops
import numpy as np# 设置日志
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')# 从 config.ini 加载配置
config = configparser.ConfigParser()
config.read("config.ini", encoding="utf-8")OLLAMA_URL = config.get("ollama", "url")
OLLAMA_MODEL = config.get("ollama", "model")
OPENROUTER_URL = config.get("openrouter", "url")
OPENROUTER_API_KEY = config.get("openrouter", "api_key")
OPENROUTER_MODEL = config.get("openrouter", "model")# 拆帧函数
def extract_keyframes_by_diff(video_path, output_dir, diff_threshold=30, fps=2, max_frames=None):"""从视频中提取关键帧(基于图像变化)参数:video_path: 视频文件路径output_dir: 输出帧目录diff_threshold: 像素差阈值,越小越敏感fps: 初始扫描帧率(例如2即每秒取2帧,再做变化判断)max_frames: 最大输出帧数,默认不限制"""os.makedirs(output_dir, exist_ok=True)clip = VideoFileClip(video_path)last_frame = Nonecount = 0logging.info(f"开始按帧提取,初始采样帧率:{fps},变化阈值:{diff_threshold}")for i, frame in enumerate(clip.iter_frames(fps=fps)):if max_frames and count >= max_frames:breakimg = Image.fromarray(frame)if last_frame:# 灰度差异diff = ImageChops.difference(img.convert('L'), last_frame.convert('L'))diff_score = np.mean(np.array(diff))if diff_score < diff_threshold:continue  # 差异太小,跳过当前帧# 保存关键帧frame_path = os.path.join(output_dir, f"keyframe_{count:05d}.jpg")img.save(frame_path)last_frame = imgcount += 1logging.info(f"关键帧提取完成,共保留 {count} 帧")return sorted([os.path.join(output_dir, f) for f in os.listdir(output_dir) if f.endswith(".jpg")])# 用 gemma3:4b 多模态识图
def describe_image(img_path):logging.info(f"正在识别图片内容:{img_path}")with open(img_path, "rb") as f:image_bytes = f.read()image_b64 = base64.b64encode(image_bytes).decode("utf-8")#logging.info(image_b64)payload = {"model": OLLAMA_MODEL,"messages": [{"role": "user","content": "请用中文描述这张图片的内容,不要输出markdown格式","images": [image_b64]}],"stream": False}response = requests.post(OLLAMA_URL, json=payload)#logging.info(response.json())result = response.json().get('message', {}).get('content', '').strip()logging.info(f"识别结果:{result}")return result# 用 deepseek 生成中文文案(带完整验证与日志)
def generate_script(prompt):logging.info("正在生成视频文案")try:response = requests.post(OPENROUTER_URL,headers={"Authorization": f"Bearer {OPENROUTER_API_KEY}","Content-Type": "application/json"},json={"model": OPENROUTER_MODEL,"messages": [{"role": "user", "content": prompt}],"stream": False})response.raise_for_status()data = response.json()if "choices" in data and data["choices"]:result = data["choices"][0]["message"]["content"]logging.info(f"记录生成的文案内容:{result}")return resultlogging.error("API 响应中没有 choices 字段: %s", data)return "生成失败,API 响应格式异常。"except requests.RequestException as e:logging.error("API 请求失败: %s", e)return "生成失败,请检查网络或服务状态。"# 合成音频
def synthesize_audio(text, output_path):logging.info("正在合成解说音频")doubaoTTS.tts_doubao(text, output_path)logging.info(f"音频保存到:{output_path}")# 合成视频
def compose_video_with_audio(frame_dir, audio_path, output_path, fps=1):logging.info("正在合成最终视频")frame_paths = sorted([os.path.join(frame_dir, f) for f in os.listdir(frame_dir) if f.endswith(".jpg")])clip = ImageSequenceClip(frame_paths, fps=fps)audio = AudioFileClip(audio_path)video = clip.set_audio(audio)video.write_videofile(output_path, codec='libx264', audio_codec='aac')logging.info(f"视频生成成功:{output_path}")def main(video_path, work_dir, fps=1):frame_dir = os.path.join(work_dir, "frames")os.makedirs(frame_dir, exist_ok=True)# 1. 拆帧frame_dir = os.path.join(work_dir, "frames")extract_keyframes_by_diff(video_path, frame_dir, diff_threshold=25, fps=2, max_frames=30)# 2. 多帧识图 + 容错处理descriptions = []frame_files = sorted([f for f in os.listdir(frame_dir) if f.lower().endswith(".jpg")])for i, frame_file in enumerate(frame_files):frame_path = os.path.join(frame_dir, frame_file)try:description = describe_image(frame_path)if description:descriptions.append(f"第{i+1}帧:{description}")else:logging.warning(f"帧 {frame_file} 描述为空,已跳过")except Exception as e:logging.error(f"处理帧 {frame_file} 时发生错误:{e}")continueif not descriptions:logging.error("没有任何帧成功识别,流程中止")return# 3. 保存描述到文案文件full_description = "\n".join(descriptions)description_txt_path = os.path.join(work_dir, "frame_descriptions.txt")with open(description_txt_path, "w", encoding="utf-8") as f:f.write(full_description)logging.info(f"帧内容描述已保存到:{description_txt_path}")# 4. 文案生成prompt = ("你是一个擅长写短视频解说词的创作者,现在请根据以下每一帧画面内容,""用轻松幽默的语气,生成一段通俗易懂、符合短视频风格的中文解说,""要求内容简洁、节奏明快、具有代入感,可以适当加入网络流行语或比喻,""但不要生硬搞笑,也不要重复描述画面,请让内容听起来像是一个博主在视频里自然说话:\n"f"{full_description}"
)script = generate_script(prompt)# 5. 保存文案内容script_txt_path = os.path.join(work_dir, "narration_script.txt")with open(script_txt_path, "w", encoding="utf-8") as f:f.write(script)logging.info(f"生成的文案已保存到:{script_txt_path}")# 6. 合成解说音频audio_path = os.path.join(work_dir, "narration.mp3")synthesize_audio(script, audio_path)# 7. 合成视频(使用原视频音频)logging.info("正在加载原视频音频")original_clip = VideoFileClip(video_path)original_audio = original_clip.audioframe_paths = sorted([os.path.join(frame_dir, f) for f in os.listdir(frame_dir) if f.lower().endswith(".jpg")])image_clip = ImageSequenceClip(frame_paths, fps=fps).set_audio(original_audio)output_video_path = os.path.join(work_dir, "final_output_with_original_audio.mp4")image_clip.write_videofile(output_video_path, codec='libx264', audio_codec='aac')logging.info(f"视频已成功生成:{output_video_path}")# 示例调用
main("2025-05-11_15-12-01_UTC.mp4", "output", fps=1)  # 请取消注释后运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/81489.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

每日算法刷题计划Day5 5.13:leetcode数组3道题,用时1h

11. 26. 删除有序数组中的重复项(简单&#xff0c;双指针) 26. 删除有序数组中的重复项 - 力扣&#xff08;LeetCode&#xff09; 思想: 1.我的思想: 双指针遍历集合储存已有元素 2.官方思想&#xff1a; 题目条件有序数组删除重复元素&#xff0c;所以重复元素都是连续存在…

Transformer 架构在目标检测中的应用:YOLO 系列模型解析

目录 Transformer 架构在目标检测中的应用&#xff1a;YOLO 系列模型解析 一、YOLO 模型概述 二、YOLO 模型的核心架构 &#xff08;一&#xff09;主干网络 &#xff08;二&#xff09;颈部结构 &#xff08;三&#xff09;头部结构 三、YOLO 模型的工作原理 &#xf…

一个完整的项目示例:taro开发微信小程序

前一周完成了一个项目&#xff0c;体测成绩转换的工具&#xff0c;没做记录&#xff0c;。这次计划开发一个地图应用小程序&#xff0c;记录一下。方便给使用的人。 一、申请微信小程序&#xff0c;填写相应的信息&#xff0c;取得开发者ID。这个要给腾讯地图使用的。 二、申…

动态规划-LCR 166.珠宝的最大价值-力扣(LeetCode)

一、题目解析 frame二维矩阵中每个值代表珠宝的价值&#xff0c;现在从左上角开始拿珠宝&#xff0c;只能向右或向下拿珠宝&#xff0c;到达右下角时停止拿珠宝&#xff0c;要求拿的珠宝价值最大。 二、算法解析 1.状态表示 我们想要知道的是到达[i,j]为位置时的最大价值&am…

安装nerdctl和buildkitd脚本命令

#!/bin/bash set -euo pipefail # 检查是否以root权限运行 if [ "$(id -u)" -ne 0 ]; then echo "错误&#xff1a;请使用root权限或sudo运行本脚本" >&2 exit 1 fi # 检测openEuler系统&#xff08;兼容大小写&#xff09; detect_distrib…

实现视频分片上传 OSS

访问 OSS 有两种方式&#xff0c;本文用到的是使用临时访问凭证上传到 OSS&#xff0c;不同语言版本的代码参考&#xff1a; 使用STS临时访问凭证访问OSS_对象存储(OSS)-阿里云帮助中心 1.安装并使用 首先我们要安装 OSS&#xff1a; npm install ali-oss --save 接着我们…

动态规划(3)学习方法论:构建思维模型

引言 动态规划是算法领域中一个强大而优雅的解题方法,但对于许多学习者来说,它也是最难以掌握的算法范式之一。与贪心算法或分治法等直观的算法相比,动态规划往往需要更抽象的思维和更系统的学习方法。在前两篇文章中,我们介绍了动态规划的基础概念、原理以及问题建模与状…

elementplus el-tree 二次封装支持配置删除后展示展开或折叠编辑复选框懒加载功能

本文介绍了基于 ElementPlus 的 el-tree 组件进行二次封装的 TreeView 组件&#xff0c;使用 Vue3 和 JavaScript 实现。TreeView 组件通过 props 接收树形数据、配置项等&#xff0c;支持懒加载、节点展开/收起、节点点击、删除、编辑等操作。组件内部通过 ref 管理树实例&…

2025年渗透测试面试题总结-安恒[实习]安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[实习]安全工程师 一面 1. 自我介绍 2. 前两段实习做了些什么 3. 中等难度的算法题 4. Java的C…

网络编程中的直接内存与零拷贝

本篇文章会介绍 JDK 与 Linux 网络编程中的直接内存与零拷贝的相关知识&#xff0c;最后还会介绍一下 Linux 系统与 JDK 对网络通信的实现。 1、直接内存 所有的网络通信和应用程序中&#xff08;任何语言&#xff09;&#xff0c;每个 TCP Socket 的内核中都有一个发送缓冲区…

TransmittableThreadLocal使用场景

&#x1f680; 为什么要用 TransmittableThreadLocal&#xff1f;一文读懂线程上下文传递问题 在 Java Web 开发中&#xff0c;我们经常用 ThreadLocal 来保存每个请求的用户信息&#xff0c;例如 userId。但当我们使用线程池或异步方法&#xff08;如 Async&#xff09;时&am…

Milvus(24):全文搜索、文本匹配

1 全文搜索 全文搜索是一种在文本数据集中检索包含特定术语或短语的文档&#xff0c;然后根据相关性对结果进行排序的功能。该功能克服了语义搜索的局限性&#xff08;语义搜索可能会忽略精确的术语&#xff09;&#xff0c;确保您获得最准确且与上下文最相关的结果。此外&…

2000 元以下罕见的真三色光源投影仪:雷克赛恩Cyber Pro1重新定义入门级投影体验

当性价比遇上技术瓶颈 在 2000元以下的1080P投影仪&#xff0c;单LCD 技术长期主导。而三色光源的DLP和3LCD真1080P都在4000元以上。 单LCD投影为纯白光光源&#xff0c;依赖CF滤光膜导致光效低下&#xff0c;普遍存在" 色彩失真 " 等问题。数据显示&#xff0c;该价…

Maven 下载安装与配置教程

## 1. Maven 简介 Maven 是一个项目管理和构建自动化工具&#xff0c;主要用于 Java 项目。Maven 可以帮助开发者管理项目的构建、报告和文档&#xff0c;简化项目依赖管理。 ## 2. 下载 Maven 1. 访问 Maven 官方网站 [https://maven.apache.org/download.cgi](https://maven.…

C# 深入理解类(从类的外部访问静态成员)

从类的外部访问静态成员 在前一章中&#xff0c;我们看到使用点运算符可以从类的外部访问public实例成员。点运算符由实 例名、点和成员名组成。 就像实例成员&#xff0c;静态成员也可以使用点运算符从类的外部访问。但因为没有实例&#xff0c;所以最常 用的访问静态成员的方…

Java在微服务架构中的最佳实践:从设计到部署

在2025年的云计算和分布式系统时代&#xff0c;微服务架构已成为构建高可扩展、高可用系统的标准方法&#xff0c;广泛应用于电商、金融和物联网等领域。Java凭借其成熟的生态系统、强大的并发支持和跨平台能力&#xff0c;是微服务开发的首选语言。例如&#xff0c;我们的订单…

文件读取漏洞路径与防御总结

文件读取漏洞路径与防御总结 文件读取漏洞允许攻击者通过路径遍历等手段访问未授权的文件。以下是Linux和Windows系统中常见敏感路径的归纳及防御建议&#xff1a; Linux 系统常见敏感路径 系统关键文件&#xff1a; /etc/passwd&#xff1a;用户账户信息&#xff08;可被用来…

react-router基本写法

1. 创建项目并安装所有依赖 npx create-react-app react-router-pro npm i 2. 安装所有的 react router 包 npm i react-router-dom 3. 启动项目 npm run start router/index.js // 创建路由实例 绑定path elementimport Layout from "/pages/Layout"; import…

uni-app 开发HarmonyOS的鸿蒙影视项目分享:从实战案例到开源后台

最近&#xff0c;HBuilderX 新版本发布&#xff0c;带来了令人兴奋的消息——uni-app 现在支持 Harmony Next 平台的 App 开发。这对于开发者来说无疑是一个巨大的福音&#xff0c;意味着使用熟悉的 Vue 3 语法和开发框架&#xff0c;就可以为鸿蒙生态贡献自己的力量。 前言 作…

纯css实现蜂窝效果

<!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>蜂窝效果</title><style>body {margin: 0…