eaf-base-api 2.3.3__tar.gz → 2.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eaf_base_api
3
- Version: 2.3.3
3
+ Version: 2.3.4
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Home-page: https://github.com/EchterAlsFake/eaf_base_api
6
6
  Author: Johannes Habel
@@ -10,9 +10,12 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
10
10
  Classifier: Programming Language :: Python
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: ffmpeg-progress-yield
14
13
  Requires-Dist: m3u8
15
14
  Requires-Dist: httpx
15
+ Provides-Extra: remux
16
+ Requires-Dist: av; extra == "remux"
17
+ Provides-Extra: ffmpeg
18
+ Requires-Dist: ffmpeg-progress-yield; extra == "ffmpeg"
16
19
  Dynamic: author
17
20
  Dynamic: author-email
18
21
  Dynamic: classifier
@@ -21,6 +24,7 @@ Dynamic: description-content-type
21
24
  Dynamic: home-page
22
25
  Dynamic: license
23
26
  Dynamic: license-file
27
+ Dynamic: provides-extra
24
28
  Dynamic: requires-dist
25
29
  Dynamic: summary
26
30
 
@@ -11,7 +11,6 @@ import threading
11
11
  from typing import Union
12
12
  from functools import lru_cache
13
13
  from urllib.parse import urljoin
14
- from ffmpeg_progress_yield import FfmpegProgress
15
14
  from concurrent.futures import ThreadPoolExecutor, as_completed
16
15
 
17
16
  try:
@@ -470,13 +469,24 @@ class BaseCore:
470
469
  self.logger.debug(f"Fetched {len(segments)} segments from m3u8 URL")
471
470
  return segments
472
471
 
473
- def download(self, video, quality: str, downloader: str, path: str, callback=None) -> None:
472
+ def download_segment(self, url: str, timeout: int) -> tuple[str, bytes, bool]:
473
+ """
474
+ Attempt to download a single segment, retrying on failure.
475
+ Returns a tuple of the URL, content (empty if failed after retries), and a success flag.
476
+ """
477
+ content = self.fetch(url, timeout=timeout, get_bytes=True)
478
+ return url, content, True # Success
479
+
480
+ def download(self, video, quality: str, downloader: str, path: str, callback=None, remux: bool = False,
481
+ callback_remux=None) -> None:
474
482
  """
475
483
  :param video:
476
484
  :param callback:
477
485
  :param downloader:
478
486
  :param quality:
479
487
  :param path:
488
+ :param remux:
489
+ :param callback_remux:
480
490
  :return:
481
491
  """
482
492
 
@@ -484,92 +494,117 @@ class BaseCore:
484
494
  callback = Callback.text_progress_bar
485
495
 
486
496
  if downloader == "default":
487
- self.default(video=video, quality=quality, path=path, callback=callback)
497
+ self.default(video=video, quality=quality, path=path, callback=callback, remux=remux, callback_remux=callback_remux)
488
498
 
489
499
  elif downloader == "threaded":
490
500
  threaded_download = self.threaded(max_workers=20, timeout=10)
491
- threaded_download(self, video=video, quality=quality, path=path, callback=callback)
501
+ threaded_download(self, video=video, quality=quality, path=path, callback=callback, remux=remux,
502
+ callback_remux=callback_remux)
492
503
 
493
504
  elif downloader == "FFMPEG":
494
505
  self.FFMPEG(video=video, quality=quality, path=path, callback=callback)
495
506
 
496
- def download_segment(self, url: str, timeout: int) -> tuple[str, bytes, bool]:
497
- """
498
- Attempt to download a single segment, retrying on failure.
499
- Returns a tuple of the URL, content (empty if failed after retries), and a success flag.
500
- """
501
-
502
- content = self.fetch(url, timeout=timeout, get_bytes=True)
503
- return url, content, True # Success
504
-
505
507
  def threaded(self, max_workers: int = 20, timeout: int = 10):
506
- """
507
- Creates a wrapper function for the actual download process, with retry logic.
508
- """
509
-
510
- def wrapper(self, video, quality: str, callback, path: str):
511
- """
512
- Download video segments in parallel, with retries for failures, and write to a file.
513
- """
508
+ def wrapper(self, video, quality: str, callback, path: str, remux: bool = True, callback_remux=None):
514
509
  segments = self.get_segments(quality=quality, m3u8_url_master=video.m3u8_base_url)
515
510
  length = len(segments)
516
- completed, successful_downloads = 0, 0
511
+ completed = 0
512
+ ts_buffers = []
517
513
 
518
514
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
519
- future_to_hls_part = {
520
- executor.submit(self.download_segment, url, timeout): os.path.basename(url)
521
- for url in segments
515
+ future_to_url = {
516
+ executor.submit(self.download_segment, url, timeout): url for url in segments
522
517
  }
523
518
 
524
- for future in as_completed(future_to_hls_part):
525
- hls_part = future_to_hls_part[future]
519
+ for future in as_completed(future_to_url):
520
+ url = future_to_url[future]
526
521
  try:
527
522
  _, data, success = future.result()
528
523
  completed += 1
529
524
  if success:
530
- successful_downloads += 1
531
- callback(completed, length) # Update progress callback
525
+ ts_buffers.append((segments.index(url), data))
526
+ callback(completed, length)
532
527
  except Exception as e:
533
- self.logger.error(f"Error processing segment {hls_part}: {e}")
534
- raise SegmentError(f"Error processing segment {hls_part}: {e}")
535
-
536
- # Write only successful segments to the output file
537
- with open(path, 'wb') as file:
538
- for segment_url in segments:
539
- matched_futures = [
540
- future for future, hls_part in future_to_hls_part.items()
541
- if hls_part == os.path.basename(segment_url)
542
- ]
543
- if matched_futures:
544
- future = matched_futures[0]
545
- try:
546
- _, data, success = future.result()
547
- if success:
548
- file.write(data)
549
- except Exception as e:
550
- self.logger.error(f"Exception writing segment {segment_url}: {e}")
528
+ self.logger.error(f"Error processing segment {url}: {e}")
529
+ raise SegmentError(f"Error processing segment {url}: {e}")
530
+
531
+ ts_buffers.sort(key=lambda x: x[0])
532
+ ts_combined = b''.join(data for _, data in ts_buffers)
533
+
534
+ if remux:
535
+ tmp_path = f"{path}.tmp"
536
+ with open(tmp_path, 'wb') as file:
537
+ file.write(ts_combined)
538
+ self._convert_ts_to_mp4(tmp_path, path, callback=callback_remux)
539
+ os.remove(tmp_path)
540
+ else:
541
+ with open(path, 'wb') as file:
542
+ file.write(ts_combined)
551
543
 
552
544
  return wrapper
553
545
 
554
- def default(self, video, quality, callback, path, start: int = 0) -> bool:
546
+ def default(self, video, quality, callback, path, start: int = 0, remux: bool = True, callback_remux=None) -> bool:
555
547
  buffer = b''
556
548
  segments = self.get_segments(quality=quality, m3u8_url_master=video.m3u8_base_url)[start:]
557
549
  length = len(segments)
558
550
 
559
551
  for i, url in enumerate(segments):
560
552
  for _ in range(5):
553
+ try:
554
+ segment = self.fetch(url, get_bytes=True)
555
+ buffer += segment
556
+ callback(i + 1, length)
557
+ break
558
+ except Exception as e:
559
+ self.logger.error(f"Failed to fetch segment {url}: {e}")
560
+ continue
561
+
562
+ if remux:
563
+ tmp_path = f"{path}.tmp"
564
+ with open(tmp_path, 'wb') as file:
565
+ file.write(buffer)
566
+ self._convert_ts_to_mp4(tmp_path, path, callback=callback_remux)
567
+ os.remove(tmp_path)
568
+ else:
569
+ with open(path, 'wb') as file:
570
+ file.write(buffer)
571
+
572
+ return True
561
573
 
562
- segment = self.fetch(url, get_bytes=True)
563
- buffer += segment
564
- callback(i + 1, length)
565
- break
574
+ def _convert_ts_to_mp4(self, input_path: str, output_path: str, callback=None):
575
+ try:
576
+ import av
577
+ except (ModuleNotFoundError, ImportError):
578
+ raise ModuleNotFoundError("PyAV is required for remuxing. Install with `pip install av`. Not supported on Termux!")
566
579
 
567
- with open(path, 'wb') as file:
568
- file.write(buffer)
580
+ input_ = av.open(input_path)
581
+ output = av.open(output_path, "w")
582
+
583
+ in_stream = input_.streams.video[0]
584
+ out_stream = output.add_stream_from_template(template=in_stream)
585
+
586
+ packets = list(input_.demux(in_stream))
587
+ total = len(packets)
588
+
589
+ for idx, packet in enumerate(packets):
590
+ if packet.dts is None:
591
+ continue
592
+ packet.stream = out_stream
593
+ output.mux(packet)
594
+ if callback:
595
+ callback(idx + 1, total)
596
+
597
+ input_.close()
598
+ output.close()
569
599
 
570
- return True
571
600
 
572
601
  def FFMPEG(self, video, quality: str, callback, path: str) -> bool:
602
+ try:
603
+ from ffmpeg_progress_yield import FfmpegProgress
604
+
605
+ except (ModuleNotFoundError, ImportError):
606
+ raise ModuleNotFoundError("To use FFmpeg progress, you need to install ffmpeg-progress-yield. "
607
+ "You can do so with: `pip install ffmpeg-progress-yield`")
573
608
  base_url = video.m3u8_base_url
574
609
  new_segment = self.get_m3u8_by_quality(quality=quality, m3u8_url=base_url)
575
610
  url_components = base_url.split('/')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eaf_base_api
3
- Version: 2.3.3
3
+ Version: 2.3.4
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Home-page: https://github.com/EchterAlsFake/eaf_base_api
6
6
  Author: Johannes Habel
@@ -10,9 +10,12 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
10
10
  Classifier: Programming Language :: Python
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: ffmpeg-progress-yield
14
13
  Requires-Dist: m3u8
15
14
  Requires-Dist: httpx
15
+ Provides-Extra: remux
16
+ Requires-Dist: av; extra == "remux"
17
+ Provides-Extra: ffmpeg
18
+ Requires-Dist: ffmpeg-progress-yield; extra == "ffmpeg"
16
19
  Dynamic: author
17
20
  Dynamic: author-email
18
21
  Dynamic: classifier
@@ -21,6 +24,7 @@ Dynamic: description-content-type
21
24
  Dynamic: home-page
22
25
  Dynamic: license
23
26
  Dynamic: license-file
27
+ Dynamic: provides-extra
24
28
  Dynamic: requires-dist
25
29
  Dynamic: summary
26
30
 
@@ -1,3 +1,8 @@
1
- ffmpeg-progress-yield
2
1
  m3u8
3
2
  httpx
3
+
4
+ [ffmpeg]
5
+ ffmpeg-progress-yield
6
+
7
+ [remux]
8
+ av
@@ -2,9 +2,13 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="eaf_base_api",
5
- version="2.3.3",
5
+ version="2.3.4",
6
6
  packages=find_packages(),
7
- install_requires=["ffmpeg-progress-yield", "m3u8", "httpx"],
7
+ install_requires=["m3u8", "httpx"],
8
+ extras_require={
9
+ "remux": ["av"],
10
+ "ffmpeg": ["ffmpeg-progress-yield"],
11
+ },
8
12
  entry_points={
9
13
  'console_scripts': [
10
14
  # If you want to create any executable scripts
File without changes
File without changes
File without changes