redmine_apijs 6.4.0 → 6.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README +10 -9
  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 +12 -36
  7. data/assets/javascripts/{app.js → apijs-redmine.js} +26 -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 +66 -11
  45. data/lib/apijs_const.rb +1 -1
  46. data/lib/apijs_files.rb +1 -1
  47. data/lib/image.py +169 -121
  48. data/lib/redmine_apijs.rb +1 -1
  49. data/lib/useragentparser.rb +11 -9
  50. data/lib/video.py +10 -8
  51. data/redmine_apijs.gemspec +15 -4
  52. metadata +18 -6
@@ -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 J/20/08/2020
3
+ # Updated V/18/06/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.4.0-gem'
28
+ description 'Integrate the apijs JavaScript library into Redmine. Provides a gallery for image and video attachments.'
29
+ version '6.8.0-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 J/20/08/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
@@ -23,12 +23,9 @@ module ApijsAttachment
23
23
  unloadable
24
24
  if Rails::VERSION::MAJOR >= 3
25
25
  include Rails.application.routes.url_helpers
26
- else
27
- include ActionController::UrlWriter
28
- end
29
- if Rails::VERSION::MAJOR >= 4
30
26
  before_create :update_date
31
27
  else
28
+ include ActionController::UrlWriter
32
29
  before_save :update_date
33
30
  end
34
31
  after_destroy :delete_cache
@@ -93,9 +90,9 @@ module ApijsAttachment
93
90
 
94
91
  def getShowButton(setting_show_filename, setting_show_exifdate, description)
95
92
  if self.isImage?
96
- 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 + "');"
97
94
  elsif self.isVideo?
98
- 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 + "');"
99
96
  elsif self.is_text?
100
97
  return "self.location.href = '" + self.getUrl('redmineshow', false) + "';"
101
98
  end
@@ -154,14 +151,37 @@ module ApijsAttachment
154
151
  end
155
152
 
156
153
  # commande python
157
- def getCmd(source, target, width, height, fixed=false)
154
+ def getPython
158
155
 
159
156
  if Redmine::Platform.mswin?
160
- cmd = 'python.exe'
157
+ cmd = `python.exe --version`
158
+ if $? == 0
159
+ cmd = 'python.exe'
160
+ else
161
+ cmd = `python --version`
162
+ if $? == 0
163
+ cmd = 'python'
164
+ else
165
+ cmd = nil
166
+ end
167
+ end
161
168
  else
162
- cmd = `command -v python3 || command -v python || command -v python2`.to_s.strip!
169
+ cmd = `python3 --version`
170
+ if $? == 0
171
+ cmd = 'python3'
172
+ else
173
+ cmd = nil
174
+ end
163
175
  end
164
176
 
177
+ return cmd
178
+ end
179
+
180
+ def getCmd(source, target, width, height, fixed=false)
181
+
182
+ cmd = getPython
183
+ cmd = 'notfound' if not cmd or cmd.length == 0
184
+
165
185
  script = File.join(File.dirname(__FILE__), (self.isPhoto? ? 'image.py' : 'video.py'))
166
186
  return cmd + ' ' + script.to_s + ' ' + source.to_s + ' ' + target.to_s + ' ' +
167
187
  width.to_s + ' ' + height.to_s + (fixed ? ' 90 fixed' : ' 90') + ' 2>&1'
@@ -250,6 +270,41 @@ module ApijsAttachment
250
270
  logger.info 'APIJS::ApijsAttachment#update_filedir: moving file from ' + src + ' to ' + dest
251
271
  end
252
272
 
273
+ # informations
274
+ def getProgramVersions(helpPil, helpSco)
275
+
276
+ cmd = getPython
277
+
278
+ if not cmd or cmd.length == 0
279
+ pyt = 'not found'
280
+ pil = pyt
281
+ sco = pyt
282
+ else
283
+ pyt = `#{cmd} --version 2>&1`.gsub('Python', '').strip!
284
+ pyt = pyt.split(/\D/).slice(0, 3).join('.')
285
+
286
+ pil = `#{cmd} -c "from PIL import Image; print(Image.__version__)" 2>&1`.strip!
287
+ if pil.include? "o module named"
288
+ pil = 'not available'
289
+ elsif pil.include? "__version__"
290
+ pil = 'available'
291
+ else
292
+ pil = pil.split(/\D/).slice(0, 3).join('.')
293
+ end
294
+
295
+ sco = `#{cmd} -c "import scour; print(scour.__version__)" 2>&1`.strip!
296
+ if sco.include? "o module named"
297
+ sco = 'not available'
298
+ elsif sco.include? "__version__"
299
+ sco = 'available'
300
+ else
301
+ sco = sco.split(/\D/).slice(0, 3).join('.')
302
+ end
303
+ end
304
+
305
+ return sprintf('%s / %s %s / %s %s', pyt, pil, helpPil, sco, helpSco)
306
+ end
307
+
253
308
  end
254
309
  end
255
310
 
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
- #!/usr/bin/env python
1
+ #!/usr/bin/python3
2
2
  # -*- coding: utf8 -*-
3
3
  # Created J/26/12/2013
4
- # Updated D/26/07/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
@@ -17,9 +18,7 @@
17
18
  # GNU General Public License (GPL) for more details.
18
19
 
19
20
  import os
20
- import re
21
21
  import sys
22
- from PIL import Image, ImageSequence
23
22
 
24
23
  try:
25
24
  filein = str(sys.argv[1])
@@ -36,166 +35,215 @@ except:
36
35
  if not os.path.exists(os.path.dirname(fileout)):
37
36
  os.makedirs(os.path.dirname(fileout))
38
37
 
39
- # python-scour
40
- if ".svg" in fileout:
41
- from scour.scour import (sanitizeOptions, start)
42
- options = sanitizeOptions()
43
- options.strip_xml_prolog = True # --strip-xml-prolog
44
- options.remove_metadata = True # --remove-metadata
45
- options.strip_comments = True # --enable-comment-stripping
46
- options.strip_ids = True # --enable-id-stripping
47
- options.indent_type = None # --indent=none
48
- start(options, open(filein, 'rb'), open(fileout, 'wb'))
49
- exit(0)
38
+ fileout = fileout + '.save'
50
39
 
51
- # python-pil
52
- source = Image.open(filein)
53
- if size[1] == 0 and size[0] == 0:
54
- size = (source.size[0], source.size[1])
55
- elif size[1] == 0:
56
- size = (size[0], int(size[0] / (source.size[0] / source.size[1])))
57
- elif size[0] == 0:
58
- size = (int(size[1] * (source.size[0] / source.size[1])), size[1])
59
-
60
- # (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
61
85
  # based on https://stackoverflow.com/a/41827681/2980105
62
- 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 = []
63
92
 
64
- frames = []
65
93
  try:
66
94
  while True:
67
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
68
- new_frame.paste(source, (0,0), source.convert('RGBA'))
69
- new_frame.thumbnail(size, Image.ANTIALIAS)
70
-
71
- if fixed:
72
- offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
73
- offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
74
- final_frame = Image.new('RGBA', size, (255,255,255,1))
75
- final_frame.paste(new_frame, (offset_x, offset_y))
76
- frames.append(final_frame)
77
- else:
78
- frames.append(new_frame)
79
-
80
- b = source.tell()
81
- source.seek(b + 1)
82
- 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():
83
107
  break
84
108
  except EOFError:
85
109
  pass
86
- # Animated GIF resizing
87
- # based on https://stackoverflow.com/a/41827681/2980105
88
- elif source.format == 'GIF' and source.is_animated and ".gif" in fileout:
89
110
 
90
- p = source.getpalette()
91
- 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
+
92
119
  try:
93
120
  while True:
94
- # If the GIF uses local colour tables, each frame will have its own palette.
95
- # If not, we need to apply the global palette to the new frame.
96
- if not source.getpalette():
97
- source.putpalette(p)
98
-
99
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
100
- new_frame.paste(source, (0,0), source.convert('RGBA'))
101
- new_frame.thumbnail(size, Image.ANTIALIAS)
102
-
103
- if fixed:
104
- offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
105
- offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
106
- final_frame = Image.new('RGBA', size, (255,255,255,1))
107
- final_frame.paste(new_frame, (offset_x, offset_y))
108
- frames.append(final_frame)
109
- else:
110
- frames.append(new_frame)
111
-
112
- b = source.tell()
113
- source.seek(b + 1)
114
- 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():
115
128
  break
116
129
  except EOFError:
117
130
  pass
118
- # Animated WEBP resizing (since PIL 5.0.0)
119
- # based on https://stackoverflow.com/a/41827681/2980105
120
- elif source.format == 'WEBP' and ".webp" in fileout:
121
131
 
122
- 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
+
123
140
  try:
124
141
  while True:
125
- new_frame = Image.new('RGBA', source.size, (255,255,255,1))
126
- new_frame.paste(source, (0,0), source.convert('RGBA'))
127
- new_frame.thumbnail(size, Image.ANTIALIAS)
128
-
129
- if fixed:
130
- offset_x = int(max((size[0] - new_frame.size[0]) / 2, 0))
131
- offset_y = int(max((size[1] - new_frame.size[1]) / 2, 0))
132
- final_frame = Image.new('RGBA', size, (255,255,255,1))
133
- final_frame.paste(new_frame, (offset_x, offset_y))
134
- frames.append(final_frame)
135
- else:
136
- frames.append(new_frame)
137
-
138
- b = source.tell()
139
- source.seek(b + 1)
140
- 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():
141
149
  break
142
150
  except EOFError:
143
151
  pass
144
- # Standard resizing
145
- elif fixed:
146
- source.thumbnail(size, Image.ANTIALIAS)
147
- offset_x = int(max((size[0] - source.size[0]) / 2, 0))
148
- offset_y = int(max((size[1] - source.size[1]) / 2, 0))
149
- dest = Image.new('RGBA', size, (255,255,255,1))
150
- dest.paste(source, (offset_x, offset_y))
151
- else:
152
- source.thumbnail(size, Image.ANTIALIAS)
153
- dest = Image.new('RGBA', (source.size[0], source.size[1]), (255,255,255,1))
154
- dest.paste(source)
155
152
 
153
+ return dest
154
+
155
+
156
+ # File saving
156
157
  # https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html
157
- # source.info.get('loop')=None
158
- 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
+
159
172
  # The image quality, on a scale from 0 (best-speed) to 9 (best-compression), the default is 6
160
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
+
161
179
  try:
162
- if len(frames) == 1:
163
- 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)
164
182
  else:
165
- 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:],
166
184
  loop=0, duration=source.info.get('duration'))
167
185
  except:
168
186
  dest.save(fileout, 'PNG', optimize=True, compress_level=9)
169
- elif ".gif" in fileout:
170
- try:
171
- if len(frames) == 1:
172
- frames[0].save(fileout, 'GIF', optimize=True)
173
- else:
174
- frames[0].save(fileout, 'GIF', optimize=True, save_all=True, append_images=frames[1:],
175
- loop=0, duration=source.info.get('duration'), default_image=source.info.get('default_image'))
176
- except:
177
- dest.save(fileout, 'GIF', optimize=True)
178
- elif ".webp" in fileout:
187
+
188
+ def saveWebp(dest, fileout, quality):
189
+
179
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)
180
191
  # The method, on a scale from 0 (fast) to 6 (slower-better), the default is 0
181
192
  if quality < 1:
182
193
  quality = 80
183
194
  elif quality > 100:
184
195
  quality = 100
196
+
185
197
  try:
186
- if len(frames) == 1:
187
- frames[0].save(fileout, 'WEBP', method=5, quality=quality)
198
+ if len(dest) == 1:
199
+ dest[0].save(fileout, 'WEBP', method=5, quality=quality)
188
200
  else:
189
- 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:],
190
202
  loop=0, duration=source.info.get('duration'))
191
203
  except:
192
204
  dest.save(fileout, 'WEBP', method=5, quality=quality)
193
- else:
205
+
206
+ def saveJpg(dest, fileout, quality):
207
+
194
208
  # The image quality, on a scale from 0 (worst) to 95 (best), the default is 75
195
209
  if quality < 1:
196
210
  quality = 75
197
211
  elif quality > 95:
198
212
  quality = 95
199
- 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', ''))
200
248
 
201
249
  exit(0)