mirror of
https://github.com/zoriya/go-astisub.git
synced 2026-08-16 02:33:21 +00:00
fix: Add support for X-TIMESTAMP-MAP r/w persistence (#112)
* Add support for X-TIMESTAMP-MAP r/w persistence * Refactor TimestampMap struct per review feedback * Fix MPEGTS / LOCAL ordering, update tests * Clean up timestamp map vtt test
This commit is contained in:
@@ -392,6 +392,7 @@ type Metadata struct {
|
||||
STLTranslatorName string
|
||||
Title string
|
||||
TTMLCopyright string
|
||||
WebVTTTimestampMap *WebVTTTimestampMap
|
||||
}
|
||||
|
||||
// Region represents a subtitle's region
|
||||
|
||||
@@ -25,7 +25,7 @@ const (
|
||||
webvttBlockNameText = "text"
|
||||
webvttDefaultStyleID = "astisub-webvtt-default-style-id"
|
||||
webvttTimeBoundariesSeparator = " --> "
|
||||
webvttTimestampMap = "X-TIMESTAMP-MAP"
|
||||
webvttTimestampMapHeader = "X-TIMESTAMP-MAP"
|
||||
)
|
||||
|
||||
// Vars
|
||||
@@ -44,11 +44,36 @@ func parseDurationWebVTT(i string) (time.Duration, error) {
|
||||
return parseDuration(i, ".", 3)
|
||||
}
|
||||
|
||||
// WebVTTTimestampMap is a structure for storing timestamps for WEBVTT's
|
||||
// X-TIMESTAMP-MAP feature commonly used for syncing cue times with
|
||||
// MPEG-TS streams.
|
||||
type WebVTTTimestampMap struct {
|
||||
Local time.Duration
|
||||
MpegTS int64
|
||||
}
|
||||
|
||||
// Offset calculates and returns the time offset described by the
|
||||
// timestamp map.
|
||||
func (t *WebVTTTimestampMap) Offset() time.Duration {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(t.MpegTS)*time.Second/90000 - t.Local
|
||||
}
|
||||
|
||||
// String implements Stringer interface for TimestampMap, returning
|
||||
// the fully formatted header string for the instance.
|
||||
func (t *WebVTTTimestampMap) String() string {
|
||||
mpegts := fmt.Sprintf("MPEGTS:%d", t.MpegTS)
|
||||
local := fmt.Sprintf("LOCAL:%s", formatDurationWebVTT(t.Local))
|
||||
return fmt.Sprintf("%s=%s,%s", webvttTimestampMapHeader, local, mpegts)
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc8216#section-3.5
|
||||
// Eg., `X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000` => 10s
|
||||
//
|
||||
// `X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:180000` => 2s
|
||||
func parseTimestampMapWebVTT(line string) (timeOffset time.Duration, err error) {
|
||||
func parseWebVTTTimestampMap(line string) (timestampMap *WebVTTTimestampMap, err error) {
|
||||
splits := strings.Split(line, "=")
|
||||
if len(splits) <= 1 {
|
||||
err = fmt.Errorf("astisub: invalid X-TIMESTAMP-MAP, no '=' found")
|
||||
@@ -81,7 +106,10 @@ func parseTimestampMapWebVTT(line string) (timeOffset time.Duration, err error)
|
||||
}
|
||||
}
|
||||
|
||||
timeOffset = time.Duration(mpegts)*time.Second/90000 - local
|
||||
timestampMap = &WebVTTTimestampMap{
|
||||
Local: local,
|
||||
MpegTS: mpegts,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -110,7 +138,6 @@ func ReadFromWebVTT(i io.Reader) (o *Subtitles, err error) {
|
||||
var blockName string
|
||||
var comments []string
|
||||
var index int
|
||||
var timeOffset time.Duration
|
||||
var webVTTStyles *StyleAttributes
|
||||
|
||||
for scanner.Scan() {
|
||||
@@ -256,17 +283,22 @@ func ReadFromWebVTT(i io.Reader) (o *Subtitles, err error) {
|
||||
// Append item
|
||||
o.Items = append(o.Items, item)
|
||||
|
||||
case strings.HasPrefix(line, webvttTimestampMap):
|
||||
case strings.HasPrefix(line, webvttTimestampMapHeader):
|
||||
if len(item.Lines) > 0 {
|
||||
err = errors.New("astisub: found timestamp map after processing subtitle items")
|
||||
return
|
||||
}
|
||||
|
||||
timeOffset, err = parseTimestampMapWebVTT(line)
|
||||
var timestampMap *WebVTTTimestampMap
|
||||
timestampMap, err = parseWebVTTTimestampMap(line)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("astisub: parsing webvtt timestamp map failed: %w", err)
|
||||
return
|
||||
}
|
||||
if o.Metadata == nil {
|
||||
o.Metadata = new(Metadata)
|
||||
}
|
||||
o.Metadata.WebVTTTimestampMap = timestampMap
|
||||
|
||||
// Text
|
||||
default:
|
||||
@@ -287,10 +319,6 @@ func ReadFromWebVTT(i io.Reader) (o *Subtitles, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if timeOffset > 0 {
|
||||
o.Add(timeOffset)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -440,7 +468,17 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) {
|
||||
|
||||
// Add header
|
||||
var c []byte
|
||||
c = append(c, []byte("WEBVTT\n\n")...)
|
||||
c = append(c, []byte("WEBVTT")...)
|
||||
|
||||
// Write X-TIMESTAMP-MAP if set
|
||||
if s.Metadata != nil {
|
||||
webVTTTimestampMap := s.Metadata.WebVTTTimestampMap
|
||||
if webVTTTimestampMap != nil {
|
||||
c = append(c, []byte("\n")...)
|
||||
c = append(c, []byte(webVTTTimestampMap.String())...)
|
||||
}
|
||||
}
|
||||
c = append(c, []byte("\n\n")...)
|
||||
|
||||
var style []string
|
||||
for _, s := range s.Styles {
|
||||
|
||||
@@ -74,11 +74,11 @@ func TestTimestampMap(t *testing.T) {
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
line: "X-TIMESTAMP-MAP=MPEGTS:180000, LOCAL:00:00:00.000",
|
||||
line: "X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:180000",
|
||||
expectedOffset: 2 * time.Second,
|
||||
},
|
||||
{
|
||||
line: "X-TIMESTAMP-MAP=MPEGTS:180000, LOCAL:00:00:00.500",
|
||||
line: "X-TIMESTAMP-MAP=LOCAL:00:00:00.500,MPEGTS:180000",
|
||||
expectedOffset: 1500 * time.Millisecond,
|
||||
},
|
||||
{
|
||||
@@ -107,12 +107,13 @@ func TestTimestampMap(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
offset, err := parseTimestampMapWebVTT(c.line)
|
||||
assert.Equal(t, c.expectedOffset, offset)
|
||||
timestampMap, err := parseWebVTTTimestampMap(c.line)
|
||||
assert.Equal(t, c.expectedOffset, timestampMap.Offset())
|
||||
if c.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.line, timestampMap.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+10
-2
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/asticode/go-astisub"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -117,17 +118,24 @@ func TestWebVTTWithTimestampMap(t *testing.T) {
|
||||
|
||||
assert.Len(t, s.Items, 2)
|
||||
|
||||
assert.Equal(t, s.Items[0].StartAt.Milliseconds(), int64(933))
|
||||
assert.Equal(t, s.Items[0].EndAt.Milliseconds(), int64(2366))
|
||||
assert.Equal(t, s.Items[1].StartAt.Milliseconds(), int64(2400))
|
||||
assert.Equal(t, s.Items[1].EndAt.Milliseconds(), int64(3633))
|
||||
assert.Equal(t, s.Metadata.WebVTTTimestampMap.Offset(), time.Duration(time.Second*2))
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
err = s.WriteToWebVTT(b)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `WEBVTT
|
||||
X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:180000
|
||||
|
||||
1
|
||||
00:00:02.933 --> 00:00:04.366
|
||||
00:00:00.933 --> 00:00:02.366
|
||||
♪ ♪
|
||||
|
||||
2
|
||||
00:00:04.400 --> 00:00:05.633
|
||||
00:00:02.400 --> 00:00:03.633
|
||||
Evening.
|
||||
`, b.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user