mirror of
https://github.com/iluvcapra/ptulsconv.git
synced 2025-12-31 17:00:46 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f29e95ba9 | ||
|
|
82f07b13a6 | ||
|
|
fbcbba1098 | ||
|
|
622f04963f | ||
|
|
5b36dcb5aa | ||
|
|
fd02d962d0 | ||
|
|
2021159666 | ||
|
|
f825b92586 | ||
|
|
4318946596 | ||
|
|
2a98954885 | ||
|
|
79d8cc5b69 | ||
|
|
5785dc3364 | ||
|
|
4e64edcd85 | ||
|
|
58277367c5 |
4
.flake8
Normal file
4
.flake8
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[flake8]
|
||||||
|
per-file-ignores =
|
||||||
|
ptulsconv/__init__.py: F401
|
||||||
|
ptulsconv/docparser/__init__.py: F401
|
||||||
3
.github/workflows/python-package.yml
vendored
3
.github/workflows/python-package.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
|
python-version: [3.8, 3.9, "3.10", "3.11"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.5.0
|
- uses: actions/checkout@v2.5.0
|
||||||
@@ -38,3 +38,4 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: |
|
run: |
|
||||||
pytest
|
pytest
|
||||||
|
flake8 ptulsconv
|
||||||
|
|||||||
Binary file not shown.
@@ -2,7 +2,8 @@
|
|||||||
Parse and convert Pro Tools text exports
|
Parse and convert Pro Tools text exports
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '1.0.7'
|
__version__ = '2.0.0'
|
||||||
__author__ = 'Jamie Hardt'
|
__author__ = 'Jamie Hardt'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__copyright__ = "%s %s (c) 2023 %s. All rights reserved." % (__name__, __version__, __author__)
|
__copyright__ = "%s %s (c) 2023 %s. All rights reserved." \
|
||||||
|
% (__name__, __version__, __author__)
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ from optparse import OptionParser, OptionGroup
|
|||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ptulsconv import __name__, __version__, __author__, __copyright__
|
from ptulsconv import __name__, __copyright__
|
||||||
from ptulsconv.commands import convert
|
from ptulsconv.commands import convert
|
||||||
from ptulsconv.reporting import print_status_style, print_banner_style, print_section_header_style, print_fatal_error
|
from ptulsconv.reporting import print_status_style, \
|
||||||
|
print_banner_style, print_section_header_style, \
|
||||||
|
print_fatal_error
|
||||||
|
|
||||||
|
|
||||||
def dump_field_map(output=sys.stdout):
|
def dump_field_map(output=sys.stdout):
|
||||||
@@ -19,18 +21,18 @@ def dump_formats():
|
|||||||
print_section_header_style("`raw` format:")
|
print_section_header_style("`raw` format:")
|
||||||
sys.stderr.write("A JSON document of the parsed Pro Tools export.\n")
|
sys.stderr.write("A JSON document of the parsed Pro Tools export.\n")
|
||||||
print_section_header_style("`tagged` Format:")
|
print_section_header_style("`tagged` Format:")
|
||||||
sys.stderr.write("A JSON document containing one record for each clip, with\n"
|
sys.stderr.write(
|
||||||
"all tags parsed and all tagging rules applied. \n")
|
"A JSON document containing one record for each clip, with\n"
|
||||||
|
"all tags parsed and all tagging rules applied. \n")
|
||||||
print_section_header_style("`doc` format:")
|
print_section_header_style("`doc` format:")
|
||||||
sys.stderr.write("Creates a directory with folders for different types\n"
|
sys.stderr.write("Creates a directory with folders for different types\n"
|
||||||
"of ADR reports.\n\n")
|
"of ADR reports.\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for the command-line invocation"""
|
"""Entry point for the command-line invocation"""
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
parser.usage = "ptulsconv [options] TEXT_EXPORT.txt"
|
parser.usage = "ptulsconv [options] [TEXT_EXPORT.txt]"
|
||||||
|
|
||||||
parser.add_option('-f', '--format',
|
parser.add_option('-f', '--format',
|
||||||
dest='output_format',
|
dest='output_format',
|
||||||
@@ -45,28 +47,33 @@ def main():
|
|||||||
warn_options.add_option('-W', action='store_false',
|
warn_options.add_option('-W', action='store_false',
|
||||||
dest='warnings',
|
dest='warnings',
|
||||||
default=True,
|
default=True,
|
||||||
help='Suppress warnings for common errors (missing code numbers etc.)')
|
help='Suppress warnings for common '
|
||||||
|
'errors (missing code numbers etc.)')
|
||||||
|
|
||||||
parser.add_option_group(warn_options)
|
parser.add_option_group(warn_options)
|
||||||
|
|
||||||
informational_options = OptionGroup(title="Informational Options",
|
informational_options = OptionGroup(title="Informational Options",
|
||||||
parser=parser,
|
parser=parser,
|
||||||
description='Print useful information and exit without processing '
|
description='Print useful '
|
||||||
'input files.')
|
'information '
|
||||||
|
'and exit without processing '
|
||||||
|
'input files.')
|
||||||
|
|
||||||
informational_options.add_option('--show-formats',
|
informational_options.add_option(
|
||||||
dest='show_formats',
|
'--show-formats',
|
||||||
action='store_true',
|
dest='show_formats',
|
||||||
default=False,
|
action='store_true',
|
||||||
help='Display helpful information about the '
|
default=False,
|
||||||
'available output formats.')
|
help='Display helpful information about the available '
|
||||||
|
'output formats.')
|
||||||
|
|
||||||
informational_options.add_option('--show-available-tags',
|
informational_options.add_option(
|
||||||
dest='show_tags',
|
'--show-available-tags',
|
||||||
action='store_true',
|
dest='show_tags',
|
||||||
default=False,
|
action='store_true',
|
||||||
help='Display tag mappings for the FMP XML '
|
default=False,
|
||||||
'output style and exit.')
|
help='Display tag mappings for the FMP XML output style '
|
||||||
|
'and exit.')
|
||||||
|
|
||||||
parser.add_option_group(informational_options)
|
parser.add_option_group(informational_options)
|
||||||
|
|
||||||
@@ -74,9 +81,9 @@ def main():
|
|||||||
|
|
||||||
(options, args) = parser.parse_args(sys.argv)
|
(options, args) = parser.parse_args(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
print_section_header_style("Startup")
|
print_section_header_style("Startup")
|
||||||
print_status_style("This run started %s" % (datetime.datetime.now().isoformat()))
|
print_status_style("This run started %s" %
|
||||||
|
(datetime.datetime.now().isoformat()))
|
||||||
|
|
||||||
if options.show_tags:
|
if options.show_tags:
|
||||||
dump_field_map()
|
dump_field_map()
|
||||||
@@ -85,15 +92,19 @@ def main():
|
|||||||
elif options.show_formats:
|
elif options.show_formats:
|
||||||
dump_formats()
|
dump_formats()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if len(args) < 2:
|
|
||||||
print_fatal_error("Error: No input file")
|
|
||||||
parser.print_help(sys.stderr)
|
|
||||||
sys.exit(22)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
major_mode = options.output_format
|
major_mode = options.output_format
|
||||||
convert(input_file=args[1], major_mode=major_mode, warnings=options.warnings)
|
|
||||||
|
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)
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print_fatal_error("Error trying to read input file")
|
print_fatal_error("Error trying to read input file")
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ from fractions import Fraction
|
|||||||
from typing import Optional, SupportsFloat
|
from typing import Optional, SupportsFloat
|
||||||
|
|
||||||
|
|
||||||
class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps drop_frame")):
|
class TimecodeFormat(namedtuple("_TimecodeFormat",
|
||||||
|
"frame_duration logical_fps drop_frame")):
|
||||||
"""
|
"""
|
||||||
A struct reperesenting a timecode datum.
|
A struct reperesenting a timecode datum.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
|
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
|
||||||
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
|
frame_count = smpte_to_frame_count(
|
||||||
|
smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
|
||||||
if frame_count is None:
|
if frame_count is None:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -23,29 +25,34 @@ class TimecodeFormat(namedtuple("_TimecodeFormat", "frame_duration logical_fps d
|
|||||||
|
|
||||||
def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
|
def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
|
||||||
frame_count = int(seconds / self.frame_duration)
|
frame_count = int(seconds / self.frame_duration)
|
||||||
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame)
|
return frame_count_to_smpte(frame_count, self.logical_fps,
|
||||||
|
self.drop_frame)
|
||||||
|
|
||||||
|
|
||||||
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> Optional[int]:
|
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
||||||
|
drop_frame_hint=False) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Convert a string with a SMPTE timecode representation into a frame count.
|
Convert a string with a SMPTE timecode representation into a frame count.
|
||||||
|
|
||||||
:param smpte_rep_string: The timecode string
|
:param smpte_rep_string: The timecode string
|
||||||
:param frames_per_logical_second: Num of frames in a logical second. This is asserted to be
|
:param frames_per_logical_second: Num of frames in a logical second. This
|
||||||
in one of `[24,25,30,48,50,60]`
|
is asserted to be in one of `[24,25,30,48,50,60]`
|
||||||
:param drop_frame_hint: `True` if the timecode rep is drop frame. This is ignored (and implied `True`) if
|
:param drop_frame_hint: `True` if the timecode rep is drop frame. This is
|
||||||
the last separator in the timecode string is a semicolon. This is ignored (and implied `False`) if
|
ignored (and implied `True`) if the last separator in the timecode
|
||||||
`frames_per_logical_second` is not 30 or 60.
|
string is a semicolon. This is ignored (and implied `False`) if
|
||||||
|
`frames_per_logical_second` is not 30 or 60.
|
||||||
"""
|
"""
|
||||||
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
||||||
|
|
||||||
m = re.search(r'(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?', smpte_rep_string)
|
m = re.search(
|
||||||
|
r'(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?', smpte_rep_string)
|
||||||
|
|
||||||
if m is None:
|
if m is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
hh, mm, ss, sep, ff, frac = m.groups()
|
hh, mm, ss, sep, ff, frac = m.groups()
|
||||||
hh, mm, ss, ff, frac = int(hh), int(mm), int(ss), int(ff), float(frac or 0.0)
|
hh, mm, ss, ff, frac = int(hh), int(
|
||||||
|
mm), int(ss), int(ff), float(frac or 0.0)
|
||||||
|
|
||||||
drop_frame = drop_frame_hint
|
drop_frame = drop_frame_hint
|
||||||
if sep == ";":
|
if sep == ";":
|
||||||
@@ -54,8 +61,8 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
|||||||
if frames_per_logical_second not in [30, 60]:
|
if frames_per_logical_second not in [30, 60]:
|
||||||
drop_frame = False
|
drop_frame = False
|
||||||
|
|
||||||
raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * frames_per_logical_second + \
|
raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * \
|
||||||
ss * frames_per_logical_second + ff
|
frames_per_logical_second + ss * frames_per_logical_second + ff
|
||||||
|
|
||||||
frames = raw_frames
|
frames = raw_frames
|
||||||
if drop_frame is True:
|
if drop_frame is True:
|
||||||
@@ -68,7 +75,8 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
|||||||
return frames
|
return frames
|
||||||
|
|
||||||
|
|
||||||
def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False,
|
def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int,
|
||||||
|
drop_frame: bool = False,
|
||||||
fractional_frame: Optional[float] = None) -> str:
|
fractional_frame: Optional[float] = None) -> str:
|
||||||
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
||||||
assert fractional_frame is None or fractional_frame < 1.0
|
assert fractional_frame is None or fractional_frame < 1.0
|
||||||
@@ -90,7 +98,8 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
|
|||||||
|
|
||||||
hh = hh % 24
|
hh = hh % 24
|
||||||
if fractional_frame is not None and fractional_frame > 0:
|
if fractional_frame is not None and fractional_frame > 0:
|
||||||
return "%02i:%02i:%02i%s%02i%s" % (hh, mm, ss, separator, ff, ("%.3f" % fractional_frame)[1:])
|
return "%02i:%02i:%02i%s%02i%s" % (hh, mm, ss, separator, ff,
|
||||||
|
("%.3f" % fractional_frame)[1:])
|
||||||
else:
|
else:
|
||||||
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import csv
|
import csv
|
||||||
from typing import List
|
from typing import List, Optional, Iterator
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
from .docparser.adr_entity import make_entities
|
import ptsl
|
||||||
from .reporting import print_section_header_style, print_status_style, print_warning
|
|
||||||
from .validations import *
|
from .docparser.adr_entity import make_entities, ADRLine
|
||||||
|
from .reporting import print_section_header_style, print_status_style,\
|
||||||
|
print_warning
|
||||||
|
from .validations import validate_unique_field, validate_non_empty_field,\
|
||||||
|
validate_dependent_value
|
||||||
|
|
||||||
from ptulsconv.docparser import parse_document
|
from ptulsconv.docparser import parse_document
|
||||||
from ptulsconv.docparser.tag_compiler import TagCompiler
|
from ptulsconv.docparser.tag_compiler import TagCompiler
|
||||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||||
from fractions import Fraction
|
|
||||||
|
|
||||||
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
|
from ptulsconv.pdf.supervisor_1pg import output_report as output_supervisor_1pg
|
||||||
from ptulsconv.pdf.line_count import output_report as output_line_count
|
from ptulsconv.pdf.line_count import output_report as output_line_count
|
||||||
@@ -48,9 +51,9 @@ class MyEncoder(JSONEncoder):
|
|||||||
|
|
||||||
def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
||||||
"""
|
"""
|
||||||
Writes ADR lines as CSV to the current working directory. Creates directories
|
Writes ADR lines as CSV to the current working directory. Creates
|
||||||
for each character number and name pair, and within that directory, creates
|
directories for each character number and name pair, and within that
|
||||||
a CSV file for each reel.
|
directory, creates a CSV file for each reel.
|
||||||
"""
|
"""
|
||||||
reels = set([ln.reel for ln in lines])
|
reels = set([ln.reel for ln in lines])
|
||||||
|
|
||||||
@@ -59,12 +62,15 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
|||||||
os.makedirs(dir_name, exist_ok=True)
|
os.makedirs(dir_name, exist_ok=True)
|
||||||
os.chdir(dir_name)
|
os.chdir(dir_name)
|
||||||
for reel in reels:
|
for reel in reels:
|
||||||
these_lines = [ln for ln in lines if ln.character_id == n and ln.reel == reel]
|
these_lines = [ln for ln in lines
|
||||||
|
if ln.character_id == n and ln.reel == reel]
|
||||||
|
|
||||||
if len(these_lines) == 0:
|
if len(these_lines) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
outfile_name = "%s_%s_%s_%s.csv" % (these_lines[0].title, n, these_lines[0].character_name, reel,)
|
outfile_name = "%s_%s_%s_%s.csv" % (these_lines[0].title, n,
|
||||||
|
these_lines[0].character_name,
|
||||||
|
reel,)
|
||||||
|
|
||||||
with open(outfile_name, mode='w', newline='') as outfile:
|
with open(outfile_name, mode='w', newline='') as outfile:
|
||||||
writer = csv.writer(outfile, dialect='excel')
|
writer = csv.writer(outfile, dialect='excel')
|
||||||
@@ -78,18 +84,21 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
|||||||
for event in these_lines:
|
for event in these_lines:
|
||||||
this_start = event.start or 0
|
this_start = event.start or 0
|
||||||
this_finish = event.finish or 0
|
this_finish = event.finish or 0
|
||||||
this_row = [event.title, event.character_name, event.cue_number,
|
this_row = [event.title, event.character_name,
|
||||||
event.reel, event.version,
|
event.cue_number, event.reel, event.version,
|
||||||
time_format.seconds_to_smpte(this_start), time_format.seconds_to_smpte(this_finish),
|
time_format.seconds_to_smpte(this_start),
|
||||||
|
time_format.seconds_to_smpte(this_finish),
|
||||||
float(this_start), float(this_finish),
|
float(this_start), float(this_finish),
|
||||||
event.prompt,
|
event.prompt,
|
||||||
event.reason, event.note, "TV" if event.tv else ""]
|
event.reason, event.note, "TV"
|
||||||
|
if event.tv else ""]
|
||||||
|
|
||||||
writer.writerow(this_row)
|
writer.writerow(this_row)
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
|
|
||||||
def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine], title):
|
def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine],
|
||||||
|
title):
|
||||||
"""
|
"""
|
||||||
Create PDF output.
|
Create PDF output.
|
||||||
"""
|
"""
|
||||||
@@ -103,22 +112,22 @@ def generate_documents(session_tc_format, scenes, adr_lines: Iterator[ADRLine],
|
|||||||
supervisor = next((x.supervisor for x in adr_lines), "")
|
supervisor = next((x.supervisor for x in adr_lines), "")
|
||||||
|
|
||||||
output_continuity(scenes=scenes, tc_display_format=session_tc_format,
|
output_continuity(scenes=scenes, tc_display_format=session_tc_format,
|
||||||
title=title, client=client, supervisor=supervisor)
|
title=title, client=client,
|
||||||
|
supervisor=supervisor)
|
||||||
|
|
||||||
# reels = sorted([r for r in compiler.compile_all_time_spans() if r[0] == 'Reel'],
|
|
||||||
# key=lambda x: x[2])
|
|
||||||
reels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6']
|
reels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6']
|
||||||
|
|
||||||
if len(adr_lines) == 0:
|
if len(adr_lines) == 0:
|
||||||
print_status_style("No ADR lines were found in the "
|
print_status_style("No ADR lines were found in the input document. "
|
||||||
"input document. ADR reports will not be generated.")
|
"ADR reports will not be generated.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
create_adr_reports(adr_lines, tc_display_format=session_tc_format,
|
create_adr_reports(adr_lines, tc_display_format=session_tc_format,
|
||||||
reel_list=sorted(reels))
|
reel_list=sorted(reels))
|
||||||
|
|
||||||
|
|
||||||
def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat, reel_list: List[str]):
|
def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
||||||
|
reel_list: List[str]):
|
||||||
"""
|
"""
|
||||||
Creates a directory heirarchy and a respective set of ADR reports,
|
Creates a directory heirarchy and a respective set of ADR reports,
|
||||||
given a list of lines.
|
given a list of lines.
|
||||||
@@ -139,7 +148,8 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
|||||||
print_status_style("Creating Director's Logs director and reports")
|
print_status_style("Creating Director's Logs director and reports")
|
||||||
os.makedirs("Director Logs", exist_ok=True)
|
os.makedirs("Director Logs", exist_ok=True)
|
||||||
os.chdir("Director Logs")
|
os.chdir("Director Logs")
|
||||||
output_summary(lines, tc_display_format=tc_display_format, by_character=True)
|
output_summary(lines, tc_display_format=tc_display_format,
|
||||||
|
by_character=True)
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
print_status_style("Creating CSV outputs")
|
print_status_style("Creating CSV outputs")
|
||||||
@@ -154,7 +164,7 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
|||||||
output_talent_sides(lines, tc_display_format=tc_display_format)
|
output_talent_sides(lines, tc_display_format=tc_display_format)
|
||||||
|
|
||||||
|
|
||||||
def convert(input_file, major_mode, output=sys.stdout, warnings=True):
|
def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
|
||||||
"""
|
"""
|
||||||
Primary worker function, accepts the input file and decides
|
Primary worker function, accepts the input file and decides
|
||||||
what to do with it based on the `major_mode`.
|
what to do with it based on the `major_mode`.
|
||||||
@@ -162,7 +172,24 @@ def convert(input_file, major_mode, output=sys.stdout, warnings=True):
|
|||||||
:param input_file: a path to the input file.
|
:param input_file: a path to the input file.
|
||||||
:param major_mode: the selected output mode, 'raw', 'tagged' or 'doc'.
|
:param major_mode: the selected output mode, 'raw', 'tagged' or 'doc'.
|
||||||
"""
|
"""
|
||||||
session = parse_document(input_file)
|
session_text = ""
|
||||||
|
if input_file is not None:
|
||||||
|
with open(input_file, "r") as file:
|
||||||
|
session_text = file.read()
|
||||||
|
else:
|
||||||
|
with ptsl.open_engine(
|
||||||
|
company_name="The ptulsconv developers",
|
||||||
|
application_name="ptulsconv") as engine:
|
||||||
|
req = engine.export_session_as_text()
|
||||||
|
req.utf8_encoding()
|
||||||
|
req.include_track_edls()
|
||||||
|
req.include_markers()
|
||||||
|
req.time_type("tc")
|
||||||
|
req.dont_show_crossfades()
|
||||||
|
req.selected_tracks_only()
|
||||||
|
session_text = req.export_string
|
||||||
|
|
||||||
|
session = parse_document(session_text)
|
||||||
session_tc_format = session.header.timecode_format
|
session_tc_format = session.header.timecode_format
|
||||||
|
|
||||||
if major_mode == 'raw':
|
if major_mode == 'raw':
|
||||||
@@ -179,19 +206,22 @@ def convert(input_file, major_mode, output=sys.stdout, warnings=True):
|
|||||||
elif major_mode == 'doc':
|
elif major_mode == 'doc':
|
||||||
generic_events, adr_lines = make_entities(compiled_events)
|
generic_events, adr_lines = make_entities(compiled_events)
|
||||||
|
|
||||||
scenes = sorted([s for s in compiler.compile_all_time_spans() if s[0] == 'Sc'],
|
scenes = sorted([s for s in compiler.compile_all_time_spans()
|
||||||
|
if s[0] == 'Sc'],
|
||||||
key=lambda x: x[2])
|
key=lambda x: x[2])
|
||||||
|
|
||||||
# TODO: Breakdown by titles
|
# TODO: Breakdown by titles
|
||||||
titles = set([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("Multiple titles per export is not supported, "
|
print_warning("Multiple titles per export is not supported, "
|
||||||
"found multiple titles: %s Exiting." % titles)
|
"found multiple titles: %s Exiting." % titles)
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
title = list(titles)[0]
|
title = list(titles)[0]
|
||||||
|
|
||||||
print_status_style("%i generic events found." % len(generic_events))
|
print_status_style(
|
||||||
|
"%i generic events found." % len(generic_events)
|
||||||
|
)
|
||||||
print_status_style("%i ADR events found." % len(adr_lines))
|
print_status_style("%i ADR events found." % len(adr_lines))
|
||||||
|
|
||||||
if warnings:
|
if warnings:
|
||||||
@@ -200,23 +230,25 @@ def convert(input_file, major_mode, output=sys.stdout, warnings=True):
|
|||||||
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]):
|
def perform_adr_validations(lines: Iterator[ADRLine]):
|
||||||
"""
|
"""
|
||||||
Performs validations on the input.
|
Performs validations on the input.
|
||||||
"""
|
"""
|
||||||
for warning in chain(validate_unique_field(lines,
|
for warning in chain(
|
||||||
field='cue_number',
|
validate_unique_field(lines,
|
||||||
scope='title'),
|
field='cue_number',
|
||||||
validate_non_empty_field(lines,
|
scope='title'),
|
||||||
field='cue_number'),
|
validate_non_empty_field(lines,
|
||||||
validate_non_empty_field(lines,
|
field='cue_number'),
|
||||||
field='character_id'),
|
validate_non_empty_field(lines,
|
||||||
validate_non_empty_field(lines,
|
field='character_id'),
|
||||||
field='title'),
|
validate_non_empty_field(lines,
|
||||||
validate_dependent_value(lines,
|
field='title'),
|
||||||
key_field='character_id',
|
validate_dependent_value(lines,
|
||||||
dependent_field='character_name'),
|
key_field='character_id',
|
||||||
validate_dependent_value(lines,
|
dependent_field='character_name'),
|
||||||
key_field='character_id',
|
validate_dependent_value(lines,
|
||||||
dependent_field='actor_name')):
|
key_field='character_id',
|
||||||
|
dependent_field='actor_name')):
|
||||||
|
|
||||||
print_warning(warning.report_message())
|
print_warning(warning.report_message())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
This module defines classes and methods for converting :class:`Event` objects into
|
This module defines classes and methods for converting :class:`Event` objects
|
||||||
:class:`ADRLine` objects.
|
into :class:`ADRLine` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ptulsconv.docparser.tag_compiler import Event
|
from ptulsconv.docparser.tag_compiler import Event
|
||||||
@@ -11,15 +11,16 @@ from fractions import Fraction
|
|||||||
from ptulsconv.docparser.tag_mapping import TagMapping
|
from ptulsconv.docparser.tag_mapping import TagMapping
|
||||||
|
|
||||||
|
|
||||||
def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'], List['ADRLine']]:
|
def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'],
|
||||||
|
List['ADRLine']]:
|
||||||
"""
|
"""
|
||||||
Accepts a list of Events and converts them into either ADRLine events or
|
Accepts a list of Events and converts them into either ADRLine events or
|
||||||
GenricEvents by calling :func:`make_entity` on each member.
|
GenricEvents by calling :func:`make_entity` on each member.
|
||||||
|
|
||||||
:param from_events: A list of `Event` objects.
|
:param from_events: A list of `Event` objects.
|
||||||
|
|
||||||
:returns: A tuple of two lists, the first containing :class:`GenericEvent` and the
|
:returns: A tuple of two lists, the first containing :class:`GenericEvent`
|
||||||
second containing :class:`ADRLine`.
|
and the second containing :class:`ADRLine`.
|
||||||
"""
|
"""
|
||||||
generic_events = list()
|
generic_events = list()
|
||||||
adr_lines = list()
|
adr_lines = list()
|
||||||
@@ -67,14 +68,15 @@ class GenericEvent:
|
|||||||
scene: Optional[str] = None
|
scene: Optional[str] = None
|
||||||
version: Optional[str] = None
|
version: Optional[str] = None
|
||||||
reel: Optional[str] = None
|
reel: Optional[str] = None
|
||||||
start: Fraction = Fraction(0,1)
|
start: Fraction = Fraction(0, 1)
|
||||||
finish: Fraction = Fraction(0,1)
|
finish: Fraction = Fraction(0, 1)
|
||||||
omitted: bool = False
|
omitted: bool = False
|
||||||
note: Optional[str] = None
|
note: Optional[str] = None
|
||||||
requested_by: Optional[str] = None
|
requested_by: Optional[str] = None
|
||||||
|
|
||||||
tag_mapping = [
|
tag_mapping = [
|
||||||
TagMapping(source='Title', target="title", alt=TagMapping.ContentSource.Session),
|
TagMapping(source='Title', target="title",
|
||||||
|
alt=TagMapping.ContentSource.Session),
|
||||||
TagMapping(source="Supv", target="supervisor"),
|
TagMapping(source="Supv", target="supervisor"),
|
||||||
TagMapping(source="Client", target="client"),
|
TagMapping(source="Client", target="client"),
|
||||||
TagMapping(source="Sc", target="scene"),
|
TagMapping(source="Sc", target="scene"),
|
||||||
@@ -111,9 +113,11 @@ class ADRLine(GenericEvent):
|
|||||||
TagMapping(source="P", target="priority"),
|
TagMapping(source="P", target="priority"),
|
||||||
TagMapping(source="QN", target="cue_number"),
|
TagMapping(source="QN", target="cue_number"),
|
||||||
TagMapping(source="CN", target="character_id"),
|
TagMapping(source="CN", target="character_id"),
|
||||||
TagMapping(source="Char", target="character_name", alt=TagMapping.ContentSource.Track),
|
TagMapping(source="Char", target="character_name",
|
||||||
|
alt=TagMapping.ContentSource.Track),
|
||||||
TagMapping(source="Actor", target="actor_name"),
|
TagMapping(source="Actor", target="actor_name"),
|
||||||
TagMapping(source="Line", target="prompt", alt=TagMapping.ContentSource.Clip),
|
TagMapping(source="Line", target="prompt",
|
||||||
|
alt=TagMapping.ContentSource.Clip),
|
||||||
TagMapping(source="R", target="reason"),
|
TagMapping(source="R", target="reason"),
|
||||||
TagMapping(source="Mins", target="time_budget_mins",
|
TagMapping(source="Mins", target="time_budget_mins",
|
||||||
formatter=(lambda n: float(n))),
|
formatter=(lambda n: float(n))),
|
||||||
@@ -131,5 +135,3 @@ class ADRLine(GenericEvent):
|
|||||||
TagMapping(source="OPT", target="optional",
|
TagMapping(source="OPT", target="optional",
|
||||||
formatter=(lambda x: len(x) > 0))
|
formatter=(lambda x: len(x) > 0))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,19 +21,24 @@ class SessionDescriptor:
|
|||||||
|
|
||||||
def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]:
|
def markers_timed(self) -> Iterator[Tuple['MarkerDescriptor', Fraction]]:
|
||||||
for marker in self.markers:
|
for marker in self.markers:
|
||||||
marker_time = Fraction(marker.time_reference, int(self.header.sample_rate))
|
marker_time = Fraction(marker.time_reference,
|
||||||
#marker_time = self.header.convert_timecode(marker.location)
|
int(self.header.sample_rate))
|
||||||
|
# marker_time = self.header.convert_timecode(marker.location)
|
||||||
yield marker, marker_time
|
yield marker, marker_time
|
||||||
|
|
||||||
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor', 'TrackClipDescriptor']]:
|
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor',
|
||||||
|
'TrackClipDescriptor']]:
|
||||||
for track in self.tracks:
|
for track in self.tracks:
|
||||||
for clip in track.clips:
|
for clip in track.clips:
|
||||||
yield track, clip
|
yield track, clip
|
||||||
|
|
||||||
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor", "TrackClipDescriptor",
|
def track_clips_timed(self) -> Iterator[Tuple["TrackDescriptor",
|
||||||
Fraction, Fraction, Fraction]]:
|
"TrackClipDescriptor",
|
||||||
|
Fraction, Fraction, Fraction]
|
||||||
|
]:
|
||||||
"""
|
"""
|
||||||
:return: A Generator that yields track, clip, start time, finish time, and timestamp
|
:return: A Generator that yields track, clip, start time, finish time,
|
||||||
|
and timestamp
|
||||||
"""
|
"""
|
||||||
for track, clip in self.tracks_clips():
|
for track, clip in self.tracks_clips():
|
||||||
start_time = self.header.convert_timecode(clip.start_timecode)
|
start_time = self.header.convert_timecode(clip.start_timecode)
|
||||||
@@ -105,7 +110,8 @@ class HeaderDescriptor:
|
|||||||
if self.timecode_fps in frame_rates.keys():
|
if self.timecode_fps in frame_rates.keys():
|
||||||
return frame_rates[self.timecode_fps]
|
return frame_rates[self.timecode_fps]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unrecognized TC rate (%s)" % self.timecode_format)
|
raise ValueError("Unrecognized TC rate (%s)" %
|
||||||
|
self.timecode_format)
|
||||||
|
|
||||||
|
|
||||||
class TrackDescriptor:
|
class TrackDescriptor:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from dataclasses import dataclass
|
# from dataclasses import dataclass
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
from parsimonious.nodes import NodeVisitor
|
from parsimonious.nodes import NodeVisitor
|
||||||
from parsimonious.grammar import Grammar
|
from parsimonious.grammar import Grammar
|
||||||
|
|
||||||
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor, FileDescriptor, \
|
from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor,\
|
||||||
TrackClipDescriptor, ClipDescriptor, PluginDescriptor, MarkerDescriptor
|
FileDescriptor, TrackClipDescriptor, ClipDescriptor, PluginDescriptor,\
|
||||||
|
MarkerDescriptor
|
||||||
|
|
||||||
|
|
||||||
protools_text_export_grammar = Grammar(
|
protools_text_export_grammar = Grammar(
|
||||||
r"""
|
r"""
|
||||||
document = header files_section? clips_section? plugin_listing? track_listing? markers_listing?
|
document = header files_section? clips_section? plugin_listing?
|
||||||
|
track_listing? markers_listing?
|
||||||
header = "SESSION NAME:" fs string_value rs
|
header = "SESSION NAME:" fs string_value rs
|
||||||
"SAMPLE RATE:" fs float_value rs
|
"SAMPLE RATE:" fs float_value rs
|
||||||
"BIT DEPTH:" fs integer_value "-bit" rs
|
"BIT DEPTH:" fs integer_value "-bit" rs
|
||||||
@@ -17,21 +19,29 @@ protools_text_export_grammar = Grammar(
|
|||||||
"# OF AUDIO CLIPS:" fs integer_value rs
|
"# OF AUDIO CLIPS:" fs integer_value rs
|
||||||
"# OF AUDIO FILES:" fs integer_value rs block_ending
|
"# OF AUDIO FILES:" fs integer_value rs block_ending
|
||||||
|
|
||||||
frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" / "23.976")
|
frame_rate = ("60" / "59.94" / "30" / "29.97" / "25" / "24" /
|
||||||
files_section = files_header files_column_header file_record* block_ending
|
"23.976")
|
||||||
|
files_section = files_header files_column_header file_record*
|
||||||
|
block_ending
|
||||||
files_header = "F I L E S I N S E S S I O N" rs
|
files_header = "F I L E S I N S E S S I O N" rs
|
||||||
files_column_header = "Filename" isp fs "Location" rs
|
files_column_header = "Filename" isp fs "Location" rs
|
||||||
file_record = string_value fs string_value rs
|
file_record = string_value fs string_value rs
|
||||||
|
|
||||||
clips_section = clips_header clips_column_header clip_record* block_ending
|
clips_section = clips_header clips_column_header clip_record*
|
||||||
|
block_ending
|
||||||
clips_header = "O N L I N E C L I P S I N S E S S I O N" rs
|
clips_header = "O N L I N E C L I P S I N S E S S I O N" rs
|
||||||
clips_column_header = string_value fs string_value rs
|
clips_column_header = string_value fs string_value rs
|
||||||
clip_record = string_value fs string_value (fs "[" integer_value "]")? rs
|
clip_record = string_value fs string_value
|
||||||
|
(fs "[" integer_value "]")? rs
|
||||||
|
|
||||||
plugin_listing = plugin_header plugin_column_header plugin_record* block_ending
|
plugin_listing = plugin_header plugin_column_header plugin_record*
|
||||||
|
block_ending
|
||||||
plugin_header = "P L U G - I N S L I S T I N G" rs
|
plugin_header = "P L U G - I N S L I S T I N G" rs
|
||||||
plugin_column_header = "MANUFACTURER " fs "PLUG-IN NAME " fs
|
plugin_column_header = "MANUFACTURER " fs
|
||||||
"VERSION " fs "FORMAT " fs "STEMS " fs
|
"PLUG-IN NAME " fs
|
||||||
|
"VERSION " fs
|
||||||
|
"FORMAT " fs
|
||||||
|
"STEMS " fs
|
||||||
"NUMBER OF INSTANCES" rs
|
"NUMBER OF INSTANCES" rs
|
||||||
plugin_record = string_value fs string_value fs string_value fs
|
plugin_record = string_value fs string_value fs string_value fs
|
||||||
string_value fs string_value fs string_value rs
|
string_value fs string_value fs string_value rs
|
||||||
@@ -45,8 +55,10 @@ protools_text_export_grammar = Grammar(
|
|||||||
"USER DELAY:" fs integer_value " Samples" rs
|
"USER DELAY:" fs integer_value " Samples" rs
|
||||||
"STATE: " track_state_list rs
|
"STATE: " track_state_list rs
|
||||||
("PLUG-INS: " ( fs string_value )* rs)?
|
("PLUG-INS: " ( fs string_value )* rs)?
|
||||||
"CHANNEL " fs "EVENT " fs "CLIP NAME " fs
|
"CHANNEL " fs "EVENT " fs
|
||||||
"START TIME " fs "END TIME " fs "DURATION " fs
|
"CLIP NAME " fs
|
||||||
|
"START TIME " fs "END TIME " fs
|
||||||
|
"DURATION " fs
|
||||||
("TIMESTAMP " fs)? "STATE" rs
|
("TIMESTAMP " fs)? "STATE" rs
|
||||||
|
|
||||||
track_state_list = (track_state " ")*
|
track_state_list = (track_state " ")*
|
||||||
@@ -56,15 +68,20 @@ protools_text_export_grammar = Grammar(
|
|||||||
track_clip_entry = integer_value isp fs
|
track_clip_entry = integer_value isp fs
|
||||||
integer_value isp fs
|
integer_value isp fs
|
||||||
string_value fs
|
string_value fs
|
||||||
string_value fs string_value fs string_value fs (string_value fs)?
|
string_value fs string_value fs string_value fs
|
||||||
|
(string_value fs)?
|
||||||
track_clip_state rs
|
track_clip_state rs
|
||||||
|
|
||||||
track_clip_state = ("Muted" / "Unmuted")
|
track_clip_state = ("Muted" / "Unmuted")
|
||||||
|
|
||||||
markers_listing = markers_listing_header markers_column_header marker_record*
|
markers_listing = markers_listing_header markers_column_header
|
||||||
|
marker_record*
|
||||||
markers_listing_header = "M A R K E R S L I S T I N G" rs
|
markers_listing_header = "M A R K E R S L I S T I N G" rs
|
||||||
markers_column_header = "# " fs "LOCATION " fs "TIME REFERENCE " fs
|
markers_column_header = "# " fs "LOCATION " fs
|
||||||
"UNITS " fs "NAME " fs "COMMENTS" rs
|
"TIME REFERENCE " fs
|
||||||
|
"UNITS " fs
|
||||||
|
"NAME " fs
|
||||||
|
"COMMENTS" rs
|
||||||
|
|
||||||
marker_record = integer_value isp fs string_value fs integer_value isp fs
|
marker_record = integer_value isp fs string_value fs integer_value isp fs
|
||||||
string_value fs string_value fs string_value rs
|
string_value fs string_value fs string_value rs
|
||||||
@@ -79,15 +96,14 @@ protools_text_export_grammar = Grammar(
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
def parse_document(path: str) -> SessionDescriptor:
|
def parse_document(session_text: str) -> SessionDescriptor:
|
||||||
"""
|
"""
|
||||||
Parse a Pro Tools text export.
|
Parse a Pro Tools text export.
|
||||||
:param path: path to a file
|
:param session_text: Pro Tools session text export
|
||||||
:return: the session descriptor
|
:return: the session descriptor
|
||||||
"""
|
"""
|
||||||
with open(path, 'r') as f:
|
ast = protools_text_export_grammar.parse(session_text)
|
||||||
ast = protools_text_export_grammar.parse(f.read())
|
return DocParserVisitor().visit(ast)
|
||||||
return DocParserVisitor().visit(ast)
|
|
||||||
|
|
||||||
|
|
||||||
class DocParserVisitor(NodeVisitor):
|
class DocParserVisitor(NodeVisitor):
|
||||||
@@ -126,23 +142,28 @@ class DocParserVisitor(NodeVisitor):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_files_section(_, visited_children):
|
def visit_files_section(_, visited_children):
|
||||||
return list(map(lambda child: FileDescriptor(filename=child[0], path=child[2]), visited_children[2]))
|
return list(map(
|
||||||
|
lambda child: FileDescriptor(filename=child[0], path=child[2]),
|
||||||
|
visited_children[2]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_clips_section(_, visited_children):
|
def visit_clips_section(_, visited_children):
|
||||||
channel = next(iter(visited_children[2][3]), 1)
|
channel = next(iter(visited_children[2][3]), 1)
|
||||||
|
|
||||||
return list(map(lambda child: ClipDescriptor(clip_name=child[0], file=child[2], channel=channel),
|
return list(map(
|
||||||
visited_children[2]))
|
lambda child: ClipDescriptor(clip_name=child[0], file=child[2],
|
||||||
|
channel=channel),
|
||||||
|
visited_children[2]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_plugin_listing(_, visited_children):
|
def visit_plugin_listing(_, visited_children):
|
||||||
return list(map(lambda child: PluginDescriptor(manufacturer=child[0],
|
return list(map(lambda child:
|
||||||
plugin_name=child[2],
|
PluginDescriptor(manufacturer=child[0],
|
||||||
version=child[4],
|
plugin_name=child[2],
|
||||||
format=child[6],
|
version=child[4],
|
||||||
stems=child[8],
|
format=child[6],
|
||||||
count_instances=child[10]),
|
stems=child[8],
|
||||||
|
count_instances=child[10]),
|
||||||
visited_children[2]))
|
visited_children[2]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -24,20 +24,25 @@ class TagCompiler:
|
|||||||
items.
|
items.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Intermediate = namedtuple('Intermediate', 'track_content track_tags track_comment_tags '
|
Intermediate = namedtuple('Intermediate',
|
||||||
'clip_content clip_tags clip_tag_mode start finish')
|
'track_content track_tags track_comment_tags '
|
||||||
|
'clip_content clip_tags clip_tag_mode start '
|
||||||
|
'finish')
|
||||||
|
|
||||||
session: doc_entity.SessionDescriptor
|
session: doc_entity.SessionDescriptor
|
||||||
|
|
||||||
def compile_all_time_spans(self) -> List[Tuple[str, str, Fraction, Fraction]]:
|
def compile_all_time_spans(self) -> List[Tuple[str, str, Fraction,
|
||||||
|
Fraction]]:
|
||||||
"""
|
"""
|
||||||
:returns: A `List` of (key: str, value: str, start: Fraction, finish: Fraction)
|
:returns: A `List` of (key: str, value: str, start: Fraction,
|
||||||
|
finish: Fraction)
|
||||||
"""
|
"""
|
||||||
ret_list = list()
|
ret_list = list()
|
||||||
for element in self.parse_data():
|
for element in self.parse_data():
|
||||||
if element.clip_tag_mode == TagPreModes.TIMESPAN:
|
if element.clip_tag_mode == TagPreModes.TIMESPAN:
|
||||||
for k in element.clip_tags.keys():
|
for k in element.clip_tags.keys():
|
||||||
ret_list.append((k, element.clip_tags[k], element.start, element.finish))
|
ret_list.append((k, element.clip_tags[k], element.start,
|
||||||
|
element.finish))
|
||||||
|
|
||||||
return ret_list
|
return ret_list
|
||||||
|
|
||||||
@@ -73,26 +78,31 @@ class TagCompiler:
|
|||||||
step3 = self.collect_time_spans(step2)
|
step3 = self.collect_time_spans(step2)
|
||||||
step4 = self.apply_tags(step3)
|
step4 = self.apply_tags(step3)
|
||||||
for datum in step4:
|
for datum in step4:
|
||||||
yield Event(clip_name=datum[0], track_name=datum[1], session_name=datum[2],
|
yield Event(clip_name=datum[0], track_name=datum[1],
|
||||||
tags=datum[3], start=datum[4], finish=datum[5])
|
session_name=datum[2], tags=datum[3], start=datum[4],
|
||||||
|
finish=datum[5])
|
||||||
|
|
||||||
def _marker_tags(self, at):
|
def _marker_tags(self, at):
|
||||||
retval = dict()
|
retval = dict()
|
||||||
applicable = [(m, t) for (m, t) in self.session.markers_timed() if t <= at]
|
|
||||||
|
applicable = [(m, t) for (m, t) in
|
||||||
|
self.session.markers_timed() if t <= at]
|
||||||
|
|
||||||
for marker, _ in sorted(applicable, key=lambda x: x[1]):
|
for marker, _ in sorted(applicable, key=lambda x: x[1]):
|
||||||
retval.update(parse_tags(marker.comments or "").tag_dict)
|
retval.update(parse_tags(marker.comments or "").tag_dict)
|
||||||
retval.update(parse_tags(marker.name or "").tag_dict)
|
retval.update(parse_tags(marker.name or "").tag_dict)
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def filter_out_directives(self, clips : Iterator[Intermediate]) -> Iterator[Intermediate]:
|
def filter_out_directives(self,
|
||||||
|
clips: Iterator[Intermediate]) \
|
||||||
|
-> Iterator[Intermediate]:
|
||||||
for clip in clips:
|
for clip in clips:
|
||||||
if clip.clip_tag_mode == 'Directive':
|
if clip.clip_tag_mode == 'Directive':
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
yield clip
|
yield clip
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _coalesce_tags(clip_tags: dict, track_tags: dict,
|
def _coalesce_tags(clip_tags: dict, track_tags: dict,
|
||||||
track_comment_tags: dict,
|
track_comment_tags: dict,
|
||||||
@@ -117,29 +127,33 @@ class TagCompiler:
|
|||||||
track_comments_parsed = parse_tags(track.comments)
|
track_comments_parsed = parse_tags(track.comments)
|
||||||
clip_parsed = parse_tags(clip.clip_name)
|
clip_parsed = parse_tags(clip.clip_name)
|
||||||
|
|
||||||
yield TagCompiler.Intermediate(track_content=track_parsed.content,
|
yield TagCompiler.Intermediate(
|
||||||
track_tags=track_parsed.tag_dict,
|
track_content=track_parsed.content,
|
||||||
track_comment_tags=track_comments_parsed.tag_dict,
|
track_tags=track_parsed.tag_dict,
|
||||||
clip_content=clip_parsed.content,
|
track_comment_tags=track_comments_parsed.tag_dict,
|
||||||
clip_tags=clip_parsed.tag_dict,
|
clip_content=clip_parsed.content,
|
||||||
clip_tag_mode=clip_parsed.mode,
|
clip_tags=clip_parsed.tag_dict,
|
||||||
start=start, finish=finish)
|
clip_tag_mode=clip_parsed.mode,
|
||||||
|
start=start, finish=finish)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]:
|
def apply_appends(parsed: Iterator[Intermediate]) -> \
|
||||||
|
Iterator[Intermediate]:
|
||||||
|
|
||||||
def should_append(a, b):
|
def should_append(a, b):
|
||||||
return b.clip_tag_mode == TagPreModes.APPEND and b.start >= a.finish
|
return b.clip_tag_mode == TagPreModes.APPEND and \
|
||||||
|
b.start >= a.finish
|
||||||
|
|
||||||
def do_append(a, b):
|
def do_append(a, b):
|
||||||
merged_tags = dict(a.clip_tags)
|
merged_tags = dict(a.clip_tags)
|
||||||
merged_tags.update(b.clip_tags)
|
merged_tags.update(b.clip_tags)
|
||||||
return TagCompiler.Intermediate(track_content=a.track_content,
|
return TagCompiler.Intermediate(
|
||||||
track_tags=a.track_tags,
|
track_content=a.track_content,
|
||||||
track_comment_tags=a.track_comment_tags,
|
track_tags=a.track_tags,
|
||||||
clip_content=a.clip_content + ' ' + b.clip_content,
|
track_comment_tags=a.track_comment_tags,
|
||||||
clip_tags=merged_tags, clip_tag_mode=a.clip_tag_mode,
|
clip_content=a.clip_content + ' ' + b.clip_content,
|
||||||
start=a.start, finish=b.finish)
|
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)
|
yield from apply_appends(parsed, should_append, do_append)
|
||||||
|
|
||||||
@@ -158,12 +172,14 @@ class TagCompiler:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _time_span_tags(at_time: Fraction, applicable_spans) -> dict:
|
def _time_span_tags(at_time: Fraction, applicable_spans) -> dict:
|
||||||
retval = dict()
|
retval = dict()
|
||||||
for tags in reversed([a[0] for a in applicable_spans if a[1] <= at_time <= a[2]]):
|
for tags in reversed([a[0] for a in applicable_spans
|
||||||
|
if a[1] <= at_time <= a[2]]):
|
||||||
retval.update(tags)
|
retval.update(tags)
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def apply_tags(self, parsed_with_time_spans) -> Iterator[Tuple[str, str, str, dict, Fraction, Fraction]]:
|
def apply_tags(self, parsed_with_time_spans) ->\
|
||||||
|
Iterator[Tuple[str, str, str, dict, Fraction, Fraction]]:
|
||||||
|
|
||||||
session_parsed = parse_tags(self.session.header.session_name)
|
session_parsed = parse_tags(self.session.header.session_name)
|
||||||
|
|
||||||
@@ -171,14 +187,16 @@ class TagCompiler:
|
|||||||
event: 'TagCompiler.Intermediate'
|
event: 'TagCompiler.Intermediate'
|
||||||
marker_tags = self._marker_tags(event.start)
|
marker_tags = self._marker_tags(event.start)
|
||||||
time_span_tags = self._time_span_tags(event.start, time_spans)
|
time_span_tags = self._time_span_tags(event.start, time_spans)
|
||||||
tags = self._coalesce_tags(clip_tags=event.clip_tags,
|
tags = self._coalesce_tags(
|
||||||
track_tags=event.track_tags,
|
clip_tags=event.clip_tags,
|
||||||
track_comment_tags=event.track_comment_tags,
|
track_tags=event.track_tags,
|
||||||
timespan_tags=time_span_tags,
|
track_comment_tags=event.track_comment_tags,
|
||||||
marker_tags=marker_tags,
|
timespan_tags=time_span_tags,
|
||||||
session_tags=session_parsed.tag_dict)
|
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,
|
def apply_appends(source: Iterator,
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class TagMapping:
|
|||||||
for rule in rules:
|
for rule in rules:
|
||||||
if rule.target in done:
|
if rule.target in done:
|
||||||
continue
|
continue
|
||||||
if rule.apply(tags, clip_content, track_content, session_content, to):
|
if rule.apply(tags, clip_content, track_content, session_content,
|
||||||
|
to):
|
||||||
done.update(rule.target)
|
done.update(rule.target)
|
||||||
|
|
||||||
def __init__(self, source: str,
|
def __init__(self, source: str,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from parsimonious import NodeVisitor, Grammar
|
from parsimonious import NodeVisitor, Grammar
|
||||||
from typing import Dict, Union
|
from typing import Dict
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
@@ -52,8 +52,9 @@ class TagListVisitor(NodeVisitor):
|
|||||||
modifier_opt, line_opt, _, tag_list_opt = visited_children
|
modifier_opt, line_opt, _, tag_list_opt = visited_children
|
||||||
|
|
||||||
return TaggedStringResult(content=next(iter(line_opt), None),
|
return TaggedStringResult(content=next(iter(line_opt), None),
|
||||||
tag_dict=next(iter(tag_list_opt), dict()),
|
tag_dict=next(iter(tag_list_opt), dict()),
|
||||||
mode=TagPreModes(next(iter(modifier_opt), 'Normal'))
|
mode=TagPreModes(
|
||||||
|
next(iter(modifier_opt), 'Normal'))
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#import ffmpeg # ffmpeg-python
|
# import ffmpeg # ffmpeg-python
|
||||||
|
|
||||||
# TODO: Implement movie export
|
# TODO: Implement movie export
|
||||||
|
|
||||||
# def create_movie(event):
|
# def create_movie(event):
|
||||||
# start = event['Movie.Start_Offset_Seconds']
|
# start = event['Movie.Start_Offset_Seconds']
|
||||||
# duration = event['PT.Clip.Finish_Seconds'] - event['PT.Clip.Start_Seconds']
|
# duration = event['PT.Clip.Finish_Seconds'] -
|
||||||
|
# event['PT.Clip.Start_Seconds']
|
||||||
# input_movie = event['Movie.Filename']
|
# input_movie = event['Movie.Filename']
|
||||||
# print("Will make movie starting at {}, dur {} from movie {}".format(start, duration, input_movie))
|
# print("Will make movie starting at {}, dur {} from movie {}"
|
||||||
|
# .format(start, duration, input_movie))
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# def export_movies(events):
|
# def export_movies(events):
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ from typing import List
|
|||||||
|
|
||||||
# This is from https://code.activestate.com/recipes/576832/ for
|
# This is from https://code.activestate.com/recipes/576832/ for
|
||||||
# generating page count messages
|
# generating page count messages
|
||||||
|
|
||||||
|
|
||||||
class ReportCanvas(canvas.Canvas):
|
class ReportCanvas(canvas.Canvas):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
canvas.Canvas.__init__(self, *args, **kwargs)
|
canvas.Canvas.__init__(self, *args, **kwargs)
|
||||||
@@ -38,10 +40,12 @@ class ReportCanvas(canvas.Canvas):
|
|||||||
|
|
||||||
def draw_page_number(self, page_count):
|
def draw_page_number(self, page_count):
|
||||||
self.saveState()
|
self.saveState()
|
||||||
self.setFont('Helvetica', 10) #FIXME make this customizable
|
self.setFont('Helvetica', 10) # FIXME make this customizable
|
||||||
self.drawString(0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count))
|
self.drawString(0.5 * inch, 0.5 * inch,
|
||||||
|
"Page %d of %d" % (self._pageNumber, page_count))
|
||||||
right_edge = self._pagesize[0] - 0.5 * inch
|
right_edge = self._pagesize[0] - 0.5 * inch
|
||||||
self.drawRightString(right_edge, 0.5 * inch, self._report_date.strftime("%m/%d/%Y %H:%M"))
|
self.drawRightString(right_edge, 0.5 * inch,
|
||||||
|
self._report_date.strftime("%m/%d/%Y %H:%M"))
|
||||||
|
|
||||||
top_line = self.beginPath()
|
top_line = self.beginPath()
|
||||||
top_line.moveTo(0.5 * inch, 0.75 * inch)
|
top_line.moveTo(0.5 * inch, 0.75 * inch)
|
||||||
@@ -76,13 +80,15 @@ def make_doc_template(page_size, filename, document_title,
|
|||||||
title_box, report_box = header_box.split_x(3.5 * inch, direction='r')
|
title_box, report_box = header_box.split_x(3.5 * inch, direction='r')
|
||||||
|
|
||||||
on_page_lambda = (lambda c, _:
|
on_page_lambda = (lambda c, _:
|
||||||
draw_header_footer(c, report_box, title_box,
|
draw_header_footer(c, report_box, title_box,
|
||||||
footer_box,title=title,
|
footer_box, title=title,
|
||||||
supervisor=supervisor,
|
supervisor=supervisor,
|
||||||
document_subheader=document_subheader,
|
document_subheader=document_subheader,
|
||||||
client=client, doc_title=document_header))
|
client=client,
|
||||||
|
doc_title=document_header))
|
||||||
|
|
||||||
frames = [Frame(page_box.min_x, page_box.min_y, page_box.width, page_box.height)]
|
frames = [Frame(page_box.min_x, page_box.min_y,
|
||||||
|
page_box.width, page_box.height)]
|
||||||
|
|
||||||
page_template = PageTemplate(id="Main",
|
page_template = PageTemplate(id="Main",
|
||||||
frames=frames,
|
frames=frames,
|
||||||
@@ -119,12 +125,17 @@ def time_format(mins, zero_str="-"):
|
|||||||
return "%i:%02i" % (hh, mm)
|
return "%i:%02i" % (hh, mm)
|
||||||
|
|
||||||
|
|
||||||
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str,
|
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box,
|
||||||
document_subheader: str, client: str, doc_title="", font_name='Helvetica'):
|
footer_box, title: str, supervisor: str,
|
||||||
|
document_subheader: str, client: str, doc_title="",
|
||||||
|
font_name='Helvetica'):
|
||||||
|
|
||||||
(_supervisor_box, client_box,), title_box = right_box.divide_y([16., 16., ])
|
(_supervisor_box, client_box,), title_box = \
|
||||||
title_box.draw_text_cell(a_canvas, title, font_name, 18, inset_y=2., inset_x=5.)
|
right_box.divide_y([16., 16., ])
|
||||||
client_box.draw_text_cell(a_canvas, client, font_name, 11, inset_y=2., inset_x=5.)
|
title_box.draw_text_cell(a_canvas, title, font_name, 18,
|
||||||
|
inset_y=2., inset_x=5.)
|
||||||
|
client_box.draw_text_cell(a_canvas, client, font_name, 11,
|
||||||
|
inset_y=2., inset_x=5.)
|
||||||
|
|
||||||
a_canvas.saveState()
|
a_canvas.saveState()
|
||||||
a_canvas.setLineWidth(0.5)
|
a_canvas.setLineWidth(0.5)
|
||||||
@@ -139,16 +150,20 @@ def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box,
|
|||||||
a_canvas.drawPath(tline2)
|
a_canvas.drawPath(tline2)
|
||||||
a_canvas.restoreState()
|
a_canvas.restoreState()
|
||||||
|
|
||||||
(doc_title_cell, spotting_version_cell,), _ = left_box.divide_y([18., 14], direction='d')
|
(doc_title_cell, spotting_version_cell,), _ = \
|
||||||
|
left_box.divide_y([18., 14], direction='d')
|
||||||
|
|
||||||
doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14., inset_y=2.)
|
doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14.,
|
||||||
|
inset_y=2.)
|
||||||
|
|
||||||
if document_subheader is not None:
|
if document_subheader is not None:
|
||||||
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, font_name, 12., inset_y=2.)
|
spotting_version_cell.draw_text_cell(a_canvas, document_subheader,
|
||||||
|
font_name, 12., inset_y=2.)
|
||||||
|
|
||||||
if supervisor is not None:
|
if supervisor is not None:
|
||||||
a_canvas.setFont(font_name, 11.)
|
a_canvas.setFont(font_name, 11.)
|
||||||
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, supervisor)
|
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2.,
|
||||||
|
footer_box.min_y, supervisor)
|
||||||
|
|
||||||
|
|
||||||
class GRect:
|
class GRect:
|
||||||
@@ -201,10 +216,12 @@ class GRect:
|
|||||||
else:
|
else:
|
||||||
if direction == 'l':
|
if direction == 'l':
|
||||||
return (GRect(self.min_x, self.min_y, at, self.height),
|
return (GRect(self.min_x, self.min_y, at, self.height),
|
||||||
GRect(self.min_x + at, self.y, self.width - at, self.height))
|
GRect(self.min_x + at, self.y,
|
||||||
|
self.width - at, self.height))
|
||||||
else:
|
else:
|
||||||
return (GRect(self.max_x - at, self.y, at, self.height),
|
return (GRect(self.max_x - at, self.y, at, self.height),
|
||||||
GRect(self.min_x, self.y, self.width - at, self.height))
|
GRect(self.min_x, self.y,
|
||||||
|
self.width - at, self.height))
|
||||||
|
|
||||||
def split_y(self, at, direction='u'):
|
def split_y(self, at, direction='u'):
|
||||||
if at >= self.height:
|
if at >= self.height:
|
||||||
@@ -214,19 +231,23 @@ class GRect:
|
|||||||
else:
|
else:
|
||||||
if direction == 'u':
|
if direction == 'u':
|
||||||
return (GRect(self.x, self.y, self.width, at),
|
return (GRect(self.x, self.y, self.width, at),
|
||||||
GRect(self.x, self.y + at, self.width, self.height - at))
|
GRect(self.x, self.y + at,
|
||||||
|
self.width, self.height - at))
|
||||||
else:
|
else:
|
||||||
return (GRect(self.x, self.max_y - at, self.width, at),
|
return (GRect(self.x, self.max_y - at, self.width, at),
|
||||||
GRect(self.x, self.y, self.width, self.height - at))
|
GRect(self.x, self.y,
|
||||||
|
self.width, self.height - at))
|
||||||
|
|
||||||
def inset_xy(self, dx, dy):
|
def inset_xy(self, dx, dy):
|
||||||
return GRect(self.x + dx, self.y + dy, self.width - dx * 2, self.height - dy * 2)
|
return GRect(self.x + dx, self.y + dy,
|
||||||
|
self.width - dx * 2, self.height - dy * 2)
|
||||||
|
|
||||||
def inset(self, d):
|
def inset(self, d):
|
||||||
return self.inset_xy(d, d)
|
return self.inset_xy(d, d)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<GRect x=%f y=%f width=%f height=%f>" % (self.x, self.y, self.width, self.height)
|
return "<GRect x=%f y=%f width=%f height=%f>" % \
|
||||||
|
(self.x, self.y, self.width, self.height)
|
||||||
|
|
||||||
def divide_x(self, x_list, direction='l'):
|
def divide_x(self, x_list, direction='l'):
|
||||||
ret_list = list()
|
ret_list = list()
|
||||||
@@ -259,13 +280,17 @@ class GRect:
|
|||||||
|
|
||||||
def draw_border_impl(en):
|
def draw_border_impl(en):
|
||||||
if en == 'min_x':
|
if en == 'min_x':
|
||||||
coordinates = ((self.min_x, self.min_y), (self.min_x, self.max_y))
|
coordinates = ((self.min_x, self.min_y),
|
||||||
|
(self.min_x, self.max_y))
|
||||||
elif en == 'max_x':
|
elif en == 'max_x':
|
||||||
coordinates = ((self.max_x, self.min_y), (self.max_x, self.max_y))
|
coordinates = ((self.max_x, self.min_y),
|
||||||
|
(self.max_x, self.max_y))
|
||||||
elif en == 'min_y':
|
elif en == 'min_y':
|
||||||
coordinates = ((self.min_x, self.min_y), (self.max_x, self.min_y))
|
coordinates = ((self.min_x, self.min_y),
|
||||||
|
(self.max_x, self.min_y))
|
||||||
elif en == 'max_y':
|
elif en == 'max_y':
|
||||||
coordinates = ((self.min_x, self.max_y), (self.max_x, self.max_y))
|
coordinates = ((self.min_x, self.max_y),
|
||||||
|
(self.max_x, self.max_y))
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Tuple, List
|
|||||||
from reportlab.lib.pagesizes import portrait, letter
|
from reportlab.lib.pagesizes import portrait, letter
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
from reportlab.lib.units import inch
|
from reportlab.lib.units import inch
|
||||||
from reportlab.platypus import Paragraph, Table, Spacer
|
from reportlab.platypus import Paragraph, Table
|
||||||
|
|
||||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||||
from ptulsconv.pdf import make_doc_template
|
from ptulsconv.pdf import make_doc_template
|
||||||
@@ -12,14 +12,15 @@ from ptulsconv.pdf import make_doc_template
|
|||||||
|
|
||||||
# TODO: A Continuity
|
# TODO: A Continuity
|
||||||
|
|
||||||
def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
|
def table_for_scene(scene, tc_format, font_name='Helvetica'):
|
||||||
scene_style = getSampleStyleSheet()['Normal']
|
scene_style = getSampleStyleSheet()['Normal']
|
||||||
scene_style.fontName = font_name
|
scene_style.fontName = font_name
|
||||||
scene_style.leftIndent = 0.
|
scene_style.leftIndent = 0.
|
||||||
scene_style.leftPadding = 0.
|
scene_style.leftPadding = 0.
|
||||||
scene_style.spaceAfter = 18.
|
scene_style.spaceAfter = 18.
|
||||||
|
|
||||||
tc_data = "<em>%s</em><br />%s" % (tc_format.seconds_to_smpte(scene[2]), tc_format.seconds_to_smpte(scene[3]))
|
tc_data = "<em>%s</em><br />%s" % (tc_format.seconds_to_smpte(scene[2]),
|
||||||
|
tc_format.seconds_to_smpte(scene[3]))
|
||||||
|
|
||||||
row = [
|
row = [
|
||||||
Paragraph(tc_data, scene_style),
|
Paragraph(tc_data, scene_style),
|
||||||
@@ -36,7 +37,7 @@ def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
|
|||||||
|
|
||||||
def output_report(scenes: List[Tuple[str, str, Fraction, Fraction]],
|
def output_report(scenes: List[Tuple[str, str, Fraction, Fraction]],
|
||||||
tc_display_format: TimecodeFormat,
|
tc_display_format: TimecodeFormat,
|
||||||
title: str, client: str, supervisor, paper_size = letter):
|
title: str, client: str, supervisor, paper_size=letter):
|
||||||
filename = "%s Continuity.pdf" % title
|
filename = "%s Continuity.pdf" % title
|
||||||
document_header = "Continuity"
|
document_header = "Continuity"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
# from reportlab.pdfbase import pdfmetrics
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
# from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
from reportlab.lib.units import inch
|
||||||
from reportlab.lib.pagesizes import letter, portrait
|
from reportlab.lib.pagesizes import letter, portrait
|
||||||
@@ -14,9 +14,12 @@ from .__init__ import time_format, make_doc_template
|
|||||||
from ..docparser.adr_entity import ADRLine
|
from ..docparser.adr_entity import ADRLine
|
||||||
|
|
||||||
|
|
||||||
def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_priorities=False, include_omitted=False):
|
def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]],
|
||||||
|
show_priorities=False, include_omitted=False):
|
||||||
columns = list()
|
columns = list()
|
||||||
reel_numbers = reel_list or sorted(set([x.reel for x in lines if x.reel is not None]))
|
reel_numbers = reel_list or sorted(
|
||||||
|
set([x.reel for x in lines if x.reel is not None])
|
||||||
|
)
|
||||||
|
|
||||||
num_column_width = 15. / 32. * inch
|
num_column_width = 15. / 32. * inch
|
||||||
|
|
||||||
@@ -33,7 +36,10 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
|
|||||||
'heading': 'Role',
|
'heading': 'Role',
|
||||||
'value_getter': lambda recs: recs[0].character_name,
|
'value_getter': lambda recs: recs[0].character_name,
|
||||||
'value_getter2': lambda recs: recs[0].actor_name or "",
|
'value_getter2': lambda recs: recs[0].actor_name or "",
|
||||||
'style_getter': lambda col_index: [('LINEAFTER', (col_index, 0), (col_index, -1), 1.0, colors.black)],
|
'style_getter': lambda col_index: [('LINEAFTER',
|
||||||
|
(col_index, 0),
|
||||||
|
(col_index, -1),
|
||||||
|
1.0, colors.black)],
|
||||||
'width': 1.75 * inch,
|
'width': 1.75 * inch,
|
||||||
'summarize': False
|
'summarize': False
|
||||||
})
|
})
|
||||||
@@ -41,30 +47,48 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
|
|||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'TV',
|
'heading': 'TV',
|
||||||
'value_getter': lambda recs: len([r for r in recs if r.tv]),
|
'value_getter': lambda recs: len([r for r in recs if r.tv]),
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
'value_getter2': (lambda recs:
|
||||||
for r in recs if r.tv])),
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'),
|
for r in recs if r.tv]))
|
||||||
('LINEBEFORE', (col_index, 0), (col_index, -1), 1., colors.black),
|
),
|
||||||
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)],
|
'style_getter': (lambda col_index:
|
||||||
|
[('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER'),
|
||||||
|
('LINEBEFORE', (col_index, 0), (col_index, -1),
|
||||||
|
1., colors.black),
|
||||||
|
('LINEAFTER', (col_index, 0), (col_index, -1),
|
||||||
|
.5, colors.gray)]
|
||||||
|
),
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'Opt',
|
'heading': 'Opt',
|
||||||
'value_getter': lambda recs: len([r for r in recs if r.optional]),
|
'value_getter': lambda recs: len([r for r in recs if r.optional]),
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
'value_getter2': (lambda recs:
|
||||||
for r in recs if r.optional])),
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'),
|
for r in recs if r.optional]))
|
||||||
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)],
|
),
|
||||||
|
'style_getter': (lambda col_index:
|
||||||
|
[('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER'),
|
||||||
|
('LINEAFTER', (col_index, 0), (col_index, -1),
|
||||||
|
.5, colors.gray)]
|
||||||
|
),
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'Eff',
|
'heading': 'Eff',
|
||||||
'value_getter': lambda recs: len([r for r in recs if r.effort]),
|
'value_getter': lambda recs: len([r for r in recs if r.effort]),
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
'value_getter2': (lambda recs:
|
||||||
for r in recs if r.effort])),
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
|
for r in recs if r.effort]))
|
||||||
|
),
|
||||||
|
'style_getter': (lambda col_index:
|
||||||
|
[('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER')]
|
||||||
|
),
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -80,23 +104,26 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
|
|||||||
})
|
})
|
||||||
|
|
||||||
if len(reel_numbers) > 0:
|
if len(reel_numbers) > 0:
|
||||||
# columns.append({
|
|
||||||
# 'heading': 'RX',
|
|
||||||
# 'value_getter': lambda recs: blank_len([r for r in recs if 'Reel' not in r.keys()]),
|
|
||||||
# 'value_getter2': lambda recs: time_format(sum([r.get('Time Budget Mins', 0.) for r in recs
|
|
||||||
# if 'Reel' not in r.keys()])),
|
|
||||||
# 'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
|
|
||||||
# 'width': num_column_width
|
|
||||||
# })
|
|
||||||
|
|
||||||
for n in reel_numbers:
|
for n in reel_numbers:
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': n,
|
'heading': n,
|
||||||
'value_getter': lambda recs, n1=n: len([r for r in recs if r.reel == n1]),
|
'value_getter': (lambda recs, n1=n:
|
||||||
'value_getter2': lambda recs, n1=n: time_format(sum([r.time_budget_mins or 0. for r
|
len([r for r in recs if r.reel == n1])
|
||||||
in recs if r.reel == n1])),
|
),
|
||||||
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER'),
|
'value_getter2': (lambda recs, n1=n:
|
||||||
('LINEAFTER', (col_index, 0), (col_index, -1), .5, colors.gray)],
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
|
for r in recs
|
||||||
|
if r.reel == n1]))
|
||||||
|
),
|
||||||
|
'style_getter': (lambda col_index:
|
||||||
|
[('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER'),
|
||||||
|
('LINEAFTER', (col_index, 0),
|
||||||
|
(col_index, -1),
|
||||||
|
.5, colors.gray)]
|
||||||
|
),
|
||||||
|
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,18 +131,26 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
|
|||||||
for n in range(1, 6,):
|
for n in range(1, 6,):
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'P%i' % n,
|
'heading': 'P%i' % n,
|
||||||
'value_getter': lambda recs: len([r for r in recs if r.priority == n]),
|
'value_getter': lambda recs: len([r for r in recs
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
if r.priority == n]),
|
||||||
for r in recs if r.priority == n])),
|
'value_getter2': (lambda recs:
|
||||||
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
|
for r in recs
|
||||||
|
if r.priority == n]))
|
||||||
|
),
|
||||||
'style_getter': lambda col_index: [],
|
'style_getter': lambda col_index: [],
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': '>P5',
|
'heading': '>P5',
|
||||||
'value_getter': lambda recs: len([r for r in recs if (r.priority or 5) > 5]),
|
'value_getter': lambda recs: len([r for r in recs
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
if (r.priority or 5) > 5]),
|
||||||
for r in recs if (r.priority or 5) > 5])),
|
'value_getter2': (lambda recs:
|
||||||
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
|
for r in recs
|
||||||
|
if (r.priority or 5) > 5]))
|
||||||
|
),
|
||||||
'style_getter': lambda col_index: [],
|
'style_getter': lambda col_index: [],
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
@@ -124,32 +159,47 @@ def build_columns(lines: List[ADRLine], reel_list: Optional[List[str]], show_pri
|
|||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'Omit',
|
'heading': 'Omit',
|
||||||
'value_getter': lambda recs: len([r for r in recs if r.omitted]),
|
'value_getter': lambda recs: len([r for r in recs if r.omitted]),
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
'value_getter2': (lambda recs:
|
||||||
for r in recs if r.omitted])),
|
time_format(sum([r.time_budget_mins or 0.
|
||||||
'style_getter': lambda col_index: [('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
|
for r in recs if r.omitted]))),
|
||||||
|
'style_getter': (lambda col_index:
|
||||||
|
[('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER')]
|
||||||
|
),
|
||||||
'width': num_column_width
|
'width': num_column_width
|
||||||
})
|
})
|
||||||
|
|
||||||
columns.append({
|
columns.append({
|
||||||
'heading': 'Total',
|
'heading': 'Total',
|
||||||
'value_getter': lambda recs: len([r for r in recs if not r.omitted]),
|
'value_getter': lambda recs: len([r for r in recs if not r.omitted]),
|
||||||
'value_getter2': lambda recs: time_format(sum([r.time_budget_mins or 0.
|
'value_getter2': (lambda recs:
|
||||||
for r in recs if not r.omitted]), zero_str=None),
|
time_format(
|
||||||
'style_getter': lambda col_index: [('LINEBEFORE', (col_index, 0), (col_index, -1), 1.0, colors.black),
|
sum([r.time_budget_mins or 0.
|
||||||
('ALIGN', (col_index, 0), (col_index, -1), 'CENTER')],
|
|
||||||
|
for r in recs if not r.omitted])
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'style_getter': (lambda col_index:
|
||||||
|
[('LINEBEFORE', (col_index, 0), (col_index, -1),
|
||||||
|
1.0, colors.black),
|
||||||
|
('ALIGN', (col_index, 0), (col_index, -1),
|
||||||
|
'CENTER')]
|
||||||
|
),
|
||||||
'width': 0.5 * inch
|
'width': 0.5 * inch
|
||||||
})
|
})
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size):
|
def populate_columns(lines: List[ADRLine], columns, include_omitted,
|
||||||
|
_page_size):
|
||||||
data = list()
|
data = list()
|
||||||
styles = list()
|
styles = list()
|
||||||
columns_widths = list()
|
columns_widths = list()
|
||||||
|
|
||||||
sorted_character_numbers: List[str] = sorted(set([x.character_id for x in lines]),
|
sorted_character_numbers: List[str] = sorted(
|
||||||
key=lambda x: str(x))
|
set([x.character_id for x in lines]),
|
||||||
|
key=lambda x: str(x))
|
||||||
|
|
||||||
# construct column styles
|
# construct column styles
|
||||||
|
|
||||||
@@ -174,8 +224,10 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
row_data.append(col['value_getter'](list(char_records)))
|
row_data.append(col['value_getter'](list(char_records)))
|
||||||
row_data2.append(col['value_getter2'](list(char_records)))
|
row_data2.append(col['value_getter2'](list(char_records)))
|
||||||
|
|
||||||
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index), colors.red),
|
styles.extend([('TEXTCOLOR', (0, row2_index), (-1, row2_index),
|
||||||
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)])
|
colors.red),
|
||||||
|
('LINEBELOW', (0, row2_index), (-1, row2_index),
|
||||||
|
0.5, colors.black)])
|
||||||
|
|
||||||
data.append(row_data)
|
data.append(row_data)
|
||||||
data.append(row_data2)
|
data.append(row_data2)
|
||||||
@@ -192,7 +244,8 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
summary_row1.append("")
|
summary_row1.append("")
|
||||||
summary_row2.append("")
|
summary_row2.append("")
|
||||||
|
|
||||||
styles.append(('LINEABOVE', (0, row1_index), (-1, row1_index), 2.0, colors.black))
|
styles.append(('LINEABOVE', (0, row1_index), (-1, row1_index), 2.0,
|
||||||
|
colors.black))
|
||||||
|
|
||||||
data.append(summary_row1)
|
data.append(summary_row1)
|
||||||
data.append(summary_row2)
|
data.append(summary_row2)
|
||||||
@@ -204,17 +257,20 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
# pass
|
# pass
|
||||||
|
|
||||||
|
|
||||||
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False,
|
def output_report(lines: List[ADRLine], reel_list: List[str],
|
||||||
page_size=portrait(letter), font_name='Helvetica'):
|
include_omitted=False, page_size=portrait(letter),
|
||||||
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
|
font_name='Helvetica'):
|
||||||
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size)
|
columns = build_columns(lines, include_omitted=include_omitted,
|
||||||
|
reel_list=reel_list)
|
||||||
|
data, style, columns_widths = populate_columns(lines, columns,
|
||||||
|
include_omitted, page_size)
|
||||||
|
|
||||||
style.append(('FONTNAME', (0, 0), (-1, -1), font_name))
|
style.append(('FONTNAME', (0, 0), (-1, -1), font_name))
|
||||||
style.append(('FONTSIZE', (0, 0), (-1, -1), 9.))
|
style.append(('FONTSIZE', (0, 0), (-1, -1), 9.))
|
||||||
style.append(('LINEBELOW', (0, 0), (-1, 0), 1.0, colors.black))
|
style.append(('LINEBELOW', (0, 0), (-1, 0), 1.0, colors.black))
|
||||||
# style.append(('LINEBELOW', (0, 1), (-1, -1), 0.25, colors.gray))
|
# 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
|
title = "%s Line Count" % lines[0].title
|
||||||
filename = title + '.pdf'
|
filename = title + '.pdf'
|
||||||
@@ -226,7 +282,8 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
|
|||||||
document_header='Line Count')
|
document_header='Line Count')
|
||||||
|
|
||||||
# header_data, header_style, header_widths = build_header(columns_widths)
|
# header_data, header_style, header_widths = build_header(columns_widths)
|
||||||
# header_table = Table(data=header_data, style=header_style, colWidths=header_widths)
|
# header_table = Table(data=header_data, style=header_style,
|
||||||
|
# colWidths=header_widths)
|
||||||
|
|
||||||
table = Table(data=data, style=style, colWidths=columns_widths)
|
table = Table(data=data, style=style, colWidths=columns_widths)
|
||||||
|
|
||||||
@@ -241,6 +298,7 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
|
|||||||
omitted_count = len([x for x in lines if x.omitted])
|
omitted_count = len([x for x in lines if x.omitted])
|
||||||
|
|
||||||
if not include_omitted and omitted_count > 0:
|
if not include_omitted and omitted_count > 0:
|
||||||
story.append(Paragraph("* %i Omitted lines are excluded." % omitted_count, style))
|
story.append(Paragraph("* %i Omitted lines are excluded." %
|
||||||
|
omitted_count, style))
|
||||||
|
|
||||||
doc.build(story)
|
doc.build(story)
|
||||||
|
|||||||
@@ -27,23 +27,28 @@ def build_aux_data_field(line: ADRLine):
|
|||||||
tag_field = ""
|
tag_field = ""
|
||||||
if line.effort:
|
if line.effort:
|
||||||
bg_color = 'red'
|
bg_color = 'red'
|
||||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "EFF")
|
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||||
|
% (bg_color, fg_color, "EFF")
|
||||||
elif line.tv:
|
elif line.tv:
|
||||||
bg_color = 'blue'
|
bg_color = 'blue'
|
||||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "TV")
|
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||||
|
% (bg_color, fg_color, "TV")
|
||||||
elif line.adlib:
|
elif line.adlib:
|
||||||
bg_color = 'purple'
|
bg_color = 'purple'
|
||||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " % (bg_color, fg_color, "ADLIB")
|
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font> " \
|
||||||
|
% (bg_color, fg_color, "ADLIB")
|
||||||
elif line.optional:
|
elif line.optional:
|
||||||
bg_color = 'green'
|
bg_color = 'green'
|
||||||
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" % (bg_color, fg_color, "OPTIONAL")
|
tag_field += "<font backColor=%s textColor=%s fontSize=11>%s</font>" \
|
||||||
|
% (bg_color, fg_color, "OPTIONAL")
|
||||||
|
|
||||||
entries.append(tag_field)
|
entries.append(tag_field)
|
||||||
|
|
||||||
return "<br />".join(entries)
|
return "<br />".join(entries)
|
||||||
|
|
||||||
|
|
||||||
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvetica'):
|
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat,
|
||||||
|
font_name='Helvetica'):
|
||||||
story = list()
|
story = list()
|
||||||
|
|
||||||
this_scene = None
|
this_scene = None
|
||||||
@@ -60,7 +65,8 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
|
|||||||
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
||||||
('BOTTOMPADDING', (0, 0), (-1, -1), 24.)]
|
('BOTTOMPADDING', (0, 0), (-1, -1), 24.)]
|
||||||
|
|
||||||
cue_number_field = "%s<br /><font fontSize=7>%s</font>" % (line.cue_number, line.character_name)
|
cue_number_field = "%s<br /><font fontSize=7>%s</font>" \
|
||||||
|
% (line.cue_number, line.character_name)
|
||||||
|
|
||||||
time_data = time_format(line.time_budget_mins)
|
time_data = time_format(line.time_budget_mins)
|
||||||
|
|
||||||
@@ -79,7 +85,8 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
line_table = Table(data=line_table_data,
|
line_table = Table(data=line_table_data,
|
||||||
colWidths=[inch * 0.75, inch, inch * 3., 0.5 * inch, inch * 2.],
|
colWidths=[inch * 0.75, inch, inch * 3., 0.5 * inch,
|
||||||
|
inch * 2.],
|
||||||
style=table_style)
|
style=table_style)
|
||||||
|
|
||||||
if (line.scene or "[No Scene]") != this_scene:
|
if (line.scene or "[No Scene]") != this_scene:
|
||||||
@@ -97,7 +104,7 @@ def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvet
|
|||||||
|
|
||||||
def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
|
def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
|
||||||
tc_data = tc_format.seconds_to_smpte(line.start) + "<br />" + \
|
tc_data = tc_format.seconds_to_smpte(line.start) + "<br />" + \
|
||||||
tc_format.seconds_to_smpte(line.finish)
|
tc_format.seconds_to_smpte(line.finish)
|
||||||
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':
|
||||||
@@ -111,11 +118,12 @@ def build_tc_data(line: ADRLine, tc_format: TimecodeFormat):
|
|||||||
return tc_data
|
return tc_data
|
||||||
|
|
||||||
|
|
||||||
def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat, character_number=None,
|
def generate_report(page_size, lines: List[ADRLine], tc_rate: TimecodeFormat,
|
||||||
include_omitted=True):
|
character_number=None, include_omitted=True):
|
||||||
if character_number is not None:
|
if character_number is not None:
|
||||||
lines = [r for r in lines if r.character_id == character_number]
|
lines = [r for r in lines if r.character_id == character_number]
|
||||||
title = "%s ADR Report (%s)" % (lines[0].title, lines[0].character_name)
|
title = "%s ADR Report (%s)" % (lines[0].title,
|
||||||
|
lines[0].character_name)
|
||||||
document_header = "%s ADR Report" % lines[0].character_name
|
document_header = "%s ADR Report" % lines[0].character_name
|
||||||
else:
|
else:
|
||||||
title = "%s ADR Report" % lines[0].title
|
title = "%s ADR Report" % lines[0].title
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from reportlab.pdfgen.canvas import Canvas
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
# from reportlab.pdfbase import pdfmetrics
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
# from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
from reportlab.lib.units import inch
|
from reportlab.lib.units import inch
|
||||||
from reportlab.lib.pagesizes import letter
|
from reportlab.lib.pagesizes import letter
|
||||||
@@ -11,20 +11,23 @@ from reportlab.platypus import Paragraph
|
|||||||
|
|
||||||
from .__init__ import GRect
|
from .__init__ import GRect
|
||||||
|
|
||||||
from ptulsconv.broadcast_timecode import TimecodeFormat, footage_to_frame_count
|
from ptulsconv.broadcast_timecode import TimecodeFormat
|
||||||
from ptulsconv.docparser.adr_entity import ADRLine
|
from ptulsconv.docparser.adr_entity import ADRLine
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
font_name = 'Helvetica'
|
font_name = 'Helvetica'
|
||||||
|
|
||||||
|
|
||||||
def draw_header_block(canvas, rect, record: ADRLine):
|
def draw_header_block(canvas, rect, record: ADRLine):
|
||||||
rect.draw_text_cell(canvas, record.cue_number, "Helvetica", 44, vertical_align='m')
|
rect.draw_text_cell(canvas, record.cue_number, "Helvetica", 44,
|
||||||
|
vertical_align='m')
|
||||||
|
|
||||||
|
|
||||||
def draw_character_row(canvas, rect, record: ADRLine):
|
def draw_character_row(canvas, rect, record: ADRLine):
|
||||||
label_frame, value_frame = rect.split_x(1.25 * inch)
|
label_frame, value_frame = rect.split_x(1.25 * inch)
|
||||||
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.)
|
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10,
|
||||||
|
force_baseline=9.)
|
||||||
line = "%s / %s" % (record.character_id, record.character_name)
|
line = "%s / %s" % (record.character_id, record.character_name)
|
||||||
if record.actor_name is not None:
|
if record.actor_name is not None:
|
||||||
line = line + " / " + record.actor_name
|
line = line + " / " + record.actor_name
|
||||||
@@ -33,7 +36,8 @@ def draw_character_row(canvas, rect, record: ADRLine):
|
|||||||
|
|
||||||
|
|
||||||
def draw_cue_number_block(canvas, rect, record: ADRLine):
|
def draw_cue_number_block(canvas, rect, record: ADRLine):
|
||||||
(label_frame, number_frame,), aux_frame = rect.divide_y([0.20 * inch, 0.375 * inch], direction='d')
|
(label_frame, number_frame,), aux_frame = \
|
||||||
|
rect.divide_y([0.20 * inch, 0.375 * inch], direction='d')
|
||||||
label_frame.draw_text_cell(canvas, "CUE NUMBER", font_name, 10,
|
label_frame.draw_text_cell(canvas, "CUE NUMBER", font_name, 10,
|
||||||
inset_y=5., vertical_align='t')
|
inset_y=5., vertical_align='t')
|
||||||
number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14,
|
number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14,
|
||||||
@@ -55,18 +59,25 @@ def draw_cue_number_block(canvas, rect, record: ADRLine):
|
|||||||
rect.draw_border(canvas, 'max_x')
|
rect.draw_border(canvas, 'max_x')
|
||||||
|
|
||||||
|
|
||||||
def draw_timecode_block(canvas, rect, record: ADRLine, tc_display_format: TimecodeFormat):
|
def draw_timecode_block(canvas, rect, record: ADRLine,
|
||||||
|
tc_display_format: TimecodeFormat):
|
||||||
(in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y(
|
(in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y(
|
||||||
[0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d')
|
[0.20 * inch, 0.25 * inch, 0.20 * inch, 0.25 * inch], direction='d')
|
||||||
|
|
||||||
in_label_frame.draw_text_cell(canvas, "IN", font_name, 10,
|
in_label_frame.draw_text_cell(canvas, "IN", font_name, 10,
|
||||||
vertical_align='t', inset_y=5., inset_x=5.)
|
vertical_align='t', inset_y=5., inset_x=5.)
|
||||||
in_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.start), font_name, 14,
|
in_frame.draw_text_cell(canvas,
|
||||||
inset_x=10., inset_y=2., draw_baseline=True)
|
tc_display_format.seconds_to_smpte(record.start),
|
||||||
|
font_name, 14,
|
||||||
|
inset_x=10., inset_y=2.,
|
||||||
|
draw_baseline=True)
|
||||||
out_label_frame.draw_text_cell(canvas, "OUT", font_name, 10,
|
out_label_frame.draw_text_cell(canvas, "OUT", font_name, 10,
|
||||||
vertical_align='t', inset_y=5., inset_x=5.)
|
vertical_align='t', inset_y=5., inset_x=5.)
|
||||||
out_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.finish), font_name, 14,
|
out_frame.draw_text_cell(canvas,
|
||||||
inset_x=10., inset_y=2., draw_baseline=True)
|
tc_display_format.seconds_to_smpte(record.finish),
|
||||||
|
font_name, 14,
|
||||||
|
inset_x=10., inset_y=2.,
|
||||||
|
draw_baseline=True)
|
||||||
|
|
||||||
rect.draw_border(canvas, 'max_x')
|
rect.draw_border(canvas, 'max_x')
|
||||||
|
|
||||||
@@ -91,13 +102,15 @@ def draw_reason_block(canvas, rect, record: ADRLine):
|
|||||||
|
|
||||||
p = Paragraph(record.note or "", style)
|
p = Paragraph(record.note or "", style)
|
||||||
|
|
||||||
notes_value.draw_flowable(canvas, p, draw_baselines=True, inset_x=5., inset_y=5.)
|
notes_value.draw_flowable(canvas, p, draw_baselines=True,
|
||||||
|
inset_x=5., inset_y=5.)
|
||||||
|
|
||||||
|
|
||||||
def draw_prompt(canvas, rect, prompt=""):
|
def draw_prompt(canvas, rect, prompt=""):
|
||||||
label, block = rect.split_y(0.20 * inch, direction='d')
|
label, block = rect.split_y(0.20 * inch, direction='d')
|
||||||
|
|
||||||
label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
|
label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t',
|
||||||
|
inset_y=5., inset_x=0.)
|
||||||
|
|
||||||
style = getSampleStyleSheet()['BodyText']
|
style = getSampleStyleSheet()['BodyText']
|
||||||
style.fontName = font_name
|
style.fontName = font_name
|
||||||
@@ -117,7 +130,8 @@ def draw_prompt(canvas, rect, prompt=""):
|
|||||||
def draw_notes(canvas, rect, note=""):
|
def draw_notes(canvas, rect, note=""):
|
||||||
label, block = rect.split_y(0.20 * inch, direction='d')
|
label, block = rect.split_y(0.20 * inch, direction='d')
|
||||||
|
|
||||||
label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
|
label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t',
|
||||||
|
inset_y=5., inset_x=0.)
|
||||||
|
|
||||||
style = getSampleStyleSheet()['BodyText']
|
style = getSampleStyleSheet()['BodyText']
|
||||||
style.fontName = font_name
|
style.fontName = font_name
|
||||||
@@ -169,31 +183,43 @@ def draw_take_grid(canvas, rect):
|
|||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
|
|
||||||
def draw_aux_block(canvas, rect, recording_time_sec_this_line, recording_time_sec):
|
def draw_aux_block(canvas, rect, recording_time_sec_this_line,
|
||||||
|
recording_time_sec):
|
||||||
rect.draw_border(canvas, 'min_x')
|
rect.draw_border(canvas, 'min_x')
|
||||||
|
|
||||||
content_rect = rect.inset_xy(10., 10.)
|
content_rect = rect.inset_xy(10., 10.)
|
||||||
lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.], direction='d')
|
lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.],
|
||||||
|
direction='d')
|
||||||
|
|
||||||
lines[0].draw_text_cell(canvas,
|
lines[0].draw_text_cell(canvas,
|
||||||
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), font_name, 9.)
|
"Time for this line: %.1f mins" %
|
||||||
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), font_name, 9.)
|
(recording_time_sec_this_line / 60.),
|
||||||
lines[2].draw_text_cell(canvas, "Actual Start: ______________", font_name, 9., vertical_align='b')
|
font_name, 9.)
|
||||||
lines[3].draw_text_cell(canvas, "Record Date: ______________", font_name, 9., vertical_align='b')
|
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" %
|
||||||
lines[4].draw_text_cell(canvas, "Engineer: ______________", font_name, 9., vertical_align='b')
|
(recording_time_sec / 60.), font_name, 9.)
|
||||||
lines[5].draw_text_cell(canvas, "Location: ______________", font_name, 9., vertical_align='b')
|
lines[2].draw_text_cell(canvas, "Actual Start: ______________",
|
||||||
|
font_name, 9., vertical_align='b')
|
||||||
|
lines[3].draw_text_cell(canvas, "Record Date: ______________",
|
||||||
|
font_name, 9., vertical_align='b')
|
||||||
|
lines[4].draw_text_cell(canvas, "Engineer: ______________",
|
||||||
|
font_name, 9., vertical_align='b')
|
||||||
|
lines[5].draw_text_cell(canvas, "Location: ______________",
|
||||||
|
font_name, 9., vertical_align='b')
|
||||||
|
|
||||||
|
|
||||||
def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines):
|
def draw_footer(canvas, rect, record: ADRLine, report_date, line_no,
|
||||||
|
total_lines):
|
||||||
rect.draw_border(canvas, 'max_y')
|
rect.draw_border(canvas, 'max_y')
|
||||||
report_date_s = [report_date.strftime("%c")]
|
report_date_s = [report_date.strftime("%c")]
|
||||||
spotting_name = [record.spot] if record.spot is not None else []
|
spotting_name = [record.spot] if record.spot is not None else []
|
||||||
pages_s = ["Line %i of %i" % (line_no, total_lines)]
|
pages_s = ["Line %i of %i" % (line_no, total_lines)]
|
||||||
footer_s = " - ".join(report_date_s + spotting_name + pages_s)
|
footer_s = " - ".join(report_date_s + spotting_name + pages_s)
|
||||||
rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10., inset_y=2.)
|
rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10.,
|
||||||
|
inset_y=2.)
|
||||||
|
|
||||||
|
|
||||||
def create_report_for_character(records, report_date, tc_display_format: TimecodeFormat):
|
def create_report_for_character(records, report_date,
|
||||||
|
tc_display_format: TimecodeFormat):
|
||||||
|
|
||||||
outfile = "%s_%s_%s_Log.pdf" % (records[0].title,
|
outfile = "%s_%s_%s_Log.pdf" % (records[0].title,
|
||||||
records[0].character_id,
|
records[0].character_id,
|
||||||
@@ -201,20 +227,24 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
|
|||||||
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!"
|
||||||
|
|
||||||
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
|
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
|
||||||
|
|
||||||
page: GRect = GRect(0, 0, letter[0], letter[1])
|
page: GRect = GRect(0, 0, letter[0], letter[1])
|
||||||
page = page.inset(inch * 0.5)
|
page = page.inset(inch * 0.5)
|
||||||
(header_row, char_row, data_row, prompt_row, notes_row, takes_row), footer = \
|
(header_row, char_row, data_row,
|
||||||
page.divide_y([0.875 * inch, 0.375 * inch, inch, 3.0 * inch, 1.5 * inch, 3 * inch], direction='d')
|
prompt_row, notes_row, takes_row), footer = \
|
||||||
|
page.divide_y([0.875 * inch, 0.375 * inch, inch,
|
||||||
|
3.0 * inch, 1.5 * inch, 3 * inch], direction='d')
|
||||||
|
|
||||||
cue_header_block, title_header_block = header_row.split_x(4.0 * inch)
|
cue_header_block, title_header_block = header_row.split_x(4.0 * inch)
|
||||||
(cue_number_block, timecode_block), reason_block = data_row.divide_x([1.5 * inch, 1.5 * inch])
|
(cue_number_block, timecode_block), reason_block = \
|
||||||
|
data_row.divide_x([1.5 * inch, 1.5 * inch])
|
||||||
(take_grid_block), aux_block = takes_row.split_x(5.25 * inch)
|
(take_grid_block), aux_block = takes_row.split_x(5.25 * inch)
|
||||||
|
|
||||||
c = Canvas(outfile, pagesize=letter,)
|
c = Canvas(outfile, pagesize=letter,)
|
||||||
|
|
||||||
c.setTitle("%s %s (%s) Supervisor's Log" % (records[0].title, records[0].character_name,
|
c.setTitle("%s %s (%s) Supervisor's Log" % (records[0].title,
|
||||||
|
records[0].character_name,
|
||||||
records[0].character_id))
|
records[0].character_id))
|
||||||
c.setAuthor(records[0].supervisor)
|
c.setAuthor(records[0].supervisor)
|
||||||
|
|
||||||
@@ -223,7 +253,8 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
|
|||||||
line_n = 1
|
line_n = 1
|
||||||
for record in records:
|
for record in records:
|
||||||
record: ADRLine
|
record: ADRLine
|
||||||
recording_time_sec_this_line: float = (record.time_budget_mins or 6.0) * 60.0
|
recording_time_sec_this_line: float = (
|
||||||
|
record.time_budget_mins or 6.0) * 60.0
|
||||||
recording_time_sec = recording_time_sec + recording_time_sec_this_line
|
recording_time_sec = recording_time_sec + recording_time_sec_this_line
|
||||||
|
|
||||||
draw_header_block(c, cue_header_block, record)
|
draw_header_block(c, cue_header_block, record)
|
||||||
@@ -233,14 +264,17 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
|
|||||||
# draw_title_box(c, title_header_block, record)
|
# draw_title_box(c, title_header_block, record)
|
||||||
draw_character_row(c, char_row, record)
|
draw_character_row(c, char_row, record)
|
||||||
draw_cue_number_block(c, cue_number_block, record)
|
draw_cue_number_block(c, cue_number_block, record)
|
||||||
draw_timecode_block(c, timecode_block, record, tc_display_format=tc_display_format)
|
draw_timecode_block(c, timecode_block, record,
|
||||||
|
tc_display_format=tc_display_format)
|
||||||
draw_reason_block(c, reason_block, record)
|
draw_reason_block(c, reason_block, record)
|
||||||
draw_prompt(c, prompt_row, prompt=record.prompt)
|
draw_prompt(c, prompt_row, prompt=record.prompt or "")
|
||||||
draw_notes(c, notes_row, note="")
|
draw_notes(c, notes_row, note="")
|
||||||
draw_take_grid(c, take_grid_block)
|
draw_take_grid(c, take_grid_block)
|
||||||
draw_aux_block(c, aux_block, recording_time_sec_this_line, recording_time_sec)
|
draw_aux_block(c, aux_block, recording_time_sec_this_line,
|
||||||
|
recording_time_sec)
|
||||||
|
|
||||||
draw_footer(c, footer, record, report_date, line_no=line_n, total_lines=total_lines)
|
draw_footer(c, footer, record, report_date, line_no=line_n,
|
||||||
|
total_lines=total_lines)
|
||||||
line_n = line_n + 1
|
line_n = line_n + 1
|
||||||
|
|
||||||
c.showPage()
|
c.showPage()
|
||||||
@@ -254,5 +288,6 @@ def output_report(lines, tc_display_format: TimecodeFormat):
|
|||||||
character_numbers = set([x.character_id for x in lines])
|
character_numbers = set([x.character_id for x in lines])
|
||||||
|
|
||||||
for n in character_numbers:
|
for n in character_numbers:
|
||||||
create_report_for_character([e for e in events if e.character_id == n], report_date,
|
create_report_for_character([e for e in events if e.character_id == n],
|
||||||
|
report_date,
|
||||||
tc_display_format=tc_display_format)
|
tc_display_format=tc_display_format)
|
||||||
|
|||||||
@@ -5,36 +5,42 @@ from .__init__ import make_doc_template
|
|||||||
from reportlab.lib.units import inch
|
from reportlab.lib.units import inch
|
||||||
from reportlab.lib.pagesizes import letter
|
from reportlab.lib.pagesizes import letter
|
||||||
|
|
||||||
from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table, HRFlowable
|
from reportlab.platypus import Paragraph, Spacer, KeepTogether, Table,\
|
||||||
|
HRFlowable
|
||||||
from reportlab.lib.styles import getSampleStyleSheet
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
# from reportlab.pdfbase import pdfmetrics
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
# from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
from ..broadcast_timecode import TimecodeFormat
|
from ..broadcast_timecode import TimecodeFormat
|
||||||
from ..docparser.adr_entity import ADRLine
|
from ..docparser.adr_entity import ADRLine
|
||||||
|
|
||||||
|
|
||||||
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat, font_name="Helvetica"):
|
def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
||||||
|
font_name="Helvetica"):
|
||||||
character_numbers = set([n.character_id for n in lines])
|
character_numbers = set([n.character_id for n in lines])
|
||||||
#pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
|
# pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
|
||||||
|
|
||||||
for n in character_numbers:
|
for n in character_numbers:
|
||||||
char_lines = [line for line in lines if not line.omitted and line.character_id == n]
|
char_lines = [line for line in lines
|
||||||
|
if not line.omitted and line.character_id == n]
|
||||||
character_name = char_lines[0].character_name
|
character_name = char_lines[0].character_name
|
||||||
|
|
||||||
char_lines = sorted(char_lines, key=lambda line: line.start)
|
char_lines = sorted(char_lines, key=lambda line: line.start)
|
||||||
|
|
||||||
title = "%s (%s) %s ADR Script" % (char_lines[0].title, character_name, n)
|
title = "%s (%s) %s ADR Script" % (char_lines[0].title,
|
||||||
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
|
character_name, n)
|
||||||
|
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title,
|
||||||
|
n, character_name)
|
||||||
|
|
||||||
doc = make_doc_template(page_size=letter, filename=filename, document_title=title,
|
doc = make_doc_template(page_size=letter, filename=filename,
|
||||||
|
document_title=title,
|
||||||
title=char_lines[0].title,
|
title=char_lines[0].title,
|
||||||
document_subheader=char_lines[0].spot,
|
document_subheader=char_lines[0].spot or "",
|
||||||
supervisor=char_lines[0].supervisor,
|
supervisor=char_lines[0].supervisor or "",
|
||||||
client=char_lines[0].client,
|
client=char_lines[0].client or "",
|
||||||
document_header=character_name)
|
document_header=character_name or "")
|
||||||
|
|
||||||
story = []
|
story = []
|
||||||
|
|
||||||
@@ -58,7 +64,8 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat, font_
|
|||||||
start_tc = tc_display_format.seconds_to_smpte(line.start)
|
start_tc = tc_display_format.seconds_to_smpte(line.start)
|
||||||
finish_tc = tc_display_format.seconds_to_smpte(line.finish)
|
finish_tc = tc_display_format.seconds_to_smpte(line.finish)
|
||||||
data_block = [[Paragraph(line.cue_number, number_style),
|
data_block = [[Paragraph(line.cue_number, number_style),
|
||||||
Paragraph(start_tc + " - " + finish_tc, number_style)
|
Paragraph(start_tc + " - " + finish_tc,
|
||||||
|
number_style)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
# RIGHTWARDS ARROW →
|
# RIGHTWARDS ARROW →
|
||||||
|
|||||||
@@ -35,13 +35,15 @@ def print_warning(warning_string):
|
|||||||
sys.stderr.write(" - %s\n" % warning_string)
|
sys.stderr.write(" - %s\n" % warning_string)
|
||||||
|
|
||||||
|
|
||||||
def print_advisory_tagging_error(failed_string, position, parent_track_name=None, clip_time=None):
|
def print_advisory_tagging_error(failed_string, position,
|
||||||
|
parent_track_name=None, clip_time=None):
|
||||||
if sys.stderr.isatty():
|
if sys.stderr.isatty():
|
||||||
sys.stderr.write("\n")
|
sys.stderr.write("\n")
|
||||||
sys.stderr.write(" ! \033[33;1mTagging error: \033[0m")
|
sys.stderr.write(" ! \033[33;1mTagging error: \033[0m")
|
||||||
ok_string = failed_string[:position]
|
ok_string = failed_string[:position]
|
||||||
not_ok_string = failed_string[position:]
|
not_ok_string = failed_string[position:]
|
||||||
sys.stderr.write("\033[32m\"%s\033[31;1m%s\"\033[0m\n" % (ok_string, not_ok_string))
|
sys.stderr.write("\033[32m\"%s\033[31;1m%s\"\033[0m\n" %
|
||||||
|
(ok_string, not_ok_string))
|
||||||
|
|
||||||
if parent_track_name is not None:
|
if parent_track_name is not None:
|
||||||
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)
|
sys.stderr.write(" ! > On track \"%s\"\n" % parent_track_name)
|
||||||
|
|||||||
@@ -14,15 +14,20 @@ class ValidationError:
|
|||||||
|
|
||||||
def report_message(self):
|
def report_message(self):
|
||||||
if self.event is not None:
|
if self.event is not None:
|
||||||
return f"{self.message}: event at {self.event.start} with number {self.event.cue_number}"
|
return (f"{self.message}: event at {self.event.start} with number"
|
||||||
|
"{self.event.cue_number}")
|
||||||
else:
|
else:
|
||||||
return self.message
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
def validate_unique_count(input_lines: Iterator[ADRLine], field='title', count=1):
|
def validate_unique_count(input_lines: Iterator[ADRLine], field='title',
|
||||||
|
count=1):
|
||||||
values = set(list(map(lambda e: getattr(e, field), input_lines)))
|
values = set(list(map(lambda e: getattr(e, field), input_lines)))
|
||||||
if len(values) > count:
|
if len(values) > count:
|
||||||
yield ValidationError(message="Field {} has too many values (max={}): {}".format(field, count, values))
|
yield ValidationError(
|
||||||
|
message="Field {} has too many values (max={}): {}"
|
||||||
|
.format(field, count, values)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
|
def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
|
||||||
@@ -33,7 +38,8 @@ def validate_value(input_lines: Iterator[ADRLine], key_field, predicate):
|
|||||||
event=event)
|
event=event)
|
||||||
|
|
||||||
|
|
||||||
def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number', scope=None):
|
def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number',
|
||||||
|
scope=None):
|
||||||
values = dict()
|
values = dict()
|
||||||
for event in input_lines:
|
for event in input_lines:
|
||||||
this = getattr(event, field)
|
this = getattr(event, field)
|
||||||
@@ -44,26 +50,31 @@ def validate_unique_field(input_lines: Iterator[ADRLine], field='cue_number', sc
|
|||||||
|
|
||||||
values.setdefault(key, set())
|
values.setdefault(key, set())
|
||||||
if this in values[key]:
|
if this in values[key]:
|
||||||
yield ValidationError(message='Re-used {}'.format(field), event=event)
|
yield ValidationError(message='Re-used {}'.format(field),
|
||||||
|
event=event)
|
||||||
else:
|
else:
|
||||||
values[key].update(this)
|
values[key].update(this)
|
||||||
|
|
||||||
|
|
||||||
def validate_non_empty_field(input_lines: Iterator[ADRLine], field='cue_number'):
|
def validate_non_empty_field(input_lines: Iterator[ADRLine],
|
||||||
|
field='cue_number'):
|
||||||
for event in input_lines:
|
for event in input_lines:
|
||||||
if getattr(event, field, None) is None:
|
if getattr(event, field, None) is None:
|
||||||
yield ValidationError(message='Empty field {}'.format(field), event=event)
|
yield ValidationError(message='Empty field {}'.format(field),
|
||||||
|
event=event)
|
||||||
|
|
||||||
|
|
||||||
def validate_dependent_value(input_lines: Iterator[ADRLine], key_field, dependent_field):
|
def validate_dependent_value(input_lines: Iterator[ADRLine], key_field,
|
||||||
|
dependent_field):
|
||||||
"""
|
"""
|
||||||
Validates that two events with the same value in `key_field` always have the
|
Validates that two events with the same value in `key_field` always have
|
||||||
same value in `dependent_field`
|
the same value in `dependent_field`
|
||||||
"""
|
"""
|
||||||
key_values = set((getattr(x, key_field) for x in input_lines))
|
key_values = set((getattr(x, key_field) for x in input_lines))
|
||||||
|
|
||||||
for key_value in key_values:
|
for key_value in key_values:
|
||||||
rows = [(getattr(x, key_field), getattr(x, dependent_field)) for x in input_lines
|
rows = [(getattr(x, key_field), getattr(x, dependent_field))
|
||||||
|
for x in input_lines
|
||||||
if getattr(x, key_field) == key_value]
|
if getattr(x, key_field) == key_value]
|
||||||
unique_rows = set(rows)
|
unique_rows = set(rows)
|
||||||
if len(unique_rows) > 1:
|
if len(unique_rows) > 1:
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import ptulsconv
|
|||||||
from ptulsconv.docparser.adr_entity import ADRLine
|
from ptulsconv.docparser.adr_entity import ADRLine
|
||||||
|
|
||||||
# TODO Get a third-party test for Avid Marker lists
|
# TODO Get a third-party test for Avid Marker lists
|
||||||
def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(), reel_start_frame=0, fps=24):
|
|
||||||
|
|
||||||
|
def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
|
||||||
|
reel_start_frame=0, fps=24):
|
||||||
doc = TreeBuilder(element_factory=None)
|
doc = TreeBuilder(element_factory=None)
|
||||||
|
|
||||||
doc.start('Avid:StreamItems', {'xmlns:Avid': 'http://www.avid.com'})
|
doc.start('Avid:StreamItems', {'xmlns:Avid': 'http://www.avid.com'})
|
||||||
@@ -48,26 +51,35 @@ def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
|
|||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
doc.start('AvClass', {'id': 'ATTR'})
|
doc.start('AvClass', {'id': 'ATTR'})
|
||||||
doc.start('AvProp', {'id': 'ATTR', 'name': '__OMFI:ATTR:NumItems', 'type': 'int32'})
|
doc.start('AvProp', {'id': 'ATTR',
|
||||||
|
'name': '__OMFI:ATTR:NumItems',
|
||||||
|
'type': 'int32'})
|
||||||
doc.data('7')
|
doc.data('7')
|
||||||
doc.end('AvProp')
|
doc.end('AvProp')
|
||||||
|
|
||||||
doc.start('List', {'id': 'OMFI:ATTR:AttrRefs'})
|
doc.start('List', {'id': 'OMFI:ATTR:AttrRefs'})
|
||||||
|
|
||||||
insert_elem('1', 'OMFI:ATTB:IntAttribute', 'int32', '_ATN_CRM_LONG_CREATE_DATE', report_date.strftime("%s"))
|
insert_elem('1', 'OMFI:ATTB:IntAttribute', 'int32',
|
||||||
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_COLOR', 'yellow')
|
'_ATN_CRM_LONG_CREATE_DATE', report_date.strftime("%s"))
|
||||||
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_USER', line.supervisor or "")
|
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||||
|
'_ATN_CRM_COLOR', 'yellow')
|
||||||
|
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||||
|
'_ATN_CRM_USER', line.supervisor or "")
|
||||||
|
|
||||||
marker_name = "%s: %s" % (line.cue_number, line.prompt)
|
marker_name = "%s: %s" % (line.cue_number, line.prompt)
|
||||||
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string', '_ATN_CRM_COM', marker_name)
|
insert_elem('2', 'OMFI:ATTB:StringAttribute', 'string',
|
||||||
|
'_ATN_CRM_COM', marker_name)
|
||||||
|
|
||||||
start_frame = int(line.start * fps)
|
start_frame = int(line.start * fps)
|
||||||
|
|
||||||
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string', '_ATN_CRM_TC',
|
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string',
|
||||||
|
'_ATN_CRM_TC',
|
||||||
str(start_frame - reel_start_frame))
|
str(start_frame - reel_start_frame))
|
||||||
|
|
||||||
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string', '_ATN_CRM_TRK', 'V1')
|
insert_elem('2', "OMFI:ATTB:StringAttribute", 'string',
|
||||||
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32', '_ATN_CRM_LENGTH', '1')
|
'_ATN_CRM_TRK', 'V1')
|
||||||
|
insert_elem('1', "OMFI:ATTB:IntAttribute", 'int32',
|
||||||
|
'_ATN_CRM_LENGTH', '1')
|
||||||
|
|
||||||
doc.start('ListElem', {})
|
doc.start('ListElem', {})
|
||||||
doc.end('ListElem')
|
doc.end('ListElem')
|
||||||
@@ -82,17 +94,22 @@ def avid_marker_list(lines: List[ADRLine], report_date=datetime.datetime.now(),
|
|||||||
def dump_fmpxml(data, input_file_name, output, adr_field_map):
|
def dump_fmpxml(data, input_file_name, output, adr_field_map):
|
||||||
doc = TreeBuilder(element_factory=None)
|
doc = TreeBuilder(element_factory=None)
|
||||||
|
|
||||||
doc.start('FMPXMLRESULT', {'xmlns': 'http://www.filemaker.com/fmpxmlresult'})
|
doc.start('FMPXMLRESULT', {'xmlns':
|
||||||
|
'http://www.filemaker.com/fmpxmlresult'})
|
||||||
|
|
||||||
doc.start('ERRORCODE', {})
|
doc.start('ERRORCODE', {})
|
||||||
doc.data('0')
|
doc.data('0')
|
||||||
doc.end('ERRORCODE')
|
doc.end('ERRORCODE')
|
||||||
|
|
||||||
doc.start('PRODUCT', {'NAME': ptulsconv.__name__, 'VERSION': ptulsconv.__version__})
|
doc.start('PRODUCT', {'NAME': ptulsconv.__name__,
|
||||||
|
'VERSION': ptulsconv.__version__})
|
||||||
doc.end('PRODUCT')
|
doc.end('PRODUCT')
|
||||||
|
|
||||||
doc.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy', 'LAYOUT': 'summary', 'TIMEFORMAT': 'hh:mm:ss',
|
doc.start('DATABASE', {'DATEFORMAT': 'MM/dd/yy',
|
||||||
'RECORDS': str(len(data['events'])), 'NAME': os.path.basename(input_file_name)})
|
'LAYOUT': 'summary',
|
||||||
|
'TIMEFORMAT': 'hh:mm:ss',
|
||||||
|
'RECORDS': str(len(data['events'])),
|
||||||
|
'NAME': os.path.basename(input_file_name)})
|
||||||
doc.end('DATABASE')
|
doc.end('DATABASE')
|
||||||
|
|
||||||
doc.start('METADATA', {})
|
doc.start('METADATA', {})
|
||||||
@@ -102,7 +119,8 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
|
|||||||
if tp is int or tp is float:
|
if tp is int or tp is float:
|
||||||
ft = 'NUMBER'
|
ft = 'NUMBER'
|
||||||
|
|
||||||
doc.start('FIELD', {'EMPTYOK': 'YES', 'MAXREPEAT': '1', 'NAME': field[1], 'TYPE': ft})
|
doc.start('FIELD', {'EMPTYOK': 'YES', 'MAXREPEAT': '1',
|
||||||
|
'NAME': field[1], 'TYPE': ft})
|
||||||
doc.end('FIELD')
|
doc.end('FIELD')
|
||||||
doc.end('METADATA')
|
doc.end('METADATA')
|
||||||
|
|
||||||
@@ -157,7 +175,8 @@ def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
|
|||||||
|
|
||||||
print_status_style("Running xsltproc")
|
print_status_style("Running xsltproc")
|
||||||
|
|
||||||
xsl_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt', xsl_name + ".xsl")
|
xsl_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt',
|
||||||
|
xsl_name + ".xsl")
|
||||||
print_status_style("Using xsl: %s" % xsl_path)
|
print_status_style("Using xsl: %s" % xsl_path)
|
||||||
subprocess.run(['xsltproc', xsl_path, '-'],
|
subprocess.run(['xsltproc', xsl_path, '-'],
|
||||||
input=str_data, text=True,
|
input=str_data, text=True,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ classifiers = [
|
|||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Topic :: Multimedia',
|
'Topic :: Multimedia',
|
||||||
'Topic :: Multimedia :: Sound/Audio',
|
'Topic :: Multimedia :: Sound/Audio',
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
@@ -21,11 +20,16 @@ classifiers = [
|
|||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Topic :: Text Processing :: Filters"
|
"Topic :: Text Processing :: Filters"
|
||||||
]
|
]
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.8"
|
||||||
dynamic = ["version", "description"]
|
dynamic = ["version", "description"]
|
||||||
keywords = ["text-processing", "parsers", "film",
|
keywords = ["text-processing", "parsers", "film",
|
||||||
"broadcast", "editing", "editorial"]
|
"broadcast", "editing", "editorial"]
|
||||||
dependencies = ['parsimonious', 'tqdm', 'reportlab']
|
dependencies = [
|
||||||
|
'parsimonious',
|
||||||
|
'tqdm',
|
||||||
|
'reportlab',
|
||||||
|
'py-ptsl >= 101.1.0'
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
doc = [
|
doc = [
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ 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):
|
def test_header_export(self):
|
||||||
|
with open(self.path,"r") as file:
|
||||||
session = parse_document(self.path)
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertIsNotNone(session.header)
|
self.assertIsNotNone(session.header)
|
||||||
self.assertEqual(session.header.session_name, 'Robin Hood Spotting')
|
self.assertEqual(session.header.session_name, 'Robin Hood Spotting')
|
||||||
@@ -19,7 +19,8 @@ class TestRobinHood1(unittest.TestCase):
|
|||||||
|
|
||||||
def test_all_sections(self):
|
def test_all_sections(self):
|
||||||
|
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertIsNotNone(session.header)
|
self.assertIsNotNone(session.header)
|
||||||
self.assertIsNotNone(session.files)
|
self.assertIsNotNone(session.files)
|
||||||
@@ -30,7 +31,8 @@ class TestRobinHood1(unittest.TestCase):
|
|||||||
|
|
||||||
def test_tracks(self):
|
def test_tracks(self):
|
||||||
|
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertEqual(len(session.tracks), 14)
|
self.assertEqual(len(session.tracks), 14)
|
||||||
self.assertListEqual(["Scenes", "Robin", "Will", "Marian", "John",
|
self.assertListEqual(["Scenes", "Robin", "Will", "Marian", "John",
|
||||||
@@ -54,7 +56,10 @@ class TestRobinHood1(unittest.TestCase):
|
|||||||
list(map(lambda t: t.comments, session.tracks)))
|
list(map(lambda t: t.comments, session.tracks)))
|
||||||
|
|
||||||
def test_a_track(self):
|
def test_a_track(self):
|
||||||
session = parse_document(self.path)
|
|
||||||
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
guy_track = session.tracks[5]
|
guy_track = session.tracks[5]
|
||||||
self.assertEqual(guy_track.name, 'Guy')
|
self.assertEqual(guy_track.name, 'Guy')
|
||||||
self.assertEqual(guy_track.comments, '[ADR] {Actor=Basil Rathbone} $CN=5')
|
self.assertEqual(guy_track.comments, '[ADR] {Actor=Basil Rathbone} $CN=5')
|
||||||
@@ -71,7 +76,8 @@ class TestRobinHood1(unittest.TestCase):
|
|||||||
self.assertEqual(guy_track.clips[5].state, 'Unmuted')
|
self.assertEqual(guy_track.clips[5].state, 'Unmuted')
|
||||||
|
|
||||||
def test_memory_locations(self):
|
def test_memory_locations(self):
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertEqual(len(session.markers), 1)
|
self.assertEqual(len(session.markers), 1)
|
||||||
self.assertEqual(session.markers[0].number, 1)
|
self.assertEqual(session.markers[0].number, 1)
|
||||||
|
|||||||
@@ -7,23 +7,30 @@ 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):
|
def test_skipped_segments(self):
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertIsNone(session.files)
|
self.assertIsNone(session.files)
|
||||||
self.assertIsNone(session.clips)
|
self.assertIsNone(session.clips)
|
||||||
|
|
||||||
def test_plugins(self):
|
def test_plugins(self):
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertEqual(len(session.plugins), 2)
|
self.assertEqual(len(session.plugins), 2)
|
||||||
|
|
||||||
def test_stereo_track(self):
|
def test_stereo_track(self):
|
||||||
session = parse_document(self.path)
|
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(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[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[1].clip_name, 'RobinHood.1-01.R')
|
||||||
|
|
||||||
def test_a_track(self):
|
def test_a_track(self):
|
||||||
session = parse_document(self.path)
|
with open(self.path,"r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
guy_track = session.tracks[8]
|
guy_track = session.tracks[8]
|
||||||
self.assertEqual(guy_track.name, 'Guy')
|
self.assertEqual(guy_track.name, 'Guy')
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ 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):
|
def test_a_track(self):
|
||||||
session = parse_document(self.path)
|
with open(self.path, "r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
|
|
||||||
marian_track = session.tracks[6]
|
marian_track = session.tracks[6]
|
||||||
self.assertEqual(marian_track.name, 'Marian')
|
self.assertEqual(marian_track.name, 'Marian')
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ 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):
|
def test_header_export_df(self):
|
||||||
session = parse_document(self.path)
|
|
||||||
|
with open(self.path, "r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
self.assertEqual(session.header.timecode_drop_frame, True)
|
self.assertEqual(session.header.timecode_drop_frame, True)
|
||||||
|
|
||||||
def test_a_track(self):
|
def test_a_track(self):
|
||||||
session = parse_document(self.path)
|
|
||||||
|
with open(self.path, "r") as file:
|
||||||
|
session = parse_document(file.read())
|
||||||
|
|
||||||
guy_track = session.tracks[4]
|
guy_track = session.tracks[4]
|
||||||
self.assertEqual(guy_track.name, 'Robin')
|
self.assertEqual(guy_track.name, 'Robin')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class TestPDFExport(unittest.TestCase):
|
|||||||
tempdir = tempfile.TemporaryDirectory()
|
tempdir = tempfile.TemporaryDirectory()
|
||||||
os.chdir(tempdir.name)
|
os.chdir(tempdir.name)
|
||||||
try:
|
try:
|
||||||
commands.convert(path, major_mode='doc')
|
commands.convert(input_file=path, major_mode='doc')
|
||||||
except:
|
except:
|
||||||
assert False, "Error processing file %s" % path
|
assert False, "Error processing file %s" % path
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
Reference in New Issue
Block a user