eaf-base-api 2.4.7__tar.gz → 2.4.8__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.4.7
3
+ Version: 2.4.8
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
6
6
  License: LGPLv3
@@ -369,7 +369,7 @@ class Helper:
369
369
  ):
370
370
  """
371
371
  Yields Video/ErrorVideo in deterministic (page_idx, vid_idx) order while fetching concurrently.
372
- Extremely detailed logs are emitted for: scheduling, completion, ordering, and exceptions.
372
+ Stops scraping pages once a 404 page is encountered (self.core.fetch returns an httpx.Response).
373
373
  """
374
374
  logger = self.logger
375
375
  run_id = uuid.uuid4().hex[:8] # correlate all logs for this iterator call
@@ -395,6 +395,10 @@ class Helper:
395
395
  next_page_idx = 0 # ordering cursor
396
396
  next_video_idx = 0
397
397
 
398
+ # NEW: stop paging after first 404
399
+ stop_after_404 = False
400
+ stop_at_page_idx: Optional[int] = None
401
+
398
402
  def flush_ready():
399
403
  nonlocal next_page_idx, next_video_idx
400
404
  flushed = 0
@@ -444,17 +448,15 @@ class Helper:
444
448
  # queue of discovered videos we haven't submitted yet
445
449
  pending_videos: deque[Tuple[int, int, str]] = deque()
446
450
 
447
- # helper to respect videos_concurrency strictly, otherwise we gonna be rate limited instantly lol
451
+ # helper to respect videos_concurrency strictly
448
452
  def schedule_videos():
449
453
  scheduled = 0
450
454
  while pending_videos and len(video_in_flight) < videos_concurrency:
451
455
  pidx, vid_idx, vurl = pending_videos.popleft()
452
456
  if other_return:
453
457
  vf = video_executor.submit(self._other_return, vurl)
454
-
455
458
  else:
456
459
  vf = video_executor.submit(self._make_video_safe, vurl)
457
-
458
460
  video_in_flight[vf] = (pidx, vid_idx)
459
461
  scheduled += 1
460
462
  logger.debug(
@@ -491,7 +493,6 @@ class Helper:
491
493
 
492
494
  # If nothing in flight but items queued (shouldn't happen often), try to schedule then continue
493
495
  if not waiting_on:
494
- # No futures to wait on; try to schedule (covers corner cases)
495
496
  schedule_videos()
496
497
  if not (page_in_flight or video_in_flight):
497
498
  break
@@ -504,7 +505,7 @@ class Helper:
504
505
  if fut in page_in_flight:
505
506
  pidx, url = page_in_flight.pop(fut)
506
507
  try:
507
- html = fut.result()
508
+ html_or_resp = fut.result()
508
509
  logger.info(
509
510
  "[%s] PAGE done pidx=%d url=%s (inflight_page=%d inflight_video=%d)",
510
511
  run_id, pidx, url, len(page_in_flight), len(video_in_flight)
@@ -518,20 +519,56 @@ class Helper:
518
519
  page_counts[pidx] = 0
519
520
  # now-ordered items may be ready
520
521
  yield from flush_ready()
521
- # schedule next page if available
522
- try:
523
- npidx, nurl = next(page_iter)
524
- nfut = page_executor.submit(self.core.fetch, nurl)
525
- page_in_flight[nfut] = (npidx, nurl)
526
- logger.debug(
527
- "[%s] scheduled NEXT PAGE pidx=%d url=%s after failure",
528
- run_id, npidx, nurl
529
- )
530
- except StopIteration:
531
- pass
522
+ # schedule next page if available (unless we've already stopped)
523
+ if not stop_after_404:
524
+ try:
525
+ npidx, nurl = next(page_iter)
526
+ nfut = page_executor.submit(self.core.fetch, nurl)
527
+ page_in_flight[nfut] = (npidx, nurl)
528
+ logger.debug(
529
+ "[%s] scheduled NEXT PAGE pidx=%d url=%s after failure",
530
+ run_id, npidx, nurl
531
+ )
532
+ except StopIteration:
533
+ pass
532
534
  continue
533
535
 
534
- # Extract video URLs
536
+ # Detect a 404 "page" (httpx.Response returned)
537
+ is_404 = False
538
+ try:
539
+ if isinstance(html_or_resp, httpx.Response) and html_or_resp.status_code == 404:
540
+ is_404 = True
541
+ except Exception:
542
+ # Fallback duck-typing if httpx import fails for some reason
543
+ if getattr(html_or_resp, "status_code", None) == 404 and hasattr(html_or_resp, "headers"):
544
+ is_404 = True
545
+
546
+ if is_404:
547
+ logger.warning(
548
+ "[%s] PAGE 404 pidx=%d url=%s -> stopping further page scheduling",
549
+ run_id, pidx, url
550
+ )
551
+ stop_after_404 = True
552
+ stop_at_page_idx = pidx
553
+ # 404 page has 0 videos
554
+ page_counts[pidx] = 0
555
+ # Flush anything now ready
556
+ yield from flush_ready()
557
+ # Do NOT schedule any more pages
558
+ continue
559
+
560
+ # If we've already hit a 404 and this page index is after it, ignore this page
561
+ if stop_after_404 and stop_at_page_idx is not None and pidx > stop_at_page_idx:
562
+ logger.info(
563
+ "[%s] ignoring PAGE pidx=%d url=%s (after first 404 at pidx=%d)",
564
+ run_id, pidx, url, stop_at_page_idx
565
+ )
566
+ page_counts[pidx] = 0
567
+ yield from flush_ready()
568
+ continue
569
+
570
+ # Normal path: Extract video URLs
571
+ html = html_or_resp
535
572
  try:
536
573
  video_urls = extractor(html) or []
537
574
  logger.debug(
@@ -551,26 +588,24 @@ class Helper:
551
588
  run_id, pidx, page_counts[pidx]
552
589
  )
553
590
 
554
- # NEW: queue videos instead of submitting all at once
555
591
  for vid_idx, vurl in enumerate(video_urls):
556
592
  pending_videos.append((pidx, vid_idx, vurl))
557
- # Fill available slots respecting videos_concurrency
558
593
  schedule_videos()
559
594
 
560
- # Try flushing any now-ready items
561
595
  yield from flush_ready()
562
596
 
563
- # Fetch next page
564
- try:
565
- npidx, nurl = next(page_iter)
566
- nfut = page_executor.submit(self.core.fetch, nurl)
567
- page_in_flight[nfut] = (npidx, nurl)
568
- logger.debug(
569
- "[%s] scheduled NEXT PAGE pidx=%d url=%s (inflight_page=%d)",
570
- run_id, npidx, nurl, len(page_in_flight)
571
- )
572
- except StopIteration:
573
- logger.debug("[%s] no more pages to schedule", run_id)
597
+ # Fetch next page only if we haven't seen a 404
598
+ if not stop_after_404:
599
+ try:
600
+ npidx, nurl = next(page_iter)
601
+ nfut = page_executor.submit(self.core.fetch, nurl)
602
+ page_in_flight[nfut] = (npidx, nurl)
603
+ logger.debug(
604
+ "[%s] scheduled NEXT PAGE pidx=%d url=%s (inflight_page=%d)",
605
+ run_id, npidx, nurl, len(page_in_flight)
606
+ )
607
+ except StopIteration:
608
+ logger.debug("[%s] no more pages to schedule", run_id)
574
609
 
575
610
  # VIDEO completed
576
611
  elif fut in video_in_flight:
@@ -858,11 +893,7 @@ class BaseCore:
858
893
 
859
894
  # 404: usually not recoverable, but we try once (if attempt==0) in case of transient CDN
860
895
  if status == 404:
861
- if attempt == 0 and max_retries > 1:
862
- self.logger.info(f"Got 404 once for {url}, retrying in case of transient edge/CDN issue.")
863
- continue
864
- self.logger.error(f"Resource not found (404) for URL: {url}")
865
- return response
896
+ return response # Immediately returning response, because 404 usually means no more content when searching
866
897
 
867
898
  # 410: permanent gone
868
899
  if status == 410:
@@ -1279,85 +1310,90 @@ class BaseCore:
1279
1310
  raise ModuleNotFoundError(f"PyAV is required for remuxing. Install with pip install av. Not supported on Termux! {e}")
1280
1311
 
1281
1312
  input_ = av_open(input_path)
1282
- output = av_open(output_path, mode="w", format="mp4", options={"movflags": "faststart"})
1283
-
1284
- # --- VIDEO: keep your exact remux approach ---
1285
- in_video = input_.streams.video[0]
1286
- out_video = output.add_stream_from_template(template=in_video)
1287
-
1288
- # --- AUDIO: copy if MP4-compatible; else transcode to AAC ---
1289
- in_audio = next((s for s in input_.streams if s.type == "audio"), None)
1290
- out_audio = None
1291
- transcode_audio = False
1292
- resampler = None
1293
-
1294
- if in_audio:
1295
- # Common MP4-safe audio codecs to copy without transcoding.
1296
- copy_ok = {"aac", "alac", "mp3"}
1297
- codec_name = (in_audio.codec_context.name or "").lower()
1298
-
1299
- if codec_name in copy_ok:
1300
- out_audio = output.add_stream_from_template(template=in_audio)
1301
- else:
1302
- # Build an AAC encoder stream. Match input rate/layout where possible.
1303
- transcode_audio = True
1304
- sample_rate = in_audio.codec_context.sample_rate or 48000
1305
- layout = (
1306
- in_audio.codec_context.layout.name
1307
- if getattr(in_audio.codec_context, "layout", None)
1308
- else "stereo"
1309
- )
1310
- out_audio = output.add_stream("aac", rate=sample_rate)
1311
- # Setting layout/channels helps encoder pick sensible defaults.
1312
- try:
1313
- out_audio.layout = layout
1314
- except Exception:
1315
- pass
1313
+ if input_.format.name.lower() == "mpegts":
1314
+ output = av_open(output_path, mode="w", format="mp4", options={"movflags": "faststart"})
1315
+
1316
+ # --- VIDEO: keep your exact remux approach ---
1317
+ in_video = input_.streams.video[0]
1318
+ out_video = output.add_stream_from_template(template=in_video)
1319
+
1320
+ # --- AUDIO: copy if MP4-compatible; else transcode to AAC ---
1321
+ in_audio = next((s for s in input_.streams if s.type == "audio"), None)
1322
+ out_audio = None
1323
+ transcode_audio = False
1324
+ resampler = None
1325
+
1326
+ if in_audio:
1327
+ # Common MP4-safe audio codecs to copy without transcoding.
1328
+ copy_ok = {"aac", "alac", "mp3"}
1329
+ codec_name = (in_audio.codec_context.name or "").lower()
1330
+
1331
+ if codec_name in copy_ok:
1332
+ out_audio = output.add_stream_from_template(template=in_audio)
1333
+ else:
1334
+ # Build an AAC encoder stream. Match input rate/layout where possible.
1335
+ transcode_audio = True
1336
+ sample_rate = in_audio.codec_context.sample_rate or 48000
1337
+ layout = (
1338
+ in_audio.codec_context.layout.name
1339
+ if getattr(in_audio.codec_context, "layout", None)
1340
+ else "stereo"
1341
+ )
1342
+ out_audio = output.add_stream("aac", rate=sample_rate)
1343
+ # Setting layout/channels helps encoder pick sensible defaults.
1344
+ try:
1345
+ out_audio.layout = layout
1346
+ except Exception:
1347
+ pass
1348
+
1349
+ # Ensure frames handed to the AAC encoder are in a compatible sample format.
1350
+ # ('fltp' is the usual format for AAC encoders.)
1351
+ resampler = AudioResampler(format="fltp", layout=layout, rate=sample_rate)
1352
+
1353
+ # --- DEMUX both streams so audio is included ---
1354
+ demux_streams = [in_video] + ([in_audio] if in_audio else [])
1355
+ packets = list(input_.demux(demux_streams)) # keeps your progress logic
1356
+ total = len(packets)
1357
+
1358
+ for idx, packet in enumerate(packets):
1359
+ if packet.dts is None:
1360
+ if callback:
1361
+ callback(idx + 1, total)
1362
+ continue
1316
1363
 
1317
- # Ensure frames handed to the AAC encoder are in a compatible sample format.
1318
- # ('fltp' is the usual format for AAC encoders.)
1319
- resampler = AudioResampler(format="fltp", layout=layout, rate=sample_rate)
1364
+ if packet.stream == in_video:
1365
+ # Your original video remux path (no explicit rescale).
1366
+ packet.stream = out_video
1367
+ output.mux(packet)
1320
1368
 
1321
- # --- DEMUX both streams so audio is included ---
1322
- demux_streams = [in_video] + ([in_audio] if in_audio else [])
1323
- packets = list(input_.demux(demux_streams)) # keeps your progress logic
1324
- total = len(packets)
1369
+ elif in_audio and packet.stream == in_audio:
1370
+ if not transcode_audio:
1371
+ # Audio is already MP4-compatible; just remux.
1372
+ packet.stream = out_audio
1373
+ output.mux(packet)
1374
+ else:
1375
+ # Decode -> (optionally resample) -> encode AAC -> mux.
1376
+ for frame in packet.decode():
1377
+ # Resample to match encoder expectations.
1378
+ frames = resampler.resample(frame) if resampler else [frame]
1379
+ for f in frames:
1380
+ for enc_pkt in out_audio.encode(f):
1381
+ output.mux(enc_pkt)
1325
1382
 
1326
- for idx, packet in enumerate(packets):
1327
- if packet.dts is None:
1328
1383
  if callback:
1329
1384
  callback(idx + 1, total)
1330
- continue
1331
1385
 
1332
- if packet.stream == in_video:
1333
- # Your original video remux path (no explicit rescale).
1334
- packet.stream = out_video
1335
- output.mux(packet)
1386
+ # Flush audio encoder if we transcoded.
1387
+ if transcode_audio and out_audio:
1388
+ for enc_pkt in out_audio.encode(None):
1389
+ output.mux(enc_pkt)
1336
1390
 
1337
- elif in_audio and packet.stream == in_audio:
1338
- if not transcode_audio:
1339
- # Audio is already MP4-compatible; just remux.
1340
- packet.stream = out_audio
1341
- output.mux(packet)
1342
- else:
1343
- # Decode -> (optionally resample) -> encode AAC -> mux.
1344
- for frame in packet.decode():
1345
- # Resample to match encoder expectations.
1346
- frames = resampler.resample(frame) if resampler else [frame]
1347
- for f in frames:
1348
- for enc_pkt in out_audio.encode(f):
1349
- output.mux(enc_pkt)
1350
-
1351
- if callback:
1352
- callback(idx + 1, total)
1353
-
1354
- # Flush audio encoder if we transcoded.
1355
- if transcode_audio and out_audio:
1356
- for enc_pkt in out_audio.encode(None):
1357
- output.mux(enc_pkt)
1358
-
1359
- input_.close()
1360
- output.close()
1391
+ input_.close()
1392
+ output.close()
1393
+
1394
+ else:
1395
+ self.logger.info("Stream seems to be already in MP4! Skipping remux...")
1396
+ os.rename(input_path, output_path)
1361
1397
 
1362
1398
  def FFMPEG(self, video, quality: str, callback, path: str) -> bool:
1363
1399
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eaf_base_api
3
- Version: 2.4.7
3
+ Version: 2.4.8
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
6
6
  License: LGPLv3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "eaf_base_api"
7
- version = "2.4.7"
7
+ version = "2.4.8"
8
8
  description = "A base API for EchterAlsFake's Porn APIs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6"
File without changes
File without changes
File without changes
File without changes