feat(core): 实现扫描并删除iPhone导出相册中的实况视频功能
- 新增文件扫描逻辑,遍历目录下所有文件并记录路径与元信息 - 新增核心处理逻辑,识别.HEIC图片及其对应的.MOV实况视频 - 自动将匹配的实况视频移动至 duplicates 目录以供用户确认删除 - 添加构建脚本build.sh用于编译和压缩可执行文件 - 更新.gitignore忽略.idea目录 - 重写README.md,明确工具用途、需求及效果预览 - 初始化模块依赖管理(go.mod, go.sum)及相关工具类和变量定义
This commit is contained in:
@@ -13,3 +13,4 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# kenaito-iphone-export-photo-remove-duplicates
|
||||
|
||||
#### Description
|
||||
IPhone导出的相册中存在图片和实况视频,这个工具用来删除图片对应的实况视频,从而减小占用空间。
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
@@ -1,37 +1,12 @@
|
||||
# kenaito-iphone-export-photo-remove-duplicates
|
||||
## IPhone导出相册删除实况工具
|
||||
|
||||
#### 介绍
|
||||
IPhone导出的相册中存在图片和实况视频,这个工具用来删除图片对应的实况视频,从而减小占用空间。
|
||||
#### 需求
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
- 扫描某个目录
|
||||
- 找到与照片对应的实况视频
|
||||
- 删除实况视频
|
||||
|
||||
#### 效果预览
|
||||

|
||||
|
||||
#### 安装教程
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 使用说明
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
|
||||
|
||||
#### 特技
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||

|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
rm -f kenaito-media-group-tool.exe
|
||||
rm -f kenaito-media-group-tool-release.exe
|
||||
# build
|
||||
go build -o kenaito-media-group-tool.exe main.go
|
||||
# upx compress
|
||||
./upx -o kenaito-media-group-tool-release.exe kenaito-media-group-tool.exe
|
||||
rm -f kenaito-media-group-tool.exe
|
||||
@@ -0,0 +1,41 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kenaito-media-group-tool/util"
|
||||
"kenaito-media-group-tool/vars"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileScanner struct {
|
||||
}
|
||||
|
||||
func (FileScanner) DoScan(rootDir string) {
|
||||
fmt.Printf("=== 开始扫描文件 \n")
|
||||
if err := filepath.Walk(rootDir, visitFile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 定义walkFn回调函数visit
|
||||
func visitFile(currentPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err // 如果有错误,直接返回
|
||||
}
|
||||
if !info.IsDir() {
|
||||
ext := path.Ext(currentPath)
|
||||
if ".exe" != ext {
|
||||
vars.GlobalFilePathList = append(vars.GlobalFilePathList, currentPath)
|
||||
// filename, include ext
|
||||
vars.GlobalFilePath2FileNameExtMap[currentPath] = filepath.Base(currentPath)
|
||||
// filename, exclude ext
|
||||
vars.GlobalFilePath2FileNameMap[currentPath] = util.GetFileNameWithoutExt(currentPath)
|
||||
// file ext
|
||||
vars.GlobalFilePath2FileExtMap[currentPath] = ext
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/redmask-hb/GoSimplePrint/goPrint"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"kenaito-media-group-tool/util"
|
||||
"kenaito-media-group-tool/vars"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DoDeleteLiveVideo(rootDir string) {
|
||||
pathSep := string(os.PathSeparator)
|
||||
uid := strings.ReplaceAll(uuid.NewV4().String(), "-", "")
|
||||
total := len(vars.GlobalFilePathList)
|
||||
fmt.Printf("=== 文件总数: %d \n", total)
|
||||
|
||||
for _, filePath := range vars.GlobalFilePathList {
|
||||
fileExt := vars.GlobalFilePath2FileExtMap[filePath]
|
||||
if ".HEIC" == fileExt {
|
||||
vars.GlobalHEICFilePathNameMap[filePath] = vars.GlobalFilePath2FileNameMap[filePath]
|
||||
continue
|
||||
}
|
||||
if ".MOV" == fileExt {
|
||||
vars.GlobalMOVFilePathPathNameMap[filePath] = vars.GlobalFilePath2FileNameMap[filePath]
|
||||
}
|
||||
}
|
||||
|
||||
var movFilePath []string
|
||||
for _, heicName := range vars.GlobalHEICFilePathNameMap {
|
||||
for movPath, movName := range vars.GlobalMOVFilePathPathNameMap {
|
||||
if heicName == movName {
|
||||
println(movPath)
|
||||
movFilePath = append(movFilePath, movPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Printf("=== 发现实况视频文件数量: %d\n", len(movFilePath))
|
||||
|
||||
duplicatesDir := rootDir + pathSep + "duplicates-" + uid
|
||||
util.CreateDir(duplicatesDir)
|
||||
|
||||
fmt.Printf("=== 开始移动实况视频文件")
|
||||
doMoveFileToDir(movFilePath, duplicatesDir)
|
||||
fmt.Printf("=== 实况视频已移动到: %v 中\n", duplicatesDir)
|
||||
|
||||
}
|
||||
|
||||
// 批量移动文件到目录
|
||||
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)
|
||||
count = count + 1
|
||||
bar.PrintBar(util.CalcPercentage(count, total))
|
||||
}
|
||||
bar.PrintEnd("=== Finish")
|
||||
}
|
||||
|
||||
// 移动文件到目录
|
||||
func moveFileToDir(sourceFilePath string, targetDirectory string) bool {
|
||||
splits := strings.Split(sourceFilePath, string(os.PathSeparator))
|
||||
fileName := splits[len(splits)-1]
|
||||
targetFilePath := targetDirectory + fileName
|
||||
err := os.Rename(sourceFilePath, targetFilePath)
|
||||
//fmt.Printf("=== 移动文件, 源: %s, 目标: %s \n", sourceFilePath, targetFilePath)
|
||||
return err == nil
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,15 @@
|
||||
module kenaito-media-group-tool
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.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
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d h1:h/hohIqMUCML2Rp9BXXAu0I3ZR68d7eqMHLPNq7N2tg=
|
||||
github.com/redmask-hb/GoSimplePrint v0.0.0-20210302075413-3a3af92bcb7d/go.mod h1:LiYo3EFlYfk46Re4zgQysMo6yO3/kAXslB2fyMdl+uw=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"kenaito-media-group-tool/core"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== 警告:该程序会改变目录结构,将在5秒后开始执行")
|
||||
time.Sleep(time.Second * 5)
|
||||
rootDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println("=== 获取当前路径异常", err)
|
||||
return
|
||||
}
|
||||
scanner := core.FileScanner{}
|
||||
scanner.DoScan(rootDir)
|
||||
core.DoDeleteLiveVideo(rootDir)
|
||||
fmt.Println("=== 3s后自动退出")
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CreateDir 创建目录
|
||||
func CreateDir(dirPath string) bool {
|
||||
err := os.Mkdir(dirPath, 0755)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// CalcPercentage 计算percentage相对于total的百分比
|
||||
func CalcPercentage(percentage int, total int) int {
|
||||
return int(float64(percentage) / float64(total) * 100)
|
||||
}
|
||||
|
||||
func GetFileNameWithoutExt(path string) string {
|
||||
// 获取文件名(包含后缀)
|
||||
filename := filepath.Base(path)
|
||||
// 去除后缀
|
||||
ext := filepath.Ext(filename)
|
||||
name := filename[:len(filename)-len(ext)]
|
||||
return name
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package vars
|
||||
|
||||
var GlobalFilePathList []string // 所有文件的路径
|
||||
var GlobalFilePath2FileNameExtMap = make(map[string]string) // 文件路径和文件名称(包含后缀)的映射关系
|
||||
var GlobalFilePath2FileNameMap = make(map[string]string) // 文件路径和文件名称(不包含后缀)的映射关系
|
||||
var GlobalFilePath2FileExtMap = make(map[string]string) // 文件路径和文件扩展名的映射关系
|
||||
|
||||
var GlobalHEICFilePathNameMap = make(map[string]string) // 文件路径和HEIC文件名称的映射关系
|
||||
var GlobalMOVFilePathPathNameMap = make(map[string]string) // 文件路径和MOV文件名称的映射关系
|
||||
Reference in New Issue
Block a user