加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
main.go 26.76 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
package main
import (
"BilibiliConvertGo/upgrade"
"BilibiliConvertGo/util"
"embed"
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
"github.com/jageros/eventhub"
"golang.org/x/sys/windows"
"gopkg.in/eapache/queue.v1"
"image/color"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
)
//go:embed assets
var assets embed.FS
//go:embed assets/simhei.ttf
var fontData []byte
const (
sourcePathEvent = 1
targetPathEvent = 2
logAreaEvent = 3
progressUpdateEvent = 4
videoCntUpdateEvent = 5
successCntUpdateEvent = 6
)
const (
logAreaEventCommandClear = 1
logAreaEventCommandAppend = 2
logAreaEventCommandReset = 3
)
const (
progressCircleSize = 130
logMaxLineCnt = 30
versionCode = 11
)
const updateUrl = "http://bilibili.liang.hciot.com.cn/windows/version_" + runtime.GOOS + "_" + runtime.GOARCH
const openappUrl = "http://bilibili.liang.hciot.com.cn/openapp.php"
const transferUrl = "http://bilibili.liang.hciot.com.cn/transfer.php"
type Job struct {
title string
sourcePath string
videoPath string
audioPath string
savePath string
part string
page int
fromPC bool
overrideOutput bool
sourceClearFlag bool
onlyAudio bool
bitrate int64
}
var (
sourcePathInput = &widget.Entry{}
sourcePathInputValue = ""
targetPathInput = &widget.Entry{}
targetPathInputValue = ""
logAreaEntry = &widget.Entry{}
logAreaEntryValue = ""
circularBuffer = &StringManipulator{}
progressCircle = &canvas.Circle{}
progressLabelValue = "0 %"
progressLabel = &widget.Label{}
videoCntLabelValue = "0"
videoCntLabel = &widget.Label{}
successCntLabelValue = "0"
successCntLabel = &widget.Label{}
mySoftwareSetting = &softwareSetting{}
jobs = queue.New()
removeTempFileJob = queue.New()
jobStart = false
videoCnt = 0
successCnt = 0
skipCnt = 0
executeFlag = false
)
// 程序启动
func main() {
//设置中文字体:解决中文乱码问题
initFont()
eventInit()
myapp := app.New()
window := myapp.NewWindow(fmt.Sprintf("B站缓存转视频工具(版本号:%d)", versionCode))
window.SetMaster()
window.SetMainMenu(getMenuGroup(myapp, window))
window.SetContent(container.NewVBox(getTopCtn(window), widget.NewSeparator(), getMainCtn(window)))
window.Resize(fyne.NewSize(800, 440))
// 软件设置初始化
mySoftwareSetting = NewSoftwareSetting()
clearTempDir()
entryInit()
logAreaInit()
// 开启视频合并task
go func() {
for {
executeJob()
time.Sleep(500 * time.Millisecond)
}
}()
// 开启视频转换解密文件清理task
go func() {
for {
removeTempFileTask()
time.Sleep(250 * time.Millisecond)
}
}()
// 应用更新检测
checkAndUpgrade(window)
go util.HttpGetWithResult(openappUrl)
window.ShowAndRun()
}
// 应用更新
func checkAndUpgrade(win fyne.Window) {
go func() {
upgrade.CheckVersion(updateUrl, func(res upgrade.VersionInfo) {
if versionCode < res.VersionCode {
confirmCnt := container.NewBorder(widget.NewLabel(fmt.Sprintf("检查到新版本:%s", res.Version)), nil, nil, nil)
cdl := dialog.NewCustomConfirm("应用更新", "确认更新", "以后再说", confirmCnt, func(b bool) {
if b {
cmd := exec.Command("cmd", "/c", "start", "/b", "BilibiliConvertUpdate.exe", "-cmd", "upgrade", fmt.Sprintf("%d", versionCode))
_ = cmd.Run()
}
}, win)
cdl.Show()
}
}, func(err string) {
d := dialog.NewInformation("应用更新", err, win)
d.Show()
})
}()
}
// 清空缓存文件夹
func clearTempDir() {
err := os.RemoveAll(".bilibili_convert_temp")
if err != nil {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("缓存文件夹清理失败:%s", err))
return
}
err = os.Mkdir(".bilibili_convert_temp", os.ModePerm)
if err != nil {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("创建缓存文件夹失败:%s", err))
return
}
}
// 输入框初始化
func entryInit() {
t, _ := filepath.Abs("")
abs := storage.NewFileURI(t).Path()
sourcePathInputValue = mySoftwareSetting.SourcePath
if sourcePathInputValue == "" {
sourcePathInputValue = abs
}
sourcePathInput.SetText(sourcePathInputValue)
sourcePathInput.Refresh()
targetPathInputValue = mySoftwareSetting.TargetPath
if targetPathInputValue == "" {
targetPathInputValue = abs
}
targetPathInput.SetText(targetPathInputValue)
targetPathInput.Refresh()
}
// 日志框初始化
func logAreaInit() {
// 尝试从网上加载初始化信息
initStr := ""
circularBuffer = NewStringManipulator("", logMaxLineCnt)
loadFromAssets := false
client := resty.New()
resp, err := client.R().
EnableTrace().
Get("http://bilibili.liang.hciot.com.cn/windows/init.log")
if err != nil {
loadFromAssets = true
} else {
if resp.StatusCode() != 200 || len(resp.Body()) <= 0 {
loadFromAssets = true
}
}
// bytes转string
if loadFromAssets {
open, err := assets.ReadFile("assets/init.log")
if err == nil {
initStr = util.BytesToString(open)
}
} else {
initStr = util.BytesToString(resp.Body())
}
// 重置日志输出框
eventhub.Publish(logAreaEvent, logAreaEventCommandReset, initStr)
}
// 事件监听初始化
func eventInit() {
eventhub.Subscribe(sourcePathEvent, func(args ...interface{}) {
t, str := "", ""
for _, arg := range args {
//.(type)是判断interface{}的具体类型,只能在switch case语句中使用
switch arg.(type) {
case string:
t = fmt.Sprintf("%v", arg)
default:
t = fmt.Sprintf("%v", arg)
}
str += t
}
str = strings.TrimSpace(str)
index := strings.Index(str, "://")
if index != -1 {
str = str[index+3:]
}
if checkEntry(str) {
sourcePathInputValue = str
sourcePathInput.SetText(sourcePathInputValue)
sourcePathInput.Refresh()
}
})
eventhub.Subscribe(targetPathEvent, func(args ...interface{}) {
t, str := "", ""
for _, arg := range args {
//.(type)是判断interface{}的具体类型,只能在switch case语句中使用
switch arg.(type) {
case string:
t = fmt.Sprintf("%v", arg)
default:
t = fmt.Sprintf("%v", arg)
}
str += t
}
str = strings.TrimSpace(str)
index := strings.Index(str, "://")
if index != -1 {
str = str[index+3:]
}
if checkEntry(str) {
targetPathInputValue = str
targetPathInput.SetText(targetPathInputValue)
}
})
eventhub.Subscribe(logAreaEvent, func(args ...interface{}) {
command := args[0]
switch command {
case 1:
// 清空消息
circularBuffer = NewStringManipulator("", logMaxLineCnt)
logAreaEntryValue = ""
break
case 2:
// 追加消息
circularBuffer.AppendString(fmt.Sprintf("%v\n", args[1]))
logAreaEntryValue = circularBuffer.GetString()
break
case 3:
// 设置消息
circularBuffer = NewStringManipulator("", logMaxLineCnt)
circularBuffer.AppendString(fmt.Sprintf("%v\n", args[1]))
logAreaEntryValue = circularBuffer.GetString()
break
default:
break
}
logAreaEntry.SetText(logAreaEntryValue)
})
eventhub.Subscribe(progressUpdateEvent, func(args ...interface{}) {
value := fmt.Sprintf("%v", args[0])
v, _ := strconv.ParseFloat(value, 32)
updateCircle(progressCircle, progressCircleSize, float32(v))
})
eventhub.Subscribe(videoCntUpdateEvent, func(args ...interface{}) {
value := fmt.Sprintf("%v", args[0])
videoCntLabelValue = value
videoCntLabel.SetText(videoCntLabelValue)
videoCntLabel.Refresh()
})
eventhub.Subscribe(successCntUpdateEvent, func(args ...interface{}) {
value := fmt.Sprintf("%v", args[0])
successCntLabelValue = value
successCntLabel.SetText(successCntLabelValue)
successCntLabel.Refresh()
})
}
// 检测选择文件夹是否合法,若不存在,则创建
func checkEntry(input string) bool {
stat, err := os.Stat(input)
if err != nil {
if os.IsNotExist(err) {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("创建文件夹:%s", input))
err := os.MkdirAll(input, os.ModePerm)
if err != nil {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("无效的文件夹:%s", input))
return false
}
stat, err = os.Stat(input)
}
}
if !stat.IsDir() {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("您选择的并不是一个目录:%s", input))
return false
}
return true
}
// 字体初始化
func initFont() {
temp, err := ioutil.TempDir("", "font")
if err != nil {
return
}
tempPath := filepath.Join(temp, "simhe.ttf")
_ = os.WriteFile(tempPath, fontData, 0755)
_ = os.Setenv("FYNE_FONT", tempPath)
}
// 更新圆形的尺寸
// param1: 需要变换的circle
// param2: circle为100%时的尺寸
// param3: 当前显示的百分比
func updateCircle(obj fyne.CanvasObject, max float32, percentage float32) {
value := max * (percentage / 100)
obj.Resize(fyne.NewSize(value, value))
pos := (max - value) / 2
obj.Move(fyne.NewPos(pos, pos))
canvas.Refresh(obj)
// 更新进度文字显示
progressLabelValue = fmt.Sprintf("%.2f", percentage) + " %"
progressLabel.SetText(progressLabelValue)
progressLabel.Refresh()
}
func getTopCtn(win fyne.Window) *fyne.Container {
// 输入框
sourceLabel := widget.NewLabel("缓存路径")
sourcePathInput = widget.NewEntry()
sourceBtn := widget.NewButton("打开", func() {
folderOpen := dialog.NewFolderOpen(func(list fyne.ListableURI, err error) {
if err != nil {
dialog.ShowError(err, win)
return
}
if list == nil {
log.Println("Cancelled")
return
}
eventhub.Publish(sourcePathEvent, list.Path())
}, win)
folderOpen.Resize(fyne.NewSize(800, 800))
testData, _ := filepath.Abs(sourcePathInputValue)
dir, err := storage.ListerForURI(storage.NewFileURI(testData))
if err != nil {
fmt.Println(err)
}
folderOpen.SetLocation(dir)
folderOpen.Show()
})
sourceGroup := container.NewBorder(layout.NewSpacer(), layout.NewSpacer(), sourceLabel, sourceBtn, sourcePathInput)
targetLabel := widget.NewLabel("保存路径")
targetPathInput = widget.NewEntry()
targetBtn := widget.NewButton("打开", func() {
folderOpen := dialog.NewFolderOpen(func(list fyne.ListableURI, err error) {
if err != nil {
dialog.ShowError(err, win)
return
}
if list == nil {
log.Println("Cancelled")
return
}
eventhub.Publish(targetPathEvent, list.Path())
}, win)
folderOpen.Resize(fyne.NewSize(800, 800))
testData, _ := filepath.Abs(targetPathInputValue)
dir, err := storage.ListerForURI(storage.NewFileURI(testData))
if err != nil {
fmt.Println(err)
}
folderOpen.SetLocation(dir)
folderOpen.Show()
})
targetGroup := container.NewBorder(layout.NewSpacer(), layout.NewSpacer(), targetLabel, targetBtn, targetPathInput)
return container.NewVBox(sourceGroup, targetGroup)
}
// 检测文件夹是否合法
func checkEntryInput() bool {
// 检测源文件路径
if checkEntry(sourcePathInput.Text) {
sourcePathInputValue = sourcePathInput.Text
sourcePathInput.Refresh()
} else {
return false
}
// 检测目标文件路径
if checkEntry(targetPathInput.Text) {
targetPathInputValue = targetPathInput.Text
targetPathInput.Refresh()
} else {
return false
}
return true
}
// 获得指定盘符的剩余空间大小,单位字节
func getDiskFreeSpace(path string) (int64, error) {
if strings.Index(path, ":") == -1 {
return 0, nil
}
h := syscall.MustLoadDLL("kernel32.dll")
c := h.MustFindProc("GetDiskFreeSpaceExW")
lpFreeBytesAvailable := int64(0)
lpTotalNumberOfBytes := int64(0)
lpTotalNumberOfFreeBytes := int64(0)
_, _, err := c.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))
return lpFreeBytesAvailable, err
}
// 获取文件夹的大小,单位字节
func getDirectorySize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return nil
})
if err != nil {
return 0, err
}
return size, nil
}
// 格式化字节大小
func formatByteSize(size int64) string {
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
)
switch {
case size >= GB:
return fmt.Sprintf("%0.2f G", float64(size)/GB)
case size >= MB:
return fmt.Sprintf("%0.2f MB", float64(size)/MB)
case size >= KB:
return fmt.Sprintf("%0.2f K", float64(size)/KB)
default:
return fmt.Sprintf("%d B", size)
}
}
func getMainCtn(win fyne.Window) *fyne.Container {
// 转换按钮
transferBtn := widget.NewButton("转换", func() {
if checkFFmpeg() {
if !checkEntryInput() {
return
}
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("源文件路径:%s", sourcePathInputValue))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("目标路径:%s", targetPathInputValue))
// 统计清零
eventhub.Publish(videoCntUpdateEvent, 0)
eventhub.Publish(successCntUpdateEvent, 0)
// 记录点击次数
go util.HttpGetWithResult(transferUrl)
// 输出盘符剩余空间大小
freeSpace, err := getDiskFreeSpace(targetPathInputValue)
if err != nil && err != windows.ERROR_SUCCESS {
errMsg := fmt.Sprintf("【错误】无法判断保存文件夹剩余大小:%s", err.Error())
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
}
// 源视频目录的大小
size, err := getDirectorySize(sourcePathInputValue)
if err != nil {
errMsg := fmt.Sprintf("【错误】无法判断源文件夹视频大小:%s", err.Error())
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
}
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("源视频目录大小:%s; 目标盘符剩余空间:%s", formatByteSize(size), formatByteSize(freeSpace)))
if freeSpace < (size + 1024*1024*1024*mySoftwareSetting.DiskProtect) {
errMsg := fmt.Sprintf("【失败】所选的保存路径空间太小了,设定的磁盘保护值为【%v】G\r\n转换后剩余[%s],建议您换一个盘符(例如D盘,E盘)", mySoftwareSetting.DiskProtect, formatByteSize(freeSpace-size))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
}
currentAbsPath, err := filepath.Abs("")
if err != nil {
errMsg := "无法判断当前文件夹信息,请重试"
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
} else {
currentDiskFreeSpace, err := getDiskFreeSpace(currentAbsPath)
if err != nil && err != windows.ERROR_SUCCESS {
errMsg := fmt.Sprintf("【错误】无法判断当前盘符大小:%s", err.Error())
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
} else {
if currentDiskFreeSpace <= (size + 1024*1024*1024*mySoftwareSetting.DiskProtect) {
errMsg := fmt.Sprintf("当前盘符太小了,请把软件移动到其他盘符再运行\r\n设定的磁盘保护值为【%v】G,但当前仅剩【%s】", mySoftwareSetting.DiskProtect, formatByteSize(currentDiskFreeSpace))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, errMsg+"\r\n")
dialog.NewInformation("报错啦!!!∑(゚Д゚ノ)ノ", errMsg, win).Show()
return
}
}
}
// 解析视频
videoInfos := getVideoInfos(sourcePathInputValue)
// 更新视频个数
videoCnt = len(videoInfos)
eventhub.Publish(videoCntUpdateEvent, videoCnt)
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("找到视频个数:%d", videoCnt))
successCnt = 0
skipCnt = 0
for i := range videoInfos {
videoInfo := videoInfos[i]
if util.IsAnyBlank(videoInfo.title, videoInfo.part, videoInfo.videoPath, videoInfo.audioPath) {
continue
} else {
// 判断是否设置不覆盖保存,且视频已存在
// 目标视频的所在目录
outputVideoSavePath := targetPathInputValue + string(filepath.Separator) + videoInfo.title
// 创建目录
err := os.MkdirAll(outputVideoSavePath, os.ModePerm)
if err != nil {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("无法创建文件夹:%s"+outputVideoSavePath))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, "已跳过该任务")
continue
}
// 拼接视频的名称
suffix := "mp4"
if mySoftwareSetting.OutputFormat != "" {
suffix = mySoftwareSetting.OutputFormat
}
if mySoftwareSetting.OnlyAudio {
suffix = "mp3"
if mySoftwareSetting.OutputAudioFormat != "" {
suffix = mySoftwareSetting.OutputAudioFormat
}
}
if mySoftwareSetting.OutputNameFormat != "" {
tName := mySoftwareSetting.OutputNameFormat
tName = strings.ReplaceAll(tName, "$[page]", fmt.Sprintf("%d", videoInfo.page))
tName = strings.ReplaceAll(tName, "$[part]", videoInfo.part)
tName = strings.ReplaceAll(tName, "$[title]", videoInfo.title)
outputVideoSavePath = outputVideoSavePath + string(filepath.Separator) + tName + "." + suffix
} else {
outputVideoSavePath = outputVideoSavePath + string(filepath.Separator) + strconv.Itoa(videoInfo.page) + "-" + videoInfo.part + "." + suffix
}
// 如果设置了,不覆盖转换则检测文件是否已经存在
if !mySoftwareSetting.OverrideOutput {
_, err = os.Stat(outputVideoSavePath)
if err == nil {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("视频[%s: %d、%s]已存在,不处理", videoInfo.title, videoInfo.page, videoInfo.part))
skipCnt++
continue
}
}
// 如果是电脑端的缓存,需要先解密
if videoInfo.fromPC {
abs, err := filepath.Abs("")
if err != nil {
continue
}
tempPath := abs + string(filepath.Separator) + ".bilibili_convert_temp" + string(filepath.Separator) + uuid.New().String() + ".m4s"
// 如果仅仅处理音频,就不需要解析视频文件
if !mySoftwareSetting.OnlyAudio {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>开始解密视频[%s: %s、%s]", videoInfo.title, videoInfo.part, videoInfo.videoPath))
// 更新进度
eventhub.Publish(progressUpdateEvent, 0)
if util.DecodeM4s(videoInfo.videoPath, tempPath, func(p float32) {
if p < 0 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>视频[%s: %s、%s]解密失败!", videoInfo.title, videoInfo.part, videoInfo.videoPath))
} else if p >= 100 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>视频[%s: %s、%s]解密完成", videoInfo.title, videoInfo.part, videoInfo.videoPath))
} else {
eventhub.Publish(progressUpdateEvent, p)
}
}) {
videoInfo.videoPath = tempPath
} else {
continue
}
}
tempPath = abs + string(filepath.Separator) + ".bilibili_convert_temp" + string(filepath.Separator) + uuid.New().String() + ".m4s"
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>开始解密视频[%s: %s、%s]", videoInfo.title, videoInfo.part, videoInfo.audioPath))
// 更新进度
eventhub.Publish(progressUpdateEvent, 0)
if util.DecodeM4s(videoInfo.audioPath, tempPath, func(p float32) {
if p < 0 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>视频[%d、%s=>%s]解密失败!", videoInfo.page, videoInfo.part, videoInfo.audioPath))
} else if p == 100 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("==>视频[%d、%s=>%s]解密完成", videoInfo.page, videoInfo.part, videoInfo.audioPath))
} else {
eventhub.Publish(progressUpdateEvent, p)
}
}) {
videoInfo.audioPath = tempPath
} else {
continue
}
}
// 添加任务
jobs.Add(Job{
title: videoInfo.title,
sourcePath: videoInfo.sourcePath,
videoPath: videoInfo.videoPath,
audioPath: videoInfo.audioPath,
part: videoInfo.part,
page: videoInfo.page,
fromPC: videoInfo.fromPC,
savePath: outputVideoSavePath,
overrideOutput: mySoftwareSetting.OverrideOutput,
sourceClearFlag: mySoftwareSetting.SourceClearFlag,
onlyAudio: mySoftwareSetting.OnlyAudio,
bitrate: mySoftwareSetting.Bitrate,
})
}
}
jobStart = true
executeFlag = true
} else {
msg := "无法检测ffmpeg环境,请重新下载该软件或者更新后重试"
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, msg)
}
})
// 清除日志
clearLogBtn := widget.NewButton("清空日志", func() {
eventhub.Publish(logAreaEvent, logAreaEventCommandClear)
})
// 进度条
backGroundCircle := canvas.NewCircle(color.NRGBA{R: 251, G: 251, B: 251, A: 255})
backGroundCircle.StrokeColor = color.Gray{0x99}
backGroundCircle.StrokeWidth = 2
backGroundCircle.Resize(fyne.NewSize(progressCircleSize, progressCircleSize))
canvas.Refresh(backGroundCircle)
progressCircle = canvas.NewCircle(color.NRGBA{R: 207, G: 255, B: 134, A: 255})
progress := container.NewWithoutLayout(backGroundCircle, progressCircle)
// label
progressLabel = widget.NewLabel(progressLabelValue)
progressLabelBorder := container.NewBorder(layout.NewSpacer(), nil, widget.NewLabel("进度:"), progressLabel)
videoCntLabel = widget.NewLabel(videoCntLabelValue)
videoCntLabelBorder := container.NewBorder(nil, nil, widget.NewLabel("视频数:"), videoCntLabel)
successCntLabel = widget.NewLabel(successCntLabelValue)
successCntLabelBorder := container.NewBorder(nil, nil, widget.NewLabel("转换完成数:"), successCntLabel)
// 赞助
file, _ := assets.ReadFile("assets/qrCode.png")
qrCode := canvas.NewImageFromResource(fyne.NewStaticResource("qrCode.png", file))
qrCode.Resize(fyne.NewSize(progressCircleSize, progressCircleSize))
qrCode.Refresh()
sponsor := container.NewGridWrap(fyne.NewSize(progressCircleSize, progressCircleSize), qrCode)
control := container.NewVBox(
transferBtn,
clearLogBtn,
progress,
widget.NewLabel(" "),
widget.NewLabel(" "),
widget.NewLabel(" "),
progressLabelBorder,
videoCntLabelBorder,
successCntLabelBorder,
sponsor,
widget.NewLabel("↗↑疯狂暗示↑↑↖"))
logAreaEntry = widget.NewMultiLineEntry()
return container.NewBorder(layout.NewSpacer(), layout.NewSpacer(), control, nil, logAreaEntry)
}
// 菜单组
func getMenuGroup(myApp fyne.App, myWindow fyne.Window) *fyne.MainMenu {
settingMenuItem := fyne.NewMenuItem("软件设置", func() {
myWindow := myApp.NewWindow("软件设置")
myWindow.SetContent(mySoftwareSetting.GetMenu(myWindow))
myWindow.Resize(fyne.NewSize(600, 250))
myWindow.Show()
})
themeMenuItem := fyne.NewMenuItem("主题设置", func() {
w := myApp.NewWindow("主题设置")
w.SetContent(NewSettings().LoadAppearanceScreen(w))
w.Resize(fyne.NewSize(480, 480))
w.Show()
})
checkUpgradeMenu := fyne.NewMenuItem("检测更新", func() {
if !isProcessRunning("BilibiliConvertUpdate.exe") {
cmd := exec.Command("cmd", "/c", "start", "/b", "BilibiliConvertUpdate.exe", "-cmd", "upgrade", fmt.Sprintf("%d", versionCode))
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
_ = cmd.Run()
}
})
restartAndUpgradeMenu := fyne.NewMenuItem("重启并更新", func() {
if !isProcessRunning("BilibiliConvertUpdate.exe") {
cmd := exec.Command("cmd", "/c", "start", "/b", "BilibiliConvertUpdate.exe", "-cmd", "apply", fmt.Sprintf("%d", versionCode))
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
_ = cmd.Run()
}
})
return fyne.NewMainMenu(
// a quit item will be appended to our first menu
fyne.NewMenu("菜单", settingMenuItem, fyne.NewMenuItemSeparator(), themeMenuItem),
fyne.NewMenu("更新", checkUpgradeMenu, fyne.NewMenuItemSeparator(), restartAndUpgradeMenu),
)
}
// 检测进程是否在运行
func isProcessRunning(processName string) bool {
cmd := exec.Command("tasklist")
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
return strings.Contains(string(output), processName)
}
// 调用ffmpeg进行视频合并的task
func executeJob() {
if jobStart && jobs.Length() > 0 {
job := jobs.Remove()
videoInfo := job.(Job)
// 更新进度
eventhub.Publish(progressUpdateEvent, 0)
mergeM4S(videoInfo.videoPath, videoInfo.audioPath, videoInfo.savePath, videoInfo.overrideOutput, videoInfo.onlyAudio, videoInfo.bitrate, func(p float32) {
fmt.Println(p)
eventhub.Publish(progressUpdateEvent, p)
if p == 100 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("视频[%s: %d、%s]转换完成", videoInfo.title, videoInfo.page, videoInfo.part))
// 更新转换成功个数
successCnt++
eventhub.Publish(successCntUpdateEvent, successCnt)
if videoInfo.fromPC {
if !videoInfo.onlyAudio {
removeTempFileJob.Add(videoInfo.videoPath)
}
removeTempFileJob.Add(videoInfo.audioPath)
}
if videoInfo.sourceClearFlag && videoInfo.sourcePath != "" {
removeTempFileJob.Add(videoInfo.sourcePath)
}
}
})
} else if executeFlag && jobs.Length() == 0 {
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, "==========视频转换完成==========")
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("|| 视频数:%d\t\t\t\t||", videoCnt))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("|| 成功数:%d\t\t\t\t||", successCnt))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("|| 跳过数:%d\t\t\t\t||", skipCnt))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, fmt.Sprintf("|| 转换完成度:%.2f%%\t\t||", float32(successCnt+skipCnt)/float32(videoCnt)*float32(100)))
eventhub.Publish(logAreaEvent, logAreaEventCommandAppend, "================================")
jobStart = false
executeFlag = false
}
}
// 删除解密文件的task
func removeTempFileTask() {
if removeTempFileJob.Length() > 0 {
path := removeTempFileJob.Remove()
filePath := path.(string)
_ = os.RemoveAll(filePath)
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化