Fixed #2
This commit is contained in:
84
camera.py
84
camera.py
@ -32,6 +32,8 @@ class NewPictureHandler(watchdog.events.FileSystemEventHandler):
|
|||||||
def __init__(self, callback: callable):
|
def __init__(self, callback: callable):
|
||||||
self._lastPicture = 0
|
self._lastPicture = 0
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
self.lastClosedType = None
|
||||||
|
self.lastClosedTime = 0
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def on_created(self, event: watchdog.events.FileSystemEvent) -> None:
|
def on_created(self, event: watchdog.events.FileSystemEvent) -> None:
|
||||||
@ -48,6 +50,20 @@ class NewPictureHandler(watchdog.events.FileSystemEventHandler):
|
|||||||
Thread(target=self._callback, args=(event.src_path, timeGap)).start()
|
Thread(target=self._callback, args=(event.src_path, timeGap)).start()
|
||||||
self._lastPicture = time.perf_counter()
|
self._lastPicture = time.perf_counter()
|
||||||
|
|
||||||
|
def on_closed(self, event: watchdog.events.FileSystemEvent) -> None:
|
||||||
|
exExt = '.'.join(event.src_path.split('.')[:-1])
|
||||||
|
|
||||||
|
if exExt.endswith('_Environment'):
|
||||||
|
self.lastClosedType = 'ENVIRONMENT'
|
||||||
|
elif exExt.endswith('_Player'):
|
||||||
|
self.lastClosedType = 'PLAYER'
|
||||||
|
elif exExt.endswith('_UI'):
|
||||||
|
self.lastClosedType = 'UI'
|
||||||
|
else:
|
||||||
|
self.lastClosedType = 'COMBINED'
|
||||||
|
|
||||||
|
self.lastClosedTime = time.perf_counter()
|
||||||
|
|
||||||
def createNewPictureObserver(callback: callable) -> Observer:
|
def createNewPictureObserver(callback: callable) -> Observer:
|
||||||
"""
|
"""
|
||||||
Creates an Observer that watches the VRChat pictures folder and calls callback
|
Creates an Observer that watches the VRChat pictures folder and calls callback
|
||||||
@ -77,9 +93,10 @@ def createNewPictureObserver(callback: callable) -> Observer:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
observer.schedule(NewPictureHandler(callback), path, recursive=True)
|
handler = NewPictureHandler(callback)
|
||||||
|
observer.schedule(handler, path, recursive=True)
|
||||||
observer.start()
|
observer.start()
|
||||||
return observer
|
return handler, observer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -92,9 +109,9 @@ class Camera:
|
|||||||
onCameraEnabled: callable=None):
|
onCameraEnabled: callable=None):
|
||||||
self.oscClient = oscClient
|
self.oscClient = oscClient
|
||||||
self.additionalPictures = 0 # How many additional pictures to take
|
self.additionalPictures = 0 # How many additional pictures to take
|
||||||
self.addPicsMinGap = 0 # How long to wait between multi-shot batches
|
self.lastPicture = 0
|
||||||
self._lastMode = 0
|
self._lastMode = 0
|
||||||
self._newPictureObserver = createNewPictureObserver(self._newPicture)
|
self._newPicHandler, self._newPicObserver = createNewPictureObserver(self._newPicture)
|
||||||
self._multiPictureOngoing = False
|
self._multiPictureOngoing = False
|
||||||
self._onEnabledCallback = onCameraEnabled
|
self._onEnabledCallback = onCameraEnabled
|
||||||
self._masks = {'UI': False}
|
self._masks = {'UI': False}
|
||||||
@ -112,11 +129,48 @@ class Camera:
|
|||||||
Safely closes the picture watcher, and the connection to VRChat.
|
Safely closes the picture watcher, and the connection to VRChat.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._newPictureObserver is not None:
|
if self._newPicObserver is not None:
|
||||||
self._newPictureObserver.stop()
|
self._newPicObserver.stop()
|
||||||
self._newPictureObserver.join()
|
self._newPicObserver.join()
|
||||||
|
|
||||||
|
def _waitForCameraReady(self, timeout: float=7.5):
|
||||||
|
"""
|
||||||
|
Waits until the camera is considered to be "ready" again.
|
||||||
|
This is done by checking self._newPicHandler's last closed flags.
|
||||||
|
This function will also raise a TimeoutError if it has taken more than
|
||||||
|
`timeout` seconds to get the expected close event.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If the camera is not in multilayer mode, use COMBINED as the last type.
|
||||||
|
if self._lastMode != 4:
|
||||||
|
lastType = 'COMBINED'
|
||||||
|
# Otherwise, check for the UI flag.
|
||||||
|
elif self._masks['UI']:
|
||||||
|
lastType = 'UI'
|
||||||
|
# If none of the above are true, the only other outcome is PLAYER. See #2 for more details.
|
||||||
|
else:
|
||||||
|
lastType = 'PLAYER'
|
||||||
|
|
||||||
|
# Start a timer for the timeout.
|
||||||
|
st = time.perf_counter()
|
||||||
|
|
||||||
|
# Spin until we're looking at relevant close events with a 0.05s margin for error.
|
||||||
|
while self._newPicHandler.lastClosedTime < self.lastPicture - 0.05 and\
|
||||||
|
time.perf_counter() - st < timeout:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
# Spin until the desired picture has been reached.
|
||||||
|
while self._newPicHandler.lastClosedType != lastType and\
|
||||||
|
time.perf_counter() - st < timeout:
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
# If the timeout was exceeded, raise TimeoutError.
|
||||||
|
if time.perf_counter() - st > timeout:
|
||||||
|
raise TimeoutError()
|
||||||
|
|
||||||
def _newPicture(self, _, timeGap: float):
|
def _newPicture(self, _, timeGap: float):
|
||||||
|
self.lastPicture = time.perf_counter()
|
||||||
|
|
||||||
if self._multiPictureOngoing:
|
if self._multiPictureOngoing:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -124,17 +178,25 @@ class Camera:
|
|||||||
if self._lastMode == 5:
|
if self._lastMode == 5:
|
||||||
return
|
return
|
||||||
|
|
||||||
if timeGap < self.addPicsMinGap:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.additionalPictures == 0:
|
if self.additionalPictures == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._multiPictureOngoing = True
|
self._multiPictureOngoing = True
|
||||||
|
|
||||||
|
try:
|
||||||
for _ in range(self.additionalPictures):
|
for _ in range(self.additionalPictures):
|
||||||
time.sleep(1.1)
|
# Wait until the camera is ready
|
||||||
|
self._waitForCameraReady()
|
||||||
|
time.sleep(0.75)
|
||||||
|
# Take a new picture
|
||||||
self.oscClient.send_message('/usercamera/Capture', True)
|
self.oscClient.send_message('/usercamera/Capture', True)
|
||||||
|
# Reset the lastPicture time, the set that happens at the start of this
|
||||||
|
# function is not fast enough and causes race conditions.
|
||||||
|
self.lastPicture = time.perf_counter()
|
||||||
|
# Wait for the camera to be ready one last time to prevent a cycle
|
||||||
|
self._waitForCameraReady()
|
||||||
|
except TimeoutError:
|
||||||
|
print('WARNING: Timeout occured during multishot. Bailing out.')
|
||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
self._multiPictureOngoing = False
|
self._multiPictureOngoing = False
|
||||||
|
|||||||
Reference in New Issue
Block a user