mirror of
https://github.com/iluvcapra/ptulsconv.git
synced 2026-01-01 09:20:49 +00:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a176d3b1f5 | ||
|
|
8a6f5e755b | ||
|
|
b4fef4b13f | ||
|
|
6fc7f26e9c | ||
|
|
09b3f9349b | ||
|
|
f6ee807ede | ||
|
|
f114012d4a | ||
|
|
c03b3dfb8d | ||
|
|
d2da8f1cb0 | ||
|
|
10c0e4f038 | ||
|
|
6703184f8f | ||
|
|
1c11e4d570 | ||
|
|
94317c288f | ||
|
|
9e374df367 | ||
|
|
fc2e823116 | ||
|
|
fbc7531374 | ||
|
|
1fb17b13ea | ||
|
|
21c32e282c | ||
|
|
8407d31333 | ||
|
|
97d6eeda02 | ||
|
|
3bee7a8391 | ||
|
|
68d38f8ed5 | ||
|
|
8e043b7175 | ||
|
|
a7b5adfffb | ||
|
|
da5b743191 | ||
|
|
caa5381306 | ||
|
|
9e2b932cad | ||
|
|
05ea48078f | ||
|
|
c26fa8dd75 | ||
|
|
9f8e3cf824 | ||
|
|
3b438b1399 | ||
|
|
41b1a3185f | ||
|
|
8877982a47 | ||
|
|
bb6fbcfd37 | ||
|
|
434b8816ee | ||
|
|
5ebaf6b473 | ||
|
|
d0f415b38f | ||
|
|
c5d6d82831 | ||
|
|
66a71283d5 | ||
|
|
15ad328edc | ||
|
|
a48eccb0d0 | ||
|
|
fa2cef35b2 | ||
|
|
c8053f65ae | ||
|
|
d9da7317a7 | ||
|
|
ab614cbc32 | ||
|
|
5a75a77f77 | ||
|
|
4daa5f0496 | ||
|
|
de48bcfe24 |
4
.github/workflows/python-package.yml
vendored
4
.github/workflows/python-package.yml
vendored
@@ -19,9 +19,9 @@ jobs:
|
|||||||
python-version: [3.7, 3.8, 3.9, "3.10"]
|
python-version: [3.7, 3.8, 3.9, "3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2.5.0
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4.3.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
8
.github/workflows/pythonpublish.yml
vendored
8
.github/workflows/pythonpublish.yml
vendored
@@ -8,9 +8,9 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v2.5.0
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v4.3.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -22,8 +22,8 @@ jobs:
|
|||||||
pip install parsimonious
|
pip install parsimonious
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
env:
|
env:
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
TWINE_USERNAME: __token__
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
TWINE_PASSWORD: ${{ secrets.PYPI_UPLOAD_API_KEY }}
|
||||||
run: |
|
run: |
|
||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist bdist_wheel
|
||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -104,3 +104,4 @@ venv.bak/
|
|||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/example/Charade/Session File Backups/
|
/example/Charade/Session File Backups/
|
||||||
|
lcov.info
|
||||||
|
|||||||
66
.idea/workspace.xml
generated
Normal file
66
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="68bdb183-5bdf-4b42-962e-28e58c31a89d" name="Default Changelist" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/ptulsconv.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/ptulsconv.iml" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="GitSEFilterConfiguration">
|
||||||
|
<file-type-list>
|
||||||
|
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||||
|
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||||
|
<filtered-out-file-type name="TAG" />
|
||||||
|
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||||
|
</file-type-list>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="1yyIGrXKNUCYtI4PSaCWGoLG76R" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
<option name="showMembers" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||||
|
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="project-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="68bdb183-5bdf-4b42-962e-28e58c31a89d" name="Default Changelist" comment="" />
|
||||||
|
<created>1633217312285</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1633217312285</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00001" summary="Reorganized README a little">
|
||||||
|
<created>1633221191797</created>
|
||||||
|
<option name="number" value="00001" />
|
||||||
|
<option name="presentableId" value="LOCAL-00001" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1633221191797</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00002" summary="Manpage 0.8.2 bump">
|
||||||
|
<created>1633221729867</created>
|
||||||
|
<option name="number" value="00002" />
|
||||||
|
<option name="presentableId" value="LOCAL-00002" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1633221729867</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="3" />
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="VcsManagerConfiguration">
|
||||||
|
<MESSAGE value="Reorganized README a little" />
|
||||||
|
<MESSAGE value="Manpage 0.8.2 bump" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Manpage 0.8.2 bump" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.vim/coc-settings.json
Normal file
5
.vim/coc-settings.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"python.linting.pylintEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.linting.mypyEnabled": false
|
||||||
|
}
|
||||||
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Contributing to ptulsconv
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Before submitting PRs or patches, please make sure your branch passes all of the unit tests by running Pytest.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
~/ptulsconv$ pytest
|
||||||
|
```
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
|
|
||||||
Read Pro Tools text exports and generate PDF reports, JSON output.
|
Read Pro Tools text exports and generate PDF reports, JSON output.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
For a quick overview of how to cue ADR with `ptulsconv`, check out the [Quickstart](doc/QUICKSTART.md).
|
||||||
|
|
||||||
|
|
||||||
## Theory of Operation
|
## Theory of Operation
|
||||||
|
|
||||||
|
|||||||
92
doc/HOWTO.md
Normal file
92
doc/HOWTO.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# How To Use `ptulsconv`
|
||||||
|
|
||||||
|
## Theory of Operation
|
||||||
|
|
||||||
|
[Avid Pro Tools][avp] exports a tab-delimited text file organized in multiple
|
||||||
|
parts with an uneven syntax that usually can't "drop in" to other tools like
|
||||||
|
Excel or Filemaker. `ptulsconv` will accept a text export from Pro Tools and,
|
||||||
|
by default, create a set of PDF reports useful for ADR reporting.
|
||||||
|
|
||||||
|
## Tagging
|
||||||
|
|
||||||
|
### Fields in Clip Names
|
||||||
|
|
||||||
|
Track names, track comments, and clip names can also contain meta-tags, or
|
||||||
|
"fields," to add additional columns to the CSV output. Thus, if a clip has the
|
||||||
|
name:
|
||||||
|
|
||||||
|
`Fireworks explosion {note=Replace for final} $V=1 [FX] [DESIGN]`
|
||||||
|
|
||||||
|
The row output for this clip will contain columns for the values:
|
||||||
|
|
||||||
|
|...| Clip Name| note | V | FX | DESIGN | ...|
|
||||||
|
|---|------------|------|---|----|--------|----|
|
||||||
|
|...| Fireworks explosion| Replace for final | 1 | FX | DESIGN | ... |
|
||||||
|
|
||||||
|
These fields can be defined in the clip name in three ways:
|
||||||
|
* `$NAME=VALUE` creates a field named `NAME` with a one-word value `VALUE`.
|
||||||
|
* `{NAME=VALUE}` creates a field named `NAME` with the value `VALUE`. `VALUE`
|
||||||
|
in this case may contain spaces or any chartacter up to the closing bracket.
|
||||||
|
* `[NAME]` creates a field named `NAME` with a value `NAME`. This can be used
|
||||||
|
to create a boolean-valued field; in the CSV output, clips with the field
|
||||||
|
will have it, and clips without will have the column with an empty value.
|
||||||
|
|
||||||
|
For example, if two clips are named:
|
||||||
|
|
||||||
|
`"Squad fifty-one, what is your status?" [FUTZ] {Ch=Dispatcher} [ADR]`
|
||||||
|
|
||||||
|
`"We are ten-eight at Rampart Hospital." {Ch=Gage} [ADR]`
|
||||||
|
|
||||||
|
The output will contain the range:
|
||||||
|
|
||||||
|
|...| PT.Clip.Name| Ch | FUTZ | ADR | ...|
|
||||||
|
|---|------------|------|---|----|-----|
|
||||||
|
|...| "Squad fifty-one, what is your status?"| Dispatcher | FUTZ | ADR | ... |
|
||||||
|
|...| "We are ten-eight at Rampart Hospital."| Gage | | ADR | ... |
|
||||||
|
|
||||||
|
|
||||||
|
### Fields in Track Names and Markers
|
||||||
|
|
||||||
|
Fields set in track names, and in track comments, will be applied to *each*
|
||||||
|
clip on that track. If a track comment contains the text `{Dept=Foley}` for
|
||||||
|
example, every clip on that track will have a "Foley" value in a "Dept" column.
|
||||||
|
|
||||||
|
Likewise, fields set on the session name will apply to all clips in the session.
|
||||||
|
|
||||||
|
Fields set in markers, and in marker comments, will be applied to all clips
|
||||||
|
whose finish is *after* that marker. Fields in markers are applied cumulatively
|
||||||
|
from breakfast to dinner in the session. The latest marker applying to a clip has
|
||||||
|
precedence, so if one marker comes after the other, but both define a field, the
|
||||||
|
value in the later marker
|
||||||
|
|
||||||
|
An important note here is that, always, fields set on the clip name have the
|
||||||
|
highest precedence. If a field is set in a clip name, the same field set on the
|
||||||
|
track, the value set on the clip will prevail.
|
||||||
|
|
||||||
|
### Using `@` to Apply Fields to a Span of Clips
|
||||||
|
|
||||||
|
A clip name beginning with "@" will not be included in the CSV output, but its
|
||||||
|
fields will be applied to clips within its time range on lower tracks.
|
||||||
|
|
||||||
|
If track 1 has a clip named `@ {Sc=1- The House}`, any clips beginning within
|
||||||
|
that range on lower tracks will have a field `Sc` with that value.
|
||||||
|
|
||||||
|
### Using `&` to Combine Clips
|
||||||
|
|
||||||
|
A clip name beginning with `&` will have its parsed clip name appended to the
|
||||||
|
preceding cue, and the fields of following cues will be applied, earlier clips
|
||||||
|
having precedence. The clips need not be touching, and the clips will be
|
||||||
|
combined into a single row of the output. The start time of the first clip will
|
||||||
|
become the start time of the row, and the finish time of the last clip will
|
||||||
|
become the finish time of the row.
|
||||||
|
|
||||||
|
|
||||||
|
## What is `ptulsconv` Useful For?
|
||||||
|
|
||||||
|
The main purpose of `ptulsconv` is to read a Pro Tools text export and convert
|
||||||
|
it into PDFs useful for ADR recording.
|
||||||
|
|
||||||
|
|
||||||
|
## Is it useful for anything else?
|
||||||
|
|
||||||
|
|
||||||
86
doc/QUICKSTART.md
Normal file
86
doc/QUICKSTART.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Quick Start for ADR Spotting and Reporting with `ptulsconv`
|
||||||
|
|
||||||
|
## Step 1: Use Pro Tools to spot ADR Lines
|
||||||
|
|
||||||
|
`ptulsconv` can be used to spot ADR lines similarly to other programs.
|
||||||
|
|
||||||
|
1. Create a new Pro Tools session, name this session after your project.
|
||||||
|
1. Create new tracks, one for each character. Name each track after a
|
||||||
|
character.
|
||||||
|
1. On each track, create a clip group (or edit in some audio) at the time you
|
||||||
|
would like an ADR line to appear in the report. Name the clip after the
|
||||||
|
dialogue you are replacing at that time.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 2: Add More Information to Your Spots
|
||||||
|
|
||||||
|
Clips, tracks and markers in your session can contain additional information
|
||||||
|
to make your ADR reports more complete and useful. You add this information
|
||||||
|
with *tagging*.
|
||||||
|
|
||||||
|
- Every ADR clip must have a unique cue number. After the name of each clip,
|
||||||
|
add the letters "$QN=" and then a unique number (any combination of letters
|
||||||
|
or numbers that don't contain a space). You can type these yourself or add
|
||||||
|
them with batch-renaming when you're done spotting.
|
||||||
|
- ADR spots should usually have a reason indicated, so you can remember exactly
|
||||||
|
why you're replacing a particular line. Do this by adding the the text "{R="
|
||||||
|
to your clip names after the prompt and then some short text describing the
|
||||||
|
reason, and then a closing "}". You can type anything, including spaces.
|
||||||
|
- If a line is a TV cover line, you can add the text "[TV]" to the end.
|
||||||
|
|
||||||
|
So for example, some ADR spot's clip name might look like:
|
||||||
|
|
||||||
|
"Get to the ladder! {R=Noise} $QN=J1001"
|
||||||
|
"Forget your feelings! {R=TV Cover} $QN=J1002 [TV]"
|
||||||
|
|
||||||
|
These tags can appear in any order.
|
||||||
|
|
||||||
|
- You can add the name of an actor to a character's track, so this information
|
||||||
|
will appear on your reports. In the track name, or in the track comments,
|
||||||
|
type "{Actor=xxx}" replacing the xxx with the actor's name.
|
||||||
|
- Characters need to have a number (perhaps from the cast list) to express how
|
||||||
|
they should be collated. Add "$CN=xxx" with a unique number to each track (or
|
||||||
|
the track's comments.)
|
||||||
|
- Set the scene for each line with markers. Create a marker at the beginning of
|
||||||
|
a scene and make it's name "{Sc=xxx}", replacing the xxx with the scene
|
||||||
|
number and name.
|
||||||
|
|
||||||
|
Many tags are available to express different details of each line, like
|
||||||
|
priority, time budget, picture version and reel, notes etc. charater or the
|
||||||
|
project, find them by running `ptulsconv` with the `--show-available-tags`
|
||||||
|
option.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 3: Export Relevant Tracks from Pro Tools as a Text File
|
||||||
|
|
||||||
|
Export the file as a UTF-8 and be sure to include clips and markers. Export
|
||||||
|
using the Timecode time format.
|
||||||
|
|
||||||
|
Do not export crossfades.
|
||||||
|
|
||||||
|
|
||||||
|
## Step 4: Run `ptulsconv` on the Text Export
|
||||||
|
|
||||||
|
In your Terminal, run the following command:
|
||||||
|
|
||||||
|
ptulsconv path/to/your/TEXT_EXPORT.txt
|
||||||
|
|
||||||
|
`ptulsconv` will create a folder named "Title_CURRENT_DATE", and within that
|
||||||
|
folder it will create several PDFs and folders:
|
||||||
|
|
||||||
|
- "TITLE ADR Report" 📄 a PDF tabular report of every ADR line you've spotted.
|
||||||
|
- "TITLE Continuity" 📄 a PDF listing every scene you have indicated and its
|
||||||
|
timecode.
|
||||||
|
- "TITLE Line Count" 📄 a PDF tabular report giving line counts by reel, and the
|
||||||
|
time budget per character and reel (if provided in the tagging).
|
||||||
|
- "CSV/" a folder containing CSV documents of all spotted ADR, groupd by
|
||||||
|
character and reel.
|
||||||
|
- "Director Logs/" 📁 a folder containing PDF tabular reports, like the overall
|
||||||
|
report except groupd by character.
|
||||||
|
- "Supervisor Logs/" 📁 a folder containing PDF reports, one page per line,
|
||||||
|
designed for note taking during a session, particularly on an iPad.
|
||||||
|
- "Talent Scripts/" 📁 a folder containing PDF scripts or sides, with the timecode
|
||||||
|
and prompts for each line, grouped by character but with most other
|
||||||
|
information suppressed.
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from ptulsconv.docparser.ptuls_grammar import protools_text_export_grammar
|
from ptulsconv.docparser.ptuls_grammar import protools_text_export_grammar
|
||||||
|
|
||||||
__version__ = '0.8.3'
|
__version__ = '1.0.4'
|
||||||
__author__ = 'Jamie Hardt'
|
__author__ = 'Jamie Hardt'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
|
__copyright__ = "%s %s (c) 2022 %s. All rights reserved." % (__name__, __version__, __author__)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from optparse import OptionParser, OptionGroup
|
|||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ptulsconv import __name__, __version__, __author__
|
from ptulsconv import __name__, __version__, __author__,__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
|
||||||
|
|
||||||
@@ -17,11 +17,24 @@ from ptulsconv.reporting import print_status_style, print_banner_style, print_se
|
|||||||
|
|
||||||
def dump_field_map(output=sys.stdout):
|
def dump_field_map(output=sys.stdout):
|
||||||
from ptulsconv.docparser.tag_mapping import TagMapping
|
from ptulsconv.docparser.tag_mapping import TagMapping
|
||||||
from ptulsconv.docparser.adr_entity import ADRLine
|
from ptulsconv.docparser.adr_entity import ADRLine, GenericEvent
|
||||||
|
|
||||||
|
TagMapping.print_rules(GenericEvent, output=output)
|
||||||
TagMapping.print_rules(ADRLine, output=output)
|
TagMapping.print_rules(ADRLine, output=output)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_formats():
|
||||||
|
print_section_header_style("`raw` format:")
|
||||||
|
sys.stderr.write("A JSON document of the parsed Pro Tools export.\n")
|
||||||
|
print_section_header_style("`tagged` Format:")
|
||||||
|
sys.stderr.write("A JSON document containing one record for each clip, with\n"
|
||||||
|
"all tags parsed and all tagging rules applied. \n")
|
||||||
|
print_section_header_style("`doc` format:")
|
||||||
|
sys.stderr.write("Creates a directory with folders for different types\n"
|
||||||
|
"of ADR reports.\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for the command-line invocation"""
|
"""Entry point for the command-line invocation"""
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
@@ -49,6 +62,13 @@ def main():
|
|||||||
description='Print useful information and exit without processing '
|
description='Print useful information and exit without processing '
|
||||||
'input files.')
|
'input files.')
|
||||||
|
|
||||||
|
informational_options.add_option('--show-formats',
|
||||||
|
dest='show_formats',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Display helpful information about the '
|
||||||
|
'available output formats.')
|
||||||
|
|
||||||
informational_options.add_option('--show-available-tags',
|
informational_options.add_option('--show-available-tags',
|
||||||
dest='show_tags',
|
dest='show_tags',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@@ -58,9 +78,10 @@ def main():
|
|||||||
|
|
||||||
parser.add_option_group(informational_options)
|
parser.add_option_group(informational_options)
|
||||||
|
|
||||||
|
print_banner_style(__copyright__)
|
||||||
|
|
||||||
(options, args) = parser.parse_args(sys.argv)
|
(options, args) = parser.parse_args(sys.argv)
|
||||||
|
|
||||||
print_banner_style("%s %s (c) 2021 %s. All rights reserved." % (__name__, __version__, __author__))
|
|
||||||
|
|
||||||
print_section_header_style("Startup")
|
print_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()))
|
||||||
@@ -69,6 +90,10 @@ def main():
|
|||||||
dump_field_map()
|
dump_field_map()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
elif options.show_formats:
|
||||||
|
dump_formats()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
print_fatal_error("Error: No input file")
|
print_fatal_error("Error: No input file")
|
||||||
parser.print_help(sys.stderr)
|
parser.print_help(sys.stderr)
|
||||||
|
|||||||
@@ -2,20 +2,23 @@ from fractions import Fraction
|
|||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
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")):
|
||||||
|
|
||||||
def smpte_to_seconds(self, smpte: str) -> Fraction:
|
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
|
||||||
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
|
frame_count = smpte_to_frame_count(smpte, self.logical_fps, drop_frame_hint=self.drop_frame)
|
||||||
|
if frame_count is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
return frame_count * self.frame_duration
|
return frame_count * self.frame_duration
|
||||||
|
|
||||||
def seconds_to_smpte(self, seconds: Fraction) -> str:
|
def seconds_to_smpte(self, seconds: SupportsFloat) -> str:
|
||||||
frame_count = int(seconds / self.frame_duration)
|
frame_count = int(seconds / self.frame_duration)
|
||||||
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame)
|
return frame_count_to_smpte(frame_count, self.logical_fps, self.drop_frame)
|
||||||
|
|
||||||
|
|
||||||
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> int:
|
def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Convert a string with a SMPTE timecode representation into a frame count.
|
Convert a string with a SMPTE timecode representation into a frame count.
|
||||||
|
|
||||||
@@ -28,7 +31,11 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
|||||||
"""
|
"""
|
||||||
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
||||||
|
|
||||||
m = re.search("(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?", smpte_rep_string)
|
m = re.search(r'(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?', smpte_rep_string)
|
||||||
|
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
|
||||||
hh, mm, ss, sep, ff, frac = m.groups()
|
hh, mm, ss, 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)
|
||||||
|
|
||||||
@@ -47,14 +54,14 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int,
|
|||||||
frames_dropped_per_inst = (frames_per_logical_second / 15)
|
frames_dropped_per_inst = (frames_per_logical_second / 15)
|
||||||
mins = hh * 60 + mm
|
mins = hh * 60 + mm
|
||||||
inst_count = mins - math.floor(mins / 10)
|
inst_count = mins - math.floor(mins / 10)
|
||||||
dropped_frames = frames_dropped_per_inst * inst_count
|
dropped_frames = int(frames_dropped_per_inst) * inst_count
|
||||||
frames = raw_frames - dropped_frames
|
frames = raw_frames - dropped_frames
|
||||||
|
|
||||||
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: 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
|
||||||
|
|
||||||
@@ -80,8 +87,10 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
|
|||||||
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||||
|
|
||||||
|
|
||||||
def footage_to_frame_count(footage_string):
|
def footage_to_frame_count(footage_string) -> Optional[int]:
|
||||||
m = re.search("(\d+)\+(\d+)(\.\d+)?", footage_string)
|
m = re.search(r'(\d+)\+(\d+)(\.\d+)?', footage_string)
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
feet, frm, frac = m.groups()
|
feet, frm, frac = m.groups()
|
||||||
feet, frm, frac = int(feet), int(frm), float(frac or 0.0)
|
feet, frm, frac = int(feet), int(frm), float(frac or 0.0)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
import csv
|
import csv
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
from .docparser.adr_entity import make_entities
|
from .docparser.adr_entity import make_entities
|
||||||
from .reporting import print_section_header_style, print_status_style, print_warning
|
from .reporting import print_section_header_style, print_status_style, print_warning
|
||||||
@@ -59,10 +60,12 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
|||||||
'Reason', 'Note', 'TV'])
|
'Reason', 'Note', 'TV'])
|
||||||
|
|
||||||
for event in these_lines:
|
for event in these_lines:
|
||||||
|
this_start = event.start or 0
|
||||||
|
this_finish = event.finish or 0
|
||||||
this_row = [event.title, event.character_name, event.cue_number,
|
this_row = [event.title, event.character_name, event.cue_number,
|
||||||
event.reel, event.version,
|
event.reel, event.version,
|
||||||
time_format.seconds_to_smpte(event.start), time_format.seconds_to_smpte(event.finish),
|
time_format.seconds_to_smpte(this_start), time_format.seconds_to_smpte(this_finish),
|
||||||
float(event.start), float(event.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 ""]
|
||||||
|
|
||||||
@@ -126,7 +129,7 @@ def create_adr_reports(lines: List[ADRLine], tc_display_format: TimecodeFormat,
|
|||||||
# return parsed
|
# return parsed
|
||||||
|
|
||||||
|
|
||||||
def convert(input_file, major_mode='fmpxml', output=sys.stdout, warnings=True):
|
def convert(input_file, major_mode, output=sys.stdout, warnings=True):
|
||||||
|
|
||||||
session = parse_document(input_file)
|
session = parse_document(input_file)
|
||||||
session_tc_format = session.header.timecode_format
|
session_tc_format = session.header.timecode_format
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from ptulsconv.docparser.tag_compiler import Event
|
from ptulsconv.docparser.tag_compiler import Event
|
||||||
from typing import Optional, List, Tuple, Any
|
from typing import Optional, List, Tuple
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
@@ -11,12 +11,10 @@ def make_entities(from_events: List[Event]) -> Tuple[List['GenericEvent'], List[
|
|||||||
adr_lines = list()
|
adr_lines = list()
|
||||||
|
|
||||||
for event in from_events:
|
for event in from_events:
|
||||||
result: Any = make_entity(event)
|
result = make_entity(event)
|
||||||
if type(result) is ADRLine:
|
if type(result) is ADRLine:
|
||||||
result: ADRLine
|
|
||||||
adr_lines.append(result)
|
adr_lines.append(result)
|
||||||
elif type(result) is GenericEvent:
|
elif type(result) is GenericEvent:
|
||||||
result: GenericEvent
|
|
||||||
generic_events.append(result)
|
generic_events.append(result)
|
||||||
|
|
||||||
return generic_events, adr_lines
|
return generic_events, adr_lines
|
||||||
@@ -41,17 +39,17 @@ def make_entity(from_event: Event) -> Optional[object]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GenericEvent:
|
class GenericEvent:
|
||||||
title: Optional[str]
|
title: str = ""
|
||||||
supervisor: Optional[str]
|
supervisor: Optional[str] = None
|
||||||
client: Optional[str]
|
client: Optional[str] = None
|
||||||
scene: Optional[str]
|
scene: Optional[str] = None
|
||||||
version: Optional[str]
|
version: Optional[str] = None
|
||||||
reel: Optional[str]
|
reel: Optional[str] = None
|
||||||
start: Optional[Fraction]
|
start: Fraction = Fraction(0,1)
|
||||||
finish: Optional[Fraction]
|
finish: Fraction = Fraction(0,1)
|
||||||
omitted: bool
|
omitted: bool = False
|
||||||
note: Optional[str]
|
note: Optional[str] = None
|
||||||
requested_by: Optional[str]
|
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),
|
||||||
@@ -69,21 +67,21 @@ class GenericEvent:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ADRLine(GenericEvent):
|
class ADRLine(GenericEvent):
|
||||||
priority: Optional[int]
|
priority: Optional[int] = None
|
||||||
cue_number: Optional[str]
|
cue_number: Optional[str] = None
|
||||||
character_id: Optional[str]
|
character_id: Optional[str] = None
|
||||||
character_name: Optional[str]
|
character_name: Optional[str] = None
|
||||||
actor_name: Optional[str]
|
actor_name: Optional[str] = None
|
||||||
prompt: Optional[str]
|
prompt: Optional[str] = None
|
||||||
reason: Optional[str]
|
reason: Optional[str] = None
|
||||||
time_budget_mins: Optional[float]
|
time_budget_mins: Optional[float] = None
|
||||||
spot: Optional[str]
|
spot: Optional[str] = None
|
||||||
shot: Optional[str]
|
shot: Optional[str] = None
|
||||||
effort: bool
|
effort: bool = False
|
||||||
tv: bool
|
tv: bool = False
|
||||||
tbw: bool
|
tbw: bool = False
|
||||||
adlib: bool
|
adlib: bool = False
|
||||||
optional: bool
|
optional: bool = False
|
||||||
|
|
||||||
tag_mapping = [
|
tag_mapping = [
|
||||||
|
|
||||||
@@ -111,30 +109,30 @@ class ADRLine(GenericEvent):
|
|||||||
formatter=(lambda x: len(x) > 0))
|
formatter=(lambda x: len(x) > 0))
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
# def __init__(self):
|
||||||
self.title = None
|
# self.title = None
|
||||||
self.supervisor = None
|
# self.supervisor = None
|
||||||
self.client = None
|
# self.client = None
|
||||||
self.scene = None
|
# self.scene = None
|
||||||
self.version = None
|
# self.version = None
|
||||||
self.reel = None
|
# self.reel = None
|
||||||
self.start = None
|
# self.start = None
|
||||||
self.finish = None
|
# self.finish = None
|
||||||
self.priority = None
|
# self.priority = None
|
||||||
self.cue_number = None
|
# self.cue_number = None
|
||||||
self.character_id = None
|
# self.character_id = None
|
||||||
self.character_name = None
|
# self.character_name = None
|
||||||
self.actor_name = None
|
# self.actor_name = None
|
||||||
self.prompt = None
|
# self.prompt = None
|
||||||
self.reason = None
|
# self.reason = None
|
||||||
self.requested_by = None
|
# self.requested_by = None
|
||||||
self.time_budget_mins = None
|
# self.time_budget_mins = None
|
||||||
self.note = None
|
# self.note = None
|
||||||
self.spot = None
|
# self.spot = None
|
||||||
self.shot = None
|
# self.shot = None
|
||||||
self.effort = False
|
# self.effort = False
|
||||||
self.tv = False
|
# self.tv = False
|
||||||
self.tbw = False
|
# self.tbw = False
|
||||||
self.omitted = False
|
# self.omitted = False
|
||||||
self.adlib = False
|
# self.adlib = False
|
||||||
self.optional = False
|
# self.optional = False
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ 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 = self.header.convert_timecode(marker.location)
|
marker_time = Fraction(marker.time_reference, int(self.header.sample_rate))
|
||||||
|
#marker_time = self.header.convert_timecode(marker.location)
|
||||||
yield marker, marker_time
|
yield marker, marker_time
|
||||||
|
|
||||||
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor', 'TrackClipDescriptor']]:
|
def tracks_clips(self) -> Iterator[Tuple['TrackDescriptor', 'TrackClipDescriptor']]:
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ protools_text_export_grammar = Grammar(
|
|||||||
fs = "\t"
|
fs = "\t"
|
||||||
rs = "\n"
|
rs = "\n"
|
||||||
block_ending = rs rs
|
block_ending = rs rs
|
||||||
string_value = ~"[^\t\n]*"
|
string_value = ~r"[^\t\n]*"
|
||||||
integer_value = ~"\d+"
|
integer_value = ~r"\d+"
|
||||||
float_value = ~"\d+(\.\d+)?"
|
float_value = ~r"\d+(\.\d+)?"
|
||||||
isp = ~"[^\d\t\n]*"
|
isp = ~r"[^\d\t\n]*"
|
||||||
""")
|
""")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import sys
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from typing import Iterator, Tuple, Callable, Generator, Dict, List
|
from typing import Iterator, Tuple, Callable, Generator, Dict, List
|
||||||
@@ -72,9 +71,9 @@ class TagCompiler:
|
|||||||
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, time in sorted(applicable, key=lambda x: x[1]):
|
for marker, _ in sorted(applicable, key=lambda x: x[1]):
|
||||||
retval.update(parse_tags(marker.comments).tag_dict)
|
retval.update(parse_tags(marker.comments or "").tag_dict)
|
||||||
retval.update(parse_tags(marker.name).tag_dict)
|
retval.update(parse_tags(marker.name or "").tag_dict)
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|||||||
@@ -18,24 +18,24 @@ tag_grammar = Grammar(
|
|||||||
key_tag = "[" key "]" word_sep?
|
key_tag = "[" key "]" word_sep?
|
||||||
short_tag = "$" key "=" word word_sep?
|
short_tag = "$" key "=" word word_sep?
|
||||||
full_text_tag = "{" key "=" value "}" word_sep?
|
full_text_tag = "{" key "=" value "}" word_sep?
|
||||||
key = ~"[A-Za-z][A-Za-z0-9_]*"
|
key = ~r"[A-Za-z][A-Za-z0-9_]*"
|
||||||
value = ~"[^}]+"
|
value = ~r"[^}]+"
|
||||||
tag_junk = word word_sep?
|
tag_junk = word word_sep?
|
||||||
word = ~"[^ \[\{\$][^ ]*"
|
word = ~r"[^ \[\{\$][^ ]*"
|
||||||
word_sep = ~" +"
|
word_sep = ~r" +"
|
||||||
modifier = ("@" / "&") word_sep?
|
modifier = ("@" / "&") word_sep?
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_tags(prompt) -> "TaggedStringResult":
|
def parse_tags(prompt: str) -> "TaggedStringResult":
|
||||||
ast = tag_grammar.parse(prompt)
|
ast = tag_grammar.parse(prompt)
|
||||||
return TagListVisitor().visit(ast)
|
return TagListVisitor().visit(ast)
|
||||||
|
|
||||||
|
|
||||||
class TaggedStringResult:
|
class TaggedStringResult:
|
||||||
content: Optional[str]
|
content: str
|
||||||
tag_dict: Optional[Dict[str, str]]
|
tag_dict: Dict[str, str]
|
||||||
mode: TagPreModes
|
mode: TagPreModes
|
||||||
|
|
||||||
def __init__(self, content, tag_dict, mode):
|
def __init__(self, content, tag_dict, mode):
|
||||||
|
|||||||
18
ptulsconv/footage.py
Normal file
18
ptulsconv/footage.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from fractions import Fraction
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def footage_to_seconds(footage: str) -> Optional[Fraction]:
|
||||||
|
m = re.match(r'(\d+)\+(\d+)(\.\d+)?', footage)
|
||||||
|
if m is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
feet, frames, _ = m.groups()
|
||||||
|
feet, frames = int(feet), int(frames)
|
||||||
|
|
||||||
|
fps = 24
|
||||||
|
frames_per_foot = 16
|
||||||
|
|
||||||
|
total_frames = feet * frames_per_foot + frames
|
||||||
|
|
||||||
|
return Fraction(total_frames, fps)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import ffmpeg # ffmpeg-python
|
#import ffmpeg # ffmpeg-python
|
||||||
|
|
||||||
# TODO: Implement movie export
|
# TODO: Implement movie export
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ from reportlab.platypus.frames import Frame
|
|||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
# TODO: A Generic report useful for spotting
|
# TODO: A Generic report useful for spotting
|
||||||
# TODO: A report useful for M&E mixer's notes
|
# TODO: A report useful for M&E mixer's notes
|
||||||
|
# TODO: Use a default font that doesn't need to be installed
|
||||||
|
|
||||||
# This is from https://code.activestate.com/recipes/576832/ for
|
# This is from https://code.activestate.com/recipes/576832/ for
|
||||||
# generating page count messages
|
# generating page count messages
|
||||||
@@ -36,7 +38,7 @@ class ReportCanvas(canvas.Canvas):
|
|||||||
|
|
||||||
def draw_page_number(self, page_count):
|
def draw_page_number(self, page_count):
|
||||||
self.saveState()
|
self.saveState()
|
||||||
self.setFont("Futura", 10)
|
self.setFont('Helvetica', 10) #FIXME make this customizable
|
||||||
self.drawString(0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count))
|
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"))
|
||||||
@@ -60,7 +62,8 @@ def make_doc_template(page_size, filename, document_title,
|
|||||||
document_header: str,
|
document_header: str,
|
||||||
client: str,
|
client: str,
|
||||||
document_subheader: str,
|
document_subheader: str,
|
||||||
left_margin=0.5 * inch) -> ADRDocTemplate:
|
left_margin=0.5 * inch,
|
||||||
|
fonts: List[TTFont] = []) -> ADRDocTemplate:
|
||||||
right_margin = top_margin = bottom_margin = 0.5 * inch
|
right_margin = top_margin = bottom_margin = 0.5 * inch
|
||||||
page_box = GRect(0., 0., page_size[0], page_size[1])
|
page_box = GRect(0., 0., page_size[0], page_size[1])
|
||||||
_, page_box = page_box.split_x(left_margin, direction='l')
|
_, page_box = page_box.split_x(left_margin, direction='l')
|
||||||
@@ -85,7 +88,9 @@ def make_doc_template(page_size, filename, document_title,
|
|||||||
frames=frames,
|
frames=frames,
|
||||||
onPage=on_page_lambda)
|
onPage=on_page_lambda)
|
||||||
|
|
||||||
pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc'))
|
for font in fonts:
|
||||||
|
pdfmetrics.registerFont(font)
|
||||||
|
|
||||||
doc = ADRDocTemplate(filename,
|
doc = ADRDocTemplate(filename,
|
||||||
title=document_title,
|
title=document_title,
|
||||||
author=supervisor,
|
author=supervisor,
|
||||||
@@ -99,6 +104,11 @@ def make_doc_template(page_size, filename, document_title,
|
|||||||
|
|
||||||
|
|
||||||
def time_format(mins, zero_str="-"):
|
def time_format(mins, zero_str="-"):
|
||||||
|
"""
|
||||||
|
Formats a duration `mins` into a string
|
||||||
|
"""
|
||||||
|
if mins is None:
|
||||||
|
return zero_str
|
||||||
if mins == 0. and zero_str is not None:
|
if mins == 0. and zero_str is not None:
|
||||||
return zero_str
|
return zero_str
|
||||||
elif mins < 60.:
|
elif mins < 60.:
|
||||||
@@ -110,11 +120,11 @@ def time_format(mins, zero_str="-"):
|
|||||||
|
|
||||||
|
|
||||||
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str,
|
def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box, title: str, supervisor: str,
|
||||||
document_subheader: str, client: str, doc_title=""):
|
document_subheader: str, client: str, doc_title="", font_name='Helvetica'):
|
||||||
|
|
||||||
(_supervisor_box, client_box,), title_box = right_box.divide_y([16., 16., ])
|
(_supervisor_box, client_box,), title_box = right_box.divide_y([16., 16., ])
|
||||||
title_box.draw_text_cell(a_canvas, title, "Futura", 18, inset_y=2., inset_x=5.)
|
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, "Futura", 11, 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)
|
||||||
@@ -131,13 +141,13 @@ def draw_header_footer(a_canvas: ReportCanvas, left_box, right_box, footer_box,
|
|||||||
|
|
||||||
(doc_title_cell, spotting_version_cell,), _ = left_box.divide_y([18., 14], direction='d')
|
(doc_title_cell, spotting_version_cell,), _ = left_box.divide_y([18., 14], direction='d')
|
||||||
|
|
||||||
doc_title_cell.draw_text_cell(a_canvas, doc_title, 'Futura', 14., inset_y=2.)
|
doc_title_cell.draw_text_cell(a_canvas, doc_title, font_name, 14., inset_y=2.)
|
||||||
|
|
||||||
if document_subheader is not None:
|
if document_subheader is not None:
|
||||||
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, 'Futura', 12., inset_y=2.)
|
spotting_version_cell.draw_text_cell(a_canvas, document_subheader, font_name, 12., inset_y=2.)
|
||||||
|
|
||||||
if supervisor is not None:
|
if supervisor is not None:
|
||||||
a_canvas.setFont('Futura', 11.)
|
a_canvas.setFont(font_name, 11.)
|
||||||
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, supervisor)
|
a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, supervisor)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ from ptulsconv.pdf import make_doc_template
|
|||||||
|
|
||||||
# TODO: A Continuity
|
# TODO: A Continuity
|
||||||
|
|
||||||
def table_for_scene(scene, tc_format):
|
def table_for_scene(scene, tc_format, font_name = 'Helvetica'):
|
||||||
scene_style = getSampleStyleSheet()['Normal']
|
scene_style = getSampleStyleSheet()['Normal']
|
||||||
scene_style.fontName = 'Futura'
|
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.
|
||||||
@@ -29,7 +29,7 @@ def table_for_scene(scene, tc_format):
|
|||||||
style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
('LEFTPADDING', (0, 0), (0, 0), 0.0),
|
||||||
('BOTTOMPADDING', (0, 0), (-1, -1), 12.),
|
('BOTTOMPADDING', (0, 0), (-1, -1), 12.),
|
||||||
('FONTNAME', (0, 0), (-1, -1), 'Futura')]
|
('FONTNAME', (0, 0), (-1, -1), font_name)]
|
||||||
|
|
||||||
return Table(data=[row], style=style, colWidths=[1.0 * inch, 6.5 * inch])
|
return Table(data=[row], style=style, colWidths=[1.0 * inch, 6.5 * inch])
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
styles = list()
|
styles = list()
|
||||||
columns_widths = list()
|
columns_widths = list()
|
||||||
|
|
||||||
sorted_character_numbers = sorted(set([x.character_id for x in lines]),
|
sorted_character_numbers: List[str] = sorted(set([x.character_id for x in lines]),
|
||||||
key=lambda x: str(x))
|
key=lambda x: str(x))
|
||||||
|
|
||||||
# construct column styles
|
# construct column styles
|
||||||
@@ -164,13 +164,16 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
|
|
||||||
for n in sorted_character_numbers:
|
for n in sorted_character_numbers:
|
||||||
char_records = [x for x in lines if x.character_id == n]
|
char_records = [x for x in lines if x.character_id == n]
|
||||||
|
if len(char_records) > 0:
|
||||||
row_data = list()
|
row_data = list()
|
||||||
row_data2 = list()
|
row_data2 = list()
|
||||||
|
|
||||||
for col in columns:
|
for col in columns:
|
||||||
row1_index = len(data)
|
row1_index = len(data)
|
||||||
row2_index = row1_index + 1
|
row2_index = row1_index + 1
|
||||||
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), colors.red),
|
||||||
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)])
|
('LINEBELOW', (0, row2_index), (-1, row2_index), 0.5, colors.black)])
|
||||||
|
|
||||||
@@ -202,16 +205,16 @@ def populate_columns(lines: List[ADRLine], columns, include_omitted, _page_size)
|
|||||||
|
|
||||||
|
|
||||||
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False,
|
def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=False,
|
||||||
page_size=portrait(letter)):
|
page_size=portrait(letter), font_name='Helvetica'):
|
||||||
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
|
columns = build_columns(lines, include_omitted=include_omitted, reel_list=reel_list)
|
||||||
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size)
|
data, style, columns_widths = populate_columns(lines, columns, include_omitted, page_size)
|
||||||
|
|
||||||
style.append(('FONTNAME', (0, 0), (-1, -1), "Futura"))
|
style.append(('FONTNAME', (0, 0), (-1, -1), font_name))
|
||||||
style.append(('FONTSIZE', (0, 0), (-1, -1), 9.))
|
style.append(('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'
|
||||||
@@ -230,7 +233,7 @@ def output_report(lines: List[ADRLine], reel_list: List[str], include_omitted=Fa
|
|||||||
story = [Spacer(height=0.5 * inch, width=1.), table]
|
story = [Spacer(height=0.5 * inch, width=1.), table]
|
||||||
|
|
||||||
style = getSampleStyleSheet()['Normal']
|
style = getSampleStyleSheet()['Normal']
|
||||||
style.fontName = 'Futura'
|
style.fontName = font_name
|
||||||
style.fontSize = 12.
|
style.fontSize = 12.
|
||||||
style.spaceBefore = 16.
|
style.spaceBefore = 16.
|
||||||
style.spaceAfter = 16.
|
style.spaceAfter = 16.
|
||||||
|
|||||||
@@ -34,23 +34,26 @@ def build_aux_data_field(line: ADRLine):
|
|||||||
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:
|
||||||
|
bg_color = 'green'
|
||||||
|
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):
|
def build_story(lines: List[ADRLine], tc_rate: TimecodeFormat, font_name='Helvetica'):
|
||||||
story = list()
|
story = list()
|
||||||
|
|
||||||
this_scene = None
|
this_scene = None
|
||||||
scene_style = getSampleStyleSheet()['Normal']
|
scene_style = getSampleStyleSheet()['Normal']
|
||||||
scene_style.fontName = 'Futura'
|
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.
|
||||||
line_style = getSampleStyleSheet()['Normal']
|
line_style = getSampleStyleSheet()['Normal']
|
||||||
line_style.fontName = 'Futura'
|
line_style.fontName = font_name
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
table_style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
table_style = [('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ from reportlab.platypus import Paragraph
|
|||||||
|
|
||||||
from .__init__ import GRect
|
from .__init__ import GRect
|
||||||
|
|
||||||
from ptulsconv.broadcast_timecode import TimecodeFormat
|
from ptulsconv.broadcast_timecode import TimecodeFormat, footage_to_frame_count
|
||||||
from ptulsconv.docparser.adr_entity import ADRLine
|
from ptulsconv.docparser.adr_entity import ADRLine
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
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')
|
||||||
@@ -23,19 +24,19 @@ def draw_header_block(canvas, rect, record: ADRLine):
|
|||||||
|
|
||||||
def draw_character_row(canvas, rect, record: ADRLine):
|
def draw_character_row(canvas, rect, record: ADRLine):
|
||||||
label_frame, value_frame = rect.split_x(1.25 * inch)
|
label_frame, value_frame = rect.split_x(1.25 * inch)
|
||||||
label_frame.draw_text_cell(canvas, "CHARACTER", "Futura", 10, force_baseline=9.)
|
label_frame.draw_text_cell(canvas, "CHARACTER", font_name, 10, force_baseline=9.)
|
||||||
line = "%s / %s" % (record.character_id, record.character_name)
|
line = "%s / %s" % (record.character_id, record.character_name)
|
||||||
if record.actor_name is not None:
|
if record.actor_name is not None:
|
||||||
line = line + " / " + record.actor_name
|
line = line + " / " + record.actor_name
|
||||||
value_frame.draw_text_cell(canvas, line, "Futura", 12, force_baseline=9.)
|
value_frame.draw_text_cell(canvas, line, font_name, 12, force_baseline=9.)
|
||||||
rect.draw_border(canvas, ['min_y', 'max_y'])
|
rect.draw_border(canvas, ['min_y', 'max_y'])
|
||||||
|
|
||||||
|
|
||||||
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", "Futura", 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, "Futura", 14,
|
number_frame.draw_text_cell(canvas, record.cue_number, font_name, 14,
|
||||||
inset_x=10., inset_y=2., draw_baseline=True)
|
inset_x=10., inset_y=2., draw_baseline=True)
|
||||||
|
|
||||||
tags = {'tv': 'TV',
|
tags = {'tv': 'TV',
|
||||||
@@ -49,7 +50,7 @@ def draw_cue_number_block(canvas, rect, record: ADRLine):
|
|||||||
if getattr(record, key):
|
if getattr(record, key):
|
||||||
tag_field = tag_field + tags[key] + " "
|
tag_field = tag_field + tags[key] + " "
|
||||||
|
|
||||||
aux_frame.draw_text_cell(canvas, tag_field, "Futura", 10,
|
aux_frame.draw_text_cell(canvas, tag_field, font_name, 10,
|
||||||
inset_x=10., inset_y=2., vertical_align='t')
|
inset_x=10., inset_y=2., vertical_align='t')
|
||||||
rect.draw_border(canvas, 'max_x')
|
rect.draw_border(canvas, 'max_x')
|
||||||
|
|
||||||
@@ -58,13 +59,13 @@ def draw_timecode_block(canvas, rect, record: ADRLine, tc_display_format: Timeco
|
|||||||
(in_label_frame, in_frame, out_label_frame, out_frame), _ = rect.divide_y(
|
(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", "Futura", 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), "Futura", 14,
|
in_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.start), font_name, 14,
|
||||||
inset_x=10., inset_y=2., draw_baseline=True)
|
inset_x=10., inset_y=2., draw_baseline=True)
|
||||||
out_label_frame.draw_text_cell(canvas, "OUT", "Futura", 10,
|
out_label_frame.draw_text_cell(canvas, "OUT", font_name, 10,
|
||||||
vertical_align='t', inset_y=5., inset_x=5.)
|
vertical_align='t', inset_y=5., inset_x=5.)
|
||||||
out_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.finish), "Futura", 14,
|
out_frame.draw_text_cell(canvas, tc_display_format.seconds_to_smpte(record.finish), font_name, 14,
|
||||||
inset_x=10., inset_y=2., draw_baseline=True)
|
inset_x=10., inset_y=2., draw_baseline=True)
|
||||||
|
|
||||||
rect.draw_border(canvas, 'max_x')
|
rect.draw_border(canvas, 'max_x')
|
||||||
@@ -75,16 +76,16 @@ def draw_reason_block(canvas, rect, record: ADRLine):
|
|||||||
reason_label, reason_value = reason_cell.split_x(.75 * inch)
|
reason_label, reason_value = reason_cell.split_x(.75 * inch)
|
||||||
notes_label, notes_value = notes_cell.split_x(.75 * inch)
|
notes_label, notes_value = notes_cell.split_x(.75 * inch)
|
||||||
|
|
||||||
reason_label.draw_text_cell(canvas, "Reason:", "Futura", 12,
|
reason_label.draw_text_cell(canvas, "Reason:", font_name, 12,
|
||||||
inset_x=5., inset_y=5., vertical_align='b')
|
inset_x=5., inset_y=5., vertical_align='b')
|
||||||
reason_value.draw_text_cell(canvas, record.reason or "", "Futura", 12,
|
reason_value.draw_text_cell(canvas, record.reason or "", font_name, 12,
|
||||||
inset_x=5., inset_y=5., draw_baseline=True,
|
inset_x=5., inset_y=5., draw_baseline=True,
|
||||||
vertical_align='b')
|
vertical_align='b')
|
||||||
notes_label.draw_text_cell(canvas, "Note:", "Futura", 12,
|
notes_label.draw_text_cell(canvas, "Note:", font_name, 12,
|
||||||
inset_x=5., inset_y=5., vertical_align='t')
|
inset_x=5., inset_y=5., vertical_align='t')
|
||||||
|
|
||||||
style = getSampleStyleSheet()['BodyText']
|
style = getSampleStyleSheet()['BodyText']
|
||||||
style.fontName = 'Futura'
|
style.fontName = font_name
|
||||||
style.fontSize = 12
|
style.fontSize = 12
|
||||||
style.leading = 14
|
style.leading = 14
|
||||||
|
|
||||||
@@ -96,10 +97,10 @@ def draw_reason_block(canvas, rect, record: ADRLine):
|
|||||||
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", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.)
|
label.draw_text_cell(canvas, "PROMPT", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
|
||||||
|
|
||||||
style = getSampleStyleSheet()['BodyText']
|
style = getSampleStyleSheet()['BodyText']
|
||||||
style.fontName = 'Futura'
|
style.fontName = font_name
|
||||||
style.fontSize = 14
|
style.fontSize = 14
|
||||||
|
|
||||||
style.leading = 24
|
style.leading = 24
|
||||||
@@ -116,10 +117,10 @@ 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", "Futura", 10, vertical_align='t', inset_y=5., inset_x=0.)
|
label.draw_text_cell(canvas, "NOTES", font_name, 10, vertical_align='t', inset_y=5., inset_x=0.)
|
||||||
|
|
||||||
style = getSampleStyleSheet()['BodyText']
|
style = getSampleStyleSheet()['BodyText']
|
||||||
style.fontName = 'Futura'
|
style.fontName = font_name
|
||||||
style.fontSize = 14
|
style.fontSize = 14
|
||||||
style.leading = 24
|
style.leading = 24
|
||||||
|
|
||||||
@@ -175,12 +176,12 @@ def draw_aux_block(canvas, rect, recording_time_sec_this_line, recording_time_se
|
|||||||
lines, last_line = content_rect.divide_y([12., 12., 24., 24., 24., 24.], direction='d')
|
lines, 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.), "Futura", 9.)
|
"Time for this line: %.1f mins" % (recording_time_sec_this_line / 60.), font_name, 9.)
|
||||||
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), "Futura", 9.)
|
lines[1].draw_text_cell(canvas, "Running time: %03.1f mins" % (recording_time_sec / 60.), font_name, 9.)
|
||||||
lines[2].draw_text_cell(canvas, "Actual Start: ______________", "Futura", 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: ______________", "Futura", 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: ______________", "Futura", 9., vertical_align='b')
|
lines[4].draw_text_cell(canvas, "Engineer: ______________", font_name, 9., vertical_align='b')
|
||||||
lines[5].draw_text_cell(canvas, "Location: ______________", "Futura", 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):
|
||||||
@@ -189,7 +190,7 @@ def draw_footer(canvas, rect, record: ADRLine, report_date, line_no, total_lines
|
|||||||
spotting_name = [record.spot] if record.spot is not None else []
|
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="Futura", font_size=10., inset_y=2.)
|
rect.draw_text_cell(canvas, footer_s, font_name=font_name, font_size=10., inset_y=2.)
|
||||||
|
|
||||||
|
|
||||||
def create_report_for_character(records, report_date, tc_display_format: TimecodeFormat):
|
def create_report_for_character(records, report_date, tc_display_format: TimecodeFormat):
|
||||||
@@ -200,7 +201,7 @@ def create_report_for_character(records, report_date, tc_display_format: Timecod
|
|||||||
assert outfile is not None
|
assert outfile 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)
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ 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):
|
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
|
||||||
|
|
||||||
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, character_name, n)
|
||||||
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
|
filename = "%s_%s_%s_ADR Script.pdf" % (char_lines[0].title, n, character_name)
|
||||||
@@ -39,7 +39,7 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat):
|
|||||||
story = []
|
story = []
|
||||||
|
|
||||||
prompt_style = getSampleStyleSheet()['Normal']
|
prompt_style = getSampleStyleSheet()['Normal']
|
||||||
prompt_style.fontName = 'Futura'
|
prompt_style.fontName = font_name
|
||||||
prompt_style.fontSize = 18.
|
prompt_style.fontSize = 18.
|
||||||
|
|
||||||
prompt_style.leading = 24.
|
prompt_style.leading = 24.
|
||||||
@@ -47,7 +47,7 @@ def output_report(lines: List[ADRLine], tc_display_format: TimecodeFormat):
|
|||||||
prompt_style.rightIndent = 1.5 * inch
|
prompt_style.rightIndent = 1.5 * inch
|
||||||
|
|
||||||
number_style = getSampleStyleSheet()['Normal']
|
number_style = getSampleStyleSheet()['Normal']
|
||||||
number_style.fontName = 'Futura'
|
number_style.fontName = font_name
|
||||||
number_style.fontSize = 14
|
number_style.fontSize = 14
|
||||||
|
|
||||||
number_style.leading = 24
|
number_style.leading = 24
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
setuptools~=56.2.0
|
astroid==2.9.3
|
||||||
reportlab~=3.5.67
|
isort==5.10.1
|
||||||
ffmpeg~=1.4
|
lazy-object-proxy==1.7.1
|
||||||
parsimonious~=0.8.1
|
mccabe==0.6.1
|
||||||
tqdm~=4.60.0
|
parsimonious==0.9.0
|
||||||
|
Pillow==9.1.1
|
||||||
|
platformdirs==2.4.1
|
||||||
|
pylint==2.12.2
|
||||||
|
regex==2022.6.2
|
||||||
|
reportlab==3.6.10
|
||||||
|
six==1.16.0
|
||||||
|
toml==0.10.2
|
||||||
|
tqdm==4.64.0
|
||||||
|
typing_extensions==4.0.1
|
||||||
|
wrapt==1.13.3
|
||||||
|
|||||||
4
test-coverage.sh
Executable file
4
test-coverage.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
coverage run -m pytest . ; coverage-lcov
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ import os.path
|
|||||||
|
|
||||||
|
|
||||||
class TestRobinHood1(unittest.TestCase):
|
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):
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ import os.path
|
|||||||
|
|
||||||
|
|
||||||
class TestRobinHood5(unittest.TestCase):
|
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)
|
session = parse_document(self.path)
|
||||||
@@ -4,7 +4,7 @@ import os.path
|
|||||||
|
|
||||||
|
|
||||||
class TestRobinHood6(unittest.TestCase):
|
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)
|
session = parse_document(self.path)
|
||||||
@@ -4,7 +4,7 @@ import os.path
|
|||||||
|
|
||||||
|
|
||||||
class TestRobinHoodDF(unittest.TestCase):
|
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)
|
session = parse_document(self.path)
|
||||||
34
tests/functional/test_pdf_export.py
Normal file
34
tests/functional/test_pdf_export.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
from ptulsconv import commands
|
||||||
|
|
||||||
|
class TestBroadcastTimecode(unittest.TestCase):
|
||||||
|
def test_report_generation(self):
|
||||||
|
"""
|
||||||
|
Setp through every text file in export_cases and make sure it can
|
||||||
|
be converted into PDF docs without throwing an error
|
||||||
|
"""
|
||||||
|
files = [os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting.txt"]
|
||||||
|
#files.append(os.path.dirname(__file__) + "/../export_cases/Robin Hood Spotting2.txt")
|
||||||
|
for path in files:
|
||||||
|
tempdir = tempfile.TemporaryDirectory()
|
||||||
|
os.chdir(tempdir.name)
|
||||||
|
try:
|
||||||
|
commands.convert(path, major_mode='doc')
|
||||||
|
except:
|
||||||
|
assert False, "Error processing file %s" % path
|
||||||
|
finally:
|
||||||
|
tempdir.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
15
tests/unittests/test_footage.py
Normal file
15
tests/unittests/test_footage.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import unittest
|
||||||
|
from ptulsconv import footage
|
||||||
|
|
||||||
|
class TestFootage(unittest.TestCase):
|
||||||
|
def test_basic_footage(self):
|
||||||
|
r1 = "90+0"
|
||||||
|
f1 = footage.footage_to_seconds(r1)
|
||||||
|
self.assertEqual(float(f1 or 0), 60.0)
|
||||||
|
|
||||||
|
def test_feet_and_frames(self):
|
||||||
|
r1 = "1+8"
|
||||||
|
f1 = footage.footage_to_seconds(r1)
|
||||||
|
self.assertEqual(float(f1 or 0), 1.0)
|
||||||
|
|
||||||
|
|
||||||
@@ -97,14 +97,14 @@ class TestTagCompiler(unittest.TestCase):
|
|||||||
|
|
||||||
markers = [doc_entity.MarkerDescriptor(number=1,
|
markers = [doc_entity.MarkerDescriptor(number=1,
|
||||||
location="01:00:00:00",
|
location="01:00:00:00",
|
||||||
time_reference=48000 * 60,
|
time_reference=48000 * 3600,
|
||||||
units="Samples",
|
units="Samples",
|
||||||
name="Marker 1 {Part=1}",
|
name="Marker 1 {Part=1}",
|
||||||
comments=""
|
comments=""
|
||||||
),
|
),
|
||||||
doc_entity.MarkerDescriptor(number=2,
|
doc_entity.MarkerDescriptor(number=2,
|
||||||
location="01:00:01:00",
|
location="01:00:01:00",
|
||||||
time_reference=48000 * 61,
|
time_reference=48000 * 3601,
|
||||||
units="Samples",
|
units="Samples",
|
||||||
name="Marker 2 {Part=2}",
|
name="Marker 2 {Part=2}",
|
||||||
comments="[M1]"
|
comments="[M1]"
|
||||||
@@ -4,7 +4,7 @@ import os.path
|
|||||||
|
|
||||||
|
|
||||||
class TaggingIntegratedTests(unittest.TestCase):
|
class TaggingIntegratedTests(unittest.TestCase):
|
||||||
path = os.path.dirname(__file__) + '/export_cases/Tag Tests/Tag Tests.txt'
|
path = os.path.dirname(__file__) + '/../export_cases/Tag Tests/Tag Tests.txt'
|
||||||
|
|
||||||
def test_event_list(self):
|
def test_event_list(self):
|
||||||
with open(self.path, 'r') as f:
|
with open(self.path, 'r') as f:
|
||||||
Reference in New Issue
Block a user