stash/pkg/ffmpeg/downloader.go

231 lines
5.3 KiB
Go
Raw Normal View History

2019-02-11 06:39:21 +00:00
package ffmpeg
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/utils"
2019-02-11 06:39:21 +00:00
)
func GetPaths(paths []string) (string, string) {
2019-02-11 06:39:21 +00:00
var ffmpegPath, ffprobePath string
// Check if ffmpeg exists in the PATH
if pathBinaryHasCorrectFlags() {
ffmpegPath, _ = exec.LookPath("ffmpeg")
ffprobePath, _ = exec.LookPath("ffprobe")
}
// Check if ffmpeg exists in the config directory
if ffmpegPath == "" {
ffmpegPath = utils.FindInPaths(paths, getFFMPEGFilename())
2019-02-11 06:39:21 +00:00
}
if ffprobePath == "" {
ffprobePath = utils.FindInPaths(paths, getFFProbeFilename())
2019-02-11 06:39:21 +00:00
}
return ffmpegPath, ffprobePath
}
func Download(configDirectory string) error {
2020-10-03 07:28:02 +00:00
for _, url := range getFFMPEGURL() {
err := DownloadSingle(configDirectory, url)
if err != nil {
return err
}
}
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
}
2020-10-03 07:28:02 +00:00
func DownloadSingle(configDirectory, url string) error {
2019-02-11 06:39:21 +00:00
if url == "" {
return fmt.Errorf("no ffmpeg url for this platform")
}
// Configure where we want to download the archive
urlExt := path.Ext(url)
2020-10-03 07:28:02 +00:00
urlBase := path.Base(url)
archivePath := filepath.Join(configDirectory, urlBase)
2019-02-11 06:39:21 +00:00
_ = os.Remove(archivePath) // remove archive if it already exists
out, err := os.Create(archivePath)
if err != nil {
2019-02-11 06:39:21 +00:00
return err
}
defer out.Close()
logger.Infof("Downloading %s...", url)
2019-02-11 06:39:21 +00:00
// Make the HTTP request
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
reader := &progressReader{
Reader: resp.Body,
total: resp.ContentLength,
}
2019-02-11 06:39:21 +00:00
// Write the response to the archive file location
_, err = io.Copy(out, reader)
if err != nil {
2019-02-11 06:39:21 +00:00
return err
}
logger.Info("Downloading complete")
2019-02-11 06:39:21 +00:00
if urlExt == ".zip" {
logger.Infof("Unzipping %s...", archivePath)
2019-02-11 06:39:21 +00:00
if err := unzip(archivePath, configDirectory); err != nil {
return err
}
// On OSX or Linux set downloaded files permissions
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
if err := os.Chmod(filepath.Join(configDirectory, "ffmpeg"), 0755); err != nil {
return err
}
if err := os.Chmod(filepath.Join(configDirectory, "ffprobe"), 0755); err != nil {
return err
}
// TODO: In future possible clear xattr to allow running on osx without user intervention
// TODO: this however may not be required.
// xattr -c /path/to/binary -- xattr.Remove(path, "com.apple.quarantine")
}
logger.Infof("ffmpeg and ffprobe successfully installed in %s", configDirectory)
2019-02-11 06:39:21 +00:00
} else {
return fmt.Errorf("ffmpeg was downloaded to %s", archivePath)
2019-02-11 06:39:21 +00:00
}
return nil
}
2020-10-03 07:28:02 +00:00
func getFFMPEGURL() []string {
var urls []string
2019-02-11 06:39:21 +00:00
switch runtime.GOOS {
case "darwin":
2020-10-03 07:28:02 +00:00
urls = []string{"https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip", "https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip"}
2019-02-11 06:39:21 +00:00
case "linux":
2020-10-03 07:28:02 +00:00
// TODO: get appropriate arch (arm,arm64,amd64) and xz untar from https://johnvansickle.com/ffmpeg/
// or get the ffmpeg,ffprobe zip repackaged ones from https://ffbinaries.com/downloads
urls = []string{""}
2019-02-11 06:39:21 +00:00
case "windows":
2020-10-03 07:28:02 +00:00
urls = []string{"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"}
2019-02-11 06:39:21 +00:00
default:
2020-10-03 07:28:02 +00:00
urls = []string{""}
2019-02-11 06:39:21 +00:00
}
2020-10-03 07:28:02 +00:00
return urls
2019-02-11 06:39:21 +00:00
}
func getFFMPEGFilename() string {
if runtime.GOOS == "windows" {
return "ffmpeg.exe"
}
return "ffmpeg"
2019-02-11 06:39:21 +00:00
}
func getFFProbeFilename() string {
if runtime.GOOS == "windows" {
return "ffprobe.exe"
}
return "ffprobe"
2019-02-11 06:39:21 +00:00
}
// Checks if FFMPEG in the path has the correct flags
func pathBinaryHasCorrectFlags() bool {
ffmpegPath, err := exec.LookPath("ffmpeg")
if err != nil {
return false
}
bytes, _ := exec.Command(ffmpegPath).CombinedOutput()
output := string(bytes)
hasOpus := strings.Contains(output, "--enable-libopus")
hasVpx := strings.Contains(output, "--enable-libvpx")
hasX264 := strings.Contains(output, "--enable-libx264")
hasX265 := strings.Contains(output, "--enable-libx265")
hasWebp := strings.Contains(output, "--enable-libwebp")
return hasOpus && hasVpx && hasX264 && hasX265 && hasWebp
}
func unzip(src, configDirectory string) error {
zipReader, err := zip.OpenReader(src)
if err != nil {
return err
}
defer zipReader.Close()
for _, f := range zipReader.File {
if f.FileInfo().IsDir() {
continue
}
filename := f.FileInfo().Name()
if filename != "ffprobe" && filename != "ffmpeg" && filename != "ffprobe.exe" && filename != "ffmpeg.exe" {
continue
}
rc, err := f.Open()
if err != nil {
return err
}
2019-02-11 06:39:21 +00:00
unzippedPath := filepath.Join(configDirectory, filename)
unzippedOutput, err := os.Create(unzippedPath)
if err != nil {
2019-02-11 06:39:21 +00:00
return err
}
_, err = io.Copy(unzippedOutput, rc)
if err != nil {
return err
}
if err := unzippedOutput.Close(); err != nil {
return err
}
}
return nil
}