davanstrien/prelinger-sample / scripts /fetch_prelinger_pd.py
davanstrien's picture
download
raw
4.42 kB
# /// script
# requires-python = ">=3.11"
# dependencies = ["requests"]
# ///
"""Mirror the explicitly-PD subset of the Prelinger Archives into a bucket.
Filter: collection:prelinger, mediatype:movies, licenseurl contains 'publicdomain'
(per-item machine-checkable rights basis — see the vault note for the licensing
rationale). For each item: best mp4 derivative (512kb preferred) -> /out/films/,
plus a metadata sidecar -> /out/meta/{id}.json written only AFTER the film copy
completes, so sidecar-exists == film-is-whole (FUSE has no rename; copy-then-marker
replaces tmp-then-rename).
Resumable: re-run skips items whose sidecar exists. Politeness: 4 concurrent
downloads, streaming, no retry hammering.
"""
import json
import shutil
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import requests
OUT = Path("/out")
FILMS, META = OUT / "films", OUT / "meta"
QUERY = "collection:prelinger AND mediatype:movies AND licenseurl:*publicdomain*"
WORKERS = 4
UA = {"User-Agent": "prelinger-pd-mirror/0.1 (davanstrien; HF dataset prep)"}
def scrape_items() -> list[dict]:
items, cursor = [], None
while True:
params = {"q": QUERY, "count": 10000,
"fields": "identifier,licenseurl,title,date,runtime,item_size"}
if cursor:
params["cursor"] = cursor
r = requests.get("https://archive.org/services/search/v1/scrape",
params=params, headers=UA, timeout=120).json()
items.extend(r.get("items", []))
cursor = r.get("cursor")
if not cursor:
return items
def pick_mp4(identifier: str) -> tuple[str, int] | None:
try:
meta = requests.get(f"https://archive.org/metadata/{identifier}",
headers=UA, timeout=60).json()
except (requests.RequestException, json.JSONDecodeError):
return None
mp4s = [(f["name"], int(f.get("size", 0) or 0))
for f in meta.get("files", []) if f["name"].endswith(".mp4")]
if not mp4s:
return None
mp4s.sort(key=lambda x: ("512kb" not in x[0], x[0]))
return mp4s[0]
def fetch(item: dict) -> str:
ident = item["identifier"]
sidecar = META / f"{ident}.json"
if sidecar.exists():
return "skip"
picked = pick_mp4(ident)
if picked is None:
return "no-mp4"
name, size = picked
local = Path("/tmp") / f"{ident}.mp4"
try:
with requests.get(f"https://archive.org/download/{ident}/{name}",
headers=UA, stream=True, timeout=1200) as r:
r.raise_for_status()
with open(local, "wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
dest = FILMS / f"{ident}.mp4"
shutil.copyfile(local, dest)
sidecar.write_text(json.dumps({
"identifier": ident,
"file": name,
"bytes": local.stat().st_size,
"licenseurl": item.get("licenseurl"),
"title": item.get("title"),
"date": item.get("date"),
"runtime": item.get("runtime"),
"source": f"https://archive.org/details/{ident}",
"fetched": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}))
return "ok"
except requests.RequestException as e:
print(f"[err] {ident}: {type(e).__name__}", flush=True)
return "err"
finally:
local.unlink(missing_ok=True)
def main():
FILMS.mkdir(parents=True, exist_ok=True)
META.mkdir(parents=True, exist_ok=True)
items = scrape_items()
print(f"[plan] {len(items)} PD items to mirror", flush=True)
counts = {"ok": 0, "skip": 0, "err": 0, "no-mp4": 0}
t0 = time.time()
with ThreadPoolExecutor(WORKERS) as ex:
for n, res in enumerate(ex.map(fetch, items), 1):
counts[res] += 1
if n % 25 == 0 or n == len(items):
gb = 0.0
for f in FILMS.glob("*.mp4"):
try:
gb += f.stat().st_size / 1e9
except OSError: # racing a concurrent copy on the FUSE mount
pass
print(f"[progress] {n}/{len(items)} {counts} "
f"{gb:.1f} GB {time.time()-t0:.0f}s", flush=True)
print(f"[MIRROR DONE] {counts}", flush=True)
if __name__ == "__main__":
main()

Xet Storage Details

Size:
4.42 kB
·
Xet hash:
9f1763bf481a7c52e3c27d76dfaba1bc591bea38ae575b0b5775351869131905

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.