更新记录
1.0.3(2025-09-22)
适配uniapp端
1.0.2(2024-06-16)
修改文件读取API失败函数的回调参数类型
1.0.1(2024-04-27)
增加对图片的编辑
查看更多
平台兼容性
uni-app(4.66)
Vue2 |
Vue2插件版本 |
Vue3 |
Vue2插件版本 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
Android插件版本 |
iOS |
鸿蒙 |
√ |
1.0.3 |
√ |
1.0.3 |
- |
- |
- |
- |
7.0 |
1.0.3 |
- |
- |
微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
快应用-华为 |
快应用-联盟 |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
uni-app x(4.66)
Chrome |
Safari |
Android |
Android插件版本 |
iOS |
鸿蒙 |
微信小程序 |
- |
- |
7.0 |
1.0.0 |
- |
- |
- |
xwq-ffmpeg
文档说明
插件集成ffmpeg实现安卓端对视频、音频、图片的编辑功能
- 功能支持
- 视频裁剪
- 视频合并 (注:同种类型文件合并)
- 视频格式转换
- 音频合并 (注:同种类型文件合并)
- 音频格式转换
- 获取媒体信息
- 图片裁剪
- 图片格式转换
- 图片旋转
- ....更多功能可查阅官网
UNI-APPx用方式
<template>
<view>
<video :src="path" :autoplay="autoplay" :controls="true" :style="{height:'250px',width:'100%'}" :object-fit="objectFit" class="`video_1`" >
</video>
</view>
<view class="btn-wrap" :style="{marginBottom:'15px'}">
<button class="btn" @click='executeFFmpeg'>{{title}}</button>
<button class="btn" @click='videoMerge'>视频合并</button>
<button class="btn" @click='changeVideoType'>视频格式转换</button>
<button class="btn" @click='audioMerge'>音频合并</button>
<button class="btn" @click='changeAudioType'>音频格式转换</button>
<button class="btn" @click='getVideoInfo'>获取媒体信息</button>
<button class="btn" @click='imageRota'>图片旋转</button>
<button class="btn" @click='imageCrop'>图片裁剪</button>
<button class="btn" @click='getVideoInfo'>获取媒体信息</button>
</view>
<view class="content-wrap">
<text :style="{color:'red'}">媒体信息输出:</text>
<view class="content">
<textarea :value="content"></textarea>
</view>
</view>
<image :src="imagePath" :style="{width:'100%',height:'auto'}"></image>
</template>
<script setup>
import {useExecuteFFmpeg,
FfmpegType,
getMediaInfo,
getLocalPath,
TaskStartCallbackOpt,
StatisticsCallBackOpt,
ffmpegVideoMerge,
MergeType,
convertTypeV
} from '../../uni_modules/xwq-ffmpeg';
const title=ref('执行命令')
const objectFit='cover'
const autoplay=ref(false)
const content=ref('')
const imagePath=ref('')
const path=ref('/static/test002.mp4')
const executeFFmpeg=async ()=>{
// -vf "scale=1080x1080"
// libx265 mpeg4
let result=await getLocalPath('/static/test.mp4');
let outPath=`/storage/emulated/0/DCIM/Camera/video_${Date.now()}.mp4`
let cmd=`-i ${result} -c:v libx265 -s 540x280 -y ${outPath}`;
useExecuteFFmpeg({
cmd,
success:()=>{
console.log('成功回调====')
path.value=outPath
autoplay.value=true
},
cancel:()=>{
console.log('取消执行回调===')
},
fail:()=>{
console.log('失败回调====')
},
taskStart:(val:TaskStartCallbackOpt)=>{
console.log('任务开始回调===',val)
},
statistics:(val:StatisticsCallBackOpt)=>{
// console.log('统计信息开始回调===',val)
}
} as FfmpegType)
}
/**
*视频合并
*/
const videoMerge=async()=>{
let filePath=['/static/test.mp4','/static/test002.mp4']
let outPath=`/storage/emulated/0/DCIM/Camera/merge_video${Date.now()}.mp4`
ffmpegVideoMerge({
filePath,
outPath,
success:()=>{
path.value=outPath
autoplay.value=true
},
fail:()=>{
console.log('失败回调====')
}
} as MergeType)
}
//视频格式转换
const changeVideoType=()=>{
let inputPath='/static/test.mp4';
let videoType='flv';
let outPath=`/storage/emulated/0/DCIM/Camera/convertType${Date.now()}.${videoType}`;
convertTypeV(inputPath,videoType,outPath).then((filePath:string)=>{
path.value=filePath
autoplay.value=true
});
}
//音频合并
const audioMerge=()=>{
let filePath=['/static/test001.mp3','/static/test002.mp3']
// let filePath=['https://www.example.com/files/web/video/test001.mp4','https://www.example.com/files/web/video/test002.mp4']
let outPath=`/storage/emulated/0/Android/data/uni.UNI43B527F/files/merge_Audio${Date.now()}.mp3`
ffmpegVideoMerge({
type:2,
filePath,
outPath,
success:()=>{
// path.value=outPath
// autoplay.value=true
},
fail:()=>{
console.log('失败回调====')
}
} as MergeType)
}
//音频格式转换
const changeAudioType=()=>{
let inputPath='/static/test001.mp3';
let videoType='FLAC';
let outPath=`/storage/emulated/0/Android/data/uni.UNI43B527F/files/convertType${Date.now()}.${videoType}`;
convertTypeV(inputPath,videoType,outPath).then((filePath:string)=>{
console.log('音频filePath====',filePath)
});
}
//获取视频信息
const getVideoInfo=()=>{
let inputPath='/static/test.mp4'
// let inputPath='/storage/emulated/0/DCIM/Camera/video_1714100294515.mp4'
getMediaInfo(inputPath,(val:string)=>{
content.value=val
})
}
//图片格式转换
const changeImageType=async ()=>{
let inputPath=await getLocalPath('/static/logo.png');
let outPath=`/storage/emulated/0/DCIM/Camera/logo_${Date.now()}.jpg`
let cmd=`-i ${inputPath} ${outPath}`;
useExecuteFFmpeg({
cmd,
success:()=>{
console.log('成功回调====')
imagePath.value=outPath
}
} as FfmpegType)
}
//图片旋转
const imageRota=async ()=>{
let inputPath=await getLocalPath('/static/crop.png');
let outPath=`/storage/emulated/0/DCIM/Camera/logo_${Date.now()}.png`
let cmd=`-i ${inputPath} -vf "transpose=0" ${outPath}`;
// transpose=0:逆时针旋转 90 度。
// transpose=1:顺时针旋转 90 度。
// transpose=4,vflip:垂直翻转。
useExecuteFFmpeg({
cmd,
success:()=>{
console.log('成功回调====')
imagePath.value=outPath
}
} as FfmpegType)
}
//图片裁剪
const imageCrop=async ()=>{
let inputPath=await getLocalPath('/static/crop.png');
let outPath=`/storage/emulated/0/DCIM/Camera/crop_${Date.now()}.png`
let cmd=`-i ${inputPath} -vf "crop=300:200:0:0" ${outPath}`;
// crop=w:h:x:y
// w裁剪后的宽度
// h裁剪后的高
//x y 裁剪起点坐标
useExecuteFFmpeg({
cmd,
success:()=>{
console.log('成功回调====')
imagePath.value=outPath
}
} as FfmpegType)
}
</script>
<style>
.btn-wrap{
flex-direction: row;
flex-wrap: wrap;
padding: 10px;
justify-content: flex-start;
}
.btn{
margin: 0 10px 15px 0;
}
.content-wrap{
padding: 10px;
width: 100%;
min-height:100px;
}
.content{
width: 100%;
height: 100%;
border: 1px solid #ccc;
background-color: #f2f2f2;
}
</style>
UNIAPP 使用方式
<template>
<scroll-view scroll-y="true" :style="{flex:1}">
<view>
<video :src="path" :autoplay="autoplay" :controls="true" :style="{height:'250px',width:'100%'}"
:object-fit="objectFit" class="`video_1`">
</video>
</view>
<view class="btn-wrap" :style="{marginBottom:'15px'}">
<button class="btn" @click='executeFFmpeg'>{{title}}</button>
<button class="btn" @click='videoMerge'>视频合并</button>
<button class="btn" @click='changeVideoType'>视频格式转换</button>
<button class="btn" @click='audioMerge'>音频合并</button>
<button class="btn" @click='changeAudioType'>音频格式转换</button>
<button class="btn" @click='changeImageType'>图片格式转换</button>
<button class="btn" @click='imageRota'>图片旋转</button>
<button class="btn" @click='imageCrop'>图片裁剪</button>
<button class="btn" @click='getVideoInfo'>获取媒体信息</button>
<button class="btn" @click='delogoV'>添加视频滤镜</button>
<button class="btn" @click='createM3u8'>视频切割成m3u8</button>
</view>
<view class="content-wrap">
<text :style="{color:'red'}">媒体信息输出:</text>
<view class="content">
<textarea :value="content" disabled></textarea>
</view>
</view>
<image :src="imagePath" :style="{width:'100%',height:'auto'}"></image>
</scroll-view>
</template>
<script>
import {
useExecuteFFmpeg,
FfmpegType,
getMediaInfo,
getLocalPath,
TaskStartCallbackOpt,
StatisticsCallBackOpt,
ffmpegVideoMerge,
MergeType,
convertTypeV
} from '../../uni_modules/xwq-ffmpeg';
export default {
data() {
return {
title: "执行命令",
objectFit: 'cover',
url: 'https://www.example.com/files/op_admin/public/20240417/a0d944165e6194e5f6b2e056fc72e1b4.mp4',
autoplay: false,
content: '',
imagePath: '',
path: '/static/test002.mp4'
}
},
onLoad() {
console.log('资源地址====',plus.io.convertLocalFileSystemURL(this.path))
},
methods: {
async executeFFmpeg() {
// -vf "scale=1080x1080"
// libx265
// -loglevel debug
// -crf 23/-qp 28 压缩质量
// -hwaccel cuvid 使用 cuvid 硬件加速来进行视频编码。这通常用于NVIDIA的GPU,以加速H.264编码过程
// -threads 4: 使用4个线程来进行编码工作。这可以提高编码速度,但具体效果取决于硬件和具体任务。
let result = await plus.io.convertLocalFileSystemURL(this.path);
let outPath = `/storage/emulated/0/DCIM/Camera/video_${Date.now()}.mp4`
let cmd = `-i ${result} -c:v libx264 -qp 28 -s 540x280 -y ${outPath} -threads 4`;
console.log('cmd====',cmd)
useExecuteFFmpeg({
cmd,
success: () => {
console.log('成功回调====',outPath)
this.path = outPath
this.autoplay = true
},
cancel: () => {
console.log('取消执行回调===')
},
fail: () => {
console.log('失败回调====')
},
taskStart: (val) => {
console.log('任务开始回调===', val)
}
// statistics:(val:StatisticsCallBackOpt)=>{
// console.log('统计信息开始回调===',val)
// }
})
},
async videoMerge() {
// let filePath=['/static/test.mp4','/static/test002.mp4']
let filePath = ['https://www.example.com/files/web/video/test001.mp4',
'https://www.example.com/files/web/video/test002.mp4'
]
let outPath = `/storage/emulated/0/DCIM/Camera/merge_video${Date.now()}.mp4`
ffmpegVideoMerge({
filePath,
outPath,
success: () => {
this.path = outPath
this.autoplay = true
},
fail: () => {
console.log('失败回调====')
}
})
},
//视频格式转换
changeVideoType() {
let inputPath = this.url;
let videoType = 'flv';
let outPath = `/storage/emulated/0/DCIM/Camera/convertType${Date.now()}.${videoType}`;
convertTypeV(inputPath, videoType, outPath).then((filePath) => {
this.path = filePath
this.autoplay = true
});
},
//音频合并
audioMerge() {
let filePath = [plus.io.convertLocalFileSystemURL('/static/test001.mp3'), plus.io.convertLocalFileSystemURL('/static/test002.mp3')]
// let filePath=['https://www.example.com/files/web/video/test001.mp4','https://www.example.com/files/web/video/test002.mp4']
let outPath = `/storage/emulated/0/Android/data/uni.UNI43B527F/files/merge_Audio${Date.now()}.mp3`
ffmpegVideoMerge({
type: 2,
filePath,
outPath,
success: () => {
// path.value=outPath
// autoplay.value=true
},
fail: () => {
console.log('失败回调====')
}
})
},
//音频格式转换
changeAudioType() {
let inputPath = plus.io.convertLocalFileSystemURL('/static/test001.mp3');
let videoType = 'FLAC';
let outPath =
`/storage/emulated/0/Android/data/uni.UNI43B527F/files/convertType${Date.now()}.${videoType}`;
convertTypeV(inputPath, videoType, outPath).then((filePath) => {
console.log('音频filePath====', filePath)
});
},
//获取视频信息
getVideoInfo() {
let inputPath = plus.io.convertLocalFileSystemURL(this.path)
// let inputPath='/storage/emulated/0/DCIM/Camera/video_1714100294515.mp4'
getMediaInfo(inputPath, (val) => {
this.content = val
})
},
//添加滤镜
async delogoV() {
// let inputPath='/static/delogo.mp4';
let inputPath = await plus.io.convertLocalFileSystemURL(this.path);
let outPath = `/storage/emulated/0/DCIM/Camera/delogo22${Date.now()}.mp4`;
let watermark = '/static/logo.png';
// let cmd=`-i ${inputPath} -vf "delogo=x=10:y=10:w=200:h=100" -c:v libx264 -crf 20 -c:a copy ${outPath}`;
let cmd = `-i ${inputPath} -vf "delogo=x=10:y=10:w=450:h=350" ${outPath}`;
// let cmd=`ffmpeg -i ${inputPath} -i ${watermark} -filter_complex "overlay=10:10" -y ${outPath}`
// let cmd=`-i ${inputPath} -vf "drawbox=x=50:y=50:w=200:h=100:color=red:t=5" ${outPath}`
useExecuteFFmpeg({
cmd,
success: () => {
console.log('成功回调=====')
// imagePath.value=outPath
},
fail: () => {
console.log('失败====')
}
})
},
async createM3u8() {
let inputPath = await plus.io.convertLocalFileSystemURL(this.path);
let outPath = `/storage/emulated/0/Android/data/uni.UNI43B527F/files/m3u8${Date.now()}.m3u8`;
// let cmd=`-i ${inputPath} -vf -profile:v baseline -level 3.0 -s 640x360 -start_number 0 -hls_time 10 -hls_list_size 0 -f hls ${outPath}`;
let cmd =
`-i ${inputPath} -codec: copy -start_number 0 -hls_time 4 -hls_list_size 0 -f hls ${outPath}`;
useExecuteFFmpeg({
cmd,
success: () => {
console.log('成功回调=====')
// imagePath.value=outPath
},
fail: () => {
console.log('失败====')
}
})
},
//图片格式转换
async changeImageType() {
let inputPath = await plus.io.convertLocalFileSystemURL('/static/logo.png');
let outPath = `/storage/emulated/0/DCIM/Camera/logo_${Date.now()}.jpg`
let cmd = `-i ${inputPath} ${outPath}`;
useExecuteFFmpeg({
cmd,
success: () => {
console.log('成功回调====')
imagePath.value = outPath
}
})
},
async imageRota() {
let inputPath = await plus.io.convertLocalFileSystemURL('/static/crop.png');
let outPath = `/storage/emulated/0/DCIM/Camera/logo_${Date.now()}.png`
let cmd = `-i ${inputPath} -vf "transpose=0" ${outPath}`;
// transpose=0:逆时针旋转 90 度。
// transpose=1:顺时针旋转 90 度。
// transpose=4,vflip:垂直翻转。
useExecuteFFmpeg({
cmd,
success: () => {
console.log('成功回调====')
imagePath.value = outPath
}
})
},
imageCrop() {
uni.navigateTo({
url: '/pages/ffmpeg/cropImage'
})
// let inputPath=await plus.io.convertLocalFileSystemURL('/static/crop.png');
// let outPath=`/storage/emulated/0/DCIM/Camera/crop_${Date.now()}.png`
// let cmd=`-i ${inputPath} -vf "crop=300:200:0:0" ${outPath}`;
// // crop=w:h:x:y
// // w裁剪后的宽度
// // h裁剪后的高
// //x y 裁剪起点坐标
// useExecuteFFmpeg({
// cmd,
// success:()=>{
// console.log('成功回调====')
// imagePath.value=outPath
// }
// } as FfmpegType)
}
}
}
</script>
<style>
.video_1 {
z-index: -99
}
.btn-wrap {
flex-direction: row;
flex-wrap: wrap;
padding: 10px;
justify-content: flex-start;
}
.btn {
margin: 0 10px 15px 0;
}
.content-wrap {
padding: 10px;
width: 100%;
min-height: 100px;
}
.content {
width: 100%;
height: 100%;
border: 1px solid #ccc;
background-color: #f2f2f2;
}
</style>
调用方法说明
方法参数说明
属性 |
类型 |
描述 |
cmd |
string |
FFmpeg命令,可参照官网例子 |
success |
function |
成功回调 |
cancel |
function |
取消回调 |
fail |
function |
失败回调 |
taskStart |
function |
任务开始回调 |
statistics |
function |
统计信息回调 |
方法参数说明
属性 |
类型 |
描述 |
type |
number |
合并的文件类型 默认1 视频格式 ,可选2 音频格式 |
filePath |
string |
文件路径 只支持同种类型文件 |
outPath |
string |
文件输出路径 |
success |
function |
成功回调 |
fail |
function |
失败回调 |
方法参数说明
属性 |
类型 |
描述 |
inputPath |
string |
输入地址 |
outPath |
string |
输出地址 |
vType |
string |
要转换的视频类型 |
属性 |
类型 |
描述 |
filePath |
string |
文件地址 |
属性 |
类型 |
描述 |
sessionId |
number |
null |
会话ID,取消某个任务时需要传 |