vue纯静态实现 视频转GIF 功能(附源码)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、实现后的效果
  • 二、使用步骤
    • 1.引入库
    • 2.下载or复制出来js
    • 3. 前端实现
  • 总结


前言

一天一个小demo 今天来一个vue纯静态实现 视频转GIF 功能

上一篇我们讲到了使用html+node.js+sealos实现了一个 多人实时在线聊天的简单小demo ,这次我们来写一个 视频转换成GIF的小功能 使用到的技术为 vue3 + @ffmpeg 很简单。


一、实现后的效果

下面是实现的效果,是我录制视频后转换成GIF的效果,当然下面这个GIF就是转换出来的真实效果哦。
在这里插入图片描述

二、使用步骤

1.引入库

代码如下(初始化一个vue3项目):
我来安装依赖 下面版本号是我现在demo的版本号

 "@ffmpeg/core": "0.12.4","@ffmpeg/ffmpeg": "0.12.7","@ffmpeg/util": "0.12.1",
npm i  @ffmpeg/core@0.12.4
npm i  @ffmpeg/ffmpeg@0.12.7
npm i  @ffmpeg/util@0.12.1

2.下载or复制出来js

我这里把这些给放到public项目里面了

https://unpkg.com/@ffmpeg/core@0.12.4/dist/esm/ffmpeg-core.js
https://unpkg.com/@ffmpeg/core@0.12.4/dist/esm/ffmpeg-core.wasm
https://unpkg.com/@ffmpeg/core@0.12.4/dist/esm/ffmpeg-core.worker.js

在这里插入图片描述

3. 前端实现

<template><div class="video-conversion"><div class="hero-section"><h1>视频转GIF工具</h1><p class="subtitle">简单、快速地将视频转换为高质量GIF动画</p></div><div class="content-wrapper"><div class="upload-section" :class="{ 'has-video': videoUrl }"><div class="upload-area" v-if="!videoUrl" @click="triggerFileInput"><el-icon class="upload-icon"><Upload /></el-icon><p class="upload-text">点击或拖拽视频文件到这里</p><p class="upload-hint">支持 MP4、WebM、MOV 格式 (最大50MB)</p></div><input type="file" @change="handleFileSelect" accept="video/*" ref="fileInput" class="hidden-input"></div><div v-if="videoUrl" class="preview-container"><div class="video-preview"><div class="video-header"><h3>视频预览</h3><el-button type="primary" size="small" @click="triggerFileInput" class="change-video-btn"><el-icon><VideoCamera /></el-icon>更换视频</el-button></div><video :src="videoUrl" controls ref="videoPreview"></video></div><div class="settings-panel"><h3>转换设置</h3><div class="settings-group"><el-form :model="settings" label-position="top"><el-form-item label="GIF宽度"><el-slider v-model="width" :min="100" :max="800" :step="10" show-input /></el-form-item><el-form-item label="帧率 (FPS)"><el-slider v-model="fps" :min="1" :max="30" :step="1" show-input /></el-form-item></el-form></div><el-button type="primary" :loading="isConverting" @click="convertToGif" class="convert-btn">{{ isConverting ? '正在转换...' : '开始转换' }}</el-button></div></div><div v-if="gifUrl" class="result-section"><div class="result-preview"><h3>预览</h3><img :src="gifUrl" alt="转换后的GIF"><div class="action-buttons"><el-button type="success" :href="gifUrl" download="converted.gif" @click="downloadGif">下载GIF</el-button><el-button @click="resetConverter">重新转换</el-button></div></div></div></div><el-alert v-if="error" :title="error" type="error" show-icon class="error-alert" /></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import { FFmpeg } from '@ffmpeg/ffmpeg'
import { fetchFile, toBlobURL } from '@ffmpeg/util'
import { Upload, VideoCamera } from '@element-plus/icons-vue'const ffmpeg = new FFmpeg()
const loaded = ref(false)
const isConverting = ref(false)
const videoUrl = ref('')
const gifUrl = ref('')
const error = ref('')
const width = ref(480)
const fps = ref(10)
const fileInput = ref(null)const load = async () => {try {const baseURL = window.location.origin;// 修改核心文件的加载方式await ffmpeg.load({coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')});loaded.value = true;console.log('FFmpeg 加载成功');} catch (err) {console.error('FFmpeg 加载错误:', err);error.value = '加载转换工具失败:' + err.message;}
};// 修改处理文件选择的方法
const handleFileSelect = (event) => {const file = event.target.files[0]if (!file) returnif (file.size > 50 * 1024 * 1024) {error.value = '文件大小不能超过50MB'return}// 如果已经有之前的视频URL,先释放它if (videoUrl.value) {URL.revokeObjectURL(videoUrl.value)}videoUrl.value = URL.createObjectURL(file)gifUrl.value = ''error.value = ''width.value = 480 // 重置设置fps.value = 10
}// 转换为GIF
const convertToGif = async () => {if (!loaded.value) {error.value = '转换工具尚未加载完成'return}try {isConverting.value = trueerror.value = ''// 写入文件到FFmpeg虚拟文件系统const videoFile = await fetch(videoUrl.value)const videoData = await videoFile.arrayBuffer()await ffmpeg.writeFile('input.mp4', new Uint8Array(videoData))// 执行转换命令await ffmpeg.exec(['-i', 'input.mp4','-vf', `scale=${width.value}:-1:flags=lanczos,fps=${fps.value}`,'-c:v', 'gif','output.gif'])// 读取转换后的文件const data = await ffmpeg.readFile('output.gif')const blob = new Blob([data], { type: 'image/gif' })gifUrl.value = URL.createObjectURL(blob)} catch (err) {error.value = '转换失败:' + err.message} finally {isConverting.value = false}
}const triggerFileInput = () => {fileInput.value.click()
}const downloadGif = () => {const link = document.createElement('a')link.href = gifUrl.valuelink.download = 'converted.gif'document.body.appendChild(link)link.click()document.body.removeChild(link)
}const resetConverter = () => {videoUrl.value = ''gifUrl.value = ''error.value = ''width.value = 480fps.value = 10
}onMounted(() => {load()
})
</script><style scoped>
.video-conversion {max-width: 1200px;margin: 0 auto;padding: 40px 20px;color: #1d1d1f;
}.hero-section {text-align: center;margin-bottom: 60px;
}.hero-section h1 {font-size: 48px;font-weight: 600;margin-bottom: 16px;background: linear-gradient(135deg, #1a1a1a 0%, #4a4a4a 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;
}.subtitle {font-size: 24px;color: #86868b;font-weight: 400;
}.content-wrapper {background: white;border-radius: 20px;box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);overflow: hidden;
}.upload-section {padding: 40px;transition: all 0.3s ease;
}.upload-area {border: 2px dashed #d2d2d7;border-radius: 12px;padding: 40px;text-align: center;cursor: pointer;transition: all 0.3s ease;
}.upload-area:hover {border-color: #0071e3;background: rgba(0, 113, 227, 0.05);
}.upload-icon {font-size: 48px;color: #86868b;margin-bottom: 20px;
}.upload-text {font-size: 20px;color: #1d1d1f;margin-bottom: 8px;
}.upload-hint {font-size: 14px;color: #86868b;
}.hidden-input {display: none;
}.preview-container {display: grid;grid-template-columns: 2fr 1fr;gap: 30px;padding: 30px;background: #f5f5f7;
}.video-preview {background: white;padding: 20px;border-radius: 12px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}.video-preview video {width: 100%;border-radius: 8px;
}.settings-panel {background: white;padding: 30px;border-radius: 12px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}.settings-panel h3 {font-size: 20px;margin-bottom: 24px;color: #1d1d1f;
}.settings-group {margin-bottom: 30px;
}.convert-btn {width: 100%;height: 44px;font-size: 16px;
}.result-section {padding: 40px;background: white;
}.result-preview {text-align: center;
}.result-preview img {max-width: 100%;border-radius: 12px;margin: 20px 0;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}.action-buttons {display: flex;gap: 16px;justify-content: center;margin-top: 24px;
}.error-alert {position: fixed;top: 20px;right: 20px;z-index: 1000;
}.video-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 15px;
}.video-header h3 {margin: 0;font-size: 18px;color: #1d1d1f;
}.change-video-btn {display: flex;align-items: center;gap: 5px;padding: 8px 15px;font-size: 14px;
}.change-video-btn .el-icon {font-size: 16px;
}.has-video .upload-area {border: 2px solid #d2d2d7;margin-bottom: 20px;
}.has-video .upload-area:hover {border-color: #0071e3;
}@media (max-width: 768px) {.preview-container {grid-template-columns: 1fr;}.hero-section h1 {font-size: 32px;}.subtitle {font-size: 18px;}
}
</style>

总结

以上就是今天要讲的内容,本文仅仅简单介绍了@ffmpeg 库 的使用,而@ffmpeg提供了大量能使我们快速便捷地处理视频数据的函数和方法。

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

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

相关文章

嵌入式八股文面试题(二)C语言算法

相关概念请查看文章&#xff1a;C语言概念。 1. 如何实现一个简单的内存池&#xff1f; 简单实现&#xff1a; #include <stdio.h> #include <stdlib.h>//内存块 typedef struct MemoryBlock {void *data; // 内存块起始地址struct MemoryBlock *next; // 下一个内…

【Python】集合

个人主页&#xff1a;GUIQU. 归属专栏&#xff1a;Python 文章目录 1. 集合的创建2. 集合的基本操作2.1 访问集合元素2.2 添加元素2.3 删除元素 3. 集合的数学运算3.1 交集&#xff08;& 或 intersection() 方法&#xff09;3.2 并集&#xff08;| 或 union() 方法&#xf…

Flutter_学习记录_基本组件的使用记录_2

1. PopupMenuButton的使用 代码案例&#xff1a; import package:flutter/material.dart;// ----PopupMemuButtonDemo的案例---- class PopupMemuButtonDemo extends StatefulWidget {const PopupMemuButtonDemo({super.key});overrideState<PopupMemuButtonDemo> crea…

基于java手机销售网站设计和实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

初识计算机网络

从此篇我将开始网络新篇章&#xff01; 1. 网络发展史 最初的计算机之间相互独立存在&#xff0c;每个计算机只能持有自己的数据&#xff0c;数据无法共享。此时的计算机为独立模式 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#x…

PyTorch 中 `torch.cuda.amp` 相关警告的解决方法

在最近的写代码过程中&#xff0c;遇到了两个与 PyTorch 的混合精度训练相关的警告信息。这里随手记录一下。 警告内容 警告 1: torch.cuda.amp.autocast FutureWarning: torch.cuda.amp.autocast(args...) is deprecated. Please use torch.amp.autocast(cuda, args...) i…

【PS 2022】Adobe Genuine Service Alert 弹出

电脑总是弹出Adobe Genuine Service Alert弹窗 1. 不关掉弹窗并打开任务管理器&#xff0c;找到Adobe Genuine Service Alert&#xff0c;并右键进入文件所在位置 2 在任务管理器中结束进程并将文件夹中的 .exe 文件都使用空文档替换掉 3. 打开PS不弹出弹窗&#xff0c;解决&a…

Vue2生命周期面试题

在 Vue 2 中&#xff0c;this.$el 和 this.$data 都是 Vue 实例的属性&#xff0c;代表不同的内容。 1. this.$el this.$el 是 Vue 实例的根 DOM 元素&#xff0c;它指向 Vue 实例所控制的根节点元素。在 Vue 中&#xff0c;el 是在 Vue 实例创建时&#xff0c;指定的根元素&…

unity 安装Entities

因为Entities目前不支持用资源名动态加载资源&#xff01;没错&#xff0c;AssetsBundle或Addressables都不能用于Entities&#xff1b;也就意味着现阶段不能用Entities开发DLC或热更游戏。 Entities必须使用SubScene&#xff0c;而SubScene不能从资源动态加载&#xff0c;路被…

基于 PyTorch 的树叶分类任务:从数据准备到模型训练与测试

基于 PyTorch 的树叶分类任务&#xff1a;从数据准备到模型训练与测试 1. 引言 在计算机视觉领域&#xff0c;图像分类是一个经典的任务。本文将详细介绍如何使用 PyTorch 实现一个树叶分类任务。我们将从数据准备开始&#xff0c;逐步构建模型、训练模型&#xff0c;并在测试…

团结引擎 Shader Graph:解锁图形创作新高度

Shader Graph 始终致力于为开发者提供直观且高效的着色器构建工具&#xff0c;持续推动图形渲染创作的创新与便捷。在团结引擎1.4.0中&#xff0c;Shader Graph 迎来了重大更新&#xff0c;新增多项强大功能并优化操作体验&#xff0c;助力开发者更轻松地实现高质量的渲染效果与…

C# OpenCV机器视觉:模仿Halcon各向异性扩散滤波

在一个充满创意与挑战的图像处理工作室里&#xff0c;阿强是一位热情的图像魔法师。他总是在追求更加出色的图像效果&#xff0c;然而&#xff0c;传统的图像处理方法有时候并不能满足他的需求。 有一天&#xff0c;阿强听说了 Halcon 中的各向异性扩散滤波功能&#xff0c;它…

超详细的数据结构3(初阶C语言版)栈和队列。

文章目录 栈和队列1.栈1.1 概念与结构1.2 栈的实现 2. 队列2.1 概念与结构2.2 队列的实现 总结 栈和队列 1.栈 1.1 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进行插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另…

利用邮件合并将Excel的信息转为Word(单个测试用例转Word)

利用邮件合并将Excel的信息转为Word 效果一览效果前效果后 场景及问题解决方案 一、准备工作准备Excel数据源准备Word模板 二、邮件合并操作步骤连接Excel数据源插入合并域预览并生成合并文档 效果一览 效果前 效果后 场景及问题 在执行项目时的验收阶段&#xff0c;对于测试…

一个基于ESP32S3和INMP441麦克风实现音频强度控制RGB灯带律动的代码及效果展示

一个基于ESP32S3和INMP441麦克风实现音频强度控制RGB灯带律动的代码示例&#xff0c;使用Arduino语言&#xff1a; 硬件连接 INMP441 VCC → ESP32的3.3VINMP441 GND → ESP32的GNDINMP441 SCK → ESP32的GPIO 17INMP441 WS → ESP32的GPIO 18INMP441 SD → ESP32的GPIO 16RG…

用户认证综合实验

实验需求 需求一&#xff1a;根据下表&#xff0c;完成相关配置 需求二&#xff1a;配置DHCP协议&#xff0c;具体要求如下 需求三&#xff1a;防火墙安全区域配置 需求四&#xff1a;防火墙地址组信息 需求五&#xff1a;管理员 为 FW 配置一个配置管理员。要求管理员可以通…

Curser2_解除机器码限制

# Curser1_无限白嫖试用次数 文末有所需工具下载地址 Cursor Device ID Changer 一个用于修改 Cursor 编辑器设备 ID 的跨平台工具集。当遇到设备 ID 锁定问题时&#xff0c;可用于重置设备标识。 功能特性 ✨ 支持 Windows 和 macOS 系统&#x1f504; 自动生成符合格式的…

linux部署node服务

1、安装nvm管理node版本 # 下载、解压到指定目录 wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.39.1.tar.gz tar -zxvf nvm-0.39.0.tar.gz -C /opt/nvm # 配置环境 vim ~/.bashrc~&#xff1a;这是一个路径简写符号&#xff0c;代表当前用户的主目录。在大多数 …

Kotlin实战经验:将接口回调转换成suspend挂起函数

在 Kotlin 协程中, suspendCoroutine 和 suspendCancellableCoroutine 是用于将回调或基于 future 的异步操作转换成挂起函数。 suspendCoroutine 用途:将回调式异步操作转换为可挂起函数 行为: 启动一个新的协程来处理基于回调的操作挂起当前协程,直到调用回调回调负责…

【DeepSeek服务器繁忙,请稍后再试...如何解决?】

DeepSeek服务器繁忙&#xff0c;请稍后再试...如何解决&#xff1f; DeepSeek该咋使用&#xff1f;解决办法&#xff1a;本地桌面工具接下来说下&#xff0c;DeepSeek提示词该咋写&#xff1f; DeepSeek该咋使用&#xff1f; 首先&#xff0c;先说下DeepSeek该咋使用&#xff…