Compare commits

..

7 Commits

Author SHA1 Message Date
jamie ab19793bd1 Merge branch 'master' of https://github.com/iluvcapra/ptulsconv into bug-ruff-2026 2026-09-17 20:41:35 -07:00
jamie 7730f6be23 Ruff fixes in progress 2026-09-17 20:35:11 -07:00
jamie e286132101 Ruff fixes in progress 2026-09-17 20:26:12 -07:00
jamie 4afe4b7a3e Ruff fixes in progress 2026-09-17 20:25:28 -07:00
jamie cb8de2d785 Ruff fixes in progress 2026-09-17 20:20:54 -07:00
jamie 9e9ad75f77 Ruff fixes in progress 2026-09-17 20:06:46 -07:00
jamie f7808e2236 Ruff format 2026-09-17 15:23:42 -07:00
20 changed files with 278 additions and 215 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps: steps:
- uses: actions/checkout@v2.5.0 - uses: actions/checkout@v2.5.0
+22
View File
@@ -0,0 +1,22 @@
name: Test Toot
on:
workflow_dispatch:
jobs:
print-tag:
runs-on: ubuntu-latest
steps:
- name: Report to Mastodon
uses: cbrgm/mastodon-github-action@v1.0.1
env:
MASTODON_URL: ${{ secrets.MASTODON_URL }}
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
with:
message: |
This is a test toot, automatically posted by a github action.
${{ github.server_url }}/${{ github.repository }}
${{ github.ref }}
+16
View File
@@ -14,6 +14,9 @@ build:
pre_install: pre_install:
- pip install --upgrade pip - pip install --upgrade pip
- pip install --upgrade --upgrade-strategy only-if-needed --group doc . - pip install --upgrade --upgrade-strategy only-if-needed --group doc .
# build:
# html:
# - python -m sphinx -T -b html -d _build/doctrees -D language=en . $READTHEDOCS_OUTPUT/html
post_build: post_build:
- echo "Command run at 'post_build' step" - echo "Command run at 'post_build' step"
- echo `date` - echo `date`
@@ -26,3 +29,16 @@ sphinx:
#If using Sphinx, optionally build your docs in additional formats such as PDF #If using Sphinx, optionally build your docs in additional formats such as PDF
formats: formats:
- pdf - pdf
# FIXME: We should be installing the `doc` dependency group and not the `doc`
# extra.
#Optionally declare the Python requirements required to build your docs
python:
install:
- method: pip
path: .
extra_requirements:
- doc
+2 -9
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "ptulsconv" name = "ptulsconv"
version = "4.0.2" version = "4.0.0"
description = "Read Pro Tools Text exports and generate PDF ADR Reports, JSON" description = "Read Pro Tools Text exports and generate PDF ADR Reports, JSON"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.9"
@@ -9,6 +9,7 @@ license-files = ["LICENSE"]
keywords = ["text-processing", "parsers", "film", keywords = ["text-processing", "parsers", "film",
"broadcast", "editing", "editorial"] "broadcast", "editing", "editorial"]
classifiers = [ classifiers = [
'License :: OSI Approved :: MIT License',
'Topic :: Multimedia', 'Topic :: Multimedia',
'Topic :: Multimedia :: Sound/Audio', 'Topic :: Multimedia :: Sound/Audio',
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
@@ -16,7 +17,6 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Topic :: Text Processing :: Filters" "Topic :: Text Processing :: Filters"
] ]
@@ -42,13 +42,6 @@ doc = [
"sphinx-rtd-theme>=3.0.2", "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] [project.scripts]
ptulsconv = "ptulsconv:__main__.main" ptulsconv = "ptulsconv:__main__.main"
+15 -10
View File
@@ -1,20 +1,20 @@
# type: ignore[reportUndefinedVariable]
# Export Items as Text.py # Export Items as Text.py
# (c) 2021 Jamie Hardt. All rights reserved. # (c) 2021 Jamie Hardt. All rights reserved.
# #
# #
import datetime import datetime
import json import json
from zoneinfo import ZoneInfo
item_records = [] item_records = list()
for i in range(RPR_CountMediaItems(0)): for i in range(RPR_CountMediaItems(0)):
this_item = RPR_GetMediaItem(0, i) this_item = RPR_GetMediaItem(0, i)
item_record = {} item_record = {}
item_record["mute"] = RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0.0 item_record["mute"] = (
True if RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0.0 else False
)
item_record["duration"] = RPR_GetMediaItemInfo_Value(this_item, "D_LENGTH") item_record["duration"] = RPR_GetMediaItemInfo_Value(this_item, "D_LENGTH")
_, item_record["duration_tc"], _, _, _ = RPR_format_timestr_len( _, item_record["duration_tc"], _, _, _ = RPR_format_timestr_len(
@@ -26,7 +26,9 @@ for i in range(RPR_CountMediaItems(0)):
item_record["position"], "", 128, 5 item_record["position"], "", 128, 5
) )
item_record["selected"] = RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0.0 item_record["selected"] = (
True if RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0.0 else False
)
_, _, _, item_record["notes"], _ = RPR_GetSetMediaItemInfo_String( _, _, _, item_record["notes"], _ = RPR_GetSetMediaItemInfo_String(
this_item, "P_NOTES", "", False this_item, "P_NOTES", "", False
) )
@@ -52,11 +54,13 @@ for i in range(RPR_CountMediaItems(0)):
item_record["track_index"] = RPR_GetMediaTrackInfo_Value( item_record["track_index"] = RPR_GetMediaTrackInfo_Value(
item_track, "IP_TRACKNUMBER" item_track, "IP_TRACKNUMBER"
) )
item_record["track_muted"] = RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0.0 item_record["track_muted"] = (
True if RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0.0 else False
)
item_records = item_records + [item_record] item_records = item_records + [item_record]
output = {} output = dict()
output["items"] = item_records output["items"] = item_records
_, output["project_title"], _ = RPR_GetProjectName(0, "", 1024) _, output["project_title"], _ = RPR_GetProjectName(0, "", 1024)
_, _, output["project_author"], _ = RPR_GetSetProjectAuthor(0, False, "", 1024) _, _, output["project_author"], _ = RPR_GetSetProjectAuthor(0, False, "", 1024)
@@ -72,14 +76,15 @@ output_title = output["project_title"]
if output_title == "": if output_title == "":
output_title = "unsaved project" output_title = "unsaved project"
output_file_name = f"{output_title} Text Export {now.strftime('%Y%m%d_%H%M')}.txt" output_file_name = "%s Text Export %s.txt" % (output_title, now.strftime("%Y%m%d_%H%M"))
output_path = output_path + "/" + output_file_name output_path = output_path + "/" + output_file_name
with open(output_path, "w") as f: with open(output_path, "w") as f:
json.dump(output, f, allow_nan=True, indent=4) json.dump(output, f, allow_nan=True, indent=4)
RPR_ShowMessageBox( RPR_ShowMessageBox(
f'Exported text file "{output_file_name}" to project folder. Text Export Complete', 'Exported text file "%s" to project folder.' % output_file_name,
"Text Export Complete",
0, 0,
) )
+23 -9
View File
@@ -9,6 +9,7 @@ from ptulsconv.commands import convert
from ptulsconv.docparser.adr_entity import ADR_TAG_MAPPING, GENERIC_TAG_MAPPING from ptulsconv.docparser.adr_entity import ADR_TAG_MAPPING, GENERIC_TAG_MAPPING
from ptulsconv.reporting import ( from ptulsconv.reporting import (
print_banner_style, print_banner_style,
print_fatal_error,
print_section_header_style, print_section_header_style,
print_status_style, print_status_style,
) )
@@ -101,7 +102,7 @@ def main():
(options, args) = parser.parse_args(sys.argv) (options, args) = parser.parse_args(sys.argv)
print_section_header_style("Startup") print_section_header_style("Startup")
print_status_style(f"This run started {datetime.datetime.now().isoformat()}") print_status_style("This run started %s" % (datetime.datetime.now().isoformat()))
if options.show_tags: if options.show_tags:
dump_field_map() dump_field_map()
@@ -110,16 +111,29 @@ def main():
elif options.show_formats: elif options.show_formats:
dump_formats() dump_formats()
sys.exit(0) sys.exit(0)
try:
major_mode = options.output_format
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
)
if len(args) < 2: except FileNotFoundError as e:
print_status_style( print_fatal_error("Error trying to read input file")
"No input file provided, will connect to Pro Tools with PTSL..." raise e
)
convert(major_mode=major_mode, warnings=options.warnings) except Exception as e:
else: import traceback
convert(input_file=args[1], major_mode=major_mode, warnings=options.warnings)
print_fatal_error("Error trying to convert file")
print("\033[31m" + e.__repr__() + "\033[0m", file=sys.stderr)
print(traceback.format_exc())
if __name__ == "__main__": if __name__ == "__main__":
+20 -24
View File
@@ -2,24 +2,21 @@
Useful functions for parsing and working with timecode. Useful functions for parsing and working with timecode.
""" """
from __future__ import annotations
import math import math
import re import re
from collections import namedtuple
from fractions import Fraction from fractions import Fraction
from typing import NamedTuple from typing import Optional, SupportsFloat
class TimecodeFormat(NamedTuple): class TimecodeFormat(
namedtuple("_TimecodeFormat", "frame_duration logical_fps drop_frame")
):
""" """
A struct reperesenting a timecode datum. A struct reperesenting a timecode datum.
""" """
frame_duration: Fraction def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
logical_fps: int
drop_frame: bool
def smpte_to_seconds(self, smpte: str) -> Fraction | None:
frame_count = smpte_to_frame_count( 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
) )
@@ -28,14 +25,14 @@ class TimecodeFormat(NamedTuple):
else: else:
return frame_count * self.frame_duration return frame_count * self.frame_duration
def seconds_to_smpte(self, seconds: Fraction) -> str: def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
frame_count = int(seconds / self.frame_duration) frame_count = int(seconds / self.frame_duration)
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame) return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame)
def smpte_to_frame_count( def smpte_to_frame_count(
smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False
) -> int | None: ) -> Optional[int]:
""" """
Convert a string with a SMPTE timecode representation into a frame count. Convert a string with a SMPTE timecode representation into a frame count.
@@ -86,7 +83,7 @@ def frame_count_to_smpte(
frame_count: int, frame_count: int,
frames_per_logical_second: int, frames_per_logical_second: int,
drop_frame: bool = False, drop_frame: bool = False,
fractional_frame: float | None = None, fractional_frame: Optional[float] = None,
) -> str: ) -> str:
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60] assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
assert fractional_frame is None or fractional_frame < 1.0 assert fractional_frame is None or fractional_frame < 1.0
@@ -107,21 +104,20 @@ def frame_count_to_smpte(
ss, ff = divmod(rem, frames_per_logical_second) ss, ff = divmod(rem, frames_per_logical_second)
hh = hh % 24 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: if fractional_frame is not None and fractional_frame > 0:
fpart = "{fractional_frame:.3f}"[1:] return "%02i:%02i:%02i%s%02i%s" % (
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}{fpart}" hh,
mm,
ss,
separator,
ff,
("%.3f" % fractional_frame)[1:],
)
else: else:
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}" return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
# "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
def footage_to_frame_count(footage_string) -> int | None: def footage_to_frame_count(footage_string) -> Optional[int]:
m = re.search(r"(\d+)\+(\d+)(\.\d+)?", footage_string) m = re.search(r"(\d+)\+(\d+)(\.\d+)?", footage_string)
if m is None: if m is None:
return None return None
@@ -135,4 +131,4 @@ def footage_to_frame_count(footage_string) -> int | None:
def frame_count_to_footage(frame_count): def frame_count_to_footage(frame_count):
feet, frm = divmod(frame_count, 16) feet, frm = divmod(frame_count, 16)
return f"{feet}+{frm:02}" return "%i+%02i" % (feet, frm)
+21 -18
View File
@@ -3,8 +3,6 @@ This module provides the main input document parsing and transform
implementation. implementation.
""" """
from __future__ import annotations
import csv import csv
import datetime import datetime
import os import os
@@ -13,6 +11,7 @@ from collections.abc import Iterator
from fractions import Fraction from fractions import Fraction
from itertools import chain from itertools import chain
from json import JSONEncoder from json import JSONEncoder
from typing import List, Optional
import ptsl import ptsl
@@ -40,25 +39,26 @@ class FractionEncoder(JSONEncoder):
as a dict. as a dict.
""" """
force_denominator: int | None force_denominator: Optional[int]
def default(self, o): def default(self, o):
""" """
if isinstance(o, Fraction): if isinstance(o, Fraction):
return {"numerator": o.numerator, "denominator": o.denominator} return dict(numerator=o.numerator, denominator=o.denominator)
else: else:
return o.__dict__ 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 Writes ADR lines as CSV to the current working directory. Creates
directories for each character number and name pair, and within that directories for each character number and name pair, and within that
directory, creates a CSV file for each reel. directory, creates a CSV file for each reel.
""" """
reels: set[str | None] = {ln.reel for ln in lines} reels: set[str | None] = set([ln.reel for ln in lines])
for n, name in [(n.character_id, n.character_name) for n in lines]: for n, name in [(n.character_id, n.character_name) for n in lines]:
dir_name = f"{n}_{name}" dir_name = "%s_%s" % (n, name)
os.makedirs(dir_name, exist_ok=True) os.makedirs(dir_name, exist_ok=True)
os.chdir(dir_name) os.chdir(dir_name)
for reel in reels: for reel in reels:
@@ -69,8 +69,11 @@ def output_adr_csv(lines: list[ADRLine], time_format: TimecodeFormat):
if len(these_lines) == 0: if len(these_lines) == 0:
continue continue
outfile_name = ( outfile_name = "%s_%s_%s_%s.csv" % (
f"{these_lines[0].title}_{n}_{these_lines[0].character_name}_{reel}.csv" these_lines[0].title,
n,
these_lines[0].character_name,
reel,
) )
with open(outfile_name, mode="w", newline="") as outfile: with open(outfile_name, mode="w", newline="") as outfile:
@@ -116,13 +119,13 @@ def output_adr_csv(lines: list[ADRLine], time_format: TimecodeFormat):
os.chdir("..") 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. Create PDF output.
""" """
print_section_header_style("Creating PDF Reports") print_section_header_style("Creating PDF Reports")
report_date = datetime.datetime.now() report_date = datetime.datetime.now()
reports_dir = f"{title}_{report_date.strftime('%Y-%m-%d_%H%M%S')}" reports_dir = "%s_%s" % (title, report_date.strftime("%Y-%m-%d_%H%M%S"))
os.makedirs(reports_dir, exist_ok=False) os.makedirs(reports_dir, exist_ok=False)
os.chdir(reports_dir) os.chdir(reports_dir)
@@ -152,7 +155,7 @@ def generate_documents(session_tc_format, scenes, adr_lines: list[ADRLine], titl
def create_adr_reports( def create_adr_reports(
lines: list[ADRLine], tc_display_format: TimecodeFormat, reel_list: list[str] lines: List[ADRLine], tc_display_format: TimecodeFormat, reel_list: List[str]
): ):
""" """
Creates a directory heirarchy and a respective set of ADR reports, Creates a directory heirarchy and a respective set of ADR reports,
@@ -237,18 +240,18 @@ def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
) )
# TODO: Breakdown by titles # TODO: Breakdown by titles
titles = {x.title for x in (generic_events + adr_lines)} titles = set([x.title for x in (generic_events + adr_lines)])
if len(titles) != 1: if len(titles) != 1:
print_warning( print_warning(
"Multiple titles per export is not supported, " "Multiple titles per export is not supported, "
f"found multiple titles: {titles} Exiting." "found multiple titles: %s Exiting." % titles
) )
sys.exit(-1) exit(-1)
title = next(iter(titles)) title = list(titles)[0]
print_status_style(f"{len(generic_events)} generic events found.") print_status_style("%i generic events found." % len(generic_events))
print_status_style(f"{len(adr_lines)} ADR events found.") print_status_style("%i ADR events found." % len(adr_lines))
if warnings: if warnings:
perform_adr_validations(iter(adr_lines)) perform_adr_validations(iter(adr_lines))
+4 -1
View File
@@ -2,4 +2,7 @@
Docparser module Docparser module
""" """
from .pt_doc_parser import parse_document # pyright: ignore[reportUnusedImport, reportUnusedImport] # noqa: F401, I001
from .pt_doc_parser import parse_document
__all__ = [parse_document]
+17 -15
View File
@@ -1,9 +1,10 @@
from __future__ import annotations from __future__ import annotations
from collections import namedtuple
from collections.abc import Generator, Iterator from collections.abc import Generator, Iterator
from dataclasses import dataclass from dataclasses import dataclass
from fractions import Fraction from fractions import Fraction
from typing import Callable, NamedTuple from typing import Callable
from ptulsconv.docparser import doc_entity from ptulsconv.docparser import doc_entity
@@ -26,15 +27,12 @@ class TagCompiler:
items. items.
""" """
class Intermediate(NamedTuple): Intermediate = namedtuple(
track_content: str "Intermediate",
clip_content: str "track_content track_tags track_comment_tags "
track_tags: dict[str, str] "clip_content clip_tags clip_tag_mode start "
track_comment_tags: dict[str, str] "finish",
clip_tags: dict[str, str] )
clip_tag_mode: TagPreModes
start: Fraction
finish: Fraction
session: doc_entity.SessionDescriptor session: doc_entity.SessionDescriptor
@@ -133,6 +131,7 @@ class TagCompiler:
return effective_tags return effective_tags
def parse_data(self) -> Iterator[Intermediate]: def parse_data(self) -> Iterator[Intermediate]:
for track, clip, start, finish, _ in self.session.track_clips_timed(): for track, clip, start, finish, _ in self.session.track_clips_timed():
if clip.state == "Muted": if clip.state == "Muted":
continue continue
@@ -154,6 +153,7 @@ class TagCompiler:
@staticmethod @staticmethod
def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]: def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]:
def should_append(a, b): def should_append(a, b):
return b.clip_tag_mode == TagPreModes.APPEND and b.start >= a.finish return b.clip_tag_mode == TagPreModes.APPEND and b.start >= a.finish
@@ -177,13 +177,14 @@ class TagCompiler:
def collect_time_spans( def collect_time_spans(
parsed: Iterator[Intermediate], parsed: Iterator[Intermediate],
) -> Iterator[tuple[Intermediate, tuple[dict, Fraction, Fraction]]]: ) -> Iterator[tuple[Intermediate, tuple[dict, Fraction, Fraction]]]:
time_spans = [] time_spans = []
for item in parsed: for item in parsed:
if item.clip_tag_mode == TagPreModes.TIMESPAN: if item.clip_tag_mode == TagPreModes.TIMESPAN:
time_spans.append((item.clip_tags, item.start, item.finish)) time_spans.append((item.clip_tags, item.start, item.finish))
else: else:
yield item, tuple(time_spans) yield item, list(time_spans)
@staticmethod @staticmethod
def _time_span_tags(at_time: Fraction, applicable_spans) -> dict: def _time_span_tags(at_time: Fraction, applicable_spans) -> dict:
@@ -198,6 +199,7 @@ class TagCompiler:
def apply_tags( def apply_tags(
self, parsed_with_time_spans self, parsed_with_time_spans
) -> Iterator[tuple[str, str, str, dict, Fraction, Fraction]]: ) -> Iterator[tuple[str, str, str, dict, Fraction, Fraction]]:
session_parsed = parse_tags(self.session.header.session_name) session_parsed = parse_tags(self.session.header.session_name)
for event, time_spans in parsed_with_time_spans: for event, time_spans in parsed_with_time_spans:
@@ -224,13 +226,13 @@ class TagCompiler:
def apply_appends( def apply_appends(
source: Iterator, should_append: Callable, preform_append: Callable source: Iterator, should_append: Callable, do_append: Callable
) -> Generator: ) -> Generator:
""" """
:param source: :param source:
:param should_append: Called with two variables a and b, your :param should_append: Called with two variables a and b, your
function should return true if b should be function should return true if b should be
appended to a appended to a
:param do_append: Called with two variables a and b, your function :param do_append: Called with two variables a and b, your function
should return should return
:returns: A Generator :returns: A Generator
@@ -238,7 +240,7 @@ def apply_appends(
this_element = next(source) this_element = next(source)
for element in source: for element in source:
if should_append(this_element, element): if should_append(this_element, element):
this_element = preform_append(this_element, element) this_element = do_append(this_element, element)
else: else:
yield this_element yield this_element
this_element = element this_element = element
@@ -1,4 +1,5 @@
from enum import Enum from enum import Enum
from typing import Dict
from parsimonious import Grammar, NodeVisitor from parsimonious import Grammar, NodeVisitor
@@ -36,7 +37,7 @@ def parse_tags(prompt: str) -> "TaggedStringResult":
class TaggedStringResult: class TaggedStringResult:
content: str content: str
tag_dict: dict[str, str] tag_dict: Dict[str, str]
mode: TagPreModes mode: TagPreModes
def __init__(self, content, tag_dict, mode): def __init__(self, content, tag_dict, mode):
@@ -52,7 +53,7 @@ class TagListVisitor(NodeVisitor):
return TaggedStringResult( return TaggedStringResult(
content=next(iter(line_opt), None), content=next(iter(line_opt), None),
tag_dict=next(iter(tag_list_opt), {}), tag_dict=next(iter(tag_list_opt), dict()),
mode=TagPreModes(next(iter(modifier_opt), "Normal")), mode=TagPreModes(next(iter(modifier_opt), "Normal")),
) )
@@ -73,7 +74,7 @@ class TagListVisitor(NodeVisitor):
@staticmethod @staticmethod
def visit_tag_list(_, visited_children): def visit_tag_list(_, visited_children):
retdict = {} retdict = dict()
for child in visited_children: for child in visited_children:
if child[0] is not None: if child[0] is not None:
k, v = child[0] k, v = child[0]
+2 -3
View File
@@ -2,13 +2,12 @@
Methods for converting string reprentations of film footage. Methods for converting string reprentations of film footage.
""" """
from __future__ import annotations
import re import re
from fractions import Fraction from fractions import Fraction
from typing import Optional
def footage_to_seconds(footage: str) -> Fraction | None: def footage_to_seconds(footage: str) -> Optional[Fraction]:
""" """
Converts a string representation of a footage (35mm, 24fps) Converts a string representation of a footage (35mm, 24fps)
into a :class:`Fraction`, this fraction being a some number of into a :class:`Fraction`, this fraction being a some number of
+16 -21
View File
@@ -26,7 +26,7 @@ class ReportCanvas(canvas.Canvas):
def showPage(self): def showPage(self):
self._saved_page_states.append(dict(self.__dict__)) self._saved_page_states.append(dict(self.__dict__))
self._startPage() # pyright: ignore[reportAttributeAccessIssue] self._startPage()
def save(self): def save(self):
"""add page info to each page (page x of y)""" """add page info to each page (page x of y)"""
@@ -41,11 +41,9 @@ class ReportCanvas(canvas.Canvas):
self.saveState() self.saveState()
self.setFont("Helvetica", 10) # FIXME make this customizable self.setFont("Helvetica", 10) # FIXME make this customizable
self.drawString( self.drawString(
0.5 * inch, 0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count)
0.5 * inch,
f"Page {self._pageNumber} of {page_count}", # pyright: ignore[reportAttributeAccessIssue]
) )
right_edge = self._pagesize[0] - 0.5 * inch # pyright: ignore[reportAttributeAccessIssue] right_edge = self._pagesize[0] - 0.5 * inch
self.drawRightString( self.drawRightString(
right_edge, 0.5 * inch, self._report_date.strftime("%m/%d/%Y %H:%M") right_edge, 0.5 * inch, self._report_date.strftime("%m/%d/%Y %H:%M")
) )
@@ -64,7 +62,7 @@ class ADRDocTemplate(BaseDocTemplate):
def make_doc_template( def make_doc_template(
page_size: tuple[float, float], page_size,
filename, filename,
document_title, document_title,
title: str, title: str,
@@ -73,22 +71,18 @@ def make_doc_template(
client: str, client: str,
document_subheader: str, document_subheader: str,
left_margin=0.5 * inch, left_margin=0.5 * inch,
fonts: list[TTFont] | None = None, fonts: list[TTFont] = [],
) -> ADRDocTemplate: ) -> ADRDocTemplate:
if fonts is None:
fonts = []
right_margin = top_margin = bottom_margin = 0.5 * inch 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: 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(left_margin, direction="l")
_, page_box = page_box.split_x(right_margin, direction="r") # pyright: ignore[reportAssignmentType] _, page_box = page_box.split_x(right_margin, direction="r")
_, page_box = page_box.split_y(bottom_margin, direction="u") # pyright: ignore[reportAssignmentType] _, page_box = page_box.split_y(bottom_margin, direction="u")
_, page_box = page_box.split_y(top_margin, direction="d") # pyright: ignore[reportAssignmentType] _, page_box = page_box.split_y(top_margin, direction="d")
footer_box, page_box = page_box.split_y(0.25 * inch, direction="u") # pyright: ignore[reportAssignmentType] 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") # pyright: ignore[reportAssignmentType] 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") # pyright: ignore[reportOptionalMemberAccess] title_box, report_box = header_box.split_x(3.5 * inch, direction="r")
def on_page_lambda(c, _): def on_page_lambda(c, _):
draw_header_footer( draw_header_footer(
@@ -103,6 +97,7 @@ def make_doc_template(
doc_title=document_header, 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)
@@ -297,7 +292,7 @@ class GRect:
rem = self rem = self
for item in x_list: for item in x_list:
s, rem = rem.split_x(item, direction) # pyright: ignore[reportOptionalMemberAccess] s, rem = rem.split_x(item, direction)
ret_list.append(s) ret_list.append(s)
return ret_list, rem return ret_list, rem
@@ -307,7 +302,7 @@ class GRect:
rem = self rem = self
for item in y_list: for item in y_list:
s, rem = rem.split_y(item, direction) # pyright: ignore[reportOptionalMemberAccess] s, rem = rem.split_y(item, direction)
ret_list.append(s) ret_list.append(s)
return ret_list, rem return ret_list, rem
@@ -404,7 +399,7 @@ class GRect:
cp.rect(self.min_x, self.min_y, self.width, self.height) cp.rect(self.min_x, self.min_y, self.width, self.height)
a_canvas.clipPath(cp, stroke=0, fill=0) 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) flowable.drawOn(a_canvas, inset_rect.x, inset_rect.max_y - h)
+1 -1
View File
@@ -17,7 +17,7 @@ def table_for_scene(scene, tc_format, font_name="Helvetica"):
scene_style.spaceAfter = 18.0 scene_style.spaceAfter = 18.0
tc_data = f"<em>{tc_format.seconds_to_smpte(scene[2])}</em><br />{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 = [ row = [
Paragraph(tc_data, scene_style), Paragraph(tc_data, scene_style),
Paragraph(scene[1], scene_style), Paragraph(scene[1], scene_style),
+38 -33
View File
@@ -1,8 +1,11 @@
from __future__ import annotations from typing import List, Optional
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, portrait from reportlab.lib.pagesizes import letter, portrait
from reportlab.lib.styles import getSampleStyleSheet 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.units import inch
from reportlab.platypus import Paragraph, Spacer, Table from reportlab.platypus import Paragraph, Spacer, Table
@@ -11,13 +14,15 @@ from .__init__ import make_doc_template, time_format
def build_columns( def build_columns(
lines: list[ADRLine], lines: List[ADRLine],
reel_list: list[str] | None, reel_list: Optional[List[str]],
show_priorities=False, show_priorities=False,
include_omitted=False, include_omitted=False,
): ):
columns = [] columns = list()
reel_numbers = reel_list or sorted({x.reel for x in lines if x.reel is not None}) reel_numbers = reel_list or sorted(
set([x.reel for x in lines if x.reel is not None])
)
num_column_width = 15.0 / 32.0 * inch num_column_width = 15.0 / 32.0 * inch
@@ -155,17 +160,17 @@ def build_columns(
): ):
columns.append( columns.append(
{ {
"heading": f"P{n}", "heading": "P%i" % n,
"value_getter": lambda recs, N=n: len( "value_getter": lambda recs: len(
[r for r in recs if r.priority == N] [r for r in recs if r.priority == n]
), ),
"value_getter2": ( "value_getter2": (
lambda recs, N=n: time_format( lambda recs: time_format(
sum( sum(
[ [
r.time_budget_mins or 0.0 r.time_budget_mins or 0.0
for r in recs for r in recs
if r.priority == N if r.priority == n
] ]
) )
) )
@@ -238,20 +243,22 @@ def build_columns(
return columns return columns
def populate_columns(lines: list[ADRLine], columns, include_omitted, _page_size): def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size):
data = [] data = list()
styles = [] styles = list()
columns_widths = [] columns_widths = list()
sorted_character_numbers: list[str | None] = sorted( sorted_character_numbers: List[str] = sorted(
{x.character_id for x in lines}, key=lambda x: str(x) set([x.character_id for x in lines]), key=lambda x: str(x)
) )
# construct column styles
for i, c in enumerate(columns): for i, c in enumerate(columns):
styles.extend(c["style_getter"](i)) styles.extend(c["style_getter"](i))
columns_widths.append(c["width"]) columns_widths.append(c["width"])
data.append([x["heading"] for x in columns]) data.append(list(map(lambda x: x["heading"], columns)))
if not include_omitted: if not include_omitted:
lines = [x for x in lines if not x.omitted] lines = [x for x in lines if not x.omitted]
@@ -259,8 +266,8 @@ def populate_columns(lines: list[ADRLine], columns, include_omitted, _page_size)
for n in sorted_character_numbers: for n in sorted_character_numbers:
char_records = [x for x in lines if x.character_id == n] char_records = [x for x in lines if x.character_id == n]
if len(char_records) > 0: if len(char_records) > 0:
row_data = [] row_data = list()
row_data2 = [] row_data2 = list()
for col in columns: for col in columns:
row1_index = len(data) row1_index = len(data)
@@ -284,8 +291,8 @@ def populate_columns(lines: list[ADRLine], columns, include_omitted, _page_size)
data.append(row_data) data.append(row_data)
data.append(row_data2) data.append(row_data2)
summary_row1 = [] summary_row1 = list()
summary_row2 = [] summary_row2 = list()
row1_index = len(data) row1_index = len(data)
for col in columns: for col in columns:
@@ -309,15 +316,12 @@ def populate_columns(lines: list[ADRLine], columns, include_omitted, _page_size)
def output_report( def output_report(
lines: list[ADRLine], lines: List[ADRLine],
reel_list: list[str], reel_list: List[str],
page_size: tuple[float, float] | None = None,
include_omitted=False, include_omitted=False,
page_size=portrait(letter),
font_name="Helvetica", font_name="Helvetica",
): ):
if page_size is None:
page_size = portrait(letter)
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list) columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
data, style, columns_widths = populate_columns( data, style, columns_widths = populate_columns(
lines, columns, include_omitted, page_size lines, columns, include_omitted, page_size
@@ -330,17 +334,16 @@ def output_report(
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc')) # pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
title = f"{lines[0].title} Line Count" title = "%s Line Count" % lines[0].title
filename = title + ".pdf" filename = title + ".pdf"
doc = make_doc_template( doc = make_doc_template(
page_size=page_size, page_size=page_size,
filename=filename, filename=filename,
document_title=title, document_title=title,
title=lines[0].title, title=lines[0].title,
document_subheader=lines[0].spot or "", document_subheader=lines[0].spot,
client=lines[0].client or "", client=lines[0].client,
supervisor=lines[0].supervisor or "", supervisor=lines[0].supervisor,
document_header="Line Count", document_header="Line Count",
) )
@@ -361,6 +364,8 @@ def output_report(
omitted_count = len([x for x in lines if x.omitted]) omitted_count = len([x for x in lines if x.omitted])
if not include_omitted and omitted_count > 0: if not include_omitted and omitted_count > 0:
story.append(Paragraph(f"* {omitted_count} Omitted lines are excluded.", style)) story.append(
Paragraph("* %i Omitted lines are excluded." % omitted_count, style)
)
doc.build(story) doc.build(story)
+41 -32
View File
@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import List
from reportlab.lib.pagesizes import letter, portrait from reportlab.lib.pagesizes import letter, portrait
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
@@ -12,7 +13,7 @@ from .__init__ import make_doc_template, time_format
def build_aux_data_field(line: ADRLine): def build_aux_data_field(line: ADRLine):
entries = [] entries = list()
if line.reason is not None: if line.reason is not None:
entries.append("Reason: " + line.reason) entries.append("Reason: " + line.reason)
if line.note is not None: if line.note is not None:
@@ -26,30 +27,40 @@ def build_aux_data_field(line: ADRLine):
tag_field = "" tag_field = ""
if line.effort: if line.effort:
bg_color = "red" bg_color = "red"
tag_field += ( tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>EFF</font> " bg_color,
fg_color,
"EFF",
) )
elif line.tv: elif line.tv:
bg_color = "blue" bg_color = "blue"
tag_field += ( tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>TV</font> " bg_color,
fg_color,
"TV",
) )
elif line.adlib: elif line.adlib:
bg_color = "purple" bg_color = "purple"
tag_field += ( tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (
f"<font backColor={bg_color} textColor={fg_color} fontSize=11>ADLIB</font> " bg_color,
fg_color,
"ADLIB",
) )
elif line.optional: elif line.optional:
bg_color = "green" bg_color = "green"
tag_field += f"font backColor={bg_color} textColor={fg_color} fontSize=11>OPTIONAL</font>" tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" % (
bg_color,
fg_color,
"OPTIONAL",
)
entries.append(tag_field) entries.append(tag_field)
return "<br />".join(entries) return "<br />".join(entries)
def build_story(lines: list[ADRLine], tc_rate: TimecodeFormat, font_name="Helvetica"): def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name="Helvetica"):
story = [] story = list()
this_scene = None this_scene = None
scene_style = getSampleStyleSheet()["Normal"] scene_style = getSampleStyleSheet()["Normal"]
@@ -67,14 +78,15 @@ def build_story(lines: list[ADRLine], tc_rate: TimecodeFormat, font_name="Helvet
("BOTTOMPADDING", (0, 0), (-1, -1), 24.0), ("BOTTOMPADDING", (0, 0), (-1, -1), 24.0),
] ]
cue_number_field = ( cue_number_field = "%s<br /><font fontSize=7>%s</font>" % (
f"{line.cue_number}<br /><font fontSize=7>{line.character_name}</font>" line.cue_number,
line.character_name,
) )
time_data = time_format(line.time_budget_mins) time_data = time_format(line.time_budget_mins)
if line.priority is not None: if line.priority is not None:
time_data = time_data + "<br />" + "P: " + str(line.priority) time_data = time_data + "<br />" + "P: " + line.priority
aux_data_field = build_aux_data_field(line) aux_data_field = build_aux_data_field(line)
@@ -84,7 +96,7 @@ def build_story(lines: list[ADRLine], tc_rate: TimecodeFormat, font_name="Helvet
[ [
Paragraph(cue_number_field, line_style), Paragraph(cue_number_field, line_style),
Paragraph(tc_data, line_style), Paragraph(tc_data, line_style),
Paragraph(line.prompt or "[No Prompt]", line_style), Paragraph(line.prompt, line_style),
Paragraph(time_data, line_style), Paragraph(time_data, line_style),
Paragraph(aux_data_field, line_style), Paragraph(aux_data_field, line_style),
] ]
@@ -123,11 +135,11 @@ def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
third_line = [] third_line = []
if line.reel is not None: if line.reel is not None:
if line.reel[0:1] == "R": if line.reel[0:1] == "R":
third_line.append(f"{line.reel}") third_line.append("%s" % line.reel)
else: else:
third_line.append(f"Reel {line.reel}") third_line.append("Reel %s" % line.reel)
if line.version is not None: if line.version is not None:
third_line.append(f"({line.version})") third_line.append("(%s)" % line.version)
if len(third_line) > 0: if len(third_line) > 0:
tc_data = tc_data + "<br/>" + " ".join(third_line) tc_data = tc_data + "<br/>" + " ".join(third_line)
return tc_data return tc_data
@@ -135,17 +147,17 @@ def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
def generate_report( def generate_report(
page_size, page_size,
lines: list[ADRLine], lines: List[ADRLine],
tc_rate: TimecodeFormat, tc_rate: TimecodeFormat,
character_number=None, character_number=None,
include_omitted=True, include_omitted=True,
): ):
if character_number is not None: if character_number is not None:
lines = [r for r in lines if r.character_id == character_number] lines = [r for r in lines if r.character_id == character_number]
title = f"{lines[0].title} ADR Report ({lines[0].character_name})" title = "%s ADR Report (%s)" % (lines[0].title, lines[0].character_name)
document_header = f"{lines[0].character_name} ADR Report" document_header = "%s ADR Report" % lines[0].character_name
else: else:
title = f"{lines[0].title} ADR Report" title = "%s ADR Report" % lines[0].title
document_header = "ADR Report" document_header = "ADR Report"
if not include_omitted: if not include_omitted:
@@ -160,9 +172,9 @@ def generate_report(
document_title=title, document_title=title,
document_header=document_header, document_header=document_header,
title=lines[0].title, title=lines[0].title,
supervisor=lines[0].supervisor or "", supervisor=lines[0].supervisor,
client=lines[0].client or "", client=lines[0].client,
document_subheader=lines[0].spot or "", document_subheader=lines[0].spot,
left_margin=0.75 * inch, left_margin=0.75 * inch,
) )
story = build_story(lines, tc_rate) story = build_story(lines, tc_rate)
@@ -170,16 +182,13 @@ def generate_report(
def output_report( def output_report(
lines: list[ADRLine], lines: List[ADRLine],
tc_display_format: TimecodeFormat, tc_display_format: TimecodeFormat,
page_size: tuple[float, float] | None = None, page_size=portrait(letter),
by_character: bool = False, by_character=False,
): ):
if page_size is None:
page_size = portrait(letter)
if by_character: if by_character:
character_numbers = {r.character_id for r in lines} character_numbers = set(r.character_id for r in lines)
for n in character_numbers: for n in character_numbers:
generate_report(page_size, lines, tc_display_format, n) generate_report(page_size, lines, tc_display_format, n)
else: else:
+14 -10
View File
@@ -24,7 +24,7 @@ def draw_header_block(canvas, rect, record: ADRLine):
def draw_character_row(canvas, rect, record: ADRLine): def draw_character_row(canvas, rect, record: ADRLine):
label_frame, value_frame = rect.split_x(1.25 * inch) label_frame, value_frame = rect.split_x(1.25 * inch)
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.0) label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.0)
line = f"{record.character_id} / {record.character_name}" line = "%s / %s" % (record.character_id, record.character_name)
if record.actor_name is not None: if record.actor_name is not None:
line = line + " / " + record.actor_name line = line + " / " + record.actor_name
value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.0) value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.0)
@@ -61,9 +61,9 @@ def draw_cue_number_block(canvas, rect, record: ADRLine):
"omitted": "OMIT", "omitted": "OMIT",
} }
tag_field = "" tag_field = ""
for key, value in tags.items(): for key in tags:
if getattr(record, key): if getattr(record, key):
tag_field = tag_field + value + " " tag_field = tag_field + tags[key] + " "
aux_frame.draw_text_cell( aux_frame.draw_text_cell(
canvas, tag_field, font_name, 10, inset_x=10.0, inset_y=2.0, vertical_align="t" canvas, tag_field, font_name, 10, inset_x=10.0, inset_y=2.0, vertical_align="t"
@@ -221,7 +221,7 @@ def draw_aux_block(canvas, rect, recording_time_sec_this_line, recording_time_se
rect.draw_border(canvas, "min_x") rect.draw_border(canvas, "min_x")
content_rect = rect.inset_xy(10.0, 10.0) content_rect = rect.inset_xy(10.0, 10.0)
lines, _ = content_rect.divide_y( lines, last_line = content_rect.divide_y(
[12.0, 12.0, 24.0, 24.0, 24.0, 24.0], direction="d" [12.0, 12.0, 24.0, 24.0, 24.0, 24.0], direction="d"
) )
@@ -255,7 +255,7 @@ def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines
rect.draw_border(canvas, "max_y") rect.draw_border(canvas, "max_y")
report_date_s = [report_date.strftime("%c")] report_date_s = [report_date.strftime("%c")]
spotting_name = [record.spot] if record.spot is not None else [] spotting_name = [record.spot] if record.spot is not None else []
pages_s = [f"Line {line_no} of {total_lines}"] pages_s = ["Line %i of %i" % (line_no, total_lines)]
footer_s = " - ".join(report_date_s + spotting_name + pages_s) footer_s = " - ".join(report_date_s + spotting_name + pages_s)
rect.draw_text_cell( rect.draw_text_cell(
canvas, footer_s, font_name=font_name, font_size=10.0, inset_y=2.0 canvas, footer_s, font_name=font_name, font_size=10.0, inset_y=2.0
@@ -266,8 +266,11 @@ def create_report_for_character(
records, report_date, tc_display_format: TimecodeFormat 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 is not None
assert outfile[-4:] == ".pdf", "Output file must have 'pdf' extension!" assert outfile[-4:] == ".pdf", "Output file must have 'pdf' extension!"
@@ -282,7 +285,7 @@ def create_report_for_character(
) )
) )
cue_header_block, _title_header_block = header_row.split_x(4.0 * inch) cue_header_block, title_header_block = header_row.split_x(4.0 * inch)
(cue_number_block, timecode_block), reason_block = data_row.divide_x( (cue_number_block, timecode_block), reason_block = data_row.divide_x(
[1.5 * inch, 1.5 * inch] [1.5 * inch, 1.5 * inch]
) )
@@ -294,7 +297,8 @@ def create_report_for_character(
) )
c.setTitle( c.setTitle(
f"{records[0].title} {records[0].character_name} ({records[0].character_id}) Supervisor's Log" "%s %s (%s) Supervisor's Log"
% (records[0].title, records[0].character_name, records[0].character_id)
) )
c.setAuthor(records[0].supervisor) c.setAuthor(records[0].supervisor)
@@ -335,7 +339,7 @@ def create_report_for_character(
def output_report(lines, tc_display_format: TimecodeFormat): def output_report(lines, tc_display_format: TimecodeFormat):
report_date = datetime.datetime.now() report_date = datetime.datetime.now()
events = sorted(lines, key=lambda x: x.start) events = sorted(lines, key=lambda x: x.start)
character_numbers = {x.character_id for x in lines} character_numbers = set([x.character_id for x in lines])
for n in character_numbers: for n in character_numbers:
create_report_for_character( create_report_for_character(
+8 -6
View File
@@ -1,3 +1,5 @@
from typing import List
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.pagesizes import letter from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.styles import getSampleStyleSheet
@@ -12,9 +14,9 @@ from .__init__ import make_doc_template
def output_report( def output_report(
lines: list[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica" lines: List[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica"
): ):
character_numbers = {n.character_id for n in lines} character_numbers = set([n.character_id for n in lines])
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc')) # pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
for n in character_numbers: for n in character_numbers:
@@ -25,8 +27,8 @@ def output_report(
char_lines = sorted(char_lines, key=lambda line: line.start) char_lines = sorted(char_lines, key=lambda line: line.start)
title = f"{char_lines[0].title} ({character_name}) {n} ADR Script" title = "%s (%s) %s ADR Script" % (char_lines[0].title, character_name, n)
filename = f"{char_lines[0].title}_{n}_{character_name}_ADR Script.pdf" filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
doc = make_doc_template( doc = make_doc_template(
page_size=letter, page_size=letter,
@@ -62,7 +64,7 @@ def output_report(
finish_tc = tc_display_format.seconds_to_smpte(line.finish) finish_tc = tc_display_format.seconds_to_smpte(line.finish)
data_block = [ data_block = [
[ [
Paragraph(line.cue_number or "[No QN]", number_style), Paragraph(line.cue_number, number_style),
Paragraph(start_tc + " - " + finish_tc, number_style), Paragraph(start_tc + " - " + finish_tc, number_style),
] ]
] ]
@@ -78,7 +80,7 @@ def output_report(
colWidths=[1.5 * inch, 6.0 * inch], colWidths=[1.5 * inch, 6.0 * inch],
style=[("LEFTPADDING", (0, 0), (-1, -1), 0.0)], style=[("LEFTPADDING", (0, 0), (-1, -1), 0.0)],
), ),
Paragraph(line.prompt or "[No Prompt]", prompt_style), Paragraph(line.prompt, prompt_style),
Spacer(1.0, inch * 1.5), Spacer(1.0, inch * 1.5),
] ]
) )
+3 -1
View File
@@ -43,7 +43,9 @@ def print_advisory_tagging_error(
sys.stderr.write(" ! \033[33;1mTagging error: \033[0m") sys.stderr.write(" ! \033[33;1mTagging error: \033[0m")
ok_string = failed_string[:position] ok_string = failed_string[:position]
not_ok_string = failed_string[position:] not_ok_string = failed_string[position:]
sys.stderr.write(f'\033[32m"{ok_string}\033[31;1m{not_ok_string}"\033[0m\n') sys.stderr.write(
f'\033[32m"{ok_string}\033[31;1m{not_ok_string}"\033[0m\n'
)
if parent_track_name is not None: if parent_track_name is not None:
sys.stderr.write(f' ! > On track "{parent_track_name}"\n') sys.stderr.write(f' ! > On track "{parent_track_name}"\n')
+10 -18
View File
@@ -1,13 +1,11 @@
from __future__ import annotations import datetime
import glob import glob
import os import os
import os.path import os.path
import pathlib import pathlib
import subprocess import subprocess
import sys import sys
from datetime import datetime from typing import List
from importlib.metadata import version as module_version
from xml.etree.ElementTree import TreeBuilder, tostring from xml.etree.ElementTree import TreeBuilder, tostring
import ptulsconv import ptulsconv
@@ -17,16 +15,13 @@ from ptulsconv.docparser.adr_entity import ADRLine
def avid_marker_list( def avid_marker_list(
lines: list[ADRLine], lines: List[ADRLine],
report_date: datetime | None = None, report_date=datetime.datetime.now(),
reel_start_frame=0, reel_start_frame=0,
fps=24, fps=24,
): ):
doc = TreeBuilder(element_factory=None) doc = TreeBuilder(element_factory=None)
if report_date is None:
report_date = datetime.now()
doc.start("Avid:StreamItems", {"xmlns:Avid": "http://www.avid.com"}) doc.start("Avid:StreamItems", {"xmlns:Avid": "http://www.avid.com"})
doc.start("Avid:XMLFileData", {}) doc.start("Avid:XMLFileData", {})
doc.start("AvProp", {"name": "DomainMagic", "type": "string"}) doc.start("AvProp", {"name": "DomainMagic", "type": "string"})
@@ -62,8 +57,6 @@ def avid_marker_list(
doc.start("List", {"id": "OMFI:ATTR:AttrRefs"}) doc.start("List", {"id": "OMFI:ATTR:AttrRefs"})
assert report_date
insert_elem( insert_elem(
"1", "1",
"OMFI:ATTB:IntAttribute", "OMFI:ATTB:IntAttribute",
@@ -82,7 +75,7 @@ def avid_marker_list(
line.supervisor or "", line.supervisor or "",
) )
marker_name = f"{line.cue_number}: {line.prompt}" marker_name = "%s: %s" % (line.cue_number, line.prompt)
insert_elem( insert_elem(
"2", "OMFI:ATTB:StringAttribute", "string", "_ATN_CRM_COM", marker_name "2", "OMFI:ATTB:StringAttribute", "string", "_ATN_CRM_COM", marker_name
) )
@@ -119,8 +112,7 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
doc.data("0") doc.data("0")
doc.end("ERRORCODE") doc.end("ERRORCODE")
version = module_version("ptulsconv") doc.start("PRODUCT", {"NAME": ptulsconv.__name__, "VERSION": ptulsconv.__version__})
doc.start("PRODUCT", {"NAME": ptulsconv.__name__, "VERSION": f"{version}"})
doc.end("PRODUCT") doc.end("PRODUCT")
doc.start( doc.start(
@@ -155,7 +147,7 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
doc.start("COL", {}) doc.start("COL", {})
doc.start("DATA", {}) doc.start("DATA", {})
for key_attempt in field[0]: for key_attempt in field[0]:
if key_attempt in event: if key_attempt in event.keys():
doc.data(str(event[key_attempt])) doc.data(str(event[key_attempt]))
break break
doc.end("DATA") doc.end("DATA")
@@ -178,7 +170,7 @@ def xform_options():
def dump_xform_options(output=sys.stdout): def dump_xform_options(output=sys.stdout):
print("# Available transforms:", file=output) print("# Available transforms:", file=output)
print(f"# Transform dir: {xslt_path}", file=output) print("# Transform dir: %s" % xslt_path, file=output)
for f in xform_options(): for f in xform_options():
base = os.path.basename(f) base = os.path.basename(f)
name, _ = os.path.splitext(base) name, _ = os.path.splitext(base)
@@ -196,14 +188,14 @@ def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
dump_fmpxml(data, input_file, pipe, adr_field_map) dump_fmpxml(data, input_file, pipe, adr_field_map)
str_data = pipe.getvalue() str_data = pipe.getvalue()
print_status_style(f"Base XML size {len(str_data)}") print_status_style("Base XML size %i" % (len(str_data)))
print_status_style("Running xsltproc") print_status_style("Running xsltproc")
xsl_path = os.path.join( xsl_path = os.path.join(
pathlib.Path(__file__).parent.absolute(), "xslt", xsl_name + ".xsl" pathlib.Path(__file__).parent.absolute(), "xslt", xsl_name + ".xsl"
) )
print_status_style(f"Using xsl: {xsl_path}") print_status_style("Using xsl: %s" % xsl_path)
subprocess.run( subprocess.run(
["xsltproc", xsl_path, "-"], ["xsltproc", xsl_path, "-"],
input=str_data, input=str_data,