flutter使用tauri实现一个一键视频转4K软件

目录
  • 前言
  • 开发原因
  • 工作原理
  • 开发过程

前言

先说结论,tauri是一个非常优秀的前端桌面开发框架,但是,rust门槛太高了。

一开始我是用electron来开发的,但是打包后发现软件运行不是很流畅,有那么一点卡顿。于是为了所谓的性能,我尝试用tauri来做我的软件。在学了两星期的rust后,我发现rust真的太难学了,最后硬是边做边查勉强做出来了。

软件运行起来是比electron做的丝滑很多,但是写rust真的是很痛苦,rust的写法和其他语言是截然不同的,在不知道之前我是个rust吹,觉得rust就是牛逼,真的上手后发现rust的门槛真的太高了,各种逆天的写法直接把我劝退,tauri无缝衔接前端真的又很爽。

如果golang也出一个和tauri一样的桌面端框架,那么golang将会是未来开发中的不二语言。

开发原因

我平时喜欢看一些动漫视频或者是收藏一些做得不错的动漫MAD,但是有时候因为番剧出的年代久远的问题,就算找最高清的资源,视频也不过720P,又或者是在b站上看一些动漫MAD的时候,up主虽然用的转场技巧比较不错,但是使用的动漫素材的质量比较差,十分可惜。

于是我想能不能做一个视频转4K的软件?类似于修复视频的功能。虽然网络上的修复视频软件有很多了,但是效果还是达不到我的要求,于是说干就干。

工作原理

视频其实就是一帧一帧的图片组成,如果要把视频转成4K,那么只要把视频分解成图片,再将图片转4K图片,最后将4K图片合并成4K视频就可以了。
于是我搜了一圈,了解到有Real-ESRGAN]这样的一个将图片转成4K的软件。并且里面也提供好了视频转4K的案例。

先用ffmpeg将视频分解成图片:

ffmpeg -i 原视频 -qscale:v 1 -qmin 1 -qmax 1 -vsync 0 临时图片路径/frame%08d.png

再用Real-ESRGAN将图片转4K图片:

./realesrgan-ncnn-vulkan.exe -i 临时图片目录 -o 4K图片目录 -n realesr-animevideov3 -s 2 -f jpg

最后查看原视频的帧数,然后用ffmpeg将4K图片合成4K视频:

ffmpeg -i 原视频
ffmpeg -r 23.98 -i 4K图片路径/frame%08d.jpg -c:v libx264 -r 帧数 -pix_fmt yuv420p 4K视频

只不过这样操作起来非常繁琐,并且只能一个个转,不能批量操作,也不能看到进度。虽然可以写一个cmd脚本批量操作,但是看不到进度,体验不是很好的。于是说干就干,开发软件!

开发过程

tauri提供了一些后端的操作权限给前端,也就是在前端就能完成读写文件,这就非常方便了!但是也是有一定限制的,比如要读取任意文件,就要rust去操作。

前提工作先准备一个文件,导出一个数组,pids,以便关闭软件时杀死所有windows进程。

export const pids: number[] = []

首先是创建3个文件夹,临时图片文件夹,图片转4K图片文件夹,输出视频文件夹,用来存放输出的资源的:

    await readDir(`${basePath.value}/img_temp`).catch(() => {
        createDir(`${basePath.value}/img_temp`)
    })
    await readDir(`${basePath.value}/img_out`).catch(() => {
        createDir(`${basePath.value}/img_out`)
    })
    await readDir(`${basePath.value}/output`).catch(() => {
        createDir(`${basePath.value}/output`)
    })

然后是选定一个input文件夹,然后读取文件夹下面的视频,是rust实现:

fn read_dir_file(path: String) -> Vec<String> {
    let mut arr: Vec<String> = vec![];
    for item in read_dir(path).unwrap() {
        if !item.as_ref().unwrap().path().is_dir() {
            arr.push(item.unwrap().file_name().into_string().unwrap());
        }
    }
    return arr;
}

因为返回的是一个数组,前端获取到之后,就遍历去操作。但后面为了方便,我则是遍历这个数组,然后创建子组件,传入文件路径,让子组件去操作:

import { invoke } from '@tauri-apps/api/tauri'
const fileList = await invoke<string[]>('read_dir_file', { path: path.value })

首先还是遍历创建子文件夹,方便管理:

    await readDir(`${props.basePath}/img_temp/${fileName}`).catch(() => {
        createDir(`${props.basePath}/img_temp/${fileName}`)
    })
    await readDir(`${props.basePath}/img_out/${fileName}`).catch(() => {
        createDir(`${props.basePath}/img_out/${fileName}`)
    })

接着调用tauri提供的shell指令: 不过在此之前,先要配置tauri.conf.json,让tauri支持任意命令

"tauri": {
        "allowlist": {
            "shell": {
                "scope": [
                    {
                        "name": "ffmpeg",
                        "cmd": "cmd",
                        "args": ["/C", { "validator": "\\S+" }]
                    }
                ]
            },
        },
    }

然后先执行读取视频的信息得到帧数和视频的总秒数,以便计算进度,并且把返回的pid存到数组中:

import { Command } from '@tauri-apps/api/shell'
const fps = ref('5')
const duration = ref('')
const cmd1 = `ffmpeg -i ${props.basePath}/input/${props.file}`
const command1 = new Command('ffmpeg', ['/C', cmd1])
const child1 = await command1.spawn()
    command1.stderr.on('data', (line) => {
        const fpsResult = line.match(/\w{2}\.?\w{0,2}(?= fps)/)
        /** 匹配视频持续时间的信息 */
        const durationResult = line.match(/(?<=Duration: ).+(?=, start)/)
        if (fpsResult) {
            fps.value = fpsResult[0]
            console.log('fps', fps.value)
        }
        if (durationResult) {
            duration.value = durationResult[0]
            console.log('duration', duration.value)
        }
    })
    pids.push(child1.pid)

用正则匹配帧数和持续时间,存到变量中。在命令执行完毕后,接着执行将视频分解图片的任务:

command1.on('close', async () => {
const cmd2 = `${props.ffmpegPath}  -i ${props.basePath}/input/${props.file} -qscale:v 1 -qmin 1 -qmax 1 -vsync 0 ${props.basePath}/img_temp/${fileName}/frame%08d.png`
    const command2 = new Command('ffmpeg', ['/C', cmd2])
    const child2 = await command2.spawn()
    pids.push(child2.pid)
})

至于监听进度,图片的总数是可以通过帧数和视频总秒数计算出来的,总秒数乘以帧数,就是要转换的图片总数。由于得到的持续时间是'00:04:32'这种格式的,先写一个函数将时间转成秒数:

/**
 * @description  将字符串的时间转成总秒数的时间 00:04:35
 * @param time   字符串的时间
 * @returns      返回秒数的时间
 */
export function formatTime(time: string) {
    const hours = Number(time.split(':')[0])
    const mimutes = Number(time.split(':')[1])
    const seconds = Number(time.split(':')[2])
    return hours * 60 * 60 + mimutes * 60 + seconds
}

总图片就可以计算出来了,然后在输出时,使用节流,每隔1秒读取一次该文件夹下面的图片数量,则进度就是当前的图片数量/图片总数。

读取文件数量需要rust操作"

fn read_dir_file_count(path: String) -> i32 {
    let dir = read_dir(path).unwrap();
    let mut count: i32 = 0;
    for _ in dir {
        count += 1;
    }
    return count;
}

则整体是:

const total = formatTime(duration.value) * Number(fps.value)
            command2.stderr.on('data', async (line) => {
                const current = await invoke<number>('read_dir_file_count', {
                    path: `${props.basePath}/img_temp/${fileName}`,
                })
                console.log(current, total)
                precent1.value = Math.round((current / total) * 100)
            })

precent1就是绑定的进度条的变量。

在任务关闭后,执行优化图片的命令:

command2.on('close', async () => {
    const cmd3 = `${props.realesrgan} -i ${props.basePath}/img_temp/${fileName} -o ${props.basePath}/img_out/${fileName} -n realesr-animevideov3 -s 2 -f jpg`
    const command3 = new Command('ffmpeg', ['/C', cmd3])
    const child3 = await command3.spawn()
    pids.push(child3.pid)
})

监听转换的进度仍是读取文件夹下面当前的图片数量,用节流函数,优化性能:

command3.stderr.on('data', throttle(fn, 2000))
                async function fn() {
                    const current = await invoke<number>('read_dir_file_count', {
                        path: `${props.basePath}/img_out/${fileName}`,
                    })
                    precent2.value = Math.round((current / total) * 100)
                    console.log(current, total, (current / total) * 100)
                    // console.log(line)
}

最后在命令完成后,执行4K图片转4K视频的命令:

command3.on('close', async () => {
const cmd4 = `${props.ffmpegPath}  -r ${fps.value} -i  ${props.basePath}/img_out/${fileName}/frame%08d.jpg -i  ${props.basePath}/input/${props.file} -map 0:v:0 -map 1:a:0 -c:a copy -c:v ${props.model} -r ${fps.value} -pix_fmt yuv420p ${props.basePath}/output/${props.file}`
const command4 = new Command('ffmpeg', ['/C', cmd4])
const child4 = await command4.spawn()
pids.push(child4.pid)
})

监听进度此时则是去获取stderr输出的信息,然后匹配到当前转换的时间,再除以总时间

const total = formatTime(duration.value)
command4.stderr.on('data', throttle(fn, 200))
                    async function fn(data: string) {
                        /** 控制台的信息 */
                        const result = data.match(/(?<=time=).+(?= bitrate)/)
                        if (result) {
                            const current = formatTime(result[0])
                            console.log(current, total)
                            precent3.value = Math.round((current / total) * 100)
                        }
                    }

最后,如果关闭软件时,则是先把所有的任务都杀死,再关闭:

async function closeApp() {
    await Promise.all(
        pids.map(async (pid) => {
            return new Promise((resolve) => {
                const cmd = `taskkill /f /t /pid ${pid}`
                const command = new Command('ffmpeg', ['/C', cmd])
                command.spawn()
                command.on('close', () => {
                    resolve(0)
                })
            })
        })
    )
    appWindow.close()
}

以下是我的演示视频,不过文件有点大

www.bilibili.com/video/BV1EW…

这是我的项目地址:github.com/Minori-ty/m…

软件也已经打包好了,开箱即用github.com/Minori-ty/m…

以上就是flutter使用tauri实现一个一键视频转4K软件的详细内容,更多关于flutter tauri视频转4K软件的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android Flutter实现精灵图的使用详解

    目录 前言 如何使用精灵图 自定义实现加载 Flame加载精灵图 前言 在日常开发中遇到的图片展示一般是静态图和Gif图两种形式(静态和动态的不同).与此同时当需要对图片做效果时让其动起来,常用方案是Gif图播放或者是帧动画(多种静态图轮询播放).但在游戏开发中还有一种动图表现形式叫做Sprite图(雪碧图),其在前端开发中也是很常见.为什么需要使用精灵图,因为每张图片显示都需要去发起请求获取,若页面图片数量较多(一个页面有几十个小图)并发请求将是一个大数量级,可能会造成页面加载速度降低,精灵图

  • 详解flutter中常用的container layout实例

    目录 简介 Container的使用 旋转Container Container中的BoxConstraints 总结 简介 在上一篇文章中,我们列举了flutter中的所有layout类,并且详细介绍了两个非常常用的layout:Row和Column. 掌握了上面两个基本的layout还是不够的,如果需要应付日常的layout使用,我们还需要掌握多一些layout组件.今天我们会介绍一个功能强大的layout:Container layout. Container的使用 Container是一

  • Flutter学习LogUtil封装与实现实例详解

    目录 一. 为什么要封装打印类 二. 需要哪些类 三. 打印输出的抽象类 四. 格式化日志内容 格式化堆栈 堆栈裁切工具类 格式化堆栈信息 格式化JSON 五. 需要用到的常量 六. 为了控制多个打印器的设置做了一个配置类 七. Log的管理类 九. 调用LogUtil 十. 定义一个Flutter 控制台打印输出的方法 十一. 现在使用前初始化log打印器一次 使用 一. 为什么要封装打印类 虽然 flutter/原生给我们提供了日志打印的功能,但是超出一定长度以后会被截断 Json打印挤在一

  • Flutter 异步编程之单线程下异步模型图文示例详解

    目录 一. 本专栏图示概念规范 1. 任务概念规范 2. 任务的状态 3. 时刻与时间线 4.同步与异步 二.理解单线程中的异步任务 1. 任务的分配 2.异步任务特点 3. 异步任务完成与回调 三. Dart 语言中的异步 1.编程语言中与异步模型的对应关系 2.Dart 编程中的异步任务 3.当前任务分析 四.异步模型的延伸 1. 单线程异步模型的局限性 2. 多线程与异步的关系 3. Dart 中如何解决单线程异步模型的局限性 一. 本专栏图示概念规范 本专栏是对 异步编程 的系统探索,会

  • Flutter 假异步的实现示例

    就像 android 有 handle 一样,消息队列这东西好像还真是系统必备,Flutter 也有自己的消息队列,只不过队列直接封装在了 Dart 的线程类型 Isolate 里面了,不过 Flutter 还是提供了 Futrue 这个 API 来专门来操作各种消息,以及实现基于消息队列的假异步 Flutter 的"异步"机制 这里的异步是加了引号的,可见此异步非真异步,而是假异步.Flutter 的 异步 不是开新线程,而是往所属线程的 消息队列 中添加任务,当然大家也可以按上文那

  • Flutter实现一个支持渐变背景的Button示例详解

    目录 Flutter中的按钮 不完美的地方 在child中处理 外面套一个wrapper MaterialStateProperty MaterialStatesController 边距问题 EnhancedButton Flutter中的按钮 自Flutter 1.20 新增了ButtonStyleButton 系列按钮,可以说非常好用了,默认样式比之前漂亮了许多,扩展性也增加了很多.按钮样式统一由ButtonStyle这个类提供,支持根据各种状态(MaterialState)变化的属性,也

  • flutter使用tauri实现一个一键视频转4K软件

    目录 前言 开发原因 工作原理 开发过程 前言 先说结论,tauri是一个非常优秀的前端桌面开发框架,但是,rust门槛太高了. 一开始我是用electron来开发的,但是打包后发现软件运行不是很流畅,有那么一点卡顿.于是为了所谓的性能,我尝试用tauri来做我的软件.在学了两星期的rust后,我发现rust真的太难学了,最后硬是边做边查勉强做出来了. 软件运行起来是比electron做的丝滑很多,但是写rust真的是很痛苦,rust的写法和其他语言是截然不同的,在不知道之前我是个rust吹,觉

  • 5分钟搭建一个WebRTC视频聊天

    在上篇文章给大家介绍了在Ubuntu上搭建一个基于webrtc的多人视频聊天服务实例代码详解,感兴趣的朋友可以参考下.今天给大家分享一篇关于5分钟搭建一个WebRTC视频聊天. 百度一下WebRTC,我想也是一堆.本以为用这位朋友( 搭建WebRtc环境 )的SkyRTC-demo 就可以一马平川的实现聊天,结果折腾了半天,文本信息都发不出去,更别说视频了.于是自己动手. 想在公网上实现视频通信,需要下面3个核心元素: 一个是NAT穿透服务器(ICE Server),实现内网穿透,具体的作用可以

  • Flutter学习之创建一个内嵌的navigation详解

    目录 简介 搭建主Navigator 构建子路由 总结 简介 我们在flutter中可以使用Navigator.push或者Navigator.pushNamed方法来向Navigator中添加不同的页面,从而达到页面调整的目的. 一般情况下这样已经足够了,但是有时候我们有多个Navigator的情况下,上面的使用方式就不够用了.比如我们有一个主页面app的Navigator,然后里面有一个匹配好友的功能,这个功能有多个页面,因为匹配好友功能的多个页面实际上是一个完整的流程,所以这些页面需要被放

  • 基于Python实现视频自动下载软件

    目录 序言 效果展示 下载视频 下载弹幕 下载评论 软件生成 打包 序言 哈喽兄弟们,今天来实现一个Python采集视频.弹幕.评论与一体的小软件. 平常咱们都是直接代码运行,不过今天我们做成软件,这样的话,咱们不仅能自己用,还能分享给小伙伴,女朋友一起使用. 内容有点多,拿好小本本,做好笔记,发车了~ 效果展示 我们先来看看效果 整体界面 我随便找个视频下载一下 弹幕和评论我都顺便下载了 有一说一,确实方便,就是下载视频太大的话,会卡一下. 不过我这里视频没有做去水印,所以下载下来还是有水印的

  • 一个Vue视频媒体多段裁剪组件的实现示例

    近日项目有个新需求,需要对视频或音频进行多段裁剪然后拼接.例如,一段视频长30分钟,我需要将5-10分钟.17-22分钟.24-29分钟这三段拼接到一起成一整段视频.裁剪在前端,拼接在后端. 网上简单找了找,基本都是客户端内的工具,没有纯网页的裁剪.既然没有,那就动手写一个. 代码已上传到GitHub: https://github.com/fengma1992/media-cut-tool 废话不多,下面就来看看怎么设计的. 效果图 图中底部的功能块为裁剪工具组件,上方的视频为演示用,当然也能

  • 做了一个flash视频墙[附源文件与xml文件]

    作者:dickmaoas代码: 复制代码 代码如下: //design by dickmao 2007.1 fscommand("showmenu", "false"); import mx.managers.DepthManager;//载入类 item = 1;//计数器,载入动画的个数 var mclListener = new Object();//侦听载入动做完成的侦听器 var mcLoader = new MovieClipLoader();//新建载

  • Python实现一键下载视频脚本

    目录 需求 解决方案 代码 总结 需求 小编通常会上一些专业的视频网站比如腾讯视频.优酷,在上面看电影.电视剧.这些网站有个优点,可以缓存视频,在通勤路上比如地铁就可以愉快的刷剧了,因为地铁上的网速通常不怎么好. 但是有一些经典电影或者电视剧,这些视频并没有提供,那么我们只能上一些小电影网站看了,资源是有了,但是问题来了,这些小电影网站大多数都没有视频下载功能,那在地铁上就没法看了. 如果可以把这些视频下载下来,再传输到手机里不就可以离线看了吗? 接下来小编就演示下如何用 python 脚本来实

  • 短视频(douyin)去水印工具的实现代码

    现在视频号非常火热,之前在做抖音和快手的人就直接把之前的视频直接搬运过来了.但是从抖音app下载的视频都是带官方水印的?这个是怎么去掉的?哦,不对,他们应该都有保留原视频的吧.但是还有很多人是直接搬运别人的视频的,那他们是怎么去水印的呢? 其实早就有很多现成的工具,如小程序.去水印app都能直接去水印,甚至还有收费的. 赶紧研究研究,说不准咱也能搞一个比他们更好的工具出来. 一顿操作猛如虎,各种抓包看数据,结果简单的不得了,分分钟内就能给一坨视频去水印. 其实这些去水印的工具都太夸张(忽悠)了,

  • 基于Python制作一键桌面整理工具

    目录 前言 效果展示 开发思路 完整代码 前言 我承认我不是一个爱整理桌面的人,因为我觉得乱糟糟的桌面,反而容易找到文件. 哈哈,可是最近桌面实在是太乱了,自己都看不下去了,几乎占满了整个屏幕.虽然一键整理桌面的软件很多,但是对于其他路径下的文件,我同样需要整理,于是我想到使用Python,完成这个需求. 效果展示 我一共为将文件分为9个大类,分别是图片.视频.音频.文档.压缩文件.常用格式.程序脚本.可执行程序和字体文件. # 不同文件组成的嵌套字典 file_dict = { '图片': [

  • 以视频爬取实例讲解Python爬虫神器Beautiful Soup用法

    1.安装BeautifulSoup4 easy_install安装方式,easy_install需要提前安装 easy_install beautifulsoup4 pip安装方式,pip也需要提前安装.此外PyPi中还有一个名字是 BeautifulSoup 的包,那是 Beautiful Soup3 的发布版本.在这里不建议安装. pip install beautifulsoup4 Debain或ubuntu安装方式 apt-get install Python-bs4 你也可以通过源码安

随机推荐