34 Commits

Author SHA1 Message Date
Jamie Hardt
fc2e823116 Merge branch 'master' of https://github.com/iluvcapra/ptulsconv 2022-11-06 11:54:03 -08:00
Jamie Hardt
fbc7531374 Include version string in all outputs 2022-11-06 11:54:00 -08:00
Jamie Hardt
1fb17b13ea Update pythonpublish.yml
Updated to API key publishing method instead of username-password, which is deprecated.
2022-11-06 11:15:53 -08:00
Jamie Hardt
21c32e282c Updated version and banner 2022-11-04 18:03:38 -07:00
Jamie Hardt
8407d31333 Update adr_entity.py 2022-11-04 18:03:29 -07:00
Jamie Hardt
97d6eeda02 Bugfixes/linted 2022-11-04 13:34:38 -07:00
Jamie Hardt
3bee7a8391 Update CONTRIBUTING.md 2022-11-04 13:23:04 -07:00
Jamie Hardt
68d38f8ed5 Create CONTRIBUTING.md 2022-11-04 13:22:39 -07:00
Jamie Hardt
8e043b7175 Added footage decode featue 2022-11-04 13:19:08 -07:00
Jamie Hardt
a7b5adfffb Bug fixes 2022-11-04 13:06:38 -07:00
Jamie Hardt
da5b743191 Create coc-settings.json 2022-11-04 12:50:20 -07:00
Jamie Hardt
caa5381306 Merge branch 'master' of https://github.com/iluvcapra/ptulsconv 2022-11-04 12:50:13 -07:00
Jamie Hardt
9e2b932cad Update broadcast_timecode.py
Subtle bug fixes
2022-11-04 12:50:11 -07:00
Jamie Hardt
05ea48078f Fixed a bug, talent sides weren't sorting by time 2022-09-04 14:36:13 -07:00
Jamie Hardt
c26fa8dd75 Update summary_log.py
Added more metadata readout
2022-08-17 22:21:26 -07:00
Jamie Hardt
9f8e3cf824 Update line_count.py
Fixed a bug with omitted cues
2022-08-17 22:21:16 -07:00
Jamie Hardt
3b438b1399 Merge branch 'master' of https://github.com/iluvcapra/ptulsconv 2022-08-17 21:32:56 -07:00
Jamie Hardt
41b1a3185f Update __init__.py
Nudge version
2022-08-17 21:32:53 -07:00
Jamie Hardt
8877982a47 Update summary_log.py
Add "OMITTED" to ADR summary pdf
2022-08-17 21:32:45 -07:00
Jamie Hardt
bb6fbcfd37 Update __main__.py
Print more fields to --available-tags
2022-08-17 21:32:31 -07:00
Jamie Hardt
434b8816ee Merge branch 'master' of https://github.com/iluvcapra/ptulsconv 2022-06-27 12:06:55 -07:00
Jamie Hardt
5ebaf6b473 Updated requirements.txt 2022-06-27 12:06:03 -07:00
Jamie Hardt
d0f415b38f Create workspace.xml 2022-01-16 17:14:30 -08:00
Jamie Hardt
c5d6d82831 Merge branch 'master' of https://github.com/iluvcapra/ptulsconv 2022-01-16 17:14:23 -08:00
Jamie Hardt
66a71283d5 Futura has bee purged 2022-01-11 14:53:19 -08:00
Jamie Hardt
15ad328edc Begin testing text files 2022-01-11 14:26:38 -08:00
Jamie Hardt
a48eccb0d0 Cleaned up some regexps 2022-01-11 14:24:17 -08:00
Jamie Hardt
fa2cef35b2 Fixed an error in the tag compiler test 2022-01-11 14:05:21 -08:00
Jamie Hardt
c8053f65ae Reorganized trst sources 2022-01-11 13:56:44 -08:00
Jamie Hardt
d9da7317a7 Added lcov.info 2022-01-11 12:54:08 -08:00
Jamie Hardt
ab614cbc32 Commented out line 2022-01-11 12:53:50 -08:00
Jamie Hardt
5a75a77f77 Reorganized tests a little 2022-01-11 12:33:01 -08:00
Jamie Hardt
4daa5f0496 Bumped to Python 3.9 2021-12-24 12:47:45 -08:00
Jamie Hardt
de48bcfe24 Added comment 2021-12-24 12:46:44 -08:00
36 changed files with 354 additions and 162 deletions

View File

@@ -22,8 +22,8 @@ jobs:
pip install parsimonious
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_API_KEY }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

1
.gitignore vendored
View File

@@ -104,3 +104,4 @@ venv.bak/
.mypy_cache/
.DS_Store
/example/Charade/Session File Backups/
lcov.info

66
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="68bdb183-5bdf-4b42-962e-28e58c31a89d" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/ptulsconv.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/ptulsconv.iml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="GitSEFilterConfiguration">
<file-type-list>
<filtered-out-file-type name="LOCAL_BRANCH" />
<filtered-out-file-type name="REMOTE_BRANCH" />
<filtered-out-file-type name="TAG" />
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
</file-type-list>
</component>
<component name="ProjectId" id="1yyIGrXKNUCYtI4PSaCWGoLG76R" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
<option name="showMembers" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="project-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="68bdb183-5bdf-4b42-962e-28e58c31a89d" name="Default Changelist" comment="" />
<created>1633217312285</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1633217312285</updated>
</task>
<task id="LOCAL-00001" summary="Reorganized README a little">
<created>1633221191797</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1633221191797</updated>
</task>
<task id="LOCAL-00002" summary="Manpage 0.8.2 bump">
<created>1633221729867</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1633221729867</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Reorganized README a little" />
<MESSAGE value="Manpage 0.8.2 bump" />
<option name="LAST_COMMIT_MESSAGE" value="Manpage 0.8.2 bump" />
</component>
</project>

5
.vim/coc-settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.linting.mypyEnabled": false
}

9
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,9 @@
# Contributing to ptulsconv
## Testing
Before submitting PRs or patches, please make sure your branch passes all of the unit tests by running Pytest.
```sh
~/ptulsconv$ pytest
```

View File

@@ -1,5 +1,6 @@
from ptulsconv.docparser.ptuls_grammar import protools_text_export_grammar
__version__ = '0.8.3'
__version__ = '1.0.1'
__author__ = 'Jamie Hardt'
__license__ = 'MIT'
__copyright__ = "%s %s (c) 2022 %s. All rights reserved." % (__name__, __version__, __author__)

View File

@@ -2,7 +2,7 @@ from optparse import OptionParser, OptionGroup
import datetime
import sys
from ptulsconv import __name__, __version__, __author__
from ptulsconv import __name__, __version__, __author__,__copyright__
from ptulsconv.commands import convert
from ptulsconv.reporting import print_status_style, print_banner_style, print_section_header_style, print_fatal_error
@@ -17,8 +17,9 @@ from ptulsconv.reporting import print_status_style, print_banner_style, print_se
def dump_field_map(output=sys.stdout):
from ptulsconv.docparser.tag_mapping import TagMapping
from ptulsconv.docparser.adr_entity import ADRLine
from ptulsconv.docparser.adr_entity import ADRLine, GenericEvent
TagMapping.print_rules(GenericEvent, output=output)
TagMapping.print_rules(ADRLine, output=output)
@@ -58,9 +59,10 @@ def main():
parser.add_option_group(informational_options)
print_banner_style(__copyright__)
(options, args) = parser.parse_args(sys.argv)
print_banner_style("%s %s (c) 2021 %s. All rights reserved." % (__name__, __version__, __author__))
print_section_header_style("Startup")
print_status_style("This run started %s" % (datetime.datetime.now().isoformat()))

View File

@@ -2,20 +2,23 @@ from fractions import Fraction
import re
import math
from collections import namedtuple
from typing import Optional, SupportsFloat
class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps drop_frame")):
def smpte_to_seconds(self, smpte: str) -> Fraction:
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
return frame_count * self.frame_duration
if frame_count is None:
return None
else:
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)
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) -> int:
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> Optional[int]:
"""
Convert a string with a SMPTE timecode representation into a frame count.
@@ -28,7 +31,11 @@ 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("(\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)
@@ -47,14 +54,14 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
frames_dropped_per_inst = (frames_per_logical_second / 15)
mins = hh * 60 + mm
inst_count = mins - math.floor(mins / 10)
dropped_frames = frames_dropped_per_inst * inst_count
dropped_frames = int(frames_dropped_per_inst) * inst_count
frames = raw_frames - dropped_frames
return frames
def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False,
fractional_frame: float = None) -> str:
fractional_frame: Optional[float] = None) -> str:
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
assert fractional_frame is None or fractional_frame < 1.0
@@ -80,8 +87,10 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
def footage_to_frame_count(footage_string):
m = re.search("(\d+)\+(\d+)(\.\d+)?", footage_string)
def footage_to_frame_count(footage_string) -> Optional[int]:
m = re.search(r'(\d+)\+(\d+)(\.\d+)?', footage_string)
if m is None:
return None
feet, frm, frac = m.groups()
feet, frm, frac = int(feet), int(frm), float(frac or 0.0)

View File

@@ -5,6 +5,7 @@ import sys
from itertools import chain
import csv
from typing import List
from fractions import Fraction
from .docparser.adr_entity import make_entities
from .reporting import print_section_header_style, print_status_style, print_warning
@@ -59,10 +60,12 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
'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(event.start), time_format.seconds_to_smpte(event.finish),
float(event.start), float(event.finish),
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 ""]
@@ -126,7 +129,7 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
# return parsed
def convert(input_file, major_mode='fmpxml', output=sys.stdout, warnings=True):
def convert(input_file, major_mode, output=sys.stdout, warnings=True):
session = parse_document(input_file)
session_tc_format = session.header.timecode_format

View File

@@ -1,5 +1,5 @@
from ptulsconv.docparser.tag_compiler import Event
from typing import Optional, List, Tuple, Any
from typing import Optional, List, Tuple
from dataclasses import dataclass
from fractions import Fraction
@@ -11,12 +11,10 @@ def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'], List[
adr_lines = list()
for event in from_events:
result: Any = make_entity(event)
result = make_entity(event)
if type(result) is ADRLine:
result: ADRLine
adr_lines.append(result)
elif type(result) is GenericEvent:
result: GenericEvent
generic_events.append(result)
return generic_events, adr_lines
@@ -41,17 +39,17 @@ def make_entity(from_event: Event) -> Optional[object]:
@dataclass
class GenericEvent:
title: Optional[str]
supervisor: Optional[str]
client: Optional[str]
scene: Optional[str]
version: Optional[str]
reel: Optional[str]
start: Optional[Fraction]
finish: Optional[Fraction]
omitted: bool
note: Optional[str]
requested_by: Optional[str]
title: str = ""
supervisor: Optional[str] = None
client: Optional[str] = None
scene: Optional[str] = None
version: Optional[str] = None
reel: Optional[str] = None
start: Fraction = Fraction(0,1)
finish: Fraction = Fraction(0,1)
omitted: bool = False
note: Optional[str] = None
requested_by: Optional[str] = None
tag_mapping = [
TagMapping(source='Title', target="title", alt=TagMapping.ContentSource.Session),
@@ -69,21 +67,21 @@ class GenericEvent:
@dataclass
class ADRLine(GenericEvent):
priority: Optional[int]
cue_number: Optional[str]
character_id: Optional[str]
character_name: Optional[str]
actor_name: Optional[str]
prompt: Optional[str]
reason: Optional[str]
time_budget_mins: Optional[float]
spot: Optional[str]
shot: Optional[str]
effort: bool
tv: bool
tbw: bool
adlib: bool
optional: bool
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
effort: bool = False
tv: bool = False
tbw: bool = False
adlib: bool = False
optional: bool = False
tag_mapping = [
@@ -111,30 +109,30 @@ class ADRLine(GenericEvent):
formatter=(lambda x: len(x) > 0))
]
def __init__(self):
self.title = None
self.supervisor = None
self.client = None
self.scene = None
self.version = None
self.reel = None
self.start = None
self.finish = None
self.priority = None
self.cue_number = None
self.character_id = None
self.character_name = None
self.actor_name = None
self.prompt = None
self.reason = None
self.requested_by = None
self.time_budget_mins = None
self.note = None
self.spot = None
self.shot = None
self.effort = False
self.tv = False
self.tbw = False
self.omitted = False
self.adlib = False
self.optional = False
# def __init__(self):
# self.title = None
# self.supervisor = None
# self.client = None
# self.scene = None
# self.version = None
# self.reel = None
# self.start = None
# self.finish = None
# self.priority = None
# self.cue_number = None
# self.character_id = None
# self.character_name = None
# self.actor_name = None
# self.prompt = None
# self.reason = None
# self.requested_by = None
# self.time_budget_mins = None
# self.note = None
# self.spot = None
# self.shot = None
# self.effort = False
# self.tv = False
# self.tbw = False
# self.omitted = False
# self.adlib = False
# self.optional = False

View File

@@ -21,7 +21,8 @@ class SessionDescriptor:
def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]:
for marker in self.markers:
marker_time = self.header.convert_timecode(marker.location)
marker_time = Fraction(marker.time_reference, int(self.header.sample_rate))
#marker_time = self.header.convert_timecode(marker.location)
yield marker, marker_time
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor', 'TrackClipDescriptor']]:

View File

@@ -1,4 +1,3 @@
import sys
from collections import namedtuple
from fractions import Fraction
from typing import Iterator, Tuple, Callable, Generator, Dict, List
@@ -72,9 +71,9 @@ class TagCompiler:
def _marker_tags(self, at):
retval = dict()
applicable = [(m, t) for (m, t) in self.session.markers_timed() if t <= at]
for marker, time in sorted(applicable, key=lambda x: x[1]):
retval.update(parse_tags(marker.comments).tag_dict)
retval.update(parse_tags(marker.name).tag_dict)
for marker, _ in sorted(applicable, key=lambda x: x[1]):
retval.update(parse_tags(marker.comments or "").tag_dict)
retval.update(parse_tags(marker.name or "").tag_dict)
return retval
@@ -186,4 +185,4 @@ def apply_appends(source: Iterator,
yield this_element
this_element = element
yield this_element
yield this_element

View File

@@ -28,14 +28,14 @@ tag_grammar = Grammar(
)
def parse_tags(prompt) -> "TaggedStringResult":
def parse_tags(prompt: str) -> "TaggedStringResult":
ast = tag_grammar.parse(prompt)
return TagListVisitor().visit(ast)
class TaggedStringResult:
content: Optional[str]
tag_dict: Optional[Dict[str, str]]
content: str
tag_dict: Dict[str, str]
mode: TagPreModes
def __init__(self, content, tag_dict, mode):

18
ptulsconv/footage.py Normal file
View File

@@ -0,0 +1,18 @@
from fractions import Fraction
import re
from typing import Optional
def footage_to_seconds(footage: str) -> Optional[Fraction]:
m = re.match(r'(\d+)\+(\d+)(\.\d+)?', footage)
if m is None:
return None
feet, frames, _ = m.groups()
feet, frames = int(feet), int(frames)
fps = 24
frames_per_foot = 16
total_frames = feet * frames_per_foot + frames
return Fraction(total_frames, fps)

View File

@@ -1,4 +1,4 @@
import ffmpeg # ffmpeg-python
#import ffmpeg # ffmpeg-python
# TODO: Implement movie export

View File

@@ -9,9 +9,11 @@ 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
# This is from https://code.activestate.com/recipes/576832/ for
# generating page count messages
@@ -36,7 +38,7 @@ class ReportCanvas(canvas.Canvas):
def draw_page_number(self, page_count):
self.saveState()
self.setFont("Futura", 10)
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"))
@@ -60,7 +62,8 @@ def make_doc_template(page_size, filename, document_title,
document_header: str,
client: str,
document_subheader: str,
left_margin=0.5 * inch) -> ADRDocTemplate:
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')
@@ -85,7 +88,9 @@ def make_doc_template(page_size, filename, document_title,
frames=frames,
onPage=on_page_lambda)
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
for font in fonts:
pdfmetrics.registerFont(font)
doc = ADRDocTemplate(filename,
title=document_title,
author=supervisor,
@@ -99,6 +104,11 @@ def make_doc_template(page_size, filename, document_title,
def time_format(mins, zero_str="-"):
"""
Formats a duration `mins` into a string
"""
if mins is None:
return zero_str
if mins == 0. and zero_str is not None:
return zero_str
elif mins < 60.:
@@ -110,11 +120,11 @@ def time_format(mins, zero_str="-"):
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str,
document_subheader: str, client: str, doc_title=""):
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, "Futura", 18, inset_y=2., inset_x=5.)
client_box.draw_text_cell(a_canvas, client, "Futura", 11, inset_y=2., inset_x=5.)
title_box.draw_text_cell(a_canvas, title, font_name, 18, inset_y=2., inset_x=5.)
client_box.draw_text_cell(a_canvas, client, font_name, 11, inset_y=2., inset_x=5.)
a_canvas.saveState()
a_canvas.setLineWidth(0.5)
@@ -131,13 +141,13 @@ def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box,
(doc_title_cell, spotting_version_cell,), _ = left_box.divide_y([18., 14], direction='d')
doc_title_cell.draw_text_cell(a_canvas, doc_title, 'Futura', 14., inset_y=2.)
doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14., inset_y=2.)
if document_subheader is not None:
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, 'Futura', 12., inset_y=2.)
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, font_name, 12., inset_y=2.)
if supervisor is not None:
a_canvas.setFont('Futura', 11.)
a_canvas.setFont(font_name, 11.)
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, supervisor)

View File

@@ -12,9 +12,9 @@ from ptulsconv.pdf import make_doc_template
# TODO: A Continuity
def table_for_scene(scene, tc_format):
def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
scene_style = getSampleStyleSheet()['Normal']
scene_style.fontName = 'Futura'
scene_style.fontName = font_name
scene_style.leftIndent = 0.
scene_style.leftPadding = 0.
scene_style.spaceAfter = 18.
@@ -29,7 +29,7 @@ def table_for_scene(scene, tc_format):
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), 'Futura')]
('FONTNAME', (0, 0), (-1, -1), font_name)]
return Table(data=[row], style=style, colWidths=[1.0 * inch, 6.5 * inch])

View File

@@ -148,7 +148,7 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
styles = list()
columns_widths = list()
sorted_character_numbers = sorted(set([x.character_id for x in lines]),
sorted_character_numbers: List[str] = sorted(set([x.character_id for x in lines]),
key=lambda x: str(x))
# construct column styles
@@ -164,18 +164,21 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
for n in sorted_character_numbers:
char_records = [x for x in lines if x.character_id == n]
row_data = list()
row_data2 = list()
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)))
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index), colors.red),
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)])
if len(char_records) > 0:
row_data = list()
row_data2 = list()
data.append(row_data)
data.append(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)))
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()
@@ -202,16 +205,16 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False,
page_size=portrait(letter)):
page_size=portrait(letter), font_name='Helvetica'):
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size)
style.append(('FONTNAME', (0, 0), (-1, -1), "Futura"))
style.append(('FONTNAME', (0, 0), (-1, -1), font_name))
style.append(('FONTSIZE', (0, 0), (-1, -1), 9.))
style.append(('LINEBELOW', (0, 0), (-1, 0), 1.0, colors.black))
# style.append(('LINEBELOW', (0, 1), (-1, -1), 0.25, colors.gray))
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
title = "%s Line Count" % lines[0].title
filename = title + '.pdf'
@@ -230,7 +233,7 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
story = [Spacer(height=0.5 * inch, width=1.), table]
style = getSampleStyleSheet()['Normal']
style.fontName = 'Futura'
style.fontName = font_name
style.fontSize = 12.
style.spaceBefore = 16.
style.spaceAfter = 16.

View File

@@ -34,23 +34,26 @@ def build_aux_data_field(line: ADRLine):
elif line.adlib:
bg_color = 'purple'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "ADLIB")
elif line.optional:
bg_color = 'green'
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" % (bg_color, fg_color, "OPTIONAL")
entries.append(tag_field)
return "<br />".join(entries)
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat):
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvetica'):
story = list()
this_scene = None
scene_style = getSampleStyleSheet()['Normal']
scene_style.fontName = 'Futura'
scene_style.fontName = font_name
scene_style.leftIndent = 0.
scene_style.leftPadding = 0.
scene_style.spaceAfter = 18.
line_style = getSampleStyleSheet()['Normal']
line_style.fontName = 'Futura'
line_style.fontName = font_name
for line in lines:
table_style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),

View File

@@ -11,11 +11,12 @@ from reportlab.platypus import Paragraph
from .__init__ import GRect
from ptulsconv.broadcast_timecode import TimecodeFormat
from ptulsconv.broadcast_timecode import TimecodeFormat, footage_to_frame_count
from ptulsconv.docparser.adr_entity import ADRLine
import datetime
font_name = 'Helvetica'
def draw_header_block(canvas, rect, record: ADRLine):
rect.draw_text_cell(canvas, record.cue_number, "Helvetica", 44, vertical_align='m')
@@ -23,19 +24,19 @@ def draw_header_block(canvas, rect, record: ADRLine):
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", "Futura", 10, force_baseline=9.)
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.)
line = "%s / %s" % (record.character_id, record.character_name)
if record.actor_name is not None:
line = line + " / " + record.actor_name
value_frame.draw_text_cell(canvas, line, "Futura", 12, force_baseline=9.)
value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.)
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", "Futura", 10,
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, "Futura", 14,
number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14,
inset_x=10., inset_y=2., draw_baseline=True)
tags = {'tv': 'TV',
@@ -49,7 +50,7 @@ def draw_cue_number_block(canvas, rect, record: ADRLine):
if getattr(record, key):
tag_field = tag_field + tags[key] + " "
aux_frame.draw_text_cell(canvas, tag_field, "Futura", 10,
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')
@@ -58,13 +59,13 @@ def draw_timecode_block(canvas, rect, record: ADRLine, tc_display_format: Timeco
(in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y(
[0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d')
in_label_frame.draw_text_cell(canvas, "IN", "Futura", 10,
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), "Futura", 14,
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", "Futura", 10,
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), "Futura", 14,
out_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.finish), font_name, 14,
inset_x=10., inset_y=2., draw_baseline=True)
rect.draw_border(canvas, 'max_x')
@@ -75,16 +76,16 @@ def draw_reason_block(canvas, rect, record: ADRLine):
reason_label, reason_value = reason_cell.split_x(.75 * inch)
notes_label, notes_value = notes_cell.split_x(.75 * inch)
reason_label.draw_text_cell(canvas, "Reason:", "Futura", 12,
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 "", "Futura", 12,
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:", "Futura", 12,
notes_label.draw_text_cell(canvas, "Note:", font_name, 12,
inset_x=5., inset_y=5., vertical_align='t')
style = getSampleStyleSheet()['BodyText']
style.fontName = 'Futura'
style.fontName = font_name
style.fontSize = 12
style.leading = 14
@@ -96,10 +97,10 @@ def draw_reason_block(canvas, rect, record: ADRLine):
def draw_prompt(canvas, rect, prompt=""):
label, block = rect.split_y(0.20 * inch, direction='d')
label.draw_text_cell(canvas, "PROMPT", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.)
label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
style = getSampleStyleSheet()['BodyText']
style.fontName = 'Futura'
style.fontName = font_name
style.fontSize = 14
style.leading = 24
@@ -116,10 +117,10 @@ def draw_prompt(canvas, rect, prompt=""):
def draw_notes(canvas, rect, note=""):
label, block = rect.split_y(0.20 * inch, direction='d')
label.draw_text_cell(canvas, "NOTES", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.)
label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
style = getSampleStyleSheet()['BodyText']
style.fontName = 'Futura'
style.fontName = font_name
style.fontSize = 14
style.leading = 24
@@ -175,12 +176,12 @@ def draw_aux_block(canvas, rect, recording_time_sec_this_line, recording_time_se
lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.], direction='d')
lines[0].draw_text_cell(canvas,
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), "Futura", 9.)
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), "Futura", 9.)
lines[2].draw_text_cell(canvas, "Actual Start: ______________", "Futura", 9., vertical_align='b')
lines[3].draw_text_cell(canvas, "Record Date: ______________", "Futura", 9., vertical_align='b')
lines[4].draw_text_cell(canvas, "Engineer: ______________", "Futura", 9., vertical_align='b')
lines[5].draw_text_cell(canvas, "Location: ______________", "Futura", 9., vertical_align='b')
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), font_name, 9.)
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), font_name, 9.)
lines[2].draw_text_cell(canvas, "Actual Start: ______________", font_name, 9., vertical_align='b')
lines[3].draw_text_cell(canvas, "Record Date: ______________", font_name, 9., vertical_align='b')
lines[4].draw_text_cell(canvas, "Engineer: ______________", font_name, 9., vertical_align='b')
lines[5].draw_text_cell(canvas, "Location: ______________", font_name, 9., vertical_align='b')
def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines):
@@ -189,7 +190,7 @@ def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines
spotting_name = [record.spot] if record.spot is not None else []
pages_s = ["Line %i of %i" % (line_no, total_lines)]
footer_s = " - ".join(report_date_s + spotting_name + pages_s)
rect.draw_text_cell(canvas, footer_s, font_name="Futura", font_size=10., inset_y=2.)
rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10., inset_y=2.)
def create_report_for_character(records, report_date, tc_display_format: TimecodeFormat):
@@ -200,7 +201,7 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
assert outfile is not None
assert outfile[-4:] == '.pdf', "Output file must have 'pdf' extension!"
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
page: GRect = GRect(0, 0, letter[0], letter[1])
page = page.inset(inch * 0.5)

View File

@@ -16,15 +16,15 @@ from ..broadcast_timecode import TimecodeFormat
from ..docparser.adr_entity import ADRLine
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat):
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica"):
character_numbers = set([n.character_id for n in lines])
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
for n in character_numbers:
char_lines = [line for line in lines if not line.omitted and line.character_id == n]
character_name = char_lines[0].character_name
sorted(char_lines, key=lambda line: line.start)
char_lines = sorted(char_lines, key=lambda line: line.start)
title = "%s (%s) %s ADR Script" % (char_lines[0].title, character_name, n)
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
@@ -39,7 +39,7 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat):
story = []
prompt_style = getSampleStyleSheet()['Normal']
prompt_style.fontName = 'Futura'
prompt_style.fontName = font_name
prompt_style.fontSize = 18.
prompt_style.leading = 24.
@@ -47,7 +47,7 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat):
prompt_style.rightIndent = 1.5 * inch
number_style = getSampleStyleSheet()['Normal']
number_style.fontName = 'Futura'
number_style.fontName = font_name
number_style.fontSize = 14
number_style.leading = 24

View File

@@ -1,5 +1,15 @@
setuptools~=56.2.0
reportlab~=3.5.67
ffmpeg~=1.4
parsimonious~=0.8.1
tqdm~=4.60.0
astroid==2.9.3
isort==5.10.1
lazy-object-proxy==1.7.1
mccabe==0.6.1
parsimonious==0.9.0
Pillow==9.1.1
platformdirs==2.4.1
pylint==2.12.2
regex==2022.6.2
reportlab==3.6.10
six==1.16.0
toml==0.10.2
tqdm==4.64.0
typing_extensions==4.0.1
wrapt==1.13.3

4
test-coverage.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
coverage run -m pytest . ; coverage-lcov

View File

@@ -4,7 +4,7 @@ import os.path
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):

View File

@@ -4,7 +4,7 @@ import os.path
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):
session = parse_document(self.path)

View File

@@ -4,7 +4,7 @@ import os.path
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):
session = parse_document(self.path)

View File

@@ -4,7 +4,7 @@ import os.path
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):
session = parse_document(self.path)

View File

@@ -0,0 +1,34 @@
import unittest
import tempfile
import os.path
import os
import glob
from ptulsconv import commands
class TestBroadcastTimecode(unittest.TestCase):
def test_report_generation(self):
"""
Setp through every text file in export_cases and make sure it can
be converted into PDF docs without throwing an error
"""
files = [os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting.txt"]
#files.append(os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting2.txt")
for path in files:
tempdir = tempfile.TemporaryDirectory()
os.chdir(tempdir.name)
try:
commands.convert(path, major_mode='doc')
except:
assert False, "Error processing file %s" % path
finally:
tempdir.cleanup()
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,15 @@
import unittest
from ptulsconv import footage
class TestFootage(unittest.TestCase):
def test_basic_footage(self):
r1 = "90+0"
f1 = footage.footage_to_seconds(r1)
self.assertEqual(float(f1 or 0), 60.0)
def test_feet_and_frames(self):
r1 = "1+8"
f1 = footage.footage_to_seconds(r1)
self.assertEqual(float(f1 or 0), 1.0)

View File

@@ -97,14 +97,14 @@ class TestTagCompiler(unittest.TestCase):
markers = [doc_entity.MarkerDescriptor(number=1,
location="01:00:00:00",
time_reference=48000 * 60,
time_reference=48000 * 3600,
units="Samples",
name="Marker 1 {Part=1}",
comments=""
),
doc_entity.MarkerDescriptor(number=2,
location="01:00:01:00",
time_reference=48000 * 61,
time_reference=48000 * 3601,
units="Samples",
name="Marker 2 {Part=2}",
comments="[M1]"

View File

@@ -4,7 +4,7 @@ import os.path
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: