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):
|
||||
self._lastPicture = 0
|
||||
self._callback = callback
|
||||
self.lastClosedType = None
|
||||
self.lastClosedTime = 0
|
||||
super().__init__()
|
||||
|
||||
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()
|
||||
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:
|
||||
"""
|
||||
Creates an Observer that watches the VRChat pictures folder and calls callback
|
||||
@ -77,9 +93,10 @@ def createNewPictureObserver(callback: callable) -> Observer:
|
||||
return None
|
||||
|
||||
observer = Observer()
|
||||
observer.schedule(NewPictureHandler(callback), path, recursive=True)
|
||||
handler = NewPictureHandler(callback)
|
||||
observer.schedule(handler, path, recursive=True)
|
||||
observer.start()
|
||||
return observer
|
||||
return handler, observer
|
||||
|
||||
|
||||
|
||||
@ -92,9 +109,9 @@ class Camera:
|
||||
onCameraEnabled: callable=None):
|
||||
self.oscClient = oscClient
|
||||
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._newPictureObserver = createNewPictureObserver(self._newPicture)
|
||||
self._newPicHandler, self._newPicObserver = createNewPictureObserver(self._newPicture)
|
||||
self._multiPictureOngoing = False
|
||||
self._onEnabledCallback = onCameraEnabled
|
||||
self._masks = {'UI': False}
|
||||
@ -112,11 +129,48 @@ class Camera:
|
||||
Safely closes the picture watcher, and the connection to VRChat.
|
||||
"""
|
||||
|
||||
if self._newPictureObserver is not None:
|
||||
self._newPictureObserver.stop()
|
||||
self._newPictureObserver.join()
|
||||
if self._newPicObserver is not None:
|
||||
self._newPicObserver.stop()
|
||||
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):
|
||||
self.lastPicture = time.perf_counter()
|
||||
|
||||
if self._multiPictureOngoing:
|
||||
return
|
||||
|
||||
@ -124,17 +178,25 @@ class Camera:
|
||||
if self._lastMode == 5:
|
||||
return
|
||||
|
||||
if timeGap < self.addPicsMinGap:
|
||||
return
|
||||
|
||||
if self.additionalPictures == 0:
|
||||
return
|
||||
|
||||
self._multiPictureOngoing = True
|
||||
|
||||
try:
|
||||
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)
|
||||
# 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)
|
||||
self._multiPictureOngoing = False
|
||||
|
||||
Reference in New Issue
Block a user