mirror of
https://github.com/iluvcapra/ptulsconv.git
synced 2026-10-02 01:37:17 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c4f6faf2a | |||
| 754e4d5251 | |||
| 888d9aa5d9 | |||
| bd0b09d1db | |||
| 3c15237ed2 |
@@ -42,6 +42,13 @@ doc = [
|
||||
"sphinx-rtd-theme>=3.0.2",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
ignore = [
|
||||
"DTZ005" # datetime created without a tz
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"reaper/Export Items as Text.py" = ["F821"]
|
||||
|
||||
[project.scripts]
|
||||
ptulsconv = "ptulsconv:__main__.main"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# type: ignore[reportUndefinedVariable]
|
||||
|
||||
# Export Items as Text.py
|
||||
# (c) 2021 Jamie Hardt. All rights reserved.
|
||||
#
|
||||
#
|
||||
|
||||
import datetime
|
||||
import json
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
item_records = list()
|
||||
item_records = []
|
||||
|
||||
for i in range(RPR_CountMediaItems(0)):
|
||||
this_item = RPR_GetMediaItem(0, i)
|
||||
|
||||
item_record = {}
|
||||
item_record["mute"] = (
|
||||
True if RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0.0 else False
|
||||
)
|
||||
item_record["mute"] = RPR_GetMediaItemInfo_Value(this_item, "B_MUTE_ACTUAL") > 0.0
|
||||
|
||||
item_record["duration"] = RPR_GetMediaItemInfo_Value(this_item, "D_LENGTH")
|
||||
_, item_record["duration_tc"], _, _, _ = RPR_format_timestr_len(
|
||||
@@ -26,9 +26,7 @@ for i in range(RPR_CountMediaItems(0)):
|
||||
item_record["position"], "", 128, 5
|
||||
)
|
||||
|
||||
item_record["selected"] = (
|
||||
True if RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0.0 else False
|
||||
)
|
||||
item_record["selected"] = RPR_GetMediaItemInfo_Value(this_item, "B_UISEL") > 0.0
|
||||
_, _, _, item_record["notes"], _ = RPR_GetSetMediaItemInfo_String(
|
||||
this_item, "P_NOTES", "", False
|
||||
)
|
||||
@@ -54,13 +52,11 @@ for i in range(RPR_CountMediaItems(0)):
|
||||
item_record["track_index"] = RPR_GetMediaTrackInfo_Value(
|
||||
item_track, "IP_TRACKNUMBER"
|
||||
)
|
||||
item_record["track_muted"] = (
|
||||
True if RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0.0 else False
|
||||
)
|
||||
item_record["track_muted"] = RPR_GetMediaTrackInfo_Value(item_track, "B_MUTE") > 0.0
|
||||
|
||||
item_records = item_records + [item_record]
|
||||
|
||||
output = dict()
|
||||
output = {}
|
||||
output["items"] = item_records
|
||||
_, output["project_title"], _ = RPR_GetProjectName(0, "", 1024)
|
||||
_, _, output["project_author"], _ = RPR_GetSetProjectAuthor(0, False, "", 1024)
|
||||
@@ -76,15 +72,14 @@ output_title = output["project_title"]
|
||||
if output_title == "":
|
||||
output_title = "unsaved project"
|
||||
|
||||
output_file_name = "%s Text Export %s.txt" % (output_title, now.strftime("%Y%m%d_%H%M"))
|
||||
output_file_name = f"{output_title} Text Export {now.strftime('%Y%m%d_%H%M')}.txt"
|
||||
output_path = output_path + "/" + output_file_name
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(output, f, allow_nan=True, indent=4)
|
||||
|
||||
RPR_ShowMessageBox(
|
||||
'Exported text file "%s" to project folder.' % output_file_name,
|
||||
"Text Export Complete",
|
||||
f'Exported text file "{output_file_name}" to project folder. Text Export Complete',
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ def main():
|
||||
(options, args) = parser.parse_args(sys.argv)
|
||||
|
||||
print_section_header_style("Startup")
|
||||
print_status_style("This run started %s" % (datetime.datetime.now().isoformat()))
|
||||
print_status_style(f"This run started {datetime.datetime.now().isoformat()}")
|
||||
|
||||
if options.show_tags:
|
||||
dump_field_map()
|
||||
@@ -124,17 +124,6 @@ def main():
|
||||
input_file=args[1], major_mode=major_mode, warnings=options.warnings
|
||||
)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print_fatal_error("Error trying to read input file")
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
print_fatal_error("Error trying to convert file")
|
||||
print("\033[31m" + e.__repr__() + "\033[0m", file=sys.stderr)
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
Useful functions for parsing and working with timecode.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from fractions import Fraction
|
||||
from typing import Optional, SupportsFloat
|
||||
from typing import SupportsFloat
|
||||
|
||||
|
||||
class TimecodeFormat(
|
||||
@@ -16,7 +18,7 @@ class TimecodeFormat(
|
||||
A struct reperesenting a timecode datum.
|
||||
"""
|
||||
|
||||
def smpte_to_seconds(self, smpte: str) -> Optional[Fraction]:
|
||||
def smpte_to_seconds(self, smpte: str) -> Fraction | None:
|
||||
frame_count = smpte_to_frame_count(
|
||||
smpte, self.logical_fps, drop_frame_hint=self.drop_frame
|
||||
)
|
||||
@@ -32,7 +34,7 @@ class TimecodeFormat(
|
||||
|
||||
def smpte_to_frame_count(
|
||||
smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False
|
||||
) -> Optional[int]:
|
||||
) -> int | None:
|
||||
"""
|
||||
Convert a string with a SMPTE timecode representation into a frame count.
|
||||
|
||||
@@ -83,7 +85,7 @@ def frame_count_to_smpte(
|
||||
frame_count: int,
|
||||
frames_per_logical_second: int,
|
||||
drop_frame: bool = False,
|
||||
fractional_frame: Optional[float] = None,
|
||||
fractional_frame: float | None = None,
|
||||
) -> str:
|
||||
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60]
|
||||
assert fractional_frame is None or fractional_frame < 1.0
|
||||
@@ -104,20 +106,21 @@ def frame_count_to_smpte(
|
||||
ss, ff = divmod(rem, frames_per_logical_second)
|
||||
|
||||
hh = hh % 24
|
||||
|
||||
hh = int(hh)
|
||||
mm = int(mm)
|
||||
ss = int(ss)
|
||||
ff = int(ff)
|
||||
|
||||
if fractional_frame is not None and fractional_frame > 0:
|
||||
return "%02i:%02i:%02i%s%02i%s" % (
|
||||
hh,
|
||||
mm,
|
||||
ss,
|
||||
separator,
|
||||
ff,
|
||||
("%.3f" % fractional_frame)[1:],
|
||||
)
|
||||
fpart = "{fractional_frame:.3f}"[1:]
|
||||
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}{fpart}"
|
||||
else:
|
||||
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||
return f"{hh:02}:{mm:02}:{ss:02}{separator}{ff:02}"
|
||||
# "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff)
|
||||
|
||||
|
||||
def footage_to_frame_count(footage_string) -> Optional[int]:
|
||||
def footage_to_frame_count(footage_string) -> int | None:
|
||||
m = re.search(r"(\d+)\+(\d+)(\.\d+)?", footage_string)
|
||||
if m is None:
|
||||
return None
|
||||
@@ -131,4 +134,4 @@ def footage_to_frame_count(footage_string) -> Optional[int]:
|
||||
|
||||
def frame_count_to_footage(frame_count):
|
||||
feet, frm = divmod(frame_count, 16)
|
||||
return "%i+%02i" % (feet, frm)
|
||||
return f"{feet}+{frm:02}"
|
||||
|
||||
+19
-21
@@ -3,6 +3,8 @@ This module provides the main input document parsing and transform
|
||||
implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import os
|
||||
@@ -11,7 +13,7 @@ from collections.abc import Iterator
|
||||
from fractions import Fraction
|
||||
from itertools import chain
|
||||
from json import JSONEncoder
|
||||
from typing import List, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import ptsl
|
||||
|
||||
@@ -39,26 +41,25 @@ class FractionEncoder(JSONEncoder):
|
||||
as a dict.
|
||||
"""
|
||||
|
||||
force_denominator: Optional[int]
|
||||
force_denominator: int | None
|
||||
|
||||
def default(self, o):
|
||||
""" """
|
||||
if isinstance(o, Fraction):
|
||||
return dict(numerator=o.numerator, denominator=o.denominator)
|
||||
return {"numerator": o.numerator, "denominator": o.denominator}
|
||||
else:
|
||||
return o.__dict__
|
||||
|
||||
|
||||
def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
||||
def output_adr_csv(lines: list[ADRLine], time_format: TimecodeFormat):
|
||||
"""
|
||||
Writes ADR lines as CSV to the current working directory. Creates
|
||||
directories for each character number and name pair, and within that
|
||||
directory, creates a CSV file for each reel.
|
||||
"""
|
||||
|
||||
reels: set[str | None] = set([ln.reel for ln in lines])
|
||||
reels: set[str | None] = {ln.reel for ln in lines}
|
||||
for n, name in [(n.character_id, n.character_name) for n in lines]:
|
||||
dir_name = "%s_%s" % (n, name)
|
||||
dir_name = f"{n}_{name}"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
os.chdir(dir_name)
|
||||
for reel in reels:
|
||||
@@ -69,11 +70,8 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
||||
if len(these_lines) == 0:
|
||||
continue
|
||||
|
||||
outfile_name = "%s_%s_%s_%s.csv" % (
|
||||
these_lines[0].title,
|
||||
n,
|
||||
these_lines[0].character_name,
|
||||
reel,
|
||||
outfile_name = (
|
||||
f"{these_lines[0].title}_{n}_{these_lines[0].character_name}_{reel}.csv"
|
||||
)
|
||||
|
||||
with open(outfile_name, mode="w", newline="") as outfile:
|
||||
@@ -119,13 +117,13 @@ def output_adr_csv(lines: List[ADRLine], time_format: TimecodeFormat):
|
||||
os.chdir("..")
|
||||
|
||||
|
||||
def generate_documents(session_tc_format, scenes, adr_lines: List[ADRLine], title):
|
||||
def generate_documents(session_tc_format, scenes, adr_lines: list[ADRLine], title):
|
||||
"""
|
||||
Create PDF output.
|
||||
"""
|
||||
print_section_header_style("Creating PDF Reports")
|
||||
report_date = datetime.datetime.now()
|
||||
reports_dir = "%s_%s" % (title, report_date.strftime("%Y-%m-%d_%H%M%S"))
|
||||
reports_dir = f"{title}_{report_date.strftime('%Y-%m-%d_%H%M%S')}"
|
||||
os.makedirs(reports_dir, exist_ok=False)
|
||||
os.chdir(reports_dir)
|
||||
|
||||
@@ -155,7 +153,7 @@ def generate_documents(session_tc_format, scenes, adr_lines: List[ADRLine], titl
|
||||
|
||||
|
||||
def create_adr_reports(
|
||||
lines: List[ADRLine], tc_display_format: TimecodeFormat, reel_list: List[str]
|
||||
lines: list[ADRLine], tc_display_format: TimecodeFormat, reel_list: list[str]
|
||||
):
|
||||
"""
|
||||
Creates a directory heirarchy and a respective set of ADR reports,
|
||||
@@ -240,18 +238,18 @@ def convert(major_mode, input_file=None, output=sys.stdout, warnings=True):
|
||||
)
|
||||
|
||||
# TODO: Breakdown by titles
|
||||
titles = set([x.title for x in (generic_events + adr_lines)])
|
||||
titles = {x.title for x in (generic_events + adr_lines)}
|
||||
if len(titles) != 1:
|
||||
print_warning(
|
||||
"Multiple titles per export is not supported, "
|
||||
"found multiple titles: %s Exiting." % titles
|
||||
f"found multiple titles: {titles} Exiting."
|
||||
)
|
||||
exit(-1)
|
||||
sys.exit(-1)
|
||||
|
||||
title = list(titles)[0]
|
||||
title = next(iter(titles))
|
||||
|
||||
print_status_style("%i generic events found." % len(generic_events))
|
||||
print_status_style("%i ADR events found." % len(adr_lines))
|
||||
print_status_style(f"{len(generic_events)} generic events found.")
|
||||
print_status_style(f"{len(adr_lines)} ADR events found.")
|
||||
|
||||
if warnings:
|
||||
perform_adr_validations(iter(adr_lines))
|
||||
|
||||
@@ -131,7 +131,6 @@ class TagCompiler:
|
||||
return effective_tags
|
||||
|
||||
def parse_data(self) -> Iterator[Intermediate]:
|
||||
|
||||
for track, clip, start, finish, _ in self.session.track_clips_timed():
|
||||
if clip.state == "Muted":
|
||||
continue
|
||||
@@ -153,7 +152,6 @@ class TagCompiler:
|
||||
|
||||
@staticmethod
|
||||
def apply_appends(parsed: Iterator[Intermediate]) -> Iterator[Intermediate]:
|
||||
|
||||
def should_append(a, b):
|
||||
return b.clip_tag_mode == TagPreModes.APPEND and b.start >= a.finish
|
||||
|
||||
@@ -177,7 +175,6 @@ class TagCompiler:
|
||||
def collect_time_spans(
|
||||
parsed: Iterator[Intermediate],
|
||||
) -> Iterator[tuple[Intermediate, tuple[dict, Fraction, Fraction]]]:
|
||||
|
||||
time_spans = []
|
||||
|
||||
for item in parsed:
|
||||
@@ -199,7 +196,6 @@ class TagCompiler:
|
||||
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)
|
||||
|
||||
for event, time_spans in parsed_with_time_spans:
|
||||
@@ -226,7 +222,7 @@ class TagCompiler:
|
||||
|
||||
|
||||
def apply_appends(
|
||||
source: Iterator, should_append: Callable, do_append: Callable
|
||||
source: Iterator, should_append: Callable, preform_append: Callable
|
||||
) -> Generator:
|
||||
"""
|
||||
:param source:
|
||||
@@ -240,7 +236,7 @@ def apply_appends(
|
||||
this_element = next(source)
|
||||
for element in source:
|
||||
if should_append(this_element, element):
|
||||
this_element = do_append(this_element, element)
|
||||
this_element = preform_append(this_element, element)
|
||||
else:
|
||||
yield this_element
|
||||
this_element = element
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""
|
||||
Methods for converting string reprentations of film footage.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from fractions import Fraction
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def footage_to_seconds(footage: str) -> Optional[Fraction]:
|
||||
def footage_to_seconds(footage: str) -> Fraction | None:
|
||||
"""
|
||||
Converts a string representation of a footage (35mm, 24fps)
|
||||
into a :class:`Fraction`, this fraction being a some number of
|
||||
|
||||
+18
-10
@@ -1,11 +1,13 @@
|
||||
import datetime
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
from importlib.metadata import version as module_version
|
||||
from xml.etree.ElementTree import TreeBuilder, tostring
|
||||
|
||||
import ptulsconv
|
||||
@@ -15,13 +17,16 @@ from ptulsconv.docparser.adr_entity import ADRLine
|
||||
|
||||
|
||||
def avid_marker_list(
|
||||
lines: List[ADRLine],
|
||||
report_date=datetime.datetime.now(),
|
||||
lines: list[ADRLine],
|
||||
report_date: datetime | None = None,
|
||||
reel_start_frame=0,
|
||||
fps=24,
|
||||
):
|
||||
doc = TreeBuilder(element_factory=None)
|
||||
|
||||
if report_date is None:
|
||||
report_date = datetime.now()
|
||||
|
||||
doc.start("Avid:StreamItems", {"xmlns:Avid": "http://www.avid.com"})
|
||||
doc.start("Avid:XMLFileData", {})
|
||||
doc.start("AvProp", {"name": "DomainMagic", "type": "string"})
|
||||
@@ -57,6 +62,8 @@ def avid_marker_list(
|
||||
|
||||
doc.start("List", {"id": "OMFI:ATTR:AttrRefs"})
|
||||
|
||||
assert report_date
|
||||
|
||||
insert_elem(
|
||||
"1",
|
||||
"OMFI:ATTB:IntAttribute",
|
||||
@@ -75,7 +82,7 @@ def avid_marker_list(
|
||||
line.supervisor or "",
|
||||
)
|
||||
|
||||
marker_name = "%s: %s" % (line.cue_number, line.prompt)
|
||||
marker_name = f"{line.cue_number}: {line.prompt}"
|
||||
insert_elem(
|
||||
"2", "OMFI:ATTB:StringAttribute", "string", "_ATN_CRM_COM", marker_name
|
||||
)
|
||||
@@ -112,7 +119,8 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
|
||||
doc.data("0")
|
||||
doc.end("ERRORCODE")
|
||||
|
||||
doc.start("PRODUCT", {"NAME": ptulsconv.__name__, "VERSION": ptulsconv.__version__})
|
||||
version = module_version("ptulsconv")
|
||||
doc.start("PRODUCT", {"NAME": ptulsconv.__name__, "VERSION": f"{version}"})
|
||||
doc.end("PRODUCT")
|
||||
|
||||
doc.start(
|
||||
@@ -147,7 +155,7 @@ def dump_fmpxml(data, input_file_name, output, adr_field_map):
|
||||
doc.start("COL", {})
|
||||
doc.start("DATA", {})
|
||||
for key_attempt in field[0]:
|
||||
if key_attempt in event.keys():
|
||||
if key_attempt in event:
|
||||
doc.data(str(event[key_attempt]))
|
||||
break
|
||||
doc.end("DATA")
|
||||
@@ -170,7 +178,7 @@ def xform_options():
|
||||
|
||||
def dump_xform_options(output=sys.stdout):
|
||||
print("# Available transforms:", file=output)
|
||||
print("# Transform dir: %s" % xslt_path, file=output)
|
||||
print(f"# Transform dir: {xslt_path}", file=output)
|
||||
for f in xform_options():
|
||||
base = os.path.basename(f)
|
||||
name, _ = os.path.splitext(base)
|
||||
@@ -188,14 +196,14 @@ def fmp_transformed_dump(data, input_file, xsl_name, output, adr_field_map):
|
||||
dump_fmpxml(data, input_file, pipe, adr_field_map)
|
||||
|
||||
str_data = pipe.getvalue()
|
||||
print_status_style("Base XML size %i" % (len(str_data)))
|
||||
print_status_style(f"Base XML size {len(str_data)}")
|
||||
|
||||
print_status_style("Running xsltproc")
|
||||
|
||||
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(f"Using xsl: {xsl_path}")
|
||||
subprocess.run(
|
||||
["xsltproc", xsl_path, "-"],
|
||||
input=str_data,
|
||||
|
||||
Reference in New Issue
Block a user