From e05e56bcb52e1ec5ce42ccd3e91dfd40ea47513a Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Wed, 9 Oct 2019 23:50:09 -0700 Subject: [PATCH] Reporting enhancements --- ptulsconv/__main__.py | 43 +++++++++++++++++++++++++----- ptulsconv/commands.py | 22 ++++++++++++---- ptulsconv/reporting.py | 51 ++++++++++++++++++++++++++++++++++++ ptulsconv/transformations.py | 42 ++++++++++++++++------------- 4 files changed, 129 insertions(+), 29 deletions(-) create mode 100644 ptulsconv/reporting.py diff --git a/ptulsconv/__main__.py b/ptulsconv/__main__.py index 8a23fe4..951f2a5 100644 --- a/ptulsconv/__main__.py +++ b/ptulsconv/__main__.py @@ -1,16 +1,18 @@ from ptulsconv.commands import convert, dump_field_map +from ptulsconv import __version__, __author__ from optparse import OptionParser +from .reporting import print_status_style, print_banner_style, print_section_header_style, print_fatal_error +import datetime import sys - def main(): parser = OptionParser() parser.usage = "ptulsconv TEXT_EXPORT.txt" parser.add_option('-i', dest='in_time', help="Don't output events occurring before this timecode, and offset" " all events relative to this timecode.", metavar='TC') parser.add_option('-o', dest='out_time', help="Don't output events occurring after this timecode.", metavar='TC') - parser.add_option('-P', '--progress', default=False, action='store_true', dest='show_progress', - help='Show progress bar.') + # parser.add_option('-P', '--progress', default=False, action='store_true', dest='show_progress', + # help='Show progress bar.') parser.add_option('-m', '--include-muted', default=False, action='store_true', dest='include_muted', help='Read muted clips.') @@ -20,17 +22,46 @@ def main(): (options, args) = parser.parse_args(sys.argv) + print_banner_style("ptulsconv %s (c) 2019 %s. All rights reserved." % (__version__, __author__)) + + print_section_header_style("Startup") + print_status_style("This run started %s" % (datetime.datetime.now().isoformat() ) ) + if options.show_tags: dump_field_map('ADR') sys.exit(0) if len(args) < 2: - print("Error: No input file", file=sys.stderr) + print_fatal_error("Error: No input file") parser.print_help(sys.stderr) sys.exit(22) - convert(input_file=args[1], start=options.in_time, end=options.out_time, include_muted=options.include_muted, - progress=options.show_progress, output=sys.stdout) + print_status_style("Input file is %s" % (args[1])) + if options.in_time: + print_status_style("Start at time %s" % (options.in_time)) + else: + print_status_style("No start time given.") + + if options.out_time: + print_status_style("End at time %s." % (options.out_time)) + else: + print_status_style("No end time given.") + + if options.include_muted: + print_status_style("Muted regions are included.") + else: + print_status_style("Muted regions are ignored.") + + try: + convert(input_file=args[1], start=options.in_time, end=options.out_time, + include_muted=options.include_muted, + progress=False, output=sys.stdout, log_output=sys.stderr) + except FileNotFoundError as e: + print_fatal_error("Error trying to read input file") + raise e + except Exception as e: + print_fatal_error("Error trying to convert file") + raise e if __name__ == "__main__": diff --git a/ptulsconv/commands.py b/ptulsconv/commands.py index 9333ebe..e35706c 100644 --- a/ptulsconv/commands.py +++ b/ptulsconv/commands.py @@ -5,6 +5,8 @@ import sys from xml.etree.ElementTree import TreeBuilder, tostring import ptulsconv +from .reporting import print_section_header_style, print_status_style + # field_map maps tags in the text export to fields in FMPXMLRESULT # - tuple field 0 is a list of tags, the first tag with contents will be used as source # - tuple field 1 is the field in FMPXMLRESULT @@ -107,17 +109,27 @@ def dump_field_map(field_map_name, output=sys.stdout): output.write("# %-24s-> %-20s | %-8s| %-7i\n" % (tag[:24], field[1][:20], field[2].__name__, n+1 )) -def convert(input_file, output_format='fmpxml', start=None, end=None, progress=False, include_muted=False, - output=sys.stdout): +def convert(input_file, output_format='fmpxml', start=None, end=None, + progress=False, include_muted=False, + output=sys.stdout, log_output=sys.stderr): + with open(input_file, 'r') as file: + print_section_header_style('Parsing') ast = ptulsconv.protools_text_export_grammar.parse(file.read()) dict_parser = ptulsconv.DictionaryParserVisitor() parsed = dict_parser.visit(ast) - tcxform = ptulsconv.transformations.TimecodeInterpreter() - tagxform = ptulsconv.transformations.TagInterpreter(show_progress=progress, ignore_muted=(not include_muted)) + print_status_style('Session title: %s' % parsed['header']['session_name']) + print_status_style('Session timecode format: %f' % parsed['header']['timecode_format']) + print_status_style('Fount %i tracks' % len(parsed['tracks'])) + print_status_style('Found %i markers' % len(parsed['markers'])) - parsed = tagxform.transform(tcxform.transform(parsed)) + tcxform = ptulsconv.transformations.TimecodeInterpreter() + tagxform = ptulsconv.transformations.TagInterpreter(show_progress=progress, ignore_muted=(not include_muted), + log_output=log_output) + + parsed = tcxform.transform(parsed) + parsed = tagxform.transform(parsed) if start is not None and end is not None: start_fs = tcxform.convert_time(start, diff --git a/ptulsconv/reporting.py b/ptulsconv/reporting.py new file mode 100644 index 0000000..40488f7 --- /dev/null +++ b/ptulsconv/reporting.py @@ -0,0 +1,51 @@ +import sys + +def print_banner_style(str): + if sys.stderr.isatty(): + sys.stderr.write("\n\033[1m%s\033[0m\n\n" % str) + else: + sys.stderr.write("\n%s\n\n" % str) + +def print_section_header_style(str): + if sys.stderr.isatty(): + sys.stderr.write("\n\033[4m%s\033[0m\n\n" % str) + else: + sys.stderr.write("%s\n\n" % str) + +def print_status_style(str): + if sys.stderr.isatty(): + sys.stderr.write("\033[3m - %s\033[0m\n" % str) + else: + sys.stderr.write(" - %s\n" % str) + +def print_advisory_tagging_error(failed_string, position, parent_track_name=None, clip_time=None): + if sys.stderr.isatty(): + sys.stderr.write("\n") + sys.stderr.write(" ! \033[33;1mTagging error: \033[0m") + ok_string = failed_string[:position] + not_ok_string = failed_string[position:] + sys.stderr.write("\033[102;30;1m%s\033[101m%s\033[0m\n" % (ok_string, not_ok_string)) + + if parent_track_name is not None: + sys.stderr.write(" ! > On track \"%s\"\n" % (parent_track_name)) + + if clip_time is not None: + sys.stderr.write(" ! > In clip name at %s\n" % (clip_time)) + else: + sys.stderr.write("\n") + sys.stderr.write(" ! Tagging error: \"%s\"\n" % failed_string) + sys.stderr.write(" ! %s _______________⬆\n" % (" " * position)) + + if parent_track_name is not None: + sys.stderr.write(" ! > On track \"%s\"\n" % (parent_track_name)) + + if clip_time is not None: + sys.stderr.write(" ! > In clip name at %s\n" % (clip_time)) + + sys.stderr.write("\n") + +def print_fatal_error(str): + if sys.stderr.isatty(): + sys.stderr.write("\n\033[5;31;1m*** %s ***\033[0m\n" % str) + else: + sys.stderr.write("\n%s\n" % str) \ No newline at end of file diff --git a/ptulsconv/transformations.py b/ptulsconv/transformations.py index ccf8a4a..827aaac 100644 --- a/ptulsconv/transformations.py +++ b/ptulsconv/transformations.py @@ -3,6 +3,7 @@ from parsimonious import Grammar, NodeVisitor from parsimonious.exceptions import IncompleteParseError import math import sys +from .reporting import print_advisory_tagging_error, print_section_header_style, print_status_style from tqdm import tqdm @@ -17,19 +18,27 @@ class TimecodeInterpreter(Transformation): self.apply_session_start = False def transform(self, input_dict: dict) -> dict: + print_section_header_style('Converting Timecodes') + retval = super().transform(input_dict) rate = input_dict['header']['timecode_format'] start_tc = self.convert_time(input_dict['header']['start_timecode'], rate, drop_frame=input_dict['header']['timecode_drop_frame']) retval['header']['start_timecode_decoded'] = start_tc + print_status_style('Converted start timecode.') + retval['tracks'] = self.convert_tracks(input_dict['tracks'], timecode_rate=rate, drop_frame=retval['header']['timecode_drop_frame']) + print_status_style('Converted clip timecodes for %i tracks.' % len(retval['tracks'])) + for marker in retval['markers']: marker['location_decoded'] = self.convert_time(marker['location'], rate, drop_frame=retval['header']['timecode_drop_frame']) + print_status_style('Converted %i markers.' % len(retval['markers'])) + return retval def convert_tracks(self, tracks, timecode_rate, drop_frame): @@ -121,16 +130,19 @@ class TagInterpreter(Transformation): def generic_visit(self, node, visited_children): return visited_children or node - def __init__(self, ignore_muted=True, show_progress=False): + def __init__(self, ignore_muted=True, show_progress=False, log_output=sys.stderr): self.visitor = TagInterpreter.TagListVisitor() self.ignore_muted = ignore_muted self.show_progress = show_progress + self.log_output = log_output def transform(self, input_dict: dict) -> dict: transformed = list() timespan_rules = list() - title_tags = self.parse_tags(input_dict['header']['session_name'], "") + print_section_header_style('Parsing Tags') + + title_tags = self.parse_tags(input_dict['header']['session_name']) markers = sorted(input_dict['markers'], key=lambda m: m['location_decoded']['frame_count']) if self.show_progress: @@ -142,8 +154,8 @@ class TagInterpreter(Transformation): if 'Muted' in track['state'] and self.ignore_muted: continue - track_tags = self.parse_tags(track['name'], "" % (track['name'])) - comment_tags = self.parse_tags(track['comments'], "" % (track['name'])) + track_tags = self.parse_tags(track['name'], parent_track_name=track['name']) + comment_tags = self.parse_tags(track['comments'], parent_track_name=track['name']) track_context_tags = track_tags['tags'] track_context_tags.update(comment_tags['tags']) @@ -151,8 +163,7 @@ class TagInterpreter(Transformation): if clip['state'] == 'Muted' and self.ignore_muted: continue - clip_tags = self.parse_tags(clip['clip_name'], - "" % (track['name'], clip['event'], clip['start_time'])) + clip_tags = self.parse_tags(clip['clip_name'], parent_track_name=track['name'], clip_time=clip['start_time']) clip_start = clip['start_time_decoded']['frame_count'] if clip_tags['mode'] == 'Normal': event = dict() @@ -189,6 +200,7 @@ class TagInterpreter(Transformation): tags=clip_tags['tags']) timespan_rules.append(rule) + print_status_style('Processed %i clips' % len(transformed)) return dict(header=input_dict['header'], events=transformed) def effective_timespan_tags_at_time(_, rules, time) -> dict: @@ -204,8 +216,8 @@ class TagInterpreter(Transformation): retval = dict() for marker in markers: - marker_name_tags = self.parse_tags(marker['name'], "Marker %i" % (marker['number'])) - marker_comment_tags = self.parse_tags(marker['comments'], "Marker %i" % (marker['number'])) + marker_name_tags = self.parse_tags(marker['name'], marker_index=marker['number']) + marker_comment_tags = self.parse_tags(marker['comments'], marker_index=marker['number']) effective_tags = marker_name_tags['tags'] effective_tags.update(marker_comment_tags['tags']) @@ -215,26 +227,20 @@ class TagInterpreter(Transformation): break return retval - def report(self, mesg, *args): - print(mesg % ( args) , file=sys.stderr) - sys.stderr.write("\033[F") - sys.stderr.write("\033[K") - - def parse_tags(self, source, context_str=None): + def parse_tags(self, source, parent_track_name=None, clip_time=None, marker_index=None): try: parse_tree = self.tag_grammar.parse(source) return self.visitor.visit(parse_tree) except IncompleteParseError as e: - if context_str is not None: - self.report("Error reading tags in: ") + print_advisory_tagging_error(failed_string=source, + parent_track_name=parent_track_name, + clip_time=clip_time, position=e.pos) trimmed_source = source[:e.pos] parse_tree = self.tag_grammar.parse(trimmed_source) return self.visitor.visit(parse_tree) - - class SubclipOfSequence(Transformation): def __init__(self, start, end):