|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
version = '0.15.0' |
|
print(f"------- 'Auto Synced Translated Dubs' script by ThioJoe - Release version {version} -------") |
|
|
|
|
|
|
|
from Scripts.shared_imports import * |
|
import Scripts.TTS as TTS |
|
import Scripts.audio_builder as audio_builder |
|
import Scripts.auth as auth |
|
import Scripts.translate as translate |
|
from Scripts.utils import parseBool |
|
|
|
|
|
import re |
|
import copy |
|
import winsound |
|
|
|
|
|
import ffprobe |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
languageNums = batchConfig['SETTINGS']['enabled_languages'].replace(' ','').split(',') |
|
srtFile = os.path.abspath(batchConfig['SETTINGS']['srt_file_path'].strip("\"")) |
|
|
|
|
|
videoFilePath = batchConfig['SETTINGS']['original_video_file_path'] |
|
|
|
|
|
for num in languageNums: |
|
|
|
if not batchConfig.has_section(f'LANGUAGE-{num}'): |
|
raise ValueError(f'Invalid language number in batch.ini: {num} - Make sure the section [LANGUAGE-{num}] exists') |
|
|
|
|
|
for num in languageNums: |
|
if not batchConfig.has_option(f'LANGUAGE-{num}', 'synth_language_code'): |
|
raise ValueError(f'Invalid configuration in batch.ini: {num} - Make sure the option "synth_language_code" exists under [LANGUAGE-{num}]') |
|
if not batchConfig.has_option(f'LANGUAGE-{num}', 'synth_voice_name'): |
|
raise ValueError(f'Invalid configuration in batch.ini: {num} - Make sure the option "synth_voice_name" exists under [LANGUAGE-{num}]') |
|
if not batchConfig.has_option(f'LANGUAGE-{num}', 'translation_target_language'): |
|
raise ValueError(f'Invalid configuration in batch.ini: {num} - Make sure the option "translation_target_language" exists under [LANGUAGE-{num}]') |
|
if not batchConfig.has_option(f'LANGUAGE-{num}', 'synth_voice_gender'): |
|
raise ValueError(f'Invalid configuration in batch.ini: {num} - Make sure the option "synth_voice_gender" exists under [LANGUAGE-{num}]') |
|
|
|
|
|
batchSettings = {} |
|
for num in languageNums: |
|
batchSettings[num] = { |
|
'synth_language_code': batchConfig[f'LANGUAGE-{num}']['synth_language_code'], |
|
'synth_voice_name': batchConfig[f'LANGUAGE-{num}']['synth_voice_name'], |
|
'translation_target_language': batchConfig[f'LANGUAGE-{num}']['translation_target_language'], |
|
'synth_voice_gender': batchConfig[f'LANGUAGE-{num}']['synth_voice_gender'] |
|
} |
|
|
|
|
|
|
|
|
|
def parse_srt_file(srtFileLines, preTranslated=False): |
|
|
|
subtitleTimeLineRegex = re.compile(r'\d\d:\d\d:\d\d,\d\d\d --> \d\d:\d\d:\d\d,\d\d\d') |
|
|
|
|
|
subsDict = {} |
|
|
|
|
|
addBufferMilliseconds = int(config['add_line_buffer_milliseconds']) |
|
|
|
|
|
|
|
|
|
|
|
for lineNum, line in enumerate(srtFileLines): |
|
line = line.strip() |
|
if line.isdigit() and subtitleTimeLineRegex.match(srtFileLines[lineNum + 1]): |
|
lineWithTimestamps = srtFileLines[lineNum + 1].strip() |
|
lineWithSubtitleText = srtFileLines[lineNum + 2].strip() |
|
|
|
|
|
count = 3 |
|
while True: |
|
|
|
if (lineNum+count) < len(srtFileLines) and srtFileLines[lineNum + count].strip(): |
|
lineWithSubtitleText += ' ' + srtFileLines[lineNum + count].strip() |
|
count += 1 |
|
else: |
|
break |
|
|
|
|
|
subsDict[line] = {'start_ms': '', 'end_ms': '', 'duration_ms': '', 'text': '', 'break_until_next': '', 'srt_timestamps_line': lineWithTimestamps} |
|
|
|
time = lineWithTimestamps.split(' --> ') |
|
time1 = time[0].split(':') |
|
time2 = time[1].split(':') |
|
|
|
|
|
processedTime1 = int(time1[0]) * 3600000 + int(time1[1]) * 60000 + int(time1[2].split(',')[0]) * 1000 + int(time1[2].split(',')[1]) |
|
processedTime2 = int(time2[0]) * 3600000 + int(time2[1]) * 60000 + int(time2[2].split(',')[0]) * 1000 + int(time2[2].split(',')[1]) |
|
timeDifferenceMs = str(processedTime2 - processedTime1) |
|
|
|
|
|
if addBufferMilliseconds > 0 and not preTranslated: |
|
subsDict[line]['start_ms_buffered'] = str(processedTime1 + addBufferMilliseconds) |
|
subsDict[line]['end_ms_buffered'] = str(processedTime2 - addBufferMilliseconds) |
|
subsDict[line]['duration_ms_buffered'] = str((processedTime2 - addBufferMilliseconds) - (processedTime1 + addBufferMilliseconds)) |
|
else: |
|
subsDict[line]['start_ms_buffered'] = str(processedTime1) |
|
subsDict[line]['end_ms_buffered'] = str(processedTime2) |
|
subsDict[line]['duration_ms_buffered'] = str(processedTime2 - processedTime1) |
|
|
|
|
|
subsDict[line]['start_ms'] = str(processedTime1) |
|
subsDict[line]['end_ms'] = str(processedTime2) |
|
subsDict[line]['duration_ms'] = timeDifferenceMs |
|
subsDict[line]['text'] = lineWithSubtitleText |
|
if lineNum > 0: |
|
|
|
subsDict[str(int(line)-1)]['break_until_next'] = processedTime1 - int(subsDict[str(int(line) - 1)]['end_ms']) |
|
else: |
|
subsDict[line]['break_until_next'] = 0 |
|
|
|
|
|
|
|
if addBufferMilliseconds > 0 and not preTranslated: |
|
for key, value in subsDict.items(): |
|
subsDict[key]['start_ms'] = value['start_ms_buffered'] |
|
subsDict[key]['end_ms'] = value['end_ms_buffered'] |
|
subsDict[key]['duration_ms'] = value['duration_ms_buffered'] |
|
|
|
return subsDict |
|
|
|
|
|
|
|
|
|
with open(srtFile, 'r', encoding='utf-8-sig') as f: |
|
originalSubLines = f.readlines() |
|
|
|
originalLanguageSubsDict = parse_srt_file(originalSubLines) |
|
|
|
|
|
|
|
def get_duration(filename): |
|
import subprocess, json |
|
result = subprocess.check_output( |
|
f'ffprobe -v quiet -show_streams -select_streams v:0 -of json "{filename}"', shell=True).decode() |
|
fields = json.loads(result)['streams'][0] |
|
try: |
|
duration = fields['tags']['DURATION'] |
|
except KeyError: |
|
duration = fields['duration'] |
|
durationMS = round(float(duration)*1000) |
|
return durationMS |
|
|
|
|
|
if config['debug_mode'] and ORIGINAL_VIDEO_PATH.lower() == "debug.test": |
|
|
|
totalAudioLength = int(originalLanguageSubsDict[str(len(originalLanguageSubsDict))]['end_ms']) |
|
else: |
|
totalAudioLength = get_duration(ORIGINAL_VIDEO_PATH) |
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(OUTPUT_DIRECTORY): |
|
os.makedirs(OUTPUT_DIRECTORY) |
|
if not os.path.exists(OUTPUT_FOLDER): |
|
os.makedirs(OUTPUT_FOLDER) |
|
|
|
|
|
if not os.path.exists('workingFolder'): |
|
os.makedirs('workingFolder') |
|
|
|
|
|
|
|
def manually_prepare_dictionary(dictionaryToPrep): |
|
|
|
|
|
for key, value in dictionaryToPrep.items(): |
|
dictionaryToPrep[key]['translated_text'] = value['text'] |
|
|
|
|
|
return {int(k): v for k, v in dictionaryToPrep.items()} |
|
|
|
def get_pretranslated_subs_dict(langData): |
|
|
|
files = os.listdir(OUTPUT_FOLDER) |
|
|
|
if os.path.exists(OUTPUT_YTSYNCED_FOLDER): |
|
altFiles = os.listdir(OUTPUT_YTSYNCED_FOLDER) |
|
else: |
|
altFiles = None |
|
|
|
|
|
if altFiles and files: |
|
print("Found YouTube-synced translations in: " + OUTPUT_YTSYNCED_FOLDER) |
|
userResponse = input("Use YouTube-synced translations instead of those in main output folder? (y/n): ") |
|
if userResponse.lower() == 'y': |
|
files = altFiles |
|
print("Using YouTube-synced translations...\n") |
|
elif altFiles and not files: |
|
print("Found YouTube-synced translations to use in: " + OUTPUT_YTSYNCED_FOLDER) |
|
files = altFiles |
|
|
|
|
|
for file in files: |
|
if file.replace(' ', '').endswith(f"-{langData['translation_target_language']}.srt"): |
|
|
|
with open(f"{OUTPUT_FOLDER}/{file}", 'r', encoding='utf-8-sig') as f: |
|
pretranslatedSubLines = f.readlines() |
|
print(f"Pre-translated file found: {file}") |
|
|
|
|
|
preTranslatedDict = parse_srt_file(pretranslatedSubLines, preTranslated=True) |
|
|
|
|
|
preTranslatedDict = manually_prepare_dictionary(preTranslatedDict) |
|
|
|
|
|
return preTranslatedDict |
|
|
|
|
|
return None |
|
|
|
|
|
def process_language(langData, processedCount, totalLanguages): |
|
langDict = { |
|
'targetLanguage': langData['translation_target_language'], |
|
'voiceName': langData['synth_voice_name'], |
|
'languageCode': langData['synth_language_code'], |
|
'voiceGender': langData['synth_voice_gender'], |
|
'translateService': langData['translate_service'], |
|
'formality': langData['formality'] |
|
} |
|
|
|
individualLanguageSubsDict = copy.deepcopy(originalLanguageSubsDict) |
|
|
|
|
|
print(f"\n----- Beginning Processing of Language ({processedCount}/{totalLanguages}): {langDict['languageCode']} -----") |
|
|
|
|
|
if langDict['languageCode'].lower() == config['original_language'].lower(): |
|
print("Original language is the same as the target language. Skipping translation.") |
|
individualLanguageSubsDict = manually_prepare_dictionary(individualLanguageSubsDict) |
|
|
|
elif config['skip_translation'] == False: |
|
|
|
individualLanguageSubsDict = translate.translate_dictionary(individualLanguageSubsDict, langDict, skipTranslation=config['skip_translation']) |
|
if config['stop_after_translation']: |
|
print("Stopping at translation is enabled. Skipping TTS and building audio.") |
|
return |
|
|
|
elif config['skip_translation'] == True: |
|
print("Skip translation enabled. Checking for pre-translated subtitles...") |
|
|
|
pretranslatedSubsDict = get_pretranslated_subs_dict(langData) |
|
if pretranslatedSubsDict != None: |
|
individualLanguageSubsDict = pretranslatedSubsDict |
|
else: |
|
print(f"\nPre-translated subtitles not found for language '{langDict['languageCode']}' in folder '{OUTPUT_FOLDER}'. Skipping.") |
|
print(f"Note: Ensure the subtitle filename for this language ends with: ' - {langData['translation_target_language']}.srt'\n") |
|
return |
|
|
|
|
|
if cloudConfig['batch_tts_synthesize'] == True and cloudConfig['tts_service'] == 'azure': |
|
individualLanguageSubsDict = TTS.synthesize_dictionary_batch(individualLanguageSubsDict, langDict, skipSynthesize=config['skip_synthesize']) |
|
else: |
|
individualLanguageSubsDict = TTS.synthesize_dictionary(individualLanguageSubsDict, langDict, skipSynthesize=config['skip_synthesize']) |
|
|
|
|
|
individualLanguageSubsDict = audio_builder.build_audio(individualLanguageSubsDict, langDict, totalAudioLength, config['two_pass_voice_synth']) |
|
|
|
|
|
|
|
|
|
processedCount = 0 |
|
totalLanguages = len(batchSettings) |
|
|
|
|
|
print(f"\n----- Beginning Processing of Languages -----") |
|
batchSettings = translate.set_translation_info(batchSettings) |
|
for langNum, langData in batchSettings.items(): |
|
processedCount += 1 |
|
|
|
process_language(langData, processedCount, totalLanguages) |
|
|
|
|
|
|
|
|
|
|