Compare commits

...

5 Commits

Author SHA1 Message Date
Hugo Posnic
5957b2f95f Update screenshots 2023-04-04 23:40:28 +02:00
Hugo Posnic
ee1219f70e Update CHANGELOG.md 2023-04-04 23:29:52 +02:00
Hugo Posnic
c66c63b7be Add a warning banner for overwrite mode 2023-04-04 23:29:37 +02:00
Hugo Posnic
208030e25c Update README.md 2023-04-04 22:53:13 +02:00
Hugo Posnic
0f7803a82c Add support for SVG images 2023-04-04 22:47:22 +02:00
16 changed files with 192 additions and 67 deletions

View File

@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.
## [1.7.0] - DEV
### Added
- SVG support.
- Add a warning banner for overwrite mode.
- New start screen with an AdwStatusPage.
- Add debug information in about window.

View File

@ -4,13 +4,13 @@
## Compress your images
Curtail (previously ImCompressor) is an useful image compressor, supporting PNG, JPEG and WebP file types.
Curtail (previously ImCompressor) is an useful image compressor, supporting PNG, JPEG, WebP and SVG file types.
It support both lossless and lossy compression modes with an option to whether keep or not metadata of images.
It is inspired by [Trimage](https://github.com/Kilian/Trimage) and [Image-Optimizer](https://github.com/GijsGoudzwaard/Image-Optimizer).
### Supported formats
PNG, JPEG, WebP
PNG, JPEG, WebP, SVG
## Screenshot
@ -71,6 +71,7 @@ Curtail uses a number of open source projects to work properly:
- [pngquant](https://pngquant.org)
- [Jpegoptim](https://github.com/tjko/jpegoptim)
- [libwebp](https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html)
- [Scour](https://github.com/scour-project/scour)
## Donations

View File

@ -69,6 +69,7 @@
}
]
},
"python3-scour.json",
{
"name": "curtail",
"builddir": true,

View File

@ -8,7 +8,7 @@
<name translatable="no">Curtail</name>
<summary>Compress your images</summary>
<description>
<p>Optimize your images with Curtail, a useful image compressor that supports PNG, JPEG and WebP file types.</p>
<p>Optimize your images with Curtail, a useful image compressor that supports PNG, JPEG, WebP and SVG file types.</p>
<p>It supports both lossless and lossy compression modes with an option to whether keep or not metadata of images.</p>
</description>
<developer_name translatable="no">Hugo Posnic</developer_name>

View File

@ -53,8 +53,13 @@
</key>
<key type="b" name="jpg-progressive">
<default>false</default>
<summary>Enable progressive encoding for jpegs</summary>
<summary>Enable progressive encoding for JPEG images.</summary>
<description>Optionally encode jpeg images progressively.</description>
</key>
<key type="b" name="svg-maximum-level">
<default>false</default>
<summary>Enable maximum compression for SVG images.</summary>
<description>Optionally enable maximum cleaning of SVG images.</description>
</key>
<key type="i" name="compression-timeout">
<default>30</default>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -11,7 +11,8 @@
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Save the compressed image into a new file</property>
<property name="title" translatable="yes">Safe Mode</property>
<property name="subtitle" translatable="yes">Save the compressed image into a new file</property>
<property name="activatable-widget">toggle_new_file</property>
<child>
<object class="GtkSwitch" id="toggle_new_file">
@ -64,7 +65,7 @@
</property>
<property name="valign">center</property>
</object>
</child>"
</child>
</object>
</child>
</object>
@ -199,6 +200,22 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="no">SVG</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">SVG Maximum Compression Level</property>
<property name="subtitle" translatable="yes">Can be more destructive for the image</property>
<child>
<object class="GtkSwitch" id="toggle_svg_maximum_level">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>

View File

@ -6,6 +6,13 @@
<child>
<object class="GtkBox" id="mainbox">
<property name="orientation">vertical</property>
<child>
<object class="AdwBanner" id="warning_banner">
<property name="action-name">win.preferences</property>
<property name="button-label" translatable="yes">_Change Mode</property>
<property name="title" translatable="yes">Images will be overwritten, proceed carefully.</property>
</object>
</child>
<child>
<object class="AdwStatusPage" id="homebox">
<property name="vexpand">true</property>

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: curtail\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-03 17:17+0200\n"
"POT-Creation-Date: 2023-04-04 23:29+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -25,7 +25,7 @@ msgstr ""
#: data/com.github.huluti.Curtail.appdata.xml.in:11
msgid ""
"Optimize your images with Curtail, a useful image compressor that supports "
"PNG, JPEG and WebP file types."
"PNG, JPEG, WebP and SVG file types."
msgstr ""
#: data/com.github.huluti.Curtail.appdata.xml.in:12
@ -472,7 +472,7 @@ msgid "Initial version"
msgstr ""
#: data/com.github.huluti.Curtail.desktop.in:3 data/ui/window.ui:4
#: data/ui/window.ui:115
#: data/ui/window.ui:122
msgid "Curtail"
msgstr ""
@ -489,7 +489,7 @@ msgstr ""
msgid "Save the compressed image into a new file."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:11 data/ui/preferences.ui:30
#: data/com.github.huluti.Curtail.gschema.xml:11 data/ui/preferences.ui:31
msgid "Keep metadata"
msgstr ""
@ -498,7 +498,7 @@ msgstr ""
msgid "This setting preserves metadata of images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:16 data/ui/preferences.ui:41
#: data/com.github.huluti.Curtail.gschema.xml:16 data/ui/preferences.ui:42
msgid "Preserve file attributes if possible"
msgstr ""
@ -510,7 +510,7 @@ msgstr ""
msgid "Use lossy mode to compress images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:26 data/ui/preferences.ui:25
#: data/com.github.huluti.Curtail.gschema.xml:26 data/ui/preferences.ui:26
msgid "Suffix to append at end of new file"
msgstr ""
@ -518,7 +518,7 @@ msgstr ""
msgid "Suffix to append at end of new file."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:31 data/ui/preferences.ui:84
#: data/com.github.huluti.Curtail.gschema.xml:31 data/ui/preferences.ui:85
msgid "PNG Lossy Compression Level"
msgstr ""
@ -526,7 +526,7 @@ msgstr ""
msgid "Lossy compression level to use for PNG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:36 data/ui/preferences.ui:103
#: data/com.github.huluti.Curtail.gschema.xml:36 data/ui/preferences.ui:104
msgid "PNG Lossless Compression Level"
msgstr ""
@ -534,7 +534,7 @@ msgstr ""
msgid "Lossless compression level to use for PNG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:41 data/ui/preferences.ui:128
#: data/com.github.huluti.Curtail.gschema.xml:41 data/ui/preferences.ui:129
msgid "JPG Lossy Compression Level"
msgstr ""
@ -542,7 +542,7 @@ msgstr ""
msgid "Lossy compression level to use for JPG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:46 data/ui/preferences.ui:163
#: data/com.github.huluti.Curtail.gschema.xml:46 data/ui/preferences.ui:164
msgid "WebP Lossy Compression Level"
msgstr ""
@ -550,7 +550,7 @@ msgstr ""
msgid "Lossy compression level to use for WebP images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:51 data/ui/preferences.ui:182
#: data/com.github.huluti.Curtail.gschema.xml:51 data/ui/preferences.ui:183
msgid "WebP Lossless Compression Level"
msgstr ""
@ -559,7 +559,7 @@ msgid "Lossless compression level to use for WebP images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:56
msgid "Enable progressive encoding for jpegs"
msgid "Enable progressive encoding for JPEG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:57
@ -567,10 +567,18 @@ msgid "Optionally encode jpeg images progressively."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:61
msgid "Compression Timeout"
msgid "Enable maximum compression for SVG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:62
msgid "Optionally enable maximum cleaning of SVG images."
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:66
msgid "Compression Timeout"
msgstr ""
#: data/com.github.huluti.Curtail.gschema.xml:67
msgid "Compression timeout for each image."
msgstr ""
@ -608,29 +616,41 @@ msgid "General"
msgstr ""
#: data/ui/preferences.ui:14
msgid "Safe Mode"
msgstr ""
#: data/ui/preferences.ui:15
msgid "Save the compressed image into a new file"
msgstr ""
#: data/ui/preferences.ui:52
#: data/ui/preferences.ui:53
msgid "Compression timeout"
msgstr ""
#: data/ui/preferences.ui:53
#: data/ui/preferences.ui:54
msgid "For each image"
msgstr ""
#: data/ui/preferences.ui:77
#: data/ui/preferences.ui:78
msgid "Compression"
msgstr ""
#: data/ui/preferences.ui:104 data/ui/preferences.ui:183
#: data/ui/preferences.ui:105 data/ui/preferences.ui:184
msgid "The higher it is, the slower it is"
msgstr ""
#: data/ui/preferences.ui:147
#: data/ui/preferences.ui:148
msgid "Progressive Encode JPG"
msgstr ""
#: data/ui/preferences.ui:208
msgid "SVG Maximum Compression Level"
msgstr ""
#: data/ui/preferences.ui:209
msgid "Can be more destructive for the image"
msgstr ""
#: data/ui/menu.ui:10
msgid "Keyboard Shortcuts"
msgstr ""
@ -639,39 +659,47 @@ msgstr ""
msgid "About Curtail"
msgstr ""
#: data/ui/window.ui:14
#: data/ui/window.ui:12
msgid "_Change Mode"
msgstr ""
#: data/ui/window.ui:13
msgid "Images will be overwritten, proceed carefully."
msgstr ""
#: data/ui/window.ui:21
msgid "Drop images here to compress them"
msgstr ""
#: data/ui/window.ui:20
#: data/ui/window.ui:27
msgid "_Browse Files"
msgstr ""
#: data/ui/window.ui:39
#: data/ui/window.ui:46
msgid "Lossless"
msgstr ""
#: data/ui/window.ui:51
#: data/ui/window.ui:58
msgid "Lossy"
msgstr ""
#: data/ui/window.ui:101 src/window.py:174
#: data/ui/window.ui:108 src/window.py:186
msgid "Browse Files"
msgstr ""
#: data/ui/window.ui:108
#: data/ui/window.ui:115
msgid "Clear Results"
msgstr ""
#: data/ui/window.ui:122
#: data/ui/window.ui:129
msgid "Main Menu"
msgstr ""
#: src/compressor.py:86
#: src/compressor.py:90
msgid "Compression has reached the configured timeout of {} seconds."
msgstr ""
#: src/compressor.py:90
#: src/compressor.py:94
msgid "An unknown error has occured"
msgstr ""
@ -679,43 +707,47 @@ msgstr ""
msgid "All images"
msgstr ""
#: src/tools.py:36
#: src/tools.py:37
msgid "PNG images"
msgstr ""
#: src/tools.py:40
#: src/tools.py:41
msgid "JPEG images"
msgstr ""
#: src/tools.py:44
#: src/tools.py:45
msgid "WebP images"
msgstr ""
#: src/tools.py:113 src/tools.py:121 src/tools.py:128 src/tools.py:135
#: src/tools.py:163
#: src/tools.py:49
msgid "SVG images"
msgstr ""
#: src/tools.py:121 src/tools.py:128 src/tools.py:135 src/tools.py:142
#: src/tools.py:149 src/tools.py:179
msgid "Version not found"
msgstr ""
#: src/window.py:167
msgid "Images are saved with '{}' suffix."
#: src/window.py:173
msgid "Safe mode with '{}' suffix"
msgstr ""
#: src/window.py:170
msgid "Images are overwritten."
#: src/window.py:176
msgid "Overwrite mode"
msgstr ""
#: src/window.py:251
#: src/window.py:263
msgid "This file doesn't exist."
msgstr ""
#: src/window.py:256
#: src/window.py:268
msgid "Format of this file is not supported."
msgstr ""
#: src/window.py:308
#: src/window.py:320
msgid "translator-credits"
msgstr ""
#: src/window.py:312
#: src/window.py:324
msgid "Contributors"
msgstr ""

14
python3-scour.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "python3-scour",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"scour\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/75/19/f519ef8aa2f379935a44212c5744e2b3a46173bf04e0110fb7f4af4028c9/scour-0.38.2.tar.gz",
"sha256": "6881ec26660c130c5ecd996ac6f6b03939dd574198f50773f2508b81a68e0daf"
}
]
}

View File

@ -49,11 +49,13 @@ class Compressor():
self.png_lossless_level = self._settings.get_int('png-lossless-level')
self.jpg_lossy_level = self._settings.get_int('jpg-lossy-level')
self.do_jpg_progressive = self._settings.get_boolean('jpg-progressive')
self.jpg_progressive = self._settings.get_boolean('jpg-progressive')
self.webp_lossless_level = self._settings.get_int('webp-lossless-level')
self.webp_lossy_level = self._settings.get_int('webp-lossy-level')
self.svg_maximum_level = self._settings.get_boolean('svg-maximum-level')
def compress_images(self):
self.thread = threading.Thread(target=self._compress_images)
self.thread.start()
@ -68,6 +70,8 @@ class Compressor():
command = self.build_jpg_command(result_item)
elif file_type == 'webp':
command = self.build_webp_command(result_item)
elif file_type == 'svg':
command = self.build_svg_command(result_item)
self.run_command(command, result_item) # compress image
GLib.idle_add(self.c_enable_compression, True)
@ -129,7 +133,7 @@ class Compressor():
jpegoptim = 'jpegoptim --max={} -o -f "{}"'
jpegoptim2 = 'jpegoptim -o -f "{}"'
if self.do_jpg_progressive:
if self.jpg_progressive:
jpegoptim += ' --all-progressive'
jpegoptim2 += ' --all-progressive'
@ -174,3 +178,13 @@ class Compressor():
return command
def build_svg_command(self, result_item):
command = 'scour -i {} -o {}'.format(result_item.filename,
result_item.new_filename)
if self.svg_maximum_level:
command += ' --enable-viewboxing --enable-id-stripping'
command += ' --enable-comment-stripping --shorten-ids --indent=none'
return command

View File

@ -37,6 +37,7 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
spin_jpg_lossy_level = Gtk.Template.Child()
spin_webp_lossy_level = Gtk.Template.Child()
toggle_jpg_progressive = Gtk.Template.Child()
toggle_svg_maximum_level = Gtk.Template.Child()
_settings = Gio.Settings.new(SETTINGS_SCHEMA)
@ -68,7 +69,7 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
'new-file')
# Suffix
self.enable_suffix_section()
self.entry_suffix.set_sensitive(self._settings.get_boolean('new-file'))
self.entry_suffix.set_text(self._settings.get_string('suffix'))
self.entry_suffix.connect('changed', self.on_string_changed, 'suffix')
@ -113,12 +114,19 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
self.toggle_jpg_progressive.connect('notify::active', self.on_bool_changed,
'jpg-progressive')
# Maxium SVG compression
self.toggle_svg_maximum_level.set_active(self._settings.get_boolean('svg-maximum-level'))
self.toggle_svg_maximum_level.connect('notify::active', self.on_bool_changed,
'svg-maximum-level')
def on_bool_changed(self, switch, state, key):
self._settings.set_boolean(key, switch.get_active())
# Additional actions
if key == 'new-file':
self.parent.set_saving_subtitle()
self.enable_suffix_section()
new_file = self._settings.get_boolean('new-file')
self.parent.set_saving_subtitle(new_file)
self.parent.show_warning_banner(not new_file)
self.entry_suffix.set_sensitive(new_file)
def on_string_changed(self, entry, key):
self._settings.set_string(key, entry.get_text())
@ -129,8 +137,3 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
def on_int_changed(self, spin, key):
self._settings.set_int(key, spin.get_value())
def enable_suffix_section(self):
boolean = self._settings.get_boolean('new-file')
self.entry_suffix.set_sensitive(boolean)

View File

@ -31,6 +31,7 @@ def add_filechooser_filters(dialog):
all_images.add_mime_type('image/jpeg')
all_images.add_mime_type('image/png')
all_images.add_mime_type('image/webp')
all_images.add_mime_type('image/svg+xml')
png_images = Gtk.FileFilter()
png_images.set_name(_("PNG images"))
@ -44,11 +45,16 @@ def add_filechooser_filters(dialog):
webp_images.set_name(_("WebP images"))
webp_images.add_mime_type('image/webp')
svg_images = Gtk.FileFilter()
svg_images.set_name(_("SVG images"))
svg_images.add_mime_type('image/svg+xml')
file_filters = Gio.ListStore.new(Gtk.FileFilter)
file_filters.append(all_images)
file_filters.append(png_images)
file_filters.append(jpeg_images)
file_filters.append(webp_images)
file_filters.append(svg_images)
dialog.set_filters(file_filters)
@ -62,6 +68,8 @@ def get_file_type(filename):
return 'png'
elif content_type == 'image/webp':
return 'webp'
elif content_type == 'image/svg+xml':
return 'svg'
else:
return None
else:
@ -109,42 +117,51 @@ def debug_infos():
try:
jpegoptim = subprocess.check_output(['jpegoptim', '--version'])
jpegoptim = extract_version(jpegoptim.decode('utf-8'))
except Exception as err:
except Exception:
jpegoptim = _('Version not found')
# Oxipng
try:
oxipng = subprocess.check_output(['oxipng', '--version'])
oxipng = extract_version(oxipng.decode('utf-8'))
except Exception as err:
except Exception:
oxipng = _('Version not found')
# pngquant
try:
pngquant = subprocess.check_output(['pngquant', '--version'])
pngquant = extract_version(pngquant.decode('utf-8'))
except Exception as err:
except Exception:
pngquant = _('Version not found')
# Libwebp
try:
libwebp = subprocess.check_output(['cwebp', '-version'])
libwebp = extract_version(libwebp.decode('utf-8'))
except Exception as err:
except Exception:
libwebp = _('Version not found')
# Scour
try:
scour = subprocess.check_output(['scour', '--version'])
scour = extract_version(scour.decode('utf-8'))
except Exception:
scour = _('Version not found')
debug = '''Python: {}\n
Gtk: {}\n
Jpegoptim: {}\n
Oxipng: {}\n
pngquant: {}\n
Libwebp: {}\n'''.format(
Libwebp: {}\n
Scour: {}\n'''.format(
python_version,
gtk_version,
jpegoptim,
oxipng,
pngquant,
libwebp
libwebp,
scour
)
return debug

View File

@ -44,6 +44,7 @@ class CurtailWindow(Gtk.ApplicationWindow):
filechooser_button_headerbar = Gtk.Template.Child()
clear_button_headerbar = Gtk.Template.Child()
menu_button = Gtk.Template.Child()
warning_banner = Gtk.Template.Child()
mainbox = Gtk.Template.Child()
homebox = Gtk.Template.Child()
resultbox = Gtk.Template.Child()
@ -75,6 +76,9 @@ class CurtailWindow(Gtk.ApplicationWindow):
# Saving subtitle
self.set_saving_subtitle()
# Warning banner
self.show_warning_banner()
# Mainbox - drag&drop
drop_target_main = Gtk.DropTarget.new(type=Gdk.FileList, actions=Gdk.DragAction.COPY)
drop_target_main.connect('drop', self.on_dnd_drop)
@ -162,14 +166,22 @@ class CurtailWindow(Gtk.ApplicationWindow):
return row
def set_saving_subtitle(self):
if self._settings.get_boolean('new-file'):
label = _("Images are saved with '{}' suffix.")\
def set_saving_subtitle(self, new_file=None):
if new_file is None:
new_file = self._settings.get_boolean('new-file')
if new_file:
label = _("Safe mode with '{}' suffix")\
.format(self._settings.get_string('suffix'))
else:
label = _("Images are overwritten.")
label = _("Overwrite mode")
self.window_title.set_subtitle(label)
def show_warning_banner(self, show=None):
if show is None:
show = not self._settings.get_boolean('new-file')
self.warning_banner.set_revealed(show)
def on_select(self, *args):
dialog = Gtk.FileDialog(title=_("Browse Files"))
add_filechooser_filters(dialog)
@ -226,7 +238,7 @@ class CurtailWindow(Gtk.ApplicationWindow):
def check_extension(self, filename):
file_type = get_file_type(filename)
if file_type:
return file_type in ('png', 'jpg', 'webp')
return file_type in ('png', 'jpg', 'webp', 'svg')
else:
return False