Fix bug when original files are deleted and gallery is updated

pull/7/head v0.4.1
Toni Melisma 3 years ago
parent 451df1de79
commit b43a542fef
No known key found for this signature in database
GPG Key ID: FFF9A7EDDEA34756

@ -2,12 +2,14 @@ package main
import (
"embed"
"errors"
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
@ -293,20 +295,19 @@ func isMediaFile(filename string, noVideos bool) bool {
func isSymlinkDir(targetPath string) (is bool) {
entry, err := os.Lstat(targetPath)
if err != nil {
log.Println("Couldn't list directory x contents:", targetPath, err.Error())
log.Println("Couldn't lstat dir path:", targetPath, err.Error())
exit(1)
}
if entry.Mode()&os.ModeSymlink != 0 {
realPath, err := filepath.EvalSymlinks(targetPath)
if err != nil {
log.Println("Couldn't list directory contents:", targetPath)
exit(1)
return false
}
realEntry, err := os.Lstat(realPath)
if err != nil {
log.Println("Couldn't list directory contents:", targetPath)
log.Println("Couldn't lstat file path:", targetPath)
exit(1)
}
@ -339,7 +340,7 @@ func createDirectoryTree(absoluteDirectory string, parentDirectory string, noVid
// List directory contents
list, err := os.ReadDir(absoluteDirectory)
if err != nil {
log.Println("Couldn't list directory contents:", absoluteDirectory)
log.Println("Couldn't read directory contents:", absoluteDirectory)
exit(1)
}
@ -529,19 +530,21 @@ func createDirectory(destination string, dryRun bool, dirMode os.FileMode) {
}
}
func symlinkFile(source string, destination string) {
func symlinkFile(source string, destination string) error {
if _, err := os.Stat(destination); err == nil {
err := os.Remove(destination)
if err != nil {
log.Println("couldn't remove symlink:", source, destination)
return
return err
}
}
err := os.Symlink(source, destination)
if err != nil {
log.Println("couldn't symlink:", source, destination)
return
return err
}
return nil
}
// TODO add copyFile and option to use in lieu of symlinking
@ -727,19 +730,19 @@ func getGalleryDirectoryNames(galleryDirectory string, config configuration) (th
return
}
func transformImage(source string, fullsizeDestination string, thumbnailDestination string, config configuration) {
func transformImage(source string, fullsizeDestination string, thumbnailDestination string, config configuration) error {
if config.files.imageExtension == ".jpg" {
// First create full-size image
image, err := vips.NewImageFromFile(source)
if err != nil {
log.Println("couldn't open full-size image:", source, err.Error())
return
return err
}
err = image.AutoRotate()
if err != nil {
log.Println("couldn't autorotate full-size image:", source, err.Error())
return
return err
}
// Calculate the scaling factor used to make the image smaller
@ -753,48 +756,49 @@ func transformImage(source string, fullsizeDestination string, thumbnailDestinat
err = image.Resize(scale, vips.KernelAuto)
if err != nil {
log.Println("couldn't resize full-size image:", source, err.Error())
return
return err
}
ep := vips.NewDefaultJPEGExportParams()
fullsizeBuffer, _, err := image.Export(ep)
if err != nil {
log.Println("couldn't export full-size image:", source, err.Error())
return
return err
}
err = os.WriteFile(fullsizeDestination, fullsizeBuffer, config.files.fileMode)
if err != nil {
log.Println("couldn't write full-size image:", fullsizeDestination, err.Error())
return
return err
}
// After full-size image, create thumbnail
err = image.Thumbnail(config.media.thumbnailWidth, config.media.thumbnailHeight, vips.InterestingAttention)
if err != nil {
log.Println("couldn't crop thumbnail:", err.Error())
return
return err
}
thumbnailBuffer, _, err := image.Export(ep)
if err != nil {
log.Println("couldn't export thumbnail image:", source, err.Error())
return
return err
}
err = os.WriteFile(thumbnailDestination, thumbnailBuffer, config.files.fileMode)
if err != nil {
log.Println("couldn't write thumbnail image:", thumbnailDestination, err.Error())
return
return err
}
} else {
log.Println("Can't figure out what format to convert full size image to:", source)
exit(1)
return errors.New("invalid target format for full-size image")
}
return nil
}
func transformVideo(source string, fullsizeDestination string, thumbnailDestination string, config configuration) {
func transformVideo(source string, fullsizeDestination string, thumbnailDestination string, config configuration) error {
// Resize full-size video
ffmpegCommand := exec.Command("ffmpeg", "-y", "-i", source, "-pix_fmt", "yuv420p", "-vcodec", "libx264", "-acodec", "aac", "-movflags", "faststart", "-r", "24", "-vf", "scale='min("+strconv.Itoa(config.media.videoMaxSize)+",iw)':'min("+strconv.Itoa(config.media.videoMaxSize)+",ih)':force_original_aspect_ratio=decrease", "-crf", "28", "-loglevel", "fatal", fullsizeDestination)
// TODO capture stdout/err to bytes buffer instead
@ -804,7 +808,7 @@ func transformVideo(source string, fullsizeDestination string, thumbnailDestinat
err := ffmpegCommand.Run()
if err != nil {
log.Println("Could not create full-size video transcode:", source)
return
return err
}
// Create thumbnail image of video
@ -816,47 +820,49 @@ func transformVideo(source string, fullsizeDestination string, thumbnailDestinat
err = ffmpegCommand2.Run()
if err != nil {
log.Println("Could not create thumbnail of video:", source)
return
return err
}
// Take thumbnail and overlay triangle image on top of it
image, err := vips.NewImageFromFile(thumbnailDestination)
if err != nil {
log.Println("Could not open video thumbnail:", thumbnailDestination)
return
return err
}
playbuttonOverlayBuffer, err := assets.ReadFile("assets/playbutton.png")
playbuttonOverlayImage, err := vips.NewImageFromBuffer(playbuttonOverlayBuffer)
if err != nil {
log.Println("Could not open play button overlay asset")
return
return err
}
// Overlay play button in the middle of thumbnail picture
err = image.Composite(playbuttonOverlayImage, vips.BlendModeOver, (config.media.thumbnailWidth/2)-(playbuttonOverlayImage.Width()/2), (config.media.thumbnailHeight/2)-(playbuttonOverlayImage.Height()/2))
if err != nil {
log.Println("Could not composite play button overlay on top of:", thumbnailDestination)
return
return err
}
ep := vips.NewDefaultJPEGExportParams()
imageBytes, _, err := image.Export(ep)
if err != nil {
log.Println("Could not export video thumnail:", thumbnailDestination)
return
return err
}
err = os.WriteFile(thumbnailDestination, imageBytes, config.files.fileMode)
if err != nil {
log.Println("Could not write video thumnail:", thumbnailDestination)
return
return err
}
return nil
}
func createOriginal(source string, destination string) {
func createOriginal(source string, destination string) error {
// TODO add option to copy
symlinkFile(source, destination)
return symlinkFile(source, destination)
}
func getGalleryFilenames(sourceFilename string, config configuration) (thumbnailFilename string, fullsizeFilename string) {
@ -872,6 +878,15 @@ func getGalleryFilenames(sourceFilename string, config configuration) (thumbnail
return
}
func cleanWipFiles(sourceFilepath string) {
wipJobMutex.Lock()
os.Remove(wipJobs[sourceFilepath].thumbnailFilepath)
os.Remove(wipJobs[sourceFilepath].fullsizeFilepath)
os.Remove(wipJobs[sourceFilepath].originalFilepath)
delete(wipJobs, sourceFilepath)
wipJobMutex.Unlock()
}
// transformFile takes a transformation job (an image or video) and creates a thumbnail, full-size
// image and a copy of the original
func transformFile(thisJob transformationJob, progressBar *pb.ProgressBar, config configuration) {
@ -884,14 +899,29 @@ func transformFile(thisJob transformationJob, progressBar *pb.ProgressBar, confi
// Do the actual transformation and increment the progress bar
if isImageFile(thisJob.filename) {
transformImage(thisJob.sourceFilepath, thisJob.fullsizeFilepath, thisJob.thumbnailFilepath, config)
err := transformImage(thisJob.sourceFilepath, thisJob.fullsizeFilepath, thisJob.thumbnailFilepath, config)
if err != nil {
cleanWipFiles(thisJob.sourceFilepath)
progressBar.Increment()
return
}
} else if isVideoFile(thisJob.filename) {
transformVideo(thisJob.sourceFilepath, thisJob.fullsizeFilepath, thisJob.thumbnailFilepath, config)
err := transformVideo(thisJob.sourceFilepath, thisJob.fullsizeFilepath, thisJob.thumbnailFilepath, config)
if err != nil {
cleanWipFiles(thisJob.sourceFilepath)
progressBar.Increment()
return
}
} else {
log.Println("could not infer whether file is image or video(2):", thisJob.sourceFilepath)
exit(1)
}
createOriginal(thisJob.sourceFilepath, thisJob.originalFilepath)
err := createOriginal(thisJob.sourceFilepath, thisJob.originalFilepath)
if err != nil {
cleanWipFiles(thisJob.sourceFilepath)
progressBar.Increment()
return
}
progressBar.Increment()
wipJobMutex.Lock()
@ -905,6 +935,7 @@ func transformationWorker(thisDirectoryWG *sync.WaitGroup, thisDirectoryJobs cha
defer thisDirectoryWG.Done()
for thisJob := range thisDirectoryJobs {
transformFile(thisJob, progressBar, config)
runtime.GC()
}
}
@ -1029,6 +1060,7 @@ func main() {
DryRun bool `arg:"--dry-run" help:"dry run; don't change anything, just print what would be done"`
CleanUp bool `arg:"-c,--cleanup" help:"cleanup, delete files and directories in gallery which don't exist in source"`
NoVideos bool `arg:"--no-videos" help:"ignore videos, only include images"`
Logfile string `arg:"-l,--log" help:"recommended: log file to save errors and failed filenames to instead of stdout"`
}
// TODO implement verbose
// TODO implement logging into a file
@ -1039,14 +1071,30 @@ func main() {
// Validate source and gallery arguments, make paths absolute
args.Source, args.Gallery = validateSourceAndGallery(args.Source, args.Gallery)
fmt.Println("Creating gallery...")
// Initialize configuration (assets, directories, file types)
config := initializeConfig()
log.Println("Creating gallery...")
log.Println("Source:", args.Source)
log.Println("Gallery:", args.Gallery)
log.Println()
log.Println("Finding all media files...")
// Open log file if parameter provided
if args.Logfile != "" {
fmt.Println("Logfile:", args.Logfile)
logHandle, err := os.OpenFile(args.Logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, config.files.fileMode)
if err != nil {
fmt.Println("error opening logfile:", args.Logfile)
exit(1)
}
defer logHandle.Close()
log.SetOutput(logHandle)
log.Println("Creating gallery...")
log.Println("Source:", args.Source)
log.Println("Gallery:", args.Gallery)
}
fmt.Println("Source:", args.Source)
fmt.Println("Gallery:", args.Gallery)
fmt.Println()
fmt.Println("Finding all media files...")
// Creating a directory struct of both source as well as gallery directories
source := createDirectoryTree(args.Source, "", args.NoVideos)
@ -1060,7 +1108,7 @@ func main() {
// If there are changes, create the gallery
if changes > 0 {
log.Println(changes, "files to update")
fmt.Println(changes, "files to update")
if !exists(gallery.absPath) {
createDirectory(gallery.absPath, args.DryRun, config.files.directoryMode)
}

Loading…
Cancel
Save