/* * @Author: BlackTeay * @Date: 2024-05-09 15:47:52 * @LastEditTime: 2024-05-09 16:07:41 * @LastEditors: BlackTeay * @Description: * @FilePath: /hls_builder/upload.go * Copyright 2024 JLNTV NMTD, All Rights Reserved. */ package main import ( "fmt" "log" "os" "os/exec" "path/filepath" "regexp" "strconv" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" ) type UploadStats struct { Successed int Failed int TotalSize string AverageSpeed string Elapsed string } // 上传到Ucloud func upload(localPath string, keyPath string, progressBar *widget.ProgressBar, myWindow fyne.Window, onComplete func()) { fmt.Println("上传到Ucloud") progressBar.SetValue(0) fmt.Println("localPath:", localPath) fmt.Println("keyPath:", keyPath) // 统计 .ts .m3u8 文件数量 tsFiles, m3u8Files, err := countMediaFiles(localPath) if err != nil { log.Fatalf("Error counting media files: %s", err) } fmt.Printf("Total .ts files: %d\n", tsFiles) fmt.Printf("Total .m3u8 files: %d\n", m3u8Files) filesTotal := float64(tsFiles + m3u8Files) fmt.Printf("Total files: %d\n", int(filesTotal)) // 获取 us3cli 的路径 us3cliPath, cleanup, err := getUs3cliPath() if err != nil { log.Fatal(err) } defer cleanup() // 确保在函数返回时删除临时目录 bucket := "us3://jlntv-live/replay/" + keyPath // us3ConfigPath, cleanup, err := getUs3Config() // if err != nil { // log.Fatal(err) // } // defer cleanup() // 确保在函数返回时删除临时目录 // cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--config", us3ConfigPath) cmd := exec.Command(us3cliPath, "cp", localPath, bucket, "-r", "--parallel", "20", "--accesskey", "TOKEN_6096c736-12b7-4c20-bfa5-e85e0e9c9b65", "--secretkey", "cc8a5965-3325-4231-9f64-a6626f624049", "--endpoint", "cn-bj.ufileos.com") //输出cmd fmt.Println(cmd.String()) stdout, err := cmd.StdoutPipe() if err != nil { log.Fatal("Error creating stdout pipe:", err) } if err := cmd.Start(); err != nil { log.Fatalf("Failed to start command: %s", err) } progress := 0.0 go func() { buffer := make([]byte, 4096) // 创建一个足够大的buffer for { n, err := stdout.Read(buffer) if n > 0 { fmt.Print("STDOUT:", string(buffer[:n])) // 打印实时输出 filesUploadedTotal, err := extractTotalValue(string(buffer[:n])) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Total:", filesUploadedTotal) } stats, err := parseUploadStats(string(buffer[:n])) if err != nil { fmt.Println("Error parsing upload stats:", err) } else { fmt.Printf("Uploaded: %d, Failed: %d, Total Size: %s, Speed: %s\n", stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed) if float64(stats.Successed) == filesTotal { progressBar.SetValue(1) showCustomDialog := func() { replayURL := fmt.Sprintf("https://video.jlntv.cn/replay/%s/playlist.m3u8", keyPath) copyBtn := widget.NewButton("复制地址", func() { fmt.Println("copyed:", replayURL) // dialog.ShowInformation("Submitted", "You submitted: "+input.Text, myWindow) clipboard := myWindow.Clipboard() clipboard.SetContent(replayURL) // showToast(myWindow, "地址已复制到剪贴板", 3*time.Second) dialog.ShowInformation("提示", "地址已复制到剪贴板!", myWindow) }) // 创建自定义内容的容器 content := container.NewVBox( widget.NewLabel("Upload Stats:"), widget.NewLabel(fmt.Sprintf("用时:%s秒", stats.Elapsed)), widget.NewLabel(fmt.Sprintf("成功:%d", stats.Successed)), widget.NewLabel(fmt.Sprintf("失败:%d", stats.Failed)), widget.NewLabel(fmt.Sprintf("平均速度:%s", stats.AverageSpeed)), widget.NewLabel(fmt.Sprintf("总大小:%s", stats.TotalSize)), widget.NewLabel(fmt.Sprintf("回看地址: %s", replayURL)), copyBtn, ) // 设置内容容器的最小尺寸 content.Resize(fyne.NewSize(500, 600)) // 你可以根据需要调整这个尺寸 // 创建并显示自定义对话框 customDialog := dialog.NewCustom("上传成功", "关闭", content, myWindow) customDialog.Show() } showCustomDialog() // dialog.ShowInformation("完成", fmt.Sprintf("✅ HLS上传完毕\n总用时:%s秒\n成功:%d 失败:%d 总大小:%s 平均速度:%s", stats.Elapsed, stats.Successed, stats.Failed, stats.TotalSize, stats.AverageSpeed), myWindow) fmt.Println("Upload completed!") } else { progress = filesUploadedTotal / filesTotal fmt.Println("Progress:", progress) progressBar.SetValue(progress) } } } if err != nil { break } } }() if err := cmd.Wait(); err != nil { log.Printf("Command finished with error: %v", err) } if onComplete != nil { onComplete() } } // parseUploadStats 函数解析上传统计信息字符串,并返回一个包含上传统计详情的结构体指针。 // 参数 line 为待解析的上传统计信息字符串。 // 返回 *UploadStats 指向解析出的上传统计详情结构体。 // 返回 error 表示在解析过程中遇到的任何错误。 func parseUploadStats(line string) (*UploadStats, error) { stats := &UploadStats{} var err error // 初始化正则表达式用于匹配不同的上传统计信息。 successedRegex := regexp.MustCompile(`(\d+) Successed`) failedRegex := regexp.MustCompile(`(\d+) Failed`) sizeRegex := regexp.MustCompile(`Size: ([\d.]+ MB)`) speedRegex := regexp.MustCompile(`Average speed ([\d.]+ MB/s)`) elapsedRegex := regexp.MustCompile(`Elapsed\s*:\s*([\d.]+)s`) // 提取成功上传的文件数量。 successedMatches := successedRegex.FindStringSubmatch(line) if len(successedMatches) > 1 { stats.Successed, err = strconv.Atoi(successedMatches[1]) if err != nil { return nil, fmt.Errorf("error parsing successed number: %v", err) } } // 提取失败上传的文件数量。 failedMatches := failedRegex.FindStringSubmatch(line) if len(failedMatches) > 1 { stats.Failed, err = strconv.Atoi(failedMatches[1]) if err != nil { return nil, fmt.Errorf("error parsing failed number: %v", err) } } // 提取上传的总大小。 sizeMatches := sizeRegex.FindStringSubmatch(line) if len(sizeMatches) > 1 { stats.TotalSize = sizeMatches[1] } // 提取平均上传速度。 speedMatches := speedRegex.FindStringSubmatch(line) if len(speedMatches) > 1 { stats.AverageSpeed = speedMatches[1] } // 提取上传所花费的时间。 elapsedMatches := elapsedRegex.FindStringSubmatch(line) if len(elapsedMatches) > 1 { stats.Elapsed = elapsedMatches[1] } return stats, nil } // extractTotalValue 函数从给定的文本中提取总值。 // // 参数: // // text string - 包含待搜索总值的文本。 // // 返回值: // // float64 - 从文本中提取到的总值,如果未找到则返回默认值200。 // error - 如果在提取过程中发生错误,则返回相应的错误;否则返回nil。 func extractTotalValue(text string) (float64, error) { // 使用正则表达式匹配文本中的总值部分 re := regexp.MustCompile(`Total:(\d+)`) matches := re.FindStringSubmatch(text) if len(matches) > 1 { // 将匹配到的总值字符串转换为整数 total, err := strconv.Atoi(matches[1]) if err != nil { // 如果转换过程中发生错误,返回错误信息 return 0, err } // 返回转换后的总值 return float64(total), nil } // 如果未在文本中找到总值,返回默认值200和一个错误信息 return 200, fmt.Errorf("no total found in the text") } // countMediaFiles 统计给定目录中 .ts 和 .m3u8 文件的数量。 // // 参数: // // directory string - 需要遍历统计文件的目录路径。 // // 返回值: // // int - .ts 文件的数量。 // int - .m3u8 文件的数量。 // error - 遍历目录过程中遇到的任何错误。 func countMediaFiles(directory string) (int, int, error) { var tsCount, m3u8Count int // 分别用于记录 .ts 和 .m3u8 文件的数量 // 使用 filepath.Walk 遍历指定目录及其子目录中的所有文件 err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { // 检查遍历过程是否遇到错误 if err != nil { return err // 如果有错误,则终止遍历并返回该错误 } // 检查当前路径项是否为文件,不遍历目录 if !info.IsDir() { // 根据文件扩展名统计 .ts 和 .m3u8 文件数量 switch filepath.Ext(info.Name()) { case ".ts": tsCount++ // .ts 文件计数 case ".m3u8": m3u8Count++ // .m3u8 文件计数 } } return nil // 继续遍历下一个文件或目录 }) // 返回 .ts 和 .m3u8 文件的计数结果,以及遍历过程中可能遇到的错误 return tsCount, m3u8Count, err }