👍️ [Update] pythonのメイン処理部分を移動/webui_mainloop.pyをビルドできるように修正
This commit is contained in:
103
src-python/models/osc/osc_tools.py
Normal file
103
src-python/models/osc/osc_tools.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from time import sleep
|
||||
from pythonosc import osc_message_builder
|
||||
from pythonosc import udp_client
|
||||
from pythonosc import dispatcher
|
||||
from pythonosc import osc_server
|
||||
from tinyoscquery.queryservice import OSCQueryService
|
||||
from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient
|
||||
from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port
|
||||
|
||||
# send OSC message typing
|
||||
def sendTyping(flag=False, ip_address="127.0.0.1", port=9000):
|
||||
typing = osc_message_builder.OscMessageBuilder(address="/chatbox/typing")
|
||||
typing.add_arg(flag)
|
||||
b_typing = typing.build()
|
||||
client = udp_client.SimpleUDPClient(ip_address, port)
|
||||
client.send(b_typing)
|
||||
|
||||
# send OSC message
|
||||
def sendMessage(message=None, ip_address="127.0.0.1", port=9000):
|
||||
if message is not None:
|
||||
msg = osc_message_builder.OscMessageBuilder(address="/chatbox/input")
|
||||
msg.add_arg(f"{message}")
|
||||
msg.add_arg(True)
|
||||
msg.add_arg(True)
|
||||
b_msg = msg.build()
|
||||
client = udp_client.SimpleUDPClient(ip_address, port)
|
||||
client.send(b_msg)
|
||||
|
||||
def sendTestAction(ip_address="127.0.0.1", port=9000):
|
||||
client = udp_client.SimpleUDPClient(ip_address, port)
|
||||
client.send_message("/input/Vertical", 1)
|
||||
sleep(0.01)
|
||||
client.send_message("/input/Vertical", False)
|
||||
|
||||
# send Input Voice
|
||||
def sendInputVoice(flag=False, ip_address="127.0.0.1", port=9000):
|
||||
input_voice = osc_message_builder.OscMessageBuilder(address="/input/Voice")
|
||||
input_voice.add_arg(flag)
|
||||
b_input_voice = input_voice.build()
|
||||
client = udp_client.SimpleUDPClient(ip_address, port)
|
||||
client.send(b_input_voice)
|
||||
|
||||
def sendChangeVoice(ip_address="127.0.0.1", port=9000):
|
||||
sendInputVoice(flag=0, ip_address=ip_address, port=port)
|
||||
sleep(0.05)
|
||||
sendInputVoice(flag=1, ip_address=ip_address, port=port)
|
||||
sleep(0.05)
|
||||
sendInputVoice(flag=0, ip_address=ip_address, port=port)
|
||||
sleep(0.05)
|
||||
|
||||
def getOSCParameterValue(address, server_name="VRChat-Client"):
|
||||
value = None
|
||||
try:
|
||||
browser = OSCQueryBrowser()
|
||||
sleep(1)
|
||||
service = browser.find_service_by_name(server_name)
|
||||
if service is not None:
|
||||
oscq = OSCQueryClient(service)
|
||||
mute_self_node = oscq.query_node(address)
|
||||
value = mute_self_node.value[0]
|
||||
browser.zc.close()
|
||||
browser.browser.cancel()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
return value
|
||||
|
||||
def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"):
|
||||
osc_port = get_open_udp_port()
|
||||
http_port = get_open_tcp_port()
|
||||
osc_dispatcher = dispatcher.Dispatcher()
|
||||
for filter, target in dict_filter_and_target.items():
|
||||
osc_dispatcher.map(filter, target)
|
||||
osc_udp_server = osc_server.ThreadingOSCUDPServer((ip_address, osc_port), osc_dispatcher)
|
||||
|
||||
osc_client = OSCQueryService(title, http_port, osc_port)
|
||||
for filter, target in dict_filter_and_target.items():
|
||||
osc_client.advertise_endpoint(filter)
|
||||
|
||||
osc_udp_server.serve_forever()
|
||||
|
||||
if __name__ == "__main__":
|
||||
osc_parameter_prefix = "/avatar/parameters/"
|
||||
osc_avatar_change_path = "/avatar/change"
|
||||
param_MuteSelf = "MuteSelf"
|
||||
param_Voice = "Voice"
|
||||
|
||||
def print_handler_all(address, *args):
|
||||
print(f"all {address}: {args}")
|
||||
|
||||
def print_handler_muteself(address, *args):
|
||||
print(f"muteself {address}: {args}")
|
||||
|
||||
def print_handler_voice(address, *args):
|
||||
print(f"voice {address}: {args}")
|
||||
|
||||
dict_filter_and_target = {
|
||||
# osc_parameter_prefix + "*": print_handler_all,
|
||||
osc_parameter_prefix + param_MuteSelf: print_handler_muteself,
|
||||
osc_parameter_prefix + param_Voice: print_handler_voice,
|
||||
}
|
||||
|
||||
receiveOscParameters(dict_filter_and_target)
|
||||
304
src-python/models/overlay/overlay.py
Normal file
304
src-python/models/overlay/overlay.py
Normal file
@@ -0,0 +1,304 @@
|
||||
import os
|
||||
import ctypes
|
||||
import time
|
||||
from psutil import process_iter
|
||||
from threading import Thread
|
||||
import openvr
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
try:
|
||||
from . import overlay_utils as utils
|
||||
except ImportError:
|
||||
import overlay_utils as utils
|
||||
|
||||
def mat34Id(array):
|
||||
arr = openvr.HmdMatrix34_t()
|
||||
for i in range(3):
|
||||
for j in range(4):
|
||||
arr[i][j] = array[i][j]
|
||||
return arr
|
||||
|
||||
def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation):
|
||||
arr = np.zeros((3, 4))
|
||||
rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation))
|
||||
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
arr[i][j] = rot[i][j]
|
||||
|
||||
arr[0][3] = x_pos * z_pos
|
||||
arr[1][3] = y_pos * z_pos
|
||||
arr[2][3] = - z_pos
|
||||
return arr
|
||||
|
||||
def getHMDBaseMatrix():
|
||||
x_pos = 0.0
|
||||
y_pos = -0.4
|
||||
z_pos = 1.0
|
||||
x_rotation = 0.0
|
||||
y_rotation = 0.0
|
||||
z_rotation = 0.0
|
||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||
return arr
|
||||
|
||||
def getLeftHandBaseMatrix():
|
||||
x_pos = 0.0
|
||||
y_pos = -0.06
|
||||
z_pos = -0.14
|
||||
x_rotation = -62.0
|
||||
y_rotation = 154.0
|
||||
z_rotation = 71.0
|
||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||
return arr
|
||||
|
||||
def getRightHandBaseMatrix():
|
||||
x_pos = 0.0
|
||||
y_pos = -0.06
|
||||
z_pos = -0.14
|
||||
x_rotation = -62.0
|
||||
y_rotation = -154.0
|
||||
z_rotation = -71.0
|
||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||
return arr
|
||||
|
||||
class Overlay:
|
||||
def __init__(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, display_duration, fadeout_duration, opacity, ui_scaling):
|
||||
self.initialized = False
|
||||
settings = {
|
||||
"color": [1, 1, 1],
|
||||
"opacity": opacity,
|
||||
"x_pos": x_pos,
|
||||
"y_pos": y_pos,
|
||||
"z_pos": z_pos,
|
||||
"x_rotation": x_rotation,
|
||||
"y_rotation": y_rotation,
|
||||
"z_rotation": z_rotation,
|
||||
"display_duration": display_duration,
|
||||
"fadeout_duration": fadeout_duration,
|
||||
"ui_scaling": ui_scaling,
|
||||
}
|
||||
self.settings = settings
|
||||
self.system = None
|
||||
self.overlay = None
|
||||
self.handle = None
|
||||
self.lastUpdate = time.monotonic()
|
||||
self.thread_overlay = None
|
||||
self.fadeRatio = 1
|
||||
self.loop = True
|
||||
|
||||
def init(self):
|
||||
try:
|
||||
self.system = openvr.init(openvr.VRApplication_Background)
|
||||
self.overlay = openvr.IVROverlay()
|
||||
self.overlay_system = openvr.IVRSystem()
|
||||
self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI")
|
||||
self.overlay.showOverlay(self.handle)
|
||||
self.initialized = True
|
||||
|
||||
self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)))
|
||||
self.updateColor(self.settings["color"])
|
||||
self.updateOpacity(self.settings["opacity"])
|
||||
self.updateUiScaling(self.settings["ui_scaling"])
|
||||
self.updatePosition(
|
||||
self.settings["x_pos"],
|
||||
self.settings["y_pos"],
|
||||
self.settings["z_pos"],
|
||||
self.settings["x_rotation"],
|
||||
self.settings["y_rotation"],
|
||||
self.settings["z_rotation"],
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print("Could not initialise OpenVR", e)
|
||||
|
||||
def updateImage(self, img):
|
||||
if self.initialized is True:
|
||||
width, height = img.size
|
||||
img = img.tobytes()
|
||||
img = (ctypes.c_char * len(img)).from_buffer_copy(img)
|
||||
self.overlay.setOverlayRaw(self.handle, img, width, height, 4)
|
||||
self.updateOpacity(self.settings["opacity"])
|
||||
self.lastUpdate = time.monotonic()
|
||||
|
||||
def clearImage(self):
|
||||
if self.initialized is True:
|
||||
self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)))
|
||||
|
||||
def updateColor(self, col):
|
||||
"""
|
||||
col is a 3-tuple representing (r, g, b)
|
||||
"""
|
||||
self.settings["color"] = col
|
||||
if self.initialized is True:
|
||||
r, g, b = self.settings["color"]
|
||||
self.overlay.setOverlayColor(self.handle, r, g, b)
|
||||
|
||||
def updateOpacity(self, opacity, with_fade=False):
|
||||
self.settings["opacity"] = opacity
|
||||
|
||||
if self.initialized is True:
|
||||
if with_fade is True:
|
||||
if self.fadeRatio > 0:
|
||||
self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"])
|
||||
else:
|
||||
self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"])
|
||||
|
||||
def updateUiScaling(self, ui_scaling):
|
||||
self.settings["ui_scaling"] = ui_scaling
|
||||
if self.initialized is True:
|
||||
self.overlay.setOverlayWidthInMeters(self.handle, self.settings["ui_scaling"])
|
||||
|
||||
def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker="HMD"):
|
||||
"""
|
||||
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
|
||||
tracker is a string representing the tracker to use ("HMD", "LeftHand", "RightHand")
|
||||
"""
|
||||
|
||||
self.settings["x_pos"] = x_pos
|
||||
self.settings["y_pos"] = y_pos
|
||||
self.settings["z_pos"] = z_pos
|
||||
self.settings["x_rotation"] = x_rotation
|
||||
self.settings["y_rotation"] = y_rotation
|
||||
self.settings["z_rotation"] = z_rotation
|
||||
|
||||
match tracker:
|
||||
case "HMD":
|
||||
base_matrix = getHMDBaseMatrix()
|
||||
trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd
|
||||
case "LeftHand":
|
||||
base_matrix = getLeftHandBaseMatrix()
|
||||
trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand)
|
||||
case "RightHand":
|
||||
base_matrix = getRightHandBaseMatrix()
|
||||
trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand)
|
||||
case _:
|
||||
base_matrix = getHMDBaseMatrix()
|
||||
trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd
|
||||
|
||||
translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings["z_pos"])
|
||||
rotation = (self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"])
|
||||
transform = utils.transform_matrix(base_matrix, translation, rotation)
|
||||
self.transform = mat34Id(transform)
|
||||
|
||||
if self.initialized is True:
|
||||
self.overlay.setOverlayTransformTrackedDeviceRelative(
|
||||
self.handle,
|
||||
trackerIndex,
|
||||
self.transform
|
||||
)
|
||||
|
||||
def updateDisplayDuration(self, display_duration):
|
||||
self.settings["display_duration"] = display_duration
|
||||
|
||||
def updateFadeoutDuration(self, fadeout_duration):
|
||||
self.settings["fadeout_duration"] = fadeout_duration
|
||||
|
||||
def checkActive(self):
|
||||
try:
|
||||
if self.system is not None and self.initialized is True:
|
||||
new_event = openvr.VREvent_t()
|
||||
while self.system.pollNextEvent(new_event):
|
||||
if new_event.eventType == openvr.VREvent_Quit:
|
||||
return False
|
||||
return True
|
||||
except Exception as e:
|
||||
print("Could not check SteamVR running")
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def evaluateOpacityFade(self, lastUpdate, currentTime):
|
||||
if (currentTime - lastUpdate) > self.settings["display_duration"]:
|
||||
timeThroughInterval = currentTime - lastUpdate - self.settings["display_duration"]
|
||||
self.fadeRatio = 1 - timeThroughInterval / self.settings["fadeout_duration"]
|
||||
if self.fadeRatio < 0:
|
||||
self.fadeRatio = 0
|
||||
self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"])
|
||||
|
||||
def update(self):
|
||||
currTime = time.monotonic()
|
||||
if self.settings["fadeout_duration"] != 0:
|
||||
self.evaluateOpacityFade(self.lastUpdate, currTime)
|
||||
else:
|
||||
self.updateOpacity(self.settings["opacity"])
|
||||
|
||||
def mainloop(self):
|
||||
self.loop = True
|
||||
while self.checkActive() is True and self.loop is True:
|
||||
startTime = time.monotonic()
|
||||
self.update()
|
||||
sleepTime = (1 / 16) - (time.monotonic() - startTime)
|
||||
if sleepTime > 0:
|
||||
time.sleep(sleepTime)
|
||||
|
||||
def main(self):
|
||||
self.init()
|
||||
if self.initialized is True:
|
||||
self.mainloop()
|
||||
|
||||
def startOverlay(self):
|
||||
self.thread_overlay = Thread(target=self.main)
|
||||
self.thread_overlay.daemon = True
|
||||
self.thread_overlay.start()
|
||||
|
||||
def shutdownOverlay(self):
|
||||
if isinstance(self.thread_overlay, Thread):
|
||||
self.loop = False
|
||||
self.thread_overlay.join()
|
||||
self.thread_overlay = None
|
||||
if isinstance(self.overlay, openvr.IVROverlay) and isinstance(self.handle, int):
|
||||
self.overlay.destroyOverlay(self.handle)
|
||||
self.overlay = None
|
||||
if isinstance(self.system, openvr.IVRSystem):
|
||||
openvr.shutdown()
|
||||
self.system = None
|
||||
self.initialized = False
|
||||
|
||||
@staticmethod
|
||||
def checkSteamvrRunning() -> bool:
|
||||
_proc_name = "vrmonitor.exe" if os.name == "nt" else "vrmonitor"
|
||||
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.createOverlayImageShort("こんにちは、世界!さようなら", "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.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura")
|
||||
# overlay.updateImage(img)
|
||||
# time.sleep(0.5)
|
||||
|
||||
# img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese")
|
||||
# overlay.updateImage(img)
|
||||
# time.sleep(0.5)
|
||||
|
||||
# overlay.shutdownOverlay()
|
||||
|
||||
x_pos = 0
|
||||
y_pos = 0
|
||||
z_pos = 0
|
||||
x_rotation = 0
|
||||
y_rotation = 0
|
||||
z_rotation = 0
|
||||
|
||||
base_matrix = getLeftHandBaseMatrix()
|
||||
translation = (x_pos * z_pos, y_pos * z_pos, z_pos)
|
||||
rotation = (x_rotation, y_rotation, z_rotation)
|
||||
transform = utils.transform_matrix(base_matrix, translation, rotation)
|
||||
transform = mat34Id(transform)
|
||||
print(transform)
|
||||
231
src-python/models/overlay/overlay_image.py
Normal file
231
src-python/models/overlay/overlay_image.py
Normal file
@@ -0,0 +1,231 @@
|
||||
from os import path as os_path
|
||||
# from datetime import datetime
|
||||
from typing import Tuple
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
class OverlayImage:
|
||||
# TEXT_COLOR_LARGE = (223, 223, 223)
|
||||
# TEXT_COLOR_SMALL = (190, 190, 190)
|
||||
# TEXT_COLOR_SEND = (70, 161, 146)
|
||||
# TEXT_COLOR_RECEIVE = (220, 20, 60)
|
||||
# TEXT_COLOR_TIME = (120, 120, 120)
|
||||
# FONT_SIZE_LARGE = HEIGHT
|
||||
# FONT_SIZE_SMALL = int(FONT_SIZE_LARGE * 2 / 3)
|
||||
LANGUAGES = {
|
||||
"Japanese": "NotoSansJP-Regular",
|
||||
"Korean": "NotoSansKR-Regular",
|
||||
"Chinese Simplified": "NotoSansSC-Regular",
|
||||
"Chinese Traditional": "NotoSansTC-Regular",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def concatenateImagesVertically(img1: Image, img2: Image) -> Image:
|
||||
dst = Image.new("RGBA", (img1.width, img1.height + img2.height))
|
||||
dst.paste(img1, (0, 0))
|
||||
dst.paste(img2, (0, img1.height))
|
||||
return dst
|
||||
|
||||
@staticmethod
|
||||
def addImageMargin(image: Image, top: int, right: int, bottom: int, left: int, color: Tuple[int, int, int, int]) -> Image:
|
||||
width, height = image.size
|
||||
new_width = width + right + left
|
||||
new_height = height + top + bottom
|
||||
result = Image.new(image.mode, (new_width, new_height), color)
|
||||
result.paste(image, (left, top))
|
||||
return result
|
||||
|
||||
# def create_textimage(self, message_type, size, text, language):
|
||||
# font_size = self.FONT_SIZE_LARGE if size == "large" else self.FONT_SIZE_SMALL
|
||||
# text_color = self.TEXT_COLOR_LARGE if size == "large" else self.TEXT_COLOR_SMALL
|
||||
# anchor = "lm" if message_type == "receive" else "rm"
|
||||
# text_x = 0 if message_type == "receive" else self.WIDTH
|
||||
# align = "left" if message_type == "receive" else "right"
|
||||
|
||||
# font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular")
|
||||
# img = Image.new("RGBA", (0, 0), (0, 0, 0, 0))
|
||||
# draw = ImageDraw.Draw(img)
|
||||
# font = ImageFont.truetype(os_path.join(os_path.dirname(__file__), "fonts", f"{font_family}.ttf"), font_size)
|
||||
# # font = ImageFont.truetype(os_path.join("./fonts", f"{font_family}.ttf"), font_size)
|
||||
# text_width = draw.textlength(text, font)
|
||||
# character_width = text_width // len(text)
|
||||
# character_line_num = int(self.WIDTH // character_width)
|
||||
# if len(text) > character_line_num:
|
||||
# text = "\n".join([text[i:i+character_line_num] for i in range(0, len(text), character_line_num)])
|
||||
|
||||
# n_num = len(text.split("\n")) - 1
|
||||
# text_height = int(font_size*(n_num+2))
|
||||
|
||||
# img = Image.new("RGBA", (self.WIDTH, text_height), (0, 0, 0, 0))
|
||||
# draw = ImageDraw.Draw(img)
|
||||
|
||||
# text_y = text_height // 2
|
||||
|
||||
# draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align)
|
||||
# return img
|
||||
|
||||
# def create_textimage_message_type(self, message_type):
|
||||
# anchor = "lm" if message_type == "receive" else "rm"
|
||||
# text = "Receive" if message_type == "receive" else "Send"
|
||||
# text_color = self.TEXT_COLOR_RECEIVE if message_type == "receive" else self.TEXT_COLOR_SEND
|
||||
# text_color_time = self.TEXT_COLOR_TIME
|
||||
|
||||
# now = datetime.now()
|
||||
# formatted_time = now.strftime("%H:%M")
|
||||
# font_size = self.FONT_SIZE_SMALL
|
||||
# img = Image.new("RGBA", (0, 0), (0, 0, 0, 0))
|
||||
# draw = ImageDraw.Draw(img)
|
||||
# font = ImageFont.truetype(os_path.join(os_path.dirname(__file__), "fonts", "NotoSansJP-Regular.ttf"), font_size)
|
||||
# # font = ImageFont.truetype(os_path.join("./fonts", "NotoSansJP-Regular.ttf"), font_size)
|
||||
# text_height = font_size*2
|
||||
# text_width = draw.textlength(formatted_time, font)
|
||||
# character_width = text_width // len(formatted_time)
|
||||
# img = Image.new("RGBA", (self.WIDTH, text_height), (0, 0, 0, 0))
|
||||
# draw = ImageDraw.Draw(img)
|
||||
# text_y = text_height // 2
|
||||
# text_time_x = 0 if message_type == "receive" else self.WIDTH - (text_width + character_width)
|
||||
# text_x = (text_width + character_width) if message_type == "receive" else self.WIDTH
|
||||
|
||||
# draw.text((text_time_x, text_y), formatted_time, text_color_time, anchor=anchor, stroke_width=0, font=font)
|
||||
# draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
||||
# return img
|
||||
|
||||
# def create_textbox(self, message_type, message, your_language, translation, target_language):
|
||||
# message_type_img = self.create_textimage_message_type(message_type)
|
||||
# if len(translation) > 0 and target_language is not None:
|
||||
# img = self.create_textimage(message_type, "small", message, your_language)
|
||||
# translation_img = self.create_textimage(message_type, "large",translation, target_language)
|
||||
# img = self.concatenateImagesVertically(img, translation_img)
|
||||
# else:
|
||||
# img = self.create_textimage(message_type, "large", message, your_language)
|
||||
# return self.concatenateImagesVertically(message_type_img, img)
|
||||
|
||||
# def create_overlay_image_long(self, message_type, message, your_language, translation="", target_language=None):
|
||||
# if len(self.log_data) > 10:
|
||||
# self.log_data.pop(0)
|
||||
|
||||
# self.log_data.append(
|
||||
# {
|
||||
# "message_type":message_type,
|
||||
# "message":message,
|
||||
# "your_language":your_language,
|
||||
# "translation":translation,
|
||||
# "target_language":target_language,
|
||||
# }
|
||||
# )
|
||||
|
||||
# imgs = []
|
||||
# for log in self.log_data:
|
||||
# message_type = log["message_type"]
|
||||
# message = log["message"]
|
||||
# your_language = log["your_language"]
|
||||
# translation = log["translation"]
|
||||
# target_language = log["target_language"]
|
||||
# img = self.create_textbox(message_type, message, your_language, translation, target_language)
|
||||
# imgs.append(img)
|
||||
|
||||
# img = imgs[0]
|
||||
# for i in imgs[1:]:
|
||||
# img = self.concatenateImagesVertically(img, i)
|
||||
# img = self.addImageMargin(img, 0, 20, 0, 20, (0, 0, 0, 0))
|
||||
|
||||
# width, height = img.size
|
||||
# background = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
# draw = ImageDraw.Draw(background)
|
||||
# draw.rounded_rectangle([(0, 0), (width, height)], radius=15, fill=self.BACKGROUND_COLOR, outline=self.BACKGROUND_OUTLINE_COLOR, width=5)
|
||||
# img = Image.alpha_composite(background, img)
|
||||
# return img
|
||||
|
||||
def getUiSize(self):
|
||||
return {
|
||||
"width": int(960*4),
|
||||
"height": int(23*4),
|
||||
"font_size": int(23*4),
|
||||
}
|
||||
|
||||
def getUiColors(self, ui_type):
|
||||
match ui_type:
|
||||
case "default":
|
||||
background_color = (41, 42, 45)
|
||||
background_outline_color = (41, 42, 45)
|
||||
text_color = (223, 223, 223)
|
||||
case "sakura":
|
||||
background_color = (225, 40, 30)
|
||||
background_outline_color = (255, 255, 255)
|
||||
text_color = (223, 223, 223)
|
||||
return {
|
||||
"background_color": background_color,
|
||||
"background_outline_color": background_outline_color,
|
||||
"text_color": text_color
|
||||
}
|
||||
|
||||
def createDecorationImage(self, ui_type, image_size):
|
||||
decoration_image = Image.new("RGBA", image_size, (0, 0, 0, 0))
|
||||
match ui_type:
|
||||
case "default":
|
||||
pass
|
||||
case "sakura":
|
||||
margin = 7
|
||||
alpha_ratio = 0.4
|
||||
overlay_tl = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_tl_sakura.png"))
|
||||
overlay_br = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_br_sakura.png"))
|
||||
if overlay_tl.size[1] > image_size[1]:
|
||||
overlay_tl = overlay_tl.resize((image_size[1]-margin, image_size[1]-margin))
|
||||
if overlay_br.size[1] > image_size[1]:
|
||||
overlay_br = overlay_br.resize((image_size[1]-margin, image_size[1]-margin))
|
||||
|
||||
alpha = overlay_tl.getchannel("A")
|
||||
alpha = alpha.point(lambda x: x * alpha_ratio)
|
||||
overlay_tl.putalpha(alpha)
|
||||
alpha = overlay_br.getchannel("A")
|
||||
alpha = alpha.point(lambda x: x * alpha_ratio)
|
||||
overlay_br.putalpha(alpha)
|
||||
decoration_image.paste(overlay_tl, (margin, margin))
|
||||
decoration_image.paste(overlay_br, (image_size[0]-overlay_br.size[0]-margin, image_size[1]-overlay_br.size[1]-margin))
|
||||
return decoration_image
|
||||
|
||||
def createTextboxShort(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)
|
||||
font = ImageFont.truetype(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "fonts", f"{font_family}.ttf"), font_size)
|
||||
text_width = draw.textlength(text, font)
|
||||
character_width = text_width // len(text)
|
||||
character_line_num = int((base_width) // character_width) - 12
|
||||
if len(text) > character_line_num:
|
||||
text = "\n".join([text[i:i+character_line_num] for i in range(0, len(text), character_line_num)])
|
||||
text_height = font_size * (len(text.split("\n")) + 1) + 20
|
||||
img = Image.new("RGBA", (base_width, text_height), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
text_x = base_width // 2
|
||||
text_y = text_height // 2
|
||||
draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center")
|
||||
return img
|
||||
|
||||
def createOverlayImageShort(self, message, your_language, translation="", target_language=None, ui_type="default"):
|
||||
ui_size = self.getUiSize()
|
||||
height = ui_size["height"]
|
||||
width = ui_size["width"]
|
||||
font_size = ui_size["font_size"]
|
||||
|
||||
ui_colors = self.getUiColors(ui_type)
|
||||
text_color = ui_colors["text_color"]
|
||||
background_color = ui_colors["background_color"]
|
||||
background_outline_color = ui_colors["background_outline_color"]
|
||||
|
||||
img = self.createTextboxShort(message, your_language, text_color, width, height, font_size)
|
||||
if len(translation) > 0 and target_language is not None:
|
||||
translation_img = self.createTextboxShort(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))
|
||||
draw = ImageDraw.Draw(background)
|
||||
draw.rounded_rectangle([(0, 0), img.size], radius=30, fill=background_color, outline=background_outline_color, width=5)
|
||||
|
||||
decoration_image = self.createDecorationImage(ui_type, img.size)
|
||||
background = Image.alpha_composite(background, decoration_image)
|
||||
img = Image.alpha_composite(background, img)
|
||||
return img
|
||||
87
src-python/models/overlay/overlay_utils.py
Normal file
87
src-python/models/overlay/overlay_utils.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import numpy as np
|
||||
|
||||
def toHomogeneous(matrix):
|
||||
homogeneous_matrix = np.vstack([matrix, [0, 0, 0, 1]])
|
||||
return homogeneous_matrix
|
||||
|
||||
# 移動行列を生成する関数
|
||||
def calcTranslationMatrix(translation):
|
||||
tx, ty, tz = translation
|
||||
return np.array([
|
||||
[1, 0, 0, tx],
|
||||
[0, 1, 0, ty],
|
||||
[0, 0, 1, tz],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
|
||||
# X軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixX(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
return np.array([
|
||||
[1, 0, 0, 0],
|
||||
[0, c, -s, 0],
|
||||
[0, s, c, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
|
||||
# Y軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixY(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
return np.array([
|
||||
[c, 0, s, 0],
|
||||
[0, 1, 0, 0],
|
||||
[-s, 0, c, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
|
||||
# Z軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixZ(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
return np.array([
|
||||
[c, -s, 0, 0],
|
||||
[s, c, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
|
||||
# 3x4行列の座標を基準として回転や移動を行う関数
|
||||
def transform_matrix(base_matrix, translation, rotation):
|
||||
homogeneous_base_matrix = toHomogeneous(base_matrix)
|
||||
translation_matrix = calcTranslationMatrix(translation)
|
||||
rotation_matrix_x = calcRotationMatrixX(rotation[0])
|
||||
rotation_matrix_y = calcRotationMatrixY(rotation[1])
|
||||
rotation_matrix_z = calcRotationMatrixZ(rotation[2])
|
||||
rotation_matrix = np.dot(rotation_matrix_z, np.dot(rotation_matrix_y, rotation_matrix_x))
|
||||
transformation_matrix = translation_matrix.copy()
|
||||
transformation_matrix[:3, :3] = rotation_matrix[:3, :3]
|
||||
result_matrix = np.dot(homogeneous_base_matrix, transformation_matrix)
|
||||
return result_matrix[:3, :]
|
||||
|
||||
def euler_to_rotation_matrix(angles):
|
||||
phi = angles[0] * np.pi / 180
|
||||
theta = angles[1] * np.pi / 180
|
||||
psi = angles[2]* np.pi / 180
|
||||
R_x = np.array([[1, 0, 0],
|
||||
[0, np.cos(phi), -np.sin(phi)],
|
||||
[0, np.sin(phi), np.cos(phi)]])
|
||||
R_y = np.array([[np.cos(theta), 0, np.sin(theta)],
|
||||
[0, 1, 0],
|
||||
[-np.sin(theta), 0, np.cos(theta)]])
|
||||
R_z = np.array([[np.cos(psi), -np.sin(psi), 0],
|
||||
[np.sin(psi), np.cos(psi), 0],
|
||||
[0, 0, 1]])
|
||||
return np.dot(R_z, np.dot(R_y, R_x))
|
||||
|
||||
if __name__ == "__main__":
|
||||
base_matrix = np.array([
|
||||
[1, 0, 0, 1],
|
||||
[0, 1, 0, 1],
|
||||
[0, 0, 1, 1]
|
||||
])
|
||||
translation = [1, 2, 3]
|
||||
rotation = [0, 0, 90]
|
||||
result_matrix = transform_matrix(base_matrix, translation, rotation)
|
||||
print(result_matrix)
|
||||
730
src-python/models/transcription/transcription_languages.py
Normal file
730
src-python/models/transcription/transcription_languages.py
Normal file
@@ -0,0 +1,730 @@
|
||||
transcription_lang = {
|
||||
"Afrikaans":{
|
||||
"South Africa":{
|
||||
"Google": "af-ZA",
|
||||
"Whisper": "af",
|
||||
},
|
||||
},
|
||||
"Albanian":{
|
||||
"Albania":{
|
||||
"Google": "sq-AL",
|
||||
"Whisper": "sq",
|
||||
},
|
||||
},
|
||||
"Amharic":{
|
||||
"Ethiopia":{
|
||||
"Google": "am-ET",
|
||||
"Whisper": "am",
|
||||
},
|
||||
},
|
||||
"Arabic":{
|
||||
"Algeria":{
|
||||
"Google": "ar-DZ",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Bahrain":{
|
||||
"Google": "ar-BH",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Egypt":{
|
||||
"Google": "ar-EG",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Israel":{
|
||||
"Google": "ar-IL",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Iraq":{
|
||||
"Google": "ar-IQ",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Jordan":{
|
||||
"Google": "ar-JO",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Kuwait":{
|
||||
"Google": "ar-KW",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Lebanon":{
|
||||
"Google": "ar-LB",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Mauritania":{
|
||||
"Google": "ar-MR",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Morocco":{
|
||||
"Google": "ar-MA",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Oman":{
|
||||
"Google": "ar-OM",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Qatar":{
|
||||
"Google": "ar-QA",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Saudi Arabia":{
|
||||
"Google": "ar-SA",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Palestine":{
|
||||
"Google": "ar-PS",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Syria":{
|
||||
"Google": "ar-SY",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Tunisia":{
|
||||
"Google": "ar-TN",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"United Arab Emirates":{
|
||||
"Google": "ar-AE",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
"Yemen":{
|
||||
"Google": "ar-YE",
|
||||
"Whisper": "ar",
|
||||
},
|
||||
},
|
||||
"Armenian": {
|
||||
"Armenia": {
|
||||
"Google": "hy-AM",
|
||||
"Whisper": "hy",
|
||||
},
|
||||
},
|
||||
"Azerbaijani": {
|
||||
"Azerbaijan": {
|
||||
"Google": "az-AZ",
|
||||
"Whisper": "az",
|
||||
},
|
||||
},
|
||||
"Basque":{
|
||||
"Spain":{
|
||||
"Google": "eu-ES",
|
||||
"Whisper": "eu",
|
||||
},
|
||||
},
|
||||
"Bengali":{
|
||||
"Bangladesh":{
|
||||
"Google": "bn-BD",
|
||||
"Whisper": "bn",
|
||||
},
|
||||
"India":{
|
||||
"Google": "bn-IN",
|
||||
"Whisper": "bn",
|
||||
},
|
||||
},
|
||||
"Bosnian":{
|
||||
"Bosnia and Herzegovina":{
|
||||
"Google": "bs-BA",
|
||||
"Whisper": "bs",
|
||||
}
|
||||
},
|
||||
"Bulgarian":{
|
||||
"Bulgaria":{
|
||||
"Google": "bg-BG",
|
||||
"Whisper": "bg",
|
||||
},
|
||||
},
|
||||
"Burmese":{
|
||||
"Myanmar":{
|
||||
"Google": "my-MM",
|
||||
"Whisper": "my",
|
||||
},
|
||||
},
|
||||
"Catalan":{
|
||||
"Spain":{
|
||||
"Google": "ca-ES",
|
||||
"Whisper": "ca",
|
||||
},
|
||||
},
|
||||
"Chinese Simplified":{
|
||||
"China":{
|
||||
"Google": "cmn-Hans-CN",
|
||||
"Whisper": "zh",
|
||||
},
|
||||
"Hong Kong":{
|
||||
"Google": "cmn-Hans-HK",
|
||||
"Whisper": "zh",
|
||||
},
|
||||
},
|
||||
"Chinese Traditional":{
|
||||
"Taiwan":{
|
||||
"Google": "cmn-Hant-TW",
|
||||
"Whisper": "zh",
|
||||
},
|
||||
"Hong Kong":{
|
||||
"Google": "yue-Hant-HK",
|
||||
"Whisper": "yue",
|
||||
},
|
||||
},
|
||||
"Croatian":{
|
||||
"Croatia":{
|
||||
"Google": "hr-HR",
|
||||
"Whisper": "hr",
|
||||
},
|
||||
},
|
||||
"Czech":{
|
||||
"Czech Republic":{
|
||||
"Google": "cs-CZ",
|
||||
"Whisper": "cs",
|
||||
},
|
||||
},
|
||||
"Danish":{
|
||||
"Denmark":{
|
||||
"Google": "da-DK",
|
||||
"Whisper": "da",
|
||||
},
|
||||
},
|
||||
"Dutch":{
|
||||
"Belgium":{
|
||||
"Google": "nl-BE",
|
||||
"Whisper": "nl",
|
||||
},
|
||||
"Netherlands":{
|
||||
"Google": "nl-NL",
|
||||
"Whisper": "nl",
|
||||
},
|
||||
},
|
||||
"English": {
|
||||
"Australia":{
|
||||
"Google": "en-AU",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Canada":{
|
||||
"Google": "en-CA",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Ghana":{
|
||||
"Google": "en-GH",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Hong Kong":{
|
||||
"Google": "en-HK",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"India":{
|
||||
"Google": "en-IN",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Ireland":{
|
||||
"Google": "en-IE",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Kenya":{
|
||||
"Google": "en-KE",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"New Zealand":{
|
||||
"Google": "en-NZ",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Nigeria":{
|
||||
"Google": "en-NG",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Philippines":{
|
||||
"Google": "en-PH",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Singapore":{
|
||||
"Google": "en-SG",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"South Africa":{
|
||||
"Google": "en-ZA",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"Tanzania":{
|
||||
"Google": "en-TZ",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"United Kingdom":{
|
||||
"Google": "en-GB",
|
||||
"Whisper": "en",
|
||||
},
|
||||
"United States":{
|
||||
"Google": "en-US",
|
||||
"Whisper": "en",
|
||||
},
|
||||
},
|
||||
"Estonian":{
|
||||
"Estonia":{
|
||||
"Google": "et-EE",
|
||||
"Whisper": "et",
|
||||
},
|
||||
},
|
||||
"Filipino":{
|
||||
"Philippines":{
|
||||
"Google": "fil-PH",
|
||||
"Whisper": "tl",
|
||||
},
|
||||
},
|
||||
"Finnish":{
|
||||
"Finland":{
|
||||
"Google": "fi-FI",
|
||||
"Whisper": "fi",
|
||||
},
|
||||
},
|
||||
"French":{
|
||||
"Belgium":{
|
||||
"Google": "fr-BE",
|
||||
"Whisper": "fr",
|
||||
},
|
||||
"Canada":{
|
||||
"Google": "fr-CA",
|
||||
"Whisper": "fr",
|
||||
},
|
||||
"France":{
|
||||
"Google": "fr-FR",
|
||||
"Whisper": "fr",
|
||||
},
|
||||
"Switzerland":{
|
||||
"Google": "fr-CH",
|
||||
"Whisper": "fr",
|
||||
},
|
||||
},
|
||||
"Galician":{
|
||||
"Spain":{
|
||||
"Google": "gl-ES",
|
||||
"Whisper": "gl",
|
||||
},
|
||||
},
|
||||
"Georgian":{
|
||||
"Georgia":{
|
||||
"Google": "ka-GE",
|
||||
"Whisper": "ka",
|
||||
},
|
||||
},
|
||||
"German":{
|
||||
"Austria":{
|
||||
"Google": "de-AT",
|
||||
"Whisper": "de",
|
||||
},
|
||||
"Germany":{
|
||||
"Google": "de-DE",
|
||||
"Whisper": "de",
|
||||
},
|
||||
"Switzerland":{
|
||||
"Google": "de-CH",
|
||||
"Whisper": "de",
|
||||
},
|
||||
},
|
||||
"Greek":{
|
||||
"Greece":{
|
||||
"Google": "el-GR",
|
||||
"Whisper": "el",
|
||||
},
|
||||
},
|
||||
"Gujarati":{
|
||||
"India":{
|
||||
"Google": "gu-IN",
|
||||
"Whisper": "gu",
|
||||
},
|
||||
},
|
||||
"Hebrew":{
|
||||
"Israel":{
|
||||
"Google": "iw-IL",
|
||||
"Whisper": "he",
|
||||
},
|
||||
},
|
||||
"Hindi": {
|
||||
"India":{
|
||||
"Google": "hi-IN",
|
||||
"Whisper": "hi",
|
||||
},
|
||||
},
|
||||
"Hungarian":{
|
||||
"Hungary":{
|
||||
"Google": "hu-HU",
|
||||
"Whisper": "hu",
|
||||
},
|
||||
},
|
||||
"Icelandic":{
|
||||
"Iceland":{
|
||||
"Google": "is-IS",
|
||||
"Whisper": "is",
|
||||
},
|
||||
},
|
||||
"Indonesian":{
|
||||
"Indonesia":{
|
||||
"Google": "id-ID",
|
||||
"Whisper": "id",
|
||||
},
|
||||
},
|
||||
"Italian":{
|
||||
"Italy":{
|
||||
"Google": "it-IT",
|
||||
"Whisper": "it",
|
||||
},
|
||||
"Switzerland":{
|
||||
"Google": "it-CH",
|
||||
"Whisper": "it",
|
||||
},
|
||||
},
|
||||
"Japanese":{
|
||||
"Japan":{
|
||||
"Google": "ja-JP",
|
||||
"Whisper": "ja",
|
||||
},
|
||||
},
|
||||
# "Javanese":{
|
||||
# "Indonesia":{
|
||||
# "Google": "jv-ID",
|
||||
# },
|
||||
# },
|
||||
"Kannada":{
|
||||
"India":{
|
||||
"Google": "kn-IN",
|
||||
"Whisper": "kn",
|
||||
},
|
||||
},
|
||||
"Kazakh":{
|
||||
"Kazakhstan":{
|
||||
"Google": "kk-KZ",
|
||||
"Whisper": "kk",
|
||||
},
|
||||
},
|
||||
"Khmer":{
|
||||
"Cambodia":{
|
||||
"Google": "km-KH",
|
||||
"Whisper": "km",
|
||||
},
|
||||
},
|
||||
# "Kinyarwanda":{
|
||||
# "rwanda":{
|
||||
# "Google": "rw-RW",
|
||||
# },
|
||||
# },
|
||||
"Korean":{
|
||||
"South Korea":{
|
||||
"Google": "ko-KR",
|
||||
"Whisper": "ko",
|
||||
},
|
||||
},
|
||||
"Lao":{
|
||||
"Laos":{
|
||||
"Google": "lo-LA",
|
||||
"Whisper": "lo",
|
||||
},
|
||||
},
|
||||
"Latvian":{
|
||||
"Latvia":{
|
||||
"Google": "lv-LV",
|
||||
"Whisper": "lv",
|
||||
},
|
||||
},
|
||||
"Lithuanian":{
|
||||
"Lithuania":{
|
||||
"Google": "lt-LT",
|
||||
"Whisper": "lt",
|
||||
},
|
||||
},
|
||||
"Macedonian":{
|
||||
"North Macedonia":{
|
||||
"Google": "mk-MK",
|
||||
"Whisper": "mk",
|
||||
},
|
||||
},
|
||||
"Malay":{
|
||||
"Malaysia":{
|
||||
"Google": "ms-MY",
|
||||
"Whisper": "ms",
|
||||
},
|
||||
},
|
||||
"Malayalam":{
|
||||
"India":{
|
||||
"Google": "ml-IN",
|
||||
"Whisper": "ml",
|
||||
},
|
||||
},
|
||||
"Mongolian":{
|
||||
"Mongolia":{
|
||||
"Google": "mn-MN",
|
||||
"Whisper": "mn",
|
||||
},
|
||||
},
|
||||
"Nepali":{
|
||||
"Nepal":{
|
||||
"Google": "ne-NP",
|
||||
"Whisper": "ne",
|
||||
},
|
||||
},
|
||||
"Norwegian":{
|
||||
"Norway":{
|
||||
"Google": "no-NO",
|
||||
"Whisper": "no",
|
||||
},
|
||||
},
|
||||
"Persian":{
|
||||
"Iran":{
|
||||
"Google": "fa-IR",
|
||||
"Whisper": "fa",
|
||||
},
|
||||
},
|
||||
"Polish":{
|
||||
"Poland":{
|
||||
"Google": "pl-PL",
|
||||
"Whisper": "pl",
|
||||
},
|
||||
},
|
||||
"Portuguese":{
|
||||
"Brazil":{
|
||||
"Google": "pt-BR",
|
||||
"Whisper": "pt",
|
||||
},
|
||||
"Portugal":{
|
||||
"Google": "pt-PT",
|
||||
"Whisper": "pt",
|
||||
},
|
||||
},
|
||||
# "Punjabi":{
|
||||
# "India":{
|
||||
# "Google": "pa-Guru-IN",
|
||||
# },
|
||||
# },
|
||||
"Romanian":{
|
||||
"Romania":{
|
||||
"Google": "ro-RO",
|
||||
"Whisper": "ro",
|
||||
},
|
||||
},
|
||||
"Russian":{
|
||||
"Russia":{
|
||||
"Google": "ru-RU",
|
||||
"Whisper": "ru",
|
||||
},
|
||||
},
|
||||
"Serbian":{
|
||||
"Serbia":{
|
||||
"Google": "sr-RS",
|
||||
"Whisper": "sr",
|
||||
},
|
||||
},
|
||||
"Sinhala":{
|
||||
"Sri Lanka":{
|
||||
"Google": "si-LK",
|
||||
"Whisper": "si",
|
||||
},
|
||||
},
|
||||
"Slovak":{
|
||||
"Slovakia":{
|
||||
"Google": "sk-SK",
|
||||
"Whisper": "sk",
|
||||
},
|
||||
},
|
||||
"Slovenian":{
|
||||
"Slovenia":{
|
||||
"Google": "sl-SI",
|
||||
"Whisper": "sl",
|
||||
},
|
||||
},
|
||||
# "Sesotho":{
|
||||
# "South Africa":{
|
||||
# "Google": "st-ZA",
|
||||
# },
|
||||
# },
|
||||
"Spanish":{
|
||||
"Argentina":{
|
||||
"Google": "es-AR",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Bolivia":{
|
||||
"Google": "es-BO",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Chile":{
|
||||
"Google": "es-CL",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Colombia":{
|
||||
"Google": "es-CO",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Costa Rica":{
|
||||
"Google": "es-CR",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Dominican Republic":{
|
||||
"Google": "es-DO",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Ecuador":{
|
||||
"Google": "es-EC",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"El Salvador":{
|
||||
"Google": "es-SV",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Guatemala":{
|
||||
"Google": "es-GT",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Honduras":{
|
||||
"Google": "es-HN",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Mexico":{
|
||||
"Google": "es-MX",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Nicaragua":{
|
||||
"Google": "es-NI",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Panama":{
|
||||
"Google": "es-PA",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Paraguay":{
|
||||
"Google": "es-PY",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Peru":{
|
||||
"Google": "es-PE",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Puerto Rico":{
|
||||
"Google": "es-PR",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Spain":{
|
||||
"Google": "es-ES",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"United States":{
|
||||
"Google": "es-US",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Uruguay":{
|
||||
"Google": "es-UY",
|
||||
"Whisper": "es",
|
||||
},
|
||||
"Venezuela":{
|
||||
"Google": "es-VE",
|
||||
"Whisper": "es",
|
||||
},
|
||||
},
|
||||
"Sundanese":{
|
||||
"Indonesia":{
|
||||
"Google": "su-ID",
|
||||
"Whisper": "su",
|
||||
},
|
||||
},
|
||||
"Swahili":{
|
||||
"Kenya":{
|
||||
"Google": "sw-KE",
|
||||
"Whisper": "sw",
|
||||
},
|
||||
"Tanzania":{
|
||||
"Google": "sw-TZ",
|
||||
"Whisper": "sw",
|
||||
},
|
||||
},
|
||||
# "Swazi":{
|
||||
# "Eswatini":{
|
||||
# "Google": "ss-Latn-ZA",
|
||||
# },
|
||||
# },
|
||||
"Swedish":{
|
||||
"Sweden":{
|
||||
"Google": "sv-SE",
|
||||
"Whisper": "sv",
|
||||
},
|
||||
},
|
||||
"Tamil":{
|
||||
"India":{
|
||||
"Google": "ta-IN",
|
||||
"Whisper": "ta",
|
||||
},
|
||||
"malaysia":{
|
||||
"Google": "ta-MY",
|
||||
"Whisper": "ta",
|
||||
},
|
||||
"Singapore":{
|
||||
"Google": "ta-SG",
|
||||
"Whisper": "ta",
|
||||
},
|
||||
"Sri Lanka":{
|
||||
"Google": "ta-LK",
|
||||
"Whisper": "ta",
|
||||
},
|
||||
},
|
||||
"Telugu":{
|
||||
"India":{
|
||||
"Google": "te-IN",
|
||||
"Whisper": "te",
|
||||
},
|
||||
},
|
||||
"Thai":{
|
||||
"Thailand":{
|
||||
"Google": "th-TH",
|
||||
"Whisper": "th",
|
||||
},
|
||||
},
|
||||
# "Tsonga":{
|
||||
# "South Africa":{
|
||||
# "Google": "ts-ZA",
|
||||
# },
|
||||
# },
|
||||
# "Setswana":{
|
||||
# "South Africa":{
|
||||
# "Google": "tn-Latn-ZA",
|
||||
# },
|
||||
# },
|
||||
"Turkish":{
|
||||
"Turkey":{
|
||||
"Google": "tr-TR",
|
||||
"Whisper": "tr",
|
||||
},
|
||||
},
|
||||
"Ukrainian":{
|
||||
"Ukraine":{
|
||||
"Google": "uk-UA",
|
||||
"Whisper": "uk",
|
||||
},
|
||||
},
|
||||
"Urdu":{
|
||||
"India":{
|
||||
"Google": "ur-IN",
|
||||
"Whisper": "ur",
|
||||
},
|
||||
"Pakistan":{
|
||||
"Google": "ur-PK",
|
||||
"Whisper": "ur",
|
||||
},
|
||||
},
|
||||
"Uzbek":{
|
||||
"Uzbekistan":{
|
||||
"Google": "uz-UZ",
|
||||
"Whisper": "uz",
|
||||
},
|
||||
},
|
||||
# "Venda":{
|
||||
# "South Africa":{
|
||||
# "Google": "ve-ZA",
|
||||
# },
|
||||
# },
|
||||
"Vietnamese":{
|
||||
"Vietnam":{
|
||||
"Google": "vi-VN",
|
||||
"Whisper": "vi",
|
||||
},
|
||||
},
|
||||
# "Xhosa":{
|
||||
# "South Africa":{
|
||||
# "Google": "xh-ZA",
|
||||
# },
|
||||
# },
|
||||
# "Zulu":{
|
||||
# "South Africa":{
|
||||
# "Google": "zu-ZA",
|
||||
# },
|
||||
# },
|
||||
}
|
||||
142
src-python/models/transcription/transcription_recorder.py
Normal file
142
src-python/models/transcription/transcription_recorder.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from speech_recognition import Recognizer, Microphone
|
||||
from pyaudiowpatch import get_sample_size, paInt16
|
||||
from datetime import datetime
|
||||
from queue import Queue
|
||||
|
||||
class BaseRecorder:
|
||||
def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
self.recorder = Recognizer()
|
||||
self.recorder.energy_threshold = energy_threshold
|
||||
self.recorder.dynamic_energy_threshold = dynamic_energy_threshold
|
||||
self.record_timeout = record_timeout
|
||||
self.stop = None
|
||||
|
||||
if source is None:
|
||||
raise ValueError("audio source can't be None")
|
||||
|
||||
self.source = source
|
||||
|
||||
def adjustForNoise(self):
|
||||
with self.source:
|
||||
self.recorder.adjust_for_ambient_noise(self.source)
|
||||
|
||||
def recordIntoQueue(self, audio_queue):
|
||||
def record_callback(_, audio):
|
||||
audio_queue.put((audio.get_raw_data(), datetime.now()))
|
||||
|
||||
self.stop, self.pause, self.resume = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout)
|
||||
|
||||
class SelectedMicRecorder(BaseRecorder):
|
||||
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
source=Microphone(
|
||||
device_index=device['index'],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
)
|
||||
super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout)
|
||||
# self.adjustForNoise()
|
||||
|
||||
class SelectedSpeakerRecorder(BaseRecorder):
|
||||
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
|
||||
source = Microphone(speaker=True,
|
||||
device_index= device["index"],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
chunk_size=get_sample_size(paInt16),
|
||||
channels=device["maxInputChannels"]
|
||||
)
|
||||
super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout)
|
||||
# self.adjustForNoise()
|
||||
|
||||
class BaseEnergyRecorder:
|
||||
def __init__(self, source):
|
||||
self.recorder = Recognizer()
|
||||
self.recorder.energy_threshold = 0
|
||||
self.recorder.dynamic_energy_threshold = False
|
||||
self.record_timeout = 0
|
||||
self.stop = None
|
||||
|
||||
if source is None:
|
||||
raise ValueError("audio source can't be None")
|
||||
|
||||
self.source = source
|
||||
|
||||
def adjustForNoise(self):
|
||||
with self.source:
|
||||
self.recorder.adjust_for_ambient_noise(self.source)
|
||||
|
||||
def recordIntoQueue(self, energy_queue):
|
||||
def recordCallback(_, energy):
|
||||
energy_queue.put(energy)
|
||||
|
||||
self.stop, self.pause, self.resume = self.recorder.listen_energy_in_background(self.source, recordCallback)
|
||||
|
||||
class SelectedMicEnergyRecorder(BaseEnergyRecorder):
|
||||
def __init__(self, device):
|
||||
source=Microphone(
|
||||
device_index=device['index'],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
)
|
||||
super().__init__(source=source)
|
||||
# self.adjustForNoise()
|
||||
|
||||
class SelectedSpeakerEnergyRecorder(BaseEnergyRecorder):
|
||||
def __init__(self, device):
|
||||
|
||||
source = Microphone(speaker=True,
|
||||
device_index= device["index"],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
channels=device["maxInputChannels"]
|
||||
)
|
||||
super().__init__(source=source)
|
||||
# self.adjustForNoise()
|
||||
|
||||
class BaseEnergyAndAudioRecorder:
|
||||
def __init__(self, source, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
self.recorder = Recognizer()
|
||||
self.recorder.energy_threshold = energy_threshold
|
||||
self.recorder.dynamic_energy_threshold = dynamic_energy_threshold
|
||||
self.record_timeout = record_timeout
|
||||
self.stop = None
|
||||
|
||||
if source is None:
|
||||
raise ValueError("audio source can't be None")
|
||||
|
||||
self.source = source
|
||||
|
||||
def adjustForNoise(self):
|
||||
with self.source:
|
||||
self.recorder.adjust_for_ambient_noise(self.source)
|
||||
|
||||
def recordIntoQueue(self, audio_queue, energy_queue=None):
|
||||
def audioRecordCallback(_, audio):
|
||||
audio_queue.put((audio.get_raw_data(), datetime.now()))
|
||||
|
||||
def energyRecordCallback(energy):
|
||||
energy_queue.put(energy)
|
||||
|
||||
self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(
|
||||
source=self.source,
|
||||
callback=audioRecordCallback,
|
||||
phrase_time_limit=self.record_timeout,
|
||||
callback_energy=energyRecordCallback if energy_queue is not None else None)
|
||||
|
||||
class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder):
|
||||
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
source=Microphone(
|
||||
device_index=device['index'],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
)
|
||||
super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout)
|
||||
# self.adjustForNoise()
|
||||
|
||||
class SelectedSpeakerEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder):
|
||||
def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout):
|
||||
|
||||
source = Microphone(speaker=True,
|
||||
device_index= device["index"],
|
||||
sample_rate=int(device["defaultSampleRate"]),
|
||||
chunk_size=get_sample_size(paInt16),
|
||||
channels=device["maxInputChannels"]
|
||||
)
|
||||
super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout)
|
||||
# self.adjustForNoise()
|
||||
141
src-python/models/transcription/transcription_transcriber.py
Normal file
141
src-python/models/transcription/transcription_transcriber.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import time
|
||||
from io import BytesIO
|
||||
from threading import Event
|
||||
import wave
|
||||
from speech_recognition import Recognizer, AudioData, AudioFile
|
||||
from datetime import timedelta
|
||||
from pyaudiowpatch import get_sample_size, paInt16
|
||||
from .transcription_languages import transcription_lang
|
||||
from .transcription_whisper import getWhisperModel, checkWhisperWeight
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from pydub import AudioSegment
|
||||
|
||||
PHRASE_TIMEOUT = 3
|
||||
MAX_PHRASES = 10
|
||||
|
||||
class AudioTranscriber:
|
||||
def __init__(self, speaker, source, phrase_timeout, max_phrases, transcription_engine, root=None, whisper_weight_type=None):
|
||||
self.speaker = speaker
|
||||
self.phrase_timeout = phrase_timeout
|
||||
self.max_phrases = max_phrases
|
||||
self.transcript_data = []
|
||||
self.transcript_changed_event = Event()
|
||||
self.audio_recognizer = Recognizer()
|
||||
self.transcription_engine = "Google"
|
||||
self.whisper_model = None
|
||||
self.audio_sources = {
|
||||
"sample_rate": source.SAMPLE_RATE,
|
||||
"sample_width": source.SAMPLE_WIDTH,
|
||||
"channels": source.channels,
|
||||
"last_sample": bytes(),
|
||||
"last_spoken": None,
|
||||
"new_phrase": True,
|
||||
"process_data_func": self.processSpeakerData if speaker else self.processSpeakerData
|
||||
}
|
||||
|
||||
if transcription_engine == "Whisper" and checkWhisperWeight(root, whisper_weight_type) is True:
|
||||
self.whisper_model = getWhisperModel(root, whisper_weight_type)
|
||||
self.transcription_engine = "Whisper"
|
||||
|
||||
def transcribeAudioQueue(self, audio_queue, language, country, avg_logprob=-0.8, no_speech_prob=0.6):
|
||||
if audio_queue.empty():
|
||||
time.sleep(0.01)
|
||||
return False
|
||||
audio, time_spoken = audio_queue.get()
|
||||
self.updateLastSampleAndPhraseStatus(audio, time_spoken)
|
||||
|
||||
text = ''
|
||||
try:
|
||||
audio_data = self.audio_sources["process_data_func"]()
|
||||
match self.transcription_engine:
|
||||
case "Google":
|
||||
text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country][self.transcription_engine])
|
||||
case "Whisper":
|
||||
audio_data = np.frombuffer(audio_data.get_raw_data(convert_rate=16000, convert_width=2), np.int16).flatten().astype(np.float32) / 32768.0
|
||||
if isinstance(audio_data, torch.Tensor):
|
||||
audio_data = audio_data.detach().numpy()
|
||||
segments, _ = self.whisper_model.transcribe(
|
||||
audio_data,
|
||||
beam_size=5,
|
||||
temperature=0.0,
|
||||
log_prob_threshold=-0.8,
|
||||
no_speech_threshold=0.6,
|
||||
language=transcription_lang[language][country][self.transcription_engine],
|
||||
word_timestamps=False,
|
||||
without_timestamps=True,
|
||||
task="transcribe",
|
||||
vad_filter=False,
|
||||
)
|
||||
for s in segments:
|
||||
if s.avg_logprob < avg_logprob or s.no_speech_prob > no_speech_prob:
|
||||
continue
|
||||
text += s.text
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
pass
|
||||
|
||||
if text != '':
|
||||
self.updateTranscript(text)
|
||||
return True
|
||||
|
||||
def updateLastSampleAndPhraseStatus(self, data, time_spoken):
|
||||
source_info = self.audio_sources
|
||||
if source_info["last_spoken"] and time_spoken - source_info["last_spoken"] > timedelta(seconds=self.phrase_timeout):
|
||||
source_info["last_sample"] = bytes()
|
||||
source_info["new_phrase"] = True
|
||||
else:
|
||||
source_info["new_phrase"] = False
|
||||
|
||||
source_info["last_sample"] += data
|
||||
source_info["last_spoken"] = time_spoken
|
||||
|
||||
def processMicData(self):
|
||||
audio_data = AudioData(self.audio_sources["last_sample"], self.audio_sources["sample_rate"], self.audio_sources["sample_width"])
|
||||
return audio_data
|
||||
|
||||
def processSpeakerData(self):
|
||||
temp_file = BytesIO()
|
||||
with wave.open(temp_file, 'wb') as wf:
|
||||
wf.setnchannels(self.audio_sources["channels"])
|
||||
wf.setsampwidth(get_sample_size(paInt16))
|
||||
wf.setframerate(self.audio_sources["sample_rate"])
|
||||
wf.writeframes(self.audio_sources["last_sample"])
|
||||
temp_file.seek(0)
|
||||
|
||||
if self.audio_sources["channels"] > 2:
|
||||
audio = AudioSegment.from_file(temp_file, format="wav")
|
||||
mono_audio = audio.set_channels(1)
|
||||
temp_file = BytesIO()
|
||||
mono_audio.export(temp_file, format="wav")
|
||||
temp_file.seek(0)
|
||||
|
||||
with AudioFile(temp_file) as source:
|
||||
audio = self.audio_recognizer.record(source)
|
||||
return audio
|
||||
|
||||
def updateTranscript(self, text):
|
||||
source_info = self.audio_sources
|
||||
transcript = self.transcript_data
|
||||
|
||||
if source_info["new_phrase"] or len(transcript) == 0:
|
||||
if len(transcript) > self.max_phrases:
|
||||
transcript.pop(-1)
|
||||
transcript.insert(0, text)
|
||||
else:
|
||||
transcript[0] = text
|
||||
|
||||
def getTranscript(self):
|
||||
if len(self.transcript_data) > 0:
|
||||
text = self.transcript_data.pop(-1)
|
||||
else:
|
||||
text = ""
|
||||
return text
|
||||
|
||||
def clearTranscriptData(self):
|
||||
self.transcript_data.clear()
|
||||
self.audio_sources["last_sample"] = bytes()
|
||||
self.audio_sources["new_phrase"] = True
|
||||
70
src-python/models/transcription/transcription_utils.py
Normal file
70
src-python/models/transcription/transcription_utils.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from pyaudiowpatch import PyAudio, paWASAPI
|
||||
|
||||
def getInputDevices():
|
||||
devices = {}
|
||||
with PyAudio() as p:
|
||||
for host_index in range(0, p.get_host_api_count()):
|
||||
host = p.get_host_api_info_by_index(host_index)
|
||||
for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']):
|
||||
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
|
||||
if device["maxInputChannels"] > 0 and device["isLoopbackDevice"] is False:
|
||||
if host["name"] in devices.keys():
|
||||
devices[host["name"]].append(device)
|
||||
else:
|
||||
devices[host["name"]] = [device]
|
||||
if len(devices) == 0:
|
||||
devices = {"NoHost": [{"name": "NoDevice"}]}
|
||||
return devices
|
||||
|
||||
def getDefaultInputDevice():
|
||||
with PyAudio() as p:
|
||||
api_info = p.get_default_host_api_info()
|
||||
defaultInputDevice = api_info["defaultInputDevice"]
|
||||
|
||||
for host_index in range(0, p.get_host_api_count()):
|
||||
host = p.get_host_api_info_by_index(host_index)
|
||||
for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']):
|
||||
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
|
||||
if device["index"] == defaultInputDevice:
|
||||
return {"host": host, "device": device}
|
||||
return {"host": {"name": "NoHost"}, "device": {"name": "NoDevice"}}
|
||||
|
||||
def getOutputDevices():
|
||||
devices = []
|
||||
with PyAudio() as p:
|
||||
wasapi_info = p.get_host_api_info_by_type(paWASAPI)
|
||||
for host_index in range(0, p.get_host_api_count()):
|
||||
host = p.get_host_api_info_by_index(host_index)
|
||||
if host["name"] == wasapi_info["name"]:
|
||||
for device_index in range(0, p.get_host_api_info_by_index(host_index)['deviceCount']):
|
||||
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
|
||||
if not device["isLoopbackDevice"]:
|
||||
for loopback in p.get_loopback_device_info_generator():
|
||||
if device["name"] in loopback["name"]:
|
||||
devices.append(loopback)
|
||||
|
||||
if len(devices) == 0:
|
||||
devices = [{"name": "NoDevice"}]
|
||||
else:
|
||||
devices = [dict(t) for t in {tuple(d.items()) for d in devices}]
|
||||
return devices
|
||||
|
||||
def getDefaultOutputDevice():
|
||||
with PyAudio() as p:
|
||||
wasapi_info = p.get_host_api_info_by_type(paWASAPI)
|
||||
defaultOutputDevice = wasapi_info["defaultOutputDevice"]
|
||||
|
||||
for host_index in range(0, p.get_host_api_count()):
|
||||
for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']):
|
||||
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
|
||||
if device["index"] == defaultOutputDevice:
|
||||
default_speakers = device
|
||||
if not default_speakers["isLoopbackDevice"]:
|
||||
for loopback in p.get_loopback_device_info_generator():
|
||||
if default_speakers["name"] in loopback["name"]:
|
||||
return {"device": loopback}
|
||||
return {"device": {"name": "NoDevice"}}
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("getOutputDevices()", getOutputDevices())
|
||||
print("getDefaultOutputDevice()", getDefaultOutputDevice())
|
||||
98
src-python/models/transcription/transcription_whisper.py
Normal file
98
src-python/models/transcription/transcription_whisper.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from os import path as os_path, makedirs as os_makedirs
|
||||
from requests import get as requests_get
|
||||
from typing import Callable
|
||||
import huggingface_hub
|
||||
from faster_whisper import WhisperModel
|
||||
import logging
|
||||
logger = logging.getLogger('faster_whisper')
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
|
||||
_MODELS = {
|
||||
"tiny": "Systran/faster-whisper-tiny",
|
||||
"base": "Systran/faster-whisper-base",
|
||||
"small": "Systran/faster-whisper-small",
|
||||
"medium": "Systran/faster-whisper-medium",
|
||||
"large-v1": "Systran/faster-whisper-large-v1",
|
||||
"large-v2": "Systran/faster-whisper-large-v2",
|
||||
"large-v3": "Systran/faster-whisper-large-v3",
|
||||
}
|
||||
|
||||
_FILENAMES = [
|
||||
"config.json",
|
||||
"preprocessor_config.json",
|
||||
"model.bin",
|
||||
"tokenizer.json",
|
||||
"vocabulary.txt",
|
||||
"vocabulary.json",
|
||||
]
|
||||
|
||||
def downloadFile(url, path, func=None):
|
||||
try:
|
||||
res = requests_get(url, stream=True)
|
||||
res.raise_for_status()
|
||||
file_size = int(res.headers.get('content-length', 0))
|
||||
total_chunk = 0
|
||||
with open(os_path.join(path), 'wb') as file:
|
||||
for chunk in res.iter_content(chunk_size=1024*5):
|
||||
file.write(chunk)
|
||||
if isinstance(func, Callable):
|
||||
total_chunk += len(chunk)
|
||||
func(total_chunk/file_size)
|
||||
|
||||
except Exception as e:
|
||||
print("error:downloadFile()", e)
|
||||
|
||||
def checkWhisperWeight(root, weight_type):
|
||||
path = os_path.join(root, "weights", "whisper", weight_type)
|
||||
result = False
|
||||
try:
|
||||
WhisperModel(
|
||||
path,
|
||||
device="cpu",
|
||||
device_index=0,
|
||||
compute_type="int8",
|
||||
cpu_threads=4,
|
||||
num_workers=1,
|
||||
local_files_only=True,
|
||||
)
|
||||
result = True
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
def downloadWhisperWeight(root, weight_type, callbackFunc):
|
||||
path = os_path.join(root, "weights", "whisper", weight_type)
|
||||
os_makedirs(path, exist_ok=True)
|
||||
if checkWhisperWeight(root, weight_type) is True:
|
||||
return
|
||||
|
||||
for filename in _FILENAMES:
|
||||
print("Downloading", filename, "...")
|
||||
file_path = os_path.join(path, filename)
|
||||
url = huggingface_hub.hf_hub_url(_MODELS[weight_type], filename)
|
||||
downloadFile(url, file_path, func=callbackFunc)
|
||||
|
||||
def getWhisperModel(root, weight_type):
|
||||
path = os_path.join(root, "weights", "whisper", weight_type)
|
||||
return WhisperModel(
|
||||
path,
|
||||
device="cpu",
|
||||
device_index=0,
|
||||
compute_type="int8",
|
||||
cpu_threads=4,
|
||||
num_workers=1,
|
||||
local_files_only=True,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
def callback(value):
|
||||
print(value)
|
||||
pass
|
||||
|
||||
downloadWhisperWeight("./", "tiny", callback)
|
||||
downloadWhisperWeight("./", "base", callback)
|
||||
downloadWhisperWeight("./", "small", callback)
|
||||
downloadWhisperWeight("./", "medium", callback)
|
||||
downloadWhisperWeight("./", "large-v1", callback)
|
||||
downloadWhisperWeight("./", "large-v2", callback)
|
||||
downloadWhisperWeight("./", "large-v3", callback)
|
||||
384
src-python/models/translation/translation_languages.py
Normal file
384
src-python/models/translation/translation_languages.py
Normal file
@@ -0,0 +1,384 @@
|
||||
translation_lang = {}
|
||||
dict_deepl_languages = {
|
||||
"Arabic":"ar",
|
||||
"Bulgarian":"bg",
|
||||
"Czech":"cs",
|
||||
"Danish":"da",
|
||||
"German":"de",
|
||||
"Greek":"el",
|
||||
"English":"en",
|
||||
"Spanish":"es",
|
||||
"Estonian":"et",
|
||||
"Finnish":"fi",
|
||||
"French":"fr",
|
||||
"Irish":"ga",
|
||||
"Croatian":"hr",
|
||||
"Hungarian":"hu",
|
||||
"Indonesian":"id",
|
||||
"Icelandic":"is",
|
||||
"Italian":"it",
|
||||
"Japanese":"ja",
|
||||
"Korean":"ko",
|
||||
"Lithuanian":"lt",
|
||||
"Latvian":"lv",
|
||||
"Maltese":"mt",
|
||||
"Bokmal":"nb",
|
||||
"Dutch":"nl",
|
||||
"Norwegian":"no",
|
||||
"Polish":"pl",
|
||||
"Portuguese":"pt",
|
||||
"Romanian":"ro",
|
||||
"Russian":"ru",
|
||||
"Slovak":"sk",
|
||||
"Slovenian":"sl",
|
||||
"Swedish":"sv",
|
||||
"Turkish":"tr",
|
||||
"Ukrainian":"uk",
|
||||
"Chinese Simplified":"zh",
|
||||
"Chinese Traditional":"zh"
|
||||
}
|
||||
translation_lang["DeepL"] = {
|
||||
"source":dict_deepl_languages,
|
||||
"target":dict_deepl_languages,
|
||||
}
|
||||
|
||||
dict_deepl_api_source_languages = {
|
||||
"Japanese":"ja",
|
||||
"English":"en",
|
||||
"Bulgarian":"bg",
|
||||
"Czech":"cs",
|
||||
"Danish":"da",
|
||||
"German":"de",
|
||||
"Greek":"el",
|
||||
"Spanish":"es",
|
||||
"Estonian":"et",
|
||||
"Finnish":"fi",
|
||||
"French":"fr",
|
||||
"Hungarian":"hu",
|
||||
"Indonesian":"id",
|
||||
"Italian":"it",
|
||||
"Korean":"ko",
|
||||
"Lithuanian":"lt",
|
||||
"Latvian":"lv",
|
||||
"Norwegian":"nb",
|
||||
"Dutch":"nl",
|
||||
"Polish":"pl",
|
||||
"Portuguese":"pt",
|
||||
"Romanian":"ro",
|
||||
"Russian":"ru",
|
||||
"Slovak":"sk",
|
||||
"Slovenian":"sl",
|
||||
"Swedish":"sv",
|
||||
"Turkish":"tr",
|
||||
"Ukrainian":"uk",
|
||||
"Chinese Simplified":"zh",
|
||||
"Chinese Traditional":"zh"
|
||||
}
|
||||
dict_deepl_api_target_languages = {
|
||||
"Japanese":"ja",
|
||||
"English American":"en-US",
|
||||
"English British":"en-GB",
|
||||
"Bulgarian":"bg",
|
||||
"Czech":"cs",
|
||||
"Danish":"da",
|
||||
"German":"de",
|
||||
"Greek":"el",
|
||||
"English":"en",
|
||||
"Spanish":"es",
|
||||
"Estonian":"et",
|
||||
"Finnish":"fi",
|
||||
"French":"fr",
|
||||
"Hungarian":"hu",
|
||||
"Indonesian":"id",
|
||||
"Italian":"it",
|
||||
"Korean":"ko",
|
||||
"Lithuanian":"lt",
|
||||
"Latvian":"lv",
|
||||
"Norwegian":"nb",
|
||||
"Dutch":"nl",
|
||||
"Polish":"pl",
|
||||
"Portuguese Brazilian":"pt-BR",
|
||||
"Portuguese European":"pt-PT",
|
||||
"Romanian":"ro",
|
||||
"Russian":"ru",
|
||||
"Slovak":"sk",
|
||||
"Slovenian":"sl",
|
||||
"Swedish":"sv",
|
||||
"Turkish":"tr",
|
||||
"Ukrainian":"uk",
|
||||
"Chinese Simplified":"zh",
|
||||
"Chinese Traditional":"zh"
|
||||
}
|
||||
translation_lang["DeepL_API"] = {
|
||||
"source": dict_deepl_api_source_languages,
|
||||
"target": dict_deepl_api_target_languages,
|
||||
}
|
||||
|
||||
dict_google_languages = {
|
||||
"Japanese":"ja",
|
||||
"English":"en",
|
||||
"Chinese Simplified":"zh",
|
||||
"Chinese Traditional":"zh-TW",
|
||||
"Arabic":"ar",
|
||||
"Russian":"ru",
|
||||
"French":"fr",
|
||||
"German":"de",
|
||||
"Spanish":"es",
|
||||
"Portuguese":"pt",
|
||||
"Italian":"it",
|
||||
"Korean":"ko",
|
||||
"Greek":"el",
|
||||
"Dutch":"nl",
|
||||
"Hindi":"hi",
|
||||
"Turkish":"tr",
|
||||
"Malay":"ms",
|
||||
"Thai":"th",
|
||||
"Vietnamese":"vi",
|
||||
"Indonesian":"id",
|
||||
"Hebrew":"he",
|
||||
"Polish":"pl",
|
||||
"Mongolian":"mn",
|
||||
"Czech":"cs",
|
||||
"Hungarian":"hu",
|
||||
"Estonian":"et",
|
||||
"Bulgarian":"bg",
|
||||
"Danish":"da",
|
||||
"Finnish":"fi",
|
||||
"Romanian":"ro",
|
||||
"Swedish":"sv",
|
||||
"Slovenian":"sl",
|
||||
"Persian/Farsi":"fa",
|
||||
"Bosnian":"bs",
|
||||
"Serbian":"sr",
|
||||
"Filipino":"tl",
|
||||
"Haitiancreole":"ht",
|
||||
"Catalan":"ca",
|
||||
"Croatian":"hr",
|
||||
"Latvian":"lv",
|
||||
"Lithuanian":"lt",
|
||||
"Urdu":"ur",
|
||||
"Ukrainian":"uk",
|
||||
"Welsh":"cy",
|
||||
"Swahili":"sw",
|
||||
"Samoan":"sm",
|
||||
"Slovak":"sk",
|
||||
"Afrikaans":"af",
|
||||
"Norwegian":"no",
|
||||
"Bengali":"bn",
|
||||
"Malagasy":"mg",
|
||||
"Maltese":"mt",
|
||||
"Gujarati":"gu",
|
||||
"Tamil":"ta",
|
||||
"Telugu":"te",
|
||||
"Punjabi":"pa",
|
||||
"Amharic":"am",
|
||||
"Azerbaijani":"az",
|
||||
"Belarusian":"be",
|
||||
"Cebuano":"ceb",
|
||||
"Esperanto":"eo",
|
||||
"Basque":"eu",
|
||||
"Irish":"ga"
|
||||
}
|
||||
translation_lang["Google"] = {
|
||||
"source":dict_google_languages,
|
||||
"target":dict_google_languages,
|
||||
}
|
||||
|
||||
dict_bing_languages = {
|
||||
"Japanese":"ja",
|
||||
"English":"en",
|
||||
"Chinese Simplified":"zh",
|
||||
"Chinese Traditional":"zh-Hant",
|
||||
"Arabic":"ar",
|
||||
"Russian":"ru",
|
||||
"French":"fr",
|
||||
"German":"de",
|
||||
"Spanish":"es",
|
||||
"Portuguese":"pt",
|
||||
"Italian":"it",
|
||||
"Korean":"ko",
|
||||
"Greek":"el",
|
||||
"Dutch":"nl",
|
||||
"Hindi":"hi",
|
||||
"Turkish":"tr",
|
||||
"Malay":"ms",
|
||||
"Thai":"th",
|
||||
"Vietnamese":"vi",
|
||||
"Indonesian":"id",
|
||||
"Hebrew":"he",
|
||||
"Polish":"pl",
|
||||
"Czech":"cs",
|
||||
"Hungarian":"hu",
|
||||
"Estonian":"et",
|
||||
"Bulgarian":"bg",
|
||||
"Danish":"da",
|
||||
"Finnish":"fi",
|
||||
"Romanian":"ro",
|
||||
"Swedish":"sv",
|
||||
"Slovenian":"sl",
|
||||
"Persian/Farsi":"fa",
|
||||
"Bosnian":"bs",
|
||||
"Serbian":"sr",
|
||||
"Fijian":"fj",
|
||||
"Filipino":"tl",
|
||||
"Haitiancreole":"ht",
|
||||
"Catalan":"ca",
|
||||
"Croatian":"hr",
|
||||
"Latvian":"lv",
|
||||
"Lithuanian":"lt",
|
||||
"Urdu":"ur",
|
||||
"Ukrainian":"uk",
|
||||
"Welsh":"cy",
|
||||
"Tahiti":"ty",
|
||||
"Tongan":"to",
|
||||
"Swahili":"sw",
|
||||
"Samoan":"sm",
|
||||
"Slovak":"sk",
|
||||
"Afrikaans":"af",
|
||||
"Norwegian":"no",
|
||||
"Bengali":"bn",
|
||||
"Malagasy":"mg",
|
||||
"Maltese":"mt",
|
||||
"Queretaro otomi":"otq",
|
||||
"Klingon/tlhingan Hol":"tlh",
|
||||
"Gujarati":"gu",
|
||||
"Tamil":"ta",
|
||||
"Telugu":"te",
|
||||
"Punjabi":"pa",
|
||||
"Irish":"ga"
|
||||
}
|
||||
translation_lang["Bing"] = {
|
||||
"source":dict_bing_languages,
|
||||
"target":dict_bing_languages,
|
||||
}
|
||||
|
||||
dict_papago_languages = {
|
||||
"German": "de",
|
||||
"English": "en",
|
||||
"Spanish":"es",
|
||||
"French": "fr",
|
||||
"Hindi": "hi",
|
||||
"Indonesian": "id",
|
||||
"Italian": "it",
|
||||
"Japanese": "ja",
|
||||
"Korean": "ko",
|
||||
"Portuguese": "pt",
|
||||
"Russian": "ru",
|
||||
"Thai": "th",
|
||||
"Vietnamese": "vi",
|
||||
"Chinese Simplified":"zh-CN",
|
||||
"Chinese Traditional":"zh-TW",
|
||||
}
|
||||
|
||||
translation_lang["Papago"] = {
|
||||
"source":dict_papago_languages,
|
||||
"target":dict_papago_languages,
|
||||
}
|
||||
|
||||
dict_ctranslate2_languages = {
|
||||
"English": "en",
|
||||
"Chinese Simplified": "zh",
|
||||
"Chinese Traditional":"zh",
|
||||
"German": "de",
|
||||
"Spanish": "es",
|
||||
"Russian": "ru",
|
||||
"Korean": "ko",
|
||||
"French": "fr",
|
||||
"Japanese": "ja",
|
||||
"Portuguese": "pt",
|
||||
"Turkish": "tr",
|
||||
"Polish": "pl",
|
||||
"Catalan": "ca",
|
||||
"Dutch": "nl",
|
||||
"Arabic": "ar",
|
||||
"Swedish": "sv",
|
||||
"Italian": "it",
|
||||
"Indonesian": "id",
|
||||
"Hindi": "hi",
|
||||
"Finnish": "fi",
|
||||
"Vietnamese": "vi",
|
||||
"Hebrew": "he",
|
||||
"Ukrainian": "uk",
|
||||
"Greek": "el",
|
||||
"Malay": "ms",
|
||||
"Czech": "cs",
|
||||
"Romanian": "ro",
|
||||
"Danish": "da",
|
||||
"Hungarian": "hu",
|
||||
"Tamil": "ta",
|
||||
"Norwegian": "no",
|
||||
"Thai": "th",
|
||||
"Urdu": "ur",
|
||||
"Croatian": "hr",
|
||||
"Bulgarian": "bg",
|
||||
"Lithuanian": "lt",
|
||||
"Latin": "la",
|
||||
"Maori": "mi",
|
||||
"Malayalam": "ml",
|
||||
"Welsh": "cy",
|
||||
"Slovak": "sk",
|
||||
"Telugu": "te",
|
||||
"Persian": "fa",
|
||||
"Latvian": "lv",
|
||||
"Bengali": "bn",
|
||||
"Serbian": "sr",
|
||||
"Azerbaijani": "az",
|
||||
"Slovenian": "sl",
|
||||
"Kannada": "kn",
|
||||
"Estonian": "et",
|
||||
"Macedonian": "mk",
|
||||
"Breton": "br",
|
||||
"Basque": "eu",
|
||||
"Icelandic": "is",
|
||||
"Armenian": "hy",
|
||||
"Nepali": "ne",
|
||||
"Mongolian": "mn",
|
||||
"Bosnian": "bs",
|
||||
"Kazakh": "kk",
|
||||
"Albanian": "sq",
|
||||
"Swahili": "sw",
|
||||
"Galician": "gl",
|
||||
"Marathi": "mr",
|
||||
"Punjabi": "pa",
|
||||
"Sinhala": "si",
|
||||
"Khmer": "km",
|
||||
"Shona": "sn",
|
||||
"Yoruba": "yo",
|
||||
"Somali": "so",
|
||||
"Afrikaans": "af",
|
||||
"Occitan": "oc",
|
||||
"Georgian": "ka",
|
||||
"Belarusian": "be",
|
||||
"Tajik": "tg",
|
||||
"Sindhi": "sd",
|
||||
"Gujarati": "gu",
|
||||
"Amharic": "am",
|
||||
"Yiddish": "yi",
|
||||
"Lao": "lo",
|
||||
"Uzbek": "uz",
|
||||
"Faroese": "fo",
|
||||
"Haitian creole": "ht",
|
||||
"Pashto": "ps",
|
||||
"Turkmen": "tk",
|
||||
"Nynorsk": "nn",
|
||||
"Maltese": "mt",
|
||||
"Sanskrit": "sa",
|
||||
"Luxembourgish": "lb",
|
||||
"Myanmar": "my",
|
||||
"Tibetan": "bo",
|
||||
"Filipino": "tl",
|
||||
"Malagasy": "mg",
|
||||
"Assamese": "as",
|
||||
"Tatar": "tt",
|
||||
"Hawaiian": "haw",
|
||||
"Lingala": "ln",
|
||||
"Hausa": "ha",
|
||||
"Bashkir": "ba",
|
||||
"Javanese": "jw",
|
||||
"Sundanese": "su"
|
||||
}
|
||||
|
||||
translation_lang["CTranslate2"] = {
|
||||
"source":dict_ctranslate2_languages,
|
||||
"target":dict_ctranslate2_languages,
|
||||
}
|
||||
140
src-python/models/translation/translation_translator.py
Normal file
140
src-python/models/translation/translation_translator.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import os
|
||||
from deepl import Translator as deepl_Translator
|
||||
from translators import translate_text as other_web_Translator
|
||||
from .translation_languages import translation_lang
|
||||
from .translation_utils import ctranslate2_weights
|
||||
|
||||
import ctranslate2
|
||||
import transformers
|
||||
|
||||
# Translator
|
||||
class Translator():
|
||||
def __init__(self):
|
||||
self.deepl_client = None
|
||||
self.ctranslate2_translator = None
|
||||
self.ctranslate2_tokenizer = None
|
||||
self.is_loaded_ctranslate2_model = False
|
||||
|
||||
def authenticationDeepLAuthKey(self, authkey):
|
||||
result = True
|
||||
try:
|
||||
self.deepl_client = deepl_Translator(authkey)
|
||||
self.deepl_client.translate_text(" ", target_lang="EN-US")
|
||||
except Exception:
|
||||
self.deepl_client = None
|
||||
result = False
|
||||
return result
|
||||
|
||||
def changeCTranslate2Model(self, path, model_type):
|
||||
self.is_loaded_ctranslate2_model = False
|
||||
directory_name = ctranslate2_weights[model_type]["directory_name"]
|
||||
tokenizer = ctranslate2_weights[model_type]["tokenizer"]
|
||||
weight_path = os.path.join(path, "weights", "ctranslate2", directory_name)
|
||||
tokenizer_path = os.path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
|
||||
self.ctranslate2_translator = ctranslate2.Translator(
|
||||
weight_path,
|
||||
device="cpu",
|
||||
device_index=0,
|
||||
compute_type="int8",
|
||||
inter_threads=1,
|
||||
intra_threads=4
|
||||
)
|
||||
try:
|
||||
self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)
|
||||
except Exception as e:
|
||||
print("Error: changeCTranslate2Model()", e)
|
||||
tokenizer_path = os.path.join("./weights", "ctranslate2", directory_name, "tokenizer")
|
||||
self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)
|
||||
self.is_loaded_ctranslate2_model = True
|
||||
|
||||
def isLoadedCTranslate2Model(self):
|
||||
return self.is_loaded_ctranslate2_model
|
||||
|
||||
def translateCTranslate2(self, message, source_language, target_language):
|
||||
result = False
|
||||
if self.is_loaded_ctranslate2_model is True:
|
||||
try:
|
||||
self.ctranslate2_tokenizer.src_lang = source_language
|
||||
source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message))
|
||||
target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]]
|
||||
results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix])
|
||||
target = results[0].hypotheses[0][1:]
|
||||
result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target))
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def getLanguageCode(translator_name, target_country, source_language, target_language):
|
||||
match translator_name:
|
||||
case "DeepL_API":
|
||||
if target_language == "English":
|
||||
if target_country in ["United States", "Canada", "Philippines"]:
|
||||
target_language = "English American"
|
||||
else:
|
||||
target_language = "English British"
|
||||
elif target_language == "Portuguese":
|
||||
if target_country in ["Portugal"]:
|
||||
target_language = "Portuguese European"
|
||||
else:
|
||||
target_language = "Portuguese Brazilian"
|
||||
case _:
|
||||
pass
|
||||
source_language=translation_lang[translator_name]["source"][source_language]
|
||||
target_language=translation_lang[translator_name]["target"][target_language]
|
||||
return source_language, target_language
|
||||
|
||||
def translate(self, translator_name, source_language, target_language, target_country, message):
|
||||
try:
|
||||
result = ""
|
||||
source_language, target_language = self.getLanguageCode(translator_name, target_country, source_language, target_language)
|
||||
match translator_name:
|
||||
case "DeepL":
|
||||
result = other_web_Translator(
|
||||
query_text=message,
|
||||
translator="deepl",
|
||||
from_language=source_language,
|
||||
to_language=target_language,
|
||||
)
|
||||
case "DeepL_API":
|
||||
if self.deepl_client is None:
|
||||
result = False
|
||||
else:
|
||||
result = self.deepl_client.translate_text(
|
||||
message,
|
||||
source_lang=source_language,
|
||||
target_lang=target_language,
|
||||
).text
|
||||
case "Google":
|
||||
result = other_web_Translator(
|
||||
query_text=message,
|
||||
translator="google",
|
||||
from_language=source_language,
|
||||
to_language=target_language,
|
||||
)
|
||||
case "Bing":
|
||||
result = other_web_Translator(
|
||||
query_text=message,
|
||||
translator="bing",
|
||||
from_language=source_language,
|
||||
to_language=target_language,
|
||||
)
|
||||
case "Papago":
|
||||
result = other_web_Translator(
|
||||
query_text=message,
|
||||
translator="papago",
|
||||
from_language=source_language,
|
||||
to_language=target_language,
|
||||
)
|
||||
case "CTranslate2":
|
||||
result = self.translateCTranslate2(
|
||||
message=message,
|
||||
source_language=source_language,
|
||||
target_language=target_language,
|
||||
)
|
||||
except Exception:
|
||||
import traceback
|
||||
with open('error.log', 'a') as f:
|
||||
traceback.print_exc(file=f)
|
||||
result = False
|
||||
return result
|
||||
86
src-python/models/translation/translation_utils.py
Normal file
86
src-python/models/translation/translation_utils.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import tempfile
|
||||
from zipfile import ZipFile
|
||||
from os import path as os_path
|
||||
from os import makedirs as os_makedirs
|
||||
from requests import get as requests_get
|
||||
from typing import Callable
|
||||
import hashlib
|
||||
|
||||
ctranslate2_weights = {
|
||||
"Small": { # M2M-100 418M-parameter model
|
||||
"url": "https://github.com/misyaguziya/VRCT-weights/releases/download/v1.0/m2m100_418m.zip",
|
||||
"directory_name": "m2m100_418m",
|
||||
"tokenizer": "facebook/m2m100_418M",
|
||||
"hash": {
|
||||
"model.bin": "e7c26a9abb5260abd0268fbe3040714070dec254a990b4d7fd3f74c5230e3acb",
|
||||
"sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a",
|
||||
"shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c",
|
||||
}
|
||||
},
|
||||
"Large": { # M2M-100 1.2B-parameter model
|
||||
"url": "https://github.com/misyaguziya/VRCT-weights/releases/download/v1.0/m2m100_12b.zip",
|
||||
"directory_name": "m2m100_12b",
|
||||
"tokenizer": "facebook/m2m100_1.2b",
|
||||
"hash": {
|
||||
"model.bin": "abb7bf4ba7e5e016b6e3ed480c752459b2f783ac8fca372e7587675e5bf3a919",
|
||||
"sentencepiece.model": "d8f7c76ed2a5e0822be39f0a4f95a55eb19c78f4593ce609e2edbc2aea4d380a",
|
||||
"shared_vocabulary.txt": "bd440aa21b8ca3453fc792a0018a1f3fe68b3464aadddd4d16a4b72f73c86d8c",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def calculate_file_hash(file_path, block_size=65536):
|
||||
hash_object = hashlib.sha256()
|
||||
|
||||
with open(file_path, 'rb') as file:
|
||||
for block in iter(lambda: file.read(block_size), b''):
|
||||
hash_object.update(block)
|
||||
|
||||
return hash_object.hexdigest()
|
||||
|
||||
def checkCTranslate2Weight(path, weight_type="Small"):
|
||||
weight_directory_name = ctranslate2_weights[weight_type]["directory_name"]
|
||||
hash_data = ctranslate2_weights[weight_type]["hash"]
|
||||
files = [
|
||||
"model.bin",
|
||||
"sentencepiece.model",
|
||||
"shared_vocabulary.txt"
|
||||
]
|
||||
|
||||
# check already downloaded
|
||||
already_downloaded = False
|
||||
if all(os_path.exists(os_path.join(path, weight_directory_name, file)) for file in files):
|
||||
# check hash
|
||||
for file in files:
|
||||
original_hash = hash_data[file]
|
||||
current_hash = calculate_file_hash(os_path.join(path, weight_directory_name, file))
|
||||
if original_hash != current_hash:
|
||||
break
|
||||
already_downloaded = True
|
||||
return already_downloaded
|
||||
|
||||
def downloadCTranslate2Weight(root, weight_type="Small", func=None):
|
||||
url = ctranslate2_weights[weight_type]["url"]
|
||||
filename = "weight.zip"
|
||||
path = os_path.join(root, "weights", "ctranslate2")
|
||||
os_makedirs(path, exist_ok=True)
|
||||
|
||||
if checkCTranslate2Weight(path, weight_type):
|
||||
return
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmp_path:
|
||||
res = requests_get(url, stream=True)
|
||||
file_size = int(res.headers.get('content-length', 0))
|
||||
total_chunk = 0
|
||||
with open(os_path.join(tmp_path, filename), 'wb') as file:
|
||||
for chunk in res.iter_content(chunk_size=1024*5):
|
||||
file.write(chunk)
|
||||
if isinstance(func, Callable):
|
||||
total_chunk += len(chunk)
|
||||
func(total_chunk/file_size)
|
||||
|
||||
with ZipFile(os_path.join(tmp_path, filename)) as zf:
|
||||
zf.extractall(path)
|
||||
except Exception as e:
|
||||
print("error:downloadCTranslate2Weight()", e)
|
||||
73
src-python/models/xsoverlay/notification.py
Normal file
73
src-python/models/xsoverlay/notification.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# ###########################################################################################################################
|
||||
# DOCUMENT:https://xiexe.github.io/XSOverlayDocumentation/#/NotificationsAPI
|
||||
# SOURCE:https://zenn.dev/eeharumt/scraps/95f49a62dd809a
|
||||
# messageType: int = 0 # 1: ポップアップ通知, 2: メディアプレーヤー情報
|
||||
# index: int = 0 # メディアプレーヤーでのみ使用され、手首のアイコンを変更する
|
||||
# timeout: float = 0.5 # 通知インジケータが表示され続ける時間[秒]
|
||||
# height: float = 175 # 通知インジケータの高さ
|
||||
# opacity: float = 1 # 通知インジケータの透明度。0.0-1.0の範囲で低いほど透明に
|
||||
# volume: float = 0.7 # 通知音の大きさ
|
||||
# audioPath: str = "" # 通知音ファイルのパス。規定音として"default", "error", "warning"を指定可能。空文字列で通知音なしにできる。
|
||||
# title: str = "" # 通知タイトル、リッチテキストフォーマットをサポート。
|
||||
# content: str = "" # 通知内容、リッチテキストフォーマットをサポート。省略することで小サイズ通知となる。
|
||||
# useBase64Icon: bool = False # TrueにすることでBase64の画像を表示する
|
||||
# icon: str = "" # Base64画像イメージまたは画像ファイルパス。規定アイコンとして"default", "error", or "warning"を指定可能
|
||||
# sourceApp: str = "" # 通知したアプリ名(デバック用)
|
||||
# ##########################################################################################################################
|
||||
|
||||
import socket
|
||||
import json
|
||||
import base64
|
||||
from os import path as os_path
|
||||
|
||||
def XSOverlay(
|
||||
endpoint:tuple=("127.0.0.1", 42069), messageType:int=1, index:int=0, timeout:float=2,
|
||||
height:float=120.0, opacity:float=1.0, volume:float=0.0, audioPath:str="",
|
||||
title:str="", content:str="", useBase64Icon:bool=False, icon:str="default", sourceApp:str=""
|
||||
) -> int:
|
||||
|
||||
if icon in ["default", "error", "warning"]:
|
||||
icon_data = icon
|
||||
elif useBase64Icon:
|
||||
try:
|
||||
with open(icon, "rb") as f:
|
||||
icon_data_bytes = f.read()
|
||||
icon_data = base64.b64encode(icon_data_bytes).decode("utf-8")
|
||||
except Exception:
|
||||
icon_data = "default"
|
||||
else:
|
||||
icon_data = icon
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
data_msg = {
|
||||
"messageType": messageType,
|
||||
"index": index,
|
||||
"timeout":timeout,
|
||||
"height": height,
|
||||
"opacity": opacity,
|
||||
"volume": volume,
|
||||
"audioPath": audioPath,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"useBase64Icon": useBase64Icon,
|
||||
"icon": icon_data,
|
||||
"sourceApp": sourceApp,
|
||||
}
|
||||
msg_str = json.dumps(data_msg)
|
||||
response = sock.sendto(msg_str.encode("utf-8"), endpoint)
|
||||
sock.close()
|
||||
return response
|
||||
|
||||
def xsoverlayForVRCT(content:str="") -> int:
|
||||
response = XSOverlay(
|
||||
title="VRCT",
|
||||
content=content,
|
||||
useBase64Icon=True,
|
||||
icon=os_path.join(os_path.dirname(__file__), "img", "xsoverlay2.png"),
|
||||
sourceApp="VRCT"
|
||||
)
|
||||
return response
|
||||
|
||||
if __name__ == "__main__":
|
||||
xsoverlayForVRCT(content="notification test")
|
||||
Reference in New Issue
Block a user