DazedTL/modules/csvtl.py

195 lines
No EOL
6 KiB
Python

from concurrent.futures import ThreadPoolExecutor, as_completed
import os
from pathlib import Path
import re
import sys
import textwrap
import threading
import time
import traceback
import tiktoken
import csv
from colorama import Fore
from dotenv import load_dotenv
import openai
from retry import retry
from tqdm import tqdm
#Globals
load_dotenv()
openai.organization = os.getenv('org')
openai.api_key = os.getenv('key')
APICOST = .002 # Depends on the model https://openai.com/pricing
PROMPT = Path('prompt.txt').read_text(encoding='utf-8')
THREADS = 20
LOCK = threading.Lock()
WIDTH = 60
MAXHISTORY = 10
ESTIMATE = ''
TOTALCOST = 0
TOKENS = 0
TOTALTOKENS = 0
#tqdm Globals
BAR_FORMAT='{l_bar}{bar:10}{r_bar}{bar:-10b}'
POSITION=0
LEAVE=False
def handleCSV(filename, estimate):
global ESTIMATE, TOKENS, TOTALTOKENS, TOTALCOST
ESTIMATE = estimate
with open('translated/' + filename, 'w+t', newline='', encoding='UTF-8') as writeFile:
start = time.time()
translatedData = openFiles(filename, writeFile)
if estimate:
# Print Result
end = time.time()
tqdm.write(getResultString(['', TOKENS, None], end - start, filename))
TOTALCOST += TOKENS * .001 * APICOST
TOTALTOKENS += TOKENS
TOKENS = 0
os.remove('translated/' + filename)
else:
# Print Result
end = time.time()
tqdm.write(getResultString(translatedData, end - start, filename))
TOTALCOST += translatedData[1] * .001 * APICOST
TOTALTOKENS += translatedData[1]
return getResultString(['', TOTALTOKENS, None], end - start, 'TOTAL')
def openFiles(filename, writeFile):
with open('files/' + filename, 'r', encoding='UTF-8') as readFile, writeFile:
translatedData = parseCSV(readFile, writeFile, filename)
return translatedData
def getResultString(translatedData, translationTime, filename):
# File Print String
tokenString = Fore.YELLOW + '[' + str(translatedData[1]) + \
' Tokens/${:,.4f}'.format(translatedData[1] * .001 * APICOST) + ']'
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
if translatedData[2] == None:
# Success
return filename + ': ' + tokenString + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
else:
# Fail
try:
raise translatedData[2]
except Exception as e:
errorString = str(e) + '|' + translatedData[3] + Fore.RED
return filename + ': ' + tokenString + timeString + Fore.RED + u' \u2717 ' +\
errorString + Fore.RESET
def parseCSV(readFile, writeFile, filename):
totalTokens = 0
totalLines = 0
textHistory = []
global LOCK
# Get total for progress bar
totalLines = len(readFile.readlines())
readFile.seek(0)
reader = csv.reader(readFile, delimiter=',')
writer = csv.writer(writeFile, delimiter=',', quoting=csv.QUOTE_NONNUMERIC)
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
pbar.desc=filename
pbar.total=totalLines
for row in reader:
try:
totalTokens += translateCSV(row, pbar, writer, textHistory)
except Exception as e:
tracebackLineNo = str(traceback.extract_tb(sys.exc_info()[2])[-1].lineno)
return [reader, totalTokens, e, tracebackLineNo]
return [reader, totalTokens, None]
def translateCSV(row, pbar, writer, textHistory):
translatedText = ''
maxHistory = MAXHISTORY
tokens = 0
global LOCK, ESTIMATE
try:
jaString = row[0]
# Remove repeating characters because it confuses ChatGPT
jaString = re.sub(r'([\u3000-\uffef])\1{2,}', r'\1\1', jaString)
# Sub Vars
jaString = re.sub(r'(\\+[a-zA-Z]+)\[([a-zA-Z0-9]+)\]', r'[\1|\2]', jaString)
# Translate
response = translateGPT(jaString, 'Previous text for context: ' + ' '.join(textHistory))
# Check if there is an actual difference first
if response[0] != row[0]:
translatedText = response[0]
else:
translatedText = row[1]
tokens += response[1]
# ReSub Vars
translatedText = re.sub(r'\[([\\a-zA-Z]+)\|([a-zA-Z0-9]+)]', r'\1[\2]', translatedText)
translatedText = re.sub('"', "'", translatedText)
# TextHistory is what we use to give GPT Context, so thats appended here.
textHistory.append(': ' + translatedText)
# Textwrap
translatedText = textwrap.fill(translatedText, width=WIDTH)
# Set Data
row[1] = translatedText
# Keep textHistory list at length maxHistory
with LOCK:
if len(textHistory) > maxHistory:
textHistory.pop(0)
if not ESTIMATE:
writer.writerow(row)
pbar.update(1)
except Exception as e:
tracebackLineNo = str(traceback.extract_tb(sys.exc_info()[2])[-1].lineno)
raise Exception(str(e) + '|Line:' + tracebackLineNo + '| Failed to translate: ' + jaString)
return tokens
@retry(exceptions=Exception, tries=5, delay=5)
def translateGPT(t, history):
with LOCK:
# If ESTIMATE is True just count this as an execution and return.
if ESTIMATE:
global TOKENS
enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
TOKENS += len(enc.encode(t)) * 2 + len(enc.encode(history)) + len(enc.encode(PROMPT))
return (t, 0)
# If there isn't any Japanese in the text just skip
if not re.search(r'[一-龠]+|[ぁ-ゔ]+|[ァ-ヴ]+', t):
return(t, 0)
"""Translate text using GPT"""
system = PROMPT + history
response = openai.ChatCompletion.create(
temperature=0,
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": t}
],
request_timeout=30,
)
return [response.choices[0].message.content, response.usage.total_tokens]