-
Notifications
You must be signed in to change notification settings - Fork 169
Expand file tree
/
Copy pathnemo_convert_png.py
More file actions
190 lines (152 loc) · 6.09 KB
/
nemo_convert_png.py
File metadata and controls
190 lines (152 loc) · 6.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
'''Extension for Nemo's context menu to easily convert images to PNG and
optimize their filesize with pngcrush.'''
from __future__ import annotations
import os
import subprocess
from urllib.parse import unquote_plus, urlparse
from PIL import Image, UnidentifiedImageError
import PySimpleGUI as sg
import gi
gi.require_version('Nemo', '3.0')
from gi.repository import GObject, Nemo # type: ignore pylint: disable=wrong-import-position
EXTENSIONS = ('jpg', 'jpeg', 'gif', 'tiff', 'bmp', 'png')
uqp= unquote_plus
def get_files(files_in: list[GObject]) -> list[str]|None:
"""
Retrieve filenames as cross-platform safe strings from file objects.
:param files_in: List of file objects.
"""
files = []
for file_in in files_in:
file_in = unquote_plus(file_in.get_uri()[7:])
if os.path.isfile(file_in):
files.append(file_in)
if files:
return files
return None
def convert_one(file: str) -> None:
'''
Converts an image to a PNG.
:param file: Filename of the image to convert.
'''
filename = f'{file.split(".")[-2]}.png'
try:
img = Image.open(file).convert('RGB')
except UnidentifiedImageError:
img = False
if img:
os.remove(file)
img.save(filename, 'PNG')
def convert_images(_, files: list[str]) -> list[str]:
'''
Called by the context menu item "Convert selected image(s) to PNG".
:param files: The currently selected files.
'''
filenames = [f'{file.split(".")[-2]}.png' for file in files]
count = sum(not file.endswith('png') for file in files)
for i, file in enumerate(files):
if not file.endswith('png'):
sg.OneLineProgressMeter('Please wait...', i+1, count, 'pb', 'Converting images', orientation='h')
convert_one(file)
sg.OneLineProgressMeter('', count, count, key='pb')
return filenames
def crush_one(file: str) -> None:
'''
Runs pngcrush on a png file.
:param file: The file to execute this action on.
'''
subprocess.run(['pngcrush', '-rem', 'alla', '-nofilecheck', '-fix', '-ow',
'-reduce', '-m', '0', file], check=False)
def crush_images(_, files: list[str]) -> None:
'''
Called by the context menu item "Optimize image(s) with pngcrush.
:param files: The currently selected files.
'''
for i, file in enumerate(files):
sg.OneLineProgressMeter('Please wait...', i+1, len(files), 'pb',
'Optimize images with pngcrush', orientation='h')
crush_one(file)
sg.OneLineProgressMeter('', len(files), len(files), key='pb')
def convert_and_crush(_, files: list[str]) -> None:
'''
Called by the context menu item "Convert to PNG and optimize.
:param files: The currently selected files.
'''
converted = convert_images(None, files)
crush_images(None, converted)
class PNGConverter(GObject.GObject, Nemo.MenuProvider):
'''Class for extension context menu items.'''
def __init__(self):
'''File manager crashes if init is not called.'''
...
def get_background_items( # pylint: disable=arguments-differ
self, _, folder: GObject) -> list[Nemo.MenuItem]|None:
'''
Called when context menu is called with no file objects selected.
:param folder: Nemo's current working directory.
'''
folder = urlparse(folder.get_uri()).path
files = [uqp(os.path.join(folder, f))
for f in os.listdir(uqp(folder))
if os.path.isfile(uqp(os.path.join(folder, f))) and
f.lower().endswith(EXTENSIONS)]
if all(file.endswith('png') for file in files):
crush = Nemo.MenuItem(
name='CrushImages',
label='Optimize image(s) with pngcrush',
tip='Optimize image filesizes with pngcrush'
)
crush.connect('activate', crush_images, files)
return [crush]
if any(file.endswith(EXTENSIONS) for file in files):
convert = Nemo.MenuItem(
name="ConvertAllImagestoPNG",
label="Convert all images to PNG",
tip="Convert all images to PNG"
)
convert.connect('activate', convert_images, files)
crush = Nemo.MenuItem(
name='ConvertandCrush',
label="Convert images to PNG and optimize",
tip="Convert images to PNG and optimize filesizes with pngcrush"
)
crush.connect('activate', convert_and_crush, files)
return [convert, crush]
def get_file_items( # pylint: disable=arguments-differ
self, _, files: list[GObject]) -> list[Nemo.MenuItem]|None:
'''
Called when context menu is called with files selected.
:param files: The currently selected file objects.
'''
files = get_files(files) # type: ignore
try:
is_iter = iter(files)
check = all(file.lower().endswith('png') for file in files)
except TypeError:
is_iter = False
check = False
if check:
convert = Nemo.MenuItem(
name="CrushImages",
label="Optimize image(s) with pngcrush",
tip="Optimize filesize(s) with pngcrush"
)
convert.connect('activate', crush_images, files)
return [convert]
if is_iter:
check = all(file.lower().endswith(EXTENSIONS) for file in files)
if check:
convert = Nemo.MenuItem(
name="ConvertImagetoPNG",
label="Convert selected image(s) to .png",
tip="Convert image(s) to .png"
)
convert.connect('activate', convert_images, files)
crush = Nemo.MenuItem(
name="ConvertandCrush",
label="Convert to PNG and optimize with pngcrush",
tip="Convert image(s) to PNG and optimize filesize(s) with\
pngcrush"
)
crush.connect('activate', convert_and_crush, files)
return [convert, crush]