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]()}}
);
};