Merge pull request #8 from iluvcapra/bug-flake8

Add Flake8 to build tests, clean up code style
This commit is contained in:
Jamie Hardt
2023-07-21 14:26:53 -07:00
committed by GitHub
26 changed files with 651 additions and 396 deletions

4
.flake8 Normal file
View File

@@ -0,0 +1,4 @@
[flake8]
per-file-ignores =
ptulsconv/__init__.py: F401
ptulsconv/docparser/__init__.py: F401

View File

@@ -38,3 +38,4 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest pytest
flake8 ptulsconv

View File

@@ -5,4 +5,5 @@ Parse and convert Pro Tools text exports
__version__ = '2.0.0' __version__ = '2.0.0'
__author__ = 'Jamie Hardt' __author__ = 'Jamie Hardt'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = "%s %s (c) 2023 %s. All rights reserved." % (__name__, __version__, __author__) __copyright__ = "%s %s (c) 2023 %s. All rights reserved." \
% (__name__, __version__, __author__)

View File

@@ -2,9 +2,11 @@ from optparse import OptionParser, OptionGroup
import datetime import datetime
import sys import sys
from ptulsconv import __name__, __version__, __author__, __copyright__ from ptulsconv import __name__, __copyright__
from ptulsconv.commands import convert from ptulsconv.commands import convert
from ptulsconv.reporting import print_status_style, print_banner_style, print_section_header_style, print_fatal_error from ptulsconv.reporting import print_status_style, \
print_banner_style, print_section_header_style, \
print_fatal_error
def dump_field_map(output=sys.stdout): def dump_field_map(output=sys.stdout):
@@ -19,14 +21,14 @@ def dump_formats():
print_section_header_style("`raw` format:") print_section_header_style("`raw` format:")
sys.stderr.write("A JSON document of the parsed Pro Tools export.\n") sys.stderr.write("A JSON document of the parsed Pro Tools export.\n")
print_section_header_style("`tagged` Format:") print_section_header_style("`tagged` Format:")
sys.stderr.write("A JSON document containing one record for each clip, with\n" sys.stderr.write(
"A JSON document containing one record for each clip, with\n"
"all tags parsed and all tagging rules applied. \n") "all tags parsed and all tagging rules applied. \n")
print_section_header_style("`doc` format:") print_section_header_style("`doc` format:")
sys.stderr.write("Creates a directory with folders for different types\n" sys.stderr.write("Creates a directory with folders for different types\n"
"of ADR reports.\n\n") "of ADR reports.\n\n")
def main(): def main():
"""Entry point for the command-line invocation""" """Entry point for the command-line invocation"""
parser = OptionParser() parser = OptionParser()
@@ -45,28 +47,33 @@ def main():
warn_options.add_option('-W', action='store_false', warn_options.add_option('-W', action='store_false',
dest='warnings', dest='warnings',
default=True, default=True,
help='Suppress warnings for common errors (missing code numbers etc.)') help='Suppress warnings for common '
'errors (missing code numbers etc.)')
parser.add_option_group(warn_options) parser.add_option_group(warn_options)
informational_options = OptionGroup(title="Informational Options", informational_options = OptionGroup(title="Informational Options",
parser=parser, parser=parser,
description='Print useful information and exit without processing ' description='Print useful '
'information '
'and exit without processing '
'input files.') 'input files.')
informational_options.add_option('--show-formats', informational_options.add_option(
'--show-formats',
dest='show_formats', dest='show_formats',
action='store_true', action='store_true',
default=False, default=False,
help='Display helpful information about the ' help='Display helpful information about the available '
'available output formats.') 'output formats.')
informational_options.add_option('--show-available-tags', informational_options.add_option(
'--show-available-tags',
dest='show_tags', dest='show_tags',
action='store_true', action='store_true',
default=False, default=False,
help='Display tag mappings for the FMP XML ' help='Display tag mappings for the FMP XML output style '
'output style and exit.') 'and exit.')
parser.add_option_group(informational_options) parser.add_option_group(informational_options)
@@ -74,9 +81,9 @@ def main():
(options, args) = parser.parse_args(sys.argv) (options, args) = parser.parse_args(sys.argv)
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() dump_field_map()
@@ -89,7 +96,9 @@ def main():
major_mode = options.output_format major_mode = options.output_format
if len(args) < 2: if len(args) < 2:
print_status_style("No input file provided, will connect to Pro Tools with PTSL...") print_status_style(
"No input file provided, will connect to Pro Tools "
"with PTSL...")
convert(major_mode=major_mode, convert(major_mode=major_mode,
warnings=options.warnings) warnings=options.warnings)
else: else:

View File

@@ -9,13 +9,15 @@ from fractions import Fraction
from typing import Optional, SupportsFloat from typing import Optional, SupportsFloat
class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps drop_frame")): class TimecodeFormat(namedtuple("_TimecodeFormat",
"frame_duration logical_fps drop_frame")):
""" """
A struct reperesenting a timecode datum. A struct reperesenting a timecode datum.
""" """
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]: def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame) frame_count = smpte_to_frame_count(
smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
if frame_count is None: if frame_count is None:
return None return None
else: else:
@@ -23,29 +25,34 @@ class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps d
def seconds_to_smpte(self, seconds: SupportsFloat) -> str: def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
frame_count = int(seconds / self.frame_duration) frame_count = int(seconds / self.frame_duration)
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame) 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) -> Optional[int]: def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
drop_frame_hint=False) -> Optional[int]:
""" """
Convert a string with a SMPTE timecode representation into a frame count. Convert a string with a SMPTE timecode representation into a frame count.
:param smpte_rep_string: The timecode string :param smpte_rep_string: The timecode string
:param frames_per_logical_second: Num of frames in a logical second. This is asserted to be :param frames_per_logical_second: Num of frames in a logical second. This
in one of `[24,25,30,48,50,60]` is asserted to be in one of `[24,25,30,48,50,60]`
:param drop_frame_hint: `True` if the timecode rep is drop frame. This is ignored (and implied `True`) if :param drop_frame_hint: `True` if the timecode rep is drop frame. This is
the last separator in the timecode string is a semicolon. This is ignored (and implied `False`) if ignored (and implied `True`) if the last separator in the timecode
string is a semicolon. This is ignored (and implied `False`) if
`frames_per_logical_second` is not 30 or 60. `frames_per_logical_second` is not 30 or 60.
""" """
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60] assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
m = re.search(r'(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?', smpte_rep_string) m = re.search(
r'(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?', smpte_rep_string)
if m is None: if m is None:
return None return None
hh, mm, ss, sep, ff, frac = m.groups() hh, mm, ss, sep, ff, frac = m.groups()
hh, mm, ss, ff, frac = int(hh), int(mm), int(ss), int(ff), float(frac or 0.0) hh, mm, ss, ff, frac = int(hh), int(
mm), int(ss), int(ff), float(frac or 0.0)
drop_frame = drop_frame_hint drop_frame = drop_frame_hint
if sep == ";": if sep == ";":
@@ -54,8 +61,8 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
if frames_per_logical_second not in [30, 60]: if frames_per_logical_second not in [30, 60]:
drop_frame = False drop_frame = False
raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * frames_per_logical_second + \ raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * \
ss * frames_per_logical_second + ff frames_per_logical_second + ss * frames_per_logical_second + ff
frames = raw_frames frames = raw_frames
if drop_frame is True: if drop_frame is True:
@@ -68,7 +75,8 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
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: Optional[float] = None) -> str: fractional_frame: Optional[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
@@ -90,7 +98,8 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
hh = hh % 24 hh = hh % 24
if fractional_frame is not None and fractional_frame > 0: if fractional_frame is not None and fractional_frame > 0:
return "%02i:%02i:%02i%s%02i%s" % (hh, mm, ss, separator, ff, ("%.3f" % fractional_frame)[1:]) return "%02i:%02i:%02i%s%02i%s" % (hh, mm, ss, separator, ff,
("%.3f" % fractional_frame)[1:])
else: else:
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff) return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)

View File

@@ -8,19 +8,20 @@ import os
import sys import sys
from itertools import chain from itertools import chain
import csv import csv
from typing import List from typing import List, Optional, Iterator
from fractions import Fraction from fractions import Fraction
import ptsl import ptsl
from .docparser.adr_entity import make_entities from .docparser.adr_entity import make_entities, ADRLine
from .reporting import print_section_header_style, print_status_style, print_warning from .reporting import print_section_header_style, print_status_style,\
from .validations import * print_warning
from .validations import validate_unique_field, validate_non_empty_field,\
validate_dependent_value
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 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
@@ -50,9 +51,9 @@ class MyEncoder(JSONEncoder):
def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat): def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
""" """
Writes ADR lines as CSV to the current working directory. Creates directories Writes ADR lines as CSV to the current working directory. Creates
for each character number and name pair, and within that directory, creates directories for each character number and name pair, and within that
a CSV file for each reel. directory, creates a CSV file for each reel.
""" """
reels = set([ln.reel for ln in lines]) reels = set([ln.reel for ln in lines])
@@ -61,12 +62,15 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
os.makedirs(dir_name, exist_ok=True) os.makedirs(dir_name, exist_ok=True)
os.chdir(dir_name) os.chdir(dir_name)
for reel in reels: for reel in reels:
these_lines = [ln for ln in lines if ln.character_id == n and ln.reel == reel] these_lines = [ln for ln in lines
if ln.character_id == n and ln.reel == reel]
if len(these_lines) == 0: if len(these_lines) == 0:
continue continue
outfile_name = "%s_%s_%s_%s.csv" % (these_lines[0].title, n, these_lines[0].character_name, reel,) outfile_name = "%s_%s_%s_%s.csv" % (these_lines[0].title, n,
these_lines[0].character_name,
reel,)
with open(outfile_name, mode='w', newline='') as outfile: with open(outfile_name, mode='w', newline='') as outfile:
writer = csv.writer(outfile, dialect='excel') writer = csv.writer(outfile, dialect='excel')
@@ -80,18 +84,21 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
for event in these_lines: for event in these_lines:
this_start = event.start or 0 this_start = event.start or 0
this_finish = event.finish or 0 this_finish = event.finish or 0
this_row = [event.title, event.character_name, event.cue_number, this_row = [event.title, event.character_name,
event.reel, event.version, event.cue_number, event.reel, event.version,
time_format.seconds_to_smpte(this_start), time_format.seconds_to_smpte(this_finish), time_format.seconds_to_smpte(this_start),
time_format.seconds_to_smpte(this_finish),
float(this_start), float(this_finish), float(this_start), float(this_finish),
event.prompt, event.prompt,
event.reason, event.note, "TV" if event.tv else ""] event.reason, event.note, "TV"
if event.tv else ""]
writer.writerow(this_row) writer.writerow(this_row)
os.chdir("..") os.chdir("..")
def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine], title): def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine],
title):
""" """
Create PDF output. Create PDF output.
""" """
@@ -105,22 +112,22 @@ def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine],
supervisor = next((x.supervisor for x in adr_lines), "") supervisor = next((x.supervisor for x in adr_lines), "")
output_continuity(scenes=scenes, tc_display_format=session_tc_format, output_continuity(scenes=scenes, tc_display_format=session_tc_format,
title=title, client=client, supervisor=supervisor) title=title, client=client,
supervisor=supervisor)
# reels = sorted([r for r in compiler.compile_all_time_spans() if r[0] == 'Reel'],
# key=lambda x: x[2])
reels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6'] reels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6']
if len(adr_lines) == 0: if len(adr_lines) == 0:
print_status_style("No ADR lines were found in the " print_status_style("No ADR lines were found in the input document. "
"input document. ADR reports will not be generated.") "ADR reports will not be generated.")
else: else:
create_adr_reports(adr_lines, tc_display_format=session_tc_format, create_adr_reports(adr_lines, tc_display_format=session_tc_format,
reel_list=sorted(reels)) reel_list=sorted(reels))
def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat, reel_list: List[str]): def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
reel_list: List[str]):
""" """
Creates a directory heirarchy and a respective set of ADR reports, Creates a directory heirarchy and a respective set of ADR reports,
given a list of lines. given a list of lines.
@@ -141,7 +148,8 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
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, tc_display_format=tc_display_format, 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")
@@ -179,7 +187,7 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
req.time_type("tc") req.time_type("tc")
req.dont_show_crossfades() req.dont_show_crossfades()
req.selected_tracks_only() req.selected_tracks_only()
session_text = req.export_string() session_text = req.export_string
session = parse_document(session_text) session = parse_document(session_text)
session_tc_format = session.header.timecode_format session_tc_format = session.header.timecode_format
@@ -198,7 +206,8 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
elif major_mode == 'doc': elif major_mode == 'doc':
generic_events, adr_lines = make_entities(compiled_events) generic_events, adr_lines = make_entities(compiled_events)
scenes = sorted([s for s in compiler.compile_all_time_spans() if s[0] == 'Sc'], scenes = sorted([s for s in compiler.compile_all_time_spans()
if s[0] == 'Sc'],
key=lambda x: x[2]) key=lambda x: x[2])
# TODO: Breakdown by titles # TODO: Breakdown by titles
@@ -210,7 +219,9 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
title = list(titles)[0] title = list(titles)[0]
print_status_style("%i generic events found." % len(generic_events)) print_status_style(
"%i generic events found." % len(generic_events)
)
print_status_style("%i ADR events found." % len(adr_lines)) print_status_style("%i ADR events found." % len(adr_lines))
if warnings: if warnings:
@@ -223,7 +234,8 @@ def perform_adr_validations(lines : Iterator[ADRLine]):
""" """
Performs validations on the input. Performs validations on the input.
""" """
for warning in chain(validate_unique_field(lines, for warning in chain(
validate_unique_field(lines,
field='cue_number', field='cue_number',
scope='title'), scope='title'),
validate_non_empty_field(lines, validate_non_empty_field(lines,
@@ -238,4 +250,5 @@ def perform_adr_validations(lines : Iterator[ADRLine]):
validate_dependent_value(lines, validate_dependent_value(lines,
key_field='character_id', key_field='character_id',
dependent_field='actor_name')): dependent_field='actor_name')):
print_warning(warning.report_message()) print_warning(warning.report_message())

View File

@@ -1,6 +1,6 @@
""" """
This module defines classes and methods for converting :class:`Event` objects into This module defines classes and methods for converting :class:`Event` objects
:class:`ADRLine` objects. into :class:`ADRLine` objects.
""" """
from ptulsconv.docparser.tag_compiler import Event from ptulsconv.docparser.tag_compiler import Event
@@ -11,15 +11,16 @@ from fractions import Fraction
from ptulsconv.docparser.tag_mapping import TagMapping from ptulsconv.docparser.tag_mapping import TagMapping
def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'], List['ADRLine']]: def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'],
List['ADRLine']]:
""" """
Accepts a list of Events and converts them into either ADRLine events or Accepts a list of Events and converts them into either ADRLine events or
GenricEvents by calling :func:`make_entity` on each member. GenricEvents by calling :func:`make_entity` on each member.
:param from_events: A list of `Event` objects. :param from_events: A list of `Event` objects.
:returns: A tuple of two lists, the first containing :class:`GenericEvent` and the :returns: A tuple of two lists, the first containing :class:`GenericEvent`
second containing :class:`ADRLine`. and the second containing :class:`ADRLine`.
""" """
generic_events = list() generic_events = list()
adr_lines = list() adr_lines = list()
@@ -74,7 +75,8 @@ class GenericEvent:
requested_by: Optional[str] = None requested_by: Optional[str] = None
tag_mapping = [ tag_mapping = [
TagMapping(source='Title', target="title", alt=TagMapping.ContentSource.Session), TagMapping(source='Title', target="title",
alt=TagMapping.ContentSource.Session),
TagMapping(source="Supv", target="supervisor"), TagMapping(source="Supv", target="supervisor"),
TagMapping(source="Client", target="client"), TagMapping(source="Client", target="client"),
TagMapping(source="Sc", target="scene"), TagMapping(source="Sc", target="scene"),
@@ -111,9 +113,11 @@ class ADRLine(GenericEvent):
TagMapping(source="P", target="priority"), TagMapping(source="P", target="priority"),
TagMapping(source="QN", target="cue_number"), TagMapping(source="QN", target="cue_number"),
TagMapping(source="CN", target="character_id"), TagMapping(source="CN", target="character_id"),
TagMapping(source="Char", target="character_name", alt=TagMapping.ContentSource.Track), TagMapping(source="Char", target="character_name",
alt=TagMapping.ContentSource.Track),
TagMapping(source="Actor", target="actor_name"), TagMapping(source="Actor", target="actor_name"),
TagMapping(source="Line", target="prompt", alt=TagMapping.ContentSource.Clip), TagMapping(source="Line", target="prompt",
alt=TagMapping.ContentSource.Clip),
TagMapping(source="R", target="reason"), TagMapping(source="R", target="reason"),
TagMapping(source="Mins", target="time_budget_mins", TagMapping(source="Mins", target="time_budget_mins",
formatter=(lambda n: float(n))), formatter=(lambda n: float(n))),
@@ -131,5 +135,3 @@ class ADRLine(GenericEvent):
TagMapping(source="OPT", target="optional", TagMapping(source="OPT", target="optional",
formatter=(lambda x: len(x) > 0)) formatter=(lambda x: len(x) > 0))
] ]

View File

@@ -21,19 +21,24 @@ class SessionDescriptor:
def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]: def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]:
for marker in self.markers: for marker in self.markers:
marker_time = Fraction(marker.time_reference, int(self.header.sample_rate)) marker_time = Fraction(marker.time_reference,
int(self.header.sample_rate))
# marker_time = self.header.convert_timecode(marker.location) # marker_time = self.header.convert_timecode(marker.location)
yield marker, marker_time yield marker, marker_time
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor', 'TrackClipDescriptor']]: def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor',
'TrackClipDescriptor']]:
for track in self.tracks: for track in self.tracks:
for clip in track.clips: for clip in track.clips:
yield track, clip yield track, clip
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor", "TrackClipDescriptor", def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor",
Fraction, Fraction, Fraction]]: "TrackClipDescriptor",
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
""" """
for track, clip in self.tracks_clips(): for track, clip in self.tracks_clips():
start_time = self.header.convert_timecode(clip.start_timecode) start_time = self.header.convert_timecode(clip.start_timecode)
@@ -105,7 +110,8 @@ class HeaderDescriptor:
if self.timecode_fps in frame_rates.keys(): if self.timecode_fps in frame_rates.keys():
return frame_rates[self.timecode_fps] 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)
class TrackDescriptor: class TrackDescriptor:

View File

@@ -1 +1 @@
from dataclasses import dataclass # from dataclasses import dataclass

View File

@@ -1,13 +1,15 @@
from parsimonious.nodes import NodeVisitor from parsimonious.nodes import NodeVisitor
from parsimonious.grammar import Grammar from parsimonious.grammar import Grammar
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor, FileDescriptor, \ from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor,\
TrackClipDescriptor, ClipDescriptor, PluginDescriptor, MarkerDescriptor FileDescriptor, TrackClipDescriptor, ClipDescriptor, PluginDescriptor,\
MarkerDescriptor
protools_text_export_grammar = Grammar( protools_text_export_grammar = Grammar(
r""" r"""
document = header files_section? clips_section? plugin_listing? track_listing? markers_listing? document = header files_section? clips_section? plugin_listing?
track_listing? markers_listing?
header = "SESSION NAME:" fs string_value rs header = "SESSION NAME:" fs string_value rs
"SAMPLE RATE:" fs float_value rs "SAMPLE RATE:" fs float_value rs
"BIT DEPTH:" fs integer_value "-bit" rs "BIT DEPTH:" fs integer_value "-bit" rs
@@ -17,21 +19,29 @@ protools_text_export_grammar = Grammar(
"# OF AUDIO CLIPS:" fs integer_value rs "# OF AUDIO CLIPS:" fs integer_value rs
"# OF AUDIO FILES:" fs integer_value rs block_ending "# OF AUDIO FILES:" fs integer_value rs block_ending
frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" / "23.976") frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" /
files_section = files_header files_column_header file_record* block_ending "23.976")
files_section = files_header files_column_header file_record*
block_ending
files_header = "F I L E S I N S E S S I O N" rs files_header = "F I L E S I N S E S S I O N" rs
files_column_header = "Filename" isp fs "Location" rs files_column_header = "Filename" isp fs "Location" rs
file_record = string_value fs string_value rs file_record = string_value fs string_value rs
clips_section = clips_header clips_column_header clip_record* block_ending clips_section = clips_header clips_column_header clip_record*
block_ending
clips_header = "O N L I N E C L I P S I N S E S S I O N" rs clips_header = "O N L I N E C L I P S I N S E S S I O N" rs
clips_column_header = string_value fs string_value rs clips_column_header = string_value fs string_value rs
clip_record = string_value fs string_value (fs "[" integer_value "]")? rs clip_record = string_value fs string_value
(fs "[" integer_value "]")? rs
plugin_listing = plugin_header plugin_column_header plugin_record* block_ending plugin_listing = plugin_header plugin_column_header plugin_record*
block_ending
plugin_header = "P L U G - I N S L I S T I N G" rs plugin_header = "P L U G - I N S L I S T I N G" rs
plugin_column_header = "MANUFACTURER " fs "PLUG-IN NAME " fs plugin_column_header = "MANUFACTURER " fs
"VERSION " fs "FORMAT " fs "STEMS " fs "PLUG-IN NAME " fs
"VERSION " fs
"FORMAT " fs
"STEMS " fs
"NUMBER OF INSTANCES" rs "NUMBER OF INSTANCES" rs
plugin_record = string_value fs string_value fs string_value fs plugin_record = string_value fs string_value fs string_value fs
string_value fs string_value fs string_value rs string_value fs string_value fs string_value rs
@@ -45,8 +55,10 @@ protools_text_export_grammar = Grammar(
"USER DELAY:" fs integer_value " Samples" rs "USER DELAY:" fs integer_value " Samples" rs
"STATE: " track_state_list rs "STATE: " track_state_list rs
("PLUG-INS: " ( fs string_value )* rs)? ("PLUG-INS: " ( fs string_value )* rs)?
"CHANNEL " fs "EVENT " fs "CLIP NAME " fs "CHANNEL " fs "EVENT " fs
"START TIME " fs "END TIME " fs "DURATION " fs "CLIP NAME " fs
"START TIME " fs "END TIME " fs
"DURATION " fs
("TIMESTAMP " fs)? "STATE" rs ("TIMESTAMP " fs)? "STATE" rs
track_state_list = (track_state " ")* track_state_list = (track_state " ")*
@@ -56,15 +68,20 @@ protools_text_export_grammar = Grammar(
track_clip_entry = integer_value isp fs track_clip_entry = integer_value isp fs
integer_value isp fs integer_value isp fs
string_value fs string_value fs
string_value fs string_value fs string_value fs (string_value fs)? string_value fs string_value fs string_value fs
(string_value fs)?
track_clip_state rs track_clip_state rs
track_clip_state = ("Muted" / "Unmuted") track_clip_state = ("Muted" / "Unmuted")
markers_listing = markers_listing_header markers_column_header marker_record* markers_listing = markers_listing_header markers_column_header
marker_record*
markers_listing_header = "M A R K E R S L I S T I N G" rs markers_listing_header = "M A R K E R S L I S T I N G" rs
markers_column_header = "# " fs "LOCATION " fs "TIME REFERENCE " fs markers_column_header = "# " fs "LOCATION " fs
"UNITS " fs "NAME " fs "COMMENTS" rs "TIME REFERENCE " fs
"UNITS " fs
"NAME " fs
"COMMENTS" rs
marker_record = integer_value isp fs string_value fs integer_value isp fs marker_record = integer_value isp fs string_value fs integer_value isp fs
string_value fs string_value fs string_value rs string_value fs string_value fs string_value rs
@@ -125,18 +142,23 @@ class DocParserVisitor(NodeVisitor):
@staticmethod @staticmethod
def visit_files_section(_, visited_children): def visit_files_section(_, visited_children):
return list(map(lambda child: FileDescriptor(filename=child[0], path=child[2]), visited_children[2])) return list(map(
lambda child: FileDescriptor(filename=child[0], path=child[2]),
visited_children[2]))
@staticmethod @staticmethod
def visit_clips_section(_, visited_children): def visit_clips_section(_, visited_children):
channel = next(iter(visited_children[2][3]), 1) channel = next(iter(visited_children[2][3]), 1)
return list(map(lambda child: ClipDescriptor(clip_name=child[0], file=child[2], channel=channel), return list(map(
lambda child: ClipDescriptor(clip_name=child[0], file=child[2],
channel=channel),
visited_children[2])) visited_children[2]))
@staticmethod @staticmethod
def visit_plugin_listing(_, visited_children): def visit_plugin_listing(_, visited_children):
return list(map(lambda child: PluginDescriptor(manufacturer=child[0], return list(map(lambda child:
PluginDescriptor(manufacturer=child[0],
plugin_name=child[2], plugin_name=child[2],
version=child[4], version=child[4],
format=child[6], format=child[6],

View File

@@ -24,20 +24,25 @@ class TagCompiler:
items. items.
""" """
Intermediate = namedtuple('Intermediate', 'track_content track_tags track_comment_tags ' Intermediate = namedtuple('Intermediate',
'clip_content clip_tags clip_tag_mode start finish') '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_all_time_spans(self) -> List[Tuple[str, str, Fraction, Fraction]]: def compile_all_time_spans(self) -> List[Tuple[str, str, Fraction,
Fraction]]:
""" """
:returns: A `List` of (key: str, value: str, start: Fraction, finish: Fraction) :returns: A `List` of (key: str, value: str, start: Fraction,
finish: Fraction)
""" """
ret_list = list() ret_list = list()
for element in self.parse_data(): for element in self.parse_data():
if element.clip_tag_mode == TagPreModes.TIMESPAN: if element.clip_tag_mode == TagPreModes.TIMESPAN:
for k in element.clip_tags.keys(): for k in element.clip_tags.keys():
ret_list.append((k, element.clip_tags[k], element.start, element.finish)) ret_list.append((k, element.clip_tags[k], element.start,
element.finish))
return ret_list return ret_list
@@ -73,26 +78,31 @@ class TagCompiler:
step3 = self.collect_time_spans(step2) step3 = self.collect_time_spans(step2)
step4 = self.apply_tags(step3) step4 = self.apply_tags(step3)
for datum in step4: for datum in step4:
yield Event(clip_name=datum[0], track_name=datum[1], session_name=datum[2], yield Event(clip_name=datum[0], track_name=datum[1],
tags=datum[3], start=datum[4], finish=datum[5]) 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, _ in sorted(applicable, key=lambda x: x[1]): for marker, _ in sorted(applicable, key=lambda x: x[1]):
retval.update(parse_tags(marker.comments or "").tag_dict) retval.update(parse_tags(marker.comments or "").tag_dict)
retval.update(parse_tags(marker.name or "").tag_dict) retval.update(parse_tags(marker.name or "").tag_dict)
return retval return retval
def filter_out_directives(self, clips : Iterator[Intermediate]) -> Iterator[Intermediate]: def filter_out_directives(self,
clips: Iterator[Intermediate]) \
-> Iterator[Intermediate]:
for clip in clips: for clip in clips:
if clip.clip_tag_mode == 'Directive': if clip.clip_tag_mode == 'Directive':
continue continue
else: else:
yield clip yield clip
@staticmethod @staticmethod
def _coalesce_tags(clip_tags: dict, track_tags: dict, def _coalesce_tags(clip_tags: dict, track_tags: dict,
track_comment_tags: dict, track_comment_tags: dict,
@@ -117,7 +127,8 @@ class TagCompiler:
track_comments_parsed = parse_tags(track.comments) track_comments_parsed = parse_tags(track.comments)
clip_parsed = parse_tags(clip.clip_name) clip_parsed = parse_tags(clip.clip_name)
yield TagCompiler.Intermediate(track_content=track_parsed.content, yield TagCompiler.Intermediate(
track_content=track_parsed.content,
track_tags=track_parsed.tag_dict, track_tags=track_parsed.tag_dict,
track_comment_tags=track_comments_parsed.tag_dict, track_comment_tags=track_comments_parsed.tag_dict,
clip_content=clip_parsed.content, clip_content=clip_parsed.content,
@@ -126,15 +137,18 @@ class TagCompiler:
start=start, finish=finish) start=start, finish=finish)
@staticmethod @staticmethod
def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]: def apply_appends(parsed: Iterator[Intermediate]) -> \
Iterator[Intermediate]:
def should_append(a, b): def should_append(a, b):
return b.clip_tag_mode == TagPreModes.APPEND and b.start >= a.finish return b.clip_tag_mode == TagPreModes.APPEND and \
b.start >= a.finish
def do_append(a, b): def do_append(a, b):
merged_tags = dict(a.clip_tags) merged_tags = dict(a.clip_tags)
merged_tags.update(b.clip_tags) merged_tags.update(b.clip_tags)
return TagCompiler.Intermediate(track_content=a.track_content, return TagCompiler.Intermediate(
track_content=a.track_content,
track_tags=a.track_tags, track_tags=a.track_tags,
track_comment_tags=a.track_comment_tags, track_comment_tags=a.track_comment_tags,
clip_content=a.clip_content + ' ' + b.clip_content, clip_content=a.clip_content + ' ' + b.clip_content,
@@ -158,12 +172,14 @@ class TagCompiler:
@staticmethod @staticmethod
def _time_span_tags(at_time: Fraction, applicable_spans) -> dict: def _time_span_tags(at_time: Fraction, applicable_spans) -> dict:
retval = dict() retval = dict()
for tags in reversed([a[0] for a in applicable_spans if a[1] <= at_time <= a[2]]): for tags in reversed([a[0] for a in applicable_spans
if a[1] <= at_time <= a[2]]):
retval.update(tags) retval.update(tags)
return retval return retval
def apply_tags(self, parsed_with_time_spans) -> Iterator[Tuple[str, str, str, dict, Fraction, Fraction]]: def apply_tags(self, parsed_with_time_spans) ->\
Iterator[Tuple[str, str, str, dict, Fraction, Fraction]]:
session_parsed = parse_tags(self.session.header.session_name) session_parsed = parse_tags(self.session.header.session_name)
@@ -171,14 +187,16 @@ class TagCompiler:
event: 'TagCompiler.Intermediate' event: 'TagCompiler.Intermediate'
marker_tags = self._marker_tags(event.start) marker_tags = self._marker_tags(event.start)
time_span_tags = self._time_span_tags(event.start, time_spans) time_span_tags = self._time_span_tags(event.start, time_spans)
tags = self._coalesce_tags(clip_tags=event.clip_tags, tags = self._coalesce_tags(
clip_tags=event.clip_tags,
track_tags=event.track_tags, track_tags=event.track_tags,
track_comment_tags=event.track_comment_tags, track_comment_tags=event.track_comment_tags,
timespan_tags=time_span_tags, timespan_tags=time_span_tags,
marker_tags=marker_tags, marker_tags=marker_tags,
session_tags=session_parsed.tag_dict) session_tags=session_parsed.tag_dict)
yield event.clip_content, event.track_content, session_parsed.content, tags, event.start, event.finish yield (event.clip_content, event.track_content,
session_parsed.content, tags, event.start, event.finish)
def apply_appends(source: Iterator, def apply_appends(source: Iterator,

View File

@@ -48,7 +48,8 @@ class TagMapping:
for rule in rules: for rule in rules:
if rule.target in done: if rule.target in done:
continue continue
if rule.apply(tags, clip_content, track_content, session_content, to): if rule.apply(tags, clip_content, track_content, session_content,
to):
done.update(rule.target) done.update(rule.target)
def __init__(self, source: str, def __init__(self, source: str,

View File

@@ -1,5 +1,5 @@
from parsimonious import NodeVisitor, Grammar from parsimonious import NodeVisitor, Grammar
from typing import Dict, Union from typing import Dict
from enum import Enum from enum import Enum
@@ -53,7 +53,8 @@ class TagListVisitor(NodeVisitor):
return TaggedStringResult(content=next(iter(line_opt), None), return TaggedStringResult(content=next(iter(line_opt), None),
tag_dict=next(iter(tag_list_opt), dict()), tag_dict=next(iter(tag_list_opt), dict()),
mode=TagPreModes(next(iter(modifier_opt), 'Normal')) mode=TagPreModes(
next(iter(modifier_opt), 'Normal'))
) )
@staticmethod @staticmethod

View File

@@ -4,9 +4,11 @@
# def create_movie(event): # def create_movie(event):
# start = event['Movie.Start_Offset_Seconds'] # start = event['Movie.Start_Offset_Seconds']
# duration = event['PT.Clip.Finish_Seconds'] - event['PT.Clip.Start_Seconds'] # duration = event['PT.Clip.Finish_Seconds'] -
# event['PT.Clip.Start_Seconds']
# input_movie = event['Movie.Filename'] # input_movie = event['Movie.Filename']
# print("Will make movie starting at {}, dur {} from movie {}".format(start, duration, input_movie)) # print("Will make movie starting at {}, dur {} from movie {}"
# .format(start, duration, input_movie))
# #
# #
# def export_movies(events): # def export_movies(events):

View File

@@ -17,6 +17,8 @@ from typing import List
# 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
class ReportCanvas(canvas.Canvas): class ReportCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs) canvas.Canvas.__init__(self, *args, **kwargs)
@@ -39,9 +41,11 @@ class ReportCanvas(canvas.Canvas):
def draw_page_number(self, page_count): def draw_page_number(self, page_count):
self.saveState() self.saveState()
self.setFont('Helvetica', 10) # FIXME make this customizable self.setFont('Helvetica', 10) # FIXME make this customizable
self.drawString(0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count)) self.drawString(0.5 * inch, 0.5 * inch,
"Page %d of %d" % (self._pageNumber, page_count))
right_edge = self._pagesize[0] - 0.5 * inch right_edge = self._pagesize[0] - 0.5 * inch
self.drawRightString(right_edge, 0.5 * inch, self._report_date.strftime("%m/%d/%Y %H:%M")) self.drawRightString(right_edge, 0.5 * inch,
self._report_date.strftime("%m/%d/%Y %H:%M"))
top_line = self.beginPath() top_line = self.beginPath()
top_line.moveTo(0.5 * inch, 0.75 * inch) top_line.moveTo(0.5 * inch, 0.75 * inch)
@@ -80,9 +84,11 @@ def make_doc_template(page_size, filename, document_title,
footer_box, title=title, footer_box, title=title,
supervisor=supervisor, supervisor=supervisor,
document_subheader=document_subheader, document_subheader=document_subheader,
client=client, doc_title=document_header)) client=client,
doc_title=document_header))
frames = [Frame(page_box.min_x, page_box.min_y, page_box.width, page_box.height)] frames = [Frame(page_box.min_x, page_box.min_y,
page_box.width, page_box.height)]
page_template = PageTemplate(id="Main", page_template = PageTemplate(id="Main",
frames=frames, frames=frames,
@@ -119,12 +125,17 @@ def time_format(mins, zero_str="-"):
return "%i:%02i" % (hh, mm) return "%i:%02i" % (hh, mm)
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str, def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box,
document_subheader: str, client: str, doc_title="", font_name='Helvetica'): footer_box, title: str, supervisor: str,
document_subheader: str, client: str, doc_title="",
font_name='Helvetica'):
(_supervisor_box, client_box,), title_box = right_box.divide_y([16., 16., ]) (_supervisor_box, client_box,), title_box = \
title_box.draw_text_cell(a_canvas, title, font_name, 18, inset_y=2., inset_x=5.) right_box.divide_y([16., 16., ])
client_box.draw_text_cell(a_canvas, client, font_name, 11, inset_y=2., inset_x=5.) title_box.draw_text_cell(a_canvas, title, font_name, 18,
inset_y=2., inset_x=5.)
client_box.draw_text_cell(a_canvas, client, font_name, 11,
inset_y=2., inset_x=5.)
a_canvas.saveState() a_canvas.saveState()
a_canvas.setLineWidth(0.5) a_canvas.setLineWidth(0.5)
@@ -139,16 +150,20 @@ def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box,
a_canvas.drawPath(tline2) a_canvas.drawPath(tline2)
a_canvas.restoreState() a_canvas.restoreState()
(doc_title_cell, spotting_version_cell,), _ = left_box.divide_y([18., 14], direction='d') (doc_title_cell, spotting_version_cell,), _ = \
left_box.divide_y([18., 14], direction='d')
doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14., inset_y=2.) doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14.,
inset_y=2.)
if document_subheader is not None: if document_subheader is not None:
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, font_name, 12., inset_y=2.) spotting_version_cell.draw_text_cell(a_canvas, document_subheader,
font_name, 12., inset_y=2.)
if supervisor is not None: if supervisor is not None:
a_canvas.setFont(font_name, 11.) a_canvas.setFont(font_name, 11.)
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, supervisor) a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2.,
footer_box.min_y, supervisor)
class GRect: class GRect:
@@ -201,10 +216,12 @@ class GRect:
else: else:
if direction == 'l': if direction == 'l':
return (GRect(self.min_x, self.min_y, at, self.height), return (GRect(self.min_x, self.min_y, at, self.height),
GRect(self.min_x + at, self.y, self.width - at, self.height)) GRect(self.min_x + at, self.y,
self.width - at, self.height))
else: else:
return (GRect(self.max_x - at, self.y, at, self.height), return (GRect(self.max_x - at, self.y, at, self.height),
GRect(self.min_x, self.y, self.width - at, self.height)) GRect(self.min_x, self.y,
self.width - at, self.height))
def split_y(self, at, direction='u'): def split_y(self, at, direction='u'):
if at >= self.height: if at >= self.height:
@@ -214,19 +231,23 @@ class GRect:
else: else:
if direction == 'u': if direction == 'u':
return (GRect(self.x, self.y, self.width, at), return (GRect(self.x, self.y, self.width, at),
GRect(self.x, self.y + at, self.width, self.height - at)) GRect(self.x, self.y + at,
self.width, self.height - at))
else: else:
return (GRect(self.x, self.max_y - at, self.width, at), return (GRect(self.x, self.max_y - at, self.width, at),
GRect(self.x, self.y, self.width, self.height - at)) GRect(self.x, self.y,
self.width, self.height - at))
def inset_xy(self, dx, dy): def inset_xy(self, dx, dy):
return GRect(self.x + dx, self.y + dy, self.width - dx * 2, self.height - dy * 2) return GRect(self.x + dx, self.y + dy,
self.width - dx * 2, self.height - dy * 2)
def inset(self, d): def inset(self, d):
return self.inset_xy(d, d) return self.inset_xy(d, d)
def __repr__(self): def __repr__(self):
return "<GRect x=%f y=%f width=%f height=%f>" % (self.x, self.y, self.width, self.height) return "<GRect x=%f y=%f width=%f height=%f>" % \
(self.x, self.y, self.width, self.height)
def divide_x(self, x_list, direction='l'): def divide_x(self, x_list, direction='l'):
ret_list = list() ret_list = list()
@@ -259,13 +280,17 @@ class GRect:
def draw_border_impl(en): def draw_border_impl(en):
if en == 'min_x': if en == 'min_x':
coordinates = ((self.min_x, self.min_y), (self.min_x, self.max_y)) coordinates = ((self.min_x, self.min_y),
(self.min_x, self.max_y))
elif en == 'max_x': elif en == 'max_x':
coordinates = ((self.max_x, self.min_y), (self.max_x, self.max_y)) coordinates = ((self.max_x, self.min_y),
(self.max_x, self.max_y))
elif en == 'min_y': elif en == 'min_y':
coordinates = ((self.min_x, self.min_y), (self.max_x, self.min_y)) coordinates = ((self.min_x, self.min_y),
(self.max_x, self.min_y))
elif en == 'max_y': elif en == 'max_y':
coordinates = ((self.min_x, self.max_y), (self.max_x, self.max_y)) coordinates = ((self.min_x, self.max_y),
(self.max_x, self.max_y))
else: else:
return return

View File

@@ -4,7 +4,7 @@ from typing import Tuple, List
from reportlab.lib.pagesizes import portrait, letter from reportlab.lib.pagesizes import portrait, letter
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch from reportlab.lib.units import inch
from reportlab.platypus import Paragraph, Table, Spacer from reportlab.platypus import Paragraph, Table
from ptulsconv.broadcast_timecode import TimecodeFormat from ptulsconv.broadcast_timecode import TimecodeFormat
from ptulsconv.pdf import make_doc_template from ptulsconv.pdf import make_doc_template
@@ -19,7 +19,8 @@ def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
scene_style.leftPadding = 0. scene_style.leftPadding = 0.
scene_style.spaceAfter = 18. scene_style.spaceAfter = 18.
tc_data = "<em>%s</em><br />%s" % (tc_format.seconds_to_smpte(scene[2]), tc_format.seconds_to_smpte(scene[3])) tc_data = "<em>%s</em><br />%s" % (tc_format.seconds_to_smpte(scene[2]),
tc_format.seconds_to_smpte(scene[3]))
row = [ row = [
Paragraph(tc_data, scene_style), Paragraph(tc_data, scene_style),

View File

@@ -1,7 +1,7 @@
from typing import List, Optional from typing import List, Optional
from reportlab.pdfbase import pdfmetrics # from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont # from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import inch from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter, portrait from reportlab.lib.pagesizes import letter, portrait
@@ -14,9 +14,12 @@ from .__init__ import time_format, make_doc_template
from ..docparser.adr_entity import ADRLine from ..docparser.adr_entity import ADRLine
def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_priorities=False, include_omitted=False): def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]],
show_priorities=False, include_omitted=False):
columns = list() columns = list()
reel_numbers = reel_list or sorted(set([x.reel for x in lines if x.reel is not None])) reel_numbers = reel_list or sorted(
set([x.reel for x in lines if x.reel is not None])
)
num_column_width = 15. / 32. * inch num_column_width = 15. / 32. * inch
@@ -33,7 +36,10 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
'heading': 'Role', 'heading': 'Role',
'value_getter': lambda recs: recs[0].character_name, 'value_getter': lambda recs: recs[0].character_name,
'value_getter2': lambda recs: recs[0].actor_name or "", 'value_getter2': lambda recs: recs[0].actor_name or "",
'style_getter': lambda col_index: [('LINEAFTER', (col_index, 0), (col_index, -1), 1.0, colors.black)], 'style_getter': lambda col_index: [('LINEAFTER',
(col_index, 0),
(col_index, -1),
1.0, colors.black)],
'width': 1.75 * inch, 'width': 1.75 * inch,
'summarize': False 'summarize': False
}) })
@@ -41,30 +47,48 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
columns.append({ columns.append({
'heading': 'TV', 'heading': 'TV',
'value_getter': lambda recs: len([r for r in recs if r.tv]), 'value_getter': lambda recs: len([r for r in recs if r.tv]),
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. 'value_getter2': (lambda recs:
for r in recs if r.tv])), time_format(sum([r.time_budget_mins or 0.
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'), for r in recs if r.tv]))
('LINEBEFORE', (col_index, 0), (col_index, -1), 1., colors.black), ),
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)], 'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER'),
('LINEBEFORE', (col_index, 0), (col_index, -1),
1., colors.black),
('LINEAFTER', (col_index, 0), (col_index, -1),
.5, colors.gray)]
),
'width': num_column_width 'width': num_column_width
}) })
columns.append({ columns.append({
'heading': 'Opt', 'heading': 'Opt',
'value_getter': lambda recs: len([r for r in recs if r.optional]), 'value_getter': lambda recs: len([r for r in recs if r.optional]),
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. 'value_getter2': (lambda recs:
for r in recs if r.optional])), time_format(sum([r.time_budget_mins or 0.
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'), for r in recs if r.optional]))
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)], ),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER'),
('LINEAFTER', (col_index, 0), (col_index, -1),
.5, colors.gray)]
),
'width': num_column_width 'width': num_column_width
}) })
columns.append({ columns.append({
'heading': 'Eff', 'heading': 'Eff',
'value_getter': lambda recs: len([r for r in recs if r.effort]), 'value_getter': lambda recs: len([r for r in recs if r.effort]),
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. 'value_getter2': (lambda recs:
for r in recs if r.effort])), time_format(sum([r.time_budget_mins or 0.
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')], for r in recs if r.effort]))
),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER')]
),
'width': num_column_width 'width': num_column_width
}) })
@@ -80,23 +104,26 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
}) })
if len(reel_numbers) > 0: if len(reel_numbers) > 0:
# columns.append({
# 'heading': 'RX',
# 'value_getter': lambda recs: blank_len([r for r in recs if 'Reel' not in r.keys()]),
# 'value_getter2': lambda recs: time_format(sum([r.get('Time Budget Mins', 0.) for r in recs
# if 'Reel' not in r.keys()])),
# 'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
# 'width': num_column_width
# })
for n in reel_numbers: for n in reel_numbers:
columns.append({ columns.append({
'heading': n, 'heading': n,
'value_getter': lambda recs, n1=n: len([r for r in recs if r.reel == n1]), 'value_getter': (lambda recs, n1=n:
'value_getter2': lambda recs, n1=n: time_format(sum([r.time_budget_mins or 0. for r len([r for r in recs if r.reel == n1])
in recs if r.reel == n1])), ),
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'), 'value_getter2': (lambda recs, n1=n:
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)], time_format(sum([r.time_budget_mins or 0.
for r in recs
if r.reel == n1]))
),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER'),
('LINEAFTER', (col_index, 0),
(col_index, -1),
.5, colors.gray)]
),
'width': num_column_width 'width': num_column_width
}) })
@@ -104,18 +131,26 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
for n in range(1, 6,): for n in range(1, 6,):
columns.append({ columns.append({
'heading': 'P%i' % n, 'heading': 'P%i' % n,
'value_getter': lambda recs: len([r for r in recs if r.priority == n]), 'value_getter': lambda recs: len([r for r in recs
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. if r.priority == n]),
for r in recs if r.priority == n])), 'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs
if r.priority == n]))
),
'style_getter': lambda col_index: [], 'style_getter': lambda col_index: [],
'width': num_column_width 'width': num_column_width
}) })
columns.append({ columns.append({
'heading': '>P5', 'heading': '>P5',
'value_getter': lambda recs: len([r for r in recs if (r.priority or 5) > 5]), 'value_getter': lambda recs: len([r for r in recs
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. if (r.priority or 5) > 5]),
for r in recs if (r.priority or 5) > 5])), 'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs
if (r.priority or 5) > 5]))
),
'style_getter': lambda col_index: [], 'style_getter': lambda col_index: [],
'width': num_column_width 'width': num_column_width
}) })
@@ -124,31 +159,46 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
columns.append({ columns.append({
'heading': 'Omit', 'heading': 'Omit',
'value_getter': lambda recs: len([r for r in recs if r.omitted]), 'value_getter': lambda recs: len([r for r in recs if r.omitted]),
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. 'value_getter2': (lambda recs:
for r in recs if r.omitted])), time_format(sum([r.time_budget_mins or 0.
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')], for r in recs if r.omitted]))),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER')]
),
'width': num_column_width 'width': num_column_width
}) })
columns.append({ columns.append({
'heading': 'Total', 'heading': 'Total',
'value_getter': lambda recs: len([r for r in recs if not r.omitted]), 'value_getter': lambda recs: len([r for r in recs if not r.omitted]),
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0. 'value_getter2': (lambda recs:
for r in recs if not r.omitted]), zero_str=None), time_format(
'style_getter': lambda col_index: [('LINEBEFORE', (col_index, 0), (col_index, -1), 1.0, colors.black), sum([r.time_budget_mins or 0.
('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
for r in recs if not r.omitted])
)
),
'style_getter': (lambda col_index:
[('LINEBEFORE', (col_index, 0), (col_index, -1),
1.0, colors.black),
('ALIGN', (col_index, 0), (col_index, -1),
'CENTER')]
),
'width': 0.5 * inch 'width': 0.5 * inch
}) })
return columns return columns
def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size): def populate_columns(lines: List[ADRLine], columns, include_omitted,
_page_size):
data = list() data = list()
styles = list() styles = list()
columns_widths = list() columns_widths = list()
sorted_character_numbers: List[str] = sorted(set([x.character_id for x in lines]), sorted_character_numbers: List[str] = sorted(
set([x.character_id for x in lines]),
key=lambda x: str(x)) key=lambda x: str(x))
# construct column styles # construct column styles
@@ -174,8 +224,10 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
row_data.append(col['value_getter'](list(char_records))) row_data.append(col['value_getter'](list(char_records)))
row_data2.append(col['value_getter2'](list(char_records))) row_data2.append(col['value_getter2'](list(char_records)))
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index), colors.red), styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index),
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)]) colors.red),
('LINEBELOW', (0, row2_index), (-1, row2_index),
0.5, colors.black)])
data.append(row_data) data.append(row_data)
data.append(row_data2) data.append(row_data2)
@@ -192,7 +244,8 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
summary_row1.append("") summary_row1.append("")
summary_row2.append("") summary_row2.append("")
styles.append(('LINEABOVE', (0, row1_index), (-1, row1_index), 2.0, colors.black)) styles.append(('LINEABOVE', (0, row1_index), (-1, row1_index), 2.0,
colors.black))
data.append(summary_row1) data.append(summary_row1)
data.append(summary_row2) data.append(summary_row2)
@@ -204,10 +257,13 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
# pass # pass
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False, def output_report(lines: List[ADRLine], reel_list: List[str],
page_size=portrait(letter), font_name='Helvetica'): include_omitted=False, page_size=portrait(letter),
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list) font_name='Helvetica'):
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size) columns = build_columns(lines, include_omitted=include_omitted,
reel_list=reel_list)
data, style, columns_widths = populate_columns(lines, columns,
include_omitted, page_size)
style.append(('FONTNAME', (0, 0), (-1, -1), font_name)) style.append(('FONTNAME', (0, 0), (-1, -1), font_name))
style.append(('FONTSIZE', (0, 0), (-1, -1), 9.)) style.append(('FONTSIZE', (0, 0), (-1, -1), 9.))
@@ -226,7 +282,8 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
document_header='Line Count') document_header='Line Count')
# header_data, header_style, header_widths = build_header(columns_widths) # header_data, header_style, header_widths = build_header(columns_widths)
# header_table = Table(data=header_data, style=header_style, colWidths=header_widths) # header_table = Table(data=header_data, style=header_style,
# colWidths=header_widths)
table = Table(data=data, style=style, colWidths=columns_widths) table = Table(data=data, style=style, colWidths=columns_widths)
@@ -241,6 +298,7 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
omitted_count = len([x for x in lines if x.omitted]) omitted_count = len([x for x in lines if x.omitted])
if not include_omitted and omitted_count > 0: if not include_omitted and omitted_count > 0:
story.append(Paragraph("* %i Omitted lines are excluded." % omitted_count, style)) story.append(Paragraph("* %i Omitted lines are excluded." %
omitted_count, style))
doc.build(story) doc.build(story)

View File

@@ -27,23 +27,28 @@ def build_aux_data_field(line: ADRLine):
tag_field = "" tag_field = ""
if line.effort: if line.effort:
bg_color = 'red' bg_color = 'red'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "EFF") tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
% (bg_color, fg_color, "EFF")
elif line.tv: elif line.tv:
bg_color = 'blue' bg_color = 'blue'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "TV") tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
% (bg_color, fg_color, "TV")
elif line.adlib: elif line.adlib:
bg_color = 'purple' bg_color = 'purple'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "ADLIB") tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
% (bg_color, fg_color, "ADLIB")
elif line.optional: elif line.optional:
bg_color = 'green' bg_color = 'green'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" % (bg_color, fg_color, "OPTIONAL") tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" \
% (bg_color, fg_color, "OPTIONAL")
entries.append(tag_field) entries.append(tag_field)
return "<br />".join(entries) return "<br />".join(entries)
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvetica'): def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat,
font_name='Helvetica'):
story = list() story = list()
this_scene = None this_scene = None
@@ -60,7 +65,8 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
('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.time_budget_mins) time_data = time_format(line.time_budget_mins)
@@ -79,7 +85,8 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
]] ]]
line_table = Table(data=line_table_data, line_table = Table(data=line_table_data,
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.scene or "[No Scene]") != this_scene: if (line.scene or "[No Scene]") != this_scene:
@@ -111,11 +118,12 @@ def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
return tc_data return tc_data
def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat, character_number=None, def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat,
include_omitted=True): character_number=None, include_omitted=True):
if character_number is not None: if character_number is not None:
lines = [r for r in lines if r.character_id == 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

View File

@@ -1,7 +1,7 @@
from reportlab.pdfgen.canvas import Canvas from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase import pdfmetrics # from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont # from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import inch from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter from reportlab.lib.pagesizes import letter
@@ -11,20 +11,23 @@ from reportlab.platypus import Paragraph
from .__init__ import GRect from .__init__ import GRect
from ptulsconv.broadcast_timecode import TimecodeFormat, footage_to_frame_count from ptulsconv.broadcast_timecode import TimecodeFormat
from ptulsconv.docparser.adr_entity import ADRLine from ptulsconv.docparser.adr_entity import ADRLine
import datetime import datetime
font_name = 'Helvetica' font_name = 'Helvetica'
def draw_header_block(canvas, rect, record: ADRLine): def draw_header_block(canvas, rect, record: ADRLine):
rect.draw_text_cell(canvas, record.cue_number, "Helvetica", 44, vertical_align='m') rect.draw_text_cell(canvas, record.cue_number, "Helvetica", 44,
vertical_align='m')
def draw_character_row(canvas, rect, record: ADRLine): def draw_character_row(canvas, rect, record: ADRLine):
label_frame, value_frame = rect.split_x(1.25 * inch) label_frame, value_frame = rect.split_x(1.25 * inch)
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.) label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10,
force_baseline=9.)
line = "%s / %s" % (record.character_id, record.character_name) line = "%s / %s" % (record.character_id, record.character_name)
if record.actor_name is not None: if record.actor_name is not None:
line = line + " / " + record.actor_name line = line + " / " + record.actor_name
@@ -33,7 +36,8 @@ def draw_character_row(canvas, rect, record: ADRLine):
def draw_cue_number_block(canvas, rect, record: ADRLine): def draw_cue_number_block(canvas, rect, record: ADRLine):
(label_frame, number_frame,), aux_frame = rect.divide_y([0.20 * inch, 0.375 * inch], direction='d') (label_frame, number_frame,), aux_frame = \
rect.divide_y([0.20 * inch, 0.375 * inch], direction='d')
label_frame.draw_text_cell(canvas, "CUE NUMBER", font_name, 10, label_frame.draw_text_cell(canvas, "CUE NUMBER", font_name, 10,
inset_y=5., vertical_align='t') inset_y=5., vertical_align='t')
number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14, number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14,
@@ -55,18 +59,25 @@ def draw_cue_number_block(canvas, rect, record: ADRLine):
rect.draw_border(canvas, 'max_x') rect.draw_border(canvas, 'max_x')
def draw_timecode_block(canvas, rect, record: ADRLine, tc_display_format: TimecodeFormat): def draw_timecode_block(canvas, rect, record: ADRLine,
tc_display_format: TimecodeFormat):
(in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y( (in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y(
[0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d') [0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d')
in_label_frame.draw_text_cell(canvas, "IN", font_name, 10, in_label_frame.draw_text_cell(canvas, "IN", font_name, 10,
vertical_align='t', inset_y=5., inset_x=5.) vertical_align='t', inset_y=5., inset_x=5.)
in_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.start), font_name, 14, in_frame.draw_text_cell(canvas,
inset_x=10., inset_y=2., draw_baseline=True) tc_display_format.seconds_to_smpte(record.start),
font_name, 14,
inset_x=10., inset_y=2.,
draw_baseline=True)
out_label_frame.draw_text_cell(canvas, "OUT", font_name, 10, out_label_frame.draw_text_cell(canvas, "OUT", font_name, 10,
vertical_align='t', inset_y=5., inset_x=5.) vertical_align='t', inset_y=5., inset_x=5.)
out_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.finish), font_name, 14, out_frame.draw_text_cell(canvas,
inset_x=10., inset_y=2., draw_baseline=True) tc_display_format.seconds_to_smpte(record.finish),
font_name, 14,
inset_x=10., inset_y=2.,
draw_baseline=True)
rect.draw_border(canvas, 'max_x') rect.draw_border(canvas, 'max_x')
@@ -91,13 +102,15 @@ def draw_reason_block(canvas, rect, record: ADRLine):
p = Paragraph(record.note or "", style) p = Paragraph(record.note or "", style)
notes_value.draw_flowable(canvas, p, draw_baselines=True, inset_x=5., inset_y=5.) notes_value.draw_flowable(canvas, p, draw_baselines=True,
inset_x=5., inset_y=5.)
def draw_prompt(canvas, rect, prompt=""): def draw_prompt(canvas, rect, prompt=""):
label, block = rect.split_y(0.20 * inch, direction='d') label, block = rect.split_y(0.20 * inch, direction='d')
label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.) label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t',
inset_y=5., inset_x=0.)
style = getSampleStyleSheet()['BodyText'] style = getSampleStyleSheet()['BodyText']
style.fontName = font_name style.fontName = font_name
@@ -117,7 +130,8 @@ def draw_prompt(canvas, rect, prompt=""):
def draw_notes(canvas, rect, note=""): def draw_notes(canvas, rect, note=""):
label, block = rect.split_y(0.20 * inch, direction='d') label, block = rect.split_y(0.20 * inch, direction='d')
label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.) label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t',
inset_y=5., inset_x=0.)
style = getSampleStyleSheet()['BodyText'] style = getSampleStyleSheet()['BodyText']
style.fontName = font_name style.fontName = font_name
@@ -169,31 +183,43 @@ def draw_take_grid(canvas, rect):
canvas.restoreState() canvas.restoreState()
def draw_aux_block(canvas, rect, recording_time_sec_this_line, recording_time_sec): def draw_aux_block(canvas, rect, recording_time_sec_this_line,
recording_time_sec):
rect.draw_border(canvas, 'min_x') rect.draw_border(canvas, 'min_x')
content_rect = rect.inset_xy(10., 10.) content_rect = rect.inset_xy(10., 10.)
lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.], direction='d') lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.],
direction='d')
lines[0].draw_text_cell(canvas, lines[0].draw_text_cell(canvas,
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), font_name, 9.) "Time for this line: %.1f mins" %
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), font_name, 9.) (recording_time_sec_this_line / 60.),
lines[2].draw_text_cell(canvas, "Actual Start: ______________", font_name, 9., vertical_align='b') font_name, 9.)
lines[3].draw_text_cell(canvas, "Record Date: ______________", font_name, 9., vertical_align='b') lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" %
lines[4].draw_text_cell(canvas, "Engineer: ______________", font_name, 9., vertical_align='b') (recording_time_sec / 60.), font_name, 9.)
lines[5].draw_text_cell(canvas, "Location: ______________", font_name, 9., vertical_align='b') lines[2].draw_text_cell(canvas, "Actual Start: ______________",
font_name, 9., vertical_align='b')
lines[3].draw_text_cell(canvas, "Record Date: ______________",
font_name, 9., vertical_align='b')
lines[4].draw_text_cell(canvas, "Engineer: ______________",
font_name, 9., vertical_align='b')
lines[5].draw_text_cell(canvas, "Location: ______________",
font_name, 9., vertical_align='b')
def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines): def draw_footer(canvas, rect, record: ADRLine, report_date, line_no,
total_lines):
rect.draw_border(canvas, 'max_y') rect.draw_border(canvas, 'max_y')
report_date_s = [report_date.strftime("%c")] report_date_s = [report_date.strftime("%c")]
spotting_name = [record.spot] if record.spot is not None else [] spotting_name = [record.spot] if record.spot is not None else []
pages_s = ["Line %i of %i" % (line_no, total_lines)] pages_s = ["Line %i of %i" % (line_no, total_lines)]
footer_s = " - ".join(report_date_s + spotting_name + pages_s) footer_s = " - ".join(report_date_s + spotting_name + pages_s)
rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10., inset_y=2.) rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10.,
inset_y=2.)
def create_report_for_character(records, report_date, tc_display_format: TimecodeFormat): def create_report_for_character(records, report_date,
tc_display_format: TimecodeFormat):
outfile = "%s_%s_%s_Log.pdf" % (records[0].title, outfile = "%s_%s_%s_Log.pdf" % (records[0].title,
records[0].character_id, records[0].character_id,
@@ -205,16 +231,20 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
page: GRect = GRect(0, 0, letter[0], letter[1]) page: GRect = GRect(0, 0, letter[0], letter[1])
page = page.inset(inch * 0.5) page = page.inset(inch * 0.5)
(header_row, char_row, data_row, prompt_row, notes_row, takes_row), footer = \ (header_row, char_row, data_row,
page.divide_y([0.875 * inch, 0.375 * inch, inch, 3.0 * inch, 1.5 * inch, 3 * inch], direction='d') prompt_row, notes_row, takes_row), footer = \
page.divide_y([0.875 * inch, 0.375 * inch, inch,
3.0 * inch, 1.5 * inch, 3 * inch], direction='d')
cue_header_block, title_header_block = header_row.split_x(4.0 * inch) cue_header_block, title_header_block = header_row.split_x(4.0 * inch)
(cue_number_block, timecode_block), reason_block = data_row.divide_x([1.5 * inch, 1.5 * inch]) (cue_number_block, timecode_block), reason_block = \
data_row.divide_x([1.5 * inch, 1.5 * inch])
(take_grid_block), aux_block = takes_row.split_x(5.25 * inch) (take_grid_block), aux_block = takes_row.split_x(5.25 * inch)
c = Canvas(outfile, pagesize=letter,) c = Canvas(outfile, pagesize=letter,)
c.setTitle("%s %s (%s) Supervisor's Log" % (records[0].title, records[0].character_name, c.setTitle("%s %s (%s) Supervisor's Log" % (records[0].title,
records[0].character_name,
records[0].character_id)) records[0].character_id))
c.setAuthor(records[0].supervisor) c.setAuthor(records[0].supervisor)
@@ -223,7 +253,8 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
line_n = 1 line_n = 1
for record in records: for record in records:
record: ADRLine record: ADRLine
recording_time_sec_this_line: float = (record.time_budget_mins or 6.0) * 60.0 recording_time_sec_this_line: float = (
record.time_budget_mins or 6.0) * 60.0
recording_time_sec = recording_time_sec + recording_time_sec_this_line recording_time_sec = recording_time_sec + recording_time_sec_this_line
draw_header_block(c, cue_header_block, record) draw_header_block(c, cue_header_block, record)
@@ -233,14 +264,17 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
# draw_title_box(c, title_header_block, record) # draw_title_box(c, title_header_block, record)
draw_character_row(c, char_row, record) draw_character_row(c, char_row, record)
draw_cue_number_block(c, cue_number_block, record) draw_cue_number_block(c, cue_number_block, record)
draw_timecode_block(c, timecode_block, record, tc_display_format=tc_display_format) draw_timecode_block(c, timecode_block, record,
tc_display_format=tc_display_format)
draw_reason_block(c, reason_block, record) draw_reason_block(c, reason_block, record)
draw_prompt(c, prompt_row, prompt=record.prompt) draw_prompt(c, prompt_row, prompt=record.prompt or "")
draw_notes(c, notes_row, note="") draw_notes(c, notes_row, note="")
draw_take_grid(c, take_grid_block) draw_take_grid(c, take_grid_block)
draw_aux_block(c, aux_block, recording_time_sec_this_line, recording_time_sec) draw_aux_block(c, aux_block, recording_time_sec_this_line,
recording_time_sec)
draw_footer(c, footer, record, report_date, line_no=line_n, total_lines=total_lines) draw_footer(c, footer, record, report_date, line_no=line_n,
total_lines=total_lines)
line_n = line_n + 1 line_n = line_n + 1
c.showPage() c.showPage()
@@ -254,5 +288,6 @@ def output_report(lines, tc_display_format: TimecodeFormat):
character_numbers = set([x.character_id for x in lines]) character_numbers = set([x.character_id for x in lines])
for n in character_numbers: for n in character_numbers:
create_report_for_character([e for e in events if e.character_id == n], report_date, create_report_for_character([e for e in events if e.character_id == n],
report_date,
tc_display_format=tc_display_format) tc_display_format=tc_display_format)

View File

@@ -5,36 +5,42 @@ from .__init__ import make_doc_template
from reportlab.lib.units import inch from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter from reportlab.lib.pagesizes import letter
from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table, HRFlowable from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table,\
HRFlowable
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics # from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont # from reportlab.pdfbase.ttfonts import TTFont
from ..broadcast_timecode import TimecodeFormat from ..broadcast_timecode import TimecodeFormat
from ..docparser.adr_entity import ADRLine from ..docparser.adr_entity import ADRLine
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica"): def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat,
font_name="Helvetica"):
character_numbers = set([n.character_id for n in lines]) character_numbers = set([n.character_id for n in lines])
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc')) # pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
for n in character_numbers: for n in character_numbers:
char_lines = [line for line in lines if not line.omitted and line.character_id == n] char_lines = [line for line in lines
if not line.omitted and line.character_id == n]
character_name = char_lines[0].character_name character_name = char_lines[0].character_name
char_lines = sorted(char_lines, key=lambda line: line.start) char_lines = sorted(char_lines, key=lambda line: line.start)
title = "%s (%s) %s ADR Script" % (char_lines[0].title, character_name, n) title = "%s (%s) %s ADR Script" % (char_lines[0].title,
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name) character_name, n)
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title,
n, character_name)
doc = make_doc_template(page_size=letter, filename=filename, document_title=title, doc = make_doc_template(page_size=letter, filename=filename,
document_title=title,
title=char_lines[0].title, title=char_lines[0].title,
document_subheader=char_lines[0].spot, document_subheader=char_lines[0].spot or "",
supervisor=char_lines[0].supervisor, supervisor=char_lines[0].supervisor or "",
client=char_lines[0].client, client=char_lines[0].client or "",
document_header=character_name) document_header=character_name or "")
story = [] story = []
@@ -58,7 +64,8 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat, font_
start_tc = tc_display_format.seconds_to_smpte(line.start) start_tc = tc_display_format.seconds_to_smpte(line.start)
finish_tc = tc_display_format.seconds_to_smpte(line.finish) finish_tc = tc_display_format.seconds_to_smpte(line.finish)
data_block = [[Paragraph(line.cue_number, number_style), data_block = [[Paragraph(line.cue_number, number_style),
Paragraph(start_tc + " - " + finish_tc, number_style) Paragraph(start_tc + " - " + finish_tc,
number_style)
]] ]]
# RIGHTWARDS ARROW → # RIGHTWARDS ARROW →

View File

@@ -35,13 +35,15 @@ def print_warning(warning_string):
sys.stderr.write(" - %s\n" % warning_string) sys.stderr.write(" - %s\n" % warning_string)
def print_advisory_tagging_error(failed_string, position, parent_track_name=None, clip_time=None): def print_advisory_tagging_error(failed_string, position,
parent_track_name=None, clip_time=None):
if sys.stderr.isatty(): if sys.stderr.isatty():
sys.stderr.write("\n") sys.stderr.write("\n")
sys.stderr.write(" ! \033[33;1mTagging error: \033[0m") sys.stderr.write(" ! \033[33;1mTagging error: \033[0m")
ok_string = failed_string[:position] ok_string = failed_string[:position]
not_ok_string = failed_string[position:] not_ok_string = failed_string[position:]
sys.stderr.write("\033[32m\"%s\033[31;1m%s\"\033[0m\n" % (ok_string, not_ok_string)) sys.stderr.write("\033[32m\"%s\033[31;1m%s\"\033[0m\n" %
(ok_string, not_ok_string))
if parent_track_name is not None: if parent_track_name is not None:
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name) sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)

View File

@@ -14,15 +14,20 @@ class ValidationError:
def report_message(self): def report_message(self):
if self.event is not None: if self.event is not None:
return f"{self.message}: event at {self.event.start} with number {self.event.cue_number}" return (f"{self.message}: event at {self.event.start} with number"
"{self.event.cue_number}")
else: else:
return self.message return self.message
def validate_unique_count(input_lines: Iterator[ADRLine], field='title', count=1): def validate_unique_count(input_lines: Iterator[ADRLine], field='title',
count=1):
values = set(list(map(lambda e: getattr(e, field), input_lines))) values = set(list(map(lambda e: getattr(e, field), input_lines)))
if len(values) > count: if len(values) > count:
yield ValidationError(message="Field {} has too many values (max={}): {}".format(field, count, values)) yield ValidationError(
message="Field {} has too many values (max={}): {}"
.format(field, count, values)
)
def validate_value(input_lines: Iterator[ADRLine], key_field, predicate): def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
@@ -33,7 +38,8 @@ def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
event=event) event=event)
def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number', scope=None): def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number',
scope=None):
values = dict() values = dict()
for event in input_lines: for event in input_lines:
this = getattr(event, field) this = getattr(event, field)
@@ -44,26 +50,31 @@ def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number', sc
values.setdefault(key, set()) values.setdefault(key, set())
if this in values[key]: if this in values[key]:
yield ValidationError(message='Re-used {}'.format(field), event=event) yield ValidationError(message='Re-used {}'.format(field),
event=event)
else: else:
values[key].update(this) values[key].update(this)
def validate_non_empty_field(input_lines: Iterator[ADRLine], field='cue_number'): def validate_non_empty_field(input_lines: Iterator[ADRLine],
field='cue_number'):
for event in input_lines: for event in input_lines:
if getattr(event, field, None) is None: if getattr(event, field, None) is None:
yield ValidationError(message='Empty field {}'.format(field), event=event) yield ValidationError(message='Empty field {}'.format(field),
event=event)
def validate_dependent_value(input_lines: Iterator[ADRLine], key_field, dependent_field): def validate_dependent_value(input_lines: Iterator[ADRLine], key_field,
dependent_field):
""" """
Validates that two events with the same value in `key_field` always have the Validates that two events with the same value in `key_field` always have
same value in `dependent_field` the same value in `dependent_field`
""" """
key_values = set((getattr(x, key_field) for x in input_lines)) key_values = set((getattr(x, key_field) for x in input_lines))
for key_value in key_values: for key_value in key_values:
rows = [(getattr(x, key_field), getattr(x, dependent_field)) for x in input_lines rows = [(getattr(x, key_field), getattr(x, dependent_field))
for x in input_lines
if getattr(x, key_field) == key_value] if getattr(x, key_field) == key_value]
unique_rows = set(rows) unique_rows = set(rows)
if len(unique_rows) > 1: if len(unique_rows) > 1:

View File

@@ -12,7 +12,10 @@ import ptulsconv
from ptulsconv.docparser.adr_entity import ADRLine from ptulsconv.docparser.adr_entity import ADRLine
# TODO Get a third-party test for Avid Marker lists # TODO Get a third-party test for Avid Marker lists
def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(), reel_start_frame=0, fps=24):
def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
reel_start_frame=0, fps=24):
doc = TreeBuilder(element_factory=None) doc = TreeBuilder(element_factory=None)
doc.start('Avid:StreamItems', {'xmlns:Avid': 'http://www.avid.com'}) doc.start('Avid:StreamItems', {'xmlns:Avid': 'http://www.avid.com'})
@@ -48,26 +51,35 @@ def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
for line in lines: for line in lines:
doc.start('AvClass', {'id': 'ATTR'}) doc.start('AvClass', {'id': 'ATTR'})
doc.start('AvProp', {'id': 'ATTR', 'name': '__OMFI:ATTR:NumItems', 'type': 'int32'}) doc.start('AvProp', {'id': 'ATTR',
'name': '__OMFI:ATTR:NumItems',
'type': 'int32'})
doc.data('7') doc.data('7')
doc.end('AvProp') doc.end('AvProp')
doc.start('List', {'id': 'OMFI:ATTR:AttrRefs'}) doc.start('List', {'id': 'OMFI:ATTR:AttrRefs'})
insert_elem('1', 'OMFI:ATTB:IntAttribute', 'int32', '_ATN_CRM_LONG_CREATE_DATE', report_date.strftime("%s")) insert_elem('1', 'OMFI:ATTB:IntAttribute', 'int32',
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_COLOR', 'yellow') '_ATN_CRM_LONG_CREATE_DATE', report_date.strftime("%s"))
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_USER', line.supervisor or "") insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
'_ATN_CRM_COLOR', 'yellow')
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
'_ATN_CRM_USER', line.supervisor or "")
marker_name = "%s: %s" % (line.cue_number, line.prompt) marker_name = "%s: %s" % (line.cue_number, line.prompt)
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_COM', marker_name) insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
'_ATN_CRM_COM', marker_name)
start_frame = int(line.start * fps) start_frame = int(line.start * fps)
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string', '_ATN_CRM_TC', insert_elem('2', "OMFI:ATTB:StringAttribute", 'string',
'_ATN_CRM_TC',
str(start_frame - reel_start_frame)) str(start_frame - reel_start_frame))
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string', '_ATN_CRM_TRK', 'V1') insert_elem('2', "OMFI:ATTB:StringAttribute", 'string',
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32', '_ATN_CRM_LENGTH', '1') '_ATN_CRM_TRK', 'V1')
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32',
'_ATN_CRM_LENGTH', '1')
doc.start('ListElem', {}) doc.start('ListElem', {})
doc.end('ListElem') doc.end('ListElem')
@@ -82,17 +94,22 @@ def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
def dump_fmpxml(data, input_file_name, output, adr_field_map): def dump_fmpxml(data, input_file_name, output, adr_field_map):
doc = TreeBuilder(element_factory=None) doc = TreeBuilder(element_factory=None)
doc.start('FMPXMLRESULT', {'xmlns': 'http://www.filemaker.com/fmpxmlresult'}) doc.start('FMPXMLRESULT', {'xmlns':
'http://www.filemaker.com/fmpxmlresult'})
doc.start('ERRORCODE', {}) doc.start('ERRORCODE', {})
doc.data('0') doc.data('0')
doc.end('ERRORCODE') doc.end('ERRORCODE')
doc.start('PRODUCT', {'NAME': ptulsconv.__name__, 'VERSION': ptulsconv.__version__}) doc.start('PRODUCT', {'NAME': ptulsconv.__name__,
'VERSION': ptulsconv.__version__})
doc.end('PRODUCT') doc.end('PRODUCT')
doc.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy', 'LAYOUT': 'summary', 'TIMEFORMAT': 'hh:mm:ss', doc.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy',
'RECORDS': str(len(data['events'])), 'NAME': os.path.basename(input_file_name)}) 'LAYOUT': 'summary',
'TIMEFORMAT': 'hh:mm:ss',
'RECORDS': str(len(data['events'])),
'NAME': os.path.basename(input_file_name)})
doc.end('DATABASE') doc.end('DATABASE')
doc.start('METADATA', {}) doc.start('METADATA', {})
@@ -102,7 +119,8 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
if tp is int or tp is float: if tp is int or tp is float:
ft = 'NUMBER' ft = 'NUMBER'
doc.start('FIELD', {'EMPTYOK': 'YES', 'MAXREPEAT': '1', 'NAME': field[1], 'TYPE': ft}) doc.start('FIELD', {'EMPTYOK': 'YES', 'MAXREPEAT': '1',
'NAME': field[1], 'TYPE': ft})
doc.end('FIELD') doc.end('FIELD')
doc.end('METADATA') doc.end('METADATA')
@@ -157,7 +175,8 @@ def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
print_status_style("Running xsltproc") print_status_style("Running xsltproc")
xsl_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt', xsl_name + ".xsl") xsl_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt',
xsl_name + ".xsl")
print_status_style("Using xsl: %s" % xsl_path) print_status_style("Using xsl: %s" % xsl_path)
subprocess.run(['xsltproc', xsl_path, '-'], subprocess.run(['xsltproc', xsl_path, '-'],
input=str_data, text=True, input=str_data, text=True,