Compare commits
1 Commits
main
...
220340121Z
Author | SHA1 | Date |
---|---|---|
|
0b64e6df34 | 10 months ago |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 5.1 MiB |
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="django" name="Django">
|
||||
<configuration>
|
||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||
<option name="settingsModule" value="djangoProject/settings.py" />
|
||||
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
||||
<option name="environment" value="<map/>" />
|
||||
<option name="doNotUseTestRunner" value="false" />
|
||||
<option name="trackFilePattern" value="migrations" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="tr" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/../djangoProject\templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
@ -1,25 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="75" name="Python" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<option value="E302" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<option value="N802" />
|
||||
<option value="N803" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="D:\software\Miniconda\minconda" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="tr" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/djangoProject.iml" filepath="$PROJECT_DIR$/.idea/djangoProject.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -1,20 +0,0 @@
|
||||
import os
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from django.core.asgi import get_asgi_application
|
||||
# Import other Channels classes and consumers here.
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
# from apps.websocket_app.urls import websocket_urlpatterns
|
||||
from djangoProject.urls import websocket_urlpatterns
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'websocket_demo.settings')
|
||||
|
||||
# application = get_asgi_application()
|
||||
application = ProtocolTypeRouter({
|
||||
# Explicitly set 'http' key using Django's ASGI application.
|
||||
"http": get_asgi_application(),
|
||||
'websocket': AuthMiddlewareStack(
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
})
|
@ -1,28 +0,0 @@
|
||||
"""
|
||||
URL configuration for djangoProject project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.urls import path, include
|
||||
from django.contrib import admin
|
||||
from django.views.generic import TemplateView
|
||||
from ocr_app.views import SubtitleConsumer
|
||||
|
||||
urlpatterns = [
|
||||
path('', TemplateView.as_view(template_name="index.html")),
|
||||
path('api/', include('ocr_app.urls')), # 包含 ocr_app 的 URL 配置
|
||||
]
|
||||
websocket_urlpatterns = [
|
||||
path('ws/', SubtitleConsumer.as_asgi())
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
"""
|
||||
WSGI config for djangoProject project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
|
||||
|
||||
application = get_wsgi_application()
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OcrAppConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'ocr_app'
|
@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
@ -1,8 +0,0 @@
|
||||
# ocr_app/serializers.py
|
||||
from rest_framework import serializers
|
||||
|
||||
class OCRSerializer(serializers.Serializer):
|
||||
mode = serializers.ChoiceField(choices=['baidu', 'tencent'])
|
||||
|
||||
class AudioSerializer(serializers.Serializer):
|
||||
audio_file = serializers.FileField()
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -1,9 +0,0 @@
|
||||
# ocr_app/urls.py
|
||||
from django.urls import path
|
||||
from .views import OCRView,TranslateAudioView
|
||||
|
||||
urlpatterns = [
|
||||
path('ocr/', OCRView.as_view(), name='ocr_view'),
|
||||
path('audio/',TranslateAudioView.as_view(), name='audio_view'),
|
||||
|
||||
]
|
@ -1,190 +0,0 @@
|
||||
import hashlib
|
||||
import requests
|
||||
import time
|
||||
import hmac
|
||||
import json
|
||||
import mss
|
||||
import io
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from paddleocr import PaddleOCR
|
||||
import base64
|
||||
|
||||
|
||||
def take_screenshot():
|
||||
with mss.mss() as sct:
|
||||
# 捕获整个屏幕
|
||||
screenshot = sct.shot(output="result.jpg")
|
||||
|
||||
def sign_request_tencent(secret_id, secret_key, method, endpoint, uri, params):
|
||||
SERVICE = 'tmt'
|
||||
timestamp = int(time.time())
|
||||
date = time.strftime('%Y-%m-%d', time.gmtime(timestamp))
|
||||
|
||||
# 1. Build Canonical Request String
|
||||
http_request_method = method
|
||||
canonical_uri = uri
|
||||
canonical_querystring = ''
|
||||
canonical_headers = f'content-type:application/json\nhost:{endpoint}\n'
|
||||
signed_headers = 'content-type;host'
|
||||
payload_hash = hashlib.sha256(json.dumps(params).encode('utf-8')).hexdigest()
|
||||
canonical_request = (http_request_method + '\n' +
|
||||
canonical_uri + '\n' +
|
||||
canonical_querystring + '\n' +
|
||||
canonical_headers + '\n' +
|
||||
signed_headers + '\n' +
|
||||
payload_hash)
|
||||
|
||||
# 2. Build String to Sign
|
||||
algorithm = 'TC3-HMAC-SHA256'
|
||||
credential_scope = f"{date}/{SERVICE}/tc3_request"
|
||||
string_to_sign = (algorithm + '\n' +
|
||||
str(timestamp) + '\n' +
|
||||
credential_scope + '\n' +
|
||||
hashlib.sha256(canonical_request.encode('utf-8')).hexdigest())
|
||||
|
||||
# 3. Sign String
|
||||
def sign(key, msg):
|
||||
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
|
||||
|
||||
secret_date = sign(('TC3' + secret_key).encode('utf-8'), date)
|
||||
secret_service = sign(secret_date, SERVICE)
|
||||
secret_signing = sign(secret_service, 'tc3_request')
|
||||
signature = hmac.new(secret_signing, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
# 4. Build Authorization Header
|
||||
authorization = (f"{algorithm} "
|
||||
f"Credential={secret_id}/{credential_scope}, "
|
||||
f"SignedHeaders={signed_headers}, "
|
||||
f"Signature={signature}")
|
||||
|
||||
return authorization, timestamp
|
||||
|
||||
# 定义百度翻译函数
|
||||
def translate_to_chinese_baidu(text):
|
||||
APP_ID = '20240909002145465'
|
||||
SECRET_KEY = 'JSYATnaiL1qi8NRfcpHj'
|
||||
url = "http://api.fanyi.baidu.com/api/trans/vip/translate"
|
||||
salt = str(time.time())
|
||||
sign = hashlib.md5((APP_ID + text + salt + SECRET_KEY).encode('utf-8')).hexdigest()
|
||||
params = {
|
||||
'q': text,
|
||||
'from': 'en',
|
||||
'to': 'zh',
|
||||
'appid': APP_ID,
|
||||
'salt': salt,
|
||||
'sign': sign
|
||||
}
|
||||
response = requests.get(url, params=params)
|
||||
result = response.json()
|
||||
|
||||
# 添加错误处理和日志记录
|
||||
if 'trans_result' in result:
|
||||
return result['trans_result'][0]['dst']
|
||||
else:
|
||||
# 打印错误信息和完整的API响应
|
||||
print(f"翻译API响应错误: {result}")
|
||||
return text # 如果翻译失败,返回原文
|
||||
|
||||
|
||||
def translate_to_chinese_tencent(text):
|
||||
SECRET_ID = 'AKIDELOFsGROg9B0hieOuCH9nCQnwrZ5NRJy'
|
||||
SECRET_KEY = 'Zx87sTs50iDoOpZ6RXP4UjqlV5TdbO0R'
|
||||
REGION = 'ap-beijing'
|
||||
ENDPOINT = 'tmt.tencentcloudapi.com'
|
||||
VERSION = '2018-03-21'
|
||||
ACTION = 'TextTranslate'
|
||||
params = {
|
||||
"SourceText": text,
|
||||
"Source": "en",
|
||||
"Target": "zh",
|
||||
"ProjectId": 0
|
||||
}
|
||||
|
||||
method = 'POST'
|
||||
uri = '/'
|
||||
authorization, timestamp = sign_request_tencent(SECRET_ID, SECRET_KEY, method, ENDPOINT, uri, params)
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Host': ENDPOINT,
|
||||
'X-TC-Action': ACTION,
|
||||
'X-TC-Timestamp': str(timestamp),
|
||||
'X-TC-Version': VERSION,
|
||||
'X-TC-Region': REGION,
|
||||
'Authorization': authorization
|
||||
}
|
||||
|
||||
response = requests.post(f'https://{ENDPOINT}{uri}', headers=headers, data=json.dumps(params))
|
||||
result = response.json()
|
||||
|
||||
if 'Response' in result and 'TargetText' in result['Response']:
|
||||
return result['Response']['TargetText']
|
||||
else:
|
||||
print(f"翻译API响应错误: {result}")
|
||||
return text # 如果翻译失败,返回原文
|
||||
|
||||
|
||||
def translate_text(mode):
|
||||
ocr = PaddleOCR(use_angle_cls=True, lang="en", ocr_version="PP-OCRv4") # need to run only once to download and load model into memory
|
||||
img_path = './result.jpg'
|
||||
slice = {'horizontal_stride': 300, 'vertical_stride': 500, 'merge_x_thres': 50, 'merge_y_thres': 35}
|
||||
# 加载图像
|
||||
image = Image.open(img_path).convert('RGB')
|
||||
draw = ImageDraw.Draw(image)
|
||||
results = ocr.ocr(img_path, cls=True)
|
||||
# 处理并绘制结果
|
||||
for res in results:
|
||||
for line in res:
|
||||
box = [tuple(point) for point in line[0]]
|
||||
# 找出边界框
|
||||
box = [(min(point[0] for point in box), min(point[1] for point in box)),
|
||||
(max(point[0] for point in box), max(point[1] for point in box))]
|
||||
txt = line[1][0]
|
||||
height = box[1][1] - box[0][1]
|
||||
font = ImageFont.truetype("./simfang.ttf", size=int(height)) # 根据需要调整大小
|
||||
print(txt)
|
||||
if mode == "baidu":
|
||||
translated_text = translate_to_chinese_baidu(txt)
|
||||
draw.rectangle(box, outline="white", width=height, fill="white") # 绘制矩形
|
||||
draw.text((box[0][0], box[0][1]), translated_text, fill="black", font=font) # 在矩形上方绘制文本
|
||||
# time.sleep(0.6)
|
||||
elif mode == "tencent":
|
||||
translated_text = translate_to_chinese_tencent(txt)
|
||||
draw.rectangle(box, outline="white", width=1, fill="white") # 绘制矩形
|
||||
draw.text((box[0][0], box[0][1]), translated_text, fill="black", font=font) # 在矩形上方绘制文本
|
||||
# time.sleep(0.6)
|
||||
else:
|
||||
print("当前不支持该种翻译模式")
|
||||
image.save(img_path, 'JPEG')
|
||||
return image
|
||||
|
||||
# 音频识别翻译
|
||||
|
||||
def get_access_token():
|
||||
API_KEY = "DlBJVQvNy3pC0v04bGNoqK9r"
|
||||
SECRET_KEY = "ssHRyWUQ4bzK6Yj65D3ZYU0uU5w3X8RB"
|
||||
url = "https://aip.baidubce.com/oauth/2.0/token"
|
||||
params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
|
||||
response = requests.post(url, params=params)
|
||||
return response.json().get("access_token")
|
||||
|
||||
def translate_audio(audio_base64):
|
||||
token = get_access_token()
|
||||
url = f"https://aip.baidubce.com/rpc/2.0/mt/v2/speech-translation?access_token={token}"
|
||||
payload = json.dumps({
|
||||
"from": "en",
|
||||
"to": "zh",
|
||||
"format": "wav",
|
||||
"voice": audio_base64
|
||||
})
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
response = requests.post(url, headers=headers, data=payload)
|
||||
return response.json()
|
||||
|
||||
def process_audio_for_translation(audio_base64):
|
||||
result = translate_audio(audio_base64)
|
||||
if 'error_code' in result:
|
||||
print("Error:", result['error_msg'])
|
||||
else:
|
||||
result = result['result']['target']
|
||||
return result
|
@ -1,76 +0,0 @@
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
from django.shortcuts import render
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from .serializers import OCRSerializer, AudioSerializer
|
||||
from .utils import take_screenshot, translate_text, process_audio_for_translation
|
||||
from channels.generic.websocket import WebsocketConsumer
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
|
||||
class OCRView(APIView):
|
||||
def post(self, request):
|
||||
serializer = OCRSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
mode = serializer.validated_data['mode']
|
||||
take_screenshot()
|
||||
image = translate_text(mode)
|
||||
if image is None:
|
||||
return Response({'error': '无法识别的图像文件'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 将图片对象转换为 Base64 编码字符串
|
||||
image_io = BytesIO()
|
||||
image.save(image_io, format='JPEG')
|
||||
image_io.seek(0)
|
||||
image_base64 = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
||||
return Response({'image': image_base64}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class TranslateAudioView(APIView):
|
||||
def post(self, request):
|
||||
serializer = AudioSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
audio_file = serializer.validated_data['audio_file']
|
||||
audio_data = audio_file.read()
|
||||
audio_base64 = base64.b64encode(audio_data).decode('utf-8')
|
||||
translation = process_audio_for_translation(audio_base64)
|
||||
|
||||
if translation:
|
||||
return Response({'translation': translation}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response({'error': '无法翻译音频文件'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
else:
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
class SubtitleConsumer(WebsocketConsumer):
|
||||
def connect(self):
|
||||
self.accept()
|
||||
|
||||
def disconnect(self, close_code):
|
||||
pass
|
||||
|
||||
def receive(self, text_data):
|
||||
"""
|
||||
接收消息
|
||||
:param text_data: 客户端发送的消息
|
||||
:return:
|
||||
"""
|
||||
print(text_data)
|
||||
poetryList = [
|
||||
"云想衣裳花想容",
|
||||
"春风拂槛露华浓",
|
||||
"若非群玉山头见",
|
||||
"会向瑶台月下逢",
|
||||
]
|
||||
for i in poetryList:
|
||||
time.sleep(0.5)
|
||||
self.send(i)
|
Before Width: | Height: | Size: 376 KiB |
@ -1,2 +0,0 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover"><title>vue-悬浮窗</title><link href="/static/css/app.cfbbec5a.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5646d24e.css" rel="preload" as="style"><link href="/static/js/app.39387daa.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.966affc1.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5646d24e.css" rel="stylesheet"><link href="/static/css/app.cfbbec5a.css" rel="stylesheet"></head><body style="background-color: #f7f8fa;"><noscript><strong>We're sorry but pneumonia-client doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.966affc1.js"></script><script src="/static/js/app.39387daa.js"></script></body><script>var cnzz_protocol = (("https:" == document.location.protocol) ? "https://" : "http://");
|
||||
document.write(unescape("%3Cspan style='display:none' id='cnzz_stat_icon_1278599877'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/z_stat.php%3Fid%3D1278599877' type='text/javascript'%3E%3C/script%3E"));</script></html>
|
@ -1,8 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -1 +0,0 @@
|
||||
build_cx.py
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="tr" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (Screen-Translate-2.1.1)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="tr" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Screen-Translate-2.1.1.iml" filepath="$PROJECT_DIR$/.idea/Screen-Translate-2.1.1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -1,14 +0,0 @@
|
||||
import tkinter
|
||||
from tkinter import ttk
|
||||
|
||||
import sv_ttk
|
||||
|
||||
root = tkinter.Tk()
|
||||
|
||||
button = ttk.Button(root, text="Click me!")
|
||||
button.pack()
|
||||
|
||||
# This is where the magic happens
|
||||
sv_ttk.set_theme("dark")
|
||||
|
||||
root.mainloop()
|
@ -1,801 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import keyboard
|
||||
import requests
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from typing import Literal
|
||||
from PIL import Image, ImageDraw
|
||||
from pystray import Icon as icon
|
||||
from pystray import Menu as menu
|
||||
from pystray import MenuItem as item
|
||||
|
||||
from screen_translate._version import __version__
|
||||
from screen_translate.Globals import gClass, path_logo_icon, dir_captured, fJson, app_name, dir_user_manual
|
||||
from screen_translate.Logging import logger
|
||||
|
||||
from screen_translate.utils.Style import set_ui_style, init_theme, get_theme_list, get_current_theme
|
||||
from screen_translate.utils.Translate import translate
|
||||
from screen_translate.utils.Capture import captureFullScreen
|
||||
from screen_translate.utils.Helper import get_opac_value, nativeNotify, startFile, OpenUrl
|
||||
from screen_translate.utils.LangCode import engine_select_source_dict, engine_select_target_dict, engineList
|
||||
from screen_translate.components.custom.MBox import Mbox
|
||||
from screen_translate.components.custom.Tooltip import CreateToolTip
|
||||
from screen_translate.components.window.History import HistoryWindow
|
||||
from screen_translate.components.window.Settings import SettingWindow
|
||||
from screen_translate.components.window.Capture_Window import CaptureWindow
|
||||
from screen_translate.components.window.Capture_Snip import SnipWindow
|
||||
from screen_translate.components.window.Mask import MaskWindow
|
||||
from screen_translate.components.window.About import AboutWindow
|
||||
from screen_translate.components.window.Ex_Query import QueryWindow
|
||||
from screen_translate.components.window.Ex_Result import ResultWindow
|
||||
from screen_translate.components.window.Log import LogWindow
|
||||
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
def console():
|
||||
logger.info("--- Welcome to Screen Translate ---")
|
||||
logger.info("Use The GUI Window to start capturing and translating")
|
||||
logger.info("You can minimize this window")
|
||||
logger.info("This window is for debugging purposes")
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
class AppTray:
|
||||
"""
|
||||
Tray app
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.icon: icon = None # type: ignore
|
||||
self.menu: menu = None # type: ignore
|
||||
self.menu_items = None # type: ignore
|
||||
gClass.tray = self # type: ignore
|
||||
self.create_tray()
|
||||
|
||||
# -- Tray icon
|
||||
def create_image(self, width, height, color1, color2):
|
||||
# Generate an image and draw a pattern
|
||||
image = Image.new("RGB", (width, height), color1)
|
||||
dc = ImageDraw.Draw(image)
|
||||
dc.rectangle((width // 2, 0, width, height // 2), fill=color2)
|
||||
dc.rectangle((0, height // 2, width // 2, height), fill=color2)
|
||||
|
||||
return image
|
||||
|
||||
# -- Create tray
|
||||
def create_tray(self):
|
||||
try:
|
||||
trayIco = Image.open(path_logo_icon)
|
||||
except Exception:
|
||||
trayIco = self.create_image(64, 64, "black", "white")
|
||||
|
||||
self.menu_items = (
|
||||
item(f"{app_name} {__version__}", lambda *args: None, enabled=False), # do nothing
|
||||
menu.SEPARATOR,
|
||||
item("Snip and Translate", self.snip_win),
|
||||
item("Open Capture Window", self.open_capture_window),
|
||||
menu.SEPARATOR,
|
||||
item(
|
||||
"Generate",
|
||||
menu(
|
||||
item("Mask Window", self.open_mask),
|
||||
item("Query Window", self.open_query),
|
||||
item("Result Window", self.open_result),
|
||||
),
|
||||
),
|
||||
item(
|
||||
"View",
|
||||
menu(
|
||||
item("settings", self.open_settings),
|
||||
item("History", self.open_history),
|
||||
item("Captured Images", self.open_history),
|
||||
item("Log", self.open_log),
|
||||
),
|
||||
),
|
||||
item("Show Main Window", self.open_app),
|
||||
menu.SEPARATOR,
|
||||
item("Exit", self.exit_app),
|
||||
item("Hidden onclick", self.open_app, default=True, visible=False), # onclick the icon will open_app
|
||||
)
|
||||
self.menu = menu(*self.menu_items)
|
||||
self.icon = icon("Screen Translate", trayIco, f"Screen Translate V{__version__}", self.menu)
|
||||
self.icon.run_detached()
|
||||
|
||||
# -- Open app
|
||||
def open_app(self):
|
||||
assert gClass.mw is not None # Show main window
|
||||
gClass.mw.show()
|
||||
|
||||
def open_query(self):
|
||||
assert gClass.ex_qw is not None
|
||||
gClass.ex_qw.show()
|
||||
|
||||
def open_result(self):
|
||||
assert gClass.ex_resw is not None
|
||||
gClass.ex_resw.show()
|
||||
|
||||
def open_capture_window(self):
|
||||
assert gClass.cw is not None
|
||||
gClass.cw.show()
|
||||
|
||||
def open_settings(self):
|
||||
assert gClass.sw is not None
|
||||
gClass.sw.show()
|
||||
|
||||
def open_history(self):
|
||||
assert gClass.hw is not None
|
||||
gClass.hw.show()
|
||||
|
||||
def open_log(self):
|
||||
assert gClass.lw is not None
|
||||
gClass.lw.show()
|
||||
|
||||
def open_mask(self):
|
||||
assert gClass.mask is not None
|
||||
gClass.mask.show()
|
||||
|
||||
def snip_win(self):
|
||||
assert gClass.mw is not None
|
||||
gClass.mw.start_snip_window()
|
||||
|
||||
# -- Exit app by flagging runing false to stop main loop
|
||||
def exit_app(self):
|
||||
gClass.running = False
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
class MainWindow:
|
||||
"""Main Menu Window"""
|
||||
|
||||
def __init__(self):
|
||||
# ----------------------------------------------
|
||||
# Debug console info
|
||||
|
||||
# --- Declarations and Layout ---
|
||||
self.root = tk.Tk()
|
||||
self.root.title(app_name)
|
||||
self.root.geometry("900x300")
|
||||
self.root.wm_attributes("-topmost", False) # Default False
|
||||
self.alwaysOnTop = False
|
||||
self.notified_hidden = False
|
||||
gClass.mw = self # type: ignore
|
||||
|
||||
# ----------------------------------------------
|
||||
# Styles
|
||||
init_theme()
|
||||
self.style = ttk.Style()
|
||||
gClass.style = self.style
|
||||
gClass.native_theme = get_current_theme() # get first theme before changing
|
||||
gClass.theme_lists = list(get_theme_list())
|
||||
|
||||
# rearrange some positions
|
||||
try:
|
||||
gClass.theme_lists.remove("sv")
|
||||
except Exception: # sv theme is not available
|
||||
gClass.theme_lists.remove("sv-dark")
|
||||
gClass.theme_lists.remove("sv-light")
|
||||
|
||||
gClass.theme_lists.insert(0, gClass.native_theme) # add native theme to top of list
|
||||
logger.debug(f"Available Theme to use: {gClass.theme_lists}")
|
||||
gClass.theme_lists.insert(len(gClass.theme_lists), "custom")
|
||||
|
||||
set_ui_style(fJson.settingCache["theme"])
|
||||
# ----------------------------------------------
|
||||
# Frames
|
||||
self.frame_1 = ttk.Frame(self.root)
|
||||
self.frame_1.pack(side=tk.TOP, fill=tk.X, expand=False)
|
||||
self.frame_1.bind("<Button-1>", lambda event: self.root.focus_set())
|
||||
|
||||
self.frame_2_tb_q = ttk.Frame(self.root)
|
||||
self.frame_2_tb_q.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||||
|
||||
self.frame_3 = ttk.Frame(self.root)
|
||||
self.frame_3.pack(side=tk.TOP, fill=tk.X, expand=False)
|
||||
self.frame_3.bind("<Button-1>", lambda event: self.root.focus_set())
|
||||
|
||||
self.frame_4_tb_res = ttk.Frame(self.root)
|
||||
self.frame_4_tb_res.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
|
||||
|
||||
# --- Top frame_1 ---
|
||||
# Button
|
||||
self.btn_translate = ttk.Button(self.frame_1, text="Translate", command=self.start_tl)
|
||||
self.btn_translate.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.btn_translate, "Translate the text in the top frame")
|
||||
|
||||
self.btn_capture_translate = ttk.Button(self.frame_1, text="Capture & Translate", command=self.start_capture_window)
|
||||
self.btn_capture_translate.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.btn_capture_translate, "Capture and translate the text inside capture area. Need to generate the capture UI first")
|
||||
|
||||
self.btn_snip_translate = ttk.Button(self.frame_1, text="Snip & Translate", command=self.start_snip_window)
|
||||
self.btn_snip_translate.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.btn_snip_translate, "Snip and translate the selected text.")
|
||||
|
||||
# Opacity
|
||||
self.slider_capture_opac = ttk.Scale(self.frame_1, from_=0.0, to=1.0, value=0.8, orient=tk.HORIZONTAL, command=self.opac_change)
|
||||
self.slider_capture_opac.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
self.lbl_capture_opac = ttk.Label(self.frame_1, text="Capture Window Opacity: " + "0.8")
|
||||
self.lbl_capture_opac.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
self.frame_status = ttk.Frame(self.frame_1)
|
||||
self.frame_status.pack(side=tk.RIGHT, fill=tk.X, expand=False)
|
||||
|
||||
self.lb_status = ttk.Progressbar(self.frame_status, orient=tk.HORIZONTAL, length=120, mode="determinate")
|
||||
self.lb_status.pack(side=tk.RIGHT, padx=5, pady=5)
|
||||
|
||||
# --- Top frame_2 ---
|
||||
# TB
|
||||
# Translation Textbox (Query/Source)
|
||||
self.frame_tb_query_bg = ttk.Frame(self.frame_2_tb_q, style="Darker.TFrame")
|
||||
self.frame_tb_query_bg.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
self.sb_query = tk.Scrollbar(self.frame_tb_query_bg)
|
||||
self.sb_query.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.tb_query = tk.Text(
|
||||
self.frame_tb_query_bg,
|
||||
height=5,
|
||||
width=100,
|
||||
relief=tk.FLAT,
|
||||
font=(fJson.settingCache["tb_mw_q_font"], fJson.settingCache["tb_mw_q_font_size"]),
|
||||
autoseparators=True,
|
||||
undo=True,
|
||||
maxundo=-1,
|
||||
)
|
||||
self.tb_query.pack(padx=1, pady=1, fill=tk.BOTH, expand=True)
|
||||
self.tb_query.configure(yscrollcommand=self.sb_query.set)
|
||||
self.sb_query.configure(command=self.tb_query.yview)
|
||||
self.tb_query.bind("<KeyRelease>", self.tb_query_change)
|
||||
|
||||
# --- Bottom frame_3 ---
|
||||
# Langoptions onstart
|
||||
self.lbl_engines = ttk.Label(self.frame_3, text="TL Engine:")
|
||||
self.lbl_engines.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.lbl_engines, 'The provider use to translate the text. You can set it to "None" if you only want to use the OCR')
|
||||
|
||||
self.cb_tl_engine = ttk.Combobox(self.frame_3, values=engineList, state="readonly")
|
||||
self.cb_tl_engine.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
self.cb_tl_engine.bind("<<ComboboxSelected>>", self.cb_engine_change)
|
||||
|
||||
self.lbl_source = ttk.Label(self.frame_3, text="From:")
|
||||
self.lbl_source.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.lbl_source, "Source Language (Text to be translated)")
|
||||
|
||||
self.cb_sourceLang = ttk.Combobox(self.frame_3, values=engine_select_source_dict[fJson.settingCache["engine"]], state="readonly", width=29)
|
||||
self.cb_sourceLang.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
self.cb_sourceLang.bind("<<ComboboxSelected>>", self.cb_source_change)
|
||||
|
||||
self.lbl_target = ttk.Label(self.frame_3, text="To:")
|
||||
self.lbl_target.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
CreateToolTip(self.lbl_target, "Target Language (Results)")
|
||||
|
||||
self.cb_targetLang = ttk.Combobox(self.frame_3, values=engine_select_target_dict[fJson.settingCache["engine"]], state="readonly", width=29)
|
||||
self.cb_targetLang.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
self.cb_targetLang.bind("<<ComboboxSelected>>", self.cb_target_change)
|
||||
|
||||
self.btn_swap = ttk.Button(self.frame_3, text="⮁ Swap", command=self.swapTl)
|
||||
self.btn_swap.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
self.btn_clear = ttk.Button(self.frame_3, text="✕ Clear", command=self.clear_tb)
|
||||
self.btn_clear.pack(side=tk.LEFT, padx=5, pady=5)
|
||||
|
||||
# --- Bottom tk.Frame 2 ---
|
||||
# TB
|
||||
# Translation Textbox (Result)
|
||||
self.frame_tb_result_bg = ttk.Frame(self.frame_4_tb_res, style="Darker.TFrame")
|
||||
self.frame_tb_result_bg.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
self.sb_result = tk.Scrollbar(self.frame_tb_result_bg)
|
||||
self.sb_result.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.tb_result = tk.Text(
|
||||
self.frame_tb_result_bg,
|
||||
height=5,
|
||||
width=100,
|
||||
relief=tk.FLAT,
|
||||
font=(fJson.settingCache["tb_mw_q_font"], fJson.settingCache["tb_mw_q_font_size"]),
|
||||
autoseparators=True,
|
||||
undo=True,
|
||||
maxundo=-1,
|
||||
)
|
||||
self.tb_result.pack(padx=1, pady=1, fill=tk.BOTH, expand=True)
|
||||
self.tb_result.configure(yscrollcommand=self.sb_result.set)
|
||||
self.sb_result.configure(command=self.tb_result.yview)
|
||||
self.tb_result.bind("<KeyRelease>", self.tb_result_change)
|
||||
|
||||
# --- Menubar ---
|
||||
# Menubar
|
||||
self.menubar = tk.Menu(self.root)
|
||||
self.filemenu = tk.Menu(self.menubar, tearoff=0)
|
||||
self.filemenu.add_checkbutton(label="Always on Top", command=self.always_on_top)
|
||||
self.filemenu.add_separator()
|
||||
self.filemenu.add_command(label="Hide", command=self.on_closing)
|
||||
self.filemenu.add_command(label="Exit Application", command=self.quit_app)
|
||||
self.menubar.add_cascade(label="File", menu=self.filemenu)
|
||||
|
||||
self.filemenu2 = tk.Menu(self.menubar, tearoff=0)
|
||||
self.filemenu2.add_command(label="Setting", command=self.open_Setting, accelerator="F2") # Open Setting Window
|
||||
self.filemenu2.add_command(label="History", command=self.open_History, accelerator="F3") # Open History Window
|
||||
self.filemenu2.add_command(label="Captured Image", command=self.open_Img_Captured, accelerator="F4") # Open Captured img folder
|
||||
self.filemenu2.add_command(label="Log", command=self.open_Log) # Open Error Log
|
||||
self.menubar.add_cascade(label="View", menu=self.filemenu2)
|
||||
|
||||
self.filemenu3 = tk.Menu(self.menubar, tearoff=0)
|
||||
self.filemenu3.add_command(label="Capture Window", command=self.open_Capture_Screen, accelerator="F5") # Open Capture Screen Window
|
||||
self.filemenu3.add_command(label="Mask Window", command=self.open_Mask_Window, accelerator="Ctrl + Alt + F5") # Open Mask window
|
||||
self.filemenu3.add_command(label="Query Window", command=self.open_Query_Window, accelerator="F6")
|
||||
self.filemenu3.add_command(label="Result Window", command=self.open_Result_Window, accelerator="F7")
|
||||
self.menubar.add_cascade(label="Generate", menu=self.filemenu3)
|
||||
|
||||
self.filemenu4 = tk.Menu(self.menubar, tearoff=0)
|
||||
self.filemenu4.add_command(label="Tesseract", command=self.openTesLink) # Open Tesseract Downloads
|
||||
self.filemenu4.add_command(label="Libretranslate", command=self.openLibreTlLink) # Open Tesseract Downloads
|
||||
self.menubar.add_cascade(label="Get", menu=self.filemenu4)
|
||||
|
||||
self.filemenu5 = tk.Menu(self.menubar, tearoff=0)
|
||||
self.filemenu5.add_command(label="Tutorial", command=self.open_Tutorial) # Open Mbox Tutorials
|
||||
self.filemenu5.add_command(label="FAQ", command=self.open_Faq) # Open FAQ
|
||||
self.filemenu5.add_command(label="Known Bugs", command=self.open_KnownBugs) # Open Knownbugs
|
||||
self.filemenu5.add_separator()
|
||||
self.filemenu5.add_command(label="Open User Manual", command=self.open_UserManual) # Open user manual folder
|
||||
self.filemenu5.add_command(label="Open GitHub Repo", command=lambda aurl="https://github.com/Dadangdut33/Screen-Translate": OpenUrl(aurl))
|
||||
self.filemenu5.add_command(label="Open Changelog", command=self.open_Changelog)
|
||||
self.filemenu5.add_separator()
|
||||
self.filemenu5.add_command(label="Contributor", command=self.open_Contributor) # Open Contributor
|
||||
self.filemenu5.add_command(label="About STL", command=self.open_About, accelerator="F1") # Open about frame
|
||||
self.menubar.add_cascade(label="Help", menu=self.filemenu5)
|
||||
|
||||
# Add to self.root
|
||||
self.root.configure(menu=self.menubar)
|
||||
|
||||
# Bind key shortcut
|
||||
self.root.bind("<F1>", self.open_About)
|
||||
self.root.bind("<F2>", self.open_Setting)
|
||||
self.root.bind("<F3>", self.open_History)
|
||||
self.root.bind("<F4>", self.open_Img_Captured)
|
||||
self.root.bind("<F5>", self.open_Capture_Screen)
|
||||
self.root.bind("<Control-Alt-F5>", self.open_Mask_Window)
|
||||
self.root.bind("<F6>", self.open_Query_Window)
|
||||
self.root.bind("<F7>", self.open_Result_Window)
|
||||
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
# ------------------ on Start ------------------
|
||||
self.root.after(1000, self.isRunningPoll)
|
||||
self.onInit()
|
||||
|
||||
# --- Logo ---
|
||||
try:
|
||||
self.root.iconbitmap(path_logo_icon)
|
||||
except tk.TclError:
|
||||
logger.warning("Error Loading icon: Logo not found!")
|
||||
except Exception as e:
|
||||
logger.warning("Error loading icon")
|
||||
logger.exception(e)
|
||||
|
||||
def isRunningPoll(self):
|
||||
if not gClass.running:
|
||||
self.quit_app()
|
||||
|
||||
self.root.after(1000, self.isRunningPoll)
|
||||
|
||||
# --- Functions ---
|
||||
def onInit(self):
|
||||
self.cb_tl_engine.set(fJson.settingCache["engine"])
|
||||
self.cb_sourceLang.set(fJson.settingCache["sourceLang"])
|
||||
self.cb_targetLang.set(fJson.settingCache["targetLang"])
|
||||
if self.cb_tl_engine.get() == "None":
|
||||
self.cb_targetLang["state"] = "disabled"
|
||||
|
||||
try:
|
||||
if fJson.settingCache["hk_cap_window"] != "":
|
||||
keyboard.add_hotkey(fJson.settingCache["hk_cap_window"], gClass.hk_cap_window_callback)
|
||||
if fJson.settingCache["hk_snip_cap"] != "":
|
||||
keyboard.add_hotkey(fJson.settingCache["hk_snip_cap"], gClass.hk_snip_mode_callback)
|
||||
except KeyError as e:
|
||||
logger.error("Error: Invalid Hotkey Options")
|
||||
logger.exception(e)
|
||||
|
||||
self.hkPollThread = threading.Thread(target=self.hkPoll, daemon=True)
|
||||
self.hkPollThread.start()
|
||||
|
||||
# detect if run from startup or not using sys, with -s as argument marking silent start (hide window)
|
||||
logger.info("Checking if run from startup...")
|
||||
logger.debug(sys.argv)
|
||||
if "-s" in sys.argv:
|
||||
logger.info("Run from startup, hiding window...")
|
||||
self.root.withdraw()
|
||||
|
||||
def quit_app(self):
|
||||
gClass.running = False
|
||||
|
||||
logger.info("Stopping tray...")
|
||||
if gClass.tray:
|
||||
gClass.tray.icon.stop()
|
||||
|
||||
logger.info("Destroying windows...")
|
||||
gClass.sw.root.destroy() # type: ignore # setting window
|
||||
gClass.hw.root.destroy() # type: ignore # history window
|
||||
gClass.cw.root.destroy() # type: ignore # capture window
|
||||
gClass.csw.root.destroy() # type: ignore # capture snip window
|
||||
gClass.aw.root.destroy() # type: ignore # about window
|
||||
gClass.lw.root.destroy() # type: ignore # log window
|
||||
gClass.mask.root.destroy() # type: ignore # mask window
|
||||
gClass.ex_qw.root.destroy() # type: ignore # external query window
|
||||
gClass.ex_resw.root.destroy() # type: ignore # external result window
|
||||
self.root.destroy()
|
||||
|
||||
logger.info("Exiting...")
|
||||
try:
|
||||
sys.exit(0)
|
||||
except SystemExit:
|
||||
logger.info("Exit successful")
|
||||
except Exception:
|
||||
logger.error("Exit failed, killing process")
|
||||
os._exit(0)
|
||||
|
||||
# Show window
|
||||
def show(self):
|
||||
self.root.after(0, self.root.deiconify)
|
||||
|
||||
# On Close
|
||||
def on_closing(self):
|
||||
"""
|
||||
Confirmation on close
|
||||
"""
|
||||
# Only show notification once
|
||||
if not self.notified_hidden:
|
||||
nativeNotify("Hidden to tray", "The app is still running in the background.", path_logo_icon, app_name)
|
||||
self.notified_hidden = True
|
||||
|
||||
self.root.withdraw()
|
||||
|
||||
# Open Setting Window
|
||||
def open_Setting(self, event=None):
|
||||
assert gClass.sw is not None
|
||||
gClass.sw.show()
|
||||
|
||||
# Open History Window
|
||||
def open_History(self, event=None):
|
||||
assert gClass.hw is not None
|
||||
gClass.hw.show()
|
||||
|
||||
# Open result box
|
||||
def open_Result_Window(self, event=None):
|
||||
assert gClass.ex_resw is not None
|
||||
gClass.ex_resw.show()
|
||||
|
||||
# Open query box
|
||||
def open_Query_Window(self, event=None):
|
||||
assert gClass.ex_qw is not None
|
||||
gClass.ex_qw.show()
|
||||
|
||||
# Open About Window
|
||||
def open_About(self, event=None):
|
||||
assert gClass.aw is not None
|
||||
gClass.aw.show()
|
||||
|
||||
# Open Capture Window
|
||||
def open_Capture_Screen(self, event=None):
|
||||
assert gClass.cw is not None
|
||||
gClass.cw.show()
|
||||
|
||||
# Open mask window
|
||||
def open_Mask_Window(self, event=None):
|
||||
assert gClass.mask is not None
|
||||
gClass.mask.show()
|
||||
|
||||
# Open captured image folder
|
||||
def open_Img_Captured(self, event=None):
|
||||
startFile(dir_captured)
|
||||
|
||||
# Open log window
|
||||
def open_Log(self, event=None):
|
||||
assert gClass.lw is not None
|
||||
gClass.lw.show()
|
||||
|
||||
# Hotkey
|
||||
def hkPoll(self):
|
||||
while gClass.running:
|
||||
if gClass.hk_cw_pressed and not gClass.cw_hidden: # If the hotkey for capture and translate is pressed
|
||||
time.sleep(fJson.settingCache["hk_cap_window_delay"] / 1000)
|
||||
self.start_capture_window()
|
||||
gClass.hk_cw_pressed = False
|
||||
|
||||
if gClass.hk_snip_pressed: # If the hotkey for snip and translate is pressed
|
||||
time.sleep(fJson.settingCache["hk_snip_cap_delay"] / 1000)
|
||||
self.start_snip_window()
|
||||
gClass.hk_snip_pressed = False
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
# Slider
|
||||
def opac_change(self, event):
|
||||
value = get_opac_value(event)
|
||||
self.lbl_capture_opac.configure(text=f"Capture Window Opacity: {round(value, 3)}")
|
||||
gClass.slider_cw_change(value, update_slider=True)
|
||||
|
||||
def tb_query_change(self, event):
|
||||
gClass.insert_ex_q(self.tb_query.get(1.0, tk.END).strip())
|
||||
|
||||
def tb_result_change(self, event):
|
||||
gClass.insert_ex_res(self.tb_result.get(1.0, tk.END).strip())
|
||||
|
||||
# Menubar
|
||||
def always_on_top(self):
|
||||
self.alwaysOnTop = not self.alwaysOnTop
|
||||
self.root.wm_attributes("-topmost", self.alwaysOnTop)
|
||||
|
||||
# ---------------------------------
|
||||
# Mbox
|
||||
# Tutorials
|
||||
def open_Tutorial(self):
|
||||
Mbox(
|
||||
"Tutorial",
|
||||
"""1. *First*, make sure your screen scaling is 100%. If scaling is not 100%, the capturer won't work properly. If by any chance you don't want to set your monitor scaling to 100%, you can set the xy offset in the setting
|
||||
\r2. *Second*, you need to install tesseract, you can quickly go to the download link by pressing the download tesseract in menu bar
|
||||
\r3. *Then*, check the settings. Make sure tesseract path is correct
|
||||
\r4. *FOR MULTI MONITOR USER*, set offset in setting. If you have multiple monitor setup, you might need to set the offset in settings.
|
||||
\rWhat you should do in the setting window:\n- Check how the program see your monitors in settings by clicking that one button.\n- You can also see how the capture area captured your images by enabling save capture image in settings and then see the image in 'img_captured' directory
|
||||
\r\n------------------------------------------------------------------------------\nYou can check for visual tutorial in help -> open user manual if you are still confused.""",
|
||||
0,
|
||||
self.root,
|
||||
)
|
||||
|
||||
# FAQ
|
||||
def open_Faq(self):
|
||||
Mbox(
|
||||
"FAQ",
|
||||
"""Q : Do you collect the screenshot?\nA : No, no data is collected by me. Image and text captured will only be use for query and the captured image is only saved locally
|
||||
\rQ : Is this safe?\nA : Yes, it is safe, you can check the code on the github linked in the menubar, or open it yourself on your machine.
|
||||
\rQ : I could not capture anything, help!?\nA : You might need to check the captured image and see wether it actually capture the stuff that you targeted or not. If not, you might want to set offset in setting or change your monitor scaling to 100%
|
||||
""",
|
||||
0,
|
||||
self.root,
|
||||
)
|
||||
|
||||
# Download tesseract
|
||||
def openTesLink(self):
|
||||
Mbox("Info", "Please download the v5.0.0-alpha.20210811 Version (the latest version might be okay too) and install all language pack", 0, self.root)
|
||||
logger.info("Please download the v5.0.0-alpha.20210811 Version (the latest version might be okay too) and install all language pack")
|
||||
OpenUrl("https://github.com/UB-Mannheim/tesseract/wiki")
|
||||
|
||||
def openLibreTlLink(self):
|
||||
Mbox("Info", "You can follow the instruction on their github pages. It is recommended to build it with the models so you can use it fully offline.", 0, self.root)
|
||||
OpenUrl("https://github.com/LibreTranslate/LibreTranslate")
|
||||
|
||||
# Open known bugs
|
||||
def open_KnownBugs(self):
|
||||
Mbox(
|
||||
"Known Bugs",
|
||||
"""- Monitor scaling needs to be 100% or it won't capture accurately (You can fix this easily by setting offset or set your monitor scaling to 100%)""",
|
||||
0,
|
||||
self.root,
|
||||
)
|
||||
|
||||
# Open user manual
|
||||
def open_UserManual(self):
|
||||
try:
|
||||
startFile(dir_user_manual)
|
||||
except Exception:
|
||||
OpenUrl("https://github.com/Dadangdut33/Screen-Translate/tree/main/user_manual")
|
||||
|
||||
# Open contributor
|
||||
def open_Contributor(self):
|
||||
OpenUrl("https://github.com/Dadangdut33/Screen-Translate/graphs/contributors")
|
||||
|
||||
# Open changelog
|
||||
def open_Changelog(self):
|
||||
try:
|
||||
startFile(dir_user_manual + r"\changelog.txt")
|
||||
except Exception:
|
||||
Mbox("Error", "Changelog file not found\n\nProgram will now try open the one in the repository instead of the local copy.", 0, self.root)
|
||||
try:
|
||||
OpenUrl("https://github.com/Dadangdut33/Screen-Translate/blob/main/user_manual/Changelog.txt")
|
||||
# download
|
||||
req = requests.get("https://raw.githubusercontent.com/Dadangdut33/Screen-Translate/main/user_manual/Changelog.txt")
|
||||
with open(dir_user_manual + r"\changelog.txt", "wb") as f:
|
||||
f.write(req.content)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
Mbox("Error", str(e), 0, self.root)
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Widgets functions
|
||||
def swapTl(self):
|
||||
tmp = self.tb_query.get(1.0, tk.END).strip()
|
||||
self.tb_query.delete(1.0, tk.END)
|
||||
self.tb_query.insert(tk.END, self.tb_result.get(1.0, tk.END).strip())
|
||||
self.tb_result.delete(1.0, tk.END)
|
||||
self.tb_result.insert(tk.END, tmp)
|
||||
|
||||
# swap cb but check first
|
||||
tmp = self.cb_sourceLang.get()
|
||||
if self.cb_targetLang.get() in self.cb_sourceLang["values"]:
|
||||
self.cb_sourceLang.set(self.cb_targetLang.get())
|
||||
|
||||
if tmp in self.cb_targetLang["values"]:
|
||||
self.cb_targetLang.set(tmp)
|
||||
|
||||
fJson.savePartialSetting("sourceLang", self.cb_sourceLang.get())
|
||||
fJson.savePartialSetting("targetLang", self.cb_targetLang.get())
|
||||
gClass.update_ex_cw_setting()
|
||||
|
||||
# Clear TB
|
||||
def clear_tb(self):
|
||||
self.tb_query.delete(1.0, tk.END)
|
||||
self.tb_result.delete(1.0, tk.END)
|
||||
|
||||
def cb_engine_change(self, _event=None):
|
||||
# save
|
||||
fJson.savePartialSetting("engine", self.cb_tl_engine.get())
|
||||
self.cb_lang_update()
|
||||
|
||||
def cb_lang_update(self):
|
||||
# update
|
||||
self.cb_sourceLang["values"] = engine_select_source_dict[self.cb_tl_engine.get()]
|
||||
self.cb_targetLang["values"] = engine_select_target_dict[self.cb_tl_engine.get()]
|
||||
|
||||
if self.cb_sourceLang.get() not in self.cb_sourceLang["values"]:
|
||||
self.cb_sourceLang.current(0)
|
||||
|
||||
if self.cb_targetLang.get() not in self.cb_targetLang["values"]:
|
||||
self.cb_targetLang.current(0)
|
||||
|
||||
if self.cb_tl_engine.get() == "None":
|
||||
self.cb_targetLang["state"] = "disabled"
|
||||
else:
|
||||
self.cb_targetLang["state"] = "readonly"
|
||||
|
||||
# save
|
||||
fJson.savePartialSetting("sourceLang", self.cb_sourceLang.get())
|
||||
fJson.savePartialSetting("targetLang", self.cb_targetLang.get())
|
||||
|
||||
# update external
|
||||
gClass.update_ex_cw_setting()
|
||||
|
||||
def cb_source_change(self, _event=None):
|
||||
fJson.savePartialSetting("sourceLang", self.cb_sourceLang.get())
|
||||
# update external
|
||||
gClass.update_ex_cw_setting()
|
||||
|
||||
def cb_target_change(self, _event=None):
|
||||
fJson.savePartialSetting("targetLang", self.cb_targetLang.get())
|
||||
# update external
|
||||
gClass.update_ex_cw_setting()
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
def get_params(self):
|
||||
return self.cb_tl_engine.get(), self.cb_sourceLang.get(), self.cb_targetLang.get(), self.tb_query.get(1.0, tk.END)
|
||||
|
||||
def param_check(self, engine: Literal["Google Translate", "Deepl", "MyMemoryTranslator", "PONS", "LibreTranslate", "None"], from_lang: str, to_lang: str, query: str, withOCR: bool = True):
|
||||
logger.info("Checking params...")
|
||||
logger.debug(f"engine: {engine} | source: {from_lang} | to: {to_lang}")
|
||||
# If source and destination are the same
|
||||
if engine != "None" and ((from_lang) == (to_lang)):
|
||||
gClass.lb_stop()
|
||||
logger.warning("Error Language is the same as source! Please choose a different language")
|
||||
Mbox("Error: Language target is the same as source", "Language target is the same as source! Please choose a different language", 2, self.root)
|
||||
return False
|
||||
|
||||
if engine != "None" and from_lang == "Auto-Detect" and withOCR:
|
||||
gClass.lb_stop()
|
||||
logger.warning("Error: Invalid Language source! Must specify source langauge when using OCR")
|
||||
Mbox("Error: Invalid Source Language Selected", "Must specify source langauge when using OCR", 2, self.root)
|
||||
return False
|
||||
|
||||
# If langto not set
|
||||
if to_lang == "Auto-Detect":
|
||||
gClass.lb_stop()
|
||||
logger.warning("Error: Invalid Language Selected! Must specify language destination")
|
||||
Mbox("Error: Invalid Language Selected", "Must specify language destination", 2, self.root)
|
||||
return False
|
||||
|
||||
# If the text is empty
|
||||
if len(query) == 0:
|
||||
gClass.lb_stop()
|
||||
logger.warning("Error: No text detected! Please select a text to translate")
|
||||
Mbox("Error: No text detected", "Please select a text to translate", 2, self.root)
|
||||
return False
|
||||
|
||||
logger.info("Passed param check!")
|
||||
return True
|
||||
|
||||
def start_tl(self):
|
||||
engine, from_lang, to_lang, query = self.get_params()
|
||||
|
||||
if not self.param_check(engine, from_lang, to_lang, query, False): # type: ignore
|
||||
return
|
||||
|
||||
if engine == "None":
|
||||
logger.warning("Error: No translation engine selected! Please select a translation engine if only translate!")
|
||||
Mbox("Error", "Please select a translation engine if only translate!", 0, self.root)
|
||||
return
|
||||
|
||||
gClass.lb_start()
|
||||
try:
|
||||
tlThread = threading.Thread(target=translate, args=(query, from_lang, to_lang, engine), daemon=True)
|
||||
tlThread.start()
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
Mbox("Error", str(e), 0, self.root)
|
||||
gClass.lb_stop()
|
||||
|
||||
def start_capture_window(self):
|
||||
engine, from_lang, to_lang, query = self.get_params()
|
||||
|
||||
if not self.param_check(engine, from_lang, to_lang, query): # type: ignore
|
||||
return
|
||||
|
||||
if gClass.cw_hidden:
|
||||
logger.warning("Capture window is not generated yet!")
|
||||
Mbox("Error", "Capture window is not generated yet!", 0, self.root)
|
||||
return
|
||||
|
||||
assert gClass.cw is not None
|
||||
gClass.cw.start_capping() # window hiding handled in the cw window
|
||||
|
||||
def start_snip_window(self):
|
||||
engine, from_lang, to_lang, query = self.get_params()
|
||||
|
||||
if not self.param_check(engine, from_lang, to_lang, query): # type: ignore
|
||||
return
|
||||
|
||||
# ----------------- hide other window -----------------
|
||||
if fJson.settingCache["hide_mw_on_cap"]:
|
||||
assert gClass.mw is not None
|
||||
gClass.mw.root.attributes("-alpha", 0)
|
||||
|
||||
assert gClass.ex_qw is not None
|
||||
prev_ex_qw_opac = gClass.ex_qw.currentOpacity
|
||||
if fJson.settingCache["hide_ex_qw_on_cap"]:
|
||||
gClass.ex_qw.root.attributes("-alpha", 0)
|
||||
|
||||
assert gClass.ex_resw is not None
|
||||
prev_ex_resw_opac = gClass.ex_resw.currentOpacity
|
||||
if fJson.settingCache["hide_ex_resw_on_cap"]:
|
||||
gClass.ex_resw.root.attributes("-alpha", 0)
|
||||
|
||||
# ----------------- start snipping -----------------
|
||||
success, imgObj = captureFullScreen()
|
||||
|
||||
# ----------------- show other window -----------------
|
||||
if fJson.settingCache["hide_mw_on_cap"]:
|
||||
assert gClass.mw is not None
|
||||
gClass.mw.root.attributes("-alpha", 1)
|
||||
|
||||
if fJson.settingCache["hide_ex_qw_on_cap"]:
|
||||
assert gClass.ex_qw is not None
|
||||
gClass.ex_qw.root.attributes("-alpha", prev_ex_qw_opac)
|
||||
|
||||
if fJson.settingCache["hide_ex_resw_on_cap"]:
|
||||
assert gClass.ex_resw is not None
|
||||
gClass.ex_resw.root.attributes("-alpha", prev_ex_resw_opac)
|
||||
|
||||
# check if snipping is successful
|
||||
if not success:
|
||||
Mbox("Error", f"Failed to start snipping mode.\nReason: {imgObj}", 0, self.root)
|
||||
return
|
||||
|
||||
assert gClass.csw is not None
|
||||
gClass.csw.start_snipping(imgObj)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
console()
|
||||
|
||||
tray = AppTray() # Start tray app in the background
|
||||
mw = MainWindow()
|
||||
cw = CaptureWindow(mw.root)
|
||||
csw = SnipWindow(mw.root)
|
||||
ex_qw = QueryWindow(mw.root)
|
||||
ex_resw = ResultWindow(mw.root)
|
||||
mask = MaskWindow(mw.root)
|
||||
hw = HistoryWindow(mw.root)
|
||||
lw = LogWindow(mw.root)
|
||||
sw = SettingWindow(mw.root)
|
||||
aw = AboutWindow(mw.root)
|
||||
mw.root.mainloop()
|
@ -1,4 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), "lib"))
|
Before Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 101 KiB |
@ -1,48 +0,0 @@
|
||||
from screen_translate._version import __version__
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
# ! DEEPL SCRAPER IS NOT SUPPORTED when build using cx_freeze !
|
||||
# I don't really know why but it just get stuck there when calling sync_playwright()
|
||||
print("WARNING: DEEPL SCRAPER IS NOT SUPPORTED when build using cx_freeze !")
|
||||
print("I don't really know why but it just get stuck there when calling sync_playwright()")
|
||||
print("Use pyinstaller instead if you want to build the app yourself")
|
||||
|
||||
|
||||
build_options = {
|
||||
"packages": ["asyncio"],
|
||||
"includes": [],
|
||||
"excludes": ["pyinstaller", "cx_Freeze"],
|
||||
"include_files": [
|
||||
("theme", "theme"),
|
||||
("user_manual", "user_manual"),
|
||||
("assets", "lib/assets"),
|
||||
("LICENSE", "LICENSE.txt"),
|
||||
("user_manual/readme.txt", "README.txt"),
|
||||
],
|
||||
}
|
||||
|
||||
import sys
|
||||
|
||||
# ask for console or not
|
||||
console = input("Do you want to hide console window? (y/n) (default y): ").lower()
|
||||
if console == "n":
|
||||
base = None
|
||||
print(">> Console window will be shown")
|
||||
else:
|
||||
base = "Win32GUI" if sys.platform == "win32" else None
|
||||
print(">> Console window will be hidden")
|
||||
|
||||
target = Executable("Main.py", base=base, target_name="ScreenTranslate", icon="assets/logo.ico")
|
||||
|
||||
setup(
|
||||
name="Screen Translate",
|
||||
version=__version__,
|
||||
author="Dadangdut33",
|
||||
url="https://github.com/Dadangdut33/Screen-Translate",
|
||||
download_url="https://github.com/Dadangdut33/Screen-Translate/releases/latest",
|
||||
license="MIT",
|
||||
license_files=["LICENSE"],
|
||||
description="A Screen Translator/OCR Translator made by using Python and Tesseract",
|
||||
options={"build_exe": build_options},
|
||||
executables=[target],
|
||||
)
|
@ -1,99 +0,0 @@
|
||||
"""
|
||||
Pyinstaller script to move stuff, rename, and also make a clean output folder
|
||||
"""
|
||||
|
||||
import os, shutil
|
||||
from PyInstaller.__main__ import generate_parser, run # type: ignore
|
||||
from screen_translate._version import __version__
|
||||
|
||||
|
||||
def run_makespec(filenames, **opts):
|
||||
# Split pathex by using the path separator
|
||||
temppaths = opts["pathex"][:]
|
||||
pathex = opts["pathex"] = []
|
||||
for p in temppaths:
|
||||
pathex.extend(p.split(os.pathsep))
|
||||
|
||||
import PyInstaller.building.makespec
|
||||
|
||||
spec_file = PyInstaller.building.makespec.main(filenames, **opts)
|
||||
return spec_file
|
||||
|
||||
|
||||
options = [
|
||||
"Main.py",
|
||||
"--clean",
|
||||
"--additional-hooks-dir=./_pyinstaller_hooks",
|
||||
"--runtime-hook=./_pyinstaller_hooks/add_lib.py",
|
||||
"--icon=./assets/logo.ico",
|
||||
"--add-data=./theme;theme",
|
||||
"--add-data=./assets;assets",
|
||||
"--add-data=./user_manual;user_manual",
|
||||
"--add-data=./user_manual/Readme.txt;.",
|
||||
"--add-data=./user_manual/Changelog.txt;.",
|
||||
"--add-data=./LICENSE.txt;.",
|
||||
"--exclude-module=pyinstaller",
|
||||
"--exclude-module=cx_Freeze",
|
||||
]
|
||||
|
||||
# ask for console or not
|
||||
console = input("Do you want to hide console window? (y/n) (default y): ").lower()
|
||||
specName = f"ScreenTranslate {__version__}"
|
||||
argsName = f"-n{specName}"
|
||||
|
||||
if console == "n":
|
||||
specName += "-C"
|
||||
argsName += "-C"
|
||||
extend = [argsName, "-c"]
|
||||
print(">> Console window will be shown")
|
||||
else:
|
||||
extend = [argsName, "-w"]
|
||||
print(">> Console window will be hidden")
|
||||
|
||||
options.extend(extend)
|
||||
|
||||
|
||||
# -----------------
|
||||
# make spec folder
|
||||
parser = generate_parser()
|
||||
args = parser.parse_args(options)
|
||||
run_makespec(**vars(args))
|
||||
|
||||
# Edit spec folder
|
||||
specFile = f"{specName}.spec"
|
||||
spec = ""
|
||||
with open(specFile, "r") as f:
|
||||
spec = f.read()
|
||||
# replace exe name to ScreenTranslate
|
||||
spec = spec.replace(f"name='{specName}'", f"name='ScreenTranslate'", 1)
|
||||
|
||||
# write spec file
|
||||
with open(specFile, "w") as f:
|
||||
f.write(spec)
|
||||
|
||||
# create license.txt file
|
||||
with open("LICENSE", "r") as f:
|
||||
license = f.read()
|
||||
with open("LICENSE.txt", "w") as f2:
|
||||
f2.write(license)
|
||||
|
||||
# run pyinstaller
|
||||
run([specFile])
|
||||
|
||||
# delete license.txt file
|
||||
print(">> Deleting created license.txt file")
|
||||
os.remove("LICENSE.txt")
|
||||
|
||||
output_folder = f"dist/{specName}"
|
||||
|
||||
# create lib folder in output folder
|
||||
lib_folder = f"{output_folder}/lib"
|
||||
os.mkdir(lib_folder)
|
||||
|
||||
# move all .dll .pyd files to lib folder with some exception
|
||||
print(">> Moving .dll files to lib folder")
|
||||
dontMove = ["python3.dll", "python310.dll", "python38.dll", "python39.dll", "libopenblas64__v0.3.21-gcc_10_3_0.dll"]
|
||||
for file in os.listdir(output_folder):
|
||||
if file.endswith(".dll") or file.endswith(".pyd"):
|
||||
if file not in dontMove:
|
||||
shutil.move(f"{output_folder}/{file}", f"{lib_folder}/{file}")
|
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 195 KiB |
Before Width: | Height: | Size: 60 KiB |