Bulk Image Converter & Compressor (WebP, AVIF, JPG, PNG)
Pricing
from $1.40 / 1,000 image converteds
Bulk Image Converter & Compressor (WebP, AVIF, JPG, PNG)
Convert and compress images in bulk to WebP, AVIF, JPEG or PNG: resize, strip EXIF/GPS metadata, get a per-image savings report and a download link for every file.
Pricing
from $1.40 / 1,000 image converteds
Rating
0.0
(0)
Developer
James Brown
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
15 days ago
Last modified
Categories
Security
Limited permissionsShare
Convert and compress images in bulk. Give it a list of image URLs and/or files you uploaded to an Apify key-value store, pick WebP, AVIF, JPEG or PNG, set the quality and a maximum width/height, and get back a smaller file for every image plus a savings report: original and new format, size and dimensions, and the percentage of bytes saved.
- Four output formats, including AVIF. WebP and AVIF for the web, JPEG for everything else, lossless PNG when you need transparency. Inputs can be JPEG, PNG, WebP, GIF, BMP, TIFF or AVIF.
- Metadata stripping, on by default. Removes EXIF (camera model, timestamps, GPS location), XMP and comments. Embedded colour profiles are first baked into sRGB so the picture looks the same. Camera rotation is applied to the pixels before EXIF is dropped, so photos never end up sideways. Switch stripping off to keep EXIF, ICC profile and XMP.
- Resize while you convert. Maximum width and/or height, aspect ratio kept, never enlarged.
- A report you can use. One dataset row per image with sizes, dimensions, bytes saved % and a download link to the converted file.
- No external services. The images are processed inside the Actor with Pillow. Nothing is uploaded to any third-party API.
- Polite fetching. Honours
robots.txt, refuses private/internal addresses, one request per second per host, 25 MB size cap, image content types only.
Input
| Field | Description |
|---|---|
imageUrls | Public http(s) URLs of images (up to 25 MB each). |
kvsKeys | Keys of image files you have uploaded to a key-value store. Read directly, no download. |
sourceStore | Name or ID of the store that holds those keys. Empty = this run's own default store. |
outputFormat | webp (default), avif, jpeg or png. |
quality | 1-100, default 80. Applies to WebP, AVIF and JPEG; PNG is always lossless. |
maxWidth / maxHeight | Shrink images larger than this (px, 0 = no limit). Aspect ratio kept, never enlarged. |
stripMetadata | Default on. Removes EXIF/GPS, XMP, comments and colour profiles. |
filenamePrefix | Optional text put in front of each output file name, e.g. web-. |
outputStore | Optional name or ID of a store for the converted files, instead of the run's default store. |
Run it with no input and it converts three small generated sample images (a photo carrying EXIF, a transparent gradient logo and a banner) so you can see the report, the savings and the file links in under a minute.
Example input
{"imageUrls": ["https://www.python.org/static/img/python-logo.png","https://example.com/images/product-photo.jpg"],"outputFormat": "avif","quality": 60,"maxWidth": 1600,"stripMetadata": true,"filenamePrefix": "web-"}
To convert files you uploaded yourself, put them in a key-value store (Console → Storage → Key-value stores → create or open one → upload), then:
{"sourceStore": "my-uploads","kvsKeys": ["holiday-1.jpg", "holiday-2.jpg"],"outputFormat": "webp","outputStore": "my-uploads"}
(Converted files are named after the source, e.g. holiday-1.webp. An output file never overwrites a source key listed in kvsKeys; a name clash gets a -2 suffix.)
Example output (one dataset row; the report from the no-input sample run, WebP at quality 80)
{"row": 1,"source": "kvs:sample-photo.jpg","sourceType": "kvs","status": "converted","error": null,"originalFormat": "jpeg","originalBytes": 322865,"originalWidth": 1600,"originalHeight": 1067,"newFormat": "webp","newBytes": 87808,"newWidth": 1600,"newHeight": 1067,"bytesSavedPercent": 72.8,"metadataStripped": true,"outputKey": "sample-photo.webp","outputUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/sample-photo.webp","duplicateOfRow": null,"warnings": []}
Fields
| Field | Meaning |
|---|---|
row | 1-based position in the input (URLs first, then store keys). |
source, sourceType | The URL (url) or kvs:<key> (kvs). |
status | converted, unchanged, failed or duplicate (see below). |
error | Why a failed row failed. The message never contains the URL's query string. |
originalFormat, originalBytes, originalWidth, originalHeight | The source. Dimensions are as displayed, i.e. after applying camera rotation. |
newFormat, newBytes, newWidth, newHeight | The stored result. |
bytesSavedPercent | (original - new) / original, one decimal. Negative if the file got bigger (for example a JPEG photo converted to PNG). |
metadataStripped | true if the source had metadata and it was removed; false when there was nothing to remove or stripping is off. |
outputKey, outputUrl | Key of the converted file in the output key-value store and its Apify link. |
duplicateOfRow | On duplicate rows, the first row with the same source. |
warnings | Notes about this image: flattened transparency, animated input reduced to its first frame, and so on. |
recoveredAfterRestart | Only present (true) on a row delivered again after a restart; it was not charged a second time. |
Statuses. converted: new file saved (charged). unchanged: the target format equals the source format, nothing was resized, and re-encoding would not have been smaller, so your original bytes are saved under the new key as they are (free). failed: unreadable, not an image, too big, blocked by robots.txt, HTTP error, and so on (free). duplicate: the same URL or store key listed twice (free; the first one is converted).
The run also saves a SUMMARY record (counts and total savings) to the default key-value store, and shows the same totals as the final status message.
Pricing (pay per event)
| Event | Price | When it is charged |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Image converted | $0.002 | Per image converted and saved (failed images, duplicates and unchanged images are free) |
| Images | Approximate cost |
|---|---|
| 100 | $0.205 |
| 1,000 | $2.005 |
| 10,000 | $20.005 |
- Actor start: $0.005 per run (Apify's
apify-actor-startevent). Maximum run memory is capped at 1,024 MB so the start event is never multiplied. - Image converted: $0.002 per image, charged after the converted file and its report row are saved.
- Free: failed images, duplicates,
unchangedimages, and rows redelivered after a restart.
The no-input sample run (three images) costs $0.011. If you set a maximum cost per run, the actor stops cleanly when it is reached and says so in the status message.
What is stored, and where
- Report rows go to the run's default dataset.
- Converted files go to a key-value store: the run's default store, or the one named in
outputStore.outputUrlis the Apify link to the file in that store. If the store requires it the link carries a signature, so anyone who has the link can download the file without your API token, and it stops working when the store is deleted. Do not share a report if the images are private. - Default (unnamed) storage expires according to your Apify plan's data-retention period. Download the files in time, or set
outputStoreto a named store, which is kept until you delete it. Files already in the output store under the same key are overwritten. - The run also keeps
PROGRESS(counters only) andSUMMARYin its default key-value store. - Your images are not sent to any service other than the Apify storage you chose. Source URLs appear in the dataset rows (they are your input); the Actor does not write URLs to its log.
Fetching rules
For URL inputs the Actor:
- reads
robots.txtper RFC 9309 with its own user agent (ApifyActor-ImageConverter); a disallowed URL is afailedrow, and ifrobots.txtcannot be read (timeout, connection error, 5xx, 429) it fails closed and does not fetch the image. There is no option to turn this off; - only fetches
httpandhttps, and refuses any host that resolves to a private, loopback, link-local or otherwise non-public address, on every redirect hop (up to 5); - waits at least one second between requests to the same host (the
robots.txtrequest counts), and fetches different hosts independently; - Credentials in URLs (
user:pass@host) are not sent: the actor never makes authenticated requests, so use a URL that needs no login. - accepts only
image/*content types (SVG is refused), stops reading as soon as the body passes 25 MB, and gives up on one image after 90 seconds.
Speed is therefore about one image per second per host plus conversion time. 500 images from one site take about 10 minutes.
Limitations
- Supported inputs: JPEG, PNG, WebP, GIF, BMP, TIFF, AVIF. Not supported: SVG, HEIC/HEIF, RAW, PDF, ICO.
- Size limits: 25 MB per source file; up to 40 megapixels per image (a decompression-bomb guard; checked from the file header before any pixels are decoded); AVIF output is limited to 24 megapixels (it needs more memory), so set
maxWidth/maxHeightfor very large images. WebP is limited to 16,383 px per side and AVIF/JPEG have their own format limits; such images come back asfailedwith the reason. - Animated GIF/WebP and multi-page TIFF: only the first frame is converted (a warning says so).
- Transparency is kept for WebP, AVIF and PNG, and flattened onto white for JPEG.
- PNG output is lossless;
qualitydoes not apply. Photos usually get larger as PNG. - Quality numbers are per encoder, not comparable across formats. Judge on your own images.
- Metadata: with
stripMetadataoff, EXIF, ICC profile and XMP are carried over where the output format supports them. PNG text chunks, IPTC and other private blocks are not carried over. When stripping, an image whose embedded profile cannot be read keeps its stored colours (a warning says so). - Charged per conversion, not per byte saved. Converting to a format that makes the file bigger is still a conversion you asked for and is charged.
- Memory: the Actor runs at 1 GB. Very large lossless PNG or slow WebP encodes of 30+ megapixel images can take 20 seconds or more each.
- Restarts: progress is kept in the key-value store (
PROGRESS), so a restart or migration does not re-charge finished images. An image that was in flight at the interruption is converted again and delivered uncharged withrecoveredAfterRestart: true; a rare duplicate row carries that flag.
Licences of what runs in the Actor
- Pillow (MIT-CMU / HPND licence), including its AVIF support, with the codec libraries bundled in the Linux wheel: libjpeg-turbo (IJG / BSD-3), libwebp (BSD-3), libavif (BSD-2), Little CMS (MIT), libtiff, OpenJPEG (BSD-2), zlib, HarfBuzz (MIT) and FreeType (used under the FreeType Licence, a BSD-style licence, not its GPL option). The Linux wheel the Actor installs (checked for Pillow 13.0.0) ships none of the GPL/LGPL components that appear in Pillow's build list for other platforms (libimagequant, FriBiDi).
- httpx (BSD-3-Clause) and the Apify SDK for Python (Apache-2.0).
- certifi (MPL-2.0), the CA certificate bundle that httpx pulls in. It is used unmodified.
- No GPL/AGPL code, and no external API or paid service.
Responsible use
You are responsible for having the right to copy and re-encode the images you submit. Respect the terms of the sites you fetch from; the Actor honours robots.txt but cannot know a site's licence terms.
FAQ
How do I convert images to WebP in bulk?
Paste the image URLs into imageUrls (or list uploaded files in kvsKeys), leave outputFormat on webp and run. Every converted file is saved to the key-value store and its link is in the outputUrl column of the dataset.
Can it convert to AVIF?
Yes. Set outputFormat to avif. AVIF output is limited to 24 megapixels per image; use maxWidth / maxHeight for larger sources.
Does it remove EXIF and GPS data?
Yes, by default (stripMetadata). EXIF, XMP and comments are removed, colours are converted to sRGB first so the picture looks the same, and camera rotation is applied to the pixels. Turn the option off to keep EXIF, ICC profile and XMP.
How do I compress images without changing the format?
Set outputFormat to the source format (for example jpeg for JPEG photos), lower quality and optionally set maxWidth. If re-encoding cannot make a file smaller the original is kept and the row is marked unchanged (free).
Are my images sent to another service? No. Conversion runs inside the Actor with Pillow. Images fetched from URLs are downloaded to the run; nothing is uploaded anywhere except the Apify storage you chose.
Related Actors
- Domain Health Checker: SPF, DMARC, DKIM and SSL expiry checks for any domain.
- PDF to Markdown for RAG: turn PDF, DOCX, PPTX and HTML into Markdown for LLMs.
- Email Validator & List Cleaner: syntax, MX and disposable-domain checks for email lists.