Skip to content

Commit 5fb5b95

Browse files
authored
Merge pull request #17 from mutablelogic/ffmpeg61
Lots of changes for decoding
2 parents 274c43c + 5e96709 commit 5fb5b95

Some content is hidden

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

54 files changed

+2483
-809
lines changed

README.md

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ you are interested in, please see below "Contributing & Distribution" below.
1616

1717
## Requirements
1818

19-
In order to build the examples, you'll need the library and header files for [ffmpeg 6](https://ffmpeg.org/download.html) installed.
20-
The `chromaprint` library is also required for fingerprinting audio files. On Macintosh with [homebrew](http://bew.sh/), for example:
19+
In order to build the examples, you'll need the library and header files for [FFmpeg 6](https://ffmpeg.org/download.html) installed.The `chromaprint` library is also required for fingerprinting audio files. On Macintosh with [homebrew](http://bew.sh/), for example:
2120

2221
```bash
2322
brew install ffmpeg@6 chromaprint make
@@ -67,7 +66,10 @@ import (
6766
)
6867

6968
func main() {
70-
manager := media.NewManager()
69+
manager, err := media.NewManager()
70+
if err != nil {
71+
log.Fatal(err)
72+
}
7173

7274
// Open a media file for reading. The format of the file is guessed.
7375
// Alteratively, you can pass a format as the second argument. Further optional
@@ -102,9 +104,57 @@ func main() {
102104
}
103105
```
104106

105-
### Decoding
107+
### Decoding - Video Frames
106108

107-
TODO
109+
This example shows you how to decode video frames from a media file into images.
110+
111+
```go
112+
import (
113+
media "github.com/mutablelogic/go-media"
114+
)
115+
116+
func main() {
117+
manager, err := media.NewManager()
118+
if err != nil {
119+
log.Fatal(err)
120+
}
121+
122+
media, err := manager.Open("etc/test/sample.mp4", nil)
123+
if err != nil {
124+
log.Fatal(err)
125+
}
126+
defer media.Close()
127+
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+
})
138+
if err != nil {
139+
log.Fatal(err)
140+
}
141+
142+
// The frame function is called for each frame in the stream
143+
framefn := func(frame Frame) error {
144+
image, err := frame.Image()
145+
if err != nil {
146+
return err
147+
}
148+
// TODO: Do something with the image here....
149+
return nil
150+
}
151+
152+
// decode frames from the stream
153+
if err := decoder.Decode(context.Background(), framefn); err != nil {
154+
log.Fatal(err)
155+
}
156+
}
157+
```
108158

109159
### Encoding
110160

@@ -116,7 +166,53 @@ TODO
116166

117167
### Retrieving Metadata and Artwork from a media file
118168

119-
TODO
169+
Here is an example of opening a media file and retrieving metadata and artwork.
170+
171+
```go
172+
package main
173+
174+
import (
175+
"log"
176+
"os"
177+
178+
media "github.com/mutablelogic/go-media"
179+
file "github.com/mutablelogic/go-media/pkg/file"
180+
)
181+
182+
func main() {
183+
manager, err := media.NewManager()
184+
if err != nil {
185+
log.Fatal(err)
186+
}
187+
188+
// Open a media file for reading. The format of the file is guessed.
189+
// Alteratively, you can pass a format as the second argument. Further optional
190+
// arguments can be used to set the format options.
191+
reader, err := manager.Open(os.Args[1], nil)
192+
if err != nil {
193+
log.Fatal(err)
194+
}
195+
defer reader.Close()
196+
197+
// Retrieve all the metadata from the file, and display it. If you pass
198+
// keys to the Metadata function, then only entries with those keys will be
199+
// returned.
200+
for _, metadata := range reader.Metadata() {
201+
log.Print(metadata.Key(), " => ", metadata.Value())
202+
}
203+
204+
// Retrieve artwork by using the MetaArtwork key. The value is of type []byte.
205+
// which needs to be converted to an image. There is a utility method to
206+
// detect the image type.
207+
for _, artwork := range reader.Metadata(media.MetaArtwork) {
208+
mimetype, ext, err := file.MimeType(artwork.Value().([]byte))
209+
if err != nil {
210+
log.Fatal(err)
211+
}
212+
log.Print("got artwork", mimetype, ext)
213+
}
214+
}
215+
```
120216

121217
### Audio Fingerprinting
122218

@@ -139,8 +235,14 @@ The license is Apache 2 so feel free to redistribute. Redistributions in either
139235
code or binary form must reproduce the copyright notice, and please link back to this
140236
repository for more information:
141237

238+
> go-media
239+
> https://github.com/mutablelogic/go-media/
142240
> Copyright (c) 2021-2024 David Thorpe, All rights reserved.
143241
242+
This software links to shared libraries of [FFmpeg](http://ffmpeg.org/) licensed under
243+
the [LGPLv2.1](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html).
244+
144245
## References
145246

146247
* https://ffmpeg.org/doxygen/6.1/index.html
248+
* https://pkg.go.dev/github.com/mutablelogic/go-media

cmd/cli/context.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// PUBLIC METHODS
11+
12+
// ContextForSignal returns a context object which is cancelled when a signal
13+
// is received. It returns nil if no signal parameter is provided
14+
func ContextForSignal(signals ...os.Signal) context.Context {
15+
if len(signals) == 0 {
16+
return nil
17+
}
18+
19+
ch := make(chan os.Signal, 1)
20+
ctx, cancel := context.WithCancel(context.Background())
21+
22+
// Send message on channel when signal received
23+
signal.Notify(ch, signals...)
24+
25+
// When any signal received, call cancel
26+
go func() {
27+
<-ch
28+
cancel()
29+
}()
30+
31+
// Return success
32+
return ctx
33+
}

cmd/cli/decode.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"context"
55
"fmt"
6-
"os"
76

87
// Packages
98
"github.com/djthorpe/go-tablewriter"
@@ -20,7 +19,7 @@ type DecodeCmd struct {
2019
func (cmd *DecodeCmd) Run(globals *Globals) error {
2120
var format media.Format
2221

23-
manager := media.NewManager()
22+
manager := globals.manager
2423
if cmd.Format != "" {
2524
if formats := manager.InputFormats(media.NONE, cmd.Format); len(formats) == 0 {
2625
return fmt.Errorf("unknown format %q", cmd.Format)
@@ -45,13 +44,13 @@ func (cmd *DecodeCmd) Run(globals *Globals) error {
4544
}
4645

4746
// Demultiplex the stream
47+
writer := globals.writer
4848
header := []tablewriter.TableOpt{tablewriter.OptHeader()}
49-
tablewriter := tablewriter.New(os.Stdout, tablewriter.OptOutputText())
5049
return decoder.Demux(context.Background(), func(packet media.Packet) error {
5150
if packet == nil {
5251
return nil
5352
}
54-
if err := tablewriter.Write(packet, header...); err != nil {
53+
if err := writer.Write(packet, header...); err != nil {
5554
return err
5655
}
5756
// Reset the header

cmd/cli/fingerprint.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/fs"
7+
"path/filepath"
8+
"time"
9+
10+
// Packages
11+
"github.com/mutablelogic/go-media"
12+
"github.com/mutablelogic/go-media/pkg/chromaprint"
13+
"github.com/mutablelogic/go-media/pkg/file"
14+
)
15+
16+
type FimgerprintCmd struct {
17+
Path string `arg:"" required:"" help:"Media file or path" type:"path"`
18+
}
19+
20+
func (cmd *FimgerprintCmd) Run(globals *Globals) error {
21+
// Create the walker with the processor callback
22+
walker := file.NewWalker(func(ctx context.Context, root, relpath string, info fs.FileInfo) error {
23+
if info.IsDir() || info.Size() == 0 {
24+
return nil
25+
}
26+
if err := cmd.mediaWalker(ctx, globals.manager, filepath.Join(root, relpath)); err != nil {
27+
if err == context.Canceled {
28+
globals.manager.Infof("Cancelled\n")
29+
} else {
30+
globals.manager.Errorf("Error processing %q: %v\n", relpath, err)
31+
}
32+
}
33+
return nil
34+
})
35+
36+
// Walk the filesystem
37+
return walker.Walk(globals.ctx, cmd.Path)
38+
}
39+
40+
func (cmd *FimgerprintCmd) mediaWalker(ctx context.Context, manager media.Manager, path string) error {
41+
reader, err := manager.Open(path, nil)
42+
if err != nil {
43+
return err
44+
}
45+
defer reader.Close()
46+
47+
// Create a decoder for audio - needs to be s16 pcm for chromaprint
48+
decoder, err := reader.Decoder(func(stream media.Stream) (media.Parameters, error) {
49+
if stream.Type().Is(media.AUDIO) {
50+
return manager.AudioParameters("mono", "s16", 22050)
51+
} else {
52+
return nil, nil
53+
}
54+
})
55+
if err != nil {
56+
return err
57+
}
58+
59+
// Create a fingerprinter
60+
fingerprint := chromaprint.New(22050, 1, time.Minute)
61+
defer fingerprint.Close()
62+
63+
// Decode the frames
64+
if err := decoder.Decode(ctx, func(frame media.Frame) error {
65+
_, err := fingerprint.Write(frame.Int16(0))
66+
if err != nil {
67+
return err
68+
}
69+
return nil
70+
}); err != nil {
71+
return err
72+
}
73+
74+
if hash, err := fingerprint.Finish(); err != nil {
75+
return err
76+
} else {
77+
fmt.Println(path, "=>", hash)
78+
}
79+
80+
// Return success
81+
return nil
82+
}

cmd/cli/formats.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,42 @@
11
package main
22

33
import (
4-
"os"
5-
64
// Packages
75
"github.com/djthorpe/go-tablewriter"
8-
"github.com/mutablelogic/go-media"
96
)
107

8+
type CodecsCmd struct{}
9+
1110
type SampleFormatsCmd struct{}
1211

1312
type ChannelLayoutsCmd struct{}
1413

1514
type PixelFormatsCmd struct{}
1615

16+
func (cmd *CodecsCmd) Run(globals *Globals) error {
17+
manager := globals.manager
18+
writer := globals.writer
19+
codecs := manager.Codecs()
20+
return writer.Write(codecs, tablewriter.OptHeader())
21+
}
22+
1723
func (cmd *SampleFormatsCmd) Run(globals *Globals) error {
18-
manager := media.NewManager()
19-
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
20-
return writer.Write(manager.SampleFormats())
24+
manager := globals.manager
25+
writer := globals.writer
26+
formats := manager.SampleFormats()
27+
return writer.Write(formats, tablewriter.OptHeader())
2128
}
2229

2330
func (cmd *ChannelLayoutsCmd) Run(globals *Globals) error {
24-
manager := media.NewManager()
25-
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
26-
return writer.Write(manager.ChannelLayouts())
31+
manager := globals.manager
32+
writer := globals.writer
33+
layouts := manager.ChannelLayouts()
34+
return writer.Write(layouts, tablewriter.OptHeader())
2735
}
2836

2937
func (cmd *PixelFormatsCmd) Run(globals *Globals) error {
30-
manager := media.NewManager()
31-
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
32-
return writer.Write(manager.PixelFormats())
38+
manager := globals.manager
39+
writer := globals.writer
40+
formats := manager.PixelFormats()
41+
return writer.Write(formats, tablewriter.OptHeader())
3342
}

0 commit comments

Comments
 (0)