Files
core/ffmpeg/skills/skills.go
Jan Stabenow 9c0b535199 Add v16.7.2
2022-05-13 19:26:45 +02:00

469 lines
8.8 KiB
Go

// Package skills provides an easy way to find out which
// codec, de/muxers, filters, and formats ffmpeg
// supports and which devices are available for ffmpeg on
// the system.
package skills
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"regexp"
"strings"
)
// Codec represents a codec with its availabe encoders and decoders
type Codec struct {
Id string
Name string
Encoders []string
Decoders []string
}
type ffCodecs struct {
Audio []Codec
Video []Codec
Subtitle []Codec
}
// HWDevice represents a hardware device (e.g. USB device)
type HWDevice struct {
Id string
Name string
Extra string
Media string
}
// Device represents a type of device (e.g. V4L2) including connected actual devices
type Device struct {
Id string
Name string
Devices []HWDevice
}
type ffDevices struct {
Demuxers []Device
Muxers []Device
}
// Format represents a supported format (e.g. flv)
type Format struct {
Id string
Name string
}
type ffFormats struct {
Demuxers []Format
Muxers []Format
}
// Protocol represents a supported protocol (e.g. rtsp)
type Protocol struct {
Id string
Name string
}
type ffProtocols struct {
Input []Protocol
Output []Protocol
}
type HWAccel struct {
Id string
Name string
}
// Filter represents a supported filter (e.g. anullsrc, test2)
type Filter struct {
Id string
Name string
}
// Library represents a linked av library
type Library struct {
Name string
Compiled string
Linked string
}
type ffmpeg struct {
Version string
Compiler string
Configuration string
Libraries []Library
}
// Skills are the detected capabilities of a ffmpeg binary
type Skills struct {
FFmpeg ffmpeg
Filters []Filter
HWAccels []HWAccel
Codecs ffCodecs
Devices ffDevices
Formats ffFormats
Protocols ffProtocols
}
// New returns all skills that ffmpeg provides
func New(binary string) (Skills, error) {
c := Skills{}
ffmpeg, err := version(binary)
if len(ffmpeg.Version) == 0 || err != nil {
if err != nil {
return Skills{}, fmt.Errorf("can't parse ffmpeg version info: %w", err)
}
return Skills{}, fmt.Errorf("can't parse ffmpeg version info")
}
c.FFmpeg = ffmpeg
c.Filters = filters(binary)
c.HWAccels = hwaccels(binary)
c.Codecs = codecs(binary)
c.Formats = formats(binary)
c.Devices = devices(binary)
c.Protocols = protocols(binary)
return c, nil
}
func version(binary string) (ffmpeg, error) {
cmd := exec.Command(binary, "-version")
cmd.Env = []string{}
out, err := cmd.CombinedOutput()
if err != nil {
return ffmpeg{}, err
}
return parseVersion(out), nil
}
func parseVersion(data []byte) ffmpeg {
f := ffmpeg{}
reVersion := regexp.MustCompile(`^ffmpeg version ([0-9]+\.[0-9]+(\.[0-9]+)?)`)
reCompiler := regexp.MustCompile(`(?m)^\s*built with (.*)$`)
reConfiguration := regexp.MustCompile(`(?m)^\s*configuration: (.*)$`)
reLibrary := regexp.MustCompile(`(?m)^\s*(lib(?:[a-z]+))\s+([0-9]+\.\s*[0-9]+\.\s*[0-9]+) /\s+([0-9]+\.\s*[0-9]+\.\s*[0-9]+)`)
if matches := reVersion.FindSubmatch(data); matches != nil {
f.Version = string(matches[1])
if len(matches[2]) == 0 {
f.Version = f.Version + ".0"
}
}
if matches := reCompiler.FindSubmatch(data); matches != nil {
f.Compiler = string(matches[1])
}
if matches := reConfiguration.FindSubmatch(data); matches != nil {
f.Configuration = string(matches[1])
}
for _, matches := range reLibrary.FindAllSubmatch(data, -1) {
l := Library{
Name: string(matches[1]),
Compiled: string(matches[2]),
Linked: string(matches[3]),
}
f.Libraries = append(f.Libraries, l)
}
return f
}
func filters(binary string) []Filter {
cmd := exec.Command(binary, "-filters")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseFilters(stdout)
}
func parseFilters(data []byte) []Filter {
filters := []Filter{}
re := regexp.MustCompile(`^\s[TSC.]{3} ([0-9A-Za-z_]+)\s+(?:.*?)\s+(.*)?$`)
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
matches := re.FindStringSubmatch(line)
if matches == nil {
continue
}
filters = append(filters, Filter{
Id: matches[1],
Name: matches[2],
})
}
return filters
}
func codecs(binary string) ffCodecs {
cmd := exec.Command(binary, "-codecs")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseCodecs(stdout)
}
func parseCodecs(data []byte) ffCodecs {
codecs := ffCodecs{}
re := regexp.MustCompile(`^\s([D.])([E.])([VAS]).{3} ([0-9A-Za-z_]+)\s+(.*?)(?:\(decoders:([^\)]+)\))?\s?(?:\(encoders:([^\)]+)\))?$`)
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
matches := re.FindStringSubmatch(line)
if matches == nil {
continue
}
codec := Codec{
Id: matches[4],
Name: strings.TrimSpace(matches[5]),
}
if matches[1] == "D" {
if len(matches[6]) == 0 {
codec.Decoders = append(codec.Decoders, matches[4])
} else {
codec.Decoders = strings.Split(strings.TrimSpace(matches[6]), " ")
}
}
if matches[2] == "E" {
if len(matches[7]) == 0 {
codec.Encoders = append(codec.Encoders, matches[4])
} else {
codec.Encoders = strings.Split(strings.TrimSpace(matches[7]), " ")
}
}
switch matches[3] {
case "V":
codecs.Video = append(codecs.Video, codec)
case "A":
codecs.Audio = append(codecs.Audio, codec)
case "S":
codecs.Subtitle = append(codecs.Subtitle, codec)
}
}
return codecs
}
func formats(binary string) ffFormats {
cmd := exec.Command(binary, "-formats")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseFormats(stdout)
}
func parseFormats(data []byte) ffFormats {
formats := ffFormats{}
re := regexp.MustCompile(`^\s([D ])([E ]) ([0-9A-Za-z_,]+)\s+(.*?)$`)
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
matches := re.FindStringSubmatch(line)
if matches == nil {
continue
}
id := strings.Split(matches[3], ",")[0]
format := Format{
Id: id,
Name: matches[4],
}
if matches[1] == "D" {
formats.Demuxers = append(formats.Demuxers, format)
}
if matches[2] == "E" {
formats.Muxers = append(formats.Muxers, format)
}
}
return formats
}
func devices(binary string) ffDevices {
cmd := exec.Command(binary, "-devices")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseDevices(stdout, binary)
}
func parseDevices(data []byte, binary string) ffDevices {
devices := ffDevices{}
re := regexp.MustCompile(`^\s([D ])([E ]) ([0-9A-Za-z_,]+)\s+(.*?)$`)
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
matches := re.FindStringSubmatch(line)
if matches == nil {
continue
}
id := strings.Split(matches[3], ",")[0]
device := Device{
Id: id,
Name: matches[4],
}
switch id {
case "avfoundation":
device.Devices, _ = DevicesAvfoundation(binary)
case "alsa":
device.Devices, _ = DevicesALSA()
case "video4linux2":
device.Devices, _ = DevicesV4L()
case "fbdev":
device.Devices, _ = DevicesFramebuffer()
}
if matches[1] == "D" {
devices.Demuxers = append(devices.Demuxers, device)
}
if matches[2] == "E" {
devices.Muxers = append(devices.Muxers, device)
}
}
return devices
}
func protocols(binary string) ffProtocols {
cmd := exec.Command(binary, "-protocols")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseProtocols(stdout)
}
func parseProtocols(data []byte) ffProtocols {
protocols := ffProtocols{}
mode := ""
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if line == "Input:" {
mode = "input"
continue
} else if line == "Output:" {
mode = "output"
continue
}
if len(mode) == 0 {
continue
}
id := strings.TrimSpace(line)
protocol := Protocol{
Id: id,
Name: id,
}
if mode == "input" {
protocols.Input = append(protocols.Input, protocol)
} else if mode == "output" {
protocols.Output = append(protocols.Output, protocol)
}
}
return protocols
}
func hwaccels(binary string) []HWAccel {
cmd := exec.Command(binary, "-hwaccels")
cmd.Env = []string{}
stdout, _ := cmd.Output()
return parseHWAccels(stdout)
}
func parseHWAccels(data []byte) []HWAccel {
hwaccels := []HWAccel{}
re := regexp.MustCompile(`^[A-Za-z0-9]+$`)
start := false
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if line == "Hardware acceleration methods:" {
start = true
continue
}
if !start {
continue
}
if !re.MatchString(line) {
continue
}
id := strings.TrimSpace(line)
hwaccels = append(hwaccels, HWAccel{
Id: id,
Name: id,
})
}
return hwaccels
}