From feefb054bf7ad177ce951016c31de7cf61141ad7 Mon Sep 17 00:00:00 2001 From: mappu Date: Mon, 12 Apr 2021 12:16:25 +1200 Subject: [PATCH] support translator notes (scrcaps), emit ASS format instead of SRT --- main.go | 3 +- scrape.go | 4 --- writesubs.go | 90 ++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/main.go b/main.go index e91d6a8..f944a23 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" ) @@ -116,7 +117,7 @@ func performDownload(ctx context.Context, cfg *config, targetUrl string) error { return err } - err = ltc.WriteSRT(fh, float64(msecsDuration)/1000) + err = ltc.WriteSubtitle(fh, float64(msecsDuration)/1000) fh.Close() if err != nil { return err diff --git a/scrape.go b/scrape.go index 9aaf1f2..caa8109 100644 --- a/scrape.go +++ b/scrape.go @@ -103,10 +103,6 @@ func (ltc *loadTupContent) Validate() error { return fmt.Errorf("scrsecs/scrcaps length mismatch") } - if !(len(ltc.Scrcaps) == 0 || (len(ltc.Scrcaps) == 1 && ltc.Scrcaps[0] == "")) { - return errors.New("unsupported use of strcaps") - } - if len(ltc.VideoID) == 0 { return errors.New("unexpected blank video ID") } diff --git a/writesubs.go b/writesubs.go index 4e7a3e4..6ad4870 100644 --- a/writesubs.go +++ b/writesubs.go @@ -3,10 +3,11 @@ package main import ( "fmt" "io" + "sort" "time" ) -func secs_to_srt_time(secs float64) string { +func secs_to_ass_time(secs float64) string { dur := time.Duration(secs) * time.Second hh := int64(dur.Hours()) @@ -14,45 +15,92 @@ func secs_to_srt_time(secs float64) string { 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) + return fmt.Sprintf("%02d:%02d:%02d.%03d", hh, mm, ss, ms) } -func (ltc *loadTupContent) WriteSRT(w io.Writer, totalVideoDurationSecs float64) error { - /* +// WriteSubtitle streams the video subtitles to the supplied writer in ASS format. +func (ltc *loadTupContent) WriteSubtitle(w io.Writer, totalVideoDurationSecs float64) error { - SRT file format (example from Wikipedia): + w.Write([]byte(`[Script Info] +; Script generated by loadtup-dl +ScriptType: v4.00+ +Collisions: Normal +Timer: 100,0000 +Video Aspect Ratio: 0 + +[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,18,&H00FFFFFF,&H00FFFFFF,&H00000008,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0 +Style: TLNote, Arial,12,&H00FFFFFF,&H00FFFFFF,&H00000008,&H80000008,-1,0,0,0,100,100,0.00,0.00,1,1.00,2.00,2,30,30,30,0 - 1 - 00:02:17,440 --> 00:02:20,375 - Senator, we're making - our final approach into Coruscant. +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +`)) - 2 - 00:02:20,476 --> 00:02:22,501 - Very good, Lieutenant. - */ + type entry struct { + startTime float64 + entry string + } + entries := make([]entry, 0, len(ltc.Caps)+len(ltc.Scrcaps)) - ctr := 1 for i := 0; i < len(ltc.Caps); i += 1 { if ltc.Caps[i] == "" { // Don't show anything continue } - start := secs_to_srt_time(ltc.Secs[i]) + start := secs_to_ass_time(ltc.Secs[i]) var end string if i < len(ltc.Caps)-1 { - end = secs_to_srt_time(ltc.Secs[i+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_srt_time(totalVideoDurationSecs) + end = secs_to_ass_time(totalVideoDurationSecs) } - fmt.Fprintf(w, "%d\n%s --> %s\n%s\n\n", - ctr, start, end, ltc.Caps[i]) - // We emitted a message, increase the counter - ctr += 1 + // 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,,{\\a2}%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,,{\\a6}%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