diff --git a/ptulsconv/commands.py b/ptulsconv/commands.py
index 28aea9b..8a5d0db 100644
--- a/ptulsconv/commands.py
+++ b/ptulsconv/commands.py
@@ -15,6 +15,7 @@ from .validations import *
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.talent_sides import output_report as output_talent_sides
+from ptulsconv.pdf.summary_log import output_report as output_summary
# 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
@@ -208,10 +209,14 @@ def convert(input_file, output_format='fmpxml', start=None, end=None, select_ree
validate_non_empty_field(parsed, field='QN'),
validate_non_empty_field(parsed, field='CN'),
validate_non_empty_field(parsed, field='Char'),
+ validate_non_empty_field(parsed, field='Title'),
validate_dependent_value(parsed, key_field='CN',
dependent_field='Char'),
validate_dependent_value(parsed, key_field='CN',
- dependent_field='Actor'),):
+ dependent_field='Actor'),
+ validate_unique_count(parsed, field='Title', count=1),
+ validate_unique_count(parsed, field='Spotting', count=1),
+ validate_unique_count(parsed, field='Supervisor', count=1)):
print_warning(warning.report_message())
@@ -220,9 +225,12 @@ def convert(input_file, output_format='fmpxml', start=None, end=None, select_ree
elif output_format == 'full':
print("Sorry, the `full` output type is not yet supported.")
normalized_records = normalize_record_keys(parsed)
+
output_supervisor_1pg(normalized_records)
output_talent_sides(normalized_records)
output_line_count(normalized_records)
+ output_summary(normalized_records)
+
elif output_format == 'fmpxml':
if xsl is None:
fmp_dump(parsed, input_file, output)
diff --git a/ptulsconv/pdf/common.py b/ptulsconv/pdf/common.py
index d3de29b..c72c61f 100644
--- a/ptulsconv/pdf/common.py
+++ b/ptulsconv/pdf/common.py
@@ -1,6 +1,51 @@
from reportlab.pdfbase.pdfmetrics import (getAscent, getDescent)
+from reportlab.lib.units import inch
+from reportlab.pdfgen import canvas
+import datetime
+# This is from https://code.activestate.com/recipes/576832/ for
+# generating page count messages
+class NumberedCanvas(canvas.Canvas):
+ def __init__(self, *args, **kwargs):
+ canvas.Canvas.__init__(self, *args, **kwargs)
+ self._saved_page_states = []
+ self._report_date = datetime.datetime.now()
+ def showPage(self):
+ self._saved_page_states.append(dict(self.__dict__))
+ self._startPage()
+
+ def save(self):
+ """add page info to each page (page x of y)"""
+ num_pages = len(self._saved_page_states)
+ for state in self._saved_page_states:
+ self.__dict__.update(state)
+ self.draw_page_number(num_pages)
+ canvas.Canvas.showPage(self)
+ canvas.Canvas.save(self)
+
+ def draw_page_number(self, page_count):
+ self.saveState()
+ self.setFont("Futura", 10)
+ self.drawString(0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count))
+ right_edge = self._pagesize[0] - 0.5 * inch
+ self.drawRightString(right_edge, 0.5 * inch, self._report_date.strftime("%c"))
+ topline = self.beginPath()
+ topline.moveTo(0.5 * inch, 0.75 * inch)
+ topline.lineTo(right_edge, 0.75 * inch)
+ self.setLineWidth(0.5)
+ self.drawPath(topline)
+ self.restoreState()
+
+def time_format(mins):
+ if mins < 60.:
+ return "%im" % round(mins)
+ else:
+ m = round(mins)
+ hh, mm = divmod(m, 60)
+ return "%ih%im" % (hh, mm)
+
+## draws the title block inside the given rect
def draw_title_block(canvas, rect, record):
(supervisor, client,), title = rect.divide_y([16., 16., ])
title.draw_text_cell(canvas, record['Title'], "Futura", 18, inset_y=2.)
@@ -56,8 +101,12 @@ class GRect:
elif at <= 0:
return self, None
else:
- return (GRect(self.min_x, self.min_y, at, self.height),
- GRect(self.min_x + at, self.y, self.width - at, self.height))
+ if direction == 'l':
+ return (GRect(self.min_x, self.min_y, at, self.height),
+ GRect(self.min_x + at, self.y, self.width - at, self.height))
+ else:
+ return (GRect(self.max_x - at, self.y, at, self.height),
+ GRect(self.min_x, self.y, self.width - at, self.height))
def split_y(self, at, direction='u'):
if at >= self.height:
diff --git a/ptulsconv/pdf/line_count.py b/ptulsconv/pdf/line_count.py
index 129b4a7..9c87d20 100644
--- a/ptulsconv/pdf/line_count.py
+++ b/ptulsconv/pdf/line_count.py
@@ -10,7 +10,7 @@ from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, Table, TableStyle
-from .common import GRect
+from .common import GRect, time_format, NumberedCanvas
import datetime
@@ -26,14 +26,6 @@ def build_columns(records, show_priorities = False):
else:
return str(l)
- def time_format(mins):
- if mins < 60.:
- return "%im" % round(mins)
- else:
- m = round(mins)
- hh, mm = divmod(m, 60)
- return "%ih%im" % (hh, mm)
-
columns.append({
'heading': '#',
'value_getter': lambda recs: recs[0]['Character Number'],
@@ -180,7 +172,7 @@ def output_report(records):
page = page.inset(inch * 0.5)
title_box, table_box = page.split_y(inch, 'd')
- c = Canvas('Line Count.pdf', pagesize=(letter[1], letter[0]))
+ c = NumberedCanvas('Line Count.pdf', pagesize=(letter[1], letter[0]))
c.setFont('Futura', 18.)
c.drawCentredString(title_box.center_x, title_box.center_y, "Line Count")
diff --git a/ptulsconv/pdf/summary_log.py b/ptulsconv/pdf/summary_log.py
new file mode 100644
index 0000000..9039792
--- /dev/null
+++ b/ptulsconv/pdf/summary_log.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+from .common import GRect, draw_title_block, time_format, NumberedCanvas
+from reportlab.lib.units import inch
+from reportlab.lib.pagesizes import letter, landscape, portrait
+
+from reportlab.platypus import BaseDocTemplate, Paragraph, Spacer, \
+ KeepTogether, Table, HRFlowable, PageTemplate, Frame
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.lib import colors
+
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
+
+
+def build_aux_data_field(line):
+ entries = list()
+ if 'Reason' in line.keys():
+ entries.append("Reason: " + line["Reason"])
+ if 'Note' in line.keys():
+ entries.append("Note: " + line["Note"])
+ if 'Requested by' in line.keys():
+ entries.append("Requested by: " + line["Requested by"])
+ if 'Shot' in line.keys():
+ entries.append("Shot: " + line["Shot"])
+
+ tag_field = ""
+ for tag in line.keys():
+ if line[tag] == tag and tag != 'ADR':
+ tag_field += "" + tag + " "
+
+ entries.append(tag_field)
+
+ return "
".join(entries)
+
+
+def build_story(lines):
+ story = list()
+
+ this_scene = None
+ scene_style = getSampleStyleSheet()['Normal']
+ scene_style.fontName = 'Futura'
+ scene_style.leftIndent = 0.
+ line_style = getSampleStyleSheet()['Normal']
+ line_style.fontName = 'Futura'
+
+ for line in lines:
+ table_style = [('VALIGN', (0, 0), (-1, -1), 'TOP'), ('LEFTPADDING', (0, 0), (0, 0), 0.0)]
+
+ if 'Omitted' in line.keys():
+ cue_number_field = "" + line['Cue Number'] + "
" + \
+ line['Character Name'] + ""
+ table_style.append(('BACKGROUND', (0, 0), (-1, 0), colors.lightpink))
+ else:
+ cue_number_field = line['Cue Number'] + "
" + line['Character Name'] + ""
+
+ time_data = time_format(line.get('Time Budget Mins', 0.))
+
+ if 'Priority' in line.keys():
+ time_data = time_data + "
" + "P:" + int(line['Priority'])
+
+ aux_data_field = build_aux_data_field(line)
+
+ line_table_data = [[Paragraph(cue_number_field, line_style),
+ Paragraph(line['PT.Clip.Start'] + "
" + line['PT.Clip.Finish'], line_style),
+ Paragraph(line['Line'], line_style),
+ Paragraph(time_data, line_style),
+ Paragraph(aux_data_field, line_style)
+ ]]
+
+ line_table = Table(data=line_table_data,
+ colWidths=[inch, inch, inch * 3., 0.5 * inch, inch * 2.],
+ style=table_style)
+
+ if line['Scene'] != this_scene:
+ this_scene = line['Scene']
+ story.append(KeepTogether([
+ Spacer(1., 0.25 * inch),
+ Paragraph("" + this_scene + "", scene_style),
+ line_table]))
+ else:
+ line_table.setStyle(table_style + [('LINEABOVE', (0, 0), (-1,0), .5, colors.gray)])
+ story.append(KeepTogether([line_table]))
+
+ return story
+
+
+
+def output_report(records):
+ page_size = portrait(letter)
+ page_box = GRect(inch * 0.5, inch * 0.5, page_size[0] - inch, page_size[1] - inch)
+ title_box, page_box = page_box.split_y(0.875 * inch, 'd')
+ footer_box, page_box = page_box.split_y(0.25 * inch, 'u')
+
+ title_block, header_block = title_box.split_x(inch * 4., direction='r')
+
+ pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
+
+ page_template = PageTemplate(id="Main",
+ frames=[Frame(page_box.min_x, page_box.min_y, page_box.width, page_box.height)],
+ onPage=lambda canvas, _: draw_title_block(canvas, title_block, lines[0]))
+
+ lines = sorted(records['events'], key=lambda line: line['PT.Clip.Start_Seconds'])
+
+ doc = BaseDocTemplate("Summary.pdf",
+ pagesize=page_size, leftMargin=0.5 * inch,
+ rightMargin=0.5 * inch, topMargin=0.5 * inch,
+ bottomMargin=0.5 * inch)
+
+ doc.addPageTemplates([page_template])
+
+ story = build_story(lines)
+
+ doc.build(story, canvasmaker=NumberedCanvas)
diff --git a/ptulsconv/pdf/talent_sides.py b/ptulsconv/pdf/talent_sides.py
index 3187b8a..00d009c 100644
--- a/ptulsconv/pdf/talent_sides.py
+++ b/ptulsconv/pdf/talent_sides.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from .common import GRect, draw_title_block
+from .common import GRect, draw_title_block, NumberedCanvas
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter
@@ -69,4 +69,4 @@ def output_report(records):
)
)
- doc.build(story)
+ doc.build(story, canvasmaker=NumberedCanvas)
diff --git a/ptulsconv/validations.py b/ptulsconv/validations.py
index 3b04c04..298a78c 100644
--- a/ptulsconv/validations.py
+++ b/ptulsconv/validations.py
@@ -10,6 +10,11 @@ class ValidationError:
def report_message(self):
return f"{self.message}: event at {self.event['PT.Clip.Start']} on track {self.event['PT.Track.Name']}"
+def validate_unique_count(input_dict, field='Title', count=1):
+ values = set(list(map(lambda e: e[field], input_dict['events'])))
+ if len(values) > count:
+ yield ValidationError(message="Field {} has too many values (max={}): {}".format(field, count, values))
+
def validate_value(input_dict, key_field, predicate):
for event in input_dict['events']:
val = event[key_field]