├── Hydrax-Abyss.to-DownloadHelper.py ├── LICENSE ├── README.md └── requirements.txt /Hydrax-Abyss.to-DownloadHelper.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | from concurrent.futures import ThreadPoolExecutor, wait 3 | from json import loads 4 | from os import makedirs, remove, system 5 | from os.path import abspath, exists, expandvars, join 6 | from re import search 7 | from time import sleep 8 | 9 | from requests import RequestException, Timeout, get 10 | from STPyV8 import JSContext 11 | from tqdm import tqdm 12 | 13 | version = "v2.1" 14 | # 1 = 360p 15 | # 2 = 720p 16 | # 3 = 1080p 17 | max_quality = 3 # Max resolution for automatic selection, or uses minimum available 18 | automatic = True # Set "False" to select resolution manually 19 | download_directory = r"" # Set download directory, insert path inside "" 20 | request_timeout = 180 # Seconds to wait between bytes before timeout 21 | request_retry = 60 # Retry attempts 22 | request_wait = 6 # Seconds to wait before retrying 23 | error_file = "Abyss_error.log" # File name of error log, insert name inside "" 24 | enable_error_log = True # Set "True" to enable error logging to file 25 | 26 | turbo = True # Set "True" to multithread download 27 | turbo_squared = False # Set "True" to download all Vid_ID at the same time 28 | delete_fragment = True # Set "True" to delete downloaded fragments 29 | active_download = 10 # Max active download connections 30 | fragments_to_temp = True # Set "False" to download fragments in `download_directory` instead of `%TEMP%` 31 | 32 | split_by_bytes = True # Set "True" to split by bytes with size `turbo_chunk_size_bytes` 33 | # Set "False" to split by fragment files in amount of files `turbo_fragment` 34 | turbo_chunk_size_bytes = 65536 * 128 # Size of each fragment in bytes 35 | # Or 36 | turbo_fragment = 60 # Number of fragment files the video will get divided into 37 | 38 | 39 | def log_error(err): 40 | if enable_error_log: 41 | with open(error_file, "a", encoding="utf8") as f: 42 | f.write(f"{err}\n") 43 | 44 | 45 | def get_turbo_download(vid_ID_text): 46 | try: 47 | ( 48 | vid_ID, 49 | available_resolution, 50 | values, 51 | resolution_option, 52 | extension, 53 | download_path, 54 | piece_length, 55 | quality, 56 | file_name, 57 | atob_domain, 58 | quality_prefix, 59 | atob_ID, 60 | ) = get_data(vid_ID_text) 61 | 62 | download_url = f"https://{atob_domain}/{quality_prefix[quality]}{atob_ID}" 63 | if exists(download_path) and get_size(download_path) == int( 64 | piece_length[quality] 65 | ): 66 | print(f"\n{bcolors.OKGREEN}{file_name} already exists{bcolors.ENDC}\n") 67 | else: 68 | if split_by_bytes: 69 | chunk_range, chunk_size = generate_range_byte( 70 | int(piece_length[quality]), turbo_chunk_size_bytes 71 | ) 72 | else: 73 | chunk_range, chunk_size = generate_range_split( 74 | int(piece_length[quality]), turbo_fragment 75 | ) 76 | 77 | range_min, range_max = chunk_range[-1].split("-") 78 | last_chunk_size = (int(range_max) - int(range_min)) + 1 79 | 80 | if fragments_to_temp: 81 | makedirs(expandvars("%TEMP%\\abyss_fragments"), exist_ok=True) 82 | 83 | while True: 84 | with ThreadPoolExecutor(max_workers=active_download) as pool: 85 | fragment_list = [] 86 | futures = [] 87 | for count, byte_range in enumerate(chunk_range): 88 | fragment_file_name = f"{file_name}.abyss{count+1:04}" 89 | 90 | if fragments_to_temp: 91 | fragment_download_path = join( 92 | expandvars("%TEMP%\\abyss_fragments"), 93 | fragment_file_name, 94 | ) 95 | else: 96 | fragment_download_path = join( 97 | download_directory, 98 | fragment_file_name, 99 | ) 100 | 101 | fragment_list.append(fragment_download_path) 102 | 103 | if not exists(fragment_download_path): 104 | write_method = "wb" 105 | futures.append( 106 | pool.submit( 107 | start_download, 108 | vid_ID, 109 | available_resolution, 110 | byte_range, 111 | download_url, 112 | fragment_download_path, 113 | write_method, 114 | fragment_file_name, 115 | ) 116 | ) 117 | else: 118 | downloaded_size = get_size(fragment_download_path) 119 | 120 | if count + 1 == len(chunk_range): 121 | if downloaded_size == last_chunk_size: 122 | print( 123 | f"\n{bcolors.OKGREEN}{fragment_file_name} already exists{bcolors.ENDC}\n" 124 | ) 125 | continue 126 | else: 127 | if downloaded_size == chunk_size: 128 | print( 129 | f"\n{bcolors.OKGREEN}{fragment_file_name} already exists{bcolors.ENDC}\n" 130 | ) 131 | continue 132 | 133 | write_method = "wb" 134 | futures.append( 135 | pool.submit( 136 | start_download, 137 | vid_ID, 138 | available_resolution, 139 | byte_range, 140 | download_url, 141 | fragment_download_path, 142 | write_method, 143 | fragment_file_name, 144 | ) 145 | ) 146 | 147 | wait(futures) 148 | 149 | ok_fragment = 0 150 | for count, i in enumerate(fragment_list): 151 | if count + 1 == len(fragment_list): 152 | if get_size(i) == last_chunk_size: 153 | ok_fragment += 1 154 | else: 155 | if get_size(i) == chunk_size: 156 | ok_fragment += 1 157 | 158 | if ok_fragment != len(fragment_list): 159 | print("\n" * 10) 160 | print( 161 | f"{bcolors.WARNING}Fragments not verified, retrying: {vid_ID}{bcolors.ENDC}" 162 | ) 163 | print("\n" * 5) 164 | else: 165 | print("\n" * 10) 166 | print(f"{bcolors.OKGREEN}Verified, merging: {vid_ID}{bcolors.ENDC}") 167 | print("\n" * 5) 168 | 169 | try: 170 | with open(download_path, "wb") as out_file: 171 | for i in fragment_list: 172 | with open(i, "rb") as in_file: 173 | out_file.write(in_file.read()) 174 | 175 | except Exception as err: 176 | print( 177 | error := f""" 178 | {bcolors.WARNING}Failed to merge, retrying: {vid_ID} - get_turbo_download 179 | {err}{bcolors.ENDC} 180 | """ 181 | ) 182 | log_error(error) 183 | 184 | if delete_fragment: 185 | for i in fragment_list: 186 | try: 187 | if exists(i): 188 | remove(i) 189 | 190 | except Exception as err: 191 | print( 192 | error := f""" 193 | {bcolors.FAIL}Failed to delete: {i} - get_turbo_download 194 | {err}{bcolors.ENDC} 195 | """ 196 | ) 197 | log_error(error) 198 | 199 | with open(i, "wb"): 200 | pass 201 | 202 | global failed_delete 203 | failed_delete = True 204 | break 205 | 206 | except Exception as err: 207 | print( 208 | error := f""" 209 | {bcolors.FAIL}Error downloading: {vid_ID} - get_turbo_download 210 | {err}{bcolors.ENDC} 211 | """ 212 | ) 213 | log_error(error) 214 | 215 | 216 | def start_download( 217 | vid_ID, 218 | available_resolution, 219 | byte_range, 220 | download_url, 221 | download_path, 222 | write_method, 223 | file_name, 224 | ): 225 | try: 226 | if automatic and not turbo: 227 | print(f"\nAvailable resolution {vid_ID}: {available_resolution}") 228 | 229 | headers = {"Referer": "https://abysscdn.com/", "Range": f"bytes={byte_range}"} 230 | for i in range(request_retry): 231 | i += 1 232 | if i == request_retry: 233 | raise Exception(f"\n{bcolors.FAIL}Reached max retry{bcolors.ENDC}") 234 | 235 | try: 236 | r = get( 237 | download_url, headers=headers, stream=True, timeout=request_timeout 238 | ) 239 | 240 | with tqdm.wrapattr( 241 | open(download_path, write_method), 242 | "write", 243 | miniters=1, 244 | desc=file_name, 245 | total=int(r.headers.get("content-length", 0)), 246 | unit_scale=True, 247 | unit_divisor=1024, 248 | ) as f: 249 | for chunk in r.iter_content(chunk_size=64 * 1024): 250 | f.write(chunk) 251 | 252 | if r.status_code != 200 and r.status_code != 206: 253 | print( 254 | f"\n{bcolors.WARNING}Retrying {i}/{request_retry}... {download_url}{bcolors.ENDC}\n" 255 | ) 256 | sleep(request_wait) 257 | else: 258 | break 259 | 260 | except Timeout as err: 261 | print( 262 | error := f""" 263 | {bcolors.WARNING}Connection timed out - start_download - {download_url} - {vid_ID} 264 | {err} 265 | Retrying {i}/{request_retry}... {download_url}{bcolors.ENDC} 266 | """ 267 | ) 268 | log_error(error) 269 | sleep(request_wait) 270 | except RequestException as err: 271 | print( 272 | error := f""" 273 | {bcolors.WARNING}Request exception - start_download - {download_url} - {vid_ID} 274 | {err} 275 | Retrying {i}/{request_retry}... {download_url}{bcolors.ENDC} 276 | """ 277 | ) 278 | log_error(error) 279 | sleep(request_wait) 280 | except Exception as err: 281 | print( 282 | error := f""" 283 | {bcolors.FAIL}Error downloading: {vid_ID} - start_download 284 | {err}{bcolors.ENDC} 285 | """ 286 | ) 287 | log_error(error) 288 | 289 | 290 | def download(vid_ID_text): 291 | try: 292 | ( 293 | vid_ID, 294 | available_resolution, 295 | values, 296 | resolution_option, 297 | extension, 298 | download_path, 299 | piece_length, 300 | quality, 301 | file_name, 302 | atob_domain, 303 | quality_prefix, 304 | atob_ID, 305 | ) = get_data(vid_ID_text) 306 | 307 | download_url = f"https://{atob_domain}/{quality_prefix[quality]}{atob_ID}" 308 | if not automatic: 309 | print(f""" 310 | Downloading Vid_ID: {vid_ID} 311 | Available resolution {available_resolution} 312 | """) 313 | if "360p" in values: 314 | print("[1] 360p") 315 | if "720p" in values: 316 | print("[2] 720p") 317 | if "1080p" in values: 318 | print("[3] 1080p") 319 | 320 | while True: 321 | resolution_selected = input("Select option: ") 322 | 323 | if resolution_selected in resolution_option: 324 | quality = resolution_selected 325 | file_name = ( 326 | f"{vid_ID}_{resolution_option[quality]}.{extension[quality]}" 327 | ) 328 | download_path = join(abspath(download_directory), file_name) 329 | download_url = ( 330 | f"https://{atob_domain}/{quality_prefix[quality]}{atob_ID}" 331 | ) 332 | break 333 | else: 334 | print(f"{bcolors.WARNING}Select a valid option{bcolors.ENDC}") 335 | 336 | if not exists(download_path): 337 | byte_range = "0-" 338 | write_method = "wb" 339 | start_download( 340 | vid_ID, 341 | available_resolution, 342 | byte_range, 343 | download_url, 344 | download_path, 345 | write_method, 346 | file_name, 347 | ) 348 | else: 349 | downloaded_size = get_size(download_path) 350 | if downloaded_size == int(piece_length[quality]): 351 | print(f"\n{bcolors.OKGREEN}{file_name} already exists{bcolors.ENDC}\n") 352 | else: 353 | byte_range = f"{downloaded_size}-" 354 | write_method = "ab" 355 | start_download( 356 | vid_ID, 357 | available_resolution, 358 | byte_range, 359 | download_url, 360 | download_path, 361 | write_method, 362 | file_name, 363 | ) 364 | 365 | except Exception as err: 366 | print( 367 | error := f""" 368 | {bcolors.FAIL}Error downloading: {vid_ID} - download 369 | {err}{bcolors.ENDC} 370 | """ 371 | ) 372 | log_error(error) 373 | 374 | 375 | def get_vid_ID_text(vid_ID_list): 376 | print(f"\n{bcolors.BOLD}Getting Vid_ID text{bcolors.ENDC}\n") 377 | 378 | vid_ID_list_text = [] 379 | for vid_ID in vid_ID_list: 380 | try: 381 | vid_ID_url = f"https://abysscdn.com/?v={vid_ID}" 382 | referer = "https://abyss.to/" 383 | attempted = False 384 | 385 | for i in range(request_retry): 386 | i += 1 387 | if i == request_retry: 388 | raise Exception( 389 | f"\n{bcolors.FAIL}Reached max retry{bcolors.ENDC}\n" 390 | ) 391 | 392 | try: 393 | r = get( 394 | vid_ID_url, 395 | headers={"Referer": f"{referer}"}, 396 | timeout=request_timeout, 397 | ) 398 | 399 | if r.status_code != 200: 400 | print( 401 | f"\n{bcolors.WARNING}Retrying {i}/{request_retry}... {vid_ID_url}{bcolors.ENDC}\n" 402 | ) 403 | sleep(request_wait) 404 | else: 405 | encoded_re = r"(゚ω゚ノ=.+?) \('_'\);" 406 | atob_re = r"JSON\.parse\(atob\(\"" 407 | invalid_re = r"Invalid embedded domain name" 408 | 409 | if not search(invalid_re, r.text) and search(atob_re, r.text): 410 | vid_ID_list_text.append(r.text) 411 | break 412 | elif not search(invalid_re, r.text) and search( 413 | encoded_re, r.text 414 | ): 415 | encoded = ( 416 | search(r"(゚ω゚ノ=.+?) \('_'\);", r.text).group(1) 417 | + ".toString()" 418 | ) 419 | 420 | with JSContext() as ctxt: 421 | decoded = ctxt.eval(encoded) 422 | 423 | vid_ID_list_text.append(decoded) 424 | break 425 | elif search(invalid_re, r.text): 426 | while True: 427 | if attempted: 428 | print( 429 | f"{bcolors.WARNING}Failed. Make sure you are using the correct URL{bcolors.ENDC}" 430 | ) 431 | 432 | url = input( 433 | f"""Enter origin URL of "{vid_ID}", or enter "skip": """ 434 | ) 435 | 436 | if url == "skip": 437 | break 438 | 439 | try: 440 | referer = search( 441 | r"(https?://[^/]+?)[:/]", url 442 | ).group(1) 443 | 444 | attempted = True 445 | break 446 | except Exception: 447 | print( 448 | f"{bcolors.WARNING}Enter a valid URL, e.g. https://abyss.to/{bcolors.ENDC}" 449 | ) 450 | 451 | attempted = False 452 | 453 | if url == "skip": 454 | break 455 | 456 | print( 457 | f"\n{bcolors.WARNING}Retrying {i}/{request_retry}... {vid_ID_url}{bcolors.ENDC}\n" 458 | ) 459 | else: 460 | print( 461 | f"{bcolors.WARNING}Cannot get Vid_ID text - {vid_ID}{bcolors.ENDC}" 462 | ) 463 | 464 | except Timeout as err: 465 | print( 466 | error := f""" 467 | {bcolors.WARNING}Connection timed out - get_vid_ID_text - {vid_ID_url} - {vid_ID} 468 | {err} 469 | Retrying {i}/{request_retry}... {vid_ID_url}{bcolors.ENDC} 470 | """ 471 | ) 472 | log_error(error) 473 | sleep(request_wait) 474 | except RequestException as err: 475 | print( 476 | error := f""" 477 | {bcolors.WARNING}Request exception - get_vid_ID_text - {vid_ID_url} - {vid_ID} 478 | {err} 479 | Retrying {i}/{request_retry}... {vid_ID_url}{bcolors.ENDC} 480 | """ 481 | ) 482 | log_error(error) 483 | sleep(request_wait) 484 | 485 | except Exception as err: 486 | print( 487 | error := f""" 488 | {bcolors.FAIL}Failed to get Vid_ID text: {vid_ID} - get_vid_ID_text 489 | {err}{bcolors.ENDC} 490 | """ 491 | ) 492 | log_error(error) 493 | 494 | return vid_ID_list_text 495 | 496 | 497 | def get_data(vid_ID_text): 498 | atob = search( 499 | r'atob\("(.+?)"\)', 500 | vid_ID_text, 501 | ).group(1) 502 | decoded_atob = b64decode(atob) 503 | json = loads(decoded_atob) 504 | values = str(json.values()) 505 | 506 | sources = json["sources"] 507 | vid_ID = json["slug"] 508 | atob_ID = json["id"] 509 | atob_domain = json["domain"] 510 | 511 | print(f"\n{bcolors.BOLD}Getting content length: {vid_ID}{bcolors.ENDC}\n") 512 | 513 | resolution_option = {} 514 | quality_prefix = {} 515 | piece_length = {} 516 | extension = {} 517 | if "360p" in values: 518 | resolution_option.update({"1": "360p"}) 519 | quality_prefix.update({"1": ""}) 520 | piece_length.update({"1": get_content_length(atob_domain, "", atob_ID)}) 521 | extension.update({"1": search(r"360p.+?type': '(.+?)'", str(sources)).group(1)}) 522 | if "720p" in values: 523 | resolution_option.update({"2": "720p"}) 524 | quality_prefix.update({"2": "www"}) 525 | piece_length.update({"2": get_content_length(atob_domain, "www", atob_ID)}) 526 | extension.update({"2": search(r"720p.+?type': '(.+?)'", str(sources)).group(1)}) 527 | if "1080p" in values: 528 | resolution_option.update({"3": "1080p"}) 529 | quality_prefix.update({"3": "whw"}) 530 | piece_length.update({"3": get_content_length(atob_domain, "whw", atob_ID)}) 531 | extension.update( 532 | {"3": search(r"1080p.+?type': '(.+?)'", str(sources)).group(1)} 533 | ) 534 | 535 | available_resolution = ", ".join([i for i in resolution_option.values()]) 536 | 537 | global max_quality 538 | if str(max_quality) not in ["1", "2", "3"]: 539 | print( 540 | f"""{bcolors.WARNING}Invalid max_quality: "{max_quality}", using "3"{bcolors.ENDC}""" 541 | ) 542 | max_quality = 3 543 | 544 | try: 545 | quality = max([i for i in resolution_option if i <= str(max_quality)]) 546 | except Exception: 547 | quality = min(resolution_option.keys()) 548 | 549 | file_name = f"{vid_ID}_{resolution_option[quality]}.{extension[quality]}" 550 | download_path = join(abspath(download_directory), file_name) 551 | 552 | return ( 553 | vid_ID, 554 | available_resolution, 555 | values, 556 | resolution_option, 557 | extension, 558 | download_path, 559 | piece_length, 560 | quality, 561 | file_name, 562 | atob_domain, 563 | quality_prefix, 564 | atob_ID, 565 | ) 566 | 567 | 568 | def generate_range_split(file_size, split): 569 | result_size = 0 570 | chunk_range = [] 571 | chunk_size = file_size // split 572 | for i in range(split): 573 | min_val = i * chunk_size 574 | max_val = min_val + chunk_size - 1 575 | if i == split - 1: 576 | max_val = file_size - 1 577 | 578 | result_size += max_val - min_val + 1 579 | chunk_range.append(f"{min_val}-{max_val}") 580 | 581 | return chunk_range, chunk_size 582 | 583 | 584 | def generate_range_byte(file_size, chunk_size): 585 | chunk_range = [] 586 | for i in range(0, file_size, chunk_size): 587 | min_val = i 588 | max_val = min(i + chunk_size - 1, file_size - 1) 589 | chunk_range.append(f"{min_val}-{max_val}") 590 | 591 | return chunk_range, chunk_size 592 | 593 | 594 | def get_content_length(atob_domain, quality_prefix, atob_ID): 595 | url = f"https://{atob_domain}/{quality_prefix}{atob_ID}" 596 | headers = {"Referer": "https://abysscdn.com/", "Range": "bytes=0-1"} 597 | for i in range(request_retry): 598 | i += 1 599 | if i == request_retry: 600 | raise Exception(f"\n{bcolors.FAIL}Reached max retry{bcolors.ENDC}") 601 | 602 | try: 603 | r = get(url, headers=headers, timeout=request_timeout) 604 | 605 | if r.status_code != 200 and r.status_code != 206: 606 | print( 607 | f"\n{bcolors.WARNING}Retrying {i}/{request_retry}... {url}{bcolors.ENDC}\n" 608 | ) 609 | sleep(request_wait) 610 | else: 611 | content_length = str(r.headers["Content-Range"]).split("/")[1] 612 | break 613 | 614 | except Timeout as err: 615 | print( 616 | error := f""" 617 | {bcolors.WARNING}Connection timed out - get_content_length - {url} 618 | {err} 619 | Retrying {i}/{request_retry}... {url}{bcolors.ENDC} 620 | """ 621 | ) 622 | log_error(error) 623 | sleep(request_wait) 624 | except RequestException as err: 625 | print( 626 | error := f""" 627 | {bcolors.WARNING}Request exception - get_content_length - {url} 628 | {err} 629 | Retrying {i}/{request_retry}... {url}{bcolors.ENDC} 630 | """ 631 | ) 632 | log_error(error) 633 | sleep(request_wait) 634 | 635 | return content_length 636 | 637 | 638 | def get_size(file): 639 | with open(file, "rb") as f: 640 | size = f.read() 641 | 642 | return len(size) 643 | 644 | 645 | def get_input(): 646 | print( 647 | f"{bcolors.WARNING}If download slows down, try restarting the program{bcolors.ENDC}\n" 648 | ) 649 | print("To download multiple videos at once, separate Vid_ID with space\n") 650 | 651 | while True: 652 | vid_ID_list = list(filter(None, input("Enter Vid_ID: ").split(" "))) 653 | if vid_ID_list: 654 | vid_ID_list = list(dict.fromkeys(vid_ID_list)) 655 | return vid_ID_list 656 | 657 | 658 | def turbo_download(): 659 | if turbo_squared: 660 | print(f"{bcolors.FAIL}{bcolors.BOLD}[Turbo Mode Squared]{bcolors.ENDC}") 661 | else: 662 | print(f"{bcolors.OKGREEN}{bcolors.BOLD}[Turbo Mode]{bcolors.ENDC}") 663 | 664 | vid_ID_list = get_input() 665 | vid_ID_list_text = get_vid_ID_text(vid_ID_list) 666 | 667 | if not turbo_squared: 668 | for vid_ID_text in vid_ID_list_text: 669 | get_turbo_download(vid_ID_text) 670 | else: 671 | with ThreadPoolExecutor() as pool: 672 | pool.map(get_turbo_download, vid_ID_list_text) 673 | 674 | 675 | def automatic_download(): 676 | print(f"{bcolors.OKGREEN}{bcolors.BOLD}[Automatic Mode]{bcolors.ENDC}") 677 | 678 | vid_ID_list = get_input() 679 | vid_ID_list_text = get_vid_ID_text(vid_ID_list) 680 | 681 | with ThreadPoolExecutor() as pool: 682 | pool.map(download, vid_ID_list_text) 683 | 684 | 685 | def manual_download(): 686 | print( 687 | f"{bcolors.OKGREEN}{bcolors.BOLD}[Manual Mode]{bcolors.ENDC} {bcolors.WARNING}Simultaneous download not available{bcolors.ENDC}" 688 | ) 689 | 690 | vid_ID_list = get_input() 691 | vid_ID_list_text = get_vid_ID_text(vid_ID_list) 692 | 693 | for vid_ID_text in vid_ID_list_text: 694 | download(vid_ID_text) 695 | 696 | 697 | def version_check(): 698 | try: 699 | r = get( 700 | "https://api.github.com/repos/PatrickL546/Hydrax-Abyss.to-DownloadHelper-Python/releases/latest", 701 | timeout=3, 702 | ) 703 | 704 | online_version = r.json()["name"] 705 | new_version_link = r.json()["html_url"] 706 | 707 | if online_version > version: 708 | print( 709 | f"{bcolors.OKGREEN}New version available: {online_version}{bcolors.ENDC}" 710 | ) 711 | print(f"{bcolors.UNDERLINE}{new_version_link}{bcolors.ENDC}") 712 | 713 | except Exception: 714 | print(f"{bcolors.FAIL}Failed to check for update{bcolors.ENDC}") 715 | 716 | 717 | def main(): 718 | system(f"title Hydrax-Abyss.to-DownloadHelper {version}") 719 | version_check() 720 | 721 | if turbo: 722 | turbo_download() 723 | elif automatic: 724 | automatic_download() 725 | else: 726 | manual_download() 727 | 728 | 729 | if __name__ == "__main__": 730 | 731 | class bcolors: 732 | HEADER = "\033[95m" 733 | OKBLUE = "\033[94m" 734 | OKGREEN = "\033[92m" 735 | WARNING = "\033[93m" 736 | FAIL = "\033[91m" 737 | BOLD = "\033[1m" 738 | UNDERLINE = "\033[4m" 739 | ENDC = "\033[0m" 740 | 741 | failed_delete = False 742 | main() 743 | print("\n" * 5) 744 | 745 | if failed_delete and not fragments_to_temp: 746 | print(f"{bcolors.WARNING}Leftover fragments are safe to delete{bcolors.ENDC}") 747 | 748 | input("Done! press Enter to exit\n") 749 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 PatrickL546 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > # I am not updating this repo since I don't have use for it anymore and I don't have time to update it. Check out these repos instead. 3 | > # https://github.com/abdlhay/AbyssVideoDownloader 4 | > # https://github.com/DevLARLEY/AbyssGet 5 | 6 | # [Info](https://github.com/PatrickL546/How-to-download-hydrax-abyss.to) 7 | 8 | # [Userscript downloader](https://github.com/PatrickL546/Hydrax-Abyss.to-DownloadHelper-Userscript) 9 | 10 | ## Requirements 11 | 12 | - Vid_ID of the video. Use my [Userscript](https://github.com/PatrickL546/Hydrax-Abyss.to-DownloadHelper-Userscript) or read [my guide](https://github.com/PatrickL546/How-to-download-hydrax-abyss.to) to get it 13 | 14 | ![image](https://github.com/user-attachments/assets/c4499f2f-6593-45af-8a1d-cf257347fc89) 15 | 16 | - Windows 10 with latest updates 17 | 18 | - Install additional library listed in `requirements.txt` 19 | 20 | ## Installation 21 | 22 | - Download [Python](https://www.python.org/) 23 | 24 | - Get the latest release [here](https://github.com/PatrickL546/Hydrax-Abyss.to-DownloadHelper-Python/releases/latest). Download `Source code` zip or tar.gz 25 | 26 | - Extract the archive 27 | 28 | - Install the requirements. [How to install requirements](https://packaging.python.org/en/latest/tutorials/installing-packages/#requirements-files) 29 | 30 | - Run `Hydrax-Abyss.to-DownloadHelper.py` 31 | 32 | ## Usage 33 | 34 | - Just enter the Vid_ID to download them all at once 35 | 36 | - If the download was not completed. It will continue where it left off 37 | 38 | ### Mode 39 | 40 | - Turbo Mode Squared 41 | 42 | ![image](https://github.com/user-attachments/assets/f95c05cf-1721-4b48-8a4a-7b41b61d28e7) 43 | 44 | - Turbo Mode 45 | 46 | ![image](https://github.com/user-attachments/assets/ba53d181-42cc-442b-91ba-f1cb122043bc) 47 | 48 | - Automatic Mode 49 | 50 | ![image](https://github.com/user-attachments/assets/20a367b2-4808-4aed-8cc0-c495137144f0) 51 | 52 | - Manual Mode 53 | 54 | ![image](https://github.com/user-attachments/assets/17950047-daa1-4d4d-82d2-2a0fb5a1719c) 55 | 56 | ## Customize 57 | 58 | - Open `Hydrax-Abyss.to-DownloadHelper.py` in a text editor to change settings 59 | 60 | ```Python 61 | # 1 = 360p 62 | # 2 = 720p 63 | # 3 = 1080p 64 | max_quality = 3 # Max resolution for automatic selection, or uses minimum available 65 | automatic = True # Set "False" to select resolution manually 66 | download_directory = r"" # Set download directory, insert path inside "" 67 | request_timeout = 180 # Seconds to wait between bytes before timeout 68 | request_retry = 60 # Retry attempts 69 | request_wait = 6 # Seconds to wait before retrying 70 | error_file = "Abyss_error.log" # File name of error log, insert name inside "" 71 | enable_error_log = True # Set "True" to enable error logging to file 72 | 73 | turbo = True # Set "True" to multithread download 74 | turbo_squared = False # Set "True" to download all Vid_ID at the same time 75 | delete_fragment = True # Set "True" to delete downloaded fragments 76 | active_download = 10 # Max active download connections 77 | fragments_to_temp = True # Set "False" to download fragments in `download_directory` instead of `%TEMP%` 78 | 79 | split_by_bytes = True # Set "True" to split by bytes with size `turbo_chunk_size_bytes` 80 | # Set "False" to split by fragment files in amount of files `turbo_fragment` 81 | turbo_chunk_size_bytes = 65536 * 128 # Size of each fragment in bytes 82 | # Or 83 | turbo_fragment = 60 # Number of fragment files the video will get divided into 84 | ``` 85 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Requests==2.32.3 2 | stpyv8==12.7.224.18 3 | tqdm==4.66.4 4 | --------------------------------------------------------------------------------