diff --git a/.gitignore b/.gitignore index adf8f72..72ddd2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +### IDEA ### +~/* +.idea/* +*.iml +*/target/* +*/*.iml +/.gradle/ +/application.pid # ---> Go # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore diff --git a/README.md b/README.md index 12012c6..91fa503 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -# media-picker +# 根据视频/图片分辨率分组工具 -双击快速整理视频和图片 +- 依赖ffmpeg, https://ffmpeg.org/download.html +- https://github.com/BtbN/FFmpeg-Builds/releases -# 演示视频 -xxxxxx \ No newline at end of file +## 构建编译 +```shell +sh build.sh +``` + +## 使用方法 +```text +# 双击有风险, 双击需谨慎 +讲OdMediaPickerRelease.exe拖拽到需要整理的文件夹内,双击运行即可 +``` diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..8e1cc14 --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +rm -f OdMediaPicker.exe +rm -f OdMediaPickerRelease.exe +# build +go build -o OdMediaPicker.exe main.go +# upx compress +./upx -o OdMediaPickerRelease.exe OdMediaPicker.exe +rm -f OdMediaPicker.exe diff --git a/core/bmp.go b/core/bmp.go new file mode 100644 index 0000000..8f764bb --- /dev/null +++ b/core/bmp.go @@ -0,0 +1,79 @@ +package core + +import ( + "encoding/binary" + "fmt" + "os" +) + +// BMPFileHeader represents the bitmap file header structure. +type BMPFileHeader struct { + FileType [2]byte + FileSize uint32 + Reserved1 uint16 + Reserved2 uint16 + DataOffset uint32 +} + +// BMPInfoHeader represents the bitmap info header structure. +type BMPInfoHeader struct { + Size uint32 + Width int32 + Height int32 + Planes uint16 + BitsPerPixel uint16 + Compression uint32 + SizeImage uint32 + XPelsPerMeter int32 + YPelsPerMeter int32 + ColorsUsed uint32 + ColorsImportant uint32 +} + +func readBmpInfo(filePath string) (error, int32, int32) { + file, err := os.Open(filePath) + if err != nil { + fmt.Printf("=== Failed to open file: %v\n", err) + return err, 0, 0 + } + defer file.Close() + + // Read the file header. + var fileHeader BMPFileHeader + if err := binary.Read(file, binary.LittleEndian, &fileHeader); err != nil { + fmt.Printf("=== Failed to Read file: %v\n", err) + return err, 0, 0 + } + + // Check if it's a valid BMP file by verifying the file type. + if string(fileHeader.FileType[:]) != "BM" { + fmt.Printf("=== Not a valid BMP file: %v\n", err) + return err, 0, 0 + } + + // Read the info header. + var infoHeader BMPInfoHeader + if err := binary.Read(file, binary.LittleEndian, &infoHeader); err != nil { + fmt.Printf("=== Failed to Read file: %v\n", err) + return err, 0, 0 + } + return err, infoHeader.Width, infoHeader.Height + //fmt.Printf("=== File size: %d bytes\n", fileHeader.FileSize) + //fmt.Printf("=== Image dimensions: %dx%d\n", ) + //fmt.Printf("=== Bits per pixel: %d\n", infoHeader.BitsPerPixel) + + // At this point, you would typically read the pixel data into a slice, + // taking into account any padding required for alignment and the specific + // pixel format (e.g., RGB, RGBA, indexed color). This part is omitted here. + + // You may also need to skip over a possible color palette if present. + // The size of the palette can be inferred from the number of colors used and + // the bits per pixel value. + + // For simplicity, we'll just read the rest of the file as raw bytes. + //data, err := ioutil.ReadAll(file) + //if err != nil { + // panic(err) + //} + //fmt.Printf("=== Read %d bytes of pixel data.\n", len(data)) +} diff --git a/core/file_scanner.go b/core/file_scanner.go new file mode 100644 index 0000000..8f0e21b --- /dev/null +++ b/core/file_scanner.go @@ -0,0 +1,93 @@ +package core + +import ( + "OdMediaPicker/util" + "OdMediaPicker/vars" + "fmt" + "github.com/redmask-hb/GoSimplePrint/goPrint" + "log" + "os" + "path" + "path/filepath" + "strings" +) + +type FileScanner struct { +} + +func (FileScanner) DoScan(rootDir string) { + fmt.Printf("=== 开始扫描文件 \n") + if err := filepath.Walk(rootDir, visit); err != nil { + log.Fatal(err) + } + doReadFileMimeInfo() +} + +func doReadFileMimeInfo() { + total := len(vars.GlobalFilePathList) + fmt.Printf("=== 文件总数: %d \n", total) + // 扫描文件mime信息 + var count = 0 + bar := goPrint.NewBar(100) + bar.SetNotice("=== 扫描文件Mime:") + bar.SetGraph(">") + bar.SetNoticeColor(goPrint.FontColor.Red) + for _, currentPath := range vars.GlobalFilePathList { + // mime + vars.GlobalFilePath2MimeInfoMap[currentPath] = util.ReadFileMimeInfo(currentPath).String() + count = count + 1 + bar.PrintBar(util.CalcPercentage(count, total)) + } + bar.PrintEnd("=== Finish") +} + +func (FileScanner) DoFilter() { + total := len(vars.GlobalFilePathList) + var count = 0 + bar := goPrint.NewBar(100) + bar.SetNotice("=== 过滤已支持的媒体:") + bar.SetGraph(">") + for _, globalFilePath := range vars.GlobalFilePathList { + fileMime := vars.GlobalFilePath2MimeInfoMap[globalFilePath] + count = count + 1 + if strings.Contains(fileMime, "video/") { // 视频 + vars.GlobalVideoPathList = append(vars.GlobalVideoPathList, globalFilePath) + bar.PrintBar(util.CalcPercentage(count, total)) + continue + } + // mime格式为application/octet-stream的视频 + ext := path.Ext(globalFilePath) + if isSupportVideo(ext) { + vars.GlobalVideoPathList = append(vars.GlobalVideoPathList, globalFilePath) + bar.PrintBar(util.CalcPercentage(count, total)) + continue + } + if strings.Contains(fileMime, "image/") { // 图片 + vars.GlobalImagePathList = append(vars.GlobalImagePathList, globalFilePath) + bar.PrintBar(util.CalcPercentage(count, total)) + continue + } + if isSupportImage(ext) { + vars.GlobalImagePathList = append(vars.GlobalImagePathList, globalFilePath) + bar.PrintBar(util.CalcPercentage(count, total)) + continue + } + // 其他的文件不处理 + } + bar.PrintEnd("=== Finish") +} + +// 定义walkFn回调函数visit +func visit(currentPath string, info os.FileInfo, err error) error { + if err != nil { + return err // 如果有错误,直接返回 + } + if !info.IsDir() { + vars.GlobalFilePathList = append(vars.GlobalFilePathList, currentPath) + // filename, include ext + vars.GlobalFilePath2FileNameMap[currentPath] = filepath.Base(currentPath) + // file ext + vars.GlobalFilePath2FileExtMap[currentPath] = path.Ext(currentPath) + } + return nil +} diff --git a/core/files/ffmpeg-master-latest-win64-gpl.zip b/core/files/ffmpeg-master-latest-win64-gpl.zip new file mode 100644 index 0000000..40e8ac4 Binary files /dev/null and b/core/files/ffmpeg-master-latest-win64-gpl.zip differ diff --git a/core/files/upx-4.2.3-win64.zip b/core/files/upx-4.2.3-win64.zip new file mode 100644 index 0000000..cce072a Binary files /dev/null and b/core/files/upx-4.2.3-win64.zip differ diff --git a/core/handle_image.go b/core/handle_image.go new file mode 100644 index 0000000..1a80c24 --- /dev/null +++ b/core/handle_image.go @@ -0,0 +1,547 @@ +package core + +import ( + "OdMediaPicker/util" + "OdMediaPicker/vars" + "fmt" + uuid "github.com/satori/go.uuid" + "image" + _ "image/gif" // 导入gif支持 + _ "image/jpeg" // 导入jpeg支持 + _ "image/png" // 导入png支持 + "os" + "strings" +) + +var ignoreImagePathList []string // 忽略的文件路径 +var readErrorImagePathList []string // 读取信息异常的路径 +var imagePath2WidthHeightMap = make(map[string]string) // 图片路径和宽高比 +var supportImageTypes = []string{ + ".bmp", + ".gif", + ".jpg", + ".jpeg", + ".jpe", + ".png", + ".webp", +} + +// 水平图片 +var normalImageList []string +var horizontalGifImageList []string +var horizontal2KImageList []string +var horizontal1KImageList []string +var horizontal3KImageList []string +var horizontal4KImageList []string +var horizontal5KImageList []string +var horizontal6KImageList []string +var horizontal7KImageList []string +var horizontal8KImageList []string +var horizontal9KImageList []string +var horizontalHKImageList []string + +// 标准横向图片 +var horizontalStandard720PImageList []string +var horizontalStandard1080PImageList []string +var horizontalStandard4KImageList []string +var horizontalStandard8KImageList []string + +// 垂直图片 +var verticalGifImageList []string +var vertical1KImageList []string +var vertical2KImageList []string +var vertical3KImageList []string +var vertical4KImageList []string +var vertical5KImageList []string +var vertical6KImageList []string +var vertical7KImageList []string +var vertical8KImageList []string +var vertical9KImageList []string +var verticalHKImageList []string + +// 等比图片 +var squareGifImageList []string +var square1KImageList []string +var square2KImageList []string +var square3KImageList []string +var square4KImageList []string +var square5KImageList []string +var square6KImageList []string +var square7KImageList []string +var square8KImageList []string +var square9KImageList []string +var squareHKImageList []string + +// 标准等比图片 +var squareStandard720PImageList []string +var squareStandard1080PImageList []string +var squareStandard4KImageList []string +var squareStandard8KImageList []string + +var psdImageList []string + +func DoHandleImage(rootDir string) { + total := len(vars.GlobalImagePathList) // 总数 + successCount := 0 // 成功数 + errorCount := 0 // 失败数 + ignoreCount := 0 // 忽略数 + for _, imageFilePath := range vars.GlobalImagePathList { + suffix := vars.GlobalFilePath2FileExtMap[imageFilePath] + if isSupportImage(suffix) { + err, width, height := readImageInfo(imageFilePath) + if err == nil { + successCount = successCount + 1 + imagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", width, height) + fmt.Printf("=== Image总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount) + } else { + errorCount = errorCount + 1 + readErrorImagePathList = append(readErrorImagePathList, imageFilePath) + fmt.Printf("=== 异常图片: %s \n", imageFilePath) + } + continue + } + if strings.EqualFold(suffix, ".webp") { // 特殊文件处理, webp为网络常用图片格式 + webpErr, webpWidth, webpHeight := readWebpTypeImage(imageFilePath) + if webpErr == nil { + imagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", webpWidth, webpHeight) + successCount = successCount + 1 + } else { + errorCount = errorCount + 1 + fmt.Printf("=== 异常图片: %s \n", imageFilePath) + } + continue + } + if strings.EqualFold(suffix, ".bmp") { // 特殊文件处理 + bpmErr, bmpWidth, bmpHeight := readBmpInfo(imageFilePath) + if bpmErr == nil { + imagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", bmpWidth, bmpHeight) + successCount = successCount + 1 + } else { + errorCount = errorCount + 1 + fmt.Printf("=== 异常图片: %s \n", imageFilePath) + } + continue + } + if strings.EqualFold(suffix, ".psd") { // 特殊文件处理 + psdImageList = append(psdImageList, imageFilePath) + successCount = successCount + 1 + continue + } + // 其他的直接先忽略吧, 爱改改, 不改拉倒 + ignoreCount = ignoreCount + 1 + ignoreImagePathList = append(ignoreImagePathList, imageFilePath) + } + uid := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + if len(psdImageList) > 0 { + psdImagePath := rootDir + string(os.PathSeparator) + uid + "_图片_PSD" + if util.CreateDir(psdImagePath) { + doMoveFileToDir(psdImageList, psdImagePath) + } + } + if len(readErrorImagePathList) > 0 { + readInfoErrorPath := rootDir + string(os.PathSeparator) + uid + "_图片_读取异常" + if util.CreateDir(readInfoErrorPath) { + doMoveFileToDir(readErrorImagePathList, readInfoErrorPath) + } + } + if len(ignoreImagePathList) > 0 { + ignorePath := rootDir + string(os.PathSeparator) + uid + "_图片_已忽略" + if util.CreateDir(ignorePath) { + doMoveFileToDir(ignoreImagePathList, ignorePath) + } + } + doPickImageFile(uid, rootDir, imagePath2WidthHeightMap) + fmt.Printf("=== 图片处理完毕(UID): %s \n\n", uid) +} + +// 条件图片并分组存放 +func doPickImageFile(uid string, rootDir string, imagePath2WidthHeightMap map[string]string) { + if len(imagePath2WidthHeightMap) == 0 { + fmt.Printf("=== 当前目录下没有扫描到图片文件, %s \n", rootDir) + return + } + for currentImagePath, infoStr := range imagePath2WidthHeightMap { + width2Height := strings.Split(infoStr, "-") + width := util.String2int(width2Height[0]) + height := util.String2int(width2Height[1]) + suffix := vars.GlobalFilePath2FileExtMap[currentImagePath] + if width > height { + handleHorizontalImage(currentImagePath, width, height, suffix) + continue + } + if width < height { + handleVerticalImage(currentImagePath, height, suffix) + continue + } + handleSquareImage(currentImagePath, width, height, suffix) + } + moveNormalImage(rootDir, uid) + moveHorizontalImage(rootDir, uid) + moveVerticalImage(rootDir, uid) + moveSquareImage(rootDir, uid) +} + +func moveSquareImage(rootDir string, uid string) { + pathSeparator := string(os.PathSeparator) + squareGifImagePath := rootDir + pathSeparator + uid + "_图片_等比_GIF" + square1KImagePath := rootDir + pathSeparator + uid + "_图片_等比_1k" + square2KImagePath := rootDir + pathSeparator + uid + "_图片_等比_2k" + square3KImagePath := rootDir + pathSeparator + uid + "_图片_等比_3k" + square4KImagePath := rootDir + pathSeparator + uid + "_图片_等比_4k" + square5KImagePath := rootDir + pathSeparator + uid + "_图片_等比_5k" + square6KImagePath := rootDir + pathSeparator + uid + "_图片_等比_6k" + square7KImagePath := rootDir + pathSeparator + uid + "_图片_等比_7k" + square8KImagePath := rootDir + pathSeparator + uid + "_图片_等比_8k" + square9KImagePath := rootDir + pathSeparator + uid + "_图片_等比_9k" + squareHKImagePath := rootDir + pathSeparator + uid + "_图片_等比_原图" + if len(squareGifImageList) > 0 { + util.CreateDir(squareGifImagePath) + doMoveFileToDir(squareGifImageList, squareGifImagePath) + } + if len(square1KImageList) > 0 { + util.CreateDir(square1KImagePath) + doMoveFileToDir(square1KImageList, square1KImagePath) + } + if len(square2KImageList) > 0 { + util.CreateDir(square2KImagePath) + doMoveFileToDir(square2KImageList, square2KImagePath) + } + if len(square3KImageList) > 0 { + util.CreateDir(square3KImagePath) + doMoveFileToDir(square3KImageList, square3KImagePath) + } + if len(square4KImageList) > 0 { + util.CreateDir(square4KImagePath) + doMoveFileToDir(square4KImageList, square4KImagePath) + } + if len(square5KImageList) > 0 { + util.CreateDir(square5KImagePath) + doMoveFileToDir(square5KImageList, square5KImagePath) + } + if len(square6KImageList) > 0 { + util.CreateDir(square6KImagePath) + doMoveFileToDir(square6KImageList, square6KImagePath) + } + if len(square7KImageList) > 0 { + util.CreateDir(square7KImagePath) + doMoveFileToDir(square7KImageList, square7KImagePath) + } + if len(square8KImageList) > 0 { + util.CreateDir(square8KImagePath) + doMoveFileToDir(square8KImageList, square8KImagePath) + } + if len(square9KImageList) > 0 { + util.CreateDir(square9KImagePath) + doMoveFileToDir(square9KImageList, square9KImagePath) + } + if len(squareHKImageList) > 0 { + util.CreateDir(squareHKImagePath) + doMoveFileToDir(squareHKImageList, squareHKImagePath) + } +} + +func handleSquareImage(currentImagePath string, width int, height int, suffix string) { + if strings.EqualFold(suffix, ".gif") { + squareGifImageList = append(squareGifImageList, currentImagePath) + return + } + if width < 1000 { + normalImageList = append(normalImageList, currentImagePath) + } else if width >= 1000 && width < 2000 { + // 1280 x 720 -> 720p + if width == 1280 && height == 720 { + squareStandard720PImageList = append(squareStandard720PImageList, currentImagePath) + return + } + // 1920 x 1080 -> 1080p + if width == 1920 && height == 1080 { + squareStandard1080PImageList = append(squareStandard1080PImageList, currentImagePath) + return + } + square1KImageList = append(square1KImageList, currentImagePath) + } else if width >= 2000 && width < 3000 { + square2KImageList = append(square2KImageList, currentImagePath) + } else if width >= 3000 && width < 4000 { + // 3840 x 2160 -> 4k + if width == 3840 && height == 2160 { + squareStandard4KImageList = append(squareStandard4KImageList, currentImagePath) + return + } + square3KImageList = append(square3KImageList, currentImagePath) + } else if width >= 4000 && width < 5000 { + square4KImageList = append(square4KImageList, currentImagePath) + } else if width >= 5000 && width < 6000 { + square5KImageList = append(square5KImageList, currentImagePath) + } else if width >= 6000 && width < 7000 { + square6KImageList = append(square6KImageList, currentImagePath) + } else if width >= 7000 && width < 8000 { + // 7680 x 4320 -> 8k + if width == 7680 && height == 4320 { + squareStandard8KImageList = append(squareStandard8KImageList, currentImagePath) + return + } + square7KImageList = append(square7KImageList, currentImagePath) + } else if width >= 8000 && width < 9000 { + square8KImageList = append(square8KImageList, currentImagePath) + } else if width >= 9000 && width < 10000 { + square9KImageList = append(square9KImageList, currentImagePath) + } else if width >= 10000 { + squareHKImageList = append(squareHKImageList, currentImagePath) + } +} + +// 移动垂直图片 +func moveVerticalImage(rootDir string, uid string) { + pathSeparator := string(os.PathSeparator) + verticalGifImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_GIF" + vertical1KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_1k" + vertical2KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_2k" + vertical3KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_3k" + vertical4KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_4k" + vertical5KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_5k" + vertical6KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_6k" + vertical7KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_7k" + vertical8KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_8k" + vertical9KImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_9k" + verticalHKImagePath := rootDir + pathSeparator + uid + "_图片_竖屏_原图" + if len(verticalGifImageList) > 0 { + util.CreateDir(verticalGifImagePath) + doMoveFileToDir(verticalGifImageList, verticalGifImagePath) + } + if len(vertical1KImageList) > 0 { + util.CreateDir(vertical1KImagePath) + doMoveFileToDir(vertical1KImageList, vertical1KImagePath) + } + if len(vertical2KImageList) > 0 { + util.CreateDir(vertical2KImagePath) + doMoveFileToDir(vertical2KImageList, vertical2KImagePath) + } + if len(vertical3KImageList) > 0 { + util.CreateDir(vertical3KImagePath) + doMoveFileToDir(vertical3KImageList, vertical3KImagePath) + } + if len(vertical4KImageList) > 0 { + util.CreateDir(vertical4KImagePath) + doMoveFileToDir(vertical4KImageList, vertical4KImagePath) + } + if len(vertical5KImageList) > 0 { + util.CreateDir(vertical5KImagePath) + doMoveFileToDir(vertical5KImageList, vertical5KImagePath) + } + if len(vertical6KImageList) > 0 { + util.CreateDir(vertical6KImagePath) + doMoveFileToDir(vertical6KImageList, vertical6KImagePath) + } + if len(vertical7KImageList) > 0 { + util.CreateDir(vertical7KImagePath) + doMoveFileToDir(vertical7KImageList, vertical7KImagePath) + } + if len(vertical8KImageList) > 0 { + util.CreateDir(vertical8KImagePath) + doMoveFileToDir(vertical8KImageList, vertical8KImagePath) + } + if len(vertical9KImageList) > 0 { + util.CreateDir(vertical9KImagePath) + doMoveFileToDir(vertical9KImageList, vertical9KImagePath) + } + if len(verticalHKImageList) > 0 { + util.CreateDir(verticalHKImagePath) + doMoveFileToDir(verticalHKImageList, verticalHKImagePath) + } +} + +// 移动水平图片 +func moveHorizontalImage(rootDir string, uid string) { + pathSeparator := string(os.PathSeparator) + horizontalGifImagePath := rootDir + pathSeparator + uid + "_图片_横屏_GIF" + horizontal1KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_1k" + horizontal2KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_2k" + horizontal3KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_3k" + horizontal4KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_4k" + horizontal5KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_5k" + horizontal6KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_6k" + horizontal7KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_7k" + horizontal8KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_8k" + horizontal9KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_9k" + horizontalHKImagePath := rootDir + pathSeparator + uid + "_图片_横屏_原图" + horizontalStandard720PImagePath := rootDir + pathSeparator + uid + "_图片_横屏_720P" + horizontalStandard1080PImagePath := rootDir + pathSeparator + uid + "_图片_横屏_1080P" + horizontalStandard4KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_4KP" + horizontalStandard8KImagePath := rootDir + pathSeparator + uid + "_图片_横屏_8KP" + if len(horizontalGifImageList) > 0 { + util.CreateDir(horizontalGifImagePath) + doMoveFileToDir(horizontalGifImageList, horizontalGifImagePath) + } + if len(horizontal1KImageList) > 0 { + util.CreateDir(horizontal1KImagePath) + doMoveFileToDir(horizontal1KImageList, horizontal1KImagePath) + } + if len(horizontal2KImageList) > 0 { + util.CreateDir(horizontal2KImagePath) + doMoveFileToDir(horizontal2KImageList, horizontal2KImagePath) + } + if len(horizontal3KImageList) > 0 { + util.CreateDir(horizontal3KImagePath) + doMoveFileToDir(horizontal3KImageList, horizontal3KImagePath) + } + if len(horizontal4KImageList) > 0 { + util.CreateDir(horizontal4KImagePath) + doMoveFileToDir(horizontal4KImageList, horizontal4KImagePath) + } + if len(horizontal5KImageList) > 0 { + util.CreateDir(horizontal5KImagePath) + doMoveFileToDir(horizontal5KImageList, horizontal5KImagePath) + } + if len(horizontal6KImageList) > 0 { + util.CreateDir(horizontal6KImagePath) + doMoveFileToDir(horizontal6KImageList, horizontal6KImagePath) + } + if len(horizontal7KImageList) > 0 { + util.CreateDir(horizontal7KImagePath) + doMoveFileToDir(horizontal7KImageList, horizontal7KImagePath) + } + if len(horizontal8KImageList) > 0 { + util.CreateDir(horizontal8KImagePath) + doMoveFileToDir(horizontal8KImageList, horizontal8KImagePath) + } + if len(horizontal9KImageList) > 0 { + util.CreateDir(horizontal9KImagePath) + doMoveFileToDir(horizontal9KImageList, horizontal9KImagePath) + } + if len(horizontalHKImageList) > 0 { + util.CreateDir(horizontalHKImagePath) + doMoveFileToDir(horizontalHKImageList, horizontalHKImagePath) + } + if len(horizontalStandard720PImageList) > 0 { + util.CreateDir(horizontalStandard720PImagePath) + doMoveFileToDir(horizontalStandard720PImageList, horizontalStandard720PImagePath) + } + if len(horizontalStandard1080PImageList) > 0 { + util.CreateDir(horizontalStandard1080PImagePath) + doMoveFileToDir(horizontalStandard1080PImageList, horizontalStandard1080PImagePath) + } + if len(horizontalStandard4KImageList) > 0 { + util.CreateDir(horizontalStandard4KImagePath) + doMoveFileToDir(horizontalStandard4KImageList, horizontalStandard4KImagePath) + } + if len(horizontalStandard8KImageList) > 0 { + util.CreateDir(horizontalStandard8KImagePath) + doMoveFileToDir(horizontalStandard8KImageList, horizontalStandard8KImagePath) + } +} + +// 移动图片 +func moveNormalImage(rootDir string, uid string) { + pathSeparator := string(os.PathSeparator) + allNormalImagePath := rootDir + pathSeparator + uid + "_图片_普通" + if len(normalImageList) > 0 { + util.CreateDir(allNormalImagePath) + doMoveFileToDir(normalImageList, allNormalImagePath) + } +} + +// 处理垂直图片 +func handleVerticalImage(currentImagePath string, height int, suffix string) { + if strings.EqualFold(suffix, ".gif") { + verticalGifImageList = append(verticalGifImageList, currentImagePath) + return + } + if height < 1000 { + normalImageList = append(normalImageList, currentImagePath) + } else if height >= 1000 && height < 2000 { + vertical1KImageList = append(vertical1KImageList, currentImagePath) + } else if height >= 2000 && height < 3000 { + vertical2KImageList = append(vertical2KImageList, currentImagePath) + } else if height >= 3000 && height < 4000 { + vertical3KImageList = append(vertical3KImageList, currentImagePath) + } else if height >= 4000 && height < 5000 { + vertical4KImageList = append(vertical4KImageList, currentImagePath) + } else if height >= 5000 && height < 6000 { + vertical5KImageList = append(vertical5KImageList, currentImagePath) + } else if height >= 6000 && height < 7000 { + vertical6KImageList = append(vertical6KImageList, currentImagePath) + } else if height >= 7000 && height < 8000 { + vertical7KImageList = append(vertical7KImageList, currentImagePath) + } else if height >= 8000 && height < 9000 { + vertical8KImageList = append(vertical8KImageList, currentImagePath) + } else if height >= 9000 && height < 10000 { + vertical9KImageList = append(vertical9KImageList, currentImagePath) + } else if height >= 10000 { + verticalHKImageList = append(verticalHKImageList, currentImagePath) + } +} + +// 处理横向图片 +func handleHorizontalImage(currentImagePath string, width int, height int, suffix string) { + if strings.EqualFold(suffix, ".gif") { + horizontalGifImageList = append(horizontalGifImageList, currentImagePath) + return + } + if width < 1000 { + normalImageList = append(normalImageList, currentImagePath) + } else if width >= 1000 && width < 2000 { + // 1280 x 720 -> 720p + if width == 1280 && height == 720 { + horizontalStandard720PImageList = append(horizontalStandard720PImageList, currentImagePath) + return + } + // 1920 x 1080 -> 1080p + if width == 1920 && height == 1080 { + horizontalStandard1080PImageList = append(horizontalStandard1080PImageList, currentImagePath) + return + } + horizontal1KImageList = append(horizontal1KImageList, currentImagePath) + } else if width >= 2000 && width < 3000 { + horizontal2KImageList = append(horizontal2KImageList, currentImagePath) + } else if width >= 3000 && width < 4000 { + // 3840 x 2160 -> 4k + if width == 3840 && height == 2160 { + horizontalStandard4KImageList = append(horizontalStandard4KImageList, currentImagePath) + return + } + horizontal3KImageList = append(horizontal3KImageList, currentImagePath) + } else if width >= 4000 && width < 5000 { + horizontal4KImageList = append(horizontal4KImageList, currentImagePath) + } else if width >= 5000 && width < 6000 { + horizontal5KImageList = append(horizontal5KImageList, currentImagePath) + } else if width >= 6000 && width < 7000 { + horizontal6KImageList = append(horizontal6KImageList, currentImagePath) + } else if width >= 7000 && width < 8000 { + // 7680 x 4320 -> 8k + if width == 7680 && height == 4320 { + horizontalStandard8KImageList = append(horizontalStandard8KImageList, currentImagePath) + return + } + horizontal7KImageList = append(horizontal7KImageList, currentImagePath) + } else if width >= 8000 && width < 9000 { + horizontal8KImageList = append(horizontal8KImageList, currentImagePath) + } else if width >= 9000 && width < 10000 { + horizontal9KImageList = append(horizontal9KImageList, currentImagePath) + } else if width >= 10000 { + horizontalHKImageList = append(horizontalHKImageList, currentImagePath) + } +} + +// 判断是否属于支持的图片文件 +func isSupportImage(imageType string) bool { + for _, supportImageType := range supportImageTypes { + if strings.EqualFold(supportImageType, imageType) { + return true + } + } + return false +} + +// 读取一般图片文件信息 +func readImageInfo(filePath string) (err error, width int, height int) { + file, err := os.Open(filePath) // 图片文件路径 + if err != nil { + return err, 0, 0 + } + defer file.Close() + img, _, err := image.DecodeConfig(file) + if err != nil { + return err, 0, 0 + } + return nil, img.Width, img.Height +} diff --git a/core/handle_video.go b/core/handle_video.go new file mode 100644 index 0000000..be5d19b --- /dev/null +++ b/core/handle_video.go @@ -0,0 +1,655 @@ +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 +} diff --git a/core/webp.go b/core/webp.go new file mode 100644 index 0000000..cce8693 --- /dev/null +++ b/core/webp.go @@ -0,0 +1,25 @@ +package core + +import ( + "fmt" + "golang.org/x/image/webp" + "os" +) + +// 读取webp格式的图片信息 +func readWebpTypeImage(webpFilePath string) (err error, width int, height int) { + // 打开WebP文件 + file, err := os.Open(webpFilePath) + if err != nil { + fmt.Printf("=== Failed to open file: %v\n", err) + return err, 0, 0 + } + defer file.Close() + // 使用webp.DecodeConfig解码WebP图片配置信息(不加载完整像素数据) + imgConfig, err := webp.DecodeConfig(file) + if err != nil { + fmt.Printf("=== Failed to decode WebP image config: %v\n", err) + return err, 0, 0 + } + return nil, imgConfig.Width, imgConfig.Height +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1c9594e --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module OdMediaPicker + +go 1.21 + +require ( + github.com/gabriel-vasile/mimetype v1.4.3 + github.com/satori/go.uuid v1.2.0 + golang.org/x/image v0.16.0 +) + +require ( + github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d // indirect + golang.org/x/net v0.17.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8b4c09d --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d h1:h/hohIqMUCML2Rp9BXXAu0I3ZR68d7eqMHLPNq7N2tg= +github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d/go.mod h1:LiYo3EFlYfk46Re4zgQysMo6yO3/kAXslB2fyMdl+uw= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main.go b/main.go new file mode 100644 index 0000000..46c9ea8 --- /dev/null +++ b/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "OdMediaPicker/core" + "OdMediaPicker/vars" + _ "embed" + "fmt" + _ "image/gif" // 导入gif支持 + _ "image/jpeg" // 导入jpeg支持 + _ "image/png" // 导入png支持 + "os" + "time" +) + +func main() { + rootDir, err := os.Getwd() + if err != nil { + fmt.Println("=== 获取当前路径异常", err) + return + } + scanner := core.FileScanner{} + scanner.DoScan(rootDir) + scanner.DoFilter() + if len(vars.GlobalImagePathList) > 0 { + core.DoHandleImage(rootDir) + } + if len(vars.GlobalVideoPathList) > 0 { + core.DoHandleVideo(rootDir) + } + fmt.Println("=== 5s后自动退出") + time.Sleep(time.Second * 5) +} diff --git a/upx.1 b/upx.1 new file mode 100644 index 0000000..a4d59c4 --- /dev/null +++ b/upx.1 @@ -0,0 +1,1042 @@ +.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "UPX 1" +.TH UPX 1 "2024-03-27" "upx 4.2.3" " " +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +upx \- compress or expand executable files +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBupx\fR [\ \fIcommand\fR\ ] [\ \fIoptions\fR\ ] \fIfilename\fR... +.SH "ABSTRACT" +.IX Header "ABSTRACT" +.Vb 3 +\& The Ultimate Packer for eXecutables +\& Copyright (c) 1996\-2024 Markus Oberhumer, Laszlo Molnar & John Reiser +\& https://upx.github.io +.Ve +.PP +\&\fB\s-1UPX\s0\fR is a portable, extendable, high-performance executable packer for +several different executable formats. It achieves an excellent compression +ratio and offers \fI*very*\fR fast decompression. Your executables suffer +no memory overhead or other drawbacks for most of the formats supported, +because of in-place decompression. +.SH "DISCLAIMER" +.IX Header "DISCLAIMER" +\&\fB\s-1UPX\s0\fR comes with \s-1ABSOLUTELY NO WARRANTY\s0; for details see the file \s-1COPYING.\s0 +.PP +Please report all problems or suggestions to the authors. Thanks. +.SH "SECURITY CONTEXT" +.IX Header "SECURITY CONTEXT" +\&\s-1IMPORTANT NOTE:\s0 \fB\s-1UPX\s0\fR inherits the security context of any files it handles. +.PP +This means that packing, unpacking, or even testing or listing a file requires +the same security considerations as actually executing the file. +.PP +Use \fB\s-1UPX\s0\fR on trusted files only! +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +\&\fB\s-1UPX\s0\fR is a versatile executable packer with the following features: +.PP +.Vb 3 +\& \- secure: as UPX is documented Open Source since many years any relevant +\& Security/Antivirus software is able to peek inside UPX compressed +\& apps to verify them +\& +\& \- excellent compression ratio: typically compresses better than Zip, +\& use UPX to decrease the size of your distribution ! +\& +\& \- very fast decompression: more than 500 MB/sec on any reasonably modern +\& machine +\& +\& \- no memory overhead for your compressed executables for most of the +\& supported formats because of in\-place decompression +\& +\& \- safe: you can list, test and unpack your executables. +\& Also, a checksum of both the compressed and uncompressed file is +\& maintained internally. +\& +\& \- universal: UPX can pack a number of executable formats, including +\& Windows programs and DLLs, macOS apps and Linux executables +\& +\& \- portable: UPX is written in portable endian\-neutral C++ +\& +\& \- extendable: because of the class layout it\*(Aqs very easy to support +\& new executable formats or add new compression algorithms +\& +\& \- free: UPX is distributed with full source code under the GNU General +\& Public License v2+, with special exceptions granting the free usage +\& for commercial programs +.Ve +.PP +You probably understand now why we call \fB\s-1UPX\s0\fR the "\fIultimate\fR" +executable packer. +.SH "COMMANDS" +.IX Header "COMMANDS" +.SS "Compress" +.IX Subsection "Compress" +This is the default operation, eg. \fBupx yourfile.exe\fR will compress the file +specified on the command line. +.SS "Decompress" +.IX Subsection "Decompress" +All \fB\s-1UPX\s0\fR supported file formats can be unpacked using the \fB\-d\fR switch, eg. +\&\fBupx \-d yourfile.exe\fR will uncompress the file you've just compressed. +.SS "Test" +.IX Subsection "Test" +The \fB\-t\fR command tests the integrity of the compressed and uncompressed +data, eg. \fBupx \-t yourfile.exe\fR check whether your file can be safely +decompressed. Note, that this command doesn't check the whole file, only +the part that will be uncompressed during program execution. This means +that you should not use this command instead of a virus checker. +.SS "List" +.IX Subsection "List" +The \fB\-l\fR command prints out some information about the compressed files +specified on the command line as parameters, eg \fBupx \-l yourfile.exe\fR +shows the compressed / uncompressed size and the compression ratio of +\&\fIyourfile.exe\fR. +.SH "OPTIONS" +.IX Header "OPTIONS" +\&\fB\-q\fR: be quiet, suppress warnings +.PP +\&\fB\-q \-q\fR (or \fB\-qq\fR): be very quiet, suppress errors +.PP +\&\fB\-q \-q \-q\fR (or \fB\-qqq\fR): produce no output at all +.PP +\&\fB\-\-help\fR: prints the help +.PP +\&\fB\-\-version\fR: print the version of \fB\s-1UPX\s0\fR +.PP +\&\fB\-\-exact\fR: when compressing, require to be able to get a byte-identical file +after decompression with option \fB\-d\fR. [\s-1NOTE:\s0 this is work in progress and is +not supported for all formats yet. If you do care, as a workaround you can +compress and then decompress your program a first time \- any further +compress-decompress steps should then yield byte-identical results +as compared to the first decompressed version.] +.PP +\&\fB\-k\fR: keep backup files +.PP +\&\fB\-o file\fR: write output to file +.PP +[ ...more docs need to be written... \- type `\fBupx \-\-help\fR' for now ] +.SH "COMPRESSION LEVELS & TUNING" +.IX Header "COMPRESSION LEVELS & TUNING" +\&\fB\s-1UPX\s0\fR offers ten different compression levels from \fB\-1\fR to \fB\-9\fR, +and \fB\-\-best\fR. The default compression level is \fB\-8\fR for files +smaller than 512 KiB, and \fB\-7\fR otherwise. +.IP "\(bu" 4 +Compression levels 1, 2 and 3 are pretty fast. +.IP "\(bu" 4 +Compression levels 4, 5 and 6 achieve a good time/ratio performance. +.IP "\(bu" 4 +Compression levels 7, 8 and 9 favor compression ratio over speed. +.IP "\(bu" 4 +Compression level \fB\-\-best\fR may take a long time. +.PP +Note that compression level \fB\-\-best\fR can be somewhat slow for large +files, but you definitely should use it when releasing a final version +of your program. +.PP +Quick info for achieving the best compression ratio: +.IP "\(bu" 4 +Try \fBupx \-\-brute \-\-no\-lzma myfile.exe\fR or even +\&\fBupx \-\-ultra\-brute \-\-no\-lzma myfile.exe\fR. +.IP "\(bu" 4 +The option \fB\-\-lzma\fR enables \s-1LZMA\s0 compression, which compresses better but +is *significantly slower* at decompression. You probably do not want +to use it for large files. +.Sp +(Note that \fB\-\-lzma\fR is automatically enabled by \fB\-\-all\-methods\fR and +\&\fB\-\-brute\fR, use \fB\-\-no\-lzma\fR to override.) +.IP "\(bu" 4 +Try if \fB\-\-overlay=strip\fR works. +.IP "\(bu" 4 +For win32/pe programs there's \fB\-\-strip\-relocs=0\fR. See notes below. +.SH "OVERLAY HANDLING OPTIONS" +.IX Header "OVERLAY HANDLING OPTIONS" +Info: An \*(L"overlay\*(R" means auxiliary data attached after the logical end of +an executable, and it often contains application specific data +(this is a common practice to avoid an extra data file, though +it would be better to use resource sections). +.PP +\&\fB\s-1UPX\s0\fR handles overlays like many other executable packers do: it simply +copies the overlay after the compressed image. This works with some +files, but doesn't work with others, depending on how an application +actually accesses this overlaid data. +.PP +.Vb 1 +\& \-\-overlay=copy Copy any extra data attached to the file. [DEFAULT] +\& +\& \-\-overlay=strip Strip any overlay from the program instead of +\& copying it. Be warned, this may make the compressed +\& program crash or otherwise unusable. +\& +\& \-\-overlay=skip Refuse to compress any program which has an overlay. +.Ve +.SH "ENVIRONMENT VARIABLE" +.IX Header "ENVIRONMENT VARIABLE" +The environment variable \fB\s-1UPX\s0\fR can hold a set of default +options for \fB\s-1UPX\s0\fR. These options are interpreted first and +can be overwritten by explicit command line parameters. +For example: +.PP +.Vb 3 +\& for DOS/Windows: set UPX=\-9 \-\-compress\-icons#0 +\& for sh/ksh/zsh: UPX="\-9 \-\-compress\-icons=0"; export UPX +\& for csh/tcsh: setenv UPX "\-9 \-\-compress\-icons=0" +.Ve +.PP +Under DOS/Windows you must use '#' instead of '=' when setting the +environment variable because of a \s-1COMMAND.COM\s0 limitation. +.PP +Not all of the options are valid in the environment variable \- +\&\fB\s-1UPX\s0\fR will tell you. +.PP +You can explicitly use the \fB\-\-no\-env\fR option to ignore the +environment variable. +.SH "NOTES FOR THE SUPPORTED EXECUTABLE FORMATS" +.IX Header "NOTES FOR THE SUPPORTED EXECUTABLE FORMATS" +.SS "\s-1NOTES FOR ATARI/TOS\s0" +.IX Subsection "NOTES FOR ATARI/TOS" +This is the executable format used by the Atari \s-1ST/TT,\s0 a Motorola 68000 +based personal computer which was popular in the late '80s. Support +of this format is only because of nostalgic feelings of one of +the authors and serves no practical purpose :\-). +See https://freemint.github.io for more info. +.PP +Packed programs will be byte-identical to the original after uncompression. +All debug information will be stripped, though. +.PP +Extra options available for this executable format: +.PP +.Vb 4 +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR BVMLINUZ/I386\s0" +.IX Subsection "NOTES FOR BVMLINUZ/I386" +Same as vmlinuz/i386. +.SS "\s-1NOTES FOR DOS/COM\s0" +.IX Subsection "NOTES FOR DOS/COM" +Obviously \fB\s-1UPX\s0\fR won't work with executables that want to read data from +themselves (like some commandline utilities that ship with Win95/98/ME). +.PP +Compressed programs only work on a 286+. +.PP +Packed programs will be byte-identical to the original after uncompression. +.PP +Maximum uncompressed size: ~65100 bytes. +.PP +Extra options available for this executable format: +.PP +.Vb 1 +\& \-\-8086 Create an executable that works on any 8086 CPU. +\& +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR DOS/EXE\s0" +.IX Subsection "NOTES FOR DOS/EXE" +dos/exe stands for all \*(L"normal\*(R" 16\-bit \s-1DOS\s0 executables. +.PP +Obviously \fB\s-1UPX\s0\fR won't work with executables that want to read data from +themselves (like some command line utilities that ship with Win95/98/ME). +.PP +Compressed programs only work on a 286+. +.PP +Extra options available for this executable format: +.PP +.Vb 1 +\& \-\-8086 Create an executable that works on any 8086 CPU. +\& +\& \-\-no\-reloc Use no relocation records in the exe header. +\& +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR DOS/SYS\s0" +.IX Subsection "NOTES FOR DOS/SYS" +Compressed programs only work on a 286+. +.PP +Packed programs will be byte-identical to the original after uncompression. +.PP +Maximum uncompressed size: ~65350 bytes. +.PP +Extra options available for this executable format: +.PP +.Vb 1 +\& \-\-8086 Create an executable that works on any 8086 CPU. +\& +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR DJGPP2/COFF\s0" +.IX Subsection "NOTES FOR DJGPP2/COFF" +First of all, it is recommended to use \fB\s-1UPX\s0\fR *instead* of \fBstrip\fR. strip has +the very bad habit of replacing your stub with its own (outdated) version. +Additionally \fB\s-1UPX\s0\fR corrects a bug/feature in strip v2.8.x: it +will fix the 4 KiB alignment of the stub. +.PP +\&\fB\s-1UPX\s0\fR includes the full functionality of stubify. This means it will +automatically stubify your \s-1COFF\s0 files. Use the option \fB\-\-coff\fR to +disable this functionality (see below). +.PP +\&\fB\s-1UPX\s0\fR automatically handles Allegro packfiles. +.PP +The \s-1DLM\s0 format (a rather exotic shared library extension) is not supported. +.PP +Packed programs will be byte-identical to the original after uncompression. +All debug information and trailing garbage will be stripped, though. +.PP +Extra options available for this executable format: +.PP +.Vb 2 +\& \-\-coff Produce COFF output instead of EXE. By default +\& UPX keeps your current stub. +\& +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR LINUX\s0 [general]" +.IX Subsection "NOTES FOR LINUX [general]" +Introduction +.PP +.Vb 4 +\& Linux/386 support in UPX consists of 3 different executable formats, +\& one optimized for ELF executables ("linux/elf386"), one optimized +\& for shell scripts ("linux/sh386"), and one generic format +\& ("linux/386"). +\& +\& We will start with a general discussion first, but please +\& also read the relevant docs for each of the individual formats. +\& +\& Also, there is special support for bootable kernels \- see the +\& description of the vmlinuz/386 format. +.Ve +.PP +General user's overview +.PP +.Vb 7 +\& Running a compressed executable program trades less space on a +\& \`\`permanent\*(Aq\*(Aq storage medium (such as a hard disk, floppy disk, +\& CD\-ROM, flash memory, EPROM, etc.) for more space in one or more +\& \`\`temporary\*(Aq\*(Aq storage media (such as RAM, swap space, /tmp, etc.). +\& Running a compressed executable also requires some additional CPU +\& cycles to generate the compressed executable in the first place, +\& and to decompress it at each invocation. +\& +\& How much space is traded? It depends on the executable, but many +\& programs save 30% to 50% of permanent disk space. How much CPU +\& overhead is there? Again, it depends on the executable, but +\& decompression speed generally is at least many megabytes per second, +\& and frequently is limited by the speed of the underlying disk +\& or network I/O. +\& +\& Depending on the statistics of usage and access, and the relative +\& speeds of CPU, RAM, swap space, /tmp, and file system storage, then +\& invoking and running a compressed executable can be faster than +\& directly running the corresponding uncompressed program. +\& The operating system might perform fewer expensive I/O operations +\& to invoke the compressed program. Paging to or from swap space +\& or /tmp might be faster than paging from the general file system. +\& \`\`Medium\-sized\*(Aq\*(Aq programs which access about 1/3 to 1/2 of their +\& stored program bytes can do particularly well with compression. +\& Small programs tend not to benefit as much because the absolute +\& savings is less. Big programs tend not to benefit proportionally +\& because each invocation may use only a small fraction of the program, +\& yet UPX decompresses the entire program before invoking it. +\& But in environments where disk or flash memory storage is limited, +\& then compression may win anyway. +\& +\& Currently, executables compressed by UPX do not share RAM at runtime +\& in the way that executables mapped from a file system do. As a +\& result, if the same program is run simultaneously by more than one +\& process, then using the compressed version will require more RAM and/or +\& swap space. So, shell programs (bash, csh, etc.) and \`\`make\*(Aq\*(Aq +\& might not be good candidates for compression. +\& +\& UPX recognizes three executable formats for Linux: Linux/elf386, +\& Linux/sh386, and Linux/386. Linux/386 is the most generic format; +\& it accommodates any file that can be executed. At runtime, the UPX +\& decompression stub re\-creates in /tmp a copy of the original file, +\& and then the copy is (re\-)executed with the same arguments. +\& ELF binary executables prefer the Linux/elf386 format by default, +\& because UPX decompresses them directly into RAM, uses only one +\& exec, does not use space in /tmp, and does not use /proc. +\& Shell scripts where the underlying shell accepts a \`\`\-c\*(Aq\*(Aq argument +\& can use the Linux/sh386 format. UPX decompresses the shell script +\& into low memory, then maps the shell and passes the entire text of the +\& script as an argument with a leading \`\`\-c\*(Aq\*(Aq. +.Ve +.PP +General benefits: +.PP +.Vb 4 +\& \- UPX can compress all executables, be it AOUT, ELF, libc4, libc5, +\& libc6, Shell/Perl/Python/... scripts, standalone Java .class +\& binaries, or whatever... +\& All scripts and programs will work just as before. +\& +\& \- Compressed programs are completely self\-contained. No need for +\& any external program. +\& +\& \- UPX keeps your original program untouched. This means that +\& after decompression you will have a byte\-identical version, +\& and you can use UPX as a file compressor just like gzip. +\& [ Note that UPX maintains a checksum of the file internally, +\& so it is indeed a reliable alternative. ] +\& +\& \- As the stub only uses syscalls and isn\*(Aqt linked against libc it +\& should run under any Linux configuration that can run ELF +\& binaries. +\& +\& \- For the same reason compressed executables should run under +\& FreeBSD and other systems which can run Linux binaries. +\& [ Please send feedback on this topic ] +.Ve +.PP +General drawbacks: +.PP +.Vb 4 +\& \- It is not advisable to compress programs which usually have many +\& instances running (like \`sh\*(Aq or \`make\*(Aq) because the common segments of +\& compressed programs won\*(Aqt be shared any longer between different +\& processes. +\& +\& \- \`ldd\*(Aq and \`size\*(Aq won\*(Aqt show anything useful because all they +\& see is the statically linked stub. Since version 0.82 the section +\& headers are stripped from the UPX stub and \`size\*(Aq doesn\*(Aqt even +\& recognize the file format. The file patches/patch\-elfcode.h has a +\& patch to fix this bug in \`size\*(Aq and other programs which use GNU BFD. +.Ve +.PP +General notes: +.PP +.Vb 2 +\& \- As UPX leaves your original program untouched it is advantageous +\& to strip it before compression. +\& +\& \- If you compress a script you will lose platform independence \- +\& this could be a problem if you are using NFS mounted disks. +\& +\& \- Compression of suid, guid and sticky\-bit programs is rejected +\& because of possible security implications. +\& +\& \- For the same reason there is no sense in making any compressed +\& program suid. +\& +\& \- Obviously UPX won\*(Aqt work with executables that want to read data +\& from themselves. E.g., this might be a problem for Perl scripts +\& which access their _\|_DATA_\|_ lines. +\& +\& \- In case of internal errors the stub will abort with exitcode 127. +\& Typical reasons for this to happen are that the program has somehow +\& been modified after compression. +\& Running \`strace \-o strace.log compressed_file\*(Aq will tell you more. +.Ve +.SS "\s-1NOTES FOR LINUX/ELF386\s0" +.IX Subsection "NOTES FOR LINUX/ELF386" +Please read the general Linux description first. +.PP +The linux/elf386 format decompresses directly into \s-1RAM,\s0 +uses only one exec, does not use space in /tmp, +and does not use /proc. +.PP +Linux/elf386 is automatically selected for Linux \s-1ELF\s0 executables. +.PP +Packed programs will be byte-identical to the original after uncompression. +.PP +How it works: +.PP +.Vb 9 +\& For ELF executables, UPX decompresses directly to memory, simulating +\& the mapping that the operating system kernel uses during exec(), +\& including the PT_INTERP program interpreter (if any). +\& The brk() is set by a special PT_LOAD segment in the compressed +\& executable itself. UPX then wipes the stack clean except for +\& arguments, environment variables, and Elf_auxv entries (this is +\& required by bugs in the startup code of /lib/ld\-linux.so as of +\& May 2000), and transfers control to the program interpreter or +\& the e_entry address of the original executable. +\& +\& The UPX stub is about 1700 bytes long, partly written in assembler +\& and only uses kernel syscalls. It is not linked against any libc. +.Ve +.PP +Specific drawbacks: +.PP +.Vb 9 +\& \- For linux/elf386 and linux/sh386 formats, you will be relying on +\& RAM and swap space to hold all of the decompressed program during +\& the lifetime of the process. If you already use most of your swap +\& space, then you may run out. A system that is "out of memory" +\& can become fragile. Many programs do not react gracefully when +\& malloc() returns 0. With newer Linux kernels, the kernel +\& may decide to kill some processes to regain memory, and you +\& may not like the kernel\*(Aqs choice of which to kill. Running +\& /usr/bin/top is one way to check on the usage of swap space. +.Ve +.PP +Extra options available for this executable format: +.PP +.Vb 1 +\& (none) +.Ve +.SS "\s-1NOTES FOR LINUX/SH386\s0" +.IX Subsection "NOTES FOR LINUX/SH386" +Please read the general Linux description first. +.PP +Shell scripts where the underling shell accepts a ``\-c'' argument +can use the Linux/sh386 format. \fB\s-1UPX\s0\fR decompresses the shell script +into low memory, then maps the shell and passes the entire text of the +script as an argument with a leading ``\-c''. +It does not use space in /tmp, and does not use /proc. +.PP +Linux/sh386 is automatically selected for shell scripts that +use a known shell. +.PP +Packed programs will be byte-identical to the original after uncompression. +.PP +How it works: +.PP +.Vb 8 +\& For shell script executables (files beginning with "#!/" or "#! /") +\& where the shell is known to accept "\-c ", UPX decompresses +\& the file into low memory, then maps the shell (and its PT_INTERP), +\& and passes control to the shell with the entire decompressed file +\& as the argument after "\-c". Known shells are sh, ash, bash, bsh, csh, +\& ksh, tcsh, pdksh. Restriction: UPX cannot use this method +\& for shell scripts which use the one optional string argument after +\& the shell name in the script (example: "#! /bin/sh option3\en".) +\& +\& The UPX stub is about 1700 bytes long, partly written in assembler +\& and only uses kernel syscalls. It is not linked against any libc. +.Ve +.PP +Specific drawbacks: +.PP +.Vb 9 +\& \- For linux/elf386 and linux/sh386 formats, you will be relying on +\& RAM and swap space to hold all of the decompressed program during +\& the lifetime of the process. If you already use most of your swap +\& space, then you may run out. A system that is "out of memory" +\& can become fragile. Many programs do not react gracefully when +\& malloc() returns 0. With newer Linux kernels, the kernel +\& may decide to kill some processes to regain memory, and you +\& may not like the kernel\*(Aqs choice of which to kill. Running +\& /usr/bin/top is one way to check on the usage of swap space. +.Ve +.PP +Extra options available for this executable format: +.PP +.Vb 1 +\& (none) +.Ve +.SS "\s-1NOTES FOR LINUX/386\s0" +.IX Subsection "NOTES FOR LINUX/386" +Please read the general Linux description first. +.PP +The generic linux/386 format decompresses to /tmp and needs +/proc file system support. It starts the decompressed program +via the \fBexecve()\fR syscall. +.PP +Linux/386 is only selected if the specialized linux/elf386 +and linux/sh386 won't recognize a file. +.PP +Packed programs will be byte-identical to the original after uncompression. +.PP +How it works: +.PP +.Vb 7 +\& For files which are not ELF and not a script for a known "\-c" shell, +\& UPX uses kernel execve(), which first requires decompressing to a +\& temporary file in the file system. Interestingly \- +\& because of the good memory management of the Linux kernel \- this +\& often does not introduce a noticeable delay, and in fact there +\& will be no disk access at all if you have enough free memory as +\& the entire process takes places within the file system buffers. +\& +\& A compressed executable consists of the UPX stub and an overlay +\& which contains the original program in a compressed form. +\& +\& The UPX stub is a statically linked ELF executable and does +\& the following at program startup: +\& +\& 1) decompress the overlay to a temporary location in /tmp +\& 2) open the temporary file for reading +\& 3) try to delete the temporary file and start (execve) +\& the uncompressed program in /tmp using /proc//fd/X as +\& attained by step 2) +\& 4) if that fails, fork off a subprocess to clean up and +\& start the program in /tmp in the meantime +\& +\& The UPX stub is about 1700 bytes long, partly written in assembler +\& and only uses kernel syscalls. It is not linked against any libc. +.Ve +.PP +Specific drawbacks: +.PP +.Vb 4 +\& \- You need additional free disk space for the uncompressed program +\& in your /tmp directory. This program is deleted immediately after +\& decompression, but you still need it for the full execution time +\& of the program. +\& +\& \- You must have /proc file system support as the stub wants to open +\& /proc//exe and needs /proc//fd/X. This also means that you +\& cannot compress programs that are used during the boot sequence +\& before /proc is mounted. +\& +\& \- Utilities like \`top\*(Aq will display numerical values in the process +\& name field. This is because Linux computes the process name from +\& the first argument of the last execve syscall (which is typically +\& something like /proc//fd/3). +\& +\& \- Because of temporary decompression to disk the decompression speed +\& is not as fast as with the other executable formats. Still, I can see +\& no noticeable delay when starting programs like my ~3 MiB emacs (which +\& is less than 1 MiB when compressed :\-). +.Ve +.PP +Extra options available for this executable format: +.PP +.Vb 3 +\& \-\-force\-execve Force the use of the generic linux/386 "execve" +\& format, i.e. do not try the linux/elf386 and +\& linux/sh386 formats. +.Ve +.SS "\s-1NOTES FOR PS1/EXE\s0" +.IX Subsection "NOTES FOR PS1/EXE" +This is the executable format used by the Sony PlayStation (PSone), +a \s-1MIPS R3000\s0 based gaming console which is popular since the late '90s. +Support of this format is very similar to the Atari one, because of +nostalgic feelings of one of the authors. +.PP +Packed programs will be byte-identical to the original after uncompression, +until further notice. +.PP +Maximum uncompressed size: ~1.89 / ~7.60 MiB. +.PP +Notes: +.PP +.Vb 6 +\& \- UPX creates as default a suitable executable for CD\-Mastering +\& and console transfer. For a CD\-Master main executable you could also try +\& the special option "\-\-boot\-only" as described below. +\& It has been reported that upx packed executables are fully compatible with +\& the Sony PlayStation 2 (PS2, PStwo) and Sony PlayStation Portable (PSP) in +\& Sony PlayStation (PSone) emulation mode. +\& +\& \- Normally the packed files use the same memory areas like the uncompressed +\& versions, so they will not override other memory areas while unpacking. +\& If this isn\*(Aqt possible UPX will abort showing a \*(Aqpacked data overlap\*(Aq +\& error. With the "\-\-force" option UPX will relocate the loading address +\& for the packed file, but this isn\*(Aqt a real problem if it is a single or +\& the main executable. +.Ve +.PP +Extra options available for this executable format: +.PP +.Vb 4 +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-8\-bit Uses 8 bit size compression [default: 32 bit] +\& +\& \-\-8mib\-ram PSone has 8 MiB ram available [default: 2 MiB] +\& +\& \-\-boot\-only This format is for main exes and CD\-Mastering only ! +\& It may slightly improve the compression ratio, +\& decompression routines are faster than default ones. +\& But it cannot be used for console transfer ! +\& +\& \-\-no\-align This option disables CD mode 2 data sector format +\& alignment. May slightly improves the compression ratio, +\& but the compressed executable will not boot from a CD. +\& Use it for console transfer only ! +.Ve +.SS "\s-1NOTES FOR RTM32/PE\s0 and \s-1ARM/PE\s0" +.IX Subsection "NOTES FOR RTM32/PE and ARM/PE" +Same as win32/pe. +.SS "\s-1NOTES FOR TMT/ADAM\s0" +.IX Subsection "NOTES FOR TMT/ADAM" +This format is used by the \s-1TMT\s0 Pascal compiler \- see http://www.tmt.com/ . +.PP +Extra options available for this executable format: +.PP +.Vb 4 +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR VMLINUZ/386\s0" +.IX Subsection "NOTES FOR VMLINUZ/386" +The vmlinuz/386 and bvmlinuz/386 formats take a gzip-compressed +bootable Linux kernel image (\*(L"vmlinuz\*(R", \*(L"zImage\*(R", \*(L"bzImage\*(R"), +gzip-decompress it and re-compress it with the \fB\s-1UPX\s0\fR compression method. +.PP +vmlinuz/386 is completely unrelated to the other Linux executable +formats, and it does not share any of their drawbacks. +.PP +Notes: +.PP +.Vb 3 +\& \- Be sure that "vmlinuz/386" or "bvmlinuz/386" is displayed +\& during compression \- otherwise a wrong executable format +\& may have been used, and the kernel won\*(Aqt boot. +.Ve +.PP +Benefits: +.PP +.Vb 4 +\& \- Better compression (but note that the kernel was already compressed, +\& so the improvement is not as large as with other formats). +\& Still, the bytes saved may be essential for special needs like +\& boot disks. +\& +\& For example, this is what I get for my 2.2.16 kernel: +\& 1589708 vmlinux +\& 641073 bzImage [original] +\& 560755 bzImage.upx [compressed by "upx \-9"] +\& +\& \- Much faster decompression at kernel boot time (but kernel +\& decompression speed is not really an issue these days). +.Ve +.PP +Drawbacks: +.PP +.Vb 1 +\& (none) +.Ve +.PP +Extra options available for this executable format: +.PP +.Vb 4 +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SS "\s-1NOTES FOR WATCOM/LE\s0" +.IX Subsection "NOTES FOR WATCOM/LE" +\&\fB\s-1UPX\s0\fR has been successfully tested with the following extenders: + \s-1DOS4G, DOS4GW, PMODE/W,\s0 DOS32a, CauseWay. + The \s-1WDOS/X\s0 extender is partly supported (for details + see the file bugs \s-1BUGS\s0). +.PP +DLLs and the \s-1LX\s0 format are not supported. +.PP +Extra options available for this executable format: +.PP +.Vb 2 +\& \-\-le Produce an unbound LE output instead of +\& keeping the current stub. +.Ve +.SS "\s-1NOTES FOR WIN32/PE\s0" +.IX Subsection "NOTES FOR WIN32/PE" +The \s-1PE\s0 support in \fB\s-1UPX\s0\fR is quite stable now, but probably there are +still some incompatibilities with some files. +.PP +Because of the way \fB\s-1UPX\s0\fR (and other packers for this format) works, you +can see increased memory usage of your compressed files because the whole +program is loaded into memory at startup. +If you start several instances of huge compressed programs you're +wasting memory because the common segments of the program won't +get shared across the instances. +On the other hand if you're compressing only smaller programs, or +running only one instance of larger programs, then this penalty is +smaller, but it's still there. +.PP +If you're running executables from network, then compressed programs +will load faster, and require less bandwidth during execution. +.PP +DLLs are supported. But \s-1UPX\s0 compressed DLLs can not share common data and +code when they got used by multiple applications. So compressing msvcrt.dll +is a waste of memory, but compressing the dll plugins of a particular +application may be a better idea. +.PP +Screensavers are supported, with the restriction that the filename +must end with \*(L".scr\*(R" (as screensavers are handled slightly different +than normal exe files). +.PP +\&\s-1UPX\s0 compressed \s-1PE\s0 files have some minor memory overhead (usually in the +10 \- 30 KiB range) which can be seen by specifying the \*(L"\-i\*(R" command +line switch during compression. +.PP +Extra options available for this executable format: +.PP +.Vb 9 +\& \-\-compress\-exports=0 Don\*(Aqt compress the export section. +\& Use this if you plan to run the compressed +\& program under Wine. +\& \-\-compress\-exports=1 Compress the export section. [DEFAULT] +\& Compression of the export section can improve the +\& compression ratio quite a bit but may not work +\& with all programs (like winword.exe). +\& UPX never compresses the export section of a DLL +\& regardless of this option. +\& +\& \-\-compress\-icons=0 Don\*(Aqt compress any icons. +\& \-\-compress\-icons=1 Compress all but the first icon. +\& \-\-compress\-icons=2 Compress all icons which are not in the +\& first icon directory. [DEFAULT] +\& \-\-compress\-icons=3 Compress all icons. +\& +\& \-\-compress\-resources=0 Don\*(Aqt compress any resources at all. +\& +\& \-\-keep\-resource=list Don\*(Aqt compress resources specified by the list. +\& The members of the list are separated by commas. +\& A list member has the following format: I. +\& I is the type of the resource. Standard types +\& must be specified as decimal numbers, user types can be +\& specified by decimal IDs or strings. I is the +\& identifier of the resource. It can be a decimal number +\& or a string. For example: +\& +\& \-\-keep\-resource=2/MYBITMAP,5,6/12345 +\& +\& UPX won\*(Aqt compress the named bitmap resource "MYBITMAP", +\& it leaves every dialog (5) resource uncompressed, and +\& it won\*(Aqt touch the string table resource with identifier +\& 12345. +\& +\& \-\-force Force compression even when there is an +\& unexpected value in a header field. +\& Use with care. +\& +\& \-\-strip\-relocs=0 Don\*(Aqt strip relocation records. +\& \-\-strip\-relocs=1 Strip relocation records. [DEFAULT] +\& This option only works on executables with base +\& address greater or equal to 0x400000. Usually the +\& compressed files becomes smaller, but some files +\& may become larger. Note that the resulting file will +\& not work under Windows 3.x (Win32s). +\& UPX never strips relocations from a DLL +\& regardless of this option. +\& +\& \-\-all\-methods Compress the program several times, using all +\& available compression methods. This may improve +\& the compression ratio in some cases, but usually +\& the default method gives the best results anyway. +\& +\& \-\-all\-filters Compress the program several times, using all +\& available preprocessing filters. This may improve +\& the compression ratio in some cases, but usually +\& the default filter gives the best results anyway. +.Ve +.SH "DIAGNOSTICS" +.IX Header "DIAGNOSTICS" +Exit status is normally 0; if an error occurs, exit status +is 1. If a warning occurs, exit status is 2. +.PP +\&\fB\s-1UPX\s0\fR's diagnostics are intended to be self-explanatory. +.SH "BUGS" +.IX Header "BUGS" +Please report all bugs immediately to the authors. +.SH "AUTHORS" +.IX Header "AUTHORS" +.Vb 2 +\& Markus F.X.J. Oberhumer +\& http://www.oberhumer.com +\& +\& Laszlo Molnar +\& +\& John F. Reiser +\& +\& Jens Medoch +.Ve +.SH "COPYRIGHT" +.IX Header "COPYRIGHT" +Copyright (C) 1996\-2024 Markus Franz Xaver Johannes Oberhumer +.PP +Copyright (C) 1996\-2024 Laszlo Molnar +.PP +Copyright (C) 2000\-2024 John F. Reiser +.PP +Copyright (C) 2002\-2024 Jens Medoch +.PP +\&\fB\s-1UPX\s0\fR is distributed with full source code under the terms of the +\&\s-1GNU\s0 General Public License v2+; either under the pure GPLv2+ (see +the file \s-1COPYING\s0), or (at your option) under the GPLv+2 with special +exceptions and restrictions granting the free usage for all binaries +including commercial programs (see the file \s-1LICENSE\s0). +.PP +This program is distributed in the hope that it will be useful, +but \s-1WITHOUT ANY WARRANTY\s0; without even the implied warranty of +\&\s-1MERCHANTABILITY\s0 or \s-1FITNESS FOR A PARTICULAR PURPOSE.\s0 +.PP +You should have received a copy of the \s-1UPX\s0 License Agreements along +with this program; see the files \s-1COPYING\s0 and \s-1LICENSE.\s0 If not, +visit the \s-1UPX\s0 home page. diff --git a/util/init.go b/util/init.go new file mode 100644 index 0000000..8cf333c --- /dev/null +++ b/util/init.go @@ -0,0 +1,72 @@ +package util + +import ( + "fmt" + "github.com/gabriel-vasile/mimetype" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "time" +) + +// CheckFileIsExist 判断文件是否存在,存在返回 true,不存在返回false +func CheckFileIsExist(filename string) bool { + var exist = true + if _, err := os.Stat(filename); os.IsNotExist(err) { + exist = false + } + return exist +} + +// WriteByteArraysToFile 写字节数组到文件 +func WriteByteArraysToFile(content []byte, filename string) error { + return ioutil.WriteFile(filename, content, 0777) +} + +// String2int 字符串转int +func String2int(str string) int { + intValue, err := strconv.Atoi(str) + if err != nil { + return 0 + } + return intValue +} + +// CreateDir 创建目录 +func CreateDir(dirPath string) bool { + err := os.Mkdir(dirPath, 0755) + return err == nil +} + +// ReadFileMimeInfo 获取文件mime信息 +func ReadFileMimeInfo(filepath string) *mimetype.MIME { + mt, err := mimetype.DetectFile(filepath) + if err != nil { + log.Fatal(err) + } + return mt +} + +// SecondsToHms 将秒数转换为小时、分钟、秒的格式 +func SecondsToHms(seconds int) string { + t := time.Duration(seconds) * time.Second + h := t / time.Hour + t -= h * time.Hour + m := t / time.Minute + t -= m * time.Minute + s := t / time.Second + return fmt.Sprintf("%02d-%02d-%02d", h, m, s) +} + +// CalcPercentage 计算percentage相对于total的百分比 +func CalcPercentage(percentage int, total int) int { + return int(float64(percentage) / float64(total) * 100) +} + +// 获取文件所在文件夹 +func GetFileDirectory(filePath string) string { + directoryPath, _ := filepath.Split(filePath) + return directoryPath +} diff --git a/vars/init.go b/vars/init.go new file mode 100644 index 0000000..66bc52a --- /dev/null +++ b/vars/init.go @@ -0,0 +1,8 @@ +package vars + +var GlobalFilePathList []string // 所有文件的路径 +var GlobalFilePath2MimeInfoMap = make(map[string]string) // 文件路径和mime信息的映射关系 +var GlobalFilePath2FileNameMap = make(map[string]string) // 文件路径和文件名称的映射关系 +var GlobalFilePath2FileExtMap = make(map[string]string) // 文件路径和文件扩展名的映射关系 +var GlobalVideoPathList []string // 所有视频文件的路径 +var GlobalImagePathList []string // 所有图片文件的路径