Skip to content

Commit 8a62c32

Browse files
authored
Merge pull request #33 from vgalin/V2.0.1
V2.0.1
2 parents c3c7bd8 + 3e53272 commit 8a62c32

File tree

7 files changed

+220
-54
lines changed

7 files changed

+220
-54
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ hti = Html2Image()
4949
<summary> Multiple arguments can be passed to the constructor (click to expand):</summary>
5050

5151
- `browser` : Browser that will be used, set by default to `'chrome'` (the only browser supported by HTML2Image at the moment)
52-
- `browser_path` : The path or the command that can be used to find the executable of a specific browser.
52+
- `browser_executable` : The path or the command that can be used to find the executable of a specific browser.
5353
- `output_path` : Path to the folder to which taken screenshots will be outputed. Default is the current working directory of your python program.
5454
- `size` : 2-Tuple reprensenting the size of the screenshots that will be taken. Default value is `(1920, 1080)`.
5555
- `temp_path` : Path that will be used to put together different resources when screenshotting strings of files. Default value is `%TEMP%/html2image` on Windows, and `/tmp/html2image` on Linux and MacOS.

html2image/browsers/browser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ def __init__(self, flags):
99

1010
@property
1111
@abstractmethod
12-
def executable_path(self):
12+
def executable(self):
1313
pass
1414

15-
@executable_path.setter
15+
@executable.setter
1616
@abstractmethod
17-
def executable_path(self, value):
17+
def executable(self, value):
1818
pass
1919

2020
@abstractmethod

html2image/browsers/chrome.py

Lines changed: 113 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
from .browser import Browser
2+
from .search_utils import get_command_origin, find_first_defined_env_var
23

34
import subprocess
45
import platform
56
import os
67
import shutil
78

9+
ENV_VAR_LOOKUP_TOGGLE = 'HTML2IMAGE_TOGGLE_ENV_VAR_LOOKUP'
810

9-
def _find_chrome(user_given_path=None):
11+
CHROME_EXECUTABLE_ENV_VAR_CANDIDATES = [
12+
'HTML2IMAGE_CHROME_BIN',
13+
'HTML2IMAGE_CHROME_EXE',
14+
'CHROME_BIN',
15+
'CHROME_EXE',
16+
]
17+
18+
19+
def _find_chrome(user_given_executable=None):
1020
""" Finds a Chrome executable.
1121
1222
Search Chrome on a given path. If no path given,
1323
try to find Chrome or Chromium-browser on a Windows or Unix system.
1424
25+
Parameters
26+
----------
27+
- `user_given_executable`: str (optional)
28+
+ A filepath leading to a Chrome/ Chromium executable
29+
+ Or a filename found in the current working directory
30+
+ Or a keyword that executes Chrome/ Chromium, ex:
31+
- 'chromium' on linux systems
32+
- 'chrome' on windows (if typing `start chrome` in a cmd works)
33+
1534
Raises
1635
------
1736
- `FileNotFoundError`
@@ -23,15 +42,58 @@ def _find_chrome(user_given_path=None):
2342
+ Path of the chrome executable on the current machine.
2443
"""
2544

26-
# TODO when other browsers will be available:
27-
# Ensure that the given executable is a chrome one.
45+
# try to find a chrome bin/exe in ENV
46+
path_from_env = find_first_defined_env_var(
47+
env_var_list=CHROME_EXECUTABLE_ENV_VAR_CANDIDATES,
48+
toggle=ENV_VAR_LOOKUP_TOGGLE
49+
)
2850

29-
if user_given_path is not None:
30-
if os.path.isfile(user_given_path):
31-
return user_given_path
51+
if path_from_env:
52+
print(
53+
f'Found a potential chrome executable in the {path_from_env} '
54+
f'environment variable:\n{path_from_env}\n'
55+
)
56+
return path_from_env
57+
58+
# if an executable is given, try to use it
59+
if user_given_executable is not None:
60+
61+
# On Windows, we cannot "safely" validate that user_given_executable
62+
# seems to be a chrome executable, as we cannot run it with
63+
# the --version flag.
64+
# https://bugs.chromium.org/p/chromium/issues/detail?id=158372
65+
#
66+
# We thus do the "bare minimum" and check if user_given_executable
67+
# is a file, a filepath, or corresponds to a keyword that can be used
68+
# with the start command, like so: `start user_given_executable`
69+
if platform.system() == 'Windows':
70+
command_origin = get_command_origin(user_given_executable)
71+
if command_origin:
72+
return command_origin
73+
74+
# cannot validate user_given_executable
75+
raise FileNotFoundError()
76+
77+
# On a non-Windows OS, we can validate in a basic way that
78+
# user_given_executable leads to a Chrome / Chromium executable,
79+
# or is a command, using the --version flag
3280
else:
33-
raise FileNotFoundError('Could not find chrome in the given path.')
81+
try:
82+
if 'chrom' in subprocess.check_output(
83+
[user_given_executable, '--version']
84+
).decode('utf-8').lower():
85+
return user_given_executable
86+
except Exception:
87+
pass
88+
89+
# We got a user_given_executable but couldn't validate it
90+
raise FileNotFoundError(
91+
'Failed to find a seemingly valid chrome executable '
92+
'in the given path.'
93+
)
3494

95+
# Executable not in ENV or given by the user, try to find it
96+
# Search for executable on a Windows OS
3597
if platform.system() == 'Windows':
3698
prefixes = [
3799
os.getenv('PROGRAMFILES(X86)'),
@@ -46,45 +108,54 @@ def _find_chrome(user_given_path=None):
46108
if os.path.isfile(path_candidate):
47109
return path_candidate
48110

111+
# Search for executable on a Linux OS
49112
elif platform.system() == "Linux":
50113

51-
# search google-chrome
52-
version_result = subprocess.check_output(
53-
["google-chrome", "--version"]
54-
)
55-
56-
if 'Google Chrome' in str(version_result):
57-
return "google-chrome"
114+
chrome_commands = [
115+
'chromium',
116+
'chromium-browser',
117+
'chrome',
118+
'google-chrome'
119+
]
58120

59-
# else search chromium-browser
121+
for chrome_command in chrome_commands:
122+
if shutil.which(chrome_command):
123+
# check the --version for "chrom" ?
124+
return chrome_command
60125

61126
# snap seems to be a special case?
62127
# see https://stackoverflow.com/q/63375327/12182226
63-
version_result = subprocess.check_output(
64-
["chromium-browser", "--version"]
65-
)
66-
if 'snap' in str(version_result):
67-
chrome_snap = (
68-
'/snap/chromium/current/usr/lib/chromium-browser/chrome'
69-
)
70-
if os.path.isfile(chrome_snap):
71-
return chrome_snap
72-
else:
73-
which_result = shutil.which('chromium-browser')
74-
if which_result is not None and os.path.isfile(which_result):
75-
return which_result
76128

129+
try:
130+
version_result = subprocess.check_output(
131+
["chromium-browser", "--version"]
132+
)
133+
if 'snap' in str(version_result):
134+
chrome_snap = (
135+
'/snap/chromium/current/usr/lib/chromium-browser/chrome'
136+
)
137+
if os.path.isfile(chrome_snap):
138+
return chrome_snap
139+
except Exception:
140+
pass
141+
142+
# Search for executable on MacOS
77143
elif platform.system() == "Darwin":
78144
# MacOS system
79145
chrome_app = (
80146
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
81147
)
82-
version_result = subprocess.check_output(
83-
[chrome_app, "--version"]
84-
)
85-
if "Google Chrome" in str(version_result):
86-
return chrome_app
87148

149+
try:
150+
version_result = subprocess.check_output(
151+
[chrome_app, "--version"]
152+
)
153+
if "Google Chrome" in str(version_result):
154+
return chrome_app
155+
except Exception:
156+
pass
157+
158+
# Couldn't find an executable (or OS not in Windows, Linux or Mac)
88159
raise FileNotFoundError(
89160
'Could not find a Chrome executable on this '
90161
'machine, please specify it yourself.'
@@ -97,7 +168,7 @@ class ChromeHeadless(Browser):
97168
98169
Parameters
99170
----------
100-
- `executable_path` : str, optional
171+
- `executable` : str, optional
101172
+ Path to a chrome executable.
102173
103174
- `flags` : list of str
@@ -109,8 +180,8 @@ class ChromeHeadless(Browser):
109180
+ Whether or not to print the command used to take a screenshot.
110181
"""
111182

112-
def __init__(self, executable_path=None, flags=None, print_command=False):
113-
self.executable_path = executable_path
183+
def __init__(self, executable=None, flags=None, print_command=False):
184+
self.executable = executable
114185
if not flags:
115186
self.flags = [
116187
'--default-background-color=0',
@@ -122,12 +193,12 @@ def __init__(self, executable_path=None, flags=None, print_command=False):
122193
self.print_command = print_command
123194

124195
@property
125-
def executable_path(self):
126-
return self._executable_path
196+
def executable(self):
197+
return self._executable
127198

128-
@executable_path.setter
129-
def executable_path(self, value):
130-
self._executable_path = _find_chrome(value)
199+
@executable.setter
200+
def executable(self, value):
201+
self._executable = _find_chrome(value)
131202

132203
def screenshot(
133204
self,
@@ -171,7 +242,7 @@ def screenshot(
171242
# command used to launch chrome in
172243
# headless mode and take a screenshot
173244
command = [
174-
f'{self.executable_path}',
245+
f'{self.executable}',
175246
'--headless',
176247
f'--screenshot={os.path.join(output_path, output_file)}',
177248
f'--window-size={size[0]},{size[1]}',

html2image/browsers/firefox.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ def __init__(self):
1010
)
1111

1212
@property
13-
def executable_path(self):
13+
def executable(self):
1414
pass
1515

16-
@executable_path.setter
17-
def executable_path(self, value):
16+
@executable.setter
17+
def executable(self, value):
1818
pass
1919

2020
def render(self, **kwargs):
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import shutil
2+
import os
3+
try:
4+
from winreg import ConnectRegistry, OpenKey, QueryValueEx,\
5+
HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, KEY_READ
6+
except ImportError:
7+
# os is not Windows, and there is no need for winreg
8+
pass
9+
10+
11+
def get_command_origin(command):
12+
''' Finds the path of a given command (windows only).
13+
14+
This function is inspired by the `start` command on windows.
15+
It will search if the given command corresponds :
16+
- To an executable in the current directory
17+
- To an executable in the PATH environment variable
18+
- To an executable mentionned in the registry in the
19+
HKEY_LOCAL_MACHINE or HKEY_LOCAL_MACHINE hkeys in
20+
SOFTWARE\\[Wow6432Node\\]Microsoft\\Windows\\CurrentVersion\\App Paths\\
21+
22+
It also "indirectly" validates a path to a file.
23+
24+
Parameters
25+
----------
26+
- `command`: str
27+
+ command that will be searched.
28+
29+
Returns
30+
-------
31+
- str or None
32+
+ the path corresponding to `command`
33+
+ None, if no path was found
34+
'''
35+
36+
# `start "command"` itself could be used to run the command but we want to
37+
# assess that the command is a valid one.
38+
command = command.replace('start ', '')
39+
40+
# which search in current directory + path
41+
# and file extention can be ommited
42+
which_result = shutil.which(command)
43+
if which_result:
44+
return which_result
45+
46+
# search for command in registry
47+
48+
command = command.replace('.exe', '').strip()
49+
50+
# Once combined, these hkeys and keys are where the `start`
51+
# command seach for executable.
52+
hkeys = [HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER]
53+
keys = [
54+
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"
55+
f"App Paths\\{command}.exe",
56+
57+
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\"
58+
"CurrentVersion\\App Paths\\"
59+
f"{command}.exe",
60+
]
61+
62+
for hkey in hkeys:
63+
with ConnectRegistry(None, hkey) as registry:
64+
for key in keys:
65+
try:
66+
with OpenKey(registry, key, 0, KEY_READ) as k:
67+
# if no exception: we found the exe
68+
return QueryValueEx(k, '')[0]
69+
except Exception:
70+
# cannot open key, do nothing and proceed to the next one
71+
pass
72+
return None
73+
74+
75+
def find_first_defined_env_var(env_var_list, toggle):
76+
'''
77+
Returns the value of the first defined environment variable
78+
encountered in the `env_var_list` list, but only if the
79+
the `toggle` environment variableif defined.
80+
81+
Parameters
82+
----------
83+
- `env_var_list`: list[str]
84+
+ list of environment variable names
85+
- `toggle`: str
86+
+ environment variable name
87+
88+
89+
'''
90+
if toggle in os.environ:
91+
for env_var in env_var_list:
92+
value = os.environ.get(env_var)
93+
if value:
94+
return value
95+
return None

html2image/html2image.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Html2Image():
3737
+ Type of the browser that will be used to take screenshots.
3838
+ Default is Chrome.
3939
40-
- `browser_path` : str, optional
40+
- `browser_executable` : str, optional
4141
+ Path to a browser executable.
4242
4343
- `output_path` : str, optional
@@ -64,11 +64,11 @@ class Html2Image():
6464
def __init__(
6565
self,
6666
browser='chrome',
67-
browser_path=None,
67+
browser_executable=None,
6868
output_path=os.getcwd(),
6969
size=(1920, 1080),
7070
temp_path=None,
71-
custom_flags=[],
71+
custom_flags=None,
7272
):
7373

7474
if browser.lower() not in browser_map:
@@ -82,7 +82,7 @@ def __init__(
8282

8383
browser_class = browser_map[browser.lower()]
8484
self.browser = browser_class(
85-
executable_path=browser_path,
85+
executable=browser_executable,
8686
flags=custom_flags,
8787
)
8888

0 commit comments

Comments
 (0)