Refactoring reports to use docparser

This commit is contained in:
Jamie Hardt
2021-06-03 10:19:33 -07:00
parent caf4317b76
commit b46fc85b16
11 changed files with 219 additions and 206 deletions

View File

@@ -6,6 +6,7 @@
<w>futura</w> <w>futura</w>
<w>ptulsconv</w> <w>ptulsconv</w>
<w>retval</w> <w>retval</w>
<w>smpte</w>
<w>timecode</w> <w>timecode</w>
<w>timespan</w> <w>timespan</w>
</words> </words>

View File

@@ -1,4 +1,4 @@
from ptulsconv.commands import convert, dump_field_map, raw_output from ptulsconv.commands import convert, dump_field_map
from ptulsconv import __name__, __version__, __author__ from ptulsconv import __name__, __version__, __author__
from optparse import OptionParser, OptionGroup from optparse import OptionParser, OptionGroup
from .xml.common import dump_xform_options from .xml.common import dump_xform_options
@@ -60,17 +60,13 @@ def main():
(options, args) = parser.parse_args(sys.argv) (options, args) = parser.parse_args(sys.argv)
if options.output_format == 'raw':
raw_output(args[1])
exit(0)
print_banner_style("%s %s (c) 2020 %s. All rights reserved." % (__name__, __version__, __author__)) print_banner_style("%s %s (c) 2020 %s. All rights reserved." % (__name__, __version__, __author__))
print_section_header_style("Startup") print_section_header_style("Startup")
print_status_style("This run started %s" % (datetime.datetime.now().isoformat())) print_status_style("This run started %s" % (datetime.datetime.now().isoformat()))
if options.show_tags: if options.show_tags:
dump_field_map('ADR') dump_field_map()
sys.exit(0) sys.exit(0)
if options.show_transforms: if options.show_transforms:
@@ -84,16 +80,6 @@ def main():
print_status_style("Input file is %s" % (args[1])) 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: if options.include_muted:
print_status_style("Muted regions are included.") print_status_style("Muted regions are included.")
else: else:
@@ -105,11 +91,8 @@ def main():
output_format = 'fmpxml' output_format = 'fmpxml'
convert(input_file=args[1], output_format=output_format, convert(input_file=args[1], output_format=output_format,
#start=options.in_time,
#end=options.out_time,
include_muted=options.include_muted, include_muted=options.include_muted,
xsl=options.xslt, xsl=options.xslt,
#select_reel=options.select_reel,
progress=False, output=sys.stdout, log_output=sys.stderr, progress=False, output=sys.stdout, log_output=sys.stderr,
warnings=options.warnings) warnings=options.warnings)
except FileNotFoundError as e: except FileNotFoundError as e:

View File

@@ -1,6 +1,22 @@
from fractions import Fraction from fractions import Fraction
import re import re
import math import math
from collections import namedtuple
class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps drop_frame")):
def smpte_to_seconds(self, smpte: str) -> Fraction:
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
return frame_count * self.frame_duration
def seconds_to_smpte(self, seconds: Fraction) -> str:
frame_count = int(seconds / self.frame_duration)
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame)
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> int: def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> int:
@@ -39,14 +55,11 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
dropped_frames = frames_dropped_per_inst * inst_count dropped_frames = frames_dropped_per_inst * inst_count
frames = raw_frames - dropped_frames frames = raw_frames - dropped_frames
# if include_fractional:
# return frames, frac
# else:
return frames return frames
def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False, def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False,
fractional_frame: float = None): fractional_frame: float = None) -> str:
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60] assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
assert fractional_frame is None or fractional_frame < 1.0 assert fractional_frame is None or fractional_frame < 1.0
@@ -72,24 +85,18 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff) return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
def footage_to_frame_count(footage_string, include_fractional=False): def footage_to_frame_count(footage_string):
m = re.search("(\d+)\+(\d+)(\.\d+)?", footage_string) m = re.search("(\d+)\+(\d+)(\.\d+)?", footage_string)
feet, frm, frac = m.groups() feet, frm, frac = m.groups()
feet, frm, frac = int(feet), int(frm), float(frac or 0.0) feet, frm, frac = int(feet), int(frm), float(frac or 0.0)
frames = feet * 16 + frm frames = feet * 16 + frm
if include_fractional: return frames
return frames, frac
else:
return frames
def frame_count_to_footage(frame_count, fractional_frames=None): def frame_count_to_footage(frame_count):
assert fractional_frames is None or fractional_frames < 1.0
feet, frm = divmod(frame_count, 16) feet, frm = divmod(frame_count, 16)
return "%i+%02i" % (feet, frm)
if fractional_frames is None:
return "%i+%02i" % (feet, frm)
else:
return "%i+%02i%s" % (feet, frm, ("%.3f" % fractional_frames)[1:])

View File

@@ -11,6 +11,8 @@ from .validations import *
from ptulsconv.docparser import parse_document from ptulsconv.docparser import parse_document
from ptulsconv.docparser.tag_compiler import TagCompiler from ptulsconv.docparser.tag_compiler import TagCompiler
from ptulsconv.broadcast_timecode import TimecodeFormat
from fractions import Fraction
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
from ptulsconv.pdf.line_count import output_report as output_line_count from ptulsconv.pdf.line_count import output_report as output_line_count
@@ -18,10 +20,18 @@ from ptulsconv.pdf.talent_sides import output_report as output_talent_sides
from ptulsconv.pdf.summary_log import output_report as output_summary from ptulsconv.pdf.summary_log import output_report as output_summary
from json import JSONEncoder from json import JSONEncoder
class MyEncoder(JSONEncoder): class MyEncoder(JSONEncoder):
def default(self, o): force_denominator: Optional[int]
def default(self, o):
if isinstance(o, Fraction):
return dict(numerator=o.numerator, denominator=o.denominator)
else:
return o.__dict__ return o.__dict__
def dump_csv(events, output=sys.stdout): def dump_csv(events, output=sys.stdout):
keys = set() keys = set()
for e in events: for e in events:
@@ -69,6 +79,7 @@ def output_adr_csv(lines):
with open(outfile_name, mode='w', newline='') as outfile: with open(outfile_name, mode='w', newline='') as outfile:
dump_keyed_csv(these_lines, adr_keys, outfile) dump_keyed_csv(these_lines, adr_keys, outfile)
def output_avid_markers(lines): def output_avid_markers(lines):
reels = set([ln['Reel'] for ln in lines if 'Reel' in ln.keys()]) reels = set([ln['Reel'] for ln in lines if 'Reel' in ln.keys()])
@@ -76,44 +87,43 @@ def output_avid_markers(lines):
pass pass
def create_adr_reports(parsed): def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat):
lines = [e for e in parsed['events'] if 'ADR' in e.keys()]
print_section_header_style("Creating PDF Reports") print_section_header_style("Creating PDF Reports")
print_status_style("Creating ADR Report") print_status_style("Creating ADR Report")
output_summary(lines) output_summary(lines, tc_display_format=tc_display_format)
print_status_style("Creating Line Count") # print_status_style("Creating Line Count")
output_line_count(lines) # output_line_count(lines)
#
print_status_style("Creating Supervisor Logs directory and reports") # print_status_style("Creating Supervisor Logs directory and reports")
os.makedirs("Supervisor Logs", exist_ok=True) # os.makedirs("Supervisor Logs", exist_ok=True)
os.chdir("Supervisor Logs") # os.chdir("Supervisor Logs")
output_supervisor_1pg(lines) # output_supervisor_1pg(lines)
os.chdir("..") # os.chdir("..")
#
print_status_style("Creating Director's Logs director and reports") # print_status_style("Creating Director's Logs director and reports")
os.makedirs("Director Logs", exist_ok=True) # os.makedirs("Director Logs", exist_ok=True)
os.chdir("Director Logs") # os.chdir("Director Logs")
output_summary(lines, by_character=True) # output_summary(lines, tc_display_format=tc_display_format, by_character=True)
os.chdir("..") # os.chdir("..")
#
print_status_style("Creating CSV outputs") # print_status_style("Creating CSV outputs")
os.makedirs("CSV", exist_ok=True) # os.makedirs("CSV", exist_ok=True)
os.chdir("CSV") # os.chdir("CSV")
output_adr_csv(lines) # output_adr_csv(lines)
os.chdir("..") # os.chdir("..")
#
print_status_style("Creating Avid Marker XML files") # print_status_style("Creating Avid Marker XML files")
os.makedirs("Avid Markers", exist_ok=True) # os.makedirs("Avid Markers", exist_ok=True)
os.chdir("Avid Markers") # os.chdir("Avid Markers")
output_avid_markers(lines) # output_avid_markers(lines)
os.chdir("..") # os.chdir("..")
#
print_status_style("Creating Scripts directory and reports") # print_status_style("Creating Scripts directory and reports")
os.makedirs("Talent Scripts", exist_ok=True) # os.makedirs("Talent Scripts", exist_ok=True)
os.chdir("Talent Scripts") # os.chdir("Talent Scripts")
output_talent_sides(lines) # output_talent_sides(lines)
def parse_text_export(file): def parse_text_export(file):
@@ -127,51 +137,44 @@ def parse_text_export(file):
return parsed return parsed
def raw_output(input_file, output=sys.stdout):
from .docparser.doc_parser_visitor import DocParserVisitor
from json import JSONEncoder
class DescriptorJSONEncoder(JSONEncoder):
def default(self, obj):
return obj.__dict__
with open(input_file, 'r') as file:
ast = ptulsconv.protools_text_export_grammar.parse(file.read())
visitor = DocParserVisitor()
parsed = visitor.visit(ast)
json.dump(parsed, output, cls=DescriptorJSONEncoder)
def convert(input_file, output_format='fmpxml', def convert(input_file, output_format='fmpxml',
progress=False, include_muted=False, xsl=None, progress=False, include_muted=False, xsl=None,
output=sys.stdout, log_output=sys.stderr, warnings=True): output=sys.stdout, log_output=sys.stderr, warnings=True):
session = parse_document(input_file) session = parse_document(input_file)
compiler = TagCompiler() session_tc_format = session.header.timecode_format
compiler.session = session
compiled_events = compiler.compile_events()
lines = list(map(ADRLine.from_event, compiled_events)) if output_format == 'raw':
output.write(MyEncoder().encode(session))
if warnings: else:
for warning in chain(validate_unique_field(lines, field='cue_number'),
validate_non_empty_field(lines, field='cue_number'),
validate_non_empty_field(lines, field='character_id'),
validate_non_empty_field(lines, field='title'),
validate_dependent_value(lines, key_field='character_id',
dependent_field='character_name'),
validate_dependent_value(lines, key_field='character_id',
dependent_field='actor_name')):
print_warning(warning.report_message())
if output_format == 'json': compiler = TagCompiler()
print(MyEncoder().encode(lines)) compiler.session = session
compiled_events = list(compiler.compile_events())
if output_format == 'json':
output.write(MyEncoder().encode(compiled_events))
else:
lines = list(map(ADRLine.from_event, compiled_events))
if warnings:
for warning in chain(validate_unique_field(lines, field='cue_number'),
validate_non_empty_field(lines, field='cue_number'),
validate_non_empty_field(lines, field='character_id'),
validate_non_empty_field(lines, field='title'),
validate_dependent_value(lines, key_field='character_id',
dependent_field='character_name'),
validate_dependent_value(lines, key_field='character_id',
dependent_field='actor_name')):
print_warning(warning.report_message())
if output_format == 'adr':
create_adr_reports(lines, tc_display_format=session_tc_format)
# elif output_format == 'csv': # elif output_format == 'csv':
# dump_csv(parsed['events']) # dump_csv(parsed['events'])
# #
# elif output_format == 'adr':
# create_adr_reports(parsed)
# elif output_format == 'fmpxml': # elif output_format == 'fmpxml':
# if xsl is None: # if xsl is None:

View File

@@ -1,6 +1,7 @@
from ptulsconv.docparser.tag_compiler import Event from ptulsconv.docparser.tag_compiler import Event
from typing import Optional from typing import Optional
from dataclasses import dataclass from dataclasses import dataclass
from fractions import Fraction
from ptulsconv.docparser.tag_mapping import TagMapping from ptulsconv.docparser.tag_mapping import TagMapping
@@ -13,8 +14,8 @@ class ADRLine:
scene: Optional[str] scene: Optional[str]
version: Optional[str] version: Optional[str]
reel: Optional[str] reel: Optional[str]
start: Optional[str] start: Optional[Fraction]
finish: Optional[str] finish: Optional[Fraction]
priority: Optional[int] priority: Optional[int]
cue_number: Optional[str] cue_number: Optional[str]
character_id: Optional[str] character_id: Optional[str]
@@ -101,6 +102,8 @@ class ADRLine:
new = cls() new = cls()
TagMapping.apply_rules(cls.tag_mapping, event.tags, TagMapping.apply_rules(cls.tag_mapping, event.tags,
event.clip_name, event.track_name, event.session_name, new) event.clip_name, event.track_name, event.session_name, new)
new.start = event.start
new.finish = event.finish
return new return new

View File

@@ -1,5 +1,5 @@
from fractions import Fraction from fractions import Fraction
from ptulsconv.broadcast_timecode import smpte_to_frame_count from ptulsconv.broadcast_timecode import TimecodeFormat
from typing import Tuple, List, Iterator from typing import Tuple, List, Iterator
@@ -30,7 +30,7 @@ class SessionDescriptor:
yield track, clip yield track, clip
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor", "TrackClipDescriptor", def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor", "TrackClipDescriptor",
Fraction, Fraction, Fraction]]: Fraction, Fraction, Fraction]]:
""" """
:return: A Generator that yields track, clip, start time, finish time, and timestamp :return: A Generator that yields track, clip, start time, finish time, and timestamp
""" """
@@ -48,7 +48,7 @@ class HeaderDescriptor:
sample_rate: float sample_rate: float
bit_depth: int bit_depth: int
start_timecode: str start_timecode: str
timecode_format: str timecode_fps: str
timecode_drop_frame: bool timecode_drop_frame: bool
count_audio_tracks: int count_audio_tracks: int
count_clips: int count_clips: int
@@ -59,18 +59,20 @@ class HeaderDescriptor:
self.sample_rate = kwargs['sample_rate'] self.sample_rate = kwargs['sample_rate']
self.bit_depth = kwargs['bit_depth'] self.bit_depth = kwargs['bit_depth']
self.start_timecode = kwargs['start_timecode'] self.start_timecode = kwargs['start_timecode']
self.timecode_format = kwargs['timecode_format'] self.timecode_fps = kwargs['timecode_format']
self.timecode_drop_frame = kwargs['timecode_drop_frame'] self.timecode_drop_frame = kwargs['timecode_drop_frame']
self.count_audio_tracks = kwargs['count_audio_tracks'] self.count_audio_tracks = kwargs['count_audio_tracks']
self.count_clips = kwargs['count_clips'] self.count_clips = kwargs['count_clips']
self.count_files = kwargs['count_files'] self.count_files = kwargs['count_files']
def convert_timecode(self, tc_string: str) -> Fraction: @property
frame_count = smpte_to_frame_count(tc_string, def timecode_format(self):
self.logical_fps, return TimecodeFormat(frame_duration=self.frame_duration,
self.timecode_drop_frame) logical_fps=self.logical_fps,
drop_frame=self.timecode_drop_frame)
return self.frame_duration * frame_count def convert_timecode(self, tc_string: str) -> Fraction:
return self.timecode_format.smpte_to_seconds(tc_string)
@property @property
def start_time(self) -> Fraction: def start_time(self) -> Fraction:
@@ -91,16 +93,16 @@ class HeaderDescriptor:
@property @property
def _get_tc_format_params(self) -> Tuple[int, Fraction]: def _get_tc_format_params(self) -> Tuple[int, Fraction]:
frame_rates = {"23.976": (24, Fraction(1001, 24_000)), frame_rates = {"23.976": (24, Fraction(1001, 24_000)),
"24": (24, Fraction(1, 24)), "24": (24, Fraction(1, 24)),
"25": (25, Fraction(1, 25)), "25": (25, Fraction(1, 25)),
"29.97": (30, Fraction(1001, 30_000)), "29.97": (30, Fraction(1001, 30_000)),
"30": (30, Fraction(1, 30)), "30": (30, Fraction(1, 30)),
"59.94": (60, Fraction(1001, 60_000)), "59.94": (60, Fraction(1001, 60_000)),
"60": (60, Fraction(1, 60)) "60": (60, Fraction(1, 60))
} }
if self.timecode_format in frame_rates.keys(): if self.timecode_fps in frame_rates.keys():
return frame_rates[self.timecode_format] return frame_rates[self.timecode_fps]
else: else:
raise ValueError("Unrecognized TC rate (%s)" % self.timecode_format) raise ValueError("Unrecognized TC rate (%s)" % self.timecode_format)

View File

@@ -1,16 +1,28 @@
from collections import namedtuple from collections import namedtuple
from fractions import Fraction from fractions import Fraction
from typing import Iterator, Tuple, Callable, Generator from typing import Iterator, Tuple, Callable, Generator, Dict
import ptulsconv.docparser.doc_entity as doc_entity import ptulsconv.docparser.doc_entity as doc_entity
from .tagged_string_parser_visitor import parse_tags, TagPreModes from .tagged_string_parser_visitor import parse_tags, TagPreModes
from dataclasses import dataclass
class Event(namedtuple('Event', 'clip_name track_name session_name tags start finish')):
pass @dataclass
class Event:
clip_name: str
track_name: str
session_name: str
tags: Dict[str, str]
start: Fraction
finish: Fraction
class TagCompiler: class TagCompiler:
Intermediate = namedtuple('Intermediate', 'track_content track_tags track_comment_tags '
'clip_content clip_tags clip_tag_mode start finish')
session: doc_entity.SessionDescriptor session: doc_entity.SessionDescriptor
def compile_events(self) -> Iterator[Event]: def compile_events(self) -> Iterator[Event]:
@@ -19,11 +31,12 @@ class TagCompiler:
step2 = self.collect_time_spans(step1) step2 = self.collect_time_spans(step1)
step3 = self.apply_tags(step2) step3 = self.apply_tags(step2)
for datum in step3: for datum in step3:
yield Event(*datum) yield Event(clip_name=datum[0], track_name=datum[1], session_name=datum[2],
tags=datum[3], start=datum[4], finish=datum[5])
def _marker_tags(self, at): def _marker_tags(self, at):
retval = dict() retval = dict()
applicable = [(m, t) for (m, t) in self.session.markers_timed() if t >= at] applicable = [(m, t) for (m, t) in self.session.markers_timed() if t <= at]
for marker, time in sorted(applicable, key=lambda x: x[1]): for marker, time in sorted(applicable, key=lambda x: x[1]):
retval.update(parse_tags(marker.comments).tag_dict) retval.update(parse_tags(marker.comments).tag_dict)
retval.update(parse_tags(marker.name).tag_dict) retval.update(parse_tags(marker.name).tag_dict)
@@ -44,9 +57,6 @@ class TagCompiler:
effective_tags.update(clip_tags) effective_tags.update(clip_tags)
return effective_tags return effective_tags
Intermediate = namedtuple('Intermediate', 'track_content track_tags track_comment_tags '
'clip_content clip_tags clip_tag_mode start finish')
def parse_data(self) -> Iterator[Intermediate]: def parse_data(self) -> Iterator[Intermediate]:
for track, clip, start, finish, _ in self.session.track_clips_timed(): for track, clip, start, finish, _ in self.session.track_clips_timed():

View File

@@ -8,6 +8,8 @@ from reportlab.platypus.frames import Frame
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.ttfonts import TTFont
from ptulsconv.docparser.adr_entity import ADRLine
# This is from https://code.activestate.com/recipes/576832/ for # This is from https://code.activestate.com/recipes/576832/ for
# generating page count messages # generating page count messages
@@ -50,7 +52,7 @@ class ADRDocTemplate(BaseDocTemplate):
BaseDocTemplate.build(self, flowables, filename, canvasmaker) BaseDocTemplate.build(self, flowables, filename, canvasmaker)
def make_doc_template(page_size, filename, document_title, record, document_header="", left_margin=0.5 * inch): def make_doc_template(page_size, filename, document_title, record: ADRLine, document_header="", left_margin=0.5 * inch):
right_margin = top_margin = bottom_margin = 0.5 * inch right_margin = top_margin = bottom_margin = 0.5 * inch
page_box = GRect(0., 0., page_size[0], page_size[1]) page_box = GRect(0., 0., page_size[0], page_size[1])
_, page_box = page_box.split_x(left_margin, direction='l') _, page_box = page_box.split_x(left_margin, direction='l')
@@ -70,7 +72,7 @@ def make_doc_template(page_size, filename, document_title, record, document_head
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc')) pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
doc = ADRDocTemplate(filename, doc = ADRDocTemplate(filename,
title=document_title, title=document_title,
author=record.get('Supervisor', ""), author=record.supervisor,
pagesize=page_size, pagesize=page_size,
leftMargin=left_margin, rightMargin=right_margin, leftMargin=left_margin, rightMargin=right_margin,
topMargin=top_margin, bottomMargin=bottom_margin) topMargin=top_margin, bottomMargin=bottom_margin)
@@ -91,11 +93,11 @@ def time_format(mins, zero_str=""):
return "%i:%02i" % (hh, mm) return "%i:%02i" % (hh, mm)
def draw_header_footer(a_canvas: ReportCanvas, title_box, doc_title_box, footer_box, record, doc_title=""): def draw_header_footer(a_canvas: ReportCanvas, title_box, doc_title_box, footer_box, record: ADRLine, doc_title=""):
(supervisor, client,), title = title_box.divide_y([16., 16., ]) (supervisor, client,), title = title_box.divide_y([16., 16., ])
title.draw_text_cell(a_canvas, record['Title'], "Futura", 18, inset_y=2., inset_x=5.) title.draw_text_cell(a_canvas, record.title, "Futura", 18, inset_y=2., inset_x=5.)
client.draw_text_cell(a_canvas, record.get('Client', ''), "Futura", 11, inset_y=2., inset_x=5.) client.draw_text_cell(a_canvas, record.client, "Futura", 11, inset_y=2., inset_x=5.)
a_canvas.saveState() a_canvas.saveState()
a_canvas.setLineWidth(0.5) a_canvas.setLineWidth(0.5)
@@ -114,12 +116,12 @@ def draw_header_footer(a_canvas: ReportCanvas, title_box, doc_title_box, footer_
doc_title_cell.draw_text_cell(a_canvas, doc_title, 'Futura', 14., inset_y=2.) doc_title_cell.draw_text_cell(a_canvas, doc_title, 'Futura', 14., inset_y=2.)
if 'Spot' in record.keys(): if record.spot is not None:
spotting_version_cell.draw_text_cell(a_canvas, record['Spot'], 'Futura', 12., inset_y=2.) spotting_version_cell.draw_text_cell(a_canvas, record.spot, 'Futura', 12., inset_y=2.)
a_canvas.setFont('Futura', 11.) a_canvas.setFont('Futura', 11.)
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y,
record.get('Supervisor', 'Supervisor: ________________')) record.supervisor or "")
class GRect: class GRect:
@@ -254,6 +256,9 @@ class GRect:
def draw_text_cell(self, a_canvas, text, font_name, font_size, def draw_text_cell(self, a_canvas, text, font_name, font_size,
vertical_align='t', force_baseline=None, inset_x=0., vertical_align='t', force_baseline=None, inset_x=0.,
inset_y=0., draw_baseline=False): inset_y=0., draw_baseline=False):
if text is None:
return
a_canvas.saveState() a_canvas.saveState()
inset_rect = self.inset_xy(inset_x, inset_y) inset_rect = self.inset_xy(inset_x, inset_y)

View File

@@ -6,40 +6,41 @@ from reportlab.lib.pagesizes import letter, portrait
from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
from typing import List
from ptulsconv.docparser.adr_entity import ADRLine
from ptulsconv.broadcast_timecode import TimecodeFormat
def build_aux_data_field(line): def build_aux_data_field(line: ADRLine):
entries = list() entries = list()
if 'Reason' in line.keys(): if line.reason is not None:
entries.append("Reason: " + line["Reason"]) entries.append("Reason: " + line.reason)
if 'Note' in line.keys(): if line.note is not None:
entries.append("Note: " + line["Note"]) entries.append("Note: " + line.note)
if 'Requested by' in line.keys(): if line.requested_by is not None:
entries.append("Requested by: " + line["Requested by"]) entries.append("Requested by: " + line.requested_by)
if 'Shot' in line.keys(): if line.shot is not None:
entries.append("Shot: " + line["Shot"]) entries.append("Shot: " + line.shot)
fg_color = 'white'
tag_field = "" tag_field = ""
for tag in line.keys(): if line.effort:
if line[tag] == tag and tag != 'ADR': bg_color = 'red'
fcolor = 'white' tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "EFF")
bcolor = 'black' elif line.tv:
if tag == 'ADLIB' or tag == 'TBW': bg_color = 'blue'
bcolor = 'darkmagenta' tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "TV")
elif tag == 'EFF': elif line.adlib:
bcolor = 'red' bg_color = 'purple'
elif tag == 'TV': tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "ADLIB")
bcolor = 'blue'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bcolor, fcolor, tag)
entries.append(tag_field) entries.append(tag_field)
return "<br />".join(entries) return "<br />".join(entries)
def build_story(lines): def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat):
story = list() story = list()
this_scene = None this_scene = None
@@ -56,20 +57,20 @@ def build_story(lines):
('LEFTPADDING', (0, 0), (0, 0), 0.0), ('LEFTPADDING', (0, 0), (0, 0), 0.0),
('BOTTOMPADDING', (0, 0), (-1, -1), 24.)] ('BOTTOMPADDING', (0, 0), (-1, -1), 24.)]
cue_number_field = "%s<br /><font fontSize=7>%s</font>" % (line['Cue Number'], line['Character Name']) cue_number_field = "%s<br /><font fontSize=7>%s</font>" % (line.cue_number, line.character_name)
time_data = time_format(line.get('Time Budget Mins', 0.)) time_data = time_format(line.time_budget_mins)
if 'Priority' in line.keys(): if line.priority is not None:
time_data = time_data + "<br />" + "P: " + int(line['Priority']) time_data = time_data + "<br />" + "P: " + line.priority
aux_data_field = build_aux_data_field(line) aux_data_field = build_aux_data_field(line)
tc_data = build_tc_data(line) tc_data = build_tc_data(line, tc_rate)
line_table_data = [[Paragraph(cue_number_field, line_style), line_table_data = [[Paragraph(cue_number_field, line_style),
Paragraph(tc_data, line_style), Paragraph(tc_data, line_style),
Paragraph(line['Line'], line_style), Paragraph(line.prompt, line_style),
Paragraph(time_data, line_style), Paragraph(time_data, line_style),
Paragraph(aux_data_field, line_style) Paragraph(aux_data_field, line_style)
]] ]]
@@ -78,8 +79,8 @@ def build_story(lines):
colWidths=[inch * 0.75, inch, inch * 3., 0.5 * inch, inch * 2.], colWidths=[inch * 0.75, inch, inch * 3., 0.5 * inch, inch * 2.],
style=table_style) style=table_style)
if line.get('Scene', "[No Scene]") != this_scene: if (line.scene or "[No Scene]") != this_scene:
this_scene = line.get('Scene', "[No Scene]") this_scene = line.scene or "[No Scene]"
story.append(KeepTogether([ story.append(KeepTogether([
Spacer(1., 0.25 * inch), Spacer(1., 0.25 * inch),
Paragraph("<u>" + this_scene + "</u>", scene_style), Paragraph("<u>" + this_scene + "</u>", scene_style),
@@ -91,52 +92,51 @@ def build_story(lines):
return story return story
def build_tc_data(line): def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
tc_data = line['PT.Clip.Start'] + "<br />" + line['PT.Clip.Finish'] tc_data = tc_format.seconds_to_smpte(line.start) + "<br />" + \
tc_format.seconds_to_smpte(line.finish)
third_line = [] third_line = []
if 'Reel' in line.keys(): if line.reel is not None:
if line['Reel'][0:1] == 'R': if line.reel[0:1] == 'R':
third_line.append("%s" % (line['Reel'])) third_line.append("%s" % line.reel)
else: else:
third_line.append("Reel %s" % (line['Reel'])) third_line.append("Reel %s" % line.reel)
if 'Version' in line.keys(): if line.version is not None:
third_line.append("(%s)" % line['Version']) third_line.append("(%s)" % line.version)
if len(third_line) > 0: if len(third_line) > 0:
tc_data = tc_data + "<br/>" + " ".join(third_line) tc_data = tc_data + "<br/>" + " ".join(third_line)
return tc_data return tc_data
def generate_report(page_size, lines, character_number=None, include_done=True, def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat, character_number=None,
include_omitted=True): include_omitted=True):
if character_number is not None: if character_number is not None:
lines = [r for r in lines if r['Character Number'] == character_number] lines = [r for r in lines if r.character_id == character_number]
title = "%s ADR Report (%s)" % (lines[0]['Title'], lines[0]['Character Name']) title = "%s ADR Report (%s)" % (lines[0].title, lines[0].character_name)
document_header = "%s ADR Report" % (lines[0]['Character Name']) document_header = "%s ADR Report" % lines[0].character_name
else: else:
title = "%s ADR Report" % (lines[0]['Title']) title = "%s ADR Report" % lines[0].title
document_header = 'ADR Report' document_header = 'ADR Report'
if not include_done:
lines = [line for line in lines if 'Done' not in line.keys()]
if not include_omitted: if not include_omitted:
lines = [line for line in lines if 'Omitted' not in line.keys()] lines = [line for line in lines if not line.omitted]
lines = sorted(lines, key=lambda line: line['PT.Clip.Start_Seconds']) lines = sorted(lines, key=lambda line: line.start)
filename = title + ".pdf" filename = title + ".pdf"
doc = make_doc_template(page_size=page_size, doc = make_doc_template(page_size=page_size,
filename=filename, document_title=title, filename=filename, document_title=title,
record=lines[0], document_header=document_header, record=lines[0], document_header=document_header,
left_margin=0.75 * inch) left_margin=0.75 * inch)
story = build_story(lines) story = build_story(lines, tc_rate)
doc.build(story) doc.build(story)
def output_report(lines, page_size=portrait(letter), by_character=False): def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat,
page_size=portrait(letter), by_character=False):
if by_character: if by_character:
character_numbers = set((r['Character Number'] for r in lines)) character_numbers = set((r.character_id for r in lines))
for n in character_numbers: for n in character_numbers:
generate_report(page_size, lines, n) generate_report(page_size, lines, tc_display_format, n)
else: else:
generate_report(page_size, lines) generate_report(page_size, lines, tc_display_format)

View File

@@ -1,9 +1,9 @@
import unittest import unittest
from ptulsconv import broadcast_timecode from ptulsconv import broadcast_timecode
from fractions import Fraction
class TestBroadcastTimecode(unittest.TestCase): class TestBroadcastTimecode(unittest.TestCase):
def test_basic_to_framecount(self): def test_basic_to_frame_count(self):
r1 = "01:00:00:00" r1 = "01:00:00:00"
f1 = broadcast_timecode.smpte_to_frame_count(r1, 24, False) f1 = broadcast_timecode.smpte_to_frame_count(r1, 24, False)
self.assertEqual(f1, 86_400) self.assertEqual(f1, 86_400)
@@ -32,13 +32,7 @@ class TestBroadcastTimecode(unittest.TestCase):
s1 = broadcast_timecode.frame_count_to_smpte(c1, 30, drop_frame=True) s1 = broadcast_timecode.frame_count_to_smpte(c1, 30, drop_frame=True)
self.assertEqual(s1, "01:00:03;18") self.assertEqual(s1, "01:00:03;18")
def test_fractional_to_string(self): def test_drop_frame_to_frame_count(self):
c1 = 99
f1 = .145
s1 = broadcast_timecode.frame_count_to_smpte(c1, 25, drop_frame=False, fractional_frame=f1)
self.assertEqual(s1, "00:00:03:24.145")
def test_drop_frame_to_framecount(self):
r1 = "01:00:00;00" r1 = "01:00:00;00"
z1 = broadcast_timecode.smpte_to_frame_count(r1, 30, drop_frame_hint=True) z1 = broadcast_timecode.smpte_to_frame_count(r1, 30, drop_frame_hint=True)
self.assertEqual(z1, 107_892) self.assertEqual(z1, 107_892)
@@ -55,13 +49,13 @@ class TestBroadcastTimecode(unittest.TestCase):
f3 = broadcast_timecode.smpte_to_frame_count(r3, 30, True) f3 = broadcast_timecode.smpte_to_frame_count(r3, 30, True)
self.assertEqual(f3, 1799) self.assertEqual(f3, 1799)
def test_footage_to_framecount(self): def test_footage_to_frame_count(self):
s1 = "194+11" s1 = "194+11"
f1 = broadcast_timecode.footage_to_frame_count(s1) f1 = broadcast_timecode.footage_to_frame_count(s1)
self.assertEqual(f1, 3115) self.assertEqual(f1, 3115)
s3 = "0+0.1" s3 = "0+0.1"
f3 = broadcast_timecode.footage_to_frame_count(s3, include_fractional=False) f3 = broadcast_timecode.footage_to_frame_count(s3)
self.assertEqual(f3, 0) self.assertEqual(f3, 0)
def test_frame_count_to_footage(self): def test_frame_count_to_footage(self):
@@ -69,10 +63,12 @@ class TestBroadcastTimecode(unittest.TestCase):
s1 = broadcast_timecode.frame_count_to_footage(c1) s1 = broadcast_timecode.frame_count_to_footage(c1)
self.assertEqual(s1, "1+03") self.assertEqual(s1, "1+03")
c2 = 24 def test_seconds_to_smpte(self):
f2 = .1 secs = Fraction(25, 24)
s2 = broadcast_timecode.frame_count_to_footage(c2, fractional_frames=f2) frame_duration = Fraction(1, 24)
self.assertEqual(s2, "1+08.100") s1 = broadcast_timecode.seconds_to_smpte(secs, frame_duration, 24, False)
self.assertEqual(s1, "00:00:01:01")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@@ -6,6 +6,9 @@ from fractions import Fraction
class TestTagCompiler(unittest.TestCase): class TestTagCompiler(unittest.TestCase):
#TODO Test marker comment application
def test_one_track(self): def test_one_track(self):
c = ptulsconv.docparser.tag_compiler.TagCompiler() c = ptulsconv.docparser.tag_compiler.TagCompiler()