mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 19:46:38 +08:00
优化缩略图
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -152,6 +153,13 @@ func (s *ScraperService) writeArtworkDataToPath(dir, name, ctype string, data []
|
||||
return ""
|
||||
}
|
||||
dst := filepath.Join(dir, name+imageExtForContentType(ctype))
|
||||
// 内容与现有文件完全一致时不重写:重写会刷新源文件 mtime,而缩略图缓存键
|
||||
// 包含源文件大小与 mtime,一次「内容没变」的重新刮削会让该条目所有尺寸的
|
||||
// 缩略图一并作废,下次浏览全部重新解码。
|
||||
if artworkFileUnchanged(dst, data) {
|
||||
s.log.Debug("scrape artwork unchanged", zap.String("dst", dst))
|
||||
return dst
|
||||
}
|
||||
tmp, err := os.CreateTemp(dir, "img-*.tmp")
|
||||
if err != nil {
|
||||
s.log.Warn("scrape artwork temp create failed", zap.String("dir", dir), zap.Error(err))
|
||||
@@ -186,6 +194,26 @@ func (s *ScraperService) writeArtworkDataToPath(dir, name, ctype string, data []
|
||||
return dst
|
||||
}
|
||||
|
||||
// artworkFileUnchanged 报告 path 是否已经就是 data 这些字节。
|
||||
//
|
||||
// 刮削在刷新海报地址时会无条件重写 sidecar 图片,即使下载回来的字节一模一样。
|
||||
// 缩略图缓存键包含源文件的大小与 mtime,因此这种无谓的重写会让该条目所有尺寸
|
||||
// 的缩略图一起失效;保持一致时直接复用原文件可以保住 mtime。
|
||||
func artworkFileUnchanged(path string, data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || info.IsDir() || info.Size() != int64(len(data)) {
|
||||
return false
|
||||
}
|
||||
existing, err := os.ReadFile(path) // #nosec G304 -- path is built from a sanitized media directory.
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(existing, data)
|
||||
}
|
||||
|
||||
// imageExtForContentType maps a detected image MIME type to a file extension.
|
||||
// Unknown image types fall back to a generic ".img" so we never write an empty
|
||||
// extension that could confuse media players.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"go.uber.org/zap"
|
||||
@@ -62,6 +63,47 @@ func TestWriteArtworkDataToPathReplacesExistingSidecar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteArtworkDataToPathSkipsUnchangedSidecar 验证"内容一致就不重写":
|
||||
// 缩略图缓存键包含源文件的大小与 mtime,无谓的重写会让该条目所有尺寸的缩略图
|
||||
// 一起失效,下次浏览全部重新解码。
|
||||
func TestWriteArtworkDataToPathSkipsUnchangedSidecar(t *testing.T) {
|
||||
scraper := &ScraperService{log: zap.NewNop()}
|
||||
mediaDir := t.TempDir()
|
||||
dst := scraper.writeArtworkDataToPath(mediaDir, "poster", "image/jpeg", testJPEG)
|
||||
if dst == "" {
|
||||
t.Fatal("expected a written destination path")
|
||||
}
|
||||
// 把 mtime 拨回过去,再写入完全相同的字节:文件必须原样保留。
|
||||
past := time.Now().Add(-2 * time.Hour)
|
||||
if err := os.Chtimes(dst, past, past); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := scraper.writeArtworkDataToPath(mediaDir, "poster", "image/jpeg", testJPEG); got != dst {
|
||||
t.Fatalf("destination = %q, want %q", got, dst)
|
||||
}
|
||||
info, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if time.Since(info.ModTime()) < time.Minute {
|
||||
t.Fatalf("mtime = %v, want the original past timestamp (identical artwork was rewritten)", info.ModTime())
|
||||
}
|
||||
|
||||
// 内容变化时仍然必须覆盖。
|
||||
changed := append([]byte(nil), testJPEG...)
|
||||
changed[len(changed)-1] = 0x00
|
||||
if got := scraper.writeArtworkDataToPath(mediaDir, "poster", "image/jpeg", changed); got != dst {
|
||||
t.Fatalf("destination = %q, want %q", got, dst)
|
||||
}
|
||||
data, err := os.ReadFile(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(data, changed) {
|
||||
t.Fatal("changed artwork was not written")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldCropAdultPosterWhenTMDbArtworkMatchedCodePath(t *testing.T) {
|
||||
media := &model.Media{
|
||||
Path: filepath.Join(t.TempDir(), "IPX-235.mp4"),
|
||||
|
||||
Reference in New Issue
Block a user