- 解析 JPEG EXIF Orientation,方向 5/6/7/8 的竖拍横存图不再误入横屏目录 - 配置 ffmpeg 时用 ffmpeg 物理转正像素并保留原 EXIF;无 ffmpeg 时按显示方向归类 - 新增 ffmpeg.path / organize.rotateImages 配置项 - 补充 EXIF 解析与旋转幂等测试
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
package probe
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
)
|
||
|
||
// buildJpegWithOrientation 拼一个带 Exif 方向标记的最小 JPEG:
|
||
// SOI + APP1(Exif) + EOI,APP1 里只放 TIFF 头 + IFD0 一条 Orientation。
|
||
func buildJpegWithOrientation(t *testing.T, orientation uint16, bigEndian bool) []byte {
|
||
t.Helper()
|
||
var order binary.ByteOrder = binary.LittleEndian
|
||
if bigEndian {
|
||
order = binary.BigEndian
|
||
}
|
||
bom := []byte("II")
|
||
if bigEndian {
|
||
bom = []byte("MM")
|
||
}
|
||
// TIFF 头(8) + IFD0 计数(2) + 一条目(12)
|
||
tiff := make([]byte, 0, 8+2+12)
|
||
tiff = append(tiff, bom...)
|
||
magic := make([]byte, 2)
|
||
order.PutUint16(magic, 42)
|
||
tiff = append(tiff, magic...)
|
||
ifdOff := make([]byte, 4)
|
||
order.PutUint32(ifdOff, 8)
|
||
tiff = append(tiff, ifdOff...)
|
||
count := make([]byte, 2)
|
||
order.PutUint16(count, 1)
|
||
tiff = append(tiff, count...)
|
||
// 一条目:tag=0x0112 type=SHORT count=1 value=orientation
|
||
entry := make([]byte, 12)
|
||
order.PutUint16(entry[0:2], 0x0112)
|
||
order.PutUint16(entry[2:4], 3) // SHORT
|
||
order.PutUint32(entry[4:8], 1) // count
|
||
order.PutUint16(entry[8:10], orientation)
|
||
tiff = append(tiff, entry...)
|
||
|
||
app1Data := append([]byte("Exif\x00\x00"), tiff...)
|
||
segLen := len(app1Data) + 2
|
||
out := []byte{0xFF, 0xD8} // SOI
|
||
out = append(out, 0xFF, 0xE1)
|
||
lenB := make([]byte, 2)
|
||
binary.BigEndian.PutUint16(lenB, uint16(segLen))
|
||
out = append(out, lenB...)
|
||
out = append(out, app1Data...)
|
||
out = append(out, 0xFF, 0xD9) // EOI
|
||
return out
|
||
}
|
||
|
||
func TestReadExifOrientation(t *testing.T) {
|
||
dir := t.TempDir()
|
||
cases := []struct {
|
||
name string
|
||
ori uint16
|
||
bigEnd bool
|
||
want int
|
||
}{
|
||
{"le6", 6, false, 6},
|
||
{"be8", 8, true, 8},
|
||
{"le1", 1, false, 1},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
p := filepath.Join(dir, c.name+".jpg")
|
||
if err := os.WriteFile(p, buildJpegWithOrientation(t, c.ori, c.bigEnd), 0644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := ReadExifOrientation(p); got != c.want {
|
||
t.Fatalf("ReadExifOrientation = %d, want %d", got, c.want)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 非 JPEG 返回 0
|
||
p := filepath.Join(dir, "notjpeg.txt")
|
||
if err := os.WriteFile(p, []byte("hello"), 0644); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := ReadExifOrientation(p); got != 0 {
|
||
t.Fatalf("non-jpeg ReadExifOrientation = %d, want 0", got)
|
||
}
|
||
// 不存在的文件返回 0
|
||
if got := ReadExifOrientation(filepath.Join(dir, "missing.jpg")); got != 0 {
|
||
t.Fatalf("missing ReadExifOrientation = %d, want 0", got)
|
||
}
|
||
}
|
||
|
||
func TestNeedsDimensionSwap(t *testing.T) {
|
||
cases := map[int]bool{
|
||
1: false, 2: false, 3: false, 4: false,
|
||
5: true, 6: true, 7: true, 8: true,
|
||
0: false, 9: false,
|
||
}
|
||
for ori, want := range cases {
|
||
if got := NeedsDimensionSwap(ori); got != want {
|
||
t.Fatalf("NeedsDimensionSwap(%d) = %v, want %v", ori, got, want)
|
||
}
|
||
}
|
||
}
|