Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package imgp for openSUSE:Factory checked in at 2026-08-25 13:20:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/imgp (Old) and /work/SRC/openSUSE:Factory/.imgp.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "imgp" Tue Aug 25 13:20:46 2026 rev:9 rq:1373540 version:3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/imgp/imgp.changes 2026-08-05 17:49:17.227700394 +0200 +++ /work/SRC/openSUSE:Factory/.imgp.new.1258/imgp.changes 2026-08-25 13:21:39.245270221 +0200 @@ -1,0 +2,12 @@ +Tue Aug 25 07:38:16 UTC 2026 - Andrea Manzini <[email protected]> + +- Update to version 3.0 + * Add megapixel target functionality + * Add an option to raise the PIL/Pillow pixel limit for trusted input files + * Fix incorrect gamma handling during resizing + * Add Python 3.12, 3.13 and 3.14 support + * Retire Python 3.8 and 3.9 support (EOL) + * Add -v and --version options + * Add version option completion for Bash, Fish, and Zsh + +------------------------------------------------------------------- Old: ---- imgp-2.9.tar.gz New: ---- imgp-3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ imgp.spec ++++++ --- /var/tmp/diff_new_pack.MSWWPk/_old 2026-08-25 13:21:40.287306966 +0200 +++ /var/tmp/diff_new_pack.MSWWPk/_new 2026-08-25 13:21:40.289307037 +0200 @@ -22,7 +22,7 @@ %define _fish_completions %{_datadir}/fish/vendor_completions.d %endif Name: imgp -Version: 2.9 +Version: 3.0 Release: 0 Summary: Image resizer and rotator License: GPL-3.0-or-later ++++++ imgp-2.9.tar.gz -> imgp-3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/.circleci/config.yml new/imgp-3.0/.circleci/config.yml --- old/imgp-2.9/.circleci/config.yml 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/.circleci/config.yml 2026-08-16 08:52:22.000000000 +0200 @@ -1,4 +1,4 @@ -version: 2 +version: 2.1 test-template: &test-template working_directory: ~/imgp @@ -17,24 +17,29 @@ make test jobs: - py38: + py310: docker: - - image: python:3.8-slim + - image: python:3.10-slim <<: *test-template - py39: + py311: docker: - - image: python:3.9-slim + - image: python:3.11-slim <<: *test-template - py310: + py312: docker: - - image: python:3.10-slim + - image: python:3.12-slim <<: *test-template - py311: + py313: docker: - - image: python:3.11-slim + - image: python:3.13-slim + <<: *test-template + + py314: + docker: + - image: python:3.14-slim <<: *test-template # package-and-publish: @@ -56,14 +61,14 @@ # ghr -t ${GITHUB_API_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace ${CIRCLE_TAG} ./dist/ workflows: - version: 2 - CircleCI: jobs: &all-tests - - py38 - - py39 - py310 - py311 + - py312 + - py313 + - py314 + nightly: triggers: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/.github/workflows/lock.yml new/imgp-3.0/.github/workflows/lock.yml --- old/imgp-2.9/.github/workflows/lock.yml 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/.github/workflows/lock.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,17 +0,0 @@ -name: 'Lock threads' - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - lock: - runs-on: ubuntu-latest - steps: - - uses: dessant/lock-threads@v2 - with: - github-token: ${{ github.token }} - issue-lock-inactive-days: '30' - issue-lock-reason: '' - pr-lock-inactive-days: '30' - pr-lock-reason: '' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/CHANGELOG new/imgp-3.0/CHANGELOG --- old/imgp-2.9/CHANGELOG 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/CHANGELOG 2026-08-16 08:52:22.000000000 +0200 @@ -1,3 +1,16 @@ +imgp v3.0 +2026-08-16 + +- Add megapixel target functionality +- Add an option to raise the PIL/Pillow pixel limit for trusted input files +- Fix incorrect gamma handling during resizing +- Add Python 3.12 support +- Retire Python 3.8 and 3.9 support (EOL) +- Add `-v` and `--version` options +- Add version option completion for Bash, Fish, and Zsh + +------------------------------------------------------------------------------- + imgp v2.9 2023-09-10 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/README.md new/imgp-3.0/README.md --- old/imgp-2.9/README.md 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/README.md 2026-08-16 08:52:22.000000000 +0200 @@ -2,14 +2,14 @@ <p align="center"> <a href="https://github.com/jarun/imgp/releases/latest"><img src="https://img.shields.io/github/release/jarun/imgp.svg?maxAge=600" alt="Latest release" /></a> -<a href="https://repology.org/project/imgp/versions"><img src="https://repology.org/badge/tiny-repos/imgp.svg" alt="Availability"></a> +<a href="https://repology.org/project/imgp/versions"><img src="https://repology.org/badge/tiny-repos/imgp.svg?header=repos" alt="Availability"></a> <a href="https://pypi.org/project/imgp/"><img src="https://img.shields.io/pypi/v/imgp.svg?maxAge=600" alt="PyPI" /></a> <a href="https://circleci.com/gh/jarun/workflows/imgp"><img src="https://img.shields.io/circleci/project/github/jarun/imgp.svg" alt="Build Status" /></a> -<a href="https://github.com/jarun/imgp/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-GPLv3-yellowgreen.svg?maxAge=2592000" alt="License" /></a> +<a href="https://github.com/jarun/imgp/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-GPLv3+-yellowgreen.svg?maxAge=2592000" alt="License" /></a> </p> <p align="center"> -<a href="https://asciinema.org/a/88448"><img src="https://asciinema.org/a/88448.svg" alt="imgp_asciicast" width="600"/></a> +<a href="https://asciinema.org/a/88448"><img src="https://github.com/user-attachments/assets/b7efb760-b5f8-4118-beee-69ce9efd8de4" alt="imgp_asciicast" width="600"/></a> </p> <p align="center"><i>Watch imgp resize a directory of images in lightning speed!</i></p> @@ -36,11 +36,12 @@ - [cmdline options](#cmdline-options) - [Operational notes](#operational-notes) - [Examples](#examples) +- [Troubleshooting](#troubleshooting) - [Developers](#developers) ### Features -- resize by percentage or resolution +- resize by percentage or resolution or megapixel target - rotate clockwise by specified angle - adaptive resize considering orientation - brute force to a resolution @@ -75,15 +76,15 @@ #### Dependencies -`imgp` requires Python 3.8 or later. +`imgp` requires Python 3.10 or later. To install PIL library on Ubuntu, run: - $ sudo apt-get install python3-pil + sudo apt-get install python3-pil or, using pip3: - $ sudo pip3 install pillow + sudo pip3 install pillow pillow can be replaced by [pillow-simd](https://github.com/uploadcare/pillow-simd) on [SIMD](https://en.wikipedia.org/wiki/SIMD) processors. @@ -115,11 +116,11 @@ Install to default location (`/usr/local`): - $ sudo make install + sudo make install To remove, run: - $ sudo make uninstall + sudo make uninstall `PREFIX` is supported, in case you want to install to a different location. @@ -127,7 +128,7 @@ `imgp` is a standalone utility. From the containing directory, run: - $ ./imgp + ./imgp ### Shell completion @@ -139,7 +140,7 @@ ``` usage: imgp [-h] [-x res] [-o deg] [-a] [-c] [-e] [-f] [-H] [-i] [-k] [-m] [-M res] - [-n] [-N] [-O] [-P] [-q N] [-r] [-s byte] [-w] [-d] [PATH [PATH ...]] + [-n] [-N] [-O] [-P] [-q N] [-r] [-s byte] [-w] [-d] [-v] [PATH ...] Resize, rotate JPEG and PNG images. @@ -148,7 +149,7 @@ optional arguments: -h, --help show this help message and exit - -x res, --res res output resolution in HxV or percentage + -x res, --res res output resolution in HxV or percentage ("%" suffix) or megapixel ("MP" suffix) -o deg, --rotate deg rotate clockwise by angle (in degrees) -a, --adapt adapt to resolution by orientation [default: off] -c, --convert convert PNG to JPG format [default: off] @@ -169,6 +170,7 @@ -s byte, --size byte minimum size to process an image [default: 1024] -w, --overwrite overwrite source images [default: off] -d, --debug enable debug logs [default: off] + -v, --version show program's version number and exit ``` #### Operational notes @@ -208,16 +210,23 @@ 1366x767 -> 1025x575 120968 bytes -> 45040 bytes -3. Rotate an image clockwise by 90 degrees: +3. Ensure all images in folder `testphotos/` (contains 4 images) are 12MP at maximum: - $ imgp -o 90 ~/image.jpg + $ imgp --res 12MP -w testphotos/ + testphotos/20250111_193949_ILCE-7M3.jpg + 6000x4000 -> 4243x2828 + 8355840 bytes -> 3545149 bytes + +4. Rotate an image clockwise by 90 degrees: + + $ imgp -o 90 ~/image.jpg 120968 bytes -> 72038 bytes -4. Adapt the images in the current directory to 1366x1000 resolution. Visit all directories recursively, overwrite source images, ignore images with matching hres or vres but convert PNG images to JPEG. +5. Adapt the images in the current directory to 1366x1000 resolution. Visit all directories recursively, overwrite source images, ignore images with matching hres or vres but convert PNG images to JPEG. $ imgp -x 1366x1000 -wrack -5. Set hres=800 and adapt vres maintaining the ratio. +6. Set hres=800 and adapt vres maintaining the ratio. $ imgp -x 800x0 Source omitted. Processing current directory... @@ -230,11 +239,11 @@ 1050x1400 -> 800x1067 458092 bytes -> 78089 bytes -6. Process images greater than 50KiB only: +7. Process images greater than 50KiB only: $ imgp -wrackx 1366x1000 -s 51200 -7. Generate a 64px adapative thumbnail of the last modified file in the current dir: +8. Generate a 64px adaptive thumbnail of the last modified file in the current dir: #!/usr/bin/env sh @@ -244,7 +253,13 @@ imgp -acx 64x64 "$pop" } +### Troubleshooting + +1. To lift the pixel limit (e.g. error `'PIL.Image.DecompressionBombError' when resizing large image`) for _trusted_ input files, uncomment the following line in the source code: + + # Image.MAX_IMAGE_PIXELS = None + ### Developers -1. Copyright © 2016-2023 [Arun Prakash Jana](https://github.com/jarun) +1. Copyright © 2016-2026 [Arun Prakash Jana](https://github.com/jarun) 2. [Ananya Jana](https://github.com/ananyajana) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/auto-completion/bash/imgp-completion.bash new/imgp-3.0/auto-completion/bash/imgp-completion.bash --- old/imgp-2.9/auto-completion/bash/imgp-completion.bash 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/auto-completion/bash/imgp-completion.bash 2026-08-16 08:52:22.000000000 +0200 @@ -32,6 +32,7 @@ -x --res -d --debug -h --help + -v --version ) opts_with_arg=( -M --minres diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/auto-completion/fish/imgp.fish new/imgp-3.0/auto-completion/fish/imgp.fish --- old/imgp-2.9/auto-completion/fish/imgp.fish 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/auto-completion/fish/imgp.fish 2026-08-16 08:52:22.000000000 +0200 @@ -22,6 +22,7 @@ complete -c imgp -s r -l recurse --description 'process directories recursively' complete -c imgp -s s -l size -r --description 'min byte size to process an image' complete -c imgp -s w -l overwrite --description 'overwrite source images' -complete -c imgp -s x -l res -r --description 'output resolution in HxV or %' +complete -c imgp -s x -l res -r --description 'output resolution in HxV or % or MP' complete -c imgp -s d -l debug --description 'enable debug logs' complete -c imgp -s h -l help --description 'show help' +complete -c imgp -s v -l version --description 'show program version' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/auto-completion/zsh/_imgp new/imgp-3.0/auto-completion/zsh/_imgp --- old/imgp-2.9/auto-completion/zsh/_imgp 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/auto-completion/zsh/_imgp 2026-08-16 08:52:22.000000000 +0200 @@ -27,8 +27,9 @@ '(-r --recurse)'{-r,--recurse}'[process directories recursively]' '(-s --size)'{-s,--size}'[min byte size to process an image]:bytes' '(-w --overwrite)'{-w,--overwrite}'[overwrite source images]' - '(-x --res)'{-x,--res}'[output resolution]:HxV or %' + '(-x --res)'{-x,--res}'[output resolution]:HxV or % or MP' '(-d --debug)'{-z,--debug}'[enable debug logs]' '(-h --help)'{-h,--help}'[show help]' + '(-v --version)'{-v,--version}'[show program version]' ) _arguments -S -s $args diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/imgp new/imgp-3.0/imgp --- old/imgp-2.9/imgp 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/imgp 2026-08-16 08:52:22.000000000 +0200 @@ -2,7 +2,7 @@ # # Resize or rotate JPEG and PNG images. # -# Copyright © 2016-2023 Arun Prakash Jana <[email protected]> +# Copyright © 2016-2026 Arun Prakash Jana <[email protected]> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,15 +23,20 @@ import os import sys import time +import math import PIL from PIL import Image, ImageFile +# Uncomment the below line to lift pixel limit for _trusted_ input files +# Image.MAX_IMAGE_PIXELS = None + if not hasattr(PIL.Image, 'Resampling'): # Pillow<9.0 PIL.Image.Resampling = PIL.Image # Globals HRES = 0 # specified horizontal resolution VRES = 0 # specified vertical resolution +megapixel = None # specified megapixel target scale = None # specified scale in percentage rotate = None # specified rotation in degree adapt = False # use adaptive resolution @@ -56,8 +61,7 @@ pad = '_IMGP' # output file suffix when --overwrite is unused png_ip = Image.Resampling.LANCZOS # default interpolation for PNG fill_color = '#ffffff' # BG color to strip alpha channel -init_time = time.time() # profile the total time taken -_VERSION_ = '2.9' # current program version +_VERSION_ = '3.0' # current program version # Globals for multiprocessing pool = None @@ -71,22 +75,79 @@ :param res: resolution in hresxvres format ''' - - hxv = res.split('x') - if len(hxv) != 2: - return (0, 0) - try: - return (int(hxv[0]), int(hxv[1])) + hxv = res.split('x') + assert len(hxv) == 2 + return map(int, hxv) except Exception: return (0, 0) +def gamma_aware_resize(img, isize, resample): + '''Resize image with gamma correction for better quality + + Converts to linear color space before resizing, then converts back. + This prevents the common error of resizing in gamma-corrected space. + + Fixes issue #60: https://github.com/jarun/imgp/issues/60 + See also: http://www.ericbrasseur.org/gamma.html + + :param img: PIL Image object + :param isize: tuple of (width, height) for target size + :param resample: resampling filter to use + :return: resized PIL Image object + ''' + # For images without color (e.g., 'L', '1'), no gamma correction needed + if img.mode in ('L', '1', 'P'): + return img.resize(isize, resample) + + # Handle alpha channel separately if present + has_alpha = img.mode in ('RGBA', 'LA') + if has_alpha: + # Split into color and alpha + if img.mode == 'RGBA': + r, g, b, a = img.split() + rgb_img = Image.merge('RGB', (r, g, b)) + else: # LA + l, a = img.split() + rgb_img = Image.merge('RGB', (l, l, l)) + + # Resize alpha separately (no gamma correction needed for alpha) + resized_alpha = a.resize(isize, resample) + else: + rgb_img = img.convert('RGB') if img.mode != 'RGB' else img + + # Apply gamma correction: convert from sRGB to linear + # Using simplified sRGB gamma of 2.2 (actual sRGB is more complex but 2.2 is close) + gamma = 2.2 + linear_img = rgb_img.point(lambda x: pow(x / 255.0, gamma) * 255.0) + + # Resize in linear space + resized_linear = linear_img.resize(isize, resample) + + # Convert back to sRGB: apply inverse gamma + inv_gamma = 1.0 / gamma + resized_rgb = resized_linear.point(lambda x: pow(x / 255.0, inv_gamma) * 255.0) + + # Reattach alpha if it was present + if has_alpha: + if img.mode == 'RGBA': + r, g, b = resized_rgb.split() + result = Image.merge('RGBA', (r, g, b, resized_alpha)) + else: # LA - 8-bit pixels, grayscale with alpha + result = Image.merge('LA', (resized_rgb.split()[0], resized_alpha)) + return result + + if img.mode != 'RGB': + # Convert back to original mode if needed + return resized_rgb.convert(img.mode) + + return resized_rgb + + def lock_print(msg): '''Acquire lock and print a message''' - global lock - with lock: print(msg) @@ -102,8 +163,6 @@ def traverse_dir(path): '''Traverse a directory recursively''' - global lock, pool - try: for entry in os.scandir(path): # Add check for hidden here @@ -123,15 +182,12 @@ else: pool.apply_async(resize_image, args=(entry.path,), callback=cb) except OSError as e: - with lock: - print(e) + lock_print(e) def rotate_image(src): '''Rotate a source image''' - global lock - converted = False _progressive = progressive name, ext = os.path.splitext(src) @@ -164,8 +220,7 @@ exifdata = b'' except OSError as e: if not str(e).startswith('cannot identify image file'): - with lock: - print(e) + lock_print(e) return # Image.rotate(angle, resample=0, expand=0) expand = True means @@ -182,7 +237,7 @@ if convert and _format == 'PNG': if img.mode in ('RGBA', 'LA'): - background = Image.new(img.mode[:-1], img.size, fill_color) + background = Image.new(img.mode.rstrip('A'), img.size, fill_color) background.paste(img, img.split()[-1]) img = background elif img.mode == "P": @@ -204,15 +259,12 @@ return [1, 0] except OSError as e: - with lock: - print(e) + lock_print(e) def resize_image(src): '''Resize a source image''' - global HRES, VRES, minH, minV, lock - converted = False _progressive = progressive name, ext = os.path.splitext(src) @@ -257,12 +309,10 @@ exifdata = b'' except OSError as e: if not str(e).startswith('cannot identify image file'): - with lock: - print(e) + lock_print(e) return - hres = img.size[0] - vres = img.size[1] + hres, vres = img.size ImageFile.MAXBLOCK = hres * vres antialias = png_ip if _format == 'PNG' else Image.Resampling.LANCZOS @@ -278,6 +328,24 @@ if convert and _format != 'PNG': return + + elif megapixel: + img_mp = hres * vres / 1e6 + + shrink_factor = math.sqrt(megapixel / img_mp) + if shrink_factor >= 1 and not enlarge: + return + + _hres = round(hres * shrink_factor) + _vres = round(vres * shrink_factor) + + if debug: + lock_print(f"{img_mp} * {shrink_factor} → {_hres}x{_vres} ({_hres*_vres/1e6}MP)") + + img = gamma_aware_resize(img, (_hres, _vres), antialias) + if debug: + lock_print('resize_image: scaled to [%dx%d]' % (_hres, _vres)) + elif scale: if scale == 100: _hres = hres @@ -294,9 +362,10 @@ _vres = _vres + 1 _vres = int(_vres) - img = img.resize((_hres, _vres), antialias) + img = gamma_aware_resize(img, (_hres, _vres), antialias) if debug: lock_print('resize_image: scaled to [%dx%d]' % (_hres, _vres)) + elif HRES == 0 or VRES == 0: if HRES: if not enlarge and HRES > hres: @@ -310,7 +379,7 @@ target_vres = target_vres + 1 target_vres = int(target_vres) - img = img.resize((HRES, target_vres), antialias) + img = gamma_aware_resize(img, (HRES, target_vres), antialias) if debug: lock_print('resize_image: 0 vres %dx%d' % (HRES, target_vres)) @@ -326,16 +395,19 @@ target_hres = target_hres + 1 target_hres = int(target_hres) - img = img.resize((target_hres, VRES), antialias) + img = gamma_aware_resize(img, (target_hres, VRES), antialias) if debug: lock_print('resize_image: 0 hres %dx%d' % (target_hres, VRES)) + elif HRES == hres and VRES == vres: if debug: lock_print('resize_image: same res [%dx%d]' % (HRES, VRES)) + elif force: - img = img.resize((HRES, VRES), antialias) + img = gamma_aware_resize(img, (HRES, VRES), antialias) if debug: lock_print('resize_image: brute force res to [%dx%d]' % (HRES, VRES)) + elif adapt: ratio_img = float(hres/vres) ratio_target = float(HRES/VRES) @@ -355,7 +427,7 @@ target_vres = target_vres + 1 target_vres = int(target_vres) - img = img.resize((HRES, target_vres), antialias) + img = gamma_aware_resize(img, (HRES, target_vres), antialias) if debug: lock_print('resize_image: vres adapted %dx%d' % (HRES, target_vres)) elif (ratio_target < 1 and ratio_img < 1) or (ratio_target >= 1 and ratio_img <= 1): @@ -373,9 +445,10 @@ target_hres = target_hres + 1 target_hres = int(target_hres) - img = img.resize((target_hres, VRES), antialias) + img = gamma_aware_resize(img, (target_hres, VRES), antialias) if debug: lock_print('resize_image: hres adapted %dx%d' % (target_hres, VRES)) + else: ratio_img = float(hres/vres) ratio_target = float(HRES/VRES) @@ -393,7 +466,7 @@ target_vres = target_vres + 1 target_vres = int(target_vres) - img = img.resize((HRES, target_vres), antialias) + img = gamma_aware_resize(img, (HRES, target_vres), antialias) if debug: lock_print('resize_image: vres calculated %dx%d' % (HRES, target_vres)) else: @@ -409,7 +482,7 @@ target_hres = target_hres + 1 target_hres = int(target_hres) - img = img.resize((target_hres, VRES), antialias) + img = gamma_aware_resize(img, (target_hres, VRES), antialias) if debug: lock_print('resize_image: hres calculated %dx%d' % (target_hres, VRES)) @@ -423,7 +496,7 @@ if convert and _format == 'PNG': if img.mode in ('RGBA', 'LA'): - background = Image.new(img.mode[:-1], img.size, fill_color) + background = Image.new(img.mode.rstrip('A'), img.size, fill_color) background.paste(img, img.split()[-1]) img = background elif img.mode == "P": @@ -445,8 +518,7 @@ return [1, 0] except OSError as e: - with lock: - print(e) + lock_print(e) class ExtendedArgumentParser(argparse.ArgumentParser): @@ -460,8 +532,8 @@ file.write(''' Version %s -Copyright © 2016-2023 Arun Prakash Jana <[email protected]> -License: GPLv3 +Copyright © 2016-2026 Arun Prakash Jana <[email protected]> +License: GPL-3.0-or-later Webpage: https://github.com/jarun/imgp ''' % _VERSION_) @@ -489,7 +561,7 @@ formatter_class=argparse.RawTextHelpFormatter) addarg = argparser.add_argument addarg('-x', '--res', nargs=1, metavar='res', - help='output resolution in HxV or percentage') + help='output resolution in HxV or percentage ("%%" suffix) or megapixel ("MP" suffix)') addarg('-o', '--rotate', type=int, metavar='deg', help='rotate clockwise by angle (in degrees)') addarg('-a', '--adapt', action='store_true', @@ -521,15 +593,16 @@ addarg('-P', '--progressive', action='store_true', help='save JPEG images as progressive [default: off]') addarg('-q', '--quality', type=int, metavar='N', choices=range(1, 96), - help='quality factor (N=1-95, JPEG only) [default: 75]') + help=f'quality factor (N=1-95, JPEG only) [default: {qual}]') addarg('-r', '--recurse', action='store_true', help='process non-symbolic dirs recursively [default: off]') addarg('-s', '--size', type=int, metavar='byte', - help='minimum size to process an image [default: 1024]') + help=f'minimum size to process an image [default: {size}]') addarg('-w', '--overwrite', action='store_true', help='overwrite source images [default: off]') addarg('-d', '--debug', action='store_true', help='enable debug logs [default: off]') + addarg('-v', '--version', action='version', version=_VERSION_) addarg('keywords', nargs='*', metavar='PATH', help='source file or dir [default: current dir]') @@ -542,10 +615,11 @@ def main(): - global HRES, VRES, scale, rotate, adapt, convert, hidden, eraseexif, force, \ + global HRES, VRES, megapixel, scale, rotate, adapt, convert, hidden, eraseexif, force, \ includeimgp, keep, enlarge, optimal, progressive, mute, recurse, size, \ - qual, overwrite, debug, no_res_opt, pool, init_time, count, png_ip, minH, minV + qual, overwrite, debug, no_res_opt, pool, count, png_ip, minH, minV + init_time = time.time() # profile the total time taken args = parse_args() if args.rotate is not None and args.res is not None: @@ -556,15 +630,18 @@ if 'x' in args.res[0]: HRES, VRES = getres(args.res[0]) if HRES == 0 and VRES == 0: - print('-ve values not allowed in --res, \ - hres and vres cannot be 0 together') + print('-ve values not allowed in --res, ' + 'hres and vres cannot be 0 together') + return + elif args.res[0].upper().endswith("MP"): + try: + megapixel = float(args.res[0].upper().rstrip("MP")) + except ValueError: + print(f'failed parsing megapixel value from "{args.res[0]}"') return else: try: - if args.res[0].endswith('%'): - scale = int(args.res[0][:-1]) - else: - scale = int(args.res[0]) + scale = int(args.res[0].rstrip("%")) if scale <= 0: print('scale should be > 0%') @@ -572,8 +649,8 @@ if scale > 100 and not args.enlarge: print('use --enlarge to scale > 100%') return - except Exception: - print('invalid value for --res') + except ValueError: + print(f'invalid value for --res: "{args.res[0]}"') return elif args.rotate is not None: rotate = args.rotate % 360 @@ -591,8 +668,8 @@ if 'x' in args.minres[0]: minH, minV = getres(args.minres[0]) if minH == 0 or minV == 0: - print('-ve values not allowed in --minres, \ - H or V cannot be 0') + print('-ve values not allowed in --minres, ' + 'H or V cannot be 0') return else: if scale is not None: @@ -600,10 +677,7 @@ return try: - if args.minres[0].endswith('%'): - minscale = int(args.minres[0][:-1]) - else: - minscale = int(args.minres[0]) + minscale = int(args.minres[0].rstrip('%')) if minscale <= 0: print('--minres should be > 0%') @@ -662,7 +736,7 @@ if not mute: # Show number of images converted if count: - print('%s processed in %.4f seconds.' % (count, time.time() - init_time)) + print('%s images processed in %.4f seconds.' % (count, time.time() - init_time)) # Show number of images not converted due to low resolution if count_lowres: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/imgp.1 new/imgp-3.0/imgp.1 --- old/imgp-2.9/imgp.1 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/imgp.1 2026-08-16 08:52:22.000000000 +0200 @@ -1,4 +1,4 @@ -.TH "IMGP" "1" "10 Sep 2023" "Version 2.9" "User Commands" +.TH "IMGP" "1" "16 Aug 2026" "Version 3.0" "User Commands" .SH NAME imgp \- Resize, rotate JPEG and PNG images. .SH SYNOPSIS @@ -53,7 +53,7 @@ Show help text and exit. .TP .BI "-x, --res=" res -Output resolution in HRESxVRES or percentage. +Output resolution in HRESxVRES or percentage ("%" suffix) or megapixel ("MP" suffix). .TP .BI "-o, --rotate=" deg Rotate clockwise by a specified angle (in degrees). Negative inputs rotate anti-clockwise. Rotation by 0 degree is not allowed. @@ -114,6 +114,9 @@ .TP .BI "-d, --debug" Enable debugging. +.TP +.BI "-v, --version" +Show the program version and exit. .SH EXAMPLES .PP .IP 1. 4 @@ -151,6 +154,17 @@ .EE .PP .IP 3. 4 +Ensure all images in folder `testphotos/` (contains 4 images) are 12MP at maximum: +.PP +.EX +.IP +.B $ imgp --res 12MP -w testphotos/ +testphotos/20250111_193949_ILCE-7M3.jpg +6000x4000 -> 4243x2828 +8355840 bytes -> 3545149 bytes +.EE +.PP +.IP 4. 4 Rotate an image clockwise by 90 degrees: .PP .EX @@ -159,7 +173,7 @@ 120968 bytes -> 72038 bytes .EE .PP -.IP 4. 4 +.IP 5. 4 Adapt the images in the current directory to 1366x1000 resolution. .br Visit all directories recursively, overwrite source images, ignore images with matching hres or vres but convert PNG images to JPEG. @@ -169,7 +183,7 @@ .B $ imgp -x 1366x1000 -wrack .EE .PP -.IP 5. 4 +.IP 6. 4 Set hres=800 and adapt vres maintaining the ratio. .PP .EX @@ -186,7 +200,7 @@ 458092 bytes -> 78089 bytes .EE .PP -.IP 6. 4 +.IP 7. 4 Process images greater than 50KiB only: .PP .EX @@ -194,7 +208,7 @@ .B $ imgp -wrackx 1366x1000 -s 51200 .EE .PP -.IP 7. 4 +.IP 8. 4 Generate a 64px adaptive thumbnail of the last modified file in the current dir: .PP .EX @@ -214,8 +228,8 @@ .SH REPORTING BUGS .I https://github.com/jarun/imgp/issues .SH LICENSE -Copyright \(co 2016-2023 Arun Prakash Jana <[email protected]> +Copyright \(co 2016-2026 Arun Prakash Jana <[email protected]> .PP -License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. +License GPL-3.0-or-later+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. .br This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/packagecore.yaml new/imgp-3.0/packagecore.yaml --- old/imgp-2.9/packagecore.yaml 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/packagecore.yaml 1970-01-01 01:00:00.000000000 +0100 @@ -1,105 +0,0 @@ -name: imgp -maintainer: Arun Prakash Jana <[email protected]> -license: GPLv3 -summary: High-performance cli batch image resizer and rotator. -homepage: https://github.com/jarun/imgp -commands: - install: - - make PREFIX="/usr" install DESTDIR="${BP_DESTDIR}" -packages: - centos7.5: - builddeps: - - make - deps: - - python-pillow - - python - commands: - pre: - - yum install epel-release - centos7.6: - builddeps: - - make - deps: - - python-pillow - - python - commands: - pre: - - yum install epel-release - centos7.7: - builddeps: - - make - deps: - - python-pillow - - python - commands: - pre: - - yum install epel-release - centos8.0: - builddeps: - - make - deps: - - python3-pillow - - python3 - commands: - precompile: - - dnf install python3 - debian9: - builddeps: - - make - deps: - - python3-pillow - - python3 - debian10: - builddeps: - - make - deps: - - python3-pillow - - python3 - fedora31: - builddeps: - - make - deps: - - python3-pillow - - python3 - fedora32: - builddeps: - - make - deps: - - python3-pillow - - python3 - opensuse15.1: - builddeps: - - make - deps: - - python3-Pillow - - python3 - opensuse15.2: - builddeps: - - make - deps: - - python3-Pillow - - python3 - opensuse.tumbleweed: - builddeps: - - make - deps: - - python3-Pillow - - python3 - ubuntu16.04: - builddeps: - - make - deps: - - python3-pillow - - python3 - ubuntu18.04: - builddeps: - - make - deps: - - python3-pillow - - python3 - ubuntu20.04: - builddeps: - - make - deps: - - python3-pillow - - python3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/imgp-2.9/setup.py new/imgp-3.0/setup.py --- old/imgp-2.9/setup.py 2023-09-10 05:42:25.000000000 +0200 +++ new/imgp-3.0/setup.py 2026-08-16 08:52:22.000000000 +0200 @@ -4,14 +4,14 @@ import os.path import shutil import sys +from importlib.metadata import PackageNotFoundError, distribution from setuptools import setup, find_packages -from pkg_resources import get_distribution, DistributionNotFound def get_dist(pkgname): try: - return get_distribution(pkgname) - except DistributionNotFound: + return distribution(pkgname) + except PackageNotFoundError: return None if os.path.isfile('imgp'): @@ -34,9 +34,9 @@ author='Arun Prakash Jana', author_email='[email protected]', url='https://github.com/jarun/imgp', - license='GPLv3', + license='GPL-3.0-or-later', license_file='LICENSE', - python_requires='>=3.8', # requires pip>=9.0.0 + python_requires='>=3.10', # requires pip>=9.0.0 platforms=['any'], py_modules=['imgp'], install_requires=requirements, @@ -53,15 +53,16 @@ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Topic :: Utilities' ] )
