Renames the config flag to from "megapixel-limit" to "resolution-limit". Adds native support for the WebP image file format.
This commit is contained in:
parent
05a18bf6f2
commit
a604e9a9c6
34 changed files with 833 additions and 485 deletions
|
@ -49,12 +49,8 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||
|
||||
ind := service.Index()
|
||||
|
||||
indOpt := photoprism.IndexOptions{
|
||||
Rescan: f.Rescan,
|
||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||
Path: filepath.Clean(f.Path),
|
||||
Stack: true,
|
||||
}
|
||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false)
|
||||
|
||||
if len(indOpt.Path) > 1 {
|
||||
event.InfoMsg(i18n.MsgIndexingFiles, sanitize.Log(indOpt.Path))
|
||||
|
|
|
@ -59,12 +59,8 @@ func Index() error {
|
|||
|
||||
ind := service.Index()
|
||||
|
||||
indOpt := photoprism.IndexOptions{
|
||||
Rescan: false,
|
||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||
Path: entity.RootPath,
|
||||
Stack: true,
|
||||
}
|
||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false)
|
||||
|
||||
indexed := ind.Start(indOpt)
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func configAction(ctx *cli.Context) error {
|
|||
// Originals.
|
||||
fmt.Printf("%-25s %s\n", "originals-path", conf.OriginalsPath())
|
||||
fmt.Printf("%-25s %d\n", "originals-limit", conf.OriginalsLimit())
|
||||
fmt.Printf("%-25s %d\n", "megapixel-limit", conf.MegapixelLimit())
|
||||
fmt.Printf("%-25s %d\n", "resolution-limit", conf.ResolutionLimit())
|
||||
|
||||
// Other paths.
|
||||
fmt.Printf("%-25s %s\n", "storage-path", conf.StoragePath())
|
||||
|
|
|
@ -251,13 +251,8 @@ func facesIndexAction(ctx *cli.Context) error {
|
|||
var indexed fs.Done
|
||||
|
||||
if w := service.Index(); w != nil {
|
||||
opt := photoprism.IndexOptions{
|
||||
Path: subPath,
|
||||
Rescan: true,
|
||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||
Stack: true,
|
||||
FacesOnly: true,
|
||||
}
|
||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||
opt := photoprism.NewIndexOptions(subPath, true, convert, true, true)
|
||||
|
||||
indexed = w.Start(opt)
|
||||
}
|
||||
|
|
|
@ -69,12 +69,8 @@ func indexAction(ctx *cli.Context) error {
|
|||
var indexed fs.Done
|
||||
|
||||
if w := service.Index(); w != nil {
|
||||
opt := photoprism.IndexOptions{
|
||||
Path: subPath,
|
||||
Rescan: ctx.Bool("force"),
|
||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||
Stack: true,
|
||||
}
|
||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||
opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false)
|
||||
|
||||
indexed = w.Start(opt)
|
||||
}
|
||||
|
|
|
@ -578,8 +578,8 @@ func (c *Config) GeoApi() string {
|
|||
return "places"
|
||||
}
|
||||
|
||||
// OriginalsLimit returns the maximum size of originals in megabytes.
|
||||
func (c *Config) OriginalsLimit() int64 {
|
||||
// OriginalsLimit returns the maximum size of originals in MB.
|
||||
func (c *Config) OriginalsLimit() int {
|
||||
if c.options.OriginalsLimit <= 0 || c.options.OriginalsLimit > 100000 {
|
||||
return -1
|
||||
}
|
||||
|
@ -589,26 +589,24 @@ func (c *Config) OriginalsLimit() int64 {
|
|||
|
||||
// OriginalsLimitBytes returns the maximum size of originals in bytes.
|
||||
func (c *Config) OriginalsLimitBytes() int64 {
|
||||
if megabyte := c.OriginalsLimit(); megabyte < 1 {
|
||||
if result := c.OriginalsLimit(); result <= 0 {
|
||||
return -1
|
||||
} else {
|
||||
return megabyte * 1024 * 1024
|
||||
return int64(result) * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
// MegapixelLimit returns the maximum resolution of originals in megapixels (width x height).
|
||||
func (c *Config) MegapixelLimit() int {
|
||||
mp := c.options.MegapixelLimit
|
||||
// ResolutionLimit returns the maximum resolution of originals in megapixels (width x height).
|
||||
func (c *Config) ResolutionLimit() int {
|
||||
result := c.options.ResolutionLimit
|
||||
|
||||
if mp < 0 {
|
||||
if result <= 0 {
|
||||
return -1
|
||||
} else if c.options.MegapixelLimit > 900 {
|
||||
mp = 900
|
||||
} else if c.options.MegapixelLimit == 0 {
|
||||
mp = 100
|
||||
} else if result > 900 {
|
||||
result = 900
|
||||
}
|
||||
|
||||
return mp
|
||||
return result
|
||||
}
|
||||
|
||||
// UpdateHub updates backend api credentials for maps & places.
|
||||
|
|
|
@ -306,9 +306,9 @@ func TestConfig_GeoApi(t *testing.T) {
|
|||
func TestConfig_OriginalsLimit(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
assert.Equal(t, int64(-1), c.OriginalsLimit())
|
||||
assert.Equal(t, -1, c.OriginalsLimit())
|
||||
c.options.OriginalsLimit = 800
|
||||
assert.Equal(t, int64(800), c.OriginalsLimit())
|
||||
assert.Equal(t, 800, c.OriginalsLimit())
|
||||
}
|
||||
|
||||
func TestConfig_OriginalsLimitBytes(t *testing.T) {
|
||||
|
@ -319,16 +319,18 @@ func TestConfig_OriginalsLimitBytes(t *testing.T) {
|
|||
assert.Equal(t, int64(838860800), c.OriginalsLimitBytes())
|
||||
}
|
||||
|
||||
func TestConfig_MegapixelLimit(t *testing.T) {
|
||||
func TestConfig_ResolutionLimit(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
assert.Equal(t, 100, c.MegapixelLimit())
|
||||
c.options.MegapixelLimit = 800
|
||||
assert.Equal(t, 800, c.MegapixelLimit())
|
||||
c.options.MegapixelLimit = 950
|
||||
assert.Equal(t, 900, c.MegapixelLimit())
|
||||
c.options.MegapixelLimit = -1
|
||||
assert.Equal(t, -1, c.MegapixelLimit())
|
||||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
c.options.ResolutionLimit = 800
|
||||
assert.Equal(t, 800, c.ResolutionLimit())
|
||||
c.options.ResolutionLimit = 950
|
||||
assert.Equal(t, 900, c.ResolutionLimit())
|
||||
c.options.ResolutionLimit = 0
|
||||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
c.options.ResolutionLimit = -1
|
||||
assert.Equal(t, -1, c.ResolutionLimit())
|
||||
}
|
||||
|
||||
func TestConfig_BaseUri(t *testing.T) {
|
||||
|
|
|
@ -77,29 +77,29 @@ var GlobalFlags = []cli.Flag{
|
|||
EnvVar: "PHOTOPRISM_CONFIG_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config-path",
|
||||
Name: "config-path, conf",
|
||||
Usage: "config `PATH` to be searched for additional configuration and settings files",
|
||||
EnvVar: "PHOTOPRISM_CONFIG_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "originals-path, o",
|
||||
Name: "originals-path, media",
|
||||
Usage: "storage `PATH` of your original media files (photos and videos)",
|
||||
EnvVar: "PHOTOPRISM_ORIGINALS_PATH",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "originals-limit",
|
||||
Name: "originals-limit, max-mb",
|
||||
Value: 1000,
|
||||
Usage: "maximum size of media files in `MEGABYTES` (1-100000; -1 to disable)",
|
||||
EnvVar: "PHOTOPRISM_ORIGINALS_LIMIT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "megapixel-limit",
|
||||
Name: "resolution-limit, max-mp",
|
||||
Value: 100,
|
||||
Usage: "maximum resolution of media files in `MEGAPIXELS` (1-900; -1 to disable)",
|
||||
EnvVar: "PHOTOPRISM_MEGAPIXEL_LIMIT",
|
||||
EnvVar: "PHOTOPRISM_RESOLUTION_LIMIT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "storage-path, t",
|
||||
Name: "storage-path, storage",
|
||||
Usage: "writable storage `PATH` for cache, database, and sidecar files",
|
||||
EnvVar: "PHOTOPRISM_STORAGE_PATH",
|
||||
},
|
||||
|
@ -124,12 +124,12 @@ var GlobalFlags = []cli.Flag{
|
|||
EnvVar: "PHOTOPRISM_TEMP_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "backup-path, b",
|
||||
Name: "backup-path",
|
||||
Usage: "custom backup `PATH` for index backup files (optional)",
|
||||
EnvVar: "PHOTOPRISM_BACKUP_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "assets-path, a",
|
||||
Name: "assets-path",
|
||||
Usage: "assets `PATH` containing static resources like icons, models, and translations",
|
||||
EnvVar: "PHOTOPRISM_ASSETS_PATH",
|
||||
},
|
||||
|
@ -387,31 +387,31 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "darktable-bin",
|
||||
Usage: "Darktable CLI `COMMAND` for RAW image conversion",
|
||||
Usage: "Darktable CLI `COMMAND` for RAW to JPEG conversion",
|
||||
Value: "darktable-cli",
|
||||
EnvVar: "PHOTOPRISM_DARKTABLE_BIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "darktable-blacklist",
|
||||
Usage: "file `EXTENSIONS` incompatible with Darktable",
|
||||
Usage: "do not use Darktable to convert files with these `EXTENSIONS`",
|
||||
Value: "dng,cr3",
|
||||
EnvVar: "PHOTOPRISM_DARKTABLE_BLACKLIST",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "rawtherapee-bin",
|
||||
Usage: "RawTherapee CLI `COMMAND` for RAW image conversion",
|
||||
Usage: "RawTherapee CLI `COMMAND` for RAW to JPEG conversion",
|
||||
Value: "rawtherapee-cli",
|
||||
EnvVar: "PHOTOPRISM_RAWTHERAPEE_BIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "rawtherapee-blacklist",
|
||||
Usage: "file `EXTENSIONS` incompatible with RawTherapee",
|
||||
Usage: "do not use RawTherapee to convert files with these `EXTENSIONS`",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_RAWTHERAPEE_BLACKLIST",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sips-bin",
|
||||
Usage: "Sips `COMMAND` for RAW image conversion (macOS only)",
|
||||
Usage: "Sips `COMMAND` for RAW to JPEG conversion (macOS only)",
|
||||
Value: "sips",
|
||||
EnvVar: "PHOTOPRISM_SIPS_BIN",
|
||||
},
|
||||
|
@ -469,7 +469,7 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-colorspace",
|
||||
Usage: "convert Apple Display P3 colors in thumbnails to standard color space",
|
||||
Usage: "convert Apple Display P3 colors in thumbnails to standard color space (\"\" to disable)",
|
||||
Value: "sRGB",
|
||||
EnvVar: "PHOTOPRISM_THUMB_COLORSPACE",
|
||||
},
|
||||
|
|
|
@ -51,8 +51,8 @@ type Options struct {
|
|||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
ConfigFile string `json:"-"`
|
||||
OriginalsPath string `yaml:"OriginalsPath" json:"-" flag:"originals-path"`
|
||||
OriginalsLimit int64 `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
MegapixelLimit int `yaml:"MegapixelLimit" json:"MegapixelLimit" flag:"megapixel-limit"`
|
||||
OriginalsLimit int `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
|
||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
|
|
|
@ -75,30 +75,32 @@ func NewTestOptions(pkg string) *Options {
|
|||
|
||||
// Test config options.
|
||||
c := &Options{
|
||||
Name: "PhotoPrism",
|
||||
Version: "0.0.0",
|
||||
Copyright: "(c) 2018-2022 Michael Mayer",
|
||||
Test: true,
|
||||
Debug: true,
|
||||
Public: true,
|
||||
Experimental: true,
|
||||
ReadOnly: false,
|
||||
DetectNSFW: true,
|
||||
UploadNSFW: false,
|
||||
ExifBruteForce: false,
|
||||
AssetsPath: assetsPath,
|
||||
AutoIndex: -1,
|
||||
AutoImport: 7200,
|
||||
StoragePath: testDataPath,
|
||||
CachePath: testDataPath + "/cache",
|
||||
OriginalsPath: testDataPath + "/originals",
|
||||
ImportPath: testDataPath + "/import",
|
||||
TempPath: testDataPath + "/temp",
|
||||
ConfigPath: testDataPath + "/config",
|
||||
SidecarPath: testDataPath + "/sidecar",
|
||||
DatabaseDriver: driver,
|
||||
DatabaseDsn: dsn,
|
||||
AdminPassword: "photoprism",
|
||||
Name: "PhotoPrism",
|
||||
Version: "0.0.0",
|
||||
Copyright: "(c) 2018-2022 Michael Mayer",
|
||||
Test: true,
|
||||
Debug: true,
|
||||
Public: true,
|
||||
Experimental: true,
|
||||
ReadOnly: false,
|
||||
DetectNSFW: true,
|
||||
UploadNSFW: false,
|
||||
ExifBruteForce: false,
|
||||
AssetsPath: assetsPath,
|
||||
AutoIndex: -1,
|
||||
AutoImport: 7200,
|
||||
StoragePath: testDataPath,
|
||||
CachePath: testDataPath + "/cache",
|
||||
OriginalsPath: testDataPath + "/originals",
|
||||
ImportPath: testDataPath + "/import",
|
||||
TempPath: testDataPath + "/temp",
|
||||
ConfigPath: testDataPath + "/config",
|
||||
SidecarPath: testDataPath + "/sidecar",
|
||||
DatabaseDriver: driver,
|
||||
DatabaseDsn: dsn,
|
||||
AdminPassword: "photoprism",
|
||||
OriginalsLimit: 66,
|
||||
ResolutionLimit: 33,
|
||||
}
|
||||
|
||||
return c
|
||||
|
|
|
@ -100,12 +100,8 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
|
|||
|
||||
filesImported := 0
|
||||
|
||||
indexOpt := IndexOptions{
|
||||
Path: "/",
|
||||
Rescan: true,
|
||||
Stack: true,
|
||||
Convert: imp.conf.Settings().Index.Convert && imp.conf.SidecarWritable(),
|
||||
}
|
||||
convert := imp.conf.Settings().Index.Convert && imp.conf.SidecarWritable()
|
||||
indexOpt := NewIndexOptions("/", true, convert, true, false)
|
||||
|
||||
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
|
||||
|
||||
|
|
|
@ -23,17 +23,19 @@ type ImportJob struct {
|
|||
func ImportWorker(jobs <-chan ImportJob) {
|
||||
for job := range jobs {
|
||||
var destMainFileName string
|
||||
related := job.Related
|
||||
|
||||
o := job.IndexOpt
|
||||
imp := job.Imp
|
||||
opt := job.ImportOpt
|
||||
indexOpt := job.IndexOpt
|
||||
importPath := job.ImportOpt.Path
|
||||
impOpt := job.ImportOpt
|
||||
impPath := job.ImportOpt.Path
|
||||
related := job.Related
|
||||
|
||||
if related.Main == nil {
|
||||
log.Warnf("import: %s belongs to no supported media file", sanitize.Log(fs.RelName(job.FileName, importPath)))
|
||||
log.Warnf("import: %s belongs to no supported media file", sanitize.Log(fs.RelName(job.FileName, impPath)))
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract metadata to a JSON file with Exiftool.
|
||||
if related.Main.NeedsExifToolJson() {
|
||||
if jsonName, err := imp.convert.ToJson(related.Main); err != nil {
|
||||
log.Debugf("import: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(related.Main.BaseName()))
|
||||
|
@ -44,7 +46,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
}
|
||||
|
||||
originalName := related.Main.RelName(importPath)
|
||||
originalName := related.Main.RelName(impPath)
|
||||
|
||||
event.Publish("import.file", event.Data{
|
||||
"fileName": originalName,
|
||||
|
@ -52,7 +54,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
})
|
||||
|
||||
for _, f := range related.Files {
|
||||
relFileName := f.RelName(importPath)
|
||||
relFileName := f.RelName(impPath)
|
||||
|
||||
if destFileName, err := imp.DestinationFilename(related.Main, f); err == nil {
|
||||
destDir := filepath.Dir(destFileName)
|
||||
|
@ -78,7 +80,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
log.Infof("import: moving related %s file %s to %s", f.FileType(), sanitize.Log(relFileName), sanitize.Log(fs.RelName(destFileName, imp.originalsPath())))
|
||||
}
|
||||
|
||||
if opt.Move {
|
||||
if impOpt.Move {
|
||||
if err := f.Move(destFileName); err != nil {
|
||||
logRelName := sanitize.Log(fs.RelName(destMainFileName, imp.originalsPath()))
|
||||
log.Debugf("import: %s", err.Error())
|
||||
|
@ -99,12 +101,12 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
// Do nothing.
|
||||
} else if file, err := entity.FirstFileByHash(fileHash); err != nil {
|
||||
// Do nothing.
|
||||
} else if err := entity.AddPhotoToAlbums(file.PhotoUID, opt.Albums); err != nil {
|
||||
} else if err := entity.AddPhotoToAlbums(file.PhotoUID, impOpt.Albums); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
// Remove duplicates to save storage.
|
||||
if opt.RemoveExistingFiles {
|
||||
if impOpt.RemoveExistingFiles {
|
||||
if err := f.Remove(); err != nil {
|
||||
log.Errorf("import: failed deleting %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
} else {
|
||||
|
@ -122,71 +124,81 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Extract metadata to a JSON file with Exiftool.
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := imp.convert.ToJson(f); err != nil {
|
||||
log.Debugf("import: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.BaseName()))
|
||||
log.Debugf("import: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.RootRelName()))
|
||||
} else {
|
||||
log.Debugf("import: created %s", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if indexOpt.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f); err != nil {
|
||||
log.Errorf("import: %s in %s (convert to jpeg)", err.Error(), sanitize.Log(fs.RelName(destMainFileName, imp.originalsPath())))
|
||||
log.Errorf("import: %s in %s (convert to jpeg)", err.Error(), sanitize.Log(f.RootRelName()))
|
||||
continue
|
||||
} else {
|
||||
log.Debugf("import: created %s", sanitize.Log(jpegFile.BaseName()))
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a JPEG and the configured default thumbnail sizes exist.
|
||||
if jpg, err := f.Jpeg(); err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
if err := jpg.ResampleDefault(imp.thumbPath(), false); err != nil {
|
||||
log.Errorf("import: %s in %s (resample)", err.Error(), sanitize.Log(jpg.BaseName()))
|
||||
continue
|
||||
}
|
||||
} else if exceeds, actual := jpg.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
log.Errorf("index: %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
continue
|
||||
} else if err := jpg.CreateThumbnails(imp.thumbPath(), false); err != nil {
|
||||
log.Errorf("import: failed creating thumbnails for %s (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// Find related files.
|
||||
related, err := f.RelatedFiles(imp.conf.Settings().StackSequences())
|
||||
|
||||
// Skip import if the finding related files results in an error.
|
||||
if err != nil {
|
||||
log.Errorf("import: %s in %s (find related files)", err.Error(), sanitize.Log(fs.RelName(destMainFileName, imp.originalsPath())))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
done := make(map[string]bool)
|
||||
ind := imp.index
|
||||
limitSize := ind.conf.OriginalsLimitBytes()
|
||||
|
||||
photoUID := ""
|
||||
|
||||
if related.Main != nil {
|
||||
f := related.Main
|
||||
|
||||
// Enforce file size limit for originals.
|
||||
if limitSize > 0 && f.FileSize() > limitSize {
|
||||
log.Warnf("import: %s exceeds file size limit (%d / %d megabyte)", sanitize.Log(f.BaseName()), f.FileSize()/(1024*1024), limitSize/(1024*1024))
|
||||
// Enforce file size and resolution limits.
|
||||
if exceeds, actual := f.ExceedsFileSize(o.OriginalsLimit); exceeds {
|
||||
log.Warnf("import: %s exceeds file size limit (%d / %d MB)", sanitize.Log(f.RootRelName()), actual, o.OriginalsLimit)
|
||||
continue
|
||||
} else if exceeds, actual = f.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
log.Warnf("import: %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
continue
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, indexOpt, originalName, "")
|
||||
// Index main MediaFile.
|
||||
res := ind.MediaFile(f, o, originalName, "")
|
||||
|
||||
log.Infof("import: %s main %s file %s", res, f.FileType(), sanitize.Log(f.RelName(ind.originalsPath())))
|
||||
// Log result.
|
||||
log.Infof("import: %s main %s file %s", res, f.FileType(), sanitize.Log(f.RootRelName()))
|
||||
done[f.FileName()] = true
|
||||
|
||||
if !res.Success() {
|
||||
// Skip importing related files if the main file was not indexed successfully.
|
||||
continue
|
||||
} else if res.PhotoUID != "" {
|
||||
photoUID = res.PhotoUID
|
||||
|
||||
if err := entity.AddPhotoToAlbums(photoUID, opt.Albums); err != nil {
|
||||
// Add photo to album if a list of albums was provided when importing.
|
||||
if err := entity.AddPhotoToAlbums(photoUID, impOpt.Albums); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warnf("import: found no main file for %s, conversion to jpeg may have failed", fs.RelName(destMainFileName, imp.originalsPath()))
|
||||
log.Warnf("import: found no main file for %s, conversion to jpeg may have failed", sanitize.Log(f.RootRelName()))
|
||||
}
|
||||
|
||||
for _, f := range related.Files {
|
||||
|
@ -200,30 +212,32 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
|
||||
done[f.FileName()] = true
|
||||
|
||||
// Enforce file size limit for originals.
|
||||
if limitSize > 0 && f.FileSize() > limitSize {
|
||||
log.Warnf("import: %s exceeds file size limit (%d / %d megabyte)", sanitize.Log(f.BaseName()), f.FileSize()/(1024*1024), limitSize/(1024*1024))
|
||||
continue
|
||||
// Show warning if sidecar file exceeds size or resolution limit.
|
||||
if exceeds, actual := f.ExceedsFileSize(o.OriginalsLimit); exceeds {
|
||||
log.Warnf("import: sidecar file %s exceeds size limit (%d / %d MB)", sanitize.Log(f.RootRelName()), actual, o.OriginalsLimit)
|
||||
} else if exceeds, actual = f.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
log.Warnf("import: sidecar file %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
}
|
||||
|
||||
// Extract metadata to a JSON file with Exiftool.
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := imp.convert.ToJson(f); err != nil {
|
||||
log.Debugf("import: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.BaseName()))
|
||||
log.Debugf("import: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.RootRelName()))
|
||||
} else {
|
||||
log.Debugf("import: created %s", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, indexOpt, "", photoUID)
|
||||
// Index related MediaFile.
|
||||
res := ind.MediaFile(f, o, "", photoUID)
|
||||
|
||||
if res.Indexed() && f.IsJpeg() {
|
||||
if err := f.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
log.Errorf("import: failed creating thumbnails for %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
query.SetFileError(res.FileUID, err.Error())
|
||||
}
|
||||
// Save file error.
|
||||
if fileUid, err := res.FileError(); err != nil {
|
||||
query.SetFileError(fileUid, err.Error())
|
||||
}
|
||||
|
||||
log.Infof("import: %s related %s file %s", res, f.FileType(), sanitize.Log(f.RelName(ind.originalsPath())))
|
||||
// Log result.
|
||||
log.Infof("import: %s related %s file %s", res, f.FileType(), sanitize.Log(f.RootRelName()))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func (ind *Index) Cancel() {
|
|||
}
|
||||
|
||||
// Start indexes media files in the "originals" folder.
|
||||
func (ind *Index) Start(opt IndexOptions) fs.Done {
|
||||
func (ind *Index) Start(o IndexOptions) fs.Done {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("index: %s (panic)\nstack: %s", r, debug.Stack())
|
||||
|
@ -86,7 +86,7 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
}
|
||||
|
||||
originalsPath := ind.originalsPath()
|
||||
optionsPath := filepath.Join(originalsPath, opt.Path)
|
||||
optionsPath := filepath.Join(originalsPath, o.Path)
|
||||
|
||||
if !fs.PathExists(optionsPath) {
|
||||
event.Error(fmt.Sprintf("index: %s does not exist", sanitize.Log(optionsPath)))
|
||||
|
@ -186,7 +186,7 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
return nil
|
||||
}
|
||||
|
||||
if ind.files.Indexed(relName, entity.RootOriginals, mf.modTime, opt.Rescan) {
|
||||
if ind.files.Indexed(relName, entity.RootOriginals, mf.modTime, o.Rescan) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
continue
|
||||
}
|
||||
|
||||
if f.FileSize() == 0 || ind.files.Indexed(f.RootRelName(), f.Root(), f.ModTime(), opt.Rescan) {
|
||||
if f.FileSize() == 0 || ind.files.Indexed(f.RootRelName(), f.Root(), f.ModTime(), o.Rescan) {
|
||||
done[f.FileName()] = fs.Found
|
||||
continue
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
jobs <- IndexJob{
|
||||
FileName: mf.FileName(),
|
||||
Related: related,
|
||||
IndexOpt: opt,
|
||||
IndexOpt: o,
|
||||
Ind: ind,
|
||||
}
|
||||
|
||||
|
|
78
internal/photoprism/index_main.go
Normal file
78
internal/photoprism/index_main.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
)
|
||||
|
||||
// IndexMain indexes the main file from a group of related files and returns the result.
|
||||
func IndexMain(related *RelatedFiles, ind *Index, o IndexOptions) (result IndexResult) {
|
||||
// Skip if main file is nil.
|
||||
if related.Main == nil {
|
||||
result.Err = fmt.Errorf("index: no main file for %s", sanitize.Log(related.String()))
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
f := related.Main
|
||||
|
||||
// Enforce file size and resolution limits.
|
||||
if exceeds, actual := f.ExceedsFileSize(o.OriginalsLimit); exceeds {
|
||||
result.Err = fmt.Errorf("index: %s exceeds file size limit (%d / %d MB)", sanitize.Log(f.RootRelName()), actual, o.OriginalsLimit)
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
} else if exceeds, actual = f.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
result.Err = fmt.Errorf("index: %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
// Extract metadata to a JSON file with Exiftool.
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.RootRelName()))
|
||||
} else {
|
||||
log.Debugf("index: created %s", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpg, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
} else if exceeds, actual := jpg.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
result.Err = fmt.Errorf("index: %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
} else {
|
||||
log.Debugf("index: created %s", sanitize.Log(jpg.BaseName()))
|
||||
|
||||
if err := jpg.CreateThumbnails(ind.thumbPath(), false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
related.Files = append(related.Files, jpg)
|
||||
}
|
||||
}
|
||||
|
||||
// Index main MediaFile.
|
||||
result = ind.MediaFile(f, o, "", "")
|
||||
|
||||
// Save file error.
|
||||
if fileUid, err := result.FileError(); err != nil {
|
||||
query.SetFileError(fileUid, err.Error())
|
||||
}
|
||||
|
||||
// Log index result.
|
||||
log.Infof("index: %s main %s file %s", result, f.FileType(), sanitize.Log(f.RootRelName()))
|
||||
|
||||
return result
|
||||
}
|
|
@ -130,13 +130,6 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName, photoUID
|
|||
}
|
||||
}
|
||||
|
||||
// Skip media files that exceed the configured resolution limit, unless the file has already been indexed.
|
||||
if !fileExists && m.ExceedsMegapixelLimit() {
|
||||
log.Warnf("index: %s exceeds resolution limit (%d / %d megapixels)", logName, m.Megapixels(), Config().MegapixelLimit())
|
||||
result.Status = IndexSkipped
|
||||
return result
|
||||
}
|
||||
|
||||
// Find existing photo if a photo uid was provided or file has not been indexed yet...
|
||||
if !fileExists && photoUID != "" {
|
||||
// Find existing photo by UID.
|
||||
|
@ -242,6 +235,13 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName, photoUID
|
|||
log.Error(err)
|
||||
}
|
||||
|
||||
// Create default thumbnails if needed.
|
||||
if err := m.CreateThumbnails(ind.thumbPath(), false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(m.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
// Fetch photo details such as keywords, subject, and artist.
|
||||
details := photo.GetDetails()
|
||||
|
||||
|
|
|
@ -41,7 +41,9 @@ func TestIndex_MediaFile(t *testing.T) {
|
|||
words := mediaFile.metaData.Keywords.String()
|
||||
|
||||
t.Logf("size in megapixel: %d", mediaFile.Megapixels())
|
||||
t.Logf("megapixel limit exceeded: %t", mediaFile.ExceedsMegapixelLimit())
|
||||
|
||||
exceeds, actual := mediaFile.ExceedsResolution(conf.ResolutionLimit())
|
||||
t.Logf("megapixel limit exceeded: %t, %d / %d MP", exceeds, actual, conf.ResolutionLimit())
|
||||
|
||||
assert.Contains(t, words, "marienkäfer")
|
||||
assert.Contains(t, words, "burst")
|
||||
|
|
|
@ -1,59 +1,52 @@
|
|||
package photoprism
|
||||
|
||||
// IndexOptions represents media file indexing options.
|
||||
type IndexOptions struct {
|
||||
Path string
|
||||
Rescan bool
|
||||
Convert bool
|
||||
Stack bool
|
||||
FacesOnly bool
|
||||
Path string
|
||||
Rescan bool
|
||||
Convert bool
|
||||
Stack bool
|
||||
FacesOnly bool
|
||||
OriginalsLimit int
|
||||
ResolutionLimit int
|
||||
}
|
||||
|
||||
// NewIndexOptions returns new index options instance.
|
||||
func NewIndexOptions(path string, rescan, convert, stack, facesOnly bool) IndexOptions {
|
||||
result := IndexOptions{
|
||||
Path: path,
|
||||
Rescan: rescan,
|
||||
Convert: convert,
|
||||
Stack: stack,
|
||||
FacesOnly: facesOnly,
|
||||
OriginalsLimit: Config().OriginalsLimit(),
|
||||
ResolutionLimit: Config().ResolutionLimit(),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SkipUnchanged checks if unchanged media files should be skipped.
|
||||
func (o *IndexOptions) SkipUnchanged() bool {
|
||||
return !o.Rescan
|
||||
}
|
||||
|
||||
// IndexOptionsAll returns new index options with all options set to true.
|
||||
func IndexOptionsAll() IndexOptions {
|
||||
result := IndexOptions{
|
||||
Path: "/",
|
||||
Rescan: true,
|
||||
Convert: true,
|
||||
Stack: true,
|
||||
FacesOnly: false,
|
||||
}
|
||||
|
||||
return result
|
||||
return NewIndexOptions("/", true, true, true, false)
|
||||
}
|
||||
|
||||
// IndexOptionsFacesOnly returns new index options for updating faces only.
|
||||
func IndexOptionsFacesOnly() IndexOptions {
|
||||
result := IndexOptions{
|
||||
Path: "/",
|
||||
Rescan: true,
|
||||
Convert: true,
|
||||
Stack: true,
|
||||
FacesOnly: true,
|
||||
}
|
||||
|
||||
return result
|
||||
return NewIndexOptions("/", true, true, true, true)
|
||||
}
|
||||
|
||||
// IndexOptionsSingle returns new index options for unstacked, single files.
|
||||
func IndexOptionsSingle() IndexOptions {
|
||||
result := IndexOptions{
|
||||
Path: "/",
|
||||
Rescan: true,
|
||||
Convert: true,
|
||||
Stack: false,
|
||||
FacesOnly: false,
|
||||
}
|
||||
|
||||
return result
|
||||
return NewIndexOptions("/", true, true, false, false)
|
||||
}
|
||||
|
||||
// IndexOptionsNone returns new index options with all options set to false.
|
||||
func IndexOptionsNone() IndexOptions {
|
||||
result := IndexOptions{}
|
||||
|
||||
return result
|
||||
return NewIndexOptions("", false, false, false, false)
|
||||
}
|
||||
|
|
|
@ -11,69 +11,8 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
)
|
||||
|
||||
// IndexMain indexes the main file from a group of related files and returns the result.
|
||||
func IndexMain(related *RelatedFiles, ind *Index, opt IndexOptions) (result IndexResult) {
|
||||
// Skip if main file is nil.
|
||||
if related.Main == nil {
|
||||
result.Err = fmt.Errorf("index: no main file for %s", sanitize.Log(related.String()))
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
f := related.Main
|
||||
limitSize := ind.conf.OriginalsLimitBytes()
|
||||
|
||||
// Enforce file size limit for originals.
|
||||
if limitSize > 0 && f.FileSize() > limitSize {
|
||||
result.Err = fmt.Errorf("index: %s exceeds file size limit (%d / %d megabyte)", sanitize.Log(f.BaseName()), f.FileSize()/(1024*1024), limitSize/(1024*1024))
|
||||
result.Status = IndexFailed
|
||||
return result
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("index: created %s", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
|
||||
return result
|
||||
} else {
|
||||
log.Debugf("index: created %s", sanitize.Log(jpegFile.BaseName()))
|
||||
|
||||
if err := jpegFile.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
related.Files = append(related.Files, jpegFile)
|
||||
}
|
||||
}
|
||||
|
||||
result = ind.MediaFile(f, opt, "", "")
|
||||
|
||||
if result.Indexed() && f.IsJpeg() {
|
||||
if err := f.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
log.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
query.SetFileError(result.FileUID, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("index: %s main %s file %s", result, f.FileType(), sanitize.Log(f.RelName(ind.originalsPath())))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IndexRelated indexes a group of related files and returns the result.
|
||||
func IndexRelated(related RelatedFiles, ind *Index, opt IndexOptions) (result IndexResult) {
|
||||
func IndexRelated(related RelatedFiles, ind *Index, o IndexOptions) (result IndexResult) {
|
||||
// Skip if main file is nil.
|
||||
if related.Main == nil {
|
||||
result.Err = fmt.Errorf("index: no main file for %s", sanitize.Log(related.String()))
|
||||
|
@ -82,12 +21,10 @@ func IndexRelated(related RelatedFiles, ind *Index, opt IndexOptions) (result In
|
|||
}
|
||||
|
||||
done := make(map[string]bool)
|
||||
sizeLimit := ind.conf.OriginalsLimitBytes()
|
||||
|
||||
result = IndexMain(&related, ind, opt)
|
||||
result = IndexMain(&related, ind, o)
|
||||
|
||||
if result.Failed() {
|
||||
log.Warn(result.Err)
|
||||
log.Error(result.Err)
|
||||
return result
|
||||
} else if !result.Success() {
|
||||
// Skip related files if indexing was not completely successful.
|
||||
|
@ -121,50 +58,51 @@ func IndexRelated(related RelatedFiles, ind *Index, opt IndexOptions) (result In
|
|||
|
||||
done[f.FileName()] = true
|
||||
|
||||
// Enforce file size limit for originals.
|
||||
if sizeLimit > 0 && f.FileSize() > sizeLimit {
|
||||
log.Warnf("index: %s exceeds file size limit (%d / %d megabyte)", sanitize.Log(f.BaseName()), f.FileSize()/(1024*1024), sizeLimit/(1024*1024))
|
||||
continue
|
||||
// Show warning if sidecar file exceeds size or resolution limit.
|
||||
if exceeds, actual := f.ExceedsFileSize(o.OriginalsLimit); exceeds {
|
||||
log.Warnf("index: sidecar file %s exceeds size limit (%d / %d MB)", sanitize.Log(f.RootRelName()), actual, o.OriginalsLimit)
|
||||
} else if exceeds, actual = f.ExceedsResolution(o.ResolutionLimit); exceeds {
|
||||
log.Warnf("index: sidecar file %s exceeds resolution limit (%d / %d MP)", sanitize.Log(f.RootRelName()), actual, o.ResolutionLimit)
|
||||
}
|
||||
|
||||
// Extract metadata to a JSON file with Exiftool.
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.BaseName()))
|
||||
log.Debugf("index: %s in %s (extract metadata)", sanitize.Log(err.Error()), sanitize.Log(f.RootRelName()))
|
||||
} else {
|
||||
log.Debugf("index: created %s", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.
|
||||
if o.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpg, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
|
||||
return result
|
||||
} else {
|
||||
log.Debugf("index: created %s", sanitize.Log(jpegFile.BaseName()))
|
||||
log.Debugf("index: created %s", sanitize.Log(jpg.BaseName()))
|
||||
|
||||
if err := jpegFile.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
if err := jpg.CreateThumbnails(ind.thumbPath(), false); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.RootRelName()), err.Error())
|
||||
result.Status = IndexFailed
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
related.Files = append(related.Files, jpegFile)
|
||||
related.Files = append(related.Files, jpg)
|
||||
}
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, opt, "", result.PhotoUID)
|
||||
// Index related MediaFile.
|
||||
res := ind.MediaFile(f, o, "", result.PhotoUID)
|
||||
|
||||
if res.Indexed() && f.IsJpeg() {
|
||||
if err := f.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
log.Errorf("index: failed creating thumbnails for %s (%s)", sanitize.Log(f.BaseName()), err.Error())
|
||||
query.SetFileError(res.FileUID, err.Error())
|
||||
}
|
||||
// Save file error.
|
||||
if fileUid, err := res.FileError(); err != nil {
|
||||
query.SetFileError(fileUid, err.Error())
|
||||
}
|
||||
|
||||
log.Infof("index: %s related %s file %s", res, f.FileType(), sanitize.Log(f.BaseName()))
|
||||
// Log index result.
|
||||
log.Infof("index: %s related %s file %s", res, f.FileType(), sanitize.Log(f.RootRelName()))
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -124,6 +124,7 @@ func TestIndexRelated(t *testing.T) {
|
|||
|
||||
result := IndexRelated(related, ind, opt)
|
||||
|
||||
assert.Nil(t, result.Err)
|
||||
assert.False(t, result.Failed())
|
||||
assert.False(t, result.Stacked())
|
||||
assert.True(t, result.Success())
|
||||
|
|
|
@ -12,6 +12,7 @@ const (
|
|||
|
||||
type IndexStatus string
|
||||
|
||||
// IndexResult represents a media file indexing result.
|
||||
type IndexResult struct {
|
||||
Status IndexStatus
|
||||
Err error
|
||||
|
@ -21,30 +22,46 @@ type IndexResult struct {
|
|||
PhotoUID string
|
||||
}
|
||||
|
||||
// String returns the indexing result as string.
|
||||
func (r IndexResult) String() string {
|
||||
return string(r.Status)
|
||||
}
|
||||
|
||||
func (r IndexResult) Failed() bool {
|
||||
return r.Err != nil
|
||||
}
|
||||
|
||||
// Success checks whether a media file was successfully indexed or skipped.
|
||||
func (r IndexResult) Success() bool {
|
||||
return r.Err == nil && (r.FileID > 0 || r.Stacked() || r.Skipped() || r.Archived())
|
||||
return !r.Failed() && (r.FileID > 0 || r.Stacked() || r.Skipped() || r.Archived())
|
||||
}
|
||||
|
||||
// Failed checks if indexing has failed.
|
||||
func (r IndexResult) Failed() bool {
|
||||
return r.Err != nil && r.Status == IndexFailed
|
||||
}
|
||||
|
||||
// Indexed checks whether a media file was successfully indexed.
|
||||
func (r IndexResult) Indexed() bool {
|
||||
return r.Status == IndexAdded || r.Status == IndexUpdated || r.Status == IndexStacked
|
||||
}
|
||||
|
||||
// Stacked checks whether a media file was stacked while indexing.
|
||||
func (r IndexResult) Stacked() bool {
|
||||
return r.Status == IndexStacked
|
||||
}
|
||||
|
||||
// Skipped checks whether a media file was skipped while indexing.
|
||||
func (r IndexResult) Skipped() bool {
|
||||
return r.Status == IndexSkipped
|
||||
}
|
||||
|
||||
// Archived checks whether a media file was skipped because it is archived.
|
||||
func (r IndexResult) Archived() bool {
|
||||
return r.Status == IndexArchived
|
||||
}
|
||||
|
||||
// FileError checks if there is a file error and returns it.
|
||||
func (r IndexResult) FileError() (string, error) {
|
||||
if r.Failed() && r.FileUID != "" {
|
||||
return r.FileUID, r.Err
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,20 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/djherbis/times"
|
||||
"github.com/dustin/go-humanize/english"
|
||||
|
@ -22,6 +31,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/meta"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/capture"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
|
@ -47,8 +57,10 @@ type MediaFile struct {
|
|||
width int
|
||||
height int
|
||||
metaData meta.Data
|
||||
metaDataOnce sync.Once
|
||||
metaOnce sync.Once
|
||||
fileMutex sync.Mutex
|
||||
location *entity.Cell
|
||||
imageConfig *image.Config
|
||||
}
|
||||
|
||||
// NewMediaFile returns a new media file.
|
||||
|
@ -123,7 +135,7 @@ func (m *MediaFile) TakenAt() (time.Time, string) {
|
|||
m.takenAt = data.TakenAt.UTC()
|
||||
m.takenAtSrc = entity.SrcMeta
|
||||
|
||||
log.Infof("media: %s was taken at %s (%s)", filepath.Base(m.fileName), m.takenAt.String(), m.takenAtSrc)
|
||||
log.Infof("media: %s was taken at %s (%s)", sanitize.Log(filepath.Base(m.fileName)), m.takenAt.String(), m.takenAtSrc)
|
||||
|
||||
return m.takenAt, m.takenAtSrc
|
||||
}
|
||||
|
@ -132,7 +144,7 @@ func (m *MediaFile) TakenAt() (time.Time, string) {
|
|||
m.takenAt = nameTime
|
||||
m.takenAtSrc = entity.SrcName
|
||||
|
||||
log.Infof("media: %s was taken at %s (%s)", filepath.Base(m.fileName), m.takenAt.String(), m.takenAtSrc)
|
||||
log.Infof("media: %s was taken at %s (%s)", sanitize.Log(filepath.Base(m.fileName)), m.takenAt.String(), m.takenAtSrc)
|
||||
|
||||
return m.takenAt, m.takenAtSrc
|
||||
}
|
||||
|
@ -143,17 +155,17 @@ func (m *MediaFile) TakenAt() (time.Time, string) {
|
|||
|
||||
if err != nil {
|
||||
log.Warnf("media: %s (file stat)", err.Error())
|
||||
log.Infof("media: %s was taken at %s (now)", filepath.Base(m.fileName), m.takenAt.String())
|
||||
log.Infof("media: %s was taken at %s (now)", sanitize.Log(filepath.Base(m.fileName)), m.takenAt.String())
|
||||
|
||||
return m.takenAt, m.takenAtSrc
|
||||
}
|
||||
|
||||
if fileInfo.HasBirthTime() {
|
||||
m.takenAt = fileInfo.BirthTime().UTC()
|
||||
log.Infof("media: %s was taken at %s (file birth time)", filepath.Base(m.fileName), m.takenAt.String())
|
||||
log.Infof("media: %s was taken at %s (file birth time)", sanitize.Log(filepath.Base(m.fileName)), m.takenAt.String())
|
||||
} else {
|
||||
m.takenAt = fileInfo.ModTime().UTC()
|
||||
log.Infof("media: %s was taken at %s (file mod time)", filepath.Base(m.fileName), m.takenAt.String())
|
||||
log.Infof("media: %s was taken at %s (file mod time)", sanitize.Log(filepath.Base(m.fileName)), m.takenAt.String())
|
||||
}
|
||||
|
||||
return m.takenAt, m.takenAtSrc
|
||||
|
@ -547,12 +559,15 @@ func (m *MediaFile) MimeType() string {
|
|||
return m.mimeType
|
||||
}
|
||||
|
||||
// openFile opens the file and returns the descriptor.
|
||||
func (m *MediaFile) openFile() (*os.File, error) {
|
||||
handle, err := os.Open(m.fileName)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
|
@ -609,6 +624,9 @@ func (m *MediaFile) Copy(dest string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
m.fileMutex.Lock()
|
||||
defer m.fileMutex.Unlock()
|
||||
|
||||
thisFile, err := m.openFile()
|
||||
|
||||
if err != nil {
|
||||
|
@ -652,31 +670,36 @@ func (m *MediaFile) IsJpeg() bool {
|
|||
return m.MimeType() == fs.MimeTypeJpeg
|
||||
}
|
||||
|
||||
// IsPng returns true if this is a PNG file.
|
||||
// IsPng returns true if this is a PNG image.
|
||||
func (m *MediaFile) IsPng() bool {
|
||||
return m.MimeType() == fs.MimeTypePng
|
||||
}
|
||||
|
||||
// IsGif returns true if this is a GIF file.
|
||||
// IsGif returns true if this is a GIF image.
|
||||
func (m *MediaFile) IsGif() bool {
|
||||
return m.MimeType() == fs.MimeTypeGif
|
||||
}
|
||||
|
||||
// IsTiff returns true if this is a TIFF file.
|
||||
// IsTiff returns true if this is a TIFF image.
|
||||
func (m *MediaFile) IsTiff() bool {
|
||||
return m.HasFileType(fs.FormatTiff) && m.MimeType() == fs.MimeTypeTiff
|
||||
}
|
||||
|
||||
// IsHEIF returns true if this is a High Efficiency Image File Format file.
|
||||
// IsHEIF returns true if this is a High Efficiency Image File Format image.
|
||||
func (m *MediaFile) IsHEIF() bool {
|
||||
return m.MimeType() == fs.MimeTypeHEIF
|
||||
}
|
||||
|
||||
// IsBitmap returns true if this is a bitmap file.
|
||||
// IsBitmap returns true if this is a bitmap image.
|
||||
func (m *MediaFile) IsBitmap() bool {
|
||||
return m.MimeType() == fs.MimeTypeBitmap
|
||||
}
|
||||
|
||||
// IsWebP returns true if this is a WebP image file.
|
||||
func (m *MediaFile) IsWebP() bool {
|
||||
return m.MimeType() == fs.MimeTypeWebP
|
||||
}
|
||||
|
||||
// IsVideo returns true if this is a video file.
|
||||
func (m *MediaFile) IsVideo() bool {
|
||||
return strings.HasPrefix(m.MimeType(), "video/") || m.MediaType() == fs.MediaVideo
|
||||
|
@ -724,16 +747,6 @@ func (m *MediaFile) IsRaw() bool {
|
|||
return m.HasFileType(fs.FormatRaw)
|
||||
}
|
||||
|
||||
// IsImageOther returns true if this is a PNG, GIF, BMP or TIFF file.
|
||||
func (m *MediaFile) IsImageOther() bool {
|
||||
switch {
|
||||
case m.IsPng(), m.IsGif(), m.IsTiff(), m.IsBitmap():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsXMP returns true if this is a XMP sidecar file.
|
||||
func (m *MediaFile) IsXMP() bool {
|
||||
return m.FileType() == fs.FormatXMP
|
||||
|
@ -749,9 +762,24 @@ func (m *MediaFile) IsPlayableVideo() bool {
|
|||
return m.IsVideo() && (m.HasFileType(fs.FormatMp4) || m.HasFileType(fs.FormatAvc))
|
||||
}
|
||||
|
||||
// IsImageOther returns true if this is a PNG, GIF, BMP, TIFF, or WebP file.
|
||||
func (m *MediaFile) IsImageOther() bool {
|
||||
switch {
|
||||
case m.IsPng(), m.IsGif(), m.IsTiff(), m.IsBitmap(), m.IsWebP():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsImageNative returns true if it is a natively supported image file.
|
||||
func (m *MediaFile) IsImageNative() bool {
|
||||
return m.IsJpeg() || m.IsImageOther()
|
||||
}
|
||||
|
||||
// IsImage checks if the file is an image
|
||||
func (m *MediaFile) IsImage() bool {
|
||||
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsImageOther()
|
||||
return m.IsImageNative() || m.IsRaw() || m.IsHEIF()
|
||||
}
|
||||
|
||||
// IsLive checks if the file is a live photo.
|
||||
|
@ -820,19 +848,17 @@ func (m *MediaFile) HasJpeg() bool {
|
|||
|
||||
func (m *MediaFile) decodeDimensions() error {
|
||||
if !m.IsMedia() {
|
||||
return fmt.Errorf("failed decoding dimensions for %s", sanitize.Log(m.BaseName()))
|
||||
return fmt.Errorf("failed decoding dimensions of %s file", sanitize.Log(m.Extension()))
|
||||
}
|
||||
|
||||
if m.IsJpeg() || m.IsPng() || m.IsGif() {
|
||||
file, err := os.Open(m.FileName())
|
||||
// Media dimensions already known?
|
||||
if m.width > 0 && m.height > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil || file == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
size, _, err := image.DecodeConfig(file)
|
||||
// Extract the actual width and height from natively supported formats.
|
||||
if m.IsImageNative() {
|
||||
cfg, err := m.DecodeConfig()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -841,20 +867,63 @@ func (m *MediaFile) decodeDimensions() error {
|
|||
orientation := m.Orientation()
|
||||
|
||||
if orientation > 4 && orientation <= 8 {
|
||||
m.width = size.Height
|
||||
m.height = size.Width
|
||||
m.width = cfg.Height
|
||||
m.height = cfg.Width
|
||||
} else {
|
||||
m.width = size.Width
|
||||
m.height = size.Height
|
||||
m.width = cfg.Width
|
||||
m.height = cfg.Height
|
||||
}
|
||||
} else if data := m.MetaData(); data.Error == nil {
|
||||
m.width = data.ActualWidth()
|
||||
m.height = data.ActualHeight()
|
||||
} else {
|
||||
return data.Error
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
// Extract the width and height from metadata for other formats.
|
||||
if data := m.MetaData(); data.Error != nil {
|
||||
return data.Error
|
||||
} else {
|
||||
m.width = data.ActualWidth()
|
||||
m.height = data.ActualHeight()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeConfig extracts the raw dimensions from the header of natively supported image file formats.
|
||||
func (m *MediaFile) DecodeConfig() (_ *image.Config, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("panic %s while decoding %s dimensions\nstack: %s", r, sanitize.Log(m.Extension()), debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
if m.imageConfig != nil {
|
||||
return m.imageConfig, nil
|
||||
}
|
||||
|
||||
if !m.IsImageNative() {
|
||||
return nil, fmt.Errorf("%s not supported natively", sanitize.Log(m.Extension()))
|
||||
}
|
||||
|
||||
m.fileMutex.Lock()
|
||||
defer m.fileMutex.Unlock()
|
||||
|
||||
file, err := os.Open(m.FileName())
|
||||
|
||||
if err != nil || file == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
cfg, _, err := image.DecodeConfig(file)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.imageConfig = &cfg
|
||||
|
||||
return m.imageConfig, nil
|
||||
}
|
||||
|
||||
// Width return the width dimension of a MediaFile.
|
||||
|
@ -887,6 +956,50 @@ func (m *MediaFile) Height() int {
|
|||
return m.height
|
||||
}
|
||||
|
||||
// Megapixels returns the resolution in megapixels if possible.
|
||||
func (m *MediaFile) Megapixels() (resolution int) {
|
||||
if !m.IsMedia() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if cfg, err := m.DecodeConfig(); err == nil {
|
||||
resolution = int(math.Round(float64(cfg.Width*cfg.Height) / 1000000))
|
||||
}
|
||||
|
||||
if resolution <= 0 {
|
||||
resolution = m.metaData.Megapixels()
|
||||
}
|
||||
|
||||
return resolution
|
||||
}
|
||||
|
||||
// ExceedsFileSize checks if the file exceeds the configured file size limit in MB.
|
||||
func (m *MediaFile) ExceedsFileSize(limit int) (exceeds bool, actual int) {
|
||||
const mega = 1048576
|
||||
|
||||
if limit <= 0 {
|
||||
return false, actual
|
||||
} else if size := m.FileSize(); size <= 0 {
|
||||
return false, actual
|
||||
} else {
|
||||
actual = int(size / mega)
|
||||
return size > int64(limit)*mega, actual
|
||||
}
|
||||
}
|
||||
|
||||
// ExceedsResolution checks if an image in a natively supported format exceeds the configured resolution limit in megapixels.
|
||||
func (m *MediaFile) ExceedsResolution(limit int) (exceeds bool, actual int) {
|
||||
if limit <= 0 {
|
||||
return false, actual
|
||||
} else if !m.IsImage() {
|
||||
return false, actual
|
||||
} else if actual = m.Megapixels(); actual <= 0 {
|
||||
return false, actual
|
||||
} else {
|
||||
return actual > limit, actual
|
||||
}
|
||||
}
|
||||
|
||||
// AspectRatio returns the aspect ratio of a MediaFile.
|
||||
func (m *MediaFile) AspectRatio() float32 {
|
||||
width := float64(m.Width())
|
||||
|
@ -906,36 +1019,6 @@ func (m *MediaFile) Portrait() bool {
|
|||
return m.Width() < m.Height()
|
||||
}
|
||||
|
||||
// Megapixels returns the resolution in megapixels if possible.
|
||||
func (m *MediaFile) Megapixels() (resolution int) {
|
||||
if !m.IsMedia() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if m.IsJpeg() || m.IsPng() || m.IsGif() {
|
||||
resolution = int(math.Round(float64(m.Width()*m.Height()) / 1000000))
|
||||
}
|
||||
|
||||
if resolution <= 0 {
|
||||
resolution = m.MetaData().Megapixels()
|
||||
}
|
||||
|
||||
return resolution
|
||||
}
|
||||
|
||||
// ExceedsMegapixelLimit checks if the media file exceeds the configured resolution limit in megapixels.
|
||||
func (m *MediaFile) ExceedsMegapixelLimit() bool {
|
||||
if !m.IsMedia() {
|
||||
return false
|
||||
} else if limit := Config().MegapixelLimit(); limit <= 0 {
|
||||
return false
|
||||
} else if mp := m.Megapixels(); mp <= 0 {
|
||||
return false
|
||||
} else {
|
||||
return mp > limit
|
||||
}
|
||||
}
|
||||
|
||||
// Orientation returns the Exif orientation of the media file.
|
||||
func (m *MediaFile) Orientation() int {
|
||||
if data := m.MetaData(); data.Error == nil {
|
||||
|
@ -976,8 +1059,14 @@ func (m *MediaFile) Resample(path string, sizeName thumb.Name) (img image.Image,
|
|||
return imaging.Open(filename)
|
||||
}
|
||||
|
||||
// ResampleDefault creates the configured default thumbnails at indexing time.
|
||||
func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
||||
// CreateThumbnails creates the default thumbnail sizes if the media file
|
||||
// is a JPEG and they don't exist yet (except force is true).
|
||||
func (m *MediaFile) CreateThumbnails(thumbPath string, force bool) (err error) {
|
||||
if !m.IsJpeg() {
|
||||
// Skip.
|
||||
return
|
||||
}
|
||||
|
||||
count := 0
|
||||
start := time.Now()
|
||||
|
||||
|
@ -1125,6 +1214,9 @@ func (m *MediaFile) ColorProfile() string {
|
|||
start := time.Now()
|
||||
logName := sanitize.Log(m.BaseName())
|
||||
|
||||
m.fileMutex.Lock()
|
||||
defer m.fileMutex.Unlock()
|
||||
|
||||
// Open file.
|
||||
fileReader, err := os.Open(m.FileName())
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/meta"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
)
|
||||
|
@ -68,7 +68,7 @@ func (m *MediaFile) ReadExifToolJson() error {
|
|||
|
||||
// MetaData returns exif meta data of a media file.
|
||||
func (m *MediaFile) MetaData() (result meta.Data) {
|
||||
m.metaDataOnce.Do(func() {
|
||||
m.metaOnce.Do(func() {
|
||||
var err error
|
||||
|
||||
if m.ExifSupported() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -977,36 +978,30 @@ func TestMediaFile_Copy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_Extension(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, ".json", mediaFile.Extension())
|
||||
})
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, ".heic", mediaFile.Extension())
|
||||
})
|
||||
t.Run("/canon_eos_6d.dng", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, ".dng", mediaFile.Extension())
|
||||
})
|
||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("elephants.jpg", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1016,36 +1011,30 @@ func TestMediaFile_Extension(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsJpeg(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
})
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
})
|
||||
t.Run("/canon_eos_6d.dng", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
})
|
||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("elephants.jpg", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1055,27 +1044,23 @@ func TestMediaFile_IsJpeg(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_HasType(t *testing.T) {
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.HasFileType("jpg"))
|
||||
})
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, true, mediaFile.HasFileType("heif"))
|
||||
})
|
||||
t.Run("/iphone_7.xmp", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.xmp", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.xmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1085,36 +1070,30 @@ func TestMediaFile_HasType(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsHEIF(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsHEIF())
|
||||
})
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, true, mediaFile.IsHEIF())
|
||||
})
|
||||
t.Run("/canon_eos_6d.dng", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsHEIF())
|
||||
})
|
||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("elephants.jpg", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1124,27 +1103,23 @@ func TestMediaFile_IsHEIF(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsRaw(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsRaw())
|
||||
})
|
||||
t.Run("/iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsRaw())
|
||||
})
|
||||
t.Run("/canon_eos_6d.dng", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1152,9 +1127,7 @@ func TestMediaFile_IsRaw(t *testing.T) {
|
|||
|
||||
assert.Equal(t, true, mediaFile.IsRaw())
|
||||
})
|
||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("elephants.jpg", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1164,18 +1137,16 @@ func TestMediaFile_IsRaw(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsPng(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsPng())
|
||||
})
|
||||
t.Run("/tweethog.png", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("tweethog.png", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/tweethog.png")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1189,9 +1160,9 @@ func TestMediaFile_IsPng(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsTiff(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1200,9 +1171,7 @@ func TestMediaFile_IsTiff(t *testing.T) {
|
|||
assert.Equal(t, "", mediaFile.MimeType())
|
||||
assert.Equal(t, false, mediaFile.IsTiff())
|
||||
})
|
||||
t.Run("/purple.tiff", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("purple.tiff", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/purple.tiff")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1211,9 +1180,7 @@ func TestMediaFile_IsTiff(t *testing.T) {
|
|||
assert.Equal(t, "image/tiff", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsTiff())
|
||||
})
|
||||
t.Run("/example.tiff", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("example.tiff", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/example.tif")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1225,48 +1192,56 @@ func TestMediaFile_IsTiff(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMediaFile_IsImageOther(t *testing.T) {
|
||||
t.Run("/iphone_7.json", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsImageOther())
|
||||
})
|
||||
t.Run("/purple.tiff", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("purple.tiff", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/purple.tiff")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
})
|
||||
t.Run("/tweethog.png", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("tweethog.png", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/tweethog.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
assert.Equal(t, false, mediaFile.IsGif())
|
||||
assert.Equal(t, true, mediaFile.IsPng())
|
||||
assert.Equal(t, false, mediaFile.IsBitmap())
|
||||
assert.Equal(t, false, mediaFile.IsWebP())
|
||||
assert.Equal(t, true, mediaFile.IsImage())
|
||||
assert.Equal(t, true, mediaFile.IsImageNative())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
assert.Equal(t, false, mediaFile.IsVideo())
|
||||
assert.Equal(t, false, mediaFile.IsPlayableVideo())
|
||||
})
|
||||
t.Run("/yellow_rose-small.bmp", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("yellow_rose-small.bmp", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/yellow_rose-small.bmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, fs.FormatBitmap, mediaFile.FileType())
|
||||
assert.Equal(t, "image/bmp", mediaFile.MimeType())
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
assert.Equal(t, false, mediaFile.IsGif())
|
||||
assert.Equal(t, true, mediaFile.IsBitmap())
|
||||
assert.Equal(t, false, mediaFile.IsWebP())
|
||||
assert.Equal(t, true, mediaFile.IsImage())
|
||||
assert.Equal(t, true, mediaFile.IsImageNative())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
assert.Equal(t, false, mediaFile.IsVideo())
|
||||
assert.Equal(t, false, mediaFile.IsPlayableVideo())
|
||||
})
|
||||
t.Run("/preloader.gif", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("preloader.gif", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/preloader.gif")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1274,30 +1249,53 @@ func TestMediaFile_IsImageOther(t *testing.T) {
|
|||
|
||||
assert.Equal(t, fs.FormatGif, mediaFile.FileType())
|
||||
assert.Equal(t, "image/gif", mediaFile.MimeType())
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
assert.Equal(t, true, mediaFile.IsGif())
|
||||
assert.Equal(t, false, mediaFile.IsBitmap())
|
||||
assert.Equal(t, false, mediaFile.IsWebP())
|
||||
assert.Equal(t, true, mediaFile.IsImage())
|
||||
assert.Equal(t, true, mediaFile.IsImageNative())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
assert.Equal(t, false, mediaFile.IsVideo())
|
||||
assert.Equal(t, false, mediaFile.IsPlayableVideo())
|
||||
})
|
||||
t.Run("norway-kjetil-moe.webp", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile("testdata/norway-kjetil-moe.webp")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, fs.FormatWebP, mediaFile.FileType())
|
||||
assert.Equal(t, fs.MimeTypeWebP, mediaFile.MimeType())
|
||||
assert.Equal(t, false, mediaFile.IsJpeg())
|
||||
assert.Equal(t, false, mediaFile.IsGif())
|
||||
assert.Equal(t, false, mediaFile.IsBitmap())
|
||||
assert.Equal(t, true, mediaFile.IsWebP())
|
||||
assert.Equal(t, true, mediaFile.IsImage())
|
||||
assert.Equal(t, true, mediaFile.IsImageNative())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
assert.Equal(t, false, mediaFile.IsVideo())
|
||||
assert.Equal(t, false, mediaFile.IsPlayableVideo())
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_IsSidecar(t *testing.T) {
|
||||
t.Run("/iphone_7.xmp", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("iphone_7.xmp", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.xmp")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/IMG_4120.AAE", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("IMG_4120.AAE", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.AAE")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/test.xml", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("test.xml", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/test.xml")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1305,9 +1303,7 @@ func TestMediaFile_IsSidecar(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/test.txt", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("test.txt", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/test.txt")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1315,9 +1311,7 @@ func TestMediaFile_IsSidecar(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/test.yml", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("test.yml", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/test.yml")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1325,9 +1319,7 @@ func TestMediaFile_IsSidecar(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/test.md", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("test.md", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/test.md")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1335,9 +1327,7 @@ func TestMediaFile_IsSidecar(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, true, mediaFile.IsSidecar())
|
||||
})
|
||||
t.Run("/canon_eos_6d.dng", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
||||
|
||||
if err != nil {
|
||||
|
@ -1536,7 +1526,7 @@ func TestMediaFile_decodeDimension(t *testing.T) {
|
|||
|
||||
decodeErr := mediaFile.decodeDimensions()
|
||||
|
||||
assert.EqualError(t, decodeErr, "failed decoding dimensions for Random.docx")
|
||||
assert.EqualError(t, decodeErr, "failed decoding dimensions of .docx file")
|
||||
})
|
||||
|
||||
t.Run("clock_purple.jpg", func(t *testing.T) {
|
||||
|
@ -1685,6 +1675,242 @@ func TestMediaFile_Height(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_Megapixels(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
t.Run("Random.docx", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/Random.docx"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("elephant_mono.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/elephant_mono.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("telegram_2020-01-30_09-57-18.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/telegram_2020-01-30_09-57-18.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 1, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("6720px_white.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/6720px_white.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 30, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("example.bmp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/example.bmp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("panorama360.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/panorama360.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("panorama360.json", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/panorama360.json"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("2018-04-12 19_24_49.gif", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/2018-04-12 19_24_49.gif"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("2018-04-12 19_24_49.mov", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/2018-04-12 19_24_49.mov"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("rotate/6.png", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/rotate/6.png"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 1, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("rotate/6.tiff", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/rotate/6.tiff"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
t.Run("norway-kjetil-moe.webp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/norway-kjetil-moe.webp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, 0, f.Megapixels())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_ExceedsFileSize(t *testing.T) {
|
||||
t.Run("norway-kjetil-moe.webp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/norway-kjetil-moe.webp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsFileSize(3)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("telegram_2020-01-30_09-57-18.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/telegram_2020-01-30_09-57-18.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsFileSize(-1)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("6720px_white.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/6720px_white.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsFileSize(0)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsFileSize(10)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("example.bmp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/example.bmp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsFileSize(10)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_DecodeConfig(t *testing.T) {
|
||||
t.Run("6720px_white.jpg", func(t *testing.T) {
|
||||
f, err := NewMediaFile(conf.ExamplesPath() + "/6720px_white.jpg")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfg1, err1 := f.DecodeConfig()
|
||||
|
||||
assert.Nil(t, err1)
|
||||
assert.IsType(t, &image.Config{}, cfg1)
|
||||
assert.Equal(t, 6720, cfg1.Width)
|
||||
assert.Equal(t, 4480, cfg1.Height)
|
||||
|
||||
cfg2, err2 := f.DecodeConfig()
|
||||
|
||||
assert.Nil(t, err2)
|
||||
assert.IsType(t, &image.Config{}, cfg2)
|
||||
assert.Equal(t, 6720, cfg2.Width)
|
||||
assert.Equal(t, 4480, cfg2.Height)
|
||||
|
||||
cfg3, err3 := f.DecodeConfig()
|
||||
|
||||
assert.Nil(t, err3)
|
||||
assert.IsType(t, &image.Config{}, cfg3)
|
||||
assert.Equal(t, 6720, cfg3.Width)
|
||||
assert.Equal(t, 4480, cfg3.Height)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_ExceedsResolution(t *testing.T) {
|
||||
t.Run("norway-kjetil-moe.webp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile("testdata/norway-kjetil-moe.webp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsResolution(3)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("telegram_2020-01-30_09-57-18.jpg", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/telegram_2020-01-30_09-57-18.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsResolution(3)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 1, actual)
|
||||
}
|
||||
})
|
||||
t.Run("6720px_white.jpg", func(t *testing.T) {
|
||||
f, err := NewMediaFile(conf.ExamplesPath() + "/6720px_white.jpg")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
exceeds3, actual3 := f.ExceedsResolution(3)
|
||||
|
||||
assert.True(t, exceeds3)
|
||||
assert.Equal(t, 30, actual3)
|
||||
|
||||
exceeds30, actual30 := f.ExceedsResolution(30)
|
||||
|
||||
assert.False(t, exceeds30)
|
||||
assert.Equal(t, 30, actual30)
|
||||
|
||||
exceeds33, actual33 := f.ExceedsResolution(33)
|
||||
|
||||
assert.False(t, exceeds33)
|
||||
assert.Equal(t, 30, actual33)
|
||||
})
|
||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsResolution(3)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
t.Run("example.bmp", func(t *testing.T) {
|
||||
if f, err := NewMediaFile(conf.ExamplesPath() + "/example.bmp"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
result, actual := f.ExceedsResolution(3)
|
||||
assert.False(t, result)
|
||||
assert.Equal(t, 0, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_AspectRatio(t *testing.T) {
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
@ -1867,7 +2093,7 @@ func TestMediaFile_RenderDefaultThumbs(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = m.ResampleDefault(thumbsPath, true)
|
||||
err = m.CreateThumbnails(thumbsPath, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1881,7 +2107,7 @@ func TestMediaFile_RenderDefaultThumbs(t *testing.T) {
|
|||
|
||||
assert.FileExists(t, thumbFilename)
|
||||
|
||||
err = m.ResampleDefault(thumbsPath, false)
|
||||
err = m.CreateThumbnails(thumbsPath, false)
|
||||
|
||||
assert.Empty(t, err)
|
||||
}
|
||||
|
|
|
@ -67,5 +67,5 @@ func (m RelatedFiles) MainLogName() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return sanitize.Log(m.Main.RelName(Config().OriginalsPath()))
|
||||
return sanitize.Log(m.Main.RootRelName())
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ func TestRelatedFiles_MainLogName(t *testing.T) {
|
|||
Files: MediaFiles{},
|
||||
Main: mediaFile,
|
||||
}
|
||||
assert.Equal(t, conf.ExamplesPath()+"/telegram_2020-01-30_09-57-18.jpg", relatedFiles.MainLogName())
|
||||
assert.Equal(t, "telegram_2020-01-30_09-57-18.jpg", relatedFiles.MainLogName())
|
||||
})
|
||||
t.Run("iPhone7", func(t *testing.T) {
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/telegram_2020-01-30_09-57-18.jpg")
|
||||
|
@ -216,6 +216,6 @@ func TestRelatedFiles_MainLogName(t *testing.T) {
|
|||
Files: MediaFiles{mediaFile, mediaFile2, mediaFile3},
|
||||
Main: mediaFile3,
|
||||
}
|
||||
assert.Equal(t, conf.ExamplesPath()+"/iphone_7.heic", relatedFiles.MainLogName())
|
||||
assert.Equal(t, "iphone_7.heic", relatedFiles.MainLogName())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ func ResampleWorker(jobs <-chan ResampleJob) {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := mf.ResampleDefault(job.path, job.force); err != nil {
|
||||
if err := mf.CreateThumbnails(job.path, job.force); err != nil {
|
||||
log.Errorf("resample: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
BIN
internal/photoprism/testdata/norway-kjetil-moe.webp
vendored
Normal file
BIN
internal/photoprism/testdata/norway-kjetil-moe.webp
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -98,7 +98,7 @@ func FromFile(imageFilename, hash, thumbPath string, width, height, orientation
|
|||
img, err := Open(imageFilename, orientation)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
log.Debugf("resample: %s in %s", err, sanitize.Log(filepath.Base(imageFilename)))
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ func Create(img image.Image, fileName string, width, height int, opts ...Resampl
|
|||
err = imaging.Save(result, fileName, quality)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("resample: failed to save %s", sanitize.Log(filepath.Base(fileName)))
|
||||
log.Debugf("resample: failed to save %s", sanitize.Log(filepath.Base(fileName)))
|
||||
return result, err
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,6 @@ package thumb
|
|||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
|
|
@ -3,9 +3,6 @@ package thumb
|
|||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
|
@ -27,6 +27,14 @@ Additional information can be found in our Developer Guide:
|
|||
package thumb
|
||||
|
||||
import (
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
|
|
|
@ -18,22 +18,23 @@ const (
|
|||
FormatTiff FileFormat = "tiff" // TIFF image file.
|
||||
FormatBitmap FileFormat = "bmp" // BMP image file.
|
||||
FormatRaw FileFormat = "raw" // RAW image file.
|
||||
FormatMpo FileFormat = "mpo" // Stereoscopic Image that consists of two JPG images that are combined into one 3D image
|
||||
FormatHEIF FileFormat = "heif" // High Efficiency Image File Format
|
||||
FormatHEVC FileFormat = "hevc"
|
||||
FormatMov FileFormat = "mov" // Video files.
|
||||
FormatMp4 FileFormat = "mp4"
|
||||
FormatMpo FileFormat = "mpo"
|
||||
FormatAvc FileFormat = "avc"
|
||||
FormatAvi FileFormat = "avi"
|
||||
Format3gp FileFormat = "3gp"
|
||||
Format3g2 FileFormat = "3g2"
|
||||
FormatFlv FileFormat = "flv"
|
||||
FormatMkv FileFormat = "mkv"
|
||||
FormatMpg FileFormat = "mpg"
|
||||
FormatMts FileFormat = "mts"
|
||||
FormatOgv FileFormat = "ogv"
|
||||
FormatWebm FileFormat = "webm"
|
||||
FormatWMV FileFormat = "wmv"
|
||||
FormatWebP FileFormat = "webp" // Google WebP Image
|
||||
FormatWebM FileFormat = "webm" // Google WebM Video
|
||||
FormatHEVC FileFormat = "hevc" // H.265, High Efficiency Video Coding (HEVC)
|
||||
FormatAvc FileFormat = "avc" // H.264, Advanced Video Coding (AVC), MPEG-4 Part 10, used internally
|
||||
FormatMov FileFormat = "mov" // QuickTime File Format, can contain AVC, HEVC,...
|
||||
FormatMp4 FileFormat = "mp4" // Standard MPEG-4 Container based on QuickTime, can contain AVC, HEVC,...
|
||||
FormatAvi FileFormat = "avi" // Microsoft Audio Video Interleave (AVI)
|
||||
Format3gp FileFormat = "3gp" // Mobile Multimedia Container Format, MPEG-4 Part 12
|
||||
Format3g2 FileFormat = "3g2" // Similar to 3GP, consumes less space & bandwidth
|
||||
FormatFlv FileFormat = "flv" // Flash Video
|
||||
FormatMkv FileFormat = "mkv" // Matroska Multimedia Container, free and open
|
||||
FormatMpg FileFormat = "mpg" // Moving Picture Experts Group (MPEG)
|
||||
FormatMts FileFormat = "mts" // AVCHD (Advanced Video Coding High Definition)
|
||||
FormatOgv FileFormat = "ogv" // Ogg container format maintained by the Xiph.Org, free and open
|
||||
FormatWMV FileFormat = "wmv" // Windows Media Video
|
||||
FormatXMP FileFormat = "xmp" // Adobe XMP sidecar file (XML).
|
||||
FormatAAE FileFormat = "aae" // Apple sidecar file (XML).
|
||||
FormatXML FileFormat = "xml" // XML metadata / config / sidecar file.
|
||||
|
@ -75,7 +76,8 @@ var FileExt = FileExtensions{
|
|||
".mpo": FormatMpo,
|
||||
".mts": FormatMts,
|
||||
".ogv": FormatOgv,
|
||||
".webm": FormatWebm,
|
||||
".webp": FormatWebP,
|
||||
".webm": FormatWebM,
|
||||
".wmv": FormatWMV,
|
||||
".yml": FormatYaml,
|
||||
".yaml": FormatYaml,
|
||||
|
|
|
@ -17,10 +17,12 @@ var MediaTypes = map[FileFormat]MediaType{
|
|||
FormatGif: MediaImage,
|
||||
FormatTiff: MediaImage,
|
||||
FormatBitmap: MediaImage,
|
||||
FormatHEIF: MediaImage,
|
||||
FormatMpo: MediaImage,
|
||||
FormatAvi: MediaVideo,
|
||||
FormatHEIF: MediaImage,
|
||||
FormatHEVC: MediaVideo,
|
||||
FormatWebP: MediaImage,
|
||||
FormatWebM: MediaVideo,
|
||||
FormatAvi: MediaVideo,
|
||||
FormatAvc: MediaVideo,
|
||||
FormatMp4: MediaVideo,
|
||||
FormatMov: MediaVideo,
|
||||
|
@ -31,7 +33,6 @@ var MediaTypes = map[FileFormat]MediaType{
|
|||
FormatMpg: MediaVideo,
|
||||
FormatMts: MediaVideo,
|
||||
FormatOgv: MediaVideo,
|
||||
FormatWebm: MediaVideo,
|
||||
FormatWMV: MediaVideo,
|
||||
FormatXMP: MediaSidecar,
|
||||
FormatXML: MediaSidecar,
|
||||
|
|
|
@ -11,11 +11,12 @@ const (
|
|||
MimeTypePng = "image/png"
|
||||
MimeTypeGif = "image/gif"
|
||||
MimeTypeBitmap = "image/bmp"
|
||||
MimeTypeWebP = "image/webp"
|
||||
MimeTypeTiff = "image/tiff"
|
||||
MimeTypeHEIF = "image/heif"
|
||||
)
|
||||
|
||||
// MimeType returns the mime type of a file, empty string if unknown.
|
||||
// MimeType returns the mime type of a file, an empty string if it is unknown.
|
||||
func MimeType(filename string) string {
|
||||
handle, err := os.Open(filename)
|
||||
|
||||
|
|
Loading…
Reference in a new issue