返回 ppt-master
provider_openverse.py
根目录 / skills / ppt-master / scripts / image_sources / provider_openverse.py
1 """Openverse provider.
2
3 Zero-config (no API key required). Indexes openly licensed images across
4 Wikimedia, Flickr, museums, and other sources.
5
6 API docs: https://api.openverse.org/v1/
7 """
8
9 from __future__ import annotations
10
11 import sys
12 from pathlib import Path
13
14 _SCRIPTS_DIR = Path(__file__).resolve().parents[1]
15 if str(_SCRIPTS_DIR) not in sys.path:
16 sys.path.insert(0, str(_SCRIPTS_DIR))
17
18 from console_encoding import configure_utf8_stdio # noqa: E402
19
20 configure_utf8_stdio()
21
22 if __name__ == "__main__":
23 print(__doc__)
24 print("Use via: python3 skills/ppt-master/scripts/image_search.py ...")
25 raise SystemExit(0 if any(arg in {"-h", "--help", "help"} for arg in sys.argv[1:]) else 1)
26
27 import requests
28
29 from image_sources.provider_common import (
30 AssetCandidate,
31 ImageSearchRequest,
32 USER_AGENT,
33 build_query_progression,
34 classify_license,
35 normalize_license_name,
36 )
37
38
39 API_URL = "https://api.openverse.org/v1/images/"
40 DEFAULT_PAGE_SIZE = 20
41 DEFAULT_TIMEOUT = 30
42
43 # Map our orientation vocabulary to Openverse's ``aspect_ratio`` parameter.
44 _ASPECT_MAP = {"landscape": "wide", "portrait": "tall", "square": "square"}
45
46 # Openverse license param values. ``cc0,pdm`` covers our "no-attribution" tier;
47 # adding ``by,by-sa`` opens the "attribution-required" tier.
48 _LICENSE_PARAM = {
49 "no-attribution-only": "cc0,pdm",
50 "all": "by,by-sa,cc0,pdm",
51 }
52
53
54 def parse_results(payload: dict) -> list[AssetCandidate]:
55 """Translate an Openverse search payload into a list of candidates."""
56 candidates: list[AssetCandidate] = []
57 for item in payload.get("results", []) or []:
58 license_name = (item.get("license") or "").strip()
59 license_url = (item.get("license_url") or "").strip()
60 tier = classify_license(license_name, license_url, provider="openverse")
61 if not tier:
62 continue
63
64 download_url = (item.get("url") or item.get("thumbnail") or "").strip()
65 if not download_url:
66 continue
67
68 candidates.append(
69 AssetCandidate(
70 provider="openverse",
71 title=(item.get("title") or "").strip() or "Untitled",
72 asset_id=str(item.get("id") or ""),
73 source_page_url=(
74 item.get("foreign_landing_url") or item.get("detail_url") or ""
75 ).strip(),
76 license_name=normalize_license_name(license_name),
77 license_url=license_url,
78 license_tier=tier,
79 width=int(item.get("width") or 0),
80 height=int(item.get("height") or 0),
81 download_url=download_url,
82 author=(item.get("creator") or "").strip(),
83 raw=item,
84 )
85 )
86 return candidates
87
88
89 def search(
90 request: ImageSearchRequest,
91 *,
92 license_tier_filter: str = "no-attribution-only",
93 page_size: int = DEFAULT_PAGE_SIZE,
94 timeout: int = DEFAULT_TIMEOUT,
95 ) -> list[AssetCandidate]:
96 """Search Openverse for candidates matching ``request``.
97
98 ``license_tier_filter`` is one of ``"no-attribution-only"`` or ``"all"``.
99 Returns the candidates from the first non-empty query in the
100 progression — caller is responsible for picking the best one via
101 ``score_candidate``.
102 """
103 if license_tier_filter not in _LICENSE_PARAM:
104 raise ValueError(f"unsupported license_tier_filter: {license_tier_filter!r}")
105
106 orientation = (request.orientation or "").strip().lower()
107
108 for query in build_query_progression(request.query):
109 params: dict[str, str | int] = {
110 "q": query,
111 "page_size": page_size,
112 "license": _LICENSE_PARAM[license_tier_filter],
113 "size": "large",
114 }
115 if orientation in _ASPECT_MAP:
116 params["aspect_ratio"] = _ASPECT_MAP[orientation]
117
118 response = requests.get(
119 API_URL,
120 params=params,
121 headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
122 timeout=timeout,
123 )
124 response.raise_for_status()
125 candidates = parse_results(response.json())
126 if candidates:
127 return candidates
128
129 return []
130
130 lines PYTHON