Feat: add watch & overwrite flag #55
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
|
||||
@ -51,6 +53,8 @@ func main() {
|
||||
&cli.BoolFlag{Name: "remove-source", Aliases: []string{"rs"}, Usage: "remove source file", Required: false, Value: false},
|
||||
&cli.BoolFlag{Name: "skip-noop", Aliases: []string{"n"}, Usage: "skip noop decoder", Required: false, Value: true},
|
||||
&cli.BoolFlag{Name: "update-metadata", Usage: "update metadata & album art from network", Required: false, Value: false},
|
||||
&cli.BoolFlag{Name: "overwrite", Usage: "overwrite output file without asking", Required: false, Value: false},
|
||||
&cli.BoolFlag{Name: "watch", Usage: "watch the input dir and process new files", Required: false, Value: false},
|
||||
|
||||
&cli.BoolFlag{Name: "supported-ext", Usage: "show supported file extensions and exit", Required: false, Value: false},
|
||||
},
|
||||
@ -65,6 +69,7 @@ func main() {
|
||||
logger.Fatal("run app failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func printSupportedExtensions() {
|
||||
var exts []string
|
||||
for ext := range common.DecoderRegistry {
|
||||
@ -75,6 +80,7 @@ func printSupportedExtensions() {
|
||||
fmt.Printf("%s: %d\n", ext, len(common.DecoderRegistry[ext]))
|
||||
}
|
||||
}
|
||||
|
||||
func appMain(c *cli.Context) (err error) {
|
||||
if c.Bool("supported-ext") {
|
||||
printSupportedExtensions()
|
||||
@ -129,10 +135,16 @@ func appMain(c *cli.Context) (err error) {
|
||||
skipNoopDecoder: c.Bool("skip-noop"),
|
||||
removeSource: c.Bool("remove-source"),
|
||||
updateMetadata: c.Bool("update-metadata"),
|
||||
overwriteOutput: c.Bool("overwrite"),
|
||||
}
|
||||
|
||||
if inputStat.IsDir() {
|
||||
wacthDir := c.Bool("watch")
|
||||
if !wacthDir {
|
||||
return proc.processDir(input)
|
||||
} else {
|
||||
return proc.watchDir(input)
|
||||
}
|
||||
} else {
|
||||
return proc.processFile(input)
|
||||
}
|
||||
@ -145,6 +157,61 @@ type processor struct {
|
||||
skipNoopDecoder bool
|
||||
removeSource bool
|
||||
updateMetadata bool
|
||||
overwriteOutput bool
|
||||
}
|
||||
|
||||
func (p *processor) watchDir(inputDir string) error {
|
||||
if err := p.processDir(inputDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create watcher: %w", err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) {
|
||||
// try open with exclusive mode, to avoid file is still writing
|
||||
f, err := os.OpenFile(event.Name, os.O_RDONLY, os.ModeExclusive)
|
||||
if err != nil {
|
||||
logger.Debug("failed to open file exclusively", zap.String("path", event.Name), zap.Error(err))
|
||||
time.Sleep(1 * time.Second) // wait for file writing complete
|
||||
continue
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
if err := p.processFile(event.Name); err != nil {
|
||||
logger.Warn("failed to process file", zap.String("path", event.Name), zap.Error(err))
|
||||
}
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
logger.Error("file watcher got error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Add(inputDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to watch dir %s: %w", inputDir, err)
|
||||
}
|
||||
|
||||
signalCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
|
||||
<-signalCtx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *processor) processDir(inputDir string) error {
|
||||
@ -266,6 +333,15 @@ func (p *processor) process(inputFile string, allDec []common.NewDecoderFunc) er
|
||||
inFilename := strings.TrimSuffix(filepath.Base(inputFile), filepath.Ext(inputFile))
|
||||
outPath := filepath.Join(p.outputDir, inFilename+params.AudioExt)
|
||||
|
||||
if !p.overwriteOutput {
|
||||
_, err := os.Stat(outPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("output file %s is already exist", outPath)
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("stat output file failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if params.Meta == nil {
|
||||
outFile, err := os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
|
2
go.mod
2
go.mod
@ -3,6 +3,7 @@ module unlock-music.dev/cli
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-flac/flacpicture v0.2.0
|
||||
github.com/go-flac/flacvorbis v0.1.0
|
||||
github.com/go-flac/go-flac v0.3.1
|
||||
@ -22,5 +23,6 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
5
go.sum
5
go.sum
@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ddliu/go-httpclient v0.5.1 h1:ys4KozrhBaGdI1yuWIFwNNILqhnMU9ozTvRNfCTorvs=
|
||||
github.com/ddliu/go-httpclient v0.5.1/go.mod h1:8QVbjq00YK2f2MQyiKuWMdaKOFRcoD9VuubkNCNOuZo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/go-flac/flacpicture v0.2.0 h1:rS/ZOR/ZxlEwMf3yOPFcTAmGyoV6rDtcYdd+6CwWQAw=
|
||||
github.com/go-flac/flacpicture v0.2.0/go.mod h1:M4a1J0v6B5NHsck4GA1yZg0vFQzETVPd3kuj6Ow+q9o=
|
||||
github.com/go-flac/flacvorbis v0.1.0 h1:xStJfPrZ/IoA2oBUEwgrlaSf+Opo6/YuQfkqVhkP0cM=
|
||||
@ -46,6 +48,9 @@ golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
Loading…
Reference in New Issue
Block a user