# Changelog of Image Converter & Compressor - WebP, AVIF, strip EXIF/GPS (`quietworkslab/image-converter`) Actor

- **URL**: https://apify.com/quietworkslab/image-converter/changelog.md
- **Full Actor documentation**: https://apify.com/quietworkslab/image-converter.md

## Changelog

### 0.1 (published on Apify Store on 2026-11-01)

- First version: convert images from a key-value store (optionally only keys with a prefix) or from uploaded files to WebP, AVIF, JPEG or PNG, or keep the format.
- Optional resize (maximum width and height, never enlarges) and quality setting; optional lossy PNG color reduction.
- EXIF orientation is applied to the pixels first. EXIF, GPS, XMP, IPTC and comments are removed by default, and every result is re-read to check that none is left. The color profile is converted to sRGB or kept.
- Each image reports which kinds of metadata it had (true/false only) and its size before and after.
- Animated GIF and WebP stay animated when the output is WebP (or the same format).
- Pay-per-event: `image-processed`, one event per started 16 megapixels (AVIF output and reduced-color PNG count double); failed images and the built-in samples have no image-processed events. Stops cleanly at the maximum cost per run.
- After the review (2026-10-29): each image is charged right after its row and the progress (in OUTPUT) are saved; an image whose charge reaches the limit writes the final summary first. OUTPUT is written at the start and after every image (status RUNNING), so it exists after a timeout or lack of memory. Continuing after a migration uses the last handled key. The memory check uses what is left of the run's memory and counts the input format (AVIF files need about 18 MB per MP to decode). When the event has no price or the Actor is not pay-per-event, nothing is charged and every count says 0. Upload links with "/", "?", "#" or ".." in the key, user info or another port are refused; repeated uploads are converted once; files are read only up to 50 MB.
- After the second review (2026-10-29): stops 30 s before the run's timeout (and does not start an image that would not finish), abandons a running conversion on abort or timeout (not saved, not charged), and writes PARTIAL. Resuming relies on the store's own key order (exclusiveStartKey). "Last image" instead of "last record" decides whether a run that used up the budget ends PARTIAL or COMPLETE. A run whose budget was used up keeps the budget message when Apify then sends aborting. The FAILED summary has every field. Keys such as "my..photo" are accepted.
- After the checks on Apify (2026-10-30, builds 0.1.2 and 0.1.3): a store given only in the integration's `payload` stops the run before anything is charged, with a message to set `keyValueStoreId` to `{{resource.defaultKeyValueStoreId}}` (under limited permissions such a store cannot be read). A file the run may not read (HTTP 401/403, for example an upload link without its signature) fails with a clear "no permission" message instead of "\[object Object]"; other read errors also get a readable reason. The summary says "N% larger" when the converted files are bigger. Memory: each converted image stayed referenced until the end of the run (through the wait for a stop signal), so the memory in use grew by the output size of every image (on Apify, 20 photos of 26 MP: 134 -> 207 MB) and long batches would have ended with TOO_MANY_PIXELS; the stop listener is now removed after each image (build 0.1.3). Node.js starts with `--expose-gc`, and the loop runs a full garbage collection after every 16 MP of images and once more before refusing an image for lack of memory. The heap check no longer trusts a V8 heap limit larger than the run's memory (Apify shows about 30 GB), and the log shows that limit. When a fresh run would fit an image, the message suggests a new run instead of the same memory.
- After the independent review (2026-10-30, build 0.1.4): a store given only in the integration's `payload` is refused only when no files are uploaded either (integrations that convert uploads keep working). Input-check messages say "No image-processed events were charged" (the Actor start event still applies). When the memory left is too small for one image that a fresh run would fit, the message suggests converting that image in a run of its own (for example with `keyPrefix` set to its key), so that earlier images are not charged again. The stop signals live in `src/shutdown.js` with their own tests. README: the price table shows the prices.
- After trying the Console upload on Apify (2026-11-01, build 0.1.5): a file uploaded in the input form is stored in a new key-value store of the account, and its link has no signature, so with private storages (the default) this limited-permission Actor may not open it. The row and the run now name that store and say to choose it in "Key-value store"; when nothing but such uploads was given, the run fails with that message (no image-processed events). Uploads that are in the chosen store are converted with the store, once (they were converted twice before).
- README only (2026-11-01): links to the example tasks (ready-made inputs with their own pages) and to the other Actors of Quiet Works Lab.
- Direct image links (2026-11-01): new optional input `imageUrls`, a list of up to 1,000 links to image files, read after the store and the uploads, one GET per link. The link must return the file itself: the `Content-Type` and the first bytes are both checked, and web pages are refused with a message that asks for the direct link. Up to 5 redirects; private and local addresses are refused, also after a redirect; no logins or cookies. Limits per link: 50 MB, 30 seconds for all attempts together, an answer within 8 seconds, no pause over 8 seconds, at least 250 KB/s on average after the first 5 seconds. A link that fails gives a failed row and is not charged; if links failed and no image could be converted, the run ends as failed (PARTIAL only after the maximum cost, the timeout or an abort). After 20 links in a row have failed, or when failed links have used too much run time, the rest of the list is not tried (no rows, no charges): the run ends PARTIAL if anything was converted, and as failed otherwise. Each link's row has `linkNumber`, its line in the list. Rows and the log show links without their query string. `OUTPUT` has the number of links, and `mode` can include `url`. A stop signal aborts a running download, also during its DNS lookup. New error codes: `TOO_MANY_FRAMES` (for every source, see below), `AUTH_REQUIRED`, `NOT_FOUND`, `RATE_LIMITED`, `HTTP_ERROR`, `TOO_MANY_REDIRECTS`, `BLOCKED_URL`, `BLOCKED_ADDRESS`, `DNS_FAILED`, `DOWNLOAD_FAILED`, `SLOW_DOWNLOAD`. No input field was renamed; a run without links works as before. README: links to Video Frame Extractor and to Merge, Deduplicate & Join Datasets.
- After the independent review of the links (2026-11-01): a GIF or animated WebP with more than 10,000 frames fails with `TOO_MANY_FRAMES` before the image library opens it, for every source, and is not charged: the time to read an animation grows with the square of its frames. A stop signal also ends the reading of a file's header now. Messages about an input that is not a valid URL no longer repeat that text, for image links and for uploads, because it can hold a password or a signature. For links: an answer that never becomes a response (101) ends the download at once, and the time limit and stop signals hold whatever a download waits for; a body with no length that the time limit or a stop cuts is no longer taken for a whole file; a GIF from such a server must reach its end mark, found by walking its blocks; a silent address no longer stops the next one from being tried; messages about the time limit and certificates say what to do.
- Input (2026-11-04): `keyPrefix` is now a secret field: Apify saves its value encrypted and Apify Console hides it after saving. `OUTPUT` still shows the prefix the run used. Inputs that set it as plain text (API calls, integrations, saved tasks) work as before. No field was renamed.
