diff --git a/.gitignore b/.gitignore index 7b70f7306..ead0b09f9 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ node_modules *.db /stash +/phasher dist .DS_Store -/.local* \ No newline at end of file +/.local* diff --git a/Makefile b/Makefile index 3fd45a307..5646f94bf 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,12 @@ build: build-flags build: go build $(OUTPUT) $(BUILD_FLAGS) ./cmd/stash +# TODO: Integrate the phasher target with the rest of the Makefile, +# TODO: so it can be built as part of normal releases. +.PHONY: phasher +phasher: + go build -o $@ -trimpath -buildmode=pie -ldflags '-s -w' ./cmd/phasher + # builds a dynamically-linked release binary .PHONY: build-release build-release: LDFLAGS += -s -w diff --git a/cmd/phasher/main.go b/cmd/phasher/main.go new file mode 100644 index 000000000..f4648b74e --- /dev/null +++ b/cmd/phasher/main.go @@ -0,0 +1,83 @@ +// TODO: document in README.md +package main + +import ( + "context" + "fmt" + "os" + + flag "github.com/spf13/pflag" + "github.com/stashapp/stash/pkg/ffmpeg" + "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/hash/videophash" +) + +func customUsage() { + fmt.Fprintf(os.Stderr, "Usage:\n") + fmt.Fprintf(os.Stderr, "%s [OPTIONS] VIDEOFILE...\n\nOptions:\n", os.Args[0]) + flag.PrintDefaults() +} + +func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *bool) error { + 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. + vf := &file.VideoFile{ + BaseFile: &file.BaseFile{Path: inputfile}, + 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 +} + +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 { + fmt.Fprintln(os.Stderr, "Files will be processed sequentially! Consier using GNU Parallel.") + fmt.Fprintf(os.Stderr, "Example: parallel %v ::: *.mp4\n", os.Args[0]) + } + + ffmpegPath, ffprobePath := ffmpeg.GetPaths(nil) + encoder := ffmpeg.NewEncoder(ffmpegPath) + encoder.InitHWSupport(context.TODO()) + ffprobe := ffmpeg.FFProbe(ffprobePath) + + for _, item := range args { + if err := printPhash(encoder, ffprobe, item, quiet); err != nil { + fmt.Fprintln(os.Stderr, err) + } + } +}