diff --git a/INSTALLING.md b/INSTALLING.md index b0286c9..9d71076 100644 --- a/INSTALLING.md +++ b/INSTALLING.md @@ -1,4 +1,10 @@ -## Installing +# Installing + +## Minimum Requirements + +py-ptsl requires Python 3.11. + +## Steps py-ptsl can be installed by cloning the repository from Github, and then installing the package with `pip`. diff --git a/doc/source/engine.rst b/doc/source/engine.rst index 7c9997a..67b9c9b 100644 --- a/doc/source/engine.rst +++ b/doc/source/engine.rst @@ -1,11 +1,10 @@ +.. currentmodule:: ptsl + Engine Class ============ -.. currentmodule:: ptsl - .. automethod:: engine.open_engine .. autoclass:: Engine :members: :member-order: bysource - :special-members: __init__ diff --git a/doc/source/ptsl_versions.rst b/doc/source/ptsl_versions.rst index 0438a6c..e1a59ee 100644 --- a/doc/source/ptsl_versions.rst +++ b/doc/source/ptsl_versions.rst @@ -7,4 +7,5 @@ PTSL Versions/Pro Tools Versions +==============+====================+ | 1 | 2022.12 | | | 2023.3 | +| | 2023.6 | +--------------+--------------------+ diff --git a/ptsl/__init__.py b/ptsl/__init__.py index 45045c4..336c294 100644 --- a/ptsl/__init__.py +++ b/ptsl/__init__.py @@ -1,5 +1,11 @@ """ -py-ptsl: Native Python client for the Pro Tools scripting RPC interface +Native Python PTSL (Pro Tools Scripting Language) RPC interface + +This project is a work of Pro Tools enthusiasts and users and is not +affiliated with Avid. + +For more information on this package see the github repository at +https://github.com/iluvcapra/py-ptsl/ """ from .client import Client diff --git a/ptsl/client.py b/ptsl/client.py index fef3ea1..414dbf4 100644 --- a/ptsl/client.py +++ b/ptsl/client.py @@ -34,7 +34,7 @@ def open_client(*args, **kwargs): class Auditor: """ The Auditor is used by the client for reporting-out the status of requests - as the are run. + as they are run. """ def __init__(self, enabled: bool) -> None: @@ -75,7 +75,7 @@ def run_returning(self): class Client: """ - The Client class + The Client class: - maintains the grpc stub and channel - holds PTSL server session data - manages the connection's registration @@ -131,13 +131,16 @@ def __init__(self, raise grpc_error - def run(self, operation: Operation): + def run(self, operation: Operation) -> None: """ Run an operation on the client. - + :raises: `CommandError` if the server returns an error """ + self.auditor.run_called(operation.command_id()) + + # convert the request body into JSON request_body_json = self._prepare_operation_request_json(operation) response = self._send_sync_request(operation.command_id(), request_body_json) @@ -162,6 +165,10 @@ def run(self, operation: Operation): self.auditor.run_returning() def _prepare_operation_request_json(self, operation): + """ + Convert the request body into a JSON string (or an empty string if + there is no request body. + """ if operation.request is None: request_body_json = "" else: @@ -176,6 +183,13 @@ def _prepare_operation_request_json(self, operation): return request_body_json def _response_error_json_cleanup(self, json_in: str) -> str: + """ + This is a shim that will take a `command_error_type` value + from the response error json and convert it into a PT_UnknownError + if the `command_error_type` value is mis-formatted by the server + (for instance, if the server returns a symbold name or numeric string + value, as it sometimes has done in the past. (See `errata`). + """ error_dict = json.loads(json_in) old_val = error_dict['command_error_type'] if isinstance(old_val, str): @@ -190,6 +204,14 @@ def _response_error_json_cleanup(self, json_in: str) -> str: return json.dumps(error_dict) def _handle_completed_response(self, operation, response): + """ + Accept the response message from the server, parse + the response body JSON if present, and hand the results + to the operation. + + If the operation provides a cleanup function, this is run on + the response body JSON prior to parsing. + """ p = operation.__class__.response_body() if len(response.response_body_json) > 0 and p is not None: self.auditor.response_json_before_cleanup( @@ -206,7 +228,9 @@ def _handle_completed_response(self, operation, response): def _send_sync_request(self, command_id, request_body_json, task_id="") -> pt.Response: - + """ + Send a synchronous request to the server. + """ request = pt.Request( header=pt.RequestHeader( task_id=task_id, diff --git a/ptsl/engine.py b/ptsl/engine.py index 582090d..9b76f62 100644 --- a/ptsl/engine.py +++ b/ptsl/engine.py @@ -35,6 +35,26 @@ def open_engine(*args, **kwargs): class Engine: + """ + A callable interface for PTSL. + + The Engine exposes PTSL commands as methods, translating call + arguments into corresponding requests, and then translating + responses into return objects. So, instead of creating a request, + dispatching a command to the client with the request object, + receving a response (or error) and processing it, you can simply + call a method on the Engine with parameters, and that method returns + a value. + + One of the goals of the engine class is to hide as many redundant + value classes from the PTSL protocol as possible, so where the PTSL + client may return an enumeration value for the session sample rate, + the Engine returns a simple integer. Entity types, like :class:`Track` + or :class:`MemoryLocation` objects are retained. + + The `Engine` initializes a new :class:`Client` object by passing its + initialization parameters to :meth:`~ptsl.Client.__init__` + """ client: ptsl.Client @@ -43,20 +63,7 @@ def __init__(self, application_name: Optional[str] = None, certificate_path: Optional[str] = None, address='localhost:31416'): - """ - Open the engine. - :param company_name: Company name - :param application_name: Application name - :param certificate_path: Path to a developer certificate - :param address: server:port to connect the engine to. - - .. note:: If `certificate_path` is given, the legacy - AuthorizeConnection method will be used for setting up the - connection session. If it is `None`, then `company_name` and - `application_name` will be used with the RegisterConnection - method (available since Pro Tools 2023.3). - """ self.client = ptsl.Client(certificate_path=certificate_path, company_name=company_name, application_name=application_name, @@ -100,8 +107,8 @@ def create_session(self, """ Create a new Pro Tools session. - :param name: Session Name - :param path: Path to the new session + :param str name: Session Name + :param str path: Path to the new session :param SessionAudioFormat file_type: file type, defaults to :attr:`~ptsl.PTSL_pb2.SessionAudioFormat.SAF_WAVE` :param SampleRate sample_rate: sample rate, defaults to @@ -110,7 +117,7 @@ def create_session(self, :attr:`~ptsl.PTSL_pb2.BitDepth.Bit24` :param IOSettings io_setting: The IO Setting to use, defaults to :attr:`~ptsl.PTSL_pb2.IOSettings.IO_Last` - :param is_interelaved: Interleaved state + :param bool is_interelaved: Interleaved state """ op = ops.CreateSession( @@ -151,7 +158,7 @@ def create_session_from_template( :attr:`~ptsl.PTSL_pb2.BitDepth.Bit24` :param IOSettings io_setting: The IO Setting to use, defaults to :attr:`~ptsl.PTSL_pb2.IOSettings.IO_Last` - :param is_interelaved: Interleaved state + :param bool is_interelaved: Interleaved state """ op = ops.CreateSession( @@ -193,7 +200,7 @@ def create_session_from_aaf( :attr:`~ptsl.PTSL_pb2.BitDepth.Bit24` :param IOSettings io_setting: The IO Setting to use, defaults to :attr:`~ptsl.PTSL_pb2.IOSettings.IO_Last` - :param is_interelaved: Interleaved state + :param bool is_interelaved: Interleaved state """ op = ops.CreateSession( @@ -235,8 +242,8 @@ def save_session_as(self, path: str, name: str): """ Save the currently-open session as a new name to a different path. - :param path: Path to the new session - :param name: New name for the session + :param str path: Path to the new session + :param str name: New name for the session """ op = ops.SaveSessionAs(session_name=name, session_location=path) self.client.run(op) @@ -413,6 +420,16 @@ def edit_memory_location(self, location_number: int, comments: str): """ Edit a memory location. + + :param int location_number: Location number to edit (if location does not + exist, this will create a new location in 2023.6) + :param str name: Location name + :param str start_time: Start time + :param str end_time: End time + :param TimeProperties time_properties: Time properties, either this is a range or a marker + :param MemoryLocationReference reference: Reference + :param MemoryLocationProperties general_properties: Location properties + :param str comments: Comment field """ op = ops.EditMemoryLocation( number=location_number, @@ -517,13 +534,13 @@ def export_mix( .. note:: This method runs synchronously and will not return until the bounce has completed. - :param file_type: Export file type - :param sources: Busses to bounce - :param audio_info: Audio options - :param video_info: Video options - :param location_info: Output folder settings - :param dolby_atmos_info: Dolby Atmos output settings - :param offline_bounce: Bounce offline option + :param EM_FileType file_type: Export file type + :param List[EM_SourceInfo] sources: Busses to bounce + :param EM_AudioInfo audio_info: Audio options + :param EM_VideoInfo video_info: Video options + :param EM_LocationInfo location_info: Output folder settings + :param EM_DolbyAtmosInfo dolby_atmos_info: Dolby Atmos output settings + :param bool offline_bounce: Bounce offline option """ op = ops.ExportMix( file_name=base_name, diff --git a/tests/test_engine.py b/tests/test_engine.py index 8d3fb19..3e65c88 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -24,11 +24,11 @@ class TestEngine(TestCase): """ The :class:`TestEngine` test case exercises the :class:`Engine` interface. The client is fully mocked. These tests are here mostly to make sure the engine is translating - the auto-generated *Request classes from call arguments, and *Response classes into - return values correctly. + the *Request classes from call arguments, and *Response classes into return values, + correctly. If these fail, it probably means a breaking change has ocurred in `PTSL.proto` or the - engine's interface has changed. + engine's interface. """ MARKER_LOCATION_FIXTURE = [