changeset 2458:0e27ba33118c default tip

better blender<->asco playback cooperation. still no play support in blender; only seek
author drewp@bigasterisk.com
date Tue, 20 May 2025 16:25:06 -0700
parents d94480bfb179
children
files blender/time_sync/time_from_graph.py src/light9/ascoltami/main.py src/light9/ascoltami/play_state.py
diffstat 3 files changed, 28 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/blender/time_sync/time_from_graph.py	Tue May 20 13:48:07 2025 -0700
+++ b/blender/time_sync/time_from_graph.py	Tue May 20 16:25:06 2025 -0700
@@ -3,7 +3,6 @@
 import time
 from typing import Coroutine
 
-from attr import dataclass
 from rdfdb.patch import Patch
 from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 from rdflib import Literal
@@ -35,6 +34,8 @@
 
     def __init__(self):
         # main thread
+
+        # one mutable instance; modified by bg thread
         self.ascoPlayState = AscoPlayState(None, None, False, False, 1.0)
         self.blenderTime = BlenderTime(self.onBlenderEvent, self.ascoPlayState)
         self.blenderTime.start()
@@ -69,25 +70,19 @@
         # bg thread
         with self.lock:
             asco = L9['ascoltami']
-            
+
             self.ascoPlayState.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime'])
             self.ascoPlayState.pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime'])
             self.ascoPlayState.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0
-            self.blenderTime.durationDirty = True # todo: called too often
+            self.blenderTime.durationDirty = True  # todo: called too often
             self.ascoPlayState.playing = typedValue(bool | None, self.graph, asco, L9['playing']) or False
             self.ascoPlayState.endOfSong = typedValue(bool | None, self.graph, asco, L9['endOfSong']) or False
             log.info(f'🍇 syncFromGraph {self.ascoPlayState=}')
             self.blenderTime.curFrameDirty = True
 
-    async def setGraphPlaying(self, isBlenderPlaying: bool):
-        return
-        # bg thread
-        log.info(f'set graph playing to {isBlenderPlaying}')
-        self.graph.patchObject(self.ctx, L9['ascoltami'], L9['playing'], Literal(isBlenderPlaying))
-
     async def setInGraph(self, t: float, isBlenderPlaying: bool):
         # bg thread
-        log.info(f'set graph time to {t:.2f} {isBlenderPlaying=}')
+        log.debug(f'set graph time to {t:.2f} {isBlenderPlaying=}')
         p = Patch()
         if isBlenderPlaying:
             p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['playing'], Literal(True)))
@@ -100,5 +95,4 @@
 
         if p.isEmpty():
             return
-        log.info(f'setInGraph {p.shortSummary()}')
         await self.graph.patch(p)
--- a/src/light9/ascoltami/main.py	Tue May 20 13:48:07 2025 -0700
+++ b/src/light9/ascoltami/main.py	Tue May 20 16:25:06 2025 -0700
@@ -1,22 +1,21 @@
 import logging
-from typing import cast
 
 from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 from rdflib import ConjunctiveGraph, Literal, URIRef
-
 from starlette.applications import Starlette
 from starlette.routing import Route
 from starlette_exporter import PrometheusMiddleware, handle_metrics
 
 from light9 import networking, showconfig
 from light9.ascoltami import webapp
+from light9.ascoltami.graph_context import ascoltamiContext
 from light9.ascoltami.import_gst import Gst
 from light9.ascoltami.player import Player, PlayerState
 from light9.ascoltami.playlist import NoSuchSong, Playlist
 from light9.namespaces import L9
 from light9.newtypes import decimalLiteral
 from light9.run_local import log
-from light9.ascoltami.graph_context import ascoltamiContext
+from light9.typedgraph import typedValue
 
 
 class Ascoltami:
@@ -30,12 +29,14 @@
         self.playerState = PlayerState()
         self.playlist = Playlist(graph, show)
 
+        self.graph.addHandler(self.updateStateFromGraph)
+
     def onStateChange(self, s: PlayerState):
         g = self.stateAsGraph(s)
         self.graph.patchSubgraph(newGraph=g, context=self.ctx)
         self.playerState = s
 
-    def stateAsGraph(self, s):
+    def stateAsGraph(self, s: PlayerState):
         g = ConjunctiveGraph()
         asc = L9['ascoltami']
         if s.song:
@@ -53,6 +54,23 @@
         g.add((asc, L9['endOfSong'], Literal(s.endOfSong), self.ctx))
         return g
 
+    def updateStateFromGraph(self):
+        """react to graph changes, probably from blender. Not all predicates will correctly adjust the player"""
+        log.info('outside graph change')
+        asco = L9['ascoltami']
+        pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime'])
+        if pausedSongTime is not None and pausedSongTime != self.playerState.pausedSongTime and self.player.getSong() is not None:
+            log.info(f'graph says {pausedSongTime=} , playerState has {self.playerState.pausedSongTime}. seeking to {pausedSongTime}')
+            self.player.seek(pausedSongTime)
+        # self.playerState.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime']) or 0.0
+        # self.playerState.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0 # song & duration are not editable from outside asco
+        # self.playerState.playing = typedValue(bool | None, self.graph, asco, L9['playing']) or False
+        # self.playerState.endOfSong = typedValue(bool | None, self.graph, asco, L9['endOfSong']) or False
+        log.info(f'🍇 syncFromGraph {self.playerState=}')
+
+        # s = self.playerState
+        # s.song = self.playlist.songFromUri(g.value(L9['ascoltami'], L9['song']))
+
     def getPlayerState(self) -> PlayerState:
         return self.playerState
 
--- a/src/light9/ascoltami/play_state.py	Tue May 20 13:48:07 2025 -0700
+++ b/src/light9/ascoltami/play_state.py	Tue May 20 16:25:06 2025 -0700
@@ -1,4 +1,3 @@
-
 from dataclasses import dataclass
 import time
 from light9.run_local import log
@@ -6,7 +5,6 @@
 
 @dataclass
 class AscoPlayState:
-    # one mutable instance; modified by bg thread
     wallStartTime: float | None
     pausedSongTime: float | None
     playing: bool
@@ -19,5 +17,5 @@
             return time.time() - self.wallStartTime
         if self.pausedSongTime is not None:
             return self.pausedSongTime
-        log.warn('no time data')
+        log.warning('no time data')
         return None