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
run: |
pytest
flake8 ptulsconv

View File

@@ -5,4 +5,5 @@ Parse and convert Pro Tools text exports
__version__ = '2.0.0'
__author__ = 'Jamie Hardt'
__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 sys
from ptulsconv import __name__, __version__, __author__, __copyright__
from ptulsconv import __name__, __copyright__
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):
@@ -19,14 +21,14 @@ def dump_formats():
print_section_header_style("`raw` format:")
sys.stderr.write("A JSON document of the parsed Pro Tools export.\n")
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")
print_section_header_style("`doc` format:")
sys.stderr.write("Creates a directory with folders for different types\n"
"of ADR reports.\n\n")
def main():
"""Entry point for the command-line invocation"""
parser = OptionParser()
@@ -45,28 +47,33 @@ def main():
warn_options.add_option('-W', action='store_false',
dest='warnings',
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)
informational_options = OptionGroup(title="Informational Options",
parser=parser,
description='Print useful information and exit without processing '
description='Print useful '
'information '
'and exit without processing '
'input files.')
informational_options.add_option('--show-formats',
informational_options.add_option(
'--show-formats',
dest='show_formats',
action='store_true',
default=False,
help='Display helpful information about the '
'available output formats.')
help='Display helpful information about the available '
'output formats.')
informational_options.add_option('--show-available-tags',
informational_options.add_option(
'--show-available-tags',
dest='show_tags',
action='store_true',
default=False,
help='Display tag mappings for the FMP XML '
'output style and exit.')
help='Display tag mappings for the FMP XML output style '
'and exit.')
parser.add_option_group(informational_options)
@@ -74,9 +81,9 @@ def main():
(options, args) = parser.parse_args(sys.argv)
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:
dump_field_map()
@@ -89,7 +96,9 @@ def main():
major_mode = options.output_format
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,
warnings=options.warnings)
else:

View File

@@ -9,13 +9,15 @@ from fractions import Fraction
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.
"""
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:
return None
else:
@@ -23,29 +25,34 @@ class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps d
def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
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.
:param smpte_rep_string: The timecode string
:param frames_per_logical_second: Num of frames in a logical second. This 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
the last separator in the timecode string is a semicolon. This is ignored (and implied `False`) if
:param frames_per_logical_second: Num of frames in a logical second. This
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 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.
"""
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:
return None
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
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]:
drop_frame = False
raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * frames_per_logical_second + \
ss * frames_per_logical_second + ff
raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * \
frames_per_logical_second + ss * frames_per_logical_second + ff
frames = raw_frames
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
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:
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
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
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:
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)

View File

@@ -8,19 +8,20 @@ import os
import sys
from itertools import chain
import csv
from typing import List
from typing import List, Optional, Iterator
from fractions import Fraction
import ptsl
from .docparser.adr_entity import make_entities
from .reporting import print_section_header_style, print_status_style, print_warning
from .validations import *
from .docparser.adr_entity import make_entities, ADRLine
from .reporting import print_section_header_style, print_status_style,\
print_warning
from .validations import validate_unique_field, validate_non_empty_field,\
validate_dependent_value
from ptulsconv.docparser import parse_document
from ptulsconv.docparser.tag_compiler import TagCompiler
from ptulsconv.broadcast_timecode import TimecodeFormat
from fractions import Fraction
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
from ptulsconv.pdf.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):
"""
Writes ADR lines as CSV to the current working directory. Creates directories
for each character number and name pair, and within that directory, creates
a CSV file for each reel.
Writes ADR lines as CSV to the current working directory. Creates
directories for each character number and name pair, and within that
directory, creates a CSV file for each reel.
"""
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.chdir(dir_name)
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:
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:
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:
this_start = event.start or 0
this_finish = event.finish or 0
this_row = [event.title, event.character_name, event.cue_number,
event.reel, event.version,
time_format.seconds_to_smpte(this_start), time_format.seconds_to_smpte(this_finish),
this_row = [event.title, event.character_name,
event.cue_number, event.reel, event.version,
time_format.seconds_to_smpte(this_start),
time_format.seconds_to_smpte(this_finish),
float(this_start), float(this_finish),
event.prompt,
event.reason, event.note, "TV" if event.tv else ""]
event.reason, event.note, "TV"
if event.tv else ""]
writer.writerow(this_row)
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.
"""
@@ -105,22 +112,22 @@ def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine],
supervisor = next((x.supervisor for x in adr_lines), "")
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']
if len(adr_lines) == 0:
print_status_style("No ADR lines were found in the "
"input document. ADR reports will not be generated.")
print_status_style("No ADR lines were found in the input document. "
"ADR reports will not be generated.")
else:
create_adr_reports(adr_lines, tc_display_format=session_tc_format,
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,
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")
os.makedirs("Director Logs", exist_ok=True)
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("..")
print_status_style("Creating CSV outputs")
@@ -156,7 +164,7 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
output_talent_sides(lines, tc_display_format=tc_display_format)
def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
"""
Primary worker function, accepts the input file and decides
what to do with it based on the `major_mode`.
@@ -179,7 +187,7 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
req.time_type("tc")
req.dont_show_crossfades()
req.selected_tracks_only()
session_text = req.export_string()
session_text = req.export_string
session = parse_document(session_text)
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':
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])
# TODO: Breakdown by titles
@@ -210,7 +219,9 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
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))
if warnings:
@@ -219,11 +230,12 @@ def convert(major_mode, input_file = None, output=sys.stdout, warnings=True):
generate_documents(session_tc_format, scenes, adr_lines, title)
def perform_adr_validations(lines : Iterator[ADRLine]):
def perform_adr_validations(lines: Iterator[ADRLine]):
"""
Performs validations on the input.
"""
for warning in chain(validate_unique_field(lines,
for warning in chain(
validate_unique_field(lines,
field='cue_number',
scope='title'),
validate_non_empty_field(lines,
@@ -238,4 +250,5 @@ def perform_adr_validations(lines : Iterator[ADRLine]):
validate_dependent_value(lines,
key_field='character_id',
dependent_field='actor_name')):
print_warning(warning.report_message())

View File

@@ -1,6 +1,6 @@
"""
This module defines classes and methods for converting :class:`Event` objects into
:class:`ADRLine` objects.
This module defines classes and methods for converting :class:`Event` objects
into :class:`ADRLine` objects.
"""
from ptulsconv.docparser.tag_compiler import Event
@@ -11,15 +11,16 @@ from fractions import Fraction
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
GenricEvents by calling :func:`make_entity` on each member.
:param from_events: A list of `Event` objects.
:returns: A tuple of two lists, the first containing :class:`GenericEvent` and the
second containing :class:`ADRLine`.
:returns: A tuple of two lists, the first containing :class:`GenericEvent`
and the second containing :class:`ADRLine`.
"""
generic_events = list()
adr_lines = list()
@@ -67,14 +68,15 @@ class GenericEvent:
scene: Optional[str] = None
version: Optional[str] = None
reel: Optional[str] = None
start: Fraction = Fraction(0,1)
finish: Fraction = Fraction(0,1)
start: Fraction = Fraction(0, 1)
finish: Fraction = Fraction(0, 1)
omitted: bool = False
note: Optional[str] = None
requested_by: Optional[str] = None
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="Client", target="client"),
TagMapping(source="Sc", target="scene"),
@@ -111,9 +113,11 @@ class ADRLine(GenericEvent):
TagMapping(source="P", target="priority"),
TagMapping(source="QN", target="cue_number"),
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="Line", target="prompt", alt=TagMapping.ContentSource.Clip),
TagMapping(source="Line", target="prompt",
alt=TagMapping.ContentSource.Clip),
TagMapping(source="R", target="reason"),
TagMapping(source="Mins", target="time_budget_mins",
formatter=(lambda n: float(n))),
@@ -131,5 +135,3 @@ class ADRLine(GenericEvent):
TagMapping(source="OPT", target="optional",
formatter=(lambda x: len(x) > 0))
]

View File

@@ -21,19 +21,24 @@ class SessionDescriptor:
def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]:
for marker in self.markers:
marker_time = Fraction(marker.time_reference, int(self.header.sample_rate))
#marker_time = self.header.convert_timecode(marker.location)
marker_time = Fraction(marker.time_reference,
int(self.header.sample_rate))
# marker_time = self.header.convert_timecode(marker.location)
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 clip in track.clips:
yield track, clip
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor", "TrackClipDescriptor",
Fraction, Fraction, Fraction]]:
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor",
"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():
start_time = self.header.convert_timecode(clip.start_timecode)
@@ -105,7 +110,8 @@ class HeaderDescriptor:
if self.timecode_fps in frame_rates.keys():
return frame_rates[self.timecode_fps]
else:
raise ValueError("Unrecognized TC rate (%s)" % self.timecode_format)
raise ValueError("Unrecognized TC rate (%s)" %
self.timecode_format)
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.grammar import Grammar
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor, FileDescriptor, \
TrackClipDescriptor, ClipDescriptor, PluginDescriptor, MarkerDescriptor
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor,\
FileDescriptor, TrackClipDescriptor, ClipDescriptor, PluginDescriptor,\
MarkerDescriptor
protools_text_export_grammar = Grammar(
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
"SAMPLE RATE:" fs float_value 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 FILES:" fs integer_value rs block_ending
frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" / "23.976")
files_section = files_header files_column_header file_record* block_ending
frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" /
"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_column_header = "Filename" isp fs "Location" 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_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_column_header = "MANUFACTURER " fs "PLUG-IN NAME " fs
"VERSION " fs "FORMAT " fs "STEMS " fs
plugin_column_header = "MANUFACTURER " fs
"PLUG-IN NAME " fs
"VERSION " fs
"FORMAT " fs
"STEMS " fs
"NUMBER OF INSTANCES" rs
plugin_record = string_value fs string_value fs string_value fs
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
"STATE: " track_state_list rs
("PLUG-INS: " ( fs string_value )* rs)?
"CHANNEL " fs "EVENT " fs "CLIP NAME " fs
"START TIME " fs "END TIME " fs "DURATION " fs
"CHANNEL " fs "EVENT " fs
"CLIP NAME " fs
"START TIME " fs "END TIME " fs
"DURATION " fs
("TIMESTAMP " fs)? "STATE" rs
track_state_list = (track_state " ")*
@@ -56,15 +68,20 @@ protools_text_export_grammar = Grammar(
track_clip_entry = 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)?
track_clip_state rs
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_column_header = "# " fs "LOCATION " fs "TIME REFERENCE " fs
"UNITS " fs "NAME " fs "COMMENTS" rs
markers_column_header = "# " fs "LOCATION " fs
"TIME REFERENCE " fs
"UNITS " fs
"NAME " fs
"COMMENTS" rs
marker_record = integer_value isp fs string_value fs integer_value isp fs
string_value fs string_value fs string_value rs
@@ -125,18 +142,23 @@ class DocParserVisitor(NodeVisitor):
@staticmethod
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
def visit_clips_section(_, visited_children):
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]))
@staticmethod
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],
version=child[4],
format=child[6],

View File

@@ -24,20 +24,25 @@ class TagCompiler:
items.
"""
Intermediate = namedtuple('Intermediate', 'track_content track_tags track_comment_tags '
'clip_content clip_tags clip_tag_mode start finish')
Intermediate = namedtuple('Intermediate',
'track_content track_tags track_comment_tags '
'clip_content clip_tags clip_tag_mode start '
'finish')
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()
for element in self.parse_data():
if element.clip_tag_mode == TagPreModes.TIMESPAN:
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
@@ -73,26 +78,31 @@ class TagCompiler:
step3 = self.collect_time_spans(step2)
step4 = self.apply_tags(step3)
for datum in step4:
yield Event(clip_name=datum[0], track_name=datum[1], session_name=datum[2],
tags=datum[3], start=datum[4], finish=datum[5])
yield Event(clip_name=datum[0], track_name=datum[1],
session_name=datum[2], tags=datum[3], start=datum[4],
finish=datum[5])
def _marker_tags(self, at):
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]):
retval.update(parse_tags(marker.comments or "").tag_dict)
retval.update(parse_tags(marker.name or "").tag_dict)
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:
if clip.clip_tag_mode == 'Directive':
continue
else:
yield clip
@staticmethod
def _coalesce_tags(clip_tags: dict, track_tags: dict,
track_comment_tags: dict,
@@ -117,7 +127,8 @@ class TagCompiler:
track_comments_parsed = parse_tags(track.comments)
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_comment_tags=track_comments_parsed.tag_dict,
clip_content=clip_parsed.content,
@@ -126,15 +137,18 @@ class TagCompiler:
start=start, finish=finish)
@staticmethod
def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]:
def apply_appends(parsed: Iterator[Intermediate]) -> \
Iterator[Intermediate]:
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):
merged_tags = dict(a.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_comment_tags=a.track_comment_tags,
clip_content=a.clip_content + ' ' + b.clip_content,
@@ -158,12 +172,14 @@ class TagCompiler:
@staticmethod
def _time_span_tags(at_time: Fraction, applicable_spans) -> 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)
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)
@@ -171,14 +187,16 @@ class TagCompiler:
event: 'TagCompiler.Intermediate'
marker_tags = self._marker_tags(event.start)
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_comment_tags=event.track_comment_tags,
timespan_tags=time_span_tags,
marker_tags=marker_tags,
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,

View File

@@ -48,7 +48,8 @@ class TagMapping:
for rule in rules:
if rule.target in done:
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)
def __init__(self, source: str,

View File

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

View File

@@ -1,12 +1,14 @@
#import ffmpeg # ffmpeg-python
# import ffmpeg # ffmpeg-python
# TODO: Implement movie export
# def create_movie(event):
# 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']
# 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):

View File

@@ -17,6 +17,8 @@ from typing import List
# This is from https://code.activestate.com/recipes/576832/ for
# generating page count messages
class ReportCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
@@ -38,10 +40,12 @@ class ReportCanvas(canvas.Canvas):
def draw_page_number(self, page_count):
self.saveState()
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.setFont('Helvetica', 10) # FIXME make this customizable
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("%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.moveTo(0.5 * inch, 0.75 * inch)
@@ -77,12 +81,14 @@ def make_doc_template(page_size, filename, document_title,
on_page_lambda = (lambda c, _:
draw_header_footer(c, report_box, title_box,
footer_box,title=title,
footer_box, title=title,
supervisor=supervisor,
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",
frames=frames,
@@ -119,12 +125,17 @@ def time_format(mins, zero_str="-"):
return "%i:%02i" % (hh, mm)
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str,
document_subheader: str, client: str, doc_title="", font_name='Helvetica'):
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box,
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., ])
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.)
(_supervisor_box, client_box,), title_box = \
right_box.divide_y([16., 16., ])
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.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.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:
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:
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:
@@ -201,10 +216,12 @@ class GRect:
else:
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))
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))
GRect(self.min_x, self.y,
self.width - at, self.height))
def split_y(self, at, direction='u'):
if at >= self.height:
@@ -214,19 +231,23 @@ class GRect:
else:
if direction == 'u':
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:
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):
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):
return self.inset_xy(d, d)
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'):
ret_list = list()
@@ -259,13 +280,17 @@ class GRect:
def draw_border_impl(en):
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':
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':
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':
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:
return

View File

@@ -4,7 +4,7 @@ from typing import Tuple, List
from reportlab.lib.pagesizes import portrait, letter
from reportlab.lib.styles import getSampleStyleSheet
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.pdf import make_doc_template
@@ -12,14 +12,15 @@ from ptulsconv.pdf import make_doc_template
# TODO: A Continuity
def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
def table_for_scene(scene, tc_format, font_name='Helvetica'):
scene_style = getSampleStyleSheet()['Normal']
scene_style.fontName = font_name
scene_style.leftIndent = 0.
scene_style.leftPadding = 0.
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 = [
Paragraph(tc_data, scene_style),
@@ -36,7 +37,7 @@ def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
def output_report(scenes: List[Tuple[str, str, Fraction, Fraction]],
tc_display_format: TimecodeFormat,
title: str, client: str, supervisor, paper_size = letter):
title: str, client: str, supervisor, paper_size=letter):
filename = "%s Continuity.pdf" % title
document_header = "Continuity"

View File

@@ -1,7 +1,7 @@
from typing import List, Optional
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# from reportlab.pdfbase import pdfmetrics
# from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import inch
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
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()
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
@@ -33,7 +36,10 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
'heading': 'Role',
'value_getter': lambda recs: recs[0].character_name,
'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,
'summarize': False
})
@@ -41,30 +47,48 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
columns.append({
'heading': '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.
for r in recs if r.tv])),
'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)],
'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs if r.tv]))
),
'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
})
columns.append({
'heading': 'Opt',
'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.
for r in recs if r.optional])),
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'),
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)],
'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs if r.optional]))
),
'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
})
columns.append({
'heading': 'Eff',
'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.
for r in recs if r.effort])),
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs if r.effort]))
),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER')]
),
'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:
# 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:
columns.append({
'heading': n,
'value_getter': lambda recs, n1=n: len([r for r in recs if r.reel == n1]),
'value_getter2': lambda recs, n1=n: 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)],
'value_getter': (lambda recs, n1=n:
len([r for r in recs if r.reel == n1])
),
'value_getter2': (lambda recs, n1=n:
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
})
@@ -104,18 +131,26 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
for n in range(1, 6,):
columns.append({
'heading': 'P%i' % n,
'value_getter': lambda recs: len([r 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])),
'value_getter': lambda recs: len([r 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: [],
'width': num_column_width
})
columns.append({
'heading': '>P5',
'value_getter': lambda recs: len([r 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])),
'value_getter': lambda recs: len([r 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: [],
'width': num_column_width
})
@@ -124,31 +159,46 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
columns.append({
'heading': 'Omit',
'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.
for r in recs if r.omitted])),
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
'value_getter2': (lambda recs:
time_format(sum([r.time_budget_mins or 0.
for r in recs if r.omitted]))),
'style_getter': (lambda col_index:
[('ALIGN', (col_index, 0), (col_index, -1),
'CENTER')]
),
'width': num_column_width
})
columns.append({
'heading': 'Total',
'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.
for r in recs if not r.omitted]), zero_str=None),
'style_getter': lambda col_index: [('LINEBEFORE', (col_index, 0), (col_index, -1), 1.0, colors.black),
('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
'value_getter2': (lambda recs:
time_format(
sum([r.time_budget_mins or 0.
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
})
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()
styles = 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))
# 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_data2.append(col['value_getter2'](list(char_records)))
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index), colors.red),
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)])
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index),
colors.red),
('LINEBELOW', (0, row2_index), (-1, row2_index),
0.5, colors.black)])
data.append(row_data)
data.append(row_data2)
@@ -192,7 +244,8 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
summary_row1.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_row2)
@@ -204,17 +257,20 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
# pass
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False,
page_size=portrait(letter), font_name='Helvetica'):
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size)
def output_report(lines: List[ADRLine], reel_list: List[str],
include_omitted=False, page_size=portrait(letter),
font_name='Helvetica'):
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(('FONTSIZE', (0, 0), (-1, -1), 9.))
style.append(('LINEBELOW', (0, 0), (-1, 0), 1.0, colors.black))
# style.append(('LINEBELOW', (0, 1), (-1, -1), 0.25, colors.gray))
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
title = "%s Line Count" % lines[0].title
filename = title + '.pdf'
@@ -226,7 +282,8 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
document_header='Line Count')
# 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)
@@ -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])
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)

View File

@@ -27,23 +27,28 @@ def build_aux_data_field(line: ADRLine):
tag_field = ""
if line.effort:
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:
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:
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:
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)
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()
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),
('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)
@@ -79,7 +85,8 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
]]
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)
if (line.scene or "[No Scene]") != this_scene:
@@ -111,11 +118,12 @@ def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
return tc_data
def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat, character_number=None,
include_omitted=True):
def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat,
character_number=None, include_omitted=True):
if character_number is not None:
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
else:
title = "%s ADR Report" % lines[0].title

View File

@@ -1,7 +1,7 @@
from reportlab.pdfgen.canvas import Canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# from reportlab.pdfbase import pdfmetrics
# from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter
@@ -11,20 +11,23 @@ from reportlab.platypus import Paragraph
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
import datetime
font_name = 'Helvetica'
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):
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)
if record.actor_name is not None:
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):
(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,
inset_y=5., vertical_align='t')
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')
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(
[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,
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,
inset_x=10., inset_y=2., draw_baseline=True)
in_frame.draw_text_cell(canvas,
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,
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,
inset_x=10., inset_y=2., draw_baseline=True)
out_frame.draw_text_cell(canvas,
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')
@@ -91,13 +102,15 @@ def draw_reason_block(canvas, rect, record: ADRLine):
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=""):
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.fontName = font_name
@@ -117,7 +130,8 @@ def draw_prompt(canvas, rect, prompt=""):
def draw_notes(canvas, rect, note=""):
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.fontName = font_name
@@ -169,31 +183,43 @@ def draw_take_grid(canvas, rect):
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')
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,
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), font_name, 9.)
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), font_name, 9.)
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')
"Time for this line: %.1f mins" %
(recording_time_sec_this_line / 60.),
font_name, 9.)
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" %
(recording_time_sec / 60.), font_name, 9.)
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')
report_date_s = [report_date.strftime("%c")]
spotting_name = [record.spot] if record.spot is not None else []
pages_s = ["Line %i of %i" % (line_no, total_lines)]
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,
records[0].character_id,
@@ -201,20 +227,24 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
assert outfile is not None
assert outfile[-4:] == '.pdf', "Output file must have 'pdf' extension!"
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
page: GRect = GRect(0, 0, letter[0], letter[1])
page = page.inset(inch * 0.5)
(header_row, char_row, data_row, 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')
(header_row, char_row, data_row,
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_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)
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))
c.setAuthor(records[0].supervisor)
@@ -223,7 +253,8 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
line_n = 1
for record in records:
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
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_character_row(c, char_row, 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_prompt(c, prompt_row, prompt=record.prompt)
draw_prompt(c, prompt_row, prompt=record.prompt or "")
draw_notes(c, notes_row, note="")
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
c.showPage()
@@ -254,5 +288,6 @@ def output_report(lines, tc_display_format: TimecodeFormat):
character_numbers = set([x.character_id for x in lines])
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)

View File

@@ -5,36 +5,42 @@ from .__init__ import make_doc_template
from reportlab.lib.units import inch
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 import colors
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# from reportlab.pdfbase import pdfmetrics
# from reportlab.pdfbase.ttfonts import TTFont
from ..broadcast_timecode import TimecodeFormat
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])
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
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
char_lines = sorted(char_lines, key=lambda line: line.start)
title = "%s (%s) %s ADR Script" % (char_lines[0].title, character_name, n)
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
title = "%s (%s) %s ADR Script" % (char_lines[0].title,
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,
document_subheader=char_lines[0].spot,
supervisor=char_lines[0].supervisor,
client=char_lines[0].client,
document_header=character_name)
document_subheader=char_lines[0].spot or "",
supervisor=char_lines[0].supervisor or "",
client=char_lines[0].client or "",
document_header=character_name or "")
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)
finish_tc = tc_display_format.seconds_to_smpte(line.finish)
data_block = [[Paragraph(line.cue_number, number_style),
Paragraph(start_tc + " - " + finish_tc, number_style)
Paragraph(start_tc + " - " + finish_tc,
number_style)
]]
# RIGHTWARDS ARROW →

View File

@@ -35,13 +35,15 @@ def print_warning(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():
sys.stderr.write("\n")
sys.stderr.write(" ! \033[33;1mTagging error: \033[0m")
ok_string = failed_string[:position]
not_ok_string = failed_string[position:]
sys.stderr.write("\033[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:
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)

View File

@@ -14,15 +14,20 @@ class ValidationError:
def report_message(self):
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:
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)))
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):
@@ -33,7 +38,8 @@ def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
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()
for event in input_lines:
this = getattr(event, field)
@@ -44,26 +50,31 @@ def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number', sc
values.setdefault(key, set())
if this in values[key]:
yield ValidationError(message='Re-used {}'.format(field), event=event)
yield ValidationError(message='Re-used {}'.format(field),
event=event)
else:
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:
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
same value in `dependent_field`
Validates that two events with the same value in `key_field` always have
the same value in `dependent_field`
"""
key_values = set((getattr(x, key_field) for x in input_lines))
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]
unique_rows = set(rows)
if len(unique_rows) > 1:

View File

@@ -12,7 +12,10 @@ import ptulsconv
from ptulsconv.docparser.adr_entity import ADRLine
# 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.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:
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.end('AvProp')
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('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_COLOR', 'yellow')
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_USER', line.supervisor or "")
insert_elem('1', 'OMFI:ATTB:IntAttribute', 'int32',
'_ATN_CRM_LONG_CREATE_DATE', report_date.strftime("%s"))
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)
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)
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))
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string', '_ATN_CRM_TRK', 'V1')
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32', '_ATN_CRM_LENGTH', '1')
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string',
'_ATN_CRM_TRK', 'V1')
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32',
'_ATN_CRM_LENGTH', '1')
doc.start('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):
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.data('0')
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.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy', 'LAYOUT': 'summary', 'TIMEFORMAT': 'hh:mm:ss',
'RECORDS': str(len(data['events'])), 'NAME': os.path.basename(input_file_name)})
doc.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy',
'LAYOUT': 'summary',
'TIMEFORMAT': 'hh:mm:ss',
'RECORDS': str(len(data['events'])),
'NAME': os.path.basename(input_file_name)})
doc.end('DATABASE')
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:
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('METADATA')
@@ -157,7 +175,8 @@ def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
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)
subprocess.run(['xsltproc', xsl_path, '-'],
input=str_data, text=True,