Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS
Pricing
from $2.10 / 1,000 image processeds
Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS
Convert and compress images to WebP, AVIF, JPEG or PNG, resize them, and remove EXIF, GPS location, XMP and IPTC metadata. Reads images from your key-value store or uploaded files, applies the EXIF rotation first, and reports which metadata each image had.
Pricing
from $2.10 / 1,000 image processeds
Rating
0.0
(0)
Developer
Quiet Works Lab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Security
Limited permissionsShare
Convert and compress your images to WebP, AVIF, JPEG or PNG, resize them, and remove EXIF, GPS location, XMP, IPTC and comments. The Actor reads images from direct file links, from a key-value store in your Apify account or from files you upload, and tells you for each image which kinds of metadata it had.
Typical uses:
- Make product photos, blog images and screenshots smaller for the web (WebP or AVIF, optional maximum width and height).
- Remove the GPS location, camera details and author data from photos before you publish or share them.
- Convert the images another Actor saved in its key-value store, right after it finishes.
- Normalize a batch of images: rotate them by their EXIF orientation, convert the colors to sRGB, limit their size.
- Convert a list of image links, for example your own product images, without uploading them first.
Only use images you have the right to use.
Try it in one click
Click Start without choosing a store, uploading files or adding links. The Actor converts three built-in sample images that were drawn by a script (no image-processed events for the samples; Apify's Actor start event still applies):
- a landscape "photo" with fake GPS coordinates, an EXIF orientation tag, XMP, IPTC and a comment,
- a PNG screenshot with a text comment,
- an animated GIF with 8 frames.
You get three WebP files in the run's key-value store, one dataset record per image, and a summary: 570,182 bytes become 259,000 bytes (54.6% smaller), and the record of the photo shows "hadGps": true while the converted file has no metadata left.
How it works
- Images in: choose a key-value store (optionally only the keys that start with a prefix), upload files in the input form, and/or give direct links to image files (one GET per link, see Image links). They are read in this order, one at a time. Records that are not images (JSON, text) are skipped. The format is detected from the file's content, not from its name.
- Rotate first: the EXIF orientation is applied to the pixels before anything is removed, so portrait photos stay upright.
- Resize (optional): shrink to a maximum width and/or height, keeping the aspect ratio. Images are never enlarged.
- Convert and compress to the output format with the quality you choose.
- Remove metadata: EXIF (including the GPS location), XMP, IPTC and comments are dropped. The color profile is converted to sRGB (default) or kept.
- Check: every converted file is read again, and if any EXIF, GPS, XMP, IPTC or comment were still there, the file is not saved and the image is reported as failed.
- Save: the converted image goes to the run's default key-value store, a record with the link goes to the dataset, and a summary goes to the
OUTPUTrecord.
Input
| Field | What it does |
|---|---|
keyValueStoreId | Key-value store in your account that holds the images. Empty (with no uploads and no links) = built-in samples. |
keyPrefix | Only convert records whose key starts with this text, for example product-. The value is saved encrypted, so Apify Console hides it after saving; OUTPUT shows the prefix the run used. |
uploadedFiles | Files uploaded in the input form. This field takes only files stored in your Apify account; links to other websites go into imageUrls. See Uploaded files. |
imageUrls | Direct links to image files (not web pages), up to 1,000. Only use images you have the right to use. See Image links. |
outputFormat | webp (default), avif, jpeg, png, or original (same format as the input). |
quality | 1-100, default 80. For WebP, AVIF and JPEG; for PNG only with pngReduceColors. |
maxWidth, maxHeight | Optional maximum size in pixels. Never enlarges. |
colorProfile | srgb (default): convert colors to sRGB and write no profile. keep: keep the image's own ICC profile. An ICC profile can contain text, such as its description and copyright, and that text stays in the file with keep. |
keepMetadata | Default false = remove EXIF, GPS, XMP, IPTC and comments. true = keep EXIF (with GPS), XMP and IPTC where the output format supports them. |
pngReduceColors | For PNG output: reduce to at most 256 colors (lossy, much smaller for screenshots and graphics). Default false. |
maxImages | Optional: stop after this many images, for example to test settings on 10 images first. |
Example input:
{"keyValueStoreId": "YOUR_STORE_ID","keyPrefix": "product-","outputFormat": "webp","quality": 80,"maxWidth": 1600,"colorProfile": "srgb","keepMetadata": false}
With links:
{"imageUrls": ["https://example.com/photos/product-01.jpg","https://example.com/photos/product-02.png"],"outputFormat": "webp","maxWidth": 1600}
Image links
Only use images you have the right to use.
- The link must return the file itself (usually ending in
.jpg,.png,.webp,.gif,.avifor.tif). A link to a page that shows an image is refused withNOT_AN_IMAGE, and the message asks for the direct link. The type is checked twice: the answer'sContent-Typeand the file's first bytes. A format that is not converted fails withUNSUPPORTED_FORMAT. - Each link is read with one GET request, one link at a time, after the store and the uploads. Links inside a file are never followed and pages are never crawled. Up to 5 redirects are followed, and each target is checked again. A failed download is retried only after a server error (5xx) or a broken connection, at most twice.
429 Too Many Requestsis not retried. The same link given twice is read once. - Besides
HostandConnection: close, which HTTP needs, the only header sent isUser-Agent: image-converter (Apify Actor). The Actor does not log in, send cookies or imitate a browser: a link that needs a login fails withAUTH_REQUIRED, and so does a site that refuses automated requests with 401 or 403. Use a public link, or upload the file or put it in a key-value store instead. - Links to private, local or reserved network addresses (for example
10.x.x.x,192.168.x.x,127.0.0.1,169.254.169.254), also after a redirect, are refused withBLOCKED_ADDRESS. - A link that carries a token in its query string (a signed link) works; the dataset and the log show links without their query string. The converted file is named after the last part of the link's path.
- A link that fails gives a failed record with the reason (and the HTTP status when the server answered), is not charged, and the run goes on. If links failed and no image could be converted, the run ends as failed with the reason of the first link, also when
maxImagesstopped it. Only the maximum cost, the timeout or an abort end such a run asPARTIAL. - After 20 links in a row have failed, or when failed links have used too much run time (see Limits), the Actor stops reading links: the rest of the list is not tried, and gets no records and no charges. A converted image starts the count of 20 again. The run ends
PARTIALif anything was converted and as failed otherwise, and the message says how many links were not tried. Check the list of links, then start a new run. Failures of store records and uploads do not count and are not stopped by this. - A server that sends neither a
Content-Lengthnor chunks ends the file by closing the connection, so a cut file can look complete. A GIF from such a server must reach the GIF end mark (what comes after it does not matter); otherwise it fails withDOWNLOAD_FAILEDand is not charged. Other formats that are cut fail when they are decoded. - A refused answer (an error, a web page, a file over the size limit) is closed at once, without reading the rest. See Limits for the time limits.
Uploaded files
Apify stores each upload in a new key-value store of your account. This Actor runs with limited permissions: it may always open a store you choose in Key-value store, but not necessarily the store of an upload. Accounts keep storages private by default (Settings > Login & Privacy > General resource access: Restricted), and then the first run cannot open the uploads. It fails with a message that names the store, for example:
The run may not open 2 uploaded files: they are in your key-value store AbCdEf1234567890x, and Apify runs this Actor with limited permissions, so it may open only the store you choose. Choose AbCdEf1234567890x in "Key-value store" (pick it, or paste the ID) and start again; the files uploaded to it are then converted once. No image-processed events were charged for them.
Choose that store in Key-value store (keep the uploads in the form, or remove them) and start again. Every image in the chosen store is converted, and uploads that are in it are not converted a second time. Files uploaded at different times can be in different stores; a run reads one store.
Output
Each dataset record is one image:
{"status": "converted","source": "key-value-store","inputKey": "IMG_0001.jpg","outputKey": "IMG_0001.webp","outputUrl": "https://api.apify.com/v2/key-value-stores/RUN_STORE_ID/records/IMG_0001.webp","inputFormat": "jpeg","outputFormat": "webp","inputBytes": 532066,"outputBytes": 249698,"savedPercent": 53.1,"inputWidth": 1200,"inputHeight": 1600,"outputWidth": 1200,"outputHeight": 1600,"frames": 1,"rotatedByExif": true,"hadExif": true,"hadGps": true,"hadXmp": true,"hadIptc": true,"hadIccProfile": true,"hadComments": true,"metadataRemoved": true,"chargedEvents": 1,"errorCode": null,"errorMessage": null}
had...fields say whether the original had that kind of metadata. The values themselves (for example the GPS coordinates) are never written to the dataset or the log.- Widths and heights are as displayed, after the EXIF orientation. For animations they are the size of one frame, and
framesis the number of frames. sourceissample,key-value-store,uploadorurl. For a link,inputKeyis the link without its query string, andlinkNumberis its line in yourimageUrlslist, counting from 1, with blank lines and repeated links counted. Use it to tell apart links that differ only in their query string. Messages about the input use the same number. Records of other sources have nolinkNumber.- A failed image has
"status": "failed", anerrorCode(CORRUPT_IMAGE,UNSUPPORTED_FORMAT,NOT_AN_IMAGE,FILE_TOO_LARGE,TOO_MANY_PIXELS,TOO_MANY_FRAMES,READ_FAILED,SAVE_FAILED,METADATA_NOT_REMOVED,TIMEOUT,CONVERSION_FAILED) and a message. Failed images are not charged, and the run goes on with the next image. - Links can also fail with
AUTH_REQUIRED(HTTP 401, 403 or 407),NOT_FOUND(404 or 410),RATE_LIMITED(429),HTTP_ERROR(another status),TOO_MANY_REDIRECTS,BLOCKED_URL(a redirect to another scheme or to an address with a login),BLOCKED_ADDRESS,DNS_FAILED,DOWNLOAD_FAILED(a broken connection or no answer, after the retries) andSLOW_DOWNLOAD. - The converted files are in the run's default key-value store. The key is the original name (for a link, the last part of its path) with the new extension; characters that keys cannot contain become
-, and a number is added if two images would get the same key (photo.webp,photo-2.webp).
The OUTPUT record of the run's key-value store holds the summary. mode says where the images came from (for example key-value-store+url), and imageUrls how many links the input had. The list of links is not stored there, but the reason of the first failed link can show that link without its query string (resume.firstUrlError, and message when every link failed).
{"status": "COMPLETE","mode": "sample","message": "Converted 3 of 3 images (0 failed): 570,182 bytes -> 259,000 bytes (54.6% smaller). Location data (GPS) was found in 1 of them. ...","counts": { "found": 3, "converted": 3, "failed": 0, "skippedNotImages": 0 },"bytes": { "input": 570182, "output": 259000, "savedPercent": 54.6 },"metadataFound": { "exif": 1, "gps": 1, "xmp": 1, "iptc": 1, "iccProfile": 1, "comments": 3 },"chargedEvents": 0}
Run it after another Actor
If another Actor saves images to its default key-value store (for example product images or screenshots), convert them automatically when it finishes:
- Open that Actor (or its saved task) in Apify Console and go to its Integrations tab.
- Add this Actor as an integration that runs when the run succeeds.
- In the integration's input, set:
{"keyValueStoreId": "{{resource.defaultKeyValueStoreId}}","outputFormat": "webp","maxWidth": 1600}
{{resource.defaultKeyValueStoreId}} is replaced with the key-value store of the run that just finished. Its JSON records (such as INPUT and OUTPUT) are skipped automatically. Use keyPrefix if the images share a prefix.
Always set keyValueStoreId like this. The run information that Apify adds to the integration's input (payload) is not enough on its own: the Actor may only read a store chosen in keyValueStoreId. If keyValueStoreId is empty and no files or links are given, but payload names a store, the run stops at once with a message that tells you what to set (no image-processed events).
Formats and special cases
- Input: JPEG, PNG, WebP, GIF, AVIF and TIFF.
- Not supported: HEIC/HEIF photos compressed with HEVC (the image library used here can read AVIF but not HEVC), SVG (vector), PDF, BMP, RAW camera files. They are reported as
UNSUPPORTED_FORMATand not charged. Export HEIC photos as JPEG first. - Animated GIF and WebP: with WebP output (or
original) all frames, delays and the loop count are kept. With JPEG, PNG or AVIF output, only the first frame is converted (these outputs are still images here). For animations, all frames together count for the size limits and the price. - Exception: an animation that is kept is not rotated. The image library cannot rotate multi-frame images, so if an animated WebP carries an EXIF orientation tag, its frames stay as stored (the tag is removed with the other metadata) and
rotatedByExifisfalse. Animated files with an orientation tag are rare; choose JPEG, PNG or AVIF output to get the rotated first frame. - Transparency: kept in WebP, AVIF and PNG. JPEG has no transparency, so transparent areas become white.
- CMYK JPEGs are converted to sRGB.
- Multi-page TIFF: only the first page is converted.
- The original records are never changed or deleted.
Pricing
This Actor uses pay-per-event pricing. The current prices are on the Pricing tab of this Actor.
| Event | Price | When it is charged |
|---|---|---|
| Actor start | $0.00005 per run, once more for each GB of memory beyond 1 GB | Once per run, set by Apify (it grows with the memory you give the run; the default is 1 GB). |
Image processed (image-processed) | $0.003 per event (= $3 per 1,000 photos of up to 16 MP) | For each image that is converted and saved: one event per started 16 megapixels of the original image. AVIF output and PNG with "Reduce PNG colors" count double (one event per started 8 MP), because they take about twice as much computing time. |
Examples:
| Image | Output | Events |
|---|---|---|
| Product photo 1600 x 1200 (1.9 MP) | WebP, JPEG, PNG | 1 |
| Phone photo 4032 x 3024 (12.2 MP) | WebP, JPEG, PNG | 1 |
| Phone photo 4032 x 3024 (12.2 MP) | AVIF, or PNG with reduced colors | 2 |
| Camera photo 6000 x 4000 (24 MP) | WebP | 2 |
| 50 MP photo | WebP | 4 |
| Animated GIF, 100 frames of 480 x 270 (13 MP in total) | WebP | 1 |
- The size counted is the original's width x height, also when you shrink the image.
- Failed images and skipped records have no
image-processedevents, and neither do the built-in samples (the Actor start event, set by Apify, applies to every run). Each dataset record shows itschargedEvents, and the summary shows the total that was really charged. - A link that fails is a failed image: no
image-processedevent. Downloading itself has no event, so a link costs the same as an image from your store. Links that are not tried, because too many links failed, have no record and no event. - A run stopped by an input check (wrong store, a store given only in the integration's
payload, a link inuploadedFilesthat is not stored in Apify, a link inimageUrlsthat is not http or https or has a user name or password, too many links, no images found) ends before any image is charged. So does a run where links failed and no image could be converted: it ends as failed, with noimage-processedevents (orPARTIAL, if the maximum cost, the timeout or an abort stopped it first). - No double charges: each image is charged right after its result and the progress are saved. If Apify moves the run to another server (or you resurrect a run that ran out of time or memory), it continues after the last saved image: images already handled are not charged again. In the worst case, the image that was being handled when the run stopped is not charged.
- Abort and timeout: the Actor stops 30 seconds before the run's timeout, and at once when you abort the run, and writes a
PARTIALsummary. It does not start an image that would probably not finish in time. A download, or the reading or conversion of an image, that is already running is abandoned: that image is not saved and not charged. The run may still need a few seconds, for very large images up to the rest of that conversion, before it ends. - If a run is stopped in a way the Actor cannot see (for example out of memory), the summary keeps the last progress (
"status": "RUNNING"with the counts so far). - Maximum cost per run: before each image the Actor checks that your limit still covers it. When it does not, the Actor stops cleanly: the images converted so far are saved and the summary says
"status": "PARTIAL". Images are processed in order (the store in key order, then the uploads, then the links), so the dataset shows where it stopped. A link is not read once the limit no longer covers an image. A new run starts from the beginning again: raise the limit, or narrow the images down withkeyPrefixor fewer links. - Memory: the default 1 GB is enough for most images (see Limits). More memory makes AVIF and very large images faster, but the Actor start event is charged once per GB of memory, so it also makes each run start cost more.
Limits
- File size: up to 50 MB per file. A link whose
Content-Lengthis bigger is refused before its body is read; without aContent-Length, reading stops at 50 MB. - Links: up to 1,000 per run. One download may take at most 30 seconds (all attempts and redirects together), and less when the run's timeout is near. The server must answer within 8 seconds and must not pause for more than 8 seconds, and after the first 5 seconds the download must average at least 250 KB/s. Otherwise the image fails with
TIMEOUT,DOWNLOAD_FAILEDorSLOW_DOWNLOADand is not charged. A file of the full 50 MB needs about 1.75 MB/s on average. For a file on a slow host, upload it or put it in a key-value store and choose that store. After 20 links in a row have failed, the rest of the list is not tried (see Image links). The same happens when failed links have used too much run time: the seconds from the start of each failed download to its record, multiplied by the run's memory in GB, may reach 600, plus 40 for each charged event. These are limits the Actor enforces, not measured speeds: the time of a download depends on the host. - Animations: a GIF or an animated WebP may have up to 10,000 frames, from any source. A file with more fails with
TOO_MANY_FRAMESbefore it is opened and is not charged: the time to read an animation grows with the square of its frames. Shorten it, or export the frame you need as a still image. - Image size: up to 100 megapixels, and less with less memory, because the whole image is held in memory while it is converted. Before each image the Actor checks what is left of the run's memory (the run's memory minus what the process already uses). Typical limits:
| Memory | to WebP | to JPEG | to PNG | to AVIF | from an AVIF file |
|---|---|---|---|---|---|
| 512 MB | 25 MP | 27 MP | 38 MP | 8 MP | 17 MP |
| 1 GB (default) | 63 MP | 69 MP | 95 MP | 20 MP | 42 MP |
| 2 GB | 100 MP | 100 MP | 100 MP | 44 MP | 93 MP |
| 4 GB | 100 MP | 100 MP | 100 MP | 92 MP | 100 MP (92 MP to AVIF) |
| 8 GB | 100 MP | 100 MP | 100 MP | 100 MP | 100 MP |
Decoding an AVIF file needs about 18 MB per megapixel whatever the output, so AVIF inputs have their own column. A bigger image fails with TOO_MANY_PIXELS and a message that tells you how much memory it needs (not charged).
- Measured on Apify with 1 GB of memory (the default; synthetic test photos):
| Test (1 GB run) | Time | Peak memory of the run |
|---|---|---|
| The 3 built-in samples (default input) | 3.6 s for the whole run | 73 MB |
| 10 photos of 12 MP (3.2 MB JPEG) to WebP | 105 s for the whole run (about 10 s each) | 228 MB |
| 5 photos of 55 MP (14.6 MB JPEG, needs an EXIF rotation) to WebP | 4-5 s each for the first two, then about 27.5 s each | 537 MB |
| 20 photos of 26 MP (6.9 MB JPEG) to WebP | 250 s for the whole run (about 2 s each for the first four, then about 14 s each) | 306 MB; the memory in use after each image stayed at 129-140 MB |
| One 18 MP photo to AVIF | about 8 s | 703 MB |
| One 38 MP AVIF file to PNG (the result is 78 MB) | 6.3 s | 639 MB |
| One 30 MP PNG with reduced colors | 19.7 s | 249 MB |
| One 70 MP photo to WebP | refused at once with TOO_MANY_PIXELS ("Start the run with 2048 MB"), not charged | 100 MB |
A 1 GB run gets a quarter of a CPU core. The first seconds of a run are about as fast as a full core, then the run slows down: for a long batch on 1 GB, expect about 7 times the times of our test computer below. The AVIF and PNG times above were measured at the start of a run, so a long batch of such images takes longer per image.
- Measured on our test computer (one processing thread):
| Test | Time | Peak memory |
|---|---|---|
| One 50 MP photo (12.6 MB JPEG, needs an EXIF rotation) to WebP | 3.5 s | 454 MB |
| ... to JPEG | 5.4 s | 537 MB |
| ... to PNG (lossless; the result is 120 MB) | 4.7 s | 349 MB |
| ... to AVIF | 13.3 s | 1,823 MB |
| ... to WebP, shrunk to 2000 px wide | 0.6 s | 113 MB |
| One 100 MP photo (25 MB JPEG) to WebP | 5.5 s | 844 MB |
| One 12 MP photo to WebP / to AVIF | 1.6 s / 3.6 s | 190 MB / 490 MB |
| One 50 MP AVIF file to WebP | 5.4 s | 943 MB |
| One 16 MP photo to PNG with reduced colors | 4.3 s | - |
| 200 photos of 2 MP to WebP, one after another | 50 s (0.25 s each) | 100 MB, flat |
| 200 photos of 12 MP to WebP, one after another | 303 s (1.5 s each) | 264 MB, flat |
The test photos are synthetic (gradients with noise), which compress less than most real photos, so real files usually get smaller than in these tests.
- Images are converted one at a time. WebP, JPEG and PNG encoding use one CPU core, so more than 4 GB of memory does not make them faster; it helps AVIF and very large images.
- Each image may take up to 300 seconds (less when the run's timeout is near); a longer conversion fails with
TIMEOUTand is not charged. Reducing the colors of a PNG cannot be cut short this way, so a very large reduced-color PNG can run longer. For thousands of images, give the run a longer timeout (Run options) or split the work withkeyPrefixormaxImages. A link is not started in the last 55 seconds before the run's timeout. - Uploaded files must be stored in your Apify account, and the Actor reads them through the Apify API. Links to files on other websites go into
imageUrls. - The Actor only reads the links you give, the key-value store you choose and the files you upload (it runs with limited permissions). If your account keeps its storages private, choose the store your uploads went to (see Uploaded files).
Privacy
- The Actor reads only the links you give (one GET each, plus redirects and retries as described above), the key-value store you choose and the files you upload. It does not send your images to any other service. The server behind a link sees a request from Apify's network with the User-Agent above. Only use images you have the right to use.
- The converted images are saved in this run's default key-value store in your account, like every Actor's results, and are kept or deleted according to your account's data retention. The original records are not changed.
- The dataset, the summary and the log contain file names, sizes and true/false flags, never the metadata values (no GPS coordinates, camera serial numbers or captions). They show links without their query string, because signed links can carry tokens there. The run's
INPUTrecord, like every Actor's, keeps the links as you gave them. - The summary keeps a hash of your list of links (
resume.urlsHash), to continue the right list after a migration. It cannot be read back as such, but it has no salt, so predictable values (a URL pattern, sequential IDs) can be guessed and checked against it. - Removing metadata removes the data stored in the file. Anything visible in the picture itself (a watermark, a street sign, a face) stays.
- With
colorProfile: keep, the ICC color profile stays in the file, including any text it carries (its description and copyright, sometimes the name of the device or software that made it). - With
keepMetadata: true, EXIF (including the GPS location), XMP and IPTC stay in the converted files on purpose.
Example tasks
Ready-made inputs for common jobs. Open one, then click Try for free to copy it to your account and change the input:
- Convert JPEG and PNG images to WebP in bulk
- Compress images to AVIF for a faster website
- Remove EXIF and GPS location data from photos
- Resize and compress product photos for an online store
More from Quiet Works Lab
- Video Frame Extractor: extract thumbnails and frames from your videos, with a contact sheet per video.
- Merge, Deduplicate & Join Datasets: merge your datasets, remove duplicates by key and join fields from a lookup dataset.
- Dataset Change Tracker: compare two runs of your scraper and get only the new, changed and removed items.
- Japanese Text Normalizer & RAG Chunker: clean Japanese text before an LLM or RAG and split it into sentence-aware chunks.
Support
This Actor is developed and maintained with the help of AI (Claude Code). Issues are answered within a few days.
Please open an issue on the Issues tab with the run ID and, if you can, a sample image that shows the problem (remove anything private first).