Skip to content

Commit 25a536a

Browse files
authored
Merge pull request #29 from mutablelogic/ffmpeg61
Added Testcard generator
2 parents fef25db + 55ceecb commit 25a536a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1291
-237
lines changed

README.md

Lines changed: 87 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ and open media files and byte streams.
6161
### Demultiplexing
6262

6363
```go
64+
package main
65+
6466
import (
6567
media "github.com/mutablelogic/go-media"
6668
)
@@ -106,51 +108,82 @@ func main() {
106108

107109
### Decoding - Video
108110

109-
This example shows you how to decode video frames from a media file into images.
111+
This example shows you how to decode video frames from a media file into images, and
112+
encode those images to JPEG format.
110113

111114
```go
115+
package main
116+
112117
import (
113-
media "github.com/mutablelogic/go-media"
118+
"context"
119+
"fmt"
120+
"image/jpeg"
121+
"io"
122+
"log"
123+
"os"
124+
"path/filepath"
125+
126+
// Packages
127+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
128+
129+
// Namespace imports
130+
. "github.com/mutablelogic/go-media"
114131
)
115132

116133
func main() {
117-
manager, err := media.NewManager()
134+
// Open a media file for reading. The format of the file is guessed.
135+
input, err := ffmpeg.Open(os.Args[1])
118136
if err != nil {
119137
log.Fatal(err)
120138
}
121139

122-
media, err := manager.Open("etc/test/sample.mp4", nil)
123-
if err != nil {
124-
log.Fatal(err)
140+
// Make a map function which can be used to decode the streams and set
141+
// the parameters we want from the decode. The audio and video streams
142+
// are resampled and resized to fit the parameters we pass back the decoder.
143+
mapfunc := func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
144+
if par.Type() == VIDEO {
145+
// Convert frame to yuv420p to frame size and rate as source
146+
return ffmpeg.VideoPar("yuv420p", par.WidthHeight(), par.FrameRate()), nil
147+
}
148+
// Ignore other streams
149+
return nil, nil
125150
}
126-
defer media.Close()
127151

128-
// Create a decoder for the media file. Only video streams are decoded
129-
decoder, err := media.Decoder(func(stream Stream) (Parameters, error) {
130-
if stream.Type() == VIDEO {
131-
// Copy video
132-
return stream.Parameters(), nil
133-
} else {
134-
// Ignore other stream types
135-
return nil, nil
136-
}
137-
})
152+
// Make a folder where we're going to store the thumbnails
153+
tmp, err := os.MkdirTemp("", "decode")
138154
if err != nil {
139155
log.Fatal(err)
140156
}
141157

142-
// The frame function is called for each frame in the stream
143-
framefn := func(frame Frame) error {
144-
image, err := frame.Image()
158+
// Decode the streams and receive the video frame
159+
// If the map function is nil, the frames are copied. In this example,
160+
// we get a yuv420p frame at the same size as the original.
161+
n := 0
162+
err = input.Decode(context.Background(), mapfunc, func(stream int, frame *ffmpeg.Frame) error {
163+
// Write the frame to a file
164+
w, err := os.Create(filepath.Join(tmp, fmt.Sprintf("frame-%d-%d.jpg", stream, n)))
145165
if err != nil {
146166
return err
147167
}
148-
// TODO: Do something with the image here....
149-
return nil
150-
}
168+
defer w.Close()
151169

152-
// decode frames from the stream
153-
if err := decoder.Decode(context.Background(), framefn); err != nil {
170+
// Convert to an image and encode a JPEG
171+
if image, err := frame.Image(); err != nil {
172+
return err
173+
} else if err := jpeg.Encode(w, image, nil); err != nil {
174+
return err
175+
} else {
176+
log.Println("Wrote:", w.Name())
177+
}
178+
179+
// End after 10 frames
180+
n++
181+
if n >= 10 {
182+
return io.EOF
183+
}
184+
return nil
185+
})
186+
if err != nil {
154187
log.Fatal(err)
155188
}
156189
}
@@ -159,68 +192,67 @@ func main() {
159192
### Encoding - Audio and Video
160193

161194
This example shows you how to encode video and audio frames into a media file.
195+
It creates a testcard signal overlayed with a timestamp, and a 1KHz tone at -5dB
162196

163197
```go
164198
package main
165199

166200
import (
201+
"fmt"
167202
"io"
168203
"log"
169204
"os"
170-
"time"
171205

172-
media "github.com/mutablelogic/go-media"
206+
// Packages
173207
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
174208
generator "github.com/mutablelogic/go-media/pkg/generator"
175-
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
176209
)
177210

211+
// This example encodes an audio an video stream to a file
178212
func main() {
179213
// Create a new file with an audio and video stream
180-
// 30fps and 22050Hz mono audio
181214
file, err := ffmpeg.Create(os.Args[1],
182-
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "640x480", 30)),
215+
ffmpeg.OptStream(1, ffmpeg.VideoPar("yuv420p", "1024x720", 30)),
183216
ffmpeg.OptStream(2, ffmpeg.AudioPar("fltp", "mono", 22050)),
184217
)
185218
if err != nil {
186219
log.Fatal(err)
187220
}
188221
defer file.Close()
189222

190-
// Make an video generator which can generate YUV420P frames
191-
// with the same parameters as the video stream
192-
video, err := generator.NewYUV420P(file.Stream(1).Par())
223+
// Make an video generator which can generate frames with the same
224+
// parameters as the video stream
225+
video, err := generator.NewEBU(file.Stream(1).Par())
193226
if err != nil {
194227
log.Fatal(err)
195228
}
196229
defer video.Close()
197230

198-
// Make an audio generator which can generate a 440Hz tone
231+
// Make an audio generator which can generate a 1KHz tone
199232
// at -5dB with the same parameters as the audio stream
200-
audio, err := generator.NewSine(440, -5, file.Stream(2).Par())
233+
audio, err := generator.NewSine(1000, -5, file.Stream(2).Par())
201234
if err != nil {
202235
log.Fatal(err)
203236
}
204237
defer audio.Close()
205238

206-
// Write 1 min of frames, passing video and audio frames to the encoder
239+
// Write 90 seconds, passing video and audio frames to the encoder
207240
// and returning io.EOF when the duration is reached
208-
duration := time.Minute
209-
if err := file.Encode(func(stream int) (*ff.AVFrame, error) {
210-
var frame media.Frame
241+
duration := float64(90)
242+
err = file.Encode(func(stream int) (*ffmpeg.Frame, error) {
243+
var frame *ffmpeg.Frame
211244
switch stream {
212245
case 1:
213246
frame = video.Frame()
214247
case 2:
215248
frame = audio.Frame()
216249
}
217-
if frame.Time() >= duration {
218-
return nil, io.EOF
219-
} else {
220-
log.Println("Frame", stream, "=>", frame.Time().Truncate(time.Millisecond))
221-
return frame.(*ffmpeg.Frame).AVFrame(), nil
250+
if frame != nil && frame.Ts() < duration {
251+
return frame, nil
222252
}
223-
}, nil); err != nil {
253+
return nil, io.EOF
254+
}, nil)
255+
if err != nil {
224256
log.Fatal(err)
225257
}
226258
}
@@ -233,6 +265,7 @@ TODO
233265
### Retrieving Metadata and Artwork from a media file
234266

235267
Here is an example of opening a media file and retrieving metadata and artwork.
268+
You have to read the artwork separately from the metadata.
236269

237270
```go
238271
package main
@@ -241,20 +274,13 @@ import (
241274
"log"
242275
"os"
243276

244-
media "github.com/mutablelogic/go-media"
245-
file "github.com/mutablelogic/go-media/pkg/file"
277+
// Packages
278+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
246279
)
247280

248281
func main() {
249-
manager, err := media.NewManager()
250-
if err != nil {
251-
log.Fatal(err)
252-
}
253-
254282
// Open a media file for reading. The format of the file is guessed.
255-
// Alteratively, you can pass a format as the second argument. Further optional
256-
// arguments can be used to set the format options.
257-
reader, err := manager.Open(os.Args[1], nil)
283+
reader, err := ffmpeg.Open(os.Args[1])
258284
if err != nil {
259285
log.Fatal(err)
260286
}
@@ -268,14 +294,13 @@ func main() {
268294
}
269295

270296
// Retrieve artwork by using the MetaArtwork key. The value is of type []byte.
271-
// which needs to be converted to an image. There is a utility method to
272-
// detect the image type.
273-
for _, artwork := range reader.Metadata(media.MetaArtwork) {
274-
mimetype, ext, err := file.MimeType(artwork.Value().([]byte))
275-
if err != nil {
276-
log.Fatal(err)
297+
// which needs to be converted to an image.
298+
for _, artwork := range reader.Metadata(ffmpeg.MetaArtwork) {
299+
mimetype := artwork.Value()
300+
if mimetype != "" {
301+
// Retrieve the data using the metadata.Bytes() method
302+
log.Print("We got some artwork of mimetype ", mimetype)
277303
}
278-
log.Print("got artwork", mimetype, ext)
279304
}
280305
}
281306
```
File renamed without changes.

cmd/examples/decode/main.go

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,75 @@ package main
22

33
import (
44
"context"
5+
"fmt"
6+
"image/jpeg"
7+
"io"
58
"log"
69
"os"
10+
"path/filepath"
711

8-
media "github.com/mutablelogic/go-media"
12+
// Packages
13+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
14+
15+
// Namespace imports
16+
. "github.com/mutablelogic/go-media"
917
)
1018

1119
func main() {
12-
manager, err := media.NewManager()
20+
// Open a media file for reading. The format of the file is guessed.
21+
input, err := ffmpeg.Open(os.Args[1])
1322
if err != nil {
1423
log.Fatal(err)
1524
}
1625

17-
// Open a media file for reading. The format of the file is guessed.
18-
// Alteratively, you can pass a format as the second argument. Further optional
19-
// arguments can be used to set the format options.
20-
file, err := manager.Open(os.Args[1], nil)
21-
if err != nil {
22-
log.Fatal(err)
26+
// Make a map function which can be used to decode the streams and set
27+
// the parameters we want each audio and video stream to have.
28+
// The audio and video streams are resampled and resized to fit the
29+
// parameters we pass to the decoder.
30+
mapfunc := func(stream int, par *ffmpeg.Par) (*ffmpeg.Par, error) {
31+
if stream == input.BestStream(VIDEO) {
32+
// Convert frame to yuv420p as needed
33+
return ffmpeg.VideoPar("yuv420p", par.WidthHeight(), par.FrameRate()), nil
34+
}
35+
// Ignore other streams
36+
return nil, nil
2337
}
24-
defer file.Close()
25-
26-
// Choose which streams to demultiplex - pass the stream parameters
27-
// to the decoder. If you don't want to resample or reformat the streams,
28-
// then you can pass nil as the function and all streams will be demultiplexed.
29-
decoder, err := file.Decoder(func(stream media.Stream) (media.Parameters, error) {
30-
// Copy streams, don't resample or resize
31-
return stream.Parameters(), nil
32-
})
38+
39+
// Make a folder where we're going to store the thumbnails
40+
tmp, err := os.MkdirTemp("", "decode")
3341
if err != nil {
3442
log.Fatal(err)
3543
}
3644

37-
// Demuliplex the stream and receive the frames of audio and video.
38-
if err := decoder.Decode(context.Background(), func(frame media.Frame) error {
39-
// Each packet is specific to a stream. It can be processed here
40-
// to receive audio or video frames, then resize or resample them,
41-
// for example. Alternatively, you can pass the packet to an encoder
42-
// to remultiplex the streams without processing them.
43-
log.Print(frame)
45+
// Decode the streams and receive the video frame
46+
// If the map function is nil, the frames are copied. In this example,
47+
// we get a yuv420p frame at the same size as the original.
48+
n := 0
49+
err = input.Decode(context.Background(), mapfunc, func(stream int, frame *ffmpeg.Frame) error {
50+
// Write the frame to a file
51+
w, err := os.Create(filepath.Join(tmp, fmt.Sprintf("frame-%d.jpg", n)))
52+
if err != nil {
53+
return err
54+
}
55+
defer w.Close()
56+
57+
// Coovert to an image and encode a JPEG
58+
if image, err := frame.Image(); err != nil {
59+
return err
60+
} else if err := jpeg.Encode(w, image, nil); err != nil {
61+
return err
62+
} else {
63+
log.Println("Wrote:", w.Name())
64+
}
4465

45-
// Return io.EOF to stop processing, nil to continue
66+
// End after 10 frames
67+
n++
68+
if n >= 10 {
69+
return io.EOF
70+
}
4671
return nil
47-
}); err != nil {
72+
})
73+
if err != nil {
4874
log.Fatal(err)
4975
}
5076
}

0 commit comments

Comments
 (0)