This commit is contained in:
恍兮惚兮 2024-05-15 01:51:28 +08:00
parent 20e7106d60
commit a001ba38c7
25 changed files with 477 additions and 45 deletions

View File

@ -415,7 +415,7 @@ class MAINUI:
text, savehook_new_data[self.textsource.pname]
)
except:
print_exc()
pass
self.reader.read(text, force)
except:
print_exc()

View File

@ -5,16 +5,146 @@ from PyQt5.QtWidgets import (
QVBoxLayout,
QTextBrowser,
QLineEdit,
QMenu,
QAction,
QPushButton,
QTextEdit,
QTabWidget,
QDialog,
QLabel,
)
from traceback import print_exc
import requests
from PyQt5.QtCore import Qt, pyqtSignal
import qtawesome, functools
import threading, gobject
from PyQt5.QtGui import QCursor
import qtawesome, functools, os
import threading, gobject, uuid
from myutils.config import globalconfig
from myutils.config import globalconfig, _TR, _TRL
import myutils.ankiconnect as anki
from gui.usefulwidget import closeashidewindow, getQMessageBox, getboxlayout
from gui.usefulwidget import closeashidewindow
from myutils.ocrutil import imageCut
from gui.rangeselect import rangeselct_function
class AnkiWindow(QDialog):
setcurrenttext = pyqtSignal(str)
appenddictionary = pyqtSignal(str)
def langdu(self):
if gobject.baseobject.reader:
self.audiofile = gobject.baseobject.reader.syncttstofile(
self.wordedit.text()
)
def crop(self):
def ocroncefunction(rect):
img = imageCut(0, rect[0][0], rect[0][1], rect[1][0], rect[1][1])
fname = "./cache/ocr/cropforanki.png"
os.makedirs("./cache/ocr", exist_ok=True)
img.save(fname)
self.cropedimagepath = os.path.abspath(fname)
rangeselct_function(self, ocroncefunction, False, False)
def __init__(self, parent) -> None:
super().__init__(parent, Qt.WindowCloseButtonHint)
self.setWindowTitle("Anki Connect")
self.audiofile = None
self.cropedimagepath = None
layout = QVBoxLayout()
self.setLayout(layout)
soundbutton = QPushButton(qtawesome.icon("fa.music"), "")
soundbutton.clicked.connect(self.langdu)
cropbutton = QPushButton(qtawesome.icon("fa.crop"), "")
cropbutton.clicked.connect(self.crop)
self.wordedit = QLineEdit()
layout.addLayout(
getboxlayout([QLabel(_TR("Word")), self.wordedit, soundbutton, cropbutton])
)
self.textedit = QTextEdit()
layout.addWidget(self.textedit)
btn = QPushButton(_TR("添加"))
btn.clicked.connect(self.errorwrap)
layout.addWidget(btn)
self.setcurrenttext.connect(self.reset)
self.appenddictionary.connect(self.textedit.append)
def reset(self, text):
self.wordedit.setText(text)
self.textedit.clear()
self.cropedimagepath = None
self.audiofile = None
def errorwrap(self):
try:
self.addanki()
getQMessageBox(self, _TR("成功"), _TR("成功"))
except requests.NetWorkException:
getQMessageBox(self, _TR("错误"), _TR("无法连接到anki"))
except anki.AnkiException as e:
getQMessageBox(self, _TR("错误"), str(e))
except:
print_exc()
def addanki(self):
word = self.wordedit.text()
explain = self.textedit.toHtml()
anki.Deck.create(anki.DeckName)
try:
model = anki.Model.create(
anki.ModelName,
anki.model_fileds,
anki.model_css,
False,
[
{
"Name": "LUNACARDTEMPLATE1",
"Front": anki.model_htmlfront,
"Back": anki.model_htmlback,
}
],
)
except anki.AnkiModelExists:
model = anki.Model(anki.ModelName)
model.updateStyling(anki.model_css)
model.updateTemplates(
{
"LUNACARDTEMPLATE1": {
"Front": anki.model_htmlfront,
"Back": anki.model_htmlback,
}
}
)
media = []
for k, _ in [("audio", self.audiofile), ("image", self.cropedimagepath)]:
if _:
media.append(
[
{
"path": _,
"filename": str(uuid.uuid4()) + os.path.basename(_),
"fields": [k],
}
]
)
else:
media.append([])
anki.Note.add(
anki.DeckName,
anki.ModelName,
{
"word": word,
"explain": explain,
},
False,
[],
media[0],
media[1],
)
class searchwordW(closeashidewindow):
@ -24,6 +154,7 @@ class searchwordW(closeashidewindow):
def __init__(self, parent):
super(searchwordW, self).__init__(parent, globalconfig, "sw_geo")
self.ankiwindow = AnkiWindow(self)
self.setupUi()
# self.setWindowFlags(self.windowFlags()&~Qt.WindowMinimizeButtonHint)
self.getnewsentencesignal.connect(self.getnewsentence)
@ -55,16 +186,18 @@ class searchwordW(closeashidewindow):
self.searchtext = QLineEdit()
# self.searchtext.setFont(font)
self.searchlayout.addWidget(self.searchtext)
self.searchbutton = QPushButton(qtawesome.icon("fa.search"), "") # _TR("搜索"))
searchbutton = QPushButton(qtawesome.icon("fa.search"), "") # _TR("搜索"))
# self.searchbutton.setFont(font)
self.searchbutton.clicked.connect(lambda: self.search((self.searchtext.text())))
self.searchlayout.addWidget(self.searchbutton)
searchbutton.clicked.connect(lambda: self.search((self.searchtext.text())))
self.searchlayout.addWidget(searchbutton)
self.soundbutton = QPushButton(qtawesome.icon("fa.music"), "")
# self.searchbutton.setFont(font)
self.soundbutton.clicked.connect(self.langdu)
self.searchlayout.addWidget(self.soundbutton)
soundbutton = QPushButton(qtawesome.icon("fa.music"), "")
soundbutton.clicked.connect(self.langdu)
self.searchlayout.addWidget(soundbutton)
ankiconnect = QPushButton(qtawesome.icon("fa.adn"), "")
ankiconnect.clicked.connect(self.ankiwindow.show)
self.searchlayout.addWidget(ankiconnect)
self.tab = QTabWidget(self)
@ -86,7 +219,6 @@ class searchwordW(closeashidewindow):
textOutput = QTextBrowser(self)
# textOutput.setFont(font)
textOutput.setContextMenuPolicy(Qt.CustomContextMenu)
textOutput.setUndoRedoEnabled(False)
textOutput.setReadOnly(True)
textOutput.setOpenLinks(False)
@ -94,9 +226,10 @@ class searchwordW(closeashidewindow):
self.tab.setTabVisible(i, False)
self.textbs[self._k[i]] = textOutput
textOutput.setContextMenuPolicy(Qt.CustomContextMenu)
textOutput.customContextMenuRequested.connect(
functools.partial(self.showmenu, textOutput)
)
self.hiding = True
self.searchthreadsignal.connect(self.searchthread)
@ -104,6 +237,15 @@ class searchwordW(closeashidewindow):
if gobject.baseobject.reader:
gobject.baseobject.reader.read(self.searchtext.text(), True)
def showmenu(self, tb, point):
menu = QMenu(tb)
append = QAction(_TR("追加"))
menu.addAction(append)
print(point, (tb.pos()), self.mapToGlobal(tb.pos()))
action = menu.exec(QCursor.pos())
if action == append:
self.ankiwindow.appenddictionary.emit(tb.toHtml())
def getnewsentence(self, sentence, append):
self.showNormal()
if append:
@ -120,7 +262,7 @@ class searchwordW(closeashidewindow):
def search(self, sentence):
if sentence == "":
return
self.ankiwindow.setcurrenttext.emit(sentence)
_mp = {}
_mp.update(gobject.baseobject.cishus)

View File

@ -0,0 +1,230 @@
import requests
from .ankiconnect_config import *
class AnkiException(Exception):
pass
class AnkiModelExists(Exception):
pass
def invoke(action, **params):
response = requests.get(
f"http://127.0.0.1:{global_port}",
json={"action": action, "params": params, "version": 6},
).json()
if len(response) != 2:
raise AnkiException("response has an unexpected number of fields")
if "error" not in response:
raise AnkiException("response is missing required error field")
if "result" not in response:
raise AnkiException("response is missing required result field")
if response["error"] is not None:
if response["error"] == "Model name already exists":
raise AnkiModelExists()
else:
raise AnkiException(response["error"])
return response["result"]
class Deck:
@staticmethod
def create(name):
invoke("createDeck", deck=name)
return Deck(name)
@staticmethod
def NamesAndIds():
return [Deck(k, v) for k, v in invoke("deckNamesAndIds").items()]
def __init__(self, name, did=None):
self.name = name
self.did = did
def __repr__(self):
return f"(Deck {self.name},{self.did})"
def delete(self):
return invoke("deleteDecks", decks=[self.name], cardsToo=True)
@property
def Config(self):
return invoke("getDeckConfig", deck=self.name)
@Config.setter
def Config(self, config):
return invoke("saveDeckConfig", config=config)
class Card:
def __repr__(self):
return f"(Card {self.cardId})"
def __init__(self, cardId):
self.cardId = cardId
def forget(self):
return invoke("forgetCards", cards=[self.cardId])
def relearn(self):
return invoke("relearnCards", cards=[self.cardId])
def answer(self):
return invoke("answerCards", cards=[self.cardId])
@property
def Info(self):
return invoke("cardsInfo", cards=[self.cardId])[0]
@staticmethod
def find(query="deck:current"):
return [Card(_) for _ in invoke("findCards", query=query)]
@property
def Deck(self):
return Deck(list(invoke("getDecks", cards=[self.cardId]).keys())[0])
class Model:
@staticmethod
def create(modelName, inOrderFields: list, css, isCloze, cardTemplates):
try:
print(
invoke(
"createModel",
modelName=modelName,
inOrderFields=inOrderFields,
css=css,
isCloze=isCloze,
cardTemplates=cardTemplates,
)
)
except AnkiException as e:
if str(e) == "Model name already exists":
pass
else:
raise e
return Model(modelName)
def __init__(self, name):
self.name = name
def updateStyling(self, css):
return invoke("updateModelStyling", model={"name": self.name, "css": css})
def updateTemplates(self, templates):
return invoke(
"updateModelTemplates", model={"name": self.name, "templates": templates}
)
class AnkiConnect:
@staticmethod
def version() -> int:
return invoke("version")
@staticmethod
def createDeck(deck):
return invoke("createDeck", deck=deck)
@staticmethod
def findCards(query="deck:current"):
return Card.find(query)
class Note:
@staticmethod
def add(
deckName,
modelName,
fields: dict,
allowDuplicate: bool,
tags: list,
audio: list,
picture: list,
):
invoke(
"addNote",
note={
"deckName": deckName,
"modelName": modelName,
"fields": fields,
"options": {
"allowDuplicate": allowDuplicate,
"duplicateScope": "deck",
"duplicateScopeOptions": {
"deckName": "Default",
"checkChildren": False,
"checkAllModels": False,
},
},
"tags": tags,
"audio": audio,
"picture": picture,
},
)
if __name__ == "__main__":
print(AnkiConnect.version())
print(AnkiConnect.createDeck("shit2"))
# print(AnkiConnect.findCards()[0].Info['cardId'])
# print(Card(1504404537724).Info)
# print(AnkiConnect.findCards()[0].Deck)
print(Deck.NamesAndIds())
deck = Deck.NamesAndIds()[0]
print(deck)
Deck.create("test1111")
# print(deck.Config)
# deck.Config=deck.Config
# print(deck.delete())
# print(AnkiConnect.findCards()[0].Deck.delete())
model = Model.create(
"newModelName3111",
["expression", "sentence", "audio-text", "image"],
"Optional CSS with default to builtin css",
False,
[{"Name": "My Card 1", "Front": html, "Back": html}],
)
model.updateStyling("shitcss")
model.updateTemplates(
{
"My Card 1": {
"Front": html + "shit",
"Back": html + "shit",
}
}
)
Note.add(
"test1111",
"newModelName3111",
{
"expression": "shit122",
"sentence": "hahaa",
"meaning": "meaning",
"glossary-brief": "ss",
# "audio-text": Media(
# r"C:\dataH\LunaTranslator\cache\tts\1714228732.8068416.mp3"
# ).audio,
},
False,
[],
[
# {
# "path": r"C:\dataH\LunaTranslator\cache\tts\1714228732.8068416.mp3",
# "filename": str(uuid.uuid4()) + "1714228732.8068416.mp3",
# "fields": ["audio-text"],
# }
],
[
# {
# "path": r"C:\Users\11737\Documents\GitHub\LunaTranslator\LunaTranslator\cache\ocr\1709362617.9424458.png",
# "filename": str(uuid.uuid4()) + "1714228732.8068416.png",
# "fields": ["image"],
# }
],
)

View File

@ -0,0 +1,25 @@
DeckName="LunaDeck"
ModelName="LunaModel"
global_port = 8765
model_fileds=["word","explain","image","audio"]
model_css=''
model_htmlfront = """
{{word}}
<br>
{{explain}}
<br>
{{image}}
"""
model_htmlback = """
{{word}}
<br>
{{explain}}
<br>
{{image}}
"""

View File

@ -1,4 +1,5 @@
import gobject, os
from network.requests_common import NetWorkException
from ctypes import (
CDLL,
c_void_p,
@ -316,7 +317,7 @@ class AutoCURLHandle(CURL):
curl_easy_cleanup(self)
class CURLException(Exception):
class CURLException(NetWorkException):
def __init__(self, code) -> None:
if isinstance(code, CURLcode):
self.errorcode = code.value

View File

@ -4,8 +4,10 @@ from collections import OrderedDict
from urllib.parse import urlencode, urlsplit
from functools import partial
class NetWorkException(Exception):
pass
class Timeout(Exception):
class Timeout(NetWorkException):
pass

View File

@ -1,8 +1,8 @@
from ctypes import windll, POINTER, pointer, Structure, sizeof
from ctypes.wintypes import LPCWSTR, DWORD, LPVOID, WORD, BOOL, LPCVOID, LPWSTR, USHORT
from network.requests_common import NetWorkException
class WinhttpException(Exception):
class WinhttpException(NetWorkException):
ERROR_INVALID_PARAMETER = 87
ERROR_INVALID_OPERATION = 4317
WINHTTP_ERROR_BASE = 12000

View File

@ -51,7 +51,15 @@ class TTSbase:
threading.Thread(target=_).start()
def read(self, content, force=False):
def _(content, force):
fname = self.syncttstofile(content)
volume = globalconfig["ttscommon"]["volume"]
if fname:
self.mp3playsignal.emit(fname, volume, force)
threading.Thread(target=_, args=(content, force)).start()
def syncttstofile(self, content):
if self.loadok == False:
return
if len(content) == 0:
@ -60,13 +68,7 @@ class TTSbase:
return
rate = globalconfig["ttscommon"]["rate"]
volume = globalconfig["ttscommon"]["volume"]
voice = globalconfig["reader"][self.typename]["voice"]
voice_index = self.voicelist.index(voice)
def _():
fname = self.speak(content, rate, voice, voice_index)
if fname:
self.mp3playsignal.emit(fname, volume, force)
threading.Thread(target=_).start()
fname = self.speak(content, rate, voice, voice_index)
return fname

View File

@ -1,4 +1,4 @@
import time
import time, os
import winsharedutils
from tts.basettsclass import TTSbase

View File

@ -775,5 +775,7 @@
"半径": "نصف قطر",
"圆角": "فيليه",
"合并": "دمج",
"立即应用": "التطبيق الفوري"
"立即应用": "التطبيق الفوري",
"无法连接到anki": "غير قادر على الاتصال انكي",
"追加": "إلحاق"
}

View File

@ -775,5 +775,7 @@
"半径": "半徑",
"圆角": "圓角",
"合并": "合併",
"立即应用": "立即應用"
"立即应用": "立即應用",
"无法连接到anki": "無法連接到anki",
"追加": "追加"
}

View File

@ -775,5 +775,7 @@
"半径": "radius",
"圆角": "fillet",
"合并": "merge",
"立即应用": "apply now"
"立即应用": "apply now",
"无法连接到anki": "Unable to connect to Anki",
"追加": "Add"
}

View File

@ -775,5 +775,7 @@
"半径": "Radio",
"圆角": "Redondeado",
"合并": "Fusión",
"立即应用": "Aplicar de inmediato"
"立即应用": "Aplicar de inmediato",
"无法连接到anki": "No se puede conectar a Anki",
"追加": "Añadido"
}

View File

@ -775,5 +775,7 @@
"半径": "Rayon",
"圆角": "Coins arrondis",
"合并": "Consolidation",
"立即应用": "Appliquer maintenant"
"立即应用": "Appliquer maintenant",
"无法连接到anki": "Impossible de se connecter à Anki",
"追加": "Supplémentaire"
}

View File

@ -775,5 +775,7 @@
"半径": "raggio",
"圆角": "filetto",
"合并": "fusione",
"立即应用": "Applica ora"
"立即应用": "Applica ora",
"无法连接到anki": "Impossibile connettersi ad Anki",
"追加": "Aggiungi"
}

View File

@ -775,5 +775,7 @@
"半径": "半径はんけい",
"圆角": "フィレット",
"合并": "マージ",
"立即应用": "今すぐ適用"
"立即应用": "今すぐ適用",
"无法连接到anki": "ankiに接続できません",
"追加": "追加#ツイカ#"
}

View File

@ -775,5 +775,7 @@
"半径": "반지름",
"圆角": "필렛",
"合并": "병합",
"立即应用": "지금 적용"
"立即应用": "지금 적용",
"无法连接到anki": "anki에 연결할 수 없음",
"追加": "추가"
}

View File

@ -775,5 +775,7 @@
"半径": "promień",
"圆角": "filet",
"合并": "połączenie",
"立即应用": "aplikuj teraz"
"立即应用": "aplikuj teraz",
"无法连接到anki": "Nie można połączyć się z Anki",
"追加": "Dodaj"
}

View File

@ -775,5 +775,7 @@
"半径": "Радиус",
"圆角": "Круглый угол",
"合并": "Объединение",
"立即应用": "Немедленное применение"
"立即应用": "Немедленное применение",
"无法连接到anki": "Не удалось подключиться к ANKI",
"追加": "Добавить"
}

View File

@ -775,5 +775,7 @@
"半径": "รัศมี",
"圆角": "มุมกลม",
"合并": "การควบรวมกิจการ",
"立即应用": "สมัครตอนนี้"
"立即应用": "สมัครตอนนี้",
"无法连接到anki": "ไม่สามารถเชื่อมต่อกับ anki ได้",
"追加": "เพิ่ม"
}

View File

@ -775,5 +775,7 @@
"半径": "radius",
"圆角": "fillet",
"合并": "birleştir",
"立即应用": "şimdi uygulayın"
"立即应用": "şimdi uygulayın",
"无法连接到anki": "Anki ile bağlanılamadı",
"追加": "Ekle"
}

View File

@ -775,5 +775,7 @@
"半径": "радіус",
"圆角": "філет",
"合并": "об єднати",
"立即应用": "застосовувати зараз"
"立即应用": "застосовувати зараз",
"无法连接到anki": "Неможливо з' єднатися з Anki",
"追加": "Додати"
}

View File

@ -775,5 +775,7 @@
"半径": "Bán kính",
"圆角": "Góc tròn",
"合并": "Hợp nhất",
"立即应用": "Áp dụng ngay"
"立即应用": "Áp dụng ngay",
"无法连接到anki": "Không thể kết nối với Anki",
"追加": "Thêm"
}

View File

@ -775,5 +775,7 @@
"半径": "",
"圆角": "",
"合并": "",
"立即应用": ""
"立即应用": "",
"追加": "",
"无法连接到anki": ""
}

View File

@ -29,7 +29,7 @@ include(generate_product_version)
set(VERSION_MAJOR 2)
set(VERSION_MINOR 50)
set(VERSION_PATCH 2)
set(VERSION_PATCH 3)
add_library(pch pch.cpp)
target_precompile_headers(pch PUBLIC pch.h)