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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README +9 -8
  3. data/app/controllers/apijs_controller.rb +25 -6
  4. data/app/views/application/_browser.html.erb +1 -1
  5. data/app/views/attachments/_links.html.erb +1 -1
  6. data/app/views/settings/_apijs.html.erb +7 -4
  7. data/assets/javascripts/{app.js → apijs-redmine.js} +22 -6
  8. data/assets/javascripts/apijs-redmine.min.js +4 -3
  9. data/assets/javascripts/apijs-redmine.min.js.map +1 -0
  10. data/assets/javascripts/apijs.min.js +5 -4
  11. data/assets/javascripts/apijs.min.js.map +1 -0
  12. data/assets/stylesheets/apijs-print.min.css +5 -5
  13. data/assets/stylesheets/apijs-print.min.css.map +1 -0
  14. data/assets/stylesheets/apijs-redmine-rtl.min.css +4 -4
  15. data/assets/stylesheets/apijs-redmine-rtl.min.css.map +1 -0
  16. data/assets/stylesheets/{styles.css → apijs-redmine.css} +6 -4
  17. data/assets/stylesheets/apijs-redmine.min.css +4 -4
  18. data/assets/stylesheets/apijs-redmine.min.css.map +1 -0
  19. data/assets/stylesheets/apijs-screen-rtl.min.css +5 -5
  20. data/assets/stylesheets/apijs-screen-rtl.min.css.map +1 -0
  21. data/assets/stylesheets/apijs-screen.min.css +5 -5
  22. data/assets/stylesheets/apijs-screen.min.css.map +1 -0
  23. data/config/locales/cs.yml +3 -1
  24. data/config/locales/de.yml +3 -1
  25. data/config/locales/el.yml +34 -0
  26. data/config/locales/en.yml +3 -1
  27. data/config/locales/es.yml +3 -1
  28. data/config/locales/fr.yml +3 -1
  29. data/config/locales/hu.yml +34 -0
  30. data/config/locales/it.yml +3 -1
  31. data/config/locales/ja.yml +3 -1
  32. data/config/locales/nl.yml +3 -1
  33. data/config/locales/pl.yml +3 -1
  34. data/config/locales/pt-BR.yml +3 -1
  35. data/config/locales/pt.yml +3 -1
  36. data/config/locales/ro.yml +34 -0
  37. data/config/locales/ru.yml +3 -1
  38. data/config/locales/sk.yml +3 -1
  39. data/config/locales/tr.yml +3 -1
  40. data/config/locales/uk.yml +34 -0
  41. data/config/locales/zh.yml +3 -1
  42. data/config/routes.rb +9 -2
  43. data/init.rb +4 -4
  44. data/lib/apijs_attachment.rb +10 -5
  45. data/lib/apijs_const.rb +1 -1
  46. data/lib/apijs_files.rb +1 -1
  47. data/lib/image.py +168 -119
  48. data/lib/redmine_apijs.rb +1 -1
  49. data/lib/useragentparser.rb +11 -9
  50. data/lib/video.py +8 -6
  51. data/redmine_apijs.gemspec +15 -4
  52. 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"
@@ -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 31+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
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"
@@ -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 31+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
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"
@@ -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 31+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
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"
@@ -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 31+, Opera 19+, Edge 16+, Safari 9+ (checks even if the apijs is disabled)."
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 J/20/08/2020
3
+ # Updated S/16/01/2021
4
4
  #
5
- # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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 S/10/10/2020
3
+ # Updated J/23/09/2021
4
4
  #
5
- # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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 javascript library into Redmine.'
29
- version '6.5.0-gem'
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
 
@@ -1,8 +1,8 @@
1
1
  # encoding: utf-8
2
2
  # Created V/27/12/2013
3
- # Updated S/07/11/2020
3
+ # Updated J/18/02/2021
4
4
  #
5
- # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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 + "', '" + ((setting_show_filename) ? self.filename : 'false') + "', '" + ((setting_show_exifdate) ? format_time(self.created_on) : 'false') + "', '" + description + "');"
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 + "', '" + ((setting_show_filename) ? self.filename : 'false') + "', '" + ((setting_show_exifdate) ? format_time(self.created_on) : 'false') + "', '" + description + "');"
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 ', '').strip!
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-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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 S/07/11/2020
4
+ # Updated M/11/05/2021
5
5
  #
6
- # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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
- # python-scour
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
- # python-pil
51
- source = Image.open(filein)
52
- if size[1] == 0 and size[0] == 0:
53
- size = (source.size[0], source.size[1])
54
- elif size[1] == 0:
55
- size = (size[0], int(size[0] / (source.size[0] / source.size[1])))
56
- elif size[0] == 0:
57
- size = (int(size[1] * (source.size[0] / source.size[1])), size[1])
58
-
59
- # (Animated) PNG resizing (since PIL 7.1.0)
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
- if source.format == 'PNG' and ".png" in fileout:
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
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
67
- new_frame.paste(source, (0,0), source.convert('RGBA'))
68
- new_frame.thumbnail(size, Image.ANTIALIAS)
69
-
70
- if fixed:
71
- offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
72
- offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
73
- final_frame = Image.new('RGBA', size, (255,255,255,1))
74
- final_frame.paste(new_frame, (offset_x, offset_y))
75
- frames.append(final_frame)
76
- else:
77
- frames.append(new_frame)
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
- p = source.getpalette()
90
- frames = []
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
- # If the GIF uses local colour tables, each frame will have its own palette.
94
- # If not, we need to apply the global palette to the new frame.
95
- if not source.getpalette():
96
- source.putpalette(p)
97
-
98
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
99
- new_frame.paste(source, (0,0), source.convert('RGBA'))
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
- frames = []
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
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
125
- new_frame.paste(source, (0,0), source.convert('RGBA'))
126
- new_frame.thumbnail(size, Image.ANTIALIAS)
127
-
128
- if fixed:
129
- offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
130
- offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
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
- if ".png" in fileout:
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(frames) == 1:
162
- frames[0].save(fileout, 'PNG', optimize=True, compress_level=9)
180
+ if len(dest) == 1:
181
+ dest[0].save(fileout, 'PNG', optimize=True, compress_level=9)
163
182
  else:
164
- frames[0].save(fileout, 'PNG', optimize=True, compress_level=9, save_all=True, append_images=frames[1:],
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
- elif ".gif" in fileout:
169
- try:
170
- if len(frames) == 1:
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(frames) == 1:
186
- frames[0].save(fileout, 'WEBP', method=5, quality=quality)
198
+ if len(dest) == 1:
199
+ dest[0].save(fileout, 'WEBP', method=5, quality=quality)
187
200
  else:
188
- frames[0].save(fileout, 'WEBP', method=5, quality=quality, save_all=True, append_images=frames[1:],
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
- else:
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
- dest.convert('RGB').save(fileout, 'JPEG', optimize=True, quality=quality)
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-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
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