diff --git a/examples/example_test.go b/examples/example_test.go new file mode 100644 index 0000000..d115dfb --- /dev/null +++ b/examples/example_test.go @@ -0,0 +1,23 @@ +package examples + +import ( + "testing" + + "github.com/disintegration/imaging" +) + +func TestExampleStream(t *testing.T) { + ExampleStream("./sample_data/in1.mp4", "./sample_data/out1.mp4", false) +} + +func TestExampleReadFramAsJpeg(t *testing.T) { + reader := ExampleReadFrameAsJpeg("./sample_data/in1.mp4", 5) + img, err := imaging.Decode(reader) + if err != nil { + t.Fatal(err) + } + err = imaging.Save(img, "./sample_data/out1.jpeg") + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/go.mod b/examples/go.mod index 7ce3ade..ce2b5f9 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -4,4 +4,7 @@ go 1.14 replace github.com/u2takey/ffmpeg-go => ../../ffmpeg-go -require github.com/u2takey/ffmpeg-go v0.0.0-00010101000000-000000000000 +require ( + github.com/disintegration/imaging v1.6.2 + github.com/u2takey/ffmpeg-go v0.0.0-00010101000000-000000000000 +) diff --git a/examples/go.sum b/examples/go.sum index fdd0fa1..5224ae0 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -30,6 +32,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/u2takey/go-utils v0.0.0-20200713025200-4704d09fc2c7 h1:PT7mE8HJE1mwaSazrOdSeByJ1FoV33/fHUZrBB+zwVU= github.com/u2takey/go-utils v0.0.0-20200713025200-4704d09fc2c7/go.mod h1:ATqKFpgjUIlhGRs8j59gXmu8Cmpo1QQEHV6vwu1hs28= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/examples/readFrameAsJpeg.go b/examples/readFrameAsJpeg.go new file mode 100644 index 0000000..737e59b --- /dev/null +++ b/examples/readFrameAsJpeg.go @@ -0,0 +1,23 @@ +package examples + +import ( + "bytes" + "fmt" + "io" + "os" + + ffmpeg "github.com/u2takey/ffmpeg-go" +) + +func ExampleReadFrameAsJpeg(inFileName string, frameNum int) io.Reader { + buf := bytes.NewBuffer(nil) + err := ffmpeg.Input(inFileName). + Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}). + Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}). + WithOutput(buf, os.Stdout). + Run() + if err != nil { + panic(err) + } + return buf +} diff --git a/examples/in1.mp4 b/examples/sample_data/in1.mp4 similarity index 100% rename from examples/in1.mp4 rename to examples/sample_data/in1.mp4 diff --git a/examples/sample_data/out1.jpeg b/examples/sample_data/out1.jpeg new file mode 100644 index 0000000..174d3ac Binary files /dev/null and b/examples/sample_data/out1.jpeg differ diff --git a/sample_data/out1.mp4 b/examples/sample_data/out1.mp4 similarity index 100% rename from sample_data/out1.mp4 rename to examples/sample_data/out1.mp4 diff --git a/sample_data/overlay.png b/examples/sample_data/overlay.png similarity index 100% rename from sample_data/overlay.png rename to examples/sample_data/overlay.png diff --git a/examples/stream.go b/examples/stream.go index 755ae7e..92e8055 100644 --- a/examples/stream.go +++ b/examples/stream.go @@ -1,41 +1,14 @@ -package main +package examples import ( - "bytes" "encoding/json" - "flag" "fmt" "io" "log" - "sync" ffmpeg "github.com/u2takey/ffmpeg-go" ) -var ( - InputFile = flag.String("in_filename", "./in1.mp4", "Input filename") - OutputFile = flag.String("out_filename", "./out.mp4", "Input filename") - Dream = flag.Bool("dream", false, "Use DeepDream frame processing (requires tensorflow)") -) - -// Buffer is a goroutine safe bytes.Buffer -type Buffer struct { - buffer bytes.Buffer - mutex sync.Mutex -} - -func (s *Buffer) Write(p []byte) (n int, err error) { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.buffer.Write(p) -} - -func (s *Buffer) Read(p []byte) (n int, err error) { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.buffer.Read(p) -} - func getVideoSize(fileName string) (int, int) { log.Println("Getting video size for", fileName) data, err := ffmpeg.Probe(fileName) @@ -125,7 +98,7 @@ func process(reader io.ReadCloser, writer io.WriteCloser, w, h int) { return } -func run(inFile, outFile string) { +func runExampleStream(inFile, outFile string) { w, h := getVideoSize(inFile) log.Println(w, h) @@ -145,11 +118,20 @@ func run(inFile, outFile string) { log.Println("Done") } -func main() { - flag.Parse() - if *Dream == true { - fmt.Println("tensorflow mode not implemented, todo") - return +// ExampleStream +// inFileName: input filename +// outFileName: output filename +// dream: Use DeepDream frame processing (requires tensorflow) +func ExampleStream(inFileName, outFileName string, dream bool) { + if inFileName == "" { + inFileName = "./in1.mp4" } - run(*InputFile, *OutputFile) + if outFileName == "" { + outFileName = "./out.mp4" + } + if dream { + panic("Use DeepDream With Tensorflow haven't been implemented") + } + + runExampleStream(inFileName, outFileName) } diff --git a/ffmpeg_test.go b/ffmpeg_test.go index 8de1791..494404e 100644 --- a/ffmpeg_test.go +++ b/ffmpeg_test.go @@ -9,6 +9,12 @@ import ( "github.com/u2takey/go-utils/rand" ) +const ( + TestInputFile1 = "./examples/sample_data/in1.mp4" + TestOutputFile1 = "./examples/sample_data/out1.mp4" + TestOverlayFile = "./examples/sample_data/overlay.png" +) + func TestFluentEquality(t *testing.T) { base1 := Input("dummy1.mp4") base2 := Input("dummy1.mp4") @@ -120,7 +126,7 @@ func TestFilterWithSelector(t *testing.T) { i := Input(TestInputFile1) v1 := i.Video().HFlip() - a1 := i.Audio().Filter("aecho", []string{"0.8", "0.9", "1000", "0.3"}) + a1 := i.Audio().Filter("aecho", Args{"0.8", "0.9", "1000", "0.3"}) out := Output([]*Stream{a1, v1}, TestOutputFile1) assert.Equal(t, []string{ diff --git a/filters.go b/filters.go index 9a8f479..ffaf7a3 100644 --- a/filters.go +++ b/filters.go @@ -11,15 +11,15 @@ func AssetType(hasType, expectType string, action string) { } } -func FilterMultiOutput(streamSpec []*Stream, filterName string, args []string, kwArgs ...KwArgs) *Node { +func FilterMultiOutput(streamSpec []*Stream, filterName string, args Args, kwArgs ...KwArgs) *Node { return NewFilterNode(filterName, streamSpec, -1, args, MergeKwArgs(kwArgs)) } -func Filter(streamSpec []*Stream, filterName string, args []string, kwArgs ...KwArgs) *Stream { +func Filter(streamSpec []*Stream, filterName string, args Args, kwArgs ...KwArgs) *Stream { return FilterMultiOutput(streamSpec, filterName, args, MergeKwArgs(kwArgs)).Stream("", "") } -func (s *Stream) Filter(filterName string, args []string, kwArgs ...KwArgs) *Stream { +func (s *Stream) Filter(filterName string, args Args, kwArgs ...KwArgs) *Stream { AssetType(s.Type, "FilterableStream", "filter") return Filter([]*Stream{s}, filterName, args, MergeKwArgs(kwArgs)) } diff --git a/go.mod b/go.mod index 2648d1c..d66ee13 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/u2takey/ffmpeg-go go 1.14 require ( + github.com/disintegration/imaging v1.6.2 // indirect github.com/stretchr/testify v1.4.0 github.com/tidwall/gjson v1.6.3 github.com/u2takey/go-utils v0.0.0-20200713025200-4704d09fc2c7 diff --git a/go.sum b/go.sum index fdd0fa1..5224ae0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -30,6 +32,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/u2takey/go-utils v0.0.0-20200713025200-4704d09fc2c7 h1:PT7mE8HJE1mwaSazrOdSeByJ1FoV33/fHUZrBB+zwVU= github.com/u2takey/go-utils v0.0.0-20200713025200-4704d09fc2c7/go.mod h1:ATqKFpgjUIlhGRs8j59gXmu8Cmpo1QQEHV6vwu1hs28= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/node.go b/node.go index 76df745..613c599 100644 --- a/node.go +++ b/node.go @@ -248,13 +248,12 @@ func (n *Node) GetFilter(outgoingEdges []DagEdge) string { if n.nodeType != "FilterNode" { panic("call GetFilter on non-FilterNode") } - args := n.args - kwargs := n.kwargs + args, kwargs, ret := n.args, n.kwargs, "" if n.name == "split" || n.name == "asplit" { args = []string{fmt.Sprintf("%d", len(outgoingEdges))} } - // todo escape char - for _, k := range kwargs.SortedKeys() { + args = Args(args).EscapeWith("\\'=:") + for _, k := range kwargs.EscapeWith("\\'=:").SortedKeys() { v := getString(kwargs[k]) if v != "" { args = append(args, fmt.Sprintf("%s=%s", k, v)) @@ -262,8 +261,9 @@ func (n *Node) GetFilter(outgoingEdges []DagEdge) string { args = append(args, fmt.Sprintf("%s", k)) } } + ret = escapeChars(n.name, "\\'=:") if len(args) > 0 { - return fmt.Sprintf("%s=%s", n.name, strings.Join(args, ":")) + ret += fmt.Sprintf("=%s", strings.Join(args, ":")) } - return fmt.Sprintf("%s", n.name) + return escapeChars(ret, "\\'[],;") } diff --git a/probe_test.go b/probe_test.go index 243b055..41c56a7 100644 --- a/probe_test.go +++ b/probe_test.go @@ -7,12 +7,6 @@ import ( "github.com/tidwall/gjson" ) -const ( - TestInputFile1 = "./sample_data/in1.mp4" - TestOutputFile1 = "./sample_data/out1.mp4" - TestOverlayFile = "./sample_data/overlay.png" -) - func TestProbe(t *testing.T) { data, err := Probe(TestInputFile1, nil) assert.Nil(t, err) diff --git a/run.go b/run.go index 390ee74..b7c23c7 100644 --- a/run.go +++ b/run.go @@ -57,8 +57,6 @@ func _getFilterSpec(node *Node, outOutingEdgeMap map[Label][]NodeInfo, streamNam for _, e := range outEdges { output = append(output, formatOutStreamName(streamNameMap, e)) } - //sort.Strings(input) - //sort.Strings(output) return fmt.Sprintf("%s%s%s", strings.Join(input, ""), node.GetFilter(outEdges), strings.Join(output, "")) } diff --git a/sample_data/in1.mp4 b/sample_data/in1.mp4 deleted file mode 100644 index 2c7d59e..0000000 Binary files a/sample_data/in1.mp4 and /dev/null differ diff --git a/utils.go b/utils.go index 7987da8..f809d82 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,8 @@ import ( "sort" "strconv" "strings" + + "github.com/u2takey/go-utils/sets" ) func getString(item interface{}) string { @@ -17,6 +19,8 @@ func getString(item interface{}) string { return a case []string: return strings.Join(a, ", ") + case Args: + return strings.Join(a, ", ") case []interface{}: var r []string for _, b := range a { @@ -76,17 +80,36 @@ func getHash(item interface{}) int { } } -//def escape_chars(text, chars): -// """Helper function to escape uncomfortable characters.""" -// text = str(text) -// chars = list(set(chars)) -// if '\\' in chars: -// chars.remove('\\') -// chars.insert(0, '\\') -// for ch in chars: -// text = text.replace(ch, '\\' + ch) -// return text -// +func escapeChars(text, chars string) string { + s := sets.NewString() + for _, a := range chars { + s.Insert(string(a)) + } + sl := s.List() + if s.Has("\\") { + s.Delete("\\") + sl = append([]string{"\\"}, s.List()...) + } + for _, ch := range sl { + text = strings.ReplaceAll(text, ch, "\\"+ch) + } + return text +} + +type Args []string + +func (a Args) Sorted() Args { + sort.Strings(a) + return a +} + +func (a Args) EscapeWith(chars string) Args { + out := Args{} + for _, b := range a { + out = append(out, escapeChars(b, chars)) + } + return out +} type KwArgs map[string]interface{} @@ -100,6 +123,14 @@ func MergeKwArgs(args []KwArgs) KwArgs { return a } +func (a KwArgs) EscapeWith(chars string) KwArgs { + out := KwArgs{} + for k, v := range a { + out[escapeChars(k, chars)] = escapeChars(getString(v), chars) + } + return out +} + func (a KwArgs) Copy() KwArgs { r := KwArgs{} for k := range a {