mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
FFMPEG auto download
This commit is contained in:
24
README.md
24
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://travis-ci.org/stashapp/stash)
|
[](https://travis-ci.org/stashapp/stash)
|
||||||
|
|
||||||
**Stash is a rails app which organizes and serves your porn.**
|
**Stash is a Go app which organizes and serves your porn.**
|
||||||
|
|
||||||
See a demo [here](https://vimeo.com/275537038) (password is stashapp).
|
See a demo [here](https://vimeo.com/275537038) (password is stashapp).
|
||||||
|
|
||||||
@@ -15,18 +15,26 @@ TODO: This is not final. There is more work to be done to ease this process.
|
|||||||
### OSX / Linux
|
### OSX / Linux
|
||||||
|
|
||||||
1. `mkdir ~/.stash` && `cd ~/.stash`
|
1. `mkdir ~/.stash` && `cd ~/.stash`
|
||||||
2. Download FFMPEG ([macOS](https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-4.0-macos64-static.zip), [Linux](https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.0.3-64bit-static.tar.xz)) and extract so that just `ffmpeg` and `ffprobe` are in `~/.stash`
|
2. Create a `config.json` file (see below).
|
||||||
3. Create a `config.json` file (see below).
|
3. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
||||||
4. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
1. Create a new folder at `C:\Users\YourUsername\.stash`
|
1. Create a new folder at `C:\Users\YourUsername\.stash`
|
||||||
2. Download [FFMPEG](https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0-win64-static.zip) and extract so that just `ffmpeg.exe` and `ffprobe.exe` are in `C:\Users\YourUsername\.stash`
|
2. Create a `config.json` file (see below)
|
||||||
3. Create a `config.json` file (see below)
|
3. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
||||||
4. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
|
||||||
|
|
||||||
### Config.json
|
#### FFMPEG
|
||||||
|
|
||||||
|
If stash is unable to find or download FFMPEG then download it yourself from the link for your platform:
|
||||||
|
|
||||||
|
* [macOS](https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-4.0-macos64-static.zip)
|
||||||
|
* [Windows](https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0-win64-static.zip)
|
||||||
|
* [Linux](https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz)
|
||||||
|
|
||||||
|
The `ffmpeg(.exe)` and `ffprobe(.exe)` files should be placed in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows.
|
||||||
|
|
||||||
|
#### Config.json
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
168
ffmpeg/downloader.go
Normal file
168
ffmpeg/downloader.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stashapp/stash/utils"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPaths(configDirectory string) (string, string) {
|
||||||
|
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
|
||||||
|
ffmpegConfigPath := filepath.Join(configDirectory, getFFMPEGFilename())
|
||||||
|
ffprobeConfigPath := filepath.Join(configDirectory, getFFProbeFilename())
|
||||||
|
ffmpegConfigExists, _ := utils.FileExists(ffmpegConfigPath)
|
||||||
|
ffprobeConfigExists, _ := utils.FileExists(ffprobeConfigPath)
|
||||||
|
if ffmpegPath == "" && ffmpegConfigExists {
|
||||||
|
ffmpegPath = ffmpegConfigPath
|
||||||
|
}
|
||||||
|
if ffprobePath == "" && ffprobeConfigExists {
|
||||||
|
ffprobePath = ffprobeConfigPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return ffmpegPath, ffprobePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func Download(configDirectory string) error {
|
||||||
|
url := getFFMPEGURL()
|
||||||
|
if url == "" {
|
||||||
|
return fmt.Errorf("no ffmpeg url for this platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure where we want to download the archive
|
||||||
|
urlExt := path.Ext(url)
|
||||||
|
archivePath := filepath.Join(configDirectory, "ffmpeg"+urlExt)
|
||||||
|
_ = os.Remove(archivePath) // remove archive if it already exists
|
||||||
|
out, err := os.Create(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the response to the archive file location
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if urlExt == ".zip" {
|
||||||
|
if err := unzip(archivePath, configDirectory); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("FFMPeg was downloaded to %s. ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFFMPEGURL() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
return "https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-4.1-macos64-static.zip"
|
||||||
|
case "linux":
|
||||||
|
// TODO: untar this
|
||||||
|
//return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz"
|
||||||
|
return ""
|
||||||
|
case "windows":
|
||||||
|
return "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.1-win64-static.zip"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFFMPEGFilename() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "ffmpeg.exe"
|
||||||
|
} else {
|
||||||
|
return "ffmpeg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFFProbeFilename() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "ffprobe.exe"
|
||||||
|
} else {
|
||||||
|
return "ffprobe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
unzippedPath := filepath.Join(configDirectory, filename)
|
||||||
|
unzippedOutput, err := os.Create(unzippedPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(unzippedOutput, rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unzippedOutput.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
2
main.go
2
main.go
@@ -18,7 +18,7 @@ func main() {
|
|||||||
//fmt.Println("hello world")
|
//fmt.Println("hello world")
|
||||||
|
|
||||||
managerInstance := manager.Initialize()
|
managerInstance := manager.Initialize()
|
||||||
database.Initialize(managerInstance.Paths.FixedPaths.DatabaseFile)
|
database.Initialize(managerInstance.StaticPaths.DatabaseFile)
|
||||||
|
|
||||||
api.Start()
|
api.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
|
|||||||
func (g *PreviewGenerator) Generate() error {
|
func (g *PreviewGenerator) Generate() error {
|
||||||
instance.Paths.Generated.EmptyTmpDir()
|
instance.Paths.Generated.EmptyTmpDir()
|
||||||
logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path)
|
logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path)
|
||||||
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
|
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||||
|
|
||||||
if err := g.generateConcatFile(); err != nil {
|
if err := g.generateConcatFile(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func NewSpriteGenerator(videoFile ffmpeg.VideoFile, imageOutputPath string, vttO
|
|||||||
|
|
||||||
func (g *SpriteGenerator) Generate() error {
|
func (g *SpriteGenerator) Generate() error {
|
||||||
instance.Paths.Generated.EmptyTmpDir()
|
instance.Paths.Generated.EmptyTmpDir()
|
||||||
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
|
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||||
|
|
||||||
if err := g.generateSpriteImage(&encoder); err != nil {
|
if err := g.generateSpriteImage(&encoder); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package manager
|
package manager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bmatcuk/doublestar"
|
"github.com/stashapp/stash/ffmpeg"
|
||||||
"github.com/stashapp/stash/logger"
|
"github.com/stashapp/stash/logger"
|
||||||
"github.com/stashapp/stash/manager/paths"
|
"github.com/stashapp/stash/manager/paths"
|
||||||
"github.com/stashapp/stash/models"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type singleton struct {
|
type singleton struct {
|
||||||
Status JobStatus
|
Status JobStatus
|
||||||
Paths *paths.Paths
|
Paths *paths.Paths
|
||||||
JSON *jsonUtils
|
StaticPaths *paths.StaticPathsType
|
||||||
|
JSON *jsonUtils
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance *singleton
|
var instance *singleton
|
||||||
@@ -26,128 +25,31 @@ func GetInstance() *singleton {
|
|||||||
func Initialize() *singleton {
|
func Initialize() *singleton {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
instance = &singleton{
|
instance = &singleton{
|
||||||
Status: Idle,
|
Status: Idle,
|
||||||
Paths: paths.RefreshPaths(),
|
Paths: paths.RefreshPaths(),
|
||||||
JSON: &jsonUtils{},
|
StaticPaths: &paths.StaticPaths,
|
||||||
|
JSON: &jsonUtils{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initFFMPEG()
|
||||||
})
|
})
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *singleton) Scan() {
|
func initFFMPEG() {
|
||||||
if s.Status != Idle { return }
|
ffmpegPath, ffprobePath := ffmpeg.GetPaths(instance.StaticPaths.ConfigDirectory)
|
||||||
s.Status = Scan
|
if ffmpegPath == "" || ffprobePath == "" {
|
||||||
|
logger.Infof("couldn't find FFMPEG, attempting to download it")
|
||||||
|
if err := ffmpeg.Download(instance.StaticPaths.ConfigDirectory); err != nil {
|
||||||
|
msg := `Unable to locate / automatically download FFMPEG
|
||||||
|
|
||||||
go func() {
|
Check the readme for download links.
|
||||||
defer s.returnToIdleState()
|
The FFMPEG and FFProbe binaries should be placed in %s
|
||||||
|
|
||||||
globPath := filepath.Join(s.Paths.Config.Stash, "**/*.{zip,m4v,mp4,mov,wmv}")
|
The error was: %s
|
||||||
globResults, _ := doublestar.Glob(globPath)
|
`
|
||||||
logger.Infof("Starting scan of %d files", len(globResults))
|
logger.Fatalf(msg, instance.StaticPaths.ConfigDirectory, err)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, path := range globResults {
|
|
||||||
wg.Add(1)
|
|
||||||
task := ScanTask{FilePath: path}
|
|
||||||
go task.Start(&wg)
|
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleton) Import() {
|
|
||||||
if s.Status != Idle { return }
|
|
||||||
s.Status = Import
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer s.returnToIdleState()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
task := ImportTask{}
|
|
||||||
go task.Start(&wg)
|
|
||||||
wg.Wait()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleton) Export() {
|
|
||||||
if s.Status != Idle { return }
|
|
||||||
s.Status = Export
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer s.returnToIdleState()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
task := ExportTask{}
|
|
||||||
go task.Start(&wg)
|
|
||||||
wg.Wait()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcodes bool) {
|
|
||||||
if s.Status != Idle { return }
|
|
||||||
s.Status = Generate
|
|
||||||
|
|
||||||
qb := models.NewSceneQueryBuilder()
|
|
||||||
//this.job.total = await ObjectionUtils.getCount(Scene);
|
|
||||||
instance.Paths.Generated.EnsureTmpDir()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer s.returnToIdleState()
|
|
||||||
|
|
||||||
scenes, err := qb.All()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("failed to get scenes for generate")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delta := btoi(sprites) + btoi(previews) + btoi(markers) + btoi(transcodes)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, scene := range scenes {
|
|
||||||
wg.Add(delta)
|
|
||||||
|
|
||||||
if sprites {
|
|
||||||
task := GenerateSpriteTask{Scene: scene}
|
|
||||||
go task.Start(&wg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if previews {
|
|
||||||
task := GeneratePreviewTask{Scene: scene}
|
|
||||||
go task.Start(&wg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if markers {
|
|
||||||
task := GenerateMarkersTask{Scene: scene}
|
|
||||||
go task.Start(&wg)
|
|
||||||
}
|
|
||||||
|
|
||||||
if transcodes {
|
|
||||||
go func() {
|
|
||||||
wg.Done() // TODO
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *singleton) returnToIdleState() {
|
|
||||||
if r := recover(); r!= nil {
|
|
||||||
logger.Info("recovered from ", r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Status == Generate {
|
|
||||||
instance.Paths.Generated.RemoveTmpDir()
|
|
||||||
}
|
|
||||||
s.Status = Idle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func btoi(b bool) int {
|
|
||||||
if b {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
120
manager/manager_tasks.go
Normal file
120
manager/manager_tasks.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bmatcuk/doublestar"
|
||||||
|
"github.com/stashapp/stash/logger"
|
||||||
|
"github.com/stashapp/stash/models"
|
||||||
|
"github.com/stashapp/stash/utils"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *singleton) Scan() {
|
||||||
|
if s.Status != Idle { return }
|
||||||
|
s.Status = Scan
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.returnToIdleState()
|
||||||
|
|
||||||
|
globPath := filepath.Join(s.Paths.Config.Stash, "**/*.{zip,m4v,mp4,mov,wmv}")
|
||||||
|
globResults, _ := doublestar.Glob(globPath)
|
||||||
|
logger.Infof("Starting scan of %d files", len(globResults))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, path := range globResults {
|
||||||
|
wg.Add(1)
|
||||||
|
task := ScanTask{FilePath: path}
|
||||||
|
go task.Start(&wg)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleton) Import() {
|
||||||
|
if s.Status != Idle { return }
|
||||||
|
s.Status = Import
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.returnToIdleState()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
task := ImportTask{}
|
||||||
|
go task.Start(&wg)
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleton) Export() {
|
||||||
|
if s.Status != Idle { return }
|
||||||
|
s.Status = Export
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.returnToIdleState()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
task := ExportTask{}
|
||||||
|
go task.Start(&wg)
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcodes bool) {
|
||||||
|
if s.Status != Idle { return }
|
||||||
|
s.Status = Generate
|
||||||
|
|
||||||
|
qb := models.NewSceneQueryBuilder()
|
||||||
|
//this.job.total = await ObjectionUtils.getCount(Scene);
|
||||||
|
instance.Paths.Generated.EnsureTmpDir()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.returnToIdleState()
|
||||||
|
|
||||||
|
scenes, err := qb.All()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to get scenes for generate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := utils.Btoi(sprites) + utils.Btoi(previews) + utils.Btoi(markers) + utils.Btoi(transcodes)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, scene := range scenes {
|
||||||
|
wg.Add(delta)
|
||||||
|
|
||||||
|
if sprites {
|
||||||
|
task := GenerateSpriteTask{Scene: scene}
|
||||||
|
go task.Start(&wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if previews {
|
||||||
|
task := GeneratePreviewTask{Scene: scene}
|
||||||
|
go task.Start(&wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if markers {
|
||||||
|
task := GenerateMarkersTask{Scene: scene}
|
||||||
|
go task.Start(&wg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transcodes {
|
||||||
|
go func() {
|
||||||
|
wg.Done() // TODO
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *singleton) returnToIdleState() {
|
||||||
|
if r := recover(); r!= nil {
|
||||||
|
logger.Info("recovered from ", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Status == Generate {
|
||||||
|
instance.Paths.Generated.RemoveTmpDir()
|
||||||
|
}
|
||||||
|
s.Status = Idle
|
||||||
|
}
|
||||||
@@ -3,13 +3,9 @@ package paths
|
|||||||
import (
|
import (
|
||||||
"github.com/stashapp/stash/manager/jsonschema"
|
"github.com/stashapp/stash/manager/jsonschema"
|
||||||
"github.com/stashapp/stash/utils"
|
"github.com/stashapp/stash/utils"
|
||||||
"os"
|
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Paths struct {
|
type Paths struct {
|
||||||
FixedPaths *fixedPaths
|
|
||||||
Config *jsonschema.Config
|
Config *jsonschema.Config
|
||||||
Generated *generatedPaths
|
Generated *generatedPaths
|
||||||
JSON *jsonPaths
|
JSON *jsonPaths
|
||||||
@@ -20,15 +16,13 @@ type Paths struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RefreshPaths() *Paths {
|
func RefreshPaths() *Paths {
|
||||||
fp := newFixedPaths()
|
ensureConfigFile()
|
||||||
ensureConfigFile(fp)
|
return newPaths()
|
||||||
return newPaths(fp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPaths(fp *fixedPaths) *Paths {
|
func newPaths() *Paths {
|
||||||
p := Paths{}
|
p := Paths{}
|
||||||
p.FixedPaths = fp
|
p.Config = jsonschema.LoadConfigFile(StaticPaths.ConfigFile)
|
||||||
p.Config = jsonschema.LoadConfigFile(p.FixedPaths.ConfigFile)
|
|
||||||
p.Generated = newGeneratedPaths(p)
|
p.Generated = newGeneratedPaths(p)
|
||||||
p.JSON = newJSONPaths(p)
|
p.JSON = newJSONPaths(p)
|
||||||
|
|
||||||
@@ -38,24 +32,8 @@ func newPaths(fp *fixedPaths) *Paths {
|
|||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExecutionDirectory() string {
|
func ensureConfigFile() {
|
||||||
ex, err := os.Executable()
|
configFileExists, _ := utils.FileExists(StaticPaths.ConfigFile) // TODO: Verify JSON is correct. Pass verified
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return filepath.Dir(ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHomeDirectory() string {
|
|
||||||
currentUser, err := user.Current()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return currentUser.HomeDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureConfigFile(fp *fixedPaths) {
|
|
||||||
configFileExists, _ := utils.FileExists(fp.ConfigFile) // TODO: Verify JSON is correct. Pass verified
|
|
||||||
if configFileExists {
|
if configFileExists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package paths
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/stashapp/stash/utils"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fixedPaths struct {
|
|
||||||
ExecutionDirectory string
|
|
||||||
ConfigDirectory string
|
|
||||||
ConfigFile string
|
|
||||||
DatabaseFile string
|
|
||||||
|
|
||||||
FFMPEG string
|
|
||||||
FFProbe string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFixedPaths() *fixedPaths {
|
|
||||||
fp := fixedPaths{}
|
|
||||||
fp.ExecutionDirectory = getExecutionDirectory()
|
|
||||||
fp.ConfigDirectory = filepath.Join(getHomeDirectory(), ".stash")
|
|
||||||
fp.ConfigFile = filepath.Join(fp.ConfigDirectory, "config.json")
|
|
||||||
fp.DatabaseFile = filepath.Join(fp.ConfigDirectory, "stash-go.sqlite")
|
|
||||||
|
|
||||||
ffmpegDirectories := []string{fp.ExecutionDirectory, fp.ConfigDirectory}
|
|
||||||
ffmpegFileName := func() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return "ffmpeg.exe"
|
|
||||||
} else {
|
|
||||||
return "ffmpeg"
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ffprobeFileName := func() string {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return "ffprobe.exe"
|
|
||||||
} else {
|
|
||||||
return "ffprobe"
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for _, directory := range ffmpegDirectories {
|
|
||||||
ffmpegPath := filepath.Join(directory, ffmpegFileName)
|
|
||||||
ffprobePath := filepath.Join(directory, ffprobeFileName)
|
|
||||||
if exists, _ := utils.FileExists(ffmpegPath); exists {
|
|
||||||
fp.FFMPEG = ffmpegPath
|
|
||||||
}
|
|
||||||
if exists, _ := utils.FileExists(ffprobePath); exists {
|
|
||||||
fp.FFProbe = ffprobePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorText := fmt.Sprintf(
|
|
||||||
"FFMPEG or FFProbe not found. Place it in one of the following folders:\n\n%s",
|
|
||||||
strings.Join(ffmpegDirectories, ","),
|
|
||||||
)
|
|
||||||
if exists, _ := utils.FileExists(fp.FFMPEG); !exists {
|
|
||||||
panic(errorText)
|
|
||||||
}
|
|
||||||
if exists, _ := utils.FileExists(fp.FFProbe); !exists {
|
|
||||||
panic(errorText)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &fp
|
|
||||||
}
|
|
||||||
44
manager/paths/paths_static.go
Normal file
44
manager/paths/paths_static.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package paths
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StaticPathsType struct {
|
||||||
|
ExecutionDirectory string
|
||||||
|
ConfigDirectory string
|
||||||
|
ConfigFile string
|
||||||
|
DatabaseFile string
|
||||||
|
|
||||||
|
FFMPEG string
|
||||||
|
FFProbe string
|
||||||
|
}
|
||||||
|
|
||||||
|
var StaticPaths = StaticPathsType{
|
||||||
|
ExecutionDirectory: getExecutionDirectory(),
|
||||||
|
ConfigDirectory: getConfigDirectory(),
|
||||||
|
ConfigFile: filepath.Join(getConfigDirectory(), "config.json"),
|
||||||
|
DatabaseFile: filepath.Join(getConfigDirectory(), "stash-go.sqlite"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecutionDirectory() string {
|
||||||
|
ex, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return filepath.Dir(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHomeDirectory() string {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return currentUser.HomeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigDirectory() string {
|
||||||
|
return filepath.Join(getHomeDirectory(), ".stash")
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFile, err := ffmpeg.NewVideoFile(instance.Paths.FixedPaths.FFProbe, t.Scene.Path)
|
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("error reading video file: %s", err.Error())
|
logger.Errorf("error reading video file: %s", err.Error())
|
||||||
return
|
return
|
||||||
@@ -35,7 +35,7 @@ func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
|
|||||||
markersFolder := filepath.Join(instance.Paths.Generated.Markers, t.Scene.Checksum)
|
markersFolder := filepath.Join(instance.Paths.Generated.Markers, t.Scene.Checksum)
|
||||||
_ = utils.EnsureDir(markersFolder)
|
_ = utils.EnsureDir(markersFolder)
|
||||||
|
|
||||||
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
|
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||||
for i, sceneMarker := range sceneMarkers {
|
for i, sceneMarker := range sceneMarkers {
|
||||||
index := i + 1
|
index := i + 1
|
||||||
logger.Progressf("[generator] <%s> scene marker %d of %d", t.Scene.Checksum, index, len(sceneMarkers))
|
logger.Progressf("[generator] <%s> scene marker %d of %d", t.Scene.Checksum, index, len(sceneMarkers))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFile, err := ffmpeg.NewVideoFile(instance.Paths.FixedPaths.FFProbe, t.Scene.Path)
|
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("error reading video file: %s", err.Error())
|
logger.Errorf("error reading video file: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (t *GenerateSpriteTask) Start(wg *sync.WaitGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFile, err := ffmpeg.NewVideoFile(instance.Paths.FixedPaths.FFProbe, t.Scene.Path)
|
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("error reading video file: %s", err.Error())
|
logger.Errorf("error reading video file: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (t *ImportTask) Start(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
t.Scraped = scraped
|
t.Scraped = scraped
|
||||||
|
|
||||||
database.Reset(instance.Paths.FixedPaths.DatabaseFile)
|
database.Reset(instance.StaticPaths.DatabaseFile)
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (t *ScanTask) scanGallery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ScanTask) scanScene() {
|
func (t *ScanTask) scanScene() {
|
||||||
videoFile, err := ffmpeg.NewVideoFile(instance.Paths.FixedPaths.FFProbe, t.FilePath)
|
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.FilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err.Error())
|
logger.Error(err.Error())
|
||||||
return
|
return
|
||||||
@@ -142,7 +142,7 @@ func (t *ScanTask) makeScreenshots(probeResult ffmpeg.VideoFile, checksum string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *ScanTask) makeScreenshot(probeResult ffmpeg.VideoFile, outputPath string, quality int, width int) {
|
func (t *ScanTask) makeScreenshot(probeResult ffmpeg.VideoFile, outputPath string, quality int, width int) {
|
||||||
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
|
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||||
options := ffmpeg.ScreenshotOptions{
|
options := ffmpeg.ScreenshotOptions{
|
||||||
OutputPath: outputPath,
|
OutputPath: outputPath,
|
||||||
Quality: quality,
|
Quality: quality,
|
||||||
|
|||||||
8
utils/boolean.go
Normal file
8
utils/boolean.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func Btoi(b bool) int {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user