mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-20 02:00:05 -08:00
Some checks are pending
code_lint / golangci_lint (push) Waiting to run
code_lint / mod_tidy (push) Waiting to run
code_lint / api_docs (push) Waiting to run
code_test / test_64 (push) Waiting to run
code_test / test_32 (push) Waiting to run
code_test / test_e2e (push) Waiting to run
262 lines
4.9 KiB
Go
262 lines
4.9 KiB
Go
package recordstore
|
|
|
|
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
|
|
}
|
|
|
|
func timeLocationEncode(t time.Time) string {
|
|
_, off := t.Zone()
|
|
|
|
if off == 0 {
|
|
return "Z"
|
|
}
|
|
|
|
var ret string
|
|
|
|
if off > 0 {
|
|
ret = "+"
|
|
} else {
|
|
ret = "-"
|
|
off = -off
|
|
}
|
|
|
|
ret += leadingZeros(off/60/60, 2)
|
|
ret += leadingZeros((off/60)%60, 2)
|
|
|
|
return ret
|
|
}
|
|
|
|
func timeLocationDecode(s string) *time.Location {
|
|
if s == "Z" {
|
|
return time.UTC
|
|
}
|
|
|
|
var sign int
|
|
if s[0] == '+' {
|
|
sign = 1
|
|
} else {
|
|
sign = -1
|
|
}
|
|
|
|
v1, _ := strconv.ParseInt(s[1:3], 10, 64)
|
|
v2, _ := strconv.ParseInt(s[3:5], 10, 64)
|
|
|
|
off := sign*int(v1)*3600 + int(v2)*3600
|
|
|
|
return time.FixedZone("myzone", off)
|
|
}
|
|
|
|
// PathAddExtension adds the file extension to the 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, "%z", "(Z|\\+[0-9]{4}|-[0-9]{4})")
|
|
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",
|
|
"%z",
|
|
"%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
|
|
loc := time.Local
|
|
|
|
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 "%z":
|
|
loc = timeLocationDecode(v)
|
|
|
|
case "%s":
|
|
unixSec, _ = strconv.ParseInt(v, 10, 64)
|
|
}
|
|
}
|
|
|
|
if unixSec > 0 {
|
|
p.Start = time.Unix(unixSec, int64(micros)*1000)
|
|
} else {
|
|
p.Start = time.Date(year, month, day, hour, minute, second, micros*1000, loc)
|
|
}
|
|
|
|
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, "%z", timeLocationEncode(p.Start))
|
|
format = strings.ReplaceAll(format, "%s", strconv.FormatInt(p.Start.Unix(), 10))
|
|
return format
|
|
}
|