Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3b40fdd

Browse files
committed
feat: advanced image compressor (batch, URL, metadata, lossless)
1 parentdb9e815 commit3b40fdd

File tree

2 files changed

+146
-78
lines changed

2 files changed

+146
-78
lines changed
Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,56 @@
1-
#[How to Compress Images in Python](https://www.thepythoncode.com/article/compress-images-in-python)
2-
To run this:
3-
-`pip3 install -r requirements.txt`
4-
-`python compress_image.py --help`
1+
#Compress Image
2+
3+
Advanced Image Compressor with Batch Processing
4+
5+
This script provides advanced image compression and resizing features using Python and Pillow.
6+
7+
##Features
8+
9+
- Batch processing of multiple images or directories
10+
- Lossy and lossless compression (PNG/WebP)
11+
- Optional JPEG conversion
12+
- Resize by ratio or explicit dimensions
13+
- Preserve or strip metadata (EXIF)
14+
- Custom output directory
15+
- Progress bar using`tqdm`
16+
- Detailed logging
17+
18+
##Requirements
19+
20+
- Python 3.6+
21+
-[Pillow](https://pypi.org/project/Pillow/)
22+
-[tqdm](https://pypi.org/project/tqdm/)
23+
24+
Install dependencies:
25+
26+
```bash
27+
pip install pillow tqdm
28+
```
29+
30+
##Usage
31+
32+
```bash
33+
python compress_image.py [options]<input> [<input> ...]
34+
```
35+
36+
##Options
37+
-`-o`,`--output-dir`: Output directory (default: same as input)
38+
-`-q`,`--quality`: Compression quality (0-100, default: 85)
39+
-`-r`,`--resize-ratio`: Resize ratio (0-1, default: 1.0)
40+
-`-w`,`--width`: Output width (requires`--height`)
41+
-`-hh`,`--height`: Output height (requires`--width`)
42+
-`-j`,`--to-jpg`: Convert output to JPEG
43+
-`-m`,`--no-metadata`: Strip metadata (default: preserve)
44+
-`-l`,`--lossless`: Use lossless compression (PNG/WEBP)
45+
46+
##Examples
47+
48+
```bash
49+
python compress_image.py image.jpg -r 0.5 -q 80 -j
50+
python compress_image.py images/ -o output/ -m
51+
python compress_image.py image.png -l
52+
```
53+
54+
##License
55+
56+
MIT License.
Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,104 @@
11
importos
22
fromPILimportImage
3+
importargparse
4+
importlogging
5+
fromtqdmimporttqdm
36

7+
# Configure logging
8+
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s - %(message)s')
9+
logger=logging.getLogger(__name__)
410

511
defget_size_format(b,factor=1024,suffix="B"):
6-
"""
7-
Scale bytes to its proper byte format
8-
e.g:
9-
1253656 => '1.20MB'
10-
1253656678 => '1.17GB'
11-
"""
12+
"""Scale bytes to its proper byte format."""
1213
forunitin ["","K","M","G","T","P","E","Z"]:
1314
ifb<factor:
1415
returnf"{b:.2f}{unit}{suffix}"
1516
b/=factor
1617
returnf"{b:.2f}Y{suffix}"
17-
1818

19-
20-
defcompress_img(image_name,new_size_ratio=0.9,quality=90,width=None,height=None,to_jpg=True):
21-
# load the image to memory
22-
img=Image.open(image_name)
23-
# print the original image shape
24-
print("[*] Image shape:",img.size)
25-
# get the original image size in bytes
26-
image_size=os.path.getsize(image_name)
27-
# print the size before compression/resizing
28-
print("[*] Size before compression:",get_size_format(image_size))
29-
ifnew_size_ratio<1.0:
30-
# if resizing ratio is below 1.0, then multiply width & height with this ratio to reduce image size
31-
img=img.resize((int(img.size[0]*new_size_ratio),int(img.size[1]*new_size_ratio)),Image.LANCZOS)
32-
# print new image shape
33-
print("[+] New Image shape:",img.size)
34-
elifwidthandheight:
35-
# if width and height are set, resize with them instead
36-
img=img.resize((width,height),Image.LANCZOS)
37-
# print new image shape
38-
print("[+] New Image shape:",img.size)
39-
# split the filename and extension
40-
filename,ext=os.path.splitext(image_name)
41-
# make new filename appending _compressed to the original file name
42-
ifto_jpg:
43-
# change the extension to JPEG
44-
new_filename=f"{filename}_compressed.jpg"
45-
else:
46-
# retain the same extension of the original image
47-
new_filename=f"{filename}_compressed{ext}"
19+
defcompress_image(
20+
input_path,
21+
output_dir=None,
22+
quality=85,
23+
resize_ratio=1.0,
24+
width=None,
25+
height=None,
26+
to_jpg=False,
27+
preserve_metadata=True,
28+
lossless=False,
29+
):
30+
"""Compress an image with advanced options."""
4831
try:
49-
# save the image with the corresponding quality and optimize set to True
50-
img.save(new_filename,quality=quality,optimize=True)
51-
exceptOSError:
52-
# convert the image to RGB mode first
53-
img=img.convert("RGB")
54-
# save the image with the corresponding quality and optimize set to True
55-
img.save(new_filename,quality=quality,optimize=True)
56-
print("[+] New file saved:",new_filename)
57-
# get the new image size in bytes
58-
new_image_size=os.path.getsize(new_filename)
59-
# print the new size in a good format
60-
print("[+] Size after compression:",get_size_format(new_image_size))
61-
# calculate the saving bytes
62-
saving_diff=new_image_size-image_size
63-
# print the saving percentage
64-
print(f"[+] Image size change:{saving_diff/image_size*100:.2f}% of the original image size.")
65-
66-
32+
img=Image.open(input_path)
33+
logger.info(f"[*] Processing:{os.path.basename(input_path)}")
34+
logger.info(f"[*] Original size:{get_size_format(os.path.getsize(input_path))}")
35+
36+
# Resize if needed
37+
ifresize_ratio<1.0:
38+
new_size= (int(img.size[0]*resize_ratio),int(img.size[1]*resize_ratio))
39+
img=img.resize(new_size,Image.LANCZOS)
40+
logger.info(f"[+] Resized to:{new_size}")
41+
elifwidthandheight:
42+
img=img.resize((width,height),Image.LANCZOS)
43+
logger.info(f"[+] Resized to:{width}x{height}")
44+
45+
# Prepare output path
46+
filename,ext=os.path.splitext(os.path.basename(input_path))
47+
output_ext=".jpg"ifto_jpgelseext
48+
output_filename=f"{filename}_compressed{output_ext}"
49+
output_path=os.path.join(output_diroros.path.dirname(input_path),output_filename)
50+
51+
# Save with options
52+
save_kwargs= {"quality":quality,"optimize":True}
53+
ifnotpreserve_metadata:
54+
save_kwargs["exif"]=b""# Strip metadata
55+
iflosslessandext.lower()in (".png",".webp"):
56+
save_kwargs["lossless"]=True
57+
58+
try:
59+
img.save(output_path,**save_kwargs)
60+
exceptOSError:
61+
img=img.convert("RGB")
62+
img.save(output_path,**save_kwargs)
63+
64+
logger.info(f"[+] Saved to:{output_path}")
65+
logger.info(f"[+] New size:{get_size_format(os.path.getsize(output_path))}")
66+
exceptExceptionase:
67+
logger.error(f"[!] Error processing{input_path}:{e}")
68+
69+
defbatch_compress(
70+
input_paths,
71+
output_dir=None,
72+
quality=85,
73+
resize_ratio=1.0,
74+
width=None,
75+
height=None,
76+
to_jpg=False,
77+
preserve_metadata=True,
78+
lossless=False,
79+
):
80+
"""Compress multiple images."""
81+
ifoutput_dirandnotos.path.exists(output_dir):
82+
os.makedirs(output_dir,exist_ok=True)
83+
forpathintqdm(input_paths,desc="Compressing images"):
84+
compress_image(path,output_dir,quality,resize_ratio,width,height,to_jpg,preserve_metadata,lossless)
85+
6786
if__name__=="__main__":
68-
importargparse
69-
parser=argparse.ArgumentParser(description="Simple Python script for compressing and resizing images")
70-
parser.add_argument("image",help="Target image to compress and/or resize")
71-
parser.add_argument("-j","--to-jpg",action="store_true",help="Whether to convert the image to the JPEG format")
72-
parser.add_argument("-q","--quality",type=int,help="Quality ranging from a minimum of 0 (worst) to a maximum of 95 (best). Default is 90",default=90)
73-
parser.add_argument("-r","--resize-ratio",type=float,help="Resizing ratio from 0 to 1, setting to 0.5 will multiply width & height of the image by 0.5. Default is 1.0",default=1.0)
74-
parser.add_argument("-w","--width",type=int,help="The new width image, make sure to set it with the `height` parameter")
75-
parser.add_argument("-hh","--height",type=int,help="The new height for the image, make sure to set it with the `width` parameter")
87+
parser=argparse.ArgumentParser(description="Advanced Image Compressor with Batch Processing")
88+
parser.add_argument("input",nargs='+',help="Input image(s) or directory")
89+
parser.add_argument("-o","--output-dir",help="Output directory (default: same as input)")
90+
parser.add_argument("-q","--quality",type=int,default=85,help="Compression quality (0-100)")
91+
parser.add_argument("-r","--resize-ratio",type=float,default=1.0,help="Resize ratio (0-1)")
92+
parser.add_argument("-w","--width",type=int,help="Output width (requires --height)")
93+
parser.add_argument("-hh","--height",type=int,help="Output height (requires --width)")
94+
parser.add_argument("-j","--to-jpg",action="store_true",help="Convert output to JPEG")
95+
parser.add_argument("-m","--no-metadata",action="store_false",help="Strip metadata")
96+
parser.add_argument("-l","--lossless",action="store_true",help="Use lossless compression (PNG/WEBP)")
97+
7698
args=parser.parse_args()
77-
# print the passed arguments
78-
print("="*50)
79-
print("[*] Image:",args.image)
80-
print("[*] To JPEG:",args.to_jpg)
81-
print("[*] Quality:",args.quality)
82-
print("[*] Resizing ratio:",args.resize_ratio)
83-
ifargs.widthandargs.height:
84-
print("[*] Width:",args.width)
85-
print("[*] Height:",args.height)
86-
print("="*50)
87-
# compress the image
88-
compress_img(args.image,args.resize_ratio,args.quality,args.width,args.height,args.to_jpg)
99+
input_paths= []
100+
forpathinargs.input:
101+
ifos.path.isdir(path):input_paths.extend(os.path.join(path,f)forfinos.listdir(path)iff.lower().endswith((".jpg",".jpeg",".png",".webp")))
102+
else:input_paths.append(path)
103+
ifnotinput_paths:logger.error("No valid images found!");exit(1)
104+
batch_compress(input_paths,args.output_dir,args.quality,args.resize_ratio,args.width,args.height,args.to_jpg,args.no_metadata,args.lossless)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp