2020-02-23 18:00:08 +01:00
|
|
|
package scanner
|
|
|
|
|
|
|
|
import (
|
2020-11-16 21:57:34 +01:00
|
|
|
"crypto/md5"
|
|
|
|
"encoding/hex"
|
|
|
|
"io"
|
2020-02-23 18:00:08 +01:00
|
|
|
"log"
|
2020-08-12 13:04:41 +02:00
|
|
|
"os"
|
2020-02-23 18:00:08 +01:00
|
|
|
"path"
|
2020-12-09 11:40:37 +01:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2020-02-26 19:44:47 +01:00
|
|
|
|
2020-12-17 22:51:43 +01:00
|
|
|
"github.com/photoview/photoview/api/graphql/models"
|
2021-01-19 16:39:57 +01:00
|
|
|
"github.com/photoview/photoview/api/scanner/exif"
|
2020-07-10 18:57:27 +02:00
|
|
|
"github.com/pkg/errors"
|
2020-11-23 19:59:01 +01:00
|
|
|
"gorm.io/gorm"
|
2020-02-23 18:00:08 +01:00
|
|
|
)
|
|
|
|
|
2020-12-09 11:40:37 +01:00
|
|
|
func fileExists(testPath string) bool {
|
2020-11-16 21:57:34 +01:00
|
|
|
_, err := os.Stat(testPath)
|
|
|
|
|
|
|
|
if os.IsNotExist(err) {
|
2020-12-09 11:40:37 +01:00
|
|
|
return false
|
2020-11-16 21:57:34 +01:00
|
|
|
} else if err != nil {
|
|
|
|
// unexpected error logging
|
2020-12-09 11:40:37 +01:00
|
|
|
log.Printf("Error: checking for file existence (%s): %s", testPath, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanForSideCarFile(path string) *string {
|
|
|
|
testPath := path + ".xmp"
|
|
|
|
|
|
|
|
if fileExists(testPath) {
|
|
|
|
return &testPath
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanForRawCounterpartFile(imagePath string) *string {
|
|
|
|
ext := filepath.Ext(imagePath)
|
|
|
|
fileExtType, found := fileExtensions[strings.ToLower(ext)]
|
|
|
|
|
|
|
|
if found {
|
|
|
|
if !fileExtType.isBasicTypeSupported() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pathWithoutExt := strings.TrimSuffix(imagePath, path.Ext(imagePath))
|
|
|
|
|
|
|
|
for _, rawType := range RawMimeTypes {
|
|
|
|
for _, ext := range rawType.FileExtensions() {
|
|
|
|
testPath := pathWithoutExt + ext
|
|
|
|
if fileExists(testPath) {
|
|
|
|
return &testPath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanForCompressedCounterpartFile(imagePath string) *string {
|
|
|
|
ext := filepath.Ext(imagePath)
|
|
|
|
fileExtType, found := fileExtensions[strings.ToLower(ext)]
|
|
|
|
|
|
|
|
if found {
|
|
|
|
if fileExtType.isBasicTypeSupported() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pathWithoutExt := strings.TrimSuffix(imagePath, path.Ext(imagePath))
|
|
|
|
for _, ext := range TypeJpeg.FileExtensions() {
|
|
|
|
testPath := pathWithoutExt + ext
|
|
|
|
if fileExists(testPath) {
|
|
|
|
return &testPath
|
|
|
|
}
|
2020-11-16 21:57:34 +01:00
|
|
|
}
|
|
|
|
|
2020-12-09 11:40:37 +01:00
|
|
|
return nil
|
2020-11-16 21:57:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func hashSideCarFile(path *string) *string {
|
|
|
|
if path == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Open(*path)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("ERROR: %s", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
h := md5.New()
|
|
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
|
|
log.Printf("ERROR: %s", err)
|
|
|
|
}
|
|
|
|
hash := hex.EncodeToString(h.Sum(nil))
|
|
|
|
return &hash
|
|
|
|
}
|
|
|
|
|
2020-11-28 21:29:31 +01:00
|
|
|
func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *AlbumScannerCache) (*models.Media, bool, error) {
|
2020-07-10 18:57:27 +02:00
|
|
|
mediaName := path.Base(mediaPath)
|
2020-02-23 18:00:08 +01:00
|
|
|
|
2020-11-23 20:43:00 +01:00
|
|
|
// Check if media already exists
|
2020-02-26 21:23:13 +01:00
|
|
|
{
|
2020-11-30 21:29:49 +01:00
|
|
|
var media []*models.Media
|
|
|
|
|
2021-01-17 12:45:23 +01:00
|
|
|
result := tx.Where("path_hash = ?", models.MD5Hash(mediaPath)).Find(&media)
|
2020-11-30 21:29:49 +01:00
|
|
|
|
|
|
|
if result.Error != nil {
|
|
|
|
return nil, false, errors.Wrap(result.Error, "scan media fetch from database")
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.RowsAffected > 0 {
|
|
|
|
log.Printf("Media already scanned: %s\n", mediaPath)
|
|
|
|
return media[0], false, nil
|
2020-02-23 18:00:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-10 18:57:27 +02:00
|
|
|
log.Printf("Scanning media: %s\n", mediaPath)
|
2020-06-23 16:42:02 +02:00
|
|
|
|
2020-07-11 14:05:06 +02:00
|
|
|
mediaType, err := cache.GetMediaType(mediaPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, errors.Wrap(err, "could determine if media was photo or video")
|
|
|
|
}
|
|
|
|
|
2020-11-23 20:43:00 +01:00
|
|
|
var mediaTypeText models.MediaType
|
|
|
|
|
|
|
|
var sideCarPath *string = nil
|
|
|
|
var sideCarHash *string = nil
|
2020-11-16 21:57:34 +01:00
|
|
|
|
2020-07-11 14:05:06 +02:00
|
|
|
if mediaType.isVideo() {
|
2020-11-23 20:43:00 +01:00
|
|
|
mediaTypeText = models.MediaTypeVideo
|
2020-07-11 14:05:06 +02:00
|
|
|
} else {
|
2020-11-23 20:43:00 +01:00
|
|
|
mediaTypeText = models.MediaTypePhoto
|
2020-11-16 21:57:34 +01:00
|
|
|
// search for sidecar files
|
|
|
|
if mediaType.isRaw() {
|
|
|
|
sideCarPath = scanForSideCarFile(mediaPath)
|
|
|
|
if sideCarPath != nil {
|
|
|
|
sideCarHash = hashSideCarFile(sideCarPath)
|
|
|
|
}
|
|
|
|
}
|
2020-07-11 14:05:06 +02:00
|
|
|
}
|
|
|
|
|
2020-08-12 13:04:41 +02:00
|
|
|
stat, err := os.Stat(mediaPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2020-11-23 20:43:00 +01:00
|
|
|
media := models.Media{
|
|
|
|
Title: mediaName,
|
|
|
|
Path: mediaPath,
|
|
|
|
SideCarPath: sideCarPath,
|
|
|
|
SideCarHash: sideCarHash,
|
2020-11-26 20:48:04 +01:00
|
|
|
AlbumID: albumId,
|
2020-11-23 20:43:00 +01:00
|
|
|
Type: mediaTypeText,
|
|
|
|
DateShot: stat.ModTime(),
|
2020-02-23 18:00:08 +01:00
|
|
|
}
|
|
|
|
|
2020-11-23 20:43:00 +01:00
|
|
|
if err := tx.Create(&media).Error; err != nil {
|
|
|
|
return nil, false, errors.Wrap(err, "could not insert media into database")
|
2020-02-23 18:00:08 +01:00
|
|
|
}
|
|
|
|
|
2021-01-19 16:39:57 +01:00
|
|
|
_, err = exif.SaveEXIF(tx, &media)
|
2020-02-24 23:30:08 +01:00
|
|
|
if err != nil {
|
2021-01-19 16:39:57 +01:00
|
|
|
log.Printf("WARN: SaveEXIF for %s failed: %s\n", mediaName, err)
|
2020-02-24 23:30:08 +01:00
|
|
|
}
|
|
|
|
|
2020-07-12 14:17:49 +02:00
|
|
|
if media.Type == models.MediaTypeVideo {
|
2020-11-25 23:06:47 +01:00
|
|
|
if err = ScanVideoMetadata(tx, &media); err != nil {
|
2020-07-12 14:17:49 +02:00
|
|
|
log.Printf("WARN: ScanVideoMetadata for %s failed: %s\n", mediaName, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 20:43:00 +01:00
|
|
|
return &media, true, nil
|
2020-02-23 18:00:08 +01:00
|
|
|
}
|