diff --git a/runner/app/live/infer.py b/runner/app/live/infer.py index cb9428ba..aaf1b486 100644 --- a/runner/app/live/infer.py +++ b/runner/app/live/infer.py @@ -7,6 +7,10 @@ import os import traceback from typing import List +import logging + +from streamer import PipelineStreamer +from trickle import TrickleSubscriber # loads neighbouring modules with absolute paths infer_root = os.path.abspath(os.path.dirname(__file__)) @@ -17,7 +21,7 @@ from streamer.zeromq import ZeroMQStreamer -async def main(http_port: int, stream_protocol: str, subscribe_url: str, publish_url: str, pipeline: str, params: dict): +async def main(http_port: int, stream_protocol: str, subscribe_url: str, publish_url: str, control_url: str, pipeline: str, params: dict): if stream_protocol == "trickle": handler = TrickleStreamer(subscribe_url, publish_url, pipeline, **(params or {})) elif stream_protocol == "zeromq": @@ -29,6 +33,7 @@ async def main(http_port: int, stream_protocol: str, subscribe_url: str, publish try: handler.start() runner = await start_http_server(handler, http_port) + asyncio.create_task(start_control_subscriber(handler, control_url)) except Exception as e: logging.error(f"Error starting socket handler or HTTP server: {e}") logging.error(f"Stack trace:\n{traceback.format_exc()}") @@ -56,6 +61,19 @@ def signal_handler(sig, _): signal.signal(sig, signal_handler) return await future +async def start_control_subscriber(handler: PipelineStreamer, control_url: str): + if control_url is None or control_url.strip() == "": + logging.warning("No control-url provided, inference won't get updates from the control trickle subscription") + return + logging.info("Starting Control subscriber at %s", control_url) + subscriber = TrickleSubscriber(url=control_url) + while True: + segment = await subscriber.next() + if segment.eos(): + return + params = await segment.read() + logging.info("Received control message, updating model with params: %s", params) + handler.update_params(**json.loads(params)) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Infer process to run the AI pipeline") @@ -81,6 +99,9 @@ def signal_handler(sig, _): parser.add_argument( "--publish-url", type=str, required=True, help="URL to publish output frames (trickle). For zeromq this is the output socket address" ) + parser.add_argument( + "--control-url", type=str, help="URL to subscribe for Control API JSON messages" + ) parser.add_argument( "-v", "--verbose", action="store_true", @@ -103,7 +124,7 @@ def signal_handler(sig, _): try: asyncio.run( - main(args.http_port, args.stream_protocol, args.subscribe_url, args.publish_url, args.pipeline, params) + main(args.http_port, args.stream_protocol, args.subscribe_url, args.publish_url, args.control_url, args.pipeline, params) ) except Exception as e: logging.error(f"Fatal error in main: {e}") diff --git a/runner/app/live/trickle/__init__.py b/runner/app/live/trickle/__init__.py index 3025b73d..7c9ba1b3 100644 --- a/runner/app/live/trickle/__init__.py +++ b/runner/app/live/trickle/__init__.py @@ -1,3 +1,4 @@ from .media import run_subscribe, run_publish +from .trickle_subscriber import TrickleSubscriber -__all__ = ["run_subscribe", "run_publish"] +__all__ = ["run_subscribe", "run_publish", "TrickleSubscriber"] diff --git a/runner/app/pipelines/live_video_to_video.py b/runner/app/pipelines/live_video_to_video.py index c8d75e1e..23469cb9 100644 --- a/runner/app/pipelines/live_video_to_video.py +++ b/runner/app/pipelines/live_video_to_video.py @@ -31,15 +31,17 @@ def __call__( ): try: if not self.process: + logger.info(f"Starting stream, subscribe={kwargs['subscribe_url']} publish={kwargs['publish_url']}, control={kwargs['control_url']}") self.start_process( pipeline=self.model_id, # we use the model_id as the pipeline name for now http_port=8888, subscribe_url=kwargs["subscribe_url"], publish_url=kwargs["publish_url"], + control_url=kwargs["control_url"], initial_params=json.dumps(kwargs["params"]), # TODO: set torch device from self.torch_device ) - logger.info(f"Starting stream, subscribe={kwargs['subscribe_url']} publish={kwargs['publish_url']}") + logger.info(f"Starting stream, subscribe={kwargs['subscribe_url']} publish={kwargs['publish_url']}, control={kwargs['control_url']}") return except Exception as e: raise InferenceError(original_exception=e) diff --git a/runner/app/routes/live_video_to_video.py b/runner/app/routes/live_video_to_video.py index 9fb1b02d..5a4fa34c 100644 --- a/runner/app/routes/live_video_to_video.py +++ b/runner/app/routes/live_video_to_video.py @@ -45,6 +45,12 @@ class LiveVideoToVideoParams(BaseModel): description="Destination URL of the outgoing stream to publish.", ), ] + control_url: Annotated[ + str, + Field( + default="",description="URL for subscribing via Trickle protocol for updates in the live video-to-video generation params.", + ), + ] model_id: Annotated[ str, Field( @@ -129,5 +135,5 @@ async def live_video_to_video( ) # outputs unused for now; the orchestrator is setting these - return {'publish_url':"", 'subscribe_url': ""} + return {'publish_url':"", 'subscribe_url': "", 'control_url': ""} diff --git a/runner/app/routes/utils.py b/runner/app/routes/utils.py index 477696f3..d1c20f13 100644 --- a/runner/app/routes/utils.py +++ b/runner/app/routes/utils.py @@ -92,7 +92,9 @@ class LiveVideoToVideoResponse(BaseModel): publish_url: str = Field( ..., description="Destination URL of the outgoing stream to publish to" ) - + control_url: str = Field( + ..., description="URL for updating the live video-to-video generation" + ) class APIError(BaseModel): """API error response model.""" diff --git a/runner/gateway.openapi.yaml b/runner/gateway.openapi.yaml index 27629f0d..cd3da7b0 100644 --- a/runner/gateway.openapi.yaml +++ b/runner/gateway.openapi.yaml @@ -924,6 +924,12 @@ components: type: string title: Publish Url description: Destination URL of the outgoing stream to publish. + control_url: + type: string + title: Control Url + description: URL for subscribing via Trickle protocol for updates in the + live video-to-video generation params. + default: '' model_id: type: string title: Model Id @@ -950,10 +956,15 @@ components: type: string title: Publish Url description: Destination URL of the outgoing stream to publish to + control_url: + type: string + title: Control Url + description: URL for updating the live video-to-video generation type: object required: - subscribe_url - publish_url + - control_url title: LiveVideoToVideoResponse description: Response model for live video-to-video generation. MasksResponse: diff --git a/runner/openapi.yaml b/runner/openapi.yaml index 7a6121c8..c83cafcf 100644 --- a/runner/openapi.yaml +++ b/runner/openapi.yaml @@ -941,6 +941,12 @@ components: type: string title: Publish Url description: Destination URL of the outgoing stream to publish. + control_url: + type: string + title: Control Url + description: URL for subscribing via Trickle protocol for updates in the + live video-to-video generation params. + default: '' model_id: type: string title: Model Id @@ -966,10 +972,15 @@ components: type: string title: Publish Url description: Destination URL of the outgoing stream to publish to + control_url: + type: string + title: Control Url + description: URL for updating the live video-to-video generation type: object required: - subscribe_url - publish_url + - control_url title: LiveVideoToVideoResponse description: Response model for live video-to-video generation. MasksResponse: diff --git a/worker/runner.gen.go b/worker/runner.gen.go index ea23ca2c..933cd0b4 100644 --- a/worker/runner.gen.go +++ b/worker/runner.gen.go @@ -244,6 +244,9 @@ type LLMResponse struct { // LiveVideoToVideoParams defines model for LiveVideoToVideoParams. type LiveVideoToVideoParams struct { + // ControlUrl URL for subscribing via Trickle protocol for updates in the live video-to-video generation params. + ControlUrl *string `json:"control_url,omitempty"` + // ModelId Hugging Face model ID used for image generation. ModelId *string `json:"model_id,omitempty"` @@ -259,6 +262,9 @@ type LiveVideoToVideoParams struct { // LiveVideoToVideoResponse Response model for live video-to-video generation. type LiveVideoToVideoResponse struct { + // ControlUrl URL for updating the live video-to-video generation + ControlUrl string `json:"control_url"` + // PublishUrl Destination URL of the outgoing stream to publish to PublishUrl string `json:"publish_url"` @@ -2667,76 +2673,77 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/H8AAAD//y5cwOBHaQAA", + "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", + "YiGHih/Ux57ZyvEZS3ALa4cj33gqFZIM8MMuvLZl2zGmquFOq9ct3lfaF2utnq1Xbh5UxjSORJm9n/T6", + "hulUlLp1hYTzugbnarbokvk0B13d9lqCC6rILKNpCgmhivx4+f2n4FDALLP5RtdYwjyxKdG/mq8pbnTF", + "1hvlZnET4vZorxEhptzs3Wkcg1K2r7sisElI29BVlhVUm29PNNeQHT9enPeZElFYity1fw5yGVrsuWVu", + "S2mE6RH08Wt13LWrTap1u8HffCNjt+v3rf17dyMzeuL9wqiS8TqcvQoYzHPX7TVUWv91urkfs6Wo0yu9", + "oqVo1x69a4/+67ZHv/pbd0eTSzBbYw0EL6YLe4KAF5V4kL33f3vGNVT9ctF0ObB53t1FPGcPVAe/N+yB", + "6na9dFPoYJ69LADi+VCiDaTwIeuU5AZPVAH0BiRJwOytpTI2zgz4Z0sCd4UEhXYzaYJyNHVi5kA8r+5U", + "jNOhr5qvExxZMB1j5HQ21tVfRncVabOh1QCu3DJ/2fX77egt8oSt2ptwsipbNEXZ6hRh20Hw6HAVqcF6", + "LfSXwBV6HGbt1Ucm4uDeg/Klu8tpS/il49PX934Oj2loobpcdc1mTb2Orxz36hC/aIYiz+TKfLuudDVy", + "WFJupBdaG1y3bH/0tf6wy7axryvUq6ZvMzbYK2x5+9HeI1R98ZaJNbchjlVfZ6vPehCh41Iyvbw0rFg5", + "311dfXgDVIKs32RHWLdf1YvMtS6ie7MG47OeN6BP3dsrcf3CsSw5OT2rGyxUFJ5PFQZLTs/IRck5EjK4", + "Ztc63D/cPzQKEQVwWrDoJPpq/2j/0FiL6jmyfYDvsY61GFdBXAjVl83rl329d7NtJ5HbbYnCecNZYrYS", + "7RdhjcpB6TciWVanocCRkM36VOoDk3bH1Tva1szrnKDvrdv70MQmx+MX1qAo9vHhYYsLT+sHvymbPzZj", + "IdggIu1W4i5xsz8rM9IMG0VfPyILzTVwD/03NCEXVvuW7tHz0P3IaannQrJ/Q4KEj756HsJOWPId16YM", + "vhKCnFOZWq0fvXou6ZuCFZHKYrlh4fj4UVnoXMl3mWmGkPra/tVz+d8Z1yA5zcglyFuQFQcejGLO9QH0", + "8/X99ShSZZ5Tuax+1IFcCVKVBjRVBrurVGLQ+25sSyyqlmNOcxiLW5CSJYj8ATqMooM5dgDg2Qyg7CF6", + "2QaB6AlBw29B2BQz7n2VOBZRGqzEDYbX7Xv9IH5aFNmy6uELXpZDJKdm32Vyslfbd1C99XbjE8N6QO2Z", + "cT1sitgB+zCw7wBtW0CzL0NcCVJ3xG6JaCwMDB8ENijk8HzD4sD6Oi58+fV5Av6PqOP6OoR2Uf8nL+d2", + "0PNg6HlgLcWCCPWB57Z+770XeX7oe9t7q6KjejvyeTDIUntmEArPHnbwsys6niDy67eMHxb6VWCMooOM", + "3cI4bFFbt/2wozN28/A9SLsvayUkPNweA42Ez4wIg01oO3DYgcPjgQP61u8Bh6wdlBYgsnyDigBvoEq8", + "5aYkozwtDVLVF7xdAMDX4zeL+bvxYrEYYzlQygx4LBJ7vbpdUWBIPnfke439u2DfBfvjBbv7eYltIzzL", + "bVC7luUxde8aj4+HY9y9luwaZPE9EMpX5Pae15ifuNzvUHzmMA9bj3eBvgv0xwv0Kvoq5ybHD4h71Q2Q", + "UXRgcvYGdw4/tDpXcdPvNaqqXhTwOoKeqLjv9hztrhd2Yf8XCXvstvodtwvaC78g2G3f1kZnfOEU/xf9", + "7Q+xC/tCaL3d102HGOWJ16oX/Mz9AFLYXrAnhYqg3eyZsSL8Txd2WLHDisfHijqEHgYWbjqiRen9vFAv", + "TLifOKl3AmS6rH7FE1+d04o0v+LWG/bNj6Q88e6gIrSrDnYR/xeJeO8HhrYM9dIPBoUMKCTX+oW3qi/1", + "bSbKhLwVeV5yppfkB6phQZeRe1EUu2HVycFBIoHm49Q+3c/c9P3YTMf264H1LzVWFUPL1gspHHdAC3Yw", + "BU0Pannvr+//PwAA///ptjPPhWoAAA==", } // GetSwagger returns the content of the embedded swagger specification file