Skip to content

Commit 0175f9a

Browse files
authored
Merge pull request #1 from r-arpitgupta/vtt-segmentation
vtt segmentation
2 parents 6c644f7 + 5fce36e commit 0175f9a

File tree

3 files changed

+100
-8
lines changed

3 files changed

+100
-8
lines changed

astisub/main.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"log"
7+
"strconv"
8+
"strings"
69

710
"github.com/asticode/go-astikit"
811
"github.com/asticode/go-astisub"
@@ -19,6 +22,11 @@ var (
1922
teletextPage = flag.Int("p", 0, "the teletext page")
2023
outputPath = flag.String("o", "", "the output path")
2124
syncDuration = flag.Duration("s", 0, "the sync duration")
25+
segmentationType = flag.String("st", "UNIFIED", "the segmentation type UNIFIED/SPECIFIED. In unified"+
26+
" segmentation all segment have same duration whereas specified have user given duration for each segment.")
27+
segmentDuration = flag.Float64("sd", 5, "segmentation duration for unified segmentation type")
28+
segmentDurations = flag.String("sds", "", "segment durations for all segments seperated by comma")
29+
webvttOffset = flag.Float64("wo", 0, "webvtt offset for synchronization of segment in hls")
2230
)
2331

2432
func main() {
@@ -134,6 +142,27 @@ func main() {
134142
if err = sub.Write(*outputPath); err != nil {
135143
log.Fatalf("%s while writing to %s", err, *outputPath)
136144
}
145+
case "vtt-segment":
146+
// go run main.go vtt-segment -i <input_path> -st SPECIFIED -sds "2,4,10,12,13" -o "seg-%03d.vtt" -wo 10
147+
if *segmentationType == "SPECIFIED" && segmentDurations == nil {
148+
log.Fatalf("segment duration must be present for specified segmentation type")
149+
}
150+
var sds []float64
151+
if *segmentDurations != "" {
152+
for _, segDuration := range strings.Split(*segmentDurations, ",") {
153+
segDur, err := strconv.ParseFloat(segDuration, 64)
154+
if err != nil {
155+
log.Fatalf("%s unable to parse seg duration %s", err, segDuration)
156+
}
157+
sds = append(sds, segDur)
158+
}
159+
}
160+
segmentedSubs := sub.Segment(*segmentationType, *segmentDuration, sds)
161+
for idx, segmentedSub := range segmentedSubs {
162+
if err = segmentedSub.WriteToWebVTTFile(fmt.Sprintf(*outputPath, idx), *webvttOffset); err != nil {
163+
log.Fatalf("%s while writing to %s", err, *outputPath)
164+
}
165+
}
137166
default:
138167
log.Fatalf("Invalid subcommand %s", cmd)
139168
}

subtitles.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,28 +318,28 @@ func (sa *StyleAttributes) propagateTTMLAttributes() {
318318
sa.WebVTTAlign = *sa.TTMLTextAlign
319319
}
320320
if sa.TTMLExtent != nil {
321-
//region settings
322-
lineHeight := 5 //assuming height of line as 5.33vh
321+
// region settings
322+
lineHeight := 5 // assuming height of line as 5.33vh
323323
dimensions := strings.Split(*sa.TTMLExtent, " ")
324324
if len(dimensions) > 1 {
325325
sa.WebVTTWidth = dimensions[0]
326326
if height, err := strconv.Atoi(strings.ReplaceAll(dimensions[1], "%", "")); err == nil {
327327
sa.WebVTTLines = height / lineHeight
328328
}
329-
//cue settings
330-
//default TTML WritingMode is lrtb i.e. left to right, top to bottom
329+
// cue settings
330+
// default TTML WritingMode is lrtb i.e. left to right, top to bottom
331331
sa.WebVTTSize = dimensions[1]
332332
if sa.TTMLWritingMode != nil && strings.HasPrefix(*sa.TTMLWritingMode, "tb") {
333333
sa.WebVTTSize = dimensions[0]
334334
}
335335
}
336336
}
337337
if sa.TTMLOrigin != nil {
338-
//region settings
338+
// region settings
339339
sa.WebVTTRegionAnchor = "0%,0%"
340340
sa.WebVTTViewportAnchor = strings.ReplaceAll(strings.TrimSpace(*sa.TTMLOrigin), " ", ",")
341341
sa.WebVTTScroll = "up"
342-
//cue settings
342+
// cue settings
343343
coordinates := strings.Split(*sa.TTMLOrigin, " ")
344344
if len(coordinates) > 1 {
345345
sa.WebVTTLine = coordinates[0]
@@ -487,6 +487,51 @@ func (s *Subtitles) ForceDuration(d time.Duration, addDummyItem bool) {
487487
}
488488
}
489489

490+
func (s *Subtitles) Segment(segmentationType string, segmentDuration float64, segmentDurations []float64) []*Subtitles {
491+
if len(s.Items) == 0 {
492+
return nil
493+
}
494+
s.Order()
495+
var noOfSegs int
496+
if segmentationType == "SPECIFIED" {
497+
noOfSegs = len(segmentDurations)
498+
} else {
499+
totalDurationSecs := s.Items[len(s.Items)-1].EndAt.Seconds()
500+
noOfSegs = int(math.Ceil(totalDurationSecs / segmentDuration))
501+
}
502+
if noOfSegs == 0 {
503+
return nil
504+
}
505+
var subs []*Subtitles
506+
itemIdx := 0
507+
end := time.Duration(0)
508+
for i := 0; i < noOfSegs; i++ {
509+
sub := NewSubtitles()
510+
sub.Regions = s.Regions
511+
sub.Styles = s.Styles
512+
sub.Metadata = s.Metadata
513+
if segmentationType == "SPECIFIED" {
514+
end = end + time.Duration(segmentDurations[i]*float64(time.Second))
515+
} else {
516+
end = time.Duration(float64(i+1) * segmentDuration * float64(time.Second))
517+
}
518+
if end <= s.Items[itemIdx].StartAt {
519+
} else {
520+
for itemIdx < len(s.Items) && end > s.Items[itemIdx].StartAt {
521+
sub.Items = append(sub.Items, s.Items[itemIdx])
522+
if s.Items[itemIdx].EndAt <= end {
523+
itemIdx++
524+
} else {
525+
// handle same cue in multiple segment
526+
break
527+
}
528+
}
529+
}
530+
subs = append(subs, sub)
531+
}
532+
return subs
533+
}
534+
490535
// Fragment fragments subtitles with a specific fragment duration
491536
func (s *Subtitles) Fragment(f time.Duration) {
492537
// Nothing to fragment

webvtt.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"log"
9+
"os"
910
"regexp"
1011
"sort"
1112
"strconv"
@@ -385,18 +386,35 @@ func formatDurationWebVTT(i time.Duration) string {
385386
return formatDuration(i, ".", 3)
386387
}
387388

389+
// WriteToWebVTTFile writes subtitles in .vtt format
390+
func (s Subtitles) WriteToWebVTTFile(dst string, offset float64) error {
391+
// Do not write anything if no subtitles
392+
f, err := os.Create(dst)
393+
if err != nil {
394+
return err
395+
}
396+
return s.WriteToWebVTTWithSync(f, offset)
397+
}
398+
388399
// WriteToWebVTT writes subtitles in .vtt format
389400
func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) {
390401
// Do not write anything if no subtitles
391402
if len(s.Items) == 0 {
392403
err = ErrNoSubtitlesToWrite
393404
return
394405
}
406+
return s.WriteToWebVTTWithSync(o, 0)
407+
}
395408

409+
// WriteToWebVTTWithSync writes subtitles in .vtt format
410+
func (s Subtitles) WriteToWebVTTWithSync(o io.Writer, offset float64) (err error) {
396411
// Add header
397412
var c []byte
398-
c = append(c, []byte("WEBVTT\n\n")...)
399-
413+
if offset == 0 {
414+
c = append(c, []byte("WEBVTT\n\n")...)
415+
} else {
416+
c = append(c, []byte(fmt.Sprintf("WEBVTT\n%s=MPEGTS:%d,LOCAL:00:00:00.000\n\n", webvttTimestampMap, int(offset*90000)))...)
417+
}
400418
var style []string
401419
for _, s := range s.Styles {
402420
if s.InlineStyle != nil {

0 commit comments

Comments
 (0)