From bfe729e653ca78a412a0808ccae17ea56b038337 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Sat, 30 Jan 2021 20:50:24 +1100 Subject: [PATCH] wip: --- cut_review.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++ list_races.py | 72 ++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 cut_review.py create mode 100644 list_races.py diff --git a/cut_review.py b/cut_review.py new file mode 100644 index 0000000..9cd1ef6 --- /dev/null +++ b/cut_review.py @@ -0,0 +1,140 @@ + +""" +Tools for reviewing races + +Database info: + +matches maps to multiple races via match_races (on match_id) +matches links to racers and cawmentator: + matches.racer_{1,2}_id + matches.cawmentator_id +races contains start time: + races.timestamp +races maps to multiple runs via race_runs +race_runs contains time for each racer + race_runs.time: centiseconds + race_runs.rank: 1 for fastest time + +""" + +import datetime +import logging +import os +import subprocess +from getpass import getpass + +import argh +import mysql.connector + +from common.segments import get_best_segments, full_cut_segments + + + +def ts(dt): + return dt.strftime("%FT%T") + + +class NoSegments(Exception): + pass + + +def cut_to_file(filename, base_dir, stream, start, end, variant='source', frame_counter=False): + logging.info("Cutting {}".format(filename)) + segments = get_best_segments( + os.path.join(base_dir, stream, variant).lower(), + start, end, + ) + if None in segments: + logging.warning("Cutting {} ({} to {}) but it contains holes".format(filename, ts(start), ts(end))) + if not segments or set(segments) == {None}: + raise NoSegments("Can't cut {} ({} to {}): No segments".format(filename, ts(start), ts(end))) + filter_args = ["-vf", "scale=-1:720", "-timecode", "00:00:00.00"] + if frame_counter: + filter_args += [ + "-vf", "drawtext=" + "fontfile=DejaVuSansMono.ttf" + ":fontcolor=white" + ":text='%{e\:t}'" + ":x=(w-tw)/2" + ":y=h-(2*lh)", + ] + encoding_args = ['-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '0', '-f', 'mp4'] + with open(filename, 'w') as f: + for chunk in full_cut_segments(segments, start, end, filter_args + encoding_args): + f.write(chunk) + + +def main(match_id, race_number, host='condor.live', user='necrobot-read', password=None, database='condor_x2', base_dir='/srv/wubloader', output_dir='/tmp'): + logging.basicConfig(level=logging.INFO) + + match_id = int(match_id) + race_number = int(race_number) + + if password is None: + password = getpass("Password? ") + conn = mysql.connector.connect( + host=host, user=user, password=password, database=database, + ) + + cur = conn.cursor() + cur.execute(""" + SELECT + match_info.racer_1_name as racer_1, + match_info.racer_2_name as racer_2, + races.timestamp as start, + race_runs.time as duration + FROM match_info + JOIN match_races ON (match_info.match_id = match_races.match_id) + JOIN races ON (match_races.race_id = races.race_id) + JOIN race_runs ON (races.race_id = race_runs.race_id) + WHERE match_info.completed AND race_runs.rank = 1 + AND match_info.match_id = %(match_id)s + AND match_races.race_number = %(race_number)s + """, {'match_id': match_id, 'race_number': race_number}) + + + data = cur.fetchall() + data = [ + [item.encode('utf-8') if isinstance(item, unicode) else item for item in row] + for row in data + ] + + if not data: + raise Exception("No such race") + assert len(data) == 1, repr(data) + + (racer1, racer2, start, duration), = data + end = start + datetime.timedelta(seconds=duration/100.) + + finish_paths = [] + + for racer in (racer1, racer2): + start_path = os.path.join(output_dir, "start-{}.mp4".format(racer)) + cut_to_file(start_path, base_dir, racer, start, start + datetime.timedelta(seconds=5), frame_counter=True) + print "Cut to {}".format(start_path) + frame_offset = int(raw_input("What frame of this video do we start at? ")) + time_offset = datetime.timedelta(seconds=frame_offset/60.) + + # start each racer's finish video at TIME_OFFSET later, so they are the same + # time since their actual start. + finish_start = end - datetime.timedelta(seconds=7) + time_offset + finish_start = start - datetime.timedelta(seconds=1) + time_offset # TEST + finish_path = os.path.join(output_dir, "finish-{}.mp4".format(racer)) + finish_paths.append(finish_path) + cut_to_file(finish_path, base_dir, racer, finish_start, finish_start + datetime.timedelta(seconds=5)) + + output_path = os.path.join(output_dir, "result.mp4") + args = ['ffmpeg'] + for path in finish_paths: + args += ['-i', path] + args += [ + '-filter_complex', 'hstack', + '-y', output_path, + ] + + subprocess.check_call(args) + print "Review cut to file {}".format(output_path) + + +if __name__ == '__main__': + argh.dispatch_command(main) diff --git a/list_races.py b/list_races.py new file mode 100644 index 0000000..53e9946 --- /dev/null +++ b/list_races.py @@ -0,0 +1,72 @@ + +""" +Attempts to cut every race for a league from local segments. + +Database info: + +matches maps to multiple races via match_races (on match_id) +matches links to racers and cawmentator: + matches.racer_{1,2}_id + matches.cawmentator_id +races contains start time: + races.timestamp +races maps to multiple runs via race_runs +race_runs contains time for each racer + race_runs.time: centiseconds + race_runs.rank: 1 for fastest time + +""" + +import datetime +import logging +import os +import sys +from getpass import getpass + +import argh +import mysql.connector + +INFO_QUERY = """ + SELECT + match_info.racer_1_name as racer_1, + match_info.racer_2_name as racer_2, + match_info.match_id as match_id, + match_info.completed as completed + FROM match_info +""" + + +def ts(dt): + return dt.strftime("%FT%T") + + +def main(find1, find2, host='condor.live', user='necrobot-read', password=None, database='condor_x2'): + logging.basicConfig(level=logging.INFO) + + if password is None: + password = getpass("Password? ") + conn = mysql.connector.connect( + host=host, user=user, password=password, database=database, + ) + + cur = conn.cursor() + cur.execute(INFO_QUERY) + + data = cur.fetchall() + data = [ + [item.encode('utf-8') if isinstance(item, unicode) else item for item in row] + for row in data + ] + + logging.info("Got info on {} races".format(len(data))) + + find = [(find1.lower(), find2.lower())] + find.append(find[0][::-1]) + + for racer1, racer2, match_id, completed in data: + if (racer1.lower(), racer2.lower()) in find: + print "{}: {} vs {}, complete = {}".format(match_id, racer1, racer2, completed) + + +if __name__ == '__main__': + argh.dispatch_command(main)