MattyMroz's picture
mm_avh_working_space
068ed60
raw
history blame contribute delete
No virus
46.5 kB
# Program: Multimedia Magic - Audio Visual Heaven (MM-AVH) do automatyzacji procesu tłumaczenia napisów i lektora do filmów
# pip install pysubs2
# pip install googletrans==3.1.0a0
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': # TTS - Zosia - Harpo
return int(speed) > 0
elif voice_choice == '2': # TTS - Agnieszka - Ivona
return -10 <= int(speed) <= 10
elif voice_choice in ['3', '4']: # TTS - Zofia - Edge, TTS - Marek - Edge
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': # TTS - Zosia - Harpo
return 0 <= float(volume) <= 1
elif voice_choice == '2': # TTS - Agnieszka - Ivona
return -100 <= int(volume) <= 100
elif voice_choice in ['3', '4']: # TTS - Zofia - Edge, TTS - Marek - Edge
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' # Ustawienie domyślnej wartości
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'),
# jeśli nie jest pusty 1df708bf-af10-3e70-e577-b2d4cb763d74:fx
'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)
# Znajdź unikalne style w kolejności występowania
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'])
# Wyświetl dostępne style w kolejności występowania
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)
# Skopiuj metadane do plików wyjściowych
main_subs.info = subs.info
alt_subs.info = subs.info
# Skopiuj style do plików wyjściowych
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]
# Zapisz napisy w plikach wyjściowych
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)
# os.remove(os.path.join(dir_path, file))
# cprint("Usunięto plik źródłowy.", 'green', attrs=['bold'])
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)
# Zamieniono kodowanie pliku na ANSI
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')
# Zamień na swój klucz https://www.deepl.com/pl/pro-api?cta=header-pro-api/ za darmo 5000000 słów miesięcznie
# auth_key = "1df708bf-af10-3e70-e577-b2d4cb763d74:fx"
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):
# subprocess.Popen(
# r"C:\Users\mateu\AppData\Roaming\0install.net\desktop-integration\stubs\90d46b1a865bf05507b9fb0d2b3698b63cba3a15fbcafd836ab5523e7a3efb99\DeepL.exe")
# lub
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
# przykładowe uźycie
# def ask_chatGPT(prompt):
# print("ChatGPT: ", end="")
# prev_text = ""
# for data in chatbot.ask(
# prompt
# ):
# prev_text = data["message"]
# # print(prev_text)
# return prev_text
# prompt = "Co tam?"
# # prompt = input("You: ")
# while prompt != "Exit":
# print(ask_chatGPT(prompt))
# prompt = input("You: ")
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()
# Inicjalizacja silnika mowy
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)) # Szybkość mówienia
engine.setProperty('volume', float(tts_volume)) # Głośność
subtitles = pysrt.open(os.path.join(
self.working_space_temp_main_subs, self.filename), encoding='ANSI')
# Odczytanie napisów i zapisanie mowy do pliku WAV
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) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(22500) # 22kHz
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
# Zapisanie mowy do pliku WAV
engine.save_to_file(subtitle.text, os.path.join(
self.working_space_temp, "temp.wav"))
engine.runAndWait()
# Dodanie pustego frame'a do pliku WAV, jeśli jest to wymagane
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)
# Dodanie mowy do pliku WAV
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)
# Usunięcie pliku tymczasowego
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}'
# Tworzenie wątku dla komendy
command_thread = threading.Thread(
target=execute_command, args=(command,))
# Uruchamianie wątku dla komendy
command_thread.start()
# Wykonanie pętli w głównym wątku
for i, subtitle in enumerate(subtitles, start=1):
process_subtitle(subtitle)
# Oczekiwanie na zakończenie wątku komendy
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)
# przenieś plik z lektorem do katalogu docelowego
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':
# przenieś plik ass
destination_path = os.path.join(dir_path, file)
shutil.move(file_path, destination_path)
elif file.endswith('.srt') and settings['alt_main_translator'] == 'yes':
# przenieś plik srt
destination_path = os.path.join(dir_path, file)
shutil.move(file_path, destination_path)
# Opróżnij folder alt_subs_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)
# dla każdego pliku mkv w folderze wykonaj
for file in os.listdir(dir_path):
if file.endswith(".mkv"):
mkv_extract(dir_path, file, mkv_info(dir_path, file))
# dla każdego pliku ass w folderze wykonaj
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')
# rozdzielanie napisów
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)
# Tłumaczenie i ANSI
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))
# option = {
# 1:
# }
# cprint("╚═══════════════ WYBIERZ OPCJĘ ════════════════╝",
# 'white', attrs=['bold'])
# print("1. Ustawienia")
# print("2. EPIC AUTO: Ekstrakcja -> Tłumaczenie -> Lektor ->\n Scalanie lektora z audio ->\n (opcjonalnie) Scalenie do wideo z napisami / napisami pobocznymi ->\n Wypalenie napisów do wideo do formatu mp4\n LUB Zapisanie lektor-audio oraz napisów pobocznych do folderu z filmem")
# cprint("\n╚═════════════════════ MKV ════════════════════╝",
# 'white', attrs=['bold'])
# print("3. Informacje o plikach mkv")
# print("4. Ekstrakcja napisów / ścieżki dźwiękowej / obrazu")
# print("5. Ekstrakcja fontów")
# print("6. Scalanie napisów / audio do mkv")
# cprint("\n╚════════════════ TEKST ~ AUDIO ═══════════════╝",
# 'white', attrs=['bold'])
# print("6. Tłumaczenie napisów i tekstów")
# print("7. Teksy na mowę")
# print("8. Scalanie audio z lektorem do jednego pliku audio")
# cprint("\n╚══════════════ WYPALENIE DO MP4 ═══════════════╝",
# 'white', attrs=['bold'])
# print("9. Lektor do wideo")
# print("10. Wypalanie napisów do wideo")
# print("11. Wypalanie napisów pobocznych do wideo")
# 1. Ekstrakcja
# 2. W zalerzności języka w subtitles w lang lub lang_ietf przetłumacz na polski lub nie w przypadku und pytaj użytkownika czy tłumaczyć
# 3. Zrobienie lektora
# 4. Połączenie audio z lektorem
# 6. Wypal napisy do wideo
# 7. Dodaj ścieżkę dźwiękową lektora do wideo
# 8. Wypal napisy i dodaj ścieżkę dźwiękową lektora do wideo
# 9. Nałóż na siebie dwie ścieżki dźwiękowe
# dir_path\src\ffmpeg\bin
# dir_path\src\mkvtoolnix\mkvinfo.exe
# dir_path\src\mkvtoolnix\mkvextract.exe
# dir_path\src\mkvtoolnix\mkvmerge.exe
# dir_path\src\mkvtoolnix\mkvpropedit.exe
# dir_path\src\mkvtoolnix\mkvtoolnix-gui.exe
# [2023-05-25][12:24:19] "tracks": [
# [2023-05-25][12:24:19] {
# [2023-05-25][12:24:19] "codec": "AVC/H.264/MPEG-4p10",
# [2023-05-25][12:24:19] "id": 0,
# [2023-05-25][12:24:19] "properties": {
# [2023-05-25][12:24:19] "codec_id": "V_MPEG4/ISO/AVC",
# [2023-05-25][12:24:19] "codec_private_data": "01640028ffe1001c67640028acb280f0044fcb80b50101014000001f400005da83c60c9601000768e930332c8b00fdf8f800",
# [2023-05-25][12:24:19] "codec_private_length": 50,
# [2023-05-25][12:24:19] "default_duration": 41708333,
# [2023-05-25][12:24:19] "default_track": true,
# [2023-05-25][12:24:19] "display_dimensions": "1920x1080",
# [2023-05-25][12:24:19] "display_unit": 0,
# [2023-05-25][12:24:19] "enabled_track": true,
# [2023-05-25][12:24:19] "forced_track": false,
# [2023-05-25][12:24:19] "language": "und",
# [2023-05-25][12:24:19] "language_ietf": "und",
# [2023-05-25][12:24:19] "minimum_timestamp": 0,
# [2023-05-25][12:24:19] "number": 1,
# [2023-05-25][12:24:19] "packetizer": "mpeg4_p10_video",
# [2023-05-25][12:24:19] "pixel_dimensions": "1920x1080",
# [2023-05-25][12:24:19] "uid": 1
# [2023-05-25][12:24:19] },
# [2023-05-25][12:24:19] "type": "video"
# [2023-05-25][12:24:19] },