返回顶部
首页 > 资讯 > 前端开发 > JavaScript >flutter使用tauri实现一个一键视频转4K软件
  • 479
分享到

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

2024-04-02 19:04:59 479人浏览 薄情痞子
摘要

目录前言开发原因工作原理开发过程前言 先说结论,tauri是一个非常优秀的前端桌面开发框架,但是,rust门槛太高了。 一开始我是用electron来开发的,但是打包后发现软件运行不

前言

先说结论,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'这种格式的,先写一个函数将时间转成秒数:


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软件的资料请关注编程网其它相关文章!

--结束END--

本文标题: flutter使用tauri实现一个一键视频转4K软件

本文链接: https://lsjlt.com/news/166607.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • flutter使用tauri实现一个一键视频转4K软件
    目录前言开发原因工作原理开发过程前言 先说结论,tauri是一个非常优秀的前端桌面开发框架,但是,rust门槛太高了。 一开始我是用electron来开发的,但是打包后发现软件运行不...
    99+
    2024-04-02
  • 使用vue怎么实现一个视频上传功能
    本篇文章为大家展示了使用vue怎么实现一个视频上传功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Vue的优点Vue具体轻量级框架、简单易学、双向数据绑定、组件化、数据和结构的分离、虚拟DOM、运...
    99+
    2023-06-14
  • 利用Java实现一个短视频点赞功能
    这篇文章将为大家详细讲解有关利用Java实现一个短视频点赞功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。 这行代码是弹出点赞的时间为18s.18秒后发生了什么呢 ?继续看&nb...
    99+
    2023-05-31
    java ava 点赞
  • 怎么在vue中使用flask实现一个视频合成功能
    这篇文章给大家介绍怎么在vue中使用flask实现一个视频合成功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。vue是什么软件Vue是一套用于构建用户界面的渐进式JavaScript框架,Vue与其它大型框架的区别是...
    99+
    2023-06-06
  • Android应用中怎么实现一个视频点播功能
    这篇文章给大家介绍Android应用中怎么实现一个视频点播功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。采用了本地代理服务的方式,通过原始url给播放器返回一个本地代理的一个url ,代理URL类似:http://...
    99+
    2023-05-31
    android roi
  • 使用Flutter怎么实现一个Http网络请求
    这期内容当中小编将会给大家带来有关使用Flutter怎么实现一个Http网络请求,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. Http的请求方式简介Http网络请求方式就是描述了客户端想对指定的资源...
    99+
    2023-06-14
  • 使用Android实现一个悬浮在软键盘上的输入栏
    目录前言悬浮栏横屏时软键盘全屏监听软键盘(该方法不可靠,废弃,下面有靠谱的)靠谱的监听软键盘的方法终极悬浮方式如果变小了如果变大了最终代码总结前言 我们要实现一个悬浮在软键盘上的输入...
    99+
    2024-04-02
  • 如何使用MySQL和Java实现一个简单的视频分享功能
    如何使用MySQL和Java实现一个简单的视频分享功能随着互联网的普及和带宽的提升,视频分享成为了当今最受欢迎的网络媒体形式之一。在这篇文章中,我们将探讨如何使用MySQL和Java来实现一个简单的视频分享功能。一、数据库设计首先,我们需要...
    99+
    2023-10-22
    MySQL Java 视频分享
  • 怎么在HTML5中使用Canvas实现一个破碎重组视频特效
    本篇文章为大家展示了怎么在HTML5中使用Canvas实现一个破碎重组视频特效,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。HTML代码<video id="sourcev...
    99+
    2023-06-09
  • 使用Flutter怎么实现一个钉钉考勤日历
    使用Flutter怎么实现一个钉钉考勤日历?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。 使用  // 考勤日历 &nb...
    99+
    2023-06-14
  • 如何使用Android实现一个悬浮在软键盘上的输入栏
    这篇文章主要介绍“如何使用Android实现一个悬浮在软键盘上的输入栏”,在日常操作中,相信很多人在如何使用Android实现一个悬浮在软键盘上的输入栏问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用A...
    99+
    2023-06-29
  • 使用Flutter怎么实现一个图文并茂的列表
    今天就跟大家聊聊有关使用Flutter怎么实现一个图文并茂的列表,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。ListView 简介ListView 用于生成列表,,通常使用 bui...
    99+
    2023-06-15
  • Android应用中怎么实现一个软键盘隐藏功能
    这篇文章将为大家详细讲解有关Android应用中怎么实现一个软键盘隐藏功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。具体方法如下:...public static void hideKe...
    99+
    2023-05-31
    android roi
  • Android开发中利用VideoView实现一个多媒体视频播放器
    本篇文章给大家分享的是有关Android开发中利用VideoView实现一个多媒体视频播放器,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1)、SurfaceView在布局文件...
    99+
    2023-05-31
    android videoview roi
  • 使用react怎么实现一个Radio组件
    使用react怎么实现一个Radio组件?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。测试组件:class Test extends Comp...
    99+
    2023-06-14
  • 怎么使用react实现一个tab组件
    本教程操作环境:windows7系统、react18.0.0版、Dell G3电脑。怎么使用react实现一个tab组件?react写Tab组件使用react写TAB栏组件和对应hover事件(背景:在用gatsby开发页面时,遇到这样的组...
    99+
    2022-11-22
    tab组件 React
  • 如何使用react实现一个tab组件
    这篇“如何使用react实现一个tab组件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“如何使用react实现一个tab组件...
    99+
    2023-07-04
  • 使用CSS3怎么实现一个3D翻转效果
    本篇文章给大家分享的是有关使用CSS3怎么实现一个3D翻转效果,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。第一步非常简单,我们简单画1个演示方块,为其 添加transitio...
    99+
    2023-06-08
  • 使用Unity怎么实现一个虚拟键盘功能
    使用Unity怎么实现一个虚拟键盘功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。具体内容如下using UnityEngine;using System...
    99+
    2023-06-09
  • 使用JavaScript怎么实现一个单文件组件
    使用JavaScript怎么实现一个单文件组件?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。JavaScript是什么JavaScript是一种直译式的脚本语言...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作