diff --git a/TOOLS/__init__.py b/TOOLS/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/TOOLS/file2string.py b/TOOLS/file2string.py
index 6cdd1a72ae..6f068caa43 100755
--- a/TOOLS/file2string.py
+++ b/TOOLS/file2string.py
@@ -5,23 +5,27 @@
 # of every string, so code using the string may need to remove that to get
 # the exact contents of the original file.
 
+from __future__ import unicode_literals
 import sys
 
 # Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
 def pord(c):
     return ord(c) if type(c) == str else c
 
-def main(infile):
+def file2string(infilename, infile, outfile):
+    outfile.write("// Generated from %s\n\n" % infilename)
+
     conv = ['\\' + ("%03o" % c) for c in range(256)]
     safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
                  "0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
+
     for c in safe_chars:
         conv[ord(c)] = c
     for c, esc in ("\nn", "\tt", r"\\", '""'):
         conv[ord(c)] = '\\' + esc
     for line in infile:
-        sys.stdout.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
+        outfile.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
 
-with open(sys.argv[1], 'rb') as infile:
-    sys.stdout.write("// Generated from %s\n\n" % sys.argv[1])
-    main(infile)
+if __name__ == "__main__":
+    with open(sys.argv[1], 'rb') as infile:
+        main(sys.argv[1], infile, sys.stdout)
diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py
index 6e843560da..3f8ebd7dd6 100755
--- a/TOOLS/matroska.py
+++ b/TOOLS/matroska.py
@@ -285,8 +285,8 @@ parse_elems(elements_ebml, 'EBML')
 parse_elems(elements_matroska, 'MATROSKA')
 
 def printf(out, *args):
-    out.write(' '.join([str(x) for x in args]))
-    out.write('\n')
+    out.write(u' '.join([str(x) for x in args]))
+    out.write(u'\n')
 
 def generate_C_header(out):
     printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
diff --git a/waftools/generators/sources.py b/waftools/generators/sources.py
index b6af693e65..5d3349c442 100644
--- a/waftools/generators/sources.py
+++ b/waftools/generators/sources.py
@@ -1,14 +1,10 @@
 from waflib.Build import BuildContext
+from waflib import TaskGen
+from io import StringIO
+from TOOLS.matroska import generate_C_header, generate_C_definitions
+from TOOLS.file2string import file2string
 import os
 
-def __file2string_cmd__(ctx):
-    return '"${{BIN_PYTHON}}" "{0}/TOOLS/file2string.py" "${{SRC}}" > "${{TGT}}"' \
-                .format(ctx.srcnode.abspath())
-
-def __matroska_cmd__(ctx, argument):
-    return '"${{BIN_PYTHON}}" "{0}/TOOLS/matroska.py" "{1}" "${{SRC}}" > "${{TGT}}"' \
-                .format(ctx.srcnode.abspath(), argument)
-
 def __zshcomp_cmd__(ctx, argument):
     return '"${{BIN_PERL}}" "{0}/TOOLS/zsh.pl" "{1}" > "${{TGT}}"' \
                 .format(ctx.srcnode.abspath(), argument)
@@ -21,20 +17,30 @@ def __file2string__(ctx, **kwargs):
         **kwargs
     )
 
-def __matroska_header__(ctx, **kwargs):
-    ctx(
-        rule   = __matroska_cmd__(ctx, '--generate-header'),
-        before = ("c",),
-        name   = os.path.basename(kwargs['target']),
-        **kwargs
-    )
+def execf(self, fn):
+    setattr(self, 'rule', ' ') # waf doesn't print the task with no rule
+    target = getattr(self, 'target', None)
+    out = self.path.find_or_declare(target)
+    tmp = StringIO()
+    fn(tmp)
+    out.write(tmp.getvalue())
+    tmp.close()
 
-def __matroska_definitions__(ctx, **kwargs):
-    ctx(
-        rule   = __matroska_cmd__(ctx, '--generate-definitions'),
-        before = ("c",),
-        **kwargs
-    )
+@TaskGen.feature('file2string')
+def f2s(self):
+    def fn(out):
+        source = getattr(self, 'source', None)
+        src = self.path.find_resource(source)
+        file2string(source, iter(src.read().splitlines()), out)
+    execf(self, fn)
+
+@TaskGen.feature('ebml_header')
+def ebml_header(self):
+    execf(self, generate_C_header)
+
+@TaskGen.feature('ebml_definitions')
+def ebml_definitions(self):
+    execf(self, generate_C_definitions)
 
 def __zshcomp__(ctx, **kwargs):
     ctx(
@@ -44,7 +50,5 @@ def __zshcomp__(ctx, **kwargs):
         **kwargs
     )
 
-BuildContext.file2string          = __file2string__
-BuildContext.matroska_header      = __matroska_header__
-BuildContext.matroska_definitions = __matroska_definitions__
-BuildContext.zshcomp              = __zshcomp__
+BuildContext.file2string = __file2string__
+BuildContext.zshcomp = __zshcomp__
diff --git a/wscript b/wscript
index 94db7e296f..086b84fd28 100644
--- a/wscript
+++ b/wscript
@@ -996,7 +996,6 @@ def configure(ctx):
     ctx.find_program(cc,          var='CC')
     ctx.find_program(pkg_config,  var='PKG_CONFIG')
     ctx.find_program(ar,          var='AR')
-    ctx.find_program('python',    var='BIN_PYTHON')
     ctx.find_program('rst2html',  var='RST2HTML',  mandatory=False)
     ctx.find_program('rst2man',   var='RST2MAN',   mandatory=False)
     ctx.find_program('rst2pdf',   var='RST2PDF',   mandatory=False)
diff --git a/wscript_build.py b/wscript_build.py
index b7c7b39464..52871c2031 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -51,39 +51,64 @@ def build(ctx):
     ctx.load('waf_customizations')
     ctx.load('generators.sources')
 
-    ctx.file2string(
+    ctx(
+        features = "file2string",
         source = "TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns",
-        target = "osdep/macosx_icon.inc")
+        target = "osdep/macosx_icon.inc",
+        before = ['c']
+    )
 
-    ctx.file2string(
+    ctx(
+        features = "file2string",
         source = "video/out/x11_icon.bin",
-        target = "video/out/x11_icon.inc")
+        target = "video/out/x11_icon.inc",
+        before = ['c']
+    )
 
-    ctx.file2string(
+    ctx(
+        features = "file2string",
         source = "etc/input.conf",
-        target = "input/input_conf.h")
+        target = "input/input_conf.h",
+        before = ['c']
+    )
 
-    ctx.file2string(
+    ctx(
+        features = "file2string",
         source = "etc/builtin.conf",
-        target = "player/builtin_conf.inc")
+        target = "player/builtin_conf.inc",
+        before = ['c']
+    )
 
-    ctx.file2string(
+    ctx(
+        features = "file2string",
         source = "sub/osd_font.otf",
-        target = "sub/osd_font.h")
+        target = "sub/osd_font.h",
+        before = ['c']
+    )
 
     lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua",
                  "ytdl_hook.lua"]
+
     for fn in lua_files:
         fn = "player/lua/" + fn
-        ctx.file2string(source = fn, target = os.path.splitext(fn)[0] + ".inc")
+        ctx(
+            features = "file2string",
+            source = fn,
+            target = os.path.splitext(fn)[0] + ".inc",
+            before = ['c']
+        )
 
-    ctx.matroska_header(
-        source = "demux/ebml.c demux/demux_mkv.c",
-        target = "ebml_types.h")
+    ctx(
+        features = "ebml_header",
+        target = "ebml_types.h",
+        before = ['c']
+    )
 
-    ctx.matroska_definitions(
-        source = "demux/ebml.c",
-        target = "ebml_defs.c")
+    ctx(
+        features = "ebml_definitions",
+        target = "ebml_defs.c",
+        before = ['c']
+    )
 
     if ctx.env.DEST_OS == 'win32':
         main_fn_c = 'osdep/main-fn-win.c'