Skip to content

Commit

Permalink
Merge pull request #15 from mutablelogic/ffmpeg61
Browse files Browse the repository at this point in the history
Updates to the media abstraction
  • Loading branch information
djthorpe committed Jun 21, 2024
2 parents 1436b17 + b382447 commit d31618f
Show file tree
Hide file tree
Showing 48 changed files with 2,695 additions and 575 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ BUILD_DIR := "build"
CMD_DIR := $(filter-out cmd/ffmpeg/README.md, $(wildcard cmd/ffmpeg/*))
BUILD_TAG := ${DOCKER_REGISTRY}/go-media-${OS}-${ARCH}:${VERSION}

all: clean cmds
all: clean cli cmds

cmds: $(CMD_DIR)

Expand All @@ -47,6 +47,11 @@ test: go-dep
@${GO} test ./pkg/...
@${GO} test .


cli: go-dep mkdir
@echo Build media tool
@${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/media ./cmd/cli

$(CMD_DIR): go-dep mkdir
@echo Build cmd $(notdir $@)
@${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/$(notdir $@) ./$@
Expand Down
77 changes: 73 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,79 @@ DOCKER_REGISTRY=ghcr.io/mutablelogic make docker

## Examples

## Media Transcoding
There are a variety of types of object needed as part of media processing.
All examples require a `Manager` to be created, which is used to enumerate all supported formats
and open media files and byte streams.

* `Manager` is the main entry point for the package. It is used to open media files and byte streams,
and enumerate supported formats, codecs, pixel formats, etc.
* `Media` is a hardware device, file or byte stream. It contains metadata, artwork, and streams.
* `Decoder` is used to demultiplex media streams. Create a decoder and enumerate the streams which
you'd like to demultiplex. Provide the audio and video parameters if you want to resample or
reformat the streams.
* `Encoder` is used to multiplex media streams. Create an encoder and send the output of the
decoder to reencode the streams.

### Demultiplexing

```go
import (
media "github.com/mutablelogic/go-media"
)

func main() {
manager := media.NewManager()

// Open a media file for reading. The format of the file is guessed.
// Alteratively, you can pass a format as the second argument. Further optional
// arguments can be used to set the format options.
file, err := manager.Open(os.Args[1], nil)
if err != nil {
log.Fatal(err)
}
defer file.Close()

// Choose which streams to demultiplex - pass the stream parameters
// to the decoder. If you don't want to resample or reformat the streams,
// then you can pass nil as the function and all streams will be demultiplexed.
decoder, err := file.Decoder(func (stream media.Stream) (media.Parameters, error) {
return stream.Parameters(), nil
}
if err != nil {
log.Fatal(err)
}

// Demuliplex the stream and receive the packets. If you don't want to
// process the packets yourself, then you can pass nil as the function
if err := decoder.Demux(context.Background(), func(_ media.Packet) error {
// Each packet is specific to a stream. It can be processed here
// to receive audio or video frames, then resize or resample them,
// for example. Alternatively, you can pass the packet to an encoder
// to remultiplex the streams without processing them.
return nil
}); err != nil {
log.Fatal(err)
})
}
```

### Decoding

TODO

### Encoding

TODO

## Audio Fingerprinting
### Multiplexing

TODO

### Retrieving Metadata and Artwork from a media file

TODO

### Audio Fingerprinting

You can programmatically fingerprint audio files, compare fingerprints and identify music using the following packages:

Expand All @@ -73,5 +143,4 @@ repository for more information:
## References

* https://ffmpeg.org/doxygen/6.1/index.html

* https://ffmpeg.org/doxygen/6.1/index.html
47 changes: 41 additions & 6 deletions cmd/cli/decode.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,61 @@
package main

import (
"encoding/json"
"context"
"fmt"
"os"

// Packages
"github.com/djthorpe/go-tablewriter"
"github.com/mutablelogic/go-media"
)

type DecodeCmd struct {
Path string `arg:"" required:"" help:"Media file" type:"path"`
Path string `arg:"" required:"" help:"Media file" type:"path"`
Format string `name:"format" short:"f" help:"Format of input file (name, .extension or mimetype)" type:"string"`
Audio *bool `name:"audio" short:"a" help:"Output raw audio stream" type:"bool"`
Video *bool `name:"video" short:"v" help:"Output raw video stream" type:"bool"`
}

func (cmd *DecodeCmd) Run(globals *Globals) error {
reader, err := media.Open(cmd.Path, "")
var format media.Format

manager := media.NewManager()
if cmd.Format != "" {
if formats := manager.InputFormats(media.NONE, cmd.Format); len(formats) == 0 {
return fmt.Errorf("unknown format %q", cmd.Format)
} else if len(formats) > 1 {
return fmt.Errorf("ambiguous format %q", cmd.Format)
} else {
format = formats[0]
}
}

// Open media file
reader, err := manager.Open(cmd.Path, format)
if err != nil {
return err
}
defer reader.Close()

data, _ := json.MarshalIndent(reader, "", " ")
fmt.Println(string(data))
// Create a decoder - copy streams
decoder, err := reader.Decoder(nil)
if err != nil {
return err
}

return nil
// Demultiplex the stream
header := []tablewriter.TableOpt{tablewriter.OptHeader()}
tablewriter := tablewriter.New(os.Stdout, tablewriter.OptOutputText())
return decoder.Demux(context.Background(), func(packet media.Packet) error {
if packet == nil {
return nil
}
if err := tablewriter.Write(packet, header...); err != nil {
return err
}
// Reset the header
header = header[:0]
return nil
})
}
26 changes: 0 additions & 26 deletions cmd/cli/demuxers.go

This file was deleted.

33 changes: 33 additions & 0 deletions cmd/cli/formats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"os"

// Packages
"github.com/djthorpe/go-tablewriter"
"github.com/mutablelogic/go-media"
)

type SampleFormatsCmd struct{}

type ChannelLayoutsCmd struct{}

type PixelFormatsCmd struct{}

func (cmd *SampleFormatsCmd) Run(globals *Globals) error {
manager := media.NewManager()
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
return writer.Write(manager.SampleFormats())
}

func (cmd *ChannelLayoutsCmd) Run(globals *Globals) error {
manager := media.NewManager()
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
return writer.Write(manager.ChannelLayouts())
}

func (cmd *PixelFormatsCmd) Run(globals *Globals) error {
manager := media.NewManager()
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
return writer.Write(manager.PixelFormats())
}
14 changes: 9 additions & 5 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ type Globals struct {

type CLI struct {
Globals
Version VersionCmd `cmd:"version" help:"Print version information"`
Demuxers DemuxersCmd `cmd:"demuxers" help:"List media demultiplex (input) formats"`
Muxers MuxersCmd `cmd:"muxers" help:"List media multiplex (output) formats"`
Metadata MetadataCmd `cmd:"metadata" help:"Display media metadata information"`
Decode DecodeCmd `cmd:"decode" help:"Decode media"`
Version VersionCmd `cmd:"version" help:"Print version information"`
Demuxers DemuxersCmd `cmd:"demuxers" help:"List media demultiplex (input) formats"`
Muxers MuxersCmd `cmd:"muxers" help:"List media multiplex (output) formats"`
SampleFormats SampleFormatsCmd `cmd:"samplefmts" help:"List audio sample formats"`
ChannelLayouts ChannelLayoutsCmd `cmd:"channellayouts" help:"List audio channel layouts"`
PixelFormats PixelFormatsCmd `cmd:"pixelfmts" help:"List video pixel formats"`
Metadata MetadataCmd `cmd:"metadata" help:"Display media metadata information"`
Probe ProbeCmd `cmd:"probe" help:"Probe media file or device"`
Decode DecodeCmd `cmd:"decode" help:"Decode media"`
}

func main() {
Expand Down
3 changes: 2 additions & 1 deletion cmd/cli/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type MetadataCmd struct {
}

func (cmd *MetadataCmd) Run(globals *Globals) error {
reader, err := media.Open(cmd.Path, "")
manager := media.NewManager()
reader, err := manager.Open(cmd.Path, nil)
if err != nil {
return err
}
Expand Down
26 changes: 0 additions & 26 deletions cmd/cli/muxers.go

This file was deleted.

50 changes: 50 additions & 0 deletions cmd/cli/muxers_demuxers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"fmt"
"os"

// Packages
"github.com/djthorpe/go-tablewriter"
"github.com/mutablelogic/go-media"
)

type DemuxersCmd struct {
Filter string `arg:"" optional:"" help:"Filter by mimetype, name or .ext" type:"string"`
}

type MuxersCmd struct {
Filter string `arg:"" optional:"" help:"Filter by mimetype, name or .ext" type:"string"`
}

func (cmd *MuxersCmd) Run(globals *Globals) error {
manager := media.NewManager()
var formats []media.Format
if cmd.Filter == "" {
formats = manager.OutputFormats(media.ANY)
} else {
formats = manager.OutputFormats(media.ANY, cmd.Filter)
}
return Run(cmd.Filter, formats)
}

func (cmd *DemuxersCmd) Run(globals *Globals) error {
manager := media.NewManager()
var formats []media.Format
if cmd.Filter == "" {
formats = manager.InputFormats(media.ANY)
} else {
formats = manager.InputFormats(media.ANY, cmd.Filter)
}
return Run(cmd.Filter, formats)
}

func Run(filter string, formats []media.Format) error {
writer := tablewriter.New(os.Stdout, tablewriter.OptHeader(), tablewriter.OptOutputText())
if len(formats) == 0 {
fmt.Printf("No (de)muxers found for %q\n", filter)
return nil
} else {
return writer.Write(formats)
}
}
49 changes: 49 additions & 0 deletions cmd/cli/probe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"encoding/json"
"fmt"
"regexp"

// Packages
"github.com/mutablelogic/go-media"
)

type ProbeCmd struct {
Path string `arg:"" required:"" help:"Media file or device name" type:"string"`
Opts string `name:"opts" short:"o" help:"Options for opening the media file or device, (ie, \"framerate=30 video_size=176x144\")"`
}

var (
reDevice = regexp.MustCompile(`^([a-zA-Z0-9]+):(.*)$`)
)

func (cmd *ProbeCmd) Run(globals *Globals) error {
var format media.Format

manager := media.NewManager()
filter := media.NONE

// Try device first
if m := reDevice.FindStringSubmatch(cmd.Path); m != nil {
cmd.Path = m[2]
fmts := manager.InputFormats(filter|media.DEVICE, m[1])
if len(fmts) > 0 {
format = fmts[0]
}
}

// Open the media file or device
reader, err := manager.Open(cmd.Path, format, cmd.Opts)
if err != nil {
return err
}
defer reader.Close()

// Print out probe data
data, _ := json.MarshalIndent(reader, "", " ")
fmt.Println(string(data))

// Return success
return nil
}
Loading

0 comments on commit d31618f

Please sign in to comment.