├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ └── build.yml ├── CONTRIBUTING.md ├── GICutscenesUI ├── main.py ├── subtitles.py └── web │ ├── images │ └── icon.ico │ ├── locales │ ├── en.json │ ├── fr.json │ ├── id.json │ ├── jp.json │ ├── kr.json │ ├── ru.json │ ├── subtitles_preview.json │ ├── uk.json │ ├── vi.json │ ├── zh-hans.json │ └── zh-hant.json │ ├── main.html │ ├── scripts │ ├── about.js │ ├── console.js │ ├── language.js │ ├── main.js │ └── settings.js │ └── styles │ ├── dark.css │ ├── fontawesome │ ├── css │ │ └── all.min.css │ └── webfonts │ │ ├── fa-regular-400.woff2 │ │ └── fa-solid-900.woff2 │ └── main.css ├── README.md ├── github ├── ffmpeg.exe ├── images │ ├── animation_low.gif │ ├── icons │ │ ├── CLI │ │ │ ├── cli-1.png │ │ │ └── cli-2.png │ │ └── UI │ │ │ ├── ui-1.png │ │ │ └── ui-2.png │ ├── main.png │ ├── new_main_page.png │ ├── settings_dark.png │ ├── settings_light.png │ ├── settings_old.png │ └── subtitles.png ├── requirements.txt └── ver ├── translations.md └── useful.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 2 | # patreon: # Replace with a single Patreon username 3 | # ko_fi: # Replace with a single Ko-fi username 4 | custom: ['https://donatello.to/super_zombi'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Please create all pull requests to the `dev` branch! 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build GICutscenesUI 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-latest 11 | defaults: 12 | run: 13 | shell: pwsh 14 | working-directory: ./GICutscenesUI 15 | steps: 16 | - name: Checkout the repo 17 | uses: actions/checkout@v4 18 | - name: Set up Python 3.12 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.12" 22 | - name: Install Python dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install -r ../github/requirements.txt 26 | - name: Set up GICutscenes 27 | run: | 28 | Invoke-WebRequest -Uri "https://github.com/ToaHartor/GI-cutscenes/releases/download/v0.5.0/GICutscenes-0.5.0-win-x64-standalone.zip" -OutFile ./gicutscenes.zip 29 | 7z e gicutscenes.zip appsettings.json GICutscenes.exe 30 | Invoke-WebRequest -Uri "https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/refs/heads/main/versions.json" -OutFile ./versions.json 31 | - name: Use PyInstaller to build GICutscenesUI 32 | run: pyinstaller --noconfirm --onefile --noconsole --icon="./web/images/icon.ico" --name="GICutscenesUI" --version-file="../github/ver" --add-data="web\\;.\\web" --add-data="GICutscenes.exe;." --add-data="appsettings.json;." --add-data="versions.json;." --add-data="../github/ffmpeg.exe;." main.py 33 | - name: Upload to Release 34 | uses: xresloader/upload-to-github-release@v1 35 | with: 36 | file: ./GICutscenesUI/dist/GICutscenesUI.exe 37 | overwrite: true 38 | update_latest_release: true 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Please create all pull requests to the `dev` branch! 2 | -------------------------------------------------------------------------------- /GICutscenesUI/main.py: -------------------------------------------------------------------------------- 1 | from tkinter import Tk, font 2 | from tkinter.filedialog import askdirectory, askopenfilename, askopenfilenames 3 | import eel 4 | import sys, os 5 | import shutil 6 | import subprocess 7 | import json 8 | from json_minify import json_minify 9 | import re 10 | import base64 11 | import requests 12 | from subtitles import * 13 | 14 | CONSOLE_DEBUG_MODE = False 15 | __version__ = '0.9.1' 16 | 17 | # ---- Required Functions ---- 18 | 19 | def resource_path(relative_path=""): 20 | """ Get absolute path to resource, works for dev and for PyInstaller """ 21 | base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) 22 | return os.path.join(base_path, relative_path) 23 | 24 | @eel.expose 25 | def get_version(): 26 | return __version__ 27 | 28 | # ---- Locales ---- 29 | 30 | remove_coma_regex = r'''(?<=[}\]"']),(?!\s*[{["'])''' 31 | 32 | @eel.expose 33 | def get_translation(code): 34 | tr_file = os.path.join(resource_path("web"), "locales", code + ".json") 35 | if os.path.exists(tr_file): 36 | with open(tr_file, 'r', encoding="utf-8-sig") as file: 37 | string = json_minify(file.read()) # remove comments 38 | string = re.sub(remove_coma_regex, "", string, 0) # remove coma at the end 39 | output = json.loads(string) 40 | return output 41 | 42 | def load_subs_preview_text(): 43 | file = os.path.join(resource_path("web"), "locales", "subtitles_preview.json") 44 | with open(file, 'r', encoding="utf-8-sig") as f: 45 | string = re.sub(remove_coma_regex, "", f.read(), 0) 46 | data = json.loads(string) 47 | def wraper(lang): return data.get(lang, data.get('en')) 48 | return wraper 49 | 50 | SUBTITLES_PREVIEW_TEXT = load_subs_preview_text() 51 | 52 | 53 | # ---- EXE Functions ---- 54 | 55 | def find_script(script_name): 56 | def find_in(folder): 57 | files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))] 58 | if script_name in files: 59 | return os.path.join(folder, script_name) 60 | 61 | result = find_in(os.getcwd()) 62 | if result: return result 63 | 64 | result = find_in(resource_path()) 65 | if result: return result 66 | 67 | def file_in_temp(file): 68 | return os.path.dirname(file) == os.path.dirname(resource_path(os.path.basename(file))) 69 | 70 | 71 | # ---- Settings Functions ---- 72 | 73 | def load_settings_inline(): 74 | global SCRIPT_FILE, OUTPUT_F, FFMPEG, SUBTITLES_F 75 | set_file = os.path.join(os.getcwd(), "UI-settings.json") 76 | settings = {} 77 | if os.path.exists(set_file): 78 | with open(set_file, 'r', encoding='utf-8') as file: 79 | settings = json.loads(file.read()) 80 | if "script_file" in settings.keys(): 81 | SCRIPT_FILE = settings["script_file"] 82 | if "output_folder" in settings.keys(): 83 | OUTPUT_F = settings["output_folder"] 84 | if "FFMPEG" in settings.keys(): 85 | FFMPEG = settings["FFMPEG"] 86 | if "subtitles_folder" in settings.keys(): 87 | SUBTITLES_F = settings["subtitles_folder"] 88 | 89 | SCRIPT_FILE = settings.get("script_file") or find_script("GICutscenes.exe") 90 | OUTPUT_F = settings.get("output_folder") or os.path.join(os.getcwd(), "output") 91 | FFMPEG = settings.get("FFMPEG") or find_script("ffmpeg.exe") or "ffmpeg" 92 | SUBTITLES_F = settings.get("subtitles_folder") or "" 93 | 94 | if os.path.exists(os.path.join(os.getcwd(), "versions.json")) and file_in_temp(SCRIPT_FILE): 95 | local_ver_file = os.path.join(os.getcwd(), "versions.json") 96 | temp_ver_file = os.path.join(os.path.dirname(SCRIPT_FILE), "versions.json") 97 | with open(local_ver_file, 'r') as orig_file: 98 | data = orig_file.read() 99 | with open(temp_ver_file, 'w') as temp_file: 100 | temp_file.write(data) 101 | 102 | load_settings_inline() 103 | 104 | @eel.expose 105 | def load_settings(): 106 | set_file = os.path.join(os.getcwd(), "UI-settings.json") 107 | if os.path.exists(set_file): 108 | with open(set_file, 'r', encoding='utf-8') as file: 109 | settings = json.loads(file.read()) 110 | return settings 111 | return {} 112 | 113 | @eel.expose 114 | def save_settings(settings): 115 | settings['output_folder'] = OUTPUT_F 116 | settings['subtitles_folder'] = SUBTITLES_F 117 | if not file_in_temp(SCRIPT_FILE): 118 | settings['script_file'] = SCRIPT_FILE 119 | if not file_in_temp(FFMPEG): 120 | settings['FFMPEG'] = FFMPEG 121 | 122 | with open(os.path.join(os.getcwd(), "UI-settings.json"), 'w', encoding='utf-8') as file: 123 | file.write(json.dumps(settings, indent=4, ensure_ascii=False)) 124 | return True 125 | 126 | @eel.expose 127 | def delete_settings(): 128 | global SCRIPT_FILE, OUTPUT_F, FFMPEG, SUBTITLES_F 129 | SCRIPT_FILE = find_script("GICutscenes.exe") 130 | OUTPUT_F = os.path.join(os.getcwd(), "output") 131 | FFMPEG = find_script("ffmpeg.exe") or "ffmpeg" 132 | SUBTITLES_F = "" 133 | 134 | set_file = os.path.join(os.getcwd(), "UI-settings.json") 135 | if os.path.exists(set_file): 136 | os.remove(set_file) 137 | return True 138 | return False 139 | 140 | 141 | 142 | # ---- About Tab Functions ---- 143 | 144 | @eel.expose 145 | def get_GICutscenes_ver(): 146 | if SCRIPT_FILE: 147 | process = subprocess.Popen([SCRIPT_FILE, "--version"], stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW) 148 | answer = process.communicate()[0] 149 | try: 150 | text = answer.decode('utf-8') 151 | except UnicodeDecodeError: 152 | text = answer.decode(os.device_encoding(0)) 153 | return text.strip() 154 | 155 | def get_ffmpeg_output(cmd): 156 | process = subprocess.Popen([ 157 | FFMPEG, "-hide_banner"] + cmd, 158 | stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW 159 | ) 160 | answer = process.communicate()[0] 161 | try: 162 | text = answer.decode('utf-8') 163 | except UnicodeDecodeError: 164 | text = answer.decode(os.device_encoding(0)) 165 | return text 166 | 167 | @eel.expose 168 | def get_ffmpeg_ver(): 169 | def find_ver(text): 170 | return text.splitlines()[0].split("ffmpeg version")[-1].strip().split()[0] 171 | def find_year(text): 172 | match = re.findall(r'\b([1-3][0-9]{3})\b', text) 173 | if match is not None: 174 | return match 175 | try: 176 | text = get_ffmpeg_output(['-version']) 177 | final = {'ver': find_ver(text.strip()), 'year': find_year(text.strip())} 178 | return final 179 | except: return {} 180 | 181 | 182 | def parse_releases(relative_path): 183 | r = requests.get(f'https://api.github.com/repos/{relative_path}/tags') 184 | if r.status_code == 200: 185 | answer = r.json() 186 | latest = answer[0]["name"] 187 | return latest 188 | else: 189 | r = requests.get(f'https://github.com/{relative_path}/tags') 190 | links = [] 191 | for line in r.text.split("\n"): 192 | match = re.search(r"href=\"(.+)\">", line) 193 | if match: 194 | links.append(match.group(1)) 195 | 196 | pat = re.compile(r'releases\/tag') 197 | content = [ s for s in links if pat.findall(s) ] 198 | latest = sorted(content)[-1].split("/")[-1] 199 | return latest 200 | 201 | @eel.expose 202 | def get_latest_ui_version(): 203 | return parse_releases('SuperZombi/GICutscenesUI') 204 | 205 | @eel.expose 206 | def get_latest_script_version(): 207 | return parse_releases('ToaHartor/GI-cutscenes') 208 | 209 | @eel.expose 210 | def compare_version_files(): 211 | if SCRIPT_FILE: 212 | local_ver_file = os.path.join( 213 | os.path.dirname(SCRIPT_FILE), 214 | "versions.json" 215 | ) 216 | if os.path.exists(local_ver_file): 217 | with open(local_ver_file, 'r') as file: 218 | LOCAL = file.read() 219 | 220 | try: 221 | r = requests.get("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json") 222 | GLOBAL = r.text 223 | 224 | if json.loads(LOCAL) == json.loads(GLOBAL): 225 | return {"success": True, "status": True} 226 | else: 227 | return {"success": True, "status": False} 228 | except: None 229 | return {"success": False} 230 | 231 | @eel.expose 232 | def download_latest_version_file(): 233 | r = requests.get("https://raw.githubusercontent.com/ToaHartor/GI-cutscenes/main/versions.json") 234 | local_ver_file = os.path.join(os.path.dirname(SCRIPT_FILE), "versions.json") 235 | with open(local_ver_file, 'w') as file: 236 | file.write(r.text) 237 | 238 | if file_in_temp(SCRIPT_FILE): 239 | local_ver_file = os.path.join(os.getcwd(), "versions.json") 240 | with open(local_ver_file, 'w') as file: 241 | file.write(r.text) 242 | 243 | 244 | # ---- Logger Functions ---- 245 | 246 | def send_message_to_ui_output(type_, message): 247 | eel.putMessageInOutput(type_, message)() 248 | 249 | def log_subprocess_output(pipe, process=None): 250 | for line in pipe: 251 | if process: 252 | if STOPED_BY_USER: 253 | process.kill() 254 | send_message_to_ui_output("console", line.strip()) 255 | 256 | 257 | # ---- Explorer Functions ---- 258 | 259 | def get_disks(): 260 | import win32api 261 | return [d for d in win32api.GetLogicalDriveStrings()[0]] 262 | 263 | @eel.expose 264 | def get_all_fonts(): 265 | root = Tk() 266 | root.withdraw() 267 | return sorted(set(font.families())) 268 | 269 | @eel.expose 270 | def ask_files(): 271 | root = Tk() 272 | root.withdraw() 273 | root.wm_attributes('-topmost', 1) 274 | files = askopenfilenames(initialdir=GENSHIN_FOLDER, parent=root, 275 | filetypes=[("Genshin Impact Cutscene", "*.usm"), ("All files", "*.*")] 276 | ) 277 | return files 278 | 279 | def ask_folder(): 280 | root = Tk() 281 | root.withdraw() 282 | root.wm_attributes('-topmost', 1) 283 | return askdirectory(parent=root) 284 | 285 | @eel.expose 286 | def get_output_folder(): 287 | return OUTPUT_F 288 | 289 | @eel.expose 290 | def get_subtitles_folder(): 291 | return SUBTITLES_F 292 | 293 | @eel.expose 294 | def ask_output_folder(): 295 | global OUTPUT_F 296 | folder = ask_folder() 297 | if folder: OUTPUT_F = folder 298 | return OUTPUT_F 299 | 300 | @eel.expose 301 | def ask_subtitles_folder(): 302 | global SUBTITLES_F 303 | folder = ask_folder() 304 | if folder: SUBTITLES_F = folder 305 | return SUBTITLES_F 306 | 307 | @eel.expose 308 | def open_output_folder(): 309 | if os.name == "nt": 310 | subprocess.run(['explorer', OUTPUT_F], creationflags=subprocess.CREATE_NO_WINDOW) 311 | elif os.name == "posix": 312 | subprocess.run(['xdg-open', OUTPUT_F], creationflags=subprocess.CREATE_NO_WINDOW) 313 | 314 | def find_genshin_folder(): 315 | if os.name == "nt": 316 | templates = ( 317 | ('Games', 'Genshin Impact'), 318 | ('Program Files', 'Genshin Impact'), 319 | ('Program Files', 'HoYoPlay', 'games') 320 | ) 321 | for _disk in get_disks(): 322 | disk = f'{_disk}:'+os.sep 323 | for template in templates: 324 | path = os.path.join(disk, *template) 325 | if os.path.exists(path): 326 | assets = os.path.join(path, "Genshin Impact game", "GenshinImpact_Data", "StreamingAssets", "VideoAssets", "StandaloneWindows64") 327 | if os.path.exists(assets): return assets 328 | print("[WARN] Assets not found!") 329 | GENSHIN_FOLDER = find_genshin_folder() 330 | 331 | 332 | # ---- Subtitles Functions ---- 333 | @eel.expose 334 | def make_subs_preview(args): 335 | params = { 336 | "tempfile": "temp.ass", 337 | "width": 1920, "height": 1080, 338 | **args 339 | } 340 | width, height, tempfile = params.pop("width"), params.pop("height"), params.pop("tempfile") 341 | make_subs_template(text=SUBTITLES_PREVIEW_TEXT(params.pop("lang")), file=tempfile, **params) 342 | cmd = [ 343 | FFMPEG, '-f', 'lavfi', '-i', 344 | f'color=color=black@0.0:size={width}x{height},format=rgba,subtitles={tempfile}:alpha=1', 345 | '-vframes', '1', '-f', 'image2pipe', '-vcodec', 'png', '-' 346 | ] 347 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW) 348 | output, error = process.communicate() 349 | os.remove(tempfile) 350 | if process.returncode == 0: 351 | img_base64 = base64.b64encode(output).decode('utf-8') 352 | data_url = f"data:image/png;base64,{img_base64}" 353 | return data_url 354 | 355 | 356 | # ---- GPU Functions ---- 357 | GPU_args = { 358 | "nvidia": { 359 | "decode": 'cuda', 360 | "encode": 'h264_nvenc' 361 | }, 362 | "intel": { 363 | "decode": 'qsv', 364 | "encode": 'h264_qsv' 365 | }, 366 | "amd": { 367 | "encode": "h264_amf" 368 | } 369 | } 370 | def test_encoder(encoder, decoder=None): 371 | try: 372 | cmd = [ 373 | FFMPEG, '-f', 'lavfi', '-i', 374 | 'color=black:size=360x360', 375 | '-vframes', '1', '-f', "null", 376 | '-vcodec', encoder, '-' 377 | ] 378 | process = subprocess.Popen(cmd, creationflags=subprocess.CREATE_NO_WINDOW) 379 | returncode = process.wait() 380 | if returncode == 0: return True 381 | except: None 382 | 383 | @eel.expose 384 | def get_ffmpeg_supports(): 385 | suported = [] 386 | for gpu, args in GPU_args.items(): 387 | if test_encoder(args.get("encode")): 388 | suported.append(gpu) 389 | return suported 390 | 391 | 392 | # ---- MAIN Functions ---- 393 | STOPED_BY_USER = None 394 | @eel.expose 395 | def stop_work(): 396 | global STOPED_BY_USER 397 | STOPED_BY_USER = True 398 | 399 | @eel.expose 400 | def start_work(files, args): 401 | global STOPED_BY_USER 402 | STOPED_BY_USER = False 403 | send_message_to_ui_output("event", "start") 404 | file_lenth = len(files) 405 | send_message_to_ui_output("file_count", [0, file_lenth]) 406 | # Make folders 407 | temp_folder = resource_path("Temp") 408 | if not os.path.exists(temp_folder): 409 | os.mkdir(temp_folder) 410 | if not os.path.exists(OUTPUT_F): 411 | os.mkdir(OUTPUT_F) 412 | 413 | OLD_DIR = os.getcwd() 414 | os.chdir(os.path.dirname(SCRIPT_FILE)) 415 | 416 | for i, file in enumerate(files): 417 | if STOPED_BY_USER: break 418 | else: 419 | send_message_to_ui_output("file_count", [i, file_lenth]) 420 | send_message_to_ui_output("event", "copy_files") 421 | send_message_to_ui_output("work_file", file) 422 | send_message_to_ui_output("console", f"----- {os.path.basename(file)} -----") 423 | send_message_to_ui_output("event", "run_demux") 424 | # MAIN CALL 425 | p_status = 0 426 | if CONSOLE_DEBUG_MODE: 427 | subprocess.call([SCRIPT_FILE, 'demuxUsm', file, '--output', OUTPUT_F]) 428 | else: 429 | process = subprocess.Popen([SCRIPT_FILE, 'demuxUsm', file, '--output', OUTPUT_F], encoding=os.device_encoding(0), universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NO_WINDOW) 430 | with process.stdout: 431 | log_subprocess_output(process.stdout) 432 | p_status = process.wait() 433 | 434 | if p_status != 0: 435 | send_message_to_ui_output("event", "error") 436 | send_message_to_ui_output("sub_work", {"name": "keys", "status": False}) 437 | else: 438 | send_message_to_ui_output("event", "rename_files") 439 | 440 | # Rename to m2v 441 | old_file_name = os.path.splitext(os.path.basename(file))[0] 442 | file_name = str(old_file_name) + ".ivf" 443 | new_file_name = str(old_file_name) + ".m2v" 444 | file_name = os.path.join(OUTPUT_F, file_name) 445 | new_file_name = os.path.join(OUTPUT_F, new_file_name) 446 | if os.path.exists(file_name): 447 | if os.path.exists(new_file_name): os.remove(new_file_name) 448 | os.rename(file_name, new_file_name) 449 | else: 450 | send_message_to_ui_output("console", "\n") 451 | send_message_to_ui_output("event", "error") 452 | send_message_to_ui_output("sub_work", {"name": "keys", "status": False}) 453 | continue 454 | 455 | # Delete hca encoded Audio (cuz wav files decoded) 456 | for index in [0, 1, 2, 3]: 457 | f = str(old_file_name) + "_" + str(index) + ".hca" 458 | f = os.path.join(OUTPUT_F, f) 459 | if os.path.exists(f): 460 | os.remove(f) 461 | 462 | # Merge Video and Audio 463 | if args['merge']: 464 | if STOPED_BY_USER: break 465 | else: 466 | audio_index = int(args['audio_index']) 467 | audio_file = os.path.join(OUTPUT_F , str(old_file_name) + "_" + str(audio_index) + ".wav") 468 | output_file = os.path.join(OUTPUT_F, str(old_file_name) + ".mp4") 469 | 470 | # Subtitles 471 | subtitles_file = None 472 | if args['subtitles']: 473 | send_message_to_ui_output("console", "\nSearching for subtitles") 474 | 475 | if args.get('subtitles_provider') == "local": 476 | if args.get('subtitles_folder'): 477 | subtitles = find_subtitles( 478 | old_file_name, 479 | provider=args.get('subtitles_folder'), 480 | lang=args.get('subtitles_lang') 481 | ) 482 | elif args.get('subtitles_provider') == "url": 483 | if args.get('subtitles_url'): 484 | subtitles = find_subtitles( 485 | old_file_name, 486 | provider=args.get('subtitles_url'), 487 | lang=args.get('subtitles_lang') 488 | ) 489 | else: 490 | subtitles = find_subtitles( 491 | old_file_name, 492 | provider=args.get('subtitles_provider'), 493 | lang=args.get('subtitles_lang') 494 | ) 495 | 496 | if not subtitles: 497 | send_message_to_ui_output("console", "Subtitles not found!") 498 | send_message_to_ui_output("sub_work", {"name": "subtitles", "status": False}) 499 | else: 500 | send_message_to_ui_output("console", "Converting subtitles") 501 | send_message_to_ui_output("sub_work", {"name": "subtitles", "status": True}) 502 | subtitles_file = os.path.join(OUTPUT_F, str(old_file_name) + ".ass") 503 | srt_to_ass( 504 | subtitles, 505 | subtitles_file, 506 | font_name=args.get('subtitles_font'), 507 | font_size=args.get('subtitles_fontsize'), 508 | text_color=args.get('subtitles_text_color'), 509 | outline_color=args.get('subtitles_outline_color'), 510 | outline_width=args.get('subtitles_outline_width'), 511 | letter_spacing=args.get('subtitles_letter_spacing'), 512 | bold=args.get('subtitles_bold'), 513 | italic=args.get('subtitles_italic') 514 | ) 515 | 516 | # Merging 517 | send_message_to_ui_output("event", "run_merge") 518 | send_message_to_ui_output("console", "\nStarting ffmpeg") 519 | if os.path.exists(output_file): 520 | send_message_to_ui_output("console", f'File {output_file} already exists.') 521 | os.remove(output_file) 522 | 523 | send_message_to_ui_output("console", "Working ffmpeg...") 524 | p_status = 0 525 | bitrate = int(args['video_quality']) * 1000 526 | gp_args = GPU_args[args.get('gpu')] if args.get('gpu') and args.get('gpu') in GPU_args else {} 527 | command = [FFMPEG, '-hide_banner'] 528 | if "decode" in gp_args: 529 | if gp_args['decode'] == "qsv" and subtitles_file: pass 530 | else: 531 | command += ['-hwaccel', gp_args['decode']] 532 | command += [ 533 | '-i', new_file_name, 534 | '-i', audio_file 535 | ] 536 | if subtitles_file: 537 | subs_file = subtitles_file.replace("\\", "/").replace(":", "\\\\\\:") 538 | command += ["-vf", f'subtitles={subs_file}'] 539 | if "encode" in gp_args: 540 | command += ['-c:v', gp_args['encode']] 541 | command += [ 542 | '-b:v', str(bitrate), 543 | '-b:a', '192K', 544 | output_file 545 | ] 546 | 547 | if CONSOLE_DEBUG_MODE: 548 | subprocess.call(command) 549 | else: 550 | process = subprocess.Popen(command, encoding='utf-8', universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW) 551 | with process.stderr: 552 | log_subprocess_output(process.stderr, process) 553 | p_status = process.wait() 554 | 555 | if p_status != 0: 556 | if os.path.exists(output_file): os.remove(output_file) 557 | send_message_to_ui_output("event", "error") 558 | continue 559 | else: 560 | send_message_to_ui_output("console", "Merging complete!") 561 | if args['delete_after_merge']: 562 | send_message_to_ui_output("console", "Removing trash...") 563 | if subtitles_file: os.remove(subtitles_file) 564 | files_to_remove = [ 565 | old_file_name + ".m2v", 566 | *[f"{old_file_name}_{i}.wav" for i in [0, 1, 2, 3]] 567 | ] 568 | files_to_remove = list(map(lambda x: os.path.join(OUTPUT_F, x),files_to_remove)) 569 | for f in files_to_remove: 570 | os.remove(f) 571 | send_message_to_ui_output("console", "OK") 572 | 573 | if i != file_lenth - 1: 574 | send_message_to_ui_output("console", "\n") 575 | 576 | send_message_to_ui_output("event", "ok") 577 | 578 | send_message_to_ui_output("event", "finish") 579 | if STOPED_BY_USER: 580 | send_message_to_ui_output("console", "\nStoped by user") 581 | send_message_to_ui_output("event", "stoped") 582 | else: 583 | send_message_to_ui_output("file_count", [file_lenth, file_lenth]) 584 | shutil.rmtree(temp_folder) 585 | os.chdir(OLD_DIR) 586 | 587 | 588 | eel.init(resource_path("web")) 589 | 590 | browsers = ['chrome', 'default'] 591 | for browser in browsers: 592 | try: 593 | eel.start("main.html", size=(600, 800), mode=browser, port=0) 594 | break 595 | except Exception: 596 | print(f"Failed to launch the app using {browser.title()} browser") 597 | -------------------------------------------------------------------------------- /GICutscenesUI/subtitles.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import requests 4 | from io import StringIO 5 | import pysubs2 6 | 7 | 8 | def hex_to_rgb(hex): return tuple(int(hex.lstrip('#')[i:i+2], 16) for i in (0, 2, 4)) 9 | 10 | def find_subtitles(name, lang, provider): 11 | filename = f"{name}_{lang}.srt" 12 | if provider.startswith('http'): 13 | pattern = r'https://(github|gitlab)\.com/([^/]+)/([^/]+)' 14 | match = re.search(pattern, provider) 15 | if match: 16 | site = match.group(1) 17 | author = match.group(2) 18 | repository = match.group(3) 19 | 20 | if site == "gitlab": 21 | url = f"https://gitlab.com/{author}/{repository}/raw/master/Subtitle/{lang}/{filename}" 22 | elif site == "github": 23 | url = f"https://raw.githubusercontent.com/{author}/{repository}/main/Subtitle/{lang}/{filename}" 24 | else: 25 | url = f"{provider}/{lang}/{filename}" 26 | r = requests.get(url) 27 | if r.ok: 28 | return StringIO(r.content.decode(r.encoding)) 29 | else: 30 | path = os.path.join(provider, lang, filename) 31 | if os.path.exists(path): 32 | with open(path, 'r', encoding='utf-8') as f: 33 | data = f.read() 34 | return StringIO(data) 35 | 36 | def apply_styles( 37 | font_name="Arial", font_size=14, 38 | text_color='#ffffff', 39 | outline_color='#000000', 40 | outline_width=1, letter_spacing=0, 41 | bold=False, italic=False 42 | ): 43 | style = pysubs2.SSAStyle() 44 | style.fontname = font_name 45 | style.fontsize = font_size 46 | style.primarycolor = pysubs2.Color(*hex_to_rgb(text_color)) 47 | style.outlinecolor = pysubs2.Color(*hex_to_rgb(outline_color)) 48 | style.outline = outline_width 49 | style.shadow = 0 50 | style.spacing = letter_spacing 51 | style.bold = bold 52 | style.italic = italic 53 | return style 54 | 55 | def srt_to_ass(srt_file, ass_file, **kwargs): 56 | subs = pysubs2.SSAFile.from_string(srt_file.read(), format_="srt") 57 | subs.styles["Default"] = apply_styles(**kwargs) 58 | subs.save(ass_file, format_="ass") 59 | 60 | def make_subs_template(text, file, **kwargs): 61 | subs = pysubs2.SSAFile() 62 | subs.styles["Default"] = apply_styles(**kwargs) 63 | subs.events.append( 64 | pysubs2.SSAEvent(start=0, end=1000, text=text) 65 | ) 66 | subs.save(file) 67 | -------------------------------------------------------------------------------- /GICutscenesUI/web/images/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/GICutscenesUI/web/images/icon.ico -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "settings": "Settings", 4 | "select_files": "Add Files", 5 | "select_files_dots": "Select Files:", 6 | "clear_files": "Clear", 7 | "version": "version:", 8 | "about": "About", 9 | "translations": "Translations:", 10 | 11 | "change": "Change", 12 | "reset": "Reset", 13 | "reset_title": "Delete Settings File", 14 | "remove": "Remove", 15 | "save": "Save", 16 | "save_title": "Save Settings to File", 17 | "language": "Language:", 18 | "output_folder": "Output Folder:", 19 | "console_button": "Console", 20 | "clear": "Clear", 21 | "start": "START", 22 | "stop": "STOP", 23 | "appearance": "Appearance", 24 | "theme": "Theme:", 25 | "light": "Light", 26 | "dark": "Dark", 27 | "gpu": "GPU:", 28 | "gpu_none": "None", 29 | 30 | "merging_area": "Merging", 31 | "merge": "Merge Video and Audio", 32 | "using_ffmpeg_tooltip": "With using ffmpeg", 33 | "audio_track": "Audio track:", 34 | "quality": "Quality:", 35 | "delete_after_merge": "Delete unnecessary files after merging", 36 | 37 | "subtitles_area": "Subtitles", 38 | "subtitles_checkbox": "Add subtitles", 39 | "subtitles_tooltip": "Works only if merging is enabled", 40 | "subtitles_provider": "Subtitles provider:", 41 | "subtitles_provider_local": "Local", 42 | "subtitles_provider_url": "URL", 43 | "subtitles_language": "Language:", 44 | "subtitles_font": "Font:", 45 | "subtitles_font_size": "Font size:", 46 | "subtitles_font_default": "Default", 47 | "subtitles_customization_button": "Customization", 48 | "subtitles_customization_header": "Customization of Subtitles", 49 | "subtitles_text_color": "Text color:", 50 | "subtitles_outline_color": "Outline color:", 51 | "subtitles_outline_width": "Outline width:", 52 | "subtitles_letter_spacing": "Letter spacing:", 53 | "subtitles_bold": "Bold", 54 | "subtitles_italic": "Italic", 55 | 56 | "no_files": "No Files!", 57 | "saved": "Saved!", 58 | "confirm_delete_settings": "Are you sure you want to Delete all Settings?", 59 | 60 | "copy_files": "Copying Files...", 61 | "run_demux": "Converting and Decrypting...", 62 | "rename_files": "Renaming Files...", 63 | "run_merge": "Merging Video and Audio...", 64 | "open_output_dir": "Open Output Folder", 65 | "stopped": "Stopped", 66 | 67 | "new_update": "New update!", 68 | "update": "Update", 69 | 70 | "donate_title": "Enjoying this free program?", 71 | "donate_description": "Consider supporting development with a small donation!", 72 | "donate_thanks": "Thank you for your support!", 73 | "donate_button": "Donate" 74 | } 75 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "Menu principal", 6 | "settings": "Paramètres", 7 | "select_files": "Choix des fichiers", 8 | "select_files_dots": "Choix des fichiers:", 9 | "version": "version:", 10 | "about": "À propos", 11 | "translations": "Traductions:", 12 | 13 | "change": "Modifier", 14 | "reset": "Réinitialiser", 15 | "reset_title": "Supprimer le fichier de paramètres", 16 | "save": "Enregistrer", 17 | "save_title": "Enregistrer le fichier de paramètres", 18 | "language": "Langue:", 19 | "output_folder": "Dossier de sortie:", 20 | "merge": "Fusionner la video et l'audio", 21 | "clear": "Effacer", 22 | "start": "DÉMARRER", 23 | "appearance": "Apparence", 24 | "theme": "Thème:", 25 | "light": "Clair", 26 | "dark": "Sombre", 27 | 28 | "no_files": "Aucun fichier choisi!", 29 | "select_script_file": "Choisissez le script!", 30 | "saved": "Enregistré!", 31 | "confirm_delete_settings": "Êtes-vous sûr de vouloir supprimer vos paramètres?", 32 | 33 | "using_ffmpeg_tooltip": "Utilise ffmpeg", 34 | "track_index_tooltip": "Indice de piste", 35 | 36 | "copy_files": "Copie des fichiers...", 37 | "run_demux": "Conversion et Déchiffrement...", 38 | "rename_files": "Renommage des fichiers...", 39 | "run_merge": "Fusion des pistes video et audio...", 40 | "open_output_dir": "Ouvrir le dossier de sortie", 41 | 42 | "new_update": "Mise à jour disponible!" 43 | } 44 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "Beranda", 6 | "settings": "Pengaturan", 7 | "select_files": "Tambahkan Berkas", 8 | "select_files_dots": "Pilih Berkas:", 9 | "clear_files": "Bersihkan", 10 | "version": "Versi:", 11 | "about": "Tentang", 12 | "translations": "Terjemahan:", 13 | 14 | "change": "Ubah", 15 | "reset": "Setel Ulang", 16 | "reset_title": "Hapus Berkas Pengaturan", 17 | "remove": "Hapus", 18 | "save": "Simpan", 19 | "save_title": "Simpan Pengaturan ke Berkas", 20 | "language": "Bahasa:", 21 | "output_folder": "Folder Output:", 22 | "console_button": "konsol", 23 | "delete_after_merge": "Hapus berkas tidak diperlukan setelah penggabungan", 24 | "clear": "Bersihkan", 25 | "start": "Mulai", 26 | "stop": "BERHENTI", 27 | "appearance": "Tampilan", 28 | "theme": "Tema:", 29 | "light": "Terang", 30 | "dark": "Gelap", 31 | "gpu": "GPU:", 32 | "gpu_none": "Nonaktif", 33 | 34 | "merging_area": "Menggabungkan", 35 | "merge": "Gabungkan Video dan Audio", 36 | "using_ffmpeg_tooltip": "Memakai ffmpeg", 37 | "audio_track": "Trek audio:", 38 | "quality": "Kualitas:", 39 | 40 | "subtitles_area": "Subtitle", 41 | "subtitles_checkbox": "Tambahkan subtitle", 42 | "subtitles_tooltip": "Hanya bekerja jika penggabungan diaktifkan", 43 | "subtitles_provider": "Penyedia subtitle:", 44 | "subtitles_provider_local": "Lokal", 45 | "subtitles_provider_url": "URL", 46 | "subtitles_language": "Bahasa:", 47 | "subtitles_font": "Huruf:", 48 | "subtitles_font_size": "Ukuran huruf:", 49 | "subtitles_font_default": "Default", 50 | "subtitles_customization_button": "Personalisasi", 51 | "subtitles_customization_header": "Personalisasi Subtitle", 52 | "subtitles_text_color": "Warna teks:", 53 | "subtitles_outline_color": "Warna outline:", 54 | "subtitles_outline_width": "Lebar outline:", 55 | "subtitles_letter_spacing": "Jarak antar huruf:", 56 | "subtitles_bold": "Tebal", 57 | "subtitles_italic": "Miring", 58 | 59 | "no_files": "Berkas Kosong!", 60 | "select_script_file": "Pilih Berkas Script!", 61 | "saved": "Tersimpan!", 62 | "confirm_delete_settings": "Apakah Anda Yakin Ingin Menghapus Semua Pengaturan?", 63 | 64 | "copy_files": "Menyalin Berkas...", 65 | "run_demux": "Mengonversi dan Mendekripsi...", 66 | "rename_files": "Ubah Nama Berkas...", 67 | "run_merge": "Menggabungkan Video dan Audio...", 68 | "open_output_dir": "Buka Folder Output", 69 | "stopped": "Terhenti", 70 | 71 | "new_update": "Pembaruan Baru!", 72 | "update": "Perbarui", 73 | 74 | "donate_title": "Suka Program Gratis Ini?", 75 | "donate_description": "Pertimbangkan untuk mendukung pengembangan dengan donasi kecil!", 76 | "donate_thanks": "Terima kasih atas dukungan anda!", 77 | "donate_button": "Donasi" 78 | } 79 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "ホーム", 6 | "settings": "設定", 7 | "select_files": "ファイルを追加", 8 | "select_files_dots": "ファイルを選択:", 9 | "clear_files": "削除", 10 | "version": "バージョン:", 11 | "about": "詳細", 12 | "translations": "翻訳:", 13 | 14 | "change": "変更", 15 | "reset": "設定の初期化", 16 | "reset_title": "設定ファイルの削除", 17 | "save": "保存", 18 | "save_title": "設定ファイルを保存", 19 | "language": "言語:", 20 | "output_folder": "出力先フォルダ:", 21 | "merge": "映像と音声の統合", 22 | "delete_after_merge": "融合後に不要なファイルを削除", 23 | "clear": "ログを削除", 24 | "start": "開始", 25 | "stop": "停止", 26 | "appearance": "外観", 27 | "theme": "テーマ:", 28 | "light": "ライト", 29 | "dark": "ダーク", 30 | 31 | "no_files": "ファイルが選択されていません", 32 | "select_script_file": "スクリプトファイルを選択してください", 33 | "saved": "保存完了", 34 | "confirm_delete_settings": "本当に全ての設定を初期化してもよろしいですか?", 35 | 36 | "using_ffmpeg_tooltip": "ffmpegを使う", 37 | "track_index_tooltip": "トラックインデックス", 38 | 39 | "copy_files": "ファイルコピー中...", 40 | "run_demux": "変換と復号中...", 41 | "rename_files": "ファイル名置換中...", 42 | "run_merge": "映像と音声を融合中...", 43 | "open_output_dir": "出力フォルダを開く", 44 | "stopped": "停止しました", 45 | 46 | "new_update": "新しいアップデートがあります", 47 | "update": "アップデート" 48 | } 49 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "홈 화면", 6 | "settings": "설정", 7 | "select_files": "파일 선택", 8 | "select_files_dots": "파일 선택:", 9 | "version": "버전:", 10 | "about": "정보", 11 | "translations": "번역:", 12 | 13 | "change": "선택", 14 | "reset": "리셋", 15 | "reset_title": "설정 파일을 삭제", 16 | "save": "저장", 17 | "save_title": "설정 파일을 저장", 18 | "language": "언어:", 19 | "output_folder": "출력 폴더 지정:", 20 | "merge": "비디오와 오디오를 통합", 21 | "clear": "삭제", 22 | "start": "시작", 23 | "appearance": "언어 및 색상", 24 | "theme": "색상:", 25 | "light": "화이트", 26 | "dark": "블랙", 27 | 28 | "no_files": "파일이 존재하지않음!", 29 | "select_script_file": "스크립트 파일을 선택!", 30 | "saved": "저장 완료!", 31 | "confirm_delete_settings": "정말로 이 설정을 삭제하시겠습니까?", 32 | 33 | "using_ffmpeg_tooltip": "ffmpeg 파일을 사용", 34 | "track_index_tooltip": "오디오 언어 목록", 35 | 36 | "copy_files": "파일 복사중...", 37 | "run_demux": "전환 및 해독중...", 38 | "rename_files": "파일 제목을 변경하는중...", 39 | "run_merge": "비디오와 오디오를 통합하는중...", 40 | "open_output_dir": "폴더 열기", 41 | 42 | "new_update": "새로운 업데이트!" 43 | } 44 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "Главная", 6 | "settings": "Настройки", 7 | "select_files": "Добавить Файлы", 8 | "select_files_dots": "Выберите Файлы:", 9 | "clear_files": "Очистить", 10 | "version": "версия:", 11 | "about": "О программе", 12 | "translations": "Переводы:", 13 | 14 | "change": "Изменить", 15 | "reset": "Сбросить", 16 | "reset_title": "Удалить Файл с Настройками", 17 | "remove": "Убрать", 18 | "save": "Сохранить", 19 | "save_title": "Сохранить Настройки в Файл", 20 | "language": "Язык:", 21 | "output_folder": "Папка Вывода:", 22 | "console_button": "Консоль", 23 | "clear": "Очистить", 24 | "start": "СТАРТ", 25 | "stop": "СТОП", 26 | "appearance": "Внешний вид", 27 | "theme": "Тема:", 28 | "light": "Светлая", 29 | "dark": "Тёмная", 30 | "gpu": "Видеокарта:", 31 | "gpu_none": "Нет", 32 | 33 | "merging_area": "Рендеринг", 34 | "merge": "Объединить Видео и Аудио", 35 | "using_ffmpeg_tooltip": "С помощью ffmpeg", 36 | "audio_track": "Аудио дорожка:", 37 | "quality": "Качество:", 38 | "delete_after_merge": "Удалить ненужные файлы после объединения", 39 | 40 | "subtitles_area": "Субтитры", 41 | "subtitles_checkbox": "Добавить субтитры", 42 | "subtitles_tooltip": "Работает только если включен рендеринг", 43 | "subtitles_provider": "Расположение субтитров:", 44 | "subtitles_provider_local": "Выбрать папку", 45 | "subtitles_language": "Язык:", 46 | "subtitles_font": "Шрифт:", 47 | "subtitles_font_size": "Размер шрифта:", 48 | "subtitles_font_default": "По умолчанию", 49 | "subtitles_customization_button": "Внешний вид", 50 | "subtitles_customization_header": "Настройка субтитров", 51 | "subtitles_text_color": "Цвет текста:", 52 | "subtitles_outline_color": "Цвет обводки:", 53 | "subtitles_outline_width": "Ширина обводки:", 54 | "subtitles_letter_spacing": "Межбуквенный интервал:", 55 | "subtitles_bold": "Жирный", 56 | "subtitles_italic": "Курсивный", 57 | 58 | "no_files": "Нет Файлов!", 59 | "saved": "Сохранено!", 60 | "confirm_delete_settings": "Вы уверены, что хотите Удалить все Настройки?", 61 | 62 | "copy_files": "Копирование файлов...", 63 | "run_demux": "Конвертация и Расшифровка...", 64 | "rename_files": "Переименование файлов...", 65 | "run_merge": "Объединение Видео и Аудио...", 66 | "open_output_dir": "Открыть Папку Вывода", 67 | "stopped": "Остановлено", 68 | 69 | "new_update": "Новое обновление!", 70 | "update": "Обновить", 71 | 72 | "donate_title": "Нравится эта бесплатная программа?", 73 | "donate_description": "Поддержите развитие небольшим пожертвованием!", 74 | "donate_thanks": "Спасибо за Вашу поддержку!", 75 | "donate_button": "Пожертвовать" 76 | } 77 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/subtitles_preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": "Subtitles, will look like this!", 3 | "ru": "Субтитры, будут выглядеть так!", 4 | "id": "Subtitle, akan terlihat seperti ini!", 5 | } 6 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "ru" 4 | }, 5 | "home": "Головна", 6 | "settings": "Налаштування", 7 | "select_files": "Додати Файли", 8 | "select_files_dots": "Виберіть Файли:", 9 | "clear_files": "Очистити", 10 | "version": "версія:", 11 | "about": "Про програму", 12 | "translations": "Переклади:", 13 | 14 | "change": "Змінити", 15 | "reset": "Скинути", 16 | "reset_title": "Видалити Файл з Налаштуваннями", 17 | "remove": "Прибрати", 18 | "save": "Зберегти", 19 | "save_title": "Зберегти Налаштування у Файл", 20 | "language": "Мова:", 21 | "output_folder": "Папка Виводу:", 22 | "console_button": "Консоль", 23 | "clear": "Очистити", 24 | "start": "СТАРТ", 25 | "stop": "СТОП", 26 | "appearance": "Зовнішній вигляд", 27 | "theme": "Тема:", 28 | "light": "Світла", 29 | "dark": "Темна", 30 | "gpu": "Відеокарта:", 31 | "gpu_none": "Ні", 32 | 33 | "merging_area": "Рендеринг", 34 | "merge": "Об'єднати Відео та Аудіо", 35 | "using_ffmpeg_tooltip": "За допомогою ffmpeg", 36 | "audio_track": "Аудіо доріжка:", 37 | "quality": "Якість:", 38 | "delete_after_merge": "Видалити непотрібні файли після об’єднання", 39 | 40 | "subtitles_area": "Субтитри", 41 | "subtitles_checkbox": "Додати субтитри", 42 | "subtitles_tooltip": "Працює тільки якщо увімкнено рендеринг", 43 | "subtitles_provider": "Розташування субтитрів:", 44 | "subtitles_provider_local": "Вибрати папку", 45 | "subtitles_language": "Мова:", 46 | "subtitles_font": "Шрифт:", 47 | "subtitles_font_size": "Розмір шрифту:", 48 | "subtitles_font_default": "Стандартний", 49 | "subtitles_customization_button": "Зовнішній вигляд", 50 | "subtitles_customization_header": "Налаштування субтитрів", 51 | "subtitles_text_color": "Колір тексту:", 52 | "subtitles_outline_color": "Колір контуру:", 53 | "subtitles_outline_width": "Ширина контуру:", 54 | "subtitles_letter_spacing": "Міжлітерний інтервал:", 55 | "subtitles_bold": "Жирний", 56 | "subtitles_italic": "Курсивний", 57 | 58 | "no_files": "Немає Файлів!", 59 | "saved": "Збережено!", 60 | "confirm_delete_settings": "Ви впевнені, що хочете Видалити всі Налаштування?", 61 | 62 | "copy_files": "Копіювання файлів...", 63 | "run_demux": "Конвертація та Розшифрування...", 64 | "rename_files": "Перейменування файлів...", 65 | "run_merge": "Об'єднання Відео та Аудіо...", 66 | "open_output_dir": "Відкрити Вихідну Папку", 67 | "stopped": "Зупинено", 68 | 69 | "new_update": "Нове оновлення!", 70 | "update": "Оновити", 71 | 72 | "donate_title": "Подобається ця безкоштовна програма?", 73 | "donate_description": "Підтримайте розвиток невеликим внеском!", 74 | "donate_thanks": "Дякуємо за Вашу підтримку!", 75 | "donate_button": "Пожертвувати" 76 | } 77 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/vi.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "Trang Chủ", 6 | "settings": "Cài Đặt", 7 | "select_files": "Chọn File", 8 | "select_files_dots": "Chọn File:", 9 | "version": "phiên bản:", 10 | "about": "Về ứng dụng", 11 | "translations": "Phiên dịch:", 12 | 13 | "change": "Thay đổi", 14 | "reset": "Đặt lại", 15 | "reset_title": "Xoá file cài đặt", 16 | "save": "Lưu", 17 | "save_title": "Lưu cài đặt vào file", 18 | "language": "Ngôn ngữ:", 19 | "output_folder": "Thư mục đầu ra:", 20 | "merge": "Ghép video và âm thanh", 21 | "clear": "Xoá", 22 | "start": "BẮT ĐẦU", 23 | "appearance": "Giao diện", 24 | "theme": "Chủ đề:", 25 | "light": "Sáng", 26 | "dark": "Tối", 27 | 28 | "no_files": "Không có file!", 29 | "select_script_file": "Chọn file script!", 30 | "saved": "Đã lưu!", 31 | "confirm_delete_settings": "Bạn có muốn xoá hết tất cả cài đặt?", 32 | 33 | "using_ffmpeg_tooltip": "Sử dụng FFMPEG", 34 | "track_index_tooltip": "Track index", 35 | 36 | "copy_files": "Sao chép file...", 37 | "run_demux": "Đang giải mã và chuyển đổi...", 38 | "rename_files": "Đang đổi tên file...", 39 | "run_merge": "Ghép video và âm thanh...", 40 | "open_output_dir": "Mở thư mục đầu ra", 41 | 42 | "new_update": "Cập nhật mới!" 43 | } 44 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/zh-hans.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "en" 4 | }, 5 | "home": "主页", 6 | "settings": "设置", 7 | "select_files": "选择文件", 8 | "select_files_dots": "选择文件:", 9 | "version": "版本:", 10 | "about": "关于", 11 | "translations": "翻译人员:", 12 | 13 | "change": "更改", 14 | "reset": "重置", 15 | "reset_title": "清空设置文件", 16 | "save": "保存", 17 | "save_title": "导出设置到文件", 18 | "language": "语言:", 19 | "output_folder": "输出文件夹:", 20 | "clear": "清空", 21 | "start": "开始", 22 | "stop": "停止", 23 | "appearance": "外观", 24 | "theme": "主题:", 25 | "light": "浅色", 26 | "dark": "深色", 27 | 28 | "merging_area": "合并", 29 | "merge": "合并视频和音频", 30 | "using_ffmpeg_tooltip": "使用 ffmpeg", 31 | "audio_track": "音轨语言:", 32 | "quality": "品质:", 33 | "delete_after_merge": "合并后删除不必要的文件", 34 | 35 | "subtitles_area": "字幕", 36 | "subtitles_checkbox": "添加字幕", 37 | "subtitles_tooltip": "仅在启用合并功能时可用", 38 | "subtitles_provider": "字幕来源:", 39 | "subtitles_provider_local": "本地", 40 | "subtitles_provider_url": "URL", 41 | "subtitles_language": "语言:", 42 | "subtitles_font": "字体:", 43 | "subtitles_font_size": "字号:", 44 | "subtitles_font_default": "默认", 45 | "subtitles_customization_button": "自定义", 46 | "subtitles_customization_header": "自定义字幕", 47 | "subtitles_text_color": "文本颜色:", 48 | "subtitles_outline_color": "轮廓颜色:", 49 | "subtitles_outline_width": "轮廓宽度:", 50 | "subtitles_letter_spacing": "字符间距:", 51 | "subtitles_bold": "粗体", 52 | "subtitles_italic": "斜体", 53 | 54 | "no_files": "无文件!", 55 | "select_script_file": "选择脚本文件!", 56 | "saved": "已保存!", 57 | "confirm_delete_settings": "确定删除所有设置吗?", 58 | 59 | "copy_files": "正在复制文件...", 60 | "run_demux": "正在转换和解密...", 61 | "rename_files": "正在重命名文件...", 62 | "run_merge": "正在合并视频和音频...", 63 | "open_output_dir": "打开输出文件夹", 64 | "stopped": "停止", 65 | 66 | "new_update": "新版本可用!", 67 | "update": "升级", 68 | 69 | "donate_title": "喜欢这个免费程序吗?", 70 | "donate_description": "你可以考虑通过小额捐款来支持开发工作!", 71 | "donate_thanks": "感谢你的支持!", 72 | "donate_button": "捐赠" 73 | } 74 | -------------------------------------------------------------------------------- /GICutscenesUI/web/locales/zh-hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "__root__": { 3 | "inherit": "zh-hans" 4 | }, 5 | "home": "主頁", 6 | "settings": "設置", 7 | "select_files": "選擇文件", 8 | "select_files_dots": "選擇文件:", 9 | "version": "版本:", 10 | "about": "關於", 11 | "translations": "翻譯人員:", 12 | 13 | "change": "更改", 14 | "reset": "重置", 15 | "reset_title": "清空設置文件", 16 | "save": "保存", 17 | "save_title": "導出設置到文件", 18 | "language": "語言:", 19 | "output_folder": "輸出文件夾:", 20 | "merge": "合並視頻和音頻", 21 | "clear": "清空", 22 | "start": "開始", 23 | "appearance": "外觀", 24 | "theme": "主題:", 25 | "light": "淺色", 26 | "dark": "深色", 27 | 28 | "no_files": "無文件!", 29 | "select_script_file": "選擇腳本文件!", 30 | "saved": "已保存!", 31 | "confirm_delete_settings": "確定刪除所有設置嗎?", 32 | 33 | "using_ffmpeg_tooltip": "使用 ffmpeg", 34 | "track_index_tooltip": "音軌列表", 35 | 36 | "copy_files": "正在復製文件...", 37 | "run_demux": "正在轉換和解密...", 38 | "rename_files": "正在重命名文件...", 39 | "run_merge": "正在合並視頻和音頻...", 40 | "open_output_dir": "打開輸出文件夾", 41 | 42 | "new_update": "新的版本可用!" 43 | } 44 | -------------------------------------------------------------------------------- /GICutscenesUI/web/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GICutscenes 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 |

Select Files:

31 |
32 | 33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 | 0/0 42 | 43 |
44 |
45 | 46 | 0% 47 |
48 |
49 |

50 | 51 |
52 | 53 | 54 |
55 |

56 | 57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 | 70 | 71 |
72 | 73 |
74 | Output Folder: 75 | 76 | 77 |
78 | 79 |
80 | Appearance 81 |
82 |
83 | 84 | Language: 85 | 86 | 98 |
99 | 100 |
101 | Theme: 102 | 106 |
107 |
108 |
109 | 110 |
111 | Merging 112 |

113 | 114 | 115 | 116 |

117 |

118 | 119 | 125 |

126 |

127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |

136 |

137 | 138 | 141 |

142 |

143 | 144 | 145 |

146 |
147 | 148 |
149 | Subtitles 150 |

151 | 152 | 153 | 154 |

155 |
156 | 157 | 163 |
164 | 165 | 166 |
167 |
168 | 169 |
170 | 171 | 172 | 189 | 190 | 191 |
192 |
193 |
194 | 195 | 196 |
197 |

Customize Subtitles

198 |

199 |
200 | 201 | 204 | 205 | 206 | 207 | 208 | 209 |
210 | 214 | 218 |
219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 |
232 |

233 | 234 |

235 |
236 | 237 | 238 | 239 |
240 |

Genshin Impact Cutscenes

241 |
242 | version: 243 |
244 | 245 |

246 | 247 | 248 | GitHub 249 | 250 |

251 |
252 |
253 |
254 | 255 | 256 | 257 | 258 | 259 | 260 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 |
GI-cutscenes:
Ffmpeg:
272 |
273 | 274 | 275 | 276 | 277 | 278 | 279 |
280 |

281 | 282 |
283 | Translations: 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 |
English:SuperZombi
Russian:SuperZombi
Ukrainian:SuperZombi
Simplified Chinese:zhxycn
Traditional Chinese:zhxycn
Indonesian:GID
French:ToaHartor
Vietnamese:Lucas
Korean:yunsungwook
Japanese:コトレ
326 |
327 |
328 | 329 | 330 | 331 | 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /GICutscenesUI/web/scripts/about.js: -------------------------------------------------------------------------------- 1 | var APP_VERSION; 2 | var info_loaded = false; 3 | async function load_about_info(){ 4 | if (!info_loaded){ 5 | info_loaded = true; 6 | APP_VERSION = await eel.get_version()(); 7 | document.getElementById("version").innerHTML = APP_VERSION; 8 | 9 | let script_ver = await eel.get_GICutscenes_ver()(); 10 | let ffmpeg_ver = await eel.get_ffmpeg_ver()(); 11 | 12 | if (script_ver){ 13 | document.getElementById("script_ver").innerHTML = script_ver 14 | } 15 | if (Object.keys(ffmpeg_ver).length > 0){ 16 | document.getElementById("ffmpeg_ver").innerHTML = ffmpeg_ver.ver 17 | if (ffmpeg_ver.year.length > 0){ 18 | document.getElementById("ffmpeg_year_line").style.display = "table-row" 19 | document.getElementById("ffmpeg_year").innerHTML = ffmpeg_ver.year 20 | } 21 | } 22 | 23 | let latest_ui_ver = await eel.get_latest_ui_version()(); 24 | let latest_script_ver = await eel.get_latest_script_version()(); 25 | 26 | if (latest_ui_ver){ 27 | if ( is_this_new_ver(APP_VERSION, latest_ui_ver) ){ 28 | let el = document.createElement('code') 29 | el.className = "update" 30 | el.title = LANG("new_update") 31 | el.setAttribute("translation", "__title:new_update__") 32 | el.innerHTML = latest_ui_ver 33 | document.getElementById("version").parentNode.innerHTML += "" 34 | document.getElementById("version").parentNode.appendChild(el) 35 | } 36 | } 37 | if (latest_script_ver && script_ver){ 38 | if ( is_this_new_ver(script_ver, latest_script_ver) ){ 39 | let el = document.createElement('code') 40 | el.className = "update" 41 | el.title = LANG("new_update") 42 | el.setAttribute("translation", "__title:new_update__") 43 | el.innerHTML = latest_script_ver 44 | document.getElementById("script_ver").parentNode.innerHTML += "" 45 | document.getElementById("script_ver").parentNode.appendChild(el) 46 | } 47 | } 48 | 49 | await load_version_file(); 50 | 51 | document.getElementById("loading").style.display = "none" 52 | } 53 | } 54 | 55 | async function load_version_file(){ 56 | let compare_version_files = await eel.compare_version_files()(); 57 | 58 | if (compare_version_files["success"]){ 59 | document.getElementById("compare_ver_files_line").style.display = "table-row" 60 | if (compare_version_files["status"]){ 61 | document.getElementById("ver_file_status").innerHTML = "OK" 62 | document.getElementById("ver_file_status").classList.remove("update") 63 | document.getElementById("ver_file_status").removeAttribute("translation") 64 | let button = document.getElementById("ver_file_status").parentNode.querySelector("button") 65 | if (button){ button.remove() } 66 | } 67 | else{ 68 | document.getElementById("ver_file_status").setAttribute("translation", "__new_update__") 69 | document.getElementById("ver_file_status").classList.add("update") 70 | document.getElementById("ver_file_status").innerHTML = LANG("new_update") 71 | let button = document.createElement("button") 72 | button.classList.add("input_element") 73 | button.style.marginLeft = "5px" 74 | button.innerHTML = LANG("update") 75 | button.setAttribute("translation", "__update__") 76 | button.onclick = async () => { 77 | document.getElementById("loading").style.display = "block"; 78 | await eel.download_latest_version_file()(); 79 | await load_version_file(); 80 | document.getElementById("loading").style.display = "none"; 81 | } 82 | document.getElementById("ver_file_status").parentNode.appendChild(button) 83 | } 84 | } 85 | } 86 | 87 | 88 | function allow_reload_info(){ 89 | info_loaded = false; 90 | document.getElementById("loading").style.display = "block" 91 | } 92 | 93 | function is_this_new_ver(old_ver, new_ver){ 94 | function clean(string){ return string.replace(/[^0-9. ]/g, "") } 95 | function asNum(string){ return parseInt(clean(string).split(".").join("")) } 96 | return asNum(new_ver) > asNum(old_ver); 97 | } 98 | -------------------------------------------------------------------------------- /GICutscenesUI/web/scripts/console.js: -------------------------------------------------------------------------------- 1 | function clearConsole(){ 2 | document.getElementById("output").value = ""; 3 | } 4 | function showConsole(){ 5 | document.querySelector("#show_console").classList.add("hide") 6 | document.querySelector("#console_wrapper").classList.remove("hide") 7 | } 8 | 9 | var maxDuration = 1; 10 | 11 | eel.expose(putMessageInOutput); 12 | function putMessageInOutput(type, message) { 13 | if (type == "console"){ 14 | if (message){ 15 | const outputNode = document.querySelector('#output'); 16 | 17 | if (message.toLowerCase().includes("duration")){ 18 | message.replace(/\Duration:(.*?)\,/g, (match, contents)=>{ 19 | maxDuration = durationToSeconds(contents.trim()) 20 | }) 21 | } 22 | 23 | if (message.includes("frame")){ 24 | var last_line = outputNode.value.split('\n').filter(n=>n).at(-1); 25 | if (last_line && last_line.includes("frame")){ 26 | outputNode.value = outputNode.value.substring(0, outputNode.value.lastIndexOf(last_line)) 27 | } 28 | } 29 | 30 | if (message.includes("time")){ 31 | message.replace(/\.*time=(.*?) /g, (match, contents)=>{ 32 | let current_dur = durationToSeconds(contents.trim()) 33 | if (current_dur){ 34 | let current_percents = current_dur * 100 / maxDuration; 35 | let percents_rounded = Math.min( 36 | Math.max(0, 37 | Math.round(current_percents) 38 | ) 39 | , 100) 40 | document.querySelector("#ffmpeg_progress").value = percents_rounded 41 | document.querySelector("#ffmpeg_progress_text").innerHTML = percents_rounded + "%" 42 | } 43 | }) 44 | } 45 | 46 | outputNode.value += message; 47 | if (!message.endsWith('\n')) { 48 | outputNode.value += '\n'; 49 | } 50 | outputNode.scrollTo(0, outputNode.scrollHeight); 51 | } 52 | } 53 | else if (type == "file_count"){ 54 | document.getElementById("progress_bar").value = message[0] 55 | document.getElementById("progress_bar").max = message[1] 56 | document.getElementById("current_progress").innerHTML = message[0] + "/" + message[1] 57 | } 58 | else if (type == "event"){ 59 | let work_status = document.getElementById("current_work") 60 | if (message == "finish"){ 61 | BLOCK_START = false; 62 | work_status.innerHTML = "" 63 | toggleStartStop("start") 64 | document.querySelector("#open_dir").classList.remove("hide") 65 | document.querySelector("#browse-area").classList.remove("hide") 66 | document.querySelector("#preview_zone").classList.remove("no-remove") 67 | document.querySelector("#progress").classList.add("hide") 68 | document.querySelector("#ffmpeg_progress_area").classList.add("hide") 69 | } 70 | else if (message == "start"){ 71 | BLOCK_START = true; 72 | toggleStartStop("stop") 73 | document.querySelector("#open_dir").classList.add("hide") 74 | document.querySelector("#browse-area").classList.add("hide") 75 | document.querySelector("#preview_zone").classList.add("no-remove") 76 | document.querySelector("#progress").classList.remove("hide") 77 | if (document.querySelector("#console_wrapper").classList.contains("hide")){ 78 | document.querySelector("#show_console").classList.remove("hide") 79 | } 80 | } 81 | else if (message == "run_merge"){ 82 | document.querySelector("#ffmpeg_progress_area").classList.remove("hide") 83 | work_status.innerHTML = LANG(message) 84 | } 85 | else if (message == "ok" || message == "error" || message == "stoped"){ 86 | file_status_in_list(message) 87 | if (message == "stoped"){ 88 | work_status.innerHTML = LANG("Stopped") 89 | } 90 | } 91 | else{ 92 | work_status.innerHTML = LANG(message) 93 | } 94 | } 95 | else if (type == "work_file"){ 96 | document.querySelector("#ffmpeg_progress_area").classList.add("hide") 97 | document.querySelector("#ffmpeg_progress").value = 0 98 | document.querySelector("#ffmpeg_progress_text").innerHTML = "0%" 99 | file_status_in_list() 100 | } 101 | else if (type == "sub_work"){ 102 | addWorkToFile(message.name, message.status) 103 | } 104 | else{ 105 | console.log(message) 106 | } 107 | } 108 | 109 | function file_status_in_list(status="now"){ 110 | let index = document.getElementById("progress_bar").value 111 | let element = document.querySelectorAll("#preview_zone > div")[index] 112 | if (status == "ok"){ 113 | element.setClass("ok") 114 | element.setIcon("fa-regular fa-circle-check") 115 | } 116 | else if (status == "now"){ 117 | element.setClass("now") 118 | element.setIcon("fa-solid fa-spinner fa-spin") 119 | } 120 | else if (status == "error"){ 121 | element.setClass("error") 122 | element.setIcon("fa-regular fa-circle-exclamation") 123 | } 124 | else if (status == "stoped"){ 125 | element.setClass("stoped") 126 | element.setIcon("fa-regular fa-circle-stop") 127 | } 128 | } 129 | function addWorkToFile(workname, status=true){ 130 | let index = document.getElementById("progress_bar").value 131 | let element = document.querySelectorAll("#preview_zone > div")[index] 132 | if (workname == "subtitles"){ 133 | element.addStatus(`fa-solid fa-closed-captioning ${status?"ok":"error"}`) 134 | } 135 | else if (workname == "keys"){ 136 | element.addStatus(`fa-solid fa-key ${status?"ok":"error"}`) 137 | } 138 | } 139 | 140 | function toggleStartStop(action="start"){ 141 | let start_but = document.querySelector("#start") 142 | let stop_but = document.querySelector("#stop") 143 | if (action == "stop"){ 144 | start_but.disabled = true; 145 | stop_but.disabled = false; 146 | start_but.classList.add("hide") 147 | stop_but.classList.remove("hide") 148 | } else { 149 | stop_but.disabled = true; 150 | start_but.disabled = false; 151 | stop_but.classList.add("hide") 152 | start_but.classList.remove("hide") 153 | } 154 | } 155 | 156 | 157 | function durationToSeconds(hms){ 158 | let a = hms.split(":") 159 | let seconds = (+a[0]) * 60 * 60 + (+a[1]) * 60 + (+a[2]); 160 | return seconds 161 | } 162 | -------------------------------------------------------------------------------- /GICutscenesUI/web/scripts/language.js: -------------------------------------------------------------------------------- 1 | var LANGUAGE; 2 | function LANG(word){ 3 | return LANGUAGE[word] ? LANGUAGE[word] : word; 4 | } 5 | async function getTranslation(){ 6 | let translation = document.querySelector('.settings_element[name=lang]').value 7 | LANGUAGE = await getTranslationAsk(translation) 8 | localizeHtmlPage(LANGUAGE); 9 | } 10 | async function getTranslationAsk(lang){ 11 | var lang_file = await eel.get_translation(lang)() 12 | if (lang_file){ 13 | if (lang_file.__root__?.inherit){ 14 | let parrent_translate = await getTranslationAsk(lang_file.__root__.inherit) 15 | return {...parrent_translate, ...lang_file} 16 | } 17 | return lang_file 18 | } 19 | } 20 | function localizeHtmlPage(lang) 21 | { 22 | var objects = document.querySelectorAll('[translation]'); 23 | for (var j = 0; j < objects.length; j++) 24 | { 25 | function trim(str, ch) { 26 | var start = 0, end = str.length; 27 | while(start < end && str[start] === ch) 28 | ++start; 29 | while(end > start && str[end - 1] === ch) 30 | --end; 31 | return (start > 0 || end < str.length) ? str.substring(start, end) : str; 32 | } 33 | 34 | let atr = objects[j].getAttribute("translation") 35 | atr.split(" ").forEach(e=>{ 36 | let cur = trim(e, "_") 37 | let arr = cur.split(":") 38 | if (arr.length > 1){ 39 | if (lang[arr[1]]){ 40 | objects[j].setAttribute(arr[0], lang[arr[1]]) 41 | } 42 | } 43 | else{ 44 | if (lang[arr[0]]){ 45 | objects[j].innerHTML = lang[arr[0]] 46 | } 47 | } 48 | }) 49 | } 50 | } -------------------------------------------------------------------------------- /GICutscenesUI/web/scripts/main.js: -------------------------------------------------------------------------------- 1 | (async _=>{ 2 | await load_settings() 3 | changeTheme() 4 | await getTranslation() 5 | get_output_folder() 6 | get_subtitles_folder() 7 | get_all_fonts() 8 | get_encoders() 9 | load_settings(["subtitles_font", "gpu"]) 10 | init_subtitles_preview() 11 | let tab_now = window.location.hash.split("#").at(-1) 12 | if (tab_now){ 13 | openTab(tab_now) 14 | } 15 | setTimeout(_=>{ 16 | document.querySelector("#loader-area").classList.add("hidden") 17 | }, 50) 18 | })() 19 | 20 | function openTab(tab){ 21 | window.location.hash = tab 22 | document.querySelector(".tabcontent.showed").classList.remove("showed") 23 | document.getElementById(tab).classList.add("showed") 24 | document.querySelector(".tabs > button.active").classList.remove("active") 25 | document.querySelector(`.tabs > button[data=${tab}]`).classList.add("active") 26 | setTimeout(_=>{window.scrollTo(0, 0)}) 27 | if (tab == "about"){ 28 | load_about_info() 29 | } 30 | } 31 | 32 | function update_path(path, input){ 33 | if (path){ 34 | input.value = path 35 | input.style.width = input.value.length + "ch"; 36 | input.style.borderColor = "" 37 | } 38 | else{ 39 | input.style.borderColor = "red" 40 | } 41 | } 42 | 43 | 44 | async function selectFiles(){ 45 | let files = await eel.ask_files()(); 46 | if (files && files.length > 0){ 47 | updatePreview(files) 48 | } 49 | } 50 | function updatePreview(files){ 51 | removeIcons_and_color() 52 | let parent = document.getElementById("preview_zone") 53 | files.forEach(e=>{ 54 | if (!parent.querySelector(`.file[path='${e}']`)){ 55 | parent.appendChild(addFilePreview(e)) 56 | } 57 | }) 58 | document.querySelector("#fileCleaner").classList.remove("hide") 59 | } 60 | function addFilePreview(filename){ 61 | let base_classname = "file input_element" 62 | let div = document.createElement("div") 63 | div.className = base_classname 64 | div.setAttribute("path", filename) 65 | let icon_wrapper = document.createElement("span") 66 | icon_wrapper.className = "icon" 67 | let icon = document.createElement("i") 68 | icon.className = "fa-regular fa-file" 69 | icon_wrapper.appendChild(icon) 70 | let text = document.createElement("span") 71 | text.className = "text" 72 | text.innerHTML = filename.replace(/^.*[\\\/]/, '') 73 | let del_but = document.createElement("span") 74 | del_but.className = "icon del" 75 | del_but.innerHTML = '' 76 | del_but.title = LANG("remove") 77 | del_but.onclick = _=>{ div.remove() } 78 | let statuses = document.createElement("div") 79 | statuses.className = "statuses" 80 | div.addStatus = status=>{ 81 | let i = document.createElement("i") 82 | i.className = status 83 | statuses.appendChild(i) 84 | } 85 | div.reset = _=>{ 86 | div.className = base_classname 87 | icon.className = "fa-solid fa-file" 88 | statuses.innerHTML = "" 89 | } 90 | div.setClass = class_=>{ div.className = `${base_classname} ${class_}` } 91 | div.setIcon = classes=>{ icon.className = classes } 92 | div.appendChild(icon_wrapper) 93 | div.appendChild(text) 94 | div.appendChild(statuses) 95 | div.appendChild(del_but) 96 | return div 97 | } 98 | function clearFiles(){ 99 | let preview = document.getElementById("preview_zone") 100 | preview.innerHTML = '' 101 | document.querySelector("#fileCleaner").classList.add("hide") 102 | } 103 | 104 | function removeIcons_and_color(){ 105 | document.querySelectorAll("#preview_zone .file").forEach(e=>{e.reset()}) 106 | } 107 | 108 | async function openOutputFolder(){ 109 | eel.open_output_folder() 110 | } 111 | 112 | 113 | var BLOCK_START = false; 114 | async function start(){ 115 | if (!BLOCK_START){ 116 | var files = [] 117 | document.querySelectorAll("#preview_zone .file").forEach(e=>{ 118 | files.push(e.getAttribute("path")) 119 | }) 120 | 121 | if (files.length > 0){ 122 | clearConsole() 123 | removeIcons_and_color() 124 | // MAIN CALL 125 | let settings = parseSettings() 126 | await eel.start_work(files, settings)(); 127 | donationPopup() 128 | } 129 | else{ 130 | alert(LANG("no_files")) 131 | } 132 | } 133 | } 134 | 135 | async function stop(){ 136 | document.getElementById("stop").disabled = true; 137 | eel.stop_work()(); 138 | } 139 | -------------------------------------------------------------------------------- /GICutscenesUI/web/scripts/settings.js: -------------------------------------------------------------------------------- 1 | async function load_settings(keys=null){ 2 | let settings = await eel.load_settings()() 3 | if (keys){ 4 | settings = Object.fromEntries(Object.entries(settings).filter(([key]) => keys.includes(key))) 5 | } 6 | Object.keys(settings).forEach(key=>{ 7 | let el = document.querySelector(`.settings_element[name=${key}]`) 8 | if (el){ 9 | if (el.type == "checkbox"){ 10 | el.checked = settings[key] 11 | } 12 | else if (el.tagName == 'SELECT'){ 13 | if (el.querySelector(`option[value="${settings[key]}"]`)){ 14 | el.value = settings[key] 15 | } 16 | } 17 | else{ 18 | el.value = settings[key] 19 | } 20 | el.dispatchEvent(new Event("change")) 21 | } 22 | }) 23 | } 24 | async function exportSettings(){ 25 | let settings = parseSettings() 26 | await eel.save_settings(settings)() 27 | alert(LANG("saved")) 28 | } 29 | async function resetSettings(){ 30 | if (confirm(LANG("confirm_delete_settings"))){ 31 | let uns = await eel.delete_settings()(); 32 | if (uns){ 33 | window.location.reload() 34 | } 35 | } 36 | } 37 | 38 | function parseSettings(){ 39 | var settings = {} 40 | for (element of document.querySelectorAll(".settings_element")){ 41 | if (element.type == "checkbox"){ 42 | Object.assign(settings, { [element.name] : element.checked }); 43 | } 44 | else{ 45 | Object.assign(settings, { [element.name] : element.value }); 46 | } 47 | } 48 | return settings 49 | } 50 | 51 | function changeTheme(){ 52 | let value = document.querySelector(".settings_element[name=theme]").value 53 | if (value == 'dark'){ 54 | document.body.classList.add('dark') 55 | } 56 | else{ 57 | document.body.classList.remove('dark') 58 | } 59 | } 60 | 61 | 62 | async function change_output_folder(){ 63 | let folder = await eel.ask_output_folder()(); 64 | update_path(folder, document.getElementById("output_path")) 65 | } 66 | async function get_output_folder(){ 67 | let folder = await eel.get_output_folder()(); 68 | update_path(folder, document.getElementById("output_path")) 69 | } 70 | 71 | async function change_subtitles_folder(){ 72 | let folder = await eel.ask_subtitles_folder()(); 73 | update_path(folder, document.getElementById("subtitles_path")) 74 | } 75 | async function get_subtitles_folder(){ 76 | let folder = await eel.get_subtitles_folder()(); 77 | update_path(folder, document.getElementById("subtitles_path")) 78 | } 79 | 80 | async function get_all_fonts(){ 81 | let fonts = await eel.get_all_fonts()(); 82 | let parent = document.querySelector("#subtitles_font") 83 | fonts.forEach(font=>{ 84 | let option = document.createElement("option") 85 | option.value = font 86 | option.textContent = font 87 | parent.appendChild(option) 88 | }) 89 | } 90 | 91 | function init_subtitles_preview(){ 92 | let elements = document.querySelectorAll(".subtitles_setting") 93 | function get_values(){ 94 | let replace_keys = { 95 | "subtitles_font": "font_name", 96 | "subtitles_fontsize": "font_size", 97 | "subtitles_text_color": "text_color", 98 | "subtitles_outline_color": "outline_color", 99 | "subtitles_outline_width": "outline_width", 100 | "subtitles_letter_spacing": "letter_spacing", 101 | "subtitles_bold": "bold", 102 | "subtitles_italic": "italic" 103 | } 104 | let final = {} 105 | elements.forEach(e=>{ 106 | if (e.type == "checkbox"){ 107 | final[replace_keys[e.name]] = e.checked 108 | } 109 | else { 110 | final[replace_keys[e.name]] = e.value 111 | } 112 | }) 113 | return final 114 | } 115 | async function make_request(){ 116 | let subs_lang = document.querySelector(".input_element[name='subtitles_lang']").value.toLocaleLowerCase() 117 | let data_img = await eel.make_subs_preview({lang: subs_lang,...get_values()})(); 118 | document.getElementById("subtitles_preview").src = data_img || ""; 119 | } 120 | [document.querySelector(".input_element[name='subtitles_lang']"), ...elements].forEach(e=>{ 121 | e.onchange = make_request 122 | }) 123 | make_request() 124 | } 125 | 126 | async function get_encoders(){ 127 | let gpus = await eel.get_ffmpeg_supports()() 128 | let parrent = document.querySelector(".settings_element[name='gpu']") 129 | gpus.forEach(gpu=>{ 130 | let option = document.createElement("option") 131 | option.value = gpu 132 | option.innerHTML = gpu 133 | parrent.appendChild(option) 134 | }) 135 | } 136 | 137 | function donationPopup(){ 138 | function checkLastNotificationTime(){ 139 | let currentTime = Math.floor(Date.now() / 1000); 140 | let lastNotificationTime = localStorage.getItem('lastNotificationTime'); 141 | if (!lastNotificationTime || (currentTime - lastNotificationTime > 12*60*60)) { 142 | return true; 143 | } else { 144 | return false; 145 | } 146 | } 147 | function callback(){ 148 | document.querySelector("#donate-popup").classList.remove("show") 149 | let currentTime = Math.floor(Date.now() / 1000); 150 | localStorage.setItem('lastNotificationTime', currentTime); 151 | } 152 | document.querySelector("#donate-popup .close").onclick = callback 153 | document.querySelector("#donate-popup button").onclick = callback 154 | 155 | if (checkLastNotificationTime()){ 156 | document.querySelector("#donate-popup").classList.add("show") 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /GICutscenesUI/web/styles/dark.css: -------------------------------------------------------------------------------- 1 | .dark { 2 | background: #2d2d2d; 3 | color: white; 4 | } 5 | .dark #loader-area{ 6 | background: #2d2d2d; 7 | } 8 | .dark #loader-area > svg { 9 | stroke: white; 10 | } 11 | .dark hr { 12 | border-color: grey; 13 | } 14 | .dark code{ 15 | background: grey; 16 | } 17 | .dark #settings_actions{ 18 | background: #2d2d2d; 19 | } 20 | .dark .tabcontent{ 21 | color-scheme: dark; 22 | } 23 | .dark .tabs { 24 | background: #202020; 25 | } 26 | .dark .tabs button { 27 | color: white; 28 | } 29 | .dark .tabs button:hover { 30 | background-color: #303030; 31 | } 32 | .dark .tabs button:active, .dark .tabs button.active { 33 | background-color: #454545; 34 | } 35 | .dark .browse{ 36 | color: white; 37 | } 38 | .dark #output{ 39 | background: #181818; 40 | color: antiquewhite; 41 | } 42 | .dark .input_element { 43 | background: #444; 44 | border-color: #666; 45 | } 46 | .dark .text_style_btn { 47 | --checked-color: #888; 48 | border-color: #888; 49 | } 50 | .dark .link{ 51 | color: #00C0FF; 52 | } 53 | .dark #github{ 54 | fill: white; 55 | } 56 | .dark .translations{ 57 | color: white; 58 | } 59 | .dark .translations tr:nth-child(even) { 60 | background: #212529; 61 | } 62 | .dark .translations tr:nth-child(odd) { 63 | background: #2c3034; 64 | } 65 | .dark .translations tr:hover{ background: darkcyan; } 66 | 67 | .dark .link code{ 68 | background: #505050; 69 | } 70 | .dark #preview_zone .file .fa-file { 71 | font-weight: bold; 72 | } 73 | 74 | .dark .popup > div{ 75 | background: rgba(0, 0, 0, 0.75); 76 | color: white; 77 | } 78 | .dark .popup > div svg{ 79 | fill: lime; 80 | } 81 | .dark .popup button { 82 | --btn-color: rgba(0, 0, 0, 0.75); 83 | } 84 | -------------------------------------------------------------------------------- /GICutscenesUI/web/styles/fontawesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/GICutscenesUI/web/styles/fontawesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /GICutscenesUI/web/styles/fontawesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/GICutscenesUI/web/styles/fontawesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /GICutscenesUI/web/styles/main.css: -------------------------------------------------------------------------------- 1 | * { box-sizing: border-box; } 2 | body{ 3 | font-family: sans-serif; 4 | display: flex; 5 | flex-direction: column; 6 | max-height: 100dvh; 7 | overflow: hidden; 8 | margin: 0; padding: 0; 9 | } 10 | code{ 11 | font-family: monospace; 12 | background: lightgrey; 13 | padding: 3px 5px 3px 5px; 14 | border-radius: 8px; 15 | } 16 | input[type="checkbox"], 17 | input[type="color"], 18 | label[for] { 19 | cursor: pointer; 20 | } 21 | input[type="number"], 22 | input[type="text"]{ 23 | cursor: text; 24 | } 25 | #loader-area{ 26 | background: white; 27 | position: fixed; 28 | inset: 0; 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | z-index: 1; 33 | transition: 0.5s; 34 | } 35 | #loader-area > svg{ 36 | height: 50px; 37 | stroke: black; 38 | } 39 | #loader-area.hidden { 40 | opacity: 0; 41 | visibility: hidden; 42 | } 43 | hr{ 44 | max-width: 750px; 45 | } 46 | 47 | .tabcontent{ 48 | display: none; 49 | padding: 0 20px; 50 | overflow: auto; 51 | scrollbar-width: thin; 52 | scroll-behavior: smooth; 53 | } 54 | .tabcontent.showed{ 55 | display: block; 56 | } 57 | .tabs { 58 | overflow: hidden; 59 | background-color: #f1f1f1; 60 | border-radius: 12px; 61 | flex-shrink: 0; 62 | margin: 10px auto; 63 | margin-bottom: 6px; 64 | width: calc(100% - 20px); 65 | max-width: 750px; 66 | } 67 | .tabs button { 68 | background-color: inherit; 69 | float: left; 70 | border: none; 71 | outline: none; 72 | cursor: pointer; 73 | padding: 14px 16px; 74 | transition: 0.3s; 75 | font-weight: bold; 76 | font-size: 15px; 77 | } 78 | .tabs button:hover { 79 | background-color: #ddd; 80 | } 81 | .tabs button:active, .tabs button.active { 82 | background-color: #ccc; 83 | } 84 | 85 | .buttons-line{ 86 | display: flex; 87 | gap: 10px; 88 | justify-content: center; 89 | flex-wrap: wrap; 90 | margin: 20px; 91 | } 92 | .browse{ 93 | font: 14pt "Ubuntu", sans-serif; 94 | background: none; 95 | text-align: center; 96 | border: 2px solid #3498db; 97 | padding: 8px 20px; 98 | outline: none; 99 | border-radius: 20px; 100 | transition: 0.2s; 101 | white-space: nowrap; 102 | cursor: pointer; 103 | } 104 | .browse:hover{ 105 | padding: 8px 40px; 106 | border-color: yellow; 107 | } 108 | .browse:active{ 109 | border-color: green; 110 | } 111 | 112 | #fileCleaner{ 113 | border-color: darkred; 114 | } 115 | #fileCleaner:hover{ 116 | border-color: red; 117 | padding: 8px 30px; 118 | } 119 | #fileCleaner:active{ 120 | color: red; 121 | } 122 | 123 | #preview_zone{ 124 | max-width: 600px; 125 | margin: 10px auto; 126 | display: flex; 127 | flex-direction: column; 128 | gap: 6px; 129 | font-size: 14px; 130 | } 131 | #preview_zone .file { 132 | border-radius: 8px; 133 | padding: 6px; 134 | display: grid; 135 | grid-template-columns: 15px 1fr 1fr auto; 136 | align-items: center; 137 | justify-items: center; 138 | gap: 6px; 139 | cursor: auto; 140 | } 141 | #preview_zone .file .text { 142 | justify-self: start; 143 | font-family: monospace; 144 | line-height: 1; 145 | } 146 | #preview_zone .file .icon { 147 | display: flex; 148 | } 149 | #preview_zone .file .del { 150 | color: #f03; 151 | cursor: pointer; 152 | margin-left: 2px; 153 | font-size: 16px; 154 | } 155 | #preview_zone.no-remove .del{ 156 | display: none; 157 | } 158 | #preview_zone .file.now{ 159 | color: #0075ff; 160 | } 161 | #preview_zone .file.ok{ 162 | color: #27c93f; 163 | } 164 | #preview_zone .file.error{ 165 | color: #f03; 166 | } 167 | #preview_zone .file.stoped{ 168 | color: orange; 169 | } 170 | #preview_zone .file.stoped .statuses { 171 | visibility: hidden; 172 | } 173 | #preview_zone .file .statuses { 174 | justify-self: end; 175 | display: flex; 176 | gap: 5px; 177 | } 178 | #preview_zone .file .statuses .ok{ 179 | color: #27c93f; 180 | } 181 | #preview_zone .file .statuses .error{ 182 | color: #f03; 183 | } 184 | 185 | .input_element { 186 | border-radius: 8px; 187 | padding: 4px 8px; 188 | background: #eee; 189 | border: 2px solid lightgrey; 190 | font-size: 14px; 191 | cursor: pointer; 192 | } 193 | .path{ 194 | font-family: monospace; 195 | max-width: 70%; 196 | direction: RTL; 197 | cursor: text; 198 | } 199 | .text_style_btn { 200 | width: 30px; 201 | height: 30px; 202 | padding: 0; 203 | display: flex; 204 | align-items: center; 205 | justify-content: center; 206 | font-family: monospace; 207 | line-height: 30px; 208 | font-size: 15px; 209 | --checked-color: lightgrey; 210 | } 211 | .text_style_btn input[type="checkbox"]{ display: none; } 212 | .text_style_btn:has(input[type="checkbox"]:checked), 213 | .text_style_btn:has(input[type="checkbox"]:checked){ 214 | background: var(--checked-color); 215 | font-weight: bold; 216 | } 217 | 218 | .custom_browse{ 219 | display: block; 220 | margin: auto; 221 | padding: 8px 40px; 222 | max-width: 100%; 223 | background: #4691CF; 224 | color: white; 225 | font-size: 16px; 226 | border: none; 227 | text-transform: uppercase; 228 | } 229 | .custom_browse:hover{ 230 | padding: 8px 70px; 231 | background: #1c79c7; 232 | } 233 | 234 | #start_area{ 235 | position: fixed; 236 | right: 10px; 237 | bottom: 14px; 238 | } 239 | #start_area > * { 240 | font-size: 18pt; 241 | background-color: #1CB0F6; 242 | border: solid transparent; 243 | border-radius: 16px; 244 | color: #fff; 245 | cursor: pointer; 246 | display: inline-block; 247 | font-weight: 700; 248 | letter-spacing: .8px; 249 | line-height: 20px; 250 | outline: none; 251 | padding: 12px 20px; 252 | transition: .2s ease-out; 253 | box-shadow: 0 4px 0 #1899D6; 254 | text-transform: uppercase; 255 | user-select: none; 256 | } 257 | #start_area > *:hover { 258 | filter: brightness(1.1); 259 | -webkit-filter: brightness(1.1); 260 | } 261 | #start_area > *:active { 262 | box-shadow: 0 0px 0 #1899D6; 263 | transform: translateY(4px); 264 | } 265 | #start_area > *:disabled { 266 | filter: grayscale(1); 267 | pointer-events: none; 268 | } 269 | 270 | #stop{ 271 | background: red; 272 | box-shadow: 0 4px 0 darkred; 273 | } 274 | #stop:active { 275 | box-shadow: 0 0px 0 darkred; 276 | } 277 | #stop:disabled{ 278 | filter: grayscale(0.8); 279 | } 280 | 281 | #settings_actions { 282 | position: sticky; 283 | top: 0; 284 | text-align: center; 285 | border: 0; 286 | margin: 5px auto; 287 | margin-bottom: -5px; 288 | padding: 5px 0; 289 | background: white; 290 | } 291 | 292 | .settings-button { 293 | background-color: #2ea44f; 294 | border: 1px solid rgba(27, 31, 35, .15); 295 | border-radius: 6px; 296 | box-shadow: rgba(27, 31, 35, .1) 0 1px 0; 297 | color: #fff; 298 | cursor: pointer; 299 | display: inline-block; 300 | font-size: 15px; 301 | font-weight: 600; 302 | padding: 6px 16px; 303 | transition: .2s; 304 | margin: 0 2px 0 2px; 305 | } 306 | .settings-button:hover { 307 | background-color: #2c974b; 308 | } 309 | .settings-button:focus { 310 | box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px; 311 | outline: none; 312 | } 313 | 314 | .settings-button.blue{ 315 | background-color: #0095ff; 316 | } 317 | .settings-button.blue:hover { 318 | background-color: #07c; 319 | } 320 | .settings-button.blue:focus { 321 | box-shadow: rgba(0, 100, 189, .4) 0 0 0 3px; 322 | } 323 | 324 | input[type="checkbox"]{ 325 | vertical-align: text-bottom; 326 | } 327 | input[type="range"]{ 328 | vertical-align: middle; 329 | cursor: ew-resize; 330 | } 331 | input[type="range" i]{ 332 | color: #aaa; 333 | } 334 | fieldset{ 335 | border-radius: 12px; 336 | margin: 1em auto; 337 | max-width: 750px; 338 | } 339 | fieldset legend{ 340 | font-size: 1.15rem; 341 | } 342 | 343 | .grid-layout { 344 | display: grid; 345 | grid-template-columns: 1fr 1fr; 346 | align-items: center; 347 | white-space: nowrap; 348 | gap: 6px 10px; 349 | } 350 | .subtitles-area { 351 | width: min-content; 352 | } 353 | .subtitles-customize { 354 | text-align: right; 355 | gap: 10px; 356 | margin: auto; 357 | } 358 | #provider_local, #provider_url { 359 | display: none; 360 | width: 100%; 361 | grid-column: 2; 362 | min-width: 250px; 363 | gap: 5px; 364 | } 365 | #provider_local input, #provider_url input{ 366 | width: 100%; 367 | } 368 | #subtitles_provider:has(option[value="local"]:checked) ~ #provider_local, 369 | #subtitles_provider:has(option[value="url"]:checked) ~ #provider_url 370 | { 371 | display: flex; 372 | } 373 | #subtitles_provider:has(option.mono:checked){ 374 | font-family: monospace; 375 | } 376 | 377 | #subtitles_preview{ 378 | max-width: min(100%, 750px); 379 | max-height: 42vh; 380 | object-fit: contain; 381 | background: lime; 382 | border-radius: 8px; 383 | display: block; 384 | margin: auto; 385 | } 386 | 387 | 388 | #progress{ 389 | display: flex; 390 | flex-direction: column; 391 | gap: 5px; 392 | padding: 5px; 393 | max-width: 600px; 394 | margin: auto; 395 | } 396 | .hide { 397 | display: none !important; 398 | } 399 | #console_wrapper{ 400 | margin: 10px auto; 401 | position: relative; 402 | max-width: 750px; 403 | } 404 | #console_wrapper > button { 405 | position: absolute; 406 | right: 0; 407 | top: 0; 408 | font-size: 14px; 409 | cursor: pointer; 410 | } 411 | #output{ 412 | box-sizing: border-box; 413 | width: 100%; 414 | resize: vertical; 415 | border-radius: 8px; 416 | border: none; 417 | outline: none; 418 | padding: 6px 8px; 419 | background: lightgrey; 420 | } 421 | 422 | .flexable{ 423 | display: flex; 424 | align-items: center; 425 | justify-content: space-between; 426 | max-width: 750px; 427 | margin: auto; 428 | } 429 | .link{ 430 | color: blue; 431 | cursor: alias; 432 | vertical-align: top; 433 | text-decoration: none; 434 | } 435 | .version{ 436 | margin-left: 5px; 437 | background: rgb(0, 150, 255, 0.5) !important; 438 | } 439 | .update{ 440 | background: rgb(0, 255, 150, 0.5) !important; 441 | cursor: help; 442 | } 443 | .arrow:after{ 444 | content: '→'; 445 | display: inline-block; 446 | margin: 0 5px 0 5px; 447 | } 448 | #github{ 449 | margin-right: 2px; 450 | vertical-align: bottom; 451 | } 452 | 453 | .info_icon::after{ 454 | content: "?"; 455 | display: inline-block; 456 | color: red; 457 | font-weight: bold; 458 | cursor: help; 459 | } 460 | #loading{ 461 | height: 50px; 462 | width: 50px; 463 | } 464 | 465 | .translations{ 466 | width: 100%; 467 | } 468 | table td{ 469 | padding: 5px; 470 | } 471 | .translations tr:nth-child(even) { 472 | background: #ccc; 473 | } 474 | .translations tr:nth-child(odd) { 475 | background: #ebebeb; 476 | } 477 | .translations tr{ 478 | transition: 0.2s; 479 | } 480 | .translations tr:hover{ background: yellow; } 481 | 482 | 483 | .popup{ 484 | display: none; 485 | position: fixed; 486 | z-index: 1; 487 | left: 0; 488 | top: 0; 489 | width: 100%; 490 | height: 100%; 491 | overflow: hidden; 492 | background-color: rgba(0, 0, 0, 0.5); 493 | backdrop-filter: blur(3px); 494 | } 495 | .popup.show{ 496 | display: flex; 497 | align-items: center; 498 | justify-content: center; 499 | } 500 | .popup > div{ 501 | background: rgb(240, 250, 255, 0.85); 502 | padding: 20px; 503 | width: 75%; 504 | max-width: 750px; 505 | border-radius: 16px; 506 | box-sizing: border-box; 507 | text-align: center; 508 | position: relative; 509 | } 510 | .popup .close{ 511 | color: red; 512 | position: absolute; 513 | top: 10px; 514 | right: 10px; 515 | font-weight: bold; 516 | cursor: pointer; 517 | user-select: none; 518 | transition: 0.15s; 519 | font-size: 16pt; 520 | width: 16pt; 521 | height: 16pt; 522 | display: flex; 523 | justify-content: center; 524 | align-items: center; 525 | line-height: 16pt; 526 | } 527 | .popup .close:hover{ 528 | transform: rotate(90deg); 529 | } 530 | .popup > div svg{ 531 | fill: rgb(0, 200, 0); 532 | height: 160px; 533 | display: block; 534 | margin: auto; 535 | } 536 | 537 | .popup button { 538 | --glow-color: rgb(0, 225, 0); 539 | --btn-color: aliceblue; 540 | cursor: pointer; 541 | border: .25em solid var(--glow-color); 542 | padding: 1em 3em; 543 | color: var(--glow-color); 544 | font-size: 16px; 545 | font-weight: bold; 546 | background-color: var(--btn-color); 547 | border-radius: 1em; 548 | outline: none; 549 | box-shadow: 0 0 1em .1em var(--glow-color), 550 | inset 0 0 .5em 0 var(--glow-color); 551 | transition: all 0.3s; 552 | user-select: none; 553 | } 554 | .popup button:hover { 555 | color: var(--btn-color); 556 | background-color: var(--glow-color); 557 | box-shadow: 0 0 2em .3em var(--glow-color); 558 | } 559 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

GI-Cutscenes UI

2 | 3 |

4 | 5 |

6 |

7 | User Interface for Genshin Cutscenes Demuxer 8 |

9 |

10 |
11 | 12 |

13 | 14 | ## Screenshots: 15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 | ## Usage: 26 | 1. Download archive from [Releases](https://github.com/SuperZombi/GICutscenesUI/releases/latest) 27 | 2. Unzip it to a convenient location. 28 | 3. Start ```GICutscenesUI.exe``` 29 | 30 | ## FAQ 31 | 32 | ### Where are the game files with cutscenes? 33 | `[Game directory]\Genshin Impact game\GenshinImpact_Data\StreamingAssets\VideoAssets\StandaloneWindows64` 34 | 35 | ### I have an error when demuxing files 36 | Try to update your [GI-cutscenes script](https://github.com/ToaHartor/GI-cutscenes/releases) to the latest version.
37 | Add the `GI-cutscenes.exe` file next to the exe file you are running. 38 | 39 | ### I have an error in merging video 40 | Try to update your [ffmpeg](https://github.com/BtbN/FFmpeg-Builds/releases) to the latest version.
41 | Add the `ffmpeg.exe` file next to the exe file you are running. 42 | 43 | ### How can I add subtitles? 44 | You must enable merging. Then you need to select a subtitle provider.
45 | This can be either one of the already suggested options.
46 | But you can also download subtitles to your computer and specify the path to the folder with subtitles.
47 | The path to the subtitles should look like this: `[Subtitles folder]\LANG\Cutscene_name_LANG.srt` 48 | 49 | ### How can I make a font like in the game? 50 | Install the font that is located along the path:
51 | `[Game directory]\Genshin Impact game\GenshinImpact_Data\StreamingAssets\MiHoYoSDKRes\HttpServerResources\font` 52 | 53 |
54 | 55 | ### Help with translation 56 | 57 |
58 | 59 | #### 💲Donate 60 | 61 | 62 | 65 | 68 | 69 | 70 | 73 | 76 | 77 |
63 | 64 | 66 | Donatello 67 |
71 | 72 | 74 | Donation Alerts 75 |
78 | -------------------------------------------------------------------------------- /github/ffmpeg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/ffmpeg.exe -------------------------------------------------------------------------------- /github/images/animation_low.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/animation_low.gif -------------------------------------------------------------------------------- /github/images/icons/CLI/cli-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/icons/CLI/cli-1.png -------------------------------------------------------------------------------- /github/images/icons/CLI/cli-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/icons/CLI/cli-2.png -------------------------------------------------------------------------------- /github/images/icons/UI/ui-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/icons/UI/ui-1.png -------------------------------------------------------------------------------- /github/images/icons/UI/ui-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/icons/UI/ui-2.png -------------------------------------------------------------------------------- /github/images/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/main.png -------------------------------------------------------------------------------- /github/images/new_main_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/new_main_page.png -------------------------------------------------------------------------------- /github/images/settings_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/settings_dark.png -------------------------------------------------------------------------------- /github/images/settings_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/settings_light.png -------------------------------------------------------------------------------- /github/images/settings_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/settings_old.png -------------------------------------------------------------------------------- /github/images/subtitles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperZombi/GICutscenesUI/c49bda9bc53b9899e9bbdf86b6be3e91c403ab9c/github/images/subtitles.png -------------------------------------------------------------------------------- /github/requirements.txt: -------------------------------------------------------------------------------- 1 | Eel 2 | JSON_minify 3 | requests 4 | pysubs2 5 | pywin32 6 | pyinstaller 7 | -------------------------------------------------------------------------------- /github/ver: -------------------------------------------------------------------------------- 1 | VSVersionInfo( 2 | ffi=FixedFileInfo( 3 | filevers=(0, 9, 1, 0), 4 | prodvers=(0, 9, 1, 0), 5 | mask=0x3f, 6 | flags=0x0, 7 | OS=0x40004, 8 | fileType=0x1, 9 | subtype=0x0, 10 | date=(0, 0) 11 | ), 12 | kids=[ 13 | StringFileInfo( 14 | [ 15 | StringTable( 16 | u'040904B0', 17 | [ 18 | StringStruct(u'FileDescription', u'Genshin Impact Cutscenes'), 19 | StringStruct(u'FileVersion', u'0.9.1'), 20 | StringStruct(u'InternalName', u'GICutscenesUI'), 21 | StringStruct(u'OriginalFilename', u'GICutscenesUI.exe'), 22 | StringStruct(u'ProductName', u'GI-Cutscenes UI'), 23 | StringStruct(u'ProductVersion', u'0.9.1')]) 24 | ]), 25 | VarFileInfo([VarStruct(u'Translation', [0, 1200])]) 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /translations.md: -------------------------------------------------------------------------------- 1 | # Translations 2 | 3 | The translation engine, written by me, allows you to inherit from other languages. 4 | 5 | The engine also ignores comments and non-closed commas (this is forbidden in json by default). 6 | 7 | 8 | ## How can I inherit? 9 | First of all, in the json file you need to create a new `__root__` key. Inside it, create a dictionary with the key `inherit` and specify the string (language code). 10 | 11 | As a result, it should look something like this: 12 | ```json 13 | { 14 | "__root__": { 15 | "inherit": "en" 16 | }, 17 | "home": "Home", 18 | ... 19 | } 20 | ``` 21 | 22 | 23 | ## How it works? 24 | Before displaying the interface in the desired language, the engine checks if the language is inherited from another. 25 | 26 | If so, then it loads the specified language, and so on until there is no language without inheritance. 27 | 28 | The most recent (parent) language is the bottom layer. Child languages are written on top in the order of inheritance. 29 | 30 | 31 | ## What is it for? 32 | This allows the program not to break if new phrases are added in future updates. 33 | 34 | If some phrases are not in your language, they will be taken from the parent. 35 | 36 | If the parent language does not have this phrase, its code will be used. 37 | 38 | For example, if needed the phrase `"hello_world" : "Hello World!"` Then the interface will display its key (`hello_world`). 39 | 40 | 41 | ## ⚠Warning 42 | Please make sure you don't recurse when you inherit! 43 | -------------------------------------------------------------------------------- /useful.md: -------------------------------------------------------------------------------- 1 | ### Web site, where you can download old game resources and get cutscenes from temporary events: 2 | [whatifgaming.com](https://whatifgaming.com/?s=How+to+manually+update+to+Genshin+Impact) 3 | 4 | ### History activity cutscene: 5 | [MS Drive](https://skylandstudio.sharepoint.com/:f:/s/game-assets/En-0IV7_9UVEqQyJM_n-rRQB3Hy-B2McObA3BkdWH89xAg) 6 | 7 | ### Repositories with subtitles: 8 | * [Dimbreath/AnimeGameData](https://gitlab.com/Dimbreath/AnimeGameData) 9 | * [Sycamore0/GenshinData](https://github.com/Sycamore0/GenshinData) 10 | --------------------------------------------------------------------------------