feat(core): 添加PSD/PSB图片格式支持并重构媒体文件处理

- 新增ReadPsdImage函数支持PSD/PSB格式图片信息读取
- 修改文件扫描逻辑,将视频处理改为媒体候选处理,使用ffprobe统一判定
- 添加对PSB格式的支持,与PSD共享相同8BPS文件头处理
- 重构handle_image.go中的图片处理逻辑,优化错误处理和记录保存
- 更新文件扫描过滤规则,支持更多图片格式并跳过数据库文件
- 添加文件MD5计算功能用于文件唯一性标识
- 修改媒体处理流程,支持音频文件识别和分类处理
- 移除废弃的视频时长转换函数和相关依赖
- 更新依赖版本并添加SQLite支持用于媒体信息存储
This commit is contained in:
2025-03-18 21:04:15 +08:00
parent f5e3f6b4ef
commit b276e8c56f
16 changed files with 770 additions and 311 deletions
+17 -13
View File
@@ -4,22 +4,25 @@
## 功能特性
- **图片 / 视频双支持**:扫描当前目录(递归),自动识别图片与视频文件
- **图片 / 视频 / 音频**:扫描当前目录(递归),自动识别图片、视频与音频文件
- **标准分辨率归类**:720P、1080P、2K、2.5K、影院 2K、DCI 4K、8K 及带鱼屏(UltraWide 2K/3K/4K)等标准规格单独建目录
- **非标分辨率归类**:按像素区间自动归入 1K ~ 9K / 普通 / 原图(原画质)目录
- **方向区分**:横屏、竖屏、等比(正方形)分开整理,GIF 单独处理
- **音频归类**:音频文件单独归入「音频」目录
- **异常兜底**:读取失败、不支持的格式自动归入「读取异常」「已忽略」目录
- **自动整理**:无需人工干预,扫描 → 分析 → 移动一次完成,跨磁盘自动降级为复制后删除
- **零依赖安装**:视频信息读取使用内嵌的 `ffprobe.exe`(Windows x64),无需额外安装 ffmpeg
- **全格式支持**:视频/音频以内嵌的 `ffprobe.exe`(Windows x64)作为最终判定依据,**凡是 ffmpeg 支持的格式都能识别**,不再依赖扩展名白名单
- **媒体信息入库**:自动将文件的 MD5 与完整媒体信息(类型/宽高/时长/编码/容器等)存入根目录下的 `media-info.db`(SQLite,xorm 驱动),支持按字段查询与去重
## 支持的格式
| 类型 | 格式 |
|------|------|
| 图片 | JPG / JPEG / JPE / JFIF / PNG / GIF / BMP / WEBP / PSB,PSD 单独归入「图片_PSD」 |
| 视频 | MP4 / AVI / MOV / MKV / MPG / M4V / WMV / WEBM / FLV / TS / RM / RMVB / 3GP / 3G2 |
| 图片 | JPG / JPEG / JPE / JFIF / PNG / GIF / BMP / WEBP / PSB,PSD 单独归入「图片_PSD」;Go 无法解码的图片格式(如 TIFF)由 ffprobe 兜底 |
| 视频 | 凡 ffmpeg 支持的容器/编码均可识别(MP4 / MKV / AVI / MOV / TS / FLV / WMV / RM / RMVB / WEBM / M2TS 等),以 ffprobe 探测结果为准 |
| 音频 | 凡 ffmpeg 支持的音频(MP3 / WAV / FLAC / AAC / M4A / OGG / WMA 等)归入「音频」目录 |
> 判断依据为文件 MIME 信息 + 扩展名双重识别,扩展名大小写不敏感。
> 判断依据:图片按文件 MIME + 扩展名识别(扩展名大小写不敏感);视频/音频由 ffprobe 读取媒体流判定,支持范围与 ffmpeg 一致。
## 构建编译
@@ -72,7 +75,7 @@ kenaito-media-picker.exe
{uid}-图片_已忽略 ← 不支持格式的图片
```
### 视频
### 视频 / 音频
```text
{uid}-视频_横屏_720P / 1080P / 2DKP / 2D5KP / 4KP / 4KHP / 8KP
@@ -81,17 +84,18 @@ kenaito-media-picker.exe
{uid}-视频_竖屏_1k ~ 9k / 普通 / 原画质
{uid}-视频_等比_1k ~ 9k / 普通 / 原画质
{uid}-视频_横屏_GIF / 视频_竖屏_GIF / 视频_等比_GIF
读取异常 ← 无法读取分辨率的视频
已忽略 ← 不支持的视频格式
音频 ← 所有音频文件(ffmpeg 可识别的格式)
视频_读取异常 ← mime 为视频但 ffprobe 读取失败
已忽略 ← 非媒体文件或无法识别
```
> 视频时长与分辨率通过内嵌 `ffprobe.exe` 读取(处理时会临时释放,完成后自动删除)。
> 视频时长与分辨率通过内嵌 `ffprobe.exe` 读取(处理时会临时释放,完成后自动删除);凡 ffmpeg 支持的视频/音频格式均可识别。
## 目录结构
```text
├── main.go # 程序入口(扫描 → 过滤 → 图片/视频处理)
├── core/ # 核心逻辑:文件扫描、MIME 过滤、图片/视频分组与移动
├── main.go # 程序入口(扫描 → 过滤 → 图片/媒体处理)
├── core/ # 核心逻辑:文件扫描、MIME 过滤、图片/视频/音频分组与移动
├── util/ # 工具:MIME 识别、图片尺寸读取、ffprobe 封装、文件移动
├── vars/ # 全局状态 + 内嵌 ffprobe.exe(go:embed)
├── doc/ # 构建依赖(ffmpeg / upx 压缩包)
@@ -101,9 +105,9 @@ kenaito-media-picker.exe
## 注意事项
- 默认使用**移动**模式,原文件会从源目录移走;同名文件跨磁盘时自动降级为复制 + 删除
- 每次运行生成独立的 UUID 目录,重复运行不会相互覆盖
- 已整理文件全部移动到 `{当前目录名}-picker` 白名单目录内,重复运行会跳过该目录,并将新文件并入已有的分类目录
- GIF 图片单独归入「图片_GIF」;GIF 视频按方向归入「视频_*_GIF」
- 视频处理依赖 Windows 平台的 `ffprobe.exe`(已内嵌),非 Windows 平台需自行调整
- 视频/音频处理依赖 Windows 平台的 `ffprobe.exe`(已内嵌),非 Windows 平台需自行调整
## License
-89
View File
@@ -1,89 +0,0 @@
# 项目审查记录 2026-08-13
> 审查方式:静态代码审查(`go build` / `go vet` 均通过,此文件不含编译级错误)
> 审查范围:全部 `.go` 源码
---
## 一、严重 Bug
### 1. WEBP / BMP / PSB 永远进「读取异常」文件夹(专用分支是死代码)
- **位置**:`util/image.go:9-19`、`core/handle_image.go:22,35,46`
- **问题**:`supportImageTypes` 中包含了 `.bmp`、`.webp`、`.psb`,导致 `handle_image.go:22` 的通用分支总是先命中,调用 `image.DecodeConfig`(`main.go:6-8` 只注册了 png/jpeg/gif 解码器),对 webp/bmp 必然失败,文件全部落入「读取异常」目录。
- **影响**:`handle_image.go:35`(webp)、`:46`(bmp)的专用处理分支永远不会执行,与 README 承诺支持的格式不符。
- **修复方向**:从 `supportImageTypes` 中移除 `.bmp` / `.webp` / `.psb`,让专用分支生效;或在通用分支前先特判这三类格式。
### 2. PSD 从未被识别为图片,`ImagePsdList` 恒为空
- **位置**:`core/file_scanner.go:70-79`、`core/handle_image.go:57`
- **问题**:PSD 文件的 MIME 为 `application/vnd.adobe.photoshop`(不包含 `image/` 前缀),且 `.psd` 不在 `supportImageTypes` 中,因此在 `DoFilter` 阶段就被丢弃,永远不会进入 `GlobalImagePathList`。
- **影响**:`handle_image.go:57` 的 PSD 分支不可达,README 声称的「PSD 单独归入图片_PSD」功能实际失效。
- **修复方向**:在过滤阶段对 `.psd` / `application/vnd.adobe.photoshop` 单独识别并入图片列表。
### 3. 同名文件移动时数据丢失风险(已处理)
- **位置**:`util/video.go:152-169`(`moveFileToDir`)
- **问题**:目标文件名只取 `BaseName`。不同子目录下的同名文件(如 `a/photo.jpg`、`b/photo.jpg`)会被移动到/复制到同一目标:Unix 下 `os.Rename` 静默覆盖,跨盘 fallback 的 `copyFile`(`os.Create`)也会截断覆盖目标。
- **影响**:真实数据丢失风险,且无任何告警。
- **修复方向**:目标名冲突时追加序号或 hash,确保不覆盖已有文件。
### 4. BMP 自顶向下格式(负高度)导致解析错乱(已处理)
- **位置**:`core/bmp.go:59`、`core/handle_image.go:96-98`
- **问题**:`ReadBmpImage` 直接返回 `int32` 宽高,负高度被 `fmt.Sprintf("%d-%d")` 拼成 `"640--480"`,`strings.Split("-")` 后 `width2Height[1]` 为空串,`String2int` 返回 0,图片被错误归类。
- **影响**:此类 BMP 图片分辨率判定错误(高度变 0,全部按横屏普通处理)。
- **修复方向**:读取时对高度取绝对值,或改用不易混淆的分隔符/结构化存储。
### 5. 重复运行会重新整理已整理过的目录(已处理)
- **位置**:`core/file_scanner.go:20`、`main.go`
- **问题**:`filepath.Walk` 扫描整个根目录。第二次运行时,上次生成的 `{uid}-图片_*`、`{uid}-视频_*` 等目录会被再次扫描,其中已分类的文件会被再次移动,形成无限嵌套/重复归类。
- **影响**:重复执行程序导致目录结构越来越乱,违背 README「重复运行不会相互覆盖」的描述。
- **修复方向**:扫描时跳过已生成的 `{uid}-` 前缀目录,或提供过滤白名单。
---
## 二、次要问题
### 6. ffprobe 输出解析脆弱
- **位置**:`util/video.go:53-101`
- **问题**:对多视频流(常见于 MKV)的多行输出处理靠猜测(`\r\n\r\n\r\n\r\n` split、`N/AxN/A` 字符串替换等),且不校验 0 值分辨率,解析出错时归类到「普通」而非「读取异常」。
### 7. 视频「读取异常 / 已忽略」目录命名不一致
- **位置**:`core/handle_video.go:56,62`
- **问题**:这两个目录没有 uid 前缀(如 `读取异常`、`已忽略`),与其它 `{uid}-视频_*` 目录风格不统一,且与图片侧带 uid 的命名方式不一致。
### 8. 死代码 / 未使用数据
- **位置**:`util/file.go:47-112`、`vars/video.go`、`util/file.go:17-21`、`vars/global.go:5`
- **问题**:`VideoPath2DurationMap` / `VideoPath2WidthHeightTagMap` 赋值后从未使用(重命名逻辑已被注释掉);`util/file.go` 中保留大段注释代码;`GetFileDirectory`、`GlobalFilePath2FileNameMap` 未被使用。
### 9. `doPickImageFile` 空判断误报
- **位置**:`core/handle_image.go:91`
- **问题**:判断的是全局 `vars.ImagePath2WidthHeightMap` 而非入参 `imagePath2WidthHeightMap`;当所有图片都是错误/忽略时,会误打印「当前目录下没有扫描到图片文件」。
### 10. 目录创建失败未处理
- **位置**:`core/handle_image.go`(各 `move*Image` 函数)、`core/handle_video.go`(各 `move*Video` 函数)
- **问题**:多数 `util.CreateDir` 的返回值被忽略,若目录创建失败仍继续执行 `DoMoveFileToDir`,文件移动会静默失败。
### 11. 临时 ffprobe.exe 清理不保证
- **位置**:`core/handle_video.go:13-24,68-71`
- **问题**:通过流程末尾手动 `os.Remove` 清理,若中途异常(panic / 提前 return)会残留 `ffprobe.exe`;下次运行该文件会被扫描当作普通文件。建议 `defer` 清理。
---
## 三、优化建议
1. **修复严重 bug 1/2 的关键**:调整 `supportImageTypes` 列表 / 过滤逻辑,确保 webp、bmp、psb、psd 走各自正确的分支。
2. **重复运行防护**:扫描时跳过已生成的 `{uid}-` 目录。
3. **合并 ffprobe 调用**:分辨率 + 时长可用一次 `-show_streams -show_format` 取回,每视频省一次子进程创建。
4. **消除重复代码**:图片/视频各有约 60 个几乎相同的分组 + 移动函数,可用「分组名 → 列表」映射表驱动,大幅减小编码量。
5. **使用 `filepath.Join`**:代替 `rootDir + separator + ...` 字符串拼接,避免路径分隔符与平台问题。
6. **`moveFileToDir` 增加同名去重**:冲突时追加序号/hash,杜绝覆盖。
7. **清理死代码**:删除注释掉的 rename 逻辑、未使用的 map 与函数。
+27
View File
@@ -3,6 +3,7 @@ package core
import (
"encoding/binary"
"fmt"
"io"
"os"
)
@@ -85,3 +86,29 @@ func absInt32(v int32) int32 {
}
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
}
+11 -15
View File
@@ -3,6 +3,7 @@ package core
import (
"fmt"
"github.com/redmask-hb/GoSimplePrint/goPrint"
"kenaito-media-picker/store"
"kenaito-media-picker/util"
"kenaito-media-picker/vars"
"log"
@@ -55,29 +56,21 @@ func (FileScanner) DoFilter() {
for _, globalFilePath := range vars.GlobalFilePathList {
fileMime := vars.GlobalFilePath2MimeInfoMap[globalFilePath]
count = count + 1
if strings.Contains(fileMime, "video/") { // 视频
vars.GlobalVideoPathList = append(vars.GlobalVideoPathList, globalFilePath)
bar.PrintBar(util.CalcPercentage(count, total))
continue
}
// mime格式为application/octet-stream的视频
// 图片
ext := path.Ext(globalFilePath)
if util.IsSupportVideo(ext) {
vars.GlobalVideoPathList = append(vars.GlobalVideoPathList, globalFilePath)
bar.PrintBar(util.CalcPercentage(count, total))
continue
}
if strings.Contains(fileMime, "image/") { // 图片
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
}
if util.IsSupportImage(ext) {
vars.GlobalImagePathList = append(vars.GlobalImagePathList, globalFilePath)
// 其余作为媒体候选,由ffprobe最终判定(视频/音频/不支持),
// 只要ffmpeg支持的格式都会被识别,不再依赖扩展名白名单
if util.IsPotentialMedia(fileMime) {
vars.GlobalMediaPathList = append(vars.GlobalMediaPathList, globalFilePath)
bar.PrintBar(util.CalcPercentage(count, total))
continue
}
// 其他的文件不处理
// 明显的非媒体文件(代码/文档/压缩包等)直接不处理
}
bar.PrintEnd("=== Finish")
}
@@ -91,6 +84,9 @@ func visit(currentPath string, info os.FileInfo, err error) error {
return filepath.SkipDir // 跳过白名单目录(已整理文件的输出目录)
}
if !info.IsDir() {
if filepath.Base(currentPath) == store.DBFileName {
return nil // 跳过媒体信息数据库文件
}
vars.GlobalFilePathList = append(vars.GlobalFilePathList, currentPath)
// filename, include ext
vars.GlobalFilePath2FileNameMap[currentPath] = filepath.Base(currentPath)
+44 -13
View File
@@ -18,26 +18,15 @@ func DoHandleImage(pickerDir string) {
ignoreCount := 0 // 忽略数
for _, imageFilePath := range vars.GlobalImagePathList {
suffix := vars.GlobalFilePath2FileExtMap[imageFilePath]
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)
fmt.Printf("=== Image总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount)
} else {
errorCount = errorCount + 1
vars.ImageReadErrorPathList = append(vars.ImageReadErrorPathList, imageFilePath)
fmt.Printf("=== 异常图片: %s \n", imageFilePath)
}
continue
}
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
@@ -46,9 +35,11 @@ func DoHandleImage(pickerDir string) {
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
@@ -58,6 +49,46 @@ func DoHandleImage(pickerDir string) {
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)
+41 -46
View File
@@ -6,57 +6,60 @@ import (
"kenaito-media-picker/vars"
"os"
"strings"
"time"
)
func DoHandleVideo(pickerDir string) {
// 释放ffprobe
readerFileName := "./ffprobe.exe"
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
err := util.WriteByteArraysToFile(vars.VideoDecodeToolWin64, readerFileName)
// 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 {
fmt.Println("=== 释放解码器失败, 5秒后将自动退出", err)
time.Sleep(time.Second * 5)
return
}
total := len(vars.GlobalVideoPathList) // 总数
successCount := 0 // 成功数
errorCount := 0 // 失败数
ignoreCount := 0 // 忽略数
for _, videoFilePath := range vars.GlobalVideoPathList {
suffix := vars.GlobalFilePath2FileExtMap[videoFilePath]
if util.IsSupportVideo(suffix) {
width, height, err := util.ReadVideoWidthHeight(videoFilePath)
if err == nil {
successCount = successCount + 1
vars.VideoPath2WidthHeightMap[videoFilePath] = fmt.Sprintf("%d-%d", width, height)
vars.VideoPath2WidthHeightTagMap[videoFilePath] = fmt.Sprintf("[%dx%d]", width, height)
fmt.Printf("=== Video总数: %d, 已读取Info: %d, 成功数: %d, 失败数: %d \n", total, successCount+errorCount+ignoreCount, successCount, errorCount)
duration := util.ReadVideoDuration(videoFilePath)
if duration == 0 {
vars.VideoPath2DurationMap[videoFilePath] = "0H0M0S"
} else {
vars.VideoPath2DurationMap[videoFilePath] = util.SecondsToHms(duration)
}
} else {
if strings.Contains(fileMime, "video/") {
// mime是视频但ffprobe读不出来,视为损坏/异常视频
errorCount = errorCount + 1
vars.VideoReadErrorPathList = append(vars.VideoReadErrorPathList, videoFilePath)
fmt.Printf("=== 异常视频: %s \n", videoFilePath)
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, videoFilePath)
vars.VideoIgnorePathList = append(vars.VideoIgnorePathList, mediaFilePath)
}
if len(vars.VideoReadErrorPathList) > 0 {
readInfoErrorPath := pickerDir + string(os.PathSeparator) + "读取异常"
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) {
@@ -64,21 +67,13 @@ func DoHandleVideo(pickerDir string) {
}
}
doPickVideoFile(pickerDir, vars.VideoPath2WidthHeightMap)
// 删除ffprobe
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
fmt.Printf("=== 视频处理完毕 \n\n")
fmt.Printf("=== 视频/音频处理完毕 \n\n")
}
// 条件视频并分组存放
func doPickVideoFile(pickerDir string, videoPath2WidthHeightMap map[string]string) {
if len(videoPath2WidthHeightMap) == 0 {
fmt.Printf("=== 当前目录下没有扫描到视频文件, %s \n", pickerDir)
readerFileName := "./ffprobe.exe"
if util.CheckFileIsExist(readerFileName) {
_ = os.Remove(readerFileName)
}
return
}
for currentVideoPath, infoStr := range videoPath2WidthHeightMap {
+38
View File
@@ -0,0 +1,38 @@
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) {
md5Str, err := util.CalcFileMd5(filePath)
if 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],
MimeType: vars.GlobalFilePath2MimeInfoMap[filePath],
MediaType: mediaType,
Width: width,
Height: height,
Duration: duration,
Codec: codec,
Format: format,
RawInfo: rawInfo,
}
if st, err := os.Stat(filePath); err == nil {
info.FileSize = st.Size()
}
if err := store.SaveMedia(info); err != nil {
fmt.Printf("=== 保存媒体信息失败: %s, 错误: %v\n", filePath, err)
}
}
+19 -2
View File
@@ -1,15 +1,32 @@
module kenaito-media-picker
go 1.19
go 1.25.0
require (
github.com/gabriel-vasile/mimetype v1.4.3
github.com/glebarez/go-sqlite v1.20.3
github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d
github.com/satori/go.uuid v1.2.0
golang.org/x/image v0.16.0
xorm.io/xorm v1.3.4
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
golang.org/x/net v0.17.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
golang.org/x/sys v0.13.0 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.20.4 // indirect
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
)
+321 -4
View File
@@ -1,17 +1,334 @@
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d h1:h/hohIqMUCML2Rp9BXXAu0I3ZR68d7eqMHLPNq7N2tg=
github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d/go.mod h1:LiYo3EFlYfk46Re4zgQysMo6yO3/kAXslB2fyMdl+uw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578 h1:VstopitMQi3hZP0fzvnsLmzXZdQGc4bEcgu24cp+d4M=
github.com/remyoudompheng/bigfft v0.0.0-20230126093431-47fa9a501578/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.4 h1:vWFKzR3DhGUDl5b4srhUjhDwjxkZAc4C7BFszpu0swI=
xorm.io/xorm v1.3.4/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
+17 -2
View File
@@ -7,6 +7,8 @@ import (
_ "image/jpeg" // 导入jpeg支持
_ "image/png" // 导入png支持
"kenaito-media-picker/core"
"kenaito-media-picker/store"
"kenaito-media-picker/util"
"kenaito-media-picker/vars"
"os"
"path/filepath"
@@ -27,10 +29,23 @@ func main() {
scanner := core.FileScanner{}
scanner.DoScan(rootDir)
scanner.DoFilter()
// 初始化媒体信息数据库(失败不影响整理,仅无法记录)
if err := store.Init(filepath.Join(rootDir, store.DBFileName)); err != nil {
fmt.Println("=== 初始化数据库失败(继续整理, 不记录媒体信息):", err)
}
defer store.Close()
if len(vars.GlobalImagePathList) > 0 || len(vars.GlobalMediaPathList) > 0 {
if err := util.ReleaseFFprobe(); err != nil {
fmt.Println("=== 释放解码器失败, 5秒后将自动退出", err)
time.Sleep(time.Second * 5)
return
}
}
if len(vars.GlobalImagePathList) > 0 {
core.DoHandleImage(pickerDir)
}
if len(vars.GlobalVideoPathList) > 0 {
core.DoHandleVideo(pickerDir)
if len(vars.GlobalMediaPathList) > 0 {
core.DoHandleMedia(pickerDir)
}
util.RemoveFFprobe()
}
+80
View File
@@ -0,0 +1,80 @@
package store
import (
"database/sql"
"time"
sqlite "github.com/glebarez/go-sqlite"
"xorm.io/xorm"
)
// DBFileName 数据库文件名(存放于被整理目录的根目录,扫描时跳过)
const DBFileName = "media-info.db"
// init 将纯Go的sqlite驱动注册为 xorm 认识的 sqlite3 驱动名(避免CGO依赖)
func init() {
sql.Register("sqlite3", &sqlite.Driver{})
}
// MediaInfo 媒体文件信息记录,按字段拆分存放,便于查询
type MediaInfo struct {
Id int64 `xorm:"pk autoincr 'id'"`
FileMd5 string `xorm:"char(32) index 'file_md5'"` // 文件MD5,唯一标识
FilePath string `xorm:"text 'file_path'"` // 扫描时的源路径
FileName string `xorm:"varchar(255) 'file_name'"` // 文件名(含扩展名)
FileExt string `xorm:"varchar(32) 'file_ext'"` // 扩展名
FileSize int64 `xorm:"bigint 'file_size'"` // 文件大小(字节)
MimeType string `xorm:"varchar(128) 'mime_type'"` // MIME类型
MediaType string `xorm:"varchar(16) index 'media_type'"` // image/video/audio
Width int `xorm:"int index 'width'"` // 宽(像素)
Height int `xorm:"int index 'height'"` // 高(像素)
Duration float64 `xorm:"real 'duration'"` // 时长(秒)
Codec string `xorm:"varchar(64) 'codec'"` // 视频编码,如h264/hevc
Format string `xorm:"varchar(64) 'format'"` // 容器格式,如mp4/mkv
RawInfo string `xorm:"text 'raw_info'"` // ffprobe原始JSON(完整信息兜底)
CreatedAt time.Time `xorm:"created 'created_at'"` // 首次入库时间
UpdatedAt time.Time `xorm:"updated 'updated_at'"` // 最近更新时间
}
var engine *xorm.Engine
// Init 初始化数据库引擎并同步表结构
func Init(dbPath string) error {
e, err := xorm.NewEngine("sqlite3", dbPath)
if err != nil {
return err
}
e.ShowSQL(false)
e.SetMaxOpenConns(1) // sqlite同一时刻只允许一个写连接
engine = e
return engine.Sync(new(MediaInfo))
}
// Close 关闭数据库连接
func Close() error {
if engine != nil {
err := engine.Close()
engine = nil
return err
}
return nil
}
// SaveMedia 按MD5去重保存/更新媒体信息
func SaveMedia(info *MediaInfo) error {
if engine == nil {
return nil
}
existing := new(MediaInfo)
has, err := engine.Where("file_md5 = ?", info.FileMd5).Get(existing)
if err != nil {
return err
}
if has {
info.Id = existing.Id
_, err = engine.ID(existing.Id).Update(info)
return err
}
_, err = engine.Insert(info)
return err
}
-13
View File
@@ -1,9 +1,7 @@
package util
import (
"fmt"
"strconv"
"time"
)
// String2int 字符串转int
@@ -15,17 +13,6 @@ func String2int(str string) int {
return intValue
}
// SecondsToHms 将秒数转换为小时、分钟、秒的格式
func SecondsToHms(seconds int) string {
t := time.Duration(seconds) * time.Second
h := t / time.Hour
t -= h * time.Hour
m := t / time.Minute
t -= m * time.Minute
s := t / time.Second
return fmt.Sprintf("%02d-%02d-%02d", h, m, s)
}
// CalcPercentage 计算percentage相对于total的百分比
func CalcPercentage(percentage int, total int) int {
return int(float64(percentage) / float64(total) * 100)
+17
View File
@@ -1,8 +1,11 @@
package util
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/gabriel-vasile/mimetype"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -44,6 +47,20 @@ 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
}
//// 移动文件到根目录
//func renameFile(rootDir string, modelType string, videoList []string, pathSeparator string) {
// total := len(videoList)
+126 -101
View File
@@ -1,140 +1,165 @@
package util
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"kenaito-media-picker/vars"
"github.com/redmask-hb/GoSimplePrint/goPrint"
uuid "github.com/satori/go.uuid"
)
var supportVideoTypes = []string{
".ts",
".flv",
".rm",
".avi",
".mp4",
".mov",
".mpg",
".mkv",
".m4v",
".rmvb",
".3gp",
".3g2",
".webm",
".wmv",
// FFprobeExeName 内嵌解码器文件名
const FFprobeExeName = "ffprobe.exe"
// FFprobeExePath 返回带当前目录前缀的可执行路径(Windows/Unix均可直接执行)
func FFprobeExePath() string {
return "." + string(os.PathSeparator) + FFprobeExeName
}
func getVideoDuration(ffmpegExecPath string, videoPath string) (float64, error) {
// ffprobe命令,-v error 用于减少输出信息,-show_entries format=duration -of compact=p=0,nk=1 用于只输出时长
cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", videoPath)
var stderr bytes.Buffer
cmd.Stderr = &stderr
output, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("ffprobe failed with error: %v, stderr: %q", err, stderr.String())
// ReleaseFFprobe 释放内嵌的ffprobe到当前目录
func ReleaseFFprobe() error {
return WriteByteArraysToFile(vars.VideoDecodeToolWin64, FFprobeExeName)
}
// 解析输出的时长字符串为浮点数
durationStr := strings.TrimSpace(string(output))
duration, err := strconv.ParseFloat(durationStr, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse duration: %q, error: %v", durationStr, err)
// RemoveFFprobe 删除当前目录下的ffprobe
func RemoveFFprobe() {
if CheckFileIsExist(FFprobeExeName) {
_ = os.Remove(FFprobeExeName)
}
}
return duration, nil
// 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"`
}
func getVideoResolution(ffmpegExecPath string, filePath string) (width int, height int, err error) {
// 构建ffprobe命令
cmd := exec.Command(ffmpegExecPath, "-v", "error", "-show_entries", "stream=width,height", "-of", "csv=p=0:s=x", filePath)
// 执行命令并捕获输出
output, err := cmd.Output()
if err != nil {
return 0, 0, fmt.Errorf("failed to run ffprobe: %w", err)
}
// 解析输出字符串,格式应为 "宽度,高度"
resolutionStr := strings.TrimSpace(string(output))
parts := strings.Split(resolutionStr, ",")
if len(parts) == 2 {
width = String2int(parts[0])
height = String2int(parts[1])
return width, height, nil
}
parts = strings.Split(resolutionStr, "x")
if len(parts) == 2 {
width = String2int(parts[0])
height = String2int(parts[1])
return width, height, nil
}
parts = strings.Split(resolutionStr, "\r\n\r\n\r\n\r\n")
if len(parts) == 2 {
tempHw := parts[0]
parts = strings.Split(tempHw, "x")
if len(parts) == 2 {
width = String2int(parts[0])
height = String2int(parts[1])
return width, height, nil
}
}
parts = strings.Split(resolutionStr, "x")
if len(parts) == 3 {
width = String2int(parts[0])
height = String2int(parts[1])
return width, height, nil
}
resolutionStr = strings.ReplaceAll(resolutionStr, "\r", "")
resolutionStr = strings.ReplaceAll(resolutionStr, "\n", "")
resolutionStr = strings.ReplaceAll(resolutionStr, "N/AxN/A", "")
parts = strings.Split(resolutionStr, "x")
if len(parts) == 2 {
width = String2int(parts[0])
height = String2int(parts[1])
return width, height, nil
}
return 0, 0, fmt.Errorf("invalid resolution format: %s", resolutionStr)
// FormatInfo ffprobe容器信息
type FormatInfo struct {
Duration string `json:"duration"`
Format string `json:"format_name"`
}
// ReadVideoDuration 获取视频的时长,单位秒
func ReadVideoDuration(videoFilePath string) int {
duration, err := getVideoDuration("./ffprobe.exe", videoFilePath)
if err != nil {
fmt.Println("=== Error getting video duration:", err)
return 0
}
//fmt.Printf("=== Video duration: %.2f seconds\n", duration)
return int(math.Floor(duration)) // 向下取整
// ProbeResult ffprobe探测结果
type ProbeResult struct {
Streams []StreamInfo `json:"streams"`
Format FormatInfo `json:"format"`
raw string
}
// ReadVideoWidthHeight 获取视频的分辨率
func ReadVideoWidthHeight(videoFilePath string) (int, int, error) {
width, height, err := getVideoResolution("./ffprobe.exe", videoFilePath)
if err != nil {
fmt.Printf("=== Error getting resolution: %v\n", err)
return 0, 0, err
// 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
}
//fmt.Printf("=== Video resolution: %dx%d\n", width, height)
return width, height, nil
}
return 0, 0
}
// IsSupportVideo 判断是否属于支持的视频
func IsSupportVideo(videoType string) bool {
for _, supportVideoType := range supportVideoTypes {
if strings.EqualFold(videoType, supportVideoType) {
// 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)
+2 -1
View File
@@ -4,6 +4,7 @@ var GlobalFilePathList []string // 所有文件的路
var GlobalFilePath2MimeInfoMap = make(map[string]string) // 文件路径和mime信息的映射关系
var GlobalFilePath2FileNameMap = make(map[string]string) // 文件路径和文件名称的映射关系
var GlobalFilePath2FileExtMap = make(map[string]string) // 文件路径和文件扩展名的映射关系
var GlobalVideoPathList []string // 所有视频文件的路径
var GlobalImagePathList []string // 所有图片文件的路径
var GlobalMediaPathList []string // 待ffprobe确认的媒体候选文件
var GlobalAudioPathList []string // 已确认的音频文件路径
var GlobalPickerDir string // 已整理文件的输出目录(白名单,扫描时跳过)
-2
View File
@@ -9,8 +9,6 @@ var VideoDecodeToolWin64 []byte
var VideoIgnorePathList []string // 忽略的文件路径
var VideoReadErrorPathList []string // 读取信息异常的路径
var VideoPath2WidthHeightMap = make(map[string]string) // 视频路径和宽高比
var VideoPath2WidthHeightTagMap = make(map[string]string) // 视频路径和宽高比[640x480]
var VideoPath2DurationMap = make(map[string]string) // 视频路径和时长
var VideoHorizontalNormalList []string
var VideoHorizontalGifList []string
var VideoHorizontal1KList []string