mirror of
https://github.com/iluvcapra/ptulsconv.git
synced 2026-10-01 17:27:27 +00:00
Bug ruff 2026 (#19)
* Updated Ruff to v0.16 and fixed for default checks * Nudge version and removed license classifier --------- Co-authored-by: Jamie Hardt <jamie@squad51.us>
This commit is contained in:
@@ -109,3 +109,4 @@ lcov.info
|
||||
|
||||
.vim
|
||||
.vscode
|
||||
uv.lock
|
||||
|
||||
@@ -18,6 +18,7 @@ build:
|
||||
- echo "Command run at 'post_build' step"
|
||||
- echo `date`
|
||||
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
+14
-17
@@ -4,19 +4,18 @@
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
print(sys.path)
|
||||
|
||||
import ptulsconv
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'ptulsconv'
|
||||
copyright = '2019-2025 Jamie Hardt. All rights reserved'
|
||||
project = "ptulsconv"
|
||||
copyright = "2019-2025 Jamie Hardt. All rights reserved"
|
||||
version = "Version 2"
|
||||
release = importlib.metadata.version("ptulsconv")
|
||||
|
||||
@@ -24,31 +23,29 @@ release = importlib.metadata.version("ptulsconv")
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.githubpages',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.githubpages",
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
|
||||
latex_documents = [
|
||||
(master_doc, 'ptulsconv.tex', u'ptulsconv Documentation',
|
||||
u'Jamie Hardt', 'manual'),
|
||||
(master_doc, "ptulsconv.tex", "ptulsconv Documentation", "Jamie Hardt", "manual"),
|
||||
]
|
||||
|
||||
|
||||
@@ -67,7 +64,7 @@ epub_title = project
|
||||
# epub_uid = ''
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
epub_exclude_files = ["search.html"]
|
||||
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
+10
-4
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "ptulsconv"
|
||||
version = "4.0.0"
|
||||
version = "4.0.1"
|
||||
description = "Read Pro Tools Text exports and generate PDF ADR Reports, JSON"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
@@ -9,7 +9,6 @@ license-files = ["LICENSE"]
|
||||
keywords = ["text-processing", "parsers", "film",
|
||||
"broadcast", "editing", "editorial"]
|
||||
classifiers = [
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Topic :: Multimedia',
|
||||
'Topic :: Multimedia :: Sound/Audio',
|
||||
"Programming Language :: Python :: 3.9",
|
||||
@@ -42,17 +41,24 @@ doc = [
|
||||
"sphinx-rtd-theme>=3.0.2",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
ignore = [
|
||||
"DTZ005" # datetime created without a tz
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"reaper/Export Items as Text.py" = ["F821"]
|
||||
|
||||
[project.scripts]
|
||||
ptulsconv = "ptulsconv:__main__.main"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.8.18,<0.9.0"]
|
||||
requires = ["uv_build>=0.8.18,<0.12.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff==0.13.1",
|
||||
"ruff>=0.16.8",
|
||||
]
|
||||
doc = [
|
||||
"sphinx>=7.4.7",
|
||||
|
||||
@@ -1,47 +1,68 @@
|
||||
# type: ignore[reportUndefinedVariable]
|
||||
|
||||
# Export Items as Text.py
|
||||
# (c) 2021 Jamie Hardt. All rights reserved.
|
||||
#
|
||||
#
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import datetime
|
||||
import json
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
item_records = list()
|
||||
item_records = []
|
||||
|
||||
for i in range(0, RPR_CountMediaItems(0) ):
|
||||
for i in range(RPR_CountMediaItems(0)):
|
||||
this_item = RPR_GetMediaItem(0, i)
|
||||
|
||||
item_record = {}
|
||||
item_record["mute"] = True if RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0. else False
|
||||
|
||||
item_record["mute"] = RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0.0
|
||||
|
||||
item_record["duration"] = RPR_GetMediaItemInfo_Value(this_item, "D_LENGTH")
|
||||
_, item_record["duration_tc"], _, _, _ = RPR_format_timestr_len(item_record["duration"], "", 128, 0., 5)
|
||||
_, item_record["duration_tc"], _, _, _ = RPR_format_timestr_len(
|
||||
item_record["duration"], "", 128, 0.0, 5
|
||||
)
|
||||
|
||||
item_record["position"] = RPR_GetMediaItemInfo_Value(this_item, "D_POSITION")
|
||||
_, item_record["position_tc"], _, _ = RPR_format_timestr_pos(item_record["position"], "", 128, 5)
|
||||
_, item_record["position_tc"], _, _ = RPR_format_timestr_pos(
|
||||
item_record["position"], "", 128, 5
|
||||
)
|
||||
|
||||
item_record["selected"] = True if RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0. else False
|
||||
_, _, _, item_record["notes"], _ = RPR_GetSetMediaItemInfo_String(this_item, "P_NOTES", "", False)
|
||||
_, _, _, item_record["item_guid"], _ = RPR_GetSetMediaItemInfo_String(this_item, "GUID", "", False)
|
||||
item_record["selected"] = RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0.0
|
||||
_, _, _, item_record["notes"], _ = RPR_GetSetMediaItemInfo_String(
|
||||
this_item, "P_NOTES", "", False
|
||||
)
|
||||
_, _, _, item_record["item_guid"], _ = RPR_GetSetMediaItemInfo_String(
|
||||
this_item, "GUID", "", False
|
||||
)
|
||||
|
||||
active_take = RPR_GetActiveTake(this_item)
|
||||
_, _, _, item_record["active_take_name"], _ = RPR_GetSetMediaItemTakeInfo_String(active_take, "P_NAME", "", False)
|
||||
_, _, _, item_record["active_take_guid"], _ = RPR_GetSetMediaItemTakeInfo_String(active_take, "GUID", "", False)
|
||||
_, _, _, item_record["active_take_name"], _ = RPR_GetSetMediaItemTakeInfo_String(
|
||||
active_take, "P_NAME", "", False
|
||||
)
|
||||
_, _, _, item_record["active_take_guid"], _ = RPR_GetSetMediaItemTakeInfo_String(
|
||||
active_take, "GUID", "", False
|
||||
)
|
||||
|
||||
item_track = RPR_GetMediaItemTrack(this_item)
|
||||
_, _, _, item_record["track_name"], _ = RPR_GetSetMediaTrackInfo_String(item_track, "P_NAME", "", False)
|
||||
_, _, _, item_record["track_guid"], _ = RPR_GetSetMediaTrackInfo_String(item_track, "GUID", "", False)
|
||||
item_record["track_index"] = RPR_GetMediaTrackInfo_Value(item_track, "IP_TRACKNUMBER")
|
||||
item_record["track_muted"] = True if RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0. else False
|
||||
_, _, _, item_record["track_name"], _ = RPR_GetSetMediaTrackInfo_String(
|
||||
item_track, "P_NAME", "", False
|
||||
)
|
||||
_, _, _, item_record["track_guid"], _ = RPR_GetSetMediaTrackInfo_String(
|
||||
item_track, "GUID", "", False
|
||||
)
|
||||
item_record["track_index"] = RPR_GetMediaTrackInfo_Value(
|
||||
item_track, "IP_TRACKNUMBER"
|
||||
)
|
||||
item_record["track_muted"] = RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0.0
|
||||
|
||||
item_records = item_records + [item_record]
|
||||
|
||||
output = dict()
|
||||
output = {}
|
||||
output["items"] = item_records
|
||||
_, output["project_title"], _ = RPR_GetProjectName(0, "", 1024)
|
||||
_, _, output["project_author"], _ = RPR_GetSetProjectAuthor(0, False, "", 1024)
|
||||
output["project_frame_rate"], _, output["project_drop_frame"] = RPR_TimeMap_curFrameRate(0, True)
|
||||
output["project_frame_rate"], _, output["project_drop_frame"] = (
|
||||
RPR_TimeMap_curFrameRate(0, True)
|
||||
)
|
||||
|
||||
output_path, _ = RPR_GetProjectPath("", 1024)
|
||||
|
||||
@@ -51,12 +72,15 @@ output_title = output["project_title"]
|
||||
if output_title == "":
|
||||
output_title = "unsaved project"
|
||||
|
||||
output_file_name = "%s Text Export %s.txt" % (output_title, now.strftime('%Y%m%d_%H%M'))
|
||||
output_file_name = f"{output_title} Text Export {now.strftime('%Y%m%d_%H%M')}.txt"
|
||||
output_path = output_path + "/" + output_file_name
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(output, f, allow_nan=True, indent=4)
|
||||
|
||||
RPR_ShowMessageBox("Exported text file \"%s\" to project folder." % output_file_name, "Text Export Complete", 0)
|
||||
RPR_ShowMessageBox(
|
||||
f'Exported text file "{output_file_name}" to project folder. Text Export Complete',
|
||||
0,
|
||||
)
|
||||
|
||||
#RPR_ShowConsoleMsg(output_path)
|
||||
# RPR_ShowConsoleMsg(output_path)
|
||||
|
||||
+65
-70
@@ -1,23 +1,24 @@
|
||||
from optparse import OptionParser, OptionGroup
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
import importlib.metadata
|
||||
import sys
|
||||
from optparse import OptionGroup, OptionParser
|
||||
|
||||
from ptulsconv import __name__
|
||||
import ptulsconv
|
||||
from ptulsconv import __name__
|
||||
from ptulsconv.commands import convert
|
||||
from ptulsconv.reporting import print_status_style, \
|
||||
print_banner_style, print_section_header_style, \
|
||||
print_fatal_error
|
||||
from ptulsconv.docparser.adr_entity import ADR_TAG_MAPPING, GENERIC_TAG_MAPPING
|
||||
from ptulsconv.reporting import (
|
||||
print_banner_style,
|
||||
print_section_header_style,
|
||||
print_status_style,
|
||||
)
|
||||
|
||||
|
||||
def dump_field_map(output=sys.stdout):
|
||||
from ptulsconv.docparser.tag_mapping import TagMapping
|
||||
from ptulsconv.docparser.adr_entity import ADRLine, GenericEvent
|
||||
|
||||
TagMapping.print_rules(GenericEvent, output=output)
|
||||
TagMapping.print_rules(ADRLine, output=output)
|
||||
TagMapping.print_rules(GENERIC_TAG_MAPPING, output=output)
|
||||
TagMapping.print_rules(ADR_TAG_MAPPING, output=output)
|
||||
|
||||
|
||||
def dump_formats():
|
||||
@@ -26,10 +27,12 @@ def dump_formats():
|
||||
print_section_header_style("`tagged` Format:")
|
||||
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:")
|
||||
sys.stderr.write("Creates a directory with folders for different types\n"
|
||||
"of ADR reports.\n\n")
|
||||
sys.stderr.write(
|
||||
"Creates a directory with folders for different types\nof ADR reports.\n\n"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -37,54 +40,60 @@ def main():
|
||||
parser = OptionParser()
|
||||
parser.usage = "ptulsconv [options] [TEXT_EXPORT.txt]"
|
||||
|
||||
parser.add_option('-f', '--format',
|
||||
dest='output_format',
|
||||
metavar='FMT',
|
||||
choices=['raw', 'tagged', 'doc'],
|
||||
default='doc',
|
||||
help='Set output format, `raw`, `tagged`, `doc`.')
|
||||
parser.add_option(
|
||||
"-f",
|
||||
"--format",
|
||||
dest="output_format",
|
||||
metavar="FMT",
|
||||
choices=["raw", "tagged", "doc"],
|
||||
default="doc",
|
||||
help="Set output format, `raw`, `tagged`, `doc`.",
|
||||
)
|
||||
|
||||
parser.add_option('-m', '--movie-opts',
|
||||
dest='movie_opts',
|
||||
metavar="MOVIE_OPTS",
|
||||
help="Set movie options")
|
||||
parser.add_option(
|
||||
"-m",
|
||||
"--movie-opts",
|
||||
dest="movie_opts",
|
||||
metavar="MOVIE_OPTS",
|
||||
help="Set movie options",
|
||||
)
|
||||
|
||||
warn_options = OptionGroup(title="Warning and Validation Options",
|
||||
parser=parser)
|
||||
warn_options = OptionGroup(title="Warning and Validation Options", parser=parser)
|
||||
|
||||
warn_options.add_option('-W', action='store_false',
|
||||
dest='warnings',
|
||||
default=True,
|
||||
help='Suppress warnings for common '
|
||||
'errors (missing code numbers etc.)')
|
||||
warn_options.add_option(
|
||||
"-W",
|
||||
action="store_false",
|
||||
dest="warnings",
|
||||
default=True,
|
||||
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 '
|
||||
'input files.')
|
||||
informational_options = OptionGroup(
|
||||
title="Informational Options",
|
||||
parser=parser,
|
||||
description="Print useful information and exit without processing input files.",
|
||||
)
|
||||
|
||||
informational_options.add_option(
|
||||
'--show-formats',
|
||||
dest='show_formats',
|
||||
action='store_true',
|
||||
"--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',
|
||||
dest='show_tags',
|
||||
action='store_true',
|
||||
"--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)
|
||||
|
||||
|
||||
version = importlib.metadata.version(ptulsconv.__name__)
|
||||
print_banner_style(f"{ptulsconv.__name__} - version {version}")
|
||||
print_banner_style(ptulsconv.__copyright__)
|
||||
@@ -92,8 +101,7 @@ 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(f"This run started {datetime.datetime.now().isoformat()}")
|
||||
|
||||
if options.show_tags:
|
||||
dump_field_map()
|
||||
@@ -102,29 +110,16 @@ def main():
|
||||
elif options.show_formats:
|
||||
dump_formats()
|
||||
sys.exit(0)
|
||||
try:
|
||||
major_mode = options.output_format
|
||||
|
||||
if len(args) < 2:
|
||||
print_status_style(
|
||||
"No input file provided, will connect to Pro Tools "
|
||||
"with PTSL...")
|
||||
convert(major_mode=major_mode,
|
||||
warnings=options.warnings)
|
||||
else:
|
||||
convert(input_file=args[1],
|
||||
major_mode=major_mode,
|
||||
warnings=options.warnings)
|
||||
major_mode = options.output_format
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print_fatal_error("Error trying to read input file")
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print_fatal_error("Error trying to convert file")
|
||||
print("\033[31m" + e.__repr__() + "\033[0m", file=sys.stderr)
|
||||
print(traceback.format_exc())
|
||||
if len(args) < 2:
|
||||
print_status_style(
|
||||
"No input file provided, will connect to Pro Tools with PTSL..."
|
||||
)
|
||||
convert(major_mode=major_mode, warnings=options.warnings)
|
||||
else:
|
||||
convert(input_file=args[1], major_mode=major_mode, warnings=options.warnings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,35 +2,40 @@
|
||||
Useful functions for parsing and working with timecode.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from fractions import Fraction
|
||||
from typing import Optional, SupportsFloat
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class TimecodeFormat(namedtuple("_TimecodeFormat",
|
||||
"frame_duration logical_fps drop_frame")):
|
||||
class TimecodeFormat(NamedTuple):
|
||||
"""
|
||||
A struct reperesenting a timecode datum.
|
||||
"""
|
||||
|
||||
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
|
||||
frame_duration: Fraction
|
||||
logical_fps: int
|
||||
drop_frame: bool
|
||||
|
||||
def smpte_to_seconds(self, smpte: str) -> Fraction | None:
|
||||
frame_count = smpte_to_frame_count(
|
||||
smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
|
||||
smpte, self.logical_fps, drop_frame_hint=self.drop_frame
|
||||
)
|
||||
if frame_count is None:
|
||||
return None
|
||||
else:
|
||||
return frame_count * self.frame_duration
|
||||
|
||||
def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
|
||||
def seconds_to_smpte(self, seconds: Fraction) -> str:
|
||||
frame_count = int(seconds / self.frame_duration)
|
||||
return frame_count_to_smpte(frame_count, self.logical_fps,
|
||||
self.drop_frame)
|
||||
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
|
||||
) -> int | None:
|
||||
"""
|
||||
Convert a string with a SMPTE timecode representation into a frame count.
|
||||
|
||||
@@ -44,15 +49,13 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
||||
"""
|
||||
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 == ";":
|
||||
@@ -61,12 +64,16 @@ 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:
|
||||
frames_dropped_per_inst = (frames_per_logical_second / 15)
|
||||
frames_dropped_per_inst = frames_per_logical_second / 15
|
||||
mins = hh * 60 + mm
|
||||
inst_count = mins - math.floor(mins / 10)
|
||||
dropped_frames = int(frames_dropped_per_inst) * inst_count
|
||||
@@ -75,9 +82,12 @@ 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,
|
||||
fractional_frame: Optional[float] = None) -> str:
|
||||
def frame_count_to_smpte(
|
||||
frame_count: int,
|
||||
frames_per_logical_second: int,
|
||||
drop_frame: bool = False,
|
||||
fractional_frame: float | None = None,
|
||||
) -> str:
|
||||
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
||||
assert fractional_frame is None or fractional_frame < 1.0
|
||||
|
||||
@@ -86,7 +96,7 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int,
|
||||
if drop_frame:
|
||||
assert frames_per_logical_second in [30, 60]
|
||||
mins, _ = divmod(nominal_frames, frames_per_logical_second * 60)
|
||||
frames_dropped_per_inst = (frames_per_logical_second / 15)
|
||||
frames_dropped_per_inst = frames_per_logical_second / 15
|
||||
inst_count = mins - math.floor(mins / 10)
|
||||
dropped_frames = frames_dropped_per_inst * inst_count
|
||||
nominal_frames = nominal_frames + dropped_frames
|
||||
@@ -97,15 +107,22 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int,
|
||||
ss, ff = divmod(rem, frames_per_logical_second)
|
||||
|
||||
hh = hh % 24
|
||||
|
||||
hh = int(hh)
|
||||
mm = int(mm)
|
||||
ss = int(ss)
|
||||
ff = int(ff)
|
||||
|
||||
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:])
|
||||
fpart = "{fractional_frame:.3f}"[1:]
|
||||
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}{fpart}"
|
||||
else:
|
||||
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}"
|
||||
# "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||
|
||||
|
||||
def footage_to_frame_count(footage_string) -> Optional[int]:
|
||||
m = re.search(r'(\d+)\+(\d+)(\.\d+)?', footage_string)
|
||||
def footage_to_frame_count(footage_string) -> int | None:
|
||||
m = re.search(r"(\d+)\+(\d+)(\.\d+)?", footage_string)
|
||||
if m is None:
|
||||
return None
|
||||
feet, frm, frac = m.groups()
|
||||
@@ -118,4 +135,4 @@ def footage_to_frame_count(footage_string) -> Optional[int]:
|
||||
|
||||
def frame_count_to_footage(frame_count):
|
||||
feet, frm = divmod(frame_count, 16)
|
||||
return "%i+%02i" % (feet, frm)
|
||||
return f"{feet}+{frm:02}"
|
||||
|
||||
+116
-96
@@ -2,34 +2,36 @@
|
||||
This module provides the main input document parsing and transform
|
||||
implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import sys
|
||||
from itertools import chain
|
||||
import csv
|
||||
from typing import List, Optional, Iterator
|
||||
from collections.abc import Iterator
|
||||
from fractions import Fraction
|
||||
from itertools import chain
|
||||
from json import JSONEncoder
|
||||
|
||||
import ptsl
|
||||
|
||||
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.broadcast_timecode import TimecodeFormat
|
||||
from ptulsconv.docparser import parse_document
|
||||
from ptulsconv.docparser.tag_compiler import TagCompiler
|
||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||
|
||||
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
|
||||
from ptulsconv.pdf.line_count import output_report as output_line_count
|
||||
from ptulsconv.pdf.talent_sides import output_report as output_talent_sides
|
||||
from ptulsconv.pdf.summary_log import output_report as output_summary
|
||||
from ptulsconv.pdf.continuity import output_report as output_continuity
|
||||
from ptulsconv.pdf.line_count import output_report as output_line_count
|
||||
from ptulsconv.pdf.summary_log import output_report as output_summary
|
||||
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
|
||||
from ptulsconv.pdf.talent_sides import output_report as output_talent_sides
|
||||
|
||||
from json import JSONEncoder
|
||||
from .docparser.adr_entity import ADRLine, make_entities
|
||||
from .reporting import print_section_header_style, print_status_style, print_warning
|
||||
from .validations import (
|
||||
validate_dependent_value,
|
||||
validate_non_empty_field,
|
||||
validate_unique_field,
|
||||
)
|
||||
|
||||
|
||||
class FractionEncoder(JSONEncoder):
|
||||
@@ -37,97 +39,121 @@ class FractionEncoder(JSONEncoder):
|
||||
A subclass of :class:`JSONEncoder` which encodes :class:`Fraction` objects
|
||||
as a dict.
|
||||
"""
|
||||
force_denominator: Optional[int]
|
||||
|
||||
force_denominator: int | None
|
||||
|
||||
def default(self, o):
|
||||
"""
|
||||
|
||||
"""
|
||||
if isinstance(o, Fraction):
|
||||
return dict(numerator=o.numerator, denominator=o.denominator)
|
||||
return {"numerator": o.numerator, "denominator": o.denominator}
|
||||
else:
|
||||
return o.__dict__
|
||||
|
||||
|
||||
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 for each character number and name pair, and within that
|
||||
directory, creates a CSV file for each reel.
|
||||
"""
|
||||
|
||||
reels: set[str | None] = set([ln.reel for ln in lines])
|
||||
reels: set[str | None] = {ln.reel for ln in lines}
|
||||
for n, name in [(n.character_id, n.character_name) for n in lines]:
|
||||
dir_name = "%s_%s" % (n, name)
|
||||
dir_name = f"{n}_{name}"
|
||||
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 = (
|
||||
f"{these_lines[0].title}_{n}_{these_lines[0].character_name}_{reel}.csv"
|
||||
)
|
||||
|
||||
with open(outfile_name, mode='w', newline='') as outfile:
|
||||
writer = csv.writer(outfile, dialect='excel')
|
||||
writer.writerow(['Title', 'Character Name', 'Cue Number',
|
||||
'Reel', 'Version',
|
||||
'Start', 'Finish',
|
||||
'Start Seconds', 'Finish Seconds',
|
||||
'Prompt',
|
||||
'Reason', 'Note', 'TV'])
|
||||
with open(outfile_name, mode="w", newline="") as outfile:
|
||||
writer = csv.writer(outfile, dialect="excel")
|
||||
writer.writerow(
|
||||
[
|
||||
"Title",
|
||||
"Character Name",
|
||||
"Cue Number",
|
||||
"Reel",
|
||||
"Version",
|
||||
"Start",
|
||||
"Finish",
|
||||
"Start Seconds",
|
||||
"Finish Seconds",
|
||||
"Prompt",
|
||||
"Reason",
|
||||
"Note",
|
||||
"TV",
|
||||
]
|
||||
)
|
||||
|
||||
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),
|
||||
float(this_start), float(this_finish),
|
||||
event.prompt,
|
||||
event.reason, event.note, "TV"
|
||||
if event.tv else ""]
|
||||
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 "",
|
||||
]
|
||||
|
||||
writer.writerow(this_row)
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
def generate_documents(session_tc_format, scenes, adr_lines: List[ADRLine],
|
||||
title):
|
||||
def generate_documents(session_tc_format, scenes, adr_lines: list[ADRLine], title):
|
||||
"""
|
||||
Create PDF output.
|
||||
"""
|
||||
print_section_header_style("Creating PDF Reports")
|
||||
report_date = datetime.datetime.now()
|
||||
reports_dir = "%s_%s" % (title, report_date.strftime("%Y-%m-%d_%H%M%S"))
|
||||
reports_dir = f"{title}_{report_date.strftime('%Y-%m-%d_%H%M%S')}"
|
||||
os.makedirs(reports_dir, exist_ok=False)
|
||||
os.chdir(reports_dir)
|
||||
|
||||
client = next((x.client for x in adr_lines), "")
|
||||
supervisor = next((x.supervisor for x in adr_lines), "")
|
||||
|
||||
output_continuity(scenes=scenes, tc_display_format=session_tc_format,
|
||||
title=title, client=client or "",
|
||||
supervisor=supervisor)
|
||||
output_continuity(
|
||||
scenes=scenes,
|
||||
tc_display_format=session_tc_format,
|
||||
title=title,
|
||||
client=client or "",
|
||||
supervisor=supervisor,
|
||||
)
|
||||
|
||||
reels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6']
|
||||
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))
|
||||
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.
|
||||
@@ -148,8 +174,7 @@ 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")
|
||||
@@ -178,8 +203,8 @@ def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
|
||||
session_text = file.read()
|
||||
else:
|
||||
with ptsl.open_engine(
|
||||
company_name="The ptulsconv developers",
|
||||
application_name="ptulsconv") as engine:
|
||||
company_name="The ptulsconv developers", application_name="ptulsconv"
|
||||
) as engine:
|
||||
req = engine.export_session_as_text()
|
||||
req.utf8_encoding()
|
||||
req.include_track_edls()
|
||||
@@ -192,7 +217,7 @@ def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
|
||||
session = parse_document(session_text)
|
||||
session_tc_format = session.header.timecode_format
|
||||
|
||||
if major_mode == 'raw':
|
||||
if major_mode == "raw":
|
||||
output.write(FractionEncoder().encode(session))
|
||||
|
||||
else:
|
||||
@@ -200,35 +225,35 @@ def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
|
||||
compiler.session = session
|
||||
compiled_events = list(compiler.compile_events())
|
||||
|
||||
if major_mode == 'tagged':
|
||||
if major_mode == "tagged":
|
||||
output.write(FractionEncoder().encode(compiled_events))
|
||||
|
||||
elif major_mode == 'doc':
|
||||
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'],
|
||||
key=lambda x: x[2])
|
||||
scenes = sorted(
|
||||
[s for s in compiler.compile_all_time_spans() if s[0] == "Sc"],
|
||||
key=lambda x: x[2],
|
||||
)
|
||||
|
||||
# TODO: Breakdown by titles
|
||||
titles = set([x.title for x in (generic_events + adr_lines)])
|
||||
titles = {x.title for x in (generic_events + adr_lines)}
|
||||
if len(titles) != 1:
|
||||
print_warning("Multiple titles per export is not supported, "
|
||||
"found multiple titles: %s Exiting." % titles)
|
||||
exit(-1)
|
||||
print_warning(
|
||||
"Multiple titles per export is not supported, "
|
||||
f"found multiple titles: {titles} Exiting."
|
||||
)
|
||||
sys.exit(-1)
|
||||
|
||||
title = list(titles)[0]
|
||||
title = next(iter(titles))
|
||||
|
||||
print_status_style(
|
||||
"%i generic events found." % len(generic_events)
|
||||
)
|
||||
print_status_style("%i ADR events found." % len(adr_lines))
|
||||
print_status_style(f"{len(generic_events)} generic events found.")
|
||||
print_status_style(f"{len(adr_lines)} ADR events found.")
|
||||
|
||||
if warnings:
|
||||
perform_adr_validations(iter(adr_lines))
|
||||
|
||||
generate_documents(session_tc_format, scenes, adr_lines,
|
||||
title)
|
||||
generate_documents(session_tc_format, scenes, adr_lines, title)
|
||||
|
||||
|
||||
def perform_adr_validations(lines: Iterator[ADRLine]):
|
||||
@@ -236,20 +261,15 @@ def perform_adr_validations(lines: Iterator[ADRLine]):
|
||||
Performs validations on the input.
|
||||
"""
|
||||
for warning in chain(
|
||||
validate_unique_field(lines,
|
||||
field='cue_number',
|
||||
scope='title'),
|
||||
validate_non_empty_field(lines,
|
||||
field='cue_number'),
|
||||
validate_non_empty_field(lines,
|
||||
field='character_id'),
|
||||
validate_non_empty_field(lines,
|
||||
field='title'),
|
||||
validate_dependent_value(lines,
|
||||
key_field='character_id',
|
||||
dependent_field='character_name'),
|
||||
validate_dependent_value(lines,
|
||||
key_field='character_id',
|
||||
dependent_field='actor_name')):
|
||||
|
||||
validate_unique_field(lines, field="cue_number", scope="title"),
|
||||
validate_non_empty_field(lines, field="cue_number"),
|
||||
validate_non_empty_field(lines, field="character_id"),
|
||||
validate_non_empty_field(lines, field="title"),
|
||||
validate_dependent_value(
|
||||
lines, key_field="character_id", dependent_field="character_name"
|
||||
),
|
||||
validate_dependent_value(
|
||||
lines, key_field="character_id", dependent_field="actor_name"
|
||||
),
|
||||
):
|
||||
print_warning(warning.report_message())
|
||||
|
||||
@@ -2,7 +2,4 @@
|
||||
Docparser module
|
||||
"""
|
||||
|
||||
|
||||
from .pt_doc_parser import parse_document
|
||||
|
||||
__all__ = [parse_document]
|
||||
from .pt_doc_parser import parse_document # pyright: ignore[reportUnusedImport, reportUnusedImport] # noqa: F401, I001
|
||||
|
||||
@@ -3,16 +3,53 @@ This module defines classes and methods for converting :class:`Event` objects
|
||||
into :class:`ADRLine` objects.
|
||||
"""
|
||||
|
||||
from ptulsconv.docparser.tag_compiler import Event
|
||||
from typing import Optional, List, Tuple
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from fractions import Fraction
|
||||
from typing import ClassVar
|
||||
|
||||
from ptulsconv.docparser.tag_compiler import Event
|
||||
from ptulsconv.docparser.tag_mapping import TagMapping
|
||||
|
||||
GENERIC_TAG_MAPPING = [
|
||||
TagMapping(source="Title", target="title", alt=TagMapping.ContentSource.Session),
|
||||
TagMapping(source="Supv", target="supervisor"),
|
||||
TagMapping(source="Client", target="client"),
|
||||
TagMapping(source="Sc", target="scene"),
|
||||
TagMapping(source="Ver", target="version"),
|
||||
TagMapping(source="Reel", target="reel"),
|
||||
TagMapping(source="Note", target="note"),
|
||||
TagMapping(source="Rq", target="requested_by"),
|
||||
TagMapping(source="OMIT", target="omitted", formatter=(lambda x: len(x) > 0)),
|
||||
]
|
||||
|
||||
def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'],
|
||||
List['ADRLine']]:
|
||||
ADR_TAG_MAPPING = [
|
||||
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="Actor", target="actor_name"),
|
||||
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))
|
||||
),
|
||||
TagMapping(source="Spot", target="spot"),
|
||||
TagMapping(source="Shot", target="shot"),
|
||||
TagMapping(source="EFF", target="effort", formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="TV", target="tv", formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="TBW", target="tbw", formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="ADLIB", target="adlib", formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="OPT", target="optional", formatter=(lambda x: len(x) > 0)),
|
||||
]
|
||||
|
||||
|
||||
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.
|
||||
@@ -22,8 +59,8 @@ def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'],
|
||||
:returns: A tuple of two lists, the first containing :class:`GenericEvent`
|
||||
and the second containing :class:`ADRLine`.
|
||||
"""
|
||||
generic_events = list()
|
||||
adr_lines = list()
|
||||
generic_events = []
|
||||
adr_lines = []
|
||||
|
||||
for event in from_events:
|
||||
result = make_entity(event)
|
||||
@@ -35,7 +72,7 @@ def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'],
|
||||
return generic_events, adr_lines
|
||||
|
||||
|
||||
def make_entity(from_event: Event) -> Optional[object]:
|
||||
def make_entity(from_event: Event) -> object | None:
|
||||
"""
|
||||
Accepts an event and creates either an :class:`ADRLine` or a
|
||||
:class:`GenericEvent`. An event is an "ADRLine" if it has a cue number/"QN"
|
||||
@@ -45,15 +82,20 @@ def make_entity(from_event: Event) -> Optional[object]:
|
||||
|
||||
"""
|
||||
instance = GenericEvent
|
||||
tag_map = GenericEvent.tag_mapping
|
||||
if 'QN' in from_event.tags.keys():
|
||||
tag_map = GENERIC_TAG_MAPPING
|
||||
if "QN" in from_event.tags:
|
||||
instance = ADRLine
|
||||
tag_map += ADRLine.tag_mapping
|
||||
tag_map += ADR_TAG_MAPPING
|
||||
|
||||
new = instance()
|
||||
TagMapping.apply_rules(tag_map, from_event.tags,
|
||||
from_event.clip_name, from_event.track_name,
|
||||
from_event.session_name, new)
|
||||
TagMapping.apply_rules(
|
||||
rules=tag_map,
|
||||
tags=from_event.tags,
|
||||
clip_content=from_event.clip_name,
|
||||
track_content=from_event.track_name,
|
||||
session_content=from_event.session_name,
|
||||
to=new,
|
||||
)
|
||||
|
||||
new.start = from_event.start
|
||||
new.finish = from_event.finish
|
||||
@@ -63,75 +105,36 @@ def make_entity(from_event: Event) -> Optional[object]:
|
||||
@dataclass
|
||||
class GenericEvent:
|
||||
title: str = ""
|
||||
supervisor: Optional[str] = None
|
||||
client: Optional[str] = None
|
||||
scene: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
reel: Optional[str] = None
|
||||
supervisor: str | None = None
|
||||
client: str | None = None
|
||||
scene: str | None = None
|
||||
version: str | None = None
|
||||
reel: str | None = None
|
||||
start: Fraction = Fraction(0, 1)
|
||||
finish: Fraction = Fraction(0, 1)
|
||||
omitted: bool = False
|
||||
note: Optional[str] = None
|
||||
requested_by: Optional[str] = None
|
||||
note: str | None = None
|
||||
requested_by: str | None = None
|
||||
|
||||
tag_mapping = [
|
||||
TagMapping(source='Title', target="title",
|
||||
alt=TagMapping.ContentSource.Session),
|
||||
TagMapping(source="Supv", target="supervisor"),
|
||||
TagMapping(source="Client", target="client"),
|
||||
TagMapping(source="Sc", target="scene"),
|
||||
TagMapping(source="Ver", target="version"),
|
||||
TagMapping(source="Reel", target="reel"),
|
||||
TagMapping(source="Note", target="note"),
|
||||
TagMapping(source="Rq", target="requested_by"),
|
||||
TagMapping(source="OMIT", target="omitted",
|
||||
formatter=(lambda x: len(x) > 0)),
|
||||
]
|
||||
tag_mapping: ClassVar = GENERIC_TAG_MAPPING
|
||||
|
||||
|
||||
@dataclass
|
||||
class ADRLine(GenericEvent):
|
||||
|
||||
priority: Optional[int] = None
|
||||
cue_number: Optional[str] = None
|
||||
character_id: Optional[str] = None
|
||||
character_name: Optional[str] = None
|
||||
actor_name: Optional[str] = None
|
||||
prompt: Optional[str] = None
|
||||
reason: Optional[str] = None
|
||||
time_budget_mins: Optional[float] = None
|
||||
spot: Optional[str] = None
|
||||
shot: Optional[str] = None
|
||||
priority: int | None = None
|
||||
cue_number: str | None = None
|
||||
character_id: str | None = None
|
||||
character_name: str | None = None
|
||||
actor_name: str | None = None
|
||||
prompt: str | None = None
|
||||
reason: str | None = None
|
||||
time_budget_mins: float | None = None
|
||||
spot: str | None = None
|
||||
shot: str | None = None
|
||||
effort: bool = False
|
||||
tv: bool = False
|
||||
tbw: bool = False
|
||||
adlib: bool = False
|
||||
optional: bool = False
|
||||
|
||||
tag_mapping = [
|
||||
|
||||
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="Actor", target="actor_name"),
|
||||
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))),
|
||||
TagMapping(source="Spot", target="spot"),
|
||||
TagMapping(source="Shot", target="shot"),
|
||||
TagMapping(source="EFF", target="effort",
|
||||
formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="TV", target="tv",
|
||||
formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="TBW", target="tbw",
|
||||
formatter=(lambda x: len(x) > 0)),
|
||||
|
||||
TagMapping(source="ADLIB", target="adlib",
|
||||
formatter=(lambda x: len(x) > 0)),
|
||||
TagMapping(source="OPT", target="optional",
|
||||
formatter=(lambda x: len(x) > 0))
|
||||
]
|
||||
tag_mapping: ClassVar = ADR_TAG_MAPPING
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from fractions import Fraction
|
||||
|
||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||
from typing import Tuple, List, Iterator
|
||||
|
||||
|
||||
class SessionDescriptor:
|
||||
header: "HeaderDescriptor"
|
||||
files: List["FileDescriptor"]
|
||||
clips: List["ClipDescriptor"]
|
||||
plugins: List["PluginDescriptor"]
|
||||
tracks: List["TrackDescriptor"]
|
||||
markers: List["MarkerDescriptor"]
|
||||
header: HeaderDescriptor
|
||||
files: list[FileDescriptor]
|
||||
clips: list[ClipDescriptor]
|
||||
plugins: list[PluginDescriptor]
|
||||
tracks: list[TrackDescriptor]
|
||||
markers: list[MarkerDescriptor]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.header = kwargs['header']
|
||||
self.files = kwargs['files']
|
||||
self.clips = kwargs['clips']
|
||||
self.plugins = kwargs['plugins']
|
||||
self.tracks = kwargs['tracks']
|
||||
self.markers = kwargs['markers']
|
||||
self.header = kwargs["header"]
|
||||
self.files = kwargs["files"]
|
||||
self.clips = kwargs["clips"]
|
||||
self.plugins = kwargs["plugins"]
|
||||
self.tracks = kwargs["tracks"]
|
||||
self.markers = kwargs["markers"]
|
||||
|
||||
def markers_timed(self,
|
||||
only_ruler_markers: bool = True) -> \
|
||||
Iterator[Tuple['MarkerDescriptor', Fraction]]:
|
||||
def markers_timed(
|
||||
self, only_ruler_markers: bool = True
|
||||
) -> Iterator[tuple[MarkerDescriptor, Fraction]]:
|
||||
"""
|
||||
Iterate each marker in the session with its respective time reference.
|
||||
"""
|
||||
for marker in self.markers:
|
||||
|
||||
if marker.track_marker and only_ruler_markers:
|
||||
continue
|
||||
|
||||
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)
|
||||
yield marker, marker_time
|
||||
|
||||
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor',
|
||||
'TrackClipDescriptor']]:
|
||||
def tracks_clips(self) -> Iterator[tuple[TrackDescriptor, TrackClipDescriptor]]:
|
||||
"""
|
||||
Iterate each track clip with its respective owning clip.
|
||||
"""
|
||||
@@ -44,10 +44,11 @@ class SessionDescriptor:
|
||||
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 | None]
|
||||
]:
|
||||
"""
|
||||
Iterate each track clip with its respective owning clip and timing
|
||||
information.
|
||||
@@ -58,8 +59,11 @@ class SessionDescriptor:
|
||||
for track, clip in self.tracks_clips():
|
||||
start_time = self.header.convert_timecode(clip.start_timecode)
|
||||
finish_time = self.header.convert_timecode(clip.finish_timecode)
|
||||
timestamp_time = self.header.convert_timecode(clip.timestamp) \
|
||||
if clip.timestamp is not None else None
|
||||
timestamp_time = (
|
||||
self.header.convert_timecode(clip.timestamp)
|
||||
if clip.timestamp is not None
|
||||
else None
|
||||
)
|
||||
|
||||
yield track, clip, start_time, finish_time, timestamp_time
|
||||
|
||||
@@ -76,27 +80,29 @@ class HeaderDescriptor:
|
||||
count_files: int
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.session_name = kwargs['session_name']
|
||||
self.sample_rate = kwargs['sample_rate']
|
||||
self.bit_depth = kwargs['bit_depth']
|
||||
self.start_timecode = kwargs['start_timecode']
|
||||
self.timecode_fps = kwargs['timecode_format']
|
||||
self.timecode_drop_frame = kwargs['timecode_drop_frame']
|
||||
self.count_audio_tracks = kwargs['count_audio_tracks']
|
||||
self.count_clips = kwargs['count_clips']
|
||||
self.count_files = kwargs['count_files']
|
||||
self.session_name = kwargs["session_name"]
|
||||
self.sample_rate = kwargs["sample_rate"]
|
||||
self.bit_depth = kwargs["bit_depth"]
|
||||
self.start_timecode = kwargs["start_timecode"]
|
||||
self.timecode_fps = kwargs["timecode_format"]
|
||||
self.timecode_drop_frame = kwargs["timecode_drop_frame"]
|
||||
self.count_audio_tracks = kwargs["count_audio_tracks"]
|
||||
self.count_clips = kwargs["count_clips"]
|
||||
self.count_files = kwargs["count_files"]
|
||||
|
||||
@property
|
||||
def timecode_format(self):
|
||||
return TimecodeFormat(frame_duration=self.frame_duration,
|
||||
logical_fps=self.logical_fps,
|
||||
drop_frame=self.timecode_drop_frame)
|
||||
return TimecodeFormat(
|
||||
frame_duration=self.frame_duration,
|
||||
logical_fps=self.logical_fps,
|
||||
drop_frame=self.timecode_drop_frame,
|
||||
)
|
||||
|
||||
def convert_timecode(self, tc_string: str) -> Fraction:
|
||||
def convert_timecode(self, tc_string: str) -> Fraction | None:
|
||||
return self.timecode_format.smpte_to_seconds(tc_string)
|
||||
|
||||
@property
|
||||
def start_time(self) -> Fraction:
|
||||
def start_time(self) -> Fraction | None:
|
||||
"""
|
||||
The start time of this session.
|
||||
:return: Start time in seconds
|
||||
@@ -112,21 +118,21 @@ class HeaderDescriptor:
|
||||
return self._get_tc_format_params[1]
|
||||
|
||||
@property
|
||||
def _get_tc_format_params(self) -> Tuple[int, Fraction]:
|
||||
frame_rates = {"23.976": (24, Fraction(1001, 24_000)),
|
||||
"24": (24, Fraction(1, 24)),
|
||||
"25": (25, Fraction(1, 25)),
|
||||
"29.97": (30, Fraction(1001, 30_000)),
|
||||
"30": (30, Fraction(1, 30)),
|
||||
"59.94": (60, Fraction(1001, 60_000)),
|
||||
"60": (60, Fraction(1, 60))
|
||||
}
|
||||
def _get_tc_format_params(self) -> tuple[int, Fraction]:
|
||||
frame_rates = {
|
||||
"23.976": (24, Fraction(1001, 24_000)),
|
||||
"24": (24, Fraction(1, 24)),
|
||||
"25": (25, Fraction(1, 25)),
|
||||
"29.97": (30, Fraction(1001, 30_000)),
|
||||
"30": (30, Fraction(1, 30)),
|
||||
"59.94": (60, Fraction(1001, 60_000)),
|
||||
"60": (60, Fraction(1, 60)),
|
||||
}
|
||||
|
||||
if self.timecode_fps in frame_rates.keys():
|
||||
if self.timecode_fps in frame_rates:
|
||||
return frame_rates[self.timecode_fps]
|
||||
else:
|
||||
raise ValueError("Unrecognized TC rate (%s)" %
|
||||
self.timecode_format)
|
||||
raise ValueError(f"Unrecognized TC rate ({self.timecode_format})")
|
||||
|
||||
|
||||
class TrackDescriptor:
|
||||
@@ -134,18 +140,18 @@ class TrackDescriptor:
|
||||
name: str
|
||||
comments: str
|
||||
user_delay_samples: int
|
||||
state: List[str]
|
||||
plugins: List[str]
|
||||
clips: List["TrackClipDescriptor"]
|
||||
state: list[str]
|
||||
plugins: list[str]
|
||||
clips: list[TrackClipDescriptor]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.index = kwargs['index']
|
||||
self.name = kwargs['name']
|
||||
self.comments = kwargs['comments']
|
||||
self.user_delay_samples = kwargs['user_delay_samples']
|
||||
self.state = kwargs['state']
|
||||
self.plugins = kwargs['plugins']
|
||||
self.clips = kwargs['clips']
|
||||
self.index = kwargs["index"]
|
||||
self.name = kwargs["name"]
|
||||
self.comments = kwargs["comments"]
|
||||
self.user_delay_samples = kwargs["user_delay_samples"]
|
||||
self.state = kwargs["state"]
|
||||
self.plugins = kwargs["plugins"]
|
||||
self.clips = kwargs["clips"]
|
||||
|
||||
|
||||
class FileDescriptor(dict):
|
||||
@@ -163,14 +169,14 @@ class TrackClipDescriptor:
|
||||
state: str
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.channel = kwargs['channel']
|
||||
self.event = kwargs['event']
|
||||
self.clip_name = kwargs['clip_name']
|
||||
self.start_timecode = kwargs['start_time']
|
||||
self.finish_timecode = kwargs['finish_time']
|
||||
self.duration = kwargs['duration']
|
||||
self.timestamp = kwargs['timestamp']
|
||||
self.state = kwargs['state']
|
||||
self.channel = kwargs["channel"]
|
||||
self.event = kwargs["event"]
|
||||
self.clip_name = kwargs["clip_name"]
|
||||
self.start_timecode = kwargs["start_time"]
|
||||
self.finish_timecode = kwargs["finish_time"]
|
||||
self.duration = kwargs["duration"]
|
||||
self.timestamp = kwargs["timestamp"]
|
||||
self.state = kwargs["state"]
|
||||
|
||||
|
||||
class ClipDescriptor(dict):
|
||||
@@ -191,10 +197,10 @@ class MarkerDescriptor:
|
||||
track_marker: bool
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.number = kwargs['number']
|
||||
self.location = kwargs['location']
|
||||
self.time_reference = kwargs['time_reference']
|
||||
self.units = kwargs['units']
|
||||
self.name = kwargs['name']
|
||||
self.comments = kwargs['comments']
|
||||
self.track_marker = kwargs['track_marker']
|
||||
self.number = kwargs["number"]
|
||||
self.location = kwargs["location"]
|
||||
self.time_reference = kwargs["time_reference"]
|
||||
self.units = kwargs["units"]
|
||||
self.name = kwargs["name"]
|
||||
self.comments = kwargs["comments"]
|
||||
self.track_marker = kwargs["track_marker"]
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
from parsimonious.nodes import NodeVisitor
|
||||
from parsimonious.grammar import Grammar
|
||||
from parsimonious.nodes import NodeVisitor
|
||||
|
||||
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor, \
|
||||
FileDescriptor, TrackClipDescriptor, ClipDescriptor, PluginDescriptor, \
|
||||
MarkerDescriptor
|
||||
|
||||
from .doc_entity import (
|
||||
ClipDescriptor,
|
||||
FileDescriptor,
|
||||
HeaderDescriptor,
|
||||
MarkerDescriptor,
|
||||
PluginDescriptor,
|
||||
SessionDescriptor,
|
||||
TrackClipDescriptor,
|
||||
TrackDescriptor,
|
||||
)
|
||||
|
||||
protools_text_export_grammar = Grammar(
|
||||
r"""
|
||||
@@ -110,7 +116,8 @@ protools_text_export_grammar = Grammar(
|
||||
integer_value = ~r"\d+"
|
||||
float_value = ~r"\d+(\.\d+)?"
|
||||
isp = ~r"[^\d\t\n]*"
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def parse_document(session_text: str) -> SessionDescriptor:
|
||||
@@ -124,7 +131,6 @@ def parse_document(session_text: str) -> SessionDescriptor:
|
||||
|
||||
|
||||
class DocParserVisitor(NodeVisitor):
|
||||
|
||||
def __init__(self):
|
||||
self.track_index = 0
|
||||
|
||||
@@ -137,12 +143,14 @@ class DocParserVisitor(NodeVisitor):
|
||||
tracks = next(iter(visited_children[4]), None)
|
||||
markers = next(iter(visited_children[5]), None)
|
||||
|
||||
return SessionDescriptor(header=visited_children[0],
|
||||
files=files,
|
||||
clips=clips,
|
||||
plugins=plugins,
|
||||
tracks=tracks,
|
||||
markers=markers)
|
||||
return SessionDescriptor(
|
||||
header=visited_children[0],
|
||||
files=files,
|
||||
clips=clips,
|
||||
plugins=plugins,
|
||||
tracks=tracks,
|
||||
markers=markers,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_header(_, visited_children):
|
||||
@@ -151,41 +159,47 @@ class DocParserVisitor(NodeVisitor):
|
||||
for _ in visited_children[20]:
|
||||
tc_drop = True
|
||||
|
||||
return HeaderDescriptor(session_name=visited_children[2],
|
||||
sample_rate=visited_children[6],
|
||||
bit_depth=visited_children[10],
|
||||
start_timecode=visited_children[15],
|
||||
timecode_format=visited_children[19],
|
||||
timecode_drop_frame=tc_drop,
|
||||
count_audio_tracks=visited_children[25],
|
||||
count_clips=visited_children[29],
|
||||
count_files=visited_children[33])
|
||||
return HeaderDescriptor(
|
||||
session_name=visited_children[2],
|
||||
sample_rate=visited_children[6],
|
||||
bit_depth=visited_children[10],
|
||||
start_timecode=visited_children[15],
|
||||
timecode_format=visited_children[19],
|
||||
timecode_drop_frame=tc_drop,
|
||||
count_audio_tracks=visited_children[25],
|
||||
count_clips=visited_children[29],
|
||||
count_files=visited_children[33],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_files_section(_, visited_children):
|
||||
return list(map(
|
||||
lambda child: FileDescriptor(filename=child[0], path=child[2]),
|
||||
visited_children[2]))
|
||||
return [
|
||||
FileDescriptor(filemname=child[0], path=child[2])
|
||||
for child in 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),
|
||||
visited_children[2]))
|
||||
return [
|
||||
ClipDescriptor(clip_name=child[0], file=child[2], channel=channel)
|
||||
for child in visited_children[2]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def visit_plugin_listing(_, visited_children):
|
||||
return list(map(lambda child:
|
||||
PluginDescriptor(manufacturer=child[0],
|
||||
plugin_name=child[2],
|
||||
version=child[4],
|
||||
format=child[6],
|
||||
stems=child[8],
|
||||
count_instances=child[10]),
|
||||
visited_children[2]))
|
||||
|
||||
return [
|
||||
PluginDescriptor(
|
||||
manufacturer=child[0],
|
||||
plugin_name=child[2],
|
||||
version=child[4],
|
||||
format=child[6],
|
||||
stems=child[8],
|
||||
count_instances=child[10],
|
||||
)
|
||||
for child in visited_children[2]
|
||||
]
|
||||
|
||||
# @staticmethod
|
||||
def visit_track_block(self, _, visited_children):
|
||||
@@ -210,7 +224,7 @@ class DocParserVisitor(NodeVisitor):
|
||||
user_delay_samples=track_header[10],
|
||||
state=track_header[14],
|
||||
plugins=plugins,
|
||||
clips=clips
|
||||
clips=clips,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -227,14 +241,16 @@ class DocParserVisitor(NodeVisitor):
|
||||
if isinstance(visited_children[14], list):
|
||||
timestamp = visited_children[14][0][0]
|
||||
|
||||
return TrackClipDescriptor(channel=visited_children[0],
|
||||
event=visited_children[3],
|
||||
clip_name=visited_children[6],
|
||||
start_time=visited_children[8],
|
||||
finish_time=visited_children[10],
|
||||
duration=visited_children[12],
|
||||
timestamp=timestamp,
|
||||
state=visited_children[15])
|
||||
return TrackClipDescriptor(
|
||||
channel=visited_children[0],
|
||||
event=visited_children[3],
|
||||
clip_name=visited_children[6],
|
||||
start_time=visited_children[8],
|
||||
finish_time=visited_children[10],
|
||||
duration=visited_children[12],
|
||||
timestamp=timestamp,
|
||||
state=visited_children[15],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_track_state_list(_, visited_children):
|
||||
@@ -252,6 +268,7 @@ class DocParserVisitor(NodeVisitor):
|
||||
markers = []
|
||||
|
||||
for marker in visited_children[1][0][1]:
|
||||
marker: MarkerDescriptor
|
||||
markers.append(marker)
|
||||
|
||||
return markers
|
||||
@@ -259,26 +276,30 @@ class DocParserVisitor(NodeVisitor):
|
||||
@staticmethod
|
||||
def visit_marker_record_simple(_, visited_children):
|
||||
|
||||
return MarkerDescriptor(number=visited_children[0],
|
||||
location=visited_children[3],
|
||||
time_reference=visited_children[5],
|
||||
units=visited_children[8],
|
||||
name=visited_children[10],
|
||||
comments=visited_children[12],
|
||||
track_marker=False)
|
||||
return MarkerDescriptor(
|
||||
number=visited_children[0],
|
||||
location=visited_children[3],
|
||||
time_reference=visited_children[5],
|
||||
units=visited_children[8],
|
||||
name=visited_children[10],
|
||||
comments=visited_children[12],
|
||||
track_marker=False,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_marker_record(_, visited_children):
|
||||
track_type = visited_children[15]
|
||||
is_track_marker = (track_type == "Track")
|
||||
is_track_marker = track_type == "Track"
|
||||
|
||||
return MarkerDescriptor(number=visited_children[0],
|
||||
location=visited_children[3],
|
||||
time_reference=visited_children[5],
|
||||
units=visited_children[8],
|
||||
name=visited_children[10],
|
||||
comments=visited_children[16],
|
||||
track_marker=is_track_marker)
|
||||
return MarkerDescriptor(
|
||||
number=visited_children[0],
|
||||
location=visited_children[3],
|
||||
time_reference=visited_children[5],
|
||||
units=visited_children[8],
|
||||
name=visited_children[10],
|
||||
comments=visited_children[16],
|
||||
track_marker=is_track_marker,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_formatted_clip_name(_, visited_children):
|
||||
@@ -303,5 +324,5 @@ class DocParserVisitor(NodeVisitor):
|
||||
pass
|
||||
|
||||
def generic_visit(self, node, visited_children):
|
||||
""" The generic visit method. """
|
||||
"""The generic visit method."""
|
||||
return visited_children or node
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from collections import namedtuple
|
||||
from fractions import Fraction
|
||||
from typing import Iterator, Tuple, Callable, Generator, Dict, List
|
||||
|
||||
import ptulsconv.docparser.doc_entity as doc_entity
|
||||
from .tagged_string_parser_visitor import parse_tags, TagPreModes
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator, Iterator
|
||||
from dataclasses import dataclass
|
||||
from fractions import Fraction
|
||||
from typing import Callable, NamedTuple
|
||||
|
||||
from ptulsconv.docparser import doc_entity
|
||||
|
||||
from .tagged_string_parser_visitor import TagPreModes, parse_tags
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -13,7 +15,7 @@ class Event:
|
||||
clip_name: str
|
||||
track_name: str
|
||||
session_name: str
|
||||
tags: Dict[str, str]
|
||||
tags: dict[str, str]
|
||||
start: Fraction
|
||||
finish: Fraction
|
||||
|
||||
@@ -24,36 +26,41 @@ class TagCompiler:
|
||||
items.
|
||||
"""
|
||||
|
||||
Intermediate = namedtuple('Intermediate',
|
||||
'track_content track_tags track_comment_tags '
|
||||
'clip_content clip_tags clip_tag_mode start '
|
||||
'finish')
|
||||
class Intermediate(NamedTuple):
|
||||
track_content: str
|
||||
clip_content: str
|
||||
track_tags: dict[str, str]
|
||||
track_comment_tags: dict[str, str]
|
||||
clip_tags: dict[str, str]
|
||||
clip_tag_mode: TagPreModes
|
||||
start: Fraction
|
||||
finish: Fraction
|
||||
|
||||
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)
|
||||
"""
|
||||
ret_list = list()
|
||||
ret_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))
|
||||
for k in element.clip_tags:
|
||||
ret_list.append(
|
||||
(k, element.clip_tags[k], element.start, element.finish)
|
||||
)
|
||||
|
||||
return ret_list
|
||||
|
||||
def compile_tag_list(self) -> Dict[str, List[str]]:
|
||||
tags_dict = dict()
|
||||
def compile_tag_list(self) -> dict[str, list[str]]:
|
||||
tags_dict = {}
|
||||
|
||||
def update_tags_dict(other_dict: dict):
|
||||
for k in other_dict.keys():
|
||||
if k not in tags_dict.keys():
|
||||
for k, value in other_dict.items():
|
||||
if k not in tags_dict:
|
||||
tags_dict[k] = set()
|
||||
tags_dict[k].add(other_dict[k])
|
||||
tags_dict[k].add(value)
|
||||
|
||||
for parsed in self.parse_data():
|
||||
update_tags_dict(parsed.clip_tags)
|
||||
@@ -78,15 +85,19 @@ 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()
|
||||
retval = {}
|
||||
|
||||
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)
|
||||
@@ -94,21 +105,25 @@ class TagCompiler:
|
||||
|
||||
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':
|
||||
if clip.clip_tag_mode == "Directive":
|
||||
continue
|
||||
else:
|
||||
yield clip
|
||||
|
||||
@staticmethod
|
||||
def _coalesce_tags(clip_tags: dict, track_tags: dict,
|
||||
track_comment_tags: dict,
|
||||
timespan_tags: dict,
|
||||
marker_tags: dict, session_tags: dict):
|
||||
effective_tags = dict()
|
||||
def _coalesce_tags(
|
||||
clip_tags: dict,
|
||||
track_tags: dict,
|
||||
track_comment_tags: dict,
|
||||
timespan_tags: dict,
|
||||
marker_tags: dict,
|
||||
session_tags: dict,
|
||||
):
|
||||
effective_tags = {}
|
||||
effective_tags.update(session_tags)
|
||||
effective_tags.update(marker_tags)
|
||||
effective_tags.update(timespan_tags)
|
||||
@@ -118,9 +133,8 @@ class TagCompiler:
|
||||
return effective_tags
|
||||
|
||||
def parse_data(self) -> Iterator[Intermediate]:
|
||||
|
||||
for track, clip, start, finish, _ in self.session.track_clips_timed():
|
||||
if clip.state == 'Muted':
|
||||
if clip.state == "Muted":
|
||||
continue
|
||||
|
||||
track_parsed = parse_tags(track.name)
|
||||
@@ -134,15 +148,14 @@ class TagCompiler:
|
||||
clip_content=clip_parsed.content,
|
||||
clip_tags=clip_parsed.tag_dict,
|
||||
clip_tag_mode=clip_parsed.mode,
|
||||
start=start, finish=finish)
|
||||
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)
|
||||
@@ -151,62 +164,73 @@ class TagCompiler:
|
||||
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,
|
||||
clip_tags=merged_tags, clip_tag_mode=a.clip_tag_mode,
|
||||
start=a.start, finish=b.finish)
|
||||
clip_content=a.clip_content + " " + b.clip_content,
|
||||
clip_tags=merged_tags,
|
||||
clip_tag_mode=a.clip_tag_mode,
|
||||
start=a.start,
|
||||
finish=b.finish,
|
||||
)
|
||||
|
||||
yield from apply_appends(parsed, should_append, do_append)
|
||||
|
||||
@staticmethod
|
||||
def collect_time_spans(parsed: Iterator[Intermediate]) -> \
|
||||
Iterator[Tuple[Intermediate, Tuple[dict, Fraction, Fraction]]]:
|
||||
|
||||
time_spans = list()
|
||||
def collect_time_spans(
|
||||
parsed: Iterator[Intermediate],
|
||||
) -> Iterator[tuple[Intermediate, tuple[dict, Fraction, Fraction]]]:
|
||||
time_spans = []
|
||||
|
||||
for item in parsed:
|
||||
if item.clip_tag_mode == TagPreModes.TIMESPAN:
|
||||
time_spans.append((item.clip_tags, item.start, item.finish))
|
||||
else:
|
||||
yield item, list(time_spans)
|
||||
yield item, tuple(time_spans)
|
||||
|
||||
@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]]):
|
||||
retval = {}
|
||||
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)
|
||||
|
||||
for event, time_spans in parsed_with_time_spans:
|
||||
event: 'TagCompiler.Intermediate'
|
||||
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,
|
||||
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)
|
||||
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,
|
||||
should_append: Callable,
|
||||
do_append: Callable) -> Generator:
|
||||
def apply_appends(
|
||||
source: Iterator, should_append: Callable, preform_append: Callable
|
||||
) -> Generator:
|
||||
"""
|
||||
:param source:
|
||||
:param should_append: Called with two variables a and b, your
|
||||
function should return true if b should be
|
||||
appended to a
|
||||
function should return true if b should be
|
||||
appended to a
|
||||
:param do_append: Called with two variables a and b, your function
|
||||
should return
|
||||
:returns: A Generator
|
||||
@@ -214,7 +238,7 @@ def apply_appends(source: Iterator,
|
||||
this_element = next(source)
|
||||
for element in source:
|
||||
if should_append(this_element, element):
|
||||
this_element = do_append(this_element, element)
|
||||
this_element = preform_append(this_element, element)
|
||||
else:
|
||||
yield this_element
|
||||
this_element = element
|
||||
|
||||
@@ -1,74 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from enum import Enum
|
||||
from typing import Optional, Callable, Any, List
|
||||
from typing import Any, Callable
|
||||
|
||||
# from ptulsconv.docparser.adr_entity import GenericEvent
|
||||
|
||||
|
||||
class TagMapping:
|
||||
class ContentSource(Enum):
|
||||
Session = 1,
|
||||
Track = 2,
|
||||
Clip = 3,
|
||||
Session = (1,)
|
||||
Track = (2,)
|
||||
Clip = (3,)
|
||||
|
||||
source: str
|
||||
alternate_source: Optional[ContentSource]
|
||||
alternate_source: ContentSource | None
|
||||
formatter: Callable[[str], Any]
|
||||
|
||||
@staticmethod
|
||||
def print_rules(for_type: object, output=sys.stdout):
|
||||
format_str = "%-20s | %-20s | %-25s"
|
||||
hr = "%s+%s+%s" % ("-" * 21, "-" * 23, "-" * 26)
|
||||
print("Tag mapping for %s" % for_type.__name__)
|
||||
hr = f"{'-' * 21}+{'-' * 23}+{'-' * 26}"
|
||||
print(f"Tag mapping for {for_type.__class__}")
|
||||
print(hr)
|
||||
print(format_str % ("Tag Source", "Target", "Type"),
|
||||
file=output)
|
||||
print(format_str % ("Tag Source", "Target", "Type"), file=output)
|
||||
print(hr)
|
||||
for rule in for_type.tag_mapping:
|
||||
t = for_type.__annotations__[rule.target]
|
||||
print(format_str % (rule.source, rule.target, t),
|
||||
file=output)
|
||||
print(format_str % (rule.source, rule.target, t), file=output)
|
||||
if rule.alternate_source is TagMapping.ContentSource.Session:
|
||||
print(format_str % (" - (Session Name)", rule.target, t),
|
||||
file=output)
|
||||
print(format_str % (" - (Session Name)", rule.target, t), file=output)
|
||||
elif rule.alternate_source is TagMapping.ContentSource.Track:
|
||||
print(format_str % (" - (Track Name)", rule.target, t),
|
||||
file=output)
|
||||
print(format_str % (" - (Track Name)", rule.target, t), file=output)
|
||||
elif rule.alternate_source is TagMapping.ContentSource.Clip:
|
||||
print(format_str % (" - (Clip Name)", rule.target, t),
|
||||
file=output)
|
||||
print(format_str % (" - (Clip Name)", rule.target, t), file=output)
|
||||
|
||||
@staticmethod
|
||||
def apply_rules(rules: List['TagMapping'],
|
||||
tags: dict,
|
||||
clip_content: str,
|
||||
track_content: str,
|
||||
session_content: str,
|
||||
to: object):
|
||||
def apply_rules(
|
||||
rules: list[TagMapping],
|
||||
tags: dict,
|
||||
clip_content: str,
|
||||
track_content: str,
|
||||
session_content: str,
|
||||
to: object,
|
||||
):
|
||||
|
||||
done = set()
|
||||
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,
|
||||
target: str,
|
||||
alt: Optional[ContentSource] = None,
|
||||
formatter=None):
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
target: str,
|
||||
alt: ContentSource | None = None,
|
||||
formatter=None,
|
||||
):
|
||||
self.source = source
|
||||
self.target = target
|
||||
self.alternate_source = alt
|
||||
self.formatter = formatter or (lambda x: x)
|
||||
|
||||
def apply(self, tags: dict,
|
||||
clip_content: str,
|
||||
track_content: str,
|
||||
session_content: str, to: object) -> bool:
|
||||
def apply(
|
||||
self,
|
||||
tags: dict,
|
||||
clip_content: str,
|
||||
track_content: str,
|
||||
session_content: str,
|
||||
to: object,
|
||||
) -> bool:
|
||||
|
||||
new_value = None
|
||||
|
||||
if self.source in tags.keys():
|
||||
if self.source in tags:
|
||||
new_value = tags[self.source]
|
||||
elif self.alternate_source == TagMapping.ContentSource.Session:
|
||||
new_value = session_content
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from parsimonious import NodeVisitor, Grammar
|
||||
from typing import Dict
|
||||
from enum import Enum
|
||||
|
||||
from parsimonious import Grammar, NodeVisitor
|
||||
|
||||
|
||||
class TagPreModes(Enum):
|
||||
NORMAL = 'Normal'
|
||||
APPEND = 'Append'
|
||||
TIMESPAN = 'Timespan'
|
||||
DIRECTIVE = 'Directive'
|
||||
NORMAL = "Normal"
|
||||
APPEND = "Append"
|
||||
TIMESPAN = "Timespan"
|
||||
DIRECTIVE = "Directive"
|
||||
|
||||
|
||||
tag_grammar = Grammar(
|
||||
@@ -36,7 +36,7 @@ def parse_tags(prompt: str) -> "TaggedStringResult":
|
||||
|
||||
class TaggedStringResult:
|
||||
content: str
|
||||
tag_dict: Dict[str, str]
|
||||
tag_dict: dict[str, str]
|
||||
mode: TagPreModes
|
||||
|
||||
def __init__(self, content, tag_dict, mode):
|
||||
@@ -46,16 +46,15 @@ class TaggedStringResult:
|
||||
|
||||
|
||||
class TagListVisitor(NodeVisitor):
|
||||
|
||||
@staticmethod
|
||||
def visit_document(_, visited_children) -> TaggedStringResult:
|
||||
modifier_opt, line_opt, _, tag_list_opt = visited_children
|
||||
|
||||
return TaggedStringResult(content=next(iter(line_opt), None),
|
||||
tag_dict=next(iter(tag_list_opt), dict()),
|
||||
mode=TagPreModes(
|
||||
next(iter(modifier_opt), 'Normal'))
|
||||
)
|
||||
return TaggedStringResult(
|
||||
content=next(iter(line_opt), None),
|
||||
tag_dict=next(iter(tag_list_opt), {}),
|
||||
mode=TagPreModes(next(iter(modifier_opt), "Normal")),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def visit_line(node, _):
|
||||
@@ -63,18 +62,18 @@ class TagListVisitor(NodeVisitor):
|
||||
|
||||
@staticmethod
|
||||
def visit_modifier(node, _):
|
||||
if node.text.startswith('@'):
|
||||
if node.text.startswith("@"):
|
||||
return TagPreModes.TIMESPAN
|
||||
elif node.text.startswith('&'):
|
||||
elif node.text.startswith("&"):
|
||||
return TagPreModes.APPEND
|
||||
elif node.text.startswith('!'):
|
||||
elif node.text.startswith("!"):
|
||||
return TagPreModes.DIRECTIVE
|
||||
else:
|
||||
return TagPreModes.NORMAL
|
||||
|
||||
@staticmethod
|
||||
def visit_tag_list(_, visited_children):
|
||||
retdict = dict()
|
||||
retdict = {}
|
||||
for child in visited_children:
|
||||
if child[0] is not None:
|
||||
k, v = child[0]
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""
|
||||
Methods for converting string reprentations of film footage.
|
||||
"""
|
||||
from fractions import Fraction
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
from fractions import Fraction
|
||||
|
||||
|
||||
def footage_to_seconds(footage: str) -> Optional[Fraction]:
|
||||
def footage_to_seconds(footage: str) -> Fraction | None:
|
||||
"""
|
||||
Converts a string representation of a footage (35mm, 24fps)
|
||||
into a :class:`Fraction`, this fraction being a some number of
|
||||
@@ -15,7 +17,7 @@ def footage_to_seconds(footage: str) -> Optional[Fraction]:
|
||||
:param footage: A string reprenentation of a footage of the form
|
||||
resembling "90+01".
|
||||
"""
|
||||
m = re.match(r'(\d+)\+(\d+)(\.\d+)?', footage)
|
||||
m = re.match(r"(\d+)\+(\d+)(\.\d+)?", footage)
|
||||
if m is None:
|
||||
return None
|
||||
|
||||
|
||||
+171
-122
@@ -1,16 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from reportlab.pdfbase.pdfmetrics import (getAscent, getDescent)
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.pdfmetrics import getAscent, getDescent
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate
|
||||
from reportlab.platypus.frames import Frame
|
||||
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
from typing import List
|
||||
|
||||
# TODO: A Generic report useful for spotting
|
||||
# TODO: A report useful for M&E mixer's notes
|
||||
# TODO: Use a default font that doesn't need to be installed
|
||||
@@ -27,7 +26,7 @@ class ReportCanvas(canvas.Canvas):
|
||||
|
||||
def showPage(self):
|
||||
self._saved_page_states.append(dict(self.__dict__))
|
||||
self._startPage()
|
||||
self._startPage() # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
def save(self):
|
||||
"""add page info to each page (page x of y)"""
|
||||
@@ -40,12 +39,16 @@ 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))
|
||||
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.setFont("Helvetica", 10) # FIXME make this customizable
|
||||
self.drawString(
|
||||
0.5 * inch,
|
||||
0.5 * inch,
|
||||
f"Page {self._pageNumber} of {page_count}", # pyright: ignore[reportAttributeAccessIssue]
|
||||
)
|
||||
right_edge = self._pagesize[0] - 0.5 * inch # pyright: ignore[reportAttributeAccessIssue]
|
||||
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)
|
||||
@@ -60,49 +63,63 @@ class ADRDocTemplate(BaseDocTemplate):
|
||||
BaseDocTemplate.build(self, flowables, filename, canvasmaker)
|
||||
|
||||
|
||||
def make_doc_template(page_size, filename, document_title,
|
||||
title: str,
|
||||
supervisor: str,
|
||||
document_header: str,
|
||||
client: str,
|
||||
document_subheader: str,
|
||||
left_margin=0.5 * inch,
|
||||
fonts: List[TTFont] = []) -> ADRDocTemplate:
|
||||
right_margin = top_margin = bottom_margin = 0.5 * inch
|
||||
page_box = GRect(0., 0., page_size[0], page_size[1])
|
||||
_, page_box = page_box.split_x(left_margin, direction='l')
|
||||
_, page_box = page_box.split_x(right_margin, direction='r')
|
||||
_, page_box = page_box.split_y(bottom_margin, direction='u')
|
||||
_, page_box = page_box.split_y(top_margin, direction='d')
|
||||
def make_doc_template(
|
||||
page_size: tuple[float, float],
|
||||
filename,
|
||||
document_title,
|
||||
title: str,
|
||||
supervisor: str,
|
||||
document_header: str,
|
||||
client: str,
|
||||
document_subheader: str,
|
||||
left_margin=0.5 * inch,
|
||||
fonts: list[TTFont] | None = None,
|
||||
) -> ADRDocTemplate:
|
||||
|
||||
footer_box, page_box = page_box.split_y(0.25 * inch, direction='u')
|
||||
header_box, page_box = page_box.split_y(0.75 * inch, direction='d')
|
||||
title_box, report_box = header_box.split_x(3.5 * inch, direction='r')
|
||||
if fonts is None:
|
||||
fonts = []
|
||||
|
||||
right_margin = top_margin = bottom_margin = 0.5 * inch
|
||||
page_box: GRect = GRect(0.0, 0.0, page_size[0], page_size[1])
|
||||
_, page_box = page_box.split_x(left_margin, direction="l") # pyright: ignore[reportAssignmentType]
|
||||
_, page_box = page_box.split_x(right_margin, direction="r") # pyright: ignore[reportAssignmentType]
|
||||
_, page_box = page_box.split_y(bottom_margin, direction="u") # pyright: ignore[reportAssignmentType]
|
||||
_, page_box = page_box.split_y(top_margin, direction="d") # pyright: ignore[reportAssignmentType]
|
||||
|
||||
footer_box, page_box = page_box.split_y(0.25 * inch, direction="u") # pyright: ignore[reportAssignmentType]
|
||||
header_box, page_box = page_box.split_y(0.75 * inch, direction="d") # pyright: ignore[reportAssignmentType]
|
||||
title_box, report_box = header_box.split_x(3.5 * inch, direction="r") # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
def on_page_lambda(c, _):
|
||||
draw_header_footer(c, report_box, title_box,
|
||||
footer_box, title=title,
|
||||
supervisor=supervisor,
|
||||
document_subheader=document_subheader,
|
||||
client=client,
|
||||
doc_title=document_header)
|
||||
draw_header_footer(
|
||||
c,
|
||||
report_box,
|
||||
title_box,
|
||||
footer_box,
|
||||
title=title,
|
||||
supervisor=supervisor,
|
||||
document_subheader=document_subheader,
|
||||
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,
|
||||
onPage=on_page_lambda)
|
||||
page_template = PageTemplate(id="Main", frames=frames, onPage=on_page_lambda)
|
||||
|
||||
for font in fonts:
|
||||
pdfmetrics.registerFont(font)
|
||||
|
||||
doc = ADRDocTemplate(filename,
|
||||
title=document_title,
|
||||
author=supervisor,
|
||||
pagesize=page_size,
|
||||
leftMargin=left_margin, rightMargin=right_margin,
|
||||
topMargin=top_margin, bottomMargin=bottom_margin)
|
||||
doc = ADRDocTemplate(
|
||||
filename,
|
||||
title=document_title,
|
||||
author=supervisor,
|
||||
pagesize=page_size,
|
||||
leftMargin=left_margin,
|
||||
rightMargin=right_margin,
|
||||
topMargin=top_margin,
|
||||
bottomMargin=bottom_margin,
|
||||
)
|
||||
|
||||
doc.addPageTemplates([page_template])
|
||||
|
||||
@@ -115,27 +132,43 @@ def time_format(mins, zero_str="-"):
|
||||
"""
|
||||
if mins is None:
|
||||
return zero_str
|
||||
if mins == 0. and zero_str is not None:
|
||||
if mins == 0.0 and zero_str is not None:
|
||||
return zero_str
|
||||
elif mins < 60.:
|
||||
return "%im" % round(mins)
|
||||
elif mins < 60.0:
|
||||
return f"{round(mins)}m"
|
||||
else:
|
||||
m = round(mins)
|
||||
hh, mm = divmod(m, 60)
|
||||
return "%i:%02i" % (hh, mm)
|
||||
return f"{hh}:{mm:02}"
|
||||
|
||||
|
||||
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.0,
|
||||
16.0,
|
||||
]
|
||||
)
|
||||
title_box.draw_text_cell(a_canvas, title, font_name, 18, inset_y=2.0, inset_x=5.0)
|
||||
client_box.draw_text_cell(a_canvas, client, font_name, 11, inset_y=2.0, inset_x=5.0)
|
||||
|
||||
a_canvas.saveState()
|
||||
a_canvas.setLineWidth(0.5)
|
||||
@@ -150,20 +183,26 @@ def draw_header_footer(a_canvas: ReportCanvas, left_box, right_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.0, 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.0, inset_y=2.0)
|
||||
|
||||
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.0, inset_y=2.0
|
||||
)
|
||||
|
||||
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.setFont(font_name, 11.0)
|
||||
a_canvas.drawCentredString(
|
||||
footer_box.min_x + footer_box.width / 2.0, footer_box.min_y, supervisor
|
||||
)
|
||||
|
||||
|
||||
class GRect:
|
||||
@@ -200,71 +239,75 @@ class GRect:
|
||||
return self.y + self.height / 2
|
||||
|
||||
def normalize(self):
|
||||
if self.width < 0.:
|
||||
if self.width < 0.0:
|
||||
self.width = abs(self.width)
|
||||
self.x = self.x - self.width
|
||||
|
||||
if self.height < 0.:
|
||||
if self.height < 0.0:
|
||||
self.height = abs(self.height)
|
||||
self.y = self.y - self.height
|
||||
|
||||
def split_x(self, at, direction='l'):
|
||||
def split_x(self, at, direction="l"):
|
||||
if at >= self.width:
|
||||
return None, self
|
||||
elif at <= 0:
|
||||
return self, None
|
||||
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))
|
||||
if direction == "l":
|
||||
return (
|
||||
GRect(self.min_x, self.min_y, at, self.height),
|
||||
GRect(self.min_x + at, self.y, self.width - at, self.height),
|
||||
)
|
||||
else:
|
||||
return (GRect(self.max_x - at, self.y, at, self.height),
|
||||
GRect(self.min_x, self.y,
|
||||
self.width - at, self.height))
|
||||
return (
|
||||
GRect(self.max_x - at, self.y, at, self.height),
|
||||
GRect(self.min_x, self.y, self.width - at, self.height),
|
||||
)
|
||||
|
||||
def split_y(self, at, direction='u'):
|
||||
def split_y(self, at, direction="u"):
|
||||
if at >= self.height:
|
||||
return None, self
|
||||
elif at <= 0:
|
||||
return self, None
|
||||
else:
|
||||
if direction == 'u':
|
||||
return (GRect(self.x, self.y, self.width, at),
|
||||
GRect(self.x, self.y + at,
|
||||
self.width, self.height - at))
|
||||
if direction == "u":
|
||||
return (
|
||||
GRect(self.x, self.y, self.width, 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))
|
||||
return (
|
||||
GRect(self.x, self.max_y - at, self.width, 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 f"<GRect x={self.x} y={self.y} width={self.width} height={self.height}>"
|
||||
|
||||
def divide_x(self, x_list, direction='l'):
|
||||
ret_list = list()
|
||||
def divide_x(self, x_list, direction="l"):
|
||||
ret_list = []
|
||||
|
||||
rem = self
|
||||
for item in x_list:
|
||||
s, rem = rem.split_x(item, direction)
|
||||
s, rem = rem.split_x(item, direction) # pyright: ignore[reportOptionalMemberAccess]
|
||||
ret_list.append(s)
|
||||
|
||||
return ret_list, rem
|
||||
|
||||
def divide_y(self, y_list, direction='u'):
|
||||
ret_list = list()
|
||||
def divide_y(self, y_list, direction="u"):
|
||||
ret_list = []
|
||||
|
||||
rem = self
|
||||
for item in y_list:
|
||||
s, rem = rem.split_y(item, direction)
|
||||
s, rem = rem.split_y(item, direction) # pyright: ignore[reportOptionalMemberAccess]
|
||||
ret_list.append(s)
|
||||
|
||||
return ret_list, rem
|
||||
@@ -279,18 +322,14 @@ class GRect:
|
||||
def draw_border(self, a_canvas, edge):
|
||||
|
||||
def draw_border_impl(en):
|
||||
if en == 'min_x':
|
||||
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))
|
||||
elif en == '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))
|
||||
if en == "min_x":
|
||||
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))
|
||||
elif en == "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))
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -305,9 +344,18 @@ class GRect:
|
||||
for e in edge:
|
||||
draw_border_impl(e)
|
||||
|
||||
def draw_text_cell(self, a_canvas, text, font_name, font_size,
|
||||
vertical_align='t', force_baseline=None, inset_x=0.,
|
||||
inset_y=0., draw_baseline=False):
|
||||
def draw_text_cell(
|
||||
self,
|
||||
a_canvas,
|
||||
text,
|
||||
font_name,
|
||||
font_size,
|
||||
vertical_align="t",
|
||||
force_baseline=None,
|
||||
inset_x=0.0,
|
||||
inset_y=0.0,
|
||||
draw_baseline=False,
|
||||
):
|
||||
if text is None:
|
||||
return
|
||||
|
||||
@@ -315,9 +363,9 @@ class GRect:
|
||||
|
||||
inset_rect = self.inset_xy(inset_x, inset_y)
|
||||
|
||||
if vertical_align == 'm':
|
||||
y = inset_rect.center_y - getAscent(font_name, font_size) / 2.
|
||||
elif vertical_align == 't':
|
||||
if vertical_align == "m":
|
||||
y = inset_rect.center_y - getAscent(font_name, font_size) / 2.0
|
||||
elif vertical_align == "t":
|
||||
y = inset_rect.max_y - getAscent(font_name, font_size)
|
||||
else:
|
||||
y = inset_rect.min_y - getDescent(font_name, font_size)
|
||||
@@ -339,14 +387,15 @@ class GRect:
|
||||
a_canvas.setDash([3.0, 1.0, 2.0, 1.0])
|
||||
a_canvas.setLineWidth(0.5)
|
||||
bl = a_canvas.beginPath()
|
||||
bl.moveTo(inset_rect.min_x, y - 1.)
|
||||
bl.lineTo(inset_rect.max_x, y - 1.)
|
||||
bl.moveTo(inset_rect.min_x, y - 1.0)
|
||||
bl.lineTo(inset_rect.max_x, y - 1.0)
|
||||
a_canvas.drawPath(bl)
|
||||
|
||||
a_canvas.restoreState()
|
||||
|
||||
def draw_flowable(self, a_canvas, flowable, inset_x=0.,
|
||||
inset_y=0., draw_baselines=False):
|
||||
def draw_flowable(
|
||||
self, a_canvas, flowable, inset_x=0.0, inset_y=0.0, draw_baselines=False
|
||||
):
|
||||
a_canvas.saveState()
|
||||
|
||||
inset_rect = self.inset_xy(inset_x, inset_y)
|
||||
@@ -355,7 +404,7 @@ class GRect:
|
||||
cp.rect(self.min_x, self.min_y, self.width, self.height)
|
||||
a_canvas.clipPath(cp, stroke=0, fill=0)
|
||||
|
||||
w, h = flowable.wrap(inset_rect.width, inset_rect.height)
|
||||
_w, h = flowable.wrap(inset_rect.width, inset_rect.height)
|
||||
|
||||
flowable.drawOn(a_canvas, inset_rect.x, inset_rect.max_y - h)
|
||||
|
||||
@@ -364,7 +413,7 @@ class GRect:
|
||||
a_canvas.setLineWidth(0.5)
|
||||
leading = flowable.style.leading
|
||||
|
||||
y = inset_rect.max_y - flowable.style.fontSize - 1.
|
||||
y = inset_rect.max_y - flowable.style.fontSize - 1.0
|
||||
while y > inset_rect.min_x:
|
||||
bl = a_canvas.beginPath()
|
||||
bl.moveTo(inset_rect.min_x, y)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from fractions import Fraction
|
||||
from typing import Tuple, List
|
||||
|
||||
from reportlab.lib.pagesizes import portrait, letter
|
||||
from reportlab.lib.pagesizes import letter, portrait
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import Paragraph, Table
|
||||
@@ -10,47 +9,53 @@ from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||
from ptulsconv.pdf import make_doc_template
|
||||
|
||||
|
||||
# TODO: A Continuity
|
||||
|
||||
def table_for_scene(scene, tc_format, font_name='Helvetica'):
|
||||
scene_style = getSampleStyleSheet()['Normal']
|
||||
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.
|
||||
scene_style.leftIndent = 0.0
|
||||
scene_style.leftPadding = 0.0
|
||||
scene_style.spaceAfter = 18.0
|
||||
|
||||
tc_data = "<em>%s</em><br />%s" % (tc_format.seconds_to_smpte(scene[2]),
|
||||
tc_format.seconds_to_smpte(scene[3]))
|
||||
tc_data = f"<em>{tc_format.seconds_to_smpte(scene[2])}</em><br />{tc_format.seconds_to_smpte(scene[3])}"
|
||||
|
||||
row = [
|
||||
Paragraph(tc_data, scene_style),
|
||||
Paragraph(scene[1], scene_style),
|
||||
]
|
||||
|
||||
style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 12.),
|
||||
('FONTNAME', (0, 0), (-1, -1), font_name)]
|
||||
style = [
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("LEFTPADDING", (0, 0), (0, 0), 0.0),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 12.0),
|
||||
("FONTNAME", (0, 0), (-1, -1), font_name),
|
||||
]
|
||||
|
||||
return Table(data=[row], style=style, colWidths=[1.0 * inch, 6.5 * inch])
|
||||
|
||||
|
||||
def output_report(scenes: List[Tuple[str, str, Fraction, Fraction]],
|
||||
tc_display_format: TimecodeFormat,
|
||||
title: str, client: str, supervisor, paper_size=letter):
|
||||
filename = "%s Continuity.pdf" % title
|
||||
def output_report(
|
||||
scenes: list[tuple[str, str, Fraction, Fraction]],
|
||||
tc_display_format: TimecodeFormat,
|
||||
title: str,
|
||||
client: str,
|
||||
supervisor,
|
||||
paper_size=letter,
|
||||
):
|
||||
filename = f"{title} Continuity.pdf"
|
||||
document_header = "Continuity"
|
||||
|
||||
doc = make_doc_template(page_size=portrait(paper_size),
|
||||
filename=filename,
|
||||
document_title="Continuity",
|
||||
title=title,
|
||||
client=client,
|
||||
document_subheader="",
|
||||
supervisor=supervisor,
|
||||
document_header=document_header,
|
||||
left_margin=0.5 * inch)
|
||||
story = list()
|
||||
doc = make_doc_template(
|
||||
page_size=portrait(paper_size),
|
||||
filename=filename,
|
||||
document_title="Continuity",
|
||||
title=title,
|
||||
client=client,
|
||||
document_subheader="",
|
||||
supervisor=supervisor,
|
||||
document_header=document_header,
|
||||
left_margin=0.5 * inch,
|
||||
)
|
||||
story = []
|
||||
# story.append(Spacer(height=0.5 * inch, width=1.))
|
||||
for scene in scenes:
|
||||
story.append(table_for_scene(scene, tc_display_format))
|
||||
|
||||
+282
-220
@@ -1,213 +1,257 @@
|
||||
from typing import List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
# from reportlab.pdfbase import pdfmetrics
|
||||
# from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.pagesizes import letter, portrait
|
||||
from reportlab.lib import colors
|
||||
|
||||
from reportlab.platypus import Table, Paragraph, Spacer
|
||||
from reportlab.lib.pagesizes import letter, portrait
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import Paragraph, Spacer, Table
|
||||
|
||||
from .__init__ import time_format, make_doc_template
|
||||
from ..docparser.adr_entity import ADRLine
|
||||
from .__init__ import make_doc_template, time_format
|
||||
|
||||
|
||||
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])
|
||||
def build_columns(
|
||||
lines: list[ADRLine],
|
||||
reel_list: list[str] | None,
|
||||
show_priorities=False,
|
||||
include_omitted=False,
|
||||
):
|
||||
columns = []
|
||||
reel_numbers = reel_list or sorted({x.reel for x in lines if x.reel is not None})
|
||||
|
||||
num_column_width = 15.0 / 32.0 * inch
|
||||
|
||||
columns.append(
|
||||
{
|
||||
"heading": "#",
|
||||
"value_getter": lambda recs: recs[0].character_id,
|
||||
"value_getter2": lambda recs: "",
|
||||
"style_getter": lambda col_index: [],
|
||||
"width": 0.375 * inch,
|
||||
"summarize": False,
|
||||
}
|
||||
)
|
||||
|
||||
num_column_width = 15. / 32. * inch
|
||||
columns.append(
|
||||
{
|
||||
"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)
|
||||
],
|
||||
"width": 1.75 * inch,
|
||||
"summarize": False,
|
||||
}
|
||||
)
|
||||
|
||||
columns.append({
|
||||
'heading': '#',
|
||||
'value_getter': lambda recs: recs[0].character_id,
|
||||
'value_getter2': lambda recs: "",
|
||||
'style_getter': lambda col_index: [],
|
||||
'width': 0.375 * inch,
|
||||
'summarize': False
|
||||
})
|
||||
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.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.0, colors.black),
|
||||
("LINEAFTER", (col_index, 0), (col_index, -1), 0.5, colors.gray),
|
||||
]
|
||||
),
|
||||
"width": num_column_width,
|
||||
}
|
||||
)
|
||||
|
||||
columns.append({
|
||||
'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)],
|
||||
'width': 1.75 * inch,
|
||||
'summarize': False
|
||||
})
|
||||
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.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), 0.5, colors.gray),
|
||||
]
|
||||
),
|
||||
"width": num_column_width,
|
||||
}
|
||||
)
|
||||
|
||||
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)]
|
||||
),
|
||||
'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.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,
|
||||
}
|
||||
)
|
||||
|
||||
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)]
|
||||
),
|
||||
'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')]
|
||||
),
|
||||
'width': num_column_width
|
||||
})
|
||||
|
||||
columns.append({
|
||||
'heading': '',
|
||||
'value_getter': lambda _: '',
|
||||
'value_getter2': lambda _: '',
|
||||
'style_getter': lambda col_index: [
|
||||
('LINEBEFORE', (col_index, 0), (col_index, -1), 1., colors.black),
|
||||
('LINEAFTER', (col_index, 0), (col_index, -1), 1., colors.black),
|
||||
],
|
||||
'width': 2.
|
||||
})
|
||||
columns.append(
|
||||
{
|
||||
"heading": "",
|
||||
"value_getter": lambda _: "",
|
||||
"value_getter2": lambda _: "",
|
||||
"style_getter": lambda col_index: [
|
||||
("LINEBEFORE", (col_index, 0), (col_index, -1), 1.0, colors.black),
|
||||
("LINEAFTER", (col_index, 0), (col_index, -1), 1.0, colors.black),
|
||||
],
|
||||
"width": 2.0,
|
||||
}
|
||||
)
|
||||
|
||||
if len(reel_numbers) > 0:
|
||||
|
||||
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)]
|
||||
),
|
||||
|
||||
'width': num_column_width
|
||||
})
|
||||
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.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),
|
||||
0.5,
|
||||
colors.gray,
|
||||
),
|
||||
]
|
||||
),
|
||||
"width": num_column_width,
|
||||
}
|
||||
)
|
||||
|
||||
if show_priorities:
|
||||
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]))
|
||||
),
|
||||
'style_getter': lambda col_index: [],
|
||||
'width': num_column_width
|
||||
})
|
||||
for n in range(
|
||||
1,
|
||||
6,
|
||||
):
|
||||
columns.append(
|
||||
{
|
||||
"heading": f"P{n}",
|
||||
"value_getter": lambda recs, N=n: len(
|
||||
[r for r in recs if r.priority == N]
|
||||
),
|
||||
"value_getter2": (
|
||||
lambda recs, N=n: time_format(
|
||||
sum(
|
||||
[
|
||||
r.time_budget_mins or 0.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]))
|
||||
),
|
||||
'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.0
|
||||
for r in recs
|
||||
if (r.priority or 5) > 5
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
"style_getter": lambda col_index: [],
|
||||
"width": num_column_width,
|
||||
}
|
||||
)
|
||||
|
||||
if include_omitted:
|
||||
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')]
|
||||
),
|
||||
'width': num_column_width
|
||||
})
|
||||
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.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])
|
||||
)
|
||||
),
|
||||
'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
|
||||
})
|
||||
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.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):
|
||||
data = list()
|
||||
styles = list()
|
||||
columns_widths = list()
|
||||
def populate_columns(lines: list[ADRLine], columns, include_omitted, _page_size):
|
||||
data = []
|
||||
styles = []
|
||||
columns_widths = []
|
||||
|
||||
sorted_character_numbers: List[str] = sorted(
|
||||
set([x.character_id for x in lines]),
|
||||
key=lambda x: str(x))
|
||||
|
||||
# construct column styles
|
||||
sorted_character_numbers: list[str | None] = sorted(
|
||||
{x.character_id for x in lines}, key=lambda x: str(x)
|
||||
)
|
||||
|
||||
for i, c in enumerate(columns):
|
||||
styles.extend(c['style_getter'](i))
|
||||
columns_widths.append(c['width'])
|
||||
styles.extend(c["style_getter"](i))
|
||||
columns_widths.append(c["width"])
|
||||
|
||||
data.append(list(map(lambda x: x['heading'], columns)))
|
||||
data.append([x["heading"] for x in columns])
|
||||
|
||||
if not include_omitted:
|
||||
lines = [x for x in lines if not x.omitted]
|
||||
@@ -215,37 +259,44 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted,
|
||||
for n in sorted_character_numbers:
|
||||
char_records = [x for x in lines if x.character_id == n]
|
||||
if len(char_records) > 0:
|
||||
row_data = list()
|
||||
row_data2 = list()
|
||||
row_data = []
|
||||
row_data2 = []
|
||||
|
||||
for col in columns:
|
||||
row1_index = len(data)
|
||||
row2_index = row1_index + 1
|
||||
row_data.append(col['value_getter'](list(char_records)))
|
||||
row_data2.append(col['value_getter2'](list(char_records)))
|
||||
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)
|
||||
|
||||
summary_row1 = list()
|
||||
summary_row2 = list()
|
||||
summary_row1 = []
|
||||
summary_row2 = []
|
||||
row1_index = len(data)
|
||||
|
||||
for col in columns:
|
||||
if col.get('summarize', True):
|
||||
summary_row1.append(col['value_getter'](lines))
|
||||
summary_row2.append(col['value_getter2'](lines))
|
||||
if col.get("summarize", True):
|
||||
summary_row1.append(col["value_getter"](lines))
|
||||
summary_row2.append(col["value_getter2"](lines))
|
||||
else:
|
||||
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)
|
||||
@@ -257,29 +308,41 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted,
|
||||
# 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],
|
||||
page_size: tuple[float, float] | None = None,
|
||||
include_omitted=False,
|
||||
font_name="Helvetica",
|
||||
):
|
||||
if page_size is None:
|
||||
page_size = portrait(letter)
|
||||
|
||||
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))
|
||||
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.0))
|
||||
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'))
|
||||
|
||||
title = "%s Line Count" % lines[0].title
|
||||
filename = title + '.pdf'
|
||||
doc = make_doc_template(page_size=page_size, filename=filename,
|
||||
document_title=title, title=lines[0].title,
|
||||
document_subheader=lines[0].spot,
|
||||
client=lines[0].client,
|
||||
supervisor=lines[0].supervisor,
|
||||
document_header='Line Count')
|
||||
title = f"{lines[0].title} Line Count"
|
||||
filename = title + ".pdf"
|
||||
|
||||
doc = make_doc_template(
|
||||
page_size=page_size,
|
||||
filename=filename,
|
||||
document_title=title,
|
||||
title=lines[0].title,
|
||||
document_subheader=lines[0].spot or "",
|
||||
client=lines[0].client or "",
|
||||
supervisor=lines[0].supervisor or "",
|
||||
document_header="Line Count",
|
||||
)
|
||||
|
||||
# header_data, header_style, header_widths = build_header(columns_widths)
|
||||
# header_table = Table(data=header_data, style=header_style,
|
||||
@@ -287,18 +350,17 @@ def output_report(lines: List[ADRLine], reel_list: List[str],
|
||||
|
||||
table = Table(data=data, style=style, colWidths=columns_widths)
|
||||
|
||||
story = [Spacer(height=0.5 * inch, width=1.), table]
|
||||
story = [Spacer(height=0.5 * inch, width=1.0), table]
|
||||
|
||||
style = getSampleStyleSheet()['Normal']
|
||||
style = getSampleStyleSheet()["Normal"]
|
||||
style.fontName = font_name
|
||||
style.fontSize = 12.
|
||||
style.spaceBefore = 16.
|
||||
style.spaceAfter = 16.
|
||||
style.fontSize = 12.0
|
||||
style.spaceBefore = 16.0
|
||||
style.spaceAfter = 16.0
|
||||
|
||||
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(f"* {omitted_count} Omitted lines are excluded.", style))
|
||||
|
||||
doc.build(story)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# TODO: Complete Recordist Log
|
||||
|
||||
|
||||
def output_report(records):
|
||||
# order by start
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
from .__init__ import time_format, make_doc_template
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.pagesizes import letter, portrait
|
||||
|
||||
from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import KeepTogether, Paragraph, Spacer, Table
|
||||
|
||||
from typing import List
|
||||
from ptulsconv.docparser.adr_entity import ADRLine
|
||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||
from ptulsconv.docparser.adr_entity import ADRLine
|
||||
|
||||
from .__init__ import make_doc_template, time_format
|
||||
|
||||
|
||||
def build_aux_data_field(line: ADRLine):
|
||||
entries = list()
|
||||
entries = []
|
||||
if line.reason is not None:
|
||||
entries.append("Reason: " + line.reason)
|
||||
if line.note is not None:
|
||||
@@ -23,78 +22,91 @@ def build_aux_data_field(line: ADRLine):
|
||||
if line.shot is not None:
|
||||
entries.append("Shot: " + line.shot)
|
||||
|
||||
fg_color = 'white'
|
||||
fg_color = "white"
|
||||
tag_field = ""
|
||||
if line.effort:
|
||||
bg_color = 'red'
|
||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||
% (bg_color, fg_color, "EFF")
|
||||
bg_color = "red"
|
||||
tag_field += (
|
||||
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>EFF</font> "
|
||||
)
|
||||
elif line.tv:
|
||||
bg_color = 'blue'
|
||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||
% (bg_color, fg_color, "TV")
|
||||
bg_color = "blue"
|
||||
tag_field += (
|
||||
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>TV</font> "
|
||||
)
|
||||
elif line.adlib:
|
||||
bg_color = 'purple'
|
||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||
% (bg_color, fg_color, "ADLIB")
|
||||
bg_color = "purple"
|
||||
tag_field += (
|
||||
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>ADLIB</font> "
|
||||
)
|
||||
elif line.optional:
|
||||
bg_color = 'green'
|
||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" \
|
||||
% (bg_color, fg_color, "OPTIONAL")
|
||||
bg_color = "green"
|
||||
tag_field += f"font backColor={bg_color} textColor={fg_color} fontSize=11>OPTIONAL</font>"
|
||||
|
||||
entries.append(tag_field)
|
||||
|
||||
return "<br />".join(entries)
|
||||
|
||||
|
||||
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat,
|
||||
font_name='Helvetica'):
|
||||
story = list()
|
||||
def build_story(lines: list[ADRLine], tc_rate: TimecodeFormat, font_name="Helvetica"):
|
||||
story = []
|
||||
|
||||
this_scene = None
|
||||
scene_style = getSampleStyleSheet()['Normal']
|
||||
scene_style = getSampleStyleSheet()["Normal"]
|
||||
scene_style.fontName = font_name
|
||||
scene_style.leftIndent = 0.
|
||||
scene_style.leftPadding = 0.
|
||||
scene_style.spaceAfter = 18.
|
||||
line_style = getSampleStyleSheet()['Normal']
|
||||
scene_style.leftIndent = 0.0
|
||||
scene_style.leftPadding = 0.0
|
||||
scene_style.spaceAfter = 18.0
|
||||
line_style = getSampleStyleSheet()["Normal"]
|
||||
line_style.fontName = font_name
|
||||
|
||||
for line in lines:
|
||||
table_style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 24.)]
|
||||
table_style = [
|
||||
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
||||
("LEFTPADDING", (0, 0), (0, 0), 0.0),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 24.0),
|
||||
]
|
||||
|
||||
cue_number_field = "%s<br /><font fontSize=7>%s</font>" \
|
||||
% (line.cue_number, line.character_name)
|
||||
cue_number_field = (
|
||||
f"{line.cue_number}<br /><font fontSize=7>{line.character_name}</font>"
|
||||
)
|
||||
|
||||
time_data = time_format(line.time_budget_mins)
|
||||
|
||||
if line.priority is not None:
|
||||
time_data = time_data + "<br />" + "P: " + line.priority
|
||||
time_data = time_data + "<br />" + "P: " + str(line.priority)
|
||||
|
||||
aux_data_field = build_aux_data_field(line)
|
||||
|
||||
tc_data = build_tc_data(line, tc_rate)
|
||||
|
||||
line_table_data = [[Paragraph(cue_number_field, line_style),
|
||||
Paragraph(tc_data, line_style),
|
||||
Paragraph(line.prompt, line_style),
|
||||
Paragraph(time_data, line_style),
|
||||
Paragraph(aux_data_field, line_style)
|
||||
]]
|
||||
line_table_data = [
|
||||
[
|
||||
Paragraph(cue_number_field, line_style),
|
||||
Paragraph(tc_data, line_style),
|
||||
Paragraph(line.prompt or "[No Prompt]", line_style),
|
||||
Paragraph(time_data, line_style),
|
||||
Paragraph(aux_data_field, line_style),
|
||||
]
|
||||
]
|
||||
|
||||
line_table = Table(data=line_table_data,
|
||||
colWidths=[inch * 0.75, inch, inch * 3., 0.5 * inch,
|
||||
inch * 2.],
|
||||
style=table_style)
|
||||
line_table = Table(
|
||||
data=line_table_data,
|
||||
colWidths=[inch * 0.75, inch, inch * 3.0, 0.5 * inch, inch * 2.0],
|
||||
style=table_style,
|
||||
)
|
||||
|
||||
if (line.scene or "[No Scene]") != this_scene:
|
||||
this_scene = line.scene or "[No Scene]"
|
||||
story.append(KeepTogether([
|
||||
Spacer(1., 0.25 * inch),
|
||||
Paragraph("<u>" + this_scene + "</u>", scene_style),
|
||||
line_table]))
|
||||
story.append(
|
||||
KeepTogether(
|
||||
[
|
||||
Spacer(1.0, 0.25 * inch),
|
||||
Paragraph("<u>" + this_scene + "</u>", scene_style),
|
||||
line_table,
|
||||
]
|
||||
)
|
||||
)
|
||||
else:
|
||||
line_table.setStyle(table_style)
|
||||
story.append(KeepTogether([line_table]))
|
||||
@@ -103,31 +115,38 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat,
|
||||
|
||||
|
||||
def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
|
||||
tc_data = tc_format.seconds_to_smpte(line.start) + "<br />" + \
|
||||
tc_format.seconds_to_smpte(line.finish)
|
||||
tc_data = (
|
||||
tc_format.seconds_to_smpte(line.start)
|
||||
+ "<br />"
|
||||
+ tc_format.seconds_to_smpte(line.finish)
|
||||
)
|
||||
third_line = []
|
||||
if line.reel is not None:
|
||||
if line.reel[0:1] == 'R':
|
||||
third_line.append("%s" % line.reel)
|
||||
if line.reel[0:1] == "R":
|
||||
third_line.append(f"{line.reel}")
|
||||
else:
|
||||
third_line.append("Reel %s" % line.reel)
|
||||
third_line.append(f"Reel {line.reel}")
|
||||
if line.version is not None:
|
||||
third_line.append("(%s)" % line.version)
|
||||
third_line.append(f"({line.version})")
|
||||
if len(third_line) > 0:
|
||||
tc_data = tc_data + "<br/>" + " ".join(third_line)
|
||||
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)
|
||||
document_header = "%s ADR Report" % lines[0].character_name
|
||||
title = f"{lines[0].title} ADR Report ({lines[0].character_name})"
|
||||
document_header = f"{lines[0].character_name} ADR Report"
|
||||
else:
|
||||
title = "%s ADR Report" % lines[0].title
|
||||
document_header = 'ADR Report'
|
||||
title = f"{lines[0].title} ADR Report"
|
||||
document_header = "ADR Report"
|
||||
|
||||
if not include_omitted:
|
||||
lines = [line for line in lines if not line.omitted]
|
||||
@@ -135,22 +154,32 @@ def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat,
|
||||
lines = sorted(lines, key=lambda line: line.start)
|
||||
|
||||
filename = title + ".pdf"
|
||||
doc = make_doc_template(page_size=page_size,
|
||||
filename=filename, document_title=title,
|
||||
document_header=document_header,
|
||||
title=lines[0].title,
|
||||
supervisor=lines[0].supervisor,
|
||||
client=lines[0].client,
|
||||
document_subheader=lines[0].spot,
|
||||
left_margin=0.75 * inch)
|
||||
doc = make_doc_template(
|
||||
page_size=page_size,
|
||||
filename=filename,
|
||||
document_title=title,
|
||||
document_header=document_header,
|
||||
title=lines[0].title,
|
||||
supervisor=lines[0].supervisor or "",
|
||||
client=lines[0].client or "",
|
||||
document_subheader=lines[0].spot or "",
|
||||
left_margin=0.75 * inch,
|
||||
)
|
||||
story = build_story(lines, tc_rate)
|
||||
doc.build(story)
|
||||
|
||||
|
||||
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
||||
page_size=portrait(letter), by_character=False):
|
||||
def output_report(
|
||||
lines: list[ADRLine],
|
||||
tc_display_format: TimecodeFormat,
|
||||
page_size: tuple[float, float] | None = None,
|
||||
by_character: bool = False,
|
||||
):
|
||||
if page_size is None:
|
||||
page_size = portrait(letter)
|
||||
|
||||
if by_character:
|
||||
character_numbers = set((r.character_id for r in lines))
|
||||
character_numbers = {r.character_id for r in lines}
|
||||
for n in character_numbers:
|
||||
generate_report(page_size, lines, tc_display_format, n)
|
||||
else:
|
||||
|
||||
+182
-130
@@ -1,118 +1,151 @@
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
import datetime
|
||||
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
|
||||
# from reportlab.pdfbase import pdfmetrics
|
||||
# from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.pagesizes import letter
|
||||
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from .__init__ import GRect
|
||||
|
||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||
from ptulsconv.docparser.adr_entity import ADRLine
|
||||
|
||||
import datetime
|
||||
from .__init__ import GRect
|
||||
|
||||
font_name = 'Helvetica'
|
||||
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.)
|
||||
line = "%s / %s" % (record.character_id, record.character_name)
|
||||
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.0)
|
||||
line = f"{record.character_id} / {record.character_name}"
|
||||
if record.actor_name is not None:
|
||||
line = line + " / " + record.actor_name
|
||||
value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.)
|
||||
rect.draw_border(canvas, ['min_y', 'max_y'])
|
||||
value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.0)
|
||||
rect.draw_border(canvas, ["min_y", "max_y"])
|
||||
|
||||
|
||||
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.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,
|
||||
inset_x=10., inset_y=2., draw_baseline=True)
|
||||
(
|
||||
(
|
||||
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.0, vertical_align="t"
|
||||
)
|
||||
number_frame.draw_text_cell(
|
||||
canvas,
|
||||
record.cue_number,
|
||||
font_name,
|
||||
14,
|
||||
inset_x=10.0,
|
||||
inset_y=2.0,
|
||||
draw_baseline=True,
|
||||
)
|
||||
|
||||
tags = {'tv': 'TV',
|
||||
'optional': 'OPT',
|
||||
'adlib': 'ADLIB',
|
||||
'effort': 'EFF',
|
||||
'tbw': 'TBW',
|
||||
'omitted': 'OMIT'}
|
||||
tags = {
|
||||
"tv": "TV",
|
||||
"optional": "OPT",
|
||||
"adlib": "ADLIB",
|
||||
"effort": "EFF",
|
||||
"tbw": "TBW",
|
||||
"omitted": "OMIT",
|
||||
}
|
||||
tag_field = ""
|
||||
for key in tags.keys():
|
||||
for key, value in tags.items():
|
||||
if getattr(record, key):
|
||||
tag_field = tag_field + tags[key] + " "
|
||||
tag_field = tag_field + value + " "
|
||||
|
||||
aux_frame.draw_text_cell(canvas, tag_field, font_name, 10,
|
||||
inset_x=10., inset_y=2., vertical_align='t')
|
||||
rect.draw_border(canvas, 'max_x')
|
||||
aux_frame.draw_text_cell(
|
||||
canvas, tag_field, font_name, 10, inset_x=10.0, inset_y=2.0, vertical_align="t"
|
||||
)
|
||||
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')
|
||||
[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)
|
||||
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)
|
||||
in_label_frame.draw_text_cell(
|
||||
canvas, "IN", font_name, 10, vertical_align="t", inset_y=5.0, inset_x=5.0
|
||||
)
|
||||
in_frame.draw_text_cell(
|
||||
canvas,
|
||||
tc_display_format.seconds_to_smpte(record.start),
|
||||
font_name,
|
||||
14,
|
||||
inset_x=10.0,
|
||||
inset_y=2.0,
|
||||
draw_baseline=True,
|
||||
)
|
||||
out_label_frame.draw_text_cell(
|
||||
canvas, "OUT", font_name, 10, vertical_align="t", inset_y=5.0, inset_x=5.0
|
||||
)
|
||||
out_frame.draw_text_cell(
|
||||
canvas,
|
||||
tc_display_format.seconds_to_smpte(record.finish),
|
||||
font_name,
|
||||
14,
|
||||
inset_x=10.0,
|
||||
inset_y=2.0,
|
||||
draw_baseline=True,
|
||||
)
|
||||
|
||||
rect.draw_border(canvas, 'max_x')
|
||||
rect.draw_border(canvas, "max_x")
|
||||
|
||||
|
||||
def draw_reason_block(canvas, rect, record: ADRLine):
|
||||
reason_cell, notes_cell = rect.split_y(24., direction='d')
|
||||
reason_label, reason_value = reason_cell.split_x(.75 * inch)
|
||||
notes_label, notes_value = notes_cell.split_x(.75 * inch)
|
||||
reason_cell, notes_cell = rect.split_y(24.0, direction="d")
|
||||
reason_label, reason_value = reason_cell.split_x(0.75 * inch)
|
||||
notes_label, notes_value = notes_cell.split_x(0.75 * inch)
|
||||
|
||||
reason_label.draw_text_cell(canvas, "Reason:", font_name, 12,
|
||||
inset_x=5., inset_y=5., vertical_align='b')
|
||||
reason_value.draw_text_cell(canvas, record.reason or "", font_name, 12,
|
||||
inset_x=5., inset_y=5., draw_baseline=True,
|
||||
vertical_align='b')
|
||||
notes_label.draw_text_cell(canvas, "Note:", font_name, 12,
|
||||
inset_x=5., inset_y=5., vertical_align='t')
|
||||
reason_label.draw_text_cell(
|
||||
canvas, "Reason:", font_name, 12, inset_x=5.0, inset_y=5.0, vertical_align="b"
|
||||
)
|
||||
reason_value.draw_text_cell(
|
||||
canvas,
|
||||
record.reason or "",
|
||||
font_name,
|
||||
12,
|
||||
inset_x=5.0,
|
||||
inset_y=5.0,
|
||||
draw_baseline=True,
|
||||
vertical_align="b",
|
||||
)
|
||||
notes_label.draw_text_cell(
|
||||
canvas, "Note:", font_name, 12, inset_x=5.0, inset_y=5.0, vertical_align="t"
|
||||
)
|
||||
|
||||
style = getSampleStyleSheet()['BodyText']
|
||||
style = getSampleStyleSheet()["BodyText"]
|
||||
style.fontName = font_name
|
||||
style.fontSize = 12
|
||||
style.leading = 14
|
||||
|
||||
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.0, inset_y=5.0)
|
||||
|
||||
|
||||
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.0, inset_x=0.0
|
||||
)
|
||||
|
||||
style = getSampleStyleSheet()['BodyText']
|
||||
style = getSampleStyleSheet()["BodyText"]
|
||||
style.fontName = font_name
|
||||
style.fontSize = 14
|
||||
|
||||
@@ -124,16 +157,17 @@ def draw_prompt(canvas, rect, prompt=""):
|
||||
|
||||
block.draw_flowable(canvas, p, draw_baselines=True)
|
||||
|
||||
rect.draw_border(canvas, 'max_y')
|
||||
rect.draw_border(canvas, "max_y")
|
||||
|
||||
|
||||
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.0, inset_x=0.0
|
||||
)
|
||||
|
||||
style = getSampleStyleSheet()['BodyText']
|
||||
style = getSampleStyleSheet()["BodyText"]
|
||||
style.fontName = font_name
|
||||
style.fontSize = 14
|
||||
style.leading = 24
|
||||
@@ -142,7 +176,7 @@ def draw_notes(canvas, rect, note=""):
|
||||
|
||||
block.draw_flowable(canvas, prompt, draw_baselines=True)
|
||||
|
||||
rect.draw_border(canvas, ['max_y', 'min_y'])
|
||||
rect.draw_border(canvas, ["max_y", "min_y"])
|
||||
|
||||
|
||||
def draw_take_grid(canvas, rect):
|
||||
@@ -178,74 +212,90 @@ def draw_take_grid(canvas, rect):
|
||||
ln.lineTo(rect.max_x, rect.min_y + y)
|
||||
canvas.drawPath(ln)
|
||||
|
||||
rect.draw_border(canvas, 'max_x')
|
||||
rect.draw_border(canvas, "max_x")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
def draw_aux_block(canvas, rect, recording_time_sec_this_line,
|
||||
recording_time_sec):
|
||||
rect.draw_border(canvas, 'min_x')
|
||||
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')
|
||||
content_rect = rect.inset_xy(10.0, 10.0)
|
||||
lines, _ = content_rect.divide_y(
|
||||
[12.0, 12.0, 24.0, 24.0, 24.0, 24.0], 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')
|
||||
lines[0].draw_text_cell(
|
||||
canvas,
|
||||
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.0),
|
||||
font_name,
|
||||
9.0,
|
||||
)
|
||||
lines[1].draw_text_cell(
|
||||
canvas,
|
||||
"Running time: %03.1f mins" % (recording_time_sec / 60.0),
|
||||
font_name,
|
||||
9.0,
|
||||
)
|
||||
lines[2].draw_text_cell(
|
||||
canvas, "Actual Start: ______________", font_name, 9.0, vertical_align="b"
|
||||
)
|
||||
lines[3].draw_text_cell(
|
||||
canvas, "Record Date: ______________", font_name, 9.0, vertical_align="b"
|
||||
)
|
||||
lines[4].draw_text_cell(
|
||||
canvas, "Engineer: ______________", font_name, 9.0, vertical_align="b"
|
||||
)
|
||||
lines[5].draw_text_cell(
|
||||
canvas, "Location: ______________", font_name, 9.0, vertical_align="b"
|
||||
)
|
||||
|
||||
|
||||
def draw_footer(canvas, rect, record: ADRLine, report_date, line_no,
|
||||
total_lines):
|
||||
rect.draw_border(canvas, 'max_y')
|
||||
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)]
|
||||
pages_s = [f"Line {line_no} of {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.0, inset_y=2.0
|
||||
)
|
||||
|
||||
|
||||
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 = f"{records[0].title}_{records[0].character_id}_{records[0].character_name}_Log.pdf"
|
||||
|
||||
outfile = "%s_%s_%s_Log.pdf" % (records[0].title,
|
||||
records[0].character_id,
|
||||
records[0].character_name,)
|
||||
assert outfile is not None
|
||||
assert outfile[-4:] == '.pdf', "Output file must have 'pdf' extension!"
|
||||
assert outfile[-4:] == ".pdf", "Output file must have 'pdf' extension!"
|
||||
|
||||
# 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_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]
|
||||
)
|
||||
(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,
|
||||
records[0].character_id))
|
||||
c.setTitle(
|
||||
f"{records[0].title} {records[0].character_name} ({records[0].character_id}) Supervisor's Log"
|
||||
)
|
||||
c.setAuthor(records[0].supervisor)
|
||||
|
||||
recording_time_sec = 0.0
|
||||
@@ -253,8 +303,7 @@ def create_report_for_character(records, report_date,
|
||||
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)
|
||||
@@ -264,17 +313,18 @@ def create_report_for_character(records, report_date,
|
||||
# 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 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()
|
||||
@@ -285,9 +335,11 @@ def create_report_for_character(records, report_date,
|
||||
def output_report(lines, tc_display_format: TimecodeFormat):
|
||||
report_date = datetime.datetime.now()
|
||||
events = sorted(lines, key=lambda x: x.start)
|
||||
character_numbers = set([x.character_id for x in lines])
|
||||
character_numbers = {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,
|
||||
tc_display_format=tc_display_format)
|
||||
create_report_for_character(
|
||||
[e for e in events if e.character_id == n],
|
||||
report_date,
|
||||
tc_display_format=tc_display_format,
|
||||
)
|
||||
|
||||
@@ -1,82 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import List
|
||||
|
||||
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.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.platypus import HRFlowable, KeepTogether, Paragraph, Spacer, Table
|
||||
|
||||
# from reportlab.pdfbase import pdfmetrics
|
||||
# from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
from ..broadcast_timecode import TimecodeFormat
|
||||
from ..docparser.adr_entity import ADRLine
|
||||
from .__init__ import make_doc_template
|
||||
|
||||
|
||||
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
||||
font_name="Helvetica"):
|
||||
character_numbers = set([n.character_id for n in lines])
|
||||
def output_report(
|
||||
lines: list[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica"
|
||||
):
|
||||
character_numbers = {n.character_id for n in lines}
|
||||
# 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 = f"{char_lines[0].title} ({character_name}) {n} ADR Script"
|
||||
filename = f"{char_lines[0].title}_{n}_{character_name}_ADR Script.pdf"
|
||||
|
||||
doc = make_doc_template(page_size=letter, filename=filename,
|
||||
document_title=title,
|
||||
title=char_lines[0].title,
|
||||
document_subheader=char_lines[0].spot or "",
|
||||
supervisor=char_lines[0].supervisor or "",
|
||||
client=char_lines[0].client or "",
|
||||
document_header=character_name or "")
|
||||
doc = make_doc_template(
|
||||
page_size=letter,
|
||||
filename=filename,
|
||||
document_title=title,
|
||||
title=char_lines[0].title,
|
||||
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 = []
|
||||
|
||||
prompt_style = getSampleStyleSheet()['Normal']
|
||||
prompt_style = getSampleStyleSheet()["Normal"]
|
||||
prompt_style.fontName = font_name
|
||||
prompt_style.fontSize = 18.
|
||||
prompt_style.fontSize = 18.0
|
||||
|
||||
prompt_style.leading = 24.
|
||||
prompt_style.leading = 24.0
|
||||
prompt_style.leftIndent = 1.5 * inch
|
||||
prompt_style.rightIndent = 1.5 * inch
|
||||
|
||||
number_style = getSampleStyleSheet()['Normal']
|
||||
number_style = getSampleStyleSheet()["Normal"]
|
||||
number_style.fontName = font_name
|
||||
number_style.fontSize = 14
|
||||
|
||||
number_style.leading = 24
|
||||
number_style.leftIndent = 0.
|
||||
number_style.rightIndent = 0.
|
||||
number_style.leftIndent = 0.0
|
||||
number_style.rightIndent = 0.0
|
||||
|
||||
for line in char_lines:
|
||||
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)
|
||||
]]
|
||||
data_block = [
|
||||
[
|
||||
Paragraph(line.cue_number or "[No QN]", number_style),
|
||||
Paragraph(start_tc + " - " + finish_tc, number_style),
|
||||
]
|
||||
]
|
||||
|
||||
# RIGHTWARDS ARROW →
|
||||
# Unicode: U+2192, UTF-8: E2 86 92
|
||||
# RIGHTWARDS ARROW →
|
||||
# Unicode: U+2192, UTF-8: E2 86 92
|
||||
story.append(
|
||||
KeepTogether(
|
||||
[HRFlowable(width='50%', color=colors.black),
|
||||
Table(data=data_block, colWidths=[1.5 * inch, 6. * inch],
|
||||
style=[('LEFTPADDING', (0, 0), (-1, -1), 0.)]),
|
||||
Paragraph(line.prompt, prompt_style),
|
||||
Spacer(1., inch * 1.5)]
|
||||
[
|
||||
HRFlowable(width="50%", color=colors.black),
|
||||
Table(
|
||||
data=data_block,
|
||||
colWidths=[1.5 * inch, 6.0 * inch],
|
||||
style=[("LEFTPADDING", (0, 0), (-1, -1), 0.0)],
|
||||
),
|
||||
Paragraph(line.prompt or "[No Prompt]", prompt_style),
|
||||
Spacer(1.0, inch * 1.5),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+19
-19
@@ -9,63 +9,63 @@ import sys
|
||||
|
||||
def print_banner_style(message):
|
||||
if sys.stderr.isatty():
|
||||
sys.stderr.write("\n\033[1m%s\033[0m\n\n" % message)
|
||||
sys.stderr.write(f"\n\033[1m{message}\033[0m\n\n")
|
||||
else:
|
||||
sys.stderr.write("\n%s\n\n" % message)
|
||||
sys.stderr.write(f"\n{message}\n\n")
|
||||
|
||||
|
||||
def print_section_header_style(message):
|
||||
if sys.stderr.isatty():
|
||||
sys.stderr.write("\n\033[4m%s\033[0m\n\n" % message)
|
||||
sys.stderr.write(f"\n\033[4m{message}\033[0m\n\n")
|
||||
else:
|
||||
sys.stderr.write("%s\n\n" % message)
|
||||
sys.stderr.write(f"{message}\n\n")
|
||||
|
||||
|
||||
def print_status_style(message):
|
||||
if sys.stderr.isatty():
|
||||
sys.stderr.write("\033[3m - %s\033[0m\n" % message)
|
||||
sys.stderr.write(f"\033[3m - {message}\033[0m\n")
|
||||
else:
|
||||
sys.stderr.write(" - %s\n" % message)
|
||||
sys.stderr.write(f" - {message}\n")
|
||||
|
||||
|
||||
def print_warning(warning_string):
|
||||
if sys.stderr.isatty():
|
||||
sys.stderr.write("\033[3m - %s\033[0m\n" % warning_string)
|
||||
sys.stderr.write(f"\033[3m - {warning_string}\033[0m\n")
|
||||
else:
|
||||
sys.stderr.write(" - %s\n" % warning_string)
|
||||
sys.stderr.write(f" - {warning_string}\n")
|
||||
|
||||
|
||||
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(f'\033[32m"{ok_string}\033[31;1m{not_ok_string}"\033[0m\n')
|
||||
|
||||
if parent_track_name is not None:
|
||||
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)
|
||||
sys.stderr.write(f' ! > On track "{parent_track_name}"\n')
|
||||
|
||||
if clip_time is not None:
|
||||
sys.stderr.write(" ! > In clip name at %s\n" % clip_time)
|
||||
sys.stderr.write(f" ! > In clip name at {clip_time}\n")
|
||||
else:
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.write(" ! Tagging error: \"%s\"\n" % failed_string)
|
||||
sys.stderr.write(f' ! Tagging error: "{failed_string}"\n')
|
||||
sys.stderr.write(" ! %s _______________⬆\n" % (" " * position))
|
||||
|
||||
if parent_track_name is not None:
|
||||
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)
|
||||
sys.stderr.write(f' ! > On track "{parent_track_name}"\n')
|
||||
|
||||
if clip_time is not None:
|
||||
sys.stderr.write(" ! > In clip name at %s\n" % clip_time)
|
||||
sys.stderr.write(f" ! > In clip name at {clip_time}\n")
|
||||
|
||||
sys.stderr.write("\n")
|
||||
|
||||
|
||||
def print_fatal_error(message):
|
||||
if sys.stderr.isatty():
|
||||
sys.stderr.write("\n\033[5;31;1m*** %s ***\033[0m\n" % message)
|
||||
sys.stderr.write(f"\n\033[5;31;1m*** {message} ***\033[0m\n")
|
||||
else:
|
||||
sys.stderr.write("\n%s\n" % message)
|
||||
sys.stderr.write(f"\n{message}\n")
|
||||
|
||||
@@ -2,31 +2,34 @@
|
||||
Validation logic for enforcing various consistency rules.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ptulsconv.docparser.adr_entity import ADRLine
|
||||
from typing import Iterator, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ValidationError:
|
||||
message: str
|
||||
event: Optional[ADRLine] = None
|
||||
event: ADRLine | None = None
|
||||
|
||||
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):
|
||||
values = set(list(map(lambda e: getattr(e, field), input_lines)))
|
||||
def validate_unique_count(input_lines: Iterator[ADRLine], field="title", count=1):
|
||||
values = {getattr(e, field) for e in input_lines}
|
||||
if len(values) > count:
|
||||
yield ValidationError(
|
||||
message="Field {} has too many values (max={}): {}"
|
||||
.format(field, count, values)
|
||||
message=f"Field {field} has too many values (max={count}): {values}"
|
||||
)
|
||||
|
||||
|
||||
@@ -34,52 +37,52 @@ def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
|
||||
for event in input_lines:
|
||||
val = getattr(event, key_field)
|
||||
if not predicate(val):
|
||||
yield ValidationError(message='Field {} not in range'.format(val),
|
||||
event=event)
|
||||
yield ValidationError(message=f"Field {val} not in range", event=event)
|
||||
|
||||
|
||||
def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number',
|
||||
scope=None):
|
||||
values = dict()
|
||||
def validate_unique_field(
|
||||
input_lines: Iterator[ADRLine], field="cue_number", scope=None
|
||||
):
|
||||
values = {}
|
||||
for event in input_lines:
|
||||
this = getattr(event, field)
|
||||
if scope is not None:
|
||||
key = getattr(event, scope)
|
||||
else:
|
||||
key = '_values'
|
||||
key = "_values"
|
||||
|
||||
values.setdefault(key, set())
|
||||
if this in values[key]:
|
||||
yield ValidationError(message='Re-used {}'.format(field),
|
||||
event=event)
|
||||
yield ValidationError(message=f"Re-used {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=f"Empty field {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`
|
||||
"""
|
||||
key_values = set((getattr(x, key_field) for x in input_lines))
|
||||
key_values = {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
|
||||
if getattr(x, key_field) == key_value]
|
||||
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:
|
||||
message = "Non-unique values for key {} = ".format(key_field)
|
||||
message = f"Non-unique values for key {key_field} = "
|
||||
for u in unique_rows:
|
||||
message = message + "\n - {} -> {}".format(u[0], u[1])
|
||||
message = message + f"\n - {u[0]} -> {u[1]}"
|
||||
|
||||
yield ValidationError(message=message, event=None)
|
||||
|
||||
+128
-97
@@ -1,12 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import glob
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
from importlib.metadata import version as module_version
|
||||
from xml.etree.ElementTree import TreeBuilder, tostring
|
||||
from typing import List
|
||||
|
||||
import ptulsconv
|
||||
from ptulsconv.docparser.adr_entity import ADRLine
|
||||
@@ -14,138 +16,160 @@ 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 | None = None,
|
||||
reel_start_frame=0,
|
||||
fps=24,
|
||||
):
|
||||
doc = TreeBuilder(element_factory=None)
|
||||
|
||||
doc.start('Avid:StreamItems', {'xmlns:Avid': 'http://www.avid.com'})
|
||||
doc.start('Avid:XMLFileData', {})
|
||||
doc.start('AvProp', {'name': 'DomainMagic', 'type': 'string'})
|
||||
if report_date is None:
|
||||
report_date = datetime.now()
|
||||
|
||||
doc.start("Avid:StreamItems", {"xmlns:Avid": "http://www.avid.com"})
|
||||
doc.start("Avid:XMLFileData", {})
|
||||
doc.start("AvProp", {"name": "DomainMagic", "type": "string"})
|
||||
doc.data("Domain")
|
||||
doc.end('AvProp')
|
||||
doc.start('AvProp', {'name': 'DomainKey', 'type': 'string'})
|
||||
doc.end("AvProp")
|
||||
doc.start("AvProp", {"name": "DomainKey", "type": "string"})
|
||||
doc.data("58424a44")
|
||||
doc.end('AvProp')
|
||||
doc.end("AvProp")
|
||||
|
||||
def insert_elem(kind, attb, atype, name, value):
|
||||
doc.start('ListElem', {})
|
||||
doc.start('AvProp', {'id': 'ATTR',
|
||||
'name': 'OMFI:ATTB:Kind',
|
||||
'type': 'int32'})
|
||||
doc.start("ListElem", {})
|
||||
doc.start("AvProp", {"id": "ATTR", "name": "OMFI:ATTB:Kind", "type": "int32"})
|
||||
doc.data(kind)
|
||||
doc.end('AvProp')
|
||||
doc.end("AvProp")
|
||||
|
||||
doc.start('AvProp', {'id': 'ATTR',
|
||||
'name': 'OMFI:ATTB:Name',
|
||||
'type': 'string'})
|
||||
doc.start("AvProp", {"id": "ATTR", "name": "OMFI:ATTB:Name", "type": "string"})
|
||||
doc.data(name)
|
||||
doc.end('AvProp')
|
||||
doc.end("AvProp")
|
||||
|
||||
doc.start('AvProp', {'id': 'ATTR',
|
||||
'name': attb,
|
||||
'type': atype})
|
||||
doc.start("AvProp", {"id": "ATTR", "name": attb, "type": atype})
|
||||
doc.data(value)
|
||||
doc.end('AvProp')
|
||||
doc.end("AvProp")
|
||||
|
||||
doc.end('ListElem')
|
||||
doc.end("ListElem")
|
||||
|
||||
for line in lines:
|
||||
doc.start('AvClass', {'id': 'ATTR'})
|
||||
doc.start('AvProp', {'id': 'ATTR',
|
||||
'name': '__OMFI:ATTR:NumItems',
|
||||
'type': 'int32'})
|
||||
doc.data('7')
|
||||
doc.end('AvProp')
|
||||
doc.start("AvClass", {"id": "ATTR"})
|
||||
doc.start(
|
||||
"AvProp", {"id": "ATTR", "name": "__OMFI:ATTR:NumItems", "type": "int32"}
|
||||
)
|
||||
doc.data("7")
|
||||
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('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||
'_ATN_CRM_COLOR', 'yellow')
|
||||
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||
'_ATN_CRM_USER', line.supervisor or "")
|
||||
assert report_date
|
||||
|
||||
marker_name = "%s: %s" % (line.cue_number, line.prompt)
|
||||
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||
'_ATN_CRM_COM', marker_name)
|
||||
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 = f"{line.cue_number}: {line.prompt}"
|
||||
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',
|
||||
str(start_frame - reel_start_frame))
|
||||
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')
|
||||
doc.start("ListElem", {})
|
||||
doc.end("ListElem")
|
||||
|
||||
doc.end('List')
|
||||
doc.end('AvClass')
|
||||
doc.end("List")
|
||||
doc.end("AvClass")
|
||||
|
||||
doc.end('Avid:XMLFileData')
|
||||
doc.end('Avid:StreamItems')
|
||||
doc.end("Avid:XMLFileData")
|
||||
doc.end("Avid:StreamItems")
|
||||
|
||||
|
||||
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("ERRORCODE", {})
|
||||
doc.data("0")
|
||||
doc.end("ERRORCODE")
|
||||
|
||||
doc.start('PRODUCT', {'NAME': ptulsconv.__name__,
|
||||
'VERSION': ptulsconv.__version__})
|
||||
doc.end('PRODUCT')
|
||||
version = module_version("ptulsconv")
|
||||
doc.start("PRODUCT", {"NAME": ptulsconv.__name__, "VERSION": f"{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.end('DATABASE')
|
||||
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', {})
|
||||
doc.start("METADATA", {})
|
||||
for field in adr_field_map:
|
||||
tp = field[2]
|
||||
ft = 'TEXT'
|
||||
ft = "TEXT"
|
||||
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.end('FIELD')
|
||||
doc.end('METADATA')
|
||||
doc.start(
|
||||
"FIELD", {"EMPTYOK": "YES", "MAXREPEAT": "1", "NAME": field[1], "TYPE": ft}
|
||||
)
|
||||
doc.end("FIELD")
|
||||
doc.end("METADATA")
|
||||
|
||||
doc.start('RESULTSET', {'FOUND': str(len(data['events']))})
|
||||
for event in data['events']:
|
||||
doc.start('ROW', {})
|
||||
doc.start("RESULTSET", {"FOUND": str(len(data["events"]))})
|
||||
for event in data["events"]:
|
||||
doc.start("ROW", {})
|
||||
for field in adr_field_map:
|
||||
doc.start('COL', {})
|
||||
doc.start('DATA', {})
|
||||
doc.start("COL", {})
|
||||
doc.start("DATA", {})
|
||||
for key_attempt in field[0]:
|
||||
if key_attempt in event.keys():
|
||||
if key_attempt in event:
|
||||
doc.data(str(event[key_attempt]))
|
||||
break
|
||||
doc.end('DATA')
|
||||
doc.end('COL')
|
||||
doc.end('ROW')
|
||||
doc.end('RESULTSET')
|
||||
doc.end("DATA")
|
||||
doc.end("COL")
|
||||
doc.end("ROW")
|
||||
doc.end("RESULTSET")
|
||||
|
||||
doc.end('FMPXMLRESULT')
|
||||
doc.end("FMPXMLRESULT")
|
||||
docelem = doc.close()
|
||||
xmlstr = tostring(docelem, encoding='unicode', method='xml')
|
||||
xmlstr = tostring(docelem, encoding="unicode", method="xml")
|
||||
output.write(xmlstr)
|
||||
|
||||
|
||||
xslt_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt')
|
||||
xslt_path = os.path.join(pathlib.Path(__file__).parent.absolute(), "xslt")
|
||||
|
||||
|
||||
def xform_options():
|
||||
@@ -154,7 +178,7 @@ def xform_options():
|
||||
|
||||
def dump_xform_options(output=sys.stdout):
|
||||
print("# Available transforms:", file=output)
|
||||
print("# Transform dir: %s" % xslt_path, file=output)
|
||||
print(f"# Transform dir: {xslt_path}", file=output)
|
||||
for f in xform_options():
|
||||
base = os.path.basename(f)
|
||||
name, _ = os.path.splitext(base)
|
||||
@@ -162,22 +186,29 @@ def dump_xform_options(output=sys.stdout):
|
||||
|
||||
|
||||
def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
|
||||
from ptulsconv.reporting import print_status_style
|
||||
import io
|
||||
|
||||
from ptulsconv.reporting import print_status_style
|
||||
|
||||
pipe = io.StringIO()
|
||||
|
||||
print_status_style("Generating base XML")
|
||||
dump_fmpxml(data, input_file, pipe, adr_field_map)
|
||||
|
||||
str_data = pipe.getvalue()
|
||||
print_status_style("Base XML size %i" % (len(str_data)))
|
||||
print_status_style(f"Base XML size {len(str_data)}")
|
||||
|
||||
print_status_style("Running xsltproc")
|
||||
|
||||
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,
|
||||
stdout=output, shell=False, check=True)
|
||||
xsl_path = os.path.join(
|
||||
pathlib.Path(__file__).parent.absolute(), "xslt", xsl_name + ".xsl"
|
||||
)
|
||||
print_status_style(f"Using xsl: {xsl_path}")
|
||||
subprocess.run(
|
||||
["xsltproc", xsl_path, "-"],
|
||||
input=str_data,
|
||||
text=True,
|
||||
stdout=output,
|
||||
shell=False,
|
||||
check=True,
|
||||
)
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser import parse_document
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser import parse_document
|
||||
|
||||
|
||||
class TestRobinHood1(unittest.TestCase):
|
||||
path = os.path.dirname(__file__) + '/../export_cases/Robin Hood Spotting.txt'
|
||||
path = os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting.txt"
|
||||
|
||||
def test_header_export(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertIsNotNone(session.header)
|
||||
self.assertEqual(session.header.session_name, 'Robin Hood Spotting')
|
||||
self.assertEqual(session.header.session_name, "Robin Hood Spotting")
|
||||
self.assertEqual(session.header.sample_rate, 48000.0)
|
||||
self.assertEqual(session.header.bit_depth, 24)
|
||||
self.assertEqual(session.header.timecode_fps, '29.97')
|
||||
self.assertEqual(session.header.timecode_fps, "29.97")
|
||||
self.assertEqual(session.header.timecode_drop_frame, False)
|
||||
|
||||
def test_all_sections(self):
|
||||
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertIsNotNone(session.header)
|
||||
@@ -31,52 +32,74 @@ class TestRobinHood1(unittest.TestCase):
|
||||
|
||||
def test_tracks(self):
|
||||
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertEqual(len(session.tracks), 14)
|
||||
self.assertListEqual(["Scenes", "Robin", "Will", "Marian", "John",
|
||||
"Guy", "Much", "Butcher", "Town Crier",
|
||||
"Soldier 1", "Soldier 2", "Soldier 3",
|
||||
"Priest", "Guest at Court"],
|
||||
list(map(lambda t: t.name, session.tracks)))
|
||||
self.assertListEqual(["", "[ADR] {Actor=Errol Flynn} $CN=1",
|
||||
"[ADR] {Actor=Patrick Knowles} $CN=2",
|
||||
"[ADR] {Actor=Olivia DeHavilland} $CN=3",
|
||||
"[ADR] {Actor=Claude Raines} $CN=4",
|
||||
"[ADR] {Actor=Basil Rathbone} $CN=5",
|
||||
"[ADR] {Actor=Herbert Mundin} $CN=6",
|
||||
"[ADR] {Actor=George Bunny} $CN=101",
|
||||
"[ADR] {Actor=Leonard Mundie} $CN=102",
|
||||
"[ADR] $CN=103",
|
||||
"[ADR] $CN=104",
|
||||
"[ADR] $CN=105",
|
||||
"[ADR] {Actor=Thomas R. Mills} $CN=106",
|
||||
"[ADR] $CN=107"],
|
||||
list(map(lambda t: t.comments, session.tracks)))
|
||||
self.assertListEqual(
|
||||
[
|
||||
"Scenes",
|
||||
"Robin",
|
||||
"Will",
|
||||
"Marian",
|
||||
"John",
|
||||
"Guy",
|
||||
"Much",
|
||||
"Butcher",
|
||||
"Town Crier",
|
||||
"Soldier 1",
|
||||
"Soldier 2",
|
||||
"Soldier 3",
|
||||
"Priest",
|
||||
"Guest at Court",
|
||||
],
|
||||
list(map(lambda t: t.name, session.tracks)),
|
||||
)
|
||||
self.assertListEqual(
|
||||
[
|
||||
"",
|
||||
"[ADR] {Actor=Errol Flynn} $CN=1",
|
||||
"[ADR] {Actor=Patrick Knowles} $CN=2",
|
||||
"[ADR] {Actor=Olivia DeHavilland} $CN=3",
|
||||
"[ADR] {Actor=Claude Raines} $CN=4",
|
||||
"[ADR] {Actor=Basil Rathbone} $CN=5",
|
||||
"[ADR] {Actor=Herbert Mundin} $CN=6",
|
||||
"[ADR] {Actor=George Bunny} $CN=101",
|
||||
"[ADR] {Actor=Leonard Mundie} $CN=102",
|
||||
"[ADR] $CN=103",
|
||||
"[ADR] $CN=104",
|
||||
"[ADR] $CN=105",
|
||||
"[ADR] {Actor=Thomas R. Mills} $CN=106",
|
||||
"[ADR] $CN=107",
|
||||
],
|
||||
list(map(lambda t: t.comments, session.tracks)),
|
||||
)
|
||||
|
||||
def test_a_track(self):
|
||||
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
|
||||
guy_track = session.tracks[5]
|
||||
self.assertEqual(guy_track.name, 'Guy')
|
||||
self.assertEqual(guy_track.comments, '[ADR] {Actor=Basil Rathbone} $CN=5')
|
||||
self.assertEqual(guy_track.name, "Guy")
|
||||
self.assertEqual(guy_track.comments, "[ADR] {Actor=Basil Rathbone} $CN=5")
|
||||
self.assertEqual(guy_track.user_delay_samples, 0)
|
||||
self.assertListEqual(guy_track.state, [])
|
||||
self.assertEqual(len(guy_track.clips), 16)
|
||||
self.assertEqual(guy_track.clips[5].channel, 1)
|
||||
self.assertEqual(guy_track.clips[5].event, 6)
|
||||
self.assertEqual(guy_track.clips[5].clip_name, "\"What's your name? You Saxon dog!\" $QN=GY106")
|
||||
self.assertEqual(
|
||||
guy_track.clips[5].clip_name,
|
||||
'"What\'s your name? You Saxon dog!" $QN=GY106',
|
||||
)
|
||||
self.assertEqual(guy_track.clips[5].start_timecode, "01:04:19:15")
|
||||
self.assertEqual(guy_track.clips[5].finish_timecode, "01:04:21:28")
|
||||
self.assertEqual(guy_track.clips[5].duration, "00:00:02:13")
|
||||
self.assertEqual(guy_track.clips[5].timestamp, None)
|
||||
self.assertEqual(guy_track.clips[5].state, 'Unmuted')
|
||||
self.assertEqual(guy_track.clips[5].state, "Unmuted")
|
||||
|
||||
def test_memory_locations(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertEqual(len(session.markers), 1)
|
||||
@@ -86,5 +109,5 @@ class TestRobinHood1(unittest.TestCase):
|
||||
self.assertEqual(session.markers[0].units, "Samples")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,52 +1,56 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser import parse_document
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser import parse_document
|
||||
|
||||
|
||||
class TestRobinHood5(unittest.TestCase):
|
||||
path = os.path.dirname(__file__) + '/../export_cases/Robin Hood Spotting5.txt'
|
||||
path = os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting5.txt"
|
||||
|
||||
def test_skipped_segments(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertIsNone(session.files)
|
||||
self.assertIsNone(session.clips)
|
||||
|
||||
def test_plugins(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertEqual(len(session.plugins), 2)
|
||||
|
||||
def test_stereo_track(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
self.assertEqual(session.tracks[1].name, 'MX WT (Stereo)')
|
||||
self.assertEqual(session.tracks[1].name, "MX WT (Stereo)")
|
||||
self.assertEqual(len(session.tracks[1].clips), 2)
|
||||
self.assertEqual(session.tracks[1].clips[0].clip_name, 'RobinHood.1-01.L')
|
||||
self.assertEqual(session.tracks[1].clips[1].clip_name, 'RobinHood.1-01.R')
|
||||
self.assertEqual(session.tracks[1].clips[0].clip_name, "RobinHood.1-01.L")
|
||||
self.assertEqual(session.tracks[1].clips[1].clip_name, "RobinHood.1-01.R")
|
||||
|
||||
def test_a_track(self):
|
||||
with open(self.path,"r") as file:
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
guy_track = session.tracks[8]
|
||||
self.assertEqual(guy_track.name, 'Guy')
|
||||
self.assertEqual(guy_track.comments, '[ADR] {Actor=Basil Rathbone} $CN=5')
|
||||
self.assertEqual(guy_track.name, "Guy")
|
||||
self.assertEqual(guy_track.comments, "[ADR] {Actor=Basil Rathbone} $CN=5")
|
||||
self.assertEqual(guy_track.user_delay_samples, 0)
|
||||
self.assertListEqual(guy_track.state, ['Solo'])
|
||||
self.assertListEqual(guy_track.state, ["Solo"])
|
||||
self.assertEqual(len(guy_track.clips), 16)
|
||||
self.assertEqual(guy_track.clips[5].channel, 1)
|
||||
self.assertEqual(guy_track.clips[5].event, 6)
|
||||
self.assertEqual(guy_track.clips[5].clip_name, "\"What's your name? You Saxon dog!\" $QN=GY106")
|
||||
self.assertEqual(
|
||||
guy_track.clips[5].clip_name,
|
||||
'"What\'s your name? You Saxon dog!" $QN=GY106',
|
||||
)
|
||||
self.assertEqual(guy_track.clips[5].start_timecode, "01:04:19:15.00")
|
||||
self.assertEqual(guy_track.clips[5].finish_timecode, "01:04:21:28.00")
|
||||
self.assertEqual(guy_track.clips[5].duration, "00:00:02:13.00")
|
||||
self.assertEqual(guy_track.clips[5].timestamp, "01:04:19:09.70")
|
||||
self.assertEqual(guy_track.clips[5].state, 'Unmuted')
|
||||
self.assertEqual(guy_track.clips[5].state, "Unmuted")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser import parse_document
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser import parse_document
|
||||
|
||||
|
||||
class TestRobinHood6(unittest.TestCase):
|
||||
path = os.path.dirname(__file__) + '/../export_cases/Robin Hood Spotting6.txt'
|
||||
path = os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting6.txt"
|
||||
|
||||
def test_a_track(self):
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
|
||||
marian_track = session.tracks[6]
|
||||
self.assertEqual(marian_track.name, 'Marian')
|
||||
self.assertEqual(marian_track.comments, '[ADR] {Actor=Olivia DeHavilland} $CN=3')
|
||||
self.assertEqual(marian_track.name, "Marian")
|
||||
self.assertEqual(
|
||||
marian_track.comments, "[ADR] {Actor=Olivia DeHavilland} $CN=3"
|
||||
)
|
||||
self.assertEqual(marian_track.user_delay_samples, 0)
|
||||
self.assertListEqual(marian_track.state, ['Solo'])
|
||||
self.assertListEqual(marian_track.state, ["Solo"])
|
||||
self.assertEqual(len(marian_track.clips), 4)
|
||||
self.assertListEqual(marian_track.plugins, ['Channel Strip (mono)', 'ReVibe II (mono/5.1)'])
|
||||
self.assertListEqual(
|
||||
marian_track.plugins, ["Channel Strip (mono)", "ReVibe II (mono/5.1)"]
|
||||
)
|
||||
self.assertEqual(marian_track.clips[2].channel, 1)
|
||||
self.assertEqual(marian_track.clips[2].event, 3)
|
||||
self.assertEqual(marian_track.clips[2].clip_name,
|
||||
"\"Isn't that reason enough for a Royal Ward who must obey her guardian?\" $QN=M103")
|
||||
self.assertEqual(
|
||||
marian_track.clips[2].clip_name,
|
||||
'"Isn\'t that reason enough for a Royal Ward who must obey her guardian?" $QN=M103',
|
||||
)
|
||||
self.assertEqual(marian_track.clips[2].start_timecode, "01:08:01:11")
|
||||
self.assertEqual(marian_track.clips[2].finish_timecode, "01:08:04:24")
|
||||
self.assertEqual(marian_track.clips[2].duration, "00:00:03:12")
|
||||
self.assertEqual(marian_track.clips[2].timestamp, "01:08:01:11")
|
||||
self.assertEqual(marian_track.clips[2].state, 'Unmuted')
|
||||
self.assertEqual(marian_track.clips[2].state, "Unmuted")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser import parse_document
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser import parse_document
|
||||
|
||||
|
||||
class TestRobinHoodDF(unittest.TestCase):
|
||||
path = os.path.dirname(__file__) + '/../export_cases/Robin Hood SpottingDF.txt'
|
||||
path = os.path.dirname(__file__) + "/../export_cases/Robin Hood SpottingDF.txt"
|
||||
|
||||
def test_header_export_df(self):
|
||||
|
||||
|
||||
with open(self.path, "r") as file:
|
||||
session = parse_document(file.read())
|
||||
|
||||
|
||||
self.assertEqual(session.header.timecode_drop_frame, True)
|
||||
|
||||
def test_a_track(self):
|
||||
@@ -19,16 +20,18 @@ class TestRobinHoodDF(unittest.TestCase):
|
||||
session = parse_document(file.read())
|
||||
|
||||
guy_track = session.tracks[4]
|
||||
self.assertEqual(guy_track.name, 'Robin')
|
||||
self.assertEqual(guy_track.comments, '[ADR] {Actor=Errol Flynn} $CN=1')
|
||||
self.assertEqual(guy_track.name, "Robin")
|
||||
self.assertEqual(guy_track.comments, "[ADR] {Actor=Errol Flynn} $CN=1")
|
||||
self.assertEqual(guy_track.user_delay_samples, 0)
|
||||
self.assertListEqual(guy_track.state, [])
|
||||
self.assertEqual(len(guy_track.clips), 10)
|
||||
self.assertEqual(guy_track.clips[5].channel, 1)
|
||||
self.assertEqual(guy_track.clips[5].event, 6)
|
||||
self.assertEqual(guy_track.clips[5].clip_name, "\"Hold there! What's his fault?\" $QN=R106")
|
||||
self.assertEqual(
|
||||
guy_track.clips[5].clip_name, '"Hold there! What\'s his fault?" $QN=R106'
|
||||
)
|
||||
self.assertEqual(guy_track.clips[5].start_timecode, "01:05:30;15")
|
||||
self.assertEqual(guy_track.clips[5].finish_timecode, "01:05:32;01")
|
||||
self.assertEqual(guy_track.clips[5].duration, "00:00:01;16")
|
||||
self.assertEqual(guy_track.clips[5].timestamp, None)
|
||||
self.assertEqual(guy_track.clips[5].state, 'Unmuted')
|
||||
self.assertEqual(guy_track.clips[5].state, "Unmuted")
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import tempfile
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import os
|
||||
import glob
|
||||
from ptulsconv import commands
|
||||
|
||||
|
||||
class TestPDFExport(unittest.TestCase):
|
||||
def test_report_generation(self):
|
||||
"""
|
||||
Setp through every text file in export_cases and make sure it can
|
||||
Setp through every text file in export_cases and make sure it can
|
||||
be converted into PDF docs without throwing an error
|
||||
"""
|
||||
files = []
|
||||
files = [os.path.dirname(__file__) +
|
||||
"/../export_cases/Robin Hood Spotting.txt"]
|
||||
files = [os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting.txt"]
|
||||
for path in files:
|
||||
tempdir = tempfile.TemporaryDirectory()
|
||||
os.chdir(tempdir.name)
|
||||
try:
|
||||
commands.convert(input_file=path, major_mode='doc')
|
||||
commands.convert(input_file=path, major_mode="doc")
|
||||
except Exception as e:
|
||||
print("Error in test_report_generation")
|
||||
print(f"File: {path}")
|
||||
@@ -33,13 +29,14 @@ class TestPDFExport(unittest.TestCase):
|
||||
|
||||
def test_report_generation_track_markers(self):
|
||||
files = []
|
||||
files.append(os.path.dirname(__file__) +
|
||||
"/../export_cases/Test for ptulsconv.txt")
|
||||
files.append(
|
||||
os.path.dirname(__file__) + "/../export_cases/Test for ptulsconv.txt"
|
||||
)
|
||||
for path in files:
|
||||
tempdir = tempfile.TemporaryDirectory()
|
||||
os.chdir(tempdir.name)
|
||||
try:
|
||||
commands.convert(input_file=path, major_mode='doc')
|
||||
commands.convert(input_file=path, major_mode="doc")
|
||||
except Exception as e:
|
||||
print("Error in test_report_generation_track_markers")
|
||||
print(f"File: {path}")
|
||||
@@ -49,5 +46,5 @@ class TestPDFExport(unittest.TestCase):
|
||||
tempdir.cleanup()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser.tag_compiler import Event
|
||||
from ptulsconv.docparser.adr_entity import ADRLine, make_entity
|
||||
from fractions import Fraction
|
||||
|
||||
from ptulsconv.docparser.adr_entity import ADRLine, make_entity
|
||||
from ptulsconv.docparser.tag_compiler import Event
|
||||
|
||||
|
||||
class TestADREntity(unittest.TestCase):
|
||||
def test_event2line(self):
|
||||
tags = {
|
||||
'Ver': '1.0',
|
||||
'Actor': "Bill",
|
||||
'CN': "1",
|
||||
'QN': 'J1001',
|
||||
'R': 'Noise',
|
||||
'EFF': 'EFF'
|
||||
"Ver": "1.0",
|
||||
"Actor": "Bill",
|
||||
"CN": "1",
|
||||
"QN": "J1001",
|
||||
"R": "Noise",
|
||||
"EFF": "EFF",
|
||||
}
|
||||
event = Event(clip_name='"This is a test." (sotto voce)',
|
||||
track_name="Justin",
|
||||
session_name="Test Project",
|
||||
tags=tags,
|
||||
start=Fraction(0, 1), finish=Fraction(1, 1))
|
||||
event = Event(
|
||||
clip_name='"This is a test." (sotto voce)',
|
||||
track_name="Justin",
|
||||
session_name="Test Project",
|
||||
tags=tags,
|
||||
start=Fraction(0, 1),
|
||||
finish=Fraction(1, 1),
|
||||
)
|
||||
|
||||
line = make_entity(event)
|
||||
|
||||
self.assertIsInstance(line, ADRLine)
|
||||
self.assertEqual('Bill', line.actor_name)
|
||||
self.assertEqual('Justin', line.character_name)
|
||||
self.assertEqual("Bill", line.actor_name)
|
||||
self.assertEqual("Justin", line.character_name)
|
||||
self.assertEqual('"This is a test." (sotto voce)', line.prompt)
|
||||
self.assertEqual('Noise', line.reason)
|
||||
self.assertEqual('J1001', line.cue_number)
|
||||
self.assertEqual("Noise", line.reason)
|
||||
self.assertEqual("J1001", line.cue_number)
|
||||
self.assertEqual(True, line.effort)
|
||||
self.assertEqual('Test Project', line.title)
|
||||
self.assertEqual('1.0', line.version)
|
||||
self.assertEqual("Test Project", line.title)
|
||||
self.assertEqual("1.0", line.version)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import unittest
|
||||
from ptulsconv import broadcast_timecode
|
||||
from fractions import Fraction
|
||||
|
||||
from ptulsconv import broadcast_timecode
|
||||
|
||||
|
||||
class TestBroadcastTimecode(unittest.TestCase):
|
||||
def test_basic_to_frame_count(self):
|
||||
r1 = "01:00:00:00"
|
||||
@@ -66,7 +68,9 @@ class TestBroadcastTimecode(unittest.TestCase):
|
||||
def test_seconds_to_smpte(self):
|
||||
secs = Fraction(25, 24)
|
||||
frame_duration = Fraction(1, 24)
|
||||
tc_format = broadcast_timecode.TimecodeFormat(frame_duration=frame_duration, logical_fps=24, drop_frame=False)
|
||||
tc_format = broadcast_timecode.TimecodeFormat(
|
||||
frame_duration=frame_duration, logical_fps=24, drop_frame=False
|
||||
)
|
||||
s1 = tc_format.seconds_to_smpte(secs)
|
||||
self.assertEqual(s1, "00:00:01:01")
|
||||
|
||||
@@ -77,9 +81,11 @@ class TestBroadcastTimecode(unittest.TestCase):
|
||||
|
||||
def test_unparseable_timecode(self):
|
||||
time_str = "11.32-19"
|
||||
s1 = broadcast_timecode.smpte_to_frame_count(time_str, frames_per_logical_second=24)
|
||||
s1 = broadcast_timecode.smpte_to_frame_count(
|
||||
time_str, frames_per_logical_second=24
|
||||
)
|
||||
self.assertIsNone(s1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser.doc_entity import HeaderDescriptor
|
||||
from fractions import Fraction
|
||||
|
||||
from ptulsconv.docparser.doc_entity import HeaderDescriptor
|
||||
|
||||
|
||||
class DocParserTestCase(unittest.TestCase):
|
||||
|
||||
def test_header(self):
|
||||
header = HeaderDescriptor(session_name="Test Session",
|
||||
sample_rate=48000.0,
|
||||
bit_depth=24,
|
||||
start_timecode="00:59:52:00",
|
||||
timecode_format="30",
|
||||
timecode_drop_frame=False,
|
||||
count_audio_tracks=0,
|
||||
count_clips=0,
|
||||
count_files=0)
|
||||
header = HeaderDescriptor(
|
||||
session_name="Test Session",
|
||||
sample_rate=48000.0,
|
||||
bit_depth=24,
|
||||
start_timecode="00:59:52:00",
|
||||
timecode_format="30",
|
||||
timecode_drop_frame=False,
|
||||
count_audio_tracks=0,
|
||||
count_clips=0,
|
||||
count_files=0,
|
||||
)
|
||||
|
||||
self.assertEqual(header.session_name, "Test Session")
|
||||
self.assertEqual(header.start_time, Fraction((59 * 60 + 52) * 30, 30))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from ptulsconv import footage
|
||||
|
||||
|
||||
class TestFootage(unittest.TestCase):
|
||||
def test_basic_footage(self):
|
||||
r1 = "90+0"
|
||||
@@ -11,5 +13,3 @@ class TestFootage(unittest.TestCase):
|
||||
r1 = "1+8"
|
||||
f1 = footage.footage_to_seconds(r1)
|
||||
self.assertEqual(float(f1 or 0), 1.0)
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import unittest
|
||||
from fractions import Fraction
|
||||
|
||||
import ptulsconv.docparser.tag_compiler
|
||||
from ptulsconv.docparser import doc_entity
|
||||
from fractions import Fraction
|
||||
|
||||
|
||||
class TestTagCompiler(unittest.TestCase):
|
||||
|
||||
def test_one_track(self):
|
||||
c = ptulsconv.docparser.tag_compiler.TagCompiler()
|
||||
|
||||
@@ -16,31 +15,48 @@ class TestTagCompiler(unittest.TestCase):
|
||||
|
||||
events = c.compile_events()
|
||||
event1 = next(events)
|
||||
self.assertEqual('This is clip 1', event1.clip_name)
|
||||
self.assertEqual('Track 1', event1.track_name)
|
||||
self.assertEqual('Test Session', event1.session_name)
|
||||
self.assertEqual(dict(A='A',
|
||||
Color='Blue',
|
||||
Ver='1.1',
|
||||
Mode='2',
|
||||
Comment='This is some text in the comments',
|
||||
Part='1'), event1.tags)
|
||||
self.assertEqual("This is clip 1", event1.clip_name)
|
||||
self.assertEqual("Track 1", event1.track_name)
|
||||
self.assertEqual("Test Session", event1.session_name)
|
||||
self.assertEqual(
|
||||
dict(
|
||||
A="A",
|
||||
Color="Blue",
|
||||
Ver="1.1",
|
||||
Mode="2",
|
||||
Comment="This is some text in the comments",
|
||||
Part="1",
|
||||
),
|
||||
event1.tags,
|
||||
)
|
||||
self.assertEqual(Fraction(3600, 1), event1.start)
|
||||
|
||||
event2 = next(events)
|
||||
self.assertEqual("This is the second clip ...and this is the last clip", event2.clip_name)
|
||||
self.assertEqual('Track 1', event2.track_name)
|
||||
self.assertEqual('Test Session', event2.session_name)
|
||||
self.assertEqual(dict(R='Noise', A='A', B='B',
|
||||
Color='Red',
|
||||
Comment='This is some text in the comments',
|
||||
N='1', Mode='2',
|
||||
Ver='1.1',
|
||||
M1='M1',
|
||||
Part='2'), event2.tags)
|
||||
self.assertEqual(
|
||||
"This is the second clip ...and this is the last clip", event2.clip_name
|
||||
)
|
||||
self.assertEqual("Track 1", event2.track_name)
|
||||
self.assertEqual("Test Session", event2.session_name)
|
||||
self.assertEqual(
|
||||
dict(
|
||||
R="Noise",
|
||||
A="A",
|
||||
B="B",
|
||||
Color="Red",
|
||||
Comment="This is some text in the comments",
|
||||
N="1",
|
||||
Mode="2",
|
||||
Ver="1.1",
|
||||
M1="M1",
|
||||
Part="2",
|
||||
),
|
||||
event2.tags,
|
||||
)
|
||||
|
||||
self.assertEqual(c.session.header.convert_timecode('01:00:01:10'), event2.start)
|
||||
self.assertEqual(c.session.header.convert_timecode('01:00:03:00'), event2.finish)
|
||||
self.assertEqual(c.session.header.convert_timecode("01:00:01:10"), event2.start)
|
||||
self.assertEqual(
|
||||
c.session.header.convert_timecode("01:00:03:00"), event2.finish
|
||||
)
|
||||
|
||||
self.assertIsNone(next(events, None))
|
||||
|
||||
@@ -51,76 +67,94 @@ class TestTagCompiler(unittest.TestCase):
|
||||
|
||||
all_tags = c.compile_tag_list()
|
||||
|
||||
self.assertTrue(all_tags['Mode'] == {'2', '1'})
|
||||
self.assertTrue(all_tags["Mode"] == {"2", "1"})
|
||||
|
||||
@staticmethod
|
||||
def make_test_session():
|
||||
test_header = doc_entity.HeaderDescriptor(session_name="Test Session $Ver=1.1",
|
||||
sample_rate=48000,
|
||||
timecode_format="24",
|
||||
timecode_drop_frame=False,
|
||||
bit_depth=24,
|
||||
start_timecode='00:59:00:00',
|
||||
count_audio_tracks=1,
|
||||
count_clips=3,
|
||||
count_files=0
|
||||
)
|
||||
test_header = doc_entity.HeaderDescriptor(
|
||||
session_name="Test Session $Ver=1.1",
|
||||
sample_rate=48000,
|
||||
timecode_format="24",
|
||||
timecode_drop_frame=False,
|
||||
bit_depth=24,
|
||||
start_timecode="00:59:00:00",
|
||||
count_audio_tracks=1,
|
||||
count_clips=3,
|
||||
count_files=0,
|
||||
)
|
||||
test_clips = [
|
||||
doc_entity.TrackClipDescriptor(channel=1, event=1,
|
||||
clip_name='This is clip 1 {Color=Blue} $Mode=2',
|
||||
start_time='01:00:00:00',
|
||||
finish_time='01:00:01:03',
|
||||
duration='00:00:01:03',
|
||||
state='Unmuted',
|
||||
timestamp=None),
|
||||
doc_entity.TrackClipDescriptor(channel=1, event=2,
|
||||
clip_name='This is the second clip {R=Noise} [B] $Mode=2',
|
||||
start_time='01:00:01:10',
|
||||
finish_time='01:00:02:00',
|
||||
duration='00:00:00:14',
|
||||
state='Unmuted',
|
||||
timestamp=None),
|
||||
doc_entity.TrackClipDescriptor(channel=1, event=3,
|
||||
clip_name='& ...and this is the last clip $N=1 $Mode=2',
|
||||
start_time='01:00:02:00',
|
||||
finish_time='01:00:03:00',
|
||||
duration='00:00:01:00',
|
||||
state='Unmuted',
|
||||
timestamp=None),
|
||||
doc_entity.TrackClipDescriptor(
|
||||
channel=1,
|
||||
event=1,
|
||||
clip_name="This is clip 1 {Color=Blue} $Mode=2",
|
||||
start_time="01:00:00:00",
|
||||
finish_time="01:00:01:03",
|
||||
duration="00:00:01:03",
|
||||
state="Unmuted",
|
||||
timestamp=None,
|
||||
),
|
||||
doc_entity.TrackClipDescriptor(
|
||||
channel=1,
|
||||
event=2,
|
||||
clip_name="This is the second clip {R=Noise} [B] $Mode=2",
|
||||
start_time="01:00:01:10",
|
||||
finish_time="01:00:02:00",
|
||||
duration="00:00:00:14",
|
||||
state="Unmuted",
|
||||
timestamp=None,
|
||||
),
|
||||
doc_entity.TrackClipDescriptor(
|
||||
channel=1,
|
||||
event=3,
|
||||
clip_name="& ...and this is the last clip $N=1 $Mode=2",
|
||||
start_time="01:00:02:00",
|
||||
finish_time="01:00:03:00",
|
||||
duration="00:00:01:00",
|
||||
state="Unmuted",
|
||||
timestamp=None,
|
||||
),
|
||||
]
|
||||
test_track = doc_entity.TrackDescriptor(
|
||||
index=0,
|
||||
name="Track 1 [A] {Color=Red} $Mode=1",
|
||||
comments="{Comment=This is some text in the comments}",
|
||||
user_delay_samples=0,
|
||||
plugins=[],
|
||||
state=[],
|
||||
clips=test_clips)
|
||||
comments="{Comment=This is some text in the comments}",
|
||||
user_delay_samples=0,
|
||||
plugins=[],
|
||||
state=[],
|
||||
clips=test_clips,
|
||||
)
|
||||
|
||||
markers = [doc_entity.MarkerDescriptor(number=1,
|
||||
location="01:00:00:00",
|
||||
time_reference=48000 * 3600,
|
||||
units="Samples",
|
||||
name="Marker 1 {Part=1}",
|
||||
comments="", track_marker=False,
|
||||
),
|
||||
doc_entity.MarkerDescriptor(number=2,
|
||||
location="01:00:01:00",
|
||||
time_reference=48000 * 3601,
|
||||
units="Samples",
|
||||
name="Marker 2 {Part=2}",
|
||||
comments="[M1]", track_marker=False,
|
||||
),
|
||||
]
|
||||
markers = [
|
||||
doc_entity.MarkerDescriptor(
|
||||
number=1,
|
||||
location="01:00:00:00",
|
||||
time_reference=48000 * 3600,
|
||||
units="Samples",
|
||||
name="Marker 1 {Part=1}",
|
||||
comments="",
|
||||
track_marker=False,
|
||||
),
|
||||
doc_entity.MarkerDescriptor(
|
||||
number=2,
|
||||
location="01:00:01:00",
|
||||
time_reference=48000 * 3601,
|
||||
units="Samples",
|
||||
name="Marker 2 {Part=2}",
|
||||
comments="[M1]",
|
||||
track_marker=False,
|
||||
),
|
||||
]
|
||||
|
||||
test_session = doc_entity.SessionDescriptor(header=test_header,
|
||||
tracks=[test_track],
|
||||
clips=[],
|
||||
files=[],
|
||||
markers=markers,
|
||||
plugins=[])
|
||||
test_session = doc_entity.SessionDescriptor(
|
||||
header=test_header,
|
||||
tracks=[test_track],
|
||||
clips=[],
|
||||
files=[],
|
||||
markers=markers,
|
||||
plugins=[],
|
||||
)
|
||||
return test_session
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser.tagged_string_parser_visitor import parse_tags, TagPreModes
|
||||
from ptulsconv.docparser.tagged_string_parser_visitor import TagPreModes, parse_tags
|
||||
|
||||
|
||||
class TestTagInterpreter(unittest.TestCase):
|
||||
@@ -17,12 +17,12 @@ class TestTagInterpreter(unittest.TestCase):
|
||||
|
||||
def test_tags(self):
|
||||
s1 = parse_tags("{a=100}")
|
||||
self.assertEqual(s1.tag_dict['a'], "100")
|
||||
self.assertEqual(s1.tag_dict["a"], "100")
|
||||
|
||||
s2 = parse_tags("{b=This is a test} [option] $X=9")
|
||||
self.assertEqual(s2.tag_dict['b'], 'This is a test')
|
||||
self.assertEqual(s2.tag_dict['option'], 'option')
|
||||
self.assertEqual(s2.tag_dict['X'], "9")
|
||||
self.assertEqual(s2.tag_dict["b"], "This is a test")
|
||||
self.assertEqual(s2.tag_dict["option"], "option")
|
||||
self.assertEqual(s2.tag_dict["X"], "9")
|
||||
|
||||
def test_modes(self):
|
||||
s1 = parse_tags("@ Monday Tuesday {a=1}")
|
||||
@@ -35,5 +35,5 @@ class TestTagInterpreter(unittest.TestCase):
|
||||
self.assertEqual(s3.mode, TagPreModes.APPEND)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import unittest
|
||||
from ptulsconv.docparser import doc_entity, pt_doc_parser, tag_compiler
|
||||
import os.path
|
||||
import unittest
|
||||
|
||||
from ptulsconv.docparser import doc_entity, pt_doc_parser, tag_compiler
|
||||
|
||||
|
||||
class TaggingIntegratedTests(unittest.TestCase):
|
||||
path = os.path.dirname(__file__) + '/../export_cases/Tag Tests/Tag Tests.txt'
|
||||
path = os.path.dirname(__file__) + "/../export_cases/Tag Tests/Tag Tests.txt"
|
||||
|
||||
def test_event_list(self):
|
||||
with open(self.path, 'r') as f:
|
||||
with open(self.path, "r") as f:
|
||||
document_ast = pt_doc_parser.protools_text_export_grammar.parse(f.read())
|
||||
document: doc_entity.SessionDescriptor = pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
document: doc_entity.SessionDescriptor = (
|
||||
pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
)
|
||||
compiler = tag_compiler.TagCompiler()
|
||||
compiler.session = document
|
||||
|
||||
@@ -27,9 +30,11 @@ class TaggingIntegratedTests(unittest.TestCase):
|
||||
self.assertEqual("Region 04", events[8].clip_name)
|
||||
|
||||
def test_append(self):
|
||||
with open(self.path, 'r') as f:
|
||||
with open(self.path, "r") as f:
|
||||
document_ast = pt_doc_parser.protools_text_export_grammar.parse(f.read())
|
||||
document: doc_entity.SessionDescriptor = pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
document: doc_entity.SessionDescriptor = (
|
||||
pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
)
|
||||
compiler = tag_compiler.TagCompiler()
|
||||
compiler.session = document
|
||||
|
||||
@@ -39,20 +44,26 @@ class TaggingIntegratedTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual("Dolor sic amet the rain in spain", events[2].clip_name)
|
||||
|
||||
self.assertEqual(document.header.convert_timecode("01:00:10:00"), events[2].start)
|
||||
self.assertEqual(document.header.convert_timecode("01:00:25:00"), events[2].finish)
|
||||
self.assertEqual(
|
||||
document.header.convert_timecode("01:00:10:00"), events[2].start
|
||||
)
|
||||
self.assertEqual(
|
||||
document.header.convert_timecode("01:00:25:00"), events[2].finish
|
||||
)
|
||||
|
||||
self.assertIn('X', events[2].tags.keys())
|
||||
self.assertIn('ABC', events[2].tags.keys())
|
||||
self.assertIn('A', events[2].tags.keys())
|
||||
self.assertEqual('302', events[2].tags['X'])
|
||||
self.assertEqual('ABC', events[2].tags['ABC'])
|
||||
self.assertEqual('1', events[2].tags['A'])
|
||||
self.assertIn("X", events[2].tags.keys())
|
||||
self.assertIn("ABC", events[2].tags.keys())
|
||||
self.assertIn("A", events[2].tags.keys())
|
||||
self.assertEqual("302", events[2].tags["X"])
|
||||
self.assertEqual("ABC", events[2].tags["ABC"])
|
||||
self.assertEqual("1", events[2].tags["A"])
|
||||
|
||||
def test_successive_appends(self):
|
||||
with open(self.path, 'r') as f:
|
||||
with open(self.path, "r") as f:
|
||||
document_ast = pt_doc_parser.protools_text_export_grammar.parse(f.read())
|
||||
document: doc_entity.SessionDescriptor = pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
document: doc_entity.SessionDescriptor = (
|
||||
pt_doc_parser.DocParserVisitor().visit(document_ast)
|
||||
)
|
||||
compiler = tag_compiler.TagCompiler()
|
||||
compiler.session = document
|
||||
|
||||
@@ -62,9 +73,13 @@ class TaggingIntegratedTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual("A B C", events[3].clip_name)
|
||||
|
||||
self.assertEqual(document.header.convert_timecode("01:00:15:00"), events[3].start)
|
||||
self.assertEqual(document.header.convert_timecode("01:00:45:00"), events[3].finish)
|
||||
self.assertEqual(
|
||||
document.header.convert_timecode("01:00:15:00"), events[3].start
|
||||
)
|
||||
self.assertEqual(
|
||||
document.header.convert_timecode("01:00:45:00"), events[3].finish
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -8,13 +8,13 @@ class MyTestCase(unittest.TestCase):
|
||||
v = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
expected = [1, 2, 7, 5, 6, 15, 9, 10]
|
||||
|
||||
should = (lambda x, y: y % 4 == 0)
|
||||
do_combine = (lambda x, y: x + y)
|
||||
should = lambda x, y: y % 4 == 0
|
||||
do_combine = lambda x, y: x + y
|
||||
|
||||
r = apply_appends(iter(v), should, do_combine)
|
||||
r1 = list(r)
|
||||
self.assertEqual(r1, expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user