mirror of
https://github.com/Huluti/Curtail.git
synced 2024-08-16 01:20:23 +03:00
Add support for SVG images
This commit is contained in:
parent
5fde80b40c
commit
0f7803a82c
@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
## [1.7.0] - DEV
|
## [1.7.0] - DEV
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- 🌟 SVG support.
|
||||||
- New start screen with an AdwStatusPage.
|
- New start screen with an AdwStatusPage.
|
||||||
- Add debug information in about window.
|
- Add debug information in about window.
|
||||||
|
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
## Compress your images
|
## 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 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).
|
It is inspired by [Trimage](https://github.com/Kilian/Trimage) and [Image-Optimizer](https://github.com/GijsGoudzwaard/Image-Optimizer).
|
||||||
|
|
||||||
### Supported formats
|
### Supported formats
|
||||||
|
|
||||||
PNG, JPEG, WebP
|
PNG, JPEG, WebP, SVG
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"python3-scour.json",
|
||||||
{
|
{
|
||||||
"name": "curtail",
|
"name": "curtail",
|
||||||
"builddir": true,
|
"builddir": true,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<name translatable="no">Curtail</name>
|
<name translatable="no">Curtail</name>
|
||||||
<summary>Compress your images</summary>
|
<summary>Compress your images</summary>
|
||||||
<description>
|
<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>
|
<p>It supports both lossless and lossy compression modes with an option to whether keep or not metadata of images.</p>
|
||||||
</description>
|
</description>
|
||||||
<developer_name translatable="no">Hugo Posnic</developer_name>
|
<developer_name translatable="no">Hugo Posnic</developer_name>
|
||||||
|
@ -53,8 +53,13 @@
|
|||||||
</key>
|
</key>
|
||||||
<key type="b" name="jpg-progressive">
|
<key type="b" name="jpg-progressive">
|
||||||
<default>false</default>
|
<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>
|
<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>
|
||||||
<key type="i" name="compression-timeout">
|
<key type="i" name="compression-timeout">
|
||||||
<default>30</default>
|
<default>30</default>
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
</object>
|
</object>
|
||||||
</child>"
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -199,6 +199,22 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</template>
|
</template>
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: curtail\n"
|
"Project-Id-Version: curtail\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-04-03 17:17+0200\n"
|
"POT-Creation-Date: 2023-04-04 22:47+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -25,7 +25,7 @@ msgstr ""
|
|||||||
#: data/com.github.huluti.Curtail.appdata.xml.in:11
|
#: data/com.github.huluti.Curtail.appdata.xml.in:11
|
||||||
msgid ""
|
msgid ""
|
||||||
"Optimize your images with Curtail, a useful image compressor that supports "
|
"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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.huluti.Curtail.appdata.xml.in:12
|
#: data/com.github.huluti.Curtail.appdata.xml.in:12
|
||||||
@ -559,7 +559,7 @@ msgid "Lossless compression level to use for WebP images."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.huluti.Curtail.gschema.xml:56
|
#: data/com.github.huluti.Curtail.gschema.xml:56
|
||||||
msgid "Enable progressive encoding for jpegs"
|
msgid "Enable progressive encoding for JPEG images."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.huluti.Curtail.gschema.xml:57
|
#: data/com.github.huluti.Curtail.gschema.xml:57
|
||||||
@ -567,10 +567,18 @@ msgid "Optionally encode jpeg images progressively."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.huluti.Curtail.gschema.xml:61
|
#: data/com.github.huluti.Curtail.gschema.xml:61
|
||||||
msgid "Compression Timeout"
|
msgid "Enable maximum compression for SVG images."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: data/com.github.huluti.Curtail.gschema.xml:62
|
#: 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."
|
msgid "Compression timeout for each image."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -631,6 +639,14 @@ msgstr ""
|
|||||||
msgid "Progressive Encode JPG"
|
msgid "Progressive Encode JPG"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/ui/preferences.ui:207
|
||||||
|
msgid "SVG Maximum Compression Level"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: data/ui/preferences.ui:208
|
||||||
|
msgid "Can be more destructive for the image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: data/ui/menu.ui:10
|
#: data/ui/menu.ui:10
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -667,11 +683,11 @@ msgstr ""
|
|||||||
msgid "Main Menu"
|
msgid "Main Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/compressor.py:86
|
#: src/compressor.py:90
|
||||||
msgid "Compression has reached the configured timeout of {} seconds."
|
msgid "Compression has reached the configured timeout of {} seconds."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/compressor.py:90
|
#: src/compressor.py:94
|
||||||
msgid "An unknown error has occured"
|
msgid "An unknown error has occured"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -679,20 +695,24 @@ msgstr ""
|
|||||||
msgid "All images"
|
msgid "All images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/tools.py:36
|
#: src/tools.py:37
|
||||||
msgid "PNG images"
|
msgid "PNG images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/tools.py:40
|
#: src/tools.py:41
|
||||||
msgid "JPEG images"
|
msgid "JPEG images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/tools.py:44
|
#: src/tools.py:45
|
||||||
msgid "WebP images"
|
msgid "WebP images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/tools.py:113 src/tools.py:121 src/tools.py:128 src/tools.py:135
|
#: src/tools.py:49
|
||||||
#: src/tools.py:163
|
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"
|
msgid "Version not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
14
python3-scour.json
Normal file
14
python3-scour.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -49,11 +49,13 @@ class Compressor():
|
|||||||
self.png_lossless_level = self._settings.get_int('png-lossless-level')
|
self.png_lossless_level = self._settings.get_int('png-lossless-level')
|
||||||
|
|
||||||
self.jpg_lossy_level = self._settings.get_int('jpg-lossy-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_lossless_level = self._settings.get_int('webp-lossless-level')
|
||||||
self.webp_lossy_level = self._settings.get_int('webp-lossy-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):
|
def compress_images(self):
|
||||||
self.thread = threading.Thread(target=self._compress_images)
|
self.thread = threading.Thread(target=self._compress_images)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
@ -68,6 +70,8 @@ class Compressor():
|
|||||||
command = self.build_jpg_command(result_item)
|
command = self.build_jpg_command(result_item)
|
||||||
elif file_type == 'webp':
|
elif file_type == 'webp':
|
||||||
command = self.build_webp_command(result_item)
|
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
|
self.run_command(command, result_item) # compress image
|
||||||
GLib.idle_add(self.c_enable_compression, True)
|
GLib.idle_add(self.c_enable_compression, True)
|
||||||
|
|
||||||
@ -129,7 +133,7 @@ class Compressor():
|
|||||||
jpegoptim = 'jpegoptim --max={} -o -f "{}"'
|
jpegoptim = 'jpegoptim --max={} -o -f "{}"'
|
||||||
jpegoptim2 = 'jpegoptim -o -f "{}"'
|
jpegoptim2 = 'jpegoptim -o -f "{}"'
|
||||||
|
|
||||||
if self.do_jpg_progressive:
|
if self.jpg_progressive:
|
||||||
jpegoptim += ' --all-progressive'
|
jpegoptim += ' --all-progressive'
|
||||||
jpegoptim2 += ' --all-progressive'
|
jpegoptim2 += ' --all-progressive'
|
||||||
|
|
||||||
@ -174,3 +178,13 @@ class Compressor():
|
|||||||
|
|
||||||
return command
|
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
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
|
|||||||
spin_jpg_lossy_level = Gtk.Template.Child()
|
spin_jpg_lossy_level = Gtk.Template.Child()
|
||||||
spin_webp_lossy_level = Gtk.Template.Child()
|
spin_webp_lossy_level = Gtk.Template.Child()
|
||||||
toggle_jpg_progressive = Gtk.Template.Child()
|
toggle_jpg_progressive = Gtk.Template.Child()
|
||||||
|
toggle_svg_maximum_level = Gtk.Template.Child()
|
||||||
|
|
||||||
_settings = Gio.Settings.new(SETTINGS_SCHEMA)
|
_settings = Gio.Settings.new(SETTINGS_SCHEMA)
|
||||||
|
|
||||||
@ -113,6 +114,11 @@ class CurtailPrefsWindow(Adw.PreferencesWindow):
|
|||||||
self.toggle_jpg_progressive.connect('notify::active', self.on_bool_changed,
|
self.toggle_jpg_progressive.connect('notify::active', self.on_bool_changed,
|
||||||
'jpg-progressive')
|
'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):
|
def on_bool_changed(self, switch, state, key):
|
||||||
self._settings.set_boolean(key, switch.get_active())
|
self._settings.set_boolean(key, switch.get_active())
|
||||||
# Additional actions
|
# Additional actions
|
||||||
|
29
src/tools.py
29
src/tools.py
@ -31,6 +31,7 @@ def add_filechooser_filters(dialog):
|
|||||||
all_images.add_mime_type('image/jpeg')
|
all_images.add_mime_type('image/jpeg')
|
||||||
all_images.add_mime_type('image/png')
|
all_images.add_mime_type('image/png')
|
||||||
all_images.add_mime_type('image/webp')
|
all_images.add_mime_type('image/webp')
|
||||||
|
all_images.add_mime_type('image/svg+xml')
|
||||||
|
|
||||||
png_images = Gtk.FileFilter()
|
png_images = Gtk.FileFilter()
|
||||||
png_images.set_name(_("PNG images"))
|
png_images.set_name(_("PNG images"))
|
||||||
@ -44,11 +45,16 @@ def add_filechooser_filters(dialog):
|
|||||||
webp_images.set_name(_("WebP images"))
|
webp_images.set_name(_("WebP images"))
|
||||||
webp_images.add_mime_type('image/webp')
|
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 = Gio.ListStore.new(Gtk.FileFilter)
|
||||||
file_filters.append(all_images)
|
file_filters.append(all_images)
|
||||||
file_filters.append(png_images)
|
file_filters.append(png_images)
|
||||||
file_filters.append(jpeg_images)
|
file_filters.append(jpeg_images)
|
||||||
file_filters.append(webp_images)
|
file_filters.append(webp_images)
|
||||||
|
file_filters.append(svg_images)
|
||||||
|
|
||||||
dialog.set_filters(file_filters)
|
dialog.set_filters(file_filters)
|
||||||
|
|
||||||
@ -62,6 +68,8 @@ def get_file_type(filename):
|
|||||||
return 'png'
|
return 'png'
|
||||||
elif content_type == 'image/webp':
|
elif content_type == 'image/webp':
|
||||||
return 'webp'
|
return 'webp'
|
||||||
|
elif content_type == 'image/svg+xml':
|
||||||
|
return 'svg'
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@ -109,42 +117,51 @@ def debug_infos():
|
|||||||
try:
|
try:
|
||||||
jpegoptim = subprocess.check_output(['jpegoptim', '--version'])
|
jpegoptim = subprocess.check_output(['jpegoptim', '--version'])
|
||||||
jpegoptim = extract_version(jpegoptim.decode('utf-8'))
|
jpegoptim = extract_version(jpegoptim.decode('utf-8'))
|
||||||
except Exception as err:
|
except Exception:
|
||||||
jpegoptim = _('Version not found')
|
jpegoptim = _('Version not found')
|
||||||
|
|
||||||
# Oxipng
|
# Oxipng
|
||||||
try:
|
try:
|
||||||
oxipng = subprocess.check_output(['oxipng', '--version'])
|
oxipng = subprocess.check_output(['oxipng', '--version'])
|
||||||
oxipng = extract_version(oxipng.decode('utf-8'))
|
oxipng = extract_version(oxipng.decode('utf-8'))
|
||||||
except Exception as err:
|
except Exception:
|
||||||
oxipng = _('Version not found')
|
oxipng = _('Version not found')
|
||||||
|
|
||||||
# pngquant
|
# pngquant
|
||||||
try:
|
try:
|
||||||
pngquant = subprocess.check_output(['pngquant', '--version'])
|
pngquant = subprocess.check_output(['pngquant', '--version'])
|
||||||
pngquant = extract_version(pngquant.decode('utf-8'))
|
pngquant = extract_version(pngquant.decode('utf-8'))
|
||||||
except Exception as err:
|
except Exception:
|
||||||
pngquant = _('Version not found')
|
pngquant = _('Version not found')
|
||||||
|
|
||||||
# Libwebp
|
# Libwebp
|
||||||
try:
|
try:
|
||||||
libwebp = subprocess.check_output(['cwebp', '-version'])
|
libwebp = subprocess.check_output(['cwebp', '-version'])
|
||||||
libwebp = extract_version(libwebp.decode('utf-8'))
|
libwebp = extract_version(libwebp.decode('utf-8'))
|
||||||
except Exception as err:
|
except Exception:
|
||||||
libwebp = _('Version not found')
|
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
|
debug = '''Python: {}\n
|
||||||
Gtk: {}\n
|
Gtk: {}\n
|
||||||
Jpegoptim: {}\n
|
Jpegoptim: {}\n
|
||||||
Oxipng: {}\n
|
Oxipng: {}\n
|
||||||
pngquant: {}\n
|
pngquant: {}\n
|
||||||
Libwebp: {}\n'''.format(
|
Libwebp: {}\n
|
||||||
|
Scour: {}\n'''.format(
|
||||||
python_version,
|
python_version,
|
||||||
gtk_version,
|
gtk_version,
|
||||||
jpegoptim,
|
jpegoptim,
|
||||||
oxipng,
|
oxipng,
|
||||||
pngquant,
|
pngquant,
|
||||||
libwebp
|
libwebp,
|
||||||
|
scour
|
||||||
)
|
)
|
||||||
|
|
||||||
return debug
|
return debug
|
||||||
|
@ -226,7 +226,7 @@ class CurtailWindow(Gtk.ApplicationWindow):
|
|||||||
def check_extension(self, filename):
|
def check_extension(self, filename):
|
||||||
file_type = get_file_type(filename)
|
file_type = get_file_type(filename)
|
||||||
if file_type:
|
if file_type:
|
||||||
return file_type in ('png', 'jpg', 'webp')
|
return file_type in ('png', 'jpg', 'webp', 'svg')
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user