package main import ( "fmt" "io" "sort" "time" ) func secs_to_ass_time(secs float64) string { dur := time.Duration(secs) * time.Second hh := int64(dur.Hours()) mm := int64(dur.Minutes()) - (hh * 60) ss := int64(dur.Seconds()) - (hh * 3600) - (mm * 60) ms := int64(dur.Milliseconds()) - (hh * 3600000) - (mm * 60000) - (ss * 1000) return fmt.Sprintf("%02d:%02d:%02d.%03d", hh, mm, ss, ms) } // WriteSubtitle streams the video subtitles to the supplied writer in ASS format. func (ltc *loadTupContent) WriteSubtitle(w io.Writer, totalVideoDurationSecs float64) error { w.Write([]byte(`[Script Info] ; Script generated by loadtup-dl ScriptType: v4.00+ Collisions: Normal Timer: 100,0000 WrapStyle: 3 [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Default, Arial,16,&H00FFFFFF,&H00FFFFFF,&H00000008,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,10,10,10,0 Style: TLNote, Arial,10,&H00FFFFFF,&H00FFFFFF,&H00000008,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,8,10,10,10,0 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text `)) type entry struct { startTime float64 entry string } entries := make([]entry, 0, len(ltc.Caps)+len(ltc.Scrcaps)) for i := 0; i < len(ltc.Caps); i += 1 { if ltc.Caps[i] == "" { // Don't show anything continue } start := secs_to_ass_time(ltc.Secs[i]) var end string if i < len(ltc.Caps)-1 { end = secs_to_ass_time(ltc.Secs[i+1]) } else { // The final subtitle. Loadtup displays these for the entire // remaining video duration end = secs_to_ass_time(totalVideoDurationSecs) } // Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text entries = append(entries, entry{ startTime: ltc.Secs[i], entry: fmt.Sprintf("Dialogue: 0,%s,%s,Default,,0000,0000,0000,,%s\n", start, end, ltc.Caps[i]), }) } // Repeat for scrcaps, using top positioning and a different layer for i := 0; i < len(ltc.Scrcaps); i += 1 { if ltc.Caps[i] == "" { // Don't show anything continue } start := secs_to_ass_time(ltc.Scrsecs[i]) var end string if i < len(ltc.Scrcaps)-1 { end = secs_to_ass_time(ltc.Scrsecs[i+1]) } else { // The final subtitle. Loadtup displays these for the entire // remaining video duration end = secs_to_ass_time(totalVideoDurationSecs) } // Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text entries = append(entries, entry{ startTime: ltc.Scrsecs[i], entry: fmt.Sprintf("Dialogue: 1,%s,%s,TLNote,,0000,0000,0000,,%s\n", start, end, ltc.Scrcaps[i]), }) } // Sort all the entries by their start time, to mingle TL note entries // properly with the other subtitles sort.SliceStable(entries, func(i, j int) bool { return entries[i].startTime < entries[j].startTime }) // Emit all to writer for _, e := range entries { w.Write([]byte(e.entry)) } return nil }