|
|
|
|
|
|
|
|
|
import re |
|
import threading |
|
import shutil |
|
import pyautogui |
|
import pyperclip |
|
import time |
|
import deepl |
|
import pysrt |
|
from googletrans import Translator |
|
from pysubs2 import SSAFile |
|
from pysubs2 import SSAFile |
|
from pyasstosrt import Subtitle |
|
import subprocess |
|
import os |
|
from termcolor import cprint |
|
import json |
|
import contextlib |
|
import asyncio |
|
import wave |
|
import edge_tts |
|
import pyttsx3 |
|
from pydub import AudioSegment |
|
import webbrowser |
|
from revChatGPT.V1 import Chatbot |
|
|
|
|
|
def execute_command(command): |
|
subprocess.call(command, shell=True) |
|
|
|
|
|
def directory_path(): |
|
return os.path.dirname(os.path.realpath(__file__)) |
|
|
|
|
|
def set_option(prompt, options, description=None): |
|
cprint(prompt, 'yellow', attrs=['bold']) |
|
for key, value in options.items(): |
|
if description and key in description: |
|
print(f"Wartość domyślna: {value:5} {description[key]:5}") |
|
else: |
|
print(f"{key}. {value}") |
|
cprint("\nWpisz swój wybór lub poprawną wartość:", |
|
'green', attrs=['bold'], end=" ") |
|
return input() |
|
|
|
|
|
def is_valid_speed(speed, voice_choice): |
|
if voice_choice == '1': |
|
return int(speed) > 0 |
|
elif voice_choice == '2': |
|
return -10 <= int(speed) <= 10 |
|
elif voice_choice in ['3', '4']: |
|
return speed.startswith(('+', '-')) and speed[1:-1].isdigit() and -100 <= int(speed[1:-1]) <= 100 and speed.endswith('%') |
|
return False |
|
|
|
|
|
def is_valid_volume(volume, voice_choice): |
|
if voice_choice == '1': |
|
return 0 <= float(volume) <= 1 |
|
elif voice_choice == '2': |
|
return -100 <= int(volume) <= 100 |
|
elif voice_choice in ['3', '4']: |
|
return volume.startswith(('+', '-')) and volume[1:-1].isdigit() and -100 <= int(volume[1:-1]) <= 100 and volume.endswith('%') |
|
return False |
|
|
|
|
|
def set_settings(dir_path, settings): |
|
translator_options = { |
|
'1': 'Google Translate', |
|
'2': 'DeepL API', |
|
'3': 'DeepL Desktop', |
|
'4': 'ChatGPT 3.5 Free' |
|
} |
|
|
|
translated_line_count_options = { |
|
'1': '30', |
|
'2': '50', |
|
'3': '75', |
|
'4': '100', |
|
} |
|
|
|
alt_main_translator_options = { |
|
'1': 'yes', |
|
'2': 'no' |
|
} |
|
|
|
tts_voice_options = { |
|
'1': { |
|
'name': 'TTS - Zosia - Harpo', |
|
'speed_default': '200', |
|
'volume_default': '0.7' |
|
}, |
|
'2': { |
|
'name': 'TTS - Agnieszka - Ivona', |
|
'speed_default': '5', |
|
'volume_default': '65' |
|
}, |
|
'3': { |
|
'name': 'TTS - Zofia - Edge', |
|
'speed_default': '+40%', |
|
'volume_default': '+0%' |
|
}, |
|
'4': { |
|
'name': 'TTS - Marek - Edge', |
|
'speed_default': '+40%', |
|
'volume_default': '+0%' |
|
} |
|
} |
|
|
|
output_options = { |
|
'1': 'Oglądam w MM_AVH_Players', |
|
'2': 'Scal do mkv', |
|
'3': 'Wypal do mp4', |
|
} |
|
|
|
cprint("╔═════════════════ Ustawienia ═════════════════╗\n", |
|
'white', attrs=['bold']) |
|
translator_choice = set_option( |
|
"Wybierz translatora:", translator_options |
|
) |
|
|
|
deepl_api_key = '' |
|
cprint("\nCzy chcesz ustawić klucz API DeepL? (t / y = tak):", |
|
'yellow', attrs=['bold'], end=" ") |
|
change_settings = input("") |
|
if change_settings.lower() in ['t', 'y', 'tak', 'yes']: |
|
cprint('Wpisz klucz API DeepL:', 'green', attrs=['bold'], end=' ') |
|
deepl_api_key = input() |
|
if deepl_api_key == '': |
|
cprint("Pominięto.", 'red', attrs=['bold']) |
|
if settings['deepl_api_key']: |
|
deepl_api_key = settings['deepl_api_key'] |
|
else: |
|
cprint("Pominięto.", 'red', attrs=['bold']) |
|
if settings['deepl_api_key']: |
|
deepl_api_key = settings['deepl_api_key'] |
|
|
|
access_token = '' |
|
cprint("\nCzy chcesz ustawić token dostępu do ChatGPT? (t / y = tak):", |
|
'yellow', attrs=['bold'], end=" ") |
|
change_settings = input() |
|
if change_settings.lower() in ['t', 'y', 'tak', 'yes']: |
|
url = "https://chat.openai.com/api/auth/session" |
|
webbrowser.open(url) |
|
cprint('Wpisz token dostępu do ChatGPT:', |
|
'green', attrs=['bold'], end=' ') |
|
access_token = input() |
|
if access_token == '': |
|
cprint("Pominięto.", 'red', attrs=['bold']) |
|
if settings['access_token']: |
|
access_token = settings['access_token'] |
|
else: |
|
cprint("Pominięto.", 'red', attrs=['bold']) |
|
if settings['access_token']: |
|
access_token = settings['access_token'] |
|
|
|
translated_line_count_choice = set_option( |
|
"\nWybierz ilość tłumaczonych linii na raz:", translated_line_count_options |
|
) |
|
alt_main_translator_choice = set_option( |
|
"\nCzy tłumaczyć rozdzielone napisy?", alt_main_translator_options |
|
) |
|
tts_choice = None |
|
while tts_choice is None: |
|
tts_choice = set_option( |
|
"\nWybierz głos lektora:", tts_voice_options |
|
) |
|
if tts_choice not in tts_voice_options: |
|
tts_choice = '2' |
|
|
|
tts_speed_default = tts_voice_options.get(tts_choice).get('speed_default') |
|
|
|
cprint("\nObesługiwane zakresy szybkości:", |
|
'yellow', attrs=['bold']) |
|
print("TTS - Zosia - Harpo - szybkość głosu od 0 do ... (słowa na minute), domyślna: 200)") |
|
print("TTS - Agnieszka - Ivona - szybkość głosu od -10 do 10 (domyślna: 5)") |
|
print("TTS - Zofia - Edge - szybkość głosu (+/-) od -100% do +100%, (domyślna: +40%)") |
|
print("TTS - Marek - Edge - szybkość głosu (+/-) od -100% do +100%, (domyślna: +40%)") |
|
|
|
cprint(f"\nWpisz szybkość głosu (domyślna: {tts_speed_default}):", 'green', attrs=[ |
|
'bold'], end=" ") |
|
tts_speed_choice = input('') |
|
try: |
|
tts_speed = ( |
|
tts_speed_choice if is_valid_speed(tts_speed_choice, tts_choice) and tts_speed_choice.strip() != '' |
|
else tts_speed_default |
|
) |
|
except Exception: |
|
tts_speed = tts_speed_default |
|
cprint("\nObesługiwane zakresy głośności:", 'yellow', attrs=['bold']) |
|
print("TTS - Zosia - Harpo - głośność głosu od 0 do 1 (domyślna: 0.7)") |
|
print("TTS - Agnieszka - Ivona - głośność głosu od 0 do 100 (domyślna: 65)") |
|
print("TTS - Zofia - Edge - głośność głosu (+/-) od -100% do +100%, (domyślna: +0%)") |
|
print("TTS - Marek - Edge - głośność głosu (+/-) od -100% do +100%, (domyślna: +0%)") |
|
tts_volume_default = tts_voice_options.get( |
|
tts_choice).get('volume_default') |
|
cprint(f"\nWpisz głośność głosu (domyślna: {tts_volume_default}):", 'green', attrs=[ |
|
'bold'], end=" ") |
|
tts_volume_choice = input('') |
|
try: |
|
tts_volume = ( |
|
tts_volume_choice if is_valid_volume(tts_volume_choice, tts_choice) and tts_volume_choice.strip() != '' |
|
else tts_volume_default |
|
) |
|
except Exception: |
|
tts_volume = tts_volume_default |
|
|
|
output_options_choice = set_option( |
|
"\nWybierz sposób wyjścia:", output_options |
|
) |
|
|
|
settings_data = { |
|
'translator': translator_options.get(translator_choice, 'Google Translate'), |
|
|
|
'deepl_api_key': deepl_api_key, |
|
'access_token': access_token, |
|
'translated_line_count': translated_line_count_options.get(translated_line_count_choice, '50'), |
|
'alt_main_translator': alt_main_translator_options.get(alt_main_translator_choice, 'no'), |
|
'tts': tts_voice_options.get(tts_choice).get('name', 'TTS - Agnieszka - Ivona'), |
|
'tts_speed': tts_speed, |
|
'tts_volume': tts_volume, |
|
'output': output_options.get(output_options_choice, 'Oglądam w MM_AVH_Players') |
|
} |
|
|
|
with open(os.path.join(dir_path, 'src', 'settings.json'), 'w') as settings_file: |
|
json.dump(settings_data, settings_file, indent=4) |
|
|
|
cprint("Ustawienia zostały zapisane.\n", 'green', attrs=['bold']) |
|
|
|
|
|
def get_settings(dir_path): |
|
if os.path.isfile(os.path.join(dir_path, 'src', 'settings.json')): |
|
with open(os.path.join(dir_path, 'src', 'settings.json'), 'r') as settings_file: |
|
settings_data = json.load(settings_file) |
|
return settings_data |
|
|
|
|
|
def mkv_info(dir_path, file): |
|
command = [ |
|
dir_path + '\\src\\mkvtoolnix\\mkvmerge.exe', |
|
'--ui-language', |
|
'en', |
|
'--identify', |
|
'--identification-format', |
|
'json', |
|
dir_path + '\\' + file |
|
] |
|
|
|
process = subprocess.Popen( |
|
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) |
|
output, error = process.communicate() |
|
|
|
if process.returncode == 0: |
|
return mkv_info_print(output, file) |
|
else: |
|
print(f'Error: {error}') |
|
|
|
|
|
def mkv_info_print(output, file): |
|
data = json.loads(output) |
|
tracks_data = [] |
|
|
|
for track in data['tracks']: |
|
track_data = { |
|
'id': track['id'], |
|
'type': track['type'], |
|
'codec_id': track['properties']['codec_id'], |
|
'language': track['properties']['language'], |
|
'language_ietf': track['properties']['language_ietf'], |
|
'properties': None |
|
} |
|
|
|
if 'display_dimensions' in track['properties']: |
|
track_data['properties'] = track['properties']['display_dimensions'] |
|
elif 'audio_sampling_frequency' in track['properties']: |
|
track_data['properties'] = f"{track['properties']['audio_sampling_frequency']} Hz" |
|
|
|
tracks_data.append(track_data) |
|
|
|
cprint('WYODRĘBNIANIE Z PLIKU:', 'red', attrs=['bold']) |
|
cprint(file, 'white', attrs=['bold']) |
|
cprint('ID TYPE CODEK LANG LANG_IETF PROPERTIES', |
|
'yellow', attrs=['bold']) |
|
|
|
sorted_tracks = sorted(tracks_data, key=lambda x: x['id']) |
|
for track in sorted_tracks: |
|
print(f'{track["id"]:2} {track["type"]:10} {track["codec_id"]:20} {track["language"]:5} {track["language_ietf"]:10} {track["properties"]}') |
|
|
|
print('') |
|
return data |
|
|
|
|
|
def mkv_extract(dir_path, file, data): |
|
tracks_properties = [] |
|
while True: |
|
try: |
|
cprint('Podaj ID ścieżki do wyciągnięcia (naciśnij ENTER, aby zakończyć): ', |
|
'green', attrs=['bold'], end='') |
|
track_id = int( |
|
input('')) |
|
tracks_properties.append(track_id) |
|
except ValueError: |
|
cprint('Pominięto wyciąganie ścieżki.\n', 'red', attrs=['bold']) |
|
break |
|
try: |
|
for track_id in tracks_properties: |
|
track = data['tracks'][track_id] |
|
codec_id = track["properties"]["codec_id"] |
|
filename = f'{file[:-4]}.{codec_id.rsplit("_", 1)[-1].rsplit("/", 1)[-1].lower()}' |
|
out_file = os.path.join(dir_path, 'src', 'tmp', filename) |
|
command = [ |
|
os.path.join(dir_path, 'src', 'mkvtoolnix', 'mkvextract.exe'), |
|
'tracks', |
|
os.path.join(dir_path, file), |
|
f'{track_id}:{out_file}' |
|
] |
|
process = subprocess.Popen(command) |
|
cprint( |
|
f'Ekstrakcja ścieżki {track_id} do pliku {filename}', 'green', attrs=['bold']) |
|
process.communicate() |
|
print('') |
|
except IndexError: |
|
cprint('Znaleziono nieprawidłowe ID ścieżki!', 'red', attrs=['bold']) |
|
mkv_extract(dir_path, file, data) |
|
print('') |
|
|
|
|
|
def split_ass(dir_path, input_file): |
|
main_subs_folder = os.path.join(dir_path, 'main_subs') |
|
alt_subs_folder = os.path.join(dir_path, 'alt_subs') |
|
|
|
with open(os.path.join(dir_path, input_file), 'r', encoding='utf-8') as file: |
|
subs = SSAFile.from_file(file) |
|
|
|
|
|
styles = [] |
|
for event in subs: |
|
if event.style not in styles: |
|
styles.append(event.style) |
|
|
|
cprint("PODZIAŁ PLIKU: ", 'red', attrs=['bold']) |
|
cprint(input_file, 'white', attrs=['bold']) |
|
|
|
cprint("Dostępne style do TTS:", 'yellow', attrs=['bold']) |
|
for i, style in enumerate(styles, start=1): |
|
print(f"{i}. {style}") |
|
|
|
print('') |
|
selected_styles = [] |
|
while True: |
|
cprint("Wybierz style do zapisu (naciśnij ENTER, aby zakończyć): ", |
|
'green', attrs=['bold'], end='') |
|
selection = input("") |
|
if not selection: |
|
break |
|
selected_styles.append(styles[int(selection) - 1]) |
|
|
|
if not selected_styles: |
|
cprint("Nie wybrano żadnych stylów. Podział napisów nie został wykonany.\n", |
|
'red', attrs=['bold']) |
|
return |
|
|
|
main_subs = SSAFile() |
|
alt_subs = SSAFile() |
|
|
|
for event in subs: |
|
if event.style in selected_styles: |
|
main_subs.append(event) |
|
else: |
|
alt_subs.append(event) |
|
|
|
main_output_file = os.path.join(main_subs_folder, input_file) |
|
alt_output_file = os.path.join(alt_subs_folder, input_file) |
|
|
|
|
|
main_subs.info = subs.info |
|
alt_subs.info = subs.info |
|
|
|
|
|
main_style_names = [style_name for style_name in subs.styles.keys() |
|
if style_name in selected_styles] |
|
main_subs.styles.clear() |
|
for style_name in main_style_names: |
|
main_subs.styles[style_name] = subs.styles[style_name] |
|
|
|
alt_style_names = [style_name for style_name in subs.styles.keys() |
|
if style_name not in selected_styles] |
|
alt_subs.styles.clear() |
|
for style_name in alt_style_names: |
|
alt_subs.styles[style_name] = subs.styles[style_name] |
|
|
|
|
|
with open(main_output_file, 'w', encoding='utf-8') as main_file: |
|
main_file.write(main_subs.to_string(format_='ass')) |
|
|
|
with open(alt_output_file, 'w', encoding='utf-8') as alt_file: |
|
alt_file.write(alt_subs.to_string(format_='ass')) |
|
|
|
cprint("Podział napisów został zakończony.", 'yellow', attrs=['bold']) |
|
|
|
os.remove(os.path.join(dir_path, input_file)) |
|
cprint("Usunięto plik źródłowy.\n", 'yellow', attrs=['bold']) |
|
|
|
|
|
def ass_to_srt(dir_path, file): |
|
sub = Subtitle(os.path.join(dir_path, file)) |
|
sub.export() |
|
cprint("Zamieniono na srt:", 'yellow', attrs=['bold'], end=" ") |
|
print(file) |
|
|
|
|
|
|
|
|
|
|
|
def asnii_srt(dir_path, file): |
|
with open(os.path.join(dir_path, file), "r", encoding="utf-8") as source_file: |
|
content = source_file.read() |
|
|
|
try: |
|
with open(os.path.join(dir_path, file), "w", encoding="ANSI") as target_file: |
|
target_file.write(content) |
|
except UnicodeEncodeError: |
|
with open(os.path.join(dir_path, file), "w", encoding="ANSI", errors="ignore") as target_file: |
|
target_file.write(content) |
|
|
|
|
|
cprint("Zamieniono kodowanie na ANSI:", 'yellow', attrs=['bold'], end=' ') |
|
print(file) |
|
|
|
|
|
def translate_google(dir_path, file, translated_line_count): |
|
subs = pysrt.open(os.path.join(dir_path, file), encoding='utf-8') |
|
subs_combined = [] |
|
translated_subs = [] |
|
|
|
translator = Translator() |
|
for i, sub in enumerate(subs): |
|
sub.text = sub.text.replace("\n", " ◍ ") |
|
subs_combined.append(sub.text) |
|
|
|
if (i + 1) % translated_line_count == 0 or i == len(subs) - 1: |
|
combined_text = "\n".join(subs_combined) |
|
translated_text = translator.translate( |
|
combined_text, dest='pl').text |
|
translated_subs += translated_text.split("\n") |
|
subs_combined = [] |
|
|
|
for i, sub in enumerate(subs): |
|
sub.text = translated_subs[i] |
|
sub.text = sub.text.replace(" ◍, ", ",\n") |
|
sub.text = sub.text.replace(" ◍ ", "\n") |
|
sub.text = sub.text.replace(" ◍", "") |
|
|
|
subs.save(os.path.join(dir_path, file)) |
|
|
|
|
|
def translate_deepl_api(dir_path, file, translated_line_count, settings): |
|
subs = pysrt.open(os.path.join(dir_path, file), encoding='utf-8') |
|
|
|
|
|
auth_key = settings['deepl_api_key'] |
|
translator = deepl.Translator(auth_key) |
|
groups = [subs[i:i+translated_line_count] |
|
for i in range(0, len(subs), translated_line_count)] |
|
for group in groups: |
|
text = " @\n".join(sub.text.replace("\n", " ◍◍◍◍ ") |
|
for sub in group) |
|
translated_text = translator.translate_text( |
|
text, target_lang='PL').text |
|
translated_texts = translated_text.split(" @\n") |
|
if len(translated_texts) == len(group): |
|
for i in range(len(group)): |
|
if i < len(translated_texts): |
|
group[i].text = translated_texts[i] |
|
group[i].text = group[i].text.replace(" ◍◍◍◍, ", ",\n") |
|
group[i].text = group[i].text.replace(" ◍◍◍◍ ", "\n") |
|
group[i].text = group[i].text.replace(" ◍◍◍◍", "") |
|
subs.save(os.path.join(dir_path, file), encoding='utf-8') |
|
|
|
|
|
def translate_deepl_desktop(dir_path, file, translated_line_count): |
|
|
|
|
|
|
|
command = r'C:\Users\mateu\AppData\Roaming\Programs\Zero Install\0install-win.exe' |
|
args = ["run", "--no-wait", |
|
"https://appdownload.deepl.com/windows/0install/deepl.xml"] |
|
subprocess.call([command] + args) |
|
|
|
time.sleep(5) |
|
|
|
def auto_steps(): |
|
screen_width, screen_height = pyautogui.size() |
|
x = screen_width * 0.25 |
|
y = screen_height * 0.5 |
|
pyautogui.moveTo(x, y) |
|
pyautogui.click() |
|
pyautogui.hotkey('ctrl', 'a') |
|
pyautogui.hotkey('del') |
|
pyautogui.hotkey('ctrl', 'v') |
|
time.sleep(6) |
|
x = screen_width * 0.75 |
|
pyautogui.moveTo(x, y) |
|
pyautogui.click() |
|
pyautogui.hotkey('ctrl', 'a') |
|
pyautogui.hotkey('ctrl', 'c') |
|
|
|
subs = pysrt.open(os.path.join(dir_path, file), encoding='utf-8') |
|
groups = [subs[i:i+translated_line_count] |
|
for i in range(0, len(subs), translated_line_count)] |
|
|
|
for group in groups: |
|
text = " @\n".join(sub.text.replace("\n", " ◍◍◍◍ ") |
|
for sub in group) |
|
text = text.rstrip('\n') |
|
pyperclip.copy(text) |
|
auto_steps() |
|
|
|
translated_text = pyperclip.paste() |
|
if translated_text: |
|
for sub, trans_text in zip(group, translated_text.split(" @\n")): |
|
sub.text = trans_text.replace(" ◍◍◍◍, ", ",\n") |
|
sub.text = sub.text.replace(" ◍◍◍◍ ", "\n") |
|
sub.text = sub.text.replace(" ◍◍◍◍", "") |
|
|
|
subs.save(os.path.join(dir_path, file), encoding='utf-8') |
|
|
|
frezes = ["\nPrzetłumaczono z www.DeepL.com/Translator (wersja darmowa)\n", |
|
"Przetłumaczono z www.DeepL.com/Translator (wersja darmowa)", |
|
"\nTranslated with www.DeepL.com/Translator (free version)\n", |
|
"\nTranslated with www.DeepL.com/Translator (free version)"] |
|
|
|
with open(os.path.join(dir_path, file), 'r', encoding='utf-8') as in_file: |
|
text = in_file.read() |
|
|
|
for freze in frezes: |
|
text = text.replace(freze, "") |
|
|
|
with open(os.path.join(dir_path, file), 'w', encoding='utf-8') as out_file: |
|
out_file.write(text) |
|
|
|
|
|
def translate_chatgpt_free(dir_path, file, translated_line_count, settings): |
|
|
|
subs = pysrt.open(os.path.join(dir_path, file), encoding='utf-8') |
|
subs_combined = [] |
|
translated_subs = [] |
|
|
|
for i, sub in enumerate(subs): |
|
sub.text = sub.text.replace("\n", " ◍ ") |
|
subs_combined.append(sub.text) |
|
|
|
if (i + 1) % translated_line_count == 0 or i == len(subs) - 1: |
|
combined_text = "\n".join(subs_combined) |
|
translated_text = ask_chatgpt(combined_text, settings) |
|
translated_subs += translated_text.split("\n") |
|
subs_combined = [] |
|
|
|
for i, sub in enumerate(subs): |
|
sub.text = translated_subs[i] |
|
sub.text = sub.text.replace(" ◍, ", ",\n") |
|
sub.text = sub.text.replace(" ◍ ", "\n") |
|
sub.text = sub.text.replace(" ◍", "") |
|
|
|
subs.save(os.path.join(dir_path, file)) |
|
|
|
|
|
def ask_chatgpt(prompt, settings): |
|
chatbot = Chatbot(config={"access_token": settings['access_token']}) |
|
translate_prompt = """I want you to act as an Polish translator, spelling corrector and improver. I will speak to you in any language and you will detect the language, translate it and answer in the corrected and improved version of my text, in Polish. I want you to replace my simplified C2-level words and sentences with more beautiful and elegant, upper level English words and sentences. Keep the meaning same, but make them more literary. I want you to only reply the correction, the improvements and nothing else, do not write explanations. Don't repeat yourself, diversity matters! Translate anime subtitles: |
|
|
|
""" |
|
response = "" |
|
for message in chatbot.ask(translate_prompt + prompt): |
|
response = message["message"] |
|
print(response) |
|
return response |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def translate_srt(dir_path, file, settings): |
|
translator = settings['translator'] |
|
translated_line_count = int(settings['translated_line_count']) |
|
alt_main_translator = settings['alt_main_translator'] |
|
|
|
if alt_main_translator == 'no' and (dir_path.endswith('main_subs') or dir_path.endswith('alt_subs')): |
|
cprint("\nUwaga!", 'yellow', attrs=['bold']) |
|
print( |
|
f"Tłumaczenie folderów wyłączone.\nZakładam, że", end=' ') |
|
cprint(f"{file}", 'yellow', attrs=['bold'], end=' ') |
|
print("jest już przetłumaczony na polski.") |
|
return |
|
|
|
if translator == 'Google Translate': |
|
cprint("\nTłumaczenie za pomocą Google Translate... :", |
|
'yellow', attrs=['bold'], end=' ') |
|
print(file) |
|
translate_google(dir_path, file, translated_line_count) |
|
elif translator == 'DeepL API': |
|
cprint("\nTłumaczenie za pomocą DeepL API... :", |
|
'yellow', attrs=['bold'], end=' ') |
|
print(file) |
|
translate_deepl_api(dir_path, file, translated_line_count) |
|
elif translator == 'DeepL Desktop': |
|
cprint("\nTłumaczenie za pomocą DeepL Desktop... :", |
|
'yellow', attrs=['bold'], end=' ') |
|
print(file) |
|
translate_deepl_desktop(dir_path, file, translated_line_count) |
|
time.sleep(1) |
|
pyautogui.hotkey('alt', 'f4') |
|
elif translator == 'ChatGPT 3.5 Free': |
|
cprint("\nTłumaczenie za pomocą ChatGPT 3.5 Free... :", |
|
'yellow', attrs=['bold'], end=' ') |
|
print(file) |
|
translate_chatgpt_free(dir_path, file, translated_line_count, settings) |
|
|
|
cprint("Tłumaczenie zakończone.", 'green', attrs=['bold']) |
|
|
|
|
|
def srt_to_wav_harpo(self, tts_speed: str, tts_volume: str) -> None: |
|
self.ansi_srt() |
|
|
|
engine = pyttsx3.init() |
|
voices = engine.getProperty('voices') |
|
for voice in voices: |
|
if voice.name == 'Vocalizer Expressive Zosia Harpo 22kHz': |
|
engine.setProperty('voice', voice.id) |
|
engine.setProperty('rate', int(tts_speed)) |
|
engine.setProperty('volume', float(tts_volume)) |
|
|
|
subtitles = pysrt.open(os.path.join( |
|
self.working_space_temp_main_subs, self.filename), encoding='ANSI') |
|
|
|
|
|
output_file = os.path.splitext(os.path.join( |
|
self.working_space_temp, self.filename))[0] + '.wav' |
|
with wave.open(output_file, 'wb') as wav_file: |
|
wav_file.setnchannels(1) |
|
wav_file.setsampwidth(2) |
|
wav_file.setframerate(22500) |
|
|
|
for i, subtitle in enumerate(subtitles, start=1): |
|
print( |
|
f"{i}\n{subtitle.start.to_time().strftime('%H:%M:%S.%f')[:-3]} --> {subtitle.end.to_time().strftime('%H:%M:%S.%f')[:-3]}\n{subtitle.text}\n") |
|
|
|
start_time = subtitle.start.to_time() |
|
start_time = start_time.hour * 3600 + start_time.minute * \ |
|
60 + start_time.second + start_time.microsecond / 1000000 |
|
|
|
engine.save_to_file(subtitle.text, os.path.join( |
|
self.working_space_temp, "temp.wav")) |
|
engine.runAndWait() |
|
|
|
|
|
framerate = wav_file.getframerate() |
|
nframes = wav_file.getnframes() |
|
current_time = nframes / float(framerate) |
|
if start_time > current_time: |
|
empty_frame_duration = int( |
|
(start_time - current_time) * framerate) |
|
empty_frame = b'\x00' * empty_frame_duration * 2 |
|
wav_file.writeframes(empty_frame) |
|
|
|
|
|
with wave.open(os.path.join(self.working_space_temp, "temp.wav"), 'rb') as temp_file: |
|
data = temp_file.readframes(temp_file.getnframes()) |
|
wav_file.writeframes(data) |
|
|
|
|
|
os.remove(os.path.join(self.working_space_temp, "temp.wav")) |
|
|
|
|
|
def process_subtitle(subtitle): |
|
i = subtitle.index |
|
start_time = subtitle.start.to_time().strftime('%H:%M:%S.%f')[:-3] |
|
end_time = subtitle.end.to_time().strftime('%H:%M:%S.%f')[:-3] |
|
text = subtitle.text |
|
print(f"{i}\n{start_time} --> {end_time}\n{text}\n") |
|
time.sleep(0.02) |
|
|
|
|
|
def srt_to_wav_balabolka(dir_path, tts_path, file, tts_speed, tts_volume): |
|
file_path = os.path.join(tts_path, file) |
|
with contextlib.suppress(UnicodeDecodeError): |
|
subtitles = pysrt.open(file_path, encoding='ANSI') |
|
balcon_path = os.path.join(dir_path, "src", "balabolka", "balcon.exe") |
|
output_wav_path = os.path.join( |
|
tts_path, os.path.splitext(file)[0] + ".wav") |
|
command = f'"{balcon_path}" -fr 48 -f "{file_path}" -w "{output_wav_path}" -n "IVONA 2 Agnieszka" -s {tts_speed} -v {tts_volume}' |
|
|
|
|
|
command_thread = threading.Thread( |
|
target=execute_command, args=(command,)) |
|
|
|
|
|
command_thread.start() |
|
|
|
|
|
for i, subtitle in enumerate(subtitles, start=1): |
|
process_subtitle(subtitle) |
|
|
|
|
|
command_thread.join() |
|
|
|
|
|
async def generate_speech(subtitle, voice, output_file, rate, volume): |
|
communicate = edge_tts.Communicate( |
|
subtitle.text, voice, rate=rate, volume=volume) |
|
await communicate.save(output_file) |
|
|
|
|
|
async def generate_wav_files(subtitles, voice, rate, volume): |
|
tasks = [] |
|
mp3_files = [] |
|
file_name = os.path.splitext(subtitles.path)[0] |
|
for i, subtitle in enumerate(subtitles, start=1): |
|
output_file = f"{file_name}_{i}.mp3" |
|
mp3_files.append(output_file) |
|
tasks.append(asyncio.create_task(generate_speech( |
|
subtitle, voice, output_file, rate, volume))) |
|
if i % 50 == 0: |
|
await asyncio.gather(*tasks) |
|
tasks = [] |
|
time.sleep(2) |
|
await asyncio.gather(*tasks) |
|
return mp3_files |
|
|
|
|
|
def merge_audio_files(mp3_files, subtitles, dir_path): |
|
file_name = os.path.splitext(subtitles.path)[0] |
|
with wave.open(f"{file_name}.wav", 'wb') as wav_file: |
|
wav_file.setnchannels(1) |
|
wav_file.setsampwidth(2) |
|
wav_file.setframerate(24000) |
|
|
|
audio_segments = [] |
|
for i, mp3_file in enumerate(mp3_files, start=1): |
|
print( |
|
f"{i}\n{subtitles[i-1].start.to_time().strftime('%H:%M:%S.%f')[:-3]} --> {subtitles[i-1].end.to_time().strftime('%H:%M:%S.%f')[:-3]}\n{subtitles[i-1].text}\n") |
|
|
|
mp3_file_path = os.path.join(dir_path, mp3_file) |
|
if os.path.isfile(mp3_file_path): |
|
start_time = subtitles[i-1].start.to_time() |
|
start_time = start_time.hour * 3600 + start_time.minute * \ |
|
60 + start_time.second + start_time.microsecond / 1000000 |
|
sound = AudioSegment.from_file(mp3_file_path, format="mp3") |
|
audio_segments.append(sound) |
|
os.remove(mp3_file_path) |
|
|
|
framerate = wav_file.getframerate() |
|
nframes = wav_file.getnframes() |
|
current_time = nframes / float(framerate) |
|
if current_time < start_time: |
|
empty_frame_duration = int( |
|
(start_time - current_time) * framerate) |
|
empty_frame = b'\x00' * empty_frame_duration * 2 |
|
wav_file.writeframes(empty_frame) |
|
|
|
sound_data = sound.raw_data |
|
wav_file.writeframes(sound_data) |
|
|
|
wav_file.close() |
|
|
|
|
|
def srt_to_wav_edge_online(dir_path, file, tts, tts_speed, tts_volume): |
|
if tts == "TTS - Zofia - Edge": |
|
voice = "pl-PL-ZofiaNeural" |
|
elif tts == "TTS - Marek - Edge": |
|
voice = "pl-PL-MarekNeural" |
|
if tts_speed: |
|
rate = tts_speed |
|
if tts_volume: |
|
volume = tts_volume |
|
|
|
subtitles = pysrt.open(os.path.join(dir_path, file), encoding='ANSI') |
|
mp3_files = asyncio.run(generate_wav_files(subtitles, voice, rate, volume)) |
|
merge_audio_files(mp3_files, subtitles, dir_path) |
|
|
|
|
|
def tts_srt(dir_path, tts_path, file, settings): |
|
tts = settings['tts'] |
|
tts_speed = settings['tts_speed'] |
|
tts_volume = settings['tts_volume'] |
|
|
|
cprint( |
|
"Rozpoczynam generowanie pliku audio... :", |
|
"yellow", |
|
attrs=["bold"], |
|
end=" ", |
|
) |
|
print(file) |
|
if tts == "TTS - Zosia - Harpo": |
|
srt_to_wav_harpo(tts_path, file, tts_speed, tts_volume) |
|
elif tts == "TTS - Agnieszka - Ivona": |
|
srt_to_wav_balabolka(dir_path, tts_path, file, tts_speed, tts_volume) |
|
elif tts in ["TTS - Zofia - Edge", "TTS - Marek - Edge"]: |
|
srt_to_wav_edge_online(tts_path, file, tts, tts_speed, tts_volume) |
|
cprint("\nGenerowanie pliku audio zakończone.", "yellow", attrs=["bold"]) |
|
|
|
|
|
def merge_tts_audio(dir_path, tmp_path, main_subs_path, lector_path): |
|
ffmpeg_path = os.path.join(dir_path, "src", "ffmpeg", "bin", "ffmpeg.exe") |
|
excluded_extensions = ["srt", "ass"] |
|
|
|
main_subs_files = [f.lower() for f in os.listdir(main_subs_path)] |
|
tmp_files = [f.lower() for f in os.listdir(tmp_path)] |
|
|
|
for file in os.listdir(tmp_path): |
|
file_name, file_ext = os.path.splitext(file) |
|
file_ext = file_ext[1:].lower() |
|
|
|
if file_ext not in excluded_extensions and (file_name + ".wav").lower() in main_subs_files or (file_name + ".wav").lower() in tmp_files: |
|
file_path_1 = os.path.join(tmp_path, file) |
|
file_path_2 = os.path.join(main_subs_path, file_name + ".wav") if ( |
|
file_name + ".wav").lower() in main_subs_files else os.path.join(tmp_path, file_name + ".wav") |
|
|
|
output_file = os.path.join(lector_path, file_name + ".eac3") |
|
|
|
if not os.path.exists(output_file): |
|
command = [ |
|
ffmpeg_path, |
|
"-i", file_path_1, |
|
"-i", file_path_2, |
|
"-filter_complex", "[1:a]volume=7dB[a1];[0:a][a1]amix=inputs=2:duration=first", |
|
output_file |
|
] |
|
subprocess.call(command) |
|
|
|
os.remove(file_path_1) |
|
if file_path_2 != file_path_1: |
|
os.remove(file_path_2) |
|
|
|
for file in os.listdir(main_subs_path): |
|
file_path = os.path.join(main_subs_path, file) |
|
if os.path.isfile(file_path): |
|
os.remove(file_path) |
|
|
|
for file in os.listdir(tmp_path): |
|
file_path = os.path.join(tmp_path, file) |
|
if os.path.isfile(file_path): |
|
os.remove(file_path) |
|
|
|
|
|
def move_output_files(dir_path, alt_subs_path, lector_path, settings): |
|
for file in os.listdir(lector_path): |
|
file_path = os.path.join(lector_path, file) |
|
|
|
destination_path = os.path.join(dir_path, file) |
|
shutil.move(file_path, destination_path) |
|
|
|
for file in os.listdir(alt_subs_path): |
|
file_path = os.path.join(alt_subs_path, file) |
|
if file.endswith('.ass') and settings['alt_main_translator'] == 'no': |
|
|
|
destination_path = os.path.join(dir_path, file) |
|
shutil.move(file_path, destination_path) |
|
elif file.endswith('.srt') and settings['alt_main_translator'] == 'yes': |
|
|
|
destination_path = os.path.join(dir_path, file) |
|
shutil.move(file_path, destination_path) |
|
|
|
|
|
shutil.rmtree(alt_subs_path) |
|
os.mkdir(alt_subs_path) |
|
|
|
|
|
def marge_subtitles_and_lector(dir_path, subtitle_path, lector_path, mkv_file, tmp_path): |
|
mkvmerge_path = os.path.join(dir_path, "src", "mkvtoolnix", "mkvmerge.exe") |
|
output_file = os.path.join(tmp_path, mkv_file) |
|
|
|
command = [ |
|
mkvmerge_path, '-o', output_file, '--no-subtitles', '--no-audio', os.path.join( |
|
dir_path, mkv_file), lector_path, |
|
'--language', '0:pol', '--track-name', '0:"Napisy Poboczne PL"', subtitle_path |
|
] |
|
process = subprocess.Popen(command) |
|
cprint( |
|
f'Łączenie lektora i napisów do {mkv_file}', 'green', attrs=['bold']) |
|
process.communicate() |
|
if subtitle_path.endswith('.ass'): |
|
os.remove(subtitle_path[:-4] + '.srt') |
|
if subtitle_path.endswith('.srt'): |
|
os.remove(subtitle_path[:-4] + '.ass') |
|
os.remove(subtitle_path) |
|
os.remove(lector_path) |
|
|
|
|
|
def marge_lector(dir_path, lector_path, mkv_file, tmp_path): |
|
mkvmerge_path = os.path.join(dir_path, "src", "mkvtoolnix", "mkvmerge.exe") |
|
output_file = os.path.join(tmp_path, mkv_file) |
|
|
|
command = [ |
|
mkvmerge_path, '-o', output_file, '--no-subtitles', '--no-audio', os.path.join( |
|
dir_path, mkv_file), |
|
'--language', '0:pol', '--track-name', '0:"Lektor PL"', lector_path |
|
] |
|
process = subprocess.Popen(command) |
|
cprint(f'Dodawanie lektora do {mkv_file}', 'green', attrs=['bold']) |
|
process.communicate() |
|
os.remove(lector_path) |
|
|
|
|
|
def burn_subtitles(dir_path, tmp_path, file): |
|
for filename in os.listdir(tmp_path): |
|
if filename.endswith(".mkv") and filename == file: |
|
ffmpeg_path = os.path.join( |
|
dir_path, "src", "ffmpeg", "bin", "ffmpeg.exe") |
|
|
|
new_filename = re.sub( |
|
r'[^A-Za-z0-9.]+', '_', filename) |
|
|
|
os.rename(os.path.join(tmp_path, filename), new_filename) |
|
|
|
subprocess.call([ffmpeg_path, '-i', new_filename, '-c:v', 'libx264', '-crf', '54', '-preset', |
|
'ultrafast', '-c:a', 'copy', '-vf', 'subtitles=' + new_filename, os.path.join(tmp_path, new_filename[:-4] + '_lektor.mp4')]) |
|
os.remove(new_filename) |
|
os.rename(os.path.join(tmp_path, new_filename[:-4] + '_lektor.mp4'), |
|
os.path.join(tmp_path, filename[:-4] + '_lektor.mp4')) |
|
dst_file = os.path.join(tmp_path, filename[:-4] + '_lektor.mp4') |
|
shutil.move(dst_file, dir_path) |
|
|
|
|
|
def burn_lector(dir_path, tmp_path, file): |
|
ffmpeg_path = os.path.join(dir_path, "src", "ffmpeg", "bin", "ffmpeg.exe") |
|
|
|
tmp_path = os.path.join(tmp_path, file) |
|
mp4_path = os.path.join(dir_path, file.replace('.mkv', '_lektor.mp4')) |
|
|
|
command = [ |
|
ffmpeg_path, '-i', tmp_path, '-c:v', 'libx264', '-crf', '54', '-preset', 'ultrafast', '-c:a', 'copy', mp4_path |
|
] |
|
subprocess.run(command) |
|
os.remove(tmp_path) |
|
|
|
|
|
def output_files(dir_path, alt_subs_path, lector_path, settings, tmp_path): |
|
output_option = settings["output"] |
|
if output_option == "Oglądam w MM_AVH_Players": |
|
print("Kopiuję pliki wyjściowe do folderu z filmem...") |
|
move_output_files(dir_path, alt_subs_path, lector_path, settings) |
|
else: |
|
|
|
for file in os.listdir(dir_path): |
|
if file.endswith('.mkv'): |
|
mkv_file = file |
|
base_name = os.path.splitext(mkv_file)[0] |
|
|
|
srt_file = f"{base_name}.srt" |
|
ass_file = f"{base_name}.ass" |
|
lector_file = f"{base_name}.eac3" |
|
|
|
srt_path = os.path.join(alt_subs_path, srt_file) |
|
ass_path = os.path.join(alt_subs_path, ass_file) |
|
lector_path_2 = os.path.join(lector_path, lector_file) |
|
|
|
if os.path.isfile(srt_path) and settings['alt_main_translator'] == 'yes': |
|
marge_subtitles_and_lector( |
|
dir_path, srt_path, lector_path_2, mkv_file, tmp_path) |
|
if output_option == 'Scal do mkv': |
|
output_file = os.path.join(tmp_path, mkv_file) |
|
destination_path = os.path.join(dir_path, mkv_file)[ |
|
:-4] + '_lektor.mkv' |
|
shutil.move(output_file, destination_path) |
|
elif output_option == 'Wypal do mp4': |
|
burn_subtitles(dir_path, tmp_path, file) |
|
|
|
elif os.path.isfile(ass_path) and settings['alt_main_translator'] == 'no': |
|
marge_subtitles_and_lector( |
|
dir_path, ass_path, lector_path_2, mkv_file, tmp_path) |
|
if output_option == 'Scal do mkv': |
|
output_file = os.path.join(tmp_path, mkv_file) |
|
destination_path = os.path.join(dir_path, mkv_file)[ |
|
:-4] + '_lektor.mkv' |
|
shutil.move(output_file, destination_path) |
|
elif output_option == 'Wypal do mp4': |
|
burn_subtitles(dir_path, tmp_path, file) |
|
else: |
|
marge_lector(dir_path, lector_path_2, mkv_file, tmp_path) |
|
if output_option == 'Scal do mkv': |
|
output_file = os.path.join(tmp_path, mkv_file) |
|
destination_path = os.path.join(dir_path, mkv_file)[ |
|
:-4] + '_lektor.mkv' |
|
shutil.move(output_file, destination_path) |
|
elif output_option == 'Wypal do mp4': |
|
burn_lector(dir_path, tmp_path, file) |
|
|
|
|
|
def main(): |
|
cprint("╚═══ Multimedia Magic – Audio Visual Heaven ═══╝\n", |
|
'white', attrs=['bold']) |
|
|
|
dir_path = directory_path() |
|
settings = get_settings(dir_path) |
|
|
|
if os.path.isfile(os.path.join(dir_path, 'src', 'settings.json')): |
|
cprint("Czy chcesz zmienić ustawienia? (t / y = tak):", |
|
'white', attrs=['bold'], end=" ") |
|
change_settings = input("") |
|
if change_settings.lower() in ['t', 'y', 'tak', 'yes']: |
|
set_settings(dir_path, settings) |
|
else: |
|
cprint("Nie zmieniono ustawień.\n", 'green', attrs=['bold']) |
|
else: |
|
cprint("Ustawienia wstępne.\n", 'green', attrs=['bold']) |
|
set_settings(dir_path, settings) |
|
|
|
|
|
for file in os.listdir(dir_path): |
|
if file.endswith(".mkv"): |
|
mkv_extract(dir_path, file, mkv_info(dir_path, file)) |
|
|
|
|
|
tmp_path = os.path.join(dir_path, 'src', 'tmp') |
|
alt_subs_path = os.path.join(tmp_path, 'alt_subs') |
|
main_subs_path = os.path.join(tmp_path, 'main_subs') |
|
lector_path = os.path.join(tmp_path, 'lector') |
|
|
|
|
|
for file in os.listdir(tmp_path): |
|
if file.endswith(".ass"): |
|
split_ass(tmp_path, file) |
|
|
|
for file in os.listdir(tmp_path): |
|
if file.endswith(".ass"): |
|
ass_to_srt(tmp_path, file) |
|
for file in os.listdir(alt_subs_path): |
|
if file.endswith(".ass"): |
|
ass_to_srt(alt_subs_path, file) |
|
for file in os.listdir(main_subs_path): |
|
if file.endswith(".ass"): |
|
ass_to_srt(main_subs_path, file) |
|
|
|
|
|
for file in os.listdir(tmp_path): |
|
if file.endswith('.srt'): |
|
translate_srt(tmp_path, file, settings) |
|
asnii_srt(tmp_path, file) |
|
tts_srt(dir_path, tmp_path, file, settings) |
|
for file in os.listdir(alt_subs_path): |
|
if file.endswith('.srt'): |
|
translate_srt(alt_subs_path, file, settings) |
|
asnii_srt(alt_subs_path, file) |
|
for file in os.listdir(main_subs_path): |
|
if file.endswith('.srt'): |
|
translate_srt(main_subs_path, file, settings) |
|
asnii_srt(main_subs_path, file) |
|
tts_srt(dir_path, main_subs_path, file, settings) |
|
|
|
merge_tts_audio(dir_path, tmp_path, main_subs_path, lector_path) |
|
output_files(dir_path, alt_subs_path, lector_path, settings, tmp_path) |
|
|
|
|
|
if __name__ == "__main__": |
|
start_time = time.time() |
|
main() |
|
end_time = time.time() |
|
execution_time = end_time - start_time |
|
|
|
seconds = int(execution_time % 60) |
|
minutes = int((execution_time // 60) % 60) |
|
hours = int(execution_time // 3600) |
|
|
|
print("Czas wykonania: {} godzin, {} minut, {} sekund".format( |
|
hours, minutes, seconds)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|