看小姐姐的效果棒极了,写了一个工具,逐帧解析视频转成图片,有没有带上商业思维的小伙伴一起研究下

news2025/2/28 15:23:57

在这里插入图片描述

一个突然的想法,促成了这个项目雏形。

原理是:
上传一个视频,自动将视频每一帧保存成图片 然后前端访问 就能实现如图效果

后端是python/flask 数据库mysql

前端uniapp

项目演示: xt.iiar.cn

后端代码如下:

#学习交流 访问
# https://v.iiar.cn

import os
from flask import Flask, request, jsonify, send_from_directory
from flask_migrate import Migrate
from flask_cors import CORS
from datetime import datetime
import uuid
import cv2

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://xt:EcWzFKyTJHLjpx@127.0.0.1/xt'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
from models import db, XFile, XVideo

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
app.config['UPLOAD_FOLDER'] = os.path.join(BASE_DIR, 'uploads')
db.init_app(app)
migrate = Migrate(app, db)
CORS(app)


@app.route('/')
def index():
    return '欢迎使用XTools'


@app.route('/upload-video', methods=['POST'])
def upload_video():
    if 'video' not in request.files:
        return jsonify({'error': 'No video part'}), 400

    video_file = request.files['video']
    if video_file.filename == '':
        return jsonify({'error': 'No selected video'}), 400

    if video_file:
        base_path = 'files'
        date_path = datetime.now().strftime('%Y%m%d')
        random_dir = str(uuid.uuid4())
        output_dir = os.path.join(base_path, date_path, random_dir)

        # 检查输出目录是否存在,如果不存在,则创建
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)

        video_path = os.path.join(output_dir, video_file.filename)
        video_file.save(video_path)

        new_video = XVideo(
            name=video_file.filename,
            loc_url=f'{str(output_dir)}/{video_file.filename}',

        )
        db.session.add(new_video)
        db.session.commit()
        # 使用OpenCV读取视频
        cap = cv2.VideoCapture(video_path)
        frame_count = 0
        while True:
            ret, frame = cap.read()
            if not ret:
                break
            # 图片命名:视频名+帧数+P.jpg
            frame_name = f"{os.path.splitext(video_file.filename)[0]}_frame_{frame_count:05d}P.jpg"
            frame_path = os.path.join(output_dir, frame_name)
            frame_url = f'{str(output_dir)}/{str(frame_name)}'
            new_img = XFile(
                loc_url=frame_url,
                video_id=new_video.id
            )
            db.session.add(new_img)
            cv2.imwrite(frame_path, frame)
            frame_count += 1
            print('frame_count', frame_count)
            if frame_count % 100 == 0:
                db.session.commit()
        db.session.commit()
        cap.release()
        return jsonify({'message': f'分析完成, {frame_count} 张图片.'}), 200


@app.route('/v_img', methods=['GET', 'POST'])
def get_page():
    data = request.json
    v_id = data.get('v_id')
    page = data.get('page')
    per_page = data.get('per_page')
    video_info = XVideo.query.get(v_id)
    if not video_info:
        return reg_func(500, '', '片子不存在')
    img_s = XFile.query.filter_by(video_id=v_id)
    query_data = img_s.paginate(page=int(page), per_page=int(per_page), error_out=False)
    img_list = []
    domain = request.url_root
    for item in query_data.items:
        item_dict = item.to_dict()
        img_url = f"{domain}{item_dict['loc_url']}"
        img_list.append(img_url)

    video_dict = video_info.to_dict()
    video_dict['url'] = f"{domain}{video_dict['loc_url']}"
    video_dict['img_list'] = img_list

    return reg_func(200, video_dict, '有搞头')


@app.route('/files/<path:date>/<uuid:random_dir>/<filename>')
def uploaded_file(date, random_dir, filename):
    base_path = 'files'
    file_path = os.path.join(base_path, date, str(random_dir))
    return send_from_directory(file_path, filename)


def reg_func(code, data, msg):
    return jsonify({
        'code': code,
        'data': data,
        'msg': msg,
        'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    }), code


if __name__ == '__main__':
    app.run(debug=True, port=6789)

# 迁移脚本
# flask db init    # 初始化迁移目录
# flask db migrate -m "one"  # 创建迁移脚本
# flask db upgrade

# 导出依赖库
# pip freeze > requirements.txt

代码解释

这段代码是一个使用Flask框架实现的简单Web应用程序,旨在处理视频上传、视频帧提取,并将信息存储到MySQL数据库中。下面是代码的主要组成部分及其功能解释:

配置和初始化

  • 数据库配置: 使用SQLAlchemy作为ORM工具来操作MySQL数据库。数据库URI配置为mysql+pymysql://username:password@localhost/dbname格式,其中包含数据库类型、用户名、密码、主机和数据库名。
  • 上传目录配置: 设定一个文件上传的目录,所有上传的视频文件将保存在此目录下。
  • CORS配置: 通过CORS(app)允许跨域资源共享,这是在Web应用中常见的配置,以便于前端应用能够与后端服务进行交互。
  • 数据库迁移: 使用Flask-Migrate进行数据库迁移管理,这是一个基于Alembic的Flask扩展,用于处理SQLAlchemy数据库迁移。

路由和视图函数

  • 首页路由 (/): 返回欢迎信息。
  • 上传视频路由 (/upload-video): 处理视频文件的上传,包括文件存储和视频帧的提取。使用cv2(OpenCV库)读取视频帧,并将每帧作为图片保存到指定目录,同时将视频和图片信息存储到数据库中。
  • 获取视频图片路由 (/v_img): 根据视频ID分页查询关联的图片信息,并返回图片列表。
  • 访问上传文件路由 (/files/<path:date>/<uuid:random_dir>/<filename>): 提供一个静态文件访问的路由,允许用户通过URL直接访问上传的文件。

辅助函数

  • reg_func: 一个用于构建统一响应格式的辅助函数,包含响应码、数据、消息和时间戳。

运行和数据库迁移命令

  • 运行应用: 使用app.run(debug=True, port=6789)启动Flask应用,开启调试模式,并设置运行端口为6789。
  • 数据库迁移命令: 提供了Flask-Migrate的使用命令,用于初始化迁移目录、创建迁移脚本和升级数据库。

总结

这段代码是一个视频处理和存储的Web应用程序的后端部分。它接收上传的视频文件,提取视频帧,保存帧为图片,并将视频及图片信息存储到MySQL数据库中。同时,它也提供了API接口,用于查询和访问这些视频和图片资源。

数据模型如下:

#学习交流 访问
# https://v.iiar.cn

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

db = SQLAlchemy()


class XFile(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    video_id = db.Column(db.Integer, comment='视频id')
    create_time = db.Column(db.DateTime, default=datetime.now, comment='文件上传时间')
    loc_url = db.Column(db.String(256), comment='本地路径')

    def to_dict(self):
        return {
            'id': self.id,
            'video_id': self.video_id,
            'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time else None,
            'loc_url': self.loc_url,
        }


class XVideo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    create_time = db.Column(db.DateTime, default=datetime.now, comment='文件上传时间')
    name = db.Column(db.String(256), comment='视频名称')
    loc_url = db.Column(db.String(256), comment='本地路径')

    def to_dict(self):
        return {
            'id': self.id,
            'create_time': self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time else None,
            'name': self.name,
            'loc_url': self.loc_url,
        }

模型解释

这段代码定义了两个Python类,XFileXVideo,它们都继承自db.Model,这是Flask-SQLAlchemy扩展提供的,用于在Flask应用中操作数据库的ORM(Object-Relational Mapping,对象关系映射)模型。这些类定义了两个数据库模型,用于存储文件和视频的相关信息。

XFile 类

  • 作用: 代表一个文件实体,用于存储视频帧图片的信息。
  • 属性:
    • id: 文件的唯一标识符,设为主键。
    • video_id: 关联的视频ID,表示这个文件属于哪个视频。
    • create_time: 文件的上传时间,默认为创建记录的当前时间。
    • loc_url: 文件在本地系统中的路径。
  • 方法:
    • to_dict(): 将文件对象的信息转换为字典格式,便于将数据序列化为JSON格式进行API响应。日期时间通过strftime格式化为易读的字符串。

XVideo 类

  • 作用: 代表一个视频实体,用于存储上传的视频信息。
  • 属性:
    • id: 视频的唯一标识符,设为主键。
    • create_time: 视频的上传时间,默认为创建记录的当前时间。
    • name: 视频的名称。
    • loc_url: 视频在本地系统中的路径。
  • 方法:
    • to_dict(): 类似于XFile的方法,将视频对象的信息转换为字典格式,用于API响应。

总结

通过使用Flask-SQLAlchemy,这些类不仅定义了数据库表的结构,还提供了与数据库进行交互的方法。例如,可以直接通过这些类的实例来创建、查询、更新或删除数据库中的记录,而无需编写原始的SQL语句。这大大简化了数据库操作,使得开发者可以更专注于应用逻辑的实现。

这两个模型通过video_id属性相互关联,XFilevideo_id作为外键,指向XVideoid,表明多个XFile(视频帧图片)可以关联到一个XVideo(视频)上。这种关系在数据库设计中被称为“一对多”关系。

前端文件 uniapp写的,请求方法封装了一下 如果需要的话可以找我拿

<!--学习交流 访问-->
<!-- https://v.iiar.cn -->

<template>
	<view class="content" style="user-select:text">
		<!-- {{image_list.data.url}} -->
		<view class="" style="margin: 40px;">
			<video :src="image_list.data.url" v-if="image_list.data" style="width: 300px;height: 600px;"></video>
		</view>

		<!-- {{image_list.data.img_list}} -->
		<view class="" style="display: flex;width: 100%;flex-wrap: wrap;" v-if="image_list.data">
			<view v-for="(item,index) in image_list.data.img_list" :key="index" @click="look_images(index)">
				<image :src="item" mode="" class="image_css"></image>
			</view>
		</view>


	</view>
</template>

<script>
	import {
		get_img_list
	} from '@/api/url.js'
	export default {
		data() {
			return {
				title: 'Hello',
				image_list: [],
				v_id: 2,
				page: 1,
				per_page: 50

			}
		},
		onLoad() {
			this.get_img_list_func()
		},
		onReachBottom() {
			this.page += 1
			this.get_more_img_list_func()
		},
		methods: {
			async get_img_list_func() {
				const data = await get_img_list({
					v_id: this.v_id,
					page: this.page,
					per_page: this.per_page
				})
				this.image_list = data

			},
			async get_more_img_list_func() {
				uni.showLoading({})
				const data = await get_img_list({
					v_id: this.v_id,
					page: this.page,
					per_page: this.per_page
				})
				console.log('data', data)
				uni.hideLoading()
				// 检查data.img_list的长度
				if (data.data.img_list && data.data.img_list.length > 0) {
					// 如果有数据,将其追加到this.image_list.data.img_list中
					this.image_list.data.img_list.push(...data.data.img_list);
				} else {
					// data.img_list长度为0时的处理方案
					// 例如:显示提示信息,或执行其他逻辑
					console.log('没有新的图片数据添加');
					this.page -= 1
					uni.showToast({
						title: '到底了!真的'
					})
				}

			},

			look_images(index) {
				uni.previewImage({

					current: index,
					urls: this.image_list.data.img_list
				});
			}

		}
	}
</script>

<style>
	.image_css {
		width: 450rpx;
		height: 800rpx;
		margin: 10px;
		border-radius: 20px;
		transition: transform 0.2s;
	}

	.image_css:hover {
		transform: scale(1.1);
		/* 放大1.2倍 */
	}

	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		background-color: #1c1c1c;
		min-height: 100vh;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

代码解释

这段代码是一个使用Vue框架开发的uni-app页面模板,主要功能是显示一个视频和它关联的一系列图片。它从后端API获取视频和图片列表,支持触底加载更多图片,并提供图片预览功能。下面是代码的详细解释:

模板部分 (<template>)

  • 视频展示: 使用<video>标签显示从image_list.data.url获取的视频。如果image_list.data存在,则展示视频。
  • 图片列表展示: 通过v-for循环遍历image_list.data.img_list,显示所有图片。每个图片都可以点击,点击后会调用look_images方法进行预览。
  • user-select:text样式允许用户选择文本,可能用于调试或特定设计需求。

脚本部分 (<script>)

  • 数据部分: 包含image_list对象存储视频和图片列表,v_id表示视频的ID,pageper_page用于分页加载图片。
  • 生命周期钩子:
    • onLoad: 页面加载时调用get_img_list_func方法,获取视频及其关联的图片列表。
    • onReachBottom: 页面滚动到底部时自动触发,实现触底加载更多图片的功能。
  • 方法:
    • get_img_list_func: 获取初始视频和图片列表。
    • get_more_img_list_func: 加载更多图片,当滚动到页面底部时被调用。
    • look_images: 实现图片预览功能,uni.previewImage用于打开uni-app的图片预览界面。

样式部分 (<style>)

  • 定义.image_css类为图片添加样式,包括尺寸、边距、圆角以及过渡效果,实现鼠标悬停时的放大效果(注意:在uni-app中,:hover伪类主要用于Web平台)。
  • .content类设置了页面的布局和背景色,使内容垂直居中显示,并填充整个视口高度。

功能和特性

  • 视频和图片的动态加载: 通过异步请求从后端获取数据,并动态渲染到页面上。
  • 分页和触底加载: 支持通过分页参数加载更多图片,提高页面加载效率和用户体验。
  • 图片预览: 提供点击图片进行全屏预览的功能,增强了用户交互体验。

注意事项

  • uni.showLoading, uni.hideLoading, 和 uni.showToast是uni-app框架提供的API,用于显示加载提示和消息提示。
  • 由于uni-app支持编译到多个平台(如Web、微信小程序、App等),某些功能(如:hover伪类效果)在非Web平台上可能有所不同。开发时需考虑目标平台的特性和限制。

如果考虑手机版本的话
图片尺寸可以改为:
width: 300rpx;
height: 530rpx;
在这里插入图片描述
只是这样 电脑就会小一点
在这里插入图片描述
不过没关系,做了点击放大,左右滑动

在这里插入图片描述
但是有个小bug 就是

这个方法
uni.previewImage

在滑动到最后一张的时候 无法获取下一页的内容, 也就是网页触底加载更多

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

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

相关文章

从零开始学习Netty - 学习笔记 - NIO基础 - ByteBuffer: 简介和基本操作

NIO基础 1.三大组件 1.1. Channel & Buffer Channel 在Java NIO&#xff08;New I/O&#xff09;中&#xff0c;“Channel”&#xff08;通道&#xff09;是一个重要的概念&#xff0c;用于在非阻塞I/O操作中进行数据的传输。Java NIO提供了一种更为灵活和高效的I/O处理方…

原创java开源项目发布maven全球中央仓库详细过程示范和遇到的问题解决办法

文章目录 java项目上传到maven全球中央仓库&#xff08;原创个人开源项目发布maven中央仓库详细过程示范&#xff09;需求背景第一步 注册sonatype账号第二步 登录sonatype账号并申请新建项目第三步 准备个人GPG数字签名并发布到ubuntu第四步 准备maven配置第五步 修改项目配置…

【软考高级信息系统项目管理师--第二十四章:法律法规与标准规范】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;软考高级–信息系统项目管理师 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 第二十四章&#xff1a;法律法规与标准规范 商标专利法网络安全法 商标专利法 授…

【Linux | C++ 】基于环形队列的多生产者多消费者模型(Linux系统下C++ 代码模拟实现)

阅读导航 引言一、生产者消费者模型二、环形队列简介三、基于环形队列的生产者消费者模型&#xff08;C 代码模拟实现&#xff09;⭕Makefile文件⭕ . h 头文件✅sem.hpp✅ringQueue.hpp ⭕ . cpp 文件✅testMain.cpp 温馨提示 引言 在上一篇文章中&#xff0c;我们深入探讨了…

408计算机网络--基础概论

学习计算机网络走以前需要首先明白一个大的概念&#xff0c;计算机网络通常分为通信子网&#xff08;实现数据通信&#xff09;和资源子网&#xff08;实现资源共享/数据处理&#xff09;七层妖塔 计算机网络&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0…

【Leetcode 2415】反转二叉树的奇数层 —— 回溯法 | BFS

2415. 反转二叉树的奇数层 给你一棵 完美 二叉树的根节点root&#xff0c;请你反转这棵树中每个 奇数 层的节点值。 例如&#xff0c;假设第 3 层的节点值是[2,1,3,4,7,11,29,18]&#xff0c;那么反转后它应该变成[18,29,11,7,4,3,1,2]。 反转后&#xff0c;返回树的根节点。…

【Redis,Java】Redis的两种序列化方式—nosql数据库

redis和mysql的区别&#xff1a; redis是属于nosql的数据库&#xff0c;而mysql是属于sql数据库&#xff0c;redis是属于nosql数据库。mysql是存储在磁盘中的&#xff0c;redis是存储在内存中的&#xff0c;所以redis的读取书读快。这里所说的redis代表nosql&#xff0c;而mysq…

【elk查日志 elastic(kibana)】

文章目录 概要具体的使用方式一&#xff1a;查找接口调用历史二&#xff1a;查找自己的打印日志三&#xff1a;查找错误日志 概要 每次查日志&#xff0c;我都需要别人帮我&#xff0c;时间长了总觉得不好意思&#xff0c;所以这次下定决心好好的梳理一下&#xff0c;怎么查日…

更改WordPress作者存档链接author和用户名插件Change Author Link Structure

WordPress作者存档链接默认情况为/author/Administrator&#xff08;用户名&#xff09;&#xff0c;为了防止用户名泄露&#xff0c;我们可以将其改为/author/1&#xff08;用户ID&#xff09;&#xff0c;具体操作可参考『如何将WordPress作者存档链接中的用户名改为昵称或ID…

【大模型 幻觉】CRAG:改进传统的 RAG,增加纠正机制提升生成文本的准确性

CRAG&#xff1a;改进传统的 RAG&#xff0c;增加纠正机制提升生成文本的准确性 提出背景CRAG 框架轻量级检索评估器大规模网络搜索分解再重组算法 提出背景 论文&#xff1a;https://arxiv.org/abs/2401.15884 代码&#xff1a;https://github.com/HuskyInSalt/CRAG 大型语言…

适合tiktok运营的云手机需要满足什么条件?

TikTok作为一款全球热门的社交媒体平台&#xff0c;具有无限的市场潜力。然而&#xff0c;卖家在运营过程中常常会面临到视频0播、账号被降权、限流等问题&#xff0c;甚至可能因为多人同时使用一个IP而导致封号的风险。为了规避这些问题&#xff0c;越来越多的卖家将目光投向了…

k8s学习(RKE+k8s+rancher2.x)成长系列之简配版环境搭建(四)之Helm及cert-manager安装

安装Helm(三台都安装) 下载helm安装包并加入执行目录 tar zxf helm-v3.2.4-linux-amd64.tar.gz cd linux-amd64 cp helm /usr/bin/ helm version添加rancher稳定版仓库(三台都安装) helm repo add rancher-stable http://rancher-mirror.oss-cn-beijing.aliyuncs.com/serve…

8.2 新特性 - 透明的读写分离

文章目录 前言1. 安装部署1.1 下载安装包1.2 MySQL Shell1.3 配置 MySQL 实例1.4 启动 ReplicaSet1.5 启动 8.2 Router 2. 测试路由总结 前言 MySQL 8.0 官方推出过一个高可用方案 ReplicaSet 主要由 Router、MySQL Shell、MySQL Server 三个组件组成。 MySQL Shell 负责管理…

qt-双臂SCARA机器人动画

qt-双臂SCARA机器人动画 一、演示效果二、核心程序三、下载链接 在Qt opengl中完成的双臂SCARA机器人的简单模拟。 一、演示效果 二、核心程序 #include "glwidget.h"#include <GL/glu.h>GLWidget::GLWidget(QWidget *parent) :QGLWidget(parent),pitch(30.…

Java集合框架-1

目录 List集合 常见方法 迭代器&#xff08;Iterator&#xff09; List集合特有方法 List 的特点 创建 List 遍历List Java集合框架是Java编程语言提供的各种数据结构和算法的实现。它提供了不同类型的集合类&#xff0c;如列表(List)、集(Set)、映射(Map)等&#xff0c…

iOS通过p12导出公钥失败

通过p12文件导出证书crt文件一直报invalid password错误。 adminJiaozl Downloads % openssl pkcs12 -in ce.p12 -clcerts -nokeys -out MyCertificate.crt Enter Import Password: Mac verify error: invalid password?解决&#xff1a; 在 https://developer.apple.com/acc…

MongoDB文档插入

文章目录 MongoDB文档插入对比增删改查文档插入 MongoDB写安全机制非确认式写入 MongoDB文档查询参数说明查询操作符比较查询操作符逻辑查询操作符元素查询操作符数组查询操作符 模糊查询区别:$regex操作符中的option选项 MongoDB游标介绍游标函数手动迭代游标示例游标介绍 Mon…

AMC8美国数学思维竞赛2000-2024年真题练一练,你能做对几道?

为帮助备考AMC8美国数学竞赛的孩子们了解比赛的题型&#xff0c;掌握相关的知识&#xff0c;我会陆续分享2000-2024年的比赛真题&#xff0c;每道题都有解析&#xff0c;帮助孩子们不但会做题&#xff0c;还掌握背后的知识&#xff0c;从而达到举一反三的效果&#xff0c;提升比…

QT-LCD模拟显示

QT-LCD模拟显示 一、演示效果二、关键程序三、下载链接 一、演示效果 二、关键程序 #include "lcd_widget.h" #include <QDebug> #include <QPainter>LCDWidget::LCDWidget(QWidget *parent) : QWidget(parent),display(nullptr), display_char_buffer(…

LeetCode JS专栏刷题笔记(二)

一、前言 LeetCode - JavaScript 专栏刷题笔记第二篇。 第一篇刷题笔记详见&#xff1a;LeetCode JS专栏刷题笔记&#xff08;一&#xff09; 二、算法题目 1. 复合函数 LeetCode地址&#xff1a;2629. 复合函数 请你编写一个函数&#xff0c;它接收一个函数数组 [f1, f2, …