refactor: 按模块重构项目结构
- 删除 vars 全局状态,按职责拆分 internal/ 包:scanner/probe/classify/organize/mover/store/digest - 数据驱动归类规则替代图片/视频重复分支,store 改为结构体实例 - build.sh 按系统类型编译:Windows 用 upx 压缩,macOS/Linux 不压缩 - 忽略目录拆分:图片_已忽略 / 视频_已忽略 - 输出构建产物加入 .gitignore
This commit is contained in:
+7
-2
@@ -27,6 +27,11 @@
|
|||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
# 内嵌的 ffprobe 二进制(约 80~150MB),不入库,需自行放置于 vars/bin/
|
# 内嵌的 ffprobe 二进制(约 80~150MB),不入库,需自行放置于 internal/probe/bin/
|
||||||
vars/bin/
|
internal/probe/bin/
|
||||||
|
|
||||||
|
# 构建产物(build.sh 输出)
|
||||||
|
kenaito-media-picker
|
||||||
|
kenaito-media-picker.exe
|
||||||
|
kenaito-media-picker-release.exe
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ sh build.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 依赖 Go 1.19+(`go.mod` 指定 1.19)
|
- 依赖 Go 1.19+(`go.mod` 指定 1.19)
|
||||||
- 每个平台构建时只内嵌**本平台**的 ffprobe:`vars/bin/ffprobe_mac`(macOS)、`vars/bin/ffprobe.exe`(Windows),需提前放置(未入库,见 `.gitignore`)
|
- 每个平台构建时只内嵌**本平台**的 ffprobe:`internal/probe/bin/ffprobe_mac`(macOS)、`internal/probe/bin/ffprobe.exe`(Windows),需提前放置(未入库,见 `.gitignore`)
|
||||||
- `build.sh` 使用 `upx` 压缩产物,输出 `kenaito-media-picker.exe`(Windows 单文件)
|
- `build.sh` 按系统类型编译:**Windows** 下 `go build` 后使用 `upx` 压缩,输出 `kenaito-media-picker.exe`;**macOS / Linux** 直接构建,输出 `kenaito-media-picker`(不压缩)
|
||||||
- 编译所需工具(ffmpeg / upx)压缩包已放在 `doc/` 目录,按需解压使用
|
- 编译所需工具(ffmpeg / upx)压缩包已放在 `doc/` 目录,按需解压使用(upx 仅 Windows 构建时需要,放置于项目根目录 `upx`)
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ kenaito-media-picker.exe
|
|||||||
{uid}-视频_横屏_GIF / 视频_竖屏_GIF / 视频_等比_GIF
|
{uid}-视频_横屏_GIF / 视频_竖屏_GIF / 视频_等比_GIF
|
||||||
音频 ← 所有音频文件(ffmpeg 可识别的格式)
|
音频 ← 所有音频文件(ffmpeg 可识别的格式)
|
||||||
视频_读取异常 ← mime 为视频但 ffprobe 读取失败
|
视频_读取异常 ← mime 为视频但 ffprobe 读取失败
|
||||||
已忽略 ← 非媒体文件或无法识别
|
视频_已忽略 ← 非媒体文件或无法识别
|
||||||
```
|
```
|
||||||
|
|
||||||
> 视频时长与分辨率通过内嵌 ffprobe 读取(处理时会临时释放到当前目录、完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
|
> 视频时长与分辨率通过内嵌 ffprobe 读取(处理时会临时释放到当前目录、完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
|
||||||
@@ -95,10 +95,15 @@ kenaito-media-picker.exe
|
|||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
├── main.go # 程序入口(扫描 → 过滤 → 图片/媒体处理)
|
├── main.go # 程序入口(装配与编排)
|
||||||
├── core/ # 核心逻辑:文件扫描、MIME 过滤、图片/视频/音频分组与移动
|
├── internal/
|
||||||
├── util/ # 工具:MIME 识别、图片尺寸读取、ffprobe 封装、文件移动
|
│ ├── scanner/ # 文件扫描、MIME 读取、图片/媒体候选过滤
|
||||||
├── vars/ # 全局状态 + 按系统内嵌 ffprobe(go:embed,二进制在 vars/bin/)
|
│ ├── probe/ # 媒体探测:图片尺寸 + ffprobe 解析 + 内嵌解码器释放
|
||||||
|
│ ├── classify/ # 纯函数归类规则(方向 + 标准分辨率 + K档)
|
||||||
|
│ ├── organize/ # 整理编排:探测→归类→收集→移动→记录去向
|
||||||
|
│ ├── mover/ # 文件移动(rename / 跨盘 copy+delete)
|
||||||
|
│ ├── store/ # SQLite 媒体信息持久化
|
||||||
|
│ └── digest/ # 文件 MD5 / 指纹采样
|
||||||
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
|
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
|
||||||
└── build.sh # 编译脚本(upx 压缩)
|
└── build.sh # 编译脚本(upx 压缩)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
rm -f kenaito-media-picker.exe
|
#!/bin/sh
|
||||||
rm -f kenaito-media-picker-release.exe
|
# 构建脚本:按系统类型选择编译方式
|
||||||
# build
|
# Windows (MINGW/MSYS/CYGWIN): go build + upx 压缩 → kenaito-media-picker.exe
|
||||||
go build -o kenaito-media-picker-release.exe main.go
|
# macOS / Linux: 直接构建,不压缩 → kenaito-media-picker
|
||||||
# upx compress
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
|
echo "=== Windows: go build + upx 压缩"
|
||||||
|
rm -f kenaito-media-picker.exe kenaito-media-picker-release.exe
|
||||||
|
go build -o kenaito-media-picker-release.exe .
|
||||||
./upx -o kenaito-media-picker.exe kenaito-media-picker-release.exe
|
./upx -o kenaito-media-picker.exe kenaito-media-picker-release.exe
|
||||||
rm -f kenaito-media-picker-release.exe
|
rm -f kenaito-media-picker-release.exe
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "=== 非 Windows (macOS/Linux): 直接构建,不压缩"
|
||||||
|
rm -f kenaito-media-picker
|
||||||
|
go build -o kenaito-media-picker .
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "=== 构建完成"
|
||||||
|
|||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"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 ReadBmpImage(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" {
|
|
||||||
return fmt.Errorf("not a valid BMP file: file type is %q, expected \"BM\"", string(fileHeader.FileType[:])), 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
|
|
||||||
}
|
|
||||||
// 自顶向下 BMP 的高度为负值,取绝对值避免影响后续解析
|
|
||||||
return err, absInt32(infoHeader.Width), absInt32(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))
|
|
||||||
}
|
|
||||||
|
|
||||||
// absInt32 返回 int32 的绝对值
|
|
||||||
func absInt32(v int32) int32 {
|
|
||||||
if v < 0 {
|
|
||||||
return -v
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPsdImage 读取PSD/PSB格式的图片信息(两者共享相同的8BPS文件头)
|
|
||||||
func ReadPsdImage(filePath string) (err error, width int, height int) {
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("=== Failed to open file: %v\n", err)
|
|
||||||
return err, 0, 0
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var header [26]byte
|
|
||||||
if _, err := io.ReadFull(file, header[:]); err != nil {
|
|
||||||
fmt.Printf("=== Failed to read PSD/PSB header: %v\n", err)
|
|
||||||
return err, 0, 0
|
|
||||||
}
|
|
||||||
if string(header[0:4]) != "8BPS" {
|
|
||||||
return fmt.Errorf("not a valid PSD/PSB file: magic %q, expected \"8BPS\"", string(header[0:4])), 0, 0
|
|
||||||
}
|
|
||||||
version := binary.BigEndian.Uint16(header[4:6])
|
|
||||||
if version != 1 && version != 2 {
|
|
||||||
return fmt.Errorf("unsupported PSD/PSB version: %d", version), 0, 0
|
|
||||||
}
|
|
||||||
height = int(binary.BigEndian.Uint32(header[14:18]))
|
|
||||||
width = int(binary.BigEndian.Uint32(header[18:22]))
|
|
||||||
return nil, width, height
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
|
||||||
"kenaito-media-picker/store"
|
|
||||||
"kenaito-media-picker/util"
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
"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
|
|
||||||
mt := util.ReadFileMimeInfo(currentPath)
|
|
||||||
if mt != nil {
|
|
||||||
vars.GlobalFilePath2MimeInfoMap[currentPath] = mt.String()
|
|
||||||
} else {
|
|
||||||
vars.GlobalFilePath2MimeInfoMap[currentPath] = ""
|
|
||||||
}
|
|
||||||
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
|
|
||||||
// 图片
|
|
||||||
ext := path.Ext(globalFilePath)
|
|
||||||
if strings.Contains(fileMime, "image/") || util.IsSupportImage(ext) || strings.EqualFold(ext, ".psd") {
|
|
||||||
vars.GlobalImagePathList = append(vars.GlobalImagePathList, globalFilePath)
|
|
||||||
bar.PrintBar(util.CalcPercentage(count, total))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 其余作为媒体候选,由ffprobe最终判定(视频/音频/不支持),
|
|
||||||
// 只要ffmpeg支持的格式都会被识别,不再依赖扩展名白名单
|
|
||||||
if util.IsPotentialMedia(fileMime) {
|
|
||||||
vars.GlobalMediaPathList = append(vars.GlobalMediaPathList, 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.GlobalPickerDir != "" && currentPath == vars.GlobalPickerDir {
|
|
||||||
return filepath.SkipDir // 跳过白名单目录(已整理文件的输出目录)
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
if filepath.Base(currentPath) == store.DBFileName {
|
|
||||||
return nil // 跳过媒体信息数据库文件
|
|
||||||
}
|
|
||||||
if filepath.Base(currentPath) == util.FFprobeExeName || filepath.Base(currentPath) == "ffprobe_mac" {
|
|
||||||
return nil // 跳过ffprobe相关文件(中断残留的释放文件 / vars/bin内嵌源文件)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,750 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
_ "image/gif" // 导入gif支持
|
|
||||||
_ "image/jpeg" // 导入jpeg支持
|
|
||||||
_ "image/png" // 导入png支持
|
|
||||||
"kenaito-media-picker/util"
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DoHandleImage(pickerDir string) {
|
|
||||||
total := len(vars.GlobalImagePathList) // 总数
|
|
||||||
successCount := 0 // 成功数
|
|
||||||
errorCount := 0 // 失败数
|
|
||||||
ignoreCount := 0 // 忽略数
|
|
||||||
for _, imageFilePath := range vars.GlobalImagePathList {
|
|
||||||
suffix := vars.GlobalFilePath2FileExtMap[imageFilePath]
|
|
||||||
if strings.EqualFold(suffix, ".webp") { // 特殊文件处理, webp为网络常用图片格式
|
|
||||||
webpErr, webpWidth, webpHeight := ReadWebpImage(imageFilePath)
|
|
||||||
if webpErr == nil {
|
|
||||||
vars.ImagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", webpWidth, webpHeight)
|
|
||||||
saveMediaRecord(imageFilePath, "image", webpWidth, webpHeight, 0, "", "", "")
|
|
||||||
successCount = successCount + 1
|
|
||||||
} else {
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
|
|
||||||
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.EqualFold(suffix, ".bmp") { // 特殊文件处理
|
|
||||||
bpmErr, bmpWidth, bmpHeight := ReadBmpImage(imageFilePath)
|
|
||||||
if bpmErr == nil {
|
|
||||||
vars.ImagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", bmpWidth, bmpHeight)
|
|
||||||
saveMediaRecord(imageFilePath, "image", int(bmpWidth), int(bmpHeight), 0, "", "", "")
|
|
||||||
successCount = successCount + 1
|
|
||||||
} else {
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
|
|
||||||
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.EqualFold(suffix, ".psd") { // 特殊文件处理
|
|
||||||
vars.ImagePsdList = append(vars.ImagePsdList, imageFilePath)
|
|
||||||
successCount = successCount + 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.EqualFold(suffix, ".psb") { // 特殊文件处理, psb与psd共享文件头
|
|
||||||
psbErr, psbWidth, psbHeight := ReadPsdImage(imageFilePath)
|
|
||||||
if psbErr == nil {
|
|
||||||
vars.ImagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", psbWidth, psbHeight)
|
|
||||||
saveMediaRecord(imageFilePath, "image", psbWidth, psbHeight, 0, "", "", "")
|
|
||||||
successCount = successCount + 1
|
|
||||||
} else {
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
|
|
||||||
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if util.IsSupportImage(suffix) {
|
|
||||||
err, width, height := util.ReadImageInfo(imageFilePath)
|
|
||||||
if err == nil {
|
|
||||||
successCount = successCount + 1
|
|
||||||
vars.ImagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", width, height)
|
|
||||||
saveMediaRecord(imageFilePath, "image", width, height, 0, "", "", "")
|
|
||||||
fmt.Printf("=== Image总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount)
|
|
||||||
} else if util.CheckFileIsExist(util.FFprobeExePath()) {
|
|
||||||
// Go标准库解码失败,尝试用ffprobe兜底(ffmpeg支持的图片格式更广,如tiff/svg等)
|
|
||||||
if probe, perr := util.ProbeMediaFile(util.FFprobeExePath(), imageFilePath); perr == nil {
|
|
||||||
if w, h := probe.VideoResolution(); w > 0 && h > 0 {
|
|
||||||
successCount = successCount + 1
|
|
||||||
vars.ImagePath2WidthHeightMap[imageFilePath] = fmt.Sprintf("%d-%d", w, h)
|
|
||||||
saveMediaRecord(imageFilePath, "image", w, h, 0, probe.VideoCodec(), probe.FormatName(), probe.RawJSON())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
|
|
||||||
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
|
|
||||||
} else {
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
|
|
||||||
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 其他的直接先忽略吧, 爱改改, 不改拉倒
|
|
||||||
ignoreCount = ignoreCount + 1
|
|
||||||
vars.ImageIgnorePathList = append(vars.ImageIgnorePathList, imageFilePath)
|
|
||||||
}
|
|
||||||
if len(vars.ImagePsdList) > 0 {
|
|
||||||
psdImagePath := pickerDir + string(os.PathSeparator) + "图片_PSD"
|
|
||||||
if util.CreateDir(psdImagePath) {
|
|
||||||
util.DoMoveFileToDir(vars.ImagePsdList, psdImagePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(vars.ImageReadErrorPathList) > 0 {
|
|
||||||
readInfoErrorPath := pickerDir + string(os.PathSeparator) + "图片_读取异常"
|
|
||||||
if util.CreateDir(readInfoErrorPath) {
|
|
||||||
util.DoMoveFileToDir(vars.ImageReadErrorPathList, readInfoErrorPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(vars.ImageIgnorePathList) > 0 {
|
|
||||||
ignorePath := pickerDir + string(os.PathSeparator) + "图片_已忽略"
|
|
||||||
if util.CreateDir(ignorePath) {
|
|
||||||
util.DoMoveFileToDir(vars.ImageIgnorePathList, ignorePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
doPickImageFile(pickerDir, vars.ImagePath2WidthHeightMap)
|
|
||||||
fmt.Printf("=== 图片处理完毕 \n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 条件图片并分组存放
|
|
||||||
func doPickImageFile(pickerDir string, imagePath2WidthHeightMap map[string]string) {
|
|
||||||
if len(imagePath2WidthHeightMap) == 0 {
|
|
||||||
fmt.Printf("=== 当前目录下没有扫描到图片文件, %s \n", pickerDir)
|
|
||||||
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 strings.EqualFold(suffix, ".gif") {
|
|
||||||
handleGifImage(currentImagePath)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if width > height {
|
|
||||||
handleHorizontalImage(currentImagePath, width, height)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if width < height {
|
|
||||||
handleVerticalImage(currentImagePath, height)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
handleSquareImage(currentImagePath, width)
|
|
||||||
}
|
|
||||||
moveHorizontalImage(pickerDir)
|
|
||||||
moveVerticalImage(pickerDir)
|
|
||||||
moveSquareImage(pickerDir)
|
|
||||||
moveGifImage(pickerDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一处理gif
|
|
||||||
func handleGifImage(path string) {
|
|
||||||
vars.ImageGifList = append(vars.ImageGifList, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理垂直图片
|
|
||||||
func handleVerticalImage(currentImagePath string, height int) {
|
|
||||||
// 非标规格
|
|
||||||
if height < 1024 {
|
|
||||||
vars.ImageVerticalNormalList = append(vars.ImageVerticalNormalList, currentImagePath)
|
|
||||||
} else if height < 2048 {
|
|
||||||
vars.ImageVertical1KList = append(vars.ImageVertical1KList, currentImagePath)
|
|
||||||
} else if height < 3072 {
|
|
||||||
vars.ImageVertical2KList = append(vars.ImageVertical2KList, currentImagePath)
|
|
||||||
} else if height < 4096 {
|
|
||||||
vars.ImageVertical3KList = append(vars.ImageVertical3KList, currentImagePath)
|
|
||||||
} else if height < 5120 {
|
|
||||||
vars.ImageVertical4KList = append(vars.ImageVertical4KList, currentImagePath)
|
|
||||||
} else if height < 6144 {
|
|
||||||
vars.ImageVertical5KList = append(vars.ImageVertical5KList, currentImagePath)
|
|
||||||
} else if height < 7168 {
|
|
||||||
vars.ImageVertical6KList = append(vars.ImageVertical6KList, currentImagePath)
|
|
||||||
} else if height < 8192 {
|
|
||||||
vars.ImageVertical7KList = append(vars.ImageVertical7KList, currentImagePath)
|
|
||||||
} else if height < 9216 {
|
|
||||||
vars.ImageVertical8KList = append(vars.ImageVertical8KList, currentImagePath)
|
|
||||||
} else if height < 10240 {
|
|
||||||
vars.ImageVertical9KList = append(vars.ImageVertical9KList, currentImagePath)
|
|
||||||
} else if height < 11264 {
|
|
||||||
vars.ImageVertical10KList = append(vars.ImageVertical10KList, currentImagePath)
|
|
||||||
} else if height < 12288 {
|
|
||||||
vars.ImageVertical11KList = append(vars.ImageVertical11KList, currentImagePath)
|
|
||||||
} else if height < 13312 {
|
|
||||||
vars.ImageVertical12KList = append(vars.ImageVertical12KList, currentImagePath)
|
|
||||||
} else if height < 14336 {
|
|
||||||
vars.ImageVertical13KList = append(vars.ImageVertical13KList, currentImagePath)
|
|
||||||
} else if height < 15360 {
|
|
||||||
vars.ImageVertical14KList = append(vars.ImageVertical14KList, currentImagePath)
|
|
||||||
} else if height < 16384 {
|
|
||||||
vars.ImageVertical15KList = append(vars.ImageVertical15KList, currentImagePath)
|
|
||||||
} else if height < 17408 {
|
|
||||||
vars.ImageVertical16KList = append(vars.ImageVertical16KList, currentImagePath)
|
|
||||||
} else if height < 18432 {
|
|
||||||
vars.ImageVertical17KList = append(vars.ImageVertical17KList, currentImagePath)
|
|
||||||
} else if height < 19456 {
|
|
||||||
vars.ImageVertical18KList = append(vars.ImageVertical18KList, currentImagePath)
|
|
||||||
} else if height < 20480 {
|
|
||||||
vars.ImageVertical19KList = append(vars.ImageVertical19KList, currentImagePath)
|
|
||||||
} else if height >= 20480 { // 提示请忽略
|
|
||||||
vars.ImageVerticalHKList = append(vars.ImageVerticalHKList, currentImagePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理横向图片
|
|
||||||
func handleHorizontalImage(currentImagePath string, width int, height int) {
|
|
||||||
// 1280×720(720p)—— 标准高清(HD),常用于早期高清视频或小型屏幕
|
|
||||||
if width == 1280 && height == 720 {
|
|
||||||
vars.ImageHorizontalStandard720PList = append(vars.ImageHorizontalStandard720PList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 1600×900—— 介于HD和FHD之间
|
|
||||||
if width == 1600 && height == 900 {
|
|
||||||
vars.ImageHorizontalStandard1D5KList = append(vars.ImageHorizontalStandard1D5KList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 1920×1080(1080p)—— 全高清,主流显示器、电视和视频的分辨率
|
|
||||||
if width == 1920 && height == 1080 {
|
|
||||||
vars.ImageHorizontalStandard1080PList = append(vars.ImageHorizontalStandard1080PList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2048×1080(2dk)—— 影院2k
|
|
||||||
if width == 2048 && height == 1080 {
|
|
||||||
vars.ImageHorizontalStandard2DKList = append(vars.ImageHorizontalStandard2DKList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2560×1440(1440p)—— 俗称“2.5K”,电竞显示器或高端手机屏幕
|
|
||||||
if width == 2560 && height == 1440 {
|
|
||||||
vars.ImageHorizontalStandard2D5KList = append(vars.ImageHorizontalStandard2D5KList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2560×1080—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 2560 && height == 1080 {
|
|
||||||
vars.ImageHorizontalStandardUltraWide2List = append(vars.ImageHorizontalStandardUltraWide2List, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 3440×1440—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 3440 && height == 1440 {
|
|
||||||
vars.ImageHorizontalStandardUltraWide3List = append(vars.ImageHorizontalStandardUltraWide3List, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 5120×2160—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 5120 && height == 2160 {
|
|
||||||
vars.ImageHorizontalStandardUltraWide4List = append(vars.ImageHorizontalStandardUltraWide4List, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 3840×2160(主流4K)—— 超高清,现代高端显示器、电视和影视制作
|
|
||||||
if width == 3840 && height == 2160 {
|
|
||||||
vars.ImageHorizontalStandard4KList = append(vars.ImageHorizontalStandard4KList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 4096×2160(DCI 4K)—— 电影行业标准
|
|
||||||
if width == 4096 && height == 2160 {
|
|
||||||
vars.ImageHorizontalStandard4KHList = append(vars.ImageHorizontalStandard4KHList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 7680×4320—— 超高清,用于专业影视或高端设备
|
|
||||||
if width == 7680 && height == 4320 {
|
|
||||||
vars.ImageHorizontalStandard8KList = append(vars.ImageHorizontalStandard8KList, currentImagePath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 非标规格
|
|
||||||
if width < 1024 {
|
|
||||||
vars.ImageHorizontalNormalList = append(vars.ImageHorizontalNormalList, currentImagePath)
|
|
||||||
} else if width < 2048 {
|
|
||||||
vars.ImageHorizontal1KList = append(vars.ImageHorizontal1KList, currentImagePath)
|
|
||||||
} else if width < 3072 {
|
|
||||||
vars.ImageHorizontal2KList = append(vars.ImageHorizontal2KList, currentImagePath)
|
|
||||||
} else if width < 4096 {
|
|
||||||
vars.ImageHorizontal3KList = append(vars.ImageHorizontal3KList, currentImagePath)
|
|
||||||
} else if width < 5120 {
|
|
||||||
vars.ImageHorizontal4KList = append(vars.ImageHorizontal4KList, currentImagePath)
|
|
||||||
} else if width < 6144 {
|
|
||||||
vars.ImageHorizontal5KList = append(vars.ImageHorizontal5KList, currentImagePath)
|
|
||||||
} else if width < 7168 {
|
|
||||||
vars.ImageHorizontal6KList = append(vars.ImageHorizontal6KList, currentImagePath)
|
|
||||||
} else if width < 8192 {
|
|
||||||
vars.ImageHorizontal7KList = append(vars.ImageHorizontal7KList, currentImagePath)
|
|
||||||
} else if width < 9216 {
|
|
||||||
vars.ImageHorizontal8KList = append(vars.ImageHorizontal8KList, currentImagePath)
|
|
||||||
} else if width < 10240 {
|
|
||||||
vars.ImageHorizontal9KList = append(vars.ImageHorizontal9KList, currentImagePath)
|
|
||||||
} else if width < 11264 {
|
|
||||||
vars.ImageHorizontal10KList = append(vars.ImageHorizontal10KList, currentImagePath)
|
|
||||||
} else if width < 12288 {
|
|
||||||
vars.ImageHorizontal11KList = append(vars.ImageHorizontal11KList, currentImagePath)
|
|
||||||
} else if width < 13312 {
|
|
||||||
vars.ImageHorizontal12KList = append(vars.ImageHorizontal12KList, currentImagePath)
|
|
||||||
} else if width < 14336 {
|
|
||||||
vars.ImageHorizontal13KList = append(vars.ImageHorizontal13KList, currentImagePath)
|
|
||||||
} else if width < 15360 {
|
|
||||||
vars.ImageHorizontal14KList = append(vars.ImageHorizontal14KList, currentImagePath)
|
|
||||||
} else if width < 16384 {
|
|
||||||
vars.ImageHorizontal15KList = append(vars.ImageHorizontal15KList, currentImagePath)
|
|
||||||
} else if width < 17408 {
|
|
||||||
vars.ImageHorizontal16KList = append(vars.ImageHorizontal16KList, currentImagePath)
|
|
||||||
} else if width < 18432 {
|
|
||||||
vars.ImageHorizontal17KList = append(vars.ImageHorizontal17KList, currentImagePath)
|
|
||||||
} else if width < 19456 {
|
|
||||||
vars.ImageHorizontal18KList = append(vars.ImageHorizontal18KList, currentImagePath)
|
|
||||||
} else if width < 20480 {
|
|
||||||
vars.ImageHorizontal19KList = append(vars.ImageHorizontal19KList, currentImagePath)
|
|
||||||
} else if width >= 20480 { // 提示请忽略
|
|
||||||
vars.ImageHorizontalHKList = append(vars.ImageHorizontalHKList, currentImagePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理等比图片
|
|
||||||
func handleSquareImage(currentImagePath string, width int) {
|
|
||||||
// 非标规格
|
|
||||||
if width < 1024 {
|
|
||||||
vars.ImageSquareNormalList = append(vars.ImageSquareNormalList, currentImagePath)
|
|
||||||
} else if width < 2048 {
|
|
||||||
vars.ImageSquare1KList = append(vars.ImageSquare1KList, currentImagePath)
|
|
||||||
} else if width < 3072 {
|
|
||||||
vars.ImageSquare2KList = append(vars.ImageSquare2KList, currentImagePath)
|
|
||||||
} else if width < 4096 {
|
|
||||||
vars.ImageSquare3KList = append(vars.ImageSquare3KList, currentImagePath)
|
|
||||||
} else if width < 5120 {
|
|
||||||
vars.ImageSquare4KList = append(vars.ImageSquare4KList, currentImagePath)
|
|
||||||
} else if width < 6144 {
|
|
||||||
vars.ImageSquare5KList = append(vars.ImageSquare5KList, currentImagePath)
|
|
||||||
} else if width < 7168 {
|
|
||||||
vars.ImageSquare6KList = append(vars.ImageSquare6KList, currentImagePath)
|
|
||||||
} else if width < 8192 {
|
|
||||||
vars.ImageSquare7KList = append(vars.ImageSquare7KList, currentImagePath)
|
|
||||||
} else if width < 9216 {
|
|
||||||
vars.ImageSquare8KList = append(vars.ImageSquare8KList, currentImagePath)
|
|
||||||
} else if width < 10240 {
|
|
||||||
vars.ImageSquare9KList = append(vars.ImageSquare9KList, currentImagePath)
|
|
||||||
} else if width < 11264 {
|
|
||||||
vars.ImageSquare10KList = append(vars.ImageSquare10KList, currentImagePath)
|
|
||||||
} else if width < 12288 {
|
|
||||||
vars.ImageSquare11KList = append(vars.ImageSquare11KList, currentImagePath)
|
|
||||||
} else if width < 13312 {
|
|
||||||
vars.ImageSquare12KList = append(vars.ImageSquare12KList, currentImagePath)
|
|
||||||
} else if width < 14336 {
|
|
||||||
vars.ImageSquare13KList = append(vars.ImageSquare13KList, currentImagePath)
|
|
||||||
} else if width < 15360 {
|
|
||||||
vars.ImageSquare14KList = append(vars.ImageSquare14KList, currentImagePath)
|
|
||||||
} else if width < 16384 {
|
|
||||||
vars.ImageSquare15KList = append(vars.ImageSquare15KList, currentImagePath)
|
|
||||||
} else if width < 17408 {
|
|
||||||
vars.ImageSquare16KList = append(vars.ImageSquare16KList, currentImagePath)
|
|
||||||
} else if width < 18432 {
|
|
||||||
vars.ImageSquare17KList = append(vars.ImageSquare17KList, currentImagePath)
|
|
||||||
} else if width < 19456 {
|
|
||||||
vars.ImageSquare18KList = append(vars.ImageSquare18KList, currentImagePath)
|
|
||||||
} else if width < 20480 {
|
|
||||||
vars.ImageSquare19KList = append(vars.ImageSquare19KList, currentImagePath)
|
|
||||||
} else if width >= 20480 {
|
|
||||||
vars.ImageSquareHKList = append(vars.ImageSquareHKList, currentImagePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动水平图片
|
|
||||||
func moveHorizontalImage(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
// 标准
|
|
||||||
if len(vars.ImageHorizontalStandard720PList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_720P"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard720PList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard1D5KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_1D5KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard1D5KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard1080PList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_1080P"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard1080PList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard2DKList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_2DKP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard2DKList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard2D5KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_2D5KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard2D5KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandardUltraWide2List) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_ultraWide2KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandardUltraWide2List, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandardUltraWide3List) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_ultraWide3KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandardUltraWide3List, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandardUltraWide4List) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_ultraWide4KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandardUltraWide4List, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard4KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_4KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard4KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard4KHList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_4KHP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard4KHList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalStandard8KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_8KP"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalStandard8KList, imageDirPath)
|
|
||||||
}
|
|
||||||
// 非标准
|
|
||||||
if len(vars.ImageHorizontalNormalList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_普通"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalNormalList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal1KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_1K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal1KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal2KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_2K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal2KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal3KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_3K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal3KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal4KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_4K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal4KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal5KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_5K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal5KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal6KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_6K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal6KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal7KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_7K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal7KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal8KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_8K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal8KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal9KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_9K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal9KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal10KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_10K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal10KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal11KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_11K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal11KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal12KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_12K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal12KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal13KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_13K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal13KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal14KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_14K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal14KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal15KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_15K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal15KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal16KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_16K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal16KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal17KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_17K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal17KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal18KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_18K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal18KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontal19KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_19K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontal19KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageHorizontalHKList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_横屏_原图"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageHorizontalHKList, imageDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动垂直图片
|
|
||||||
func moveVerticalImage(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
// 非标准
|
|
||||||
if len(vars.ImageVerticalNormalList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_普通"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVerticalNormalList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical1KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_1K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical1KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical2KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_2K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical2KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical3KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_3K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical3KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical4KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_4K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical4KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical5KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_5K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical5KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical6KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_6K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical6KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical7KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_7K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical7KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical8KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_8K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical8KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical9KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_9K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical9KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical10KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_10K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical10KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical11KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_11K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical11KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical12KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_12K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical12KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical13KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_13K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical13KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical14KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_14K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical14KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical15KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_15K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical15KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical16KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_16K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical16KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical17KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_17K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical17KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical18KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_18K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical18KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVertical19KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_19K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVertical19KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageVerticalHKList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_竖屏_原图"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageVerticalHKList, imageDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动等比图片
|
|
||||||
func moveSquareImage(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
// 非标准
|
|
||||||
if len(vars.ImageSquareNormalList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_普通"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquareNormalList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare1KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_1K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare1KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare2KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_2K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare2KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare3KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_3K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare3KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare4KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_4K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare4KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare5KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_5K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare5KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare6KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_6K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare6KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare7KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_7K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare7KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare8KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_8K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare8KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare9KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_9K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare9KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare10KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_10K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare10KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare11KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_11K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare11KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare12KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_12K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare12KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare13KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_13K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare13KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare14KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_14K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare14KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare15KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_15K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare15KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare16KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_16K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare16KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare17KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_17K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare17KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare18KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_18K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare18KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquare19KList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_19K"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquare19KList, imageDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.ImageSquareHKList) > 0 {
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_等比_原图"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageSquareHKList, imageDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动gif图片
|
|
||||||
func moveGifImage(pickerDir string) {
|
|
||||||
if len(vars.ImageGifList) > 0 {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
imageDirPath := pickerDir + pathSeparator + "图片_GIF"
|
|
||||||
util.CreateDir(imageDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.ImageGifList, imageDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,718 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"kenaito-media-picker/util"
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DoHandleMedia 处理所有媒体候选文件:ffprobe判定视频/音频/不支持
|
|
||||||
func DoHandleMedia(pickerDir string) {
|
|
||||||
total := len(vars.GlobalMediaPathList)
|
|
||||||
videoCount := 0 // 视频数
|
|
||||||
audioCount := 0 // 音频数
|
|
||||||
errorCount := 0 // 异常数
|
|
||||||
ignoreCount := 0
|
|
||||||
for _, mediaFilePath := range vars.GlobalMediaPathList {
|
|
||||||
fileMime := vars.GlobalFilePath2MimeInfoMap[mediaFilePath]
|
|
||||||
probe, err := util.ProbeMediaFile(util.FFprobeExePath(), mediaFilePath)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(fileMime, "video/") {
|
|
||||||
// mime是视频但ffprobe读不出来,视为损坏/异常视频
|
|
||||||
errorCount = errorCount + 1
|
|
||||||
vars.VideoReadErrorPathList = append(vars.VideoReadErrorPathList, mediaFilePath)
|
|
||||||
fmt.Printf("=== 异常视频: %s \n", mediaFilePath)
|
|
||||||
} else {
|
|
||||||
ignoreCount = ignoreCount + 1
|
|
||||||
vars.VideoIgnorePathList = append(vars.VideoIgnorePathList, mediaFilePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
width, height := probe.VideoResolution()
|
|
||||||
if width > 0 && height > 0 { // 有视频流
|
|
||||||
videoCount = videoCount + 1
|
|
||||||
vars.VideoPath2WidthHeightMap[mediaFilePath] = fmt.Sprintf("%d-%d", width, height)
|
|
||||||
saveMediaRecord(mediaFilePath, "video", width, height, probe.DurationSeconds(), probe.VideoCodec(), probe.FormatName(), probe.RawJSON())
|
|
||||||
fmt.Printf("=== 媒体总数: %d, 已处理: %d, 视频: %d, 音频: %d, 异常: %d, 忽略: %d \n", total, videoCount+audioCount+errorCount+ignoreCount, videoCount, audioCount, errorCount, ignoreCount)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if probe.HasAudio() { // 仅有音频流
|
|
||||||
audioCount = audioCount + 1
|
|
||||||
vars.GlobalAudioPathList = append(vars.GlobalAudioPathList, mediaFilePath)
|
|
||||||
saveMediaRecord(mediaFilePath, "audio", 0, 0, probe.DurationSeconds(), probe.AudioCodec(), probe.FormatName(), probe.RawJSON())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 既无视频也无音频(字幕流等)
|
|
||||||
ignoreCount = ignoreCount + 1
|
|
||||||
vars.VideoIgnorePathList = append(vars.VideoIgnorePathList, mediaFilePath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoReadErrorPathList) > 0 {
|
|
||||||
readInfoErrorPath := pickerDir + string(os.PathSeparator) + "视频_读取异常"
|
|
||||||
if util.CreateDir(readInfoErrorPath) {
|
|
||||||
util.DoMoveFileToDir(vars.VideoReadErrorPathList, readInfoErrorPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(vars.GlobalAudioPathList) > 0 {
|
|
||||||
audioPath := pickerDir + string(os.PathSeparator) + "音频"
|
|
||||||
if util.CreateDir(audioPath) {
|
|
||||||
util.DoMoveFileToDir(vars.GlobalAudioPathList, audioPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(vars.VideoIgnorePathList) > 0 {
|
|
||||||
ignorePath := pickerDir + string(os.PathSeparator) + "已忽略"
|
|
||||||
if util.CreateDir(ignorePath) {
|
|
||||||
util.DoMoveFileToDir(vars.VideoIgnorePathList, ignorePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
doPickVideoFile(pickerDir, vars.VideoPath2WidthHeightMap)
|
|
||||||
fmt.Printf("=== 视频/音频处理完毕 \n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 条件视频并分组存放
|
|
||||||
func doPickVideoFile(pickerDir string, videoPath2WidthHeightMap map[string]string) {
|
|
||||||
if len(videoPath2WidthHeightMap) == 0 {
|
|
||||||
fmt.Printf("=== 当前目录下没有扫描到视频文件, %s \n", pickerDir)
|
|
||||||
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(pickerDir)
|
|
||||||
moveHorizontalVideo(pickerDir)
|
|
||||||
moveVerticalVideo(pickerDir)
|
|
||||||
moveSquareVideo(pickerDir)
|
|
||||||
moveGifVideo(pickerDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动垂直视频
|
|
||||||
func moveVerticalVideo(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
if len(vars.VideoVertical1KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_1k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical1KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical2KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_2k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical2KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical3KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_3k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical3KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical4KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_4k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical4KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical5KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_5k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical5KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical6KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_6k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical6KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical7KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_7k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical7KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical8KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_8k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical8KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical9KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_9k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical9KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical10KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_10k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical10KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical11KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_11k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical11KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical12KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_12k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical12KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical13KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_13k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical13KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical14KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_14k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical14KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical15KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_15k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical15KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical16KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_16k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical16KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical17KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_17k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical17KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical18KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_18k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical18KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVertical19KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_19k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVertical19KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVerticalHKList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_原画质"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVerticalHKList, videoDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动水平视频
|
|
||||||
func moveHorizontalVideo(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
if len(vars.VideoHorizontal1KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_1k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal1KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal2KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_2k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal2KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal3KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_3k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal3KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal4KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_4k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal4KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal5KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_5k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal5KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal6KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_6k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal6KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal7KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_7k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal7KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal8KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_8k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal8KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal9KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_9k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal9KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal10KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_10k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal10KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal11KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_11k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal11KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal12KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_12k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal12KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal13KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_13k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal13KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal14KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_14k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal14KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal15KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_15k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal15KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal16KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_16k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal16KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal17KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_17k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal17KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal18KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_18k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal18KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontal19KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_19k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontal19KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalHKList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_原画质"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalHKList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard720PList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_720P"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard720PList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard1D5PList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_1D5KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard1D5PList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard1080PList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_1080P"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard1080PList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard2DKList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_2DKP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard2DKList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard2D5KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_2D5KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard2D5KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandardUltraWide2List) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_UltraWide2KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandardUltraWide2List, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandardUltraWide3List) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_UltraWide3KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandardUltraWide3List, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandardUltraWide4List) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_UltraWide4KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandardUltraWide4List, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard4KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_4KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard4KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard4KHList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_4KHP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard4KHList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoHorizontalStandard8KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_8KP"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalStandard8KList, videoDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动等比视频
|
|
||||||
func moveSquareVideo(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
if len(vars.VideoSquare1KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_1k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare1KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare2KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_2k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare2KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare3KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_3k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare3KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare4KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_4k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare4KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare5KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_5k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare5KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare6KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_6k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare6KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare7KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_7k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare7KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare8KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_8k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare8KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare9KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_9k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare9KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare10KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_10k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare10KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare11KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_11k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare11KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare12KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_12k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare12KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare13KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_13k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare13KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare14KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_14k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare14KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare15KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_15k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare15KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare16KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_16k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare16KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare17KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_17k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare17KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare18KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_18k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare18KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquare19KList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_19k"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquare19KList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquareHKList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_原画质"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquareHKList, videoDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动gif视频
|
|
||||||
func moveGifVideo(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
if len(vars.VideoHorizontalGifList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_GIF"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalGifList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVerticalGifList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_GIF"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVerticalGifList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquareGifList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_GIF"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquareGifList, videoDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动普通视频
|
|
||||||
func moveNormalVideo(pickerDir string) {
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
if len(vars.VideoHorizontalNormalList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_横屏_普通"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoHorizontalNormalList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoVerticalNormalList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_竖屏_普通"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoVerticalNormalList, videoDirPath)
|
|
||||||
}
|
|
||||||
if len(vars.VideoSquareNormalList) > 0 {
|
|
||||||
videoDirPath := pickerDir + pathSeparator + "视频_等比_普通"
|
|
||||||
util.CreateDir(videoDirPath)
|
|
||||||
util.DoMoveFileToDir(vars.VideoSquareNormalList, videoDirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理垂直视频
|
|
||||||
func handleVerticalVideo(currentVideoPath string, height int, suffix string) {
|
|
||||||
if strings.EqualFold(suffix, ".gif") {
|
|
||||||
vars.VideoVerticalGifList = append(vars.VideoVerticalGifList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if height < 1024 {
|
|
||||||
vars.VideoVerticalNormalList = append(vars.VideoVerticalNormalList, currentVideoPath)
|
|
||||||
} else if height < 2048 {
|
|
||||||
vars.VideoVertical1KList = append(vars.VideoVertical1KList, currentVideoPath)
|
|
||||||
} else if height < 3072 {
|
|
||||||
vars.VideoVertical2KList = append(vars.VideoVertical2KList, currentVideoPath)
|
|
||||||
} else if height < 4096 {
|
|
||||||
vars.VideoVertical3KList = append(vars.VideoVertical3KList, currentVideoPath)
|
|
||||||
} else if height < 5120 {
|
|
||||||
vars.VideoVertical4KList = append(vars.VideoVertical4KList, currentVideoPath)
|
|
||||||
} else if height < 6144 {
|
|
||||||
vars.VideoVertical5KList = append(vars.VideoVertical5KList, currentVideoPath)
|
|
||||||
} else if height < 7168 {
|
|
||||||
vars.VideoVertical6KList = append(vars.VideoVertical6KList, currentVideoPath)
|
|
||||||
} else if height < 8192 {
|
|
||||||
vars.VideoVertical7KList = append(vars.VideoVertical7KList, currentVideoPath)
|
|
||||||
} else if height < 9216 {
|
|
||||||
vars.VideoVertical8KList = append(vars.VideoVertical8KList, currentVideoPath)
|
|
||||||
} else if height < 10240 {
|
|
||||||
vars.VideoVertical9KList = append(vars.VideoVertical9KList, currentVideoPath)
|
|
||||||
} else if height < 11264 {
|
|
||||||
vars.VideoVertical10KList = append(vars.VideoVertical10KList, currentVideoPath)
|
|
||||||
} else if height < 12288 {
|
|
||||||
vars.VideoVertical11KList = append(vars.VideoVertical11KList, currentVideoPath)
|
|
||||||
} else if height < 13312 {
|
|
||||||
vars.VideoVertical12KList = append(vars.VideoVertical12KList, currentVideoPath)
|
|
||||||
} else if height < 14336 {
|
|
||||||
vars.VideoVertical13KList = append(vars.VideoVertical13KList, currentVideoPath)
|
|
||||||
} else if height < 15360 {
|
|
||||||
vars.VideoVertical14KList = append(vars.VideoVertical14KList, currentVideoPath)
|
|
||||||
} else if height < 16384 {
|
|
||||||
vars.VideoVertical15KList = append(vars.VideoVertical15KList, currentVideoPath)
|
|
||||||
} else if height < 17408 {
|
|
||||||
vars.VideoVertical16KList = append(vars.VideoVertical16KList, currentVideoPath)
|
|
||||||
} else if height < 18432 {
|
|
||||||
vars.VideoVertical17KList = append(vars.VideoVertical17KList, currentVideoPath)
|
|
||||||
} else if height < 19456 {
|
|
||||||
vars.VideoVertical18KList = append(vars.VideoVertical18KList, currentVideoPath)
|
|
||||||
} else if height < 20480 {
|
|
||||||
vars.VideoVertical19KList = append(vars.VideoVertical19KList, currentVideoPath)
|
|
||||||
} else if height >= 20480 {
|
|
||||||
vars.VideoVerticalHKList = append(vars.VideoVerticalHKList, currentVideoPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理横向视频
|
|
||||||
func handleHorizontalVideo(currentVideoPath string, width int, height int, suffix string) {
|
|
||||||
if strings.EqualFold(suffix, ".gif") {
|
|
||||||
vars.VideoHorizontalGifList = append(vars.VideoHorizontalGifList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 1280×720(720p)—— 标准高清(HD),常用于早期高清视频或小型屏幕
|
|
||||||
if width == 1280 && height == 720 {
|
|
||||||
vars.VideoHorizontalStandard720PList = append(vars.VideoHorizontalStandard720PList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 1600×900—— 介于HD和FHD之间
|
|
||||||
if width == 1600 && height == 900 {
|
|
||||||
vars.VideoHorizontalStandard1D5PList = append(vars.VideoHorizontalStandard1D5PList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 1920×1080(1080p)—— 全高清,主流显示器、电视和视频的分辨率
|
|
||||||
if width == 1920 && height == 1080 {
|
|
||||||
vars.VideoHorizontalStandard1080PList = append(vars.VideoHorizontalStandard1080PList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2048×1080(2dk)—— 影院2k
|
|
||||||
if width == 2048 && height == 1080 {
|
|
||||||
vars.VideoHorizontalStandard2DKList = append(vars.VideoHorizontalStandard2DKList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2560×1440(1440p)—— 俗称“2.5K”,电竞显示器或高端手机屏幕
|
|
||||||
if width == 2560 && height == 1440 {
|
|
||||||
vars.VideoHorizontalStandard2D5KList = append(vars.VideoHorizontalStandard2D5KList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2560×1080—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 2560 && height == 1080 {
|
|
||||||
vars.VideoHorizontalStandardUltraWide2List = append(vars.VideoHorizontalStandardUltraWide2List, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 3440×1440—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 3440 && height == 1440 {
|
|
||||||
vars.VideoHorizontalStandardUltraWide3List = append(vars.VideoHorizontalStandardUltraWide3List, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 5120×2160—— 带鱼屏显示器(超宽屏)
|
|
||||||
if width == 5120 && height == 2160 {
|
|
||||||
vars.VideoHorizontalStandardUltraWide4List = append(vars.VideoHorizontalStandardUltraWide4List, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 3840×2160(主流4K)—— 超高清,现代高端显示器、电视和影视制作
|
|
||||||
if width == 3840 && height == 2160 {
|
|
||||||
vars.VideoHorizontalStandard4KList = append(vars.VideoHorizontalStandard4KList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 4096×2160(DCI 4K)—— 电影行业标准
|
|
||||||
if width == 4096 && height == 2160 {
|
|
||||||
vars.VideoHorizontalStandard4KHList = append(vars.VideoHorizontalStandard4KHList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 7680×4320—— 超高清,用于专业影视或高端设备
|
|
||||||
if width == 7680 && height == 4320 {
|
|
||||||
vars.VideoHorizontalStandard8KList = append(vars.VideoHorizontalStandard8KList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 非标规格
|
|
||||||
if width < 1024 {
|
|
||||||
vars.VideoHorizontalNormalList = append(vars.VideoHorizontalNormalList, currentVideoPath)
|
|
||||||
} else if width < 2048 {
|
|
||||||
vars.VideoHorizontal1KList = append(vars.VideoHorizontal1KList, currentVideoPath)
|
|
||||||
} else if width < 3072 {
|
|
||||||
vars.VideoHorizontal2KList = append(vars.VideoHorizontal2KList, currentVideoPath)
|
|
||||||
} else if width < 4096 {
|
|
||||||
vars.VideoHorizontal3KList = append(vars.VideoHorizontal3KList, currentVideoPath)
|
|
||||||
} else if width < 5120 {
|
|
||||||
vars.VideoHorizontal4KList = append(vars.VideoHorizontal4KList, currentVideoPath)
|
|
||||||
} else if width < 6144 {
|
|
||||||
vars.VideoHorizontal5KList = append(vars.VideoHorizontal5KList, currentVideoPath)
|
|
||||||
} else if width < 7168 {
|
|
||||||
vars.VideoHorizontal6KList = append(vars.VideoHorizontal6KList, currentVideoPath)
|
|
||||||
} else if width < 8192 {
|
|
||||||
vars.VideoHorizontal7KList = append(vars.VideoHorizontal7KList, currentVideoPath)
|
|
||||||
} else if width < 9216 {
|
|
||||||
vars.VideoHorizontal8KList = append(vars.VideoHorizontal8KList, currentVideoPath)
|
|
||||||
} else if width < 10240 {
|
|
||||||
vars.VideoHorizontal9KList = append(vars.VideoHorizontal9KList, currentVideoPath)
|
|
||||||
} else if width < 11264 {
|
|
||||||
vars.VideoHorizontal10KList = append(vars.VideoHorizontal10KList, currentVideoPath)
|
|
||||||
} else if width < 12288 {
|
|
||||||
vars.VideoHorizontal11KList = append(vars.VideoHorizontal11KList, currentVideoPath)
|
|
||||||
} else if width < 13312 {
|
|
||||||
vars.VideoHorizontal12KList = append(vars.VideoHorizontal12KList, currentVideoPath)
|
|
||||||
} else if width < 14336 {
|
|
||||||
vars.VideoHorizontal13KList = append(vars.VideoHorizontal13KList, currentVideoPath)
|
|
||||||
} else if width < 15360 {
|
|
||||||
vars.VideoHorizontal14KList = append(vars.VideoHorizontal14KList, currentVideoPath)
|
|
||||||
} else if width < 16384 {
|
|
||||||
vars.VideoHorizontal15KList = append(vars.VideoHorizontal15KList, currentVideoPath)
|
|
||||||
} else if width < 17408 {
|
|
||||||
vars.VideoHorizontal16KList = append(vars.VideoHorizontal16KList, currentVideoPath)
|
|
||||||
} else if width < 18432 {
|
|
||||||
vars.VideoHorizontal17KList = append(vars.VideoHorizontal17KList, currentVideoPath)
|
|
||||||
} else if width < 19456 {
|
|
||||||
vars.VideoHorizontal18KList = append(vars.VideoHorizontal18KList, currentVideoPath)
|
|
||||||
} else if width < 20480 {
|
|
||||||
vars.VideoHorizontal19KList = append(vars.VideoHorizontal19KList, currentVideoPath)
|
|
||||||
} else if width >= 20480 { // 提示请忽略
|
|
||||||
vars.VideoHorizontalHKList = append(vars.VideoHorizontalHKList, currentVideoPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理等比视频
|
|
||||||
func handleSquareVideo(currentVideoPath string, width int, height int, suffix string) {
|
|
||||||
if strings.EqualFold(suffix, ".gif") {
|
|
||||||
vars.VideoSquareGifList = append(vars.VideoSquareGifList, currentVideoPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if width < 1024 {
|
|
||||||
vars.VideoSquareNormalList = append(vars.VideoSquareNormalList, currentVideoPath)
|
|
||||||
} else if width < 2048 {
|
|
||||||
vars.VideoSquare1KList = append(vars.VideoSquare1KList, currentVideoPath)
|
|
||||||
} else if width < 3072 {
|
|
||||||
vars.VideoSquare2KList = append(vars.VideoSquare2KList, currentVideoPath)
|
|
||||||
} else if width < 4096 {
|
|
||||||
vars.VideoSquare3KList = append(vars.VideoSquare3KList, currentVideoPath)
|
|
||||||
} else if width < 5120 {
|
|
||||||
vars.VideoSquare4KList = append(vars.VideoSquare4KList, currentVideoPath)
|
|
||||||
} else if width < 6144 {
|
|
||||||
vars.VideoSquare5KList = append(vars.VideoSquare5KList, currentVideoPath)
|
|
||||||
} else if width < 7168 {
|
|
||||||
vars.VideoSquare6KList = append(vars.VideoSquare6KList, currentVideoPath)
|
|
||||||
} else if width < 8192 {
|
|
||||||
vars.VideoSquare7KList = append(vars.VideoSquare7KList, currentVideoPath)
|
|
||||||
} else if width < 9216 {
|
|
||||||
vars.VideoSquare8KList = append(vars.VideoSquare8KList, currentVideoPath)
|
|
||||||
} else if width < 10240 {
|
|
||||||
vars.VideoSquare9KList = append(vars.VideoSquare9KList, currentVideoPath)
|
|
||||||
} else if width < 11264 {
|
|
||||||
vars.VideoSquare10KList = append(vars.VideoSquare10KList, currentVideoPath)
|
|
||||||
} else if width < 12288 {
|
|
||||||
vars.VideoSquare11KList = append(vars.VideoSquare11KList, currentVideoPath)
|
|
||||||
} else if width < 13312 {
|
|
||||||
vars.VideoSquare12KList = append(vars.VideoSquare12KList, currentVideoPath)
|
|
||||||
} else if width < 14336 {
|
|
||||||
vars.VideoSquare13KList = append(vars.VideoSquare13KList, currentVideoPath)
|
|
||||||
} else if width < 15360 {
|
|
||||||
vars.VideoSquare14KList = append(vars.VideoSquare14KList, currentVideoPath)
|
|
||||||
} else if width < 16384 {
|
|
||||||
vars.VideoSquare15KList = append(vars.VideoSquare15KList, currentVideoPath)
|
|
||||||
} else if width < 17408 {
|
|
||||||
vars.VideoSquare16KList = append(vars.VideoSquare16KList, currentVideoPath)
|
|
||||||
} else if width < 18432 {
|
|
||||||
vars.VideoSquare17KList = append(vars.VideoSquare17KList, currentVideoPath)
|
|
||||||
} else if width < 19456 {
|
|
||||||
vars.VideoSquare18KList = append(vars.VideoSquare18KList, currentVideoPath)
|
|
||||||
} else if width < 20480 {
|
|
||||||
vars.VideoSquare19KList = append(vars.VideoSquare19KList, currentVideoPath)
|
|
||||||
} else if width >= 20480 {
|
|
||||||
vars.VideoSquareHKList = append(vars.VideoSquareHKList, currentVideoPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"kenaito-media-picker/store"
|
|
||||||
"kenaito-media-picker/util"
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// saveMediaRecord 将媒体信息写入数据库;大文件优先用指纹(头尾采样)复用已入库的MD5,避免全量读取
|
|
||||||
func saveMediaRecord(filePath string, mediaType string, width int, height int, duration float64, codec string, format string, rawInfo string) {
|
|
||||||
st, err := os.Stat(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("=== 读取文件信息失败: %s, 错误: %v\n", filePath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
size := st.Size()
|
|
||||||
|
|
||||||
md5Str := ""
|
|
||||||
headMd5, tailMd5 := "", ""
|
|
||||||
if size > util.FingerprintThreshold {
|
|
||||||
if head, tail, ferr := util.CalcFileFingerprint(filePath, size); ferr == nil {
|
|
||||||
headMd5, tailMd5 = head, tail
|
|
||||||
if existing, ok := store.FindMediaByFingerprint(size, head, tail); ok && existing.FileMd5 != "" {
|
|
||||||
md5Str = existing.FileMd5 // 指纹命中,复用MD5,跳过全量读取
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if md5Str == "" {
|
|
||||||
if md5Str, err = util.CalcFileMd5(filePath); err != nil {
|
|
||||||
fmt.Printf("=== 计算MD5失败: %s, 错误: %v\n", filePath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &store.MediaInfo{
|
|
||||||
FileMd5: md5Str,
|
|
||||||
FilePath: filePath,
|
|
||||||
FileName: vars.GlobalFilePath2FileNameMap[filePath],
|
|
||||||
FileExt: vars.GlobalFilePath2FileExtMap[filePath],
|
|
||||||
FileSize: size,
|
|
||||||
MimeType: vars.GlobalFilePath2MimeInfoMap[filePath],
|
|
||||||
MediaType: mediaType,
|
|
||||||
Width: width,
|
|
||||||
Height: height,
|
|
||||||
Duration: duration,
|
|
||||||
Codec: codec,
|
|
||||||
Format: format,
|
|
||||||
RawInfo: rawInfo,
|
|
||||||
HeadMd5: headMd5,
|
|
||||||
TailMd5: tailMd5,
|
|
||||||
}
|
|
||||||
if err := store.SaveMedia(info); err != nil {
|
|
||||||
fmt.Printf("=== 保存媒体信息失败: %s, 错误: %v\n", filePath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"golang.org/x/image/webp"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadWebpImage 读取webp格式的图片信息
|
|
||||||
func ReadWebpImage(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
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Package classify 提供纯函数的分辨率归类规则,不依赖任何状态。
|
||||||
|
package classify
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Orientation 画面方向
|
||||||
|
type Orientation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OrientationSquare Orientation = iota
|
||||||
|
OrientationHorizontal
|
||||||
|
OrientationVertical
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrientationOf 由宽高判定画面方向
|
||||||
|
func OrientationOf(width, height int) Orientation {
|
||||||
|
switch {
|
||||||
|
case width > height:
|
||||||
|
return OrientationHorizontal
|
||||||
|
case width < height:
|
||||||
|
return OrientationVertical
|
||||||
|
default:
|
||||||
|
return OrientationSquare
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Orientation) String() string {
|
||||||
|
switch o {
|
||||||
|
case OrientationHorizontal:
|
||||||
|
return "横屏"
|
||||||
|
case OrientationVertical:
|
||||||
|
return "竖屏"
|
||||||
|
default:
|
||||||
|
return "等比"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageDir 返回图片归类子目录名,如 "图片_横屏_1080P" / "图片_GIF"
|
||||||
|
func ImageDir(ext string, width, height int) string {
|
||||||
|
if strings.EqualFold(ext, ".gif") {
|
||||||
|
return "图片_GIF"
|
||||||
|
}
|
||||||
|
return resolutionDir("图片", ext, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoDir 返回视频归类子目录名,如 "视频_竖屏_4k" / "视频_横屏_GIF"
|
||||||
|
func VideoDir(ext string, width, height int) string {
|
||||||
|
return resolutionDir("视频", ext, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolutionDir(kind, ext string, width, height int) string {
|
||||||
|
ori := OrientationOf(width, height).String()
|
||||||
|
if strings.EqualFold(ext, ".gif") {
|
||||||
|
return kind + "_" + ori + "_GIF"
|
||||||
|
}
|
||||||
|
if suffix := matchStandard(width, height); suffix != "" {
|
||||||
|
return kind + "_" + ori + "_" + suffix
|
||||||
|
}
|
||||||
|
overflow := "原图"
|
||||||
|
if kind == "视频" {
|
||||||
|
overflow = "原画质"
|
||||||
|
}
|
||||||
|
return kind + "_" + ori + "_" + levelName(longEdge(width, height), overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// longEdge 返回长边像素
|
||||||
|
func longEdge(w, h int) int {
|
||||||
|
if w > h {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package classify
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// standardResolution 标准分辨率规则:宽高精确匹配
|
||||||
|
type standardResolution struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
suffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardResolutions = []standardResolution{
|
||||||
|
{1280, 720, "720P"}, // 720p 标准高清
|
||||||
|
{1600, 900, "1D5KP"}, // 介于 HD 与 FHD 之间
|
||||||
|
{1920, 1080, "1080P"}, // 1080p 全高清
|
||||||
|
{2048, 1080, "2DKP"}, // 影院 2K
|
||||||
|
{2560, 1440, "2D5KP"}, // 2.5K
|
||||||
|
{2560, 1080, "UltraWide2KP"}, // 带鱼屏
|
||||||
|
{3440, 1440, "UltraWide3KP"}, // 带鱼屏
|
||||||
|
{5120, 2160, "UltraWide4KP"}, // 带鱼屏
|
||||||
|
{3840, 2160, "4KP"}, // 主流 4K
|
||||||
|
{4096, 2160, "4KHP"}, // DCI 4K
|
||||||
|
{7680, 4320, "8KP"}, // 8K
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非标分辨率分档参数
|
||||||
|
const (
|
||||||
|
bucketStep = 1024 // 每 1024px 一档
|
||||||
|
maxLevel = 19 // 1K ~ 19K 共 19 档(普通另算 1 档,合计 20 档)
|
||||||
|
)
|
||||||
|
|
||||||
|
// matchStandard 精确匹配标准分辨率,命中返回后缀,否则返回空串
|
||||||
|
func matchStandard(width, height int) string {
|
||||||
|
for _, r := range standardResolutions {
|
||||||
|
if r.width == width && r.height == height {
|
||||||
|
return r.suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// levelName 按长边分档:普通(<1024) / 1K~19K(每1024一档) / overflow(≥20480)
|
||||||
|
func levelName(longEdge int, overflow string) string {
|
||||||
|
if longEdge < bucketStep {
|
||||||
|
return "普通"
|
||||||
|
}
|
||||||
|
level := longEdge / bucketStep
|
||||||
|
if level > maxLevel {
|
||||||
|
return overflow
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dK", level)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Package digest 提供文件 MD5 与指纹采样计算,供媒体入库时复用。
|
||||||
|
package digest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FingerprintThreshold 文件大于该字节数才使用指纹复用,避免全量MD5
|
||||||
|
FingerprintThreshold = 1 << 20
|
||||||
|
fingerprintHeadSize = 256 << 10 // 文件头采样256KB
|
||||||
|
fingerprintTailSize = 256 << 10 // 文件尾采样256KB
|
||||||
|
)
|
||||||
|
|
||||||
|
// CalcFileMd5 计算文件MD5(分块读取,适合大文件)
|
||||||
|
func CalcFileMd5(filePath string) (string, error) {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
h := md5.New()
|
||||||
|
if _, err := io.Copy(h, file); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcFileFingerprint 计算文件头部与尾部采样MD5(只读取很少字节,用于快速识别相同文件)
|
||||||
|
func CalcFileFingerprint(filePath string, size int64) (headMd5, tailMd5 string, err error) {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
h := md5.New()
|
||||||
|
if _, err := io.CopyN(h, file, fingerprintHeadSize); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
headMd5 = hex.EncodeToString(h.Sum(nil))
|
||||||
|
h = md5.New()
|
||||||
|
if _, err := file.Seek(size-fingerprintTailSize, io.SeekStart); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(h, file, fingerprintTailSize); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
tailMd5 = hex.EncodeToString(h.Sum(nil))
|
||||||
|
return headMd5, tailMd5, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Package mover 负责文件的移动,跨文件系统时自动回退为复制后删除。
|
||||||
|
package mover
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mover 文件移动器
|
||||||
|
type Mover struct{}
|
||||||
|
|
||||||
|
// MoveToDir 将一批文件移动到目标目录(目标文件名使用 uuid 前缀防止同名覆盖)。
|
||||||
|
// 返回移动成功的 源路径 → 目标目录 映射,供记录去向使用。
|
||||||
|
func (m *Mover) MoveToDir(filePaths []string, targetDir string) map[string]string {
|
||||||
|
moved := make(map[string]string)
|
||||||
|
if len(filePaths) == 0 {
|
||||||
|
return moved
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||||
|
fmt.Printf("=== 创建目录失败: %s, 错误: %v\n", targetDir, err)
|
||||||
|
return moved
|
||||||
|
}
|
||||||
|
|
||||||
|
bar := goPrint.NewBar(100)
|
||||||
|
bar.SetNotice("=== 移动文件到目录:")
|
||||||
|
bar.SetGraph(">")
|
||||||
|
total := len(filePaths)
|
||||||
|
for i, src := range filePaths {
|
||||||
|
if moveOne(src, targetDir) {
|
||||||
|
moved[src] = targetDir
|
||||||
|
}
|
||||||
|
bar.PrintBar(percent(i+1, total))
|
||||||
|
}
|
||||||
|
bar.PrintEnd("=== Finish")
|
||||||
|
return moved
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveOne 移动单个文件;跨文件系统时 fallback 为 copy+delete
|
||||||
|
func moveOne(src, targetDir string) bool {
|
||||||
|
fileName := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + "_" + filepath.Base(src)
|
||||||
|
target := filepath.Join(targetDir, fileName)
|
||||||
|
if err := os.Rename(src, target); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err := copyFile(src, target); err != nil {
|
||||||
|
fmt.Printf("=== 复制文件失败, 源: %s, 目标: %s, 错误: %v\n", src, target, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := os.Remove(src); err != nil {
|
||||||
|
fmt.Printf("=== 删除源文件失败: %s, 错误: %v\n", src, err)
|
||||||
|
// 复制成功但删除失败,文件已存在于两处,视为移动成功
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile 逐块复制文件
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// percent 计算百分比(0~100)
|
||||||
|
func percent(part, total int) int {
|
||||||
|
if total <= 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return int(float64(part) / float64(total) * 100)
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
// Package organize 编排整理流程:探测 → 归类 → 收集 bucket → 一次性移动并记录去向。
|
||||||
|
package organize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"kenaito-media-picker/internal/classify"
|
||||||
|
"kenaito-media-picker/internal/digest"
|
||||||
|
"kenaito-media-picker/internal/mover"
|
||||||
|
"kenaito-media-picker/internal/probe"
|
||||||
|
"kenaito-media-picker/internal/scanner"
|
||||||
|
"kenaito-media-picker/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 特殊归类目录名
|
||||||
|
const (
|
||||||
|
dirImagePSD = "图片_PSD"
|
||||||
|
dirImageErr = "图片_读取异常"
|
||||||
|
dirImageIgnored = "图片_已忽略"
|
||||||
|
dirVideoErr = "视频_读取异常"
|
||||||
|
dirVideoIgnored = "视频_已忽略"
|
||||||
|
dirAudio = "音频"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Organizer 整理器:按目标目录收集文件,处理完成后统一建目录并移动
|
||||||
|
type Organizer struct {
|
||||||
|
pickerDir string
|
||||||
|
ffprobePath string
|
||||||
|
store *store.Store
|
||||||
|
mover *mover.Mover
|
||||||
|
buckets map[string][]string // 目标目录 → 文件路径
|
||||||
|
order []string // 目标目录的创建顺序(保证输出稳定)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New 创建整理器;store 可为 nil(数据库初始化失败时仅不记录信息)
|
||||||
|
func New(pickerDir, ffprobePath string, st *store.Store) *Organizer {
|
||||||
|
return &Organizer{
|
||||||
|
pickerDir: pickerDir,
|
||||||
|
ffprobePath: ffprobePath,
|
||||||
|
store: st,
|
||||||
|
mover: &mover.Mover{},
|
||||||
|
buckets: make(map[string][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 将文件归入某个子目录
|
||||||
|
func (o *Organizer) add(f scanner.File, subDir string) {
|
||||||
|
dir := filepath.Join(o.pickerDir, subDir)
|
||||||
|
if _, ok := o.buckets[dir]; !ok {
|
||||||
|
o.order = append(o.order, dir)
|
||||||
|
}
|
||||||
|
o.buckets[dir] = append(o.buckets[dir], f.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// record 将媒体信息写入数据库(大文件优先用指纹复用已入库的 MD5)
|
||||||
|
func (o *Organizer) record(f scanner.File, mediaType string, res *probe.ProbeResult) {
|
||||||
|
st, err := os.Stat(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("=== 读取文件信息失败: %s, 错误: %v\n", f.Path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size := st.Size()
|
||||||
|
|
||||||
|
md5Str := ""
|
||||||
|
headMd5, tailMd5 := "", ""
|
||||||
|
if size > digest.FingerprintThreshold {
|
||||||
|
if head, tail, ferr := digest.CalcFileFingerprint(f.Path, size); ferr == nil {
|
||||||
|
headMd5, tailMd5 = head, tail
|
||||||
|
if existing, ok := o.store.FindMediaByFingerprint(size, head, tail); ok && existing.FileMd5 != "" {
|
||||||
|
md5Str = existing.FileMd5 // 指纹命中,复用MD5,跳过全量读取
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if md5Str == "" {
|
||||||
|
if md5Str, err = digest.CalcFileMd5(f.Path); err != nil {
|
||||||
|
fmt.Printf("=== 计算MD5失败: %s, 错误: %v\n", f.Path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := res.VideoCodec
|
||||||
|
if mediaType == "audio" {
|
||||||
|
codec = res.AudioCodec
|
||||||
|
}
|
||||||
|
info := &store.MediaInfo{
|
||||||
|
FileMd5: md5Str,
|
||||||
|
FilePath: f.Path,
|
||||||
|
FileName: f.Name,
|
||||||
|
FileExt: f.Ext,
|
||||||
|
FileSize: size,
|
||||||
|
MimeType: f.Mime,
|
||||||
|
MediaType: mediaType,
|
||||||
|
Width: res.Width,
|
||||||
|
Height: res.Height,
|
||||||
|
Duration: res.Duration,
|
||||||
|
Codec: codec,
|
||||||
|
Format: res.Format,
|
||||||
|
RawInfo: res.RawJSON,
|
||||||
|
HeadMd5: headMd5,
|
||||||
|
TailMd5: tailMd5,
|
||||||
|
}
|
||||||
|
if err := o.store.SaveMedia(info); err != nil {
|
||||||
|
fmt.Printf("=== 保存媒体信息失败: %s, 错误: %v\n", f.Path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessImages 处理全部图片:探测尺寸并归类
|
||||||
|
func (o *Organizer) ProcessImages(images []scanner.File) {
|
||||||
|
success, fail, ignore := 0, 0, 0
|
||||||
|
for _, f := range images {
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(f.Ext, ".psd"):
|
||||||
|
o.add(f, dirImagePSD)
|
||||||
|
success++
|
||||||
|
continue
|
||||||
|
case !probe.IsSupportImage(f.Ext):
|
||||||
|
o.add(f, dirImageIgnored)
|
||||||
|
ignore++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res, err := probe.ProbeImage(f.Path, o.ffprobePath)
|
||||||
|
if err != nil {
|
||||||
|
o.add(f, dirImageErr)
|
||||||
|
fail++
|
||||||
|
fmt.Printf("=== 异常图片: %s \n", f.Path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
o.record(f, "image", res)
|
||||||
|
o.add(f, classify.ImageDir(f.Ext, res.Width, res.Height))
|
||||||
|
success++
|
||||||
|
}
|
||||||
|
fmt.Printf("=== 图片处理完毕,成功: %d, 读取异常: %d, 忽略: %d \n\n", success, fail, ignore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessMedia 处理媒体候选:ffprobe 判定视频/音频/不支持
|
||||||
|
func (o *Organizer) ProcessMedia(media []scanner.File) {
|
||||||
|
video, audio, fail, ignore := 0, 0, 0, 0
|
||||||
|
for _, f := range media {
|
||||||
|
res, err := probe.ProbeMediaFile(o.ffprobePath, f.Path)
|
||||||
|
if err != nil {
|
||||||
|
if strings.HasPrefix(f.Mime, "video/") {
|
||||||
|
fail++
|
||||||
|
o.add(f, dirVideoErr)
|
||||||
|
fmt.Printf("=== 异常视频: %s \n", f.Path)
|
||||||
|
} else {
|
||||||
|
ignore++
|
||||||
|
o.add(f, dirVideoIgnored)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case res.HasVideo:
|
||||||
|
video++
|
||||||
|
o.record(f, "video", res)
|
||||||
|
o.add(f, classify.VideoDir(f.Ext, res.Width, res.Height))
|
||||||
|
case res.HasAudio:
|
||||||
|
audio++
|
||||||
|
o.record(f, "audio", res)
|
||||||
|
o.add(f, dirAudio)
|
||||||
|
default:
|
||||||
|
ignore++
|
||||||
|
o.add(f, dirVideoIgnored)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("=== 媒体处理完毕,视频: %d, 音频: %d, 读取异常: %d, 忽略: %d \n\n", video, audio, fail, ignore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush 将所有收集到的文件移动到对应目录,并回填归类去向
|
||||||
|
func (o *Organizer) Flush() {
|
||||||
|
for _, dir := range o.order {
|
||||||
|
moved := o.mover.MoveToDir(o.buckets[dir], dir)
|
||||||
|
for src, dest := range moved {
|
||||||
|
if err := o.store.UpdateMediaDest(src, dest); err != nil {
|
||||||
|
fmt.Printf("=== 更新媒体去向失败: %s, 错误: %v\n", src, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// probeBmp 读取 BMP 文件头中的宽高;自顶向下 BMP 的高度为负值,取绝对值
|
||||||
|
func probeBmp(filePath string) (*ProbeResult, error) {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var fileHeader struct {
|
||||||
|
FileType [2]byte
|
||||||
|
FileSize uint32
|
||||||
|
Reserved1 uint16
|
||||||
|
Reserved2 uint16
|
||||||
|
DataOffset uint32
|
||||||
|
}
|
||||||
|
if err := binary.Read(f, binary.LittleEndian, &fileHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if string(fileHeader.FileType[:]) != "BM" {
|
||||||
|
return nil, fmt.Errorf("not a valid BMP file")
|
||||||
|
}
|
||||||
|
var infoHeader struct {
|
||||||
|
Size uint32
|
||||||
|
Width int32
|
||||||
|
Height int32
|
||||||
|
Planes uint16
|
||||||
|
BitsPerPixel uint16
|
||||||
|
Compression uint32
|
||||||
|
SizeImage uint32
|
||||||
|
XPelsPerMeter int32
|
||||||
|
YPelsPerMeter int32
|
||||||
|
}
|
||||||
|
if err := binary.Read(f, binary.LittleEndian, &infoHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ProbeResult{Width: abs(infoHeader.Width), Height: abs(infoHeader.Height)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func abs(v int32) int {
|
||||||
|
if v < 0 {
|
||||||
|
return int(-v)
|
||||||
|
}
|
||||||
|
return int(v)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FFprobeExeName 释放到当前目录的解码器文件名
|
||||||
|
const FFprobeExeName = "ffprobe.exe"
|
||||||
|
|
||||||
|
// FFprobePath 返回当前目录下的 ffprobe 可执行路径(Windows/Unix 均可直接执行)
|
||||||
|
func FFprobePath() string {
|
||||||
|
return "." + string(os.PathSeparator) + FFprobeExeName
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasEmbeddedFFprobe 当前平台是否内嵌了 ffprobe
|
||||||
|
func HasEmbeddedFFprobe() bool {
|
||||||
|
return len(embeddedFFprobe) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseFFprobe 释放内嵌的 ffprobe 到当前目录(按当前系统自动选择对应版本)
|
||||||
|
func ReleaseFFprobe() error {
|
||||||
|
if len(embeddedFFprobe) == 0 {
|
||||||
|
return fmt.Errorf("当前系统没有内嵌的ffprobe")
|
||||||
|
}
|
||||||
|
return os.WriteFile(FFprobeExeName, embeddedFFprobe, 0777)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFFprobe 删除当前目录下释放的 ffprobe
|
||||||
|
func RemoveFFprobe() {
|
||||||
|
_ = os.Remove(FFprobeExeName)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed bin/ffprobe_mac
|
||||||
|
var embeddedFFprobe []byte // macOS 版 ffprobe(未入库,见 .gitignore)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//go:build !darwin && !windows
|
||||||
|
|
||||||
|
package probe
|
||||||
|
|
||||||
|
// embeddedFFprobe 当前平台没有内嵌的 ffprobe
|
||||||
|
var embeddedFFprobe []byte
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed bin/ffprobe.exe
|
||||||
|
var embeddedFFprobe []byte // Windows 版 ffprobe(未入库,见 .gitignore)
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportImageTypes 支持的标准图片扩展名(大小写不敏感)
|
||||||
|
var supportImageTypes = []string{
|
||||||
|
".bmp",
|
||||||
|
".gif",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".jpe",
|
||||||
|
".png",
|
||||||
|
".webp",
|
||||||
|
".psb",
|
||||||
|
".jfif",
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSupportImage 判断扩展名是否属于支持的图片格式
|
||||||
|
func IsSupportImage(ext string) bool {
|
||||||
|
for _, t := range supportImageTypes {
|
||||||
|
if strings.EqualFold(t, ext) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProbeImage 统一探测图片尺寸:
|
||||||
|
// 1. 特殊格式(webp/bmp/psd/psb)走专用解码器
|
||||||
|
// 2. 其余支持格式走 Go 标准库 image.DecodeConfig
|
||||||
|
// 3. 标准库失败时用 ffprobe 兜底(ffmpeg 支持的图片格式更广,如 tiff/svg 等)
|
||||||
|
//
|
||||||
|
// ffprobePath 为空表示不启用 ffprobe 兜底;扩展名不属于支持格式时返回错误。
|
||||||
|
func ProbeImage(filePath, ffprobePath string) (*ProbeResult, error) {
|
||||||
|
ext := strings.ToLower(filepath.Ext(filePath))
|
||||||
|
switch ext {
|
||||||
|
case ".webp":
|
||||||
|
return probeWebp(filePath)
|
||||||
|
case ".bmp":
|
||||||
|
return probeBmp(filePath)
|
||||||
|
case ".psd", ".psb":
|
||||||
|
return probePsd(filePath)
|
||||||
|
}
|
||||||
|
if !IsSupportImage(ext) {
|
||||||
|
return nil, fmt.Errorf("不支持的图片格式: %s", ext)
|
||||||
|
}
|
||||||
|
if w, h, err := decodeConfig(filePath); err == nil {
|
||||||
|
return &ProbeResult{Width: w, Height: h}, nil
|
||||||
|
}
|
||||||
|
if ffprobePath != "" {
|
||||||
|
if res, err := ProbeMediaFile(ffprobePath, filePath); err == nil && res.HasVideo {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("无法读取图片尺寸: %s", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeConfig 用 Go 标准库读取图片宽高
|
||||||
|
func decodeConfig(filePath string) (int, int, error) {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
cfg, _, err := image.DecodeConfig(f)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return cfg.Width, cfg.Height, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// Package probe 负责媒体信息探测:图片尺寸读取、ffprobe 视频/音频解析与内嵌解码器释放。
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProbeResult 媒体探测结果(图片/视频/音频通用)
|
||||||
|
type ProbeResult struct {
|
||||||
|
HasVideo bool
|
||||||
|
HasAudio bool
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
Duration float64
|
||||||
|
VideoCodec string
|
||||||
|
AudioCodec string
|
||||||
|
Format string
|
||||||
|
RawJSON string
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamInfo struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
CodecType string `json:"codec_type"`
|
||||||
|
CodecName string `json:"codec_name"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatInfo struct {
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Format string `json:"format_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type probeOutput struct {
|
||||||
|
Streams []streamInfo `json:"streams"`
|
||||||
|
Format formatInfo `json:"format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProbeMediaFile 用 ffprobe 探测媒体文件,一次调用同时获取视频/音频流与时长
|
||||||
|
func ProbeMediaFile(ffprobePath, filePath string) (*ProbeResult, error) {
|
||||||
|
cmd := exec.Command(ffprobePath, "-v", "error",
|
||||||
|
"-show_entries", "stream=index,codec_type,codec_name,width,height:format=duration,format_name",
|
||||||
|
"-of", "json", filePath)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ffprobe failed: %w", err)
|
||||||
|
}
|
||||||
|
var out probeOutput
|
||||||
|
if err := json.Unmarshal(output, &out); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse ffprobe output: %w", err)
|
||||||
|
}
|
||||||
|
res := &ProbeResult{
|
||||||
|
Format: out.Format.Format,
|
||||||
|
RawJSON: string(output),
|
||||||
|
}
|
||||||
|
if d, err := strconv.ParseFloat(out.Format.Duration, 64); err == nil {
|
||||||
|
res.Duration = d
|
||||||
|
}
|
||||||
|
for _, s := range out.Streams {
|
||||||
|
switch s.CodecType {
|
||||||
|
case "video":
|
||||||
|
res.HasVideo = true
|
||||||
|
if res.VideoCodec == "" {
|
||||||
|
res.VideoCodec = s.CodecName
|
||||||
|
}
|
||||||
|
if res.Width == 0 && s.Width > 0 && s.Height > 0 {
|
||||||
|
res.Width, res.Height = s.Width, s.Height
|
||||||
|
}
|
||||||
|
case "audio":
|
||||||
|
res.HasAudio = true
|
||||||
|
if res.AudioCodec == "" {
|
||||||
|
res.AudioCodec = s.CodecName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// probePsd 读取 PSD/PSB 格式的宽高(两者共享相同的 8BPS 文件头)
|
||||||
|
func probePsd(filePath string) (*ProbeResult, error) {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var header [26]byte
|
||||||
|
if _, err := io.ReadFull(f, header[:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if string(header[0:4]) != "8BPS" {
|
||||||
|
return nil, fmt.Errorf("not a valid PSD/PSB file")
|
||||||
|
}
|
||||||
|
version := binary.BigEndian.Uint16(header[4:6])
|
||||||
|
if version != 1 && version != 2 {
|
||||||
|
return nil, fmt.Errorf("unsupported PSD/PSB version: %d", version)
|
||||||
|
}
|
||||||
|
height := int(binary.BigEndian.Uint32(header[14:18]))
|
||||||
|
width := int(binary.BigEndian.Uint32(header[18:22]))
|
||||||
|
return &ProbeResult{Width: width, Height: height}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/image/webp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// probeWebp 读取 WebP 图片宽高(只解码配置,不加载完整像素数据)
|
||||||
|
func probeWebp(filePath string) (*ProbeResult, error) {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
cfg, err := webp.DecodeConfig(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode webp: %w", err)
|
||||||
|
}
|
||||||
|
return &ProbeResult{Width: cfg.Width, Height: cfg.Height}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// Package scanner 负责递归扫描文件、读取 MIME 并按类型过滤。
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"kenaito-media-picker/internal/probe"
|
||||||
|
|
||||||
|
"github.com/gabriel-vasile/mimetype"
|
||||||
|
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File 扫描到的文件信息
|
||||||
|
type File struct {
|
||||||
|
Path string // 完整路径
|
||||||
|
Name string // 文件名(含扩展名)
|
||||||
|
Ext string // 扩展名(含点)
|
||||||
|
Mime string // MIME 类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result 过滤结果
|
||||||
|
type Result struct {
|
||||||
|
Files []File // 全部文件
|
||||||
|
Images []File // 图片文件
|
||||||
|
Media []File // 待 ffprobe 确认的媒体候选
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options 扫描选项
|
||||||
|
type Options struct {
|
||||||
|
RootDir string // 扫描根目录
|
||||||
|
SkipDir string // 白名单输出目录,整目录跳过
|
||||||
|
SkipNames []string // 按文件名跳过(数据库、ffprobe 等)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan 递归扫描目录并读取每个文件的 MIME 信息
|
||||||
|
func Scan(opts Options) (*Result, error) {
|
||||||
|
res := &Result{}
|
||||||
|
err := filepath.Walk(opts.RootDir, func(currentPath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
if opts.SkipDir != "" && currentPath == opts.SkipDir {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, n := range opts.SkipNames {
|
||||||
|
if info.Name() == n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Files = append(res.Files, File{
|
||||||
|
Path: currentPath,
|
||||||
|
Name: info.Name(),
|
||||||
|
Ext: path.Ext(currentPath),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(res.Files)
|
||||||
|
fmt.Printf("=== 文件总数: %d \n", total)
|
||||||
|
bar := goPrint.NewBar(100)
|
||||||
|
bar.SetNotice("=== 扫描文件Mime:")
|
||||||
|
bar.SetGraph(">")
|
||||||
|
bar.SetNoticeColor(goPrint.FontColor.Red)
|
||||||
|
for i := range res.Files {
|
||||||
|
res.Files[i].Mime = detectMime(res.Files[i].Path)
|
||||||
|
bar.PrintBar(percent(i+1, total))
|
||||||
|
}
|
||||||
|
bar.PrintEnd("=== Finish")
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter 将文件分为图片与媒体候选
|
||||||
|
func (r *Result) Filter() {
|
||||||
|
for _, f := range r.Files {
|
||||||
|
if strings.Contains(f.Mime, "image/") || probe.IsSupportImage(f.Ext) || strings.EqualFold(f.Ext, ".psd") {
|
||||||
|
r.Images = append(r.Images, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isPotentialMedia(f.Mime) {
|
||||||
|
r.Media = append(r.Media, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 明显的非媒体文件(代码/文档/压缩包等)直接不处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectMime 读取文件的 MIME 类型
|
||||||
|
func detectMime(filePath string) string {
|
||||||
|
mt, err := mimetype.DetectFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return mt.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPotentialMedia 判断是否可能是媒体文件(交由 ffprobe 进一步确认)
|
||||||
|
func isPotentialMedia(fileMime string) bool {
|
||||||
|
if fileMime == "" || fileMime == "application/octet-stream" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(fileMime, "text/") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(fileMime, "video/") || strings.HasPrefix(fileMime, "audio/") || strings.HasPrefix(fileMime, "image/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch fileMime {
|
||||||
|
case "application/zip", "application/gzip", "application/x-gzip", "application/pdf",
|
||||||
|
"application/json", "application/xml", "application/x-httpd-php", "application/x-sh",
|
||||||
|
"application/javascript", "application/x-7z-compressed", "application/x-rar-compressed",
|
||||||
|
"application/x-tar", "application/x-bzip2", "application/x-archive", "application/vnd.sqlite3",
|
||||||
|
"application/vnd.microsoft.portable-executable", "application/x-dosexec", "application/x-executable",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
"application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// percent 计算百分比(0~100),total 为 0 时返回 100
|
||||||
|
func percent(part, total int) int {
|
||||||
|
if total <= 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return int(float64(part) / float64(total) * 100)
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Package store 提供媒体信息的 SQLite 持久化(xorm + 纯 Go 驱动,免 CGO)。
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -41,56 +42,62 @@ type MediaInfo struct {
|
|||||||
UpdatedAt time.Time `xorm:"updated 'updated_at'"` // 最近更新时间
|
UpdatedAt time.Time `xorm:"updated 'updated_at'"` // 最近更新时间
|
||||||
}
|
}
|
||||||
|
|
||||||
var engine *xorm.Engine
|
// Store 封装 SQLite 存取,按实例持有连接,不再依赖全局状态
|
||||||
|
type Store struct {
|
||||||
|
engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
// Init 初始化数据库引擎并同步表结构
|
// Open 打开(或创建)数据库并同步表结构
|
||||||
func Init(dbPath string) error {
|
func Open(dbPath string) (*Store, error) {
|
||||||
e, err := xorm.NewEngine("sqlite3", dbPath)
|
e, err := xorm.NewEngine("sqlite3", dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
e.ShowSQL(false)
|
e.ShowSQL(false)
|
||||||
e.SetMaxOpenConns(1) // sqlite同一时刻只允许一个写连接
|
e.SetMaxOpenConns(1) // sqlite同一时刻只允许一个写连接
|
||||||
engine = e
|
if err := e.Sync(new(MediaInfo)); err != nil {
|
||||||
return engine.Sync(new(MediaInfo))
|
_ = e.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Store{engine: e}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close 关闭数据库连接
|
// Close 关闭数据库连接
|
||||||
func Close() error {
|
func (s *Store) Close() error {
|
||||||
if engine != nil {
|
if s == nil || s.engine == nil {
|
||||||
err := engine.Close()
|
|
||||||
engine = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
err := s.engine.Close()
|
||||||
|
s.engine = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// SaveMedia 按MD5去重保存/更新媒体信息
|
// SaveMedia 按MD5去重保存/更新媒体信息
|
||||||
func SaveMedia(info *MediaInfo) error {
|
func (s *Store) SaveMedia(info *MediaInfo) error {
|
||||||
if engine == nil {
|
if s == nil || s.engine == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
existing := new(MediaInfo)
|
existing := new(MediaInfo)
|
||||||
has, err := engine.Where("file_md5 = ?", info.FileMd5).Get(existing)
|
has, err := s.engine.Where("file_md5 = ?", info.FileMd5).Get(existing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if has {
|
if has {
|
||||||
info.Id = existing.Id
|
info.Id = existing.Id
|
||||||
_, err = engine.ID(existing.Id).Update(info)
|
_, err = s.engine.ID(existing.Id).Update(info)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = engine.Insert(info)
|
_, err = s.engine.Insert(info)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindMediaByFingerprint 按 (文件大小 + 头256KB + 尾256KB) 查找已入库记录,避免对大文件重复计算全量MD5
|
// FindMediaByFingerprint 按 (文件大小 + 头256KB + 尾256KB) 查找已入库记录,避免对大文件重复计算全量MD5
|
||||||
func FindMediaByFingerprint(size int64, headMd5 string, tailMd5 string) (*MediaInfo, bool) {
|
func (s *Store) FindMediaByFingerprint(size int64, headMd5, tailMd5 string) (*MediaInfo, bool) {
|
||||||
if engine == nil || headMd5 == "" || tailMd5 == "" {
|
if s == nil || s.engine == nil || headMd5 == "" || tailMd5 == "" {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
info := new(MediaInfo)
|
info := new(MediaInfo)
|
||||||
has, err := engine.Where("file_size = ? AND head_md5 = ? AND tail_md5 = ?", size, headMd5, tailMd5).Get(info)
|
has, err := s.engine.Where("file_size = ? AND head_md5 = ? AND tail_md5 = ?", size, headMd5, tailMd5).Get(info)
|
||||||
if err != nil || !has {
|
if err != nil || !has {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -98,11 +105,11 @@ func FindMediaByFingerprint(size int64, headMd5 string, tailMd5 string) (*MediaI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMediaDest 按源路径回填文件的归类目录与去向
|
// UpdateMediaDest 按源路径回填文件的归类目录与去向
|
||||||
func UpdateMediaDest(filePath string, destDir string) error {
|
func (s *Store) UpdateMediaDest(filePath, destDir string) error {
|
||||||
if engine == nil {
|
if s == nil || s.engine == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := engine.Where("file_path = ?", filePath).Update(&MediaInfo{
|
_, err := s.engine.Where("file_path = ?", filePath).Update(&MediaInfo{
|
||||||
Category: filepath.Base(destDir),
|
Category: filepath.Base(destDir),
|
||||||
DestDir: destDir,
|
DestDir: destDir,
|
||||||
})
|
})
|
||||||
@@ -1,58 +1,70 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "image/gif" // 导入gif支持
|
|
||||||
_ "image/jpeg" // 导入jpeg支持
|
|
||||||
_ "image/png" // 导入png支持
|
|
||||||
"kenaito-media-picker/core"
|
|
||||||
"kenaito-media-picker/store"
|
|
||||||
"kenaito-media-picker/util"
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"kenaito-media-picker/internal/organize"
|
||||||
|
"kenaito-media-picker/internal/probe"
|
||||||
|
"kenaito-media-picker/internal/scanner"
|
||||||
|
"kenaito-media-picker/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("=== 警告:该程序会改变目录结构,将在5秒后开始执行")
|
fmt.Println("=== 警告:该程序会改变目录结构,将在5秒后开始执行")
|
||||||
time.Sleep(time.Second * 5)
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
rootDir, err := os.Getwd()
|
rootDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("=== 获取当前路径异常", err)
|
fmt.Println("=== 获取当前路径异常", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pickerDir := filepath.Join(rootDir, filepath.Base(rootDir)+"-picker")
|
pickerDir := filepath.Join(rootDir, filepath.Base(rootDir)+"-picker")
|
||||||
vars.GlobalPickerDir = pickerDir
|
|
||||||
fmt.Printf("=== 整理输出目录(白名单): %s \n", pickerDir)
|
fmt.Printf("=== 整理输出目录(白名单): %s \n", pickerDir)
|
||||||
scanner := core.FileScanner{}
|
|
||||||
scanner.DoScan(rootDir)
|
// 1. 扫描并过滤(跳过输出目录、数据库与 ffprobe 相关文件)
|
||||||
scanner.DoFilter()
|
res, err := scanner.Scan(scanner.Options{
|
||||||
// 初始化媒体信息数据库(失败不影响整理,仅无法记录)
|
RootDir: rootDir,
|
||||||
if err := store.Init(filepath.Join(rootDir, store.DBFileName)); err != nil {
|
SkipDir: pickerDir,
|
||||||
|
SkipNames: []string{store.DBFileName, probe.FFprobeExeName, "ffprobe_mac"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("=== 扫描失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Filter()
|
||||||
|
|
||||||
|
// 2. 初始化媒体信息数据库(失败不影响整理,仅无法记录)
|
||||||
|
st, err := store.Open(filepath.Join(rootDir, store.DBFileName))
|
||||||
|
if err != nil {
|
||||||
fmt.Println("=== 初始化数据库失败(继续整理, 不记录媒体信息):", err)
|
fmt.Println("=== 初始化数据库失败(继续整理, 不记录媒体信息):", err)
|
||||||
|
st = nil
|
||||||
}
|
}
|
||||||
defer store.Close()
|
if st != nil {
|
||||||
// 释放内嵌ffprobe(按当前系统);失败只跳过媒体整理,图片照常处理
|
defer st.Close()
|
||||||
ffprobeReady := true
|
}
|
||||||
if len(vars.GlobalImagePathList) > 0 || len(vars.GlobalMediaPathList) > 0 {
|
|
||||||
if err := util.ReleaseFFprobe(); err != nil {
|
// 3. 释放内嵌 ffprobe(按当前系统);失败只跳过媒体整理,图片照常处理
|
||||||
|
ffprobePath := ""
|
||||||
|
if (len(res.Images) > 0 || len(res.Media) > 0) && probe.HasEmbeddedFFprobe() {
|
||||||
|
if err := probe.ReleaseFFprobe(); err != nil {
|
||||||
fmt.Println("=== 释放解码器失败, 视频/音频整理将跳过(图片照常处理):", err)
|
fmt.Println("=== 释放解码器失败, 视频/音频整理将跳过(图片照常处理):", err)
|
||||||
ffprobeReady = false
|
} else {
|
||||||
|
ffprobePath = probe.FFprobePath()
|
||||||
|
defer probe.RemoveFFprobe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer util.RemoveFFprobe()
|
|
||||||
if len(vars.GlobalImagePathList) > 0 {
|
// 4. 整理
|
||||||
core.DoHandleImage(pickerDir)
|
org := organize.New(pickerDir, ffprobePath, st)
|
||||||
}
|
if len(res.Images) > 0 {
|
||||||
if len(vars.GlobalMediaPathList) > 0 && ffprobeReady {
|
org.ProcessImages(res.Images)
|
||||||
core.DoHandleMedia(pickerDir)
|
|
||||||
}
|
|
||||||
// 所有移动完成后,回填文件的归类目录与去向
|
|
||||||
for src, dest := range vars.GlobalPath2DestDirMap {
|
|
||||||
if err := store.UpdateMediaDest(src, dest); err != nil {
|
|
||||||
fmt.Printf("=== 更新媒体去向失败: %s, 错误: %v\n", src, err)
|
|
||||||
}
|
}
|
||||||
|
if len(res.Media) > 0 && ffprobePath != "" {
|
||||||
|
org.ProcessMedia(res.Media)
|
||||||
}
|
}
|
||||||
|
org.Flush()
|
||||||
|
fmt.Println("=== 整理完成")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// String2int 字符串转int
|
|
||||||
func String2int(str string) int {
|
|
||||||
intValue, err := strconv.Atoi(str)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return intValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalcPercentage 计算percentage相对于total的百分比
|
|
||||||
func CalcPercentage(percentage int, total int) int {
|
|
||||||
return int(float64(percentage) / float64(total) * 100)
|
|
||||||
}
|
|
||||||
-162
@@ -1,162 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/gabriel-vasile/mimetype"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateDir 创建目录(含递归创建父目录)
|
|
||||||
func CreateDir(dirPath string) bool {
|
|
||||||
err := os.MkdirAll(dirPath, 0755)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("=== 创建目录失败: %s, 错误: %v\n", dirPath, err)
|
|
||||||
}
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileDirectory 获取文件所在文件夹
|
|
||||||
func GetFileDirectory(filePath string) string {
|
|
||||||
directoryPath, _ := filepath.Split(filePath)
|
|
||||||
return directoryPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFileMimeInfo 获取文件mime信息,失败时返回nil
|
|
||||||
func ReadFileMimeInfo(filepath string) *mimetype.MIME {
|
|
||||||
mt, err := mimetype.DetectFile(filepath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("=== 读取Mime信息失败: %s, 错误: %v\n", filepath, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return mt
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalcFileMd5 计算文件MD5(分块读取,适合大文件)
|
|
||||||
func CalcFileMd5(filePath string) (string, error) {
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
h := md5.New()
|
|
||||||
if _, err := io.Copy(h, file); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(h.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5指纹加速相关常量
|
|
||||||
const (
|
|
||||||
FingerprintThreshold = 1 << 20 // 文件大于1MB才使用指纹复用,避免全量MD5
|
|
||||||
fingerprintHeadSize = 256 << 10 // 文件头采样256KB
|
|
||||||
fingerprintTailSize = 256 << 10 // 文件尾采样256KB
|
|
||||||
)
|
|
||||||
|
|
||||||
// CalcFileFingerprint 计算文件的头部与尾部采样MD5(只读取很少字节,用于快速识别相同文件)
|
|
||||||
func CalcFileFingerprint(filePath string, size int64) (headMd5 string, tailMd5 string, err error) {
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
h := md5.New()
|
|
||||||
if _, err := io.CopyN(h, file, fingerprintHeadSize); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
headMd5 = hex.EncodeToString(h.Sum(nil))
|
|
||||||
h = md5.New()
|
|
||||||
if _, err := file.Seek(size-fingerprintTailSize, io.SeekStart); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
if _, err := io.CopyN(h, file, fingerprintTailSize); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
tailMd5 = hex.EncodeToString(h.Sum(nil))
|
|
||||||
return headMd5, tailMd5, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//// 移动文件到根目录
|
|
||||||
//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")
|
|
||||||
//}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var supportImageTypes = []string{
|
|
||||||
".bmp",
|
|
||||||
".gif",
|
|
||||||
".jpg",
|
|
||||||
".jpeg",
|
|
||||||
".jpe",
|
|
||||||
".png",
|
|
||||||
".webp",
|
|
||||||
".psb",
|
|
||||||
".jfif",
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSupportImage 判断是否属于支持的图片文件
|
|
||||||
func IsSupportImage(imageType string) bool {
|
|
||||||
for _, supportImageType := range supportImageTypes {
|
|
||||||
if strings.EqualFold(supportImageType, strings.ToLower(imageType)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadImageInfo 读取一般图片文件信息
|
|
||||||
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
|
|
||||||
}
|
|
||||||
-221
@@ -1,221 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"kenaito-media-picker/vars"
|
|
||||||
|
|
||||||
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FFprobeExeName 内嵌解码器文件名
|
|
||||||
const FFprobeExeName = "ffprobe.exe"
|
|
||||||
|
|
||||||
// FFprobeExePath 返回带当前目录前缀的可执行路径(Windows/Unix均可直接执行)
|
|
||||||
func FFprobeExePath() string {
|
|
||||||
return "." + string(os.PathSeparator) + FFprobeExeName
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseFFprobe 释放内嵌的ffprobe到当前目录(按当前系统自动选择对应版本)
|
|
||||||
func ReleaseFFprobe() error {
|
|
||||||
if len(vars.VideoDecodeTool) == 0 {
|
|
||||||
return fmt.Errorf("当前系统没有内嵌的ffprobe")
|
|
||||||
}
|
|
||||||
return WriteByteArraysToFile(vars.VideoDecodeTool, FFprobeExeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveFFprobe 删除当前目录下的ffprobe
|
|
||||||
func RemoveFFprobe() {
|
|
||||||
if CheckFileIsExist(FFprobeExeName) {
|
|
||||||
_ = os.Remove(FFprobeExeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamInfo ffprobe流信息
|
|
||||||
type StreamInfo struct {
|
|
||||||
Index int `json:"index"`
|
|
||||||
CodecType string `json:"codec_type"`
|
|
||||||
CodecName string `json:"codec_name"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Height int `json:"height"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInfo ffprobe容器信息
|
|
||||||
type FormatInfo struct {
|
|
||||||
Duration string `json:"duration"`
|
|
||||||
Format string `json:"format_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProbeResult ffprobe探测结果
|
|
||||||
type ProbeResult struct {
|
|
||||||
Streams []StreamInfo `json:"streams"`
|
|
||||||
Format FormatInfo `json:"format"`
|
|
||||||
raw string
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoResolution 返回第一个有效的视频分辨率
|
|
||||||
func (p *ProbeResult) VideoResolution() (int, int) {
|
|
||||||
for _, s := range p.Streams {
|
|
||||||
if s.CodecType == "video" && s.Width > 0 && s.Height > 0 {
|
|
||||||
return s.Width, s.Height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasAudio 是否包含音频流
|
|
||||||
func (p *ProbeResult) HasAudio() bool {
|
|
||||||
for _, s := range p.Streams {
|
|
||||||
if s.CodecType == "audio" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoCodec 第一个视频流的编码名称
|
|
||||||
func (p *ProbeResult) VideoCodec() string {
|
|
||||||
for _, s := range p.Streams {
|
|
||||||
if s.CodecType == "video" {
|
|
||||||
return s.CodecName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// AudioCodec 第一个音频流的编码名称
|
|
||||||
func (p *ProbeResult) AudioCodec() string {
|
|
||||||
for _, s := range p.Streams {
|
|
||||||
if s.CodecType == "audio" {
|
|
||||||
return s.CodecName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatName 容器格式名称
|
|
||||||
func (p *ProbeResult) FormatName() string {
|
|
||||||
return p.Format.Format
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawJSON 原始ffprobe JSON输出
|
|
||||||
func (p *ProbeResult) RawJSON() string {
|
|
||||||
return p.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
// DurationSeconds 容器时长(秒),无法解析时返回0
|
|
||||||
func (p *ProbeResult) DurationSeconds() float64 {
|
|
||||||
d, err := strconv.ParseFloat(p.Format.Duration, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProbeMediaFile 用ffprobe探测媒体文件,一次调用同时获取视频/音频流与时长
|
|
||||||
func ProbeMediaFile(ffprobeExecPath string, filePath string) (*ProbeResult, error) {
|
|
||||||
cmd := exec.Command(ffprobeExecPath, "-v", "error",
|
|
||||||
"-show_entries", "stream=index,codec_type,codec_name,width,height:format=duration,format_name",
|
|
||||||
"-of", "json", filePath)
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("ffprobe failed: %w", err)
|
|
||||||
}
|
|
||||||
var result ProbeResult
|
|
||||||
if err := json.Unmarshal(output, &result); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse ffprobe output: %w", err)
|
|
||||||
}
|
|
||||||
result.raw = string(output)
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPotentialMedia 判断是否可能是媒体文件(交由ffprobe进一步确认)
|
|
||||||
func IsPotentialMedia(fileMime string) bool {
|
|
||||||
if fileMime == "" || fileMime == "application/octet-stream" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(fileMime, "text/") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(fileMime, "video/") || strings.HasPrefix(fileMime, "audio/") || strings.HasPrefix(fileMime, "image/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch fileMime {
|
|
||||||
case "application/zip", "application/gzip", "application/x-gzip", "application/pdf",
|
|
||||||
"application/json", "application/xml", "application/x-httpd-php", "application/x-sh",
|
|
||||||
"application/javascript", "application/x-7z-compressed", "application/x-rar-compressed",
|
|
||||||
"application/x-tar", "application/x-bzip2", "application/x-archive", "application/vnd.sqlite3",
|
|
||||||
"application/vnd.microsoft.portable-executable", "application/x-dosexec", "application/x-executable",
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
"application/msword", "application/vnd.ms-excel", "application/vnd.ms-powerpoint":
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoMoveFileToDir 批量移动文件到目录
|
|
||||||
func DoMoveFileToDir(filePathList []string, videoDirPath string) {
|
|
||||||
total := len(filePathList)
|
|
||||||
var count = 0
|
|
||||||
bar := goPrint.NewBar(100)
|
|
||||||
bar.SetNotice("=== 移动文件到目录:")
|
|
||||||
bar.SetGraph(">")
|
|
||||||
pathSeparator := string(os.PathSeparator)
|
|
||||||
for _, videoFilePath := range filePathList {
|
|
||||||
moveFileToDir(videoFilePath, videoDirPath+pathSeparator)
|
|
||||||
vars.GlobalPath2DestDirMap[videoFilePath] = videoDirPath // 记录去向
|
|
||||||
count = count + 1
|
|
||||||
bar.PrintBar(CalcPercentage(count, total))
|
|
||||||
}
|
|
||||||
bar.PrintEnd("=== Finish")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动文件到目录,跨文件系统时自动 fallback 为 copy+delete,目标文件名使用 uuid+原文件名防止同名覆盖
|
|
||||||
func moveFileToDir(sourceFilePath string, targetDirectory string) bool {
|
|
||||||
fileName := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + "_" + filepath.Base(sourceFilePath)
|
|
||||||
targetFilePath := targetDirectory + fileName
|
|
||||||
err := os.Rename(sourceFilePath, targetFilePath)
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// 跨文件系统等场景:fallback copy + delete
|
|
||||||
if err := copyFile(sourceFilePath, targetFilePath); err != nil {
|
|
||||||
fmt.Printf("=== 复制文件失败, 源: %s, 目标: %s, 错误: %v\n", sourceFilePath, targetFilePath, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if err := os.Remove(sourceFilePath); err != nil {
|
|
||||||
fmt.Printf("=== 删除源文件失败: %s, 错误: %v\n", sourceFilePath, err)
|
|
||||||
// 复制成功但删除失败,文件已存在于两处,返回 true 表示目标已就位
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyFile 逐块复制文件
|
|
||||||
func copyFile(src, dst string) error {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(out, in); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return out.Close()
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package vars
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
//go:embed bin/ffprobe_mac
|
|
||||||
var VideoDecodeTool []byte // 需在 vars/bin/ 下放置 macOS 版 ffprobe(未入库,见 .gitignore)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
//go:build !darwin && !windows
|
|
||||||
|
|
||||||
package vars
|
|
||||||
|
|
||||||
// VideoDecodeTool 当前平台没有内嵌的ffprobe
|
|
||||||
var VideoDecodeTool []byte
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
|
|
||||||
package vars
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
//go:embed bin/ffprobe.exe
|
|
||||||
var VideoDecodeTool []byte // 需在 vars/bin/ 下放置 Windows 版 ffprobe(未入库,见 .gitignore)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
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 GlobalImagePathList []string // 所有图片文件的路径
|
|
||||||
var GlobalMediaPathList []string // 待ffprobe确认的媒体候选文件
|
|
||||||
var GlobalAudioPathList []string // 已确认的音频文件路径
|
|
||||||
var GlobalPickerDir string // 已整理文件的输出目录(白名单,扫描时跳过)
|
|
||||||
var GlobalPath2DestDirMap = make(map[string]string) // 文件路径 → 移动到的目录(去向)
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package vars
|
|
||||||
|
|
||||||
var ImageIgnorePathList []string // 忽略的文件路径
|
|
||||||
var ImageReadErrorPathList []string // 读取信息异常的路径
|
|
||||||
var ImagePath2WidthHeightMap = make(map[string]string) // 图片路径和宽高比
|
|
||||||
var ImageGifList []string
|
|
||||||
var ImagePsdList []string
|
|
||||||
var ImageHorizontalNormalList []string
|
|
||||||
var ImageHorizontal1KList []string
|
|
||||||
var ImageHorizontal2KList []string
|
|
||||||
var ImageHorizontal3KList []string
|
|
||||||
var ImageHorizontal4KList []string
|
|
||||||
var ImageHorizontal5KList []string
|
|
||||||
var ImageHorizontal6KList []string
|
|
||||||
var ImageHorizontal7KList []string
|
|
||||||
var ImageHorizontal8KList []string
|
|
||||||
var ImageHorizontal9KList []string
|
|
||||||
var ImageHorizontal10KList []string
|
|
||||||
var ImageHorizontal11KList []string
|
|
||||||
var ImageHorizontal12KList []string
|
|
||||||
var ImageHorizontal13KList []string
|
|
||||||
var ImageHorizontal14KList []string
|
|
||||||
var ImageHorizontal15KList []string
|
|
||||||
var ImageHorizontal16KList []string
|
|
||||||
var ImageHorizontal17KList []string
|
|
||||||
var ImageHorizontal18KList []string
|
|
||||||
var ImageHorizontal19KList []string
|
|
||||||
var ImageHorizontalHKList []string
|
|
||||||
var ImageHorizontalStandard720PList []string
|
|
||||||
var ImageHorizontalStandard1D5KList []string
|
|
||||||
var ImageHorizontalStandard1080PList []string
|
|
||||||
var ImageHorizontalStandard2DKList []string
|
|
||||||
var ImageHorizontalStandard2D5KList []string
|
|
||||||
var ImageHorizontalStandardUltraWide2List []string
|
|
||||||
var ImageHorizontalStandardUltraWide3List []string
|
|
||||||
var ImageHorizontalStandardUltraWide4List []string
|
|
||||||
var ImageHorizontalStandard4KList []string
|
|
||||||
var ImageHorizontalStandard4KHList []string
|
|
||||||
var ImageHorizontalStandard8KList []string
|
|
||||||
var ImageVerticalNormalList []string
|
|
||||||
var ImageVertical1KList []string
|
|
||||||
var ImageVertical2KList []string
|
|
||||||
var ImageVertical3KList []string
|
|
||||||
var ImageVertical4KList []string
|
|
||||||
var ImageVertical5KList []string
|
|
||||||
var ImageVertical6KList []string
|
|
||||||
var ImageVertical7KList []string
|
|
||||||
var ImageVertical8KList []string
|
|
||||||
var ImageVertical9KList []string
|
|
||||||
var ImageVertical10KList []string
|
|
||||||
var ImageVertical11KList []string
|
|
||||||
var ImageVertical12KList []string
|
|
||||||
var ImageVertical13KList []string
|
|
||||||
var ImageVertical14KList []string
|
|
||||||
var ImageVertical15KList []string
|
|
||||||
var ImageVertical16KList []string
|
|
||||||
var ImageVertical17KList []string
|
|
||||||
var ImageVertical18KList []string
|
|
||||||
var ImageVertical19KList []string
|
|
||||||
var ImageVerticalHKList []string
|
|
||||||
var ImageSquareNormalList []string
|
|
||||||
var ImageSquare1KList []string
|
|
||||||
var ImageSquare2KList []string
|
|
||||||
var ImageSquare3KList []string
|
|
||||||
var ImageSquare4KList []string
|
|
||||||
var ImageSquare5KList []string
|
|
||||||
var ImageSquare6KList []string
|
|
||||||
var ImageSquare7KList []string
|
|
||||||
var ImageSquare8KList []string
|
|
||||||
var ImageSquare9KList []string
|
|
||||||
var ImageSquare10KList []string
|
|
||||||
var ImageSquare11KList []string
|
|
||||||
var ImageSquare12KList []string
|
|
||||||
var ImageSquare13KList []string
|
|
||||||
var ImageSquare14KList []string
|
|
||||||
var ImageSquare15KList []string
|
|
||||||
var ImageSquare16KList []string
|
|
||||||
var ImageSquare17KList []string
|
|
||||||
var ImageSquare18KList []string
|
|
||||||
var ImageSquare19KList []string
|
|
||||||
var ImageSquareHKList []string
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package vars
|
|
||||||
|
|
||||||
var VideoIgnorePathList []string // 忽略的文件路径
|
|
||||||
var VideoReadErrorPathList []string // 读取信息异常的路径
|
|
||||||
var VideoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比
|
|
||||||
var VideoHorizontalNormalList []string
|
|
||||||
var VideoHorizontalGifList []string
|
|
||||||
var VideoHorizontal1KList []string
|
|
||||||
var VideoHorizontal2KList []string
|
|
||||||
var VideoHorizontal3KList []string
|
|
||||||
var VideoHorizontal4KList []string
|
|
||||||
var VideoHorizontal5KList []string
|
|
||||||
var VideoHorizontal6KList []string
|
|
||||||
var VideoHorizontal7KList []string
|
|
||||||
var VideoHorizontal8KList []string
|
|
||||||
var VideoHorizontal9KList []string
|
|
||||||
var VideoHorizontal10KList []string
|
|
||||||
var VideoHorizontal11KList []string
|
|
||||||
var VideoHorizontal12KList []string
|
|
||||||
var VideoHorizontal13KList []string
|
|
||||||
var VideoHorizontal14KList []string
|
|
||||||
var VideoHorizontal15KList []string
|
|
||||||
var VideoHorizontal16KList []string
|
|
||||||
var VideoHorizontal17KList []string
|
|
||||||
var VideoHorizontal18KList []string
|
|
||||||
var VideoHorizontal19KList []string
|
|
||||||
var VideoHorizontalHKList []string
|
|
||||||
var VideoHorizontalStandard720PList []string
|
|
||||||
var VideoHorizontalStandard1D5PList []string
|
|
||||||
var VideoHorizontalStandard1080PList []string
|
|
||||||
var VideoHorizontalStandard2DKList []string
|
|
||||||
var VideoHorizontalStandard2D5KList []string
|
|
||||||
var VideoHorizontalStandardUltraWide2List []string
|
|
||||||
var VideoHorizontalStandardUltraWide3List []string
|
|
||||||
var VideoHorizontalStandardUltraWide4List []string
|
|
||||||
var VideoHorizontalStandard4KList []string
|
|
||||||
var VideoHorizontalStandard4KHList []string
|
|
||||||
var VideoHorizontalStandard8KList []string
|
|
||||||
var VideoVerticalNormalList []string
|
|
||||||
var VideoVerticalGifList []string
|
|
||||||
var VideoVertical1KList []string
|
|
||||||
var VideoVertical2KList []string
|
|
||||||
var VideoVertical3KList []string
|
|
||||||
var VideoVertical4KList []string
|
|
||||||
var VideoVertical5KList []string
|
|
||||||
var VideoVertical6KList []string
|
|
||||||
var VideoVertical7KList []string
|
|
||||||
var VideoVertical8KList []string
|
|
||||||
var VideoVertical9KList []string
|
|
||||||
var VideoVertical10KList []string
|
|
||||||
var VideoVertical11KList []string
|
|
||||||
var VideoVertical12KList []string
|
|
||||||
var VideoVertical13KList []string
|
|
||||||
var VideoVertical14KList []string
|
|
||||||
var VideoVertical15KList []string
|
|
||||||
var VideoVertical16KList []string
|
|
||||||
var VideoVertical17KList []string
|
|
||||||
var VideoVertical18KList []string
|
|
||||||
var VideoVertical19KList []string
|
|
||||||
var VideoVerticalHKList []string
|
|
||||||
var VideoSquareNormalList []string
|
|
||||||
var VideoSquareGifList []string
|
|
||||||
var VideoSquare1KList []string
|
|
||||||
var VideoSquare2KList []string
|
|
||||||
var VideoSquare3KList []string
|
|
||||||
var VideoSquare4KList []string
|
|
||||||
var VideoSquare5KList []string
|
|
||||||
var VideoSquare6KList []string
|
|
||||||
var VideoSquare7KList []string
|
|
||||||
var VideoSquare8KList []string
|
|
||||||
var VideoSquare9KList []string
|
|
||||||
var VideoSquare10KList []string
|
|
||||||
var VideoSquare11KList []string
|
|
||||||
var VideoSquare12KList []string
|
|
||||||
var VideoSquare13KList []string
|
|
||||||
var VideoSquare14KList []string
|
|
||||||
var VideoSquare15KList []string
|
|
||||||
var VideoSquare16KList []string
|
|
||||||
var VideoSquare17KList []string
|
|
||||||
var VideoSquare18KList []string
|
|
||||||
var VideoSquare19KList []string
|
|
||||||
var VideoSquareHKList []string
|
|
||||||
Reference in New Issue
Block a user