Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS avatar

Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS

Pricing

from $2.10 / 1,000 image processeds

Go to Apify Store
Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS

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

Quiet Works Lab

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Security

Limited permissions

Share

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

  1. 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.
  2. Rotate first: the EXIF orientation is applied to the pixels before anything is removed, so portrait photos stay upright.
  3. Resize (optional): shrink to a maximum width and/or height, keeping the aspect ratio. Images are never enlarged.
  4. Convert and compress to the output format with the quality you choose.
  5. Remove metadata: EXIF (including the GPS location), XMP, IPTC and comments are dropped. The color profile is converted to sRGB (default) or kept.
  6. 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.
  7. 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 OUTPUT record.

Input

FieldWhat it does
keyValueStoreIdKey-value store in your account that holds the images. Empty (with no uploads and no links) = built-in samples.
keyPrefixOnly 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.
uploadedFilesFiles 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.
imageUrlsDirect links to image files (not web pages), up to 1,000. Only use images you have the right to use. See Image links.
outputFormatwebp (default), avif, jpeg, png, or original (same format as the input).
quality1-100, default 80. For WebP, AVIF and JPEG; for PNG only with pngReduceColors.
maxWidth, maxHeightOptional maximum size in pixels. Never enlarges.
colorProfilesrgb (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.
keepMetadataDefault false = remove EXIF, GPS, XMP, IPTC and comments. true = keep EXIF (with GPS), XMP and IPTC where the output format supports them.
pngReduceColorsFor PNG output: reduce to at most 256 colors (lossy, much smaller for screenshots and graphics). Default false.
maxImagesOptional: 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
}

Only use images you have the right to use.

  • The link must return the file itself (usually ending in .jpg, .png, .webp, .gif, .avif or .tif). A link to a page that shows an image is refused with NOT_AN_IMAGE, and the message asks for the direct link. The type is checked twice: the answer's Content-Type and the file's first bytes. A format that is not converted fails with UNSUPPORTED_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 Requests is not retried. The same link given twice is read once.
  • Besides Host and Connection: close, which HTTP needs, the only header sent is User-Agent: image-converter (Apify Actor). The Actor does not log in, send cookies or imitate a browser: a link that needs a login fails with AUTH_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 with BLOCKED_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 maxImages stopped it. Only the maximum cost, the timeout or an abort end such a run as PARTIAL.
  • 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 PARTIAL if 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-Length nor 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 with DOWNLOAD_FAILED and 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 frames is the number of frames.
  • source is sample, key-value-store, upload or url. For a link, inputKey is the link without its query string, and linkNumber is its line in your imageUrls list, 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 no linkNumber.
  • A failed image has "status": "failed", an errorCode (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) and SLOW_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:

  1. Open that Actor (or its saved task) in Apify Console and go to its Integrations tab.
  2. Add this Actor as an integration that runs when the run succeeds.
  3. 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_FORMAT and 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 rotatedByExif is false. 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.

EventPriceWhen it is charged
Actor start$0.00005 per run, once more for each GB of memory beyond 1 GBOnce 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:

ImageOutputEvents
Product photo 1600 x 1200 (1.9 MP)WebP, JPEG, PNG1
Phone photo 4032 x 3024 (12.2 MP)WebP, JPEG, PNG1
Phone photo 4032 x 3024 (12.2 MP)AVIF, or PNG with reduced colors2
Camera photo 6000 x 4000 (24 MP)WebP2
50 MP photoWebP4
Animated GIF, 100 frames of 480 x 270 (13 MP in total)WebP1
  • The size counted is the original's width x height, also when you shrink the image.
  • Failed images and skipped records have no image-processed events, and neither do the built-in samples (the Actor start event, set by Apify, applies to every run). Each dataset record shows its chargedEvents, and the summary shows the total that was really charged.
  • A link that fails is a failed image: no image-processed event. 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 in uploadedFiles that is not stored in Apify, a link in imageUrls that 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 no image-processed events (or PARTIAL, 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 PARTIAL summary. 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 with keyPrefix or 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-Length is bigger is refused before its body is read; without a Content-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_FAILED or SLOW_DOWNLOAD and 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_FRAMES before 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:
Memoryto WebPto JPEGto PNGto AVIFfrom an AVIF file
512 MB25 MP27 MP38 MP8 MP17 MP
1 GB (default)63 MP69 MP95 MP20 MP42 MP
2 GB100 MP100 MP100 MP44 MP93 MP
4 GB100 MP100 MP100 MP92 MP100 MP (92 MP to AVIF)
8 GB100 MP100 MP100 MP100 MP100 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)TimePeak memory of the run
The 3 built-in samples (default input)3.6 s for the whole run73 MB
10 photos of 12 MP (3.2 MB JPEG) to WebP105 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 WebP4-5 s each for the first two, then about 27.5 s each537 MB
20 photos of 26 MP (6.9 MB JPEG) to WebP250 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 AVIFabout 8 s703 MB
One 38 MP AVIF file to PNG (the result is 78 MB)6.3 s639 MB
One 30 MP PNG with reduced colors19.7 s249 MB
One 70 MP photo to WebPrefused at once with TOO_MANY_PIXELS ("Start the run with 2048 MB"), not charged100 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):
TestTimePeak memory
One 50 MP photo (12.6 MB JPEG, needs an EXIF rotation) to WebP3.5 s454 MB
... to JPEG5.4 s537 MB
... to PNG (lossless; the result is 120 MB)4.7 s349 MB
... to AVIF13.3 s1,823 MB
... to WebP, shrunk to 2000 px wide0.6 s113 MB
One 100 MP photo (25 MB JPEG) to WebP5.5 s844 MB
One 12 MP photo to WebP / to AVIF1.6 s / 3.6 s190 MB / 490 MB
One 50 MP AVIF file to WebP5.4 s943 MB
One 16 MP photo to PNG with reduced colors4.3 s-
200 photos of 2 MP to WebP, one after another50 s (0.25 s each)100 MB, flat
200 photos of 12 MP to WebP, one after another303 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 TIMEOUT and 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 with keyPrefix or maxImages. 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 INPUT record, 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:

More from Quiet Works Lab

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).