| |
| |
|
|
| |
| |
| |
| |
|
|
| 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) |
|
|
| |
| |
| |
|
|