2023-07-11 05:53:53 +00:00
|
|
|
// TODO: document in README.md
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2024-03-21 01:43:40 +00:00
|
|
|
"os/exec"
|
2023-07-11 05:53:53 +00:00
|
|
|
|
|
|
|
flag "github.com/spf13/pflag"
|
|
|
|
"github.com/stashapp/stash/pkg/ffmpeg"
|
|
|
|
"github.com/stashapp/stash/pkg/hash/videophash"
|
2023-09-01 00:39:29 +00:00
|
|
|
"github.com/stashapp/stash/pkg/models"
|
2023-07-11 05:53:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func customUsage() {
|
|
|
|
fmt.Fprintf(os.Stderr, "Usage:\n")
|
|
|
|
fmt.Fprintf(os.Stderr, "%s [OPTIONS] VIDEOFILE...\n\nOptions:\n", os.Args[0])
|
|
|
|
flag.PrintDefaults()
|
|
|
|
}
|
|
|
|
|
2024-09-03 06:33:15 +00:00
|
|
|
func printPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, quiet *bool) error {
|
2023-07-11 05:53:53 +00:00
|
|
|
ffvideoFile, err := ffp.NewVideoFile(inputfile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// All we need for videophash.Generate() is
|
|
|
|
// videoFile.Path (from BaseFile)
|
|
|
|
// videoFile.Duration
|
|
|
|
// The rest of the struct isn't needed.
|
2023-09-01 00:39:29 +00:00
|
|
|
vf := &models.VideoFile{
|
|
|
|
BaseFile: &models.BaseFile{Path: inputfile},
|
2023-07-11 05:53:53 +00:00
|
|
|
Duration: ffvideoFile.FileDuration,
|
|
|
|
}
|
|
|
|
|
|
|
|
phash, err := videophash.Generate(ff, vf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if *quiet {
|
|
|
|
fmt.Printf("%x\n", *phash)
|
|
|
|
} else {
|
|
|
|
fmt.Printf("%x %v\n", *phash, vf.Path)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-21 01:43:40 +00:00
|
|
|
func getPaths() (string, string) {
|
|
|
|
ffmpegPath, _ := exec.LookPath("ffmpeg")
|
|
|
|
ffprobePath, _ := exec.LookPath("ffprobe")
|
|
|
|
|
|
|
|
return ffmpegPath, ffprobePath
|
|
|
|
}
|
|
|
|
|
2023-07-11 05:53:53 +00:00
|
|
|
func main() {
|
|
|
|
flag.Usage = customUsage
|
|
|
|
quiet := flag.BoolP("quiet", "q", false, "print only the phash")
|
|
|
|
help := flag.BoolP("help", "h", false, "print this help output")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *help {
|
|
|
|
flag.Usage()
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
|
|
|
args := flag.Args()
|
|
|
|
|
|
|
|
if len(args) < 1 {
|
|
|
|
fmt.Fprintf(os.Stderr, "Missing VIDEOFILE argument.\n")
|
|
|
|
flag.Usage()
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(args) > 1 {
|
2023-11-28 02:56:46 +00:00
|
|
|
fmt.Fprintln(os.Stderr, "Files will be processed sequentially! If required, use e.g. GNU Parallel to run concurrently.")
|
2023-07-11 05:53:53 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "Example: parallel %v ::: *.mp4\n", os.Args[0])
|
|
|
|
}
|
|
|
|
|
2024-03-21 01:43:40 +00:00
|
|
|
ffmpegPath, ffprobePath := getPaths()
|
2023-07-11 05:53:53 +00:00
|
|
|
encoder := ffmpeg.NewEncoder(ffmpegPath)
|
2023-11-28 02:56:46 +00:00
|
|
|
// don't need to InitHWSupport, phashing doesn't use hw acceleration
|
2024-09-03 06:33:15 +00:00
|
|
|
ffprobe := ffmpeg.NewFFProbe(ffprobePath)
|
2023-07-11 05:53:53 +00:00
|
|
|
|
|
|
|
for _, item := range args {
|
|
|
|
if err := printPhash(encoder, ffprobe, item, quiet); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|