Skip to content

Commit 20ea91b

Browse files
committed
api breakage: rework the subtitle dl code, added usage example
1 parent e2ac6cb commit 20ea91b

File tree

7 files changed

+118
-79
lines changed

7 files changed

+118
-79
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
conformance-files/

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ go get github.com/martinlindhe/subtitles
1616
```
1717

1818

19+
# Example
20+
21+
Fetch subtitle from thesubdb.com:
22+
```go
23+
f, _ := os.Open(fileName)
24+
25+
finder := NewSubFinder(f, fileName, "en")
26+
27+
text, err := finder.TheSubDb()
28+
```
29+
30+
1931
# See also
2032

2133
- [subber](https://github.com/martinlindhe/subber) command line tool for subtitles

dl-conformance-files

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
3+
mkdir -p conformance-files/thesubdb
4+
5+
# fetch conformance check videos from http://thesubdb.com/api/
6+
curl http://thesubdb.com/api/samples/dexter.mp4 -o conformance-files/thesubdb/dexter.mp4
7+
curl http://thesubdb.com/api/samples/justified.mp4 -o conformance-files/thesubdb/justified.mp4

finder.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package subtitles
2+
3+
import "os"
4+
5+
// SubFinder represents a video being queried for subtitles
6+
type SubFinder struct {
7+
FileName string
8+
Language string
9+
VideoFile *os.File
10+
Quiet bool
11+
}
12+
13+
// NewSubFinder creates a SubFilePair object used to download subs for a video
14+
func NewSubFinder(video *os.File, fileName string, language string) *SubFinder {
15+
16+
return &SubFinder{
17+
FileName: fileName,
18+
Language: language,
19+
VideoFile: video,
20+
}
21+
}

testextras.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,20 @@ import (
99
const tempFilePrefix = "moviehash-temp"
1010

1111
func check(e error) {
12+
1213
if e != nil {
1314
fmt.Println(e)
1415
panic(e)
1516
}
1617
}
1718

1819
func makeTime(h int, m int, s int, ms int) time.Time {
20+
1921
return time.Date(0, 1, 1, h, m, s, ms*1000*1000, time.UTC)
2022
}
2123

2224
func createTempFile(byteSize int) string {
25+
2326
data := make([]byte, byteSize)
2427

2528
cnt := uint8(0)
@@ -39,6 +42,7 @@ func createTempFile(byteSize int) string {
3942
}
4043

4144
func createZeroedTempFile(byteSize int) string {
45+
4246
data := make([]byte, byteSize)
4347

4448
f, err := ioutil.TempFile("/tmp", tempFilePrefix)

thesubdb.go

Lines changed: 40 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,80 @@ import (
66
"io/ioutil"
77
"net/http"
88
"os"
9-
10-
log "github.com/Sirupsen/logrus"
119
)
1210

13-
// FindSub finds subtitle online, returns untouched data
14-
func FindSub(videoFileName string, language string) ([]byte, error) {
15-
if !exists(videoFileName) {
16-
return nil, fmt.Errorf("%s not found", videoFileName)
17-
}
11+
// TheSubDb downloads a subtitle from thesubdb.com
12+
func (f SubFinder) TheSubDb(args ...string) ([]byte, error) {
1813

19-
if isDirectory(videoFileName) {
20-
return nil, fmt.Errorf("%s is not a file", videoFileName)
14+
apiHost := ""
15+
if len(args) > 0 {
16+
apiHost = args[0]
17+
} else {
18+
apiHost = "api.thesubdb.com"
2119
}
2220

23-
text, err := fromTheSubDb(videoFileName, language)
21+
hash, err := SubDbHashFromFile(f.VideoFile)
2422
if err != nil {
2523
return nil, err
2624
}
2725

28-
return text, nil
29-
}
26+
client := &http.Client{}
3027

31-
// FromTheSubDb downloads a subtitle from thesubdb.com
32-
func fromTheSubDb(videoFileName string, language string, optional ...string) ([]byte, error) {
28+
query := "http://" + apiHost +
29+
"/?action=download" +
30+
"&hash=" + hash +
31+
"&language=" + f.Language
3332

34-
_apiHost := "api.thesubdb.com"
35-
if len(optional) > 0 {
36-
_apiHost = optional[0]
33+
if !f.Quiet {
34+
fmt.Println("Fetching", query, "...")
3735
}
3836

39-
hash, err := createMovieHashFromMovieFile(videoFileName)
37+
req, err := http.NewRequest("GET", query, nil)
4038
if err != nil {
4139
return nil, err
4240
}
4341

44-
actualText, err := downloadSubtitleByHash(hash, language, _apiHost)
42+
req.Header.Set("User-Agent",
43+
"SubDB/1.0 (GoSubber/1.0; https://github.com/martinlindhe/subber)")
44+
45+
resp, err := client.Do(req)
4546
if err != nil {
4647
return nil, err
4748
}
4849

49-
return actualText, nil
50-
}
50+
if resp.StatusCode == 404 {
51+
return nil, fmt.Errorf("Subtitle not found")
52+
}
5153

52-
// returns a md5-sum in hex-string representation
53-
func createMovieHashFromMovieFile(fileName string) (string, error) {
54+
if resp.StatusCode != 200 {
55+
return nil, fmt.Errorf("Server error %s", resp.Status)
56+
}
5457

55-
if !exists(fileName) {
56-
return "", fmt.Errorf("File %s not found", fileName)
58+
slurp, err := ioutil.ReadAll(resp.Body)
59+
if err != nil {
60+
return nil, fmt.Errorf("Server reading request body: %v", err)
5761
}
5862

63+
return slurp, nil
64+
}
65+
66+
// SubDbHashFromFile returns a checksum in hex-string representation
67+
// conforming to http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
68+
func SubDbHashFromFile(f *os.File) (string, error) {
69+
70+
// rewind
71+
f.Seek(0, 0)
72+
5973
// block size which is required for the API call
6074
readSize := int64(64 * 1024)
6175

62-
f, err := os.Open(fileName)
63-
if err != nil {
64-
return "", err
65-
}
66-
defer f.Close()
67-
6876
fi, err := f.Stat()
6977
if err != nil {
7078
return "", err
7179
}
7280

7381
if fi.Size() < readSize {
74-
return "", fmt.Errorf("File is too small: %s", fileName)
82+
return "", fmt.Errorf("Stream is too small: %d", fi.Size())
7583
}
7684

7785
// read first part
@@ -99,40 +107,3 @@ func createMovieHashFromMovieFile(fileName string) (string, error) {
99107

100108
return fmt.Sprintf("%x", md5.Sum(combined)), nil
101109
}
102-
103-
func downloadSubtitleByHash(hash string, language string, apiHost string) ([]byte, error) {
104-
105-
client := &http.Client{}
106-
107-
query := "http://" + apiHost + "/?action=download&hash=" + hash + "&language=" + language
108-
109-
log.Printf("Fetching %s ...\n", query)
110-
111-
req, err := http.NewRequest("GET", query, nil)
112-
if err != nil {
113-
return nil, err
114-
}
115-
116-
req.Header.Set("User-Agent",
117-
"SubDB/1.0 (GoSubber/1.0; https://github.com/martinlindhe/subber)")
118-
119-
resp, err := client.Do(req)
120-
if err != nil {
121-
return nil, err
122-
}
123-
124-
if resp.StatusCode == 404 {
125-
return nil, fmt.Errorf("Subtitle not found")
126-
}
127-
128-
if resp.StatusCode != 200 {
129-
return nil, fmt.Errorf("Server error %s", resp.Status)
130-
}
131-
132-
slurp, err := ioutil.ReadAll(resp.Body)
133-
if err != nil {
134-
return nil, fmt.Errorf("Server reading request body: %v", err)
135-
}
136-
137-
return slurp, nil
138-
}

thesubdb_test.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
package subtitles
22

33
import (
4+
"fmt"
45
"os"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
89
)
910

10-
func TestCreateMovieHashFromMovieFile(t *testing.T) {
11+
func TestDownloadFromTheSubDb(t *testing.T) {
1112

12-
fileName := createTempFile(1024 * 1024 * 2)
13+
fileName := createZeroedTempFile(1024 * 1024 * 4)
14+
defer os.Remove(fileName)
1315

14-
hash, err := createMovieHashFromMovieFile(fileName)
16+
f, err := os.Open(fileName)
17+
assert.Equal(t, nil, err)
1518

19+
hash, err := SubDbHashFromFile(f)
1620
assert.Equal(t, nil, err)
17-
assert.Equal(t, "38a503307786991a982f8ded498b90e0", hash)
21+
assert.Equal(t, "0dfbe8aa4c20b52e1b8bf3cb6cbdf193", hash)
1822

19-
os.Remove(fileName)
23+
finder := NewSubFinder(f, fileName, "en")
24+
25+
text, err := finder.TheSubDb("sandbox.thesubdb.com")
26+
assert.Equal(t, nil, err)
27+
assert.True(t, len(text) > 1000)
2028
}
2129

22-
func TestDownloadFromTheSubDb(t *testing.T) {
23-
fileName := createZeroedTempFile(1024 * 1024 * 2)
30+
func subDbConformTest(t *testing.T, fileName string, expectedHash string) {
31+
if !exists(fileName) {
32+
fmt.Println("ERROR thesubdb.com conformance tests missing, run ./hash-conformance-deps if you want to run these tests")
33+
return
34+
}
2435

25-
text, err := fromTheSubDb(fileName, "en", "sandbox.thesubdb.com")
36+
f, err := os.Open(fileName)
2637
assert.Equal(t, nil, err)
27-
assert.True(t, len(text) > 1000)
2838

29-
os.Remove(fileName)
39+
hash, err := SubDbHashFromFile(f)
40+
assert.Equal(t, nil, err)
41+
assert.Equal(t, expectedHash, hash)
42+
}
43+
44+
func TestSubDbHashFromFile(t *testing.T) {
45+
46+
// NOTE for this to work, run "./hash-conformance-deps" to fetch needed files
47+
48+
// http://thesubdb.com/api/samples/dexter.mp4
49+
subDbConformTest(t, "conformance-files/thesubdb/dexter.mp4", "ffd8d4aa68033dc03d1c8ef373b9028c")
50+
51+
// http://thesubdb.com/api/samples/justified.mp4
52+
subDbConformTest(t, "conformance-files/thesubdb/justified.mp4", "edc1981d6459c6111fe36205b4aff6c2")
3053
}

0 commit comments

Comments
 (0)