Skip to content

Commit dd81930

Browse files
committed
Adding formats
1 parent d0d1853 commit dd81930

File tree

6 files changed

+367
-23
lines changed

6 files changed

+367
-23
lines changed

manager.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,11 @@ type Manager interface {
6868
// Codec name, Profile name, Framerate (fps) and VideoParameters
6969
//VideoCodecParameters(string, string, float64, VideoParameters) (Parameters, error)
7070

71-
// Return supported input formats which match any filter, which can be
72-
// a name, extension (with preceeding period) or mimetype. The MediaType
73-
// can be NONE (for any) or combinations of DEVICE and STREAM.
74-
//InputFormats(Type, ...string) []Format
75-
76-
// Return supported output formats which match any filter, which can be
77-
// a name, extension (with preceeding period) or mimetype. The MediaType
78-
// can be NONE (for any) or combinations of DEVICE and STREAM.
79-
//OutputFormats(Type, ...string) []Format
71+
// Return supported input and output container formats which match any filter,
72+
// which can be a name, extension (with preceeding period) or mimetype. The Type
73+
// can be a combination of DEVICE, INPUT, OUTPUT or ANY to select the right kind of
74+
// format
75+
Formats(Type, ...string) []Format
8076

8177
// Return all supported sample formats
8278
SampleFormats() []Metadata
@@ -107,3 +103,13 @@ type Manager interface {
107103
// Log info messages with arguments
108104
Infof(string, ...any)
109105
}
106+
107+
// A container format for a media file or stream
108+
type Format interface {
109+
// The type of the format, which can be combinations of
110+
// INPUT, OUTPUT, DEVICE, AUDIO, VIDEO and SUBTITLE
111+
Type() Type
112+
113+
// The unique name that the format can be referenced as
114+
Name() string
115+
}

pkg/ffmpeg/format.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package ffmpeg
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
// Packages
9+
media "github.com/mutablelogic/go-media"
10+
ff "github.com/mutablelogic/go-media/sys/ffmpeg61"
11+
)
12+
13+
///////////////////////////////////////////////////////////////////////////////
14+
// TYPES
15+
16+
type metaFormat struct {
17+
Type media.Type `json:"type"`
18+
Name string `json:"name"`
19+
}
20+
21+
type Format struct {
22+
metaFormat
23+
Input *ff.AVInputFormat `json:"input,omitempty"`
24+
Output *ff.AVOutputFormat `json:"output,omitempty"`
25+
Devices []*Device `json:"devices,omitempty"`
26+
}
27+
28+
type Device struct {
29+
metaDevice
30+
}
31+
32+
type metaDevice struct {
33+
Name string `json:"name" writer:",wrap,width:50"`
34+
Description string `json:"description" writer:",wrap,width:40"`
35+
Default bool `json:"default,omitempty"`
36+
}
37+
38+
///////////////////////////////////////////////////////////////////////////////
39+
// LIFECYCLE
40+
41+
func newInputFormats(demuxer *ff.AVInputFormat, t media.Type) []media.Format {
42+
names := strings.Split(demuxer.Name(), ",")
43+
result := make([]media.Format, 0, len(names))
44+
45+
// Populate devices by name
46+
for _, name := range names {
47+
result = append(result, &Format{
48+
metaFormat: metaFormat{Type: t, Name: name},
49+
Input: demuxer,
50+
})
51+
}
52+
53+
// Get devices
54+
if t.Is(media.DEVICE) {
55+
dict := ff.AVUtil_dict_alloc()
56+
defer ff.AVUtil_dict_free(dict)
57+
list, err := ff.AVDevice_list_input_sources(demuxer, "", dict)
58+
fmt.Println(err, list, dict)
59+
}
60+
61+
return result
62+
}
63+
64+
func newOutputFormats(muxer *ff.AVOutputFormat, t media.Type) []media.Format {
65+
names := strings.Split(muxer.Name(), ",")
66+
result := make([]media.Format, 0, len(names))
67+
for _, name := range names {
68+
result = append(result, &Format{
69+
metaFormat: metaFormat{Type: t, Name: name},
70+
Output: muxer,
71+
})
72+
}
73+
74+
// Get devices
75+
if t.Is(media.DEVICE) {
76+
dict := ff.AVUtil_dict_alloc()
77+
defer ff.AVUtil_dict_free(dict)
78+
list, err := ff.AVDevice_list_output_sinks(muxer, "", dict)
79+
fmt.Println(err, list, dict)
80+
}
81+
82+
return result
83+
}
84+
85+
///////////////////////////////////////////////////////////////////////////////
86+
// STRINGIFY
87+
88+
func (f *Format) String() string {
89+
data, _ := json.MarshalIndent(f, "", " ")
90+
return string(data)
91+
}
92+
93+
///////////////////////////////////////////////////////////////////////////////
94+
// PUBLIC METHODS
95+
96+
func (f *Format) Type() media.Type {
97+
return f.metaFormat.Type
98+
}
99+
100+
func (f *Format) Name() string {
101+
return f.metaFormat.Name
102+
}

pkg/ffmpeg/manager.go

Lines changed: 189 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ffmpeg
22

33
import (
44
"slices"
5+
"strings"
56

67
// Packages
78
media "github.com/mutablelogic/go-media"
@@ -69,9 +70,6 @@ func NewManager(opt ...Opt) (*Manager, error) {
6970
return manager, nil
7071
}
7172

72-
///////////////////////////////////////////////////////////////////////////////
73-
// PUBLIC METHODS
74-
7573
// Open a media file or device for reading, from a path or url.
7674
// If a format is specified, then the format will be used to open
7775
// the file. You can add additional options to the open call as
@@ -97,6 +95,194 @@ func (manager *Manager) NewReader(r io.Reader, format media.Format, opts ...stri
9795
}
9896
*/
9997

98+
///////////////////////////////////////////////////////////////////////////////
99+
// PUBLIC METHODS - FORMATS
100+
101+
func (manager *Manager) Formats(t media.Type, name ...string) []media.Format {
102+
// Create filters
103+
matchesInputFilter := func(demuxer *ff.AVInputFormat, filter string) bool {
104+
if strings.HasPrefix(filter, ".") {
105+
// By extension
106+
ext := strings.Split(demuxer.Extensions(), ",")
107+
for _, ext := range ext {
108+
if filter == "."+ext {
109+
return true
110+
}
111+
}
112+
} else if strings.Contains(filter, "/") {
113+
// By mimetype
114+
if slices.Contains(strings.Split(demuxer.MimeTypes(), ","), filter) {
115+
return true
116+
}
117+
} else {
118+
// By name
119+
if slices.Contains(strings.Split(demuxer.Name(), ","), filter) {
120+
return true
121+
}
122+
}
123+
return false
124+
}
125+
matchesOutputFilter := func(muxer *ff.AVOutputFormat, filter string) bool {
126+
if strings.HasPrefix(filter, ".") {
127+
// By extension
128+
ext := strings.Split(muxer.Extensions(), ",")
129+
for _, ext := range ext {
130+
if filter == "."+ext {
131+
return true
132+
}
133+
}
134+
} else if strings.Contains(filter, "/") {
135+
// By mimetype
136+
if slices.Contains(strings.Split(muxer.MimeTypes(), ","), filter) {
137+
return true
138+
}
139+
} else {
140+
// By name
141+
if slices.Contains(strings.Split(muxer.Name(), ","), filter) {
142+
return true
143+
}
144+
}
145+
return false
146+
}
147+
matchesInputFormat := func(demuxer *ff.AVInputFormat, t media.Type, filter ...string) bool {
148+
// Check for INPUT
149+
if !t.Is(media.INPUT) && !t.Is(media.ANY) {
150+
return false
151+
}
152+
if t.Is(media.DEVICE) {
153+
return false
154+
}
155+
if len(filter) == 0 {
156+
return true
157+
}
158+
for _, filter := range filter {
159+
if matchesInputFilter(demuxer, filter) {
160+
return true
161+
}
162+
}
163+
return false
164+
}
165+
matchesOutputFormat := func(muxer *ff.AVOutputFormat, t media.Type, filter ...string) bool {
166+
// Check for OUTPUT
167+
if !t.Is(media.OUTPUT) && !t.Is(media.ANY) {
168+
return false
169+
}
170+
if t.Is(media.DEVICE) {
171+
return false
172+
}
173+
if len(filter) == 0 {
174+
return true
175+
}
176+
for _, filter := range filter {
177+
if matchesOutputFilter(muxer, filter) {
178+
return true
179+
}
180+
}
181+
return false
182+
}
183+
matchesInputDevice := func(demuxer *ff.AVInputFormat, filter ...string) bool {
184+
if len(filter) == 0 {
185+
return true
186+
}
187+
for _, filter := range filter {
188+
if demuxer.Name() == filter {
189+
return true
190+
}
191+
}
192+
return false
193+
}
194+
matchesOutputDevice := func(muxer *ff.AVOutputFormat, filter ...string) bool {
195+
if len(filter) == 0 {
196+
return true
197+
}
198+
for _, filter := range filter {
199+
if muxer.Name() == filter {
200+
return true
201+
}
202+
}
203+
return false
204+
}
205+
206+
// Iterate over all input formats
207+
var opaque uintptr
208+
result := []media.Format{}
209+
for {
210+
demuxer := ff.AVFormat_demuxer_iterate(&opaque)
211+
if demuxer == nil {
212+
break
213+
}
214+
if matchesInputFormat(demuxer, t, name...) {
215+
result = append(result, newInputFormats(demuxer, media.INPUT)...)
216+
}
217+
}
218+
219+
// Iterate over all output formats
220+
var opaque2 uintptr
221+
for {
222+
muxer := ff.AVFormat_muxer_iterate(&opaque2)
223+
if muxer == nil {
224+
break
225+
}
226+
if matchesOutputFormat(muxer, t, name...) {
227+
result = append(result, newOutputFormats(muxer, media.OUTPUT)...)
228+
}
229+
}
230+
231+
// Return if DEVICE is not requested
232+
if !t.Is(media.DEVICE) && !t.Is(media.ANY) {
233+
return result
234+
}
235+
236+
// Iterate over all device inputs
237+
audio_input := ff.AVDevice_input_audio_device_first()
238+
for {
239+
if audio_input == nil {
240+
break
241+
}
242+
if matchesInputDevice(audio_input, name...) {
243+
result = append(result, newInputFormats(audio_input, media.INPUT|media.AUDIO|media.DEVICE)...)
244+
}
245+
audio_input = ff.AVDevice_input_audio_device_next(audio_input)
246+
}
247+
248+
video_input := ff.AVDevice_input_video_device_first()
249+
for {
250+
if video_input == nil {
251+
break
252+
}
253+
if matchesInputDevice(video_input, name...) {
254+
result = append(result, newInputFormats(video_input, media.INPUT|media.VIDEO|media.DEVICE)...)
255+
}
256+
video_input = ff.AVDevice_input_video_device_next(video_input)
257+
}
258+
259+
// Iterate over all device outputs
260+
audio_output := ff.AVDevice_output_audio_device_first()
261+
for {
262+
if audio_output == nil {
263+
break
264+
}
265+
if matchesOutputDevice(audio_output, name...) {
266+
result = append(result, newOutputFormats(audio_output, media.OUTPUT|media.AUDIO|media.DEVICE)...)
267+
}
268+
audio_output = ff.AVDevice_output_audio_device_next(audio_output)
269+
}
270+
271+
video_output := ff.AVDevice_output_video_device_first()
272+
for {
273+
if video_output == nil {
274+
break
275+
}
276+
if matchesOutputDevice(video_output, name...) {
277+
result = append(result, newOutputFormats(video_output, media.OUTPUT|media.VIDEO|media.DEVICE)...)
278+
}
279+
video_output = ff.AVDevice_output_video_device_next(video_output)
280+
}
281+
282+
// Return formats
283+
return result
284+
}
285+
100286
///////////////////////////////////////////////////////////////////////////////
101287
// PUBLIC METHODS - CODECS, PIXEL FORMATS, SAMPLE FORMATS AND CHANNEL
102288
// LAYOUTS

pkg/ffmpeg/manager_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
// Packages
7+
media "github.com/mutablelogic/go-media"
78
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
89
assert "github.com/stretchr/testify/assert"
910
)
@@ -39,3 +40,34 @@ func Test_manager_002(t *testing.T) {
3940
t.Log(v)
4041
}
4142
}
43+
44+
func Test_manager_003(t *testing.T) {
45+
assert := assert.New(t)
46+
47+
// Create a manager
48+
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(true, func(v string) {
49+
t.Log(v)
50+
}))
51+
if !assert.NoError(err) {
52+
t.FailNow()
53+
}
54+
55+
for _, v := range manager.Formats(media.ANY) {
56+
t.Log(v)
57+
}
58+
}
59+
60+
func Test_manager_004(t *testing.T) {
61+
assert := assert.New(t)
62+
63+
// Create a manager
64+
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(true, func(v string) {
65+
t.Log(v)
66+
}))
67+
if !assert.NoError(err) {
68+
t.FailNow()
69+
}
70+
for _, format := range manager.Formats(media.DEVICE) {
71+
t.Logf("%v", format)
72+
}
73+
}

0 commit comments

Comments
 (0)