summaryrefslogtreecommitdiff
path: root/workspace.py
diff options
context:
space:
mode:
authorBen Connors <benconnors@outlook.com>2019-09-25 23:05:14 -0400
committerBen Connors <benconnors@outlook.com>2019-09-25 23:05:14 -0400
commit2b8a53f98c44e6e78d49b7c246731deef75ed6d3 (patch)
treed85e383c0d54d662ea93384b177a0ad59a40917e /workspace.py
parent7f85bd8ed84b23fc4e683ab90fc7babe288f1a27 (diff)
Change module layout; start chaser work
- Fix up callbacks - Clean up function implementations - More properties to prevent editing of attributes - Start work on chasers - Implement framework for chaser steps
Diffstat (limited to 'workspace.py')
-rw-r--r--workspace.py248
1 files changed, 0 insertions, 248 deletions
diff --git a/workspace.py b/workspace.py
deleted file mode 100644
index add047b..0000000
--- a/workspace.py
+++ /dev/null
@@ -1,248 +0,0 @@
-"""Workspace module.
-
-Contains the main Workspace implementation.
-"""
-
-import datetime as dt
-import json
-import subprocess as subp
-import xml.etree.ElementTree as et
-
-from .constants import AUDIO, SCENE, BXW
-from .functions.function import Function
-from .exceptions import LoadError
-from .interfaces import XMLSerializable
-
-def ffprobe_audio_length(f: str, path: str = "ffprobe") -> int:
- """Use ffprobe to check audio length in milliseconds.
-
- Will always return the nearest whole millisecond greater than or equal to the duration.
-
- Parameters:
- f: the path to check
- path: the path of ffprobe
- """
- try:
- a = subp.check_output([path, "-show_format", "-print_format", "json", f], stderr=subp.DEVNULL)
- except subp.CalledProcessError:
- return 0
- return int(1000*float(json.loads(a)["format"]["duration"])+0.5)
-
-class Workspace(XMLSerializable):
- """Class representing a audiovisual workspace."""
- def __init__(self, name: str, author: str, version: int, modified: dt.datetime):
- self.name = name
- self.author = author
- self.version = version
- self.modified = modified
-
- self.fixtures = {}
- self._last_fixture_id = -1
- self.functions = {}
- self._last_function_id = -1
-
- self._audio_lengths = {}
-
- self._change_callbacks = {}
- self._delete_callbacks = {}
-
- def get_audio_length(self, filename: str) -> int:
- """Determine the audio length of the given file.
-
- This value is returned from the cache, if available.
- """
- if filename not in self._audio_lengths:
- self._audio_lengths[filename] = ffprobe_audio_length(filename)
- return self._audio_lengths[filename]
-
- def recheck_audio_length(self, filename: str) -> int:
- """Determine the audio length of the given file.
-
- This function re-probes the value, updating the cache result and the durations of
- Audio functions.
- """
- self._audio_lengths[filename] = ffprobe_audio_length(filename)
- for f in self.functions.values():
- if f.type == AUDIO and f.filename == filename:
- f.duration = self._audio_lengths[f.filename]
- return self._audio_lengths[filename]
-
- def recheck_audio_lengths(self):
- """Recheck and update all audio lengths."""
- for filename in self._audio_lengths:
- self._audio_lengths[filename] = ffprobe_audio_length(filename)
- for f in self.functions.values():
- if f.type == AUDIO and f.filename in self._audio_lengths:
- f.filename = f.filename
-
- def register_fixture(self, f: "Fixture"):
- """Register the fixture in the Workspace.
-
- Always called when the fixture is instantiated.
- """
- if f.id in self.fixtures:
- raise ValueError("A fixture with that ID already exists")
- self.fixtures[f.id] = f
-
- def register_function(self, f: Function):
- """Register the function in the Workspace.
-
- Always called when the function is instantiated.
- """
- if f.id in self.functions:
- raise ValueError("A function with that ID already exists")
- self.functions[f.id] = f
-
- @property
- def next_fixture_id(self):
- """Return the next fixture ID."""
- return self._last_fixture_id + 1
-
- @property
- def next_function_id(self):
- """Return the next function ID."""
- return self._last_function_id + 1
-
- def delete_channel(self, c: "Fixture.Channel"):
- """Notify that the given channel was deleted.
-
- This is used for removing deleted channels from functions.
- """
- for f in self.functions.values():
- if f.type == SCENE and c in f.scope:
- f.delete_channel(c)
-
- def delete_callbacks(self, owner, f: int = None):
- """Remove all callbacks registered by the owner.
-
- :param f: the function to remove from (all if None)
- """
- if isinstance(f, Function):
- f = f.id
- for g in self._change_callbacks:
- if f is None or g == f:
- self._change_callbacks[g] = [i for i in self._change_callbacks[g] if i[0] != owner]
- for g in self._delete_callbacks:
- if f is None or g == f:
- self._delete_callbacks[g] = [i for i in self._delete_callbacks[g] if i[0] != owner]
-
- def register_function_change_callback(self, f: int, callback, owner = None):
- """Register a callback for a function change.
-
- :param f: the ID of the function to monitor
- :param callback: a function to call, accepting the Function
- :param owner: optional ID to use for removal of the callback later
- """
- if isinstance(f, Function):
- f = f.id
- if f not in self._change_callbacks:
- self._change_callbacks[f] = []
- self._change_callbacks[f].append((owner, callback))
-
- def register_function_delete_callback(self, f: int, callback, owner = None):
- """Register a callback for a function deletion.
-
- :param f: the ID of the function to monitor
- :param callback: a function to call, accepting the Function
- :param owner: optional ID to use for removal of the callback later
- """
- if isinstance(f, Function):
- f = f.id
- if f not in self._delete_callbacks:
- self._delete_callbacks[f] = []
- self._delete_callbacks[f].append((owner, callback))
-
- def function_changed(self, f: Function):
- """Called when a function is changed.
-
- :param f: the changed function
- """
- for callback in self._change_callbacks[f.id]:
- callback(f)
-
- def function_deleted(self, f: Function):
- """Called when a function is deleted.
-
- This also handles removing the function from the Workspace.
- """
- for callback in self._delete_callbacks[f.id]:
- callback(f)
-
- self.delete_callbacks(f)
-
- del self.functions[f.id]
-
- @classmethod
- def load(cls, filename):
- """Load the workspace from a file."""
- tree = et.parse(filename)
- root = tree.getroot()
-
- return cls.deserialize(None, root)
-
- @classmethod
- def deserialize(cls, w, e):
- from .topology import Fixture
- from .functions.scene import Scene
- from .functions.audio import Audio
-
- if e.tag != BXW+"workspace":
- raise LoadError("Root tag must be workspace")
-
- try:
- name, author, version, modified, fixtures, functions = e
- except ValueError:
- raise LoadError("Invalid workspace layout")
-
- ## First load the metadata so we can create the workspace
- name = name.text
- author = author.text
- version = int(version.text)
- modified = dt.datetime.fromisoformat(modified.text)
-
- w = cls(name=name, author=author, version=version, modified=modified)
-
- ## Now load the fixtures
- for fixture in fixtures:
- Fixture.deserialize(w, fixture)
-
- ## Finally, load the functions
- for function in functions:
- type_ = function.get("type")
- if type_ == AUDIO:
- Audio.deserialize(w, function)
- elif type_ == SCENE:
- Scene.deserialize(w, function)
- else:
- raise LoadError("Unknown function type \"%s\"" % type_)
-
- return w
-
- def serialize(self) -> et.Element:
- root = et.Element(BXW+"workspace")
-
- et.SubElement(root, BXW+"name").text = self.name
- et.SubElement(root, BXW+"author").text = self.author
- et.SubElement(root, BXW+"version").text = str(self.version)
- et.SubElement(root, BXW+"modified").text = dt.datetime.now().isoformat()
-
- fixtures = et.SubElement(root, BXW+"fixtures")
- for n, fixture in enumerate(self.fixtures.values()):
- fe = fixture.serialize()
- fixtures.insert(n, fe)
-
- functions = et.SubElement(root, BXW+"functions")
- for n, function in enumerate(self.functions.values()):
- fe = function.serialize()
- functions.insert(n, fe)
-
- return root
-
-
- def save(self, filename):
- """Save the workspace to a file."""
- et.register_namespace("", BXW.strip("{}"))
- root = self.serialize()
- XMLSerializable.indent(root)
- tree = et.ElementTree(element=root)
- tree.write(filename, encoding="unicode")