Files
chunyu_project/tool/views/image_compress_view.py
T
2026-08-05 23:59:15 +08:00

193 lines
6.9 KiB
Python

import io
import uuid
from PIL import Image
from PIL.ExifTags import TAGS
from django.http import JsonResponse, FileResponse
from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import status
from rest_framework.decorators import permission_classes
from rest_framework.permissions import AllowAny
from ..serializers import ImageCompressRequestSerializer
@permission_classes([AllowAny])
class ImageCompressView(APIView):
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
serializer = ImageCompressRequestSerializer(data=request.data)
if not serializer.is_valid():
return JsonResponse(
{'error': '参数错误', 'details': serializer.errors},
status=status.HTTP_400_BAD_REQUEST
)
file = serializer.validated_data['file']
mode = serializer.validated_data['mode']
quality = serializer.validated_data['quality']
output_format = serializer.validated_data['format']
keep_exif = serializer.validated_data['keep_exif']
width = serializer.validated_data.get('width')
height = serializer.validated_data.get('height')
maintain_aspect_ratio = serializer.validated_data.get('maintain_aspect_ratio', True)
try:
img = Image.open(file)
img.verify()
file.seek(0)
img = Image.open(file)
except Exception:
return JsonResponse(
{'error': '无效的图片文件'},
status=status.HTTP_400_BAD_REQUEST
)
try:
original_size = file.size
original_format = img.format.lower() if img.format else 'unknown'
if output_format == 'original':
output_format = original_format
if width or height:
img = self._resize_image(img, width, height, maintain_aspect_ratio)
if mode == 'lossless':
compressed_data = self._compress_lossless(img, output_format, keep_exif)
else:
compressed_data = self._compress_lossy(img, output_format, quality, keep_exif)
compressed_size = len(compressed_data)
compression_ratio = (1 - compressed_size / original_size) * 100
response = FileResponse(
io.BytesIO(compressed_data),
content_type=f'image/{output_format}'
)
response['Content-Disposition'] = f'attachment; filename="compressed_{file.name}"'
response['X-Original-Size'] = str(original_size)
response['X-Compressed-Size'] = str(compressed_size)
response['X-Compression-Ratio'] = f'{compression_ratio:.1f}'
response['X-Original-Format'] = original_format
response['X-Output-Format'] = output_format
response['X-Compression-Mode'] = mode
response['X-Quality'] = str(quality)
return response
except Exception as e:
return JsonResponse(
{'error': f'图片处理失败: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@staticmethod
def _resize_image(img, width, height, maintain_aspect_ratio):
if maintain_aspect_ratio:
if width and height:
ratio = min(width / img.width, height / img.height)
new_size = (int(img.width * ratio), int(img.height * ratio))
elif width:
ratio = width / img.width
new_size = (width, int(img.height * ratio))
else:
ratio = height / img.height
new_size = (int(img.width * ratio), height)
else:
new_size = (width or img.width, height or img.height)
return img.resize(new_size, Image.Resampling.LANCZOS)
@staticmethod
def _compress_lossless(img, output_format, keep_exif):
output = io.BytesIO()
exif_data = img.info.get('exif') if keep_exif else None
if output_format == 'jpeg':
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
kwargs = {'quality': 100, 'optimize': True, 'subsampling': 0}
if exif_data:
kwargs['exif'] = exif_data
img.save(output, 'JPEG', **kwargs)
elif output_format == 'png':
img.save(output, 'PNG', optimize=True)
elif output_format == 'webp':
kwargs = {'lossless': True}
if exif_data:
kwargs['exif'] = exif_data
img.save(output, 'WebP', **kwargs)
elif output_format == 'avif':
img.save(output, 'AVIF', quality=100, speed=6)
else:
img.save(output, output_format.upper(), optimize=True)
return output.getvalue()
@staticmethod
def _compress_lossy(img, output_format, quality, keep_exif):
output = io.BytesIO()
exif_data = img.info.get('exif') if keep_exif else None
if output_format == 'jpeg':
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
kwargs = {'quality': quality, 'optimize': True}
if exif_data:
kwargs['exif'] = exif_data
img.save(output, 'JPEG', **kwargs)
elif output_format == 'png':
img.save(output, 'PNG', optimize=True, compress_level=9)
elif output_format == 'webp':
img.save(output, 'WebP', quality=quality, method=6)
elif output_format == 'avif':
img.save(output, 'AVIF', quality=quality, speed=6)
else:
img.save(output, output_format.upper(), quality=quality, optimize=True)
return output.getvalue()
@permission_classes([AllowAny])
class EXIFExtractView(APIView):
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
file = request.FILES.get('file')
if not file:
return JsonResponse(
{'error': '请上传图片文件'},
status=status.HTTP_400_BAD_REQUEST
)
try:
img = Image.open(file)
exif_data = self._extract_exif(img)
return JsonResponse({
'success': True,
'exif': exif_data
})
except Exception as e:
return JsonResponse(
{'error': f'EXIF 提取失败: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@staticmethod
def _extract_exif(img):
exif_data = {}
if hasattr(img, '_getexif'):
exif = img._getexif()
if exif:
for tag_id, value in exif.items():
tag = TAGS.get(tag_id, tag_id)
try:
exif_data[tag] = str(value)
except (TypeError, ValueError):
exif_data[tag] = repr(value)
return exif_data