Commit f60c7446 authored by Drew Parsons's avatar Drew Parsons

New upstream version 1.0.0

parents
This diff is collapsed.
package ffprobe
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os/exec"
"sync"
)
type Cmd struct {
Cmd *exec.Cmd
Done chan struct{}
mu sync.Mutex
Info *Info
Err error
}
func Start(path string) (ret *Cmd, err error) {
if !exeFound() {
err = ExeNotFound
return
}
cmd := exec.Command(exePath,
"-loglevel", "error",
"-show_format",
"-show_streams",
outputFormatFlag, "json",
path)
setHideWindow(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
return
}
err = cmd.Start()
if err != nil {
return
}
ret = &Cmd{
Cmd: cmd,
Done: make(chan struct{}),
}
go ret.runner(stdout, stderr)
return
}
func (me *Cmd) runner(stdout, stderr io.ReadCloser) {
defer close(me.Done)
lastErrLineCh := lastLineCh(stderr)
d := json.NewDecoder(bufio.NewReader(stdout))
decodeErr := d.Decode(&me.Info)
stdout.Close()
lastErrLine, lastErrLineOk := <-lastErrLineCh
stderr.Close()
waitErr := me.Cmd.Wait()
if waitErr == nil {
me.Err = decodeErr
return
}
if lastErrLineOk {
me.Err = fmt.Errorf("%s: %s", waitErr, lastErrLine)
} else {
me.Err = waitErr
}
return
}
// Returns the last line in r. ok is false if there are no lines. err is any
// error that occurs during scanning.
func lastLine(r io.Reader) (line string, ok bool, err error) {
s := bufio.NewScanner(r)
s.Split(bufio.ScanLines)
for s.Scan() {
line = s.Text()
ok = true
}
err = s.Err()
return
}
// Returns a channel that receives the last line in r.
func lastLineCh(r io.Reader) <-chan string {
ch := make(chan string, 1)
go func() {
defer close(ch)
line, ok, err := lastLine(r)
switch err {
default:
panic(err)
case nil, io.ErrClosedPipe:
}
if ok {
ch <- line
}
}()
return ch
}
// +build !windows
package ffprobe
import (
"os/exec"
)
func setHideWindow(cmd *exec.Cmd) {}
package ffprobe
import (
"os/exec"
"syscall"
)
func setHideWindow(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
}
}
// Package ffprobe wraps and interprets ffmpeg's ffprobe for Go.
package ffprobe
import "errors"
var ExeNotFound = errors.New("ffprobe and avprobe not found in $PATH")
// Runs ffprobe or avprobe or similar on the given file path.
func Run(path string) (info *Info, err error) {
pc, err := Start(path)
if err != nil {
return
}
<-pc.Done
info, err = pc.Info, pc.Err
return
}
package ffprobe
import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"testing"
"time"
_ "github.com/anacrolix/envpprof"
"github.com/anacrolix/missinggo/leaktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEmptyFile(t *testing.T) {
if !exeFound() {
t.SkipNow()
}
f, err := ioutil.TempFile("", "")
require.NoError(t, err)
defer os.Remove(f.Name())
_, err = Run(f.Name())
assert.EqualError(t, err, fmt.Sprintf("exit status 1: %s: Invalid data found when processing input", f.Name()))
}
func TestKilledWhileStuckReading(t *testing.T) {
if !exeFound() {
t.SkipNow()
}
time.Sleep(time.Second)
defer leaktest.GoroutineLeakCheck(t)()
l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
s := http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Print("got request")
<-r.Context().Done()
}),
}
go func() {
log.Printf("serve returned: %s", s.Serve(l))
}()
defer s.Close()
cmd, err := Start("http://" + l.Addr().String())
require.NoError(t, err)
require.NoError(t, cmd.Cmd.Process.Kill())
s.Close()
// time.Sleep(time.Second)
// select {}
}
module github.com/anacrolix/ffprobe
go 1.12
require (
github.com/anacrolix/envpprof v1.0.0
github.com/anacrolix/missinggo v1.1.0
github.com/stretchr/testify v1.3.0
)
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.0.0 h1:AwZ+mBP4rQ5f7JSsrsN3h7M2xDW/xSE66IPVOqlnuUc=
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/missinggo v1.1.0 h1:0lZbaNa6zTR1bELAIzCNmRGAtkHuLDPJqTiTtXoAIx8=
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 h1:1B/+1BcRhOMG1KH/YhNIU8OppSWk5d/NGyfRla88CuY=
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
package ffprobe
import (
"errors"
"fmt"
"time"
)
type Info struct {
Format map[string]interface{}
Streams []map[string]interface{}
}
// returns res attributes for the raw stream
func (info *Info) Bitrate() (bitrate uint, err error) {
bit_rate, exist := info.Format["bit_rate"]
if !exist {
err = errors.New("no bit_rate key in format")
return
}
_, err = fmt.Sscan(bit_rate.(string), &bitrate)
return
}
func (info *Info) Duration() (duration time.Duration, err error) {
di := info.Format["duration"]
if di == nil {
err = errors.New("missing value")
return
}
ds := di.(string)
if ds == "N/A" {
err = errors.New("N/A")
return
}
var f float64
_, err = fmt.Sscan(ds, &f)
if err != nil {
return
}
duration = time.Duration(f * float64(time.Second))
return
}
package ffprobe
import (
"testing"
"time"
)
func TestInfoHelperMethods(t *testing.T) {
for _, tc := range []struct {
Info // Input data
time.Duration // Expected duration
uint // Expected bitrate
}{
{Info{
Format: map[string]interface{}{"duration": "N/A"},
}, 0, 0},
{Info{Format: map[string]interface{}{"bit_rate": "128000.000000", "duration": "N/A"}}, 0, 128000},
{Info{}, 0, 0},
{Info{Format: map[string]interface{}{"duration": "1377.628452"}}, 1377628452000, 0},
} {
br, _ := tc.Bitrate()
if br != tc.uint {
t.Fatalf("bitrate is %d, but expected %d", br, tc.uint)
}
d, _ := tc.Info.Duration()
if d != tc.Duration {
t.Fatalf("duration is %s but expected %s", d, tc.Duration)
}
}
}
package ffprobe
import (
"log"
"os/exec"
)
var (
exePath string
outputFormatFlag = "-of"
)
func exeFound() bool {
return exePath != ""
}
func init() {
var err error
exePath, err = exec.LookPath("ffprobe")
if err == nil {
outputFormatFlag = "-print_format"
return
}
if !isExecErrNotFound(err) {
log.Print(err)
}
exePath, err = exec.LookPath("avprobe")
if err == nil {
return
}
if isExecErrNotFound(err) {
err = ExeNotFound
}
log.Print(err)
}
func isExecErrNotFound(err error) bool {
if err == exec.ErrNotFound {
return true
}
execErr, ok := err.(*exec.Error)
if !ok {
return false
}
return execErr.Err == exec.ErrNotFound
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment