diff --git a/pkg/api/resolver_mutation_metadata.go b/pkg/api/resolver_mutation_metadata.go index 82f678c5c..1e63f5bac 100644 --- a/pkg/api/resolver_mutation_metadata.go +++ b/pkg/api/resolver_mutation_metadata.go @@ -15,7 +15,9 @@ import ( ) func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) { - manager.GetInstance().Scan(input) + if err := manager.GetInstance().Scan(input); err != nil { + return "", err + } return "todo", nil } @@ -71,7 +73,9 @@ func (r *mutationResolver) ExportObjects(ctx context.Context, input models.Expor } func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) { - manager.GetInstance().Generate(input) + if err := manager.GetInstance().Generate(input); err != nil { + return "", err + } return "todo", nil } diff --git a/pkg/ffmpeg/downloader.go b/pkg/ffmpeg/downloader.go index 37ed444c7..7d4374070 100644 --- a/pkg/ffmpeg/downloader.go +++ b/pkg/ffmpeg/downloader.go @@ -3,7 +3,6 @@ package ffmpeg import ( "archive/zip" "fmt" - "github.com/stashapp/stash/pkg/utils" "io" "net/http" "os" @@ -12,9 +11,23 @@ import ( "path/filepath" "runtime" "strings" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/utils" ) -func GetPaths(configDirectory string) (string, string) { +func findInPaths(paths []string, baseName string) string { + for _, p := range paths { + filePath := filepath.Join(p, baseName) + if exists, _ := utils.FileExists(filePath); exists { + return filePath + } + } + + return "" +} + +func GetPaths(paths []string) (string, string) { var ffmpegPath, ffprobePath string // Check if ffmpeg exists in the PATH @@ -24,15 +37,11 @@ func GetPaths(configDirectory string) (string, string) { } // Check if ffmpeg exists in the config directory - ffmpegConfigPath := filepath.Join(configDirectory, getFFMPEGFilename()) - ffprobeConfigPath := filepath.Join(configDirectory, getFFProbeFilename()) - ffmpegConfigExists, _ := utils.FileExists(ffmpegConfigPath) - ffprobeConfigExists, _ := utils.FileExists(ffprobeConfigPath) - if ffmpegPath == "" && ffmpegConfigExists { - ffmpegPath = ffmpegConfigPath + if ffmpegPath == "" { + ffmpegPath = findInPaths(paths, getFFMPEGFilename()) } - if ffprobePath == "" && ffprobeConfigExists { - ffprobePath = ffprobeConfigPath + if ffprobePath == "" { + ffprobePath = findInPaths(paths, getFFProbeFilename()) } return ffmpegPath, ffprobePath @@ -48,6 +57,29 @@ func Download(configDirectory string) error { return nil } +type progressReader struct { + io.Reader + lastProgress int64 + bytesRead int64 + total int64 +} + +func (r *progressReader) Read(p []byte) (int, error) { + read, err := r.Reader.Read(p) + if err == nil { + r.bytesRead += int64(read) + if r.total > 0 { + progress := int64(float64(r.bytesRead) / float64(r.total) * 100) + if progress/5 > r.lastProgress { + logger.Infof("%d%% downloaded...", progress) + r.lastProgress = progress / 5 + } + } + } + + return read, err +} + func DownloadSingle(configDirectory, url string) error { if url == "" { return fmt.Errorf("no ffmpeg url for this platform") @@ -64,6 +96,8 @@ func DownloadSingle(configDirectory, url string) error { } defer out.Close() + logger.Infof("Downloading %s...", url) + // Make the HTTP request resp, err := http.Get(url) if err != nil { @@ -76,13 +110,21 @@ func DownloadSingle(configDirectory, url string) error { return fmt.Errorf("bad status: %s", resp.Status) } + reader := &progressReader{ + Reader: resp.Body, + total: resp.ContentLength, + } + // Write the response to the archive file location - _, err = io.Copy(out, resp.Body) + _, err = io.Copy(out, reader) if err != nil { return err } + logger.Info("Downloading complete") + if urlExt == ".zip" { + logger.Infof("Unzipping %s...", archivePath) if err := unzip(archivePath, configDirectory); err != nil { return err } @@ -102,6 +144,8 @@ func DownloadSingle(configDirectory, url string) error { // xattr -c /path/to/binary -- xattr.Remove(path, "com.apple.quarantine") } + logger.Infof("ffmpeg and ffprobe successfully installed in %s", configDirectory) + } else { return fmt.Errorf("ffmpeg was downloaded to %s", archivePath) } @@ -110,7 +154,7 @@ func DownloadSingle(configDirectory, url string) error { } func getFFMPEGURL() []string { - urls := []string{""} + var urls []string switch runtime.GOOS { case "darwin": urls = []string{"https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip", "https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip"} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 68bb2647d..5d29826b7 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -110,28 +110,40 @@ func initProfiling(cpuProfilePath string) { pprof.StartCPUProfile(f) } -func initFFMPEG() { - configDirectory := paths.GetStashHomeDirectory() - ffmpegPath, ffprobePath := ffmpeg.GetPaths(configDirectory) - if ffmpegPath == "" || ffprobePath == "" { - logger.Infof("couldn't find FFMPEG, attempting to download it") - if err := ffmpeg.Download(configDirectory); err != nil { - msg := `Unable to locate / automatically download FFMPEG - -Check the readme for download links. -The FFMPEG and FFProbe binaries should be placed in %s - -The error was: %s -` - logger.Fatalf(msg, configDirectory, err) - } else { - // After download get new paths for ffmpeg and ffprobe - ffmpegPath, ffprobePath = ffmpeg.GetPaths(configDirectory) +func initFFMPEG() error { + // only do this if we have a config file set + if instance.Config.GetConfigFile() != "" { + // use same directory as config path + configDirectory := instance.Config.GetConfigPath() + paths := []string{ + configDirectory, + paths.GetStashHomeDirectory(), } + ffmpegPath, ffprobePath := ffmpeg.GetPaths(paths) + + if ffmpegPath == "" || ffprobePath == "" { + logger.Infof("couldn't find FFMPEG, attempting to download it") + if err := ffmpeg.Download(configDirectory); err != nil { + msg := `Unable to locate / automatically download FFMPEG + + Check the readme for download links. + The FFMPEG and FFProbe binaries should be placed in %s + + The error was: %s + ` + logger.Errorf(msg, configDirectory, err) + return err + } else { + // After download get new paths for ffmpeg and ffprobe + ffmpegPath, ffprobePath = ffmpeg.GetPaths(paths) + } + } + + instance.FFMPEGPath = ffmpegPath + instance.FFProbePath = ffprobePath } - instance.FFMPEGPath = ffmpegPath - instance.FFProbePath = ffprobePath + return nil } func initLog() { @@ -263,6 +275,16 @@ func (s *singleton) Setup(input models.SetupInput) error { s.Config.FinalizeSetup() + initFFMPEG() + + return nil +} + +func (s *singleton) validateFFMPEG() error { + if s.FFMPEGPath == "" || s.FFProbePath == "" { + return errors.New("missing ffmpeg and/or ffprobe") + } + return nil } diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index 2455d70f7..a0b64e0e4 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -155,9 +155,13 @@ func (s *singleton) neededScan(paths []*models.StashConfig) (total *int, newFile return &t, &n } -func (s *singleton) Scan(input models.ScanMetadataInput) { +func (s *singleton) Scan(input models.ScanMetadataInput) error { + if err := s.validateFFMPEG(); err != nil { + return err + } + if s.Status.Status != Idle { - return + return nil } s.Status.SetStatus(Scan) s.Status.indefiniteProgress() @@ -264,6 +268,8 @@ func (s *singleton) Scan(input models.ScanMetadataInput) { } logger.Info("Finished gallery association") }() + + return nil } func (s *singleton) Import() error { @@ -378,9 +384,13 @@ func setGeneratePreviewOptionsInput(optionsInput *models.GeneratePreviewOptionsI } } -func (s *singleton) Generate(input models.GenerateMetadataInput) { +func (s *singleton) Generate(input models.GenerateMetadataInput) error { + if err := s.validateFFMPEG(); err != nil { + return err + } + if s.Status.Status != Idle { - return + return nil } s.Status.SetStatus(Generate) s.Status.indefiniteProgress() @@ -571,6 +581,8 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { elapsed := time.Since(start) logger.Info(fmt.Sprintf("Generate finished (%s)", elapsed)) }() + + return nil } func (s *singleton) GenerateDefaultScreenshot(sceneId string) { diff --git a/ui/v2.5/src/components/Setup/Setup.tsx b/ui/v2.5/src/components/Setup/Setup.tsx index a80fee0cd..444c57627 100644 --- a/ui/v2.5/src/components/Setup/Setup.tsx +++ b/ui/v2.5/src/components/Setup/Setup.tsx @@ -492,15 +492,24 @@ export const Setup: React.FC = () => { : renderWelcome; const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish]; + function renderCreating() { + return ( + + + + If ffmpeg is not yet in your paths, please be patient + while stash downloads it. View the console output to see download + progress. + + + ); + } + return ( {maybeRenderGeneratedSelectDialog()}

Stash Setup Wizard

- {loading ? ( - - ) : ( - {steps[step]()} - )} + {loading ? renderCreating() : {steps[step]()}}
); };