feat(core): 优化媒体文件扫描和处理功能
- 添加跳过ffprobe相关文件的逻辑,避免处理临时释放文件 - 修复图片处理中的变量引用错误 - 实现大文件指纹复用机制,避免重复全量读取计算MD5 - 新增文件头尾采样指纹算法,提升大文件处理性能 - 重构ffprobe内嵌资源管理,支持多平台自动选择对应版本 - 添加媒体信息数据库的指纹索引和去向跟踪功能 - 优化程序启动流程,失败时跳过媒体整理但保留图片处理 - 更新文档说明多平台ffprobe支持和构建要求
This commit is contained in:
@@ -27,3 +27,6 @@
|
|||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
# 内嵌的 ffprobe 二进制(约 80~150MB),不入库,需自行放置于 vars/bin/
|
||||||
|
vars/bin/
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- **音频归类**:音频文件单独归入「音频」目录
|
- **音频归类**:音频文件单独归入「音频」目录
|
||||||
- **异常兜底**:读取失败、不支持的格式自动归入「读取异常」「已忽略」目录
|
- **异常兜底**:读取失败、不支持的格式自动归入「读取异常」「已忽略」目录
|
||||||
- **自动整理**:无需人工干预,扫描 → 分析 → 移动一次完成,跨磁盘自动降级为复制后删除
|
- **自动整理**:无需人工干预,扫描 → 分析 → 移动一次完成,跨磁盘自动降级为复制后删除
|
||||||
- **全格式支持**:视频/音频以内嵌的 `ffprobe.exe`(Windows x64)作为最终判定依据,**凡是 ffmpeg 支持的格式都能识别**,不再依赖扩展名白名单
|
- **全格式支持**:视频/音频以**内嵌的 ffprobe**(按系统自动内嵌 macOS/Windows 对应版本)作为最终判定依据,**凡是 ffmpeg 支持的格式都能识别**,不再依赖扩展名白名单
|
||||||
- **媒体信息入库**:自动将文件的 MD5 与完整媒体信息(类型/宽高/时长/编码/容器等)存入根目录下的 `media-info.db`(SQLite,xorm 驱动),支持按字段查询与去重
|
- **媒体信息入库**:自动将文件的 MD5 与完整媒体信息(类型/宽高/时长/编码/容器等)存入根目录下的 `media-info.db`(SQLite,xorm 驱动),支持按字段查询与去重
|
||||||
|
|
||||||
## 支持的格式
|
## 支持的格式
|
||||||
@@ -31,6 +31,7 @@ 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`)
|
||||||
- `build.sh` 使用 `upx` 压缩产物,输出 `kenaito-media-picker.exe`(Windows 单文件)
|
- `build.sh` 使用 `upx` 压缩产物,输出 `kenaito-media-picker.exe`(Windows 单文件)
|
||||||
- 编译所需工具(ffmpeg / upx)压缩包已放在 `doc/` 目录,按需解压使用
|
- 编译所需工具(ffmpeg / upx)压缩包已放在 `doc/` 目录,按需解压使用
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ kenaito-media-picker.exe
|
|||||||
已忽略 ← 非媒体文件或无法识别
|
已忽略 ← 非媒体文件或无法识别
|
||||||
```
|
```
|
||||||
|
|
||||||
> 视频时长与分辨率通过内嵌 `ffprobe.exe` 读取(处理时会临时释放,完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
|
> 视频时长与分辨率通过内嵌 ffprobe 读取(处理时会临时释放到当前目录、完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ kenaito-media-picker.exe
|
|||||||
├── main.go # 程序入口(扫描 → 过滤 → 图片/媒体处理)
|
├── main.go # 程序入口(扫描 → 过滤 → 图片/媒体处理)
|
||||||
├── core/ # 核心逻辑:文件扫描、MIME 过滤、图片/视频/音频分组与移动
|
├── core/ # 核心逻辑:文件扫描、MIME 过滤、图片/视频/音频分组与移动
|
||||||
├── util/ # 工具:MIME 识别、图片尺寸读取、ffprobe 封装、文件移动
|
├── util/ # 工具:MIME 识别、图片尺寸读取、ffprobe 封装、文件移动
|
||||||
├── vars/ # 全局状态 + 内嵌 ffprobe.exe(go:embed)
|
├── vars/ # 全局状态 + 按系统内嵌 ffprobe(go:embed,二进制在 vars/bin/)
|
||||||
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
|
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
|
||||||
└── build.sh # 编译脚本(upx 压缩)
|
└── build.sh # 编译脚本(upx 压缩)
|
||||||
```
|
```
|
||||||
@@ -107,7 +108,7 @@ kenaito-media-picker.exe
|
|||||||
- 默认使用**移动**模式,原文件会从源目录移走;同名文件跨磁盘时自动降级为复制 + 删除
|
- 默认使用**移动**模式,原文件会从源目录移走;同名文件跨磁盘时自动降级为复制 + 删除
|
||||||
- 已整理文件全部移动到 `{当前目录名}-picker` 白名单目录内,重复运行会跳过该目录,并将新文件并入已有的分类目录
|
- 已整理文件全部移动到 `{当前目录名}-picker` 白名单目录内,重复运行会跳过该目录,并将新文件并入已有的分类目录
|
||||||
- GIF 图片单独归入「图片_GIF」;GIF 视频按方向归入「视频_*_GIF」
|
- GIF 图片单独归入「图片_GIF」;GIF 视频按方向归入「视频_*_GIF」
|
||||||
- 视频/音频处理依赖 Windows 平台的 `ffprobe.exe`(已内嵌),非 Windows 平台需自行调整
|
- ffprobe 按系统内嵌(macOS/Windows),运行前释放到当前目录、结束后自动删除;若当前平台无内嵌版本,视频/音频将不处理
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ func visit(currentPath string, info os.FileInfo, err error) error {
|
|||||||
if filepath.Base(currentPath) == store.DBFileName {
|
if filepath.Base(currentPath) == store.DBFileName {
|
||||||
return nil // 跳过媒体信息数据库文件
|
return nil // 跳过媒体信息数据库文件
|
||||||
}
|
}
|
||||||
|
if filepath.Base(currentPath) == util.FFprobeExeName || filepath.Base(currentPath) == "ffprobe_mac" {
|
||||||
|
return nil // 跳过ffprobe相关文件(中断残留的释放文件 / vars/bin内嵌源文件)
|
||||||
|
}
|
||||||
vars.GlobalFilePathList = append(vars.GlobalFilePathList, currentPath)
|
vars.GlobalFilePathList = append(vars.GlobalFilePathList, currentPath)
|
||||||
// filename, include ext
|
// filename, include ext
|
||||||
vars.GlobalFilePath2FileNameMap[currentPath] = filepath.Base(currentPath)
|
vars.GlobalFilePath2FileNameMap[currentPath] = filepath.Base(currentPath)
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func DoHandleImage(pickerDir string) {
|
|||||||
|
|
||||||
// 条件图片并分组存放
|
// 条件图片并分组存放
|
||||||
func doPickImageFile(pickerDir string, imagePath2WidthHeightMap map[string]string) {
|
func doPickImageFile(pickerDir string, imagePath2WidthHeightMap map[string]string) {
|
||||||
if len(vars.ImagePath2WidthHeightMap) == 0 {
|
if len(imagePath2WidthHeightMap) == 0 {
|
||||||
fmt.Printf("=== 当前目录下没有扫描到图片文件, %s \n", pickerDir)
|
fmt.Printf("=== 当前目录下没有扫描到图片文件, %s \n", pickerDir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-6
@@ -8,18 +8,38 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// saveMediaRecord 计算MD5并将媒体信息写入数据库
|
// saveMediaRecord 将媒体信息写入数据库;大文件优先用指纹(头尾采样)复用已入库的MD5,避免全量读取
|
||||||
func saveMediaRecord(filePath string, mediaType string, width int, height int, duration float64, codec string, format string, rawInfo string) {
|
func saveMediaRecord(filePath string, mediaType string, width int, height int, duration float64, codec string, format string, rawInfo string) {
|
||||||
md5Str, err := util.CalcFileMd5(filePath)
|
st, err := os.Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("=== 计算MD5失败: %s, 错误: %v\n", filePath, err)
|
fmt.Printf("=== 读取文件信息失败: %s, 错误: %v\n", filePath, err)
|
||||||
return
|
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{
|
info := &store.MediaInfo{
|
||||||
FileMd5: md5Str,
|
FileMd5: md5Str,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
FileName: vars.GlobalFilePath2FileNameMap[filePath],
|
FileName: vars.GlobalFilePath2FileNameMap[filePath],
|
||||||
FileExt: vars.GlobalFilePath2FileExtMap[filePath],
|
FileExt: vars.GlobalFilePath2FileExtMap[filePath],
|
||||||
|
FileSize: size,
|
||||||
MimeType: vars.GlobalFilePath2MimeInfoMap[filePath],
|
MimeType: vars.GlobalFilePath2MimeInfoMap[filePath],
|
||||||
MediaType: mediaType,
|
MediaType: mediaType,
|
||||||
Width: width,
|
Width: width,
|
||||||
@@ -28,9 +48,8 @@ func saveMediaRecord(filePath string, mediaType string, width int, height int, d
|
|||||||
Codec: codec,
|
Codec: codec,
|
||||||
Format: format,
|
Format: format,
|
||||||
RawInfo: rawInfo,
|
RawInfo: rawInfo,
|
||||||
}
|
HeadMd5: headMd5,
|
||||||
if st, err := os.Stat(filePath); err == nil {
|
TailMd5: tailMd5,
|
||||||
info.FileSize = st.Size()
|
|
||||||
}
|
}
|
||||||
if err := store.SaveMedia(info); err != nil {
|
if err := store.SaveMedia(info); err != nil {
|
||||||
fmt.Printf("=== 保存媒体信息失败: %s, 错误: %v\n", filePath, err)
|
fmt.Printf("=== 保存媒体信息失败: %s, 错误: %v\n", filePath, err)
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:6080e34165fcb0261f979bb1cef26c5fce616e2a416384b50bcce956aa265018
|
|
||||||
size 140416365
|
|
||||||
@@ -34,18 +34,25 @@ func main() {
|
|||||||
fmt.Println("=== 初始化数据库失败(继续整理, 不记录媒体信息):", err)
|
fmt.Println("=== 初始化数据库失败(继续整理, 不记录媒体信息):", err)
|
||||||
}
|
}
|
||||||
defer store.Close()
|
defer store.Close()
|
||||||
|
// 释放内嵌ffprobe(按当前系统);失败只跳过媒体整理,图片照常处理
|
||||||
|
ffprobeReady := true
|
||||||
if len(vars.GlobalImagePathList) > 0 || len(vars.GlobalMediaPathList) > 0 {
|
if len(vars.GlobalImagePathList) > 0 || len(vars.GlobalMediaPathList) > 0 {
|
||||||
if err := util.ReleaseFFprobe(); err != nil {
|
if err := util.ReleaseFFprobe(); err != nil {
|
||||||
fmt.Println("=== 释放解码器失败, 5秒后将自动退出", err)
|
fmt.Println("=== 释放解码器失败, 视频/音频整理将跳过(图片照常处理):", err)
|
||||||
time.Sleep(time.Second * 5)
|
ffprobeReady = false
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
defer util.RemoveFFprobe()
|
||||||
if len(vars.GlobalImagePathList) > 0 {
|
if len(vars.GlobalImagePathList) > 0 {
|
||||||
core.DoHandleImage(pickerDir)
|
core.DoHandleImage(pickerDir)
|
||||||
}
|
}
|
||||||
if len(vars.GlobalMediaPathList) > 0 {
|
if len(vars.GlobalMediaPathList) > 0 && ffprobeReady {
|
||||||
core.DoHandleMedia(pickerDir)
|
core.DoHandleMedia(pickerDir)
|
||||||
}
|
}
|
||||||
util.RemoveFFprobe()
|
// 所有移动完成后,回填文件的归类目录与去向
|
||||||
|
for src, dest := range vars.GlobalPath2DestDirMap {
|
||||||
|
if err := store.UpdateMediaDest(src, dest); err != nil {
|
||||||
|
fmt.Printf("=== 更新媒体去向失败: %s, 错误: %v\n", src, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-15
@@ -2,6 +2,7 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sqlite "github.com/glebarez/go-sqlite"
|
sqlite "github.com/glebarez/go-sqlite"
|
||||||
@@ -19,21 +20,25 @@ func init() {
|
|||||||
// MediaInfo 媒体文件信息记录,按字段拆分存放,便于查询
|
// MediaInfo 媒体文件信息记录,按字段拆分存放,便于查询
|
||||||
type MediaInfo struct {
|
type MediaInfo struct {
|
||||||
Id int64 `xorm:"pk autoincr 'id'"`
|
Id int64 `xorm:"pk autoincr 'id'"`
|
||||||
FileMd5 string `xorm:"char(32) index 'file_md5'"` // 文件MD5,唯一标识
|
FileMd5 string `xorm:"char(32) index 'file_md5'"` // 文件MD5,唯一标识
|
||||||
FilePath string `xorm:"text 'file_path'"` // 扫描时的源路径
|
FilePath string `xorm:"text 'file_path'"` // 扫描时的源路径
|
||||||
FileName string `xorm:"varchar(255) 'file_name'"` // 文件名(含扩展名)
|
FileName string `xorm:"varchar(255) 'file_name'"` // 文件名(含扩展名)
|
||||||
FileExt string `xorm:"varchar(32) 'file_ext'"` // 扩展名
|
FileExt string `xorm:"varchar(32) 'file_ext'"` // 扩展名
|
||||||
FileSize int64 `xorm:"bigint 'file_size'"` // 文件大小(字节)
|
FileSize int64 `xorm:"bigint 'file_size'"` // 文件大小(字节)
|
||||||
MimeType string `xorm:"varchar(128) 'mime_type'"` // MIME类型
|
MimeType string `xorm:"varchar(128) 'mime_type'"` // MIME类型
|
||||||
MediaType string `xorm:"varchar(16) index 'media_type'"` // image/video/audio
|
MediaType string `xorm:"varchar(16) index 'media_type'"` // image/video/audio
|
||||||
Width int `xorm:"int index 'width'"` // 宽(像素)
|
Width int `xorm:"int index 'width'"` // 宽(像素)
|
||||||
Height int `xorm:"int index 'height'"` // 高(像素)
|
Height int `xorm:"int index 'height'"` // 高(像素)
|
||||||
Duration float64 `xorm:"real 'duration'"` // 时长(秒)
|
Duration float64 `xorm:"real 'duration'"` // 时长(秒)
|
||||||
Codec string `xorm:"varchar(64) 'codec'"` // 视频编码,如h264/hevc
|
Codec string `xorm:"varchar(64) 'codec'"` // 视频编码,如h264/hevc
|
||||||
Format string `xorm:"varchar(64) 'format'"` // 容器格式,如mp4/mkv
|
Format string `xorm:"varchar(64) 'format'"` // 容器格式,如mp4/mkv
|
||||||
RawInfo string `xorm:"text 'raw_info'"` // ffprobe原始JSON(完整信息兜底)
|
RawInfo string `xorm:"text 'raw_info'"` // ffprobe原始JSON(完整信息兜底)
|
||||||
CreatedAt time.Time `xorm:"created 'created_at'"` // 首次入库时间
|
HeadMd5 string `xorm:"char(32) index('idx_fp') 'head_md5'"` // 文件头256KB的MD5(指纹加速)
|
||||||
UpdatedAt time.Time `xorm:"updated 'updated_at'"` // 最近更新时间
|
TailMd5 string `xorm:"char(32) index('idx_fp') 'tail_md5'"` // 文件尾256KB的MD5(指纹加速)
|
||||||
|
Category string `xorm:"varchar(64) index 'category'"` // 归类的目录名(去向)
|
||||||
|
DestDir string `xorm:"text 'dest_dir'"` // 移动后的目录路径(去向)
|
||||||
|
CreatedAt time.Time `xorm:"created 'created_at'"` // 首次入库时间
|
||||||
|
UpdatedAt time.Time `xorm:"updated 'updated_at'"` // 最近更新时间
|
||||||
}
|
}
|
||||||
|
|
||||||
var engine *xorm.Engine
|
var engine *xorm.Engine
|
||||||
@@ -78,3 +83,28 @@ func SaveMedia(info *MediaInfo) error {
|
|||||||
_, err = engine.Insert(info)
|
_, err = engine.Insert(info)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindMediaByFingerprint 按 (文件大小 + 头256KB + 尾256KB) 查找已入库记录,避免对大文件重复计算全量MD5
|
||||||
|
func FindMediaByFingerprint(size int64, headMd5 string, tailMd5 string) (*MediaInfo, bool) {
|
||||||
|
if engine == nil || headMd5 == "" || tailMd5 == "" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
info := new(MediaInfo)
|
||||||
|
has, err := engine.Where("file_size = ? AND head_md5 = ? AND tail_md5 = ?", size, headMd5, tailMd5).Get(info)
|
||||||
|
if err != nil || !has {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMediaDest 按源路径回填文件的归类目录与去向
|
||||||
|
func UpdateMediaDest(filePath string, destDir string) error {
|
||||||
|
if engine == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := engine.Where("file_path = ?", filePath).Update(&MediaInfo{
|
||||||
|
Category: filepath.Base(destDir),
|
||||||
|
DestDir: destDir,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import (
|
|||||||
// CreateDir 创建目录(含递归创建父目录)
|
// CreateDir 创建目录(含递归创建父目录)
|
||||||
func CreateDir(dirPath string) bool {
|
func CreateDir(dirPath string) bool {
|
||||||
err := os.MkdirAll(dirPath, 0755)
|
err := os.MkdirAll(dirPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("=== 创建目录失败: %s, 错误: %v\n", dirPath, err)
|
||||||
|
}
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +64,36 @@ func CalcFileMd5(filePath string) (string, error) {
|
|||||||
return hex.EncodeToString(h.Sum(nil)), nil
|
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) {
|
//func renameFile(rootDir string, modelType string, videoList []string, pathSeparator string) {
|
||||||
// total := len(videoList)
|
// total := len(videoList)
|
||||||
|
|||||||
+6
-2
@@ -24,9 +24,12 @@ func FFprobeExePath() string {
|
|||||||
return "." + string(os.PathSeparator) + FFprobeExeName
|
return "." + string(os.PathSeparator) + FFprobeExeName
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseFFprobe 释放内嵌的ffprobe到当前目录
|
// ReleaseFFprobe 释放内嵌的ffprobe到当前目录(按当前系统自动选择对应版本)
|
||||||
func ReleaseFFprobe() error {
|
func ReleaseFFprobe() error {
|
||||||
return WriteByteArraysToFile(vars.VideoDecodeToolWin64, FFprobeExeName)
|
if len(vars.VideoDecodeTool) == 0 {
|
||||||
|
return fmt.Errorf("当前系统没有内嵌的ffprobe")
|
||||||
|
}
|
||||||
|
return WriteByteArraysToFile(vars.VideoDecodeTool, FFprobeExeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveFFprobe 删除当前目录下的ffprobe
|
// RemoveFFprobe 删除当前目录下的ffprobe
|
||||||
@@ -170,6 +173,7 @@ func DoMoveFileToDir(filePathList []string, videoDirPath string) {
|
|||||||
pathSeparator := string(os.PathSeparator)
|
pathSeparator := string(os.PathSeparator)
|
||||||
for _, videoFilePath := range filePathList {
|
for _, videoFilePath := range filePathList {
|
||||||
moveFileToDir(videoFilePath, videoDirPath+pathSeparator)
|
moveFileToDir(videoFilePath, videoDirPath+pathSeparator)
|
||||||
|
vars.GlobalPath2DestDirMap[videoFilePath] = videoDirPath // 记录去向
|
||||||
count = count + 1
|
count = count + 1
|
||||||
bar.PrintBar(CalcPercentage(count, total))
|
bar.PrintBar(CalcPercentage(count, total))
|
||||||
}
|
}
|
||||||
|
|||||||
LFS
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package vars
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed bin/ffprobe_mac
|
||||||
|
var VideoDecodeTool []byte // 需在 vars/bin/ 下放置 macOS 版 ffprobe(未入库,见 .gitignore)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//go:build !darwin && !windows
|
||||||
|
|
||||||
|
package vars
|
||||||
|
|
||||||
|
// VideoDecodeTool 当前平台没有内嵌的ffprobe
|
||||||
|
var VideoDecodeTool []byte
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package vars
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed bin/ffprobe.exe
|
||||||
|
var VideoDecodeTool []byte // 需在 vars/bin/ 下放置 Windows 版 ffprobe(未入库,见 .gitignore)
|
||||||
@@ -8,3 +8,4 @@ var GlobalImagePathList []string // 所有图片文件
|
|||||||
var GlobalMediaPathList []string // 待ffprobe确认的媒体候选文件
|
var GlobalMediaPathList []string // 待ffprobe确认的媒体候选文件
|
||||||
var GlobalAudioPathList []string // 已确认的音频文件路径
|
var GlobalAudioPathList []string // 已确认的音频文件路径
|
||||||
var GlobalPickerDir string // 已整理文件的输出目录(白名单,扫描时跳过)
|
var GlobalPickerDir string // 已整理文件的输出目录(白名单,扫描时跳过)
|
||||||
|
var GlobalPath2DestDirMap = make(map[string]string) // 文件路径 → 移动到的目录(去向)
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package vars
|
package vars
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed" // 内嵌文件支持
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed ffprobe.exe
|
|
||||||
var VideoDecodeToolWin64 []byte
|
|
||||||
var VideoIgnorePathList []string // 忽略的文件路径
|
var VideoIgnorePathList []string // 忽略的文件路径
|
||||||
var VideoReadErrorPathList []string // 读取信息异常的路径
|
var VideoReadErrorPathList []string // 读取信息异常的路径
|
||||||
var VideoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比
|
var VideoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比
|
||||||
|
|||||||
Reference in New Issue
Block a user