File size: 14,326 Bytes
068ed60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
from msvcrt import getch
from os import listdir, makedirs, path
from shutil import rmtree
from typing import Dict, List

from natsort import natsorted

from constants import (WORKING_SPACE,
                       WORKING_SPACE_OUTPUT,
                       WORKING_SPACE_TEMP,
                       WORKING_SPACE_TEMP_MAIN_SUBS,
                       WORKING_SPACE_TEMP_ALT_SUBS,
                       console)

from data.settings import Settings

from modules.mkvtoolnix import MkvToolNix
from modules.subtitle import SubtitleRefactor
from modules.subtitle_to_speech import SubtitleToSpeech
from modules.translator import SubtitleTranslator
from modules.mkv_processing import MKVProcessing

from utils.cool_animation import CoolAnimation
from utils.execution_timer import execution_timer


def check_and_create_directories(directories: List[str]):  # ✅
    """
    Checks if the given directories exist, and if not, creates them.

    Args:
        directories (List[str]): A list of directory paths to check and create.
    """
    for directory in directories:
        if not path.exists(directory):
            makedirs(directory)


def display_logo():  # ✅
    """
        Displays the logo of the application using the CoolAnimation class.
    """
    mm_avh_logo: CoolAnimation = CoolAnimation()
    mm_avh_logo.display()
    console.print(
        '╚═══ Multimedia Magic – Audio Visual Heaven ═══╝\n', style='white_bold')


def ask_user(question: str) -> bool:
    """
        Asks the user a yes/no question and returns the response.

        Args:
            question (str): The question to ask the user.

        Returns:
            bool: True if the user answers yes, False otherwise.
    """
    console.print(question, style='bold green', end=' ')
    return input().lower() in ('t', 'y')


def update_settings() -> Settings:  # ✅
    """
        Asks the user if they want to update the settings. If yes, updates the settings and saves them to a file.

        Returns:
            Settings: The updated settings.
    """
    if ask_user('💾 Czy chcesz zmienić ustawienia? (T lub Y - tak):'):
        Settings.change_settings_save_to_file()
        console.print('Zapisano ustawienia.\n', style='green_bold')
    else:
        console.print('Pomijam tę opcję.\n', style='red_bold')
    return Settings.load_from_file()


def extract_tracks_from_mkv():  # ✅
    """
        Asks the user if they want to extract tracks from MKV files. If yes, extracts the tracks.
    """
    if ask_user('🧲 Czy chcesz wyciągnąć ścieżki z plików mkv? (T lub Y - tak):'):
        files: List[str] = get_mkv_files(WORKING_SPACE)
        sorted_files: List[str] = natsorted(files)
        for filename in sorted_files:
            mkv: MkvToolNix = MkvToolNix(filename)
            mkv.mkv_extract_track(mkv.get_mkv_info())
    else:
        console.print('Pomijam tę opcję.\n', style='red_bold')


def get_mkv_files(directory: str) -> List[str]:
    """
        Gets all MKV files in a directory.

        Args:
            directory (str): The directory to search for MKV files.

        Returns:
            List[str]: A list of MKV files in the directory.
    """
    return [file for file in listdir(directory)
            if path.isfile(path.join(directory, file)) and file.endswith('.mkv')]


def refactor_subtitles():  # ✅
    """
        Refactors subtitles in various formats to a standard format.
    """
    subtitle_extensions: List[str] = [
        '.sup', '.txt', '.ogg',
        '.ssa', '.ass', '.srt',
        '.sub', '.usf', '.vtt',
    ]

    files: List[str] = get_files_with_extensions(
        WORKING_SPACE_TEMP, subtitle_extensions)
    sorted_files = natsorted(files)
    for filename in sorted_files:
        refactor_subtitle_file(filename)


def get_files_with_extensions(directory: str, extensions: List[str]) -> List[str]:
    """
        Gets all files in a directory with certain extensions.

        Args:
            directory (str): The directory to search for files.
            extensions (List[str]): The extensions to look for.

        Returns:
            List[str]: A list of files in the directory with the specified extensions.
    """
    return [
        file for file in listdir(directory)
        if (
            path.isfile(path.join(directory, file)) and
            any(file.endswith(ext) for ext in extensions)
        )
    ]


def refactor_subtitle_file(filename: str):
    """
        Refactors a subtitle file to a standard format.

        Args:
            filename (str): The name of the subtitle file to refactor.
    """
    subtitle: SubtitleRefactor = SubtitleRefactor(filename)
    if filename.endswith('.ass') or filename.endswith('.ssa'):
        subtitle.split_ass()
        subtitle.ass_to_srt()
    if filename.endswith('.srt'):
        subtitle.move_srt()
    if filename.endswith('.txt'):
        subtitle.txt_to_srt(10)


def translate_subtitles(settings: Settings):  # ✅
    """
        Asks the user if they want to translate subtitle files. If yes, translates the files.

        Args:
        settings (Settings): The settings to use for translation.
    """
    if not ask_user('💭 Czy chcesz tłumaczyć pliki napisów? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    main_subs_files = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    files_to_translate = ask_to_translate_files(main_subs_files)
    translate_files(files_to_translate, settings)


def get_srt_files(directory: str) -> List[str]:
    """
        Gets all SRT files in a directory.

        Args:
            directory (str): The directory to search for SRT files.

        Returns:
            List[str]: A list of SRT files in the directory.
    """
    return [
        filename for filename in listdir(directory)
        if path.isfile(path.join(directory, filename)) and filename.endswith('.srt')
    ]


def ask_to_translate_files(files: List[str]) -> dict:
    """
        Asks the user which files they want to translate.

        Args:
            files (List[str]): A list of files to ask about.

        Returns:
            dict: A dictionary mapping file names to a boolean indicating whether the user wants to translate them.
    """
    files_to_translate: dict = {}
    for filename in files:
        console.print("\nTŁUMACZENIE PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz przetłumaczyć (T lub Y - tak):"):
            files_to_translate[filename] = True
        else:
            console.print('Pomijam tę opcję.\n', style='red_bold')
            files_to_translate[filename] = False
    return files_to_translate


def translate_files(files_to_translate: dict, settings: Settings):
    """
        Translates the specified files.

        Args:
            files_to_translate (dict): A dictionary mapping file names to a boolean indicating whether to translate them.
            settings (Settings): The settings to use for translation.
    """
    translator_instance: SubtitleTranslator = SubtitleTranslator()
    for filename, should_translate in files_to_translate.items():
        if should_translate:
            translator_instance.translate_srt(filename,
                                              WORKING_SPACE_TEMP_MAIN_SUBS,
                                              settings)
            if path.exists(path.join(WORKING_SPACE_TEMP_ALT_SUBS, filename)):
                translator_instance.translate_srt(filename,
                                                  WORKING_SPACE_TEMP_ALT_SUBS,
                                                  settings)


def convert_numbers_to_words():  # ✅
    """
        Asks the user if they want to convert numbers to words in the text. If yes, performs the conversion.
    """
    if not ask_user('🔢 Czy chcesz przekonwertować liczby na słowa w tekście? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    srt_files = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    convert_numbers_in_files(srt_files)


def get_srt_files(directory: str) -> List[str]:
    """
        Gets all SRT files in a directory.

        Args:
            directory (str): The directory to search for SRT files.

        Returns:
            List[str]: A list of SRT files in the directory.
    """
    return [
        file for file in listdir(directory)
        if path.isfile(path.join(directory, file)) and file.endswith('.srt')
    ]


def convert_numbers_in_files(files: List[str]):
    """
        Converts numbers to words in the specified files.

        Args:
            files (List[str]): A list of files to convert numbers in.
    """
    for filename in files:
        console.print(
            "\nKONWERSJA LICZB (BEZ POPRAWNOŚCI GRAMATYCZNEJ) W PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz przekonwertować liczby na słowa w tym pliku? (T lub Y - tak):"):
            subtitle: SubtitleRefactor = SubtitleRefactor(filename)
            subtitle.convert_numbers_in_srt()
        else:
            console.print(f'Pomijam plik {filename}.\n', style='red_bold')


def generate_audio_for_subtitles(settings: Settings) -> None:  # ✅
    """
        Asks the user if they want to generate audio for subtitles. If yes, generates the audio.

        Args:
            settings (Settings): The settings to use for audio generation.
    """
    if not ask_user('🎤 Czy chcesz generować audio dla napisów? (T lub Y - tak):'):
        console.print('Pomijam tę opcję.\n', style='red_bold')
        return

    main_subs_files: List[str] = get_srt_files(WORKING_SPACE_TEMP_MAIN_SUBS)
    files_to_generate_audio: Dict[str, bool] = ask_to_generate_audio_files(
        main_subs_files)
    generate_audio_files(files_to_generate_audio, settings)


def ask_to_generate_audio_files(files: List[str]) -> Dict[str, bool]:
    """
        Asks the user which files they want to generate audio for.

        Args:
            files (List[str]): A list of files to ask about.

        Returns:
            dict: A dictionary mapping file names to a boolean indicating whether the user wants to generate audio for them.
    """
    files_to_generate_audio: Dict[str, bool] = {}
    for filename in files:
        console.print("\nGENEROWANIE AUDIO DLA PLIKU:", style='yellow_bold')
        console.print(filename, style='white_bold')
        if ask_user("Czy chcesz wygenerować audio dla tego pliku? (T lub Y - tak):"):
            files_to_generate_audio[filename] = True
        else:
            console.print('Pomijam tę opcję.', style='red_bold')
            files_to_generate_audio[filename] = False
    return files_to_generate_audio


def generate_audio_files(files_to_generate_audio: Dict[str, bool], settings: Settings) -> None:
    """
        Generates audio for the specified files.

        Args:
            files_to_generate_audio (Dict[str, bool]): A dictionary mapping file names to a boolean indicating whether the user wants to generate audio for them.
            settings (Settings): The settings to use for audio generation.
    """
    audio_generator: SubtitleToSpeech
    if 'TTS - *Głos* - ElevenLans' in settings.tts:
        audio_generator = SubtitleToSpeech('')
        audio_generator.srt_to_eac3_elevenlabs()
    else:
        for filename, should_generate_audio in files_to_generate_audio.items():
            if should_generate_audio:
                audio_generator = SubtitleToSpeech(filename)
                audio_generator.generate_audio(settings)


def refactor_alt_subtitles():  # ✅
    """
        Refactors alternative subtitles to a standard format.
    """
    files: List[str] = get_srt_files(WORKING_SPACE_TEMP_ALT_SUBS)
    sorted_files = natsorted(files)
    for filename in sorted_files:
        subtitle: SubtitleRefactor = SubtitleRefactor(filename)
        subtitle.srt_to_ass()


def process_output_files(settings: Settings):
    """
        Processes output files based on user settings.

        Args:
            settings (Settings): The settings to use for processing.
    """
    files = listdir(WORKING_SPACE_OUTPUT)
    files_dict = {path.splitext(file)[0]: [] for file in files}
    for file in files:
        if not file.endswith(('.mkv', '.mp4')):
            files_dict[path.splitext(file)[0]].append(file)

    for base_name, files in files_dict.items():
        if len(files) > 0:
            # https://trac.ffmpeg.org/wiki/Encode/H.264
            # crf_value => 0 ... 18 ... 23 ... 51 ... :(
            # preset_value => 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow', 'placebo'
            subtitle_processor = MKVProcessing(filename=base_name,
                                               crf_value='18',
                                               preset_value='medium')
            subtitle_processor.process_mkv(settings)


def clear_temp_folders():
    """
        Clears temporary folders used during processing.
    """
    folders = [WORKING_SPACE_TEMP, WORKING_SPACE_TEMP_MAIN_SUBS,
               WORKING_SPACE_TEMP_ALT_SUBS]
    for folder in folders:
        rmtree(folder, ignore_errors=True)
        makedirs(folder, exist_ok=True)


@execution_timer  # ✅
def main():
    """
        Main function that runs the entire process.
    """
    display_logo()
    settings: Settings = update_settings()
    extract_tracks_from_mkv()
    refactor_subtitles()
    translate_subtitles(settings)
    convert_numbers_to_words()
    generate_audio_for_subtitles(settings)
    refactor_alt_subtitles()
    process_output_files(settings)
    clear_temp_folders()


if __name__ == '__main__':
    """
        Ensures the main function is only run if the script is executed directly (not imported as a module).
    """
    directories: List[str] = [WORKING_SPACE, WORKING_SPACE_OUTPUT,
                              WORKING_SPACE_TEMP, WORKING_SPACE_TEMP_MAIN_SUBS, WORKING_SPACE_TEMP_ALT_SUBS]
    check_and_create_directories(directories)
    main()
    console.print(
        '\n[green_italic]Naciśnij dowolny klawisz, aby zakończyć działanie programu...', end='')
    getch()