diff --git a/include/libcamera/formats.h.in b/include/libcamera/formats.h.in
index 6ae7634fe..5ff9c3bf4 100644
--- a/include/libcamera/formats.h.in
+++ b/include/libcamera/formats.h.in
@@ -35,7 +35,9 @@ constexpr uint64_t __mod(unsigned int vendor, unsigned int mod)
 
 } /* namespace */
 
-${formats}
+{% for f in formats %}
+constexpr PixelFormat {{f.name}}(__fourcc({{f.fourcc}}), __mod({{f.mod}}));
+{%- endfor %}
 
 } /* namespace formats */
 
diff --git a/utils/codegen/gen-formats.py b/utils/codegen/gen-formats.py
index 0c0932a5b..872f3fe34 100755
--- a/utils/codegen/gen-formats.py
+++ b/utils/codegen/gen-formats.py
@@ -7,6 +7,7 @@
 # Generate formats definitions from YAML
 
 import argparse
+import jinja2
 import re
 import string
 import sys
@@ -52,9 +53,7 @@ class DRMFourCC(object):
         return self.vendors[vendor], value
 
 
-def generate_h(formats, drm_fourcc):
-    template = string.Template('constexpr PixelFormat ${name}{ __fourcc(${fourcc}), __mod(${mod}) };')
-
+def generate_formats(formats, drm_fourcc):
     fmts = []
 
     for format in formats:
@@ -73,24 +72,17 @@ def generate_h(formats, drm_fourcc):
         if mod:
             data['mod'] = '%u, %u' % drm_fourcc.mod(mod)
 
-        fmts.append(template.substitute(data))
-
-    return {'formats': '\n'.join(fmts)}
-
-
-def fill_template(template, data):
+        fmts.append(data)
 
-    template = open(template, 'rb').read()
-    template = template.decode('utf-8')
-    template = string.Template(template)
-    return template.substitute(data)
+    return fmts
 
 
 def main(argv):
 
     # Parse command line arguments
     parser = argparse.ArgumentParser()
-    parser.add_argument('-o', dest='output', metavar='file', type=str,
+    parser.add_argument('-o', dest='output', metavar='file',
+                        type=argparse.FileType('w', encoding='utf-8'), default=sys.stdout,
                         help='Output file name. Defaults to standard output if not specified.')
     parser.add_argument('input', type=str,
                         help='Input file name.')
@@ -104,15 +96,13 @@ def main(argv):
     formats = yaml.safe_load(data)['formats']
     drm_fourcc = DRMFourCC(args.drm_fourcc)
 
-    data = generate_h(formats, drm_fourcc)
-    data = fill_template(args.template, data)
+    env = jinja2.Environment()
+    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())
+    string = template.render({
+        'formats': generate_formats(formats, drm_fourcc),
+    })
 
-    if args.output:
-        output = open(args.output, 'wb')
-        output.write(data.encode('utf-8'))
-        output.close()
-    else:
-        sys.stdout.write(data)
+    args.output.write(string)
 
     return 0
 
