diff options
Diffstat (limited to 'cogs')
| -rw-r--r-- | cogs/compression.py | 145 |
1 files changed, 88 insertions, 57 deletions
diff --git a/cogs/compression.py b/cogs/compression.py index f39b56a..b684f9f 100644 --- a/cogs/compression.py +++ b/cogs/compression.py | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | import mimetypes | 1 | import mimetypes |
| 2 | import os | 2 | import os |
| 3 | import subprocess | 3 | import subprocess |
| 4 | from typing import List, Optional | ||
| 4 | 5 | ||
| 5 | import discord | 6 | import discord |
| 6 | from discord.ext import commands | 7 | from discord.ext import commands |
| @@ -19,21 +20,95 @@ def filetype(filename): | |||
| 19 | return "Image" | 20 | return "Image" |
| 20 | return "Unknown" | 21 | return "Unknown" |
| 21 | 22 | ||
| 22 | |||
| 23 | class Compression(commands.Cog): | 23 | class Compression(commands.Cog): |
| 24 | def __init__(self, bot): | 24 | def __init__(self, bot): |
| 25 | self.bot = bot | 25 | self.bot = bot |
| 26 | self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp") | ||
| 27 | if not os.path.exists(self.temp_dir): | ||
| 28 | os.makedirs(self.temp_dir) | ||
| 29 | |||
| 30 | async def _compress_attachment(self, attachment: discord.Attachment) -> Optional[discord.File]: | ||
| 31 | """Compress a single attachment and return the compressed file""" | ||
| 32 | try: | ||
| 33 | file_path = os.path.join(self.temp_dir, attachment.filename) | ||
| 34 | output_path = os.path.join(self.temp_dir, f"better_{attachment.filename}") | ||
| 35 | |||
| 36 | # Save the original file | ||
| 37 | await attachment.save(file_path) | ||
| 38 | |||
| 39 | # Process the file | ||
| 40 | file_type = filetype(file_path) | ||
| 41 | logger.info(f"Compression started for: {file_type}") | ||
| 42 | |||
| 43 | if file_type == "Video": | ||
| 44 | command = f"ffmpeg -i {file_path} -c:v libx264 -crf 51 -b:v 100 -vf 'fps=10,scale=trunc(iw*0.5/2)*2:trunc(ih*0.5/2)*2' -preset ultrafast -c:a libopus -b:a 500 {output_path}" | ||
| 45 | elif file_type == "Audio": | ||
| 46 | command = f"ffmpeg -i {file_path} -ac 1 -b:a 500 -acodec libopus {output_path}" | ||
| 47 | elif file_type == "Image": | ||
| 48 | ext = os.path.splitext(file_path)[1].lower() | ||
| 49 | if ext in [".jpg", ".jpeg"]: | ||
| 50 | command = f"magick {file_path} -strip -resize 30% -quality 5 {output_path}" | ||
| 51 | elif ext == ".gif": | ||
| 52 | command = f"magick {file_path} -coalesce -resize 30% -colors 32 -strip -layers Optimize {output_path}" | ||
| 53 | else: | ||
| 54 | output_path = os.path.splitext(output_path)[0] + ".jpg" | ||
| 55 | command = f"magick {file_path} -strip -resize 30% -quality 5 {output_path}" | ||
| 56 | else: | ||
| 57 | logger.info(f"Unsupported file type: {file_type}") | ||
| 58 | os.remove(file_path) | ||
| 59 | return None | ||
| 60 | |||
| 61 | # Run the compression | ||
| 62 | subprocess.run(command, check=True, shell=True) | ||
| 63 | |||
| 64 | if os.path.exists(output_path): | ||
| 65 | logger.info(f"Successfully compressed: {attachment.filename}") | ||
| 66 | return discord.File(output_path, filename=f"compressed_{attachment.filename}") | ||
| 67 | return None | ||
| 68 | |||
| 69 | except Exception as e: | ||
| 70 | logger.error(f"Error processing {attachment.filename}: {str(e)}") | ||
| 71 | return None | ||
| 72 | |||
| 73 | finally: | ||
| 74 | # Clean up temporary files | ||
| 75 | if 'file_path' in locals() and os.path.exists(file_path): | ||
| 76 | os.remove(file_path) | ||
| 77 | |||
| 78 | async def _compress_attachments(self, attachments: List[discord.Attachment]) -> List[discord.File]: | ||
| 79 | """Compress multiple attachments and return list of compressed files""" | ||
| 80 | compressed_files = [] | ||
| 81 | for attachment in attachments: | ||
| 82 | compressed_file = await self._compress_attachment(attachment) | ||
| 83 | if compressed_file: | ||
| 84 | compressed_files.append(compressed_file) | ||
| 85 | return compressed_files | ||
| 26 | 86 | ||
| 27 | @commands.message_command( | 87 | @commands.message_command( |
| 28 | name="kompresja", | 88 | name="kompresja", |
| 29 | description="skompresuj wideło albo dźwienk :)", | 89 | description="skompresuj wideło albo dźwienk :)", |
| 30 | ) | 90 | ) |
| 31 | async def message_compression(self, ctx, message: discord.Message): | 91 | async def message_compression(self, ctx, message: discord.Message): |
| 32 | if message.attachments: | 92 | if not message.attachments: |
| 33 | for attachment in message.attachments: | ||
| 34 | await self.compression(ctx, attachment) | ||
| 35 | else: | ||
| 36 | await ctx.respond("Brak załączników w wiadomości", ephemeral=True) | 93 | await ctx.respond("Brak załączników w wiadomości", ephemeral=True) |
| 94 | return | ||
| 95 | |||
| 96 | await ctx.defer() | ||
| 97 | compressed_files = await self._compress_attachments(message.attachments) | ||
| 98 | |||
| 99 | # Send all compressed files in one message | ||
| 100 | if compressed_files: | ||
| 101 | await ctx.respond( | ||
| 102 | f"Skompresowano {len(compressed_files)} z {len(message.attachments)} plików:", | ||
| 103 | files=compressed_files | ||
| 104 | ) | ||
| 105 | |||
| 106 | # Clean up compressed files | ||
| 107 | for file in compressed_files: | ||
| 108 | if os.path.exists(file.fp.name): | ||
| 109 | os.remove(file.fp.name) | ||
| 110 | else: | ||
| 111 | await ctx.respond("Nie udało się skompresować żadnych plików.", ephemeral=True) | ||
| 37 | 112 | ||
| 38 | @commands.slash_command( | 113 | @commands.slash_command( |
| 39 | name="kompresja", | 114 | name="kompresja", |
| @@ -45,60 +120,16 @@ class Compression(commands.Cog): | |||
| 45 | required=True, | 120 | required=True, |
| 46 | type=discord.Attachment | 121 | type=discord.Attachment |
| 47 | ) | 122 | ) |
| 48 | async def compression(self, ctx, plik): | 123 | async def compression(self, ctx, plik: discord.Attachment): |
| 49 | await ctx.defer() | 124 | await ctx.defer() |
| 50 | # Save files to the temp directory inside the bot's root directory | 125 | compressed_file = await self._compress_attachment(plik) |
| 51 | bot_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 126 | |
| 52 | temp_dir = os.path.join(bot_dir, "temp") | 127 | if compressed_file: |
| 53 | if not os.path.exists(temp_dir): | 128 | await ctx.respond(file=compressed_file) |
| 54 | os.makedirs(temp_dir) | 129 | if os.path.exists(compressed_file.fp.name): |
| 55 | file_path = os.path.join(temp_dir, plik.filename) | 130 | os.remove(compressed_file.fp.name) |
| 56 | await plik.save(file_path) | ||
| 57 | |||
| 58 | input_path = file_path | ||
| 59 | output_path = os.path.join(temp_dir, f"better_{plik.filename}") | ||
| 60 | file_type = filetype(file_path) | ||
| 61 | logger.info(f"Compression started for: {file_type}") | ||
| 62 | if file_type == "Video": | ||
| 63 | # Execute ffmpeg command to compress video | ||
| 64 | ffmpeg_command = f"ffmpeg -i {input_path} -c:v libx264 -crf 51 -b:v 100 -vf 'fps=10,scale=trunc(iw*0.5/2)*2:trunc(ih*0.5/2)*2' -preset ultrafast -c:a libopus -b:a 500 {output_path}" | ||
| 65 | elif file_type == "Audio": | ||
| 66 | ffmpeg_command = f"ffmpeg -i {input_path} -ac 1 -b:a 500 -acodec libopus {output_path}" | ||
| 67 | elif file_type == "Image": | ||
| 68 | # Use ImageMagick's magick to heavily compress image (JPEG/PNG/GIF) | ||
| 69 | # For JPEG: quality 5, strip metadata, resize to 30% of the original size | ||
| 70 | # For PNG: magick to JPEG, then compress | ||
| 71 | # For GIF: optimize and reduce colors | ||
| 72 | ext = os.path.splitext(input_path)[1].lower() | ||
| 73 | if ext in [".jpg", ".jpeg"]: | ||
| 74 | img_command = f"magick {input_path} -strip -resize 30% -quality 5 {output_path}" | ||
| 75 | elif ext == ".gif": | ||
| 76 | # Optimize GIF: reduce colors, optimize layers, and resize | ||
| 77 | img_command = f"magick {input_path} -coalesce -resize 30% -colors 32 -strip -layers Optimize {output_path}" | ||
| 78 | else: | ||
| 79 | # magick non-JPEG images to JPEG for maximum compression | ||
| 80 | output_path = os.path.splitext(output_path)[0] + ".jpg" | ||
| 81 | img_command = f"magick {input_path} -strip -resize 30% -quality 5 {output_path}" | ||
| 82 | ffmpeg_command = img_command | ||
| 83 | else: | 131 | else: |
| 84 | await ctx.respond("Plik jest nieobsługiwany", ephemeral=True) | 132 | await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True) |
| 85 | logger.info("Unsupported file type") | ||
| 86 | return | ||
| 87 | |||
| 88 | try: | ||
| 89 | subprocess.run(ffmpeg_command, check=True, shell=True) | ||
| 90 | # Upload the compressed file | ||
| 91 | await ctx.respond(file=discord.File(output_path)) | ||
| 92 | logger.info(f"Compression completed for: {file_type}") | ||
| 93 | # Delete compressed file | ||
| 94 | os.remove(input_path) | ||
| 95 | os.remove(output_path) | ||
| 96 | except subprocess.CalledProcessError as e: | ||
| 97 | await ctx.respond(f"Compression failed: {str(e)}", ephemeral=True) | ||
| 98 | if os.path.exists(input_path): | ||
| 99 | os.remove(input_path) | ||
| 100 | if os.path.exists(output_path): | ||
| 101 | os.remove(output_path) | ||
| 102 | 133 | ||
| 103 | 134 | ||
| 104 | def setup(bot): | 135 | def setup(bot): |
