Make ffmpeg download location more portable (#1384)

* Download ffmpeg to config path
* Add setup message for ffmpeg download
* Handle missing ffmpeg in tasks
This commit is contained in:
WithoutPants
2021-05-18 09:14:25 +10:00
committed by GitHub
parent 3df7ee06eb
commit 22a2fc3177
5 changed files with 133 additions and 42 deletions

View File

@@ -15,7 +15,9 @@ import (
) )
func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) { 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 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) { 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 return "todo", nil
} }

View File

@@ -3,7 +3,6 @@ package ffmpeg
import ( import (
"archive/zip" "archive/zip"
"fmt" "fmt"
"github.com/stashapp/stash/pkg/utils"
"io" "io"
"net/http" "net/http"
"os" "os"
@@ -12,9 +11,23 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "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 var ffmpegPath, ffprobePath string
// Check if ffmpeg exists in the PATH // 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 // Check if ffmpeg exists in the config directory
ffmpegConfigPath := filepath.Join(configDirectory, getFFMPEGFilename()) if ffmpegPath == "" {
ffprobeConfigPath := filepath.Join(configDirectory, getFFProbeFilename()) ffmpegPath = findInPaths(paths, getFFMPEGFilename())
ffmpegConfigExists, _ := utils.FileExists(ffmpegConfigPath)
ffprobeConfigExists, _ := utils.FileExists(ffprobeConfigPath)
if ffmpegPath == "" && ffmpegConfigExists {
ffmpegPath = ffmpegConfigPath
} }
if ffprobePath == "" && ffprobeConfigExists { if ffprobePath == "" {
ffprobePath = ffprobeConfigPath ffprobePath = findInPaths(paths, getFFProbeFilename())
} }
return ffmpegPath, ffprobePath return ffmpegPath, ffprobePath
@@ -48,6 +57,29 @@ func Download(configDirectory string) error {
return nil 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 { func DownloadSingle(configDirectory, url string) error {
if url == "" { if url == "" {
return fmt.Errorf("no ffmpeg url for this platform") return fmt.Errorf("no ffmpeg url for this platform")
@@ -64,6 +96,8 @@ func DownloadSingle(configDirectory, url string) error {
} }
defer out.Close() defer out.Close()
logger.Infof("Downloading %s...", url)
// Make the HTTP request // Make the HTTP request
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
@@ -76,13 +110,21 @@ func DownloadSingle(configDirectory, url string) error {
return fmt.Errorf("bad status: %s", resp.Status) return fmt.Errorf("bad status: %s", resp.Status)
} }
reader := &progressReader{
Reader: resp.Body,
total: resp.ContentLength,
}
// Write the response to the archive file location // Write the response to the archive file location
_, err = io.Copy(out, resp.Body) _, err = io.Copy(out, reader)
if err != nil { if err != nil {
return err return err
} }
logger.Info("Downloading complete")
if urlExt == ".zip" { if urlExt == ".zip" {
logger.Infof("Unzipping %s...", archivePath)
if err := unzip(archivePath, configDirectory); err != nil { if err := unzip(archivePath, configDirectory); err != nil {
return err return err
} }
@@ -102,6 +144,8 @@ func DownloadSingle(configDirectory, url string) error {
// xattr -c /path/to/binary -- xattr.Remove(path, "com.apple.quarantine") // xattr -c /path/to/binary -- xattr.Remove(path, "com.apple.quarantine")
} }
logger.Infof("ffmpeg and ffprobe successfully installed in %s", configDirectory)
} else { } else {
return fmt.Errorf("ffmpeg was downloaded to %s", archivePath) return fmt.Errorf("ffmpeg was downloaded to %s", archivePath)
} }
@@ -110,7 +154,7 @@ func DownloadSingle(configDirectory, url string) error {
} }
func getFFMPEGURL() []string { func getFFMPEGURL() []string {
urls := []string{""} var urls []string
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin":
urls = []string{"https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip", "https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip"} urls = []string{"https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip", "https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip"}

View File

@@ -110,28 +110,40 @@ func initProfiling(cpuProfilePath string) {
pprof.StartCPUProfile(f) pprof.StartCPUProfile(f)
} }
func initFFMPEG() { func initFFMPEG() error {
configDirectory := paths.GetStashHomeDirectory() // only do this if we have a config file set
ffmpegPath, ffprobePath := ffmpeg.GetPaths(configDirectory) if instance.Config.GetConfigFile() != "" {
if ffmpegPath == "" || ffprobePath == "" { // use same directory as config path
logger.Infof("couldn't find FFMPEG, attempting to download it") configDirectory := instance.Config.GetConfigPath()
if err := ffmpeg.Download(configDirectory); err != nil { paths := []string{
msg := `Unable to locate / automatically download FFMPEG configDirectory,
paths.GetStashHomeDirectory(),
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)
} }
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 return nil
instance.FFProbePath = ffprobePath
} }
func initLog() { func initLog() {
@@ -263,6 +275,16 @@ func (s *singleton) Setup(input models.SetupInput) error {
s.Config.FinalizeSetup() 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 return nil
} }

View File

@@ -155,9 +155,13 @@ func (s *singleton) neededScan(paths []*models.StashConfig) (total *int, newFile
return &t, &n 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 { if s.Status.Status != Idle {
return return nil
} }
s.Status.SetStatus(Scan) s.Status.SetStatus(Scan)
s.Status.indefiniteProgress() s.Status.indefiniteProgress()
@@ -264,6 +268,8 @@ func (s *singleton) Scan(input models.ScanMetadataInput) {
} }
logger.Info("Finished gallery association") logger.Info("Finished gallery association")
}() }()
return nil
} }
func (s *singleton) Import() error { 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 { if s.Status.Status != Idle {
return return nil
} }
s.Status.SetStatus(Generate) s.Status.SetStatus(Generate)
s.Status.indefiniteProgress() s.Status.indefiniteProgress()
@@ -571,6 +581,8 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) {
elapsed := time.Since(start) elapsed := time.Since(start)
logger.Info(fmt.Sprintf("Generate finished (%s)", elapsed)) logger.Info(fmt.Sprintf("Generate finished (%s)", elapsed))
}() }()
return nil
} }
func (s *singleton) GenerateDefaultScreenshot(sceneId string) { func (s *singleton) GenerateDefaultScreenshot(sceneId string) {

View File

@@ -492,15 +492,24 @@ export const Setup: React.FC = () => {
: renderWelcome; : renderWelcome;
const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish]; const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish];
function renderCreating() {
return (
<Card>
<LoadingIndicator message="Creating your system" />
<Alert variant="info text-center">
If <code>ffmpeg</code> is not yet in your paths, please be patient
while stash downloads it. View the console output to see download
progress.
</Alert>
</Card>
);
}
return ( return (
<Container> <Container>
{maybeRenderGeneratedSelectDialog()} {maybeRenderGeneratedSelectDialog()}
<h1 className="text-center">Stash Setup Wizard</h1> <h1 className="text-center">Stash Setup Wizard</h1>
{loading ? ( {loading ? renderCreating() : <Card>{steps[step]()}</Card>}
<LoadingIndicator message="Creating your system" />
) : (
<Card>{steps[step]()}</Card>
)}
</Container> </Container>
); );
}; };