eaf-base-api 4.1.1__tar.gz → 4.2.1__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: 4.1.1
3
+ Version: 4.2.1
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author: Johannes Habel
6
6
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
@@ -11,7 +11,6 @@ Requires-Dist: cachetools>=7.0.0
11
11
  Requires-Dist: curl-cffi
12
12
  Requires-Dist: tenacity>=9.1.2
13
13
  Requires-Dist: m3u8 ; extra == 'hls'
14
- Requires-Dist: av ; python_full_version >= '3.12' and extra == 'hls'
15
14
  Requires-Python: >=3.12
16
15
  Project-URL: Homepage, https://github.com/EchterAlsFake/eaf_base_api
17
16
  Project-URL: Repository, https://github.com/EchterAlsFake/eaf_base_api
@@ -34,7 +33,92 @@ support, and there was a lot of code that I would have rewritten in every API ag
34
33
  package. The `BaseCore` class does all the necessary stuff like m3u8 parsing, a great caching system, network request
35
34
  fetching with retry attempts and proxy support.
36
35
 
37
- # Documentation (IMPORTANT!)
36
+ ## Consistent errors and logging (4.2)
37
+
38
+ Configure logging once in your application to capture all 15 provider APIs and
39
+ the base library:
40
+
41
+ ```python
42
+ import logging
43
+ from base_api import configure_app_logging, DownloadFailed
44
+
45
+ configure_app_logging(level=logging.WARNING, log_file="api-errors.log")
46
+
47
+ # Inside your async application:
48
+ try:
49
+ await video.download(configuration)
50
+ except DownloadFailed as error:
51
+ print(error.api, error.class_name, error.url)
52
+ # The failure and its full traceback have already been logged.
53
+ ```
54
+
55
+ Logs use Python module names, such as `xvideos_api.api` and `base_api.base`.
56
+ Request, source-loading, and download logs include the qualified class name and
57
+ URL in the message and in `LogRecord.class_name` / `LogRecord.url`. For example:
58
+
59
+ ```text
60
+ ERROR xvideos_api.api [class=xvideos_api.api.Video url=https://example.test/video/123] Download failed: disk full
61
+ Traceback (most recent call last):
62
+ ...
63
+ OSError: disk full
64
+ ```
65
+
66
+ Network logs identify the actual request URL, including CDN/segment URLs; media
67
+ errors also identify the original video page. Context stays separate for
68
+ concurrent downloads and follows parsing work into worker threads. Creating a
69
+ client/core does not install handlers or override your application's log level.
70
+ Without configuration, Python's fallback logging handler still prints warnings
71
+ and errors to stderr.
72
+
73
+ Catch common failures from `base_api`: `DownloadFailed`, `NetworkError`,
74
+ `NotFound`, `ProxyError`, `BotDetection`, `VideoUnavailable`, `LoginFailed`, and
75
+ `RegionBlocked`. Existing provider exception imports remain compatible,
76
+ including Pornhub's `PornhubAPIError` hierarchy. Translated exceptions preserve
77
+ the original exception as `__cause__`.
78
+
79
+ Ordinary download failures now raise `DownloadFailed` instead of returning
80
+ `False`. A stop signal raises `base_api.modules.errors.DownloadCancelled`;
81
+ async task cancellation propagates unchanged. Neither is logged as an error.
82
+ With `DownloadConfigHLS(return_report=True)`, incomplete/cancelled downloads
83
+ still return their explicit `DownloadReport`; unexpected exceptions still raise.
84
+
85
+ Shared request translation, download error handling, HLS preparation, and output
86
+ configuration live in `base_api.modules.provider`. The identical Tube8 and
87
+ Thumbzilla result-grid extraction lives in `base_api.modules.static_functions`.
88
+ Site-specific parsing, quality selection, and fallback behavior stay in each
89
+ provider. Updated providers require `eaf-base-api>=4.2.0`; update the base library
90
+ alongside them.
91
+
92
+ ## FFmpeg remuxing and single-pass metadata tagging (4.3)
93
+
94
+ HLS TS→MP4 remuxing (`DownloadConfigHLS(remux=True)`) now runs directly via an
95
+ `ffmpeg` binary (discovered on `PATH` or configured via `RuntimeConfig.ffmpeg_path`)
96
+ instead of `PyAV`. You can also pass `metadata` (a provider `Video` object or `dict`)
97
+ and `thumbnail` (`bytes` or path) on `DownloadConfigHLS` so `eaf_base_api` embeds
98
+ MP4 container tags and cover art in the **same single FFmpeg remux pass** without
99
+ rewriting the file a second time:
100
+
101
+ ```python
102
+ from base_api import DownloadConfigHLS, write_metadata_tags
103
+
104
+ config = DownloadConfigHLS(
105
+ quality="best",
106
+ path="./downloads",
107
+ remux=True,
108
+ metadata=video, # Writes title, author, genre, date, description, url, comment
109
+ thumbnail=cover_bytes, # Optional JPEG/PNG cover art embedded in the same pass
110
+ return_report=True,
111
+ )
112
+ report = await video.download(configuration=config)
113
+ print(report.metadata_written) # True when single-pass metadata/cover tagging succeeded
114
+ ```
115
+
116
+ For direct MP4 downloads (`DownloadConfigRAW`) or existing MP4 files, use
117
+ `write_metadata_tags(path, metadata=video, thumbnail=cover_bytes)` from
118
+ `base_api.modules.ffmpeg` (or `base_api`). If `ffmpeg` is not installed when
119
+ `remux=True`, `eaf_base_api` logs a warning and preserves the raw MPEG-TS stream.
120
+
121
+ # Documentation (IMPORTANT!)
38
122
  > [!IMPORTANT]
39
123
  > Configuring eaf_base_api is necessary if you use any of my Porn APIs, because they all depend on this project.
40
124
  > Please read through the documentation to learn how `PROXIES`, `CACHING` and `LOGGING` etc... work!
@@ -154,7 +238,10 @@ stream = helper.iterator(
154
238
  async with stream:
155
239
  async for result in stream:
156
240
  if not result.succeeded:
157
- logger.error("%s failed: %s", result.stage, result.error)
241
+ logger.error(
242
+ "%s failed for %s: %s", result.stage, result.url, result.error,
243
+ exc_info=(type(result.error), result.error, result.error.__traceback__),
244
+ )
158
245
  continue
159
246
  video = result.unwrap()
160
247
  ```
@@ -165,6 +252,31 @@ or `ErrorMode.RAISE`. `RetryPolicy` provides a strict maximum attempt count and
165
252
  optional exponential delay; the independent page and item handlers return an
166
253
  `ErrorAction` and cannot create an unbounded retry loop.
167
254
 
255
+ ## Error logging
256
+
257
+ Configure logging once in the application to capture all provider and base API logs:
258
+
259
+ ```python
260
+ import logging
261
+ from base_api.modules.logger import configure_app_logging
262
+
263
+ configure_app_logging(log_file="api.log", level=logging.INFO)
264
+ ```
265
+
266
+ Failures include the operation, video/page URL, and original traceback. The default
267
+ formatter also shows the file, line, and function. Media source failures are logged
268
+ even when a caller catches the exception; iterator failures are logged even when
269
+ the configured policy skips or yields them. Provider CLIs configure console logging
270
+ automatically.
271
+
272
+ Provider request and download exceptions use the shared types in
273
+ `base_api.modules.errors`, preserving the original exception as `__cause__`.
274
+ Pornhub's existing exception classes remain catchable as `PornhubAPIError` and as
275
+ their shared equivalents. `ScraperException` also inherits `BaseScraperError`.
276
+ Download preparation errors now carry the video URL in `DownloadFailed`, and
277
+ explicit cancellation remains `DownloadCancelled` or `asyncio.CancelledError`.
278
+ Existing boolean/download-report results from the base downloader remain supported.
279
+
168
280
  # Can I use this for myself?
169
281
  Yes, you can, but I may change stuff here and there from time to time, and it would maybe break your project.
170
282
  I would not recommend you to install and use it as a package, but just copy the code you need.
@@ -14,7 +14,92 @@ support, and there was a lot of code that I would have rewritten in every API ag
14
14
  package. The `BaseCore` class does all the necessary stuff like m3u8 parsing, a great caching system, network request
15
15
  fetching with retry attempts and proxy support.
16
16
 
17
- # Documentation (IMPORTANT!)
17
+ ## Consistent errors and logging (4.2)
18
+
19
+ Configure logging once in your application to capture all 15 provider APIs and
20
+ the base library:
21
+
22
+ ```python
23
+ import logging
24
+ from base_api import configure_app_logging, DownloadFailed
25
+
26
+ configure_app_logging(level=logging.WARNING, log_file="api-errors.log")
27
+
28
+ # Inside your async application:
29
+ try:
30
+ await video.download(configuration)
31
+ except DownloadFailed as error:
32
+ print(error.api, error.class_name, error.url)
33
+ # The failure and its full traceback have already been logged.
34
+ ```
35
+
36
+ Logs use Python module names, such as `xvideos_api.api` and `base_api.base`.
37
+ Request, source-loading, and download logs include the qualified class name and
38
+ URL in the message and in `LogRecord.class_name` / `LogRecord.url`. For example:
39
+
40
+ ```text
41
+ ERROR xvideos_api.api [class=xvideos_api.api.Video url=https://example.test/video/123] Download failed: disk full
42
+ Traceback (most recent call last):
43
+ ...
44
+ OSError: disk full
45
+ ```
46
+
47
+ Network logs identify the actual request URL, including CDN/segment URLs; media
48
+ errors also identify the original video page. Context stays separate for
49
+ concurrent downloads and follows parsing work into worker threads. Creating a
50
+ client/core does not install handlers or override your application's log level.
51
+ Without configuration, Python's fallback logging handler still prints warnings
52
+ and errors to stderr.
53
+
54
+ Catch common failures from `base_api`: `DownloadFailed`, `NetworkError`,
55
+ `NotFound`, `ProxyError`, `BotDetection`, `VideoUnavailable`, `LoginFailed`, and
56
+ `RegionBlocked`. Existing provider exception imports remain compatible,
57
+ including Pornhub's `PornhubAPIError` hierarchy. Translated exceptions preserve
58
+ the original exception as `__cause__`.
59
+
60
+ Ordinary download failures now raise `DownloadFailed` instead of returning
61
+ `False`. A stop signal raises `base_api.modules.errors.DownloadCancelled`;
62
+ async task cancellation propagates unchanged. Neither is logged as an error.
63
+ With `DownloadConfigHLS(return_report=True)`, incomplete/cancelled downloads
64
+ still return their explicit `DownloadReport`; unexpected exceptions still raise.
65
+
66
+ Shared request translation, download error handling, HLS preparation, and output
67
+ configuration live in `base_api.modules.provider`. The identical Tube8 and
68
+ Thumbzilla result-grid extraction lives in `base_api.modules.static_functions`.
69
+ Site-specific parsing, quality selection, and fallback behavior stay in each
70
+ provider. Updated providers require `eaf-base-api>=4.2.0`; update the base library
71
+ alongside them.
72
+
73
+ ## FFmpeg remuxing and single-pass metadata tagging (4.3)
74
+
75
+ HLS TS→MP4 remuxing (`DownloadConfigHLS(remux=True)`) now runs directly via an
76
+ `ffmpeg` binary (discovered on `PATH` or configured via `RuntimeConfig.ffmpeg_path`)
77
+ instead of `PyAV`. You can also pass `metadata` (a provider `Video` object or `dict`)
78
+ and `thumbnail` (`bytes` or path) on `DownloadConfigHLS` so `eaf_base_api` embeds
79
+ MP4 container tags and cover art in the **same single FFmpeg remux pass** without
80
+ rewriting the file a second time:
81
+
82
+ ```python
83
+ from base_api import DownloadConfigHLS, write_metadata_tags
84
+
85
+ config = DownloadConfigHLS(
86
+ quality="best",
87
+ path="./downloads",
88
+ remux=True,
89
+ metadata=video, # Writes title, author, genre, date, description, url, comment
90
+ thumbnail=cover_bytes, # Optional JPEG/PNG cover art embedded in the same pass
91
+ return_report=True,
92
+ )
93
+ report = await video.download(configuration=config)
94
+ print(report.metadata_written) # True when single-pass metadata/cover tagging succeeded
95
+ ```
96
+
97
+ For direct MP4 downloads (`DownloadConfigRAW`) or existing MP4 files, use
98
+ `write_metadata_tags(path, metadata=video, thumbnail=cover_bytes)` from
99
+ `base_api.modules.ffmpeg` (or `base_api`). If `ffmpeg` is not installed when
100
+ `remux=True`, `eaf_base_api` logs a warning and preserves the raw MPEG-TS stream.
101
+
102
+ # Documentation (IMPORTANT!)
18
103
  > [!IMPORTANT]
19
104
  > Configuring eaf_base_api is necessary if you use any of my Porn APIs, because they all depend on this project.
20
105
  > Please read through the documentation to learn how `PROXIES`, `CACHING` and `LOGGING` etc... work!
@@ -134,7 +219,10 @@ stream = helper.iterator(
134
219
  async with stream:
135
220
  async for result in stream:
136
221
  if not result.succeeded:
137
- logger.error("%s failed: %s", result.stage, result.error)
222
+ logger.error(
223
+ "%s failed for %s: %s", result.stage, result.url, result.error,
224
+ exc_info=(type(result.error), result.error, result.error.__traceback__),
225
+ )
138
226
  continue
139
227
  video = result.unwrap()
140
228
  ```
@@ -145,6 +233,31 @@ or `ErrorMode.RAISE`. `RetryPolicy` provides a strict maximum attempt count and
145
233
  optional exponential delay; the independent page and item handlers return an
146
234
  `ErrorAction` and cannot create an unbounded retry loop.
147
235
 
236
+ ## Error logging
237
+
238
+ Configure logging once in the application to capture all provider and base API logs:
239
+
240
+ ```python
241
+ import logging
242
+ from base_api.modules.logger import configure_app_logging
243
+
244
+ configure_app_logging(log_file="api.log", level=logging.INFO)
245
+ ```
246
+
247
+ Failures include the operation, video/page URL, and original traceback. The default
248
+ formatter also shows the file, line, and function. Media source failures are logged
249
+ even when a caller catches the exception; iterator failures are logged even when
250
+ the configured policy skips or yields them. Provider CLIs configure console logging
251
+ automatically.
252
+
253
+ Provider request and download exceptions use the shared types in
254
+ `base_api.modules.errors`, preserving the original exception as `__cause__`.
255
+ Pornhub's existing exception classes remain catchable as `PornhubAPIError` and as
256
+ their shared equivalents. `ScraperException` also inherits `BaseScraperError`.
257
+ Download preparation errors now carry the video URL in `DownloadFailed`, and
258
+ explicit cancellation remains `DownloadCancelled` or `asyncio.CancelledError`.
259
+ Existing boolean/download-report results from the base downloader remain supported.
260
+
148
261
  # Can I use this for myself?
149
262
  Yes, you can, but I may change stuff here and there from time to time, and it would maybe break your project.
150
263
  I would not recommend you to install and use it as a package, but just copy the code you need.
@@ -15,6 +15,7 @@ __all__ = [
15
15
  "FieldNotLoadableError",
16
16
  "Helper",
17
17
  "ItemFetchError",
18
+ "IteratorConfig",
18
19
  "LoadState",
19
20
  "LoaderConfigurationError",
20
21
  "LoaderContractError",
@@ -35,6 +36,35 @@ __all__ = [
35
36
  "errors",
36
37
  "media_field",
37
38
  "UnknownMediaFieldError",
39
+ "make_iterator_config",
40
+ "default_on_error",
41
+ "scrape_stream",
42
+ "stream_results",
43
+ "is_resource_gone",
44
+ "contains_resource_gone",
45
+ "parse_duration",
46
+ "parse_count",
47
+ "get_text_safe",
48
+ "get_attr_safe",
49
+ "build_m3u8_master",
50
+ "str_to_bool",
51
+ "ScraperException",
52
+ "NotFound",
53
+ "NetworkError",
54
+ "BotDetection",
55
+ "ProxyError",
56
+ "UnknownNetworkError",
57
+ "DownloadFailed",
58
+ "MetadataWriteError",
59
+ "VideoUnavailable",
60
+ "ResourceGone",
61
+ "LoginFailed",
62
+ "RegionBlocked",
63
+ "configure_app_logging",
64
+ "find_ffmpeg",
65
+ "has_ffmpeg",
66
+ "remux_ts_to_mp4",
67
+ "write_metadata_tags",
38
68
  ]
39
69
 
40
70
 
@@ -53,7 +83,28 @@ from base_api.modules.errors import (
53
83
  RequestRetriesExhausted,
54
84
  ScrapeOperationError,
55
85
  UnknownMediaFieldError,
86
+ is_resource_gone,
87
+ contains_resource_gone,
88
+ ScraperException,
89
+ NotFound,
90
+ NetworkError,
91
+ BotDetection,
92
+ ProxyError,
93
+ UnknownNetworkError,
94
+ DownloadFailed,
95
+ MetadataWriteError,
96
+ VideoUnavailable,
97
+ ResourceGone,
98
+ LoginFailed,
99
+ RegionBlocked,
56
100
  )
101
+ from base_api.modules.ffmpeg import (
102
+ find_ffmpeg,
103
+ has_ffmpeg,
104
+ remux_ts_to_mp4,
105
+ write_metadata_tags,
106
+ )
107
+ from base_api.modules.logger import configure_app_logging
57
108
  from base_api.base import (
58
109
  BaseCore,
59
110
  BaseMedia,
@@ -74,5 +125,22 @@ from base_api.base import (
74
125
  ScrapeStream,
75
126
  SegmentCacheKey,
76
127
  media_field,
128
+ scrape_stream,
129
+ stream_results,
130
+ )
131
+ from base_api.modules.config import (
132
+ config,
133
+ DownloadConfigHLS,
134
+ DownloadConfigRAW,
135
+ IteratorConfig,
136
+ make_iterator_config,
137
+ default_on_error,
138
+ )
139
+ from base_api.modules.static_functions import (
140
+ str_to_bool,
141
+ parse_duration,
142
+ parse_count,
143
+ get_text_safe,
144
+ get_attr_safe,
145
+ build_m3u8_master,
77
146
  )
78
- from base_api.modules.config import config, DownloadConfigHLS, DownloadConfigRAW