返回 ppt-master
provider_pixabay.py
根目录 / skills / ppt-master / scripts / image_sources / provider_pixabay.py
1 """Pixabay provider.
2
3 Requires ``PIXABAY_API_KEY`` in the environment. Pixabay's Content License
4 allows commercial use without attribution, so all returned candidates are
5 classified as ``no-attribution``.
6
7 API docs: https://pixabay.com/api/docs/
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://pixabay.com/api/"
43 DEFAULT_PAGE_SIZE = 20
44 DEFAULT_TIMEOUT = 30
45
46 # Pixabay uses ``horizontal`` / ``vertical`` rather than landscape/portrait,
47 # and has no ``square`` value (it falls back to ``all``).
48 _ORIENTATION_MAP = {
49 "landscape": "horizontal",
50 "portrait": "vertical",
51 }
52
53
54 def _require_api_key() -> str:
55 key = (os.environ.get("PIXABAY_API_KEY") or "").strip()
56 if not key:
57 raise RuntimeError(
58 "PIXABAY_API_KEY is not set. Add it to your environment or .env file. "
59 "Get one at https://pixabay.com/api/docs/"
60 )
61 return key
62
63
64 def parse_results(payload: dict) -> list[AssetCandidate]:
65 """Translate a Pixabay response into candidates."""
66 candidates: list[AssetCandidate] = []
67 for item in payload.get("hits", []) or []:
68 download_url = (
69 item.get("largeImageURL")
70 or item.get("webformatURL")
71 or item.get("previewURL")
72 or ""
73 ).strip()
74 if not download_url:
75 continue
76
77 candidates.append(
78 AssetCandidate(
79 provider="pixabay",
80 title=(item.get("tags") or "").strip() or "Pixabay image",
81 asset_id=str(item.get("id") or ""),
82 source_page_url=(item.get("pageURL") or "").strip(),
83 license_name=normalize_license_name("Pixabay Content License"),
84 license_url="https://pixabay.com/service/license-summary/",
85 license_tier=LICENSE_TIER_NO_ATTRIBUTION,
86 width=int(item.get("imageWidth") or 0),
87 height=int(item.get("imageHeight") or 0),
88 download_url=download_url,
89 author=(item.get("user") or "").strip(),
90 raw=item,
91 )
92 )
93 return candidates
94
95
96 def search(
97 request: ImageSearchRequest,
98 *,
99 license_tier_filter: str = "no-attribution-only",
100 page_size: int = DEFAULT_PAGE_SIZE,
101 timeout: int = DEFAULT_TIMEOUT,
102 ) -> list[AssetCandidate]:
103 """Search Pixabay for candidates.
104
105 Pixabay images are uniformly ``no-attribution``, so the ``"all"`` filter
106 behaves identically to ``"no-attribution-only"``.
107 """
108 if license_tier_filter not in {"no-attribution-only", "all"}:
109 raise ValueError(f"unsupported license_tier_filter: {license_tier_filter!r}")
110
111 api_key = _require_api_key()
112 orientation = (request.orientation or "").strip().lower()
113
114 for query in build_query_progression(request.query):
115 params: dict[str, str | int] = {
116 "key": api_key,
117 "q": query,
118 "image_type": "photo",
119 "per_page": page_size,
120 "safesearch": "true",
121 }
122 if orientation in _ORIENTATION_MAP:
123 params["orientation"] = _ORIENTATION_MAP[orientation]
124
125 response = requests.get(
126 API_URL,
127 params=params,
128 headers={
129 "User-Agent": USER_AGENT,
130 "Accept": "application/json",
131 },
132 timeout=timeout,
133 )
134 response.raise_for_status()
135 candidates = parse_results(response.json())
136 if candidates:
137 return candidates
138
139 return []
140
140 lines PYTHON