Byaidu 1 سال پیش
والد
کامیت
6519ce5079
3فایلهای تغییر یافته به همراه135 افزوده شده و 196 حذف شده
  1. 5 16
      pdf2zh/converter.py
  2. 63 158
      pdf2zh/gui.py
  3. 67 22
      pdf2zh/translator.py

+ 5 - 16
pdf2zh/converter.py

@@ -136,23 +136,12 @@ class TranslateConverter(PDFConverterEx):
         self.noto = noto
         self.translator: BaseTranslator = None
         param = service.split(":", 1)
-        service_id = param[0]
+        service_name = param[0]
         service_model = param[1] if len(param) > 1 else None
-        if service_id == "google":
-            self.translator = GoogleTranslator(service, lang_out, lang_in, None)
-        elif service_id == "deepl":
-            self.translator = DeepLTranslator(service, lang_out, lang_in, None)
-        elif service_id == "deeplx":
-            self.translator = DeepLXTranslator(service, lang_out, lang_in, None)
-        elif service_id == "ollama":
-            self.translator = OllamaTranslator(service, lang_out, lang_in, service_model)
-        elif service_id == "openai":
-            self.translator = OpenAITranslator(service, lang_out, lang_in, service_model)
-        elif service_id == "azure":
-            self.translator = AzureTranslator(service, lang_out, lang_in, None)
-        elif service_id == "tencent":
-            self.translator = TencentTranslator(service, lang_out, lang_in, None)
-        else:
+        for translator in [GoogleTranslator, DeepLTranslator, DeepLXTranslator, OllamaTranslator, OpenAITranslator, AzureTranslator, TencentTranslator]:
+            if service_name == translator.name:
+                self.translator = translator(service, lang_out, lang_in, service_model)
+        if not self.translator:
             raise ValueError("Unsupported translation service")
 
     def receive_layout(self, ltpage: LTPage):

+ 63 - 158
pdf2zh/gui.py

@@ -3,6 +3,16 @@ import shutil
 from pathlib import Path
 from pdf2zh import __version__
 from pdf2zh.pdf2zh import extract_text
+from pdf2zh.translator import (
+    BaseTranslator,
+    GoogleTranslator,
+    DeepLTranslator,
+    DeepLXTranslator,
+    OllamaTranslator,
+    OpenAITranslator,
+    AzureTranslator,
+    TencentTranslator,
+)
 
 import gradio as gr
 import numpy as np
@@ -13,106 +23,14 @@ import cgi
 
 # Map service names to pdf2zh service options
 # five value, padding with None
-service_map = {
-    "Google": (None, None, None),
-    "DeepL": ("DEEPL_SERVER_URL", "DEEPL_AUTH_KEY", None),
-    "DeepLX": ("DEEPLX_SERVER_URL", "DEEPLX_AUTH_KEY", None),
-    "Ollama": ("OLLAMA_HOST", None, None),
-    "OpenAI": ("OPENAI_BASE_URL", None, "OPENAI_API_KEY"),
-    "Azure": ("AZURE_APIKEY", "AZURE_ENDPOINT", "AZURE_REGION"),
-    "Tencent": ("TENCENT_SECRET_KEY", "TENCENT_SECRET_ID", None),
-}
-service_config = {
-    "Google": {
-        "apikey_content": {"visible": False},
-        "apikey2_visibility": {"visible": False},
-        "model_visibility": {"visible": False},
-        "apikey3_visibility": {"visible": False},
-    },
-    "DeepL": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "apikey2_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[1]),
-            "label": s[1],
-        },
-        "model_visibility": {"visible": False},
-        "apikey3_visibility": {"visible": False},
-    },
-    "DeepLX": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "apikey2_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[1]),
-            "label": s[1],
-        },
-        "model_visibility": {"visible": False},
-        "apikey3_visibility": {"visible": False},
-    },
-    "Ollama": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "apikey2_visibility": {"visible": False},
-        "model_visibility": lambda s: {"visible": True, "value": s[1]},
-        "apikey3_visibility": {"visible": False},
-    },
-    "OpenAI": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[2]),
-            "label": s[2],
-        },
-        "apikey2_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "model_visibility": {"visible": True, "value": "gpt-4o"},
-        "apikey3_visibility": {"visible": False},
-    },
-    "Azure": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "apikey2_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[1]),
-            "label": s[1],
-        },
-        "model_visibility": {"visible": False},
-        "apikey3_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[2]),
-            "label": s[2],
-        },
-    },
-    "Tencent": {
-        "apikey_content": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[0]),
-            "label": s[0],
-        },
-        "apikey2_visibility": lambda s: {
-            "visible": True,
-            "value": os.environ.get(s[1]),
-            "label": s[1],
-        },
-        "model_visibility": {"visible": False},
-        "apikey3_visibility": {"visible": False},
-    },
+service_map: dict[str, BaseTranslator] = {
+    "Google": GoogleTranslator,
+    "DeepL": DeepLTranslator,
+    "DeepLX": DeepLXTranslator,
+    "Ollama": OllamaTranslator,
+    "OpenAI": OpenAITranslator,
+    "Azure": AzureTranslator,
+    "Tencent": TencentTranslator,
 }
 lang_map = {
     "Chinese": "zh",
@@ -135,7 +53,7 @@ flag_demo = False
 if os.environ.get("PDF2ZH_DEMO"):
     flag_demo = True
     service_map = {
-        "Google": ("google", None, None),
+        "Google": GoogleTranslator,
     }
     page_map = {
         "First": [0],
@@ -147,14 +65,10 @@ if os.environ.get("PDF2ZH_DEMO"):
 
 def verify_recaptcha(response):
     recaptcha_url = "https://www.google.com/recaptcha/api/siteverify"
-
     print("reCAPTCHA", server_key, response)
-
     data = {"secret": server_key, "response": response}
     result = requests.post(recaptcha_url, data=data).json()
-
     print("reCAPTCHA", result.get("success"))
-
     return result.get("success")
 
 
@@ -167,18 +81,8 @@ def pdf_preview(file):
 
 
 def upload_file(file, service, progress=gr.Progress()):
-    """Handle file upload, validation, and initial preview."""
-    if not file or not os.path.exists(file):
-        return None, None
-
-    try:
-        # Convert first page for preview
-        preview_image = pdf_preview(file)
-
-        return file, preview_image
-    except Exception as e:
-        print(f"Error converting PDF: {e}")
-        return None, None
+    preview_image = pdf_preview(file)
+    return file, preview_image
 
 
 def download_with_limit(url, save_path, size_limit):
@@ -187,10 +91,10 @@ def download_with_limit(url, save_path, size_limit):
     with requests.get(url, stream=True, timeout=10) as response:
         response.raise_for_status()
         content = response.headers.get("Content-Disposition")
-        try:
+        try:  # filename from header
             _, params = cgi.parse_header(content)
             filename = params["filename"]
-        except Exception:
+        except Exception:  # filename from url
             filename = os.path.basename(url)
         with open(save_path / filename, "wb") as file:
             for chunk in response.iter_content(chunk_size=chunk_size):
@@ -508,45 +412,46 @@ with gr.Blocks(
                 return details_wrapper(envs_status)
 
             def on_select_service(service, evt: gr.EventData):
-                if service in service_config:
-                    config = service_config[service]
-                    apikey_content = gr.update(
-                        **(
-                            config["apikey_content"](service_map[service])
-                            if callable(config["apikey_content"])
-                            else config["apikey_content"]
-                        )
-                    )
-                    apikey2_visibility = gr.update(
-                        **(
-                            config["apikey2_visibility"](service_map[service])
-                            if callable(config["apikey2_visibility"])
-                            else config["apikey2_visibility"]
-                        )
-                    )
-                    model_visibility = gr.update(
-                        **(
-                            config["model_visibility"](service_map[service])
-                            if callable(config["model_visibility"])
-                            else config["model_visibility"]
-                        )
-                    )
-                    apikey3_visibility = gr.update(
-                        **(
-                            config["apikey3_visibility"](service_map[service])
-                            if callable(config["apikey3_visibility"])
-                            else config["apikey3_visibility"]
-                        )
-                    )
-                else:
-                    raise gr.Error("Strange Service")
-                return (
-                    env_var_checker(service_map[service]),
-                    model_visibility,
-                    apikey_content,
-                    apikey2_visibility,
-                    apikey3_visibility,
-                )
+                # if service in service_config:
+                #     config = service_config[service]
+                #     apikey_content = gr.update(
+                #         **(
+                #             config["apikey_content"](service_map[service])
+                #             if callable(config["apikey_content"])
+                #             else config["apikey_content"]
+                #         )
+                #     )
+                #     apikey2_visibility = gr.update(
+                #         **(
+                #             config["apikey2_visibility"](service_map[service])
+                #             if callable(config["apikey2_visibility"])
+                #             else config["apikey2_visibility"]
+                #         )
+                #     )
+                #     model_visibility = gr.update(
+                #         **(
+                #             config["model_visibility"](service_map[service])
+                #             if callable(config["model_visibility"])
+                #             else config["model_visibility"]
+                #         )
+                #     )
+                #     apikey3_visibility = gr.update(
+                #         **(
+                #             config["apikey3_visibility"](service_map[service])
+                #             if callable(config["apikey3_visibility"])
+                #             else config["apikey3_visibility"]
+                #         )
+                #     )
+                # else:
+                #     raise gr.Error("Strange Service")
+                # return (
+                #     env_var_checker(service_map[service]),
+                #     model_visibility,
+                #     apikey_content,
+                #     apikey2_visibility,
+                #     apikey3_visibility,
+                # )
+                pass
 
             def on_select_filetype(file_type):
                 return (

+ 67 - 22
pdf2zh/translator.py

@@ -21,6 +21,7 @@ def remove_control_characters(s):
 
 
 class BaseTranslator:
+    name = "base"
     envs = {}
 
     def __init__(self, service, lang_out, lang_in, model):
@@ -49,6 +50,8 @@ class BaseTranslator:
 
 
 class GoogleTranslator(BaseTranslator):
+    name = "google"
+
     def __init__(self, service, lang_out, lang_in, model):
         lang_out = "zh-CN" if lang_out == "auto" else lang_out
         lang_in = "en" if lang_in == "auto" else lang_in
@@ -76,8 +79,45 @@ class GoogleTranslator(BaseTranslator):
         return remove_control_characters(result)
 
 
+class BingTranslator(BaseTranslator):
+    # https://github.com/immersive-translate/old-immersive-translate/blob/6df13da22664bea2f51efe5db64c63aca59c4e79/src/background/translationService.js
+    # TODO: IID & IG
+    name = "bing"
+
+    def __init__(self, service, lang_out, lang_in, model):
+        lang_out = "zh" if lang_out == "auto" else lang_out
+        lang_in = "en" if lang_in == "auto" else lang_in
+        super().__init__(service, lang_out, lang_in, model)
+        self.session = requests.Session()
+        self.endpoint = "https://www.bing.com/ttranslatev3?isVertical=1"
+
+    def fineSID(self):
+        resp = self.session.get("https://www.bing.com/translator")
+        result = re.findall(
+            r"params_AbusePreventionHelper\s=\s\[(.*?),\"(.*?)\",", resp.text
+        )[0]
+        return result
+
+    def translate(self, text):
+        sid = self.fineSID()
+        resp = self.session.post(
+            self.endpoint,
+            data={
+                "fromLang": self.lang_in,
+                "text": text,
+                "to": self.lang_out,
+                "tryFetchingGenderDebiasedTranslations": True,
+                "token": sid[1],
+                "key": sid[0],
+            },
+        )
+        print(resp.json())
+        return resp.json()[0]["translations"][0]["text"]
+
+
 class TencentTranslator(BaseTranslator):
     # https://github.com/TencentCloud/tencentcloud-sdk-python
+    name = "tencent"
     envs = {
         "TENCENTCLOUD_SECRET_ID": None,
         "TENCENTCLOUD_SECRET_KEY": None,
@@ -100,56 +140,59 @@ class TencentTranslator(BaseTranslator):
         return resp.TargetText
 
 
-class DeepLXTranslator(BaseTranslator):
-    # https://deeplx.owo.network/endpoints/free.html
+class DeepLTranslator(BaseTranslator):
+    # https://github.com/DeepLcom/deepl-python
+    name = "deepl"
     envs = {
-        "DEEPLX_ENDPOINT": "https://api.deepl.com/v2/translate",
+        "DEEPL_SERVER_URL": "https://api.deepl.com",
+        "DEEPL_AUTH_KEY": None,
     }
 
     def __init__(self, service, lang_out, lang_in, model):
         lang_out = "zh" if lang_out == "auto" else lang_out
         lang_in = "en" if lang_in == "auto" else lang_in
         super().__init__(service, lang_out, lang_in, model)
-        self.endpoint = os.getenv("DEEPLX_ENDPOINT")
         self.session = requests.Session()
+        server_url = os.getenv("DEEPL_SERVER_URL")
+        auth_key = os.getenv("DEEPL_AUTH_KEY")
+        self.client = deepl.Translator(auth_key, server_url=server_url)
 
     def translate(self, text):
-        resp = self.session.post(
-            self.endpoint,
-            json={
-                "source_lang": self.lang_in,
-                "target_lang": self.lang_out,
-                "text": text,
-            },
+        response = self.client.translate_text(
+            text, target_lang=self.lang_out, source_lang=self.lang_in
         )
-        return resp.json()["data"]
+        return response.text
 
 
-class DeepLTranslator(BaseTranslator):
-    # https://github.com/DeepLcom/deepl-python
+class DeepLXTranslator(BaseTranslator):
+    # https://deeplx.owo.network/endpoints/free.html
+    name = "deeplx"
     envs = {
-        "DEEPL_SERVER_URL": "https://api.deepl.com",
-        "DEEPL_AUTH_KEY": None,
+        "DEEPLX_ENDPOINT": "https://api.deepl.com/translate",
     }
 
     def __init__(self, service, lang_out, lang_in, model):
         lang_out = "zh" if lang_out == "auto" else lang_out
         lang_in = "en" if lang_in == "auto" else lang_in
         super().__init__(service, lang_out, lang_in, model)
+        self.endpoint = os.getenv("DEEPLX_ENDPOINT")
         self.session = requests.Session()
-        server_url = os.getenv("DEEPL_SERVER_URL")
-        auth_key = os.getenv("DEEPL_AUTH_KEY")
-        self.client = deepl.Translator(auth_key, server_url=server_url)
 
     def translate(self, text):
-        response = self.client.translate_text(
-            text, target_lang=self.lang_out, source_lang=self.lang_in
+        resp = self.session.post(
+            self.endpoint,
+            json={
+                "source_lang": self.lang_in,
+                "target_lang": self.lang_out,
+                "text": text,
+            },
         )
-        return response.text
+        return resp.json()["data"]
 
 
 class OllamaTranslator(BaseTranslator):
     # https://github.com/ollama/ollama-python
+    name = "ollama"
     envs = {
         "OLLAMA_HOST": "http://127.0.0.1:11434",
         "OLLAMA_MODEL": "gemma2",
@@ -175,6 +218,7 @@ class OllamaTranslator(BaseTranslator):
 
 class OpenAITranslator(BaseTranslator):
     # https://github.com/openai/openai-python
+    name = "openai"
     envs = {
         "OPENAI_BASE_URL": "https://api.openai.com/v1",
         "OPENAI_API_KEY": None,
@@ -201,6 +245,7 @@ class OpenAITranslator(BaseTranslator):
 
 class AzureTranslator(BaseTranslator):
     # https://github.com/Azure/azure-sdk-for-python
+    name = "azure"
     envs = {
         "AZURE_ENDPOINT": "https://api.translator.azure.cn",
         "AZURE_APIKEY": None,