package core import ( "OdMediaPicker/util" "OdMediaPicker/vars" "bytes" _ "embed" "fmt" "github.com/redmask-hb/GoSimplePrint/goPrint" "math" "os" "os/exec" "strconv" "strings" "time" ) //go:embed files/ffprobe.exe var ffprobeWin64 []byte var videoTag = "[PickVideo]" // 标记文件已经被整理过 var ignoreVideoPathList []string // 忽略的文件路径 var readErrorVideoPathList []string // 读取信息异常的路径 var videoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比 var videoPath2WidthHeightTagMap = make(map[string]string) // 视频路径和宽高比[640x480] var videoPath2DurationMap = make(map[string]string) // 视频路径和时长 // 支持的视频格式 var supportVideoTypes = []string{ ".ts", ".flv", ".rm", ".avi", ".mp4", ".mov", ".mpg", ".mkv", ".m4v", ".rmvb", ".3gp", ".3g2", ".webm", ".wmv", } // 水平视频 var horizontalNormalVideoList []string var horizontalGifVideoList []string var horizontal1KVideoList []string var horizontal2KVideoList []string var horizontal3KVideoList []string var horizontal4KVideoList []string var horizontal5KVideoList []string var horizontal6KVideoList []string var horizontal7KVideoList []string var horizontal8KVideoList []string var horizontal9KVideoList []string var horizontalHKVideoList []string // 标准横向视频 var horizontalStandard720PVideoList []string var horizontalStandard1080PVideoList []string var horizontalStandard4KVideoList []string var horizontalStandard8KVideoList []string // 垂直视频 var verticalNormalVideoList []string var verticalGifVideoList []string var vertical1KVideoList []string var vertical2KVideoList []string var vertical3KVideoList []string var vertical4KVideoList []string var vertical5KVideoList []string var vertical6KVideoList []string var vertical7KVideoList []string var vertical8KVideoList []string var vertical9KVideoList []string var verticalHKVideoList []string // 等比视频 var squareNormalVideoList []string var squareGifVideoList []string var square1KVideoList []string var square2KVideoList []string var square3KVideoList []string var square4KVideoList []string var square5KVideoList []string var square6KVideoList []string var square7KVideoList []string var square8KVideoList []string var square9KVideoList []string var squareHKVideoList []string var squareStandard720PVideoList []string var squareStandard1080PVideoList []string var squareStandard4KVideoList []string var squareStandard8KVideoList []string func DoHandleVideo(rootDir string) { // 释放ffprobe readerFileName := "./ffprobe.exe" if util.CheckFileIsExist(readerFileName) { _ = os.Remove(readerFileName) } err := util.WriteByteArraysToFile(ffprobeWin64, readerFileName) if err != nil { fmt.Println("=== 释放解码器失败, 5秒后将自动退出", err) time.Sleep(time.Second * 5) return } total := len(vars.GlobalVideoPathList) // 总数 successCount := 0 // 成功数 errorCount := 0 // 失败数 ignoreCount := 0 // 忽略数 for _, videoFilePath := range vars.GlobalVideoPathList { suffix := vars.GlobalFilePath2FileExtMap[videoFilePath] if isSupportVideo(suffix) { width, height, err := readVideoWidthHeight(videoFilePath) if err == nil { successCount = successCount + 1 videoPath2WidthHeightMap[videoFilePath] = fmt.Sprintf("%d-%d", width, height) videoPath2WidthHeightTagMap[videoFilePath] = fmt.Sprintf("[%dx%d]", width, height) fmt.Printf("=== Video总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount) duration := readVideoDuration(videoFilePath) if duration == 0 { videoPath2DurationMap[videoFilePath] = "0H0M0S" } else { videoPath2DurationMap[videoFilePath] = util.SecondsToHms(duration) } } else { errorCount = errorCount + 1 readErrorVideoPathList = append(readErrorVideoPathList, videoFilePath) fmt.Printf("=== 异常视频: %s \n", videoFilePath) } continue } // 其他的直接先忽略吧, 爱改改, 不改拉倒 ignoreCount = ignoreCount + 1 ignoreVideoPathList = append(ignoreVideoPathList, videoFilePath) } //uuid := strings.ReplaceAll(uuid.NewV4().String(), "-", "") if len(readErrorVideoPathList) > 0 { readInfoErrorPath := rootDir + string(os.PathSeparator) + "读取异常" if util.CreateDir(readInfoErrorPath) { doMoveFileToDir(readErrorVideoPathList, readInfoErrorPath) } } if len(ignoreVideoPathList) > 0 { ignorePath := rootDir + string(os.PathSeparator) + "已忽略" if util.CreateDir(ignorePath) { doMoveFileToDir(ignoreVideoPathList, ignorePath) } } doPickVideoFile(rootDir, videoPath2WidthHeightMap) if util.CheckFileIsExist(readerFileName) { _ = os.Remove(readerFileName) } fmt.Printf("=== 视频处理完毕 \n\n") } // getVideoDuration 使用ffprobe获取视频时长 func getVideoDuration(ffmpegExecPath string, videoPath string) (float64, error) { // ffprobe命令,-v error 用于减少输出信息,-show_entries format=duration -of compact=p=0,nk=1 用于只输出时长 cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath) var stderr bytes.Buffer cmd.Stderr = &stderr output, err := cmd.Output() if err != nil { return 0, fmt.Errorf("ffprobe failed with error: %v, stderr: %q", err, stderr.String()) } // 解析输出的时长字符串为浮点数 durationStr := strings.TrimSpace(string(output)) duration, err := strconv.ParseFloat(durationStr, 64) if err != nil { return 0, fmt.Errorf("failed to parse duration: %q, error: %v", durationStr, err) } return duration, nil } func getVideoResolution(ffmpegExecPath string, filePath string) (width int, height int, err error) { // 构建ffprobe命令 cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "stream=width,height", "-of", "csv=p=0:s=x", filePath) // 执行命令并捕获输出 output, err := cmd.Output() if err != nil { return 0, 0, fmt.Errorf("failed to run ffprobe: %w", err) } // 解析输出字符串,格式应为 "宽度,高度" resolutionStr := strings.TrimSpace(string(output)) parts := strings.Split(resolutionStr, ",") if len(parts) == 2 { width = util.String2int(parts[0]) height = util.String2int(parts[1]) return width, height, nil } parts = strings.Split(resolutionStr, "x") if len(parts) == 2 { width = util.String2int(parts[0]) height = util.String2int(parts[1]) return width, height, nil } parts = strings.Split(resolutionStr, "\r\n\r\n\r\n\r\n") if len(parts) == 2 { tempHw := parts[0] parts = strings.Split(tempHw, "x") if len(parts) == 2 { width = util.String2int(parts[0]) height = util.String2int(parts[1]) return width, height, nil } } parts = strings.Split(resolutionStr, "x") if len(parts) == 3 { width = util.String2int(parts[0]) height = util.String2int(parts[1]) return width, height, nil } return 0, 0, fmt.Errorf("invalid resolution format: %s", resolutionStr) } // 获取视频的时长,单位秒 func readVideoDuration(videoFilePath string) int { duration, err := getVideoDuration("./ffprobe.exe", videoFilePath) if err != nil { fmt.Println("=== Error getting video duration:", err) return 0 } //fmt.Printf("=== Video duration: %.2f seconds\n", duration) return int(math.Floor(duration)) // 向下取整 } // 获取视频的分辨率 func readVideoWidthHeight(videoFilePath string) (int, int, error) { width, height, err := getVideoResolution("./ffprobe.exe", videoFilePath) if err != nil { fmt.Printf("=== Error getting resolution: %v\n", err) return 0, 0, err } //fmt.Printf("=== Video resolution: %dx%d\n", width, height) return width, height, nil } // 条件视频并分组存放 func doPickVideoFile(rootDir string, videoPath2WidthHeightMap map[string]string) { if len(videoPath2WidthHeightMap) == 0 { fmt.Printf("=== 当前目录下没有扫描到视频文件, %s \n", rootDir) readerFileName := "./ffprobe.exe" if util.CheckFileIsExist(readerFileName) { _ = os.Remove(readerFileName) } return } for currentVideoPath, infoStr := range videoPath2WidthHeightMap { width2Height := strings.Split(infoStr, "-") width := util.String2int(width2Height[0]) height := util.String2int(width2Height[1]) suffix := vars.GlobalFilePath2FileExtMap[currentVideoPath] if width > height { handleHorizontalVideo(currentVideoPath, width, height, suffix) continue } if width < height { handleVerticalVideo(currentVideoPath, height, suffix) continue } handleSquareVideo(currentVideoPath, width, height, suffix) } moveNormalVideo(rootDir) moveHorizontalVideo(rootDir) moveVerticalVideo(rootDir) moveSquareVideo(rootDir) } // 移动垂直视频 func moveVerticalVideo(rootDir string) { if len(vertical1KVideoList) > 0 { renameFileV2("[V][1k]", vertical1KVideoList) } if len(vertical2KVideoList) > 0 { renameFileV2("[V][2k]", vertical2KVideoList) } if len(vertical3KVideoList) > 0 { renameFileV2("[V][3k]", vertical3KVideoList) } if len(vertical4KVideoList) > 0 { renameFileV2("[V][4k]", vertical4KVideoList) } if len(vertical5KVideoList) > 0 { renameFileV2("[V][5k]", vertical5KVideoList) } if len(vertical6KVideoList) > 0 { renameFileV2("[V][6k]", vertical6KVideoList) } if len(vertical7KVideoList) > 0 { renameFileV2("[V][7k]", vertical7KVideoList) } if len(vertical8KVideoList) > 0 { renameFileV2("[V][8k]", vertical8KVideoList) } if len(vertical9KVideoList) > 0 { renameFileV2("[V][9k]", vertical9KVideoList) } if len(verticalHKVideoList) > 0 { renameFileV2("[V][原]", verticalHKVideoList) } } // 移动文件到根目录 func renameFile(rootDir string, modelType string, videoList []string, pathSeparator string) { total := len(videoList) var count = 0 bar := goPrint.NewBar(100) bar.SetNotice("=== 重命名文件:") bar.SetGraph(">") for _, videoFilePath := range videoList { wh := videoPath2WidthHeightTagMap[videoFilePath] fileName := vars.GlobalFilePath2FileNameMap[videoFilePath] if strings.Contains(fileName, videoTag) { // 处理过了 fileNames := strings.Split(fileName, videoTag) if len(fileNames) == 2 { fileName = fileNames[1] targetFilePath := rootDir + pathSeparator + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + fileName err := os.Rename(videoFilePath, targetFilePath) if err != nil { fmt.Printf("=== 重命名异常: %s \n", videoFilePath) } } } else { targetFilePath := rootDir + pathSeparator + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + " - " + fileName err := os.Rename(videoFilePath, targetFilePath) if err != nil { fmt.Printf("=== 重命名异常: %s \n", videoFilePath) } } count = count + 1 bar.PrintBar(util.CalcPercentage(count, total)) } bar.PrintEnd("=== Finish") } // 移动文件到原目录 func renameFileV2(modelType string, videoList []string) { total := len(videoList) var count = 0 bar := goPrint.NewBar(100) bar.SetNotice("=== 重命名文件:") bar.SetGraph(">") for _, videoFilePath := range videoList { wh := videoPath2WidthHeightTagMap[videoFilePath] fileName := vars.GlobalFilePath2FileNameMap[videoFilePath] filePath := util.GetFileDirectory(videoFilePath) if strings.Contains(fileName, videoTag) { // 处理过了 fileNames := strings.Split(fileName, videoTag) if len(fileNames) == 2 { fileName = fileNames[1] targetFilePath := filePath + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + fileName err := os.Rename(videoFilePath, targetFilePath) if err != nil { fmt.Printf("=== 重命名异常: %s \n", videoFilePath) } } } else { targetFilePath := filePath + "[" + videoPath2DurationMap[videoFilePath] + "]" + modelType + wh + videoTag + " - " + fileName err := os.Rename(videoFilePath, targetFilePath) if err != nil { fmt.Printf("=== 重命名异常: %s \n", videoFilePath) } } count = count + 1 bar.PrintBar(util.CalcPercentage(count, total)) } bar.PrintEnd("=== Finish") } // 移动水平视频 func moveHorizontalVideo(rootDir string) { if len(horizontal1KVideoList) > 0 { renameFileV2("[H][1k]", horizontal1KVideoList) } if len(horizontal2KVideoList) > 0 { renameFileV2("[H][2k]", horizontal2KVideoList) } if len(horizontal3KVideoList) > 0 { renameFileV2("[H][3k]", horizontal3KVideoList) } if len(horizontal4KVideoList) > 0 { renameFileV2("[H][4k]", horizontal4KVideoList) } if len(horizontal5KVideoList) > 0 { renameFileV2("[H][5k]", horizontal5KVideoList) } if len(horizontal6KVideoList) > 0 { renameFileV2("[H][6k]", horizontal6KVideoList) } if len(horizontal7KVideoList) > 0 { renameFileV2("[H][7k]", horizontal7KVideoList) } if len(horizontal8KVideoList) > 0 { renameFileV2("[H][8k]", horizontal8KVideoList) } if len(horizontal9KVideoList) > 0 { renameFileV2("[H][9k]", horizontal9KVideoList) } if len(horizontalHKVideoList) > 0 { renameFileV2("[H][原]", horizontalHKVideoList) } if len(horizontalStandard720PVideoList) > 0 { renameFileV2("[H][720P]", horizontalStandard720PVideoList) } if len(horizontalStandard1080PVideoList) > 0 { renameFileV2("[H][1080P]", horizontalStandard1080PVideoList) } if len(horizontalStandard4KVideoList) > 0 { renameFileV2("[H][4KP]", horizontalStandard4KVideoList) } if len(horizontalStandard8KVideoList) > 0 { renameFileV2("[H][8KP]", horizontalStandard8KVideoList) } } // 移动等比视频 func moveSquareVideo(rootDir string) { if len(square1KVideoList) > 0 { renameFileV2("[M][1k]", square1KVideoList) } if len(square2KVideoList) > 0 { renameFileV2("[M][2k]", square2KVideoList) } if len(square3KVideoList) > 0 { renameFileV2("[M][3k]", square3KVideoList) } if len(square4KVideoList) > 0 { renameFileV2("[M][4k]", square4KVideoList) } if len(square5KVideoList) > 0 { renameFileV2("[M][5k]", square5KVideoList) } if len(square6KVideoList) > 0 { renameFileV2("[M][6k]", square6KVideoList) } if len(square7KVideoList) > 0 { renameFileV2("[M][7k]", square7KVideoList) } if len(square8KVideoList) > 0 { renameFileV2("[M][8k]", square8KVideoList) } if len(square9KVideoList) > 0 { renameFileV2("[M][9k]", square9KVideoList) } if len(squareHKVideoList) > 0 { renameFileV2("[M][原]", squareHKVideoList) } if len(squareStandard720PVideoList) > 0 { renameFileV2("[M][720P]", squareStandard720PVideoList) } if len(squareStandard1080PVideoList) > 0 { renameFileV2("[M][1080P]", squareStandard1080PVideoList) } if len(squareStandard4KVideoList) > 0 { renameFileV2("[M][4KP]", squareStandard4KVideoList) } if len(squareStandard8KVideoList) > 0 { renameFileV2("[M][8KP]", squareStandard8KVideoList) } } // 移动普通视频 func moveNormalVideo(rootDir string) { //pathSeparator := string(os.PathSeparator) if len(horizontalNormalVideoList) > 0 { //renameFile(rootDir, "[L]", horizontalNormalVideoList, pathSeparator) renameFileV2("[L]", horizontalNormalVideoList) } if len(verticalNormalVideoList) > 0 { renameFileV2("[L]", verticalNormalVideoList) } if len(squareNormalVideoList) > 0 { renameFileV2("[L]", squareNormalVideoList) } } // 处理垂直视频 func handleVerticalVideo(currentVideoPath string, height int, suffix string) { if strings.EqualFold(suffix, ".gif") { verticalGifVideoList = append(verticalGifVideoList, currentVideoPath) return } if height < 1000 { verticalNormalVideoList = append(verticalNormalVideoList, currentVideoPath) } else if height >= 1000 && height < 2000 { vertical1KVideoList = append(vertical1KVideoList, currentVideoPath) } else if height >= 2000 && height < 3000 { vertical2KVideoList = append(vertical2KVideoList, currentVideoPath) } else if height >= 3000 && height < 4000 { vertical3KVideoList = append(vertical3KVideoList, currentVideoPath) } else if height >= 4000 && height < 5000 { vertical4KVideoList = append(vertical4KVideoList, currentVideoPath) } else if height >= 5000 && height < 6000 { vertical5KVideoList = append(vertical5KVideoList, currentVideoPath) } else if height >= 6000 && height < 7000 { vertical6KVideoList = append(vertical6KVideoList, currentVideoPath) } else if height >= 7000 && height < 8000 { vertical7KVideoList = append(vertical7KVideoList, currentVideoPath) } else if height >= 8000 && height < 9000 { vertical8KVideoList = append(vertical8KVideoList, currentVideoPath) } else if height >= 9000 && height < 10000 { vertical9KVideoList = append(vertical9KVideoList, currentVideoPath) } else if height >= 10000 { verticalHKVideoList = append(verticalHKVideoList, currentVideoPath) } } // 处理横向视频 func handleHorizontalVideo(currentVideoPath string, width int, height int, suffix string) { if strings.EqualFold(suffix, ".gif") { horizontalGifVideoList = append(horizontalGifVideoList, currentVideoPath) return } if width < 1000 { // 160 × 120 // 320 × 180 // 320 × 240 // 640 × 360 // 640 × 480 horizontalNormalVideoList = append(horizontalNormalVideoList, currentVideoPath) } else if width >= 1000 && width < 2000 { // 1280 x 720 -> 720p if width == 1280 && height == 720 { horizontalStandard720PVideoList = append(horizontalStandard720PVideoList, currentVideoPath) return } // 1920 x 1080 -> 1080p if width == 1920 && height == 1080 { horizontalStandard1080PVideoList = append(horizontalStandard1080PVideoList, currentVideoPath) return } horizontal1KVideoList = append(horizontal1KVideoList, currentVideoPath) } else if width >= 2000 && width < 3000 { horizontal2KVideoList = append(horizontal2KVideoList, currentVideoPath) } else if width >= 3000 && width < 4000 { // 3840 x 2160 -> 4k if width == 3840 && height == 2160 { horizontalStandard4KVideoList = append(horizontalStandard4KVideoList, currentVideoPath) return } horizontal3KVideoList = append(horizontal3KVideoList, currentVideoPath) } else if width >= 4000 && width < 5000 { horizontal4KVideoList = append(horizontal4KVideoList, currentVideoPath) } else if width >= 5000 && width < 6000 { horizontal5KVideoList = append(horizontal5KVideoList, currentVideoPath) } else if width >= 6000 && width < 7000 { horizontal6KVideoList = append(horizontal6KVideoList, currentVideoPath) } else if width >= 7000 && width < 8000 { // 7680 x 4320 -> 8k if width == 7680 && height == 4320 { horizontalStandard8KVideoList = append(horizontalStandard8KVideoList, currentVideoPath) return } horizontal7KVideoList = append(horizontal7KVideoList, currentVideoPath) } else if width >= 8000 && width < 9000 { horizontal8KVideoList = append(horizontal8KVideoList, currentVideoPath) } else if width >= 9000 && width < 10000 { horizontal9KVideoList = append(horizontal9KVideoList, currentVideoPath) } else if width >= 10000 { horizontalHKVideoList = append(horizontalHKVideoList, currentVideoPath) } } // 处理等比视频 func handleSquareVideo(currentVideoPath string, width int, height int, suffix string) { if strings.EqualFold(suffix, ".gif") { squareGifVideoList = append(squareGifVideoList, currentVideoPath) return } if width < 1000 { squareNormalVideoList = append(squareNormalVideoList, currentVideoPath) } else if width >= 1000 && width < 2000 { // 1280 x 720 -> 720p if width == 1280 && height == 720 { squareStandard720PVideoList = append(squareStandard720PVideoList, currentVideoPath) return } // 1920 x 1080 -> 1080p if width == 1920 && height == 1080 { squareStandard1080PVideoList = append(squareStandard1080PVideoList, currentVideoPath) return } square1KVideoList = append(square1KVideoList, currentVideoPath) } else if width >= 2000 && width < 3000 { square2KVideoList = append(square2KVideoList, currentVideoPath) } else if width >= 3000 && width < 4000 { // 3840 x 2160 -> 4k if width == 3840 && height == 2160 { squareStandard4KVideoList = append(squareStandard4KVideoList, currentVideoPath) return } square3KVideoList = append(square3KVideoList, currentVideoPath) } else if width >= 4000 && width < 5000 { square4KVideoList = append(square4KVideoList, currentVideoPath) } else if width >= 5000 && width < 6000 { square5KVideoList = append(square5KVideoList, currentVideoPath) } else if width >= 6000 && width < 7000 { square6KVideoList = append(square6KVideoList, currentVideoPath) } else if width >= 7000 && width < 8000 { // 7680 x 4320 -> 8k if width == 7680 && height == 4320 { squareStandard8KVideoList = append(squareStandard8KVideoList, currentVideoPath) return } square7KVideoList = append(square7KVideoList, currentVideoPath) } else if width >= 8000 && width < 9000 { square8KVideoList = append(square8KVideoList, currentVideoPath) } else if width >= 9000 && width < 10000 { square9KVideoList = append(square9KVideoList, currentVideoPath) } else if width >= 10000 { squareHKVideoList = append(squareHKVideoList, currentVideoPath) } } // 判断是否属于支持的视频 func isSupportVideo(videoType string) bool { for _, supportVideoType := range supportVideoTypes { if strings.EqualFold(videoType, supportVideoType) { return true } } return false } // 批量移动文件到目录 func doMoveFileToDir(filePatnList []string, videoDirPath string) { total := len(filePatnList) var count = 0 bar := goPrint.NewBar(100) bar.SetNotice("=== 移动文件到目录:") bar.SetGraph(">") pathSeparator := string(os.PathSeparator) for _, videoFilePath := range filePatnList { moveFileToDir(videoFilePath, videoDirPath+pathSeparator) count = count + 1 bar.PrintBar(util.CalcPercentage(count, total)) } bar.PrintEnd("=== Finish") } // 移动文件到目录 func moveFileToDir(sourceFilePath string, targetDirectory string) bool { splits := strings.Split(sourceFilePath, string(os.PathSeparator)) fileName := splits[len(splits)-1] targetFilePath := targetDirectory + fileName err := os.Rename(sourceFilePath, targetFilePath) //fmt.Printf("=== 移动文件, 源: %s, 目标: %s \n", sourceFilePath, targetFilePath) return err == nil }