mediamtx/internal/record/path.go

214 lines
4.1 KiB
Go

package record
import (
"regexp"
"strconv"
"strings"
"time"
"github.com/bluenviron/mediamtx/internal/conf"
)
func leadingZeros(v int, size int) string {
out := strconv.FormatInt(int64(v), 10)
if len(out) >= size {
return out
}
out2 := ""
for i := 0; i < (size - len(out)); i++ {
out2 += "0"
}
return out2 + out
}
// PathAddExtension adds the file extension to path.
func PathAddExtension(path string, format conf.RecordFormat) string {
switch format {
case conf.RecordFormatMPEGTS:
return path + ".ts"
default:
return path + ".mp4"
}
}
// CommonPath returns the common path between all segments with given recording path.
func CommonPath(v string) string {
common := ""
remaining := v
for {
i := strings.IndexAny(remaining, "\\/")
if i < 0 {
break
}
var part string
part, remaining = remaining[:i+1], remaining[i+1:]
if strings.Contains(part, "%") {
break
}
common += part
}
if len(common) > 0 {
common = common[:len(common)-1]
}
return common
}
// Path is a path of a recording segment.
type Path struct {
Start time.Time
Path string
}
// Decode decodes a Path.
func (p *Path) Decode(format string, v string) bool {
re := format
for _, ch := range []uint8{
'\\',
'.',
'+',
'*',
'?',
'^',
'$',
'(',
')',
'[',
']',
'{',
'}',
'|',
} {
re = strings.ReplaceAll(re, string(ch), "\\"+string(ch))
}
re = strings.ReplaceAll(re, "%path", "(.*?)")
re = strings.ReplaceAll(re, "%Y", "([0-9]{4})")
re = strings.ReplaceAll(re, "%m", "([0-9]{2})")
re = strings.ReplaceAll(re, "%d", "([0-9]{2})")
re = strings.ReplaceAll(re, "%H", "([0-9]{2})")
re = strings.ReplaceAll(re, "%M", "([0-9]{2})")
re = strings.ReplaceAll(re, "%S", "([0-9]{2})")
re = strings.ReplaceAll(re, "%f", "([0-9]{6})")
re = strings.ReplaceAll(re, "%s", "([0-9]{10})")
r := regexp.MustCompile(re)
var groupMapping []string
cur := format
for {
i := strings.Index(cur, "%")
if i < 0 {
break
}
cur = cur[i:]
for _, va := range []string{
"%path",
"%Y",
"%m",
"%d",
"%H",
"%M",
"%S",
"%f",
"%s",
} {
if strings.HasPrefix(cur, va) {
groupMapping = append(groupMapping, va)
}
}
cur = cur[1:]
}
matches := r.FindStringSubmatch(v)
if matches == nil {
return false
}
values := make(map[string]string)
for i, match := range matches[1:] {
values[groupMapping[i]] = match
}
var year int
var month time.Month = 1
day := 1
var hour int
var minute int
var second int
var micros int
var unixSec int64 = -1
for k, v := range values {
switch k {
case "%path":
p.Path = v
case "%Y":
tmp, _ := strconv.ParseInt(v, 10, 64)
year = int(tmp)
case "%m":
tmp, _ := strconv.ParseInt(v, 10, 64)
month = time.Month(int(tmp))
case "%d":
tmp, _ := strconv.ParseInt(v, 10, 64)
day = int(tmp)
case "%H":
tmp, _ := strconv.ParseInt(v, 10, 64)
hour = int(tmp)
case "%M":
tmp, _ := strconv.ParseInt(v, 10, 64)
minute = int(tmp)
case "%S":
tmp, _ := strconv.ParseInt(v, 10, 64)
second = int(tmp)
case "%f":
tmp, _ := strconv.ParseInt(v, 10, 64)
micros = int(tmp)
case "%s":
unixSec, _ = strconv.ParseInt(v, 10, 64)
}
}
if unixSec > 0 {
p.Start = time.Unix(unixSec, 0)
} else {
p.Start = time.Date(year, month, day, hour, minute, second, micros*1000, time.Local)
}
return true
}
// Encode encodes a path.
func (p Path) Encode(format string) string {
format = strings.ReplaceAll(format, "%path", p.Path)
format = strings.ReplaceAll(format, "%Y", strconv.FormatInt(int64(p.Start.Year()), 10))
format = strings.ReplaceAll(format, "%m", leadingZeros(int(p.Start.Month()), 2))
format = strings.ReplaceAll(format, "%d", leadingZeros(p.Start.Day(), 2))
format = strings.ReplaceAll(format, "%H", leadingZeros(p.Start.Hour(), 2))
format = strings.ReplaceAll(format, "%M", leadingZeros(p.Start.Minute(), 2))
format = strings.ReplaceAll(format, "%S", leadingZeros(p.Start.Second(), 2))
format = strings.ReplaceAll(format, "%f", leadingZeros(p.Start.Nanosecond()/1000, 6))
format = strings.ReplaceAll(format, "%s", strconv.FormatInt(p.Start.Unix(), 10))
return format
}