mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
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:
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user