Skip to content

Commit 9a74032

Browse files
committed
⚗️ Perf
1 parent d9482c7 commit 9a74032

File tree

11 files changed

+490
-200
lines changed

11 files changed

+490
-200
lines changed

.DS_Store

6 KB
Binary file not shown.

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
qumagie
2+
pics/*
3+
!pics/.gitkeep

.idea/photo_exif_do.iml

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@
3636
$ chmod +x /share/Public/quexif-linux-amd64-{{version}}
3737

3838
$ /share/Public/quexif-linux-amd64-{{version}} -p /share/Public/Photo
39-
```
39+
```
40+
41+
42+
## Thinks
43+
44+
- [go-exif](//github.com/dsoprea/go-exif/v3)

fg/fg.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func Parse() {
1414
flag.StringVar(&Mode, "m", "qumagie", "操作模式: qumagie (QuMagie 备份照片处理), dir (指定文件夹批量修改 EXIF时间), dirDate (按照上级文件夹名称修改 EXIF 时间)")
1515
flag.StringVar(&Path, "p", "", "文件夹路径")
1616
flag.StringVar(&DateTime, "datetime", "", "日期时间")
17-
flag.StringVar(&DateTpl, "tpl", "2006-01-02 15.04.05", "日期时间模板, 默认为 '2006-01-02 15.04.05' 请参照 Golang 时间 layout 设置")
17+
flag.StringVar(&DateTpl, "tpl", "2006-01-02 15.04.05", "日期时间模板, 默认为 '2006-01-02 15.04.05' 请参照 Golang 时间 layout 设置, 不适用于 QuMagie 模式")
1818

1919
flag.Parse()
2020
}

go.mod

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ go 1.23
55
require (
66
github.com/dsoprea/go-exif/v3 v3.0.1
77
github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102
8+
github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512210324-29b889a6093d
89
)
910

1011
require (
11-
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
12+
github.com/dsoprea/go-exif-extra v0.0.0-20210512210440-c683d9263a55 // indirect
13+
github.com/dsoprea/go-exif-knife v0.0.0-20210512212132-e3a47364f3e3 // indirect
14+
github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235 // indirect
15+
github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect
1216
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
13-
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect
17+
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d // indirect
18+
github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b // indirect
1419
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
20+
github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e // indirect
1521
github.com/go-errors/errors v1.4.2 // indirect
1622
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
1723
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
24+
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
25+
golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect
1826
golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect
1927
gopkg.in/yaml.v2 v2.4.0 // indirect
2028
)

go.sum

+238
Large diffs are not rendered by default.

main.go

+20-195
Original file line numberDiff line numberDiff line change
@@ -1,214 +1,39 @@
11
package main
22

33
import (
4-
"bytes"
5-
"fmt"
6-
"github.com/dsoprea/go-exif/v3"
7-
exifcommon "github.com/dsoprea/go-exif/v3/common"
8-
jpeg "github.com/dsoprea/go-jpeg-image-structure/v2"
94
"log"
10-
"os"
11-
"path/filepath"
125
"photo_exif_do/fg"
13-
"strings"
6+
"photo_exif_do/qumagie"
7+
"photo_exif_do/x_exif"
148
"time"
159
)
1610

17-
// getFileName 从文件路径中解析出文件名,并将其转换为时间对象
18-
// 时间格式为 "2006-01-02 15.04.05"
19-
func getFileName(filePath string) (time.Time, error) {
20-
// 从路径中提取文件名
21-
fileName := filepath.Base(filePath)
22-
23-
fileNameWithoutExt := strings.TrimSuffix(fileName, filepath.Ext(fileName))
24-
fileNameWithoutExt = fileNameWithoutExt[:19]
25-
26-
// 将文件名解析为时间
27-
layout := "2006-01-02 15.04.05"
28-
parsedTime, err := time.Parse(layout, fileNameWithoutExt)
29-
if err != nil {
30-
return time.Time{}, fmt.Errorf("解析时间失败: %v", err)
31-
}
32-
33-
return parsedTime, nil
34-
}
35-
36-
// ReadExif 获取exif 中的 DateTimeOriginal
37-
func ReadExif(path string) string {
38-
opt := exif.ScanOptions{}
39-
dt, err := exif.SearchFileAndExtractExif(path)
40-
if err != nil {
41-
fmt.Println(err)
42-
return ""
43-
}
44-
ets, _, err := exif.GetFlatExifData(dt, &opt)
45-
if err != nil {
46-
fmt.Println(err)
47-
return ""
48-
}
49-
for _, et := range ets {
50-
if et.TagName == "DateTimeOriginal" {
51-
return et.Value.(string)
52-
}
53-
}
54-
55-
return ""
56-
}
57-
58-
// setExifTag 设置exif标签
59-
func setExifTag(rootIB *exif.IfdBuilder, ifdPath, tagName, tagValue string) error {
60-
// fmt.Printf("setTag(): ifdPath: %v, tagName: %v, tagValue: %v",
61-
// ifdPath, tagName, tagValue)
62-
63-
ifdIb, err := exif.GetOrCreateIbFromRootIb(rootIB, ifdPath)
64-
if err != nil {
65-
return fmt.Errorf("failed to get or create IB: %v", err)
66-
}
67-
68-
if err := ifdIb.SetStandardWithName(tagName, tagValue); err != nil {
69-
return fmt.Errorf("failed to set DateTime tag: %v", err)
70-
}
71-
72-
return nil
73-
}
74-
75-
// setDateIfNone 为文件设置日期 如果已经存在则跳过
76-
func setDateIfNone(filePath string, counter int) error {
77-
parser := jpeg.NewJpegMediaParser()
78-
intfc, err := parser.ParseFile(filePath)
79-
if err != nil {
80-
return fmt.Errorf("failed to parse JPEG file: %v", err)
81-
}
82-
83-
sl := intfc.(*jpeg.SegmentList)
84-
85-
rootIb, err := sl.ConstructExifBuilder()
86-
if err != nil {
87-
//fmt.Println("No EXIF; creating it from scratch")
88-
89-
im, err := exifcommon.NewIfdMappingWithStandard()
90-
if err != nil {
91-
return fmt.Errorf("failed to create new IFD mapping with standard tags: %v", err)
92-
}
93-
ti := exif.NewTagIndex()
94-
if err := exif.LoadStandardTags(ti); err != nil {
95-
return fmt.Errorf("failed to load standard tags: %v", err)
96-
}
97-
98-
rootIb = exif.NewIfdBuilder(im, ti, exifcommon.IfdStandardIfdIdentity,
99-
exifcommon.EncodeDefaultByteOrder)
100-
}
101-
102-
// 检测是否已经有日期
103-
aDate := ReadExif(filePath)
104-
if aDate != "" {
105-
log.Printf("[SKIP](%d) %s already has date: %s\n", counter, filepath.Base(filePath), aDate)
106-
return nil
107-
}
108-
109-
// Form our timestamp string
110-
// 从名称解析日期
111-
t, err := getFileName(filePath)
112-
if err != nil {
113-
return fmt.Errorf("从文件名解析日期失败: %v", err)
114-
}
115-
ts := exifcommon.ExifFullTimestampString(t)
116-
117-
// Set DateTime
118-
ifdPath := "IFD0"
119-
if err := setExifTag(rootIb, ifdPath, "DateTime", ts); err != nil {
120-
return fmt.Errorf("failed to set tag %v: %v", "DateTime", err)
121-
}
122-
123-
// Set DateTimeOriginal
124-
ifdPath = "IFD/Exif"
125-
if err := setExifTag(rootIb, ifdPath, "DateTimeOriginal", ts); err != nil {
126-
return fmt.Errorf("failed to set tag %v: %v", "DateTimeOriginal", err)
127-
}
128-
129-
// Update the exif segment.
130-
if err := sl.SetExif(rootIb); err != nil {
131-
return fmt.Errorf("failed to set EXIF to jpeg: %v", err)
132-
}
133-
134-
// Write the modified file
135-
b := new(bytes.Buffer)
136-
if err := sl.Write(b); err != nil {
137-
return fmt.Errorf("failed to create JPEG data: %v", err)
138-
}
139-
140-
// Save the file
141-
if err := os.WriteFile(filePath, b.Bytes(), 0644); err != nil {
142-
return fmt.Errorf("failed to write JPEG file: %v", err)
143-
}
144-
145-
log.Printf("[SUCC](%d) %s -> %s\n", counter, filepath.Base(filePath), ts)
146-
147-
return nil
148-
}
149-
150-
// removeEditStr 如果文件名中包含 (edited) 则自动重命名该文件
151-
// 主要用于处理 QFiling 归档文件
152-
func removeEditStr(path string) {
153-
if strings.Contains(path, "(edited)") {
154-
newPath := strings.ReplaceAll(path, "(edited)", "")
155-
if err := os.Rename(path, newPath); err != nil {
156-
log.Printf("重命名失败: %v\n", err)
157-
} else {
158-
log.Printf("重命名成功: %s -> %s\n", path, newPath)
159-
}
160-
}
161-
}
162-
163-
// quMagie 处理 QuMagie 备份的照片
164-
func quMagie(path string) {
165-
files, err := os.ReadDir(path)
166-
if err != nil {
167-
log.Fatal(err)
168-
}
169-
170-
counter := 1
171-
172-
// 遍历目录下的文件
173-
for _, file := range files {
174-
// QuMagie 不会有子文件夹, 所以不需要递归
175-
if file.IsDir() {
176-
continue
177-
}
178-
179-
filePath := filepath.Join(path, file.Name())
180-
if strings.HasSuffix(strings.ToLower(filePath), ".jpg") || strings.HasSuffix(strings.ToLower(filePath), ".jpeg") {
181-
if err := setDateIfNone(filePath, counter); err != nil {
182-
log.Printf("[FAIL](%d) %s: %v\n", counter, filePath, err)
183-
} else {
184-
removeEditStr(filePath)
185-
}
186-
} else {
187-
log.Printf("[SKIP](%d) %s\n", counter, filePath)
188-
}
189-
190-
counter++
191-
}
192-
}
193-
19411
func main() {
19512
fg.Parse()
19613

19714
// 安全 QA
198-
log.Println("请确保已经设置了快照,程序将会直接修改文件的 exif 元数据, 建议在使用前选择少量照片进行测试后再使用")
199-
log.Println("您确认要继续吗? (y/n)")
200-
var confirm string
201-
202-
if _, err := fmt.Scanln(&confirm); err != nil {
203-
return
204-
} else if confirm != "y" {
205-
log.Fatal("已取消")
206-
}
15+
//log.Println("请确保已经设置了快照,程序将会直接修改文件的 exif 元数据, 建议在使用前选择少量照片进行测试后再使用")
16+
//log.Println("您确认要继续吗? (y/n)")
17+
//var confirm string
18+
//
19+
//if _, err := fmt.Scanln(&confirm); err != nil {
20+
// return
21+
//} else if confirm != "y" {
22+
// log.Fatal("已取消")
23+
//}
20724

20825
// 读取目录
20926
switch fg.Mode {
27+
case "dir": // 指定文件夹批量修改
28+
break
29+
case "dirDate": //按照上级文件夹名称修改
30+
break
31+
case "test":
32+
log.Println(x_exif.SetDate("pics/qumagie/2006-01-02 15.04.05.jpg", time.Now(), true))
33+
34+
log.Println(x_exif.ReadExif("pics/qumagie/2006-01-02 15.04.05.jpg"))
21035
default:
211-
quMagie(fg.Path)
36+
qumagie.Run(fg.Path)
21237
}
21338

21439
}

pics/.gitkeep

Whitespace-only changes.

qumagie/qumagie.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package qumagie
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"photo_exif_do/x_exif"
10+
"regexp"
11+
"time"
12+
)
13+
14+
// getTime 从文件路径中解析出文件名,并将其转换为时间对象
15+
// 时间格式为 "2006-01-02 15.04.05"
16+
// NOTICE 仅适用于 QuMagie 备份的文件名格式
17+
func getTime(filePath string) (time.Time, error) {
18+
// 从路径中提取文件名
19+
fileName := filepath.Base(filePath)
20+
21+
pattern := `\d{4}-\d{2}-\d{2} \d{2}\.\d{2}\.\d{2}`
22+
re := regexp.MustCompile(pattern)
23+
timeStr := re.FindString(fileName)
24+
25+
// 将文件名解析为时间
26+
layout := "2006-01-02 15.04.05"
27+
parsedTime, err := time.Parse(layout, timeStr)
28+
if err != nil {
29+
return time.Time{}, fmt.Errorf("解析时间失败: %v", err)
30+
}
31+
32+
return parsedTime, nil
33+
}
34+
35+
// Run 处理 QuMagie 备份的照片
36+
func Run(path string) {
37+
files, err := os.ReadDir(path)
38+
if err != nil {
39+
log.Fatal(err)
40+
}
41+
42+
counter := 1
43+
44+
// 遍历目录下的文件
45+
for _, file := range files {
46+
// QuMagie 不会有子文件夹, 所以不需要递归
47+
if file.IsDir() {
48+
continue
49+
}
50+
51+
filePath := filepath.Join(path, file.Name())
52+
// 从路径中提取文件名
53+
filename := filepath.Base(filePath)
54+
55+
t, err := getTime(filePath)
56+
if err != nil {
57+
log.Printf("[SKIP](%d) %s -> %v\n", counter, filename, err)
58+
counter++
59+
continue
60+
}
61+
62+
if err := x_exif.SetDate(filePath, t, true); err == nil {
63+
log.Printf("[SUCC](%d) %s -> %s\n", counter, filename, t.Format("2006-01-02 15.04.05"))
64+
x_exif.RemoveEditStr(filePath)
65+
} else if errors.Is(err, x_exif.ErrAlreadyHasDate) || errors.Is(err, x_exif.ErrMediaTypeNotSupport) {
66+
log.Printf("[SKIP](%d) %s -> %v\n", counter, filename, err)
67+
} else {
68+
log.Printf("[FAIL](%d) %s -> %v\n", counter, filename, err)
69+
}
70+
71+
counter++
72+
}
73+
}

0 commit comments

Comments
 (0)