eaf-base-api 2.4.7__tar.gz → 2.4.9__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.
- {eaf_base_api-2.4.7/eaf_base_api.egg-info → eaf_base_api-2.4.9}/PKG-INFO +1 -1
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/base.py +217 -141
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9/eaf_base_api.egg-info}/PKG-INFO +1 -1
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/pyproject.toml +1 -1
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/LICENSE +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/MANIFEST.in +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/README.md +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/__init__.py +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/modules/__init__.py +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/modules/config.py +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/modules/errors.py +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/base_api/modules/progress_bars.py +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/eaf_base_api.egg-info/SOURCES.txt +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/eaf_base_api.egg-info/dependency_links.txt +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/eaf_base_api.egg-info/requires.txt +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/eaf_base_api.egg-info/top_level.txt +0 -0
- {eaf_base_api-2.4.7 → eaf_base_api-2.4.9}/setup.cfg +0 -0
|
@@ -7,6 +7,7 @@ import time
|
|
|
7
7
|
import m3u8
|
|
8
8
|
import httpx
|
|
9
9
|
import random
|
|
10
|
+
import certifi
|
|
10
11
|
import logging
|
|
11
12
|
import traceback
|
|
12
13
|
import threading
|
|
@@ -34,6 +35,37 @@ loggers = {}
|
|
|
34
35
|
HEIGHT_FROM_URI = re.compile(r'(?<!\d)(\d{3,4})[pP](?!\d)') # e.g., 1080p, 720P
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
def _normalize_quality_value(q) -> Union[str, int]:
|
|
39
|
+
if isinstance(q, int):
|
|
40
|
+
return q
|
|
41
|
+
s = str(q).lower().strip()
|
|
42
|
+
if s in {"best", "half", "worst"}:
|
|
43
|
+
return s
|
|
44
|
+
m = re.search(r'(\d{3,4})', s)
|
|
45
|
+
if m:
|
|
46
|
+
return int(m.group(1))
|
|
47
|
+
raise ValueError(f"Invalid quality: {q}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _choose_quality_from_list(available: List[str | int], target: Union[str, int]):
|
|
51
|
+
# available like ["240", "360", "480", "720", "1080"]
|
|
52
|
+
av = sorted({int(x) for x in available})
|
|
53
|
+
if isinstance(target, str):
|
|
54
|
+
if target == "best":
|
|
55
|
+
return av[-1]
|
|
56
|
+
if target == "worst":
|
|
57
|
+
return av[0]
|
|
58
|
+
if target == "half":
|
|
59
|
+
return av[len(av) // 2]
|
|
60
|
+
raise ValueError("Invalid label.")
|
|
61
|
+
# numeric: highest ≤ target, else closest
|
|
62
|
+
le = [h for h in av if h <= target]
|
|
63
|
+
if le:
|
|
64
|
+
return le[-1]
|
|
65
|
+
# fallback closest (ties -> higher)
|
|
66
|
+
return min(av, key=lambda h: (abs(h - target), -h))
|
|
67
|
+
|
|
68
|
+
|
|
37
69
|
class ErrorVideo:
|
|
38
70
|
"""
|
|
39
71
|
Why?:
|
|
@@ -369,7 +401,7 @@ class Helper:
|
|
|
369
401
|
):
|
|
370
402
|
"""
|
|
371
403
|
Yields Video/ErrorVideo in deterministic (page_idx, vid_idx) order while fetching concurrently.
|
|
372
|
-
|
|
404
|
+
Stops scraping pages once a 404 page is encountered (self.core.fetch returns an httpx.Response).
|
|
373
405
|
"""
|
|
374
406
|
logger = self.logger
|
|
375
407
|
run_id = uuid.uuid4().hex[:8] # correlate all logs for this iterator call
|
|
@@ -395,6 +427,10 @@ class Helper:
|
|
|
395
427
|
next_page_idx = 0 # ordering cursor
|
|
396
428
|
next_video_idx = 0
|
|
397
429
|
|
|
430
|
+
# NEW: stop paging after first 404
|
|
431
|
+
stop_after_404 = False
|
|
432
|
+
stop_at_page_idx: Optional[int] = None
|
|
433
|
+
|
|
398
434
|
def flush_ready():
|
|
399
435
|
nonlocal next_page_idx, next_video_idx
|
|
400
436
|
flushed = 0
|
|
@@ -444,17 +480,15 @@ class Helper:
|
|
|
444
480
|
# queue of discovered videos we haven't submitted yet
|
|
445
481
|
pending_videos: deque[Tuple[int, int, str]] = deque()
|
|
446
482
|
|
|
447
|
-
# helper to respect videos_concurrency strictly
|
|
483
|
+
# helper to respect videos_concurrency strictly
|
|
448
484
|
def schedule_videos():
|
|
449
485
|
scheduled = 0
|
|
450
486
|
while pending_videos and len(video_in_flight) < videos_concurrency:
|
|
451
487
|
pidx, vid_idx, vurl = pending_videos.popleft()
|
|
452
488
|
if other_return:
|
|
453
489
|
vf = video_executor.submit(self._other_return, vurl)
|
|
454
|
-
|
|
455
490
|
else:
|
|
456
491
|
vf = video_executor.submit(self._make_video_safe, vurl)
|
|
457
|
-
|
|
458
492
|
video_in_flight[vf] = (pidx, vid_idx)
|
|
459
493
|
scheduled += 1
|
|
460
494
|
logger.debug(
|
|
@@ -491,7 +525,6 @@ class Helper:
|
|
|
491
525
|
|
|
492
526
|
# If nothing in flight but items queued (shouldn't happen often), try to schedule then continue
|
|
493
527
|
if not waiting_on:
|
|
494
|
-
# No futures to wait on; try to schedule (covers corner cases)
|
|
495
528
|
schedule_videos()
|
|
496
529
|
if not (page_in_flight or video_in_flight):
|
|
497
530
|
break
|
|
@@ -504,7 +537,7 @@ class Helper:
|
|
|
504
537
|
if fut in page_in_flight:
|
|
505
538
|
pidx, url = page_in_flight.pop(fut)
|
|
506
539
|
try:
|
|
507
|
-
|
|
540
|
+
html_or_resp = fut.result()
|
|
508
541
|
logger.info(
|
|
509
542
|
"[%s] PAGE done pidx=%d url=%s (inflight_page=%d inflight_video=%d)",
|
|
510
543
|
run_id, pidx, url, len(page_in_flight), len(video_in_flight)
|
|
@@ -518,20 +551,56 @@ class Helper:
|
|
|
518
551
|
page_counts[pidx] = 0
|
|
519
552
|
# now-ordered items may be ready
|
|
520
553
|
yield from flush_ready()
|
|
521
|
-
# schedule next page if available
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
554
|
+
# schedule next page if available (unless we've already stopped)
|
|
555
|
+
if not stop_after_404:
|
|
556
|
+
try:
|
|
557
|
+
npidx, nurl = next(page_iter)
|
|
558
|
+
nfut = page_executor.submit(self.core.fetch, nurl)
|
|
559
|
+
page_in_flight[nfut] = (npidx, nurl)
|
|
560
|
+
logger.debug(
|
|
561
|
+
"[%s] scheduled NEXT PAGE pidx=%d url=%s after failure",
|
|
562
|
+
run_id, npidx, nurl
|
|
563
|
+
)
|
|
564
|
+
except StopIteration:
|
|
565
|
+
pass
|
|
532
566
|
continue
|
|
533
567
|
|
|
534
|
-
#
|
|
568
|
+
# Detect a 404 "page" (httpx.Response returned)
|
|
569
|
+
is_404 = False
|
|
570
|
+
try:
|
|
571
|
+
if isinstance(html_or_resp, httpx.Response) and html_or_resp.status_code == 404:
|
|
572
|
+
is_404 = True
|
|
573
|
+
except Exception:
|
|
574
|
+
# Fallback duck-typing if httpx import fails for some reason
|
|
575
|
+
if getattr(html_or_resp, "status_code", None) == 404 and hasattr(html_or_resp, "headers"):
|
|
576
|
+
is_404 = True
|
|
577
|
+
|
|
578
|
+
if is_404:
|
|
579
|
+
logger.warning(
|
|
580
|
+
"[%s] PAGE 404 pidx=%d url=%s -> stopping further page scheduling",
|
|
581
|
+
run_id, pidx, url
|
|
582
|
+
)
|
|
583
|
+
stop_after_404 = True
|
|
584
|
+
stop_at_page_idx = pidx
|
|
585
|
+
# 404 page has 0 videos
|
|
586
|
+
page_counts[pidx] = 0
|
|
587
|
+
# Flush anything now ready
|
|
588
|
+
yield from flush_ready()
|
|
589
|
+
# Do NOT schedule any more pages
|
|
590
|
+
continue
|
|
591
|
+
|
|
592
|
+
# If we've already hit a 404 and this page index is after it, ignore this page
|
|
593
|
+
if stop_after_404 and stop_at_page_idx is not None and pidx > stop_at_page_idx:
|
|
594
|
+
logger.info(
|
|
595
|
+
"[%s] ignoring PAGE pidx=%d url=%s (after first 404 at pidx=%d)",
|
|
596
|
+
run_id, pidx, url, stop_at_page_idx
|
|
597
|
+
)
|
|
598
|
+
page_counts[pidx] = 0
|
|
599
|
+
yield from flush_ready()
|
|
600
|
+
continue
|
|
601
|
+
|
|
602
|
+
# Normal path: Extract video URLs
|
|
603
|
+
html = html_or_resp
|
|
535
604
|
try:
|
|
536
605
|
video_urls = extractor(html) or []
|
|
537
606
|
logger.debug(
|
|
@@ -551,26 +620,24 @@ class Helper:
|
|
|
551
620
|
run_id, pidx, page_counts[pidx]
|
|
552
621
|
)
|
|
553
622
|
|
|
554
|
-
# NEW: queue videos instead of submitting all at once
|
|
555
623
|
for vid_idx, vurl in enumerate(video_urls):
|
|
556
624
|
pending_videos.append((pidx, vid_idx, vurl))
|
|
557
|
-
# Fill available slots respecting videos_concurrency
|
|
558
625
|
schedule_videos()
|
|
559
626
|
|
|
560
|
-
# Try flushing any now-ready items
|
|
561
627
|
yield from flush_ready()
|
|
562
628
|
|
|
563
|
-
# Fetch next page
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
629
|
+
# Fetch next page only if we haven't seen a 404
|
|
630
|
+
if not stop_after_404:
|
|
631
|
+
try:
|
|
632
|
+
npidx, nurl = next(page_iter)
|
|
633
|
+
nfut = page_executor.submit(self.core.fetch, nurl)
|
|
634
|
+
page_in_flight[nfut] = (npidx, nurl)
|
|
635
|
+
logger.debug(
|
|
636
|
+
"[%s] scheduled NEXT PAGE pidx=%d url=%s (inflight_page=%d)",
|
|
637
|
+
run_id, npidx, nurl, len(page_in_flight)
|
|
638
|
+
)
|
|
639
|
+
except StopIteration:
|
|
640
|
+
logger.debug("[%s] no more pages to schedule", run_id)
|
|
574
641
|
|
|
575
642
|
# VIDEO completed
|
|
576
643
|
elif fut in video_in_flight:
|
|
@@ -661,7 +728,7 @@ class BaseCore:
|
|
|
661
728
|
max_keepalive_connections=getattr(self.config, "max_keepalive", 4),
|
|
662
729
|
keepalive_expiry=10,
|
|
663
730
|
)
|
|
664
|
-
ctx = ssl.create_default_context()
|
|
731
|
+
ctx = ssl.create_default_context(cafile=certifi.where())
|
|
665
732
|
if not self.config.verify_ssl:
|
|
666
733
|
ctx.check_hostname = False
|
|
667
734
|
ctx.verify_mode = ssl.CERT_NONE
|
|
@@ -858,11 +925,7 @@ class BaseCore:
|
|
|
858
925
|
|
|
859
926
|
# 404: usually not recoverable, but we try once (if attempt==0) in case of transient CDN
|
|
860
927
|
if status == 404:
|
|
861
|
-
|
|
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
|
|
928
|
+
return response # Immediately returning response, because 404 usually means no more content when searching
|
|
866
929
|
|
|
867
930
|
# 410: permanent gone
|
|
868
931
|
if status == 410:
|
|
@@ -1178,41 +1241,49 @@ class BaseCore:
|
|
|
1178
1241
|
with open(tmp_path, "wb") as out_fp:
|
|
1179
1242
|
# Submit all downloads
|
|
1180
1243
|
with ThreadPoolExecutor(max_workers=workers) as ex:
|
|
1181
|
-
|
|
1244
|
+
# submit all segments
|
|
1245
|
+
pending = {ex.submit(self.download_segment, url, timeout): i
|
|
1246
|
+
for i, url in enumerate(segments)}
|
|
1182
1247
|
|
|
1183
|
-
# Optional: per-segment retry budget (on top of fetch() retries)
|
|
1184
1248
|
seg_retries = [0] * n
|
|
1185
1249
|
max_seg_retries = 2
|
|
1186
1250
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1251
|
+
parts: List[Optional[bytes]] = [None] * n
|
|
1252
|
+
next_to_write = 0
|
|
1253
|
+
progressed = 0 # how many segments have finished (success or final failure)
|
|
1254
|
+
|
|
1255
|
+
while pending:
|
|
1256
|
+
done, _ = wait(pending, return_when=FIRST_COMPLETED)
|
|
1257
|
+
|
|
1258
|
+
for fut in done:
|
|
1259
|
+
i = pending.pop(fut)
|
|
1260
|
+
try:
|
|
1261
|
+
_, data, success = fut.result()
|
|
1262
|
+
except Exception as e:
|
|
1263
|
+
self.logger.error(f"Worker exception for segment {i}: {e}")
|
|
1264
|
+
success, data = False, b""
|
|
1265
|
+
|
|
1266
|
+
if success and data:
|
|
1267
|
+
parts[i] = data
|
|
1268
|
+
# count immediately: a segment finished successfully
|
|
1269
|
+
progressed += 1
|
|
1270
|
+
callback(progressed, n)
|
|
1271
|
+
else:
|
|
1272
|
+
# retry a couple times
|
|
1273
|
+
if seg_retries[i] < max_seg_retries:
|
|
1274
|
+
seg_retries[i] += 1
|
|
1275
|
+
pending[ex.submit(self.download_segment, segments[i], timeout)] = i
|
|
1276
|
+
continue
|
|
1277
|
+
# give up: mark placeholder and still advance progress
|
|
1278
|
+
parts[i] = b""
|
|
1279
|
+
progressed += 1
|
|
1280
|
+
callback(progressed, n)
|
|
1281
|
+
|
|
1282
|
+
# now try to flush any contiguous ready parts
|
|
1283
|
+
while next_to_write < n and parts[next_to_write] is not None:
|
|
1284
|
+
if parts[next_to_write]:
|
|
1285
|
+
out_fp.write(parts[next_to_write])
|
|
1286
|
+
next_to_write += 1
|
|
1216
1287
|
|
|
1217
1288
|
if failures:
|
|
1218
1289
|
# Clean temp file to avoid leaving corrupted output
|
|
@@ -1279,85 +1350,90 @@ class BaseCore:
|
|
|
1279
1350
|
raise ModuleNotFoundError(f"PyAV is required for remuxing. Install with pip install av. Not supported on Termux! {e}")
|
|
1280
1351
|
|
|
1281
1352
|
input_ = av_open(input_path)
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1353
|
+
if input_.format.name.lower() == "mpegts":
|
|
1354
|
+
output = av_open(output_path, mode="w", format="mp4", options={"movflags": "faststart"})
|
|
1355
|
+
|
|
1356
|
+
# --- VIDEO: keep your exact remux approach ---
|
|
1357
|
+
in_video = input_.streams.video[0]
|
|
1358
|
+
out_video = output.add_stream_from_template(template=in_video)
|
|
1359
|
+
|
|
1360
|
+
# --- AUDIO: copy if MP4-compatible; else transcode to AAC ---
|
|
1361
|
+
in_audio = next((s for s in input_.streams if s.type == "audio"), None)
|
|
1362
|
+
out_audio = None
|
|
1363
|
+
transcode_audio = False
|
|
1364
|
+
resampler = None
|
|
1365
|
+
|
|
1366
|
+
if in_audio:
|
|
1367
|
+
# Common MP4-safe audio codecs to copy without transcoding.
|
|
1368
|
+
copy_ok = {"aac", "alac", "mp3"}
|
|
1369
|
+
codec_name = (in_audio.codec_context.name or "").lower()
|
|
1370
|
+
|
|
1371
|
+
if codec_name in copy_ok:
|
|
1372
|
+
out_audio = output.add_stream_from_template(template=in_audio)
|
|
1373
|
+
else:
|
|
1374
|
+
# Build an AAC encoder stream. Match input rate/layout where possible.
|
|
1375
|
+
transcode_audio = True
|
|
1376
|
+
sample_rate = in_audio.codec_context.sample_rate or 48000
|
|
1377
|
+
layout = (
|
|
1378
|
+
in_audio.codec_context.layout.name
|
|
1379
|
+
if getattr(in_audio.codec_context, "layout", None)
|
|
1380
|
+
else "stereo"
|
|
1381
|
+
)
|
|
1382
|
+
out_audio = output.add_stream("aac", rate=sample_rate)
|
|
1383
|
+
# Setting layout/channels helps encoder pick sensible defaults.
|
|
1384
|
+
try:
|
|
1385
|
+
out_audio.layout = layout
|
|
1386
|
+
except Exception:
|
|
1387
|
+
pass
|
|
1388
|
+
|
|
1389
|
+
# Ensure frames handed to the AAC encoder are in a compatible sample format.
|
|
1390
|
+
# ('fltp' is the usual format for AAC encoders.)
|
|
1391
|
+
resampler = AudioResampler(format="fltp", layout=layout, rate=sample_rate)
|
|
1392
|
+
|
|
1393
|
+
# --- DEMUX both streams so audio is included ---
|
|
1394
|
+
demux_streams = [in_video] + ([in_audio] if in_audio else [])
|
|
1395
|
+
packets = list(input_.demux(demux_streams)) # keeps your progress logic
|
|
1396
|
+
total = len(packets)
|
|
1397
|
+
|
|
1398
|
+
for idx, packet in enumerate(packets):
|
|
1399
|
+
if packet.dts is None:
|
|
1400
|
+
if callback:
|
|
1401
|
+
callback(idx + 1, total)
|
|
1402
|
+
continue
|
|
1316
1403
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1404
|
+
if packet.stream == in_video:
|
|
1405
|
+
# Your original video remux path (no explicit rescale).
|
|
1406
|
+
packet.stream = out_video
|
|
1407
|
+
output.mux(packet)
|
|
1320
1408
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1409
|
+
elif in_audio and packet.stream == in_audio:
|
|
1410
|
+
if not transcode_audio:
|
|
1411
|
+
# Audio is already MP4-compatible; just remux.
|
|
1412
|
+
packet.stream = out_audio
|
|
1413
|
+
output.mux(packet)
|
|
1414
|
+
else:
|
|
1415
|
+
# Decode -> (optionally resample) -> encode AAC -> mux.
|
|
1416
|
+
for frame in packet.decode():
|
|
1417
|
+
# Resample to match encoder expectations.
|
|
1418
|
+
frames = resampler.resample(frame) if resampler else [frame]
|
|
1419
|
+
for f in frames:
|
|
1420
|
+
for enc_pkt in out_audio.encode(f):
|
|
1421
|
+
output.mux(enc_pkt)
|
|
1325
1422
|
|
|
1326
|
-
for idx, packet in enumerate(packets):
|
|
1327
|
-
if packet.dts is None:
|
|
1328
1423
|
if callback:
|
|
1329
1424
|
callback(idx + 1, total)
|
|
1330
|
-
continue
|
|
1331
1425
|
|
|
1332
|
-
if
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1426
|
+
# Flush audio encoder if we transcoded.
|
|
1427
|
+
if transcode_audio and out_audio:
|
|
1428
|
+
for enc_pkt in out_audio.encode(None):
|
|
1429
|
+
output.mux(enc_pkt)
|
|
1336
1430
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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()
|
|
1431
|
+
input_.close()
|
|
1432
|
+
output.close()
|
|
1433
|
+
|
|
1434
|
+
else:
|
|
1435
|
+
self.logger.info("Stream seems to be already in MP4! Skipping remux...")
|
|
1436
|
+
os.rename(input_path, output_path)
|
|
1361
1437
|
|
|
1362
1438
|
def FFMPEG(self, video, quality: str, callback, path: str) -> bool:
|
|
1363
1439
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|