diff --git a/flamenco_worker/commands.py b/flamenco_worker/commands.py
index 5c3d4ae09ca38ad2212ec9796f0e5a2354305aaf..4cd97d2bf3d0f44ec57e17e75413d118f6cd6328 100644
--- a/flamenco_worker/commands.py
+++ b/flamenco_worker/commands.py
@@ -6,6 +6,7 @@ import asyncio.subprocess
 import datetime
 import logging
 import pathlib
+import platform
 import re
 import shlex
 import shutil
@@ -1026,6 +1027,7 @@ class BlenderRenderAudioCommand(BlenderRenderCommand):
 
 
 class AbstractFFmpegCommand(AbstractSubprocessCommand, abc.ABC):
+    index_file: typing.Optional[pathlib.Path] = None
 
     def validate(self, settings: Settings) -> typing.Optional[str]:
         # Check that FFmpeg can be found and shlex-split the string.
@@ -1045,6 +1047,14 @@ class AbstractFFmpegCommand(AbstractSubprocessCommand, abc.ABC):
         cmd = self._build_ffmpeg_command(settings)
         await self.subprocess(cmd)
 
+        if self.index_file is not None and self.index_file.exists():
+            try:
+                self.index_file.unlink()
+            except IOError:
+                msg = f'unable to unlink file {self.index_file}, ignoring'
+                await self.worker.register_log(msg)
+                self._log.warning(msg)
+
     def _build_ffmpeg_command(self, settings: Settings) -> typing.List[str]:
         assert isinstance(settings['ffmpeg_cmd'], list), \
             'run validate() before _build_ffmpeg_command'
@@ -1063,6 +1073,30 @@ class AbstractFFmpegCommand(AbstractSubprocessCommand, abc.ABC):
         """
         pass
 
+    def create_index_file(self, input_files: pathlib.Path) -> pathlib.Path:
+        """Construct a list of filenames for ffmpeg to process.
+
+        The filenames are stored in a file 'ffmpeg-input.txt' that sits in the
+        same directory as the input files.
+
+        It is assumed that 'input_files' contains a glob pattern in the file
+        name, and not in any directory parts.
+
+        The index file will be deleted after successful execution of the ffmpeg
+        command.
+        """
+
+        # The index file needs to sit next to the input files, as
+        # ffmpeg checks for 'unsafe paths'.
+        self.index_file = input_files.absolute().with_name('ffmpeg-input.txt')
+
+        with self.index_file.open('w') as outfile:
+            for file_path in sorted(input_files.parent.glob(input_files.name)):
+                escaped = str(file_path.name).replace("'", "\\'")
+                print("file '%s'" % escaped, file=outfile)
+
+        return self.index_file
+
 
 @command_executor('create_video')
 class CreateVideoCommand(AbstractFFmpegCommand):
@@ -1100,10 +1134,27 @@ class CreateVideoCommand(AbstractFFmpegCommand):
         return None
 
     def ffmpeg_args(self, settings: Settings) -> typing.List[str]:
+        input_files = Path(settings['input_files'])
+
         args = [
-            '-pattern_type', 'glob',
             '-r', str(settings['fps']),
-            '-i', settings['input_files'],
+        ]
+
+        if platform.system() == 'Windows':
+            # FFMpeg on Windows doesn't support globbing, so we have to do
+            # that in Python instead.
+            index_file = self.create_index_file(input_files)
+            args += [
+                '-f', 'concat',
+                '-i', index_file.as_posix(),
+            ]
+        else:
+            args += [
+                '-pattern_type', 'glob',
+                '-i', input_files.as_posix(),
+            ]
+
+        args += [
             '-c:v', self.codec_video,
             '-crf', str(self.constant_rate_factor),
             '-g', str(self.keyframe_interval),
@@ -1124,8 +1175,6 @@ class ConcatenateVideosCommand(AbstractFFmpegCommand):
     Requires FFmpeg to be installed and available with the 'ffmpeg' command.
     """
 
-    index_file: typing.Optional[Path] = None
-
     def validate(self, settings: Settings) -> typing.Optional[str]:
         err = super().validate(settings)
         if err:
@@ -1143,35 +1192,15 @@ class ConcatenateVideosCommand(AbstractFFmpegCommand):
 
         return None
 
-    async def execute(self, settings: Settings) -> None:
-        await super().execute(settings)
-
-        if self.index_file is not None and self.index_file.exists():
-            try:
-                self.index_file.unlink()
-            except IOError:
-                msg = f'unable to unlink file {self.index_file}, ignoring'
-                await self.worker.register_log(msg)
-                self._log.warning(msg)
-
     def ffmpeg_args(self, settings: Settings) -> typing.List[str]:
-        input_files = Path(settings['input_files']).absolute()
-        self.index_file = input_files.with_name('ffmpeg-input.txt')
-
-        # Construct the list of filenames for ffmpeg to process.
-        # The index file needs to sit next to the input files, as
-        # ffmpeg checks for 'unsafe paths'.
-        with self.index_file.open('w') as outfile:
-            for video_path in sorted(input_files.parent.glob(input_files.name)):
-                escaped = str(video_path.name).replace("'", "\\'")
-                print("file '%s'" % escaped, file=outfile)
+        index_file = self.create_index_file(Path(settings['input_files']))
 
         output_file = Path(settings['output_file'])
         self._log.debug('Output file: %s', output_file)
 
         args = [
             '-f', 'concat',
-            '-i', self.index_file.as_posix(),
+            '-i', index_file.as_posix(),
             '-c', 'copy',
             '-y',
             output_file.as_posix(),
diff --git a/tests/test_commands_create_video.py b/tests/test_commands_create_video.py
index 99efb1f43538bc93bbf385071a03688a6822a52f..8668c8abcca4d66612e3faeb1346bb123c0ae448 100644
--- a/tests/test_commands_create_video.py
+++ b/tests/test_commands_create_video.py
@@ -2,6 +2,7 @@ import logging
 import shutil
 import typing
 from pathlib import Path
+import platform
 import shlex
 import subprocess
 import sys
@@ -48,11 +49,21 @@ class CreateVideoTest(AbstractCommandTest):
         self.cmd.validate(self.settings)
         cliargs = self.cmd._build_ffmpeg_command(self.settings)
 
+        if platform.system() == 'Windows':
+            input_args = [
+                '-f', 'concat',
+                '-i', Path(self.settings['input_files']).absolute().with_name('ffmpeg-input.txt').as_posix(),
+            ]
+        else:
+            input_args = [
+                '-pattern_type', 'glob',
+                '-i', '/tmp/*.png',
+            ]
+
         self.assertEqual([
             Path(sys.executable).absolute().as_posix(), '-hide_banner',
-            '-pattern_type', 'glob',
             '-r', '24',
-            '-i', '/tmp/*.png',
+            *input_args,
             '-c:v', 'h264',
             '-crf', '23',
             '-g', '18',