summaryrefslogtreecommitdiffstats
path: root/TOOLS
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xTOOLS/docutils-wrapper.py67
-rwxr-xr-xTOOLS/dylib-unhell.py181
-rwxr-xr-xTOOLS/file2string.py44
-rwxr-xr-xTOOLS/gen-osd-font.sh9
-rwxr-xr-xTOOLS/idet.sh158
-rw-r--r--TOOLS/lua/README.md20
-rw-r--r--TOOLS/lua/acompressor.lua155
-rw-r--r--TOOLS/lua/ao-null-reload.lua20
-rw-r--r--TOOLS/lua/audio-hotplug-test.lua8
-rw-r--r--TOOLS/lua/autocrop.lua298
-rw-r--r--TOOLS/lua/autodeint.lua156
-rw-r--r--TOOLS/lua/autoload.lua328
-rw-r--r--TOOLS/lua/command-test.lua124
-rw-r--r--TOOLS/lua/cycle-deinterlace-pullup.lua56
-rw-r--r--TOOLS/lua/nan-test.lua37
-rw-r--r--TOOLS/lua/observe-all.lua22
-rw-r--r--TOOLS/lua/ontop-playback.lua19
-rw-r--r--TOOLS/lua/osd-test.lua35
-rw-r--r--TOOLS/lua/pause-when-minimize.lua20
-rw-r--r--TOOLS/lua/skip-logo.lua265
-rw-r--r--TOOLS/lua/status-line.lua92
-rw-r--r--TOOLS/lua/test-hooks.lua32
-rwxr-xr-xTOOLS/macos-sdk-version.py46
-rwxr-xr-xTOOLS/macos-swift-lib-directory.py42
-rwxr-xr-xTOOLS/matroska.py479
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/.notdef.glyph6
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/font.props77
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE001.glyph16
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE002.glyph22
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE003.glyph17
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE004.glyph20
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE005.glyph20
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE006.glyph47
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE007.glyph21
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE008.glyph37
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE009.glyph17
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE00A.glyph50
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE00B.glyph27
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE010.glyph21
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE011.glyph17
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE012.glyph21
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE013.glyph17
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE101.glyph16
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE104.glyph25
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE105.glyph25
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE106.glyph22
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE107.glyph56
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE108.glyph39
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE109.glyph39
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE10A.glyph36
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE10B.glyph26
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE10C.glyph33
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE10D.glyph40
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE10E.glyph53
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE110.glyph16
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE111.glyph37
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE112.glyph15
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE113.glyph20
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE114.glyph31
-rw-r--r--TOOLS/mpv-osd-symbols.sfdir/uniE115.glyph27
-rwxr-xr-xTOOLS/mpv_identify.sh149
-rwxr-xr-xTOOLS/osxbundle.py87
-rw-r--r--TOOLS/osxbundle/meson.build8
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Info.plist874
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/MacOS/.gitkeep0
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/MacOS/lib/.gitkeep0
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/PkgInfo1
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Resources/document.icnsbin0 -> 311266 bytes
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icnsbin0 -> 742954 bytes
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf2
-rwxr-xr-xTOOLS/stats-conv.py166
-rwxr-xr-xTOOLS/umpv87
-rw-r--r--TOOLS/uncrustify.cfg165
73 files changed, 5261 insertions, 0 deletions
diff --git a/TOOLS/docutils-wrapper.py b/TOOLS/docutils-wrapper.py
new file mode 100755
index 0000000..31ba976
--- /dev/null
+++ b/TOOLS/docutils-wrapper.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""
+Wrapper around docutils rst2x commands,
+converting their dependency files to a format understood by meson/ninja.
+"""
+
+#
+# This file is part of mpv.
+#
+# mpv is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# mpv is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import subprocess
+import sys
+
+
+def convert_depfile(output, depfile):
+ with open(depfile, 'r') as f:
+ deps = f.readlines()
+
+ with open(depfile, 'w') as f:
+ f.write(os.path.abspath(output))
+ f.write(': \\\n')
+ for dep in deps:
+ dep = dep[:-1]
+ f.write('\t')
+ f.write(os.path.abspath(dep))
+ f.write(' \\\n')
+
+def remove(path):
+ try:
+ os.remove(path)
+ except FileNotFoundError:
+ pass
+
+argv = sys.argv[1:]
+
+depfile = None
+output = argv[-1]
+
+for opt, optarg in zip(argv, argv[1:]):
+ if opt == '--record-dependencies':
+ depfile = optarg
+
+try:
+ proc = subprocess.run(argv, check=True)
+ if depfile is not None:
+ convert_depfile(output, depfile)
+except:
+ remove(output)
+ if depfile is not None:
+ remove(depfile)
+ sys.exit(1)
+
+sys.exit(proc.returncode)
diff --git a/TOOLS/dylib-unhell.py b/TOOLS/dylib-unhell.py
new file mode 100755
index 0000000..c41d200
--- /dev/null
+++ b/TOOLS/dylib-unhell.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+
+import re
+import os
+import sys
+import shutil
+import subprocess
+from functools import partial
+
+sys_re = re.compile("^/System")
+usr_re = re.compile("^/usr/lib/")
+exe_re = re.compile("@executable_path")
+
+def is_user_lib(objfile, libname):
+ return not sys_re.match(libname) and \
+ not usr_re.match(libname) and \
+ not exe_re.match(libname) and \
+ not "libobjc." in libname and \
+ not "libSystem." in libname and \
+ not "libc." in libname and \
+ not "libgcc." in libname and \
+ not os.path.basename(libname) == 'Python' and \
+ not os.path.basename(objfile) in libname and \
+ not "libswift" in libname
+
+def otool(objfile, rapths):
+ command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
+ output = subprocess.check_output(command, shell = True, universal_newlines=True)
+ libs = set(filter(partial(is_user_lib, objfile), output.split()))
+
+ libs_resolved = set()
+ libs_relative = set()
+ for lib in libs:
+ lib_path = resolve_lib_path(objfile, lib, rapths)
+ libs_resolved.add(lib_path)
+ if lib_path != lib:
+ libs_relative.add(lib)
+
+ return libs_resolved, libs_relative
+
+def get_rapths(objfile):
+ rpaths = []
+ command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile
+ pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
+
+ try:
+ result = subprocess.check_output(command, shell = True, universal_newlines=True)
+ except:
+ return rpaths
+
+ for line in result.splitlines():
+ rpaths.append(pathRe.search(line).group(1).strip())
+
+ return rpaths
+
+def get_rpaths_dev_tools(binary):
+ command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\|CommandLineTools\"" % binary
+ result = subprocess.check_output(command, shell = True, universal_newlines=True)
+ pathRe = re.compile("^\s*path (.*) \(offset \d*\)$")
+ output = []
+
+ for line in result.splitlines():
+ output.append(pathRe.search(line).group(1).strip())
+
+ return output
+
+def resolve_lib_path(objfile, lib, rapths):
+ if os.path.exists(lib):
+ return lib
+
+ if lib.startswith('@rpath/'):
+ lib = lib[len('@rpath/'):]
+ for rpath in rapths:
+ lib_path = os.path.join(rpath, lib)
+ if os.path.exists(lib_path):
+ return lib_path
+ elif lib.startswith('@loader_path/'):
+ lib = lib[len('@loader_path/'):]
+ lib_path = os.path.normpath(os.path.join(objfile, lib))
+ if os.path.exists(lib_path):
+ return lib_path
+
+ raise Exception('Could not resolve library: ' + lib)
+
+def install_name_tool_change(old, new, objfile):
+ subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL)
+
+def install_name_tool_id(name, objfile):
+ subprocess.call(["install_name_tool", "-id", name, objfile], stderr=subprocess.DEVNULL)
+
+def install_name_tool_add_rpath(rpath, binary):
+ subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
+
+def install_name_tool_delete_rpath(rpath, binary):
+ subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
+
+def libraries(objfile, result = dict(), result_relative = set(), rapths = []):
+ rapths = get_rapths(objfile) + rapths
+ libs_list, libs_relative = otool(objfile, rapths)
+ result[objfile] = libs_list
+ result_relative |= libs_relative
+
+ for lib in libs_list:
+ if lib not in result:
+ libraries(lib, result, result_relative, rapths)
+
+ return result, result_relative
+
+def lib_path(binary):
+ return os.path.join(os.path.dirname(binary), 'lib')
+
+def lib_name(lib):
+ return os.path.join("@executable_path", "lib", os.path.basename(lib))
+
+def process_libraries(libs_dict, libs_dyn, binary):
+ libs_set = set(libs_dict)
+ # Remove binary from libs_set to prevent a duplicate of the binary being
+ # added to the libs directory.
+ libs_set.remove(binary)
+
+ for src in libs_set:
+ name = lib_name(src)
+ dst = os.path.join(lib_path(binary), os.path.basename(src))
+
+ shutil.copy(src, dst)
+ os.chmod(dst, 0o755)
+ install_name_tool_id(name, dst)
+
+ if src in libs_dict[binary]:
+ install_name_tool_change(src, name, binary)
+
+ for p in libs_set:
+ if p in libs_dict[src]:
+ install_name_tool_change(p, lib_name(p), dst)
+
+ for lib in libs_dyn:
+ install_name_tool_change(lib, lib_name(lib), dst)
+
+ for lib in libs_dyn:
+ install_name_tool_change(lib, lib_name(lib), binary)
+
+def process_swift_libraries(binary):
+ command = ['xcrun', '--find', 'swift-stdlib-tool']
+ swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
+ # from xcode11 on the dynamic swift libs reside in a separate directory from
+ # the std one, might need versioned paths for future swift versions
+ swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
+ swiftLibPath = os.path.abspath(swiftLibPath)
+
+ command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
+
+ if os.path.exists(swiftLibPath):
+ command.extend(['--source-libraries', swiftLibPath])
+
+ subprocess.check_output(command, universal_newlines=True)
+
+ print(">> setting additional rpath for swift libraries")
+ install_name_tool_add_rpath("@executable_path/lib", binary)
+
+def remove_dev_tools_rapths(binary):
+ for path in get_rpaths_dev_tools(binary):
+ install_name_tool_delete_rpath(path, binary)
+
+def main():
+ binary = os.path.abspath(sys.argv[1])
+ if not os.path.exists(lib_path(binary)):
+ os.makedirs(lib_path(binary))
+ print(">> gathering all linked libraries")
+ libs, libs_rel = libraries(binary)
+
+ print(">> copying and processing all linked libraries")
+ process_libraries(libs, libs_rel, binary)
+
+ print(">> removing rpath definitions towards dev tools")
+ remove_dev_tools_rapths(binary)
+
+ print(">> copying and processing swift libraries")
+ process_swift_libraries(binary)
+
+if __name__ == "__main__":
+ main()
diff --git a/TOOLS/file2string.py b/TOOLS/file2string.py
new file mode 100755
index 0000000..5b1c4a9
--- /dev/null
+++ b/TOOLS/file2string.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+# Convert the contents of a file into a C string constant.
+# Note that the compiler will implicitly add an extra 0 byte at the end
+# of every string, so code using the string may need to remove that to get
+# the exact contents of the original file.
+
+#
+# This file is part of mpv.
+#
+# mpv is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# mpv is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import sys
+
+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:
+ outfile.write('"' + ''.join(conv[c] for c in line) + '"\n')
+
+if __name__ == "__main__":
+ outfile = open(sys.argv[2], "w")
+ with open(sys.argv[1], 'rb') as infile:
+ file2string(sys.argv[1], infile, outfile)
diff --git a/TOOLS/gen-osd-font.sh b/TOOLS/gen-osd-font.sh
new file mode 100755
index 0000000..6838692
--- /dev/null
+++ b/TOOLS/gen-osd-font.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# This script is expected to be called as TOOLS/gen-osd-font.sh (it will access
+# TOOLS/mpv-osd-symbols.sfdir), and it will write sub/osd_font.otf.
+
+# Needs fontforge with python scripting
+
+fontforge -lang=py -c 'f=open(argv[1]); f.generate(argv[2])' \
+ TOOLS/mpv-osd-symbols.sfdir sub/osd_font.otf
diff --git a/TOOLS/idet.sh b/TOOLS/idet.sh
new file mode 100755
index 0000000..7fb51c1
--- /dev/null
+++ b/TOOLS/idet.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+
+: "${MPV:=mpv}"
+: "${ILDETECT_MPV:=$MPV}"
+: "${ILDETECT_MPVFLAGS:=--start=35% --length=35}"
+: "${ILDETECT_DRY_RUN:=}"
+: "${ILDETECT_QUIET:=}"
+: "${ILDETECT_RUN_INTERLACED_ONLY:=}"
+: "${ILDETECT_FORCE_RUN:=}"
+
+# This script uses ffmpeg's "idet" filter for interlace detection. In the
+# long run this should replace ildetect.sh+ildetect.so.
+
+# exit status:
+# 0 progressive
+# 1 telecine
+# 2 interlaced
+# 8 unknown
+# 16 detect fail
+# 17+ mpv's status | 16
+
+testfun()
+{
+ $ILDETECT_MPV "$@" \
+ --vf-add=lavfi="[idet]" --msg-level=ffmpeg=v \
+ --o= --vo=null --no-audio --untimed \
+ $ILDETECT_MPVFLAGS \
+ | { if [ -n "$ILDETECT_QUIET" ]; then cat; else tee /dev/stderr; fi } \
+ | grep "Parsed_idet_0: Multi frame detection: "
+}
+
+judge()
+{
+ tff=0
+ bff=0
+ progressive=0
+ undetermined=0
+ while read -r _ _ _ _ _ _ tff1 _ bff1 _ progressive1 _ undetermined1 _; do
+ case "$tff1$bff1$progressive1$undetermined1" in
+ *[!0-9]*)
+ printf >&2 'ERROR: Unrecognized idet output: %s\n' "$out"
+ exit 16
+ ;;
+ esac
+ tff=$((tff + tff1))
+ bff=$((bff + bff1))
+ progressive=$((progressive + progressive1))
+ undetermined=$((undetermined + undetermined1))
+ done <<EOF
+$(testfun "$@" | sed 's/:/: /g')
+EOF
+
+ interlaced=$((bff + tff))
+ determined=$((interlaced + progressive))
+
+ if [ "$undetermined" -gt "$determined" ] || [ "$determined" -lt 250 ]; then
+ echo >&2 "ERROR: Less than 50% or 250 frames are determined."
+ [ -n "$ILDETECT_FORCE_RUN" ] || exit 8
+ echo >&2 "Assuming interlacing."
+ if [ "$tff" -gt $((bff * 10)) ]; then
+ verdict="interlaced-tff"
+ elif [ "$bff" -gt $((tff * 10)) ]; then
+ verdict="interlaced-bff"
+ else
+ verdict="interlaced"
+ fi
+ elif [ $((interlaced * 20)) -gt "$progressive" ]; then
+ # At least 5% of the frames are interlaced!
+ if [ "$tff" -gt $((bff * 10)) ]; then
+ verdict="interlaced-tff"
+ elif [ "$bff" -gt $((tff * 10)) ]; then
+ verdict="interlaced-bff"
+ else
+ echo >&2 "ERROR: Content is interlaced, but can't determine field order."
+ [ -n "$ILDETECT_FORCE_RUN" ] || exit 8
+ echo >&2 "Assuming interlacing with default field order."
+ verdict="interlaced"
+ fi
+ else
+ # Likely progressive
+ verdict="progressive"
+ fi
+
+ printf '%s\n' "$verdict"
+}
+
+judge "$@" --vf-clr
+case "$verdict" in
+ progressive)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ [ -n "$ILDETECT_RUN_INTERLACED_ONLY" ] || \
+ $ILDETECT_MPV "$@"
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 0
+ ;;
+ interlaced-tff)
+ judge "$@" --vf-clr --vf-pre=lavfi=\[setfield=tff\],pullup
+ case "$verdict" in
+ progressive)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=tff\],pullup
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 1
+ ;;
+ *)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=tff\],yadif
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 2
+ ;;
+ esac
+ ;;
+ interlaced-bff)
+ judge "$@" --vf-clr --vf-pre=lavfi=\[setfield=bff\],pullup
+ case "$verdict" in
+ progressive)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=bff\],pullup
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 1
+ ;;
+ *)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=bff\],yadif
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 2
+ ;;
+ esac
+ ;;
+ interlaced)
+ judge "$@" --vf-clr --vf-pre=pullup
+ case "$verdict" in
+ progressive)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=pullup
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 1
+ ;;
+ *)
+ [ -n "$ILDETECT_DRY_RUN" ] || \
+ $ILDETECT_MPV "$@" --vf-pre=yadif
+ r=$?
+ [ $r -eq 0 ] || exit $((r | 16))
+ exit 2
+ ;;
+ esac
+ ;;
+ *)
+ echo >&2 "ERROR: Internal error."
+ exit 16
+ ;;
+esac
diff --git a/TOOLS/lua/README.md b/TOOLS/lua/README.md
new file mode 100644
index 0000000..79b67d6
--- /dev/null
+++ b/TOOLS/lua/README.md
@@ -0,0 +1,20 @@
+mpv lua scripts
+===============
+
+The lua scripts in this folder can be loaded on a one-time basis by
+adding the option
+
+ --script=/path/to/script.lua
+
+to mpv's command line.
+
+Where appropriate, they may also be placed in ~/.config/mpv/scripts/ from
+where they will be automatically loaded when mpv starts.
+
+This is only a small selection of internally maintained scripts. Some of them
+are just for testing mpv internals, or serve as examples. An extensive
+user-edited list of 3rd party scripts is available here:
+
+ https://github.com/mpv-player/mpv/wiki/User-Scripts
+
+(Anyone can add their own scripts to that list.)
diff --git a/TOOLS/lua/acompressor.lua b/TOOLS/lua/acompressor.lua
new file mode 100644
index 0000000..6a69140
--- /dev/null
+++ b/TOOLS/lua/acompressor.lua
@@ -0,0 +1,155 @@
+-- This script adds control to the dynamic range compression ffmpeg
+-- filter including key bindings for adjusting parameters.
+--
+-- See https://ffmpeg.org/ffmpeg-filters.html#acompressor for explanation
+-- of the parameters.
+
+local mp = require 'mp'
+local options = require 'mp.options'
+
+local o = {
+ default_enable = false,
+ show_osd = true,
+ osd_timeout = 4000,
+ filter_label = mp.get_script_name(),
+
+ key_toggle = 'n',
+ key_increase_threshold = 'F1',
+ key_decrease_threshold = 'Shift+F1',
+ key_increase_ratio = 'F2',
+ key_decrease_ratio = 'Shift+F2',
+ key_increase_knee = 'F3',
+ key_decrease_knee = 'Shift+F3',
+ key_increase_makeup = 'F4',
+ key_decrease_makeup = 'Shift+F4',
+ key_increase_attack = 'F5',
+ key_decrease_attack = 'Shift+F5',
+ key_increase_release = 'F6',
+ key_decrease_release = 'Shift+F6',
+
+ default_threshold = -25.0,
+ default_ratio = 3.0,
+ default_knee = 2.0,
+ default_makeup = 8.0,
+ default_attack = 20.0,
+ default_release = 250.0,
+
+ step_threshold = -2.5,
+ step_ratio = 1.0,
+ step_knee = 1.0,
+ step_makeup = 1.0,
+ step_attack = 10.0,
+ step_release = 10.0,
+}
+options.read_options(o)
+
+local params = {
+ { name = 'attack', min=0.01, max=2000, hide_default=true, dB='' },
+ { name = 'release', min=0.01, max=9000, hide_default=true, dB='' },
+ { name = 'threshold', min= -30, max= 0, hide_default=false, dB='dB' },
+ { name = 'ratio', min= 1, max= 20, hide_default=false, dB='' },
+ { name = 'knee', min= 1, max= 10, hide_default=true, dB='dB' },
+ { name = 'makeup', min= 0, max= 24, hide_default=false, dB='dB' },
+}
+
+local function parse_value(value)
+ -- Using nil here because tonumber differs between lua 5.1 and 5.2 when parsing fractions in combination with explicit base argument set to 10.
+ -- And we can't omit it because gsub returns 2 values which would get unpacked and cause more problems. Gotta love scripting languages.
+ return tonumber(value:gsub('dB$', ''), nil)
+end
+
+local function format_value(value, dB)
+ return string.format('%g%s', value, dB)
+end
+
+local function show_osd(filter)
+ if not o.show_osd then
+ return
+ end
+
+ if not filter.enabled then
+ mp.commandv('show-text', 'Dynamic range compressor: disabled', o.osd_timeout)
+ return
+ end
+
+ local pretty = {}
+ for _,param in ipairs(params) do
+ local value = parse_value(filter.params[param.name])
+ if not (param.hide_default and value == o['default_' .. param.name]) then
+ pretty[#pretty+1] = string.format('%s: %g%s', param.name:gsub("^%l", string.upper), value, param.dB)
+ end
+ end
+
+ if #pretty == 0 then
+ pretty = ''
+ else
+ pretty = '\n(' .. table.concat(pretty, ', ') .. ')'
+ end
+
+ mp.commandv('show-text', 'Dynamic range compressor: enabled' .. pretty, o.osd_timeout)
+end
+
+local function get_filter()
+ local af = mp.get_property_native('af', {})
+
+ for i = 1, #af do
+ if af[i].label == o.filter_label then
+ return af, i
+ end
+ end
+
+ af[#af+1] = {
+ name = 'acompressor',
+ label = o.filter_label,
+ enabled = false,
+ params = {},
+ }
+
+ for _,param in pairs(params) do
+ af[#af].params[param.name] = format_value(o['default_' .. param.name], param.dB)
+ end
+
+ return af, #af
+end
+
+local function toggle_acompressor()
+ local af, i = get_filter()
+ af[i].enabled = not af[i].enabled
+ mp.set_property_native('af', af)
+ show_osd(af[i])
+end
+
+local function update_param(name, increment)
+ for _,param in pairs(params) do
+ if param.name == string.lower(name) then
+ local af, i = get_filter()
+ local value = parse_value(af[i].params[param.name])
+ value = math.max(param.min, math.min(value + increment, param.max))
+ af[i].params[param.name] = format_value(value, param.dB)
+ af[i].enabled = true
+ mp.set_property_native('af', af)
+ show_osd(af[i])
+ return
+ end
+ end
+
+ mp.msg.error('Unknown parameter "' .. name .. '"')
+end
+
+mp.add_key_binding(o.key_toggle, "toggle-acompressor", toggle_acompressor)
+mp.register_script_message('update-param', update_param)
+
+for _,param in pairs(params) do
+ for direction,step in pairs({increase=1, decrease=-1}) do
+ mp.add_key_binding(o['key_' .. direction .. '_' .. param.name],
+ 'acompressor-' .. direction .. '-' .. param.name,
+ function() update_param(param.name, step*o['step_' .. param.name]); end,
+ { repeatable = true })
+ end
+end
+
+if o.default_enable then
+ local af, i = get_filter()
+ af[i].enabled = true
+ mp.set_property_native('af', af)
+end
diff --git a/TOOLS/lua/ao-null-reload.lua b/TOOLS/lua/ao-null-reload.lua
new file mode 100644
index 0000000..5b2330b
--- /dev/null
+++ b/TOOLS/lua/ao-null-reload.lua
@@ -0,0 +1,20 @@
+-- Handles the edge case where previous attempts to init audio have failed, but
+-- might start working due to a newly added device. This is required in
+-- particular for ao=wasapi, since the internal IMMNotificationClient code that
+-- normally triggers ao-reload will not be running in this case.
+
+function do_reload()
+ mp.command("ao-reload")
+ reloading = nil
+end
+
+function on_audio_device_list_change()
+ if mp.get_property("current-ao") == "null" and not reloading then
+ mp.msg.verbose("audio-device-list changed: reloading audio")
+ -- avoid calling ao-reload too often
+ reloading = mp.add_timeout(0.5, do_reload)
+ end
+end
+
+mp.set_property("options/audio-fallback-to-null", "yes")
+mp.observe_property("audio-device-list", "native", on_audio_device_list_change)
diff --git a/TOOLS/lua/audio-hotplug-test.lua b/TOOLS/lua/audio-hotplug-test.lua
new file mode 100644
index 0000000..8dedc68
--- /dev/null
+++ b/TOOLS/lua/audio-hotplug-test.lua
@@ -0,0 +1,8 @@
+local utils = require("mp.utils")
+
+mp.observe_property("audio-device-list", "native", function(name, val)
+ print("Audio device list changed:")
+ for index, e in ipairs(val) do
+ print(" - '" .. e.name .. "' (" .. e.description .. ")")
+ end
+end)
diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua
new file mode 100644
index 0000000..b9e1120
--- /dev/null
+++ b/TOOLS/lua/autocrop.lua
@@ -0,0 +1,298 @@
+--[[
+This script uses the lavfi cropdetect filter and the video-crop property to
+automatically crop the currently playing video with appropriate parameters.
+
+It automatically crops the video when playback starts.
+
+You can also manually crop the video by pressing the "C" (shift+c) key.
+Pressing it again undoes the crop.
+
+The workflow is as follows: First, it inserts the cropdetect filter. After
+<detect_seconds> (default is 1) seconds, it then sets video-crop based on the
+vf-metadata values gathered by cropdetect. The cropdetect filter is removed
+after video-crop is set as it is no longer needed.
+
+Since the crop parameters are determined from the 1 second of video between
+inserting the cropdetect filter and setting video-crop, the "C" key should be
+pressed at a position in the video where the crop region is unambiguous (i.e.,
+not a black frame, black background title card, or dark scene).
+
+If non-copy-back hardware decoding is in use, hwdec is temporarily disabled for
+the duration of cropdetect as the filter would fail otherwise.
+
+These are the default options. They can be overridden by adding
+script-opts-append=autocrop-<parameter>=<value> to mpv.conf.
+--]]
+local options = {
+ -- Whether to automatically apply crop at the start of playback. If you
+ -- don't want to crop automatically, add
+ -- script-opts-append=autocrop-auto=no to mpv.conf.
+ auto = true,
+ -- Delay before starting crop in auto mode. You can try to increase this
+ -- value to avoid dark scenes or fade ins at beginning. Automatic cropping
+ -- will not occur if the value is larger than the remaining playback time.
+ auto_delay = 4,
+ -- Black threshold for cropdetect. Smaller values will generally result in
+ -- less cropping. See limit of
+ -- https://ffmpeg.org/ffmpeg-filters.html#cropdetect
+ detect_limit = "24/255",
+ -- The value which the width/height should be divisible by. Smaller
+ -- values have better detection accuracy. If you have problems with
+ -- other filters, you can try to set it to 4 or 16. See round of
+ -- https://ffmpeg.org/ffmpeg-filters.html#cropdetect
+ detect_round = 2,
+ -- The ratio of the minimum clip size to the original. A number from 0 to
+ -- 1. If the picture is over cropped, try adjusting this value.
+ detect_min_ratio = 0.5,
+ -- How long to gather cropdetect data. Increasing this may be desirable to
+ -- allow cropdetect more time to collect data.
+ detect_seconds = 1,
+ -- Whether the OSD shouldn't be used when cropdetect and video-crop are
+ -- applied and removed.
+ suppress_osd = false,
+}
+
+require "mp.options".read_options(options)
+
+local cropdetect_label = mp.get_script_name() .. "-cropdetect"
+
+timers = {
+ auto_delay = nil,
+ detect_crop = nil
+}
+
+local hwdec_backup
+
+local command_prefix = options.suppress_osd and 'no-osd' or ''
+
+function is_enough_time(seconds)
+
+ -- Plus 1 second for deviation.
+ local time_needed = seconds + 1
+ local playtime_remaining = mp.get_property_native("playtime-remaining")
+
+ return playtime_remaining and time_needed < playtime_remaining
+end
+
+function is_cropable(time_needed)
+ if mp.get_property_native('current-tracks/video/image') ~= false then
+ mp.msg.warn("autocrop only works for videos.")
+ return false
+ end
+
+ if not is_enough_time(time_needed) then
+ mp.msg.warn("Not enough time to detect crop.")
+ return false
+ end
+
+ return true
+end
+
+function remove_cropdetect()
+ for _, filter in pairs(mp.get_property_native("vf")) do
+ if filter.label == cropdetect_label then
+ mp.command(
+ string.format("%s vf remove @%s", command_prefix, filter.label))
+
+ return
+ end
+ end
+end
+
+function restore_hwdec()
+ if hwdec_backup then
+ mp.set_property("hwdec", hwdec_backup)
+ hwdec_backup = nil
+ end
+end
+
+function cleanup()
+ remove_cropdetect()
+
+ -- Kill all timers.
+ for index, timer in pairs(timers) do
+ if timer then
+ timer:kill()
+ timers[index] = nil
+ end
+ end
+
+ restore_hwdec()
+end
+
+function detect_crop()
+ local time_needed = options.detect_seconds
+
+ if not is_cropable(time_needed) then
+ return
+ end
+
+ local hwdec_current = mp.get_property("hwdec-current")
+ if hwdec_current:find("-copy$") == nil and hwdec_current ~= "no" and
+ hwdec_current ~= "crystalhd" and hwdec_current ~= "rkmpp" then
+ hwdec_backup = mp.get_property("hwdec")
+ mp.set_property("hwdec", "no")
+ end
+
+ -- Insert the cropdetect filter.
+ local limit = options.detect_limit
+ local round = options.detect_round
+
+ mp.command(
+ string.format(
+ '%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
+ command_prefix, cropdetect_label, limit, round
+ )
+ )
+
+ -- Wait to gather data.
+ timers.detect_crop = mp.add_timeout(time_needed, detect_end)
+end
+
+function detect_end()
+
+ -- Get the metadata and remove the cropdetect filter.
+ local cropdetect_metadata = mp.get_property_native(
+ "vf-metadata/" .. cropdetect_label)
+ remove_cropdetect()
+
+ -- Remove the timer of detect crop.
+ if timers.detect_crop then
+ timers.detect_crop:kill()
+ timers.detect_crop = nil
+ end
+
+ restore_hwdec()
+
+ local meta = {}
+
+ -- Verify the existence of metadata.
+ if cropdetect_metadata then
+ meta = {
+ w = cropdetect_metadata["lavfi.cropdetect.w"],
+ h = cropdetect_metadata["lavfi.cropdetect.h"],
+ x = cropdetect_metadata["lavfi.cropdetect.x"],
+ y = cropdetect_metadata["lavfi.cropdetect.y"],
+ }
+ else
+ mp.msg.error("No crop data.")
+ mp.msg.info("Was the cropdetect filter successfully inserted?")
+ mp.msg.info("Does your version of ffmpeg/libav support AVFrame metadata?")
+ return
+ end
+
+ -- Verify that the metadata meets the requirements and convert it.
+ if meta.w and meta.h and meta.x and meta.y then
+ local width = mp.get_property_native("width")
+ local height = mp.get_property_native("height")
+
+ meta = {
+ w = tonumber(meta.w),
+ h = tonumber(meta.h),
+ x = tonumber(meta.x),
+ y = tonumber(meta.y),
+ min_w = width * options.detect_min_ratio,
+ min_h = height * options.detect_min_ratio,
+ max_w = width,
+ max_h = height
+ }
+ else
+ mp.msg.error("Got empty crop data.")
+ mp.msg.info("You might need to increase detect_seconds.")
+ end
+
+ apply_crop(meta)
+end
+
+function apply_crop(meta)
+
+ -- Verify if it is necessary to crop.
+ local is_effective = meta.w and meta.h and meta.x and meta.y and
+ (meta.x > 0 or meta.y > 0
+ or meta.w < meta.max_w or meta.h < meta.max_h)
+
+ -- Verify it is not over cropped.
+ local is_excessive = false
+ if is_effective and (meta.w < meta.min_w or meta.h < meta.min_h) then
+ mp.msg.info("The area to be cropped is too large.")
+ mp.msg.info("You might need to decrease detect_min_ratio.")
+ is_excessive = true
+ end
+
+ if not is_effective or is_excessive then
+ -- Clear any existing crop.
+ mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix))
+ return
+ end
+
+ -- Apply crop.
+ mp.command(string.format("%s set file-local-options/video-crop %sx%s+%s+%s",
+ command_prefix, meta.w, meta.h, meta.x, meta.y))
+end
+
+function on_start()
+
+ -- Clean up at the beginning.
+ cleanup()
+
+ -- If auto is not true, exit.
+ if not options.auto then
+ return
+ end
+
+ -- If it is the beginning, wait for detect_crop
+ -- after auto_delay seconds, otherwise immediately.
+ local playback_time = mp.get_property_native("playback-time")
+ local is_delay_needed = playback_time
+ and options.auto_delay > playback_time
+
+ if is_delay_needed then
+
+ -- Verify if there is enough time for autocrop.
+ local time_needed = options.auto_delay + options.detect_seconds
+
+ if not is_cropable(time_needed) then
+ return
+ end
+
+ timers.auto_delay = mp.add_timeout(time_needed,
+ function()
+ detect_crop()
+
+ -- Remove the timer of auto delay.
+ timers.auto_delay:kill()
+ timers.auto_delay = nil
+ end
+ )
+ else
+ detect_crop()
+ end
+end
+
+function on_toggle()
+
+ -- If it is during auto_delay, kill the timer.
+ if timers.auto_delay then
+ timers.auto_delay:kill()
+ timers.auto_delay = nil
+ end
+
+ -- Cropped => Remove it.
+ if mp.get_property("video-crop") ~= "" then
+ mp.command(string.format("%s set file-local-options/video-crop ''", command_prefix))
+ return
+ end
+
+ -- Detecting => Leave it.
+ if timers.detect_crop then
+ mp.msg.warn("Already cropdetecting!")
+ return
+ end
+
+ -- Neither => Detect crop.
+ detect_crop()
+end
+
+mp.add_key_binding("C", "toggle_crop", on_toggle)
+mp.register_event("end-file", cleanup)
+mp.register_event("file-loaded", on_start)
diff --git a/TOOLS/lua/autodeint.lua b/TOOLS/lua/autodeint.lua
new file mode 100644
index 0000000..b891c9a
--- /dev/null
+++ b/TOOLS/lua/autodeint.lua
@@ -0,0 +1,156 @@
+-- This script uses the lavfi idet filter to automatically insert the
+-- appropriate deinterlacing filter based on a short section of the
+-- currently playing video.
+--
+-- It registers the key-binding ctrl+d, which when pressed, inserts the filters
+-- ``vf=idet,lavfi-pullup,idet``. After 4 seconds, it removes these
+-- filters and decides whether the content is progressive, interlaced, or
+-- telecined and the interlacing field dominance.
+--
+-- Based on this information, it may set mpv's ``deinterlace`` property (which
+-- usually inserts the yadif filter), or insert the ``pullup`` filter if the
+-- content is telecined. It also sets field dominance with lavfi setfield.
+--
+-- OPTIONS:
+-- The default detection time may be overridden by adding
+--
+-- --script-opts=autodeint.detect_seconds=<number of seconds>
+--
+-- to mpv's arguments. This may be desirable to allow idet more
+-- time to collect data.
+--
+-- To see counts of the various types of frames for each detection phase,
+-- the verbosity can be increased with
+--
+-- --msg-level=autodeint=v
+
+require "mp.msg"
+
+script_name = mp.get_script_name()
+detect_label = string.format("%s-detect", script_name)
+pullup_label = string.format("%s", script_name)
+dominance_label = string.format("%s-dominance", script_name)
+ivtc_detect_label = string.format("%s-ivtc-detect", script_name)
+
+-- number of seconds to gather cropdetect data
+detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
+if not detect_seconds then
+ detect_seconds = 4
+end
+
+function del_filter_if_present(label)
+ -- necessary because mp.command('vf del @label:filter') raises an
+ -- error if the filter doesn't exist
+ local vfs = mp.get_property_native("vf")
+
+ for i,vf in pairs(vfs) do
+ if vf["label"] == label then
+ table.remove(vfs, i)
+ mp.set_property_native("vf", vfs)
+ return true
+ end
+ end
+ return false
+end
+
+local function add_vf(label, filter)
+ return mp.command(('vf add @%s:%s'):format(label, filter))
+end
+
+function start_detect()
+ -- exit if detection is already in progress
+ if timer then
+ mp.msg.warn("already detecting!")
+ return
+ end
+
+ mp.set_property("deinterlace","no")
+ del_filter_if_present(pullup_label)
+ del_filter_if_present(dominance_label)
+
+ -- insert the detection filters
+ if not (add_vf(detect_label, 'idet') and
+ add_vf(dominance_label, 'setfield=mode=auto') and
+ add_vf(pullup_label, 'lavfi-pullup') and
+ add_vf(ivtc_detect_label, 'idet')) then
+ mp.msg.error("failed to insert detection filters")
+ return
+ end
+
+ -- wait to gather data
+ timer = mp.add_timeout(detect_seconds, select_filter)
+end
+
+function stop_detect()
+ del_filter_if_present(detect_label)
+ del_filter_if_present(ivtc_detect_label)
+ timer = nil
+end
+
+progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
+
+function judge(label)
+ -- get the metadata
+ local result = mp.get_property_native(string.format("vf-metadata/%s", label))
+ local num_tff = tonumber(result["lavfi.idet.multiple.tff"])
+ local num_bff = tonumber(result["lavfi.idet.multiple.bff"])
+ local num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
+ local num_undetermined = tonumber(result["lavfi.idet.multiple.undetermined"])
+ local num_interlaced = num_tff + num_bff
+ local num_determined = num_interlaced + num_progressive
+
+ mp.msg.verbose(label.." progressive = "..num_progressive)
+ mp.msg.verbose(label.." interlaced-tff = "..num_tff)
+ mp.msg.verbose(label.." interlaced-bff = "..num_bff)
+ mp.msg.verbose(label.." undetermined = "..num_undetermined)
+
+ if num_determined < num_undetermined then
+ mp.msg.warn("majority undetermined frames")
+ end
+ if num_progressive > 20*num_interlaced then
+ return progressive
+ elseif num_tff > 10*num_bff then
+ return interlaced_tff
+ elseif num_bff > 10*num_tff then
+ return interlaced_bff
+ else
+ return interlaced
+ end
+end
+
+function select_filter()
+ -- handle the first detection filter results
+ local verdict = judge(detect_label)
+ local ivtc_verdict = judge(ivtc_detect_label)
+ local dominance = "auto"
+ if verdict == progressive then
+ mp.msg.info("progressive: doing nothing")
+ stop_detect()
+ del_filter_if_present(dominance_label)
+ del_filter_if_present(pullup_label)
+ return
+ else
+ if verdict == interlaced_tff then
+ dominance = "tff"
+ add_vf(dominance_label, 'setfield=mode='..dominance)
+ elseif verdict == interlaced_bff then
+ dominance = "bff"
+ add_vf(dominance_label, 'setfield=mode='..dominance)
+ else
+ del_filter_if_present(dominance_label)
+ end
+ end
+
+ -- handle the ivtc detection filter results
+ if ivtc_verdict == progressive then
+ mp.msg.info(string.format("telecined with %s field dominance: using pullup", dominance))
+ stop_detect()
+ else
+ mp.msg.info(string.format("interlaced with %s field dominance: setting deinterlace property", dominance))
+ del_filter_if_present(pullup_label)
+ mp.set_property("deinterlace","yes")
+ stop_detect()
+ end
+end
+
+mp.add_key_binding("ctrl+d", script_name, start_detect)
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
new file mode 100644
index 0000000..4003cbc
--- /dev/null
+++ b/TOOLS/lua/autoload.lua
@@ -0,0 +1,328 @@
+-- This script automatically loads playlist entries before and after the
+-- the currently played file. It does so by scanning the directory a file is
+-- located in when starting playback. It sorts the directory entries
+-- alphabetically, and adds entries before and after the current file to
+-- the internal playlist. (It stops if it would add an already existing
+-- playlist entry at the same position - this makes it "stable".)
+-- Add at most 5000 * 2 files when starting a file (before + after).
+
+--[[
+To configure this script use file autoload.conf in directory script-opts (the "script-opts"
+directory must be in the mpv configuration directory, typically ~/.config/mpv/).
+
+Example configuration would be:
+
+disabled=no
+images=no
+videos=yes
+audio=yes
+additional_image_exts=list,of,ext
+additional_video_exts=list,of,ext
+additional_audio_exts=list,of,ext
+ignore_hidden=yes
+same_type=yes
+directory_mode=recursive
+
+--]]
+
+MAXENTRIES = 5000
+MAXDIRSTACK = 20
+
+local msg = require 'mp.msg'
+local options = require 'mp.options'
+local utils = require 'mp.utils'
+
+o = {
+ disabled = false,
+ images = true,
+ videos = true,
+ audio = true,
+ additional_image_exts = "",
+ additional_video_exts = "",
+ additional_audio_exts = "",
+ ignore_hidden = true,
+ same_type = false,
+ directory_mode = "auto"
+}
+options.read_options(o, nil, function(list)
+ split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
+ if list.videos or list.additional_video_exts or
+ list.audio or list.additional_audio_exts or
+ list.images or list.additional_image_exts then
+ create_extensions()
+ end
+ if list.directory_mode then
+ validate_directory_mode()
+ end
+end)
+
+function Set (t)
+ local set = {}
+ for _, v in pairs(t) do set[v] = true end
+ return set
+end
+
+function SetUnion (a,b)
+ for k in pairs(b) do a[k] = true end
+ return a
+end
+
+function Split (s)
+ local set = {}
+ for v in string.gmatch(s, '([^,]+)') do set[v] = true end
+ return set
+end
+
+EXTENSIONS_VIDEO = Set {
+ '3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
+ 'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
+}
+
+EXTENSIONS_AUDIO = Set {
+ 'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
+ 'ogm', 'opus', 'wav', 'wma'
+}
+
+EXTENSIONS_IMAGES = Set {
+ 'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png',
+ 'svg', 'tga', 'tif', 'tiff', 'webp'
+}
+
+function split_option_exts(video, audio, image)
+ if video then o.additional_video_exts = Split(o.additional_video_exts) end
+ if audio then o.additional_audio_exts = Split(o.additional_audio_exts) end
+ if image then o.additional_image_exts = Split(o.additional_image_exts) end
+end
+split_option_exts(true, true, true)
+
+function create_extensions()
+ EXTENSIONS = {}
+ if o.videos then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_VIDEO), o.additional_video_exts) end
+ if o.audio then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_AUDIO), o.additional_audio_exts) end
+ if o.images then SetUnion(SetUnion(EXTENSIONS, EXTENSIONS_IMAGES), o.additional_image_exts) end
+end
+create_extensions()
+
+function validate_directory_mode()
+ if o.directory_mode ~= "recursive" and o.directory_mode ~= "lazy" and o.directory_mode ~= "ignore" then
+ o.directory_mode = nil
+ end
+end
+validate_directory_mode()
+
+function add_files(files)
+ local oldcount = mp.get_property_number("playlist-count", 1)
+ for i = 1, #files do
+ mp.commandv("loadfile", files[i][1], "append")
+ mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
+ end
+end
+
+function get_extension(path)
+ match = string.match(path, "%.([^%.]+)$" )
+ if match == nil then
+ return "nomatch"
+ else
+ return match
+ end
+end
+
+table.filter = function(t, iter)
+ for i = #t, 1, -1 do
+ if not iter(t[i]) then
+ table.remove(t, i)
+ end
+ end
+end
+
+table.append = function(t1, t2)
+ local t1_size = #t1
+ for i = 1, #t2 do
+ t1[t1_size + i] = t2[i]
+ end
+end
+
+-- alphanum sorting for humans in Lua
+-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
+
+function alphanumsort(filenames)
+ local function padnum(n, d)
+ return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
+ or ("%03d%s"):format(#n, n)
+ end
+
+ local tuples = {}
+ for i, f in ipairs(filenames) do
+ tuples[i] = {f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f}
+ end
+ table.sort(tuples, function(a, b)
+ return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
+ end)
+ for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end
+ return filenames
+end
+
+local autoloaded = nil
+local added_entries = {}
+local autoloaded_dir = nil
+
+function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions)
+ if dir_depth == MAXDIRSTACK then
+ return
+ end
+ msg.trace("scanning: " .. path)
+ local files = utils.readdir(path, "files") or {}
+ local dirs = dir_mode ~= "ignore" and utils.readdir(path, "dirs") or {}
+ local prefix = path == "." and "" or path
+ table.filter(files, function (v)
+ -- The current file could be a hidden file, ignoring it doesn't load other
+ -- files from the current directory.
+ if (o.ignore_hidden and not (prefix .. v == current_file) and string.match(v, "^%.")) then
+ return false
+ end
+ local ext = get_extension(v)
+ if ext == nil then
+ return false
+ end
+ return extensions[string.lower(ext)]
+ end)
+ table.filter(dirs, function(d)
+ return not ((o.ignore_hidden and string.match(d, "^%.")))
+ end)
+ alphanumsort(files)
+ alphanumsort(dirs)
+
+ for i, file in ipairs(files) do
+ files[i] = prefix .. file
+ end
+
+ table.append(total_files, files)
+ if dir_mode == "recursive" then
+ for _, dir in ipairs(dirs) do
+ scan_dir(prefix .. dir .. separator, current_file, dir_mode,
+ separator, dir_depth + 1, total_files, extensions)
+ end
+ else
+ for i, dir in ipairs(dirs) do
+ dirs[i] = prefix .. dir
+ end
+ table.append(total_files, dirs)
+ end
+end
+
+function find_and_add_entries()
+ local path = mp.get_property("path", "")
+ local dir, filename = utils.split_path(path)
+ msg.trace(("dir: %s, filename: %s"):format(dir, filename))
+ if o.disabled then
+ msg.debug("stopping: autoload disabled")
+ return
+ elseif #dir == 0 then
+ msg.debug("stopping: not a local path")
+ return
+ end
+
+ local pl_count = mp.get_property_number("playlist-count", 1)
+ this_ext = get_extension(filename)
+ -- check if this is a manually made playlist
+ if (pl_count > 1 and autoloaded == nil) or
+ (pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
+ msg.debug("stopping: manually made playlist")
+ return
+ else
+ if pl_count == 1 then
+ autoloaded = true
+ autoloaded_dir = dir
+ added_entries = {}
+ end
+ end
+
+ local extensions = {}
+ if o.same_type then
+ if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_VIDEO
+ elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
+ extensions = EXTENSIONS_AUDIO
+ else
+ extensions = EXTENSIONS_IMAGES
+ end
+ else
+ extensions = EXTENSIONS
+ end
+
+ local pl = mp.get_property_native("playlist", {})
+ local pl_current = mp.get_property_number("playlist-pos-1", 1)
+ msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
+ utils.to_string(pl)))
+
+ local files = {}
+ do
+ local dir_mode = o.directory_mode or mp.get_property("directory-mode", "lazy")
+ local separator = mp.get_property_native("platform") == "windows" and "\\" or "/"
+ scan_dir(autoloaded_dir, path, dir_mode, separator, 0, files, extensions)
+ end
+
+ if next(files) == nil then
+ msg.debug("no other files or directories in directory")
+ return
+ end
+
+ -- Find the current pl entry (dir+"/"+filename) in the sorted dir list
+ local current
+ for i = 1, #files do
+ if files[i] == path then
+ current = i
+ break
+ end
+ end
+ if current == nil then
+ return
+ end
+ msg.trace("current file position in files: "..current)
+
+ -- treat already existing playlist entries, independent of how they got added
+ -- as if they got added by autoload
+ for _, entry in ipairs(pl) do
+ added_entries[entry.filename] = true
+ end
+
+ local append = {[-1] = {}, [1] = {}}
+ for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
+ for i = 1, MAXENTRIES do
+ local pos = current + i * direction
+ local file = files[pos]
+ if file == nil or file[1] == "." then
+ break
+ end
+
+ -- skip files that are/were already in the playlist
+ if not added_entries[file] then
+ if direction == -1 then
+ msg.verbose("Prepending " .. file)
+ table.insert(append[-1], 1, {file, pl_current + i * direction + 1})
+ else
+ msg.verbose("Adding " .. file)
+ if pl_count > 1 then
+ table.insert(append[1], {file, pl_current + i * direction - 1})
+ else
+ mp.commandv("loadfile", file, "append")
+ end
+ end
+ end
+ added_entries[file] = true
+ end
+ if pl_count == 1 and direction == -1 and #append[-1] > 0 then
+ for i = 1, #append[-1] do
+ mp.commandv("loadfile", append[-1][i][1], "append")
+ end
+ mp.commandv("playlist-move", 0, current)
+ end
+ end
+
+ if pl_count > 1 then
+ add_files(append[1])
+ add_files(append[-1])
+ end
+end
+
+mp.register_event("start-file", find_and_add_entries)
diff --git a/TOOLS/lua/command-test.lua b/TOOLS/lua/command-test.lua
new file mode 100644
index 0000000..877cacd
--- /dev/null
+++ b/TOOLS/lua/command-test.lua
@@ -0,0 +1,124 @@
+-- Test script for some command API details.
+
+local utils = require("mp.utils")
+
+function join(sep, arr, count)
+ local r = ""
+ if count == nil then
+ count = #arr
+ end
+ for i = 1, count do
+ if i > 1 then
+ r = r .. sep
+ end
+ r = r .. utils.to_string(arr[i])
+ end
+ return r
+end
+
+mp.observe_property("vo-configured", "bool", function(_, v)
+ if v ~= true then
+ return
+ end
+
+ print("async expand-text")
+ mp.command_native_async({"expand-text", "hello ${path}!"},
+ function(res, val, err)
+ print("done async expand-text: " .. join(" ", {res, val, err}))
+ end)
+
+ -- make screenshot writing very slow
+ mp.set_property("screenshot-format", "png")
+ mp.set_property("screenshot-png-compression", "9")
+
+ timer = mp.add_periodic_timer(0.1, function() print("I'm alive") end)
+ timer:resume()
+
+ print("Slow screenshot command...")
+ res, err = mp.command_native({"screenshot"})
+ print("done, res: " .. utils.to_string(res))
+
+ print("Slow screenshot async command...")
+ res, err = mp.command_native_async({"screenshot"}, function(res)
+ print("done (async), res: " .. utils.to_string(res))
+ timer:kill()
+ end)
+ print("done (sending), res: " .. utils.to_string(res))
+
+ print("Broken screenshot async command...")
+ mp.command_native_async({"screenshot-to-file", "/nonexistent/bogus.png"},
+ function(res, val, err)
+ print("done err scr.: " .. join(" ", {res, val, err}))
+ end)
+
+ mp.command_native_async({name = "subprocess", args = {"sh", "-c", "echo hi && sleep 10s"}, capture_stdout = true},
+ function(res, val, err)
+ print("done subprocess: " .. join(" ", {res, val, err}))
+ end)
+
+ local x = mp.command_native_async({name = "subprocess", args = {"sleep", "inf"}},
+ function(res, val, err)
+ print("done sleep inf subprocess: " .. join(" ", {res, val, err}))
+ end)
+ mp.add_timeout(15, function()
+ print("aborting sleep inf subprocess after timeout")
+ mp.abort_async_command(x)
+ end)
+
+ -- (assuming this "freezes")
+ local y = mp.command_native_async({name = "sub-add", url = "-"},
+ function(res, val, err)
+ print("done sub-add stdin: " .. join(" ", {res, val, err}))
+ end)
+ mp.add_timeout(20, function()
+ print("aborting sub-add stdin after timeout")
+ mp.abort_async_command(y)
+ end)
+
+
+ mp.command_native_async({name = "subprocess", args = {"wc", "-c"},
+ stdin_data = "hello", capture_stdout = true},
+ function(res, val, err)
+ print("Should be '5': " .. val.stdout)
+ end)
+ -- blocking stdin by default
+ mp.command_native_async({name = "subprocess", args = {"cat"},
+ capture_stdout = true},
+ function(res, val, err)
+ print("Should be 0: " .. #val.stdout)
+ end)
+ -- stdin + detached
+ mp.command_native_async({name = "subprocess",
+ args = {"bash", "-c", "(sleep 5s ; cat)"},
+ stdin_data = "this should appear after 5s.\n",
+ detach = true},
+ function(res, val, err)
+ print("5s test: " .. val.status)
+ end)
+
+ -- This should get killed on script exit.
+ mp.command_native_async({name = "subprocess", playback_only = false,
+ args = {"sleep", "inf"}}, function()end)
+
+ -- Runs detached; should be killed on player exit (forces timeout)
+ mp.command_native({_flags={"async"}, name = "subprocess",
+ playback_only = false, args = {"sleep", "inf"}})
+end)
+
+function freeze_test(playback_only)
+ -- This "freezes" the script, should be killed via timeout.
+ counter = counter and counter + 1 or 0
+ print("freeze! " .. counter)
+ local x = mp.command_native({name = "subprocess",
+ playback_only = playback_only,
+ args = {"sleep", "inf"}})
+ print("done, killed=" .. utils.to_string(x.killed_by_us))
+end
+
+mp.register_event("shutdown", function()
+ freeze_test(false)
+end)
+
+mp.register_event("idle", function()
+ freeze_test(true)
+end)
diff --git a/TOOLS/lua/cycle-deinterlace-pullup.lua b/TOOLS/lua/cycle-deinterlace-pullup.lua
new file mode 100644
index 0000000..2902e40
--- /dev/null
+++ b/TOOLS/lua/cycle-deinterlace-pullup.lua
@@ -0,0 +1,56 @@
+-- This script cycles between deinterlacing, pullup (inverse
+-- telecine), and both filters off. It uses the "deinterlace" property
+-- so that a hardware deinterlacer will be used if available.
+--
+-- It overrides the default deinterlace toggle keybinding "D"
+-- (shift+d), so that rather than merely cycling the "deinterlace" property
+-- between on and off, it adds a "pullup" step to the cycle.
+--
+-- It provides OSD feedback as to the actual state of the two filters
+-- after each cycle step/keypress.
+--
+-- Note: if hardware decoding is enabled, pullup filter will likely
+-- fail to insert.
+--
+-- TODO: It might make sense to use hardware assisted vdpaupp=pullup,
+-- if available, but I don't have hardware to test it. Patch welcome.
+
+script_name = mp.get_script_name()
+pullup_label = string.format("%s-pullup", script_name)
+
+function pullup_on()
+ for i,vf in pairs(mp.get_property_native('vf')) do
+ if vf['label'] == pullup_label then
+ return "yes"
+ end
+ end
+ return "no"
+end
+
+function do_cycle()
+ if pullup_on() == "yes" then
+ -- if pullup is on remove it
+ mp.command(string.format("vf del @%s:pullup", pullup_label))
+ return
+ elseif mp.get_property("deinterlace") == "yes" then
+ -- if deinterlace is on, turn it off and insert pullup filter
+ mp.set_property("deinterlace", "no")
+ mp.command(string.format("vf add @%s:pullup", pullup_label))
+ return
+ else
+ -- if neither is on, turn on deinterlace
+ mp.set_property("deinterlace", "yes")
+ return
+ end
+end
+
+function cycle_deinterlace_pullup_handler()
+ do_cycle()
+ -- independently determine current state and give user feedback
+ mp.osd_message(string.format("deinterlace: %s\n"..
+ "pullup: %s",
+ mp.get_property("deinterlace"),
+ pullup_on()))
+end
+
+mp.add_key_binding("D", "cycle-deinterlace-pullup", cycle_deinterlace_pullup_handler)
diff --git a/TOOLS/lua/nan-test.lua b/TOOLS/lua/nan-test.lua
new file mode 100644
index 0000000..d3f1c8c
--- /dev/null
+++ b/TOOLS/lua/nan-test.lua
@@ -0,0 +1,37 @@
+-- Test a float property which internally uses NaN.
+-- Run with --no-config (or just scale-param1 not set).
+
+local utils = require 'mp.utils'
+
+prop_name = "scale-param1"
+
+-- internal NaN, return string "default" instead of NaN
+v = mp.get_property_native(prop_name, "fail")
+print("Exp:", "string", "\"default\"")
+print("Got:", type(v), utils.to_string(v))
+
+v = mp.get_property(prop_name)
+print("Exp:", "default")
+print("Got:", v)
+
+-- not representable -> return provided fallback value
+v = mp.get_property_number(prop_name, -100)
+print("Exp:", -100)
+print("Got:", v)
+
+mp.set_property_native(prop_name, 123)
+v = mp.get_property_number(prop_name, -100)
+print("Exp:", "number", 123)
+print("Got:", type(v), utils.to_string(v))
+
+-- try to set an actual NaN
+st, msg = mp.set_property_number(prop_name, 0.0/0)
+print("Exp:", nil, "<message>")
+print("Got:", st, msg)
+
+-- set default
+mp.set_property(prop_name, "default")
+
+v = mp.get_property(prop_name)
+print("Exp:", "default")
+print("Got:", v)
diff --git a/TOOLS/lua/observe-all.lua b/TOOLS/lua/observe-all.lua
new file mode 100644
index 0000000..0037439
--- /dev/null
+++ b/TOOLS/lua/observe-all.lua
@@ -0,0 +1,22 @@
+-- Test script for property change notification mechanism.
+-- Note that watching/reading some properties can be very expensive, or
+-- require the player to synchronously wait on network (when playing
+-- remote files), so you should in general only watch properties you
+-- are interested in.
+
+local utils = require("mp.utils")
+
+function observe(name)
+ mp.observe_property(name, "native", function(name, val)
+ print("property '" .. name .. "' changed to '" ..
+ utils.to_string(val) .. "'")
+ end)
+end
+
+for i,name in ipairs(mp.get_property_native("property-list")) do
+ observe(name)
+end
+
+for i,name in ipairs(mp.get_property_native("options")) do
+ observe("options/" .. name)
+end
diff --git a/TOOLS/lua/ontop-playback.lua b/TOOLS/lua/ontop-playback.lua
new file mode 100644
index 0000000..b02716c
--- /dev/null
+++ b/TOOLS/lua/ontop-playback.lua
@@ -0,0 +1,19 @@
+--makes mpv disable ontop when pausing and re-enable it again when resuming playback
+--please note that this won't do anything if ontop was not enabled before pausing
+
+local was_ontop = false
+
+mp.observe_property("pause", "bool", function(name, value)
+ local ontop = mp.get_property_native("ontop")
+ if value then
+ if ontop then
+ mp.set_property_native("ontop", false)
+ was_ontop = true
+ end
+ else
+ if was_ontop and not ontop then
+ mp.set_property_native("ontop", true)
+ end
+ was_ontop = false
+ end
+end)
diff --git a/TOOLS/lua/osd-test.lua b/TOOLS/lua/osd-test.lua
new file mode 100644
index 0000000..1b17819
--- /dev/null
+++ b/TOOLS/lua/osd-test.lua
@@ -0,0 +1,35 @@
+local assdraw = require 'mp.assdraw'
+local utils = require 'mp.utils'
+
+things = {}
+for i = 1, 2 do
+ things[i] = {
+ osd1 = mp.create_osd_overlay("ass-events"),
+ osd2 = mp.create_osd_overlay("ass-events")
+ }
+end
+things[1].text = "{\\an5}hello\\Nworld"
+things[2].text = "{\\pos(400, 200)}something something"
+
+mp.add_periodic_timer(2, function()
+ for i, thing in ipairs(things) do
+ thing.osd1.data = thing.text
+ thing.osd1.compute_bounds = true
+ --thing.osd1.hidden = true
+ local res = thing.osd1:update()
+ print("res " .. i .. ": " .. utils.to_string(res))
+
+ thing.osd2.hidden = true
+ if res ~= nil and res.x0 ~= nil then
+ local draw = assdraw.ass_new()
+ draw:append("{\\alpha&H80}")
+ draw:draw_start()
+ draw:pos(0, 0)
+ draw:rect_cw(res.x0, res.y0, res.x1, res.y1)
+ draw:draw_stop()
+ thing.osd2.hidden = false
+ thing.osd2.data = draw.text
+ end
+ thing.osd2:update()
+ end
+end)
diff --git a/TOOLS/lua/pause-when-minimize.lua b/TOOLS/lua/pause-when-minimize.lua
new file mode 100644
index 0000000..99add70
--- /dev/null
+++ b/TOOLS/lua/pause-when-minimize.lua
@@ -0,0 +1,20 @@
+-- This script pauses playback when minimizing the window, and resumes playback
+-- if it's brought back again. If the player was already paused when minimizing,
+-- then try not to mess with the pause state.
+
+local did_minimize = false
+
+mp.observe_property("window-minimized", "bool", function(name, value)
+ local pause = mp.get_property_native("pause")
+ if value == true then
+ if pause == false then
+ mp.set_property_native("pause", true)
+ did_minimize = true
+ end
+ elseif value == false then
+ if did_minimize and (pause == true) then
+ mp.set_property_native("pause", false)
+ end
+ did_minimize = false
+ end
+end)
diff --git a/TOOLS/lua/skip-logo.lua b/TOOLS/lua/skip-logo.lua
new file mode 100644
index 0000000..8e1f9da
--- /dev/null
+++ b/TOOLS/lua/skip-logo.lua
@@ -0,0 +1,265 @@
+--[[
+
+Automatically skip in files if video frames with pre-supplied fingerprints are
+detected. This will skip ahead by a pre-configured amount of time if a matching
+video frame is detected.
+
+This requires the vf_fingerprint video filter to be compiled in. Read the
+documentation of this filter for caveats (which will automatically apply to
+this script as well), such as no support for zero-copy hardware decoding.
+
+You need to manually gather and provide fingerprints for video frames and add
+them to a configuration file in script-opts/skip-logo.conf (the "script-opts"
+directory must be in the mpv configuration directory, typically ~/.config/mpv/).
+
+Example script-opts/skip-logo.conf:
+
+
+ cases = {
+ {
+ -- Skip ahead 10 seconds if a black frame was detected
+ -- Note: this is dangerous non-sense. It's just for demonstration.
+ name = "black frame", -- print if matched
+ skip = 10, -- number of seconds to skip forward
+ score = 0.3, -- required score
+ fingerprint = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ },
+ {
+ -- Skip ahead 20 seconds if a white frame was detected
+ -- Note: this is dangerous non-sense. It's just for demonstration.
+ name = "fun2",
+ skip = 20,
+ fingerprint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ },
+ }
+
+This is actually a lua file. Lua was chosen because it seemed less of a pain to
+parse. Future versions of this script may change the format.
+
+The fingerprint is a video frame, converted to "gray" (8 bit per pixels), full
+range, each pixel concatenated into an array, converted to a hex string. You
+can produce these fingerprints by running this manually:
+
+ mpv --vf=fingerprint:print yourfile.mkv
+
+This will log the fingerprint of each video frame to the console, along with its
+timestamp. You find the fingerprint of a unique-enough looking frame, and add
+it as entry to skip-logo.conf.
+
+You can provide a score for "fuzziness". If no score is provided, a default
+value of 0.3 is used. The score is inverse: 0 means exactly the same, while a
+higher score means a higher difference. Currently, the score is computed as
+euclidean distance between the video frame and the pre-provided fingerprint,
+thus the highest score is 16. You probably want a score lower than 1 at least.
+(This algorithm is very primitive, but also simple and fast to compute.)
+
+There's always the danger of false positives, which might be quite annoying.
+It's up to you what you hate more, the logo, or random skips if false positives
+are detected. Also, it's always active, and might eat too much CPU with files
+that have a high resolution or framerate. To temporarily disable the script,
+having a keybind like this in your input.conf will be helpful:
+
+ ctrl+k vf toggle @skip-logo
+
+This will disable/enable the fingerprint filter, which the script automatically
+adds at start.
+
+Another important caveat is that the script currently disables matching during
+seeking or playback initialization, which means it cannot match the first few
+frames of a video. This could be fixed, but the author was too lazy to do so.
+
+--]]
+
+local utils = require "mp.utils"
+local msg = require "mp.msg"
+
+local label = "skip-logo"
+local meta_property = string.format("vf-metadata/%s", label)
+
+local config = {}
+local cases = {}
+local cur_bmp
+local seeking = false
+local playback_start_pts = nil
+
+-- Convert a hex string to an array. Convert each byte to a [0,1] float by
+-- interpreting it as normalized uint8_t.
+-- The data parameter, if not nil, may be used as storage (avoiding garbage).
+local function hex_to_norm8(hex, data)
+ local size = math.floor(#hex / 2)
+ if #hex ~= size * 2 then
+ return nil
+ end
+ local res
+ if (data ~= nil) and (#data == size) then
+ res = data
+ else
+ res = {}
+ end
+ for i = 1, size do
+ local num = tonumber(hex:sub(i * 2, i * 2 + 1), 16)
+ if num == nil then
+ return nil
+ end
+ res[i] = num / 255.0
+ end
+ return res
+end
+
+local function compare_bmp(a, b)
+ if #a ~= #b then
+ return nil -- can't compare
+ end
+ local sum = 0
+ for i = 1, #a do
+ local diff = a[i] - b[i]
+ sum = sum + diff * diff
+ end
+ return math.sqrt(sum)
+end
+
+local function load_config()
+ local conf_file = mp.find_config_file("script-opts/skip-logo.conf")
+ local conf_fn
+ local err = nil
+ if conf_file then
+ if setfenv then
+ conf_fn, err = loadfile(conf_file)
+ if conf_fn then
+ setfenv(conf_fn, config)
+ end
+ else
+ conf_fn, err = loadfile(conf_file, "t", config)
+ end
+ else
+ err = "config file not found"
+ end
+
+ if conf_fn and (not err) then
+ local ok, err2 = pcall(conf_fn)
+ err = err2
+ end
+
+ if err then
+ msg.error("Failed to load config file:", err)
+ end
+
+ if config.cases then
+ for n, case in ipairs(config.cases) do
+ local err = nil
+ case.bitmap = hex_to_norm8(case.fingerprint)
+ if case.bitmap == nil then
+ err = "invalid or missing fingerprint field"
+ end
+ if case.score == nil then
+ case.score = 0.3
+ end
+ if type(case.score) ~= "number" then
+ err = "score field is not a number"
+ end
+ if type(case.skip) ~= "number" then
+ err = "skip field is not a number or missing"
+ end
+ if case.name == nil then
+ case.name = ("Entry %d"):format(n)
+ end
+ if err == nil then
+ cases[#cases + 1] = case
+ else
+ msg.error(("Entry %s: %s, ignoring."):format(case.name, err))
+ end
+ end
+ end
+end
+
+load_config()
+
+-- Returns true on match and if something was done.
+local function check_fingerprint(hex, pts)
+ local bmp = hex_to_norm8(hex, cur_bmp)
+ cur_bmp = bmp
+
+ -- If parsing the filter's result failed (well, it shouldn't).
+ assert(bmp ~= nil, "filter returned nonsense")
+
+ for _, case in ipairs(cases) do
+ local score = compare_bmp(case.bitmap, bmp)
+ if (score ~= nil) and (score <= case.score) then
+ msg.warn(("Matching %s: score=%f (required: %f) at %s, skipping %f seconds"):
+ format(case.name, score, case.score, mp.format_time(pts), case.skip))
+ mp.commandv("seek", pts + case.skip, "absolute+exact")
+ return true
+ end
+ end
+
+ return false
+end
+
+local function read_frames()
+ local result = mp.get_property_native(meta_property)
+ if result == nil then
+ return
+ end
+
+ -- Try to get all entries. Out of laziness, assume that there are at most
+ -- 100 entries. (In fact, vf_fingerprint limits it to 10.)
+ for i = 0, 99 do
+ local prefix = string.format("fp%d.", i)
+ local hex = result[prefix .. "hex"]
+
+ local pts = tonumber(result[prefix .. "pts"])
+ if (hex == nil) or (pts == nil) then
+ break
+ end
+
+ local skip = false -- blame Lua for not having "continue" or "goto", not me
+
+ -- If seeking just stopped, there will be frames before the seek target,
+ -- ignore them by checking the timestamps.
+ if playback_start_pts ~= nil then
+ if pts >= playback_start_pts then
+ playback_start_pts = nil -- just for robustness
+ else
+ skip = true
+ end
+ end
+
+ if not skip then
+ if check_fingerprint(hex, pts) then
+ break
+ end
+ end
+ end
+end
+
+mp.observe_property(meta_property, "none", function()
+ -- Ignore frames that are decoded/filtered during seeking.
+ if seeking then
+ return
+ end
+
+ read_frames()
+end)
+
+mp.observe_property("seeking", "bool", function(name, val)
+ seeking = val
+ if seeking == false then
+ playback_start_pts = mp.get_property_number("playback-time")
+ read_frames()
+ end
+end)
+
+local filters = mp.get_property_native("option-info/vf/choices", {})
+local found = false
+for _, f in ipairs(filters) do
+ if f == "fingerprint" then
+ found = true
+ break
+ end
+end
+
+if found then
+ mp.command(("no-osd vf add @%s:fingerprint"):format(label, filter))
+else
+ msg.warn("vf_fingerprint not found")
+end
diff --git a/TOOLS/lua/status-line.lua b/TOOLS/lua/status-line.lua
new file mode 100644
index 0000000..e40dce2
--- /dev/null
+++ b/TOOLS/lua/status-line.lua
@@ -0,0 +1,92 @@
+-- Rebuild the terminal status line as a lua script
+-- Be aware that this will require more cpu power!
+-- Also, this is based on a rather old version of the
+-- builtin mpv status line.
+
+-- Add a string to the status line
+function atsl(s)
+ newStatus = newStatus .. s
+end
+
+function update_status_line()
+ -- Reset the status line
+ newStatus = ""
+
+ if mp.get_property_bool("pause") then
+ atsl("(Paused) ")
+ elseif mp.get_property_bool("paused-for-cache") then
+ atsl("(Buffering) ")
+ end
+
+ if mp.get_property("aid") ~= "no" then
+ atsl("A")
+ end
+ if mp.get_property("vid") ~= "no" then
+ atsl("V")
+ end
+
+ atsl(": ")
+
+ atsl(mp.get_property_osd("time-pos"))
+
+ atsl(" / ");
+ atsl(mp.get_property_osd("duration"));
+
+ atsl(" (")
+ atsl(mp.get_property_osd("percent-pos", -1))
+ atsl("%)")
+
+ local r = mp.get_property_number("speed", -1)
+ if r ~= 1 then
+ atsl(string.format(" x%4.2f", r))
+ end
+
+ r = mp.get_property_number("avsync", nil)
+ if r ~= nil then
+ atsl(string.format(" A-V: %f", r))
+ end
+
+ r = mp.get_property("total-avsync-change", 0)
+ if math.abs(r) > 0.05 then
+ atsl(string.format(" ct:%7.3f", r))
+ end
+
+ r = mp.get_property_number("decoder-drop-frame-count", -1)
+ if r > 0 then
+ atsl(" Late: ")
+ atsl(r)
+ end
+
+ r = mp.get_property_osd("video-bitrate")
+ if r ~= nil and r ~= "" then
+ atsl(" Vb: ")
+ atsl(r)
+ end
+
+ r = mp.get_property_osd("audio-bitrate")
+ if r ~= nil and r ~= "" then
+ atsl(" Ab: ")
+ atsl(r)
+ end
+
+ r = mp.get_property_number("cache", 0)
+ if r > 0 then
+ atsl(string.format(" Cache: %d%% ", r))
+ end
+
+ -- Set the new status line
+ mp.set_property("options/term-status-msg", newStatus)
+end
+
+timer = mp.add_periodic_timer(1, update_status_line)
+
+function on_pause_change(name, value)
+ if value == false then
+ timer:resume()
+ else
+ timer:stop()
+ end
+ mp.add_timeout(0.1, update_status_line)
+end
+mp.observe_property("pause", "bool", on_pause_change)
+mp.register_event("seek", update_status_line)
diff --git a/TOOLS/lua/test-hooks.lua b/TOOLS/lua/test-hooks.lua
new file mode 100644
index 0000000..4e84d9e
--- /dev/null
+++ b/TOOLS/lua/test-hooks.lua
@@ -0,0 +1,32 @@
+local utils = require("mp.utils")
+
+function hardsleep()
+ os.execute("sleep 1s")
+end
+
+local hooks = {"on_before_start_file", "on_load", "on_load_fail",
+ "on_preloaded", "on_unload", "on_after_end_file"}
+
+for _, name in ipairs(hooks) do
+ mp.add_hook(name, 0, function()
+ print("--- hook: " .. name)
+ hardsleep()
+ print(" ... continue")
+ end)
+end
+
+local events = {"start-file", "end-file", "file-loaded", "seek",
+ "playback-restart", "idle", "shutdown"}
+for _, name in ipairs(events) do
+ mp.register_event(name, function()
+ print("--- event: " .. name)
+ end)
+end
+
+local props = {"path", "metadata"}
+for _, name in ipairs(props) do
+ mp.observe_property(name, "native", function(name, val)
+ print("property '" .. name .. "' changed to '" ..
+ utils.to_string(val) .. "'")
+ end)
+end
diff --git a/TOOLS/macos-sdk-version.py b/TOOLS/macos-sdk-version.py
new file mode 100755
index 0000000..12e1071
--- /dev/null
+++ b/TOOLS/macos-sdk-version.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+
+# This checks for the sdk path, the sdk version, and
+# the sdk build version.
+
+import re
+import os
+import string
+import subprocess
+import sys
+from shutil import which
+from subprocess import check_output
+
+def find_macos_sdk():
+ sdk = os.environ.get('MACOS_SDK', '')
+ sdk_version = os.environ.get('MACOS_SDK_VERSION', '0.0')
+ xcrun = which('xcrun')
+ xcodebuild = which('xcodebuild')
+
+ if not xcrun:
+ return sdk,sdk_version
+
+ if not sdk:
+ sdk = check_output([xcrun, '--sdk', 'macosx', '--show-sdk-path'],
+ encoding="UTF-8")
+
+ # find macOS SDK paths and version
+ if sdk_version == '0.0':
+ sdk_version = check_output([xcrun, '--sdk', 'macosx', '--show-sdk-version'],
+ encoding="UTF-8")
+
+ # use xcode tools when installed, still necessary for xcode versions <12.0
+ try:
+ sdk_version = check_output([xcodebuild, '-sdk', 'macosx', '-version', 'ProductVersion'],
+ encoding="UTF-8", stderr=subprocess.DEVNULL)
+ except:
+ pass
+
+ if not isinstance(sdk_version, str):
+ sdk_version = '10.10.0'
+
+ return sdk.strip(),sdk_version.strip()
+
+if __name__ == "__main__":
+ sdk_info = find_macos_sdk()
+ sys.stdout.write(','.join(sdk_info))
diff --git a/TOOLS/macos-swift-lib-directory.py b/TOOLS/macos-swift-lib-directory.py
new file mode 100755
index 0000000..51e8cbe
--- /dev/null
+++ b/TOOLS/macos-swift-lib-directory.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+# Finds the macos swift library directory and prints the full path to stdout.
+# First argument is the path to the swift executable.
+
+import os
+import sys
+from shutil import which
+from subprocess import check_output
+
+def find_swift_lib():
+ swift_lib_dir = os.environ.get('SWIFT_LIB_DYNAMIC', '')
+ if swift_lib_dir:
+ return swift_lib_dir
+
+ # first check for lib dir relative to swift executable
+ xcode_dir = os.path.dirname(os.path.dirname(sys.argv[1]))
+ swift_lib_dir = os.path.join(xcode_dir, "lib", "swift", "macosx")
+
+ if os.path.isdir(swift_lib_dir):
+ return swift_lib_dir
+
+ # fallback to xcode-select path
+ xcode_select = which("xcode-select")
+ if not xcode_select:
+ sys.exit(1)
+
+ xcode_path = check_output([xcode_select, "-p"], encoding="UTF-8")
+
+ swift_lib_dir = os.path.join(xcode_path, "Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx")
+ if os.path.isdir(swift_lib_dir):
+ return swift_lib_dir
+
+ # last resort if we still haven't found a path
+ swift_lib_dir = os.path.join(xcode_path, "usr/lib/swift/macosx")
+ if not os.path.isdir(swift_lib_dir):
+ sys.exit(1)
+ return swift_lib_dir
+
+if __name__ == "__main__":
+ swift_lib_dir = find_swift_lib()
+ sys.stdout.write(swift_lib_dir)
diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py
new file mode 100755
index 0000000..52bac48
--- /dev/null
+++ b/TOOLS/matroska.py
@@ -0,0 +1,479 @@
+#!/usr/bin/env python3
+"""
+Generate C definitions for parsing Matroska files.
+Can also be used to directly parse Matroska files and display their contents.
+"""
+
+#
+# This file is part of mpv.
+#
+# mpv is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# mpv is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+elements_ebml = (
+ 'EBML, 1a45dfa3, sub', (
+ 'EBMLVersion, 4286, uint',
+ 'EBMLReadVersion, 42f7, uint',
+ 'EBMLMaxIDLength, 42f2, uint',
+ 'EBMLMaxSizeLength, 42f3, uint',
+ 'DocType, 4282, str',
+ 'DocTypeVersion, 4287, uint',
+ 'DocTypeReadVersion, 4285, uint',
+ ),
+
+ 'CRC32, bf, binary',
+ 'Void, ec, binary',
+)
+
+elements_matroska = (
+ 'Segment, 18538067, sub', (
+
+ 'SeekHead*, 114d9b74, sub', (
+ 'Seek*, 4dbb, sub', (
+ 'SeekID, 53ab, ebml_id',
+ 'SeekPosition, 53ac, uint',
+ ),
+ ),
+
+ 'Info*, 1549a966, sub', (
+ 'SegmentUID, 73a4, binary',
+ 'PrevUID, 3cb923, binary',
+ 'NextUID, 3eb923, binary',
+ 'TimecodeScale, 2ad7b1, uint',
+ 'DateUTC, 4461, sint',
+ 'Title, 7ba9, str',
+ 'MuxingApp, 4d80, str',
+ 'WritingApp, 5741, str',
+ 'Duration, 4489, float',
+ ),
+
+ 'Cluster*, 1f43b675, sub', (
+ 'Timecode, e7, uint',
+ 'BlockGroup*, a0, sub', (
+ 'Block, a1, binary',
+ 'BlockDuration, 9b, uint',
+ 'ReferenceBlock*, fb, sint',
+ 'DiscardPadding, 75A2, sint',
+ 'BlockAdditions, 75A1, sub', (
+ 'BlockMore*, A6, sub', (
+ 'BlockAddID, EE, uint',
+ 'BlockAdditional, A5, binary',
+ ),
+ ),
+ ),
+ 'SimpleBlock*, a3, binary',
+ ),
+
+ 'Tracks*, 1654ae6b, sub', (
+ 'TrackEntry*, ae, sub', (
+ 'TrackNumber, d7, uint',
+ 'TrackUID, 73c5, uint',
+ 'TrackType, 83, uint',
+ 'FlagEnabled, b9, uint',
+ 'FlagDefault, 88, uint',
+ 'FlagForced, 55aa, uint',
+ 'FlagLacing, 9c, uint',
+ 'MinCache, 6de7, uint',
+ 'MaxCache, 6df8, uint',
+ 'DefaultDuration, 23e383, uint',
+ 'TrackTimecodeScale, 23314f, float',
+ 'MaxBlockAdditionID, 55ee, uint',
+ 'Name, 536e, str',
+ 'Language, 22b59c, str',
+ 'CodecID, 86, str',
+ 'CodecPrivate, 63a2, binary',
+ 'CodecName, 258688, str',
+ 'CodecDecodeAll, aa, uint',
+ 'CodecDelay, 56aa, uint',
+ 'SeekPreRoll, 56bb, uint',
+ 'Video, e0, sub', (
+ 'FlagInterlaced, 9a, uint',
+ 'PixelWidth, b0, uint',
+ 'PixelHeight, ba, uint',
+ 'DisplayWidth, 54b0, uint',
+ 'DisplayHeight, 54ba, uint',
+ 'DisplayUnit, 54b2, uint',
+ 'PixelCropTop, 54bb, uint',
+ 'PixelCropLeft, 54cc, uint',
+ 'PixelCropRight, 54dd, uint',
+ 'PixelCropBottom, 54aa, uint',
+ 'FrameRate, 2383e3, float',
+ 'ColourSpace, 2eb524, binary',
+ 'StereoMode, 53b8, uint',
+ 'Colour, 55b0, sub', (
+ 'MatrixCoefficients, 55B1, uint',
+ 'BitsPerChannel, 55B2, uint',
+ 'ChromaSubsamplingHorz, 55B3, uint',
+ 'ChromaSubsamplingVert, 55B4, uint',
+ 'CbSubsamplingHorz, 55B5, uint',
+ 'CbSubsamplingVert, 55B6, uint',
+ 'ChromaSitingHorz, 55B7, uint',
+ 'ChromaSitingVert, 55B8, uint',
+ 'Range, 55B9, uint',
+ 'TransferCharacteristics, 55BA, uint',
+ 'Primaries, 55BB, uint',
+ 'MaxCLL, 55BC, uint',
+ 'MaxFALL, 55BD, uint',
+ 'MasteringMetadata, 55D0, sub', (
+ 'PrimaryRChromaticityX, 55D1, float',
+ 'PrimaryRChromaticityY, 55D2, float',
+ 'PrimaryGChromaticityX, 55D3, float',
+ 'PrimaryGChromaticityY, 55D4, float',
+ 'PrimaryBChromaticityX, 55D5, float',
+ 'PrimaryBChromaticityY, 55D6, float',
+ 'WhitePointChromaticityX, 55D7, float',
+ 'WhitePointChromaticityY, 55D8, float',
+ 'LuminanceMax, 55D9, float',
+ 'LuminanceMin, 55DA, float',
+ ),
+ ),
+ 'Projection, 7670, sub', (
+ 'ProjectionType, 7671, uint',
+ 'ProjectionPrivate, 7672, binary',
+ 'ProjectionPoseYaw, 7673, float',
+ 'ProjectionPosePitch, 7674, float',
+ 'ProjectionPoseRoll, 7675, float',
+ ),
+ ),
+ 'Audio, e1, sub', (
+ 'SamplingFrequency, b5, float',
+ 'OutputSamplingFrequency, 78b5, float',
+ 'Channels, 9f, uint',
+ 'BitDepth, 6264, uint',
+ ),
+ 'ContentEncodings, 6d80, sub', (
+ 'ContentEncoding*, 6240, sub', (
+ 'ContentEncodingOrder, 5031, uint',
+ 'ContentEncodingScope, 5032, uint',
+ 'ContentEncodingType, 5033, uint',
+ 'ContentCompression, 5034, sub', (
+ 'ContentCompAlgo, 4254, uint',
+ 'ContentCompSettings, 4255, binary',
+ ),
+ ),
+ ),
+ ),
+ ),
+
+ 'Cues, 1c53bb6b, sub', (
+ 'CuePoint*, bb, sub', (
+ 'CueTime, b3, uint',
+ 'CueTrackPositions*, b7, sub', (
+ 'CueTrack, f7, uint',
+ 'CueClusterPosition, f1, uint',
+ 'CueRelativePosition, f0, uint',
+ 'CueDuration, b2, uint',
+ ),
+ ),
+ ),
+
+ 'Attachments, 1941a469, sub', (
+ 'AttachedFile*, 61a7, sub', (
+ 'FileDescription, 467e, str',
+ 'FileName, 466e, str',
+ 'FileMimeType, 4660, str',
+ 'FileData, 465c, binary',
+ 'FileUID, 46ae, uint',
+ ),
+ ),
+
+ 'Chapters, 1043a770, sub', (
+ 'EditionEntry*, 45b9, sub', (
+ 'EditionUID, 45bc, uint',
+ 'EditionFlagHidden, 45bd, uint',
+ 'EditionFlagDefault, 45db, uint',
+ 'EditionFlagOrdered, 45dd, uint',
+ 'ChapterAtom*, b6, sub', (
+ 'ChapterUID, 73c4, uint',
+ 'ChapterTimeStart, 91, uint',
+ 'ChapterTimeEnd, 92, uint',
+ 'ChapterFlagHidden, 98, uint',
+ 'ChapterFlagEnabled, 4598, uint',
+ 'ChapterSegmentUID, 6e67, binary',
+ 'ChapterSegmentEditionUID, 6ebc, uint',
+ 'ChapterDisplay*, 80, sub', (
+ 'ChapString, 85, str',
+ 'ChapLanguage*, 437c, str',
+ 'ChapCountry*, 437e, str',
+ ),
+ ),
+ ),
+ ),
+ 'Tags*, 1254c367, sub', (
+ 'Tag*, 7373, sub', (
+ 'Targets, 63c0, sub', (
+ 'TargetTypeValue, 68ca, uint',
+ 'TargetType, 63ca, str',
+ 'TargetTrackUID, 63c5, uint',
+ 'TargetEditionUID, 63c9, uint',
+ 'TargetChapterUID, 63c4, uint',
+ 'TargetAttachmentUID, 63c6, uint',
+ ),
+ 'SimpleTag*, 67c8, sub', (
+ 'TagName, 45a3, str',
+ 'TagLanguage, 447a, str',
+ 'TagString, 4487, str',
+ 'TagDefault, 4484, uint',
+ ),
+ ),
+ ),
+ ),
+)
+
+
+import sys
+from math import ldexp
+from binascii import hexlify
+
+def byte2num(s):
+ return int(hexlify(s), 16)
+
+class EOF(Exception): pass
+
+def camelcase_to_words(name):
+ parts = []
+ start = 0
+ for i in range(1, len(name)):
+ if name[i].isupper() and (name[i-1].islower() or
+ name[i+1:i+2].islower()):
+ parts.append(name[start:i])
+ start = i
+ parts.append(name[start:])
+ return '_'.join(parts).lower()
+
+class MatroskaElement(object):
+
+ def __init__(self, name, elid, valtype, namespace):
+ self.name = name
+ self.definename = '{0}_ID_{1}'.format(namespace, name.upper())
+ self.fieldname = camelcase_to_words(name)
+ self.structname = 'ebml_' + self.fieldname
+ self.elid = elid
+ self.valtype = valtype
+ if valtype == 'sub':
+ self.ebmltype = 'EBML_TYPE_SUBELEMENTS'
+ self.valname = 'struct ' + self.structname
+ else:
+ self.ebmltype = 'EBML_TYPE_' + valtype.upper()
+ try:
+ self.valname = {'uint': 'uint64_t', 'str': 'char *',
+ 'binary': 'bstr', 'ebml_id': 'uint32_t',
+ 'float': 'double', 'sint': 'int64_t',
+ }[valtype]
+ except KeyError:
+ raise SyntaxError('Unrecognized value type ' + valtype)
+ self.subelements = ()
+
+ def add_subelements(self, subelements):
+ self.subelements = subelements
+ self.subids = {x[0].elid for x in subelements}
+
+elementd = {}
+elementlist = []
+def parse_elems(l, namespace):
+ subelements = []
+ for el in l:
+ if isinstance(el, str):
+ name, hexid, eltype = [x.strip() for x in el.split(',')]
+ hexid = hexid.lower()
+ multiple = name.endswith('*')
+ name = name.strip('*')
+ new = MatroskaElement(name, hexid, eltype, namespace)
+ elementd[hexid] = new
+ elementlist.append(new)
+ subelements.append((new, multiple))
+ else:
+ new.add_subelements(parse_elems(el, namespace))
+ return subelements
+
+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')
+
+def generate_C_header(out):
+ printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
+ printf(out)
+
+ for el in elementlist:
+ printf(out, '#define {0.definename:40} 0x{0.elid}'.format(el))
+
+ printf(out)
+
+ for el in reversed(elementlist):
+ if not el.subelements:
+ continue
+ printf(out)
+ printf(out, 'struct {0.structname} {{'.format(el))
+ l = max(len(subel.valname) for subel, multiple in el.subelements)+1
+ for subel, multiple in el.subelements:
+ printf(out, ' {e.valname:{l}} {star}{e.fieldname};'.format(
+ e=subel, l=l, star=' *'[multiple]))
+ printf(out)
+ for subel, multiple in el.subelements:
+ printf(out, ' int n_{0.fieldname};'.format(subel))
+ printf(out, '};')
+
+ for el in elementlist:
+ if not el.subelements:
+ continue
+ printf(out, 'extern const struct ebml_elem_desc {0.structname}_desc;'.format(el))
+
+ printf(out)
+ printf(out, '#define MAX_EBML_SUBELEMENTS', max(len(el.subelements)
+ for el in elementlist))
+
+
+def generate_C_definitions(out):
+ printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
+ printf(out)
+ for el in reversed(elementlist):
+ printf(out)
+ if el.subelements:
+ printf(out, '#define N', el.fieldname)
+ printf(out, 'E_S("{0}", {1})'.format(el.name, len(el.subelements)))
+ for subel, multiple in el.subelements:
+ printf(out, 'F({0.definename}, {0.fieldname}, {1})'.format(
+ subel, int(multiple)))
+ printf(out, '}};')
+ printf(out, '#undef N')
+ else:
+ printf(out, 'E("{0.name}", {0.fieldname}, {0.ebmltype})'.format(el))
+
+def read(s, length):
+ t = s.read(length)
+ if len(t) != length:
+ raise EOF
+ return t
+
+def read_id(s):
+ t = read(s, 1)
+ i = 0
+ mask = 128
+ if ord(t) == 0:
+ raise SyntaxError
+ while not ord(t) & mask:
+ i += 1
+ mask >>= 1
+ t += read(s, i)
+ return t
+
+def read_vint(s):
+ t = read(s, 1)
+ i = 0
+ mask = 128
+ if ord(t) == 0:
+ raise SyntaxError
+ while not ord(t) & mask:
+ i += 1
+ mask >>= 1
+ t = bytes((ord(t) & (mask - 1),))
+ t += read(s, i)
+ return i+1, byte2num(t)
+
+def read_str(s, length):
+ return read(s, length)
+
+def read_uint(s, length):
+ t = read(s, length)
+ return byte2num(t)
+
+def read_sint(s, length):
+ i = read_uint(s, length)
+ mask = 1 << (length * 8 - 1)
+ if i & mask:
+ i -= 2 * mask
+ return i
+
+def read_float(s, length):
+ t = read(s, length)
+ i = byte2num(t)
+ if length == 4:
+ f = ldexp((i & 0x7fffff) + (1 << 23), (i >> 23 & 0xff) - 150)
+ if i & (1 << 31):
+ f = -f
+ elif length == 8:
+ f = ldexp((i & ((1 << 52) - 1)) + (1 << 52), (i >> 52 & 0x7ff) - 1075)
+ if i & (1 << 63):
+ f = -f
+ else:
+ raise SyntaxError
+ return f
+
+def parse_one(s, depth, parent, maxlen):
+ elid = hexlify(read_id(s)).decode('ascii')
+ elem = elementd.get(elid)
+ size, length = read_vint(s)
+ this_length = len(elid) / 2 + size + length
+ if elem is not None:
+ if elem.valtype != 'skip':
+ print(" " * depth, '[' + elid + ']', elem.name, 'size:', length, 'value:', end=' ')
+ if elem.valtype == 'sub':
+ print('subelements:')
+ while length > 0:
+ length -= parse_one(s, depth + 1, elem, length)
+ if length < 0:
+ raise SyntaxError
+ elif elem.valtype == 'str':
+ print('string', repr(read_str(s, length).decode('utf8', 'replace')))
+ elif elem.valtype in ('binary', 'ebml_id'):
+ t = read_str(s, length)
+ dec = ''
+ if elem.valtype == 'ebml_id':
+ idelem = elementd.get(hexlify(t).decode('ascii'))
+ if idelem is None:
+ dec = '(UNKNOWN)'
+ else:
+ dec = '({0.name})'.format(idelem)
+ if len(t) < 20:
+ t = hexlify(t).decode('ascii')
+ else:
+ t = '<{0} bytes>'.format(len(t))
+ print('binary', t, dec)
+ elif elem.valtype == 'uint':
+ print('uint', read_uint(s, length))
+ elif elem.valtype == 'sint':
+ print('sint', read_sint(s, length))
+ elif elem.valtype == 'float':
+ print('float', read_float(s, length))
+ elif elem.valtype == 'skip':
+ read(s, length)
+ else:
+ raise NotImplementedError
+ else:
+ print(" " * depth, '[' + elid + '] Unknown element! size:', length)
+ read(s, length)
+ return this_length
+
+if __name__ == "__main__":
+ def parse_toplevel(s):
+ parse_one(s, 0, None, 1 << 63)
+
+ if sys.argv[1] == '--generate-header':
+ generate_C_header(open(sys.argv[2], "w"))
+ elif sys.argv[1] == '--generate-definitions':
+ generate_C_definitions(open(sys.argv[2], "w"))
+ else:
+ s = open(sys.argv[1], "rb")
+ while 1:
+ start = s.tell()
+ try:
+ parse_toplevel(s)
+ except EOF:
+ if s.tell() != start:
+ raise Exception("Unexpected end of file")
+ break
diff --git a/TOOLS/mpv-osd-symbols.sfdir/.notdef.glyph b/TOOLS/mpv-osd-symbols.sfdir/.notdef.glyph
new file mode 100644
index 0000000..99cdece
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/.notdef.glyph
@@ -0,0 +1,6 @@
+StartChar: .notdef
+Encoding: 65536 -1 0
+Width: 400
+Flags: W
+LayerCount: 2
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/font.props b/TOOLS/mpv-osd-symbols.sfdir/font.props
new file mode 100644
index 0000000..8198d27
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/font.props
@@ -0,0 +1,77 @@
+SplineFontDB: 3.0
+FontName: mpv-osd-symbols-Regular
+FullName: mpv-osd-symbols Regular
+FamilyName: mpv-osd-symbols
+Weight: Normal
+Copyright: This is generated file.
+Version: 001.000
+ItalicAngle: 0
+UnderlinePosition: -133
+UnderlineWidth: 50
+Ascent: 800
+Descent: 200
+InvalidEm: 0
+sfntRevision: 0x00010000
+LayerCount: 2
+Layer: 0 0 "Back" 1
+Layer: 1 0 "Fore" 0
+XUID: [1021 879 -1597228462 15927]
+StyleMap: 0x0040
+FSType: 8
+OS2Version: 3
+OS2_WeightWidthSlopeOnly: 0
+OS2_UseTypoMetrics: 0
+CreationTime: 1408646554
+ModificationTime: 1576096543
+PfmFamily: 81
+TTFWeight: 400
+TTFWidth: 5
+LineGap: 0
+VLineGap: 0
+Panose: 0 0 5 0 0 0 0 0 0 0
+OS2TypoAscent: 800
+OS2TypoAOffset: 0
+OS2TypoDescent: -200
+OS2TypoDOffset: 0
+OS2TypoLinegap: 90
+OS2WinAscent: 1000
+OS2WinAOffset: 0
+OS2WinDescent: 200
+OS2WinDOffset: 0
+HheadAscent: 1000
+HheadAOffset: 0
+HheadDescent: -200
+HheadDOffset: 0
+OS2SubXSize: 650
+OS2SubYSize: 600
+OS2SubXOff: 0
+OS2SubYOff: 75
+OS2SupXSize: 650
+OS2SupYSize: 600
+OS2SupXOff: 0
+OS2SupYOff: 350
+OS2StrikeYSize: 50
+OS2StrikeYPos: 220
+OS2Vendor: 'PfEd'
+OS2CodePages: 00000001.00000000
+OS2UnicodeRanges: 00000000.00000000.00000000.00000000
+DEI: 91125
+LangName: 1033 "" "" "Regular" "1.000;PfEd;mpv-osd-symbols-Regular" "mpv-osd-symbols" "Version 1.000;PS 001.000;hotconv 1.0.70;makeotf.lib2.5.58329"
+Encoding: UnicodeBmp
+UnicodeInterp: none
+NameList: AGL For New Fonts
+DisplaySize: -72
+AntiAlias: 1
+FitToEm: 0
+WinInfo: 57600 8 2
+BeginPrivate: 8
+BlueValues 31 [-10 0 640 650 720 730 800 810]
+BlueScale 5 0.037
+BlueShift 1 0
+BlueFuzz 1 0
+StdHW 4 [65]
+StdVW 4 [65]
+StemSnapH 8 [65 800]
+StemSnapV 8 [65 150]
+EndPrivate
+EndSplineFont
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE001.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE001.glyph
new file mode 100644
index 0000000..ebfb715
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE001.glyph
@@ -0,0 +1,16 @@
+StartChar: uniE001
+Encoding: 57345 57345 1
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 780 20G<200 200>
+VStem: 200 375<400 400 400 800>
+LayerCount: 2
+Fore
+SplineSet
+575 400 m 1
+ 200 0 l 1
+ 200 800 l 1
+ 575 400 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE002.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE002.glyph
new file mode 100644
index 0000000..f47c153
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE002.glyph
@@ -0,0 +1,22 @@
+StartChar: uniE002
+Encoding: 57346 57346 2
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 800<200 350 200 350 200 500 500 650>
+VStem: 200 150<0 800 0 800> 500 150<0 800 0 800>
+LayerCount: 2
+Fore
+SplineSet
+350 800 m 1
+ 350 0 l 1
+ 200 0 l 1
+ 200 800 l 1
+ 350 800 l 1
+650 800 m 1
+ 650 0 l 1
+ 500 0 l 1
+ 500 800 l 1
+ 650 800 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE003.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE003.glyph
new file mode 100644
index 0000000..ba45630
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE003.glyph
@@ -0,0 +1,17 @@
+StartChar: uniE003
+Encoding: 57347 57347 3
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 720<110 775 110 775>
+VStem: 110 665<0 720 0 720>
+LayerCount: 2
+Fore
+SplineSet
+775 720 m 1
+ 775 0 l 1
+ 110 0 l 1
+ 110 720 l 1
+ 775 720 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE004.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE004.glyph
new file mode 100644
index 0000000..62ce5f7
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE004.glyph
@@ -0,0 +1,20 @@
+StartChar: uniE004
+Encoding: 57348 57348 4
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 780 20G<423 423 813 813>
+VStem: 48 765<400 400>
+LayerCount: 2
+Fore
+SplineSet
+423 800 m 1
+ 423 0 l 1
+ 48 400 l 1
+ 423 800 l 1
+813 800 m 1
+ 813 0 l 1
+ 438 400 l 1
+ 813 800 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE005.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE005.glyph
new file mode 100644
index 0000000..26db474
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE005.glyph
@@ -0,0 +1,20 @@
+StartChar: uniE005
+Encoding: 57349 57349 5
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 780 20G<95 95 485 485>
+VStem: 95 765<400 400 400 800 400 800>
+LayerCount: 2
+Fore
+SplineSet
+470 400 m 1
+ 95 0 l 1
+ 95 800 l 1
+ 470 400 l 1
+860 400 m 1
+ 485 0 l 1
+ 485 800 l 1
+ 860 400 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE006.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE006.glyph
new file mode 100644
index 0000000..474d3c1
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE006.glyph
@@ -0,0 +1,47 @@
+StartChar: uniE006
+Encoding: 57350 57350 6
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: -10 11<396 461 396 543> 196 171<397 461 396 462> 401 31 801 9<397 462 397 397>
+VStem: 20 9<369 434 369 513> 224 173<369 408 367 434 367 434> 363 34 397 65<367 404 367 408 367 404 606 801> 462 173<368 404 404 404> 830 10<368 433 433 433>
+LayerCount: 2
+Fore
+SplineSet
+430 810 m 0x9940
+ 656 810 840 626 840 400 c 0
+ 840 174 656 -10 430 -10 c 0
+ 204 -10 20 174 20 400 c 0
+ 20 626 204 810 430 810 c 0x9940
+397 606 m 1x5280
+ 462 606 l 1
+ 462 801 l 1
+ 397 801 l 1
+ 397 606 l 1x5280
+462 367 m 1
+ 462 404 l 1
+ 710 627 l 1
+ 683 657 l 1
+ 439 437 l 1
+ 363 592 l 1
+ 318 570 l 1x4280
+ 397 408 l 1
+ 397 367 l 1x44
+ 462 367 l 1
+224 434 m 1
+ 29 434 l 1
+ 29 369 l 1
+ 224 369 l 1
+ 224 434 l 1
+830 368 m 1
+ 830 433 l 1
+ 635 433 l 1
+ 635 368 l 1
+ 830 368 l 1
+461 196 m 1
+ 396 196 l 1
+ 396 1 l 1
+ 461 1 l 1
+ 461 196 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE007.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE007.glyph
new file mode 100644
index 0000000..719d9fa
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE007.glyph
@@ -0,0 +1,21 @@
+StartChar: uniE007
+Encoding: 57351 57351 7
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: -10 80<339 430 339 543> 730 80<339 430>
+VStem: 20 80<309 491 309 513>
+LayerCount: 2
+Fore
+SplineSet
+430 -10 m 0
+ 204 -10 20 174 20 400 c 0
+ 20 626 204 810 430 810 c 0
+ 656 810 840 626 840 400 c 0
+ 840 174 656 -10 430 -10 c 0
+430 70 m 1
+ 430 730 l 1
+ 248 730 100 582 100 400 c 0
+ 100 218 248 70 430 70 c 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE008.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE008.glyph
new file mode 100644
index 0000000..b36acfb
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE008.glyph
@@ -0,0 +1,37 @@
+StartChar: uniE008
+Encoding: 57352 57352 8
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: -10 80<339 521 339 543> 240 160<269 313 546 590> 480 160<408 452> 730 80<339 521>
+VStem: 20 80<309 491 309 513> 211 160<298 342> 350 160<538 582> 488 160<298 342> 760 80<309 491>
+LayerCount: 2
+Fore
+SplineSet
+430 -10 m 0xfa80
+ 204 -10 20 174 20 400 c 0
+ 20 626 204 810 430 810 c 0
+ 656 810 840 626 840 400 c 0
+ 840 174 656 -10 430 -10 c 0xfa80
+430 70 m 0
+ 612 70 760 218 760 400 c 0
+ 760 582 612 730 430 730 c 0
+ 248 730 100 582 100 400 c 0
+ 100 218 248 70 430 70 c 0
+430 480 m 0
+ 386 480 350 516 350 560 c 0
+ 350 604 386 640 430 640 c 0
+ 474 640 510 604 510 560 c 0
+ 510 516 474 480 430 480 c 0
+291 240 m 0xfd80
+ 247 240 211 276 211 320 c 0
+ 211 364 247 400 291 400 c 0
+ 335 400 371 364 371 320 c 0
+ 371 276 335 240 291 240 c 0xfd80
+568 240 m 0
+ 524 240 488 276 488 320 c 0
+ 488 364 524 400 568 400 c 0
+ 612 400 648 364 648 320 c 0
+ 648 276 612 240 568 240 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE009.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE009.glyph
new file mode 100644
index 0000000..26a2f37
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE009.glyph
@@ -0,0 +1,17 @@
+StartChar: uniE009
+Encoding: 57353 57353 9
+Width: 880
+Flags: HMW
+LayerCount: 2
+Fore
+SplineSet
+716 10 m 1
+ 378 244 l 1
+ 165 244 l 1
+ 165 559 l 1
+ 379 559 l 1
+ 716 793 l 1
+ 716 10 l 1
+EndSplineSet
+Validated: 1
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE00A.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE00A.glyph
new file mode 100644
index 0000000..634249f
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE00A.glyph
@@ -0,0 +1,50 @@
+StartChar: uniE00A
+Encoding: 57354 57354 10
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 252 48<406 406 456 456> 375 50<57 282 57 282 578 805> 500 48<406 406> 627 20G<149 149 713 713>
+VStem: 282 48<375 375 425 425> 406 50<26 252 26 252 548 774> 530 48<375 375>
+LayerCount: 2
+Fore
+SplineSet
+805 375 m 1
+ 578 375 l 1
+ 574 352 565 331 553 313 c 1
+ 713 153 l 1
+ 678 118 l 1
+ 518 278 l 1
+ 500 265 479 256 456 252 c 1
+ 456 26 l 1
+ 406 26 l 1
+ 406 252 l 1
+ 383 256 362 265 343 277 c 1
+ 184 118 l 1
+ 149 153 l 1
+ 308 312 l 1
+ 295 330 286 352 282 375 c 1
+ 57 375 l 1
+ 57 425 l 1
+ 282 425 l 1
+ 286 448 295 470 308 488 c 1
+ 149 647 l 1
+ 184 682 l 1
+ 343 523 l 1
+ 362 535 383 544 406 548 c 1
+ 406 774 l 1
+ 456 774 l 1
+ 456 548 l 1
+ 479 544 500 535 518 522 c 1
+ 678 682 l 1
+ 713 647 l 1
+ 553 487 l 1
+ 565 469 574 448 578 425 c 1
+ 805 425 l 1
+ 805 375 l 1
+430 300 m 0
+ 485 300 530 345 530 400 c 0
+ 530 455 485 500 430 500 c 0
+ 375 500 330 455 330 400 c 0
+ 330 345 375 300 430 300 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE00B.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE00B.glyph
new file mode 100644
index 0000000..21e0966
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE00B.glyph
@@ -0,0 +1,27 @@
+StartChar: uniE00B
+Encoding: 57355 57355 11
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: -10 244<341 519 341 543> 566 54<413.5 446.5 413.5 519> 740 70<413.5 446.5>
+VStem: 20 88<354 446 354 513> 752 88<354 446>
+LayerCount: 2
+Fore
+SplineSet
+430 -10 m 0
+ 204 -10 20 174 20 400 c 0
+ 20 626 204 810 430 810 c 0
+ 656 810 840 626 840 400 c 0
+ 840 174 656 -10 430 -10 c 0
+430 620 m 0
+ 463 620 490 647 490 680 c 0
+ 490 713 463 740 430 740 c 0
+ 397 740 370 713 370 680 c 0
+ 370 647 397 620 430 620 c 0
+430 234 m 0
+ 608 234 752 308 752 400 c 0
+ 752 492 608 566 430 566 c 0
+ 252 566 108 492 108 400 c 0
+ 108 308 252 234 430 234 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE010.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE010.glyph
new file mode 100644
index 0000000..519e34a
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE010.glyph
@@ -0,0 +1,21 @@
+StartChar: uniE010
+Encoding: 57360 57360 12
+Width: 334
+GlyphClass: 2
+Flags: MW
+HStem: 0 90<221 258 221 258> 550 90<221 258 221 221>
+VStem: 76 145<90 550 90 640 90 640>
+LayerCount: 2
+Fore
+SplineSet
+258 640 m 1
+ 258 550 l 1
+ 221 550 l 1
+ 221 90 l 1
+ 258 90 l 1
+ 258 0 l 1
+ 76 0 l 1
+ 76 640 l 1
+ 258 640 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE011.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE011.glyph
new file mode 100644
index 0000000..b3c7b56
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE011.glyph
@@ -0,0 +1,17 @@
+StartChar: uniE011
+Encoding: 57361 57361 13
+Width: 334
+GlyphClass: 2
+Flags: MW
+HStem: 0 640<84 250 84 250>
+VStem: 84 166<0 640 0 640>
+LayerCount: 2
+Fore
+SplineSet
+250 640 m 1
+ 250 0 l 1
+ 84 0 l 1
+ 84 640 l 1
+ 250 640 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE012.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE012.glyph
new file mode 100644
index 0000000..86c07cd
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE012.glyph
@@ -0,0 +1,21 @@
+StartChar: uniE012
+Encoding: 57362 57362 14
+Width: 334
+GlyphClass: 2
+Flags: MW
+HStem: 0 90<76 113 76 258 76 113> 550 90<76 113 76 258>
+VStem: 113 145<90 550 550 550>
+LayerCount: 2
+Fore
+SplineSet
+113 90 m 1
+ 113 550 l 1
+ 76 550 l 1
+ 76 640 l 1
+ 258 640 l 1
+ 258 0 l 1
+ 76 0 l 1
+ 76 90 l 1
+ 113 90 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE013.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE013.glyph
new file mode 100644
index 0000000..b456777
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE013.glyph
@@ -0,0 +1,17 @@
+StartChar: uniE013
+Encoding: 57363 57363 15
+Width: 334
+GlyphClass: 2
+Flags: MW
+HStem: 255 130<102 232 102 232>
+VStem: 102 130<255 385 255 385>
+LayerCount: 2
+Fore
+SplineSet
+232 385 m 1
+ 232 255 l 1
+ 102 255 l 1
+ 102 385 l 1
+ 232 385 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE101.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE101.glyph
new file mode 100644
index 0000000..8e0d4b6
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE101.glyph
@@ -0,0 +1,16 @@
+StartChar: uniE101
+Encoding: 57601 57601 16
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 780 20G<222 222>
+VStem: 222 600<400 400 400 800 400 800>
+LayerCount: 2
+Fore
+SplineSet
+822 400 m 1
+ 222 0 l 1
+ 222 800 l 1
+ 822 400 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE104.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE104.glyph
new file mode 100644
index 0000000..7c7ba13
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE104.glyph
@@ -0,0 +1,25 @@
+StartChar: uniE104
+Encoding: 57604 57604 17
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 800<3 126 3 126 3 502 3 878> 780 20G<3 126 126 126 502 502 878 878>
+VStem: 3 123<0 800 0 800>
+LayerCount: 2
+Fore
+SplineSet
+126 800 m 1x60
+ 126 0 l 1
+ 3 0 l 1xa0
+ 3 800 l 1
+ 126 800 l 1x60
+502 800 m 1
+ 502 0 l 1
+ 127 400 l 1
+ 502 800 l 1
+878 800 m 1
+ 878 0 l 1
+ 503 400 l 1
+ 878 800 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE105.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE105.glyph
new file mode 100644
index 0000000..bb0f87f
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE105.glyph
@@ -0,0 +1,25 @@
+StartChar: uniE105
+Encoding: 57605 57605 18
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 800<3 3 3 379 3 755 755 878> 780 20G<3 3 379 379 755 878 878 878>
+VStem: 755 123<0 800>
+LayerCount: 2
+Fore
+SplineSet
+3 0 m 1x60
+ 3 800 l 1
+ 378 400 l 1
+ 3 0 l 1x60
+379 0 m 1xa0
+ 379 800 l 1
+ 754 400 l 1
+ 379 0 l 1xa0
+878 0 m 1
+ 755 0 l 1
+ 755 800 l 1
+ 878 800 l 1
+ 878 0 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE106.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE106.glyph
new file mode 100644
index 0000000..b7d6ca2
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE106.glyph
@@ -0,0 +1,22 @@
+StartChar: uniE106
+Encoding: 57606 57606 19
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 649<68.5 556.5>
+VStem: 0 880
+LayerCount: 2
+Fore
+SplineSet
+880 380 m 0
+ 880 230 685 101 449 101 c 0
+ 390 101 336 107 283 118 c 1
+ 278 49 189 0 85 0 c 0
+ 52 0 24 14 0 40 c 1
+ 82 48 137 93 137 158 c 0
+ 137 163 137 168 135 172 c 0
+ 58 217 19 273 19 380 c 0
+ 19 528 258 649 449 649 c 0
+ 664 649 880 508 880 380 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE107.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE107.glyph
new file mode 100644
index 0000000..01065b6
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE107.glyph
@@ -0,0 +1,56 @@
+StartChar: uniE107
+Encoding: 57607 57607 20
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 0 60<0 60 60 820> 106 50<146 343 146 343 146 390 390 641 680 735> 207 50<99 317 99 317 99 363 363 488 529 781> 580 60<60 820 60 820>
+VStem: 0 60<60 580> 99 218<207 257 207 257> 146 197<106 156 106 156> 363 125<207 257 207 257> 529 112<106 257 106 257> 680 55<106 156 106 156> 820 60<0 60 60 580>
+LayerCount: 2
+Fore
+SplineSet
+0 60 m 2xfde0
+ 0 580 l 2
+ 0 610 30 640 60 640 c 2
+ 820 640 l 2
+ 850 640 880 610 880 580 c 2
+ 880 60 l 2
+ 880 30 850 0 820 0 c 2
+ 60 0 l 2
+ 30 0 0 30 0 60 c 2xfde0
+60 60 m 1
+ 820 60 l 1
+ 820 580 l 1
+ 60 580 l 1
+ 60 60 l 1
+317 257 m 1
+ 317 207 l 1
+ 99 207 l 1
+ 99 257 l 1
+ 317 257 l 1
+488 257 m 1
+ 488 207 l 1
+ 363 207 l 1
+ 363 257 l 1
+ 488 257 l 1
+781 257 m 1
+ 781 207 l 1
+ 529 207 l 1
+ 529 257 l 1
+ 781 257 l 1
+343 156 m 1x4280
+ 343 106 l 1
+ 146 106 l 1
+ 146 156 l 1
+ 343 156 l 1x4280
+641 156 m 1
+ 641 106 l 1
+ 390 106 l 1
+ 390 156 l 1x41c0
+ 641 156 l 1
+735 156 m 1
+ 735 106 l 1
+ 680 106 l 1
+ 680 156 l 1
+ 735 156 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE108.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE108.glyph
new file mode 100644
index 0000000..83659e3
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE108.glyph
@@ -0,0 +1,39 @@
+StartChar: uniE108
+Encoding: 57608 57608 21
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 80 60<0 60 60 820> 321 159<274 606 274 606> 660 60<60 820 60 820>
+VStem: 0 60<140 660> 274 332<321 480 321 480> 820 60<80 140 140 660>
+LayerCount: 2
+Fore
+SplineSet
+0 140 m 2
+ 0 660 l 2
+ 0 690 30 720 60 720 c 2
+ 820 720 l 2
+ 850 720 880 690 880 660 c 2
+ 880 140 l 2
+ 880 110 850 80 820 80 c 2
+ 60 80 l 2
+ 30 80 0 110 0 140 c 2
+60 140 m 1
+ 820 140 l 1
+ 820 660 l 1
+ 60 660 l 1
+ 60 140 l 1
+580 600 m 1
+ 760 600 l 1
+ 760 420 l 1
+ 580 600 l 1
+606 480 m 1
+ 606 321 l 1
+ 274 321 l 1
+ 274 480 l 1
+ 606 480 l 1
+300 200 m 1
+ 120 200 l 1
+ 120 380 l 1
+ 300 200 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE109.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE109.glyph
new file mode 100644
index 0000000..4038f3d
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE109.glyph
@@ -0,0 +1,39 @@
+StartChar: uniE109
+Encoding: 57609 57609 22
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 80 60<0 60 60 820> 296 209<285 595 285 595> 660 60<60 820 60 820>
+VStem: 0 60<140 660> 120 107<400 400 400 510> 285 310<296 505 296 505> 653 107<400 400> 820 60<80 140 140 660>
+LayerCount: 2
+Fore
+SplineSet
+0 140 m 2
+ 0 660 l 2
+ 0 690 30 720 60 720 c 2
+ 820 720 l 2
+ 850 720 880 690 880 660 c 2
+ 880 140 l 2
+ 880 110 850 80 820 80 c 2
+ 60 80 l 2
+ 30 80 0 110 0 140 c 2
+60 140 m 1
+ 820 140 l 1
+ 820 660 l 1
+ 60 660 l 1
+ 60 140 l 1
+227 400 m 1
+ 120 290 l 1
+ 120 510 l 1
+ 227 400 l 1
+760 290 m 1
+ 653 400 l 1
+ 760 510 l 1
+ 760 290 l 1
+595 505 m 1
+ 595 296 l 1
+ 285 296 l 1
+ 285 505 l 1
+ 595 505 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE10A.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE10A.glyph
new file mode 100644
index 0000000..029bf74
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE10A.glyph
@@ -0,0 +1,36 @@
+StartChar: uniE10A
+Encoding: 57610 57610 23
+Width: 1977
+GlyphClass: 2
+Flags: HMWO
+HStem: 242 248<2 155 2 156>
+LayerCount: 2
+Fore
+SplineSet
+398 58 m 1
+ 155 242 l 1
+ 2 242 l 1
+ 2 490 l 1
+ 156 490 l 1
+ 398 674 l 1
+ 398 58 l 1
+809 524 m 0
+ 814 524 819 523 823 519 c 0
+ 831 511 831 499 823 491 c 2
+ 697 365 l 1
+ 823 239 l 2
+ 830 232 830 221 823 213 c 0
+ 816 206 804 206 797 213 c 2
+ 671 339 l 1
+ 545 213 l 2
+ 538 206 525 206 518 213 c 0
+ 510 221 510 233 518 241 c 2
+ 643 367 l 1
+ 517 493 l 2
+ 510 500 510 511 517 519 c 0
+ 525 526 536 526 544 519 c 2
+ 670 393 l 1
+ 796 519 l 2
+ 799 523 804 524 809 524 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE10B.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE10B.glyph
new file mode 100644
index 0000000..780b4e4
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE10B.glyph
@@ -0,0 +1,26 @@
+StartChar: uniE10B
+Encoding: 57611 57611 24
+Width: 1977
+GlyphClass: 2
+Flags: MW
+HStem: 242 248<2 155 2 156>
+VStem: 603 47<326.5 405>
+LayerCount: 2
+Fore
+SplineSet
+650 366 m 0
+ 650 281 615 198 545 119 c 1
+ 507 142 l 1
+ 571 213 603 287 603 366 c 0
+ 603 444 571 518 507 590 c 1
+ 545 613 l 1
+ 615 534 650 452 650 366 c 0
+398 58 m 1
+ 155 242 l 1
+ 2 242 l 1
+ 2 490 l 1
+ 156 490 l 1
+ 398 674 l 1
+ 398 58 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE10C.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE10C.glyph
new file mode 100644
index 0000000..b17f57c
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE10C.glyph
@@ -0,0 +1,33 @@
+StartChar: uniE10C
+Encoding: 57612 57612 25
+Width: 1977
+GlyphClass: 2
+Flags: MW
+HStem: 242 248<2 155 2 156>
+VStem: 603 47<326.5 405> 717 47<316.5 416>
+LayerCount: 2
+Fore
+SplineSet
+764 366 m 0
+ 764 258 720 156 631 61 c 1
+ 593 84 l 1
+ 675 173 717 267 717 366 c 0
+ 717 466 675 559 593 647 c 1
+ 631 671 l 1
+ 720 578 764 476 764 366 c 0
+650 366 m 0
+ 650 281 615 198 545 119 c 1
+ 507 142 l 1
+ 571 213 603 287 603 366 c 0
+ 603 444 571 518 507 590 c 1
+ 545 613 l 1
+ 615 534 650 452 650 366 c 0
+398 58 m 1
+ 155 242 l 1
+ 2 242 l 1
+ 2 490 l 1
+ 156 490 l 1
+ 398 674 l 1
+ 398 58 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE10D.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE10D.glyph
new file mode 100644
index 0000000..24e0acf
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE10D.glyph
@@ -0,0 +1,40 @@
+StartChar: uniE10D
+Encoding: 57613 57613 26
+Width: 1977
+GlyphClass: 2
+Flags: MW
+HStem: 242 248<2 155 2 156> 709 20G<717 717>
+VStem: 603 47<326.5 405> 717 47<316.5 416> 830 47<304 426.5>
+LayerCount: 2
+Fore
+SplineSet
+877 366 m 0
+ 877 236 824 115 717 3 c 1
+ 679 27 l 1
+ 780 130 830 242 830 366 c 0
+ 830 487 780 600 679 705 c 1
+ 717 729 l 1
+ 824 617 877 496 877 366 c 0
+764 366 m 0
+ 764 258 720 156 631 61 c 1
+ 593 84 l 1
+ 675 173 717 267 717 366 c 0
+ 717 466 675 559 593 647 c 1
+ 631 671 l 1
+ 720 578 764 476 764 366 c 0
+650 366 m 0
+ 650 281 615 198 545 119 c 1
+ 507 142 l 1
+ 571 213 603 287 603 366 c 0
+ 603 444 571 518 507 590 c 1
+ 545 613 l 1
+ 615 534 650 452 650 366 c 0
+398 58 m 1
+ 155 242 l 1
+ 2 242 l 1
+ 2 490 l 1
+ 156 490 l 1
+ 398 674 l 1
+ 398 58 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE10E.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE10E.glyph
new file mode 100644
index 0000000..17b4af6
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE10E.glyph
@@ -0,0 +1,53 @@
+StartChar: uniE10E
+Encoding: 57614 57614 27
+Width: 1977
+GlyphClass: 2
+Flags: MW
+HStem: 49 95<806 818.5> 242 248<2 155 2 156>
+VStem: 559 47<326.5 405> 673 47<316.5 416> 767 91<90 103.5 648 652>
+LayerCount: 2
+Fore
+SplineSet
+720 366 m 0
+ 720 258 676 156 587 61 c 1
+ 549 84 l 1
+ 631 173 673 267 673 366 c 0
+ 673 466 631 559 549 647 c 1
+ 587 671 l 1
+ 676 578 720 476 720 366 c 0
+606 366 m 0
+ 606 281 571 198 501 119 c 1
+ 463 142 l 1
+ 527 213 559 287 559 366 c 0
+ 559 444 527 518 463 590 c 1
+ 501 613 l 1
+ 571 534 606 452 606 366 c 0
+398 58 m 1
+ 155 242 l 1
+ 2 242 l 1
+ 2 490 l 1
+ 156 490 l 1
+ 398 674 l 1
+ 398 58 l 1
+858 648 m 1
+ 824 238 l 2
+ 823 224 819 217 812 217 c 0
+ 805 217 801 224 800 238 c 2
+ 767 648 l 1
+ 767 652 l 2
+ 767 665 771 675 780 682 c 0
+ 789 690 800 694 812 694 c 0
+ 825 694 835 690 844 682 c 0
+ 853 675 858 665 858 652 c 2
+ 858 648 l 1
+858 97 m 0
+ 858 83 853 72 845 63 c 0
+ 836 53 825 49 812 49 c 0
+ 800 49 789 53 780 63 c 0
+ 771 72 767 83 767 97 c 0
+ 767 110 771 121 780 130 c 0
+ 789 140 800 144 812 144 c 0
+ 825 144 836 140 845 130 c 0
+ 853 121 858 110 858 97 c 0
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE110.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE110.glyph
new file mode 100644
index 0000000..f4d22f5
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE110.glyph
@@ -0,0 +1,16 @@
+StartChar: uniE110
+Encoding: 57616 57616 28
+Width: 880
+GlyphClass: 2
+Flags: MW
+HStem: 780 20G<656 656>
+VStem: 56 600<400 400>
+LayerCount: 2
+Fore
+SplineSet
+656 0 m 1
+ 56 400 l 1
+ 656 800 l 1
+ 656 0 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE111.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE111.glyph
new file mode 100644
index 0000000..370f803
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE111.glyph
@@ -0,0 +1,37 @@
+StartChar: uniE111
+Encoding: 57617 57617 29
+Width: 880
+GlyphClass: 2
+Flags: W
+LayerCount: 2
+Fore
+SplineSet
+0 140 m 2
+ 0 660 l 2
+ 0 690 30 720 60 720 c 2
+ 820 720 l 2
+ 850 720 880 690 880 660 c 2
+ 880 140 l 2
+ 880 110 850 80 820 80 c 2
+ 60 80 l 2
+ 30 80 0 110 0 140 c 2
+60 140 m 1
+ 820 140 l 1
+ 820 660 l 1
+ 60 660 l 1
+ 60 140 l 1
+227 400 m 1
+ 120 290 l 1
+ 120 510 l 1
+ 227 400 l 1
+760 290 m 1
+ 653 400 l 1
+ 760 510 l 1
+ 760 290 l 1
+595 505 m 1
+ 595 296 l 1
+ 285 296 l 1
+ 285 505 l 1
+ 595 505 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE112.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE112.glyph
new file mode 100644
index 0000000..90c29c2
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE112.glyph
@@ -0,0 +1,15 @@
+StartChar: uniE112
+Encoding: 57618 57618 30
+Width: 768
+VWidth: 1176
+Flags: HW
+LayerCount: 2
+Fore
+SplineSet
+512 40 m 1
+ 0 40 l 1
+ 0 168 l 1
+ 512 168 l 1
+ 512 40 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE113.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE113.glyph
new file mode 100644
index 0000000..6319b8f
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE113.glyph
@@ -0,0 +1,20 @@
+StartChar: uniE113
+Encoding: 57619 57619 31
+Width: 622
+VWidth: 1178
+Flags: HW
+LayerCount: 2
+Fore
+SplineSet
+768 42 m 5
+ 0 42 l 5
+ 0 746 l 5
+ 768 746 l 5
+ 768 42 l 5
+704 106 m 5
+ 704 618 l 5
+ 64 618 l 5
+ 64 106 l 5
+ 704 106 l 5
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE114.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE114.glyph
new file mode 100644
index 0000000..36e6577
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE114.glyph
@@ -0,0 +1,31 @@
+StartChar: uniE114
+Encoding: 57620 57620 32
+Width: 896
+VWidth: 1178
+Flags: HW
+LayerCount: 2
+Fore
+SplineSet
+768 298 m 1
+ 576 298 l 1
+ 576 42 l 1
+ 0 42 l 1
+ 0 490 l 1
+ 192 490 l 1
+ 192 746 l 1
+ 768 746 l 1
+ 768 298 l 1
+704 362 m 1
+ 704 618 l 1
+ 256 618 l 1
+ 256 490 l 1
+ 576 490 l 1
+ 576 362 l 1
+ 704 362 l 1
+512 106 m 1
+ 512 362 l 1
+ 64 362 l 1
+ 64 106 l 1
+ 512 106 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv-osd-symbols.sfdir/uniE115.glyph b/TOOLS/mpv-osd-symbols.sfdir/uniE115.glyph
new file mode 100644
index 0000000..10c1195
--- /dev/null
+++ b/TOOLS/mpv-osd-symbols.sfdir/uniE115.glyph
@@ -0,0 +1,27 @@
+StartChar: uniE115
+Encoding: 57621 57621 33
+Width: 844
+VWidth: 1112
+Flags: HW
+LayerCount: 2
+Fore
+SplineSet
+671 40 m 1
+ 575 40 l 1
+ 335 277 l 1
+ 98 40 l 1
+ -1 40 l 1
+ -1 136 l 1
+ 236 376 l 1
+ -1 613 l 1
+ -1 712 l 1
+ 98 712 l 1
+ 335 475 l 1
+ 575 712 l 1
+ 671 712 l 1
+ 671 613 l 1
+ 434 376 l 1
+ 671 139 l 1
+ 671 40 l 1
+EndSplineSet
+EndChar
diff --git a/TOOLS/mpv_identify.sh b/TOOLS/mpv_identify.sh
new file mode 100755
index 0000000..fa0e134
--- /dev/null
+++ b/TOOLS/mpv_identify.sh
@@ -0,0 +1,149 @@
+#!/bin/sh
+
+# file identification script
+#
+# manual usage:
+# mpv_identify.sh foo.mkv
+#
+# sh/dash/ksh/bash usage:
+# . mpv_identify.sh FOO_ foo.mkv
+# will fill properties into variables like FOO_length
+#
+# zsh usage:
+# mpv_identify() { emulate -L sh; . mpv_identify.sh "$@"; }
+# mpv_identify FOO_ foo.mkv
+# will fill properties into variables like FOO_length
+#
+# When multiple files were specified, their info will be put into FOO_* for the
+# first file, FOO_1_* for the second file, FOO_2_* for the third file, etc.
+
+__midentify__main() {
+
+ case "$0" in
+ mpv_identify.sh|*/mpv_identify.sh)
+ # we are NOT being sourced
+ [ -n "$1" ] && set -- '' "$@"
+ ;;
+ esac
+
+ if [ "$#" -lt 2 ]; then
+ cat >&2 <<EOF
+Usage 1 (for humans only): $0 filename.mkv
+will print all property values.
+Note that this output really shouldn't be parsed, as the
+format is subject to change.
+
+Usage 2 (for use by scripts): see top of this file
+
+NOTE: for mkv with ordered chapters, this may
+not always identify the specified file, but the
+file providing the first chapter. Specify
+--no-ordered-chapters to prevent this.
+EOF
+ return 2
+ fi
+
+ local LF="
+"
+
+ local nextprefix="$1"
+ shift
+
+ if [ -n "$nextprefix" ]; then
+ # in case of error, we always want this unset
+ unset "${nextprefix}path"
+ fi
+
+ local allprops="
+ filename
+ path
+ stream-start
+ stream-end
+ stream-length
+
+ demuxer
+
+ length
+ chapters
+ editions
+ titles
+ duration
+
+ audio
+ audio-bitrate
+ audio-codec
+ audio-codec-name
+
+ video
+ angle
+ video-bitrate
+ video-codec
+ video-format
+ video-params/aspect
+ container-fps
+ width
+ height
+ dwidth
+ dheight
+
+ sub
+ "
+ # TODO add metadata support once mpv can do it
+
+ local propstr="X-MIDENTIFY-START:$LF"
+ local key
+ for key in $allprops; do
+ propstr="${propstr}X-MIDENTIFY: $key \${=$key}$LF"
+ key="$(printf '%s\n' "$key" | tr - _)"
+ unset "$nextprefix$key"
+ done
+
+ local fileindex=0
+ local prefix=
+ local line
+ while IFS= read -r line; do
+ case "$line" in
+ X-MIDENTIFY-START:)
+ if [ -n "$nextprefix" ]; then
+ prefix="$nextprefix"
+ if [ "$fileindex" -gt 0 ]; then
+ nextprefix="${prefix%${fileindex}_}"
+ fi
+ fileindex="$((fileindex+1))"
+ nextprefix="${nextprefix}${fileindex}_"
+ for key in $allprops; do
+ key="$(printf '%s\n' "$key" | tr - _)"
+ unset "$nextprefix$key"
+ done
+ else
+ if [ "$fileindex" -gt 0 ]; then
+ printf '\n'
+ fi
+ fileindex="$((fileindex+1))"
+ fi
+ ;;
+ X-MIDENTIFY:\ *)
+ local key="${line#X-MIDENTIFY: }"
+ local value="${key#* }"
+ key="${key%% *}"
+ key="$(printf '%s\n' "$key" | tr - _)"
+ if [ -n "$nextprefix" ]; then
+ if [ -z "$prefix" ]; then
+ echo >&2 "Got X-MIDENTIFY: without X-MIDENTIFY-START:"
+ elif [ -n "$value" ]; then
+ eval "$prefix$key"='"$value"'
+ fi
+ else
+ if [ -n "$value" ]; then
+ printf '%s=%s\n' "$key" "$value"
+ fi
+ fi
+ ;;
+ esac
+ done <<EOF
+$(${MPV:-mpv} --term-playing-msg="$propstr" --vo=null --ao=null \
+ --frames=1 --quiet --no-cache --no-config -- "$@")
+EOF
+}
+
+__midentify__main "$@"
diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py
new file mode 100755
index 0000000..98699e4
--- /dev/null
+++ b/TOOLS/osxbundle.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+import os
+import shutil
+import sys
+import fileinput
+from optparse import OptionParser
+
+def sh(command):
+ return os.popen(command).read().strip()
+
+def bundle_path(binary_name):
+ return "%s.app" % binary_name
+
+def bundle_name(binary_name):
+ return os.path.basename(bundle_path(binary_name))
+
+def target_plist(binary_name):
+ return os.path.join(bundle_path(binary_name), 'Contents', 'Info.plist')
+
+def target_directory(binary_name):
+ return os.path.join(bundle_path(binary_name), 'Contents', 'MacOS')
+
+def target_binary(binary_name):
+ return os.path.join(target_directory(binary_name),
+ os.path.basename(binary_name))
+
+def copy_bundle(binary_name):
+ if os.path.isdir(bundle_path(binary_name)):
+ shutil.rmtree(bundle_path(binary_name))
+ shutil.copytree(
+ os.path.join('TOOLS', 'osxbundle', bundle_name(binary_name)),
+ bundle_path(binary_name))
+
+def copy_binary(binary_name):
+ shutil.copy(binary_name, target_binary(binary_name))
+
+def apply_plist_template(plist_file, version):
+ for line in fileinput.input(plist_file, inplace=1):
+ print(line.rstrip().replace('${VERSION}', version))
+
+def sign_bundle(binary_name):
+ sh('codesign --force --deep -s - ' + bundle_path(binary_name))
+
+def bundle_version():
+ if os.path.exists('VERSION'):
+ x = open('VERSION')
+ version = x.read()
+ x.close()
+ else:
+ version = sh("./version.sh").strip()
+ return version
+
+def main():
+ version = bundle_version().rstrip()
+
+ usage = "usage: %prog [options] arg"
+ parser = OptionParser(usage)
+ parser.add_option("-s", "--skip-deps", action="store_false", dest="deps",
+ default=True,
+ help="don't bundle the dependencies")
+
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error("incorrect number of arguments")
+ else:
+ binary_name = args[0]
+
+ print("Creating Mac OS X application bundle (version: %s)..." % version)
+ print("> copying bundle skeleton")
+ copy_bundle(binary_name)
+ print("> copying binary")
+ copy_binary(binary_name)
+ print("> generating Info.plist")
+ apply_plist_template(target_plist(binary_name), version)
+
+ if options.deps:
+ print("> bundling dependencies")
+ print(sh(" ".join(["TOOLS/dylib-unhell.py", target_binary(binary_name)])))
+
+ print("> signing bundle with ad-hoc pseudo identity")
+ sign_bundle(binary_name)
+
+ print("done.")
+
+if __name__ == "__main__":
+ main()
diff --git a/TOOLS/osxbundle/meson.build b/TOOLS/osxbundle/meson.build
new file mode 100644
index 0000000..a271b41
--- /dev/null
+++ b/TOOLS/osxbundle/meson.build
@@ -0,0 +1,8 @@
+input = join_paths(source_root, 'TOOLS', 'osxbundle',
+ 'mpv.app', 'Contents', 'Resources', 'icon.icns')
+osxbundle = custom_target('osxbundle',
+ input: input,
+ output: 'icon.icns.inc',
+ command: [file2string, '@INPUT@', '@OUTPUT@'],
+)
+sources += osxbundle
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Info.plist b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
new file mode 100644
index 0000000..e239dc7
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
@@ -0,0 +1,874 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>AAC</string>
+ <string>AC3</string>
+ <string>AIFF</string>
+ <string>M4A</string>
+ <string>MKA</string>
+ <string>MP3</string>
+ <string>OGG</string>
+ <string>PCM</string>
+ <string>VAW</string>
+ <string>WAV</string>
+ <string>WAW</string>
+ <string>WMA</string>
+ <string>aac</string>
+ <string>ac3</string>
+ <string>aiff</string>
+ <string>m4a</string>
+ <string>mka</string>
+ <string>mp3</string>
+ <string>ogg</string>
+ <string>pcm</string>
+ <string>vaw</string>
+ <string>wav</string>
+ <string>waw</string>
+ <string>wma</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Audio file</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ <key>NSPersistentStoreTypeKey</key>
+ <string>XML</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>*</string>
+ <string>*</string>
+ <string>3GP</string>
+ <string>3IV</string>
+ <string>3gp</string>
+ <string>3iv</string>
+ <string>ASF</string>
+ <string>AVI</string>
+ <string>CPK</string>
+ <string>DAT</string>
+ <string>DIVX</string>
+ <string>DV</string>
+ <string>FLAC</string>
+ <string>FLI</string>
+ <string>FLV</string>
+ <string>H264</string>
+ <string>I263</string>
+ <string>M2TS</string>
+ <string>M4V</string>
+ <string>MKV</string>
+ <string>MOV</string>
+ <string>MP2</string>
+ <string>MP4</string>
+ <string>MPEG</string>
+ <string>MPG</string>
+ <string>MPG2</string>
+ <string>MPG4</string>
+ <string>NSV</string>
+ <string>NUT</string>
+ <string>NUV</string>
+ <string>OGG</string>
+ <string>OGM</string>
+ <string>QT</string>
+ <string>RM</string>
+ <string>RMVB</string>
+ <string>VCD</string>
+ <string>VFW</string>
+ <string>VOB</string>
+ <string>WEBM</string>
+ <string>WMV</string>
+ <string>MK3D</string>
+ <string>asf</string>
+ <string>avi</string>
+ <string>cpk</string>
+ <string>dat</string>
+ <string>divx</string>
+ <string>dv</string>
+ <string>flac</string>
+ <string>fli</string>
+ <string>flv</string>
+ <string>h264</string>
+ <string>i263</string>
+ <string>m2ts</string>
+ <string>m4v</string>
+ <string>mkv</string>
+ <string>mov</string>
+ <string>mp2</string>
+ <string>mp4</string>
+ <string>mpeg</string>
+ <string>mpg</string>
+ <string>mpg2</string>
+ <string>mpg4</string>
+ <string>nsv</string>
+ <string>nut</string>
+ <string>nuv</string>
+ <string>ogg</string>
+ <string>ogm</string>
+ <string>qt</string>
+ <string>rm</string>
+ <string>rmvb</string>
+ <string>vcd</string>
+ <string>vfw</string>
+ <string>vob</string>
+ <string>webm</string>
+ <string>wmv</string>
+ <string>mk3d</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Movie file</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ <key>NSPersistentStoreTypeKey</key>
+ <string>XML</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>AQT</string>
+ <string>ASS</string>
+ <string>JSS</string>
+ <string>RT</string>
+ <string>SMI</string>
+ <string>SRT</string>
+ <string>SSA</string>
+ <string>SUB</string>
+ <string>TXT</string>
+ <string>UTF</string>
+ <string>aqt</string>
+ <string>ass</string>
+ <string>jss</string>
+ <string>rt</string>
+ <string>smi</string>
+ <string>srt</string>
+ <string>ssa</string>
+ <string>sub</string>
+ <string>txt</string>
+ <string>utf</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Subtitles file</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>LSTypeIsPackage</key>
+ <false/>
+ <key>NSPersistentStoreTypeKey</key>
+ <string>XML</string>
+ </dict>
+ </array>
+ <key>CFBundleExecutable</key>
+ <string>mpv</string>
+ <key>CFBundleIconFile</key>
+ <string>icon</string>
+ <key>CFBundleIdentifier</key>
+ <string>io.mpv</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>mpv</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${VERSION}</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>LSEnvironment</key>
+ <dict>
+ <key>MallocNanoZone</key>
+ <string>0</string>
+ <key>MPVBUNDLE</key>
+ <string>true</string>
+ </dict>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>mpv Custom Protocol</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>mpv</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>Streaming Protocol</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>mms</string>
+ <string>mmst</string>
+ <string>http</string>
+ <string>https</string>
+ <string>httpproxy</string>
+ <string>rtp</string>
+ <string>rtsp</string>
+ <string>ftp</string>
+ <string>udp</string>
+ <string>smb</string>
+ <string>srt</string>
+ <string>rist</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>CD/DVD/Bluray Media</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>cdda</string>
+ <string>dvd</string>
+ <string>vcd</string>
+ <string>bd</string>
+ </array>
+ </dict>
+ </array>
+ <key>UTImportedTypeDeclarations</key>
+ <array>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>AC3 Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.ac3</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://wiki.multimedia.cx/index.php?title=AC3</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>ac3</string>
+ <string>a52</string>
+ <string>eac3</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>DTS Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.dts</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://wiki.multimedia.cx/index.php?title=DTS</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>dts</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Free Lossless Audio Codec</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.flac</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://flac.sourceforge.net/format.html</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>flac</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Matroska Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.mka</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.matroska.org</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>mka</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Ogg Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.ogg-audio</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://xiph.org/ogg</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>ogg</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>PCM Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.pcm</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/Pulse-code_modulation</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>pcm</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.audio</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Windows Media Audio</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.wma</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/Windows_Media_Audio</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>wma</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Audio Video Interleave</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.avi</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.the-labs.com/Video/odmlff2-avidef.pdf</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>avi</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>DIVX Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.divx</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.divx.com</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>divx</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>DV Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.dv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/DV</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>dv</string>
+ <string>hdv</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Flash Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.flv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/Flash_Video</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>flv</string>
+ <string>f4v</string>
+ <string>f4p</string>
+ <string>swf</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>MPEG-2 Transport Stream</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.mts</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/.m2ts</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>trp</string>
+ <string>m2t</string>
+ <string>m2ts</string>
+ <string>mts</string>
+ <string>mtv</string>
+ <string>ts</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Matroska Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.mkv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.matroska.org</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>mkv</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Matroska stereoscopic/3D video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.mk3d</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.matroska.org</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>mk3d</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>WebM Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.webm</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.webmproject.org</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>webm</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Ogg Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.ogv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://xiph.org/ogg</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>ogm</string>
+ <string>ogv</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Real Media</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.rmvb</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.real.com</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>rmvb</string>
+ <string>rm</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Video Object</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.vob</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/VOB</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>vob</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>Windows Media Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.wmv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/Windows_Media_Video</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>wmv</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>XVID Video</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.xvid</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.xvid.org</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>xvid</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>AVC raw stream</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.h264</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://www.itu.int/rec/T-REC-H.264</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>264</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>HEVC raw stream</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.hevc</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://hevc.info</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>hevc</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>YUV stream</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.yuv</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/YUV</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>yuv</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.movie</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>YUV4MPEG2 stream</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.y4m</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://wiki.multimedia.cx/index.php?title=YUV4MPEG2</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>y4m</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.plain-text</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>SubRip Subtitle</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.subrip</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/SubRip</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>srt</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.plain-text</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>MicroDVD Subtitle</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.sub</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/MicroDVD</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>sub</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.plain-text</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>SubStation Alpha Subtitle</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.ass</string>
+ <key>UTTypeReferenceURL</key>
+ <string>https://github.com/libass/libass</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>ass</string>
+ <string>ssa</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.data</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>VobSub Subtitle</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.vobsub</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/DirectVobSub</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>idx</string>
+ <string>sub</string>
+ </array>
+ </dict>
+ </dict>
+ <dict>
+ <key>UTTypeConformsTo</key>
+ <array>
+ <string>public.plain-text</string>
+ </array>
+ <key>UTTypeDescription</key>
+ <string>SAMI Subtitle</string>
+ <key>UTTypeIconFile</key>
+ <string>document.icns</string>
+ <key>UTTypeIdentifier</key>
+ <string>io.mpv.smi</string>
+ <key>UTTypeReferenceURL</key>
+ <string>http://en.wikipedia.org/wiki/SAMI</string>
+ <key>UTTypeTagSpecification</key>
+ <dict>
+ <key>public.filename-extension</key>
+ <array>
+ <string>smi</string>
+ <string>smil</string>
+ </array>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist>
diff --git a/TOOLS/osxbundle/mpv.app/Contents/MacOS/.gitkeep b/TOOLS/osxbundle/mpv.app/Contents/MacOS/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/MacOS/.gitkeep
diff --git a/TOOLS/osxbundle/mpv.app/Contents/MacOS/lib/.gitkeep b/TOOLS/osxbundle/mpv.app/Contents/MacOS/lib/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/MacOS/lib/.gitkeep
diff --git a/TOOLS/osxbundle/mpv.app/Contents/PkgInfo b/TOOLS/osxbundle/mpv.app/Contents/PkgInfo
new file mode 100644
index 0000000..bd04210
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Resources/document.icns b/TOOLS/osxbundle/mpv.app/Contents/Resources/document.icns
new file mode 100644
index 0000000..d616296
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/Resources/document.icns
Binary files differ
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns b/TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns
new file mode 100644
index 0000000..035a934
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/Resources/icon.icns
Binary files differ
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
new file mode 100644
index 0000000..618f87e
--- /dev/null
+++ b/TOOLS/osxbundle/mpv.app/Contents/Resources/mpv.conf
@@ -0,0 +1,2 @@
+player-operation-mode=pseudo-gui
+log-file=~/Library/Logs/mpv.log
diff --git a/TOOLS/stats-conv.py b/TOOLS/stats-conv.py
new file mode 100755
index 0000000..16d787a
--- /dev/null
+++ b/TOOLS/stats-conv.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+from pyqtgraph.Qt import QtGui, QtCore
+import pyqtgraph as pg
+import sys
+import re
+
+filename = sys.argv[1]
+
+events = ".*"
+if len(sys.argv) > 2:
+ events = sys.argv[2]
+event_regex = re.compile(events)
+
+"""
+This script is meant to display stats written by mpv --dump-stats=filename.
+In general, each line in that file is an event of the form:
+
+ <timestamp in microseconds> <text> '#' <comment>
+
+e.g.:
+
+ 10474959 start flip #cplayer
+
+<text> is what MP_STATS(log, "...") writes. The rest is added by msg.c.
+
+Currently, the following event types are supported:
+
+ 'signal' <name> singular event
+ 'start' <name> start of the named event
+ 'end' <name> end of the named event
+ 'value' <float> <name> a normal value (as opposed to event)
+ 'event-timed' <ts> <name> singular event at the given timestamp
+ 'value-timed' <ts> <float> <name> a value for an event at the given timestamp
+ 'range-timed' <ts1> <ts2> <name> like start/end, but explicit times
+ <name> singular event (same as 'signal')
+
+"""
+
+class G:
+ events = {}
+ start = None
+ markers = ["o", "s", "t", "d"]
+ curveno = {}
+
+def find_marker():
+ if len(G.markers) == 0:
+ return "o"
+ m = G.markers[0]
+ G.markers = G.markers[1:]
+ return m
+
+class Event:
+ pass
+
+def get_event(event, evtype):
+ if event not in G.events:
+ e = Event()
+ e.name = event
+ e.vals = []
+ e.type = evtype
+ e.marker = "o"
+ if e.type == "event-signal":
+ e.marker = find_marker()
+ if not event_regex.match(e.name):
+ return e
+ G.events[event] = e
+ return G.events[event]
+
+colors = [(0.0, 0.5, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.75, 0.75, 0), (0.0, 0.75, 0.75), (0.75, 0, 0.75)]
+def mkColor(t):
+ return pg.mkColor(int(t[0] * 255), int(t[1] * 255), int(t[2] * 255))
+
+SCALE = 1e6 # microseconds to seconds
+
+for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
+ line = line.strip()
+ if not line:
+ continue
+ ts, event = line.split(" ", 1)
+ ts = int(ts) / SCALE
+ if G.start is None:
+ G.start = ts
+ ts = ts - G.start
+ if event.startswith("start "):
+ e = get_event(event[6:], "event")
+ e.vals.append((ts, 0))
+ e.vals.append((ts, 1))
+ elif event.startswith("end "):
+ e = get_event(event[4:], "event")
+ e.vals.append((ts, 1))
+ e.vals.append((ts, 0))
+ elif event.startswith("value "):
+ _, val, name = event.split(" ", 2)
+ val = float(val)
+ e = get_event(name, "value")
+ e.vals.append((ts, val))
+ elif event.startswith("event-timed "):
+ _, val, name = event.split(" ", 2)
+ val = int(val) / SCALE - G.start
+ e = get_event(name, "event-signal")
+ e.vals.append((val, 1))
+ elif event.startswith("range-timed "):
+ _, ts1, ts2, name = event.split(" ", 3)
+ ts1 = int(ts1) / SCALE - G.start
+ ts2 = int(ts2) / SCALE - G.start
+ e = get_event(name, "event")
+ e.vals.append((ts1, 0))
+ e.vals.append((ts1, 1))
+ e.vals.append((ts2, 1))
+ e.vals.append((ts2, 0))
+ elif event.startswith("value-timed "):
+ _, tsval, val, name = event.split(" ", 3)
+ tsval = int(tsval) / SCALE - G.start
+ val = float(val)
+ e = get_event(name, "value")
+ e.vals.append((tsval, val))
+ elif event.startswith("signal "):
+ name = event.split(" ", 2)[1]
+ e = get_event(name, "event-signal")
+ e.vals.append((ts, 1))
+ else:
+ e = get_event(event, "event-signal")
+ e.vals.append((ts, 1))
+
+# deterministically sort them; make sure the legend is sorted too
+G.sevents = list(G.events.values())
+G.sevents.sort(key=lambda x: x.name)
+hasval = False
+for e, index in zip(G.sevents, range(len(G.sevents))):
+ m = len(G.sevents)
+ if e.type == "value":
+ hasval = True
+ else:
+ e.vals = [(x, y * (m - index) / m) for (x, y) in e.vals]
+
+pg.setConfigOption('background', 'w')
+pg.setConfigOption('foreground', 'k')
+app = QtGui.QApplication([])
+win = pg.GraphicsWindow()
+#win.resize(1500, 900)
+
+ax = [None, None]
+plots = 2 if hasval else 1
+ax[0] = win.addPlot()
+if hasval:
+ win.nextRow()
+ ax[1] = win.addPlot()
+ ax[1].setXLink(ax[0])
+for cur in ax:
+ if cur is not None:
+ cur.addLegend(offset = (-1, 1))
+for e in G.sevents:
+ cur = ax[1 if e.type == "value" else 0]
+ if not cur in G.curveno:
+ G.curveno[cur] = 0
+ args = {'name': e.name,'antialias':True}
+ color = mkColor(colors[G.curveno[cur] % len(colors)])
+ if e.type == "event-signal":
+ args['symbol'] = e.marker
+ args['symbolBrush'] = pg.mkBrush(color, width=0)
+ else:
+ args['pen'] = pg.mkPen(color, width=0)
+ G.curveno[cur] += 1
+ n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
+
+QtGui.QApplication.instance().exec_()
diff --git a/TOOLS/umpv b/TOOLS/umpv
new file mode 100755
index 0000000..1eece5d
--- /dev/null
+++ b/TOOLS/umpv
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+
+"""
+This script emulates "unique application" functionality on Linux. When starting
+playback with this script, it will try to reuse an already running instance of
+mpv (but only if that was started with umpv). Other mpv instances (not started
+by umpv) are ignored, and the script doesn't know about them.
+
+This only takes filenames as arguments. Custom options can't be used; the script
+interprets them as filenames. If mpv is already running, the files passed to
+umpv are appended to mpv's internal playlist. If a file does not exist or is
+otherwise not playable, mpv will skip the playlist entry when attempting to
+play it (from the GUI perspective, it's silently ignored).
+
+If mpv isn't running yet, this script will start mpv and let it control the
+current terminal. It will not write output to stdout/stderr, because this
+will typically just fill ~/.xsession-errors with garbage.
+
+mpv will terminate if there are no more files to play, and running the umpv
+script after that will start a new mpv instance.
+
+Note: you can supply custom mpv path and options with the MPV environment
+ variable. The environment variable will be split on whitespace, and the
+ first item is used as path to mpv binary and the rest is passed as options
+ _if_ the script starts mpv. If mpv is not started by the script (i.e. mpv
+ is already running), this will be ignored.
+"""
+
+import sys
+import os
+import socket
+import errno
+import subprocess
+import string
+import shlex
+
+files = sys.argv[1:]
+
+# this is the same method mpv uses to decide this
+def is_url(filename):
+ parts = filename.split("://", 1)
+ if len(parts) < 2:
+ return False
+ # protocol prefix has no special characters => it's an URL
+ allowed_symbols = string.ascii_letters + string.digits + '_'
+ prefix = parts[0]
+ return all(map(lambda c: c in allowed_symbols, prefix))
+
+# make them absolute; also makes them safe against interpretation as options
+def make_abs(filename):
+ if not is_url(filename):
+ return os.path.abspath(filename)
+ return filename
+files = (make_abs(f) for f in files)
+
+SOCK = os.path.join(os.getenv("XDG_RUNTIME_DIR", os.getenv("HOME")), ".umpv_socket")
+
+sock = None
+try:
+ sock = socket.socket(socket.AF_UNIX)
+ sock.connect(SOCK)
+except socket.error as e:
+ if e.errno == errno.ECONNREFUSED:
+ sock = None
+ pass # abandoned socket
+ elif e.errno == errno.ENOENT:
+ sock = None
+ pass # doesn't exist
+ else:
+ raise e
+
+if sock:
+ # Unhandled race condition: what if mpv is terminating right now?
+ for f in files:
+ # escape: \ \n "
+ f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+ f = "\"" + f + "\""
+ sock.send(("raw loadfile " + f + " append\n").encode("utf-8"))
+else:
+ # Let mpv recreate socket if it doesn't already exist.
+
+ opts = shlex.split(os.getenv("MPV") or "mpv")
+ opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK,
+ "--"])
+ opts.extend(files)
+
+ subprocess.check_call(opts)
diff --git a/TOOLS/uncrustify.cfg b/TOOLS/uncrustify.cfg
new file mode 100644
index 0000000..837d9a1
--- /dev/null
+++ b/TOOLS/uncrustify.cfg
@@ -0,0 +1,165 @@
+# Usage:
+# uncrustify -l C -c TOOLS/uncrustify.cfg --no-backup --replace file.c
+#
+# Keep in mind that this uncrustify configuration still produces some
+# bad/broken formatting.
+#
+
+code_width=80
+indent_align_string=false
+indent_braces=false
+indent_braces_no_func=false
+indent_brace_parent=false
+indent_namespace=false
+indent_extern=false
+indent_class=false
+indent_class_colon=false
+indent_else_if=false
+indent_func_call_param=false
+indent_func_def_param=false
+indent_func_proto_param=false
+indent_func_class_param=false
+indent_func_ctor_var_param=false
+indent_template_param=false
+indent_func_param_double=false
+indent_relative_single_line_comments=false
+indent_col1_comment=false
+indent_access_spec_body=false
+indent_paren_nl=false
+indent_comma_paren=false
+indent_bool_paren=false
+indent_square_nl=false
+indent_preserve_sql=false
+indent_align_assign=true
+sp_balance_nested_parens=false
+align_keep_tabs=false
+align_with_tabs=false
+align_on_tabstop=false
+align_number_left=false
+align_func_params=false
+align_same_func_call_params=false
+align_var_def_colon=false
+align_var_def_attribute=false
+align_var_def_inline=false
+align_right_cmt_mix=false
+align_on_operator=false
+align_mix_var_proto=false
+align_single_line_func=false
+align_single_line_brace=false
+align_nl_cont=false
+align_left_shift=true
+nl_collapse_empty_body=false
+nl_assign_leave_one_liners=false
+nl_class_leave_one_liners=false
+nl_enum_leave_one_liners=false
+nl_getset_leave_one_liners=false
+nl_func_leave_one_liners=false
+nl_if_leave_one_liners=false
+nl_multi_line_cond=false
+nl_multi_line_define=false
+nl_before_case=false
+nl_after_case=false
+nl_after_return=false
+nl_after_semicolon=true
+nl_after_brace_open=true
+nl_after_brace_open_cmt=false
+nl_after_vbrace_open=true
+nl_after_brace_close=false
+nl_define_macro=false
+nl_squeeze_ifdef=false
+nl_ds_struct_enum_cmt=false
+nl_ds_struct_enum_close_brace=false
+nl_create_if_one_liner=false
+nl_create_for_one_liner=false
+nl_create_while_one_liner=false
+ls_for_split_full=false
+ls_func_split_full=false
+nl_after_multiline_comment=false
+eat_blanks_after_open_brace=false
+eat_blanks_before_close_brace=false
+mod_pawn_semicolon=false
+mod_full_paren_if_bool=false
+mod_remove_extra_semicolon=false
+mod_sort_import=false
+mod_sort_using=false
+mod_sort_include=false
+mod_move_case_break=false
+mod_remove_empty_return=false
+cmt_indent_multi=true
+cmt_c_group=false
+cmt_c_nl_start=false
+cmt_c_nl_end=false
+cmt_cpp_group=false
+cmt_cpp_nl_start=false
+cmt_cpp_nl_end=false
+cmt_cpp_to_c=false
+cmt_star_cont=false
+cmt_multi_check_last=true
+cmt_insert_before_preproc=false
+pp_indent_at_level=false
+pp_region_indent_code=false
+pp_if_indent_code=false
+pp_define_at_level=false
+indent_columns=4
+nl_end_of_file_min=1
+mod_full_brace_nl=2
+indent_with_tabs=0
+sp_arith=add
+sp_assign=add
+sp_enum_assign=force
+sp_bool=force
+sp_compare=force
+sp_inside_paren=remove
+sp_paren_paren=remove
+sp_before_ptr_star=force
+sp_between_ptr_star=remove
+sp_after_ptr_star=remove
+sp_after_ptr_star_func=remove
+sp_before_sparen=add
+sp_inside_sparen=remove
+sp_sparen_brace=force
+sp_before_semi=remove
+sp_after_semi_for_empty=remove
+sp_before_square=remove
+sp_inside_square=remove
+sp_after_comma=force
+sp_before_comma=remove
+sp_inside_paren_cast=remove
+sp_func_proto_paren=remove
+sp_func_def_paren=remove
+sp_inside_fparens=remove
+sp_inside_fparen=remove
+sp_square_fparen=remove
+sp_func_call_paren=remove
+sp_return_paren=force
+sp_else_brace=force
+sp_brace_else=force
+sp_brace_typedef=add
+sp_not=remove
+sp_inv=remove
+sp_addr=remove
+sp_member=remove
+sp_deref=remove
+sp_sign=remove
+sp_incdec=remove
+sp_before_nl_cont=add
+nl_end_of_file=force
+nl_if_brace=remove
+nl_brace_else=remove
+nl_elseif_brace=remove
+nl_else_brace=remove
+nl_else_if=remove
+nl_for_brace=remove
+nl_while_brace=remove
+nl_do_brace=remove
+nl_brace_while=remove
+nl_switch_brace=remove
+nl_func_type_name=remove
+nl_func_proto_type_name=remove
+nl_func_paren=remove
+nl_func_decl_start=remove
+nl_fdef_brace=force
+mod_full_brace_for=remove
+mod_full_brace_if=remove
+mod_full_brace_while=remove
+mod_paren_on_return=remove