Skip to content

Commit e4ef35e

Browse files
authored
Merge pull request #9 from machinebox/videobox
added Videobox client
2 parents 14ab5a6 + f6756b3 commit e4ef35e

11 files changed

+953
-2
lines changed

textbox/textbox_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ func TestInfo(t *testing.T) {
2525
}`)
2626
}))
2727
defer srv.Close()
28-
nb := textbox.New(srv.URL)
29-
info, err := nb.Info()
28+
b := textbox.New(srv.URL)
29+
info, err := b.Info()
3030
is.NoErr(err)
3131
is.Equal(info.Name, "textbox")
3232
is.Equal(info.Version, 1)

videobox/videobox.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package videobox
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/url"
7+
"time"
8+
9+
"github.com/machinebox/sdk-go/x/boxutil"
10+
"github.com/pkg/errors"
11+
)
12+
13+
// Video represents a video.
14+
type Video struct {
15+
ID string `json:"id"`
16+
Status VideoStatus `json:"status"`
17+
Error string `json:"error"`
18+
DownloadTotal int64 `json:"downloadTotal,omitempty"`
19+
DownloadComplete int64 `json:"downloadComplete,omitempty"`
20+
DownloadEstimatedCompletion *time.Time `json:"downloadCompleteEstimate,omitempty"`
21+
TotalFrames int `json:"framesCount,omitempty"`
22+
FramesComplete int `json:"framesComplete"`
23+
LastFrameBase64 string `json:"lastFrameBase64,omitempty"`
24+
MillisecondsComplete int `json:"millisecondsComplete"`
25+
Expires *time.Time `json:"expires,omitempty"`
26+
}
27+
28+
// Client is an HTTP client that can make requests to the box.
29+
type Client struct {
30+
addr string
31+
32+
// HTTPClient is the http.Client that will be used to
33+
// make requests.
34+
HTTPClient *http.Client
35+
}
36+
37+
// New makes a new Client.
38+
func New(addr string) *Client {
39+
return &Client{
40+
addr: addr,
41+
HTTPClient: &http.Client{
42+
Timeout: 10 * time.Second,
43+
},
44+
}
45+
}
46+
47+
// Info gets the details about the box.
48+
func (c *Client) Info() (*boxutil.Info, error) {
49+
var info boxutil.Info
50+
u, err := url.Parse(c.addr + "/info")
51+
if err != nil {
52+
return nil, err
53+
}
54+
if !u.IsAbs() {
55+
return nil, errors.New("box address must be absolute")
56+
}
57+
req, err := http.NewRequest("GET", u.String(), nil)
58+
if err != nil {
59+
return nil, err
60+
}
61+
req.Header.Set("Accept", "application/json; charset=utf-8")
62+
resp, err := c.HTTPClient.Do(req)
63+
if err != nil {
64+
return nil, err
65+
}
66+
defer resp.Body.Close()
67+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
68+
return nil, errors.New(resp.Status)
69+
}
70+
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
71+
return nil, err
72+
}
73+
return &info, nil
74+
}
75+
76+
// ErrVideobox represents an error from facebox.
77+
type ErrVideobox string
78+
79+
func (e ErrVideobox) Error() string {
80+
return "videobox: " + string(e)
81+
}

videobox/videobox_check.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package videobox
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
7+
"mime/multipart"
8+
"net/http"
9+
"net/url"
10+
"strconv"
11+
"strings"
12+
"time"
13+
14+
"github.com/pkg/errors"
15+
)
16+
17+
// Check starts processing the video in the Reader.
18+
// Videobox is asynchronous, you must use Status to check when a
19+
// video processing operation has completed before using Results to
20+
// get the results.
21+
func (c *Client) Check(video io.Reader, options *CheckOptions) (*Video, error) {
22+
var buf bytes.Buffer
23+
w := multipart.NewWriter(&buf)
24+
fw, err := w.CreateFormFile("file", "image.dat")
25+
if err != nil {
26+
return nil, err
27+
}
28+
_, err = io.Copy(fw, video)
29+
if err != nil {
30+
return nil, err
31+
}
32+
if err := options.apply(w.WriteField); err != nil {
33+
return nil, errors.Wrap(err, "setting options")
34+
}
35+
if err = w.Close(); err != nil {
36+
return nil, err
37+
}
38+
u, err := url.Parse(c.addr + "/videobox/check")
39+
if err != nil {
40+
return nil, err
41+
}
42+
if !u.IsAbs() {
43+
return nil, errors.New("box address must be absolute")
44+
}
45+
req, err := http.NewRequest("POST", u.String(), &buf)
46+
if err != nil {
47+
return nil, err
48+
}
49+
req.Header.Set("Accept", "application/json; charset=utf-8")
50+
req.Header.Set("Content-Type", w.FormDataContentType())
51+
resp, err := c.HTTPClient.Do(req)
52+
if err != nil {
53+
return nil, err
54+
}
55+
defer resp.Body.Close()
56+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
57+
return nil, errors.New(resp.Status)
58+
}
59+
return c.parseCheckResponse(resp.Body)
60+
}
61+
62+
// CheckURL starts processing the video at the specified URL.
63+
// See Check for more information.
64+
func (c *Client) CheckURL(videoURL *url.URL, options *CheckOptions) (*Video, error) {
65+
u, err := url.Parse(c.addr + "/videobox/check")
66+
if err != nil {
67+
return nil, err
68+
}
69+
if !u.IsAbs() {
70+
return nil, errors.New("box address must be absolute")
71+
}
72+
if !videoURL.IsAbs() {
73+
return nil, errors.New("url must be absolute")
74+
}
75+
form := url.Values{}
76+
form.Set("url", videoURL.String())
77+
formset := func(key, value string) error {
78+
form.Set(key, value)
79+
return nil
80+
}
81+
if err := options.apply(formset); err != nil {
82+
return nil, errors.Wrap(err, "setting options")
83+
}
84+
req, err := http.NewRequest("POST", u.String(), strings.NewReader(form.Encode()))
85+
if err != nil {
86+
return nil, err
87+
}
88+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
89+
req.Header.Set("Accept", "application/json; charset=utf-8")
90+
resp, err := c.HTTPClient.Do(req)
91+
if err != nil {
92+
return nil, err
93+
}
94+
defer resp.Body.Close()
95+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
96+
return nil, errors.New(resp.Status)
97+
}
98+
return c.parseCheckResponse(resp.Body)
99+
}
100+
101+
// CheckBase64 starts processing the video from the base64 encoded data string.
102+
// See Check for more information.
103+
func (c *Client) CheckBase64(data string, options *CheckOptions) (*Video, error) {
104+
u, err := url.Parse(c.addr + "/videobox/check")
105+
if err != nil {
106+
return nil, err
107+
}
108+
if !u.IsAbs() {
109+
return nil, errors.New("box address must be absolute")
110+
}
111+
form := url.Values{}
112+
form.Set("base64", data)
113+
formset := func(key, value string) error {
114+
form.Set(key, value)
115+
return nil
116+
}
117+
if err := options.apply(formset); err != nil {
118+
return nil, errors.Wrap(err, "setting options")
119+
}
120+
req, err := http.NewRequest("POST", u.String(), strings.NewReader(form.Encode()))
121+
if err != nil {
122+
return nil, err
123+
}
124+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
125+
req.Header.Set("Accept", "application/json; charset=utf-8")
126+
resp, err := c.HTTPClient.Do(req)
127+
if err != nil {
128+
return nil, err
129+
}
130+
defer resp.Body.Close()
131+
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
132+
return nil, errors.New(resp.Status)
133+
}
134+
return c.parseCheckResponse(resp.Body)
135+
}
136+
137+
// CheckOptions are additional options that control
138+
// the behaviour of Videobox when processing videos.
139+
type CheckOptions struct {
140+
fields map[string]string
141+
}
142+
143+
// NewCheckOptions makes a new CheckOptions object.
144+
func NewCheckOptions() *CheckOptions {
145+
return &CheckOptions{
146+
fields: make(map[string]string),
147+
}
148+
}
149+
150+
// ResultsDuration sets the duration results should be kept in Videobox
151+
// before being garbage collected.
152+
func (o *CheckOptions) ResultsDuration(duration time.Duration) {
153+
o.fields["resultsDuration"] = duration.String()
154+
}
155+
156+
// SkipFrames sets the number of frames to skip between extractions.
157+
func (o *CheckOptions) SkipFrames(frames int) {
158+
o.fields["skipframes"] = strconv.Itoa(frames)
159+
}
160+
161+
// SkipSeconds sets the number of seconds to skip between frame extractions.
162+
func (o *CheckOptions) SkipSeconds(seconds int) {
163+
o.fields["skipseconds"] = strconv.Itoa(seconds)
164+
}
165+
166+
// FrameWidth sets the width of the frame to extract.
167+
func (o *CheckOptions) FrameWidth(width int) {
168+
o.fields["frameWidth"] = strconv.Itoa(width)
169+
}
170+
171+
// FrameHeight sets the height of the frame to extract.
172+
func (o *CheckOptions) FrameHeight(height int) {
173+
o.fields["frameHeight"] = strconv.Itoa(height)
174+
}
175+
176+
// FaceboxThreshold sets the minimum confidence threshold of Facebox
177+
// matches required for it to be included in the results.
178+
func (o *CheckOptions) FaceboxThreshold(v float64) {
179+
o.fields["faceboxThreshold"] = strconv.FormatFloat(v, 'f', -1, 64)
180+
}
181+
182+
// TagboxIncludeAll includes all tags in the results.
183+
func (o *CheckOptions) TagboxIncludeAll() {
184+
o.fields["tagboxInclude"] = "all"
185+
}
186+
187+
// TagboxIncludeCustom includes only custom tags in the results.
188+
func (o *CheckOptions) TagboxIncludeCustom() {
189+
o.fields["tagboxInclude"] = "custom"
190+
}
191+
192+
// TagboxThreshold sets the minimum confidence threshold of Tagbox
193+
// matches required for it to be included in the results.
194+
func (o *CheckOptions) TagboxThreshold(v float64) {
195+
o.fields["tagboxThreshold"] = strconv.FormatFloat(v, 'f', -1, 64)
196+
}
197+
198+
// NudeboxThreshold sets the minimum confidence threshold of Nudebox
199+
// matches required for it to be included in the results.
200+
func (o *CheckOptions) NudeboxThreshold(v float64) {
201+
o.fields["nudeboxThreshold"] = strconv.FormatFloat(v, 'f', -1, 64)
202+
}
203+
204+
// apply calls writeField for each field.
205+
// If o is nil, apply is noop.
206+
func (o *CheckOptions) apply(writeField func(key, value string) error) error {
207+
if o == nil {
208+
return nil
209+
}
210+
for k, v := range o.fields {
211+
if err := writeField(k, v); err != nil {
212+
return err
213+
}
214+
}
215+
return nil
216+
}
217+
218+
func (c *Client) parseCheckResponse(r io.Reader) (*Video, error) {
219+
var checkResponse struct {
220+
Success bool
221+
Error string
222+
ID string
223+
}
224+
if err := json.NewDecoder(r).Decode(&checkResponse); err != nil {
225+
return nil, errors.Wrap(err, "decoding response")
226+
}
227+
if !checkResponse.Success {
228+
return nil, ErrVideobox(checkResponse.Error)
229+
}
230+
v := &Video{
231+
ID: checkResponse.ID,
232+
}
233+
return v, nil
234+
}

0 commit comments

Comments
 (0)