13 Commits

Author SHA1 Message Date
Jamie Hardt
6ca1e5532d Update setup.py 2020-10-10 23:14:57 -07:00
Jamie Hardt
a0d386b666 Update setup.py 2020-10-10 23:13:05 -07:00
Jamie Hardt
3226e63f1d Update __init__.py 2020-10-10 23:05:07 -07:00
Jamie Hardt
3a597b5046 Update __init__.py
Version 0.5
2020-10-10 23:02:59 -07:00
Jamie Hardt
b5d9b5acc2 Update setup.py
Added package_data
2020-10-10 22:59:44 -07:00
Jamie Hardt
9f2a080f6b Enhanced Avid marker export 2020-05-18 19:08:59 -07:00
Jamie Hardt
1903e2a1f9 Update SRT.xsl
Changed "encoding" attribute to something that should work better.
2020-05-17 13:52:01 -07:00
Jamie Hardt
69491d98d7 Create SRT.xsl
Added XSLT for creating SRT subtitles.
2020-05-17 12:50:02 -07:00
Jamie Hardt
7816f08912 Update __main__.py
Fixed typo
2020-05-17 12:49:37 -07:00
Jamie Hardt
44388c6b7d Update __main__.py
Fixed text formatting
2020-05-17 11:52:36 -07:00
Jamie Hardt
9daedca4de More documentation
Documentation of new command-line opts.
2020-05-17 11:46:29 -07:00
Jamie Hardt
93a014bdc0 Added command to extract single reels 2020-05-17 11:27:06 -07:00
Jamie Hardt
9bb2ae136a Added some more documentation 2020-05-15 18:11:07 -07:00
8 changed files with 155 additions and 37 deletions

View File

@@ -1,24 +1,38 @@
.\" Manpage for ptulsconv
.\" Contact https://github.com/iluvcapra/ptulsconv
.TH ptulsconv 1 "12 Feb 2020" "0.3.3" "ptulsconv man page"
.TH ptulsconv 1 "15 May 2020" "0.4.0" "ptulsconv man page"
.SH NAME
.BR "ptulsconv" " \- convert
.IR "Avid Pro Tools" " text exports"
.SH SYNOPSIS
ptulsconv [OPTIONS] Export.txt
.SH DESCRIPTION
Description
Convert a Pro Tools text export into a flat list of clip names with timecodes. A tagging
language is interpreted to add columns and type the data. The default output format is
an XML file for import into Filemaker Pro.
.SH OPTIONS
.IP "-h, --help"
show a help message and exit
show a help message and exit.
.TP
.RI "-i " "TC"
Don't output events before this timecode, and offset all remaining
events to start at this timecode.
Drop events before this timecode.
.TP
.RI "-o " "TC"
Don't output events occurring after this timecode.
.SH SEE ALSO
See Also
Drop events after this timecode.
.TP
.RI "-m "
Include muted clips.
.TP
.RI "--json "
Output a JSON document instead of XML. (--xform will have no effect.)
.TP
.RI "--xform " "NAME"
Convert the output with a built-in output transform.
.TP
.RI "--show-available-tags"
Print a list of tags that are interpreted and exit.
.TP
.RI "--show-available-transforms"
Print a list of built-in output transforms and exit.
.SH AUTHOR
Jamie Hardt (contact at https://github.com/iluvcapra/ptulsconv)

View File

@@ -2,6 +2,6 @@ from .ptuls_grammar import protools_text_export_grammar
from .ptuls_parser_visitor import DictionaryParserVisitor
from .transformations import TimecodeInterpreter
__version__ = '0.4.0'
__version__ = '0.5.1'
__author__ = 'Jamie Hardt'
__license__ = 'MIT'

View File

@@ -7,34 +7,52 @@ import sys
import traceback
def main():
parser = OptionParser()
parser.usage = "ptulsconv TEXT_EXPORT.txt"
parser.add_option('-i', dest='in_time', help="Don't output events occurring before this timecode, and offset"
" all events relative to this timecode.", metavar='TC')
parser.add_option('-o', dest='out_time', help="Don't output events occurring after this timecode.", metavar='TC')
filter_opts = OptionGroup(title='Filtering Options', parser=parser)
filter_opts.add_option('-i', dest='in_time', help="Don't output events occurring before this timecode.",
metavar='TC')
filter_opts.add_option('-o', dest='out_time', help="Don't output events occurring after this timecode.",
metavar='TC')
# parser.add_option('-P', '--progress', default=False, action='store_true', dest='show_progress',
# help='Show progress bar.')
parser.add_option('-m', '--include-muted', default=False, action='store_true', dest='include_muted',
help='Read muted clips.')
filter_opts.add_option('-m', '--include-muted', default=False, action='store_true', dest='include_muted',
help='Include muted clips.')
parser.add_option('--show-available-tags', dest='show_tags',
action='store_true',
default=False, help='Display tag mappings for the FMP XML output style and exit.')
filter_opts.add_option('-R', '--reel', dest='select_reel', help="Output only events in reel N, and recalculate "
" start times relative to that reel's start time.",
default=None, metavar='N')
parser.add_option('--show-available-transforms', dest='show_transforms',
action='store_true',
default=False, help='Display available built-in XSLT transforms.')
parser.add_option_group(filter_opts)
output_opts = OptionGroup(title="Output Options", parser=parser)
output_opts.add_option('--json', default=False, action='store_true', dest='write_json',
help='Output a JSON document instead of XML. If this option is enabled, --xform will have '
'no effect.')
output_opts.add_option('--xform', dest='xslt', help="Convert with built-is XSLT transform.",
default=None, metavar='NAME')
output_opts.add_option('--show-available-tags', dest='show_tags',
action='store_true',
default=False, help='Display tag mappings for the FMP XML output style and exit.')
output_opts.add_option('--show-available-transforms', dest='show_transforms',
action='store_true',
default=False, help='Display available built-in XSLT transforms.')
parser.add_option_group(output_opts)
parser.add_option('--xform', dest='xslt', help="Convert with built-is XSLT transform.",
default=None, metavar='NAME')
(options, args) = parser.parse_args(sys.argv)
print_banner_style("%s %s (c) 2020 %s. All rights reserved." % (__name__, __version__, __author__))
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:
dump_field_map('ADR')
@@ -66,8 +84,12 @@ def main():
print_status_style("Muted regions are ignored.")
try:
convert(input_file=args[1], start=options.in_time, end=options.out_time,
include_muted=options.include_muted, xsl=options.xslt,
output_format = 'fmpxml'
if options.write_json:
output_format = 'json'
convert(input_file=args[1], output_format=output_format, start=options.in_time, end=options.out_time,
include_muted=options.include_muted, xsl=options.xslt, select_reel=options.select_reel,
progress=False, output=sys.stdout, log_output=sys.stderr)
except FileNotFoundError as e:
print_fatal_error("Error trying to read input file")

View File

@@ -139,11 +139,11 @@ def fmp_transformed_dump(data, input_file, xsl_name, output):
xsl_path = os.path.join(pathlib.Path(__file__).parent.absolute(), 'xslt', xsl_name + ".xsl")
print_status_style("Using xsl: %s" % (xsl_path))
result = subprocess.run(['xsltproc', xsl_path, '-'], input=strdata, text=True,
subprocess.run(['xsltproc', xsl_path, '-'], input=strdata, text=True,
stdout=output, shell=False, check=True)
def convert(input_file, output_format='fmpxml', start=None, end=None,
def convert(input_file, output_format='fmpxml', start=None, end=None, select_reel=None,
progress=False, include_muted=False, xsl=None,
output=sys.stdout, log_output=sys.stderr):
with open(input_file, 'r') as file:
@@ -176,6 +176,11 @@ def convert(input_file, output_format='fmpxml', start=None, end=None,
subclipxform = ptulsconv.transformations.SubclipOfSequence(start=start_fs, end=end_fs)
parsed = subclipxform.transform(parsed)
if select_reel is not None:
reel_xform = ptulsconv.transformations.SelectReel(reel_num=select_reel)
parsed = reel_xform.transform(parsed)
if output_format == 'json':
json.dump(parsed, output)
elif output_format == 'fmpxml':

View File

@@ -7,6 +7,7 @@ from .reporting import print_advisory_tagging_error, print_section_header_style,
from tqdm import tqdm
class Transformation:
def transform(self, input_dict) -> dict:
return input_dict
@@ -163,7 +164,8 @@ class TagInterpreter(Transformation):
if clip['state'] == 'Muted' and self.ignore_muted:
continue
clip_tags = self.parse_tags(clip['clip_name'], parent_track_name=track['name'], clip_time=clip['start_time'])
clip_tags = self.parse_tags(clip['clip_name'], parent_track_name=track['name'],
clip_time=clip['start_time'])
clip_start = clip['start_time_decoded']['frame_count']
if clip_tags['mode'] == 'Normal':
event = dict()
@@ -176,6 +178,7 @@ class TagInterpreter(Transformation):
event['PT.Track.Name'] = track_tags['line']
event['PT.Session.Name'] = title_tags['line']
event['PT.Session.TimecodeFormat'] = input_dict['header']['timecode_format']
event['PT.Clip.Number'] = clip['event']
event['PT.Clip.Name'] = clip_tags['line']
event['PT.Clip.Start'] = clip['start_time']
@@ -194,11 +197,14 @@ class TagInterpreter(Transformation):
transformed[-1]['PT.Clip.Name'] = transformed[-1]['PT.Clip.Name'] + " " + clip_tags['line']
transformed[-1]['PT.Clip.Finish_Frames'] = clip['end_time_decoded']['frame_count']
transformed[-1]['PT.Clip.Finish'] = clip['end_time']
transformed[-1]['PT.Clip.Finish_Seconds'] = clip['end_time_decoded']['frame_count'] / input_dict['header'][
'timecode_format']
transformed[-1]['PT.Clip.Finish_Seconds'] = clip['end_time_decoded']['frame_count'] / \
input_dict['header']['timecode_format']
elif clip_tags['mode'] == 'Timespan':
rule = dict(start_time=clip_start,
rule = dict(start_time_literal=clip['start_time'],
start_time=clip_start,
start_time_seconds=clip_start / input_dict['header']['timecode_format'],
end_time=clip['end_time_decoded']['frame_count'],
tags=clip_tags['tags'])
timespan_rules.append(rule)
@@ -211,7 +217,17 @@ class TagInterpreter(Transformation):
for rule in rules:
if rule['start_time'] <= time <= rule['end_time']:
tag_keys = list(rule['tags'].keys())
tag_times = dict()
for key in tag_keys:
key: str
time_value = rule['start_time']
tag_times["Timespan." + key + ".Start_Frames"] = time_value
tag_times["Timespan." + key + ".Start"] = rule['start_time_literal']
tag_times["Timespan." + key + ".Start_Seconds"] = rule['start_time_seconds']
retval.update(rule['tags'])
retval.update(tag_times)
return retval
@@ -244,6 +260,26 @@ class TagInterpreter(Transformation):
return self.visitor.visit(parse_tree)
class SelectReel(Transformation):
def __init__(self, reel_num):
self.reel_num = reel_num
def transform(self, input_dict) -> dict:
out_events = []
for event in input_dict['events']:
if event['Reel'] == str(self.reel_num):
offset = event.get('Timespan.Reel.Start_Frames', 0)
offset_sec = event.get('Timespan.Reel.Start_Seconds', 0.)
event['PT.Clip.Start_Frames'] -= offset
event['PT.Clip.Finish_Frames'] -= offset
event['PT.Clip.Start_Seconds'] -= offset_sec
event['PT.Clip.Finish_Seconds'] -= offset_sec
out_events.append(event)
return dict(header=input_dict['header'], events=out_events)
class SubclipOfSequence(Transformation):
def __init__(self, start, end):
@@ -252,8 +288,8 @@ class SubclipOfSequence(Transformation):
def transform(self, input_dict: dict) -> dict:
out_events = []
offset = self.start
offset_sec = self.start / input_dict['header']['timecode_format']
offset = 0 #self.start
offset_sec = 0. #self.start / input_dict['header']['timecode_format']
for event in input_dict['events']:
if self.start <= event['PT.Clip.Start_Frames'] <= self.end:
e = event
@@ -263,4 +299,4 @@ class SubclipOfSequence(Transformation):
e['PT.Clip.Finish_Seconds'] = event['PT.Clip.Finish_Seconds'] - offset_sec
out_events.append(e)
return dict(events=out_events)
return dict(header=input_dict['header'], events=out_events)

View File

@@ -37,8 +37,16 @@
<AvProp id="ATTR" name="OMFI:ATTB:Kind" type="int32">2</AvProp>
<AvProp id="ATTR" name="OMFI:ATTB:Name" type="string">_ATN_CRM_COM</AvProp>
<AvProp id="ATTR" name="OMFI:ATTB:StringAttribute" type="string">
<xsl:value-of select="concat(fmp:COL[15]/fmp:DATA, ': ', fmp:COL[21]/fmp:DATA)"/>
[Reason: <xsl:value-of select="fmp:COL[18]/fmp:DATA" />]</AvProp>
<xsl:value-of select="concat('(',fmp:COL[14]/fmp:DATA,') ',fmp:COL[15]/fmp:DATA, ': ', fmp:COL[21]/fmp:DATA, ' ')"/>
<xsl:choose>
<xsl:when test="fmp:COL[18]/fmp:DATA != ''">[Reason: <xsl:value-of select="fmp:COL[18]/fmp:DATA" />]
</xsl:when>
<xsl:otherwise> </xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="fmp:COL[23]/fmp:DATA != ''">[Note: <xsl:value-of select="fmp:COL[23]/fmp:DATA" />]</xsl:when>
</xsl:choose>
</AvProp>
</ListElem>
<ListElem>
<AvProp id="ATTR" name="OMFI:ATTB:Kind" type="int32">2</AvProp>

30
ptulsconv/xslt/SRT.xsl Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fmp="http://www.filemaker.com/fmpxmlresult">
<xsl:output method="text" encoding="windows-1252"/>
<xsl:template match="/">
<xsl:for-each select="/fmp:FMPXMLRESULT/fmp:RESULTSET/fmp:ROW">
<xsl:sort data-type="number" select="number(fmp:COL[9]/fmp:DATA)" />
<xsl:value-of select="concat(position() ,'&#xA;')" />
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[9]/fmp:DATA) div 3600),'00'), ':')" />
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[9]/fmp:DATA) div 60),'00'), ':')" />
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[9]/fmp:DATA) mod 60),'00'), ',')" />
<xsl:value-of select="format-number((number(fmp:COL[9]/fmp:DATA) - floor(number(fmp:COL[9]/fmp:DATA))) * 1000,'000')" />
<xsl:text> --> </xsl:text>
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[10]/fmp:DATA) div 3600),'00'), ':')" />
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[10]/fmp:DATA) div 60),'00'), ':')" />
<xsl:value-of select="concat(format-number(floor(number(fmp:COL[10]/fmp:DATA) mod 60),'00'), ',')" />
<xsl:value-of select="format-number((number(fmp:COL[10]/fmp:DATA) - floor(number(fmp:COL[10]/fmp:DATA))) * 1000,'000')" />
<xsl:value-of select="concat('&#xA;',fmp:COL[15]/fmp:DATA, ': ', fmp:COL[21]/fmp:DATA)"/>
<xsl:value-of select="'&#xA;&#xA;'" />
</xsl:for-each>
</xsl:template>
</xsl:transform>

View File

@@ -31,9 +31,12 @@ setup(name='ptulsconv',
packages=['ptulsconv'],
keywords='text-processing parsers film tv editing editorial',
install_requires=['parsimonious', 'tqdm'],
package_data={
"ptulsconv": ["xslt/*.xsl"]
},
entry_points={
'console_scripts': [
'ptulsconv = ptulsconv.__main__:main'
]
}
)
)