From f5fbe5d42e0520e2580eba4d626c7d517e8b29df Mon Sep 17 00:00:00 2001 From: Michael Murtaugh Date: Tue, 19 May 2020 13:54:51 +0200 Subject: [PATCH] player --- Makefile | 2 +- player/ogginfo-to-srt.py | 157 +++++++++++++++++++++++++++++++++++++++ player/speaklines.sh | 11 +++ 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100755 player/ogginfo-to-srt.py create mode 100644 player/speaklines.sh diff --git a/Makefile b/Makefile index 421aa37..e22fb01 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,6 @@ between_systems.svg: between_systems.dot dot $< -Tsvg -o$@ liquidsoap/brownnoise.mp3: - sox -c2 -r44100 -n -t wav - synth 10.0 brownnoise vol 1.0 > liquidsoap/brownnoise.wav + sox -c2 -r44100 -n -t wav - synth 30.0 brownnoise vol 1.0 > liquidsoap/brownnoise.wav ffmpeg -i liquidsoap/brownnoise.wav liquidsoap/brownnoise.mp3 rm liquidsoap/brownnoise.wav diff --git a/player/ogginfo-to-srt.py b/player/ogginfo-to-srt.py new file mode 100755 index 0000000..c5ce108 --- /dev/null +++ b/player/ogginfo-to-srt.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +import re, sys, re, json, math + +""" +Usage: + + ogginfo some.ogg | python3 ogginfo-to-srt.py > some.json + + +EXAMPLE OF OGGINFO OUTPUT + +User comments section follows... + title=one + ENCODER=Liquidsoap/1.3.3 (Unix; OCaml 4.05.0) +Vorbis stream 1: + Total data length: 2882 bytes + Playback length: 0m:00.704s + Average bitrate: 32.731445 kb/s +""" + + +################################ +# PASS 1: Read the structure + +def parse_ogginfo_output(f): + state = "" + output = {} + output['streams'] = streams = [] + time = 0.0 + + for line in f: + # for debugging + # print (line.rstrip()) + + if line.startswith("User comments section follows..."): + print ("COMMENTS", file=sys.stderr) + state = "comments" + stream = {} + stream['comments'] = comments = {} + streams.append(stream) + elif line.startswith("Vorbis stream"): + print ("STREAM", file=sys.stderr) + state = "stream" + if 'stream' in stream: + stream = {} + stream['stream'] = streaminfo = {} + streams.append(stream) + else: + stream['stream'] = streaminfo = {} + + elif line.startswith("Logical stream"): + print ("EXIT STATE", file=sys.stderr) + state = "" + else: + if state == "comments": + name, value = line.strip().split("=", 1) + name = name.strip() + value = value.strip() + print ("comment", (name, value), file=sys.stderr) + comments[name] = value + elif state == "stream": + name, value = line.strip().split(":", 1) + name = name.strip() + value = value.strip() + print ("stream", (name, value), file=sys.stderr) + streaminfo[name] = value + + return output + +################################ +# PASS 2: Add start time, duration, and timecodes + +def parse_ogginfo_time (t): + m = re.search(r"(\d+)m\:(\d+\.\d+)s", t) + if m: + m, s = m.groups() + m = int(m) + s = float(s) + return (m*60)+s + +def timecode_fromsecs(rawsecs, fract=True, alwaysfract=True, fractdelim='.', alwayshours=False): + # returns a string in HH:MM:SS[.xxx] notation + # if fract is True, uses .xxx if either necessary (non-zero) + # OR alwaysfract is True + hours = math.floor(rawsecs / 3600) + rawsecs -= hours * 3600 + mins = math.floor(rawsecs / 60) + rawsecs -= mins * 60 + if fract: + secs = math.floor(rawsecs) + rawsecs -= secs + if (rawsecs > 0 or alwaysfract): + fract = "%.03f" % rawsecs + if hours or alwayshours: + return "%02d:%02d:%02d%s%s" % (hours, mins, secs, fractdelim, \ + fract[2:]) + else: + return "%02d:%02d%s%s" % (mins, secs, fractdelim, fract[2:]) + else: + if hours or alwayshours: + return "%02d:%02d:%02d" % (hours, mins, secs) + else: + return "%02d:%02d" % (mins, secs) + +def add_timing (output): + t = 0.0 + for d in output['streams']: + duration_seconds = parse_ogginfo_time(d['stream']['Playback length']) + d['start_time'] = t + d['start_time_timecode'] = timecode_fromsecs(t) + d['duration'] = duration_seconds + d['duration_timecode'] = timecode_fromsecs(duration_seconds) + t += duration_seconds + +def nextiter (items): + prev_item = None + for x in items: + if (prev_item): + yield prev_item, x + prev_item = x + yield x, None + +def print_srt(data, last_subtitle_duration=5.0, file=None): + for cur, nxt in nextiter(data['streams']): + if nxt: + print ("{} --> {}".format(cur['start_time_timecode'], nxt['start_time_timecode']), file=file) + print (json.dumps(cur['comments']), file=file) + print (file=file) + else: + end_timecode = timecode_fromsecs(cur['start_time'] + last_subtitle_duration) + print ("{} --> {}".format(cur['start_time_timecode'], end_timecode), file=file) + print (json.dumps(cur['comments']), file=file) + print (file=file) + +if __name__ == "__main__": + import argparse + + ap = argparse.ArgumentParser("") + ap.add_argument("--input", type=argparse.FileType("r"), default=sys.stdin) + ap.add_argument("--output", type=argparse.FileType("w"), default=sys.stdout) + ap.add_argument("--last-subtitle-duration", type=float, default=5.0) + ap.add_argument("--json", default=False, action="store_true") + ap.add_argument("--vtt", default=False, action="store_true") + args = ap.parse_args() + + output = parse_ogginfo_output(args.input) + add_timing(output) + if args.json: + print (json.dumps(output, indent=2), file=args.output) + else: + if args.vtt: + print ("""WEBVTT +Kind: captions +Language: en +""", file=args.output) + print_srt(output, last_subtitle_duration=args.last_subtitle_duration, file=args.output) + diff --git a/player/speaklines.sh b/player/speaklines.sh new file mode 100644 index 0000000..123e5d3 --- /dev/null +++ b/player/speaklines.sh @@ -0,0 +1,11 @@ +lineno=0 +while read line +do +lineno=$((lineno+1)) +espeak "$line" +filename=`printf "speech%04d" $lineno` +espeak "$line" -w "$filename.wav" +ffmpeg -i "$filename.wav" -metadata title="$line" -y $filename.mp3 > /dev/null 2> /dev/null +# rm "$filename.wav" +done +