Skip to content

Commit 3f7b0cf

Browse files
committed
added Videobox client
1 parent 28e4a50 commit 3f7b0cf

File tree

9 files changed

+897
-2
lines changed

9 files changed

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

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)