Start on video processing
This commit is contained in:
parent
7caad82059
commit
3564866f41
|
@ -21,12 +21,13 @@ func (p *Photo) ID() int {
|
|||
return p.PhotoID
|
||||
}
|
||||
|
||||
type PhotoPurpose string
|
||||
type MediaPurpose string
|
||||
|
||||
const (
|
||||
PhotoThumbnail PhotoPurpose = "thumbnail"
|
||||
PhotoHighRes PhotoPurpose = "high-res"
|
||||
PhotoOriginal PhotoPurpose = "original"
|
||||
PhotoThumbnail MediaPurpose = "thumbnail"
|
||||
PhotoHighRes MediaPurpose = "high-res"
|
||||
MediaOriginal MediaPurpose = "original"
|
||||
VideoWeb MediaPurpose = "video-web"
|
||||
)
|
||||
|
||||
type PhotoURL struct {
|
||||
|
@ -35,7 +36,7 @@ type PhotoURL struct {
|
|||
PhotoName string
|
||||
Width int
|
||||
Height int
|
||||
Purpose PhotoPurpose
|
||||
Purpose MediaPurpose
|
||||
ContentType string
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ func (r *photoResolver) Downloads(ctx context.Context, obj *models.Photo) ([]*mo
|
|||
|
||||
var title string
|
||||
switch {
|
||||
case url.Purpose == models.PhotoOriginal:
|
||||
case url.Purpose == models.MediaOriginal:
|
||||
title = "Original"
|
||||
case url.Purpose == models.PhotoThumbnail:
|
||||
title = "Small"
|
||||
|
@ -119,7 +119,7 @@ func (r *photoResolver) HighRes(ctx context.Context, obj *models.Photo) (*models
|
|||
// Try high res first, then
|
||||
web_types_questions := strings.Repeat("?,", len(scanner.WebMimetypes))[:len(scanner.WebMimetypes)*2-1]
|
||||
args := make([]interface{}, 0)
|
||||
args = append(args, obj.PhotoID, models.PhotoHighRes, models.PhotoOriginal)
|
||||
args = append(args, obj.PhotoID, models.PhotoHighRes, models.MediaOriginal)
|
||||
for _, webtype := range scanner.WebMimetypes {
|
||||
args = append(args, webtype)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
|||
|
||||
row := db.QueryRow("SELECT photo_url.purpose, photo_url.content_type, photo_url.photo_id FROM photo_url, photo WHERE photo_url.photo_name = ? AND photo_url.photo_id = photo.photo_id", image_name)
|
||||
|
||||
var purpose models.PhotoPurpose
|
||||
var purpose models.MediaPurpose
|
||||
var content_type string
|
||||
var photo_id int
|
||||
|
||||
|
@ -136,7 +136,7 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
|||
cachedPath = path.Join(scanner.PhotoCache(), strconv.Itoa(photo.AlbumId), strconv.Itoa(photo_id), image_name)
|
||||
}
|
||||
|
||||
if purpose == models.PhotoOriginal {
|
||||
if purpose == models.MediaOriginal {
|
||||
cachedPath = photo.Path
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ func RegisterPhotoRoutes(db *sql.DB, router *mux.Router) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = scanner.ProcessPhoto(tx, photo)
|
||||
_, err = scanner.ProcessMedia(tx, photo)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: processing image not found in cache: %s\n", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
|
|
@ -71,37 +71,6 @@ var SupportedMimetypes = [...]MediaType{
|
|||
TypeTiff,
|
||||
TypeWebp,
|
||||
TypeBmp,
|
||||
|
||||
TypeDNG,
|
||||
TypeARW,
|
||||
TypeSR2,
|
||||
TypeSRF,
|
||||
TypeCR2,
|
||||
TypeCRW,
|
||||
TypeERF,
|
||||
TypeDCS,
|
||||
TypeDRF,
|
||||
TypeDCR,
|
||||
TypeK25,
|
||||
TypeKDC,
|
||||
TypeMRW,
|
||||
TypeMDC,
|
||||
TypeNEF,
|
||||
TypeNRW,
|
||||
TypeORF,
|
||||
TypePEF,
|
||||
TypeRAF,
|
||||
TypeRAW,
|
||||
TypeRW2,
|
||||
TypeGPR,
|
||||
Type3FR,
|
||||
TypeFFF,
|
||||
TypeMEF,
|
||||
TypeCap,
|
||||
TypeIIQ,
|
||||
TypeMOS,
|
||||
TypeRWL,
|
||||
TypeSRW,
|
||||
}
|
||||
|
||||
var WebMimetypes = [...]MediaType{
|
||||
|
@ -224,6 +193,17 @@ func (imgType *MediaType) isWebCompatible() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (imgType *MediaType) isVideo() bool {
|
||||
for _, video_mime := range VideoMimetypes {
|
||||
if video_mime == *imgType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isSupported determines if the given type can be processed
|
||||
func (imgType *MediaType) isSupported() bool {
|
||||
for _, supported_mime := range SupportedMimetypes {
|
||||
if supported_mime == *imgType {
|
||||
|
@ -231,6 +211,14 @@ func (imgType *MediaType) isSupported() bool {
|
|||
}
|
||||
}
|
||||
|
||||
if DarktableCli.IsInstalled() && imgType.isRaw() {
|
||||
return true
|
||||
}
|
||||
|
||||
if FfmpegCli.IsInstalled() && imgType.isVideo() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,13 @@ import (
|
|||
)
|
||||
|
||||
// Higher order function used to check if PhotoURL for a given PhotoPurpose exists
|
||||
func makePhotoURLChecker(tx *sql.Tx, photoID int) (func(purpose models.PhotoPurpose) (*models.PhotoURL, error), error) {
|
||||
func makePhotoURLChecker(tx *sql.Tx, photoID int) (func(purpose models.MediaPurpose) (*models.PhotoURL, error), error) {
|
||||
photoURLExistsStmt, err := tx.Prepare("SELECT * FROM photo_url WHERE photo_id = ? AND purpose = ?")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(purpose models.PhotoPurpose) (*models.PhotoURL, error) {
|
||||
return func(purpose models.MediaPurpose) (*models.PhotoURL, error) {
|
||||
row := photoURLExistsStmt.QueryRow(photoID, purpose)
|
||||
photoURL, err := models.NewPhotoURLFromRow(row)
|
||||
if err != nil {
|
||||
|
@ -43,23 +43,44 @@ func makePhotoURLChecker(tx *sql.Tx, photoID int) (func(purpose models.PhotoPurp
|
|||
}, nil
|
||||
}
|
||||
|
||||
func ProcessPhoto(tx *sql.Tx, photo *models.Photo) (bool, error) {
|
||||
func ProcessMedia(tx *sql.Tx, photo *models.Photo) (bool, error) {
|
||||
imageData := EncodeImageData{
|
||||
photo: photo,
|
||||
}
|
||||
|
||||
contentType, err := imageData.ContentType()
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "get content-type of media (%s)", photo.Path)
|
||||
}
|
||||
|
||||
// Make sure photo cache directory exists
|
||||
mediaCachePath, err := makeMediaCacheDir(photo)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "cache directory error")
|
||||
}
|
||||
|
||||
if contentType.isVideo() {
|
||||
return processVideo(tx, &imageData, mediaCachePath)
|
||||
} else {
|
||||
return processPhoto(tx, &imageData, mediaCachePath)
|
||||
}
|
||||
}
|
||||
|
||||
func processPhoto(tx *sql.Tx, imageData *EncodeImageData, photoCachePath *string) (bool, error) {
|
||||
|
||||
photo := imageData.photo
|
||||
|
||||
log.Printf("Processing photo: %s\n", photo.Path)
|
||||
|
||||
didProcess := false
|
||||
|
||||
imageData := EncodeImageData{
|
||||
photo: photo,
|
||||
}
|
||||
|
||||
photoUrlFromDB, err := makePhotoURLChecker(tx, photo.PhotoID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// original photo url
|
||||
origURL, err := photoUrlFromDB(models.PhotoOriginal)
|
||||
origURL, err := photoUrlFromDB(models.MediaOriginal)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -67,19 +88,13 @@ func ProcessPhoto(tx *sql.Tx, photo *models.Photo) (bool, error) {
|
|||
// Thumbnail
|
||||
thumbURL, err := photoUrlFromDB(models.PhotoThumbnail)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "error processing thumbnail")
|
||||
return false, errors.Wrap(err, "error processing photo thumbnail")
|
||||
}
|
||||
|
||||
// Highres
|
||||
highResURL, err := photoUrlFromDB(models.PhotoHighRes)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "error processing highres")
|
||||
}
|
||||
|
||||
// Make sure photo cache directory exists
|
||||
photoCachePath, err := makePhotoCacheDir(photo)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "cache directory error")
|
||||
return false, errors.Wrap(err, "error processing photo highres")
|
||||
}
|
||||
|
||||
// Generate high res jpeg
|
||||
|
@ -195,13 +210,12 @@ func ProcessPhoto(tx *sql.Tx, photo *models.Photo) (bool, error) {
|
|||
return didProcess, nil
|
||||
}
|
||||
|
||||
func makePhotoCacheDir(photo *models.Photo) (*string, error) {
|
||||
func makeMediaCacheDir(photo *models.Photo) (*string, error) {
|
||||
|
||||
// Make root cache dir if not exists
|
||||
if _, err := os.Stat(PhotoCache()); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(PhotoCache(), os.ModePerm); err != nil {
|
||||
log.Println("ERROR: Could not make root image cache directory")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make root image cache directory")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,8 +223,7 @@ func makePhotoCacheDir(photo *models.Photo) (*string, error) {
|
|||
albumCachePath := path.Join(PhotoCache(), strconv.Itoa(photo.AlbumId))
|
||||
if _, err := os.Stat(albumCachePath); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(albumCachePath, os.ModePerm); err != nil {
|
||||
log.Println("ERROR: Could not make album image cache directory")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make album image cache directory")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,15 +231,14 @@ func makePhotoCacheDir(photo *models.Photo) (*string, error) {
|
|||
photoCachePath := path.Join(albumCachePath, strconv.Itoa(photo.PhotoID))
|
||||
if _, err := os.Stat(photoCachePath); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(photoCachePath, os.ModePerm); err != nil {
|
||||
log.Println("ERROR: Could not make photo image cache directory")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "could not make photo image cache directory")
|
||||
}
|
||||
}
|
||||
|
||||
return &photoCachePath, nil
|
||||
}
|
||||
|
||||
func saveOriginalPhotoToDB(tx *sql.Tx, photo *models.Photo, imageData EncodeImageData, photoDimensions *PhotoDimensions) error {
|
||||
func saveOriginalPhotoToDB(tx *sql.Tx, photo *models.Photo, imageData *EncodeImageData, photoDimensions *PhotoDimensions) error {
|
||||
photoName := path.Base(photo.Path)
|
||||
photoBaseName := photoName[0 : len(photoName)-len(path.Ext(photoName))]
|
||||
photoBaseExt := path.Ext(photoName)
|
||||
|
@ -239,7 +251,7 @@ func saveOriginalPhotoToDB(tx *sql.Tx, photo *models.Photo, imageData EncodeImag
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO photo_url (photo_id, photo_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.PhotoID, original_image_name, photoDimensions.Width, photoDimensions.Height, models.PhotoOriginal, contentType)
|
||||
_, err = tx.Exec("INSERT INTO photo_url (photo_id, photo_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.PhotoID, original_image_name, photoDimensions.Width, photoDimensions.Height, models.MediaOriginal, contentType)
|
||||
if err != nil {
|
||||
log.Printf("Could not insert original photo url: %d, %s\n", photo.PhotoID, photoName)
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package scanner
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/viktorstrate/photoview/api/graphql/models"
|
||||
)
|
||||
|
||||
func processVideo(tx *sql.Tx, imageData *EncodeImageData, videoCachePath *string) (bool, error) {
|
||||
video := imageData.photo
|
||||
didProcess := false
|
||||
|
||||
log.Printf("Processing video: %s", video.Path)
|
||||
|
||||
mediaUrlFromDB, err := makePhotoURLChecker(tx, video.PhotoID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
videoWebURL, err := mediaUrlFromDB(models.VideoWeb)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "error processing video web-format")
|
||||
}
|
||||
|
||||
if videoWebURL == nil {
|
||||
// TODO: Process web video
|
||||
}
|
||||
|
||||
// TODO: Process video thumbnail
|
||||
|
||||
return didProcess, nil
|
||||
}
|
|
@ -42,7 +42,7 @@ func scanAlbum(album *models.Album, cache *AlbumScannerCache, db *sql.DB) {
|
|||
ScannerError("Failed to begin database transaction: %s", err)
|
||||
}
|
||||
|
||||
processing_was_needed, err := ProcessPhoto(tx, photo)
|
||||
processing_was_needed, err := ProcessMedia(tx, photo)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
ScannerError("Failed to process photo (%s): %s", photo.Path, err)
|
||||
|
|
Loading…
Reference in New Issue