mirror of
https://github.com/datarhei/core.git
synced 2025-09-27 04:16:25 +08:00
469 lines
8.8 KiB
Go
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
|
|
}
|