From 1e7c16b06b73e2984e9954913dda5b27dd99094e Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Tue, 30 Nov 2021 17:33:33 +0330 Subject: [PATCH 01/21] use item index in WriteToWebVTT --- webvtt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webvtt.go b/webvtt.go index ebbaec8..2ac74c1 100644 --- a/webvtt.go +++ b/webvtt.go @@ -338,7 +338,7 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Loop through subtitles - for index, item := range s.Items { + for _, item := range s.Items { // Add comments if len(item.Comments) > 0 { c = append(c, []byte("NOTE ")...) @@ -350,7 +350,7 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Add time boundaries - c = append(c, []byte(strconv.Itoa(index+1))...) + c = append(c, []byte(strconv.Itoa(item.Index+1))...) c = append(c, bytesLineSeparator...) c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) c = append(c, bytesWebVTTTimeBoundariesSeparator...) From 969140edda40562efd04bfe53d2da3f51eeb8b47 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Wed, 1 Dec 2021 16:30:22 +0330 Subject: [PATCH 02/21] test repo --- README.md | 2 +- astisub/main.go | 2 +- go.mod | 2 +- srt_test.go | 2 +- ssa_test.go | 2 +- stl_test.go | 2 +- subtitles_test.go | 2 +- ttml_test.go | 5 +++-- webvtt_test.go | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d844ee3..9f9ff70 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Available operations are `parsing`, `writing`, `syncing`, `fragmenting`, `unfrag To install the library and command line program, use the following: - go get -u github.com/asticode/go-astisub/... + go get -u github.com/mysamimi/go-astisub/... # Using the library in your code diff --git a/astisub/main.go b/astisub/main.go index 51bc823..4ae50d8 100644 --- a/astisub/main.go +++ b/astisub/main.go @@ -5,7 +5,7 @@ import ( "log" "github.com/asticode/go-astikit" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" ) // Flags diff --git a/go.mod b/go.mod index f50c273..eff0d16 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/asticode/go-astisub +module github.com/mysamimi/go-astisub go 1.13 diff --git a/srt_test.go b/srt_test.go index 24cb656..2b654b3 100644 --- a/srt_test.go +++ b/srt_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/ssa_test.go b/ssa_test.go index 1807e18..2dfde34 100644 --- a/ssa_test.go +++ b/ssa_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/asticode/go-astikit" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/stl_test.go b/stl_test.go index f46d2bd..7f50cd7 100644 --- a/stl_test.go +++ b/stl_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/asticode/go-astikit" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/subtitles_test.go b/subtitles_test.go index 35b573f..e53e0fa 100644 --- a/subtitles_test.go +++ b/subtitles_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/ttml_test.go b/ttml_test.go index 990c821..8d7a130 100644 --- a/ttml_test.go +++ b/ttml_test.go @@ -2,11 +2,12 @@ package astisub_test import ( "bytes" - "github.com/asticode/go-astikit" "io/ioutil" "testing" - "github.com/asticode/go-astisub" + "github.com/asticode/go-astikit" + + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/webvtt_test.go b/webvtt_test.go index 3746515..91b2c5c 100644 --- a/webvtt_test.go +++ b/webvtt_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/asticode/go-astisub" + "github.com/mysamimi/go-astisub" "github.com/stretchr/testify/assert" ) From 102b36b4e5c90d75b3f796c7e1b027b203a851f4 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Wed, 1 Dec 2021 18:07:31 +0330 Subject: [PATCH 03/21] rev test repo --- README.md | 2 +- astisub/main.go | 2 +- go.mod | 2 +- srt_test.go | 2 +- ssa_test.go | 2 +- stl_test.go | 2 +- subtitles_test.go | 2 +- ttml_test.go | 2 +- webvtt_test.go | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9f9ff70..d844ee3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Available operations are `parsing`, `writing`, `syncing`, `fragmenting`, `unfrag To install the library and command line program, use the following: - go get -u github.com/mysamimi/go-astisub/... + go get -u github.com/asticode/go-astisub/... # Using the library in your code diff --git a/astisub/main.go b/astisub/main.go index 4ae50d8..51bc823 100644 --- a/astisub/main.go +++ b/astisub/main.go @@ -5,7 +5,7 @@ import ( "log" "github.com/asticode/go-astikit" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" ) // Flags diff --git a/go.mod b/go.mod index eff0d16..f50c273 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/mysamimi/go-astisub +module github.com/asticode/go-astisub go 1.13 diff --git a/srt_test.go b/srt_test.go index 2b654b3..24cb656 100644 --- a/srt_test.go +++ b/srt_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/ssa_test.go b/ssa_test.go index 2dfde34..1807e18 100644 --- a/ssa_test.go +++ b/ssa_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/asticode/go-astikit" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/stl_test.go b/stl_test.go index 7f50cd7..f46d2bd 100644 --- a/stl_test.go +++ b/stl_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/asticode/go-astikit" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/subtitles_test.go b/subtitles_test.go index e53e0fa..35b573f 100644 --- a/subtitles_test.go +++ b/subtitles_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/ttml_test.go b/ttml_test.go index 8d7a130..2d4395b 100644 --- a/ttml_test.go +++ b/ttml_test.go @@ -7,7 +7,7 @@ import ( "github.com/asticode/go-astikit" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) diff --git a/webvtt_test.go b/webvtt_test.go index 91b2c5c..3746515 100644 --- a/webvtt_test.go +++ b/webvtt_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/mysamimi/go-astisub" + "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) From d2f7670964fbcdfa67dc96dd5ca3c2a66433ed30 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Thu, 2 Dec 2021 23:48:37 +0330 Subject: [PATCH 04/21] fix index in Order method --- subtitles.go | 1 + 1 file changed, 1 insertion(+) diff --git a/subtitles.go b/subtitles.go index e661799..01d7162 100644 --- a/subtitles.go +++ b/subtitles.go @@ -589,6 +589,7 @@ func (s *Subtitles) Order() { var tmp = s.Items[index-1] s.Items[index-1] = s.Items[index] s.Items[index] = tmp + s.Items[index].Index = index swapped = true } } From abc0985ba868d22583d9e85fdecc136bb4852241 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Thu, 2 Dec 2021 23:59:07 +0330 Subject: [PATCH 05/21] remove index plus one! --- webvtt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webvtt.go b/webvtt.go index 2ac74c1..d422124 100644 --- a/webvtt.go +++ b/webvtt.go @@ -350,7 +350,7 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Add time boundaries - c = append(c, []byte(strconv.Itoa(item.Index+1))...) + c = append(c, []byte(strconv.Itoa(item.Index))...) c = append(c, bytesLineSeparator...) c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) c = append(c, bytesWebVTTTimeBoundariesSeparator...) From 820c2cb65105cc12d6b5742063d33724b3475987 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 3 Dec 2021 00:06:06 +0330 Subject: [PATCH 06/21] add FixIndex for Items --- subtitles.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/subtitles.go b/subtitles.go index 01d7162..9652452 100644 --- a/subtitles.go +++ b/subtitles.go @@ -596,6 +596,13 @@ func (s *Subtitles) Order() { } } +// FixIndex fix item index +func (s *Subtitles) FixIndex() { + for i := 0; i < len(s.Items); i++ { + s.Items[i].Index = i + 1 + } +} + // RemoveStyling removes the styling from the subtitles func (s *Subtitles) RemoveStyling() { s.Regions = map[string]*Region{} From 9abe444cb4a00ec1f3be39c4ee0804a6f19a53bf Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sat, 4 Dec 2021 16:24:38 +0330 Subject: [PATCH 07/21] fix unnessery change --- ttml_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttml_test.go b/ttml_test.go index 2d4395b..8f1e3b0 100644 --- a/ttml_test.go +++ b/ttml_test.go @@ -2,10 +2,10 @@ package astisub_test import ( "bytes" + "github.com/asticode/go-astikit" "io/ioutil" "testing" - "github.com/asticode/go-astikit" "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" From 4ae3e61fea5c253ac3b3419005a8c28a9e2e74ba Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sat, 4 Dec 2021 16:26:29 +0330 Subject: [PATCH 08/21] fix --- ttml_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ttml_test.go b/ttml_test.go index 8f1e3b0..990c821 100644 --- a/ttml_test.go +++ b/ttml_test.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "testing" - "github.com/asticode/go-astisub" "github.com/stretchr/testify/assert" ) From 82e090d81d1b5babd888af69b9c10d651f9e3e28 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sun, 16 Jan 2022 08:51:02 +0330 Subject: [PATCH 09/21] fix bad build test & add ClipFrom, ClipTo method --- subtitles.go | 36 +++++++++++++++++++++++++++++++++++- webvtt.go | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/subtitles.go b/subtitles.go index 9652452..cc6fbeb 100644 --- a/subtitles.go +++ b/subtitles.go @@ -596,10 +596,44 @@ func (s *Subtitles) Order() { } } +// ClipFrom clip items from input time +func (s *Subtitles) ClipFrom(cf time.Duration) { + newIndex := 0 + for index := 1; index < len(s.Items); index++ { + s.Items[index].StartAt -= cf + s.Items[index].EndAt -= cf + s.Items[index].Index = newIndex + if s.Items[index].StartAt < 0 { + s.Items[index].StartAt = 0 + } + if s.Items[index].EndAt > 0 { + s.Items[index] = s.Items[newIndex] + newIndex++ + } + } + s.Items = s.Items[:newIndex] +} + +// ClipFrom clip items until input time +func (s *Subtitles) ClipTo(ct time.Duration) { + lastIndex := 0 + for index := 1; index < len(s.Items); index++ { + lastIndex = index + if s.Items[index].StartAt > ct { + break + } + if s.Items[index].EndAt > ct { + s.Items[index].EndAt = ct + break + } + } + s.Items = s.Items[:lastIndex] +} + // FixIndex fix item index func (s *Subtitles) FixIndex() { for i := 0; i < len(s.Items); i++ { - s.Items[i].Index = i + 1 + s.Items[i].Index = i } } diff --git a/webvtt.go b/webvtt.go index d422124..2ac74c1 100644 --- a/webvtt.go +++ b/webvtt.go @@ -350,7 +350,7 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Add time boundaries - c = append(c, []byte(strconv.Itoa(item.Index))...) + c = append(c, []byte(strconv.Itoa(item.Index+1))...) c = append(c, bytesLineSeparator...) c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) c = append(c, bytesWebVTTTimeBoundariesSeparator...) From aa08cbae866f0ec8bf498016729b94f7a0c18f0e Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sun, 16 Jan 2022 09:59:56 +0330 Subject: [PATCH 10/21] overload WriteToWebVTT, 2 arg force use item index --- webvtt.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/webvtt.go b/webvtt.go index 2ac74c1..a39c5d4 100644 --- a/webvtt.go +++ b/webvtt.go @@ -277,7 +277,26 @@ func formatDurationWebVTT(i time.Duration) string { } // WriteToWebVTT writes subtitles in .vtt format -func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { +// if set true in second args write index as item index +func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { + var o io.Writer + writeWithIndex := false + for i, arg := range args { + switch i { + case 0: // default output writer + out, ok := arg.(io.Writer) + if !ok { + return fmt.Errorf("first input argument must be io.Writer") + } + o = out + case 1: + b, ok := arg.(bool) + if !ok { + return fmt.Errorf("second input argument must be boolean") + } + writeWithIndex = b + } + } // Do not write anything if no subtitles if len(s.Items) == 0 { err = ErrNoSubtitlesToWrite @@ -338,7 +357,7 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Loop through subtitles - for _, item := range s.Items { + for index, item := range s.Items { // Add comments if len(item.Comments) > 0 { c = append(c, []byte("NOTE ")...) @@ -350,7 +369,11 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } // Add time boundaries - c = append(c, []byte(strconv.Itoa(item.Index+1))...) + if writeWithIndex { + c = append(c, []byte(strconv.Itoa(item.Index+1))...) + } else { + c = append(c, []byte(strconv.Itoa(index+1))...) + } c = append(c, bytesLineSeparator...) c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) c = append(c, bytesWebVTTTimeBoundariesSeparator...) From d6b6e46cc689f3431985710b76e14d160b0c0eee Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sun, 16 Jan 2022 10:19:03 +0330 Subject: [PATCH 11/21] remove change in Order method --- subtitles.go | 3 +-- webvtt.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/subtitles.go b/subtitles.go index cc6fbeb..567403c 100644 --- a/subtitles.go +++ b/subtitles.go @@ -589,7 +589,6 @@ func (s *Subtitles) Order() { var tmp = s.Items[index-1] s.Items[index-1] = s.Items[index] s.Items[index] = tmp - s.Items[index].Index = index swapped = true } } @@ -633,7 +632,7 @@ func (s *Subtitles) ClipTo(ct time.Duration) { // FixIndex fix item index func (s *Subtitles) FixIndex() { for i := 0; i < len(s.Items); i++ { - s.Items[i].Index = i + s.Items[i].Index = i + 1 } } diff --git a/webvtt.go b/webvtt.go index a39c5d4..d982f11 100644 --- a/webvtt.go +++ b/webvtt.go @@ -370,7 +370,7 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { // Add time boundaries if writeWithIndex { - c = append(c, []byte(strconv.Itoa(item.Index+1))...) + c = append(c, []byte(strconv.Itoa(item.Index))...) } else { c = append(c, []byte(strconv.Itoa(index+1))...) } From f8731f9b57fd881d261f9240dd9dc3b4aaea1512 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sun, 16 Jan 2022 10:54:23 +0330 Subject: [PATCH 12/21] add new tests, fix bug :) --- subtitles.go | 6 +++--- subtitles_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/subtitles.go b/subtitles.go index 567403c..f4b2680 100644 --- a/subtitles.go +++ b/subtitles.go @@ -598,7 +598,7 @@ func (s *Subtitles) Order() { // ClipFrom clip items from input time func (s *Subtitles) ClipFrom(cf time.Duration) { newIndex := 0 - for index := 1; index < len(s.Items); index++ { + for index := 0; index < len(s.Items); index++ { s.Items[index].StartAt -= cf s.Items[index].EndAt -= cf s.Items[index].Index = newIndex @@ -616,7 +616,7 @@ func (s *Subtitles) ClipFrom(cf time.Duration) { // ClipFrom clip items until input time func (s *Subtitles) ClipTo(ct time.Duration) { lastIndex := 0 - for index := 1; index < len(s.Items); index++ { + for index := 0; index < len(s.Items); index++ { lastIndex = index if s.Items[index].StartAt > ct { break @@ -626,7 +626,7 @@ func (s *Subtitles) ClipTo(ct time.Duration) { break } } - s.Items = s.Items[:lastIndex] + s.Items = s.Items[:lastIndex+1] } // FixIndex fix item index diff --git a/subtitles_test.go b/subtitles_test.go index 35b573f..3803005 100644 --- a/subtitles_test.go +++ b/subtitles_test.go @@ -276,3 +276,29 @@ func TestSubtitles_RemoveStyling(t *testing.T) { Styles: map[string]*astisub.Style{}, }, s) } + +func TestSubtitles_FixIndex(t *testing.T) { + var s = mockSubtitles() + s.FixIndex() + for i := 0; i < len(s.Items); i++ { + assert.Equal(t, i+1, s.Items[i].Index) + } +} + +func TestSubtitles_ClipTo(t *testing.T) { + var s = mockSubtitles() + s.ClipTo(2 * time.Second) + assert.Equal(t, 1, len(s.Items)) + assert.Equal(t, 1*time.Second, s.Items[0].StartAt) + assert.Equal(t, 2*time.Second, s.Items[0].EndAt) +} + +func TestSubtitles_ClipFrom(t *testing.T) { + var s = mockSubtitles() + s.ClipFrom(2 * time.Second) + assert.Equal(t, 2, len(s.Items)) + assert.Equal(t, 0*time.Second, s.Items[0].StartAt) + assert.Equal(t, 1*time.Second, s.Items[0].EndAt) + assert.Equal(t, 1*time.Second, s.Items[1].StartAt) + assert.Equal(t, 5*time.Second, s.Items[1].EndAt) +} From b0e80b61ec20a129693e1cd8ee5e974d67509d2c Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Tue, 1 Feb 2022 11:06:42 +0330 Subject: [PATCH 13/21] fix clipFrom --- subtitles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subtitles.go b/subtitles.go index f4b2680..b232d4b 100644 --- a/subtitles.go +++ b/subtitles.go @@ -606,7 +606,7 @@ func (s *Subtitles) ClipFrom(cf time.Duration) { s.Items[index].StartAt = 0 } if s.Items[index].EndAt > 0 { - s.Items[index] = s.Items[newIndex] + s.Items[newIndex] = s.Items[index] newIndex++ } } From 44a67b7723a507a37bf1140a3016f3adfff94482 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Tue, 1 Feb 2022 12:28:49 +0330 Subject: [PATCH 14/21] fix clipFrom --- subtitles.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subtitles.go b/subtitles.go index b232d4b..3806649 100644 --- a/subtitles.go +++ b/subtitles.go @@ -598,6 +598,7 @@ func (s *Subtitles) Order() { // ClipFrom clip items from input time func (s *Subtitles) ClipFrom(cf time.Duration) { newIndex := 0 + var items []*Item for index := 0; index < len(s.Items); index++ { s.Items[index].StartAt -= cf s.Items[index].EndAt -= cf @@ -606,11 +607,10 @@ func (s *Subtitles) ClipFrom(cf time.Duration) { s.Items[index].StartAt = 0 } if s.Items[index].EndAt > 0 { - s.Items[newIndex] = s.Items[index] - newIndex++ + items = append(items, s.Items[index]) } } - s.Items = s.Items[:newIndex] + s.Items = items } // ClipFrom clip items until input time From 06b222630812240fefb4c59ef20f636ece2a1c76 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Tue, 1 Feb 2022 13:49:42 +0330 Subject: [PATCH 15/21] add clone method --- subtitles.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/subtitles.go b/subtitles.go index 3806649..e79fa90 100644 --- a/subtitles.go +++ b/subtitles.go @@ -6,6 +6,7 @@ import ( "math" "os" "path/filepath" + "reflect" "strconv" "strings" "time" @@ -613,6 +614,35 @@ func (s *Subtitles) ClipFrom(cf time.Duration) { s.Items = items } +func copy(source interface{}, destin interface{}) { + x := reflect.ValueOf(source) + if x.Kind() == reflect.Ptr { + starX := x.Elem() + y := reflect.New(starX.Type()) + starY := y.Elem() + starY.Set(starX) + reflect.ValueOf(destin).Elem().Set(y.Elem()) + } +} + +// Clone subtitles +func (s *Subtitles) Clone() *Subtitles { + sub := &Subtitles{} + copy(s.Metadata, sub.Metadata) + for k, r := range s.Regions { + copy(r, sub.Regions[k]) + } + for k, r := range s.Styles { + copy(r, sub.Styles[k]) + } + for i := 0; i < len(s.Items); i++ { + n := &Item{} + copy(s.Items[i], n) + sub.Items = append(sub.Items, n) + } + return sub +} + // ClipFrom clip items until input time func (s *Subtitles) ClipTo(ct time.Duration) { lastIndex := 0 From 764fc08e0eca2e6fe695ad341851f05dd4a1a12c Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 6 Feb 2026 23:26:20 +0330 Subject: [PATCH 16/21] feat: optimize writers with streaming and upgrade to Go 1.24 --- go.mod | 16 +- go.sum | 25 +-- srt.go | 114 ++++++++---- srt_test.go | 13 ++ ssa.go | 173 +++++++++++++----- webvtt.go | 379 +++++++++++++++++++++++++++++++--------- webvtt_internal_test.go | 9 +- webvtt_test.go | 13 ++ 8 files changed, 558 insertions(+), 184 deletions(-) diff --git a/go.mod b/go.mod index f50c273..79fd44c 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,17 @@ module github.com/asticode/go-astisub -go 1.13 +go 1.24.0 require ( - github.com/asticode/go-astikit v0.20.0 - github.com/asticode/go-astits v1.8.0 + github.com/asticode/go-astikit v0.57.1 + github.com/asticode/go-astits v1.15.0 github.com/stretchr/testify v1.4.0 - golang.org/x/net v0.0.0-20200904194848-62affa334b73 - golang.org/x/text v0.3.2 + golang.org/x/net v0.49.0 + golang.org/x/text v0.33.0 +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index dea5cec..bfd3149 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ -github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8= -github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg= -github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ= +github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= +github.com/asticode/go-astikit v0.57.1 h1:fEykwH98Nny08kcRbk4uer+S8h0rKveCIpG9F6NVLuA= +github.com/asticode/go-astikit v0.57.1/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astits v1.15.0 h1:yRyCiUc8Jj4F7clt2GDxHghMpWuFL5rkaLuGUd2/0J4= +github.com/asticode/go-astits v1.15.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= @@ -10,18 +11,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/srt.go b/srt.go index 9e5c8e6..246f7d9 100644 --- a/srt.go +++ b/srt.go @@ -1,6 +1,7 @@ package astisub import ( + "bufio" "fmt" "io" "strconv" @@ -220,49 +221,76 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { return } + // Init writer + w := bufio.NewWriter(o) + defer w.Flush() + // Add BOM header - var c []byte - c = append(c, BytesBOM...) + if _, err = w.Write(BytesBOM); err != nil { + err = fmt.Errorf("astisub: writing bom failed: %w", err) + return + } // Loop through subtitles for k, v := range s.Items { // Add time boundaries - c = append(c, []byte(strconv.Itoa(k+1))...) - c = append(c, bytesLineSeparator...) - c = append(c, []byte(formatDurationSRT(v.StartAt))...) - c = append(c, bytesSRTTimeBoundariesSeparator...) - c = append(c, []byte(formatDurationSRT(v.EndAt))...) - c = append(c, bytesLineSeparator...) + if _, err = w.WriteString(strconv.Itoa(k + 1)); err != nil { + err = fmt.Errorf("astisub: writing index failed: %w", err) + return + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } + if _, err = w.WriteString(formatDurationSRT(v.StartAt)); err != nil { + err = fmt.Errorf("astisub: writing start at failed: %w", err) + return + } + if _, err = w.Write(bytesSRTTimeBoundariesSeparator); err != nil { + err = fmt.Errorf("astisub: writing time boundaries separator failed: %w", err) + return + } + if _, err = w.WriteString(formatDurationSRT(v.EndAt)); err != nil { + err = fmt.Errorf("astisub: writing end at failed: %w", err) + return + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } // Loop through lines for _, l := range v.Lines { - c = append(c, []byte(l.srtBytes())...) + if err = l.writeSRT(w); err != nil { + return + } } // Add new line - c = append(c, bytesLineSeparator...) - } - - // Remove last new line - c = c[:len(c)-1] - - // Write - if _, err = o.Write(c); err != nil { - err = fmt.Errorf("astisub: writing failed: %w", err) - return + if k < len(s.Items)-1 { + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } + } } return } -func (l Line) srtBytes() (c []byte) { +func (l Line) writeSRT(w io.Writer) (err error) { for _, li := range l.Items { - c = append(c, li.srtBytes()...) + if err = li.writeSRT(w); err != nil { + return + } + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return } - c = append(c, bytesLineSeparator...) return } -func (li LineItem) srtBytes() (c []byte) { +func (li LineItem) writeSRT(w io.Writer) (err error) { // Get color var color string if li.InlineStyle != nil && li.InlineStyle.SRTColor != nil { @@ -282,32 +310,52 @@ func (li LineItem) srtBytes() (c []byte) { // Append if color != "" { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing font color failed: %w", err) + } } if b { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing bold failed: %w", err) + } } if i { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing italics failed: %w", err) + } } if u { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing underline failed: %w", err) + } } if pos != 0 { - c = append(c, []byte(fmt.Sprintf(`{\an%d}`, pos))...) + if _, err = w.Write([]byte(fmt.Sprintf(`{\an%d}`, pos))); err != nil { + return fmt.Errorf("astisub: writing position failed: %w", err) + } + } + if _, err = w.Write([]byte(escapeHTML(li.Text))); err != nil { + return fmt.Errorf("astisub: writing text failed: %w", err) } - c = append(c, []byte(escapeHTML(li.Text))...) if u { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing underline close failed: %w", err) + } } if i { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing italics close failed: %w", err) + } } if b { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing bold close failed: %w", err) + } } if color != "" { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing font close failed: %w", err) + } } return } diff --git a/srt_test.go b/srt_test.go index b81560a..57cc891 100644 --- a/srt_test.go +++ b/srt_test.go @@ -171,3 +171,16 @@ func TestSRTParseDuration(t *testing.T) { assert.Equal(t, 5*time.Second+985*time.Millisecond, s.Items[1].EndAt) assert.Equal(t, "Duration without colon milliseconds", s.Items[1].Lines[0].String()) } + +func BenchmarkWriteToSRT(b *testing.B) { + s, err := astisub.OpenFile("./testdata/example-in.srt") + if err != nil { + b.Fatal(err) + } + w := &bytes.Buffer{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + w.Reset() + s.WriteToSRT(w) + } +} diff --git a/ssa.go b/ssa.go index 228ab2a..4ba5e9d 100644 --- a/ssa.go +++ b/ssa.go @@ -1,6 +1,7 @@ package astisub import ( + "bufio" "fmt" "io" "log" @@ -409,57 +410,141 @@ func (b *ssaScriptInfo) metadata() *Metadata { } } -// bytes returns the block as bytes -func (b *ssaScriptInfo) bytes() (o []byte) { - o = []byte("[Script Info]") - o = append(o, bytesLineSeparator...) +// write writes the block to the writer +func (b *ssaScriptInfo) write(w io.Writer) (err error) { + if _, err = w.Write([]byte("[Script Info]")); err != nil { + return fmt.Errorf("astisub: writing script info header failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } for _, c := range b.comments { - o = appendStringToBytesWithNewLine(o, "; "+c) + if _, err = w.Write([]byte("; " + c)); err != nil { + return fmt.Errorf("astisub: writing comment failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.collisions) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameCollisions+": "+b.collisions) + if _, err = w.Write([]byte(ssaScriptInfoNameCollisions + ": " + b.collisions)); err != nil { + return fmt.Errorf("astisub: writing collisions failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.originalEditing) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalEditing+": "+b.originalEditing) + if _, err = w.Write([]byte(ssaScriptInfoNameOriginalEditing + ": " + b.originalEditing)); err != nil { + return fmt.Errorf("astisub: writing original editing failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.originalScript) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalScript+": "+b.originalScript) + if _, err = w.Write([]byte(ssaScriptInfoNameOriginalScript + ": " + b.originalScript)); err != nil { + return fmt.Errorf("astisub: writing original script failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.originalTiming) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalTiming+": "+b.originalTiming) + if _, err = w.Write([]byte(ssaScriptInfoNameOriginalTiming + ": " + b.originalTiming)); err != nil { + return fmt.Errorf("astisub: writing original timing failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.originalTranslation) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalTranslation+": "+b.originalTranslation) + if _, err = w.Write([]byte(ssaScriptInfoNameOriginalTranslation + ": " + b.originalTranslation)); err != nil { + return fmt.Errorf("astisub: writing original translation failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if b.playDepth != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayDepth+": "+strconv.Itoa(*b.playDepth)) + if _, err = w.Write([]byte(ssaScriptInfoNamePlayDepth + ": " + strconv.Itoa(*b.playDepth))); err != nil { + return fmt.Errorf("astisub: writing play depth failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if b.playResX != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayResX+": "+strconv.Itoa(*b.playResX)) + if _, err = w.Write([]byte(ssaScriptInfoNamePlayResX + ": " + strconv.Itoa(*b.playResX))); err != nil { + return fmt.Errorf("astisub: writing play res x failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if b.playResY != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayResY+": "+strconv.Itoa(*b.playResY)) + if _, err = w.Write([]byte(ssaScriptInfoNamePlayResY + ": " + strconv.Itoa(*b.playResY))); err != nil { + return fmt.Errorf("astisub: writing play res y failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.scriptType) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameScriptType+": "+b.scriptType) + if _, err = w.Write([]byte(ssaScriptInfoNameScriptType + ": " + b.scriptType)); err != nil { + return fmt.Errorf("astisub: writing script type failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.scriptUpdatedBy) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameScriptUpdatedBy+": "+b.scriptUpdatedBy) + if _, err = w.Write([]byte(ssaScriptInfoNameScriptUpdatedBy + ": " + b.scriptUpdatedBy)); err != nil { + return fmt.Errorf("astisub: writing script updated by failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.synchPoint) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameSynchPoint+": "+b.synchPoint) + if _, err = w.Write([]byte(ssaScriptInfoNameSynchPoint + ": " + b.synchPoint)); err != nil { + return fmt.Errorf("astisub: writing synch point failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if b.timer != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameTimer+": "+strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1)) + if _, err = w.Write([]byte(ssaScriptInfoNameTimer + ": " + strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1))); err != nil { + return fmt.Errorf("astisub: writing timer failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.title) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameTitle+": "+b.title) + if _, err = w.Write([]byte(ssaScriptInfoNameTitle + ": " + b.title)); err != nil { + return fmt.Errorf("astisub: writing title failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.updateDetails) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameUpdateDetails+": "+b.updateDetails) + if _, err = w.Write([]byte(ssaScriptInfoNameUpdateDetails + ": " + b.updateDetails)); err != nil { + return fmt.Errorf("astisub: writing update details failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } if len(b.wrapStyle) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameWrapStyle+": "+b.wrapStyle) + if _, err = w.Write([]byte(ssaScriptInfoNameWrapStyle + ": " + b.wrapStyle)); err != nil { + return fmt.Errorf("astisub: writing wrap style failed: %w", err) + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) + } } return } @@ -1173,10 +1258,13 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { return } + // Init writer + w := bufio.NewWriter(o) + defer w.Flush() + // Write Script Info block var si = newSSAScriptInfo(s.Metadata) - if _, err = o.Write(si.bytes()); err != nil { - err = fmt.Errorf("astisub: writing script info block failed: %w", err) + if err = si.write(w); err != nil { return } @@ -1185,9 +1273,14 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { // Write Styles block if len(s.Styles) > 0 { // Header - var b = []byte("\n[V4 Styles]\n") if v4plus { - b = []byte("\n[V4+ Styles]\n") + if _, err = w.WriteString("\n[V4+ Styles]\n"); err != nil { + return fmt.Errorf("astisub: writing styles header failed: %w", err) + } + } else { + if _, err = w.WriteString("\n[V4 Styles]\n"); err != nil { + return fmt.Errorf("astisub: writing styles header failed: %w", err) + } } // Format @@ -1201,25 +1294,25 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { styles[ss.name] = ss styleNames = append(styleNames, ss.name) } - b = append(b, []byte("Format: "+strings.Join(format, ", ")+"\n")...) + if _, err = w.WriteString("Format: " + strings.Join(format, ", ") + "\n"); err != nil { + return fmt.Errorf("astisub: writing styles format failed: %w", err) + } // Styles sort.Strings(styleNames) for _, n := range styleNames { - b = append(b, []byte("Style: "+styles[n].string(format)+"\n")...) - } - - // Write - if _, err = o.Write(b); err != nil { - err = fmt.Errorf("astisub: writing styles block failed: %w", err) - return + if _, err = w.WriteString("Style: " + styles[n].string(format) + "\n"); err != nil { + return fmt.Errorf("astisub: writing style failed: %w", err) + } } } // Write Events block if len(s.Items) > 0 { // Header - var b = []byte("\n[Events]\n") + if _, err = w.WriteString("\n[Events]\n"); err != nil { + return fmt.Errorf("astisub: writing events header failed: %w", err) + } // Format // We need to declare those 9 columns here otherwise VLC doesn't display subtitles properly @@ -1242,17 +1335,15 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { events = append(events, newSSAEventFromItem(*i)) } format = append(format, ssaEventFormatNameText) - b = append(b, []byte("Format: "+strings.Join(format, ", ")+"\n")...) + if _, err = w.WriteString("Format: " + strings.Join(format, ", ") + "\n"); err != nil { + return fmt.Errorf("astisub: writing events format failed: %w", err) + } // Styles for _, e := range events { - b = append(b, []byte(ssaEventCategoryDialogue+": "+e.string(format)+"\n")...) - } - - // Write - if _, err = o.Write(b); err != nil { - err = fmt.Errorf("astisub: writing events block failed: %w", err) - return + if _, err = w.WriteString(ssaEventCategoryDialogue + ": " + e.string(format) + "\n"); err != nil { + return fmt.Errorf("astisub: writing event failed: %w", err) + } } } return diff --git a/webvtt.go b/webvtt.go index d0c334c..5b8a46e 100644 --- a/webvtt.go +++ b/webvtt.go @@ -1,6 +1,7 @@ package astisub import ( + "bufio" "errors" "fmt" "io" @@ -412,11 +413,12 @@ func parseTextWebVTT(i string, sa *StyleAttributes) (o Line) { } // Push the tag to stack - sa.WebVTTTags = append(sa.WebVTTTags, WebVTTTag{ + tag := WebVTTTag{ Name: tagName, Classes: classes, Annotation: annotation, - }) + } + sa.WebVTTTags = append(sa.WebVTTTags, tag) } case html.TextToken: @@ -424,7 +426,9 @@ func parseTextWebVTT(i string, sa *StyleAttributes) (o Line) { var styleAttributes *StyleAttributes if len(sa.WebVTTTags) > 0 { tags := make([]WebVTTTag, len(sa.WebVTTTags)) - copy(tags, sa.WebVTTTags) + for i, t := range sa.WebVTTTags { + tags[i] = t + } styleAttributes = &StyleAttributes{ WebVTTTags: tags, } @@ -516,19 +520,34 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { return } + // Init writer + w := bufio.NewWriter(o) + defer w.Flush() + // Add header - var c []byte - c = append(c, []byte("WEBVTT")...) + if _, err = w.WriteString("WEBVTT"); err != nil { + err = fmt.Errorf("astisub: writing header failed: %w", err) + return + } // 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())...) + if _, err = w.Write([]byte("\n")); err != nil { + err = fmt.Errorf("astisub: writing newline failed: %w", err) + return + } + if _, err = w.WriteString(webVTTTimestampMap.String()); err != nil { + err = fmt.Errorf("astisub: writing timestamp map failed: %w", err) + return + } } } - c = append(c, []byte("\n\n")...) + if _, err = w.Write([]byte("\n\n")); err != nil { + err = fmt.Errorf("astisub: writing newline failed: %w", err) + return + } var style []string for _, s := range s.Styles { @@ -538,7 +557,10 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { } if len(style) > 0 { - c = append(c, []byte(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n")))...) + if _, err = w.WriteString(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n"))); err != nil { + err = fmt.Errorf("astisub: writing style failed: %w", err) + return + } } // Add regions @@ -549,140 +571,309 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { sort.Strings(k) for _, id := range k { - c = append(c, []byte("Region: id="+s.Regions[id].ID)...) + if _, err = w.WriteString("Region: id=" + s.Regions[id].ID); err != nil { + err = fmt.Errorf("astisub: writing region id failed: %w", err) + return + } if s.Regions[id].InlineStyle.WebVTTLines != 0 { - c = append(c, bytesSpace...) - c = append(c, []byte("lines="+strconv.Itoa(s.Regions[id].InlineStyle.WebVTTLines))...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("lines=" + strconv.Itoa(s.Regions[id].InlineStyle.WebVTTLines)); err != nil { + err = fmt.Errorf("astisub: writing lines failed: %w", err) + return + } } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTLines != 0 { - c = append(c, bytesSpace...) - c = append(c, []byte("lines="+strconv.Itoa(s.Regions[id].Style.InlineStyle.WebVTTLines))...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("lines=" + strconv.Itoa(s.Regions[id].Style.InlineStyle.WebVTTLines)); err != nil { + err = fmt.Errorf("astisub: writing lines failed: %w", err) + return + } } if s.Regions[id].InlineStyle.WebVTTRegionAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("regionanchor="+s.Regions[id].InlineStyle.WebVTTRegionAnchor)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("regionanchor=" + s.Regions[id].InlineStyle.WebVTTRegionAnchor); err != nil { + err = fmt.Errorf("astisub: writing regionanchor failed: %w", err) + return + } } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("regionanchor="+s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("regionanchor=" + s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor); err != nil { + err = fmt.Errorf("astisub: writing regionanchor failed: %w", err) + return + } } if s.Regions[id].InlineStyle.WebVTTScroll != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("scroll="+s.Regions[id].InlineStyle.WebVTTScroll)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("scroll=" + s.Regions[id].InlineStyle.WebVTTScroll); err != nil { + err = fmt.Errorf("astisub: writing scroll failed: %w", err) + return + } } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTScroll != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("scroll="+s.Regions[id].Style.InlineStyle.WebVTTScroll)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("scroll=" + s.Regions[id].Style.InlineStyle.WebVTTScroll); err != nil { + err = fmt.Errorf("astisub: writing scroll failed: %w", err) + return + } } if s.Regions[id].InlineStyle.WebVTTViewportAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("viewportanchor="+s.Regions[id].InlineStyle.WebVTTViewportAnchor)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("viewportanchor=" + s.Regions[id].InlineStyle.WebVTTViewportAnchor); err != nil { + err = fmt.Errorf("astisub: writing viewportanchor failed: %w", err) + return + } } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("viewportanchor="+s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("viewportanchor=" + s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor); err != nil { + err = fmt.Errorf("astisub: writing viewportanchor failed: %w", err) + return + } } if s.Regions[id].InlineStyle.WebVTTWidth != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("width="+s.Regions[id].InlineStyle.WebVTTWidth)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("width=" + s.Regions[id].InlineStyle.WebVTTWidth); err != nil { + err = fmt.Errorf("astisub: writing width failed: %w", err) + return + } } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTWidth != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("width="+s.Regions[id].Style.InlineStyle.WebVTTWidth)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("width=" + s.Regions[id].Style.InlineStyle.WebVTTWidth); err != nil { + err = fmt.Errorf("astisub: writing width failed: %w", err) + return + } + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return } - c = append(c, bytesLineSeparator...) } if len(s.Regions) > 0 { - c = append(c, bytesLineSeparator...) + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } } // Loop through subtitles for index, item := range s.Items { // Add comments if len(item.Comments) > 0 { - c = append(c, []byte("NOTE ")...) + if _, err = w.WriteString("NOTE "); err != nil { + err = fmt.Errorf("astisub: writing note failed: %w", err) + return + } for _, comment := range item.Comments { - c = append(c, []byte(comment)...) - c = append(c, bytesLineSeparator...) + if _, err = w.WriteString(comment); err != nil { + err = fmt.Errorf("astisub: writing comment failed: %w", err) + return + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return } - c = append(c, bytesLineSeparator...) } // Add time boundaries if writeWithIndex { - c = append(c, []byte(strconv.Itoa(item.Index))...) + if _, err = w.WriteString(strconv.Itoa(item.Index)); err != nil { + err = fmt.Errorf("astisub: writing index failed: %w", err) + return + } } else { - c = append(c, []byte(strconv.Itoa(index+1))...) + if _, err = w.WriteString(strconv.Itoa(index + 1)); err != nil { + err = fmt.Errorf("astisub: writing index failed: %w", err) + return + } + } + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } + if _, err = w.WriteString(formatDurationWebVTT(item.StartAt)); err != nil { + err = fmt.Errorf("astisub: writing start at failed: %w", err) + return + } + if _, err = w.Write(bytesWebVTTTimeBoundariesSeparator); err != nil { + err = fmt.Errorf("astisub: writing time boundaries separator failed: %w", err) + return + } + if _, err = w.WriteString(formatDurationWebVTT(item.EndAt)); err != nil { + err = fmt.Errorf("astisub: writing end at failed: %w", err) + return } - c = append(c, bytesLineSeparator...) - c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) - c = append(c, bytesWebVTTTimeBoundariesSeparator...) - c = append(c, []byte(formatDurationWebVTT(item.EndAt))...) // Add styles if item.InlineStyle != nil { if item.InlineStyle.WebVTTAlign != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("align:"+item.InlineStyle.WebVTTAlign)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("align:" + item.InlineStyle.WebVTTAlign); err != nil { + err = fmt.Errorf("astisub: writing align failed: %w", err) + return + } } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTAlign != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("align:"+item.Style.InlineStyle.WebVTTAlign)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("align:" + item.Style.InlineStyle.WebVTTAlign); err != nil { + err = fmt.Errorf("astisub: writing align failed: %w", err) + return + } } if item.InlineStyle.WebVTTLine != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("line:"+item.InlineStyle.WebVTTLine)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("line:" + item.InlineStyle.WebVTTLine); err != nil { + err = fmt.Errorf("astisub: writing line failed: %w", err) + return + } } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTLine != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("line:"+item.Style.InlineStyle.WebVTTLine)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("line:" + item.Style.InlineStyle.WebVTTLine); err != nil { + err = fmt.Errorf("astisub: writing line failed: %w", err) + return + } } if item.InlineStyle.WebVTTPosition != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("position:"+item.InlineStyle.WebVTTPosition.String())...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("position:" + item.InlineStyle.WebVTTPosition.String()); err != nil { + err = fmt.Errorf("astisub: writing position failed: %w", err) + return + } } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTPosition != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("position:"+item.Style.InlineStyle.WebVTTPosition.String())...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("position:" + item.Style.InlineStyle.WebVTTPosition.String()); err != nil { + err = fmt.Errorf("astisub: writing position failed: %w", err) + return + } } if item.Region != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("region:"+item.Region.ID)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("region:" + item.Region.ID); err != nil { + err = fmt.Errorf("astisub: writing region failed: %w", err) + return + } } if item.InlineStyle.WebVTTSize != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("size:"+item.InlineStyle.WebVTTSize)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("size:" + item.InlineStyle.WebVTTSize); err != nil { + err = fmt.Errorf("astisub: writing size failed: %w", err) + return + } } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTSize != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("size:"+item.Style.InlineStyle.WebVTTSize)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("size:" + item.Style.InlineStyle.WebVTTSize); err != nil { + err = fmt.Errorf("astisub: writing size failed: %w", err) + return + } } if item.InlineStyle.WebVTTVertical != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("vertical:"+item.InlineStyle.WebVTTVertical)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("vertical:" + item.InlineStyle.WebVTTVertical); err != nil { + err = fmt.Errorf("astisub: writing vertical failed: %w", err) + return + } } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTVertical != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("vertical:"+item.Style.InlineStyle.WebVTTVertical)...) + if _, err = w.Write(bytesSpace); err != nil { + err = fmt.Errorf("astisub: writing space failed: %w", err) + return + } + if _, err = w.WriteString("vertical:" + item.Style.InlineStyle.WebVTTVertical); err != nil { + err = fmt.Errorf("astisub: writing vertical failed: %w", err) + return + } } } // Add new line - c = append(c, bytesLineSeparator...) + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } // Loop through lines for _, l := range item.Lines { - c = append(c, l.webVTTBytes()...) + if err = l.writeWebVTT(w); err != nil { + return + } } // Add new line - c = append(c, bytesLineSeparator...) + if index < len(s.Items)-1 { + if _, err = w.Write(bytesLineSeparator); err != nil { + err = fmt.Errorf("astisub: writing line separator failed: %w", err) + return + } + } } - // Remove last new line - c = c[:len(c)-1] - - // Write - if _, err = o.Write(c); err != nil { - err = fmt.Errorf("astisub: writing failed: %w", err) - return - } return } -func (l Line) webVTTBytes() (c []byte) { +func (l Line) writeWebVTT(w io.Writer) (err error) { if l.VoiceName != "" { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing voice name failed: %w", err) + } } for idx := 0; idx < len(l.Items); idx++ { var previous, next *LineItem @@ -692,16 +883,22 @@ func (l Line) webVTTBytes() (c []byte) { if idx < len(l.Items)-1 { next = &l.Items[idx+1] } - c = append(c, l.Items[idx].webVTTBytes(previous, next)...) + if err = l.Items[idx].writeWebVTT(w, previous, next); err != nil { + return + } + } + if _, err = w.Write(bytesLineSeparator); err != nil { + return fmt.Errorf("astisub: writing line separator failed: %w", err) } - c = append(c, bytesLineSeparator...) return } -func (li LineItem) webVTTBytes(previous, next *LineItem) (c []byte) { +func (li LineItem) writeWebVTT(w io.Writer, previous, next *LineItem) (err error) { // Add timestamp if li.StartAt > 0 { - c = append(c, []byte("<"+formatDurationWebVTT(li.StartAt)+">")...) + if _, err = w.Write([]byte("<" + formatDurationWebVTT(li.StartAt) + ">")); err != nil { + return fmt.Errorf("astisub: writing start at failed: %w", err) + } } // Get color - only add TTMLColor-based tag if there are no WebVTT color tags @@ -723,28 +920,38 @@ func (li LineItem) webVTTBytes(previous, next *LineItem) (c []byte) { // Append if color != "" { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing color failed: %w", err) + } } if li.InlineStyle != nil { for idx, tag := range li.InlineStyle.WebVTTTags { if previous != nil && previous.InlineStyle != nil && len(previous.InlineStyle.WebVTTTags) > idx && tag.Name == previous.InlineStyle.WebVTTTags[idx].Name { continue } - c = append(c, []byte(tag.startTag())...) + if _, err = w.Write([]byte(tag.startTag())); err != nil { + return fmt.Errorf("astisub: writing start tag failed: %w", err) + } } } - c = append(c, []byte(escapeHTML(li.Text))...) + if _, err = w.Write([]byte(escapeHTML(li.Text))); err != nil { + return fmt.Errorf("astisub: writing text failed: %w", err) + } if li.InlineStyle != nil { for i := len(li.InlineStyle.WebVTTTags) - 1; i >= 0; i-- { tag := li.InlineStyle.WebVTTTags[i] if next != nil && next.InlineStyle != nil && len(next.InlineStyle.WebVTTTags) > i && tag.Name == next.InlineStyle.WebVTTTags[i].Name { continue } - c = append(c, []byte(tag.endTag())...) + if _, err = w.Write([]byte(tag.endTag())); err != nil { + return fmt.Errorf("astisub: writing end tag failed: %w", err) + } } } if color != "" { - c = append(c, []byte("")...) + if _, err = w.Write([]byte("")); err != nil { + return fmt.Errorf("astisub: writing color close failed: %w", err) + } } return } diff --git a/webvtt_internal_test.go b/webvtt_internal_test.go index f77b718..b20b396 100644 --- a/webvtt_internal_test.go +++ b/webvtt_internal_test.go @@ -1,12 +1,12 @@ package astisub import ( + "bytes" "strconv" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestParseTextWebVTT(t *testing.T) { @@ -165,7 +165,8 @@ func TestCueVoiceSpanRegex(t *testing.T) { } func TestLineWebVTTBytes(t *testing.T) { - require.Equal(t, "1 2 3\n", string(Line{Items: []LineItem{ + w := &bytes.Buffer{} + err := Line{Items: []LineItem{ { InlineStyle: &StyleAttributes{WebVTTTags: []WebVTTTag{ {Name: "t1"}, @@ -185,5 +186,7 @@ func TestLineWebVTTBytes(t *testing.T) { }}, Text: " 3", }, - }}.webVTTBytes())) + }}.writeWebVTT(w) + assert.NoError(t, err) + assert.Equal(t, "1 2 3\n", w.String()) } diff --git a/webvtt_test.go b/webvtt_test.go index 8dce41c..e0ab568 100644 --- a/webvtt_test.go +++ b/webvtt_test.go @@ -347,3 +347,16 @@ Normal text with magenta and unknown color` assert.Nil(t, unknownColorItem.InlineStyle.TTMLColor) // Unknown color should not be converted assert.Nil(t, unknownColorItem.InlineStyle.TTMLBackgroundColor) } + +func BenchmarkWriteToWebVTT(b *testing.B) { + s, err := astisub.OpenFile("./testdata/example-in.vtt") + if err != nil { + b.Fatal(err) + } + w := &bytes.Buffer{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + w.Reset() + s.WriteToWebVTT(w) + } +} From 8fd230f7de7551b9989329052d57a29d5d2bc0db Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Sat, 7 Feb 2026 09:56:05 +0330 Subject: [PATCH 17/21] chore: Update Go version to 1.24 in the test workflow. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1ac19d..986aed8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.24' - name: Install dependencies run: go mod download From fa8dbf1b0c85309a1999fb966a0007cbe17ce3c3 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 13 Mar 2026 22:34:42 +0330 Subject: [PATCH 18/21] refactor: use astikit.WriteChainer for robust write operations in subtitle formats and add astikit dependency. --- go.mod | 2 +- go.sum | 2 + srt.go | 116 +++-------- ssa.go | 244 ++++++++++++---------- stl.go | 9 +- webvtt.go | 452 +++++++++++++++++----------------------- webvtt_internal_test.go | 3 +- 7 files changed, 365 insertions(+), 463 deletions(-) diff --git a/go.mod b/go.mod index 79fd44c..9faaf37 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/asticode/go-astisub go 1.24.0 require ( - github.com/asticode/go-astikit v0.57.1 + github.com/asticode/go-astikit v0.58.0 github.com/asticode/go-astits v1.15.0 github.com/stretchr/testify v1.4.0 golang.org/x/net v0.49.0 diff --git a/go.sum b/go.sum index bfd3149..5826fde 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= github.com/asticode/go-astikit v0.57.1 h1:fEykwH98Nny08kcRbk4uer+S8h0rKveCIpG9F6NVLuA= github.com/asticode/go-astikit v0.57.1/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astikit v0.58.0 h1:WXNpaxCPNFReikHiXvzyDv49NpV/GMD6PV80iem6WGo= +github.com/asticode/go-astikit v0.58.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= github.com/asticode/go-astits v1.15.0 h1:yRyCiUc8Jj4F7clt2GDxHghMpWuFL5rkaLuGUd2/0J4= github.com/asticode/go-astits v1.15.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= diff --git a/srt.go b/srt.go index 246f7d9..e7b20a3 100644 --- a/srt.go +++ b/srt.go @@ -1,7 +1,6 @@ package astisub import ( - "bufio" "fmt" "io" "strconv" @@ -9,6 +8,7 @@ import ( "time" "unicode/utf8" + "github.com/asticode/go-astikit" "golang.org/x/net/html" ) @@ -221,55 +221,38 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { return } - // Init writer - w := bufio.NewWriter(o) - defer w.Flush() + // Init chainer + c := astikit.NewWriteChainer(o) // Add BOM header - if _, err = w.Write(BytesBOM); err != nil { - err = fmt.Errorf("astisub: writing bom failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("bom", BytesBOM)); err != nil { return } // Loop through subtitles for k, v := range s.Items { // Add time boundaries - if _, err = w.WriteString(strconv.Itoa(k + 1)); err != nil { - err = fmt.Errorf("astisub: writing index failed: %w", err) - return - } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) - return - } - if _, err = w.WriteString(formatDurationSRT(v.StartAt)); err != nil { - err = fmt.Errorf("astisub: writing start at failed: %w", err) - return - } - if _, err = w.Write(bytesSRTTimeBoundariesSeparator); err != nil { - err = fmt.Errorf("astisub: writing time boundaries separator failed: %w", err) - return - } - if _, err = w.WriteString(formatDurationSRT(v.EndAt)); err != nil { - err = fmt.Errorf("astisub: writing end at failed: %w", err) - return - } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("index", []byte(strconv.Itoa(k+1))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + astikit.WriteWithLabel("start at", []byte(formatDurationSRT(v.StartAt))), + astikit.WriteWithLabel("time boundaries separator", bytesSRTTimeBoundariesSeparator), + astikit.WriteWithLabel("end at", []byte(formatDurationSRT(v.EndAt))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { return } // Loop through lines for _, l := range v.Lines { - if err = l.writeSRT(w); err != nil { + if err = l.writeSRT(c); err != nil { return } } // Add new line if k < len(s.Items)-1 { - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } } @@ -277,20 +260,19 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { return } -func (l Line) writeSRT(w io.Writer) (err error) { +func (l Line) writeSRT(c *astikit.WriteChainer) (err error) { for _, li := range l.Items { - if err = li.writeSRT(w); err != nil { + if err = li.writeSRT(c); err != nil { return } } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } return } -func (li LineItem) writeSRT(w io.Writer) (err error) { +func (li LineItem) writeSRT(c *astikit.WriteChainer) (err error) { // Get color var color string if li.InlineStyle != nil && li.InlineStyle.SRTColor != nil { @@ -308,54 +290,20 @@ func (li LineItem) writeSRT(w io.Writer) (err error) { pos = li.InlineStyle.SRTPosition } - // Append - if color != "" { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing font color failed: %w", err) - } - } - if b { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing bold failed: %w", err) - } - } - if i { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing italics failed: %w", err) - } - } - if u { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing underline failed: %w", err) - } - } - if pos != 0 { - if _, err = w.Write([]byte(fmt.Sprintf(`{\an%d}`, pos))); err != nil { - return fmt.Errorf("astisub: writing position failed: %w", err) - } - } - if _, err = w.Write([]byte(escapeHTML(li.Text))); err != nil { - return fmt.Errorf("astisub: writing text failed: %w", err) - } - if u { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing underline close failed: %w", err) - } - } - if i { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing italics close failed: %w", err) - } - } - if b { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing bold close failed: %w", err) - } - } - if color != "" { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing font close failed: %w", err) - } + // Write + if _, err = c.Write( + astikit.WriteWithCondition("font color", []byte(""), color != ""), + astikit.WriteWithCondition("bold", []byte(""), b), + astikit.WriteWithCondition("italics", []byte(""), i), + astikit.WriteWithCondition("underline", []byte(""), u), + astikit.WriteWithCondition("position", []byte(fmt.Sprintf(`{\an%d}`, pos)), pos != 0), + astikit.WriteWithLabel("text", []byte(escapeHTML(li.Text))), + astikit.WriteWithCondition("underline close", []byte(""), u), + astikit.WriteWithCondition("italics close", []byte(""), i), + astikit.WriteWithCondition("bold close", []byte(""), b), + astikit.WriteWithCondition("font close", []byte(""), color != ""), + ); err != nil { + return } return } diff --git a/ssa.go b/ssa.go index 4ba5e9d..3abdd11 100644 --- a/ssa.go +++ b/ssa.go @@ -1,7 +1,6 @@ package astisub import ( - "bufio" "fmt" "io" "log" @@ -411,139 +410,155 @@ func (b *ssaScriptInfo) metadata() *Metadata { } // write writes the block to the writer -func (b *ssaScriptInfo) write(w io.Writer) (err error) { - if _, err = w.Write([]byte("[Script Info]")); err != nil { - return fmt.Errorf("astisub: writing script info header failed: %w", err) +func (b *ssaScriptInfo) write(c *astikit.WriteChainer) (err error) { + if _, err = c.Write(astikit.WriteWithLabel("script info header", []byte("[Script Info]"))); err != nil { + return } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return } - for _, c := range b.comments { - if _, err = w.Write([]byte("; " + c)); err != nil { - return fmt.Errorf("astisub: writing comment failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + for _, comment := range b.comments { + if _, err = c.Write( + astikit.WriteWithLabel("comment start", []byte("; ")), + astikit.WriteWithLabel("comment text", []byte(comment)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.collisions) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameCollisions + ": " + b.collisions)); err != nil { - return fmt.Errorf("astisub: writing collisions failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("collisions label", []byte(ssaScriptInfoNameCollisions+": ")), + astikit.WriteWithLabel("collisions", []byte(b.collisions)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.originalEditing) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameOriginalEditing + ": " + b.originalEditing)); err != nil { - return fmt.Errorf("astisub: writing original editing failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("original editing label", []byte(ssaScriptInfoNameOriginalEditing+": ")), + astikit.WriteWithLabel("original editing", []byte(b.originalEditing)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.originalScript) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameOriginalScript + ": " + b.originalScript)); err != nil { - return fmt.Errorf("astisub: writing original script failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("original script label", []byte(ssaScriptInfoNameOriginalScript+": ")), + astikit.WriteWithLabel("original script", []byte(b.originalScript)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.originalTiming) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameOriginalTiming + ": " + b.originalTiming)); err != nil { - return fmt.Errorf("astisub: writing original timing failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("original timing label", []byte(ssaScriptInfoNameOriginalTiming+": ")), + astikit.WriteWithLabel("original timing", []byte(b.originalTiming)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.originalTranslation) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameOriginalTranslation + ": " + b.originalTranslation)); err != nil { - return fmt.Errorf("astisub: writing original translation failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("original translation label", []byte(ssaScriptInfoNameOriginalTranslation+": ")), + astikit.WriteWithLabel("original translation", []byte(b.originalTranslation)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if b.playDepth != nil { - if _, err = w.Write([]byte(ssaScriptInfoNamePlayDepth + ": " + strconv.Itoa(*b.playDepth))); err != nil { - return fmt.Errorf("astisub: writing play depth failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("play depth label", []byte(ssaScriptInfoNamePlayDepth+": ")), + astikit.WriteWithLabel("play depth", []byte(strconv.Itoa(*b.playDepth))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if b.playResX != nil { - if _, err = w.Write([]byte(ssaScriptInfoNamePlayResX + ": " + strconv.Itoa(*b.playResX))); err != nil { - return fmt.Errorf("astisub: writing play res x failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("play res x label", []byte(ssaScriptInfoNamePlayResX+": ")), + astikit.WriteWithLabel("play res x", []byte(strconv.Itoa(*b.playResX))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if b.playResY != nil { - if _, err = w.Write([]byte(ssaScriptInfoNamePlayResY + ": " + strconv.Itoa(*b.playResY))); err != nil { - return fmt.Errorf("astisub: writing play res y failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("play res y label", []byte(ssaScriptInfoNamePlayResY+": ")), + astikit.WriteWithLabel("play res y", []byte(strconv.Itoa(*b.playResY))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.scriptType) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameScriptType + ": " + b.scriptType)); err != nil { - return fmt.Errorf("astisub: writing script type failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("script type label", []byte(ssaScriptInfoNameScriptType+": ")), + astikit.WriteWithLabel("script type", []byte(b.scriptType)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.scriptUpdatedBy) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameScriptUpdatedBy + ": " + b.scriptUpdatedBy)); err != nil { - return fmt.Errorf("astisub: writing script updated by failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("script updated by label", []byte(ssaScriptInfoNameScriptUpdatedBy+": ")), + astikit.WriteWithLabel("script updated by", []byte(b.scriptUpdatedBy)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.synchPoint) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameSynchPoint + ": " + b.synchPoint)); err != nil { - return fmt.Errorf("astisub: writing synch point failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("synch point label", []byte(ssaScriptInfoNameSynchPoint+": ")), + astikit.WriteWithLabel("synch point", []byte(b.synchPoint)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if b.timer != nil { - if _, err = w.Write([]byte(ssaScriptInfoNameTimer + ": " + strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1))); err != nil { - return fmt.Errorf("astisub: writing timer failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("timer label", []byte(ssaScriptInfoNameTimer+": ")), + astikit.WriteWithLabel("timer", []byte(strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.title) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameTitle + ": " + b.title)); err != nil { - return fmt.Errorf("astisub: writing title failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("title label", []byte(ssaScriptInfoNameTitle+": ")), + astikit.WriteWithLabel("title", []byte(b.title)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.updateDetails) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameUpdateDetails + ": " + b.updateDetails)); err != nil { - return fmt.Errorf("astisub: writing update details failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("update details label", []byte(ssaScriptInfoNameUpdateDetails+": ")), + astikit.WriteWithLabel("update details", []byte(b.updateDetails)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } if len(b.wrapStyle) > 0 { - if _, err = w.Write([]byte(ssaScriptInfoNameWrapStyle + ": " + b.wrapStyle)); err != nil { - return fmt.Errorf("astisub: writing wrap style failed: %w", err) - } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("wrap style label", []byte(ssaScriptInfoNameWrapStyle+": ")), + astikit.WriteWithLabel("wrap style", []byte(b.wrapStyle)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } return @@ -1258,13 +1273,12 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { return } - // Init writer - w := bufio.NewWriter(o) - defer w.Flush() + // Init chainer + c := astikit.NewWriteChainer(o) // Write Script Info block var si = newSSAScriptInfo(s.Metadata) - if err = si.write(w); err != nil { + if err = si.write(c); err != nil { return } @@ -1273,14 +1287,12 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { // Write Styles block if len(s.Styles) > 0 { // Header + var header = "\n[V4 Styles]\n" if v4plus { - if _, err = w.WriteString("\n[V4+ Styles]\n"); err != nil { - return fmt.Errorf("astisub: writing styles header failed: %w", err) - } - } else { - if _, err = w.WriteString("\n[V4 Styles]\n"); err != nil { - return fmt.Errorf("astisub: writing styles header failed: %w", err) - } + header = "\n[V4+ Styles]\n" + } + if _, err = c.Write(astikit.WriteWithLabel("styles header", []byte(header))); err != nil { + return } // Format @@ -1294,15 +1306,23 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { styles[ss.name] = ss styleNames = append(styleNames, ss.name) } - if _, err = w.WriteString("Format: " + strings.Join(format, ", ") + "\n"); err != nil { - return fmt.Errorf("astisub: writing styles format failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("styles format label", []byte("Format: ")), + astikit.WriteWithLabel("styles format", []byte(strings.Join(format, ", "))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } // Styles sort.Strings(styleNames) for _, n := range styleNames { - if _, err = w.WriteString("Style: " + styles[n].string(format) + "\n"); err != nil { - return fmt.Errorf("astisub: writing style failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("style label", []byte("Style: ")), + astikit.WriteWithLabel("style", []byte(styles[n].string(format))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } } @@ -1310,8 +1330,8 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { // Write Events block if len(s.Items) > 0 { // Header - if _, err = w.WriteString("\n[Events]\n"); err != nil { - return fmt.Errorf("astisub: writing events header failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("events header", []byte("\n[Events]\n"))); err != nil { + return } // Format @@ -1335,14 +1355,22 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { events = append(events, newSSAEventFromItem(*i)) } format = append(format, ssaEventFormatNameText) - if _, err = w.WriteString("Format: " + strings.Join(format, ", ") + "\n"); err != nil { - return fmt.Errorf("astisub: writing events format failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("events format label", []byte("Format: ")), + astikit.WriteWithLabel("events format", []byte(strings.Join(format, ", "))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } // Styles for _, e := range events { - if _, err = w.WriteString(ssaEventCategoryDialogue + ": " + e.string(format) + "\n"); err != nil { - return fmt.Errorf("astisub: writing event failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("event dialogue label", []byte(ssaEventCategoryDialogue+": ")), + astikit.WriteWithLabel("event dialogue", []byte(e.string(format))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return } } } diff --git a/stl.go b/stl.go index ad60463..25404c3 100644 --- a/stl.go +++ b/stl.go @@ -924,18 +924,19 @@ func (s Subtitles) WriteToSTL(o io.Writer) (err error) { return } + // Init chainer + c := astikit.NewWriteChainer(o) + // Write GSI block var g = newGSIBlock(s) - if _, err = o.Write(g.bytes()); err != nil { - err = fmt.Errorf("astisub: writing gsi block failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("gsi block", g.bytes())); err != nil { return } // Loop through items for idx, item := range s.Items { // Write tti block - if _, err = o.Write(newTTIBlock(item, idx+1, g.displayStandardCode).bytes(g)); err != nil { - err = fmt.Errorf("astisub: writing tti block #%d failed: %w", idx+1, err) + if _, err = c.Write(astikit.WriteWithLabel("tti block", newTTIBlock(item, idx+1, g.displayStandardCode).bytes(g))); err != nil { return } } diff --git a/webvtt.go b/webvtt.go index 5b8a46e..daecdae 100644 --- a/webvtt.go +++ b/webvtt.go @@ -1,7 +1,6 @@ package astisub import ( - "bufio" "errors" "fmt" "io" @@ -14,6 +13,8 @@ import ( "unicode/utf8" "golang.org/x/net/html" + + "github.com/asticode/go-astikit" ) // https://www.w3.org/TR/webvtt1/ @@ -495,38 +496,31 @@ func formatDurationWebVTT(i time.Duration) string { // WriteToWebVTT writes subtitles in .vtt format // if set true in second args write index as item index -func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { - var o io.Writer +// WriteToWebVTT writes subtitles in .vtt format +// if set true in second args write index as item index +func (s Subtitles) WriteToWebVTT(o io.Writer, args ...interface{}) (err error) { + // Options writeWithIndex := false for i, arg := range args { switch i { - case 0: // default output writer - out, ok := arg.(io.Writer) - if !ok { - return fmt.Errorf("first input argument must be io.Writer") - } - o = out - case 1: - b, ok := arg.(bool) - if !ok { - return fmt.Errorf("second input argument must be boolean") + case 0: + if b, ok := arg.(bool); ok { + writeWithIndex = b } - writeWithIndex = b } } + // Do not write anything if no subtitles if len(s.Items) == 0 { err = ErrNoSubtitlesToWrite return } - // Init writer - w := bufio.NewWriter(o) - defer w.Flush() + // Init chainer + c := astikit.NewWriteChainer(o) // Add header - if _, err = w.WriteString("WEBVTT"); err != nil { - err = fmt.Errorf("astisub: writing header failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("header", []byte("WEBVTT"))); err != nil { return } @@ -534,18 +528,15 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { if s.Metadata != nil { webVTTTimestampMap := s.Metadata.WebVTTTimestampMap if webVTTTimestampMap != nil { - if _, err = w.Write([]byte("\n")); err != nil { - err = fmt.Errorf("astisub: writing newline failed: %w", err) - return - } - if _, err = w.WriteString(webVTTTimestampMap.String()); err != nil { - err = fmt.Errorf("astisub: writing timestamp map failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("newline", []byte("\n")), + astikit.WriteWithLabel("timestamp map", []byte(webVTTTimestampMap.String())), + ); err != nil { return } } } - if _, err = w.Write([]byte("\n\n")); err != nil { - err = fmt.Errorf("astisub: writing newline failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("newline", []byte("\n\n"))); err != nil { return } @@ -557,8 +548,7 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { } if len(style) > 0 { - if _, err = w.WriteString(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n"))); err != nil { - err = fmt.Errorf("astisub: writing style failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("style", []byte(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n"))))); err != nil { return } } @@ -571,113 +561,87 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { sort.Strings(k) for _, id := range k { - if _, err = w.WriteString("Region: id=" + s.Regions[id].ID); err != nil { - err = fmt.Errorf("astisub: writing region id failed: %w", err) + var r = s.Regions[id] + if _, err = c.Write(astikit.WriteWithLabel("region id", []byte("Region: id="+r.ID))); err != nil { return } - if s.Regions[id].InlineStyle.WebVTTLines != 0 { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("lines=" + strconv.Itoa(s.Regions[id].InlineStyle.WebVTTLines)); err != nil { - err = fmt.Errorf("astisub: writing lines failed: %w", err) - return - } - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTLines != 0 { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("lines=" + strconv.Itoa(s.Regions[id].Style.InlineStyle.WebVTTLines)); err != nil { - err = fmt.Errorf("astisub: writing lines failed: %w", err) - return - } + + // Lines + lines := r.InlineStyle.WebVTTLines + if lines == 0 && r.Style != nil && r.Style.InlineStyle != nil { + lines = r.Style.InlineStyle.WebVTTLines } - if s.Regions[id].InlineStyle.WebVTTRegionAnchor != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("regionanchor=" + s.Regions[id].InlineStyle.WebVTTRegionAnchor); err != nil { - err = fmt.Errorf("astisub: writing regionanchor failed: %w", err) - return - } - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("regionanchor=" + s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor); err != nil { - err = fmt.Errorf("astisub: writing regionanchor failed: %w", err) + if lines != 0 { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("lines", []byte("lines="+strconv.Itoa(lines))), + ); err != nil { return } } - if s.Regions[id].InlineStyle.WebVTTScroll != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("scroll=" + s.Regions[id].InlineStyle.WebVTTScroll); err != nil { - err = fmt.Errorf("astisub: writing scroll failed: %w", err) - return - } - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTScroll != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("scroll=" + s.Regions[id].Style.InlineStyle.WebVTTScroll); err != nil { - err = fmt.Errorf("astisub: writing scroll failed: %w", err) - return - } + + // Region anchor + ra := r.InlineStyle.WebVTTRegionAnchor + if ra == "" && r.Style != nil && r.Style.InlineStyle != nil { + ra = r.Style.InlineStyle.WebVTTRegionAnchor } - if s.Regions[id].InlineStyle.WebVTTViewportAnchor != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("viewportanchor=" + s.Regions[id].InlineStyle.WebVTTViewportAnchor); err != nil { - err = fmt.Errorf("astisub: writing viewportanchor failed: %w", err) - return - } - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("viewportanchor=" + s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor); err != nil { - err = fmt.Errorf("astisub: writing viewportanchor failed: %w", err) + if ra != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("regionanchor", []byte("regionanchor="+ra)), + ); err != nil { return } } - if s.Regions[id].InlineStyle.WebVTTWidth != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("width=" + s.Regions[id].InlineStyle.WebVTTWidth); err != nil { - err = fmt.Errorf("astisub: writing width failed: %w", err) + + // Scroll + scroll := r.InlineStyle.WebVTTScroll + if scroll == "" && r.Style != nil && r.Style.InlineStyle != nil { + scroll = r.Style.InlineStyle.WebVTTScroll + } + if scroll != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("scroll", []byte("scroll="+scroll)), + ); err != nil { return } - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTWidth != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) + } + + // Viewport anchor + va := r.InlineStyle.WebVTTViewportAnchor + if va == "" && r.Style != nil && r.Style.InlineStyle != nil { + va = r.Style.InlineStyle.WebVTTViewportAnchor + } + if va != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("viewportanchor", []byte("viewportanchor="+va)), + ); err != nil { return } - if _, err = w.WriteString("width=" + s.Regions[id].Style.InlineStyle.WebVTTWidth); err != nil { - err = fmt.Errorf("astisub: writing width failed: %w", err) + } + + // Width + width := r.InlineStyle.WebVTTWidth + if width == "" && r.Style != nil && r.Style.InlineStyle != nil { + width = r.Style.InlineStyle.WebVTTWidth + } + if width != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("width", []byte("width="+width)), + ); err != nil { return } } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } } if len(s.Regions) > 0 { - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } } @@ -686,195 +650,152 @@ func (s Subtitles) WriteToWebVTT(args ...interface{}) (err error) { for index, item := range s.Items { // Add comments if len(item.Comments) > 0 { - if _, err = w.WriteString("NOTE "); err != nil { - err = fmt.Errorf("astisub: writing note failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("note", []byte("NOTE "))); err != nil { return } for _, comment := range item.Comments { - if _, err = w.WriteString(comment); err != nil { - err = fmt.Errorf("astisub: writing comment failed: %w", err) - return - } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("comment", []byte(comment)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { return } } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } } // Add time boundaries + idx := index + 1 if writeWithIndex { - if _, err = w.WriteString(strconv.Itoa(item.Index)); err != nil { - err = fmt.Errorf("astisub: writing index failed: %w", err) - return - } - } else { - if _, err = w.WriteString(strconv.Itoa(index + 1)); err != nil { - err = fmt.Errorf("astisub: writing index failed: %w", err) - return - } - } - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) - return - } - if _, err = w.WriteString(formatDurationWebVTT(item.StartAt)); err != nil { - err = fmt.Errorf("astisub: writing start at failed: %w", err) - return - } - if _, err = w.Write(bytesWebVTTTimeBoundariesSeparator); err != nil { - err = fmt.Errorf("astisub: writing time boundaries separator failed: %w", err) - return - } - if _, err = w.WriteString(formatDurationWebVTT(item.EndAt)); err != nil { - err = fmt.Errorf("astisub: writing end at failed: %w", err) + idx = item.Index + } + if _, err = c.Write( + astikit.WriteWithLabel("index", []byte(strconv.Itoa(idx))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + astikit.WriteWithLabel("start at", []byte(formatDurationWebVTT(item.StartAt))), + astikit.WriteWithLabel("time boundaries separator", bytesWebVTTTimeBoundariesSeparator), + astikit.WriteWithLabel("end at", []byte(formatDurationWebVTT(item.EndAt))), + ); err != nil { return } // Add styles if item.InlineStyle != nil { - if item.InlineStyle.WebVTTAlign != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("align:" + item.InlineStyle.WebVTTAlign); err != nil { - err = fmt.Errorf("astisub: writing align failed: %w", err) - return - } - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTAlign != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("align:" + item.Style.InlineStyle.WebVTTAlign); err != nil { - err = fmt.Errorf("astisub: writing align failed: %w", err) + // Align + align := item.InlineStyle.WebVTTAlign + if align == "" && item.Style != nil && item.Style.InlineStyle != nil { + align = item.Style.InlineStyle.WebVTTAlign + } + if align != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("align", []byte("align:"+align)), + ); err != nil { return } } - if item.InlineStyle.WebVTTLine != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("line:" + item.InlineStyle.WebVTTLine); err != nil { - err = fmt.Errorf("astisub: writing line failed: %w", err) - return - } - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTLine != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("line:" + item.Style.InlineStyle.WebVTTLine); err != nil { - err = fmt.Errorf("astisub: writing line failed: %w", err) - return - } + + // Line + line := item.InlineStyle.WebVTTLine + if line == "" && item.Style != nil && item.Style.InlineStyle != nil { + line = item.Style.InlineStyle.WebVTTLine } - if item.InlineStyle.WebVTTPosition != nil { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("position:" + item.InlineStyle.WebVTTPosition.String()); err != nil { - err = fmt.Errorf("astisub: writing position failed: %w", err) - return - } - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTPosition != nil { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) + if line != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("line", []byte("line:"+line)), + ); err != nil { return } - if _, err = w.WriteString("position:" + item.Style.InlineStyle.WebVTTPosition.String()); err != nil { - err = fmt.Errorf("astisub: writing position failed: %w", err) + } + + // Position + pos := item.InlineStyle.WebVTTPosition + if pos == nil && item.Style != nil && item.Style.InlineStyle != nil { + pos = item.Style.InlineStyle.WebVTTPosition + } + if pos != nil { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("position", []byte("position:"+pos.String())), + ); err != nil { return } } + + // Region if item.Region != nil { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("region:" + item.Region.ID); err != nil { - err = fmt.Errorf("astisub: writing region failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("region", []byte("region:"+item.Region.ID)), + ); err != nil { return } } - if item.InlineStyle.WebVTTSize != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("size:" + item.InlineStyle.WebVTTSize); err != nil { - err = fmt.Errorf("astisub: writing size failed: %w", err) - return - } - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTSize != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("size:" + item.Style.InlineStyle.WebVTTSize); err != nil { - err = fmt.Errorf("astisub: writing size failed: %w", err) - return - } + + // Size + size := item.InlineStyle.WebVTTSize + if size == "" && item.Style != nil && item.Style.InlineStyle != nil { + size = item.Style.InlineStyle.WebVTTSize } - if item.InlineStyle.WebVTTVertical != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("vertical:" + item.InlineStyle.WebVTTVertical); err != nil { - err = fmt.Errorf("astisub: writing vertical failed: %w", err) + if size != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("size", []byte("size:"+size)), + ); err != nil { return } - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTVertical != "" { - if _, err = w.Write(bytesSpace); err != nil { - err = fmt.Errorf("astisub: writing space failed: %w", err) - return - } - if _, err = w.WriteString("vertical:" + item.Style.InlineStyle.WebVTTVertical); err != nil { - err = fmt.Errorf("astisub: writing vertical failed: %w", err) + } + + // Vertical + vertical := item.InlineStyle.WebVTTVertical + if vertical == "" && item.Style != nil && item.Style.InlineStyle != nil { + vertical = item.Style.InlineStyle.WebVTTVertical + } + if vertical != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("vertical", []byte("vertical:"+vertical)), + ); err != nil { return } } } - // Add new line - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } - // Loop through lines + // Add lines for _, l := range item.Lines { - if err = l.writeWebVTT(w); err != nil { + if err = l.writeWebVTT(c); err != nil { return } } // Add new line if index < len(s.Items)-1 { - if _, err = w.Write(bytesLineSeparator); err != nil { - err = fmt.Errorf("astisub: writing line separator failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { return } } } - return } -func (l Line) writeWebVTT(w io.Writer) (err error) { +func (l Line) writeWebVTT(c *astikit.WriteChainer) (err error) { + // Voice name if l.VoiceName != "" { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing voice name failed: %w", err) + if _, err = c.Write( + astikit.WriteWithLabel("voice start", []byte("")), + ); err != nil { + return } } + + // Items for idx := 0; idx < len(l.Items); idx++ { var previous, next *LineItem if idx > 0 { @@ -883,22 +804,23 @@ func (l Line) writeWebVTT(w io.Writer) (err error) { if idx < len(l.Items)-1 { next = &l.Items[idx+1] } - if err = l.Items[idx].writeWebVTT(w, previous, next); err != nil { + if err = l.Items[idx].writeWebVTT(c, previous, next); err != nil { return } } - if _, err = w.Write(bytesLineSeparator); err != nil { - return fmt.Errorf("astisub: writing line separator failed: %w", err) + + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return } return } -func (li LineItem) writeWebVTT(w io.Writer, previous, next *LineItem) (err error) { +func (li LineItem) writeWebVTT(c *astikit.WriteChainer, previous, next *LineItem) (err error) { // Add timestamp - if li.StartAt > 0 { - if _, err = w.Write([]byte("<" + formatDurationWebVTT(li.StartAt) + ">")); err != nil { - return fmt.Errorf("astisub: writing start at failed: %w", err) - } + if _, err = c.Write( + astikit.WriteWithCondition("start at", []byte("<"+formatDurationWebVTT(li.StartAt)+">"), li.StartAt > 0), + ); err != nil { + return } // Get color - only add TTMLColor-based tag if there are no WebVTT color tags @@ -918,10 +840,10 @@ func (li LineItem) writeWebVTT(w io.Writer, previous, next *LineItem) (err error } } - // Append + // Write if color != "" { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing color failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("color start", []byte(""))); err != nil { + return } } if li.InlineStyle != nil { @@ -929,13 +851,13 @@ func (li LineItem) writeWebVTT(w io.Writer, previous, next *LineItem) (err error if previous != nil && previous.InlineStyle != nil && len(previous.InlineStyle.WebVTTTags) > idx && tag.Name == previous.InlineStyle.WebVTTTags[idx].Name { continue } - if _, err = w.Write([]byte(tag.startTag())); err != nil { - return fmt.Errorf("astisub: writing start tag failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("tag start", []byte(tag.startTag()))); err != nil { + return } } } - if _, err = w.Write([]byte(escapeHTML(li.Text))); err != nil { - return fmt.Errorf("astisub: writing text failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("text", []byte(escapeHTML(li.Text)))); err != nil { + return } if li.InlineStyle != nil { for i := len(li.InlineStyle.WebVTTTags) - 1; i >= 0; i-- { @@ -943,14 +865,14 @@ func (li LineItem) writeWebVTT(w io.Writer, previous, next *LineItem) (err error if next != nil && next.InlineStyle != nil && len(next.InlineStyle.WebVTTTags) > i && tag.Name == next.InlineStyle.WebVTTTags[i].Name { continue } - if _, err = w.Write([]byte(tag.endTag())); err != nil { - return fmt.Errorf("astisub: writing end tag failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("tag end", []byte(tag.endTag()))); err != nil { + return } } } if color != "" { - if _, err = w.Write([]byte("")); err != nil { - return fmt.Errorf("astisub: writing color close failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("color end", []byte(""))); err != nil { + return } } return diff --git a/webvtt_internal_test.go b/webvtt_internal_test.go index b20b396..28de9b0 100644 --- a/webvtt_internal_test.go +++ b/webvtt_internal_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/asticode/go-astikit" "github.com/stretchr/testify/assert" ) @@ -186,7 +187,7 @@ func TestLineWebVTTBytes(t *testing.T) { }}, Text: " 3", }, - }}.writeWebVTT(w) + }}.writeWebVTT(astikit.NewWriteChainer(w)) assert.NoError(t, err) assert.Equal(t, "1 2 3\n", w.String()) } From a2d85247c9ee0042f8f4daa6fbd3d2955db77dd3 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 13 Mar 2026 22:53:57 +0330 Subject: [PATCH 19/21] Revert Go version to 1.13 and cleanup webvtt comments --- go.mod | 2 +- webvtt.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9faaf37..81ea2c1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/asticode/go-astisub -go 1.24.0 +go 1.13 require ( github.com/asticode/go-astikit v0.58.0 diff --git a/webvtt.go b/webvtt.go index f1f5fb9..c93baba 100644 --- a/webvtt.go +++ b/webvtt.go @@ -494,8 +494,6 @@ func formatDurationWebVTT(i time.Duration) string { return formatDuration(i, ".", 3) } -// WriteToWebVTT writes subtitles in .vtt format -// if set true in second args write index as item index // WriteToWebVTT writes subtitles in .vtt format // if set true in second args write index as item index func (s Subtitles) WriteToWebVTT(o io.Writer, args ...interface{}) (err error) { From 266762f9bbc870e49354256b8a25f02d83ac42e4 Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 13 Mar 2026 22:58:23 +0330 Subject: [PATCH 20/21] Align WriteToWebVTT signature and logic with upstream master --- go.mod | 2 +- go.sum | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++- webvtt.go | 19 +------- 3 files changed, 134 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 81ea2c1..9faaf37 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/asticode/go-astisub -go 1.13 +go 1.24.0 require ( github.com/asticode/go-astikit v0.58.0 diff --git a/go.sum b/go.sum index 5826fde..3b32195 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,151 @@ github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astikit v0.57.1 h1:fEykwH98Nny08kcRbk4uer+S8h0rKveCIpG9F6NVLuA= -github.com/asticode/go-astikit v0.57.1/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= github.com/asticode/go-astikit v0.58.0 h1:WXNpaxCPNFReikHiXvzyDv49NpV/GMD6PV80iem6WGo= github.com/asticode/go-astikit v0.58.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= github.com/asticode/go-astits v1.15.0 h1:yRyCiUc8Jj4F7clt2GDxHghMpWuFL5rkaLuGUd2/0J4= github.com/asticode/go-astits v1.15.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/webvtt.go b/webvtt.go index c93baba..2f0aecb 100644 --- a/webvtt.go +++ b/webvtt.go @@ -496,18 +496,7 @@ func formatDurationWebVTT(i time.Duration) string { // WriteToWebVTT writes subtitles in .vtt format // if set true in second args write index as item index -func (s Subtitles) WriteToWebVTT(o io.Writer, args ...interface{}) (err error) { - // Options - writeWithIndex := false - for i, arg := range args { - switch i { - case 0: - if b, ok := arg.(bool); ok { - writeWithIndex = b - } - } - } - +func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { // Do not write anything if no subtitles if len(s.Items) == 0 { err = ErrNoSubtitlesToWrite @@ -665,12 +654,8 @@ func (s Subtitles) WriteToWebVTT(o io.Writer, args ...interface{}) (err error) { } // Add time boundaries - idx := index + 1 - if writeWithIndex { - idx = item.Index - } if _, err = c.Write( - astikit.WriteWithLabel("index", []byte(strconv.Itoa(idx))), + astikit.WriteWithLabel("index", []byte(strconv.Itoa(index+1))), astikit.WriteWithLabel("line separator", bytesLineSeparator), astikit.WriteWithLabel("start at", []byte(formatDurationWebVTT(item.StartAt))), astikit.WriteWithLabel("time boundaries separator", bytesWebVTTTimeBoundariesSeparator), From 932f2c7ff0d5293dbfc646087c2f3f747be5ac6d Mon Sep 17 00:00:00 2001 From: Mohammad Samimi Date: Fri, 13 Mar 2026 23:16:11 +0330 Subject: [PATCH 21/21] refactor: wrap write errors with contextual information in SSA and SRT formats --- srt.go | 2 ++ ssa.go | 1 + 2 files changed, 3 insertions(+) diff --git a/srt.go b/srt.go index 5366007..59e4f60 100644 --- a/srt.go +++ b/srt.go @@ -249,6 +249,7 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { // Loop through lines for _, l := range v.Lines { if err = l.writeSRT(c); err != nil { + err = fmt.Errorf("astisub: writing line item failed: %w", err) return } } @@ -266,6 +267,7 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { func (l Line) writeSRT(c *astikit.WriteChainer) (err error) { for _, li := range l.Items { if err = li.writeSRT(c); err != nil { + err = fmt.Errorf("astisub: writing line item failed: %w", err) return } } diff --git a/ssa.go b/ssa.go index 3abdd11..5bc8ad0 100644 --- a/ssa.go +++ b/ssa.go @@ -1279,6 +1279,7 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { // Write Script Info block var si = newSSAScriptInfo(s.Metadata) if err = si.write(c); err != nil { + err = fmt.Errorf("astisub: writing script info block failed: %w", err) return }