-
Notifications
You must be signed in to change notification settings - Fork 2
/
generator.go
153 lines (139 loc) · 3.59 KB
/
generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package services
import (
"bytes"
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
)
// Generator generates previews for video content
type Generator struct {
sourceURL string
offset time.Duration
length time.Duration
infoHash string
path string
inited bool
b []byte
err error
mux sync.Mutex
s3 *S3Storage
}
// NewGenerator initializes new Generator instance
func NewGenerator(s3 *S3Storage, sourceURL string, offset time.Duration, length time.Duration, infoHash string, path string) *Generator {
return &Generator{s3: s3, sourceURL: sourceURL, offset: offset,
length: length, infoHash: infoHash, path: path}
}
// Using following links:
// https://superuser.com/questions/538112/meaningful-thumbnails-for-a-video-using-ffmpeg
// https://stackoverflow.com/questions/52303867/how-do-i-set-ffmpeg-pipe-output
// https://www.bogotobogo.com/FFMpeg/ffmpeg_image_scaling_jpeg.php
func (s *Generator) getParams() ([]string, error) {
codec := "libwebp"
format := "image2"
params := []string{
"-ss", fmt.Sprintf("%d", int(s.offset.Seconds())),
"-i", s.sourceURL,
"-frames:v", "1",
"-c:v", codec,
"-f", format,
}
// if s.length == 0 {
// params = append(params, "-frames:v", "1")
// } else {
// params = append(params, "-t", strconv.Itoa(int(s.length.Seconds())))
// }
// vf := []string{"select=gt(scene\\,0.5)"}
// vf := []string{}
// if s.width != 0 {
// vf = append(vf, fmt.Sprintf("scale=%d:-1", s.width))
// }
// if len(vf) > 0 {
// params = append(params, "-vf", strings.Join(vf, ","))
// }
params = append(params, "-")
logrus.Infof("FFmpeg params %v", params)
return params, nil
}
func (s *Generator) getKey() string {
name := fmt.Sprintf("%v-%v.%v", int(s.offset.Seconds()), int(s.length.Seconds()), "webp")
path := ""
if s.infoHash != "" && s.path != "" {
path = s.infoHash + strings.Split(s.path, "~")[0]
} else {
h := md5.New()
io.WriteString(h, strings.Split(s.sourceURL, "?")[0])
path = fmt.Sprintf("%x", h.Sum(nil))
}
return path + "/" + name
}
func (s *Generator) get() ([]byte, error) {
key := s.getKey()
p, err := s.s3.GetPreview(key)
if err != nil && err != ErrNoPreview {
return nil, errors.Wrap(err, "Failed to fetch preview")
}
if p != nil {
b, err := ioutil.ReadAll(p)
if err != nil {
return nil, errors.Wrapf(err, "Failed to read from S3 key=%v", b)
}
p.Close()
return b, nil
}
ffmpeg, err := exec.LookPath("ffmpeg")
if err != nil {
return nil, errors.Wrap(err, "Failed to find ffmpeg")
}
params, err := s.getParams()
if err != nil {
return nil, errors.Wrap(err, "Failed to get ffmpeg params")
}
cmd := exec.Command(ffmpeg, params...)
var stdoutBuf bytes.Buffer
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, errors.Wrap(err, "Failed to get stdout")
}
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return nil, errors.Wrap(err, "Failed to start ffmpeg")
}
done := make(chan error)
go func() {
_, err := io.Copy(&stdoutBuf, stdout)
done <- err
}()
err = <-done
if err != nil {
return nil, errors.Wrap(err, "Failed to get image")
}
b := stdoutBuf.Bytes()
if len(b) == 0 {
return nil, errors.New("No preview data")
}
err = s.s3.PutPreview(key, b)
if err != nil {
logrus.Warnf("Failed to put image to S3 key=%v", key)
}
return b, nil
}
// Get gets preview
func (s *Generator) Get() (io.Reader, error) {
s.mux.Lock()
defer s.mux.Unlock()
if s.inited {
return bytes.NewReader(s.b), s.err
}
s.b, s.err = s.get()
s.inited = true
return bytes.NewReader(s.b), s.err
}