返回 ppt-master
provider_pexels.py
根目录 / skills / ppt-master / scripts / image_sources / provider_pexels.py
1 """Pexels provider.
2
3 Requires ``PEXELS_API_KEY`` in the environment. Pexels's site-wide license
4 allows commercial use without attribution, so all returned candidates are
5 classified as ``no-attribution``.
6
7 API docs: https://www.pexels.com/api/documentation/
8 """
9
10 from __future__ import annotations
11
12 import sys
13 from pathlib import Path
14
15 _SCRIPTS_DIR = Path(__file__).resolve().parents[1]
16 if str(_SCRIPTS_DIR) not in sys.path:
17 sys.path.insert(0, str(_SCRIPTS_DIR))
18
19 from console_encoding import configure_utf8_stdio # noqa: E402
20
21 configure_utf8_stdio()
22
23 if __name__ == "__main__":
24 print(__doc__)
25 print("Use via: python3 skills/ppt-master/scripts/image_search.py ...")
26 raise SystemExit(0 if any(arg in {"-h", "--help", "help"} for arg in sys.argv[1:]) else 1)
27
28 import os
29
30 import requests
31
32 from image_sources.provider_common import (
33 AssetCandidate,
34 ImageSearchRequest,
35 LICENSE_TIER_NO_ATTRIBUTION,
36 USER_AGENT,
37 build_query_progression,
38 normalize_license_name,
39 )
40
41
42 API_URL = "https://api.pexels.com/v1/search"
43 DEFAULT_PAGE_SIZE = 20
44 DEFAULT_TIMEOUT = 30
45
46 _ORIENTATION_MAP = {
47 "landscape": "landscape",
48 "portrait": "portrait",
49 "square": "square",
50 }
51
52
53 def _require_api_key() -> str:
54 key = (os.environ.get("PEXELS_API_KEY") or "").strip()
55 if not key:
56 raise RuntimeError(
57 "PEXELS_API_KEY is not set. Add it to your environment or .env file. "
58 "Get one at https://www.pexels.com/api/"
59 )
60 return key
61
62
63 def parse_results(payload: dict) -> list[AssetCandidate]:
64 """Translate a Pexels response into candidates."""
65 candidates: list[AssetCandidate] = []
66 for item in payload.get("photos", []) or []:
67 src = item.get("src") or {}
68 download_url = (src.get("original") or src.get("large2x") or src.get("large") or "").strip()
69 if not download_url:
70 continue
71
72 candidates.append(
73 AssetCandidate(
74 provider="pexels",
75 title=(item.get("alt") or "").strip() or "Pexels photo",
76 asset_id=str(item.get("id") or ""),
77 source_page_url=(item.get("url") or "").strip(),
78 license_name=normalize_license_name("Pexels License"),
79 license_url="https://www.pexels.com/license/",
80 license_tier=LICENSE_TIER_NO_ATTRIBUTION,
81 width=int(item.get("width") or 0),
82 height=int(item.get("height") or 0),
83 download_url=download_url,
84 author=(item.get("photographer") or "").strip(),
85 raw=item,
86 )
87 )
88 return candidates
89
90
91 def search(
92 request: ImageSearchRequest,
93 *,
94 license_tier_filter: str = "no-attribution-only",
95 page_size: int = DEFAULT_PAGE_SIZE,
96 timeout: int = DEFAULT_TIMEOUT,
97 ) -> list[AssetCandidate]:
98 """Search Pexels for candidates.
99
100 Pexels images are uniformly ``no-attribution``, so the ``"all"`` filter
101 behaves identically to ``"no-attribution-only"``. Both are accepted to
102 keep the dispatcher simple.
103 """
104 if license_tier_filter not in {"no-attribution-only", "all"}:
105 raise ValueError(f"unsupported license_tier_filter: {license_tier_filter!r}")
106
107 api_key = _require_api_key()
108 orientation = (request.orientation or "").strip().lower()
109
110 for query in build_query_progression(request.query):
111 params: dict[str, str | int] = {
112 "query": query,
113 "per_page": page_size,
114 "size": "large",
115 }
116 if orientation in _ORIENTATION_MAP:
117 params["orientation"] = _ORIENTATION_MAP[orientation]
118
119 response = requests.get(
120 API_URL,
121 params=params,
122 headers={
123 "Authorization": api_key,
124 "User-Agent": USER_AGENT,
125 "Accept": "application/json",
126 },
127 timeout=timeout,
128 )
129 response.raise_for_status()
130 candidates = parse_results(response.json())
131 if candidates:
132 return candidates
133
134 return []
135
135 lines PYTHON