feat: 增加 EXIF 旋转图片自动转正
- 解析 JPEG EXIF Orientation,方向 5/6/7/8 的竖拍横存图不再误入横屏目录 - 配置 ffmpeg 时用 ffmpeg 物理转正像素并保留原 EXIF;无 ffmpeg 时按显示方向归类 - 新增 ffmpeg.path / organize.rotateImages 配置项 - 补充 EXIF 解析与旋转幂等测试
This commit is contained in:
@@ -56,6 +56,7 @@ const (
|
||||
type Organizer struct {
|
||||
pickerDir string // 整理后的文件都放到这个根目录下
|
||||
ffprobePath string // ffprobe 程序路径;为空表示没有可用解码器
|
||||
ffmpegPath string // ffmpeg 程序路径;为空表示没有转正 EXIF 旋转图片的能力
|
||||
store *store.Store // 数据库;为 nil 表示不记录媒体信息
|
||||
mover *mover.Mover // 负责实际搬文件的家伙
|
||||
buckets map[string][]string // 目标目录 → 里面该放哪些文件(只记源路径)
|
||||
@@ -65,10 +66,11 @@ type Organizer struct {
|
||||
}
|
||||
|
||||
// New 创建整理器。store 可以为 nil——数据库打不开时,整理照常进行,只是不记入库信息。
|
||||
func New(pickerDir, ffprobePath string, st *store.Store) *Organizer {
|
||||
func New(pickerDir, ffprobePath, ffmpegPath string, st *store.Store) *Organizer {
|
||||
return &Organizer{
|
||||
pickerDir: pickerDir,
|
||||
ffprobePath: ffprobePath,
|
||||
ffmpegPath: ffmpegPath,
|
||||
store: st,
|
||||
mover: &mover.Mover{},
|
||||
buckets: make(map[string][]string),
|
||||
@@ -295,6 +297,10 @@ func (o *Organizer) probeImage(f scanner.File) imageProbeResult {
|
||||
return r
|
||||
}
|
||||
r.res = res
|
||||
// 先处理 EXIF 旋转:带旋转标记的图,要么用 ffmpeg 把像素转正(文件变了),
|
||||
// 要么退而求其次按"显示方向"归类(宽高对调)。
|
||||
// 必须在算 MD5 / 分析明暗之前做——转正会改文件内容,得让 MD5 对得上最终文件。
|
||||
o.correctImageRotation(f, r.res)
|
||||
// 算 MD5(顺便拿指纹,可能省掉全量计算)
|
||||
r.md5Str, r.headMd5, r.tailMd5, r.size = o.calcFileInfo(f)
|
||||
// 分析明暗(GIF 不做,动画图逐帧分析没意义)
|
||||
@@ -309,6 +315,40 @@ func (o *Organizer) probeImage(f scanner.File) imageProbeResult {
|
||||
return r
|
||||
}
|
||||
|
||||
// correctImageRotation 处理图片的 EXIF 旋转标记,保证分类方向正确。
|
||||
//
|
||||
// 手机/相机竖拍的图,文件里常横着存像素、再打一个 EXIF 旋转标记(Orientation=5~8),
|
||||
// 查看器会自动转正显示,但我们读到的宽高是"横着存"的,会把竖图错判成横图。
|
||||
//
|
||||
// 两种处理方式(按优先级):
|
||||
// 1. 配置了 ffmpeg 且允许转正:用 ffmpeg 把像素真正转正(烧进旋转),文件内容会变;
|
||||
// 2. 没有 ffmpeg:不改文件,只把宽高对调,按"显示方向"归类。
|
||||
//
|
||||
// 只有方向 5/6/7/8(90°/270° 旋转)才需要宽高对调;2/3/4 是镜像或 180°,横竖不变。
|
||||
func (o *Organizer) correctImageRotation(f scanner.File, res *probe.ProbeResult) {
|
||||
ori := probe.ReadExifOrientation(f.Path)
|
||||
if ori == 0 {
|
||||
return // 没有旋转标记,不用管
|
||||
}
|
||||
// 有 ffmpeg 且允许转正:真把图转过来
|
||||
if o.ffmpegPath != "" {
|
||||
if err := probe.RotateImage(o.ffmpegPath, f.Path); err != nil {
|
||||
fmt.Printf("=== 图片转正失败(按显示方向归类): %s, 错误: %v \n", f.Path, err)
|
||||
} else {
|
||||
// 转正后文件变了,重新读一遍尺寸,保证分类和实际文件一致
|
||||
if res2, err := probe.ProbeImage(f.Path, o.ffprobePath); err == nil {
|
||||
res.Width, res.Height = res2.Width, res2.Height
|
||||
}
|
||||
fmt.Printf("=== 已转正图片: %s (Orientation=%d)\n", f.Path, ori)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 没有 ffmpeg(或转正失败):退而求其次,按显示方向对调宽高归类
|
||||
if probe.NeedsDimensionSwap(ori) {
|
||||
res.Width, res.Height = res.Height, res.Width
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessImages 处理全部图片:先并行体检,再按顺序一个个归类。
|
||||
func (o *Organizer) ProcessImages(images []scanner.File) {
|
||||
// 统计结果,最后打一行总结给用户看
|
||||
|
||||
Reference in New Issue
Block a user