Compare commits

...

2 Commits

Author SHA1 Message Date
github6-dev 23e8343cdc cjh
10 months ago
github6-dev 01fc74bc8b c
10 months ago

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,28 @@
<?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="&lt;map/&gt;" />
<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>

@ -0,0 +1,25 @@
<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>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?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>

@ -0,0 +1,8 @@
<?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>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

Binary file not shown.

@ -0,0 +1,20 @@
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
)
),
})

@ -0,0 +1,181 @@
"""
Django settings for djangoProject project.
Generated by 'django-admin startproject' using Django 5.0.3.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-7o*9916!!*h__r0pe%_!y6sx5b$s)_da_^unz*@rxk(8*7k5+f'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'daphne',
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# demo
'ocr_app',
'rest_framework',
'corsheaders',
]
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
# 允许所有域名访问
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
# 或者你可以更具体地设置允许的域名
CORS_ALLOWED_ORIGINS = [
"https://your-frontend-domain.com",
"http://localhost:8080",
]
# 允许的请求方法
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
# 允许的请求头部
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
ROOT_URLCONF = 'djangoProject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'),]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
ASGI_APPLICATION = 'djangoProject.asgi.application'
WSGI_APPLICATION = 'djangoProject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'), # 项目默认会有的路径如果你部署的不仅是前端打包的静态文件项目目录static文件下还有其他文件最好不要删
]
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -0,0 +1,28 @@
"""
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())
]

@ -0,0 +1,16 @@
"""
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()

@ -0,0 +1,22 @@
#!/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()

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class OcrAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ocr_app'

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

@ -0,0 +1,8 @@
# 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()

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,9 @@
# 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'),
]

@ -0,0 +1,190 @@
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

@ -0,0 +1,76 @@
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,2 @@
<!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>

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,14 @@
<?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>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?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>

@ -0,0 +1,8 @@
<?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>

@ -0,0 +1,14 @@
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()

@ -0,0 +1,801 @@
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()

@ -0,0 +1,4 @@
import sys
import os
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), "lib"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

@ -0,0 +1,48 @@
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],
)

@ -0,0 +1,99 @@
"""
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}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save