Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ Provider and query may be given as positionals after `s` / `-s`, or with `-P` /
| `moveast` | [Moveast](https://moveast.me) | Free CC0 travel and documentary photography by João Pacheco | Direct high-resolution photo downloads. |
| `cupcake` | [Cupcake](https://cupcake.nilssonlee.se) | Free CC0 stock photography by Jonas Nilsson Lee | Direct master resolution photo downloads. |
| `freenaturestock` | [Free Nature Stock](https://freenaturestock.com) | Free CC0 nature & wildlife stock photography by Adrian Pelletier | Direct master full-resolution photo downloads (up to 20+ MB). |
| `goodfreephotos` | [Good Free Photos](https://goodfreephotos.com) | Free CC0 public domain photos, state parks, and landmarks by Yinan Chen | Direct master full-resolution photo downloads. |

List providers at runtime via `ast help` (printed under **PROVIDERS**).

Expand Down Expand Up @@ -310,6 +311,7 @@ src/
moveast.zig
cupcake.zig
freenaturestock.zig
goodfreephotos.zig
assets/ README banner and static assets
docs/ Provider implementation notes
install.sh curl | bash installer (Linux release binaries)
Expand Down
13 changes: 13 additions & 0 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,16 @@ This document summarizes the network endpoints, transport models, and image down
* Direct full-resolution master photo URLs constructed via Cloudflare CDN (`https://cdn.freenaturestock.com/photos/freenaturestock-{id}.jpg`).
* **Download**: Direct GET on master original camera photo JPEG files (up to 20+ MB).
* **License**: CC0 / 100% Free Public Domain.

---

## 21. Good Free Photos (`goodfreephotos.com`)

* **Search**: `GET https://www.goodfreephotos.com/page/search/?words={query}`
* **Format**: Comprehensive public domain photo archive by Yinan Chen categorized by state parks, countries, and landmarks.
* **Extraction**:
* Clean image IDs extracted from photo slug.
* Alt title descriptions extracted and HTML unescaped.
* Master full-resolution URLs constructed by rewriting `/cache/` to `/albums/` and stripping thumbnail geometry tokens (`_200_w200_..._thumb`).
* **Download**: Direct GET on master original camera photo JPEG files.
* **License**: CC0 / 100% Free Public Domain.
2 changes: 1 addition & 1 deletion docs/stock_photo_sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This document tracks all 24 stock photo websites listed in the [GrayGrids 21+ Be

- **Total Sites Listed**: 24
- **Implemented & Working**: 13 (`unsplash`, `isorepublic`, `picjumbo`, `foodiesfeed`, `picography`, `gratisography`, `startupstockphotos`, `burst`, `jaymantri`, `publicdomainarchive`, `magdeleine`, `splitshire`, `deviantart`)
- *(Additional Providers Implemented in `ast`)*: `aura` ([Aura.build](https://www.aura.build)), `negativespace` ([NegativeSpace.co](https://negativespace.co)), `skitterphoto` ([Skitterphoto.com](https://skitterphoto.com)), `libreshot` ([LibreShot.com](https://libreshot.com)), `moveast` ([Moveast.me](https://moveast.me)), `cupcake` ([Cupcake](https://cupcake.nilssonlee.se)), `freenaturestock` ([FreeNatureStock](https://freenaturestock.com))
- *(Additional Providers Implemented in `ast`)*: `aura` ([Aura.build](https://www.aura.build)), `negativespace` ([NegativeSpace.co](https://negativespace.co)), `skitterphoto` ([Skitterphoto.com](https://skitterphoto.com)), `libreshot` ([LibreShot.com](https://libreshot.com)), `moveast` ([Moveast.me](https://moveast.me)), `cupcake` ([Cupcake](https://cupcake.nilssonlee.se)), `freenaturestock` ([FreeNatureStock](https://freenaturestock.com)), `goodfreephotos` ([Good Free Photos](https://goodfreephotos.com))
- **Unimplemented / Blocked / Pending**: 11

---
Expand Down
9 changes: 9 additions & 0 deletions src/commands/search.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const libreshot = @import("../providers/libreshot.zig");
const moveast = @import("../providers/moveast.zig");
const cupcake = @import("../providers/cupcake.zig");
const freenaturestock = @import("../providers/freenaturestock.zig");
const goodfreephotos = @import("../providers/goodfreephotos.zig");
const stdio = @import("../stdio.zig");
const Allocator = std.mem.Allocator;
const Io = std.Io;
Expand Down Expand Up @@ -104,6 +105,9 @@ fn doSearch(
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) {
return freenaturestock.search(client, allocator, query, limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) {
return goodfreephotos.search(client, allocator, query, limit);
}
return error.UnknownProvider;
}

Expand Down Expand Up @@ -175,6 +179,9 @@ fn doDownload(
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) {
return freenaturestock.download(client, allocator, io, a, output_dir);
}
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) {
return goodfreephotos.download(client, allocator, io, a, output_dir);
}
return error.UnknownProvider;
}

Expand All @@ -199,6 +206,7 @@ fn doPrompt(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "moveast")) return moveast.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "cupcake")) return cupcake.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) return freenaturestock.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) return goodfreephotos.getPrompt(a);
return a.prompt orelse a.description;
}

Expand All @@ -223,6 +231,7 @@ fn doUrl(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "moveast")) return moveast.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "cupcake")) return cupcake.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) return freenaturestock.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) return goodfreephotos.getUrl(a);
return a.image_url;
}

Expand Down
9 changes: 9 additions & 0 deletions src/interactive.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const libreshot = @import("providers/libreshot.zig");
const moveast = @import("providers/moveast.zig");
const cupcake = @import("providers/cupcake.zig");
const freenaturestock = @import("providers/freenaturestock.zig");
const goodfreephotos = @import("providers/goodfreephotos.zig");
const registry = @import("providers/registry.zig");
const stdio = @import("stdio.zig");
const Allocator = std.mem.Allocator;
Expand Down Expand Up @@ -101,6 +102,9 @@ fn searchProvider(
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) {
return freenaturestock.search(client, allocator, query, config.default_limit);
}
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) {
return goodfreephotos.search(client, allocator, query, config.default_limit);
}
return error.UnknownProvider;
}

Expand Down Expand Up @@ -171,6 +175,9 @@ fn downloadAsset(
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) {
return freenaturestock.download(client, allocator, io, a, config.default_output_dir);
}
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) {
return goodfreephotos.download(client, allocator, io, a, config.default_output_dir);
}
return error.UnknownProvider;
}

Expand All @@ -195,6 +202,7 @@ fn promptOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "moveast")) return moveast.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "cupcake")) return cupcake.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) return freenaturestock.getPrompt(a);
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) return goodfreephotos.getPrompt(a);
return a.prompt;
}

Expand All @@ -219,6 +227,7 @@ fn urlOf(provider_id: []const u8, a: Asset) ?[]const u8 {
if (std.ascii.eqlIgnoreCase(provider_id, "moveast")) return moveast.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "cupcake")) return cupcake.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "freenaturestock")) return freenaturestock.getUrl(a);
if (std.ascii.eqlIgnoreCase(provider_id, "goodfreephotos")) return goodfreephotos.getUrl(a);
return a.image_url;
}

Expand Down
Loading
Loading