diff --git a/src-python/config.py b/src-python/config.py index 36cbfbd5..02ebdd43 100644 --- a/src-python/config.py +++ b/src-python/config.py @@ -1103,7 +1103,7 @@ class Config: "display_duration": 5, "fadeout_duration": 2, "opacity": 1.0, - "ui_scaling": 1.0, + "ui_scaling": 0.5, "tracker": "LeftHand", } self._SEND_MESSAGE_TO_VRC = True diff --git a/src-python/model.py b/src-python/model.py index e66f3048..622979b8 100644 --- a/src-python/model.py +++ b/src-python/model.py @@ -81,8 +81,10 @@ class Model: self.translator = Translator() self.keyword_processor = KeywordProcessor() self.overlay = Overlay( - config.OVERLAY_SMALL_LOG_SETTINGS, - config.OVERLAY_LARGE_LOG_SETTINGS + { + "small": config.OVERLAY_SMALL_LOG_SETTINGS, + "large": config.OVERLAY_LARGE_LOG_SETTINGS, + } ) self.overlay_small_log_pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS self.overlay_large_log_pre_settings = config.OVERLAY_LARGE_LOG_SETTINGS @@ -677,10 +679,10 @@ class Model: self.speaker_energy_recorder.stop() self.speaker_energy_recorder = None - def createOverlayImageSmall(self, message, translation): + def createOverlayImageSmallLog(self, message, translation): your_language = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["primary"]["language"] target_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["primary"]["language"] - return self.overlay_image.createOverlayImageSmall(message, your_language, translation, target_language) + return self.overlay_image.createOverlayImageSmallLog(message, your_language, translation, target_language) def createOverlayImageSystemMessage(self, message): ui_language = config.UI_LANGUAGE @@ -692,12 +694,12 @@ class Model: "zh-Hant":"Chinese Traditional", } language = convert_languages.get(ui_language, "Japanese") - return self.overlay_image.createOverlayImageSmall(message, language) + return self.overlay_image.createOverlayImageSmallLog(message, language) - def clearOverlayImageSmall(self): + def clearOverlayImageSmallLog(self): self.overlay.clearImage("small") - def updateOverlaySmall(self, img): + def updateOverlaySmallLog(self, img): self.overlay.updateImage(img, "small") def updateOverlaySmallLogSettings(self): @@ -720,7 +722,7 @@ class Model: case "fadeout_duration": self.overlay.updateFadeoutDuration(config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], "small") case "opacity": - self.overlay.updateOpacity(config.OVERLAY_SMALL_LOG_SETTINGS["opacity"], True, "small") + self.overlay.updateOpacity(config.OVERLAY_SMALL_LOG_SETTINGS["opacity"], "small", True) case "ui_scaling": self.overlay.updateUiScaling(config.OVERLAY_SMALL_LOG_SETTINGS["ui_scaling"], "small") case _: @@ -728,15 +730,15 @@ class Model: break self.overlay_small_log_pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS - def createOverlayImageLarge(self, message_type:str, message:str, translation:str): + def createOverlayImageLargeLog(self, message_type:str, message:str, translation:str): your_language = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]["primary"]["language"] target_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["primary"]["language"] - return self.overlay_image.createOverlayImageLarge(message_type, message, your_language, translation, target_language) + return self.overlay_image.createOverlayImageLargeLog(message_type, message, your_language, translation, target_language) - def clearOverlayImageLarge(self): + def clearOverlayImageLargeLog(self): self.overlay.clearImage("large") - def updateOverlayLarge(self, img): + def updateOverlayLargeLog(self, img): self.overlay.updateImage(img, "large") def updateOverlayLargeLogSettings(self): @@ -759,7 +761,7 @@ class Model: case "fadeout_duration": self.overlay.updateFadeoutDuration(config.OVERLAY_LARGE_LOG_SETTINGS["fadeout_duration"], "large") case "opacity": - self.overlay.updateOpacity(config.OVERLAY_LARGE_LOG_SETTINGS["opacity"], True, "large") + self.overlay.updateOpacity(config.OVERLAY_LARGE_LOG_SETTINGS["opacity"], "large", True) case "ui_scaling": self.overlay.updateUiScaling(config.OVERLAY_LARGE_LOG_SETTINGS["ui_scaling"], "large") case _: diff --git a/src-python/models/overlay/overlay.py b/src-python/models/overlay/overlay.py index bd00eb9a..55cfca65 100644 --- a/src-python/models/overlay/overlay.py +++ b/src-python/models/overlay/overlay.py @@ -63,7 +63,7 @@ def getRightHandBaseMatrix(): return arr class Overlay: - def __init__(self, small_settings, large_settings): + def __init__(self, settings_dict): self.system = None self.overlay = None self.handle = None @@ -71,75 +71,66 @@ class Overlay: self.loop = True self.thread_overlay = None - self.settings = {"small": small_settings, "large": large_settings} - self.lastUpdate = {"small": time.monotonic(), "large": time.monotonic()} - self.fadeRatio = {"small": 1, "large": 1} + self.settings = {} + self.lastUpdate = {} + self.fadeRatio = {} + for key, value in settings_dict.items(): + self.settings[key] = value + self.lastUpdate[key] = time.monotonic() + self.fadeRatio[key] = 1 def init(self): try: self.system = openvr.init(openvr.VRApplication_Background) self.overlay = openvr.IVROverlay() self.overlay_system = openvr.IVRSystem() - self.handle_small = self.overlay.createOverlay("VRCT_SMALL_LOG", "VRCT_SMALL_LOG") - self.handle_large = self.overlay.createOverlay("VRCT_LARGE_LOG", "VRCT_LARGE_LOG") - self.overlay.showOverlay(self.handle_small) - self.overlay.showOverlay(self.handle_large) + self.handle = {} + for i, size in enumerate(self.settings.keys()): + self.handle[size] = self.overlay.createOverlay(f"VRCT{i}", f"VRCT{i}") + self.overlay.showOverlay(self.handle[size]) self.initialized = True - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), "small") - self.updateColor([1, 1, 1], "small") - self.updateOpacity(self.settings["small"]["opacity"], "small") - self.updateUiScaling(self.settings["small"]["ui_scaling"], "small") - self.updatePosition( - self.settings["small"]["x_pos"], - self.settings["small"]["y_pos"], - self.settings["small"]["z_pos"], - self.settings["small"]["x_rotation"], - self.settings["small"]["y_rotation"], - self.settings["small"]["z_rotation"], - self.settings["small"]["tracker"] - ) - - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), "large") - self.updateColor([1, 1, 1], "large") - self.updateOpacity(self.settings["large"]["opacity"], "large") - self.updateUiScaling(self.settings["large"]["ui_scaling"], "large") - self.updatePosition( - self.settings["large"]["x_pos"], - self.settings["large"]["y_pos"], - self.settings["large"]["z_pos"], - self.settings["large"]["x_rotation"], - self.settings["large"]["y_rotation"], - self.settings["large"]["z_rotation"], - self.settings["large"]["tracker"] - ) + for size in self.settings.keys(): + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size) + self.updateColor([1, 1, 1], size) + self.updateOpacity(self.settings[size]["opacity"], size, True) + self.updateUiScaling(self.settings[size]["ui_scaling"], size, True) + self.updatePosition( + self.settings[size]["x_pos"], + self.settings[size]["y_pos"], + self.settings[size]["z_pos"], + self.settings[size]["x_rotation"], + self.settings[size]["y_rotation"], + self.settings[size]["z_rotation"], + self.settings[size]["tracker"] + ) except Exception as e: printLog("error:Could not initialise OpenVR", e) - def updateImage(self, img, size: str = "small"): + def updateImage(self, img, size): if self.initialized is True: width, height = img.size img = img.tobytes() img = (ctypes.c_char * len(img)).from_buffer_copy(img) try: - self.overlay.setOverlayRaw(self.handle_small[size], img, width, height, 4) + self.overlay.setOverlayRaw(self.handle[size], img, width, height, 4) except Exception as e: printLog("error:Could not update image", e) self.initialized = False self.reStartOverlay() while self.initialized is False: time.sleep(0.1) - self.overlay.setOverlayRaw(self.handle_small[size], img, width, height, 4) - self.updateOpacity(self.settings[size]["opacity"], "small") + self.overlay.setOverlayRaw(self.handle[size], img, width, height, 4) + self.updateOpacity(self.settings[size]["opacity"], size, True) self.lastUpdate[size] = time.monotonic() - def clearImage(self, size: str = "small"): + def clearImage(self, size): if self.initialized is True: self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size) - def updateColor(self, col, size: str = "small"): + def updateColor(self, col, size): """ col is a 3-tuple representing (r, g, b) """ @@ -147,7 +138,7 @@ class Overlay: r, g, b = col self.overlay.setOverlayColor(self.handle[size], r, g, b) - def updateOpacity(self, opacity, with_fade=False, size: str = "small"): + def updateOpacity(self, opacity, size, with_fade=True): self.settings[size]["opacity"] = opacity if self.initialized is True: @@ -157,12 +148,12 @@ class Overlay: else: self.overlay.setOverlayAlpha(self.handle[size], self.settings[size]["opacity"]) - def updateUiScaling(self, ui_scaling, size: str = "small"): + def updateUiScaling(self, ui_scaling, size): self.settings[size]["ui_scaling"] = ui_scaling if self.initialized is True: self.overlay.setOverlayWidthInMeters(self.handle[size], self.settings[size]["ui_scaling"]) - def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker: str="HMD", size: str = "small"): + def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker, size): """ x_pos, y_pos, z_pos are floats representing the position of overlay x_rotation, y_rotation, z_rotation are floats representing the rotation of overlay @@ -203,10 +194,10 @@ class Overlay: transform ) - def updateDisplayDuration(self, display_duration, size: str = "small"): + def updateDisplayDuration(self, display_duration, size): self.settings[size]["display_duration"] = display_duration - def updateFadeoutDuration(self, fadeout_duration, size: str = "small"): + def updateFadeoutDuration(self, fadeout_duration, size): self.settings[size]["fadeout_duration"] = fadeout_duration def checkActive(self): @@ -221,7 +212,7 @@ class Overlay: printLog("error:Could not check SteamVR running", e) return False - def evaluateOpacityFade(self, lastUpdate, currentTime, size: str = "small"): + def evaluateOpacityFade(self, lastUpdate, currentTime, size): if (currentTime - lastUpdate) > self.settings[size]["display_duration"]: timeThroughInterval = currentTime - lastUpdate - self.settings[size]["display_duration"] self.fadeRatio[size] = 1 - timeThroughInterval / self.settings[size]["fadeout_duration"] @@ -229,19 +220,19 @@ class Overlay: self.fadeRatio[size] = 0 self.overlay.setOverlayAlpha(self.handle[size], self.fadeRatio[size] * self.settings[size]["opacity"]) - def update(self, size: str = "small"): + def update(self, size): currTime = time.monotonic() if self.settings[size]["fadeout_duration"] != 0: self.evaluateOpacityFade(self.lastUpdate[size], currTime, size) else: - self.updateOpacity(self.settings[size]["opacity"], size) + self.updateOpacity(self.settings[size]["opacity"], size, True) def mainloop(self): self.loop = True while self.checkActive() is True and self.loop is True: startTime = time.monotonic() - self.update("small") - self.update("large") + for size in self.settings.keys(): + self.update(size) sleepTime = (1 / 16) - (time.monotonic() - startTime) if sleepTime > 0: time.sleep(sleepTime) @@ -261,9 +252,10 @@ class Overlay: self.loop = False self.thread_overlay.join() self.thread_overlay = None - if isinstance(self.overlay, openvr.IVROverlay) and isinstance(self.handle["small"], int) and isinstance(self.handle["large"], int): - self.overlay.destroyOverlay(self.handle["small"]) - self.overlay.destroyOverlay(self.handle["large"]) + if isinstance(self.overlay, openvr.IVROverlay): + for size in self.settings.keys(): + if isinstance(self.handle[size], int): + self.overlay.destroyOverlay(self.handle[size]) self.overlay = None if isinstance(self.system, openvr.IVRSystem): openvr.shutdown() @@ -280,35 +272,6 @@ class Overlay: return _proc_name in (p.name() for p in process_iter()) if __name__ == "__main__": - # from overlay_image import OverlayImage - # overlay_image = OverlayImage() - - # overlay = Overlay(0, 0, 1, 1, 0, 1, 1) - # overlay.startOverlay() - # time.sleep(1) - - # # Example usage - # img = overlay_image.createOverlayImageSmall("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - # overlay.updateImage(img) - # time.sleep(100000) - - # for i in range(100): - # print(i) - # overlay = Overlay(0, 0, 1, 1, 1, 1, 1) - # overlay.startOverlay() - # time.sleep(1) - - # # Example usage - # img = overlay_image.createOverlayImageSmall("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - # overlay.updateImage(img) - # time.sleep(0.5) - - # img = overlay_image.createOverlayImageSmall("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - # overlay.updateImage(img) - # time.sleep(0.5) - - # overlay.shutdownOverlay() - x_pos = 0 y_pos = 0 z_pos = 0 diff --git a/src-python/models/overlay/overlay_image.py b/src-python/models/overlay/overlay_image.py index e14ccacd..2f650e44 100644 --- a/src-python/models/overlay/overlay_image.py +++ b/src-python/models/overlay/overlay_image.py @@ -31,7 +31,7 @@ class OverlayImage: return result @staticmethod - def getUiSizeSmall(): + def getUiSizeSmallLog(): return { "width": int(960*4), "height": int(23*4), @@ -39,7 +39,7 @@ class OverlayImage: } @staticmethod - def getUiColorSmall(ui_type): + def getUiColorSmallLog(ui_type): background_color = (41, 42, 45) background_outline_color = (41, 42, 45) text_color = (223, 223, 223) @@ -54,7 +54,7 @@ class OverlayImage: "text_color": text_color } - def createTextboxSmall(self, text, language, text_color, base_width, base_height, font_size): + def createTextboxSmallLog(self, text, language, text_color, base_width, base_height, font_size): font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) @@ -77,20 +77,20 @@ class OverlayImage: draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center") return img - def createOverlayImageSmall(self, message, your_language, translation="", target_language=None, ui_type="default"): - ui_size = self.getUiSizeSmall() + def createOverlayImageSmallLog(self, message, your_language, translation="", target_language=None, ui_type="default"): + ui_size = self.getUiSizeSmallLog() height = ui_size["height"] width = ui_size["width"] font_size = ui_size["font_size"] - ui_colors = self.getUiColorSmall(ui_type) + ui_colors = self.getUiColorSmallLog(ui_type) text_color = ui_colors["text_color"] background_color = ui_colors["background_color"] background_outline_color = ui_colors["background_outline_color"] - img = self.createTextboxSmall(message, your_language, text_color, width, height, font_size) + img = self.createTextboxSmallLog(message, your_language, text_color, width, height, font_size) if len(translation) > 0 and target_language is not None: - translation_img = self.createTextboxSmall(translation, target_language, text_color, width, height, font_size) + translation_img = self.createTextboxSmallLog(translation, target_language, text_color, width, height, font_size) img = self.concatenateImagesVertically(img, translation_img) background = Image.new("RGBA", img.size, (0, 0, 0, 0)) @@ -101,7 +101,7 @@ class OverlayImage: return img @staticmethod - def getUiSizeLarge(): + def getUiSizeLargeLog(): return { "width": int(960*2), "font_size_large": int(23*4), @@ -109,7 +109,7 @@ class OverlayImage: } @staticmethod - def getUiColorLarge(): + def getUiColorLargeLog(): background_color = (41, 42, 45) background_outline_color = (41, 42, 45) text_color_large = (223, 223, 223) @@ -127,13 +127,13 @@ class OverlayImage: "text_color_time": text_color_time } - def createTextImageLarge(self, message_type, size, text, language): - ui_size = self.getUiSizeLarge() + def createTextImageLargeLog(self, message_type, size, text, language): + ui_size = self.getUiSizeLargeLog() font_size_large = ui_size["font_size_large"] font_size_small = ui_size["font_size_small"] width = ui_size["width"] - ui_color = self.getUiColorLarge() + ui_color = self.getUiColorLargeLog() text_color_large = ui_color["text_color_large"] text_color_small = ui_color["text_color_small"] @@ -164,11 +164,11 @@ class OverlayImage: return img def createTextImageMessageType(self, message_type, date_time): - ui_size = self.getUiSizeLarge() + ui_size = self.getUiSizeLargeLog() width = ui_size["width"] font_size = ui_size["font_size_small"] - ui_color = self.getUiColorLarge() + ui_color = self.getUiColorLargeLog() text_color_send = ui_color["text_color_send"] text_color_receive = ui_color["text_color_receive"] text_color_time = ui_color["text_color_time"] @@ -195,18 +195,18 @@ class OverlayImage: draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font) return img - def createTextboxLarge(self, message_type, message, your_language, translation, target_language, date_time): + def createTextboxLargeLog(self, message_type, message, your_language, translation, target_language, date_time): message_type_img = self.createTextImageMessageType(message_type, date_time) if len(translation) > 0 and target_language is not None: - img = self.createTextImageLarge(message_type, "small", message, your_language) - translation_img = self.createTextImageLarge(message_type, "large",translation, target_language) + img = self.createTextImageLargeLog(message_type, "small", message, your_language) + translation_img = self.createTextImageLargeLog(message_type, "large",translation, target_language) img = self.concatenateImagesVertically(img, translation_img) else: - img = self.createTextImageLarge(message_type, "large", message, your_language) + img = self.createTextImageLargeLog(message_type, "large", message, your_language) return self.concatenateImagesVertically(message_type_img, img) - def createOverlayImageLarge(self, message_type, message, your_language, translation="", target_language=None): - ui_color = self.getUiColorLarge() + def createOverlayImageLargeLog(self, message_type, message, your_language, translation="", target_language=None): + ui_color = self.getUiColorLargeLog() background_color = ui_color["background_color"] background_outline_color = ui_color["background_outline_color"] @@ -232,7 +232,7 @@ class OverlayImage: translation = log["translation"] target_language = log["target_language"] date_time = log["datetime"] - img = self.createTextboxLarge(message_type, message, your_language, translation, target_language, date_time) + img = self.createTextboxLargeLog(message_type, message, your_language, translation, target_language, date_time) imgs.append(img) img = imgs[0] @@ -249,9 +249,9 @@ class OverlayImage: if __name__ == "__main__": overlay = OverlayImage() - img = overlay.createOverlayImageSmall("Hello, World!", "English", "こんにちは、世界!", "Japanese") + img = overlay.createOverlayImageSmallLog("Hello, World!", "English", "こんにちは、世界!", "Japanese") img.save("overlay_small.png") - img = overlay.createOverlayImageLarge("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese") + img = overlay.createOverlayImageLargeLog("send", "Hello, World!", "English", "こんにちは、世界!", "Japanese") img.save("overlay_large0.png") - img = overlay.createOverlayImageLarge("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English") + img = overlay.createOverlayImageLargeLog("receive", "こんにちは、世界!", "Japanese", "Hello, World!", "English") img.save("overlay_large1.png") \ No newline at end of file diff --git a/src-python/webui_controller.py b/src-python/webui_controller.py index fa6cbd96..ff8fe4f7 100644 --- a/src-python/webui_controller.py +++ b/src-python/webui_controller.py @@ -250,9 +250,9 @@ class Controller: translation = " (" + "/".join(translation) + ")" model.logger.info(f"[SENT] {message}{translation}") - if config.OVERLAY_LARGE_LOG is True and model.overlay_large_log.initialized is True: - overlay_image = model.createOverlayImageLarge("send", message, translation) - model.updateOverlayLarge(overlay_image) + if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True: + overlay_image = model.createOverlayImageLargeLog("send", message, translation) + model.updateOverlayLargeLog(overlay_image) def speakerMessage(self, message) -> None: if isinstance(message, bool) and message is False: @@ -289,13 +289,13 @@ class Controller: transliteration = model.convertMessageToTransliteration(message) if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - if config.OVERLAY_SMALL_LOG is True and model.overlay_small_log.initialized is True: - overlay_image = model.createOverlayImageSmall(message, translation) - model.updateOverlaySmall(overlay_image) + if config.OVERLAY_SMALL_LOG is True and model.overlay.initialized is True: + overlay_image = model.createOverlayImageSmallLog(message, translation) + model.updateOverlaySmallLog(overlay_image) - if config.OVERLAY_LARGE_LOG is True and model.overlay_large_log.initialized is True: - overlay_image = model.createOverlayImageLarge("receive", message, translation) - model.updateOverlayLarge(overlay_image) + if config.OVERLAY_LARGE_LOG is True and model.overlay.initialized is True: + overlay_image = model.createOverlayImageLargeLog("receive", message, translation) + model.updateOverlayLargeLog(overlay_image) if config.SEND_RECEIVED_MESSAGE_TO_VRC is True: osc_message = self.messageFormatter("RECEIVED", translation, [message]) @@ -360,11 +360,9 @@ class Controller: osc_message = self.messageFormatter("SEND", translation, [message]) model.oscSendMessage(osc_message) - # if config.OVERLAY_SMALL_LOG is True: - # overlay_image = model.createOverlayImageSmall(message, translation) - # model.updateOverlay(overlay_image) - # overlay_image = model.createOverlayImageLong("send", message, translation) - # model.updateOverlay(overlay_image) + if config.OVERLAY_LARGE_LOG is True: + overlay_image = model.createOverlayImageLargeLog("send", message, translation) + model.updateOverlayLargeLog(overlay_image) # update textbox message log (Sent) if config.LOGGER_FEATURE is True: @@ -1183,7 +1181,7 @@ class Controller: def setDisableOverlaySmallLog(*args, **kwargs) -> dict: config.OVERLAY_SMALL_LOG = False if config.OVERLAY_SMALL_LOG is False: - model.clearOverlayImageSmall() + model.clearOverlayImageSmallLog() if config.OVERLAY_LARGE_LOG is False: model.shutdownOverlay() return {"status":200, "result":config.OVERLAY_SMALL_LOG} @@ -1213,7 +1211,7 @@ class Controller: def setDisableOverlayLargeLog(*args, **kwargs) -> dict: config.OVERLAY_LARGE_LOG = False if config.OVERLAY_LARGE_LOG is False: - model.clearOverlayImageLarge() + model.clearOverlayImageLargeLog() if config.OVERLAY_SMALL_LOG is False: model.shutdownOverlay() return {"status":200, "result":config.OVERLAY_LARGE_LOG} @@ -1412,9 +1410,9 @@ class Controller: @staticmethod def sendTextOverlaySmallLog(data, *args, **kwargs) -> dict: if config.OVERLAY_SMALL_LOG is True: - if model.overlay_small_log.initialized is True: + if model.overlay.initialized is True: overlay_image = model.createOverlayImageSystemMessage(data) - model.updateOverlaySmall(overlay_image) + model.updateOverlaySmallLog(overlay_image) return {"status":200, "result":data} def swapYourLanguageAndTargetLanguage(self, *args, **kwargs) -> dict: