diff --git a/.gitignore b/.gitignore
index 0cd20816e3375a8a97df93c45205003dfa43bf78..ef7d6d066680ad1da8a8203a9eb10b86a94c4f13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 *.pyc
 *.bak
+*.blend[1-9]
 /temp/
 /docs/site/
 
diff --git a/packages/flamenco-worker-python/flamenco_worker/merge-exr.blend b/packages/flamenco-worker-python/flamenco_worker/merge-exr.blend
index fc4dbb90713bc25107e5351c20f69fe852ff1c54..2dda49f47950de3bc5f9a6959b62dab5c3b27b59 100644
Binary files a/packages/flamenco-worker-python/flamenco_worker/merge-exr.blend and b/packages/flamenco-worker-python/flamenco_worker/merge-exr.blend differ
diff --git a/packages/flamenco-worker-python/flamenco_worker/runner.py b/packages/flamenco-worker-python/flamenco_worker/runner.py
index 526bd96b9bd8bef5bd10ec3324d36bf81c179f5d..5a656c64754a2156987ebf9477331086b79dae91 100644
--- a/packages/flamenco-worker-python/flamenco_worker/runner.py
+++ b/packages/flamenco-worker-python/flamenco_worker/runner.py
@@ -15,6 +15,26 @@ from . import worker
 # Timeout of subprocess.stdout.readline() call.
 SUBPROC_READLINE_TIMEOUT = 3600  # seconds
 
+MERGE_EXR_PYTHON = """\
+import bpy
+scene = bpy.context.scene
+nodes = scene.node_tree.nodes
+image1 = bpy.data.images.load("%(input1)s")
+image2 = bpy.data.images.load("%(input2)s")
+
+nodes["image1"].image = image1
+nodes["image2"].image = image2
+nodes["weight1"].outputs[0].default_value = %(weight1)i
+nodes["weight2"].outputs[0].default_value = %(weight2)i
+
+nodes["output"].base_path = "%(tmpdir)s"
+scene.render.resolution_x, scene.render.resolution_y = image1.size
+scene.render.tile_x, scene.render.tile_y = image1.size
+scene.render.filepath = "%(tmpdir)s/preview.jpg"
+
+bpy.ops.render.render(write_still=True)
+"""
+
 command_handlers = {}
 
 
@@ -201,7 +221,7 @@ class AbstractCommand(metaclass=abc.ABCMeta):
 
         return None
 
-    def _setting(self, settings: dict, key: str, is_required: bool, valtype=str) -> (
+    def _setting(self, settings: dict, key: str, is_required: bool, valtype: typing.Type = str) -> (
             typing.Any, typing.Optional[str]):
         """Parses a setting, returns either (value, None) or (None, errormsg)"""
 
@@ -561,18 +581,36 @@ class MergeExrCommand(AbstractSubprocessCommand):
             return 'blender_cmd %r does not exist' % cmd[0]
         settings['blender_cmd'] = cmd
 
-        _, err = self._setting(settings, 'input1', True, str)
+        input1, err = self._setting(settings, 'input1', True, str)
+        if err: return err
+        if '"' in input1:
+            return 'Double quotes are not allowed in filenames: %r' % input1
+        if not Path(input1).exists():
+            return 'Input 1 %r does not exist' % input1
+        input2, err = self._setting(settings, 'input2', True, str)
         if err: return err
-        _, err = self._setting(settings, 'input2', True, str)
+        if '"' in input2:
+            return 'Double quotes are not allowed in filenames: %r' % input2
+        if not Path(input2).exists():
+            return 'Input 2 %r does not exist' % input2
+
+        output, err = self._setting(settings, 'output', True, str)
         if err: return err
+        if '"' in output:
+            return 'Double quotes are not allowed in filenames: %r' % output
+
         _, err = self._setting(settings, 'weight1', True, int)
         if err: return err
+
         _, err = self._setting(settings, 'weight2', True, int)
         if err: return err
 
     async def execute(self, settings: dict):
-        import shlex
-        await self.subprocess(shlex.split(settings['cmd']))
+        from pathlib import Path
+        import shutil
+        import tempfile
+
+        blendpath = Path(__file__).with_name('merge-exr.blend')
 
         cmd = settings['blender_cmd'][:]
         cmd += [
@@ -580,11 +618,26 @@ class MergeExrCommand(AbstractSubprocessCommand):
             '--enable-autoexec',
             '-noaudio',
             '--background',
+            str(blendpath),
         ]
 
-        # TODO: set up node properties and render settings.
+        # set up node properties and render settings.
+        output = Path(settings['output'])
+        output.mkdir(parents=True, exist_ok=True)
 
-        await self.worker.register_task_update(activity='Starting Blender')
-        await self.subprocess(cmd)
+        with tempfile.TemporaryDirectory(dir=str(output)) as tmpdir:
+            tmppath = Path(tmpdir)
+            assert tmppath.exists()
+
+            settings['tmpdir'] = tmpdir
+            cmd += [
+                '--python-expr', MERGE_EXR_PYTHON % settings
+            ]
+
+            await self.worker.register_task_update(activity='Starting Blender to merge EXR files')
+            await self.subprocess(cmd)
+
+            # move output files into the correct spot.
+            shutil.move(str(tmppath / 'merged0001.exr'), str(output))
+            shutil.move(str(tmppath / 'preview.jpg'), str(output.with_suffix('.jpg')))
 
-        # TODO: move output files into the correct spot.
diff --git a/packages/flamenco-worker-python/tests/Corn field-1k.exr b/packages/flamenco-worker-python/tests/Corn field-1k.exr
new file mode 100644
index 0000000000000000000000000000000000000000..cd51fdc35a2905a2b3e47192cd1b7137e6538629
Binary files /dev/null and b/packages/flamenco-worker-python/tests/Corn field-1k.exr differ
diff --git a/packages/flamenco-worker-python/tests/Deventer-1k.exr b/packages/flamenco-worker-python/tests/Deventer-1k.exr
new file mode 100644
index 0000000000000000000000000000000000000000..78a6166870b98cac8354c2a976d339c887ab946c
Binary files /dev/null and b/packages/flamenco-worker-python/tests/Deventer-1k.exr differ
diff --git a/packages/flamenco-worker-python/tests/abstract_worker_test.py b/packages/flamenco-worker-python/tests/abstract_worker_test.py
index a2def8c6764fcbf4958b51ef340ea5d1d4c171f6..582a8a152da8720228efa257888a649345311744 100644
--- a/packages/flamenco-worker-python/tests/abstract_worker_test.py
+++ b/packages/flamenco-worker-python/tests/abstract_worker_test.py
@@ -10,3 +10,19 @@ class AbstractWorkerTest(unittest.TestCase):
             level=logging.DEBUG,
             format='%(asctime)-15s %(levelname)8s %(name)s %(message)s',
         )
+
+    def find_blender_cmd(self):
+        import os
+        import platform
+
+        if platform.system() == 'Windows':
+            blender = 'blender.exe'
+        else:
+            blender = 'blender'
+
+        for path in os.getenv('PATH').split(os.path.pathsep):
+            full_path = path + os.sep + blender
+            if os.path.exists(full_path):
+                return full_path
+
+        self.fail('Unable to find "blender" executable on $PATH')
diff --git a/packages/flamenco-worker-python/tests/test_runner_merge_exr.py b/packages/flamenco-worker-python/tests/test_runner_merge_exr.py
new file mode 100644
index 0000000000000000000000000000000000000000..294ba88742c15a236d36e65d7fcc0d3d4a333957
--- /dev/null
+++ b/packages/flamenco-worker-python/tests/test_runner_merge_exr.py
@@ -0,0 +1,44 @@
+from pathlib import Path
+
+from test_runner import AbstractCommandTest
+
+
+class MergeExrCommandTest(AbstractCommandTest):
+    def setUp(self):
+        super().setUp()
+
+        from flamenco_worker.runner import MergeExrCommand
+        import tempfile
+
+        self.tmpdir = tempfile.TemporaryDirectory()
+        self.mypath = Path(__file__).parent
+
+        self.cmd = MergeExrCommand(
+            worker=self.fworker,
+            task_id='12345',
+            command_idx=0,
+        )
+
+    def tearDown(self):
+        super().tearDown()
+        del self.tmpdir
+
+    def test_happy_flow(self):
+        output = Path(self.tmpdir.name) / 'merged.exr'
+
+        settings = {
+            'blender_cmd': self.find_blender_cmd(),
+            'input1': str(self.mypath / 'Corn field-1k.exr'),
+            'input2': str(self.mypath / 'Deventer-1k.exr'),
+            'weight1': 20,
+            'weight2': 100,
+            'output': str(output)
+        }
+
+        task = self.cmd.run(settings)
+        ok = self.loop.run_until_complete(task)
+        self.assertTrue(ok)
+
+        # Assuming that if the files exist, the merge was ok.
+        self.assertTrue(output.exists())
+        self.assertTrue(output.with_suffix('.jpg').exists())