redmine_apijs 6.5.0 → 6.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README +9 -8
- data/app/controllers/apijs_controller.rb +25 -6
- data/app/views/application/_browser.html.erb +1 -1
- data/app/views/attachments/_links.html.erb +1 -1
- data/app/views/settings/_apijs.html.erb +7 -4
- data/assets/javascripts/{app.js → apijs-redmine.js} +22 -6
- data/assets/javascripts/apijs-redmine.min.js +4 -3
- data/assets/javascripts/apijs-redmine.min.js.map +1 -0
- data/assets/javascripts/apijs.min.js +5 -4
- data/assets/javascripts/apijs.min.js.map +1 -0
- data/assets/stylesheets/apijs-print.min.css +5 -5
- data/assets/stylesheets/apijs-print.min.css.map +1 -0
- data/assets/stylesheets/apijs-redmine-rtl.min.css +4 -4
- data/assets/stylesheets/apijs-redmine-rtl.min.css.map +1 -0
- data/assets/stylesheets/{styles.css → apijs-redmine.css} +6 -4
- data/assets/stylesheets/apijs-redmine.min.css +4 -4
- data/assets/stylesheets/apijs-redmine.min.css.map +1 -0
- data/assets/stylesheets/apijs-screen-rtl.min.css +5 -5
- data/assets/stylesheets/apijs-screen-rtl.min.css.map +1 -0
- data/assets/stylesheets/apijs-screen.min.css +5 -5
- data/assets/stylesheets/apijs-screen.min.css.map +1 -0
- data/config/locales/cs.yml +3 -1
- data/config/locales/de.yml +3 -1
- data/config/locales/el.yml +34 -0
- data/config/locales/en.yml +3 -1
- data/config/locales/es.yml +3 -1
- data/config/locales/fr.yml +3 -1
- data/config/locales/hu.yml +34 -0
- data/config/locales/it.yml +3 -1
- data/config/locales/ja.yml +3 -1
- data/config/locales/nl.yml +3 -1
- data/config/locales/pl.yml +3 -1
- data/config/locales/pt-BR.yml +3 -1
- data/config/locales/pt.yml +3 -1
- data/config/locales/ro.yml +34 -0
- data/config/locales/ru.yml +3 -1
- data/config/locales/sk.yml +3 -1
- data/config/locales/tr.yml +3 -1
- data/config/locales/uk.yml +34 -0
- data/config/locales/zh.yml +3 -1
- data/config/routes.rb +9 -2
- data/init.rb +4 -4
- data/lib/apijs_attachment.rb +10 -5
- data/lib/apijs_const.rb +1 -1
- data/lib/apijs_files.rb +1 -1
- data/lib/image.py +168 -119
- data/lib/redmine_apijs.rb +1 -1
- data/lib/useragentparser.rb +11 -9
- data/lib/video.py +8 -6
- data/redmine_apijs.gemspec +15 -4
- metadata +18 -6
@@ -0,0 +1,34 @@
|
|
1
|
+
ro:
|
2
|
+
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
4
|
+
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
5
|
+
apijs_title_album: "Photo gallery"
|
6
|
+
apijs_title_download: "Descarcă (%{v})"
|
7
|
+
apijs_title_files: "Files list"
|
8
|
+
apijs_config_apijs: "General"
|
9
|
+
apijs_config_enable: "Enable apijs"
|
10
|
+
apijs_example1_title: "Manualul PHP"
|
11
|
+
apijs_example1_text: "Este important să știți că cu toate că mesajul a fost acceptat spre trimitere, aceasta NU înseamnă că mesajul într-adevăr va ajunge la destinație."
|
12
|
+
apijs_example: "Exemplu %{v}"
|
13
|
+
apijs_example2_title: "Surprise!"
|
14
|
+
apijs_example2_text: "You are watching this video without proprietary extension thanks to the new [em]video[/em] tag included in the [strong]HTML 5[/strong] language."
|
15
|
+
apijs_example2_link: "Site: %{v}"
|
16
|
+
apijs_config_sort_order: "Sort files alphabetically"
|
17
|
+
apijs_config_browser: "Check browser version"
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
19
|
+
permission_edit_attachments: "(apijs) Edit files description"
|
20
|
+
permission_rename_attachments: "(apijs) Rename files"
|
21
|
+
permission_delete_attachments: "(apijs) Remove files"
|
22
|
+
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
24
|
+
apijs_config_album: "Photo albums"
|
25
|
+
apijs_config_show_album: "Display albums"
|
26
|
+
apijs_config_show_album_infos: "Display all information"
|
27
|
+
apijs_config_show_filename: "Display the name in the slide show"
|
28
|
+
apijs_config_show_exifdate: "Display the date in the slide show"
|
29
|
+
apijs_config_mimetypes: "Use the following files"
|
30
|
+
apijs_config_album_exclude_name: "Exclude photos from album<br />whose name begins with"
|
31
|
+
apijs_config_album_exclude_desc: "Exclude photos from album<br />whose description begins with"
|
32
|
+
apijs_config_create_all: "Generate the three images"
|
33
|
+
apijs_config_create_all_info: "Generates thumbnails and image preview at the same time, it slows the first display, but accelerates the first display of the slide show."
|
34
|
+
apijs_config_programs: "Programs version"
|
data/config/locales/ru.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
ru:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
3
4
|
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
4
5
|
apijs_title_album: "Фото галлерея"
|
5
6
|
apijs_title_download: "Скачать (%{v})"
|
@@ -14,11 +15,12 @@ ru:
|
|
14
15
|
apijs_example2_link: "Вебсайт: %{v}"
|
15
16
|
apijs_config_sort_order: "Сортировать файлы в алфавитном порядке"
|
16
17
|
apijs_config_browser: "Check browser version"
|
17
|
-
apijs_config_browser_help: "Firefox 36+, Chrome
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
18
19
|
permission_edit_attachments: "(apijs) Редактировать описание файла"
|
19
20
|
permission_rename_attachments: "(apijs) Rename files"
|
20
21
|
permission_delete_attachments: "(apijs) Удалить файлы"
|
21
22
|
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
22
24
|
apijs_config_album: "Фото альбомы"
|
23
25
|
apijs_config_show_album: "Отображать фото альбомы"
|
24
26
|
apijs_config_show_album_infos: "Display all information"
|
data/config/locales/sk.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
sk:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
3
4
|
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
4
5
|
apijs_title_album: "Photo gallery"
|
5
6
|
apijs_title_download: "Prevziať (%{v})"
|
@@ -14,11 +15,12 @@ sk:
|
|
14
15
|
apijs_example2_link: "Webstránka: %{v}"
|
15
16
|
apijs_config_sort_order: "Sort files alphabetically"
|
16
17
|
apijs_config_browser: "Check browser version"
|
17
|
-
apijs_config_browser_help: "Firefox 36+, Chrome
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
18
19
|
permission_edit_attachments: "(apijs) Edit files description"
|
19
20
|
permission_rename_attachments: "(apijs) Rename files"
|
20
21
|
permission_delete_attachments: "(apijs) Remove files"
|
21
22
|
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
22
24
|
apijs_config_album: "Photo albums"
|
23
25
|
apijs_config_show_album: "Display albums"
|
24
26
|
apijs_config_show_album_infos: "Display all information"
|
data/config/locales/tr.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
tr:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
3
4
|
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
4
5
|
apijs_title_album: "Fotoğraf Galerisi"
|
5
6
|
apijs_title_download: "İndir (%{v})"
|
@@ -14,11 +15,12 @@ tr:
|
|
14
15
|
apijs_example2_link: "Web sitesi : %{v}"
|
15
16
|
apijs_config_sort_order: "Eklentileri alfabetik olarak sırala"
|
16
17
|
apijs_config_browser: "Check browser version"
|
17
|
-
apijs_config_browser_help: "Firefox 36+, Chrome
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
18
19
|
permission_edit_attachments: "(apijs) Eklenti açıklamalarını düzenle"
|
19
20
|
permission_rename_attachments: "(apijs) Rename files"
|
20
21
|
permission_delete_attachments: "(apijs) Eklenti Sil"
|
21
22
|
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
22
24
|
apijs_config_album: "Fotoğraf Albümü"
|
23
25
|
apijs_config_show_album: "Fotoğraf albümünü görüntüle"
|
24
26
|
apijs_config_show_album_infos: "Display all information"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
uk:
|
2
|
+
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
4
|
+
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
5
|
+
apijs_title_album: "Photo gallery"
|
6
|
+
apijs_title_download: "Завантажити (%{v})"
|
7
|
+
apijs_title_files: "Files list"
|
8
|
+
apijs_config_apijs: "Загальні"
|
9
|
+
apijs_config_enable: "Enable apijs"
|
10
|
+
apijs_example1_title: "PHP manual"
|
11
|
+
apijs_example1_text: "It is important to note that just because the mail was accepted for delivery, it doesn't mean the mail will actually reach the intended destination."
|
12
|
+
apijs_example: "Example %{v}"
|
13
|
+
apijs_example2_title: "Surprise!"
|
14
|
+
apijs_example2_text: "You are watching this video without proprietary extension thanks to the new [em]video[/em] tag included in the [strong]HTML 5[/strong] language."
|
15
|
+
apijs_example2_link: "Вебсайт: %{v}"
|
16
|
+
apijs_config_sort_order: "Sort files alphabetically"
|
17
|
+
apijs_config_browser: "Check browser version"
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
19
|
+
permission_edit_attachments: "(apijs) Edit files description"
|
20
|
+
permission_rename_attachments: "(apijs) Rename files"
|
21
|
+
permission_delete_attachments: "(apijs) Remove files"
|
22
|
+
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
24
|
+
apijs_config_album: "Photo albums"
|
25
|
+
apijs_config_show_album: "Display albums"
|
26
|
+
apijs_config_show_album_infos: "Display all information"
|
27
|
+
apijs_config_show_filename: "Display the name in the slide show"
|
28
|
+
apijs_config_show_exifdate: "Display the date in the slide show"
|
29
|
+
apijs_config_mimetypes: "Use the following files"
|
30
|
+
apijs_config_album_exclude_name: "Exclude photos from album<br />whose name begins with"
|
31
|
+
apijs_config_album_exclude_desc: "Exclude photos from album<br />whose description begins with"
|
32
|
+
apijs_config_create_all: "Generate the three images"
|
33
|
+
apijs_config_create_all_info: "Generates thumbnails and image preview at the same time, it slows the first display, but accelerates the first display of the slide show."
|
34
|
+
apijs_config_programs: "Programs version"
|
data/config/locales/zh.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
zh:
|
2
2
|
apijs_check_extension: "The file extension can not be changed."
|
3
|
+
apijs_clearcache_done: "The image cache was cleaned."
|
3
4
|
apijs_browser_warning: "<strong>Warning:</strong> your browser <strong>%{n} %{v}</strong> is outdated, please <a %{l}>upgrade your browser</a>."
|
4
5
|
apijs_title_album: "照片库"
|
5
6
|
apijs_title_download: "下载(%{v})"
|
@@ -14,11 +15,12 @@ zh:
|
|
14
15
|
apijs_example2_link: "网站:%{v}"
|
15
16
|
apijs_config_sort_order: "按字母顺序排序文件"
|
16
17
|
apijs_config_browser: "Check browser version"
|
17
|
-
apijs_config_browser_help: "Firefox 36+, Chrome
|
18
|
+
apijs_config_browser_help: "Firefox 36+, Chrome 32+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
|
18
19
|
permission_edit_attachments: "(apijs) 编辑文件说明"
|
19
20
|
permission_rename_attachments: "(apijs) Rename files"
|
20
21
|
permission_delete_attachments: "(apijs) 删除文件"
|
21
22
|
apijs_config_directories: "Directories size"
|
23
|
+
apijs_config_clearcache: "clear cache"
|
22
24
|
apijs_config_album: "相册"
|
23
25
|
apijs_config_show_album: "显示相册"
|
24
26
|
apijs_config_show_album_infos: "显示所有信息"
|
data/config/routes.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# Created J/12/12/2013
|
3
|
-
# Updated
|
3
|
+
# Updated S/16/01/2021
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|
@@ -44,6 +44,9 @@ if Redmine::VERSION::MAJOR >= 2
|
|
44
44
|
|
45
45
|
match 'apijs/delete', to: 'apijs#delete',
|
46
46
|
via: :post
|
47
|
+
|
48
|
+
match 'apijs/clearcache', to: 'apijs#clearcache',
|
49
|
+
via: :get
|
47
50
|
end
|
48
51
|
else
|
49
52
|
ActionController::Routing::Routes.draw do |map|
|
@@ -89,5 +92,9 @@ else
|
|
89
92
|
map.connect 'apijs/delete',
|
90
93
|
controller: 'apijs', action: 'delete',
|
91
94
|
conditions: {method: :post}
|
95
|
+
|
96
|
+
map.connect 'apijs/clearcache',
|
97
|
+
controller: 'apijs', action: 'clearcache',
|
98
|
+
conditions: {method: :get}
|
92
99
|
end
|
93
100
|
end
|
data/init.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# Created L/21/05/2012
|
3
|
-
# Updated
|
3
|
+
# Updated J/23/09/2021
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|
@@ -25,8 +25,8 @@ Redmine::Plugin.register :redmine_apijs do
|
|
25
25
|
|
26
26
|
name 'Redmine Apijs plugin'
|
27
27
|
author 'Fabrice Creuzot'
|
28
|
-
description 'Integrate the apijs
|
29
|
-
version '6.
|
28
|
+
description 'Integrate the apijs JavaScript library into Redmine. Provides a gallery for image and video attachments.'
|
29
|
+
version '6.8.1-gem'
|
30
30
|
url 'https://www.luigifab.fr/redmine/apijs'
|
31
31
|
author_url 'https://www.luigifab.fr/'
|
32
32
|
|
data/lib/apijs_attachment.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# Created V/27/12/2013
|
3
|
-
# Updated
|
3
|
+
# Updated J/18/02/2021
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|
@@ -90,9 +90,9 @@ module ApijsAttachment
|
|
90
90
|
|
91
91
|
def getShowButton(setting_show_filename, setting_show_exifdate, description)
|
92
92
|
if self.isImage?
|
93
|
-
return "apijs.dialog.dialogPhoto('" + self.getShowUrl + "', '" + (
|
93
|
+
return "apijs.dialog.dialogPhoto('" + self.getShowUrl + "', '" + (setting_show_filename ? self.filename : 'false') + "', '" + (setting_show_exifdate ? format_time(self.created_on) : 'false') + "', '" + description + "');"
|
94
94
|
elsif self.isVideo?
|
95
|
-
return "apijs.dialog.dialogVideo('" + self.getDownloadUrl + "', '" + (
|
95
|
+
return "apijs.dialog.dialogVideo('" + self.getDownloadUrl + "', '" + (setting_show_filename ? self.filename : 'false') + "', '" + (setting_show_exifdate ? format_time(self.created_on) : 'false') + "', '" + description + "');"
|
96
96
|
elsif self.is_text?
|
97
97
|
return "self.location.href = '" + self.getUrl('redmineshow', false) + "';"
|
98
98
|
end
|
@@ -280,13 +280,16 @@ module ApijsAttachment
|
|
280
280
|
pil = pyt
|
281
281
|
sco = pyt
|
282
282
|
else
|
283
|
-
pyt = `#{cmd} --version 2>&1`.gsub('Python
|
283
|
+
pyt = `#{cmd} --version 2>&1`.gsub('Python', '').strip!
|
284
|
+
pyt = pyt.split(/\D/).slice(0, 3).join('.')
|
284
285
|
|
285
286
|
pil = `#{cmd} -c "from PIL import Image; print(Image.__version__)" 2>&1`.strip!
|
286
287
|
if pil.include? "o module named"
|
287
288
|
pil = 'not available'
|
288
289
|
elsif pil.include? "__version__"
|
289
290
|
pil = 'available'
|
291
|
+
else
|
292
|
+
pil = pil.split(/\D/).slice(0, 3).join('.')
|
290
293
|
end
|
291
294
|
|
292
295
|
sco = `#{cmd} -c "import scour; print(scour.__version__)" 2>&1`.strip!
|
@@ -294,6 +297,8 @@ module ApijsAttachment
|
|
294
297
|
sco = 'not available'
|
295
298
|
elsif sco.include? "__version__"
|
296
299
|
sco = 'available'
|
300
|
+
else
|
301
|
+
sco = sco.split(/\D/).slice(0, 3).join('.')
|
297
302
|
end
|
298
303
|
end
|
299
304
|
|
data/lib/apijs_const.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Created L/01/09/2014
|
3
3
|
# Updated J/23/07/2020
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|
data/lib/apijs_files.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Created L/21/05/2012
|
3
3
|
# Updated D/03/05/2020
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|
data/lib/image.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
#!/usr/bin/python3
|
2
2
|
# -*- coding: utf8 -*-
|
3
3
|
# Created J/26/12/2013
|
4
|
-
# Updated
|
4
|
+
# Updated M/11/05/2021
|
5
5
|
#
|
6
|
-
# Copyright 2008-
|
6
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
7
|
+
# Copyright 2020-2021 | Fabrice Creuzot <fabrice~cellublue~com>
|
7
8
|
# https://www.luigifab.fr/openmage/apijs
|
8
9
|
#
|
9
10
|
# This program is free software, you can redistribute it or modify
|
@@ -18,7 +19,6 @@
|
|
18
19
|
|
19
20
|
import os
|
20
21
|
import sys
|
21
|
-
from PIL import Image, ImageSequence
|
22
22
|
|
23
23
|
try:
|
24
24
|
filein = str(sys.argv[1])
|
@@ -35,166 +35,215 @@ except:
|
|
35
35
|
if not os.path.exists(os.path.dirname(fileout)):
|
36
36
|
os.makedirs(os.path.dirname(fileout))
|
37
37
|
|
38
|
-
|
39
|
-
if ".svg" in fileout:
|
40
|
-
from scour.scour import (sanitizeOptions, start)
|
41
|
-
options = sanitizeOptions()
|
42
|
-
options.strip_xml_prolog = True # --strip-xml-prolog
|
43
|
-
options.remove_metadata = True # --remove-metadata
|
44
|
-
options.strip_comments = True # --enable-comment-stripping
|
45
|
-
options.strip_ids = True # --enable-id-stripping
|
46
|
-
options.indent_type = None # --indent=none
|
47
|
-
start(options, open(filein, 'rb'), open(fileout, 'wb'))
|
48
|
-
exit(0)
|
38
|
+
fileout = fileout + '.save'
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
40
|
+
|
41
|
+
# tools
|
42
|
+
def versionTuple(v):
|
43
|
+
return tuple(map(int, (v.split('.'))))
|
44
|
+
|
45
|
+
def calcSize(source, size):
|
46
|
+
|
47
|
+
if size[1] == 0 and size[0] == 0:
|
48
|
+
return source.size
|
49
|
+
if size[1] == 0:
|
50
|
+
return (size[0], int(size[0] / (source.size[0] / source.size[1])))
|
51
|
+
if size[0] == 0:
|
52
|
+
return (int(size[1] * (source.size[0] / source.size[1])), size[1])
|
53
|
+
|
54
|
+
return size
|
55
|
+
|
56
|
+
def createThumb(source, size, fixed, new=False):
|
57
|
+
|
58
|
+
# https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.thumbnail
|
59
|
+
# https://pillow.readthedocs.io/en/latest/handbook/concepts.html#filters-comparison-table
|
60
|
+
# https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.Image.paste
|
61
|
+
if hasattr(Image, '__version__') and versionTuple(Image.__version__) > (7,0,0):
|
62
|
+
source.thumbnail(size, Image.LANCZOS, 4)
|
63
|
+
else:
|
64
|
+
source.thumbnail(size, Image.ANTIALIAS)
|
65
|
+
|
66
|
+
if fixed:
|
67
|
+
offset_x = max(int((size[0] - source.size[0]) / 2), 0)
|
68
|
+
offset_y = max(int((size[1] - source.size[1]) / 2), 0)
|
69
|
+
dest = Image.new('RGBA', size, (255,255,255,1))
|
70
|
+
dest.paste(source, (offset_x, offset_y))
|
71
|
+
elif new:
|
72
|
+
if (source.size < size):
|
73
|
+
dest = Image.new('RGBA', source.size, (255,255,255,1))
|
74
|
+
dest.paste(source)
|
75
|
+
else:
|
76
|
+
dest = Image.new('RGBA', size, (255,255,255,1))
|
77
|
+
dest.paste(source)
|
78
|
+
else:
|
79
|
+
dest = source
|
80
|
+
|
81
|
+
return dest
|
82
|
+
|
83
|
+
|
84
|
+
# Animated resizing
|
60
85
|
# based on https://stackoverflow.com/a/41827681/2980105
|
61
|
-
|
86
|
+
def resizeAnimatedGif(source, size, fixed):
|
87
|
+
|
88
|
+
# Animated GIF resizing
|
89
|
+
# https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#gif
|
90
|
+
colors = source.getpalette()
|
91
|
+
dest = []
|
62
92
|
|
63
|
-
frames = []
|
64
93
|
try:
|
65
94
|
while True:
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
b = source.tell()
|
80
|
-
source.seek(b + 1)
|
81
|
-
if b == source.tell():
|
95
|
+
# If the GIF uses local colour tables, each frame will have its own palette.
|
96
|
+
# If not, we need to apply the global palette to the new frame.
|
97
|
+
if not source.getpalette():
|
98
|
+
source.putpalette(colors)
|
99
|
+
|
100
|
+
frame = Image.new('RGBA', source.size, (255,255,255,1))
|
101
|
+
frame.paste(source, (0,0), source.convert('RGBA'))
|
102
|
+
dest.append(createThumb(frame, size, fixed))
|
103
|
+
|
104
|
+
idx = source.tell()
|
105
|
+
source.seek(idx + 1)
|
106
|
+
if idx == source.tell():
|
82
107
|
break
|
83
108
|
except EOFError:
|
84
109
|
pass
|
85
|
-
# Animated GIF resizing
|
86
|
-
# based on https://stackoverflow.com/a/41827681/2980105
|
87
|
-
elif source.format == 'GIF' and source.is_animated and ".gif" in fileout:
|
88
110
|
|
89
|
-
|
90
|
-
|
111
|
+
return dest
|
112
|
+
|
113
|
+
def resizeAnimatedPng(source, size, fixed):
|
114
|
+
|
115
|
+
# Animated PNG resizing (since PIL 7.1.0)
|
116
|
+
# https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#apng-sequences
|
117
|
+
dest = []
|
118
|
+
|
91
119
|
try:
|
92
120
|
while True:
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
new_frame.thumbnail(size, Image.ANTIALIAS)
|
101
|
-
|
102
|
-
if fixed:
|
103
|
-
offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
|
104
|
-
offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
|
105
|
-
final_frame = Image.new('RGBA', size, (255,255,255,1))
|
106
|
-
final_frame.paste(new_frame, (offset_x, offset_y))
|
107
|
-
frames.append(final_frame)
|
108
|
-
else:
|
109
|
-
frames.append(new_frame)
|
110
|
-
|
111
|
-
b = source.tell()
|
112
|
-
source.seek(b + 1)
|
113
|
-
if b == source.tell():
|
121
|
+
frame = Image.new('RGBA', source.size, (255,255,255,1))
|
122
|
+
frame.paste(source, (0,0), source.convert('RGBA'))
|
123
|
+
dest.append(createThumb(frame, size, fixed))
|
124
|
+
|
125
|
+
idx = source.tell()
|
126
|
+
source.seek(idx + 1)
|
127
|
+
if idx == source.tell():
|
114
128
|
break
|
115
129
|
except EOFError:
|
116
130
|
pass
|
117
|
-
# Animated WEBP resizing (since PIL 5.0.0)
|
118
|
-
# based on https://stackoverflow.com/a/41827681/2980105
|
119
|
-
elif source.format == 'WEBP' and ".webp" in fileout:
|
120
131
|
|
121
|
-
|
132
|
+
return dest
|
133
|
+
|
134
|
+
def resizeAnimatedWebp(source, size, fixed):
|
135
|
+
|
136
|
+
# Animated WEBP resizing (since PIL 5.0.0)
|
137
|
+
# https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#webp
|
138
|
+
dest = []
|
139
|
+
|
122
140
|
try:
|
123
141
|
while True:
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
final_frame = Image.new('RGBA', size, (255,255,255,1))
|
132
|
-
final_frame.paste(new_frame, (offset_x, offset_y))
|
133
|
-
frames.append(final_frame)
|
134
|
-
else:
|
135
|
-
frames.append(new_frame)
|
136
|
-
|
137
|
-
b = source.tell()
|
138
|
-
source.seek(b + 1)
|
139
|
-
if b == source.tell():
|
142
|
+
frame = Image.new('RGBA', source.size, (255,255,255,1))
|
143
|
+
frame.paste(source, (0,0), source.convert('RGBA'))
|
144
|
+
dest.append(createThumb(frame, size, fixed))
|
145
|
+
|
146
|
+
idx = source.tell()
|
147
|
+
source.seek(idx + 1)
|
148
|
+
if idx == source.tell():
|
140
149
|
break
|
141
150
|
except EOFError:
|
142
151
|
pass
|
143
|
-
# Standard resizing
|
144
|
-
elif fixed:
|
145
|
-
source.thumbnail(size, Image.ANTIALIAS)
|
146
|
-
offset_x = int(max((size[0] - source.size[0]) / 2, 0))
|
147
|
-
offset_y = int(max((size[1] - source.size[1]) / 2, 0))
|
148
|
-
dest = Image.new('RGBA', size, (255,255,255,1))
|
149
|
-
dest.paste(source, (offset_x, offset_y))
|
150
|
-
else:
|
151
|
-
source.thumbnail(size, Image.ANTIALIAS)
|
152
|
-
dest = Image.new('RGBA', (source.size[0], source.size[1]), (255,255,255,1))
|
153
|
-
dest.paste(source)
|
154
152
|
|
153
|
+
return dest
|
154
|
+
|
155
|
+
|
156
|
+
# File saving
|
155
157
|
# https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html
|
156
|
-
# source.info.get('loop')=None
|
157
|
-
|
158
|
+
# todo: source.info.get('loop')=None
|
159
|
+
def saveGif(dest, fileout, quality):
|
160
|
+
|
161
|
+
try:
|
162
|
+
if len(dest) == 1:
|
163
|
+
dest[0].save(fileout, 'GIF', optimize=True)
|
164
|
+
else:
|
165
|
+
dest[0].save(fileout, 'GIF', optimize=True, save_all=True, append_images=dest[1:],
|
166
|
+
loop=0, duration=source.info.get('duration'), default_image=source.info.get('default_image'))
|
167
|
+
except:
|
168
|
+
dest.save(fileout, 'GIF', optimize=True)
|
169
|
+
|
170
|
+
def savePng(dest, fileout, quality):
|
171
|
+
|
158
172
|
# The image quality, on a scale from 0 (best-speed) to 9 (best-compression), the default is 6
|
159
173
|
# but when optimize option is True compress_level has no effect
|
174
|
+
if quality < 1:
|
175
|
+
quality = 6
|
176
|
+
elif quality > 9:
|
177
|
+
quality = 9
|
178
|
+
|
160
179
|
try:
|
161
|
-
if len(
|
162
|
-
|
180
|
+
if len(dest) == 1:
|
181
|
+
dest[0].save(fileout, 'PNG', optimize=True, compress_level=9)
|
163
182
|
else:
|
164
|
-
|
183
|
+
dest[0].save(fileout, 'PNG', optimize=True, compress_level=9, save_all=True, append_images=dest[1:],
|
165
184
|
loop=0, duration=source.info.get('duration'))
|
166
185
|
except:
|
167
186
|
dest.save(fileout, 'PNG', optimize=True, compress_level=9)
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
frames[0].save(fileout, 'GIF', optimize=True)
|
172
|
-
else:
|
173
|
-
frames[0].save(fileout, 'GIF', optimize=True, save_all=True, append_images=frames[1:],
|
174
|
-
loop=0, duration=source.info.get('duration'), default_image=source.info.get('default_image'))
|
175
|
-
except:
|
176
|
-
dest.save(fileout, 'GIF', optimize=True)
|
177
|
-
elif ".webp" in fileout:
|
187
|
+
|
188
|
+
def saveWebp(dest, fileout, quality):
|
189
|
+
|
178
190
|
# The image quality, on a scale from 0 (worst) to 100 (best), the default is 80 (lossy, 0 gives the smallest size and 100 the largest)
|
179
191
|
# The method, on a scale from 0 (fast) to 6 (slower-better), the default is 0
|
180
192
|
if quality < 1:
|
181
193
|
quality = 80
|
182
194
|
elif quality > 100:
|
183
195
|
quality = 100
|
196
|
+
|
184
197
|
try:
|
185
|
-
if len(
|
186
|
-
|
198
|
+
if len(dest) == 1:
|
199
|
+
dest[0].save(fileout, 'WEBP', method=5, quality=quality)
|
187
200
|
else:
|
188
|
-
|
201
|
+
dest[0].save(fileout, 'WEBP', method=5, quality=quality, save_all=True, append_images=dest[1:],
|
189
202
|
loop=0, duration=source.info.get('duration'))
|
190
203
|
except:
|
191
204
|
dest.save(fileout, 'WEBP', method=5, quality=quality)
|
192
|
-
|
205
|
+
|
206
|
+
def saveJpg(dest, fileout, quality):
|
207
|
+
|
193
208
|
# The image quality, on a scale from 0 (worst) to 95 (best), the default is 75
|
194
209
|
if quality < 1:
|
195
210
|
quality = 75
|
196
211
|
elif quality > 95:
|
197
212
|
quality = 95
|
198
|
-
|
213
|
+
|
214
|
+
dest.convert('RGB').save(fileout, 'JPEG', optimize=True, subsampling=0, quality=quality)
|
215
|
+
|
216
|
+
|
217
|
+
# python-scour
|
218
|
+
if ".svg" in fileout:
|
219
|
+
from scour.scour import sanitizeOptions, start
|
220
|
+
options = sanitizeOptions()
|
221
|
+
options.strip_xml_prolog = True # --strip-xml-prolog
|
222
|
+
options.remove_metadata = True # --remove-metadata
|
223
|
+
options.strip_comments = True # --enable-comment-stripping
|
224
|
+
options.strip_ids = True # --enable-id-stripping
|
225
|
+
options.indent_type = None # --indent=none
|
226
|
+
start(options, open(filein, 'rb'), open(fileout, 'wb'))
|
227
|
+
# python-pil
|
228
|
+
else:
|
229
|
+
from PIL import Image, ImageSequence
|
230
|
+
source = Image.open(filein)
|
231
|
+
size = calcSize(source, size)
|
232
|
+
|
233
|
+
if source.format == 'GIF' and ".gif" in fileout:
|
234
|
+
dest = resizeAnimatedGif(source, size, fixed)
|
235
|
+
saveGif(dest, fileout, quality)
|
236
|
+
elif source.format == 'PNG' and ".png" in fileout:
|
237
|
+
dest = resizeAnimatedPng(source, size, fixed)
|
238
|
+
savePng(dest, fileout, quality)
|
239
|
+
elif source.format == 'WEBP' and ".webp" in fileout:
|
240
|
+
dest = resizeAnimatedWeb(source, size, fixed)
|
241
|
+
saveWebp(dest, fileout, quality)
|
242
|
+
else:
|
243
|
+
dest = createThumb(source, size, fixed, True)
|
244
|
+
saveJpg(dest, fileout, quality)
|
245
|
+
|
246
|
+
if os.path.isfile(fileout):
|
247
|
+
os.rename(fileout, fileout.replace('.save', ''))
|
199
248
|
|
200
249
|
exit(0)
|
data/lib/redmine_apijs.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Created L/13/07/2020
|
3
3
|
# Updated D/27/08/2020
|
4
4
|
#
|
5
|
-
# Copyright 2008-
|
5
|
+
# Copyright 2008-2021 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
|
6
6
|
# https://www.luigifab.fr/redmine/apijs
|
7
7
|
#
|
8
8
|
# This program is free software, you can redistribute it or modify
|