diff --git a/dot_config/private_mpv/mpv.conf b/dot_config/private_mpv/mpv.conf
new file mode 100644
index 0000000..ff66aed
--- /dev/null
+++ b/dot_config/private_mpv/mpv.conf
@@ -0,0 +1 @@
+save-position-on-quit
diff --git a/dot_config/private_mpv/script-opts/videoclip.conf b/dot_config/private_mpv/script-opts/videoclip.conf
new file mode 100644
index 0000000..2878ba3
--- /dev/null
+++ b/dot_config/private_mpv/script-opts/videoclip.conf
@@ -0,0 +1,47 @@
+# Absolute paths to the folders where generated clips will be placed.
+# `~` is supported, but environment variables (e.g. `$HOME`) are not supported due to mpv limitations.
+video_folder_path=~/Desktop
+audio_folder_path=~/Desktop
+
+
+# Clean filenames (remove special characters) (yes or no)
+clean_filename=yes
+
+# Video settings
+video_width=-2
+video_height=720
+video_bitrate=1M
+# Available video formats: mp4, vp9, vp8
+video_format=mp4
+# The range of the scale is 0–51, where 0 is lossless,
+# 23 is the default, and 51 is worst quality possible.
+# Insane values like 9999 still work but produce the worst quality.
+video_quality=23
+# Use the slowest preset that you have patience for.
+# https://trac.ffmpeg.org/wiki/Encode/H.264
+preset=fastest
+# FPS / framerate. Set to "auto" or a number.
+video_fps=auto
+#video_fps=60
+
+# Audio settings
+# Available formats: opus or aac
+audio_format=opus
+# Opus sounds good at low bitrates 32-64k, but aac requires 128-256k.
+audio_bitrate=32k
+
+# Catbox.moe upload settings
+# Whether uploads should go to litterbox instead of catbox.
+# catbox files are stored permanently, while litterbox is temporary
+litterbox=yes
+# If using litterbox, time until video expires
+# Available values: 1h, 12h, 24h, 72h
+litterbox_expire=72h
+
+# Filename format
+# Available tags: %n = filename, %t = title, %s = start, %e = end, %d = duration,
+# %Y = year, %M = months, %D = day, %H = hours (24), %I = hours (12),
+# %P = am/pm %N = minutes, %S = seconds
+# Title will fallback to filename if it's not present
+#filename_template=%n_%s-%e(%d)
+filename_template=%n_%s-%e
diff --git a/dot_config/private_mpv/script-opts/webm.conf b/dot_config/private_mpv/script-opts/webm.conf
new file mode 100644
index 0000000..8360ef1
--- /dev/null
+++ b/dot_config/private_mpv/script-opts/webm.conf
@@ -0,0 +1,73 @@
+# Defaults to shift+w
+keybind=W
+# If empty, saves on the same directory of the playing video.
+# A starting "~" will be replaced by the home dir.
+# This field is delimited by double-square-brackets - [[ and ]] - instead of
+# quotes, because Windows users might run into a issue when using
+# backslashes as a path separator. Examples of valid inputs for this field
+# would be: [[]] (the default, empty value), [[C:\Users\John]] (on Windows),
+# and [[/home/john]] (on Unix-like systems eg. Linux).
+# The [[]] delimiter is not needed when using from a configuration file
+# in the script-opts folder.
+output_directory=~/Desktop
+run_detached=no
+# Template string for the output file
+# %f - Filename, with extension
+# %F - Filename, without extension
+# %T - Media title, if it exists, or filename, with extension (useful for some streams, such as YouTube).
+# %s, %e - Start and end time, with milliseconds
+# %S, %E - Start and end time, without milliseconds
+# %M - "-audio", if audio is enabled, empty otherwise
+# %R - "-(height)p", where height is the video's height, or scale_height, if it's enabled.
+# More specifiers are supported, see https://mpv.io/manual/master/#options-screenshot-template
+# Property expansion is supported (with %{} at top level, ${} when nested), see https://mpv.io/manual/master/#property-expansion
+output_template=%F-[%s-%e]%M
+# Scale video to a certain height, keeping the aspect ratio. -1 disables it.
+scale_height=-1
+# Change the FPS of the output video, dropping or duplicating frames as needed.
+# -1 means the FPS will be unchanged from the source.
+fps=-1
+# Target filesize, in kB. This will be used to calculate the bitrate
+# used on the encode. If this is set to <= 0, the video bitrate will be set
+# to 0, which might enable constant quality modes, depending on the
+# video codec that's used (VP8 and VP9, for example).
+target_filesize=2500
+# If true, will use stricter flags to ensure the resulting file doesn't
+# overshoot the target filesize. Not recommended, as constrained quality
+# mode should work well, unless you're really having trouble hitting
+# the target size.
+strict_filesize_constraint=no
+strict_bitrate_multiplier=0.95
+# In kilobits.
+strict_audio_bitrate=64
+# Sets the output format, from a few predefined ones.
+# Currently we have:
+# webm-vp8 (libvpx/libvorbis)
+# webm-vp9 (libvpx-vp9/libopus)
+# mp4 (h264/AAC)
+# mp4-nvenc (h264-NVENC/AAC)
+# raw (rawvideo/pcm_s16le).
+# mp3 (libmp3lame)
+# and gif
+output_format=mp4
+twopass=no
+# If set, applies the video filters currently used on the playback to the encode.
+apply_current_filters=yes
+# If set, writes the video's filename to the "Title" field on the metadata.
+write_filename_on_metadata=no
+# Set the number of encoding threads, for codecs libvpx and libvpx-vp9
+libvpx_threads=4
+additional_flags=
+# Constant Rate Factor (CRF). The value meaning and limits may change,
+# from codec to codec. Set to -1 to disable.
+crf=28
+# Useful for flags that may impact output filesize, such as qmin, qmax etc
+# Won't be applied when strict_filesize_constraint is on.
+non_strict_additional_flags=
+# Display the encode progress, in %. Requires run_detached to be disabled.
+# On Windows, it shows a cmd popup. "auto" will display progress on non-Windows platforms.
+display_progress=auto
+# The font size used in the menu. Isn't used for the notifications (started encode, finished encode etc)
+font_size=28
+margin=10
+message_duration=5
diff --git a/dot_config/private_mpv/scripts/symlink_sponsorblock-minimal.lua b/dot_config/private_mpv/scripts/symlink_sponsorblock-minimal.lua
new file mode 100644
index 0000000..b02411e
--- /dev/null
+++ b/dot_config/private_mpv/scripts/symlink_sponsorblock-minimal.lua
@@ -0,0 +1 @@
+/usr/lib/mpv/sponsorblock-minimal.lua
diff --git a/dot_config/private_mpv/scripts/videoclip/LICENSE b/dot_config/private_mpv/scripts/videoclip/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/dot_config/private_mpv/scripts/videoclip/README.md b/dot_config/private_mpv/scripts/videoclip/README.md
new file mode 100644
index 0000000..3b78284
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/README.md
@@ -0,0 +1,172 @@
+
+
+# videoclip
+
+[](https://tatsumoto-ren.github.io/blog/join-our-community.html)
+
+
+[](https://tatsumoto.neocities.org/blog/donating-to-tatsumoto.html)
+
+Easily create video and audio clips with mpv in a few keypresses.
+Videoclips are saved as `.mp4` or `.webm`.
+Subtitles can be embedded into the clips.
+
+## Prerequisites
+
+1) [Install mpv](https://mpv.io/installation/).
+2) Add the directory where `mpv` is installed
+ to the [PATH](https://www.mojeek.com/search?q=path+variable).
+
+ If you're using GNU/Linux, this step is likely unnecessary
+ because package managers (`apt`, `pacman`, etc.)
+ place executable files to `/usr/bin` which is already added to the `PATH`.
+ If you have installed `mpv` to a non-standard location,
+ or if you're not using the GNU operating system,
+ you need to make sure that `mpv` is added to the `PATH`.
+
+## Installation
+
+### Using git
+
+Clone the repository to the `mpv/scripts` directory.
+The command below works on the GNU operating system with `git` installed.
+
+``` bash
+git clone 'https://github.com/Ajatt-Tools/videoclip.git' ~/.config/mpv/scripts/videoclip
+```
+
+To update the user-script on demand later, you can execute:
+
+``` bash
+cd ~/.config/mpv/scripts/videoclip && git pull
+```
+
+### Manually
+
+Download
+[the repository](https://github.com/Ajatt-Tools/videoclip/archive/refs/heads/master.zip)
+and extract the folder containing
+`videoclip.lua`
+to your [mpv scripts](https://github.com/mpv-player/mpv/wiki/User-Scripts) directory:
+
+| OS | Location |
+| --- | --- |
+| GNU/Linux | `~/.config/mpv/scripts/` |
+| Windows | `C:/Users/Username/AppData/Roaming/mpv/scripts/` |
+
+Note: in [Celluloid](https://www.archlinux.org/packages/community/x86_64/celluloid/)
+user scripts are installed by switching to the "Plugins" tab
+in the preferences dialog and dropping the files there.
+
+
+
+Expected directory tree
+
+```
+~/.config/mpv/scripts
+|-- other_addon_1
+|-- other_addon_2
+`-- videoclip
+ |-- main.lua
+ |-- ...
+ `-- videoclip.lua
+```
+
+
+
+## Configuration
+
+The config file should be created by the user, if needed.
+
+| OS | Config location |
+| --- | --- |
+| GNU/Linux | `~/.config/mpv/script-opts/videoclip.conf` |
+| Windows | `C:/Users/Username/AppData/Roaming/mpv/script-opts/videoclip.conf` |
+
+If a parameter is not specified in the config file, the default value will be used.
+mpv doesn't tolerate spaces before and after `=`.
+
+Example configuration file:
+
+```
+# Absolute paths to the folders where generated clips will be placed.
+# `~` is supported, but environment variables (e.g. `$HOME`) are not supported due to mpv limitations.
+video_folder_path=~/Videos
+audio_folder_path=~/Music
+
+# Menu size
+font_size=24
+
+# OSD settings. Line alignment: https://aegisub.org/docs/3.2/ASS_Tags/#\an
+osd_align=7
+osd_outline=1.5
+
+# Clean filenames (remove special characters) (yes or no)
+clean_filename=yes
+
+# Video settings
+video_width=-2
+video_height=480
+video_bitrate=1M
+# Available video formats: mp4, vp9, vp8
+video_format=mp4
+# The range of the scale is 0–51, where 0 is lossless,
+# 23 is the default, and 51 is worst quality possible.
+# Insane values like 9999 still work but produce the worst quality.
+video_quality=23
+# Use the slowest preset that you have patience for.
+# https://trac.ffmpeg.org/wiki/Encode/H.264
+preset=faster
+# FPS / framerate. Set to "auto" or a number.
+video_fps=auto
+#video_fps=60
+
+# Audio settings
+# Available formats: opus or aac
+audio_format=opus
+# Opus sounds good at low bitrates 32-64k, but aac requires 128-256k.
+audio_bitrate=32k
+
+# Catbox.moe upload settings
+# Whether uploads should go to litterbox instead of catbox.
+# catbox files are stored permanently, while litterbox is temporary
+litterbox=yes
+# If using litterbox, time until video expires
+# Available values: 1h, 12h, 24h, 72h
+litterbox_expire=72h
+
+# Filename format
+# Available tags: %n = filename, %t = title, %s = start, %e = end, %d = duration,
+# %Y = year, %M = months, %D = day, %H = hours (24), %I = hours (12),
+# %P = am/pm %N = minutes, %S = seconds
+# Title will fallback to filename if it's not present
+#filename_template=%n_%s-%e(%d)
+filename_template=%n_%s-%e
+```
+
+### Key bindings
+
+| OS | Config location |
+| --- | --- |
+| GNU/Linux | `~/.config/mpv/input.conf` |
+| Windows | `C:/Users/Username/AppData/Roaming/mpv/input.conf` |
+
+Add this line if you want to change the key that opens the script's menu.
+
+```
+c script-binding videoclip-menu-open
+```
+
+## Usage
+
+- Open a file in mpv and press `c` to open the script menu.
+- Follow the onscreen instructions. You need to set the `start point`,
+`end point`, and then press `c` to create the clip.
+
+It is possible to create silent videoclips.
+To do that, first mute audio in mpv.
+The default key binding is `m`.
+
+If a video has visible subtitles, they will be embedded automatically.
+Toggle them off in mpv if you don't want any subtitles to be visible.
+The default key binding is `v`.
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/HEAD b/dot_config/private_mpv/scripts/videoclip/dot_git/HEAD
new file mode 100644
index 0000000..cb089cd
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/config b/dot_config/private_mpv/scripts/videoclip/dot_git/config
new file mode 100644
index 0000000..ae756d9
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/config
@@ -0,0 +1,11 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+[remote "origin"]
+ url = https://github.com/Ajatt-Tools/videoclip.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/description b/dot_config/private_mpv/scripts/videoclip/dot_git/description
new file mode 100644
index 0000000..498b267
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_applypatch-msg.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_applypatch-msg.sample
new file mode 100644
index 0000000..a5d7b84
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_applypatch-msg.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
+test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
+:
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_commit-msg.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_commit-msg.sample
new file mode 100644
index 0000000..b58d118
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_fsmonitor-watchman.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_fsmonitor-watchman.sample
new file mode 100644
index 0000000..23e856f
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_fsmonitor-watchman.sample
@@ -0,0 +1,174 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use IPC::Open2;
+
+# An example hook script to integrate Watchman
+# (https://facebook.github.io/watchman/) with git to speed up detecting
+# new and modified files.
+#
+# The hook is passed a version (currently 2) and last update token
+# formatted as a string and outputs to stdout a new update token and
+# all files that have been modified since the update token. Paths must
+# be relative to the root of the working tree and separated by a single NUL.
+#
+# To enable this hook, rename this file to "query-watchman" and set
+# 'git config core.fsmonitor .git/hooks/query-watchman'
+#
+my ($version, $last_update_token) = @ARGV;
+
+# Uncomment for debugging
+# print STDERR "$0 $version $last_update_token\n";
+
+# Check the hook interface version
+if ($version ne 2) {
+ die "Unsupported query-fsmonitor hook version '$version'.\n" .
+ "Falling back to scanning...\n";
+}
+
+my $git_work_tree = get_working_dir();
+
+my $retry = 1;
+
+my $json_pkg;
+eval {
+ require JSON::XS;
+ $json_pkg = "JSON::XS";
+ 1;
+} or do {
+ require JSON::PP;
+ $json_pkg = "JSON::PP";
+};
+
+launch_watchman();
+
+sub launch_watchman {
+ my $o = watchman_query();
+ if (is_work_tree_watched($o)) {
+ output_result($o->{clock}, @{$o->{files}});
+ }
+}
+
+sub output_result {
+ my ($clockid, @files) = @_;
+
+ # Uncomment for debugging watchman output
+ # open (my $fh, ">", ".git/watchman-output.out");
+ # binmode $fh, ":utf8";
+ # print $fh "$clockid\n@files\n";
+ # close $fh;
+
+ binmode STDOUT, ":utf8";
+ print $clockid;
+ print "\0";
+ local $, = "\0";
+ print @files;
+}
+
+sub watchman_clock {
+ my $response = qx/watchman clock "$git_work_tree"/;
+ die "Failed to get clock id on '$git_work_tree'.\n" .
+ "Falling back to scanning...\n" if $? != 0;
+
+ return $json_pkg->new->utf8->decode($response);
+}
+
+sub watchman_query {
+ my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
+ or die "open2() failed: $!\n" .
+ "Falling back to scanning...\n";
+
+ # In the query expression below we're asking for names of files that
+ # changed since $last_update_token but not from the .git folder.
+ #
+ # To accomplish this, we're using the "since" generator to use the
+ # recency index to select candidate nodes and "fields" to limit the
+ # output to file names only. Then we're using the "expression" term to
+ # further constrain the results.
+ my $last_update_line = "";
+ if (substr($last_update_token, 0, 1) eq "c") {
+ $last_update_token = "\"$last_update_token\"";
+ $last_update_line = qq[\n"since": $last_update_token,];
+ }
+ my $query = <<" END";
+ ["query", "$git_work_tree", {$last_update_line
+ "fields": ["name"],
+ "expression": ["not", ["dirname", ".git"]]
+ }]
+ END
+
+ # Uncomment for debugging the watchman query
+ # open (my $fh, ">", ".git/watchman-query.json");
+ # print $fh $query;
+ # close $fh;
+
+ print CHLD_IN $query;
+ close CHLD_IN;
+ my $response = do {local $/; };
+
+ # Uncomment for debugging the watch response
+ # open ($fh, ">", ".git/watchman-response.json");
+ # print $fh $response;
+ # close $fh;
+
+ die "Watchman: command returned no output.\n" .
+ "Falling back to scanning...\n" if $response eq "";
+ die "Watchman: command returned invalid output: $response\n" .
+ "Falling back to scanning...\n" unless $response =~ /^\{/;
+
+ return $json_pkg->new->utf8->decode($response);
+}
+
+sub is_work_tree_watched {
+ my ($output) = @_;
+ my $error = $output->{error};
+ if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
+ $retry--;
+ my $response = qx/watchman watch "$git_work_tree"/;
+ die "Failed to make watchman watch '$git_work_tree'.\n" .
+ "Falling back to scanning...\n" if $? != 0;
+ $output = $json_pkg->new->utf8->decode($response);
+ $error = $output->{error};
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ # Uncomment for debugging watchman output
+ # open (my $fh, ">", ".git/watchman-output.out");
+ # close $fh;
+
+ # Watchman will always return all files on the first query so
+ # return the fast "everything is dirty" flag to git and do the
+ # Watchman query just to get it over with now so we won't pay
+ # the cost in git to look up each individual file.
+ my $o = watchman_clock();
+ $error = $output->{error};
+
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ output_result($o->{clock}, ("/"));
+ $last_update_token = $o->{clock};
+
+ eval { launch_watchman() };
+ return 0;
+ }
+
+ die "Watchman: $error.\n" .
+ "Falling back to scanning...\n" if $error;
+
+ return 1;
+}
+
+sub get_working_dir {
+ my $working_dir;
+ if ($^O =~ 'msys' || $^O =~ 'cygwin') {
+ $working_dir = Win32::GetCwd();
+ $working_dir =~ tr/\\/\//;
+ } else {
+ require Cwd;
+ $working_dir = Cwd::cwd();
+ }
+
+ return $working_dir;
+}
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_post-update.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_post-update.sample
new file mode 100644
index 0000000..ec17ec1
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_post-update.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-applypatch.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-applypatch.sample
new file mode 100644
index 0000000..4142082
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-applypatch.sample
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+precommit="$(git rev-parse --git-path hooks/pre-commit)"
+test -x "$precommit" && exec "$precommit" ${1+"$@"}
+:
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-commit.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-commit.sample
new file mode 100644
index 0000000..29ed5ee
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-commit.sample
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=$(git hash-object -t tree /dev/null)
+fi
+
+# If you want to allow non-ASCII filenames set this variable to true.
+allownonascii=$(git config --type=bool hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ASCII filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+ # Note that the use of brackets around a tr range is ok here, (it's
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
+ # the square bracket bytes happen to fall in the designated range.
+ test $(git diff-index --cached --name-only --diff-filter=A -z $against |
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+ cat <<\EOF
+Error: Attempt to add a non-ASCII file name.
+
+This can cause problems if you want to work with people on other platforms.
+
+To be portable it is advisable to rename the file.
+
+If you know what you are doing you can disable this check using:
+
+ git config hooks.allownonascii true
+EOF
+ exit 1
+fi
+
+# If there are whitespace errors, print the offending file names and fail.
+exec git diff-index --check --cached $against --
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-merge-commit.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-merge-commit.sample
new file mode 100644
index 0000000..399eab1
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-merge-commit.sample
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git merge" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message to
+# stderr if it wants to stop the merge commit.
+#
+# To enable this hook, rename this file to "pre-merge-commit".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit"
+:
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-push.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-push.sample
new file mode 100644
index 0000000..4ce688d
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-push.sample
@@ -0,0 +1,53 @@
+#!/bin/sh
+
+# An example hook script to verify what is about to be pushed. Called by "git
+# push" after it has checked the remote status, but before anything has been
+# pushed. If this script exits with a non-zero status nothing will be pushed.
+#
+# This hook is called with the following parameters:
+#
+# $1 -- Name of the remote to which the push is being done
+# $2 -- URL to which the push is being done
+#
+# If pushing without using a named remote those arguments will be equal.
+#
+# Information about the commits which are being pushed is supplied as lines to
+# the standard input in the form:
+#
+#
+#
+# This sample shows how to prevent push of commits where the log message starts
+# with "WIP" (work in progress).
+
+remote="$1"
+url="$2"
+
+zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing"
+ exit 1
+ fi
+ fi
+done
+
+exit 0
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-rebase.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-rebase.sample
new file mode 100644
index 0000000..6cbef5c
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-rebase.sample
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+ topic="refs/heads/$2"
+else
+ topic=`git symbolic-ref HEAD` ||
+ exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+ ;;
+*)
+ exit 0 ;# we do not interrupt others.
+ ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master. Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+ echo >&2 "No such branch $topic"
+ exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+ echo >&2 "$topic is fully merged to master; better remove it."
+ exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next? If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+ not_in_topic=`git rev-list "^$topic" master`
+ if test -z "$not_in_topic"
+ then
+ echo >&2 "$topic is already up to date with master"
+ exit 1 ;# we could allow it, but there is no point.
+ else
+ exit 0
+ fi
+else
+ not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+ /usr/bin/perl -e '
+ my $topic = $ARGV[0];
+ my $msg = "* $topic has commits already merged to public branch:\n";
+ my (%not_in_next) = map {
+ /^([0-9a-f]+) /;
+ ($1 => 1);
+ } split(/\n/, $ARGV[1]);
+ for my $elem (map {
+ /^([0-9a-f]+) (.*)$/;
+ [$1 => $2];
+ } split(/\n/, $ARGV[2])) {
+ if (!exists $not_in_next{$elem->[0]}) {
+ if ($msg) {
+ print STDERR $msg;
+ undef $msg;
+ }
+ print STDERR " $elem->[1]\n";
+ }
+ }
+ ' "$topic" "$not_in_next" "$not_in_master"
+ exit 1
+fi
+
+<<\DOC_END
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+ merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+ it is deleted. If you need to build on top of it to correct
+ earlier mistakes, a new topic branch is created by forking at
+ the tip of the "master". This is not strictly necessary, but
+ it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+ branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next". Young
+ topic branches can have stupid mistakes you would rather
+ clean up before publishing, and things that have not been
+ merged into other branches can be easily rebased without
+ affecting other people. But once it is published, you would
+ not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+ Then you can delete it. More importantly, you should not
+ build on top of it -- other people may already want to
+ change things related to the topic as patches against your
+ "master", so if you need further changes, it is better to
+ fork the topic (perhaps with the same name) afresh from the
+ tip of "master".
+
+Let's look at this example:
+
+ o---o---o---o---o---o---o---o---o---o "next"
+ / / / /
+ / a---a---b A / /
+ / / / /
+ / / c---c---c---c B /
+ / / / \ /
+ / / / b---b C \ /
+ / / / / \ /
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished. It has been fully merged up to "master" and "next",
+ and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+ git rev-list ^master ^topic next
+ git rev-list ^master next
+
+ if these match, topic has not merged in next at all.
+
+To compute (2):
+
+ git rev-list master..topic
+
+ if this is empty, it is fully merged to "master".
+
+DOC_END
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-receive.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-receive.sample
new file mode 100644
index 0000000..a1fd29e
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_pre-receive.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to make use of push options.
+# The example simply echoes all push options that start with 'echoback='
+# and rejects all pushes when the "reject" push option is used.
+#
+# To enable this hook, rename this file to "pre-receive".
+
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
+ i=0
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ case "$value" in
+ echoback=*)
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
+ ;;
+ reject)
+ exit 1
+ esac
+ i=$((i + 1))
+ done
+fi
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_prepare-commit-msg.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_prepare-commit-msg.sample
new file mode 100644
index 0000000..10fa14c
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_prepare-commit-msg.sample
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source. The hook's purpose is to edit the commit
+# message file. If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples. The first one removes the
+# "# Please enter the commit message..." help message.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output. It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited. This is rarely a good idea.
+
+COMMIT_MSG_FILE=$1
+COMMIT_SOURCE=$2
+SHA1=$3
+
+/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
+
+# case "$COMMIT_SOURCE,$SHA1" in
+# ,|template,)
+# /usr/bin/perl -i.bak -pe '
+# print "\n" . `git diff --cached --name-status -r`
+# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
+# *) ;;
+# esac
+
+# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
+# if test -z "$COMMIT_SOURCE"
+# then
+# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
+# fi
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_push-to-checkout.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_push-to-checkout.sample
new file mode 100644
index 0000000..af5a0c0
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_push-to-checkout.sample
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# An example hook script to update a checked-out tree on a git push.
+#
+# This hook is invoked by git-receive-pack(1) when it reacts to git
+# push and updates reference(s) in its repository, and when the push
+# tries to update the branch that is currently checked out and the
+# receive.denyCurrentBranch configuration variable is set to
+# updateInstead.
+#
+# By default, such a push is refused if the working tree and the index
+# of the remote repository has any difference from the currently
+# checked out commit; when both the working tree and the index match
+# the current commit, they are updated to match the newly pushed tip
+# of the branch. This hook is to be used to override the default
+# behaviour; however the code below reimplements the default behaviour
+# as a starting point for convenient modification.
+#
+# The hook receives the commit with which the tip of the current
+# branch is going to be updated:
+commit=$1
+
+# It can exit with a non-zero status to refuse the push (when it does
+# so, it must not modify the index or the working tree).
+die () {
+ echo >&2 "$*"
+ exit 1
+}
+
+# Or it can make any necessary changes to the working tree and to the
+# index to bring them to the desired state when the tip of the current
+# branch is updated to the new commit, and exit with a zero status.
+#
+# For example, the hook can simply run git read-tree -u -m HEAD "$1"
+# in order to emulate git fetch that is run in the reverse direction
+# with git push, as the two-tree form of git read-tree -u -m is
+# essentially the same as git switch or git checkout that switches
+# branches while keeping the local changes in the working tree that do
+# not interfere with the difference between the branches.
+
+# The below is a more-or-less exact translation to shell of the C code
+# for the default behaviour for git's push-to-checkout hook defined in
+# the push_to_deploy() function in builtin/receive-pack.c.
+#
+# Note that the hook will be executed from the repository directory,
+# not from the working tree, so if you want to perform operations on
+# the working tree, you will have to adapt your code accordingly, e.g.
+# by adding "cd .." or using relative paths.
+
+if ! git update-index -q --ignore-submodules --refresh
+then
+ die "Up-to-date check failed"
+fi
+
+if ! git diff-files --quiet --ignore-submodules --
+then
+ die "Working directory has unstaged changes"
+fi
+
+# This is a rough translation of:
+#
+# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
+if git cat-file -e HEAD 2>/dev/null
+then
+ head=HEAD
+else
+ head=$(git hash-object -t tree --stdin &2
+ exit 1
+}
+
+unset GIT_DIR GIT_WORK_TREE
+cd "$worktree" &&
+
+if grep -q "^diff --git " "$1"
+then
+ validate_patch "$1"
+else
+ validate_cover_letter "$1"
+fi &&
+
+if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
+then
+ git config --unset-all sendemail.validateWorktree &&
+ trap 'git worktree remove -ff "$worktree"' EXIT &&
+ validate_series
+fi
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_update.sample b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_update.sample
new file mode 100644
index 0000000..c4d426b
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/hooks/executable_update.sample
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to block unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+# This boolean sets whether unannotated tags will be allowed into the
+# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowmodifytag
+# This boolean sets whether a tag may be modified after creation. By default
+# it won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
+# hooks.denycreatebranch
+# This boolean sets whether remotely creating branches will be denied
+# in the repository. By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 [ )" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "usage: $0 ][ " >&2
+ exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --type=bool hooks.allowunannotated)
+allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
+allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
+allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+ echo "*** Project description file hasn't been set" >&2
+ exit 1
+ ;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero=$(git hash-object --stdin &2
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,tag)
+ # annotated tag
+ if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+ then
+ echo "*** Tag '$refname' already exists." >&2
+ echo "*** Modifying a tag is not allowed in this repository." >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,commit)
+ # branch
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+ echo "*** Creating a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ ;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+ exit 1
+ ;;
+esac
+
+# --- Finished
+exit 0
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/index b/dot_config/private_mpv/scripts/videoclip/dot_git/index
new file mode 100644
index 0000000..f71c360
Binary files /dev/null and b/dot_config/private_mpv/scripts/videoclip/dot_git/index differ
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/info/exclude b/dot_config/private_mpv/scripts/videoclip/dot_git/info/exclude
new file mode 100644
index 0000000..a5196d1
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/logs/HEAD b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/HEAD
new file mode 100644
index 0000000..8dfb699
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 44d4087c5fd9cc15e73309778b3af5f73064b358 Rain 1759363834 -0400 clone: from https://github.com/Ajatt-Tools/videoclip.git
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/heads/master b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/heads/master
new file mode 100644
index 0000000..8dfb699
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 44d4087c5fd9cc15e73309778b3af5f73064b358 Rain 1759363834 -0400 clone: from https://github.com/Ajatt-Tools/videoclip.git
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/remotes/origin/HEAD b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/remotes/origin/HEAD
new file mode 100644
index 0000000..8dfb699
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 44d4087c5fd9cc15e73309778b3af5f73064b358 Rain 1759363834 -0400 clone: from https://github.com/Ajatt-Tools/videoclip.git
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/objects/info/.keep b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/info/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.idx b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.idx
new file mode 100644
index 0000000..29f9bc3
Binary files /dev/null and b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.idx differ
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.pack b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.pack
new file mode 100644
index 0000000..11217fa
Binary files /dev/null and b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.pack differ
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.rev b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.rev
new file mode 100644
index 0000000..4549f32
Binary files /dev/null and b/dot_config/private_mpv/scripts/videoclip/dot_git/objects/pack/readonly_pack-98877bd46235e2bcf152cdad6227778126a1e6c9.rev differ
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/packed-refs b/dot_config/private_mpv/scripts/videoclip/dot_git/packed-refs
new file mode 100644
index 0000000..aa9f642
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/packed-refs
@@ -0,0 +1,6 @@
+# pack-refs with: peeled fully-peeled sorted
+44d4087c5fd9cc15e73309778b3af5f73064b358 refs/remotes/origin/master
+08058ab4b73639120f68b8022d46b9b8ae2f2d46 refs/tags/v0.1
+^249122d245bc5ec2a0687346af730b1cc2273b21
+b8a9c7bf38c4f1b3a069b4d2fedc99fde5464872 refs/tags/v0.2
+^785eb86bc080c445e8feb947d7caa8f3a097bf2b
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/refs/heads/master b/dot_config/private_mpv/scripts/videoclip/dot_git/refs/heads/master
new file mode 100644
index 0000000..5c9c190
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/refs/heads/master
@@ -0,0 +1 @@
+44d4087c5fd9cc15e73309778b3af5f73064b358
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/refs/remotes/origin/HEAD b/dot_config/private_mpv/scripts/videoclip/dot_git/refs/remotes/origin/HEAD
new file mode 100644
index 0000000..6efe28f
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_git/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_git/refs/tags/.keep b/dot_config/private_mpv/scripts/videoclip/dot_git/refs/tags/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_github/FUNDING.yml b/dot_config/private_mpv/scripts/videoclip/dot_github/FUNDING.yml
new file mode 100644
index 0000000..8039a43
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_github/FUNDING.yml
@@ -0,0 +1,5 @@
+# These are supported funding model platforms
+
+patreon: tatsumoto_ren
+liberapay: Tatsumoto
+custom: https://tatsumoto.neocities.org/blog/donating-to-tatsumoto.html
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/config.yml b/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..54f5b38
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,13 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Bug Reports
+ url: https://tatsumoto.neocities.org/blog/join-our-community
+ about: This issue tracker is for developers. Please report any bugs you encounter over in the chats, and they will be triaged there.
+
+ - name: Questions/Support
+ url: https://tatsumoto.neocities.org/blog/join-our-community
+ about: If you have a question or need support, please post in the community chats.
+
+ - name: Feature Requests/Suggestions
+ url: https://tatsumoto.neocities.org/blog/join-our-community
+ about: Please post suggestions and feature requests in our chat.
diff --git a/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/issue.md b/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/issue.md
new file mode 100644
index 0000000..f4a2a01
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/dot_github/ISSUE_TEMPLATE/issue.md
@@ -0,0 +1,37 @@
+---
+name: Issue
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+- The add-on crashes?
+- The add-on stopped working?
+- Have a feature request?
+
+Please ask on https://tatsumoto.neocities.org/blog/join-our-community.html instead.
+It is easier for our members to provide support over in the chats. Thank you!
+
+Don't forget to provide the following information:
+
+**Environment**
+
+1) OS name [e.g. Parabola, Debian]
+2) OS version
+3) mpv version
+4) Logs
+5) Screenshots, if applicable
+
+**Describe the bug**
+A description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1) Press x '...'
+2) Click on '....'
+3) See error
+
+**Expected behavior**
+A description of what you expected to happen.
diff --git a/dot_config/private_mpv/scripts/videoclip/encoder.lua b/dot_config/private_mpv/scripts/videoclip/encoder.lua
new file mode 100644
index 0000000..ea206b0
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/encoder.lua
@@ -0,0 +1,246 @@
+--[[
+Copyright: Ren Tatsumoto and contributors
+License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
+
+Encoder provides interface for creating audio/video clips.
+]]
+
+local mp = require('mp')
+local h = require('helpers')
+local utils = require('mp.utils')
+local this = {}
+
+local function toms(timestamp)
+ --- Trim timestamp down to milliseconds.
+ return string.format("%.3f", timestamp)
+end
+
+local function clean_filename(filename)
+ filename = h.remove_extension(filename)
+ if this.config.clean_filename then
+ filename = h.remove_text_in_brackets(filename)
+ filename = h.remove_special_characters(filename)
+ -- remove_text_in_brackets might leave spaces at the start or the end, so trim those
+ filename = h.strip(filename)
+ end
+ return filename
+end
+
+local function clean_forbidden_characters(title)
+ return title:gsub('[<>:"/\\|%?%*]+', '.')
+end
+
+local function construct_output_filename_noext()
+
+ local filename = mp.get_property("filename") -- filename without path
+ local title = mp.get_property("media-title") -- if the video doesn't have a title, it will fallback to filename
+ local date = os.date("*t") -- get current date and time as table
+
+ -- Apply the same operation when the video doesn't have a title
+ -- thus it will be the same as filename
+ if title == filename then
+ filename = clean_filename(filename)
+ title = filename
+ else
+ filename = clean_filename(filename)
+ title = clean_forbidden_characters(title)
+ end
+
+ -- Available tags: %n = filename, %t = title, %s = start, %e = end, %d = duration,
+ -- %Y = year, %M = months, %D = day, %H = hours (24), %I = hours (12),
+ -- %P = am/pm %N = minutes, %S = seconds
+ filename = this.config.filename_template
+ :gsub("%%n", filename)
+ :gsub("%%t", title)
+ :gsub("%%s", h.human_readable_time(this.timings['start']))
+ :gsub("%%e", h.human_readable_time(this.timings['end']))
+ :gsub("%%d", h.human_readable_time(this.timings['end'] - this.timings['start']))
+ :gsub("%%Y", date.year)
+ :gsub("%%M", h.two_digit(date.month))
+ :gsub("%%D", h.two_digit(date.day))
+ :gsub("%%H", h.two_digit(date.hour))
+ :gsub("%%I", h.two_digit(h.twelve_hour(date.hour)['hour']))
+ :gsub("%%P", h.twelve_hour(date.hour)['sign'])
+ :gsub("%%N", h.two_digit(date.min))
+ :gsub("%%S", h.two_digit(date.sec))
+
+ return filename
+end
+
+function this.get_ext_subs_paths()
+ local track_list = mp.get_property_native('track-list')
+ local external_subs_list = {}
+ for _, track in pairs(track_list) do
+ if track.type == 'sub' and track.external == true then
+ external_subs_list[track.id] = track['external-filename']
+ end
+ end
+ return external_subs_list
+end
+
+function this.append_embed_subs_args(args)
+ local ext_subs_paths = this.get_ext_subs_paths()
+ for _, ext_subs_path in pairs(ext_subs_paths) do
+ table.insert(args, #args, table.concat { '--sub-files-append=', ext_subs_path, })
+ end
+ return args
+end
+
+this.mk_out_path_video = function(clip_filename_noext)
+ return utils.join_path(h.expand_path(this.config.video_folder_path), clip_filename_noext .. this.config.video_extension)
+end
+
+this.mkargs_video = function(out_clip_path)
+ local args = {
+ this.player,
+ mp.get_property('path'),
+ '--loop-file=no',
+ '--keep-open=no',
+ '--no-ocopy-metadata',
+ '--no-sub',
+ '--audio-channels=2',
+ '--oacopts-add=vbr=on',
+ '--oacopts-add=application=voip',
+ '--oacopts-add=compression_level=10',
+ '--vf-add=format=yuv420p',
+ '--sub-font-provider=auto',
+ '--embeddedfonts=yes',
+ table.concat { '--sub-font=', this.config.sub_font },
+ table.concat { '--ovc=', this.config.video_codec },
+ table.concat { '--oac=', this.config.audio_codec },
+ table.concat { '--start=', toms(this.timings['start']) },
+ table.concat { '--end=', toms(this.timings['end']) },
+ table.concat { '--aid=', mp.get_property("aid") }, -- track number
+ table.concat { '--mute=', mp.get_property("mute") },
+ table.concat { '--volume=', mp.get_property('volume') },
+ table.concat { '--ovcopts-add=b=', this.config.video_bitrate },
+ table.concat { '--oacopts-add=b=', this.config.audio_bitrate },
+ table.concat { '--ovcopts-add=crf=', this.config.video_quality },
+ table.concat { '--ovcopts-add=preset=', this.config.preset },
+ table.concat { '--vf-add=scale=', this.config.video_width, ':', this.config.video_height },
+ table.concat { '--ytdl-format=', mp.get_property("ytdl-format") },
+ table.concat { '--o=', out_clip_path },
+ table.concat { '--sid=', mp.get_property("sid") },
+ table.concat { '--secondary-sid=', mp.get_property("secondary-sid") },
+ table.concat { '--sub-delay=', mp.get_property("sub-delay") },
+ table.concat { '--sub-visibility=', mp.get_property("sub-visibility") },
+ table.concat { '--secondary-sub-visibility=', mp.get_property("secondary-sub-visibility") },
+ table.concat { '--sub-back-color=', mp.get_property("sub-back-color") },
+ table.concat { '--sub-border-style=', mp.get_property("sub-border-style") },
+ }
+
+ if this.config.video_fps ~= 'auto' then
+ table.insert(args, #args, table.concat { '--vf-add=fps=', this.config.video_fps })
+ end
+
+ args = this.append_embed_subs_args(args)
+
+ return args
+end
+
+this.mk_out_path_audio = function(clip_filename_noext)
+ return utils.join_path(h.expand_path(this.config.audio_folder_path), clip_filename_noext .. this.config.audio_extension)
+end
+
+this.mkargs_audio = function(out_clip_path)
+ return {
+ this.player,
+ mp.get_property('path'),
+ '--loop-file=no',
+ '--keep-open=no',
+ '--no-ocopy-metadata',
+ '--no-sub',
+ '--audio-channels=2',
+ '--video=no',
+ '--oacopts-add=vbr=on',
+ '--oacopts-add=application=voip',
+ '--oacopts-add=compression_level=10',
+ table.concat { '--oac=', this.config.audio_codec },
+ table.concat { '--start=', toms(this.timings['start']) },
+ table.concat { '--end=', toms(this.timings['end']) },
+ table.concat { '--volume=', mp.get_property('volume') },
+ table.concat { '--aid=', mp.get_property("aid") }, -- track number
+ table.concat { '--oacopts-add=b=', this.config.audio_bitrate },
+ table.concat { '--ytdl-format=', mp.get_property("ytdl-format") },
+ table.concat { '--o=', out_clip_path }
+ }
+end
+
+this.create_clip = function(clip_type, on_complete)
+ if clip_type == nil then
+ return
+ end
+
+ if not this.timings:validate() then
+ h.notify_error("Wrong timings. Aborting.", "warn", 2)
+ return
+ end
+
+ h.notify("Please wait...", "info", 9999)
+
+ local output_file_path, args = (function()
+ local clip_filename_noext = construct_output_filename_noext()
+ if clip_type == 'video' then
+ local output_path = this.mk_out_path_video(clip_filename_noext)
+ return output_path, this.mkargs_video(output_path)
+ else
+ local output_path = this.mk_out_path_audio(clip_filename_noext)
+ return output_path, this.mkargs_audio(output_path)
+ end
+ end)()
+
+ print("The following args will be executed:", table.concat(h.quote_if_necessary(args), " "))
+
+ local output_dir_path = utils.split_path(output_file_path)
+ local location_info = utils.file_info(output_dir_path)
+ if not location_info or not location_info.is_dir then
+ h.notify_error(string.format("Error: location %s doesn't exist.", output_dir_path), "error", 5)
+ return
+ end
+
+ local process_result = function(_, ret, _)
+ if ret.status ~= 0 or string.match(ret.stdout, "could not open") then
+ h.notify_error(string.format("Error: couldn't create clip %s.", output_file_path), "error", 5)
+ else
+ h.notify(string.format("Clip saved to %s.", output_file_path), "info", 2)
+ if on_complete then
+ on_complete(output_file_path)
+ end
+ end
+ end
+
+ h.subprocess_async(args, process_result)
+ this.timings:reset()
+end
+
+this.set_encoder_alive = function()
+ local args_mpvnet = { 'mpvnet', '--version' }
+ local process_result_mpvnet = function(_, ret, _)
+ -- for some reason stdout is empty
+ if ret.status ~= 0 then
+ this.alive = false
+ else
+ this.alive = true
+ this.player = 'mpvnet'
+ end
+ end
+
+ local args = { 'mpv', '--version' }
+ local process_result = function(_, ret, _)
+ if ret.status ~= 0 or string.match(ret.stdout, "mpv") == nil then
+ h.subprocess_async(args_mpvnet, process_result_mpvnet)
+ else
+ this.alive = true
+ this.player = 'mpv'
+ end
+ end
+ h.subprocess_async(args, process_result)
+end
+
+this.init = function(config, timings_mgr)
+ this.config = config
+ this.timings = timings_mgr
+ this.set_encoder_alive()
+end
+
+return this
diff --git a/dot_config/private_mpv/scripts/videoclip/helpers.lua b/dot_config/private_mpv/scripts/videoclip/helpers.lua
new file mode 100644
index 0000000..96e02c8
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/helpers.lua
@@ -0,0 +1,141 @@
+--[[
+Copyright: Ren Tatsumoto and contributors
+License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
+
+Various helper functions.
+]]
+
+local mp = require('mp')
+local this = {}
+local ass_start = mp.get_property_osd("osd-ass-cc/0")
+
+this.is_wayland = function()
+ return os.getenv('WAYLAND_DISPLAY') ~= nil
+end
+
+this.is_win = function()
+ return mp.get_property("platform") == "windows"
+end
+
+this.is_mac = function()
+ return mp.get_property("platform") == "darwin"
+end
+
+this.notify = function(message, level, duration)
+ level = level or 'info'
+ duration = duration or 1
+ mp.msg[level](message)
+ mp.osd_message(ass_start .. "{\\fs12}{\\bord0.75}" .. message, duration)
+end
+
+this.notify_error = function(message, level, duration)
+ this.notify("{\\c&H7171f8&}" .. message, level, duration)
+end
+
+this.subprocess = function(args, stdin)
+ local command_table = {
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ args = args,
+ stdin_data = (stdin or ""),
+ }
+ return mp.command_native(command_table)
+end
+
+this.subprocess_async = function(args, on_complete)
+ local command_table = {
+ name = "subprocess",
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ args = args
+ }
+ return mp.command_native_async(command_table, on_complete)
+end
+
+this.remove_extension = function(filename)
+ return filename:gsub('%.%w+$', '')
+end
+
+this.remove_text_in_brackets = function(str)
+ return str:gsub('%b[]', '')
+end
+
+this.remove_special_characters = function(str)
+ return str:gsub('[%-_]', ' '):gsub('[%c%p]', ''):gsub('%s+', ' ')
+end
+
+this.strip = function(str)
+ return str:gsub("^%s*(.-)%s*$", "%1")
+end
+
+this.two_digit = function(num)
+ return string.format("%02d", num)
+end
+
+this.twelve_hour = function(num)
+ local sign = "pm"
+ local hour = num
+
+ if num > 12 then
+ hour = hour - 12
+ else
+ sign = "am"
+ end
+
+ return { sign = sign, hour = hour }
+end
+
+this.expand_path = function (str)
+ return mp.command_native({"expand-path", str})
+end
+
+this.human_readable_time = function(seconds)
+ if type(seconds) ~= 'number' or seconds < 0 then
+ return 'empty'
+ end
+
+ local parts = {}
+
+ parts.h = math.floor(seconds / 3600)
+ parts.m = math.floor(seconds / 60) % 60
+ parts.s = math.floor(seconds % 60)
+ parts.ms = math.floor((seconds * 1000) % 1000)
+
+ local ret = string.format("%02dm%02ds%03dms", parts.m, parts.s, parts.ms)
+
+ if parts.h > 0 then
+ ret = string.format('%dh%s', parts.h, ret)
+ end
+
+ return ret
+end
+
+this.quote_if_necessary = function(args)
+ local ret = {}
+ for _, v in ipairs(args) do
+ if v:find(" ", 1, true) or v:find("[", 1, true) then
+ table.insert(ret, (v:find("'") and string.format('"%s"', v) or string.format("'%s'", v)))
+ else
+ table.insert(ret, v)
+ end
+ end
+ return ret
+end
+
+this.query_xdg_user_dir = function(name)
+ local r = this.subprocess({ "xdg-user-dir", name })
+ if r.status == 0 then
+ return this.strip(r.stdout)
+ end
+ return nil
+end
+
+this.query_user_home_dir = function()
+ --- "USERPROFILE" is used on ReactOS and other Windows-like systems.
+ return os.getenv("HOME") or os.getenv("USERPROFILE")
+end
+
+return this
diff --git a/dot_config/private_mpv/scripts/videoclip/main.lua b/dot_config/private_mpv/scripts/videoclip/main.lua
new file mode 100644
index 0000000..4603636
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/main.lua
@@ -0,0 +1 @@
+require('videoclip')
diff --git a/dot_config/private_mpv/scripts/videoclip/osd_styler.lua b/dot_config/private_mpv/scripts/videoclip/osd_styler.lua
new file mode 100644
index 0000000..7da1d24
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/osd_styler.lua
@@ -0,0 +1,108 @@
+--[[
+A helper class for styling OSD messages
+http://docs.aegisub.org/3.2/ASS_Tags/
+
+Copyright (C) 2021 Ren Tatsumoto
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+]]
+
+local OSD = {}
+OSD.__index = OSD
+
+function OSD:new()
+ return setmetatable({ messages = {} }, self)
+end
+
+function OSD:append(s)
+ table.insert(self.messages, tostring(s))
+ return self
+end
+
+function OSD:newline()
+ return self:append([[\N]])
+end
+
+function OSD:tab()
+ return self:append([[\h\h\h\h]])
+end
+
+function OSD:config(config)
+ return self
+ :size(config.font_size)
+ :align(config.osd_align)
+ :outline(config.osd_outline)
+end
+
+function OSD:outline(size)
+ return self:append('{\\bord'):append(size):append('}')
+end
+
+function OSD:size(size)
+ return self:append('{\\fs'):append(size):append('}')
+end
+
+function OSD:font(name)
+ return self:append('{\\fn'):append(name):append('}')
+end
+
+function OSD:align(number)
+ return self:append('{\\an'):append(number):append('}')
+end
+
+function OSD:get_text()
+ return table.concat(self.messages)
+end
+
+function OSD:color(code)
+ return self:append('{\\1c&H')
+ :append(code:sub(5, 6))
+ :append(code:sub(3, 4))
+ :append(code:sub(1, 2))
+ :append('&}')
+end
+
+function OSD:text(text)
+ return self:append(text)
+end
+
+function OSD:new_layer()
+ return self:append('\n')
+end
+
+function OSD:bold(s)
+ return self:append('{\\b1}'):append(s):append('{\\b0}')
+end
+
+function OSD:italics(s)
+ return self:append('{\\i1}'):append(s):append('{\\i0}')
+end
+
+function OSD:submenu(text)
+ return self:color('e56243'):bold(text):color('ffffff')
+end
+
+function OSD:item(text)
+ return self:color('f5d38a'):bold(text):color('ffffff')
+end
+
+function OSD:selected(text)
+ return self:color('48a868'):bold(text):color('ffffff')
+end
+
+function OSD:red(text)
+ return self:color('ff0000'):bold(text):color('ffffff')
+end
+
+return OSD
diff --git a/dot_config/private_mpv/scripts/videoclip/platform.lua b/dot_config/private_mpv/scripts/videoclip/platform.lua
new file mode 100644
index 0000000..f56667b
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/platform.lua
@@ -0,0 +1,95 @@
+--[[
+Copyright: Ren Tatsumoto and contributors
+License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
+
+OS-related constants and functions.
+]]
+
+local h = require('helpers')
+local mp = require('mp')
+local utils = require('mp.utils')
+local this = {}
+
+local function get_fallback_video_dir()
+ return utils.join_path(
+ h.query_user_home_dir(),
+ (this.platform == this.Platform.macos and "Movies" or "Videos")
+ )
+end
+
+local function get_fallback_music_dir()
+ return utils.join_path(h.query_user_home_dir(), "Music")
+end
+
+this.Platform = {
+ gnu_linux = "gnu_linux",
+ macos = "macos",
+ windows = "windows",
+}
+this.platform = (
+ h.is_win() and this.Platform.windows
+ or h.is_mac() and this.Platform.macos
+ or this.Platform.gnu_linux
+)
+this.default_video_folder = h.query_xdg_user_dir("VIDEOS") or get_fallback_video_dir()
+this.default_audio_folder = h.query_xdg_user_dir("MUSIC") or get_fallback_music_dir()
+this.curl_exe = (this.platform == this.Platform.windows and 'curl.exe' or 'curl')
+this.open_utility = (
+ this.platform == this.Platform.windows and 'explorer.exe'
+ or this.platform == this.Platform.macos and 'open'
+ or this.platform == this.Platform.gnu_linux and 'xdg-open'
+)
+this.open = function(file_or_url)
+ return mp.commandv('run', this.open_utility, file_or_url)
+end
+
+this.clipboard = (function()
+ local self = {}
+ if this.platform == this.Platform.windows then
+ self.clip_exe = "powershell.exe"
+ self.copy = function(text)
+ return h.subprocess({ self.clip_exe, '-command', 'Set-Clipboard -Value ' .. text })
+ end
+ else
+ if this.platform == this.Platform.macos then
+ self.clip_exe = "pbcopy"
+ self.clip_cmd = "LANG=en_US.UTF-8 pbcopy"
+ elseif h.is_wayland() then
+ self.clip_exe = "wl-copy"
+ self.clip_cmd = "wl-copy"
+ else
+ self.clip_exe = "xclip"
+ self.clip_cmd = "xclip -i -selection clipboard"
+ end
+ self.copy = function(text)
+ local handle = io.popen(self.clip_cmd, 'w')
+ if handle then
+ handle:write(text)
+ local success, status, signal = handle:close()
+ if success then
+ status = 0
+ end
+ return { status = status }
+ else
+ return { status = 1 }
+ end
+ end
+ end
+ return self
+end)()
+
+this.copy_or_open_url = function(url)
+ local cb = this.clipboard.copy(url)
+ if cb.status ~= 0 then
+ local msg = string.format(
+ "Failed to copy URL to clipboard, trying to open in browser instead. Make sure %s is installed.",
+ this.clipboard.clip_exe
+ )
+ h.notify_error(msg, "warn", 4)
+ this.open(url)
+ else
+ h.notify("Done! Copied URL to clipboard.", "info", 2)
+ end
+ return cb
+end
+return this
diff --git a/dot_config/private_mpv/scripts/videoclip/timings_mgr.lua b/dot_config/private_mpv/scripts/videoclip/timings_mgr.lua
new file mode 100644
index 0000000..1c17c5d
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/timings_mgr.lua
@@ -0,0 +1,29 @@
+--[[
+Copyright: Ren Tatsumoto and contributors
+License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
+
+Timings class
+]]
+
+local Timings = {
+ ['start'] = -1,
+ ['end'] = -1,
+}
+
+function Timings:new(o)
+ o = o or {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Timings:reset()
+ self['start'] = -1
+ self['end'] = -1
+end
+
+function Timings:validate()
+ return self['start'] >= 0 and self['start'] < self['end']
+end
+
+return Timings
diff --git a/dot_config/private_mpv/scripts/videoclip/videoclip.lua b/dot_config/private_mpv/scripts/videoclip/videoclip.lua
new file mode 100644
index 0000000..d22d004
--- /dev/null
+++ b/dot_config/private_mpv/scripts/videoclip/videoclip.lua
@@ -0,0 +1,476 @@
+--[[
+Videoclip - mp4/webm clips creator for mpv.
+
+Copyright (C) 2021 Ren Tatsumoto
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+]]
+
+local NAME = 'videoclip'
+local mp = require('mp')
+local mpopt = require('mp.options')
+local utils = require('mp.utils')
+local OSD = require('osd_styler')
+local p = require('platform')
+local h = require('helpers')
+local encoder = require('encoder')
+local Timings = require('timings_mgr')
+
+------------------------------------------------------------
+-- System-dependent variables
+
+-- Options can be changed here or in a separate config file.
+-- Config path: ~/.config/mpv/script-opts/videoclip.conf
+local config = {
+ -- absolute paths
+ -- relative paths (e.g. ~ for home dir) do NOT work.
+ video_folder_path = p.default_video_folder,
+ audio_folder_path = p.default_audio_folder,
+ -- The range of the CRF scale is 0–51, where 0 is lossless,
+ -- 23 is the default, and 51 is worst quality possible.
+ -- Insane values like 9999 still work but produce the worst quality.
+ video_quality = 23,
+ -- Use the slowest preset that you have patience for.
+ -- https://trac.ffmpeg.org/wiki/Encode/H.264
+ preset = 'faster',
+ video_format = 'mp4', -- mp4, vp9, vp8
+ video_bitrate = '1M',
+ video_width = -2,
+ video_height = 480,
+ video_fps = 'auto',
+ audio_format = 'opus', -- aac, opus
+ audio_bitrate = '32k', -- 32k, 64k, 128k, 256k. aac requires higher bitrates.
+ font_size = 24,
+ osd_align = 7, -- https://aegisub.org/docs/3.2/ASS_Tags/#\an
+ osd_outline = 1.5,
+ clean_filename = true,
+ -- Whether to upload to catbox (permanent) or litterbox (temporary)
+ litterbox = true,
+ -- Determines expire time of files uploaded to litterbox
+ litterbox_expire = '72h', -- 1h, 12h, 24h, 72h
+ sub_font = 'Noto Sans CJK JP',
+ -- Filename format
+ -- Available tags: %n = filename, %t = title, %s = start, %e = end, %d = duration,
+ -- %Y = year, %M = months, %D = day, %H = hours (24), %I = hours (12),
+ -- %P = am/pm %N = minutes, %S = seconds
+ filename_template='%n_%s-%e',
+}
+
+mpopt.read_options(config, NAME)
+local main_menu
+local pref_menu
+
+local allowed_presets = {
+ ultrafast = true,
+ superfast = true,
+ veryfast = true,
+ faster = true,
+ fast = true,
+ medium = true,
+ slow = true,
+ slower = true,
+ veryslow = true,
+}
+
+------------------------------------------------------------
+-- Utility functions
+
+local function force_resolution(width, height, clip_fn, ...)
+ local cached_prefs = {
+ video_width = config.video_width,
+ video_height = config.video_height,
+ }
+ config.video_width = width
+ config.video_height = height
+ clip_fn(...)
+ config.video_width = cached_prefs.video_width
+ config.video_height = cached_prefs.video_height
+end
+
+local function set_encoding_settings()
+ if config.video_format == 'mp4' then
+ config.video_codec = 'libx264'
+ config.video_extension = '.mp4'
+ elseif config.video_format == 'vp9' then
+ config.video_codec = 'libvpx-vp9'
+ config.video_extension = '.webm'
+ else
+ config.video_codec = 'libvpx'
+ config.video_extension = '.webm'
+ end
+
+ if config.audio_format == 'aac' then
+ config.audio_codec = 'aac'
+ config.audio_extension = '.aac'
+ else
+ config.audio_codec = 'libopus'
+ config.audio_extension = '.opus'
+ end
+end
+
+local function validate_config()
+ if not config.audio_bitrate:match('^%d+[kK]$') then
+ config.audio_bitrate = (tonumber(config.audio_bitrate) or 32) .. 'k'
+ end
+
+ if not config.video_bitrate:match('^%d+[kKmM]$') then
+ config.video_bitrate = '1M'
+ end
+
+ if not allowed_presets[config.preset] then
+ config.preset = 'faster'
+ end
+
+ set_encoding_settings()
+end
+
+local function upload_to_catbox(outfile)
+ local endpoint = config.litterbox and 'https://litterbox.catbox.moe/resources/internals/api.php' or 'https://catbox.moe/user/api.php'
+ h.notify("Uploading to " .. (config.litterbox and "litterbox.catbox.moe..." or "catbox.moe..."), "info", 9999)
+
+ -- This uses cURL to send a request to the cat-/litterbox API.
+ -- cURL is included on Windows 10 and up, most Linux distributions and macOS.
+
+ local r = h.subprocess({ -- This is technically blocking, but I don't think it has any real consequences ..?
+ p.curl_exe, '-s',
+ '-F', 'reqtype=fileupload',
+ '-F', 'time=' .. config['litterbox_expire'],
+ '-F', 'fileToUpload=@"' .. outfile .. '"',
+ endpoint
+ })
+
+ -- Exit codes in the range [0, 99] are returned by cURL itself.
+ -- Any other exit code means the shell failed to execute cURL.
+ if r.status < 0 or r.status > 99 then
+ h.notify_error("Error: Failed to upload. Make sure cURL is installed and in your PATH.", "error", 3)
+ return
+ elseif r.status ~= 0 then
+ h.notify_error("Error: Failed to upload to " .. (config.litterbox and "litterbox.catbox.moe" or "catbox.moe"), "error", 2)
+ return
+ end
+
+ mp.msg.info("Catbox URL: " .. r.stdout)
+ -- Copy to clipboard
+ p.copy_or_open_url(r.stdout)
+end
+
+------------------------------------------------------------
+-- Menu interface
+
+local Menu = {}
+Menu.__index = Menu
+
+function Menu:new(parent)
+ local o = {
+ parent = parent,
+ overlay = parent and parent.overlay or mp.create_osd_overlay('ass-events'),
+ keybindings = { },
+ }
+ return setmetatable(o, self)
+end
+
+function Menu:overlay_draw(text)
+ self.overlay.data = text
+ self.overlay:update()
+end
+
+function Menu:open()
+ if self.parent then
+ self.parent:close()
+ end
+ for _, val in pairs(self.keybindings) do
+ mp.add_forced_key_binding(val.key, val.key, val.fn)
+ end
+ self:update()
+end
+
+function Menu:close()
+ for _, val in pairs(self.keybindings) do
+ mp.remove_key_binding(val.key)
+ end
+ if self.parent then
+ self.parent:open()
+ else
+ self.overlay:remove()
+ end
+end
+
+function Menu:update()
+ local osd = OSD:new():config(config)
+ osd:append('Dummy menu.'):newline()
+ self:overlay_draw(osd:get_text())
+end
+
+------------------------------------------------------------
+-- Main menu
+
+main_menu = Menu:new()
+main_menu.timings = Timings:new()
+
+main_menu.keybindings = {
+ { key = 's', fn = function() main_menu:set_time('start') end },
+ { key = 'e', fn = function() main_menu:set_time('end') end },
+ { key = 'S', fn = function() main_menu:set_time_sub('start') end },
+ { key = 'E', fn = function() main_menu:set_time_sub('end') end },
+ { key = 'r', fn = function() main_menu:reset_timings() end },
+ { key = 'c', fn = function() main_menu:create_clip('video') end },
+ { key = 'C', fn = function() force_resolution(1920, -2, encoder.create_clip, 'video') end },
+ { key = 'a', fn = function() main_menu:create_clip('audio') end },
+ { key = 'x', fn = function() main_menu:create_clip('video', upload_to_catbox) end },
+ { key = 'X', fn = function() force_resolution(1920, -2, main_menu.create_clip, 'video', upload_to_catbox) end },
+ { key = 'p', fn = function() pref_menu:open() end },
+ { key = 'o', fn = function() p.open('https://streamable.com/') end },
+ { key = 'ESC', fn = function() main_menu:close() end },
+}
+
+function main_menu:set_time(property)
+ self.timings[property] = math.max(0, mp.get_property_number('time-pos'))
+ self:update()
+end
+
+function main_menu:set_time_sub(property)
+ local sub_delay = mp.get_property_native("sub-delay")
+ local time_pos = mp.get_property_number(string.format("sub-%s", property))
+
+ if time_pos == nil then
+ h.notify_error("Warning: No subtitles visible.", "warn", 2)
+ return
+ end
+
+ self.timings[property] = math.max(0, time_pos + sub_delay)
+ self:update()
+end
+
+function main_menu:reset_timings()
+ self.timings:reset()
+ self:update()
+end
+
+main_menu.open = function()
+ Menu.open(main_menu)
+end
+
+function main_menu:update()
+ local osd = OSD:new():config(config)
+ if encoder.alive == false then
+ osd:red("Error: "):append("mpv is not found in the PATH."):newline()
+ end
+ osd:submenu('Timings '):italics('(+shift use sub timings)'):newline()
+ osd:tab():item('s: '):append('start time '):item(h.human_readable_time(self.timings['start'])):newline()
+ osd:tab():item('e: '):append('end time '):item(h.human_readable_time(self.timings['end'])):newline()
+ osd:tab():item('r: '):append('reset'):newline()
+ osd:submenu('Create clip '):italics('(+shift to force fullHD preset)'):newline()
+ osd:tab():item('c: '):append('video clip'):newline()
+ osd:tab():item('a: '):append('audio clip'):newline()
+ osd:tab():item('x: '):append('video clip to ' .. (config.litterbox and 'litterbox.catbox.moe (' .. config.litterbox_expire .. ')' or 'catbox.moe')):newline()
+ osd:submenu('Options '):newline()
+ osd:tab():item('p: '):append('Open preferences'):newline()
+ osd:tab():item('o: '):append('Open streamable.com'):newline()
+ osd:tab():item('ESC: '):append('Close'):newline()
+
+ self:overlay_draw(osd:get_text())
+end
+
+function main_menu:create_clip(clip_type, on_complete_fn)
+ self:close()
+ encoder.create_clip(clip_type, on_complete_fn)
+end
+
+------------------------------------------------------------
+-- Preferences
+
+pref_menu = Menu:new(main_menu)
+
+pref_menu.keybindings = {
+ { key = 'f', fn = function() pref_menu:cycle_video_formats() end },
+ { key = 'a', fn = function() pref_menu:cycle_audio_formats() end },
+ { key = 'm', fn = function() pref_menu:toggle_mute_audio() end },
+ { key = 'r', fn = function() pref_menu:cycle_resolutions() end },
+ { key = 'b', fn = function() pref_menu:cycle_audio_bitrates() end },
+ { key = 'e', fn = function() pref_menu:toggle_embed_subtitles() end },
+ { key = 'x', fn = function() pref_menu:toggle_catbox() end },
+ { key = 'z', fn = function() pref_menu:cycle_litterbox_expiration() end },
+ { key = 's', fn = function() pref_menu:save() end },
+ { key = 'c', fn = function() end },
+ { key = 'ESC', fn = function() pref_menu:close() end },
+ { key = 'q', fn = function() pref_menu:close() end },
+}
+
+pref_menu.resolutions = {
+ { w = config.video_width, h = config.video_height, },
+ { w = -2, h = -2, },
+ { w = -2, h = 240, },
+ { w = -2, h = 360, },
+ { w = -2, h = 480, },
+ { w = -2, h = 720, },
+ { w = -2, h = 1080, },
+ { w = -2, h = 1440, },
+ { w = -2, h = 2160, },
+ selected = 1,
+}
+pref_menu.audio_bitrates = {
+ config.audio_bitrate,
+ '32k',
+ '64k',
+ '128k',
+ '256k',
+ '384k',
+ selected = 1,
+}
+
+pref_menu.vid_formats = { 'mp4', 'vp9', 'vp8', }
+pref_menu.aud_formats = { 'aac', 'opus', }
+pref_menu.litterbox_expirations = { '1h', '12h', '24h', '72h', }
+
+function pref_menu:get_selected_resolution()
+ return string.format(
+ '%s x %s',
+ config.video_width == -2 and 'auto' or config.video_width,
+ config.video_height == -2 and 'auto' or config.video_height
+ )
+end
+
+function pref_menu:cycle_resolutions()
+ self.resolutions.selected = self.resolutions.selected + 1 > #self.resolutions and 1 or self.resolutions.selected + 1
+ local res = self.resolutions[self.resolutions.selected]
+ config.video_width = res.w
+ config.video_height = res.h
+ self:update()
+end
+
+function pref_menu:cycle_audio_bitrates()
+ self.audio_bitrates.selected = self.audio_bitrates.selected + 1 > #self.audio_bitrates and 1 or self.audio_bitrates.selected + 1
+ config.audio_bitrate = self.audio_bitrates[self.audio_bitrates.selected]
+ self:update()
+end
+
+function pref_menu:cycle_formats(config_type)
+ local formats
+ if config_type == 'video_format' then
+ formats = pref_menu.vid_formats
+ else
+ formats = pref_menu.aud_formats
+ end
+
+ local selected = 1
+ for i, format in ipairs(formats) do
+ if config[config_type] == format then
+ selected = i
+ break
+ end
+ end
+ config[config_type] = formats[selected + 1] or formats[1]
+ set_encoding_settings()
+ self:update()
+end
+
+function pref_menu:cycle_video_formats()
+ pref_menu:cycle_formats('video_format')
+end
+
+function pref_menu:cycle_audio_formats()
+ pref_menu:cycle_formats('audio_format')
+end
+
+function pref_menu:toggle_mute_audio()
+ mp.commandv("cycle", "mute")
+ self:update()
+end
+
+function pref_menu:toggle_embed_subtitles()
+ mp.commandv("cycle", "sub-visibility")
+ self:update()
+end
+
+function pref_menu:toggle_catbox()
+ config['litterbox'] = not config['litterbox']
+ self:update()
+end
+
+function pref_menu:cycle_litterbox_expiration()
+ if not config['litterbox'] then
+ return
+ end
+ local expirations = pref_menu.litterbox_expirations
+
+ local selected = 1
+ for i, expiration in ipairs(expirations) do
+ if config['litterbox_expire'] == expiration then
+ selected = i
+ break
+ end
+ end
+ config['litterbox_expire'] = expirations[selected + 1] or expirations[1]
+ self:update()
+end
+
+function pref_menu:update()
+ local osd = OSD:new():config(config)
+ osd:submenu('Preferences'):newline()
+ osd:tab():item('r: Video resolution: '):append(self:get_selected_resolution()):newline()
+ osd:tab():item('f: Video format: '):append(config.video_format):newline()
+ osd:tab():item('a: Audio format: '):append(config.audio_format):newline()
+ osd:tab():item('b: Audio bitrate: '):append(config.audio_bitrate):newline()
+ osd:tab():item('m: Mute audio: '):append(mp.get_property("mute")):newline()
+ osd:tab():item('e: Embed subtitles: '):append(mp.get_property("sub-visibility")):newline()
+ osd:submenu('Catbox'):newline()
+ osd:tab():item('x: Using: '):append(config.litterbox and 'Litterbox (temporary)' or 'Catbox (permanent)'):newline()
+ if config.litterbox then
+ osd:tab():item('z: Litterbox expires after: '):append(config.litterbox_expire):newline()
+ else
+ osd:tab():color("b0b0b0"):text('x: Litterbox expires after: '):append("N/A"):newline()
+ end
+ osd:submenu('Save'):newline()
+ osd:tab():item('s: Save preferences'):newline()
+ self:overlay_draw(osd:get_text())
+end
+
+function pref_menu:save()
+ local function lua_to_mpv(config_value)
+ if type(config_value) == 'boolean' then
+ return config_value and 'yes' or 'no'
+ else
+ return config_value
+ end
+ end
+ local ignore_list = {
+ video_extension = true,
+ audio_extension = true,
+ video_codec = true,
+ audio_codec = true,
+ }
+ local mpv_dirpath = string.gsub(mp.get_script_directory(), "scripts[\\/][^\\/]+", "")
+ local config_filepath = utils.join_path(utils.join_path(mpv_dirpath, "script-opts"), string.format('%s.conf', NAME))
+ local handle = io.open(config_filepath, 'w')
+ if handle ~= nil then
+ handle:write(string.format("# Written by %s on %s.\n", NAME, os.date()))
+ for key, value in pairs(config) do
+ if ignore_list[key] == nil then
+ handle:write(string.format('%s=%s\n', key, lua_to_mpv(value)))
+ end
+ end
+ handle:close()
+ h.notify("Settings saved.", "info", 2)
+ else
+ h.notify_error(string.format("Couldn't open %s.", config_filepath), "error", 4)
+ end
+end
+
+------------------------------------------------------------
+-- Finally, set an 'entry point' in mpv
+
+validate_config()
+encoder.init(config, main_menu.timings)
+mp.add_key_binding('c', 'videoclip-menu-open', main_menu.open)
+mp.msg.warn("Press 'c' to open the videoclip menu.")
diff --git a/dot_config/private_mpv/scripts/webm.lua b/dot_config/private_mpv/scripts/webm.lua
new file mode 100644
index 0000000..57a0942
--- /dev/null
+++ b/dot_config/private_mpv/scripts/webm.lua
@@ -0,0 +1,2914 @@
+local mp = require("mp")
+local assdraw = require("mp.assdraw")
+local msg = require("mp.msg")
+local utils = require("mp.utils")
+local mpopts = require("mp.options")
+local options = {
+ -- Defaults to shift+w
+ keybind = "W",
+ -- If empty, saves on the same directory of the playing video.
+ -- A starting "~" will be replaced by the home dir.
+ -- This field is delimited by double-square-brackets - [[ and ]] - instead of
+ -- quotes, because Windows users might run into a issue when using
+ -- backslashes as a path separator. Examples of valid inputs for this field
+ -- would be: [[]] (the default, empty value), [[C:\Users\John]] (on Windows),
+ -- and [[/home/john]] (on Unix-like systems eg. Linux).
+ -- The [[]] delimiter is not needed when using from a configuration file
+ -- in the script-opts folder.
+ output_directory = [[]],
+ run_detached = false,
+ -- Template string for the output file
+ -- %f - Filename, with extension
+ -- %F - Filename, without extension
+ -- %T - Media title, if it exists, or filename, with extension (useful for some streams, such as YouTube).
+ -- %s, %e - Start and end time, with milliseconds
+ -- %S, %E - Start and end time, without milliseconds
+ -- %M - "-audio", if audio is enabled, empty otherwise
+ -- %R - "-(height)p", where height is the video's height, or scale_height, if it's enabled.
+ -- More specifiers are supported, see https://mpv.io/manual/master/#options-screenshot-template
+ -- Property expansion is supported (with %{} at top level, ${} when nested), see https://mpv.io/manual/master/#property-expansion
+ output_template = "%F-[%s-%e]%M",
+ -- Scale video to a certain height, keeping the aspect ratio. -1 disables it.
+ scale_height = -1,
+ -- Change the FPS of the output video, dropping or duplicating frames as needed.
+ -- -1 means the FPS will be unchanged from the source.
+ fps = -1,
+ -- Target filesize, in kB. This will be used to calculate the bitrate
+ -- used on the encode. If this is set to <= 0, the video bitrate will be set
+ -- to 0, which might enable constant quality modes, depending on the
+ -- video codec that's used (VP8 and VP9, for example).
+ target_filesize = 2500,
+ -- If true, will use stricter flags to ensure the resulting file doesn't
+ -- overshoot the target filesize. Not recommended, as constrained quality
+ -- mode should work well, unless you're really having trouble hitting
+ -- the target size.
+ strict_filesize_constraint = false,
+ strict_bitrate_multiplier = 0.95,
+ -- In kilobits.
+ strict_audio_bitrate = 64,
+ -- Sets the output format, from a few predefined ones.
+ -- Currently we have:
+ -- webm-vp8 (libvpx/libvorbis)
+ -- webm-vp9 (libvpx-vp9/libopus)
+ -- mp4 (h264/AAC)
+ -- mp4-nvenc (h264-NVENC/AAC)
+ -- raw (rawvideo/pcm_s16le).
+ -- mp3 (libmp3lame)
+ -- and gif
+ output_format = "webm-vp8",
+ twopass = true,
+ -- If set, applies the video filters currently used on the playback to the encode.
+ apply_current_filters = true,
+ -- If set, writes the video's filename to the "Title" field on the metadata.
+ write_filename_on_metadata = false,
+ -- Set the number of encoding threads, for codecs libvpx and libvpx-vp9
+ libvpx_threads = 4,
+ additional_flags = "",
+ -- Constant Rate Factor (CRF). The value meaning and limits may change,
+ -- from codec to codec. Set to -1 to disable.
+ crf = 10,
+ -- Useful for flags that may impact output filesize, such as qmin, qmax etc
+ -- Won't be applied when strict_filesize_constraint is on.
+ non_strict_additional_flags = "",
+ -- Display the encode progress, in %. Requires run_detached to be disabled.
+ -- On Windows, it shows a cmd popup. "auto" will display progress on non-Windows platforms.
+ display_progress = "auto",
+ -- The font size used in the menu. Isn't used for the notifications (started encode, finished encode etc)
+ font_size = 28,
+ margin = 10,
+ message_duration = 5,
+ -- gif dither mode, 0-5 for bayer w/ bayer_scale 0-5, 6 for paletteuse default (sierra2_4a)
+ gif_dither = 2,
+ -- Force square pixels on output video
+ -- Some players like recent Firefox versions display videos with non-square pixels with wrong aspect ratio
+ force_square_pixels = false,
+}
+
+mpopts.read_options(options)
+local base64_chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+
+-- encoding
+function base64_encode(data)
+ return ((data:gsub('.', function(x)
+ local r,b='',x:byte()
+ for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
+ return r;
+ end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
+ if (#x < 6) then return '' end
+ local c=0
+ for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
+ return base64_chars:sub(c+1,c+1)
+ end)..({ '', '==', '=' })[#data%3+1])
+end
+
+-- decoding
+function base64_decode(data)
+ data = string.gsub(data, '[^'..base64_chars..'=]', '')
+ return (data:gsub('.', function(x)
+ if (x == '=') then return '' end
+ local r,f='',(base64_chars:find(x)-1)
+ for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
+ return r;
+ end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
+ if (#x ~= 8) then return '' end
+ local c=0
+ for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
+ return string.char(c)
+ end))
+end
+local emit_event
+emit_event = function(event_name, ...)
+ return mp.commandv("script-message", "webm-" .. tostring(event_name), ...)
+end
+local test_set_options
+test_set_options = function(new_options_json)
+ local new_options = utils.parse_json(new_options_json)
+ for k, v in pairs(new_options) do
+ options[k] = v
+ end
+end
+mp.register_script_message("mpv-webm-set-options", test_set_options)
+local bold
+bold = function(text)
+ return "{\\b1}" .. tostring(text) .. "{\\b0}"
+end
+local message
+message = function(text, duration)
+ local ass = mp.get_property_osd("osd-ass-cc/0")
+ ass = ass .. text
+ return mp.osd_message(ass, duration or options.message_duration)
+end
+local append
+append = function(a, b)
+ for _, val in ipairs(b) do
+ a[#a + 1] = val
+ end
+ return a
+end
+local seconds_to_time_string
+seconds_to_time_string = function(seconds, no_ms, full)
+ if seconds < 0 then
+ return "unknown"
+ end
+ local ret = ""
+ if not (no_ms) then
+ ret = string.format(".%03d", seconds * 1000 % 1000)
+ end
+ ret = string.format("%02d:%02d%s", math.floor(seconds / 60) % 60, math.floor(seconds) % 60, ret)
+ if full or seconds > 3600 then
+ ret = string.format("%d:%s", math.floor(seconds / 3600), ret)
+ end
+ return ret
+end
+local seconds_to_path_element
+seconds_to_path_element = function(seconds, no_ms, full)
+ local time_string = seconds_to_time_string(seconds, no_ms, full)
+ local _
+ time_string, _ = time_string:gsub(":", ".")
+ return time_string
+end
+local file_exists
+file_exists = function(name)
+ local info, err = utils.file_info(name)
+ if info ~= nil then
+ return true
+ end
+ return false
+end
+local expand_properties
+expand_properties = function(text, magic)
+ if magic == nil then
+ magic = "$"
+ end
+ for prefix, raw, prop, colon, fallback, closing in text:gmatch("%" .. magic .. "{([?!]?)(=?)([^}:]*)(:?)([^}]*)(}*)}") do
+ local err
+ local prop_value
+ local compare_value
+ local original_prop = prop
+ local get_property = mp.get_property_osd
+ if raw == "=" then
+ get_property = mp.get_property
+ end
+ if prefix ~= "" then
+ for actual_prop, compare in prop:gmatch("(.-)==(.*)") do
+ prop = actual_prop
+ compare_value = compare
+ end
+ end
+ if colon == ":" then
+ prop_value, err = get_property(prop, fallback)
+ else
+ prop_value, err = get_property(prop, "(error)")
+ end
+ prop_value = tostring(prop_value)
+ if prefix == "?" then
+ if compare_value == nil then
+ prop_value = err == nil and fallback .. closing or ""
+ else
+ prop_value = prop_value == compare_value and fallback .. closing or ""
+ end
+ prefix = "%" .. prefix
+ elseif prefix == "!" then
+ if compare_value == nil then
+ prop_value = err ~= nil and fallback .. closing or ""
+ else
+ prop_value = prop_value ~= compare_value and fallback .. closing or ""
+ end
+ else
+ prop_value = prop_value .. closing
+ end
+ if colon == ":" then
+ local _
+ text, _ = text:gsub("%" .. magic .. "{" .. prefix .. raw .. original_prop:gsub("%W", "%%%1") .. ":" .. fallback:gsub("%W", "%%%1") .. closing .. "}", expand_properties(prop_value))
+ else
+ local _
+ text, _ = text:gsub("%" .. magic .. "{" .. prefix .. raw .. original_prop:gsub("%W", "%%%1") .. closing .. "}", prop_value)
+ end
+ end
+ return text
+end
+local format_filename
+format_filename = function(startTime, endTime, videoFormat)
+ local hasAudioCodec = videoFormat.audioCodec ~= ""
+ local replaceFirst = {
+ ["%%mp"] = "%%mH.%%mM.%%mS",
+ ["%%mP"] = "%%mH.%%mM.%%mS.%%mT",
+ ["%%p"] = "%%wH.%%wM.%%wS",
+ ["%%P"] = "%%wH.%%wM.%%wS.%%wT"
+ }
+ local replaceTable = {
+ ["%%wH"] = string.format("%02d", math.floor(startTime / (60 * 60))),
+ ["%%wh"] = string.format("%d", math.floor(startTime / (60 * 60))),
+ ["%%wM"] = string.format("%02d", math.floor(startTime / 60 % 60)),
+ ["%%wm"] = string.format("%d", math.floor(startTime / 60)),
+ ["%%wS"] = string.format("%02d", math.floor(startTime % 60)),
+ ["%%ws"] = string.format("%d", math.floor(startTime)),
+ ["%%wf"] = string.format("%s", startTime),
+ ["%%wT"] = string.sub(string.format("%.3f", startTime % 1), 3),
+ ["%%mH"] = string.format("%02d", math.floor(endTime / (60 * 60))),
+ ["%%mh"] = string.format("%d", math.floor(endTime / (60 * 60))),
+ ["%%mM"] = string.format("%02d", math.floor(endTime / 60 % 60)),
+ ["%%mm"] = string.format("%d", math.floor(endTime / 60)),
+ ["%%mS"] = string.format("%02d", math.floor(endTime % 60)),
+ ["%%ms"] = string.format("%d", math.floor(endTime)),
+ ["%%mf"] = string.format("%s", endTime),
+ ["%%mT"] = string.sub(string.format("%.3f", endTime % 1), 3),
+ ["%%f"] = mp.get_property("filename"),
+ ["%%F"] = mp.get_property("filename/no-ext"),
+ ["%%s"] = seconds_to_path_element(startTime),
+ ["%%S"] = seconds_to_path_element(startTime, true),
+ ["%%e"] = seconds_to_path_element(endTime),
+ ["%%E"] = seconds_to_path_element(endTime, true),
+ ["%%T"] = mp.get_property("media-title"),
+ ["%%M"] = (mp.get_property_native('aid') and not mp.get_property_native('mute') and hasAudioCodec) and '-audio' or '',
+ ["%%R"] = (options.scale_height ~= -1) and "-" .. tostring(options.scale_height) .. "p" or "-" .. tostring(mp.get_property_native('height')) .. "p",
+ ["%%t%%"] = "%%"
+ }
+ local filename = options.output_template
+ for format, value in pairs(replaceFirst) do
+ local _
+ filename, _ = filename:gsub(format, value)
+ end
+ for format, value in pairs(replaceTable) do
+ local _
+ filename, _ = filename:gsub(format, value)
+ end
+ if mp.get_property_bool("demuxer-via-network", false) then
+ local _
+ filename, _ = filename:gsub("%%X{([^}]*)}", "%1")
+ filename, _ = filename:gsub("%%x", "")
+ else
+ local x = string.gsub(mp.get_property("stream-open-filename", ""), string.gsub(mp.get_property("filename", ""), "%W", "%%%1") .. "$", "")
+ local _
+ filename, _ = filename:gsub("%%X{[^}]*}", x)
+ filename, _ = filename:gsub("%%x", x)
+ end
+ filename = expand_properties(filename, "%")
+ for format in filename:gmatch("%%t([aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ])") do
+ local _
+ filename, _ = filename:gsub("%%t" .. format, os.date("%" .. format))
+ end
+ local _
+ filename, _ = filename:gsub("[<>:\"/\\|?*]", "")
+ return tostring(filename) .. "." .. tostring(videoFormat.outputExtension)
+end
+local parse_directory
+parse_directory = function(dir)
+ local home_dir = os.getenv("HOME")
+ if not home_dir then
+ home_dir = os.getenv("USERPROFILE")
+ end
+ if not home_dir then
+ local drive = os.getenv("HOMEDRIVE")
+ local path = os.getenv("HOMEPATH")
+ if drive and path then
+ home_dir = utils.join_path(drive, path)
+ else
+ msg.warn("Couldn't find home dir.")
+ home_dir = ""
+ end
+ end
+ local _
+ dir, _ = dir:gsub("^~", home_dir)
+ return dir
+end
+local is_windows = type(package) == "table" and type(package.config) == "string" and package.config:sub(1, 1) == "\\"
+local trim
+trim = function(s)
+ return s:match("^%s*(.-)%s*$")
+end
+local get_null_path
+get_null_path = function()
+ if file_exists("/dev/null") then
+ return "/dev/null"
+ end
+ return "NUL"
+end
+local run_subprocess
+run_subprocess = function(params)
+ local res = utils.subprocess(params)
+ msg.verbose("Command stdout: ")
+ msg.verbose(res.stdout)
+ if res.status ~= 0 then
+ msg.verbose("Command failed! Reason: ", res.error, " Killed by us? ", res.killed_by_us and "yes" or "no")
+ return false
+ end
+ return true
+end
+local shell_escape
+shell_escape = function(args)
+ local ret = { }
+ for i, a in ipairs(args) do
+ local s = tostring(a)
+ if string.match(s, "[^A-Za-z0-9_/:=-]") then
+ if is_windows then
+ s = '"' .. string.gsub(s, '"', '"\\""') .. '"'
+ else
+ s = "'" .. string.gsub(s, "'", "'\\''") .. "'"
+ end
+ end
+ table.insert(ret, s)
+ end
+ local concat = table.concat(ret, " ")
+ if is_windows then
+ concat = '"' .. concat .. '"'
+ end
+ return concat
+end
+local run_subprocess_popen
+run_subprocess_popen = function(command_line)
+ local command_line_string = shell_escape(command_line)
+ command_line_string = command_line_string .. " 2>&1"
+ msg.verbose("run_subprocess_popen: running " .. tostring(command_line_string))
+ return io.popen(command_line_string)
+end
+local calculate_scale_factor
+calculate_scale_factor = function()
+ local baseResY = 720
+ local osd_w, osd_h = mp.get_osd_size()
+ return osd_h / baseResY
+end
+local should_display_progress
+should_display_progress = function()
+ if options.display_progress == "auto" then
+ return not is_windows
+ end
+ return options.display_progress
+end
+local reverse
+reverse = function(list)
+ local _accum_0 = { }
+ local _len_0 = 1
+ local _max_0 = 1
+ for _index_0 = #list, _max_0 < 0 and #list + _max_0 or _max_0, -1 do
+ local element = list[_index_0]
+ _accum_0[_len_0] = element
+ _len_0 = _len_0 + 1
+ end
+ return _accum_0
+end
+local get_pass_logfile_path
+get_pass_logfile_path = function(encode_out_path)
+ return tostring(encode_out_path) .. "-video-pass1.log"
+end
+local dimensions_changed = true
+local _video_dimensions = { }
+local get_video_dimensions
+get_video_dimensions = function()
+ if not (dimensions_changed) then
+ return _video_dimensions
+ end
+ local video_params = mp.get_property_native("video-out-params")
+ if not video_params then
+ return nil
+ end
+ dimensions_changed = false
+ local keep_aspect = mp.get_property_bool("keepaspect")
+ local w = video_params["w"]
+ local h = video_params["h"]
+ local dw = video_params["dw"]
+ local dh = video_params["dh"]
+ if mp.get_property_number("video-rotate") % 180 == 90 then
+ w, h = h, w
+ dw, dh = dh, dw
+ end
+ _video_dimensions = {
+ top_left = { },
+ bottom_right = { },
+ ratios = { }
+ }
+ local window_w, window_h = mp.get_osd_size()
+ if keep_aspect then
+ local unscaled = mp.get_property_native("video-unscaled")
+ local panscan = mp.get_property_number("panscan")
+ local fwidth = window_w
+ local fheight = math.floor(window_w / dw * dh)
+ if fheight > window_h or fheight < h then
+ local tmpw = math.floor(window_h / dh * dw)
+ if tmpw <= window_w then
+ fheight = window_h
+ fwidth = tmpw
+ end
+ end
+ local vo_panscan_area = window_h - fheight
+ local f_w = fwidth / fheight
+ local f_h = 1
+ if vo_panscan_area == 0 then
+ vo_panscan_area = window_h - fwidth
+ f_w = 1
+ f_h = fheight / fwidth
+ end
+ if unscaled or unscaled == "downscale-big" then
+ vo_panscan_area = 0
+ if unscaled or (dw <= window_w and dh <= window_h) then
+ fwidth = dw
+ fheight = dh
+ end
+ end
+ local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w)
+ local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h)
+ local split_scaling
+ split_scaling = function(dst_size, scaled_src_size, zoom, align, pan)
+ scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom)
+ align = (align + 1) / 2
+ local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size)
+ if dst_start < 0 then
+ dst_start = dst_start + 1
+ end
+ local dst_end = dst_start + scaled_src_size
+ if dst_start >= dst_end then
+ dst_start = 0
+ dst_end = 1
+ end
+ return dst_start, dst_end
+ end
+ local zoom = mp.get_property_number("video-zoom")
+ local align_x = mp.get_property_number("video-align-x")
+ local pan_x = mp.get_property_number("video-pan-x")
+ _video_dimensions.top_left.x, _video_dimensions.bottom_right.x = split_scaling(window_w, scaled_width, zoom, align_x, pan_x)
+ local align_y = mp.get_property_number("video-align-y")
+ local pan_y = mp.get_property_number("video-pan-y")
+ _video_dimensions.top_left.y, _video_dimensions.bottom_right.y = split_scaling(window_h, scaled_height, zoom, align_y, pan_y)
+ else
+ _video_dimensions.top_left.x = 0
+ _video_dimensions.bottom_right.x = window_w
+ _video_dimensions.top_left.y = 0
+ _video_dimensions.bottom_right.y = window_h
+ end
+ _video_dimensions.ratios.w = w / (_video_dimensions.bottom_right.x - _video_dimensions.top_left.x)
+ _video_dimensions.ratios.h = h / (_video_dimensions.bottom_right.y - _video_dimensions.top_left.y)
+ return _video_dimensions
+end
+local set_dimensions_changed
+set_dimensions_changed = function()
+ dimensions_changed = true
+end
+local monitor_dimensions
+monitor_dimensions = function()
+ local properties = {
+ "keepaspect",
+ "video-out-params",
+ "video-unscaled",
+ "panscan",
+ "video-zoom",
+ "video-align-x",
+ "video-pan-x",
+ "video-align-y",
+ "video-pan-y",
+ "osd-width",
+ "osd-height"
+ }
+ for _, p in ipairs(properties) do
+ mp.observe_property(p, "native", set_dimensions_changed)
+ end
+end
+local clamp
+clamp = function(min, val, max)
+ if val <= min then
+ return min
+ end
+ if val >= max then
+ return max
+ end
+ return val
+end
+local clamp_point
+clamp_point = function(top_left, point, bottom_right)
+ return {
+ x = clamp(top_left.x, point.x, bottom_right.x),
+ y = clamp(top_left.y, point.y, bottom_right.y)
+ }
+end
+local VideoPoint
+do
+ local _class_0
+ local _base_0 = {
+ set_from_screen = function(self, sx, sy)
+ local d = get_video_dimensions()
+ local point = clamp_point(d.top_left, {
+ x = sx,
+ y = sy
+ }, d.bottom_right)
+ self.x = math.floor(d.ratios.w * (point.x - d.top_left.x) + 0.5)
+ self.y = math.floor(d.ratios.h * (point.y - d.top_left.y) + 0.5)
+ end,
+ to_screen = function(self)
+ local d = get_video_dimensions()
+ return {
+ x = math.floor(self.x / d.ratios.w + d.top_left.x + 0.5),
+ y = math.floor(self.y / d.ratios.h + d.top_left.y + 0.5)
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.x = -1
+ self.y = -1
+ end,
+ __base = _base_0,
+ __name = "VideoPoint"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ VideoPoint = _class_0
+end
+local Region
+do
+ local _class_0
+ local _base_0 = {
+ is_valid = function(self)
+ return self.x > -1 and self.y > -1 and self.w > -1 and self.h > -1
+ end,
+ set_from_points = function(self, p1, p2)
+ self.x = math.min(p1.x, p2.x)
+ self.y = math.min(p1.y, p2.y)
+ self.w = math.abs(p1.x - p2.x)
+ self.h = math.abs(p1.y - p2.y)
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.x = -1
+ self.y = -1
+ self.w = -1
+ self.h = -1
+ end,
+ __base = _base_0,
+ __name = "Region"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Region = _class_0
+end
+local make_fullscreen_region
+make_fullscreen_region = function()
+ local r = Region()
+ local d = get_video_dimensions()
+ local a = VideoPoint()
+ local b = VideoPoint()
+ local xa, ya
+ do
+ local _obj_0 = d.top_left
+ xa, ya = _obj_0.x, _obj_0.y
+ end
+ a:set_from_screen(xa, ya)
+ local xb, yb
+ do
+ local _obj_0 = d.bottom_right
+ xb, yb = _obj_0.x, _obj_0.y
+ end
+ b:set_from_screen(xb, yb)
+ r:set_from_points(a, b)
+ return r
+end
+local read_double
+read_double = function(bytes)
+ local sign = 1
+ local mantissa = bytes[2] % 2 ^ 4
+ for i = 3, 8 do
+ mantissa = mantissa * 256 + bytes[i]
+ end
+ if bytes[1] > 127 then
+ sign = -1
+ end
+ local exponent = (bytes[1] % 128) * 2 ^ 4 + math.floor(bytes[2] / 2 ^ 4)
+ if exponent == 0 then
+ return 0
+ end
+ mantissa = (math.ldexp(mantissa, -52) + 1) * sign
+ return math.ldexp(mantissa, exponent - 1023)
+end
+local write_double
+write_double = function(num)
+ local bytes = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ }
+ if num == 0 then
+ return bytes
+ end
+ local anum = math.abs(num)
+ local mantissa, exponent = math.frexp(anum)
+ exponent = exponent - 1
+ mantissa = mantissa * 2 - 1
+ local sign = num ~= anum and 128 or 0
+ exponent = exponent + 1023
+ bytes[1] = sign + math.floor(exponent / 2 ^ 4)
+ mantissa = mantissa * 2 ^ 4
+ local currentmantissa = math.floor(mantissa)
+ mantissa = mantissa - currentmantissa
+ bytes[2] = (exponent % 2 ^ 4) * 2 ^ 4 + currentmantissa
+ for i = 3, 8 do
+ mantissa = mantissa * 2 ^ 8
+ currentmantissa = math.floor(mantissa)
+ mantissa = mantissa - currentmantissa
+ bytes[i] = currentmantissa
+ end
+ return bytes
+end
+local FirstpassStats
+do
+ local _class_0
+ local duration_multiplier, fields_before_duration, fields_after_duration
+ local _base_0 = {
+ get_duration = function(self)
+ local big_endian_binary_duration = reverse(self.binary_duration)
+ return read_double(reversed_binary_duration) / duration_multiplier
+ end,
+ set_duration = function(self, duration)
+ local big_endian_binary_duration = write_double(duration * duration_multiplier)
+ self.binary_duration = reverse(big_endian_binary_duration)
+ end,
+ _bytes_to_string = function(self, bytes)
+ return string.char(unpack(bytes))
+ end,
+ as_binary_string = function(self)
+ local before_duration_string = self:_bytes_to_string(self.binary_data_before_duration)
+ local duration_string = self:_bytes_to_string(self.binary_duration)
+ local after_duration_string = self:_bytes_to_string(self.binary_data_after_duration)
+ return before_duration_string .. duration_string .. after_duration_string
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self, before_duration, duration, after_duration)
+ self.binary_data_before_duration = before_duration
+ self.binary_duration = duration
+ self.binary_data_after_duration = after_duration
+ end,
+ __base = _base_0,
+ __name = "FirstpassStats"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ local self = _class_0
+ duration_multiplier = 10000000.0
+ fields_before_duration = 16
+ fields_after_duration = 1
+ self.data_before_duration_size = function(self)
+ return fields_before_duration * 8
+ end
+ self.data_after_duration_size = function(self)
+ return fields_after_duration * 8
+ end
+ self.size = function(self)
+ return (fields_before_duration + 1 + fields_after_duration) * 8
+ end
+ self.from_bytes = function(self, bytes)
+ local before_duration
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ local _max_0 = self:data_before_duration_size()
+ for _index_0 = 1, _max_0 < 0 and #bytes + _max_0 or _max_0 do
+ local b = bytes[_index_0]
+ _accum_0[_len_0] = b
+ _len_0 = _len_0 + 1
+ end
+ before_duration = _accum_0
+ end
+ local duration
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ local _max_0 = self:data_before_duration_size() + 8
+ for _index_0 = self:data_before_duration_size() + 1, _max_0 < 0 and #bytes + _max_0 or _max_0 do
+ local b = bytes[_index_0]
+ _accum_0[_len_0] = b
+ _len_0 = _len_0 + 1
+ end
+ duration = _accum_0
+ end
+ local after_duration
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = self:data_before_duration_size() + 8 + 1, #bytes do
+ local b = bytes[_index_0]
+ _accum_0[_len_0] = b
+ _len_0 = _len_0 + 1
+ end
+ after_duration = _accum_0
+ end
+ return self(before_duration, duration, after_duration)
+ end
+ FirstpassStats = _class_0
+end
+local read_logfile_into_stats_array
+read_logfile_into_stats_array = function(logfile_path)
+ local file = assert(io.open(logfile_path, "rb"))
+ local logfile_string = base64_decode(file:read())
+ file:close()
+ local stats_size = FirstpassStats:size()
+ assert(logfile_string:len() % stats_size == 0)
+ local stats = { }
+ for offset = 1, #logfile_string, stats_size do
+ local bytes = {
+ logfile_string:byte(offset, offset + stats_size - 1)
+ }
+ assert(#bytes == stats_size)
+ stats[#stats + 1] = FirstpassStats:from_bytes(bytes)
+ end
+ return stats
+end
+local write_stats_array_to_logfile
+write_stats_array_to_logfile = function(stats_array, logfile_path)
+ local file = assert(io.open(logfile_path, "wb"))
+ local logfile_string = ""
+ for _index_0 = 1, #stats_array do
+ local stat = stats_array[_index_0]
+ logfile_string = logfile_string .. stat:as_binary_string()
+ end
+ file:write(base64_encode(logfile_string))
+ return file:close()
+end
+local vp8_patch_logfile
+vp8_patch_logfile = function(logfile_path, encode_total_duration)
+ local stats_array = read_logfile_into_stats_array(logfile_path)
+ local average_duration = encode_total_duration / (#stats_array - 1)
+ for i = 1, #stats_array - 1 do
+ stats_array[i]:set_duration(average_duration)
+ end
+ stats_array[#stats_array]:set_duration(encode_total_duration)
+ return write_stats_array_to_logfile(stats_array, logfile_path)
+end
+local formats = { }
+local Format
+do
+ local _class_0
+ local _base_0 = {
+ getPreFilters = function(self)
+ return { }
+ end,
+ getPostFilters = function(self)
+ return { }
+ end,
+ getFlags = function(self)
+ return { }
+ end,
+ getCodecFlags = function(self)
+ local codecs = { }
+ if self.videoCodec ~= "" then
+ codecs[#codecs + 1] = "--ovc=" .. tostring(self.videoCodec)
+ end
+ if self.audioCodec ~= "" then
+ codecs[#codecs + 1] = "--oac=" .. tostring(self.audioCodec)
+ end
+ return codecs
+ end,
+ postCommandModifier = function(self, command, region, startTime, endTime)
+ return command
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "Basic"
+ self.supportsTwopass = true
+ self.videoCodec = ""
+ self.audioCodec = ""
+ self.outputExtension = ""
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "Format"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Format = _class_0
+end
+local RawVideo
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getColorspace = function(self)
+ local csp = mp.get_property("colormatrix")
+ local _exp_0 = csp
+ if "bt.601" == _exp_0 then
+ return "bt601"
+ elseif "bt.709" == _exp_0 then
+ return "bt709"
+ elseif "bt.2020" == _exp_0 then
+ return "bt2020"
+ elseif "smpte-240m" == _exp_0 then
+ return "smpte240m"
+ else
+ msg.info("Warning, unknown colorspace " .. tostring(csp) .. " detected, using bt.601.")
+ return "bt601"
+ end
+ end,
+ getPostFilters = function(self)
+ return {
+ "format=yuv444p16",
+ "lavfi-scale=in_color_matrix=" .. self:getColorspace(),
+ "format=bgr24"
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "Raw"
+ self.supportsTwopass = false
+ self.videoCodec = "rawvideo"
+ self.audioCodec = "pcm_s16le"
+ self.outputExtension = "avi"
+ self.acceptsBitrate = false
+ end,
+ __base = _base_0,
+ __name = "RawVideo",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ RawVideo = _class_0
+end
+formats["raw"] = RawVideo()
+local WebmVP8
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getPreFilters = function(self)
+ local colormatrixFilter = {
+ ["bt.709"] = "bt709",
+ ["bt.2020"] = "bt2020",
+ ["smpte-240m"] = "smpte240m"
+ }
+ local ret = { }
+ local colormatrix = mp.get_property_native("video-params/colormatrix")
+ if colormatrixFilter[colormatrix] then
+ append(ret, {
+ "lavfi-colormatrix=" .. tostring(colormatrixFilter[colormatrix]) .. ":bt601"
+ })
+ end
+ return ret
+ end,
+ getFlags = function(self)
+ return {
+ "--ovcopts-add=threads=" .. tostring(options.libvpx_threads),
+ "--ovcopts-add=auto-alt-ref=1",
+ "--ovcopts-add=lag-in-frames=25",
+ "--ovcopts-add=quality=good",
+ "--ovcopts-add=cpu-used=0"
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "WebM"
+ self.supportsTwopass = true
+ self.videoCodec = "libvpx"
+ self.audioCodec = "libvorbis"
+ self.outputExtension = "webm"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "WebmVP8",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ WebmVP8 = _class_0
+end
+formats["webm-vp8"] = WebmVP8()
+local WebmVP9
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ getFlags = function(self)
+ return {
+ "--ovcopts-add=threads=" .. tostring(options.libvpx_threads)
+ }
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "WebM (VP9)"
+ self.supportsTwopass = false
+ self.videoCodec = "libvpx-vp9"
+ self.audioCodec = "libopus"
+ self.outputExtension = "webm"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "WebmVP9",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ WebmVP9 = _class_0
+end
+formats["webm-vp9"] = WebmVP9()
+local MP4
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = { }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "MP4 (h264/AAC)"
+ self.supportsTwopass = true
+ self.videoCodec = "libx264"
+ self.audioCodec = "aac"
+ self.outputExtension = "mp4"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "MP4",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MP4 = _class_0
+end
+formats["mp4"] = MP4()
+local MP4NVENC
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = { }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "MP4 (h264-NVENC/AAC)"
+ self.supportsTwopass = true
+ self.videoCodec = "h264_nvenc"
+ self.audioCodec = "aac"
+ self.outputExtension = "mp4"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "MP4NVENC",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MP4NVENC = _class_0
+end
+formats["mp4-nvenc"] = MP4NVENC()
+local MP3
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = { }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "MP3 (libmp3lame)"
+ self.supportsTwopass = false
+ self.videoCodec = ""
+ self.audioCodec = "libmp3lame"
+ self.outputExtension = "mp3"
+ self.acceptsBitrate = true
+ end,
+ __base = _base_0,
+ __name = "MP3",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MP3 = _class_0
+end
+formats["mp3"] = MP3()
+local GIF
+do
+ local _class_0
+ local _parent_0 = Format
+ local _base_0 = {
+ postCommandModifier = function(self, command, region, startTime, endTime)
+ local new_command = { }
+ local start_ts = seconds_to_time_string(startTime, false, true)
+ local end_ts = seconds_to_time_string(endTime, false, true)
+ start_ts = start_ts:gsub(":", "\\\\:")
+ end_ts = end_ts:gsub(":", "\\\\:")
+ local cfilter = "[vid1]trim=start=" .. tostring(start_ts) .. ":end=" .. tostring(end_ts) .. "[vidtmp];"
+ if mp.get_property("deinterlace") == "yes" then
+ cfilter = cfilter .. "[vidtmp]yadif=mode=1[vidtmp];"
+ end
+ for _, v in ipairs(command) do
+ local _continue_0 = false
+ repeat
+ if v:match("^%-%-vf%-add=lavfi%-scale") or v:match("^%-%-vf%-add=lavfi%-crop") or v:match("^%-%-vf%-add=fps") or v:match("^%-%-vf%-add=lavfi%-eq") then
+ local n = v:gsub("^%-%-vf%-add=", ""):gsub("^lavfi%-", "")
+ cfilter = cfilter .. "[vidtmp]" .. tostring(n) .. "[vidtmp];"
+ else
+ if v:match("^%-%-video%-rotate=90") then
+ cfilter = cfilter .. "[vidtmp]transpose=1[vidtmp];"
+ else
+ if v:match("^%-%-video%-rotate=270") then
+ cfilter = cfilter .. "[vidtmp]transpose=2[vidtmp];"
+ else
+ if v:match("^%-%-video%-rotate=180") then
+ cfilter = cfilter .. "[vidtmp]transpose=1[vidtmp];[vidtmp]transpose=1[vidtmp];"
+ else
+ if v:match("^%-%-deinterlace=") then
+ _continue_0 = true
+ break
+ else
+ append(new_command, {
+ v
+ })
+ _continue_0 = true
+ break
+ end
+ end
+ end
+ end
+ end
+ _continue_0 = true
+ until true
+ if not _continue_0 then
+ break
+ end
+ end
+ cfilter = cfilter .. "[vidtmp]split[topal][vidf];"
+ cfilter = cfilter .. "[topal]palettegen[pal];"
+ cfilter = cfilter .. "[vidf]fifo[vidf];"
+ cfilter = cfilter .. "[vidf][pal]paletteuse=diff_mode=rectangle"
+ if options.gif_dither ~= 6 then
+ cfilter = cfilter .. ":dither=bayer:bayer_scale=" .. tostring(options.gif_dither)
+ end
+ cfilter = cfilter .. "[vo]"
+ append(new_command, {
+ "--lavfi-complex=" .. tostring(cfilter)
+ })
+ return new_command
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.displayName = "GIF"
+ self.supportsTwopass = false
+ self.videoCodec = "gif"
+ self.audioCodec = ""
+ self.outputExtension = "gif"
+ self.acceptsBitrate = false
+ end,
+ __base = _base_0,
+ __name = "GIF",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ GIF = _class_0
+end
+formats["gif"] = GIF()
+local Page
+do
+ local _class_0
+ local _base_0 = {
+ add_keybinds = function(self)
+ if not self.keybinds then
+ return
+ end
+ for key, func in pairs(self.keybinds) do
+ mp.add_forced_key_binding(key, key, func, {
+ repeatable = true
+ })
+ end
+ end,
+ remove_keybinds = function(self)
+ if not self.keybinds then
+ return
+ end
+ for key, _ in pairs(self.keybinds) do
+ mp.remove_key_binding(key)
+ end
+ end,
+ observe_properties = function(self)
+ self.sizeCallback = function()
+ return self:draw()
+ end
+ local properties = {
+ "keepaspect",
+ "video-out-params",
+ "video-unscaled",
+ "panscan",
+ "video-zoom",
+ "video-align-x",
+ "video-pan-x",
+ "video-align-y",
+ "video-pan-y",
+ "osd-width",
+ "osd-height"
+ }
+ for _index_0 = 1, #properties do
+ local p = properties[_index_0]
+ mp.observe_property(p, "native", self.sizeCallback)
+ end
+ end,
+ unobserve_properties = function(self)
+ if self.sizeCallback then
+ mp.unobserve_property(self.sizeCallback)
+ self.sizeCallback = nil
+ end
+ end,
+ clear = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ mp.set_osd_ass(window_w, window_h, "")
+ return mp.osd_message("", 0)
+ end,
+ prepare = function(self)
+ return nil
+ end,
+ dispose = function(self)
+ return nil
+ end,
+ show = function(self)
+ if self.visible then
+ return
+ end
+ self.visible = true
+ self:observe_properties()
+ self:add_keybinds()
+ self:prepare()
+ self:clear()
+ return self:draw()
+ end,
+ hide = function(self)
+ if not self.visible then
+ return
+ end
+ self.visible = false
+ self:unobserve_properties()
+ self:remove_keybinds()
+ self:clear()
+ return self:dispose()
+ end,
+ setup_text = function(self, ass)
+ local scale = calculate_scale_factor()
+ local margin = options.margin * scale
+ ass:append("{\\an7}")
+ ass:pos(margin, margin)
+ return ass:append("{\\fs" .. tostring(options.font_size * scale) .. "}")
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function() end,
+ __base = _base_0,
+ __name = "Page"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Page = _class_0
+end
+local EncodeWithProgress
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ draw = function(self)
+ local progress = 100 * ((self.currentTime - self.startTime) / self.duration)
+ local progressText = string.format("%d%%", progress)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append("Encoding (" .. tostring(bold(progressText)) .. ")\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ parseLine = function(self, line)
+ local matchTime = string.match(line, "Encode time[-]pos: ([0-9.]+)")
+ local matchExit = string.match(line, "Exiting... [(]([%a ]+)[)]")
+ if matchTime == nil and matchExit == nil then
+ return
+ end
+ if matchTime ~= nil and tonumber(matchTime) > self.currentTime then
+ self.currentTime = tonumber(matchTime)
+ end
+ if matchExit ~= nil then
+ self.finished = true
+ self.finishedReason = matchExit
+ end
+ end,
+ startEncode = function(self, command_line)
+ local copy_command_line
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #command_line do
+ local arg = command_line[_index_0]
+ _accum_0[_len_0] = arg
+ _len_0 = _len_0 + 1
+ end
+ copy_command_line = _accum_0
+ end
+ append(copy_command_line, {
+ '--term-status-msg=Encode time-pos: ${=time-pos}\\n'
+ })
+ self:show()
+ local processFd = run_subprocess_popen(copy_command_line)
+ for line in processFd:lines() do
+ msg.verbose(string.format('%q', line))
+ self:parseLine(line)
+ self:draw()
+ end
+ processFd:close()
+ self:hide()
+ if self.finishedReason == "End of file" then
+ return true
+ end
+ return false
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, startTime, endTime)
+ self.startTime = startTime
+ self.endTime = endTime
+ self.duration = endTime - startTime
+ self.currentTime = startTime
+ end,
+ __base = _base_0,
+ __name = "EncodeWithProgress",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ EncodeWithProgress = _class_0
+end
+local get_active_tracks
+get_active_tracks = function()
+ local accepted = {
+ video = true,
+ audio = not mp.get_property_bool("mute"),
+ sub = mp.get_property_bool("sub-visibility")
+ }
+ local active = {
+ video = { },
+ audio = { },
+ sub = { }
+ }
+ for _, track in ipairs(mp.get_property_native("track-list")) do
+ if track["selected"] and accepted[track["type"]] then
+ local count = #active[track["type"]]
+ active[track["type"]][count + 1] = track
+ end
+ end
+ return active
+end
+local filter_tracks_supported_by_format
+filter_tracks_supported_by_format = function(active_tracks, format)
+ local has_video_codec = format.videoCodec ~= ""
+ local has_audio_codec = format.audioCodec ~= ""
+ local supported = {
+ video = has_video_codec and active_tracks["video"] or { },
+ audio = has_audio_codec and active_tracks["audio"] or { },
+ sub = has_video_codec and active_tracks["sub"] or { }
+ }
+ return supported
+end
+local append_track
+append_track = function(out, track)
+ local external_flag = {
+ ["audio"] = "audio-file",
+ ["sub"] = "sub-file"
+ }
+ local internal_flag = {
+ ["video"] = "vid",
+ ["audio"] = "aid",
+ ["sub"] = "sid"
+ }
+ if track['external'] and string.len(track['external-filename']) <= 2048 then
+ return append(out, {
+ "--" .. tostring(external_flag[track['type']]) .. "=" .. tostring(track['external-filename'])
+ })
+ else
+ return append(out, {
+ "--" .. tostring(internal_flag[track['type']]) .. "=" .. tostring(track['id'])
+ })
+ end
+end
+local append_audio_tracks
+append_audio_tracks = function(out, tracks)
+ local internal_tracks = { }
+ for _index_0 = 1, #tracks do
+ local track = tracks[_index_0]
+ if track['external'] then
+ append_track(out, track)
+ else
+ append(internal_tracks, {
+ track
+ })
+ end
+ end
+ if #internal_tracks > 1 then
+ local filter_string = ""
+ for _index_0 = 1, #internal_tracks do
+ local track = internal_tracks[_index_0]
+ filter_string = filter_string .. "[aid" .. tostring(track['id']) .. "]"
+ end
+ filter_string = filter_string .. "amix[ao]"
+ return append(out, {
+ "--lavfi-complex=" .. tostring(filter_string)
+ })
+ else
+ if #internal_tracks == 1 then
+ return append_track(out, internal_tracks[1])
+ end
+ end
+end
+local get_scale_filters
+get_scale_filters = function()
+ local filters = { }
+ if options.force_square_pixels then
+ append(filters, {
+ "lavfi-scale=iw*sar:ih"
+ })
+ end
+ if options.scale_height > 0 then
+ append(filters, {
+ "lavfi-scale=-2:" .. tostring(options.scale_height)
+ })
+ end
+ return filters
+end
+local get_fps_filters
+get_fps_filters = function()
+ if options.fps > 0 then
+ return {
+ "fps=" .. tostring(options.fps)
+ }
+ end
+ return { }
+end
+local get_contrast_brightness_and_saturation_filters
+get_contrast_brightness_and_saturation_filters = function()
+ local mpv_brightness = mp.get_property("brightness")
+ local mpv_contrast = mp.get_property("contrast")
+ local mpv_saturation = mp.get_property("saturation")
+ if mpv_brightness == 0 and mpv_contrast == 0 and mpv_saturation == 0 then
+ return { }
+ end
+ local eq_saturation = (mpv_saturation + 100) / 100.0
+ local eq_contrast = (mpv_contrast + 100) / 100.0
+ local eq_brightness = (mpv_brightness / 50.0 + eq_contrast - 1) / 2.0
+ return {
+ "lavfi-eq=contrast=" .. tostring(eq_contrast) .. ":saturation=" .. tostring(eq_saturation) .. ":brightness=" .. tostring(eq_brightness)
+ }
+end
+local append_property
+append_property = function(out, property_name, option_name)
+ option_name = option_name or property_name
+ local prop = mp.get_property(property_name)
+ if prop and prop ~= "" then
+ return append(out, {
+ "--" .. tostring(option_name) .. "=" .. tostring(prop)
+ })
+ end
+end
+local append_list_options
+append_list_options = function(out, property_name, option_prefix)
+ option_prefix = option_prefix or property_name
+ local prop = mp.get_property_native(property_name)
+ if prop then
+ for _index_0 = 1, #prop do
+ local value = prop[_index_0]
+ append(out, {
+ "--" .. tostring(option_prefix) .. "-append=" .. tostring(value)
+ })
+ end
+ end
+end
+local get_playback_options
+get_playback_options = function()
+ local ret = { }
+ append_property(ret, "sub-ass-override")
+ append_property(ret, "sub-ass-force-style")
+ append_property(ret, "sub-ass-vsfilter-aspect-compat")
+ append_property(ret, "sub-auto")
+ append_property(ret, "sub-pos")
+ append_property(ret, "sub-delay")
+ append_property(ret, "video-rotate")
+ append_property(ret, "ytdl-format")
+ append_property(ret, "deinterlace")
+ return ret
+end
+local get_speed_flags
+get_speed_flags = function()
+ local ret = { }
+ local speed = mp.get_property_native("speed")
+ if speed ~= 1 then
+ append(ret, {
+ "--vf-add=setpts=PTS/" .. tostring(speed),
+ "--af-add=atempo=" .. tostring(speed),
+ "--sub-speed=1/" .. tostring(speed)
+ })
+ end
+ return ret
+end
+local get_metadata_flags
+get_metadata_flags = function()
+ local title = mp.get_property("filename/no-ext")
+ return {
+ "--oset-metadata=title=%" .. tostring(string.len(title)) .. "%" .. tostring(title)
+ }
+end
+local apply_current_filters
+apply_current_filters = function(filters)
+ local vf = mp.get_property_native("vf")
+ msg.verbose("apply_current_filters: got " .. tostring(#vf) .. " currently applied.")
+ for _index_0 = 1, #vf do
+ local _continue_0 = false
+ repeat
+ local filter = vf[_index_0]
+ msg.verbose("apply_current_filters: filter name: " .. tostring(filter['name']))
+ if filter["enabled"] == false then
+ _continue_0 = true
+ break
+ end
+ local str = filter["name"]
+ local params = filter["params"] or { }
+ for k, v in pairs(params) do
+ str = str .. ":" .. tostring(k) .. "=%" .. tostring(string.len(v)) .. "%" .. tostring(v)
+ end
+ append(filters, {
+ str
+ })
+ _continue_0 = true
+ until true
+ if not _continue_0 then
+ break
+ end
+ end
+end
+local get_video_filters
+get_video_filters = function(format, region)
+ local filters = { }
+ append(filters, format:getPreFilters())
+ if options.apply_current_filters then
+ apply_current_filters(filters)
+ end
+ if region and region:is_valid() then
+ append(filters, {
+ "lavfi-crop=" .. tostring(region.w) .. ":" .. tostring(region.h) .. ":" .. tostring(region.x) .. ":" .. tostring(region.y)
+ })
+ end
+ append(filters, get_scale_filters())
+ append(filters, get_fps_filters())
+ append(filters, get_contrast_brightness_and_saturation_filters())
+ append(filters, format:getPostFilters())
+ return filters
+end
+local get_video_encode_flags
+get_video_encode_flags = function(format, region)
+ local flags = { }
+ append(flags, get_playback_options())
+ local filters = get_video_filters(format, region)
+ for _index_0 = 1, #filters do
+ local f = filters[_index_0]
+ append(flags, {
+ "--vf-add=" .. tostring(f)
+ })
+ end
+ append(flags, get_speed_flags())
+ return flags
+end
+local calculate_bitrate
+calculate_bitrate = function(active_tracks, format, length)
+ if format.videoCodec == "" then
+ return nil, options.target_filesize * 8 / length
+ end
+ local video_kilobits = options.target_filesize * 8
+ local audio_kilobits = nil
+ local has_audio_track = #active_tracks["audio"] > 0
+ if options.strict_filesize_constraint and has_audio_track then
+ audio_kilobits = length * options.strict_audio_bitrate
+ video_kilobits = video_kilobits - audio_kilobits
+ end
+ local video_bitrate = math.floor(video_kilobits / length)
+ local audio_bitrate = audio_kilobits and math.floor(audio_kilobits / length) or nil
+ return video_bitrate, audio_bitrate
+end
+local find_path
+find_path = function(startTime, endTime)
+ local path = mp.get_property('path')
+ if not path then
+ return nil, nil, nil, nil, nil
+ end
+ local is_stream = not file_exists(path)
+ local is_temporary = false
+ if is_stream then
+ if mp.get_property('file-format') == 'hls' then
+ path = utils.join_path(parse_directory('~'), 'cache_dump.ts')
+ mp.command_native({
+ 'dump_cache',
+ seconds_to_time_string(startTime, false, true),
+ seconds_to_time_string(endTime + 5, false, true),
+ path
+ })
+ endTime = endTime - startTime
+ startTime = 0
+ is_temporary = true
+ end
+ end
+ return path, is_stream, is_temporary, startTime, endTime
+end
+local encode
+encode = function(region, startTime, endTime)
+ local format = formats[options.output_format]
+ local originalStartTime = startTime
+ local originalEndTime = endTime
+ local path, is_stream, is_temporary
+ path, is_stream, is_temporary, startTime, endTime = find_path(startTime, endTime)
+ if not path then
+ message("No file is being played")
+ return
+ end
+ local command = {
+ "mpv",
+ path,
+ "--start=" .. seconds_to_time_string(startTime, false, true),
+ "--end=" .. seconds_to_time_string(endTime, false, true),
+ "--loop-file=no",
+ "--no-pause"
+ }
+ append(command, format:getCodecFlags())
+ local active_tracks = get_active_tracks()
+ local supported_active_tracks = filter_tracks_supported_by_format(active_tracks, format)
+ for track_type, tracks in pairs(supported_active_tracks) do
+ if track_type == "audio" then
+ append_audio_tracks(command, tracks)
+ else
+ for _index_0 = 1, #tracks do
+ local track = tracks[_index_0]
+ append_track(command, track)
+ end
+ end
+ end
+ for track_type, tracks in pairs(supported_active_tracks) do
+ local _continue_0 = false
+ repeat
+ if #tracks > 0 then
+ _continue_0 = true
+ break
+ end
+ local _exp_0 = track_type
+ if "video" == _exp_0 then
+ append(command, {
+ "--vid=no"
+ })
+ elseif "audio" == _exp_0 then
+ append(command, {
+ "--aid=no"
+ })
+ elseif "sub" == _exp_0 then
+ append(command, {
+ "--sid=no"
+ })
+ end
+ _continue_0 = true
+ until true
+ if not _continue_0 then
+ break
+ end
+ end
+ if format.videoCodec ~= "" then
+ append(command, get_video_encode_flags(format, region))
+ end
+ append(command, format:getFlags())
+ if options.write_filename_on_metadata then
+ append(command, get_metadata_flags())
+ end
+ if format.acceptsBitrate then
+ if options.target_filesize > 0 then
+ local length = endTime - startTime
+ local video_bitrate, audio_bitrate = calculate_bitrate(supported_active_tracks, format, length)
+ if video_bitrate then
+ append(command, {
+ "--ovcopts-add=b=" .. tostring(video_bitrate) .. "k"
+ })
+ end
+ if audio_bitrate then
+ append(command, {
+ "--oacopts-add=b=" .. tostring(audio_bitrate) .. "k"
+ })
+ end
+ if options.strict_filesize_constraint then
+ local type = format.videoCodec ~= "" and "ovc" or "oac"
+ append(command, {
+ "--" .. tostring(type) .. "opts-add=minrate=" .. tostring(bitrate) .. "k",
+ "--" .. tostring(type) .. "opts-add=maxrate=" .. tostring(bitrate) .. "k"
+ })
+ end
+ else
+ local type = format.videoCodec ~= "" and "ovc" or "oac"
+ append(command, {
+ "--" .. tostring(type) .. "opts-add=b=0"
+ })
+ end
+ end
+ for token in string.gmatch(options.additional_flags, "[^%s]+") do
+ command[#command + 1] = token
+ end
+ if not options.strict_filesize_constraint then
+ for token in string.gmatch(options.non_strict_additional_flags, "[^%s]+") do
+ command[#command + 1] = token
+ end
+ if options.crf >= 0 then
+ append(command, {
+ "--ovcopts-add=crf=" .. tostring(options.crf)
+ })
+ end
+ end
+ local dir = ""
+ if is_stream then
+ dir = parse_directory("~")
+ else
+ local _
+ dir, _ = utils.split_path(path)
+ end
+ if options.output_directory ~= "" then
+ dir = parse_directory(options.output_directory)
+ end
+ local formatted_filename = format_filename(originalStartTime, originalEndTime, format)
+ local out_path = utils.join_path(dir, formatted_filename)
+ append(command, {
+ "--o=" .. tostring(out_path)
+ })
+ emit_event("encode-started")
+ if options.twopass and format.supportsTwopass and not is_stream then
+ local first_pass_cmdline
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #command do
+ local arg = command[_index_0]
+ _accum_0[_len_0] = arg
+ _len_0 = _len_0 + 1
+ end
+ first_pass_cmdline = _accum_0
+ end
+ append(first_pass_cmdline, {
+ "--ovcopts-add=flags=+pass1"
+ })
+ message("Starting first pass...")
+ msg.verbose("First-pass command line: ", table.concat(first_pass_cmdline, " "))
+ local res = run_subprocess({
+ args = first_pass_cmdline,
+ cancellable = false
+ })
+ if not res then
+ message("First pass failed! Check the logs for details.")
+ emit_event("encode-finished", "fail")
+ return
+ end
+ append(command, {
+ "--ovcopts-add=flags=+pass2"
+ })
+ if format.videoCodec == "libvpx" then
+ msg.verbose("Patching libvpx pass log file...")
+ vp8_patch_logfile(get_pass_logfile_path(out_path), endTime - startTime)
+ end
+ end
+ command = format:postCommandModifier(command, region, startTime, endTime)
+ msg.info("Encoding to", out_path)
+ msg.verbose("Command line:", table.concat(command, " "))
+ if options.run_detached then
+ message("Started encode, process was detached.")
+ return utils.subprocess_detached({
+ args = command
+ })
+ else
+ local res = false
+ if not should_display_progress() then
+ message("Started encode...")
+ res = run_subprocess({
+ args = command,
+ cancellable = false
+ })
+ else
+ local ewp = EncodeWithProgress(startTime, endTime)
+ res = ewp:startEncode(command)
+ end
+ if res then
+ message("Encoded successfully! Saved to\\N" .. tostring(bold(out_path)))
+ emit_event("encode-finished", "success")
+ else
+ message("Encode failed! Check the logs for details.")
+ emit_event("encode-finished", "fail")
+ end
+ os.remove(get_pass_logfile_path(out_path))
+ if is_temporary then
+ return os.remove(path)
+ end
+ end
+end
+local CropPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ reset = function(self)
+ local dimensions = get_video_dimensions()
+ local xa, ya
+ do
+ local _obj_0 = dimensions.top_left
+ xa, ya = _obj_0.x, _obj_0.y
+ end
+ self.pointA:set_from_screen(xa, ya)
+ local xb, yb
+ do
+ local _obj_0 = dimensions.bottom_right
+ xb, yb = _obj_0.x, _obj_0.y
+ end
+ self.pointB:set_from_screen(xb, yb)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ setPointA = function(self)
+ local posX, posY = mp.get_mouse_pos()
+ self.pointA:set_from_screen(posX, posY)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ setPointB = function(self)
+ local posX, posY = mp.get_mouse_pos()
+ self.pointB:set_from_screen(posX, posY)
+ if self.visible then
+ return self:draw()
+ end
+ end,
+ cancel = function(self)
+ self:hide()
+ return self.callback(false, nil)
+ end,
+ finish = function(self)
+ local region = Region()
+ region:set_from_points(self.pointA, self.pointB)
+ self:hide()
+ return self.callback(true, region)
+ end,
+ draw_box = function(self, ass)
+ local region = Region()
+ region:set_from_points(self.pointA:to_screen(), self.pointB:to_screen())
+ local d = get_video_dimensions()
+ ass:new_event()
+ ass:append("{\\an7}")
+ ass:pos(0, 0)
+ ass:append('{\\bord0}')
+ ass:append('{\\shad0}')
+ ass:append('{\\c&H000000&}')
+ ass:append('{\\alpha&H77}')
+ ass:draw_start()
+ ass:rect_cw(d.top_left.x, d.top_left.y, region.x, region.y + region.h)
+ ass:rect_cw(region.x, d.top_left.y, d.bottom_right.x, region.y)
+ ass:rect_cw(d.top_left.x, region.y + region.h, region.x + region.w, d.bottom_right.y)
+ ass:rect_cw(region.x + region.w, region.y, d.bottom_right.x, d.bottom_right.y)
+ return ass:draw_stop()
+ end,
+ draw = function(self)
+ local window = { }
+ window.w, window.h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ self:draw_box(ass)
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('Crop:')) .. "\\N")
+ ass:append(tostring(bold('1:')) .. " change point A (" .. tostring(self.pointA.x) .. ", " .. tostring(self.pointA.y) .. ")\\N")
+ ass:append(tostring(bold('2:')) .. " change point B (" .. tostring(self.pointB.x) .. ", " .. tostring(self.pointB.y) .. ")\\N")
+ ass:append(tostring(bold('r:')) .. " reset to whole screen\\N")
+ ass:append(tostring(bold('ESC:')) .. " cancel crop\\N")
+ local width, height = math.abs(self.pointA.x - self.pointB.x), math.abs(self.pointA.y - self.pointB.y)
+ ass:append(tostring(bold('ENTER:')) .. " confirm crop (" .. tostring(width) .. "x" .. tostring(height) .. ")\\N")
+ return mp.set_osd_ass(window.w, window.h, ass.text)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback, region)
+ self.pointA = VideoPoint()
+ self.pointB = VideoPoint()
+ self.keybinds = {
+ ["1"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setPointA
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["2"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setPointB
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["r"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.reset
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancel
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ENTER"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.finish
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self:reset()
+ self.callback = callback
+ if region and region:is_valid() then
+ self.pointA.x = region.x
+ self.pointA.y = region.y
+ self.pointB.x = region.x + region.w
+ self.pointB.y = region.y + region.h
+ end
+ end,
+ __base = _base_0,
+ __name = "CropPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ CropPage = _class_0
+end
+local Option
+do
+ local _class_0
+ local _base_0 = {
+ hasPrevious = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return true
+ elseif "int" == _exp_0 then
+ if self.opts.min then
+ return self.value > self.opts.min
+ else
+ return true
+ end
+ elseif "list" == _exp_0 then
+ return self.value > 1
+ end
+ end,
+ hasNext = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return true
+ elseif "int" == _exp_0 then
+ if self.opts.max then
+ return self.value < self.opts.max
+ else
+ return true
+ end
+ elseif "list" == _exp_0 then
+ return self.value < #self.opts.possibleValues
+ end
+ end,
+ leftKey = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = not self.value
+ elseif "int" == _exp_0 then
+ self.value = self.value - self.opts.step
+ if self.opts.min and self.opts.min > self.value then
+ self.value = self.opts.min
+ end
+ elseif "list" == _exp_0 then
+ if self.value > 1 then
+ self.value = self.value - 1
+ end
+ end
+ end,
+ rightKey = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = not self.value
+ elseif "int" == _exp_0 then
+ self.value = self.value + self.opts.step
+ if self.opts.max and self.opts.max < self.value then
+ self.value = self.opts.max
+ end
+ elseif "list" == _exp_0 then
+ if self.value < #self.opts.possibleValues then
+ self.value = self.value + 1
+ end
+ end
+ end,
+ getValue = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return self.value
+ elseif "int" == _exp_0 then
+ return self.value
+ elseif "list" == _exp_0 then
+ local value, _
+ do
+ local _obj_0 = self.opts.possibleValues[self.value]
+ value, _ = _obj_0[1], _obj_0[2]
+ end
+ return value
+ end
+ end,
+ setValue = function(self, value)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ self.value = value
+ elseif "int" == _exp_0 then
+ self.value = value
+ elseif "list" == _exp_0 then
+ local set = false
+ for i, possiblePair in ipairs(self.opts.possibleValues) do
+ local possibleValue, _
+ possibleValue, _ = possiblePair[1], possiblePair[2]
+ if possibleValue == value then
+ set = true
+ self.value = i
+ break
+ end
+ end
+ if not set then
+ return msg.warn("Tried to set invalid value " .. tostring(value) .. " to " .. tostring(self.displayText) .. " option.")
+ end
+ end
+ end,
+ getDisplayValue = function(self)
+ local _exp_0 = self.optType
+ if "bool" == _exp_0 then
+ return self.value and "yes" or "no"
+ elseif "int" == _exp_0 then
+ if self.opts.altDisplayNames and self.opts.altDisplayNames[self.value] then
+ return self.opts.altDisplayNames[self.value]
+ else
+ return tostring(self.value)
+ end
+ elseif "list" == _exp_0 then
+ local value, displayValue
+ do
+ local _obj_0 = self.opts.possibleValues[self.value]
+ value, displayValue = _obj_0[1], _obj_0[2]
+ end
+ return displayValue or value
+ end
+ end,
+ draw = function(self, ass, selected)
+ if selected then
+ ass:append(tostring(bold(self.displayText)) .. ": ")
+ else
+ ass:append(tostring(self.displayText) .. ": ")
+ end
+ if self:hasPrevious() then
+ ass:append("◀ ")
+ end
+ ass:append(self:getDisplayValue())
+ if self:hasNext() then
+ ass:append(" ▶")
+ end
+ return ass:append("\\N")
+ end,
+ optVisible = function(self)
+ if self.visibleCheckFn == nil then
+ return true
+ else
+ return self.visibleCheckFn()
+ end
+ end
+ }
+ _base_0.__index = _base_0
+ _class_0 = setmetatable({
+ __init = function(self, optType, displayText, value, opts, visibleCheckFn)
+ self.optType = optType
+ self.displayText = displayText
+ self.opts = opts
+ self.value = 1
+ self.visibleCheckFn = visibleCheckFn
+ return self:setValue(value)
+ end,
+ __base = _base_0,
+ __name = "Option"
+ }, {
+ __index = _base_0,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ Option = _class_0
+end
+local EncodeOptionsPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ getCurrentOption = function(self)
+ return self.options[self.currentOption][2]
+ end,
+ leftKey = function(self)
+ (self:getCurrentOption()):leftKey()
+ return self:draw()
+ end,
+ rightKey = function(self)
+ (self:getCurrentOption()):rightKey()
+ return self:draw()
+ end,
+ prevOpt = function(self)
+ for i = self.currentOption - 1, 1, -1 do
+ if self.options[i][2]:optVisible() then
+ self.currentOption = i
+ break
+ end
+ end
+ return self:draw()
+ end,
+ nextOpt = function(self)
+ for i = self.currentOption + 1, #self.options do
+ if self.options[i][2]:optVisible() then
+ self.currentOption = i
+ break
+ end
+ end
+ return self:draw()
+ end,
+ confirmOpts = function(self)
+ for _, optPair in ipairs(self.options) do
+ local optName, opt
+ optName, opt = optPair[1], optPair[2]
+ options[optName] = opt:getValue()
+ end
+ self:hide()
+ return self.callback(true)
+ end,
+ cancelOpts = function(self)
+ self:hide()
+ return self.callback(false)
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('Options:')) .. "\\N\\N")
+ for i, optPair in ipairs(self.options) do
+ local opt = optPair[2]
+ if opt:optVisible() then
+ opt:draw(ass, self.currentOption == i)
+ end
+ end
+ ass:append("\\N▲ / ▼: navigate\\N")
+ ass:append(tostring(bold('ENTER:')) .. " confirm options\\N")
+ ass:append(tostring(bold('ESC:')) .. " cancel\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback)
+ self.callback = callback
+ self.currentOption = 1
+ local scaleHeightOpts = {
+ possibleValues = {
+ {
+ -1,
+ "no"
+ },
+ {
+ 144
+ },
+ {
+ 240
+ },
+ {
+ 360
+ },
+ {
+ 480
+ },
+ {
+ 540
+ },
+ {
+ 720
+ },
+ {
+ 1080
+ },
+ {
+ 1440
+ },
+ {
+ 2160
+ }
+ }
+ }
+ local filesizeOpts = {
+ step = 250,
+ min = 0,
+ altDisplayNames = {
+ [0] = "0 (constant quality)"
+ }
+ }
+ local crfOpts = {
+ step = 1,
+ min = -1,
+ altDisplayNames = {
+ [-1] = "disabled"
+ }
+ }
+ local fpsOpts = {
+ possibleValues = {
+ {
+ -1,
+ "source"
+ },
+ {
+ 15
+ },
+ {
+ 24
+ },
+ {
+ 30
+ },
+ {
+ 48
+ },
+ {
+ 50
+ },
+ {
+ 60
+ },
+ {
+ 120
+ },
+ {
+ 240
+ }
+ }
+ }
+ local formatIds = {
+ "webm-vp8",
+ "webm-vp9",
+ "mp4",
+ "mp4-nvenc",
+ "raw",
+ "mp3",
+ "gif"
+ }
+ local formatOpts = {
+ possibleValues = (function()
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #formatIds do
+ local fId = formatIds[_index_0]
+ _accum_0[_len_0] = {
+ fId,
+ formats[fId].displayName
+ }
+ _len_0 = _len_0 + 1
+ end
+ return _accum_0
+ end)()
+ }
+ local gifDitherOpts = {
+ possibleValues = {
+ {
+ 0,
+ "bayer_scale 0"
+ },
+ {
+ 1,
+ "bayer_scale 1"
+ },
+ {
+ 2,
+ "bayer_scale 2"
+ },
+ {
+ 3,
+ "bayer_scale 3"
+ },
+ {
+ 4,
+ "bayer_scale 4"
+ },
+ {
+ 5,
+ "bayer_scale 5"
+ },
+ {
+ 6,
+ "sierra2_4a"
+ }
+ }
+ }
+ self.options = {
+ {
+ "output_format",
+ Option("list", "Output Format", options.output_format, formatOpts)
+ },
+ {
+ "twopass",
+ Option("bool", "Two Pass", options.twopass)
+ },
+ {
+ "apply_current_filters",
+ Option("bool", "Apply Current Video Filters", options.apply_current_filters)
+ },
+ {
+ "scale_height",
+ Option("list", "Scale Height", options.scale_height, scaleHeightOpts)
+ },
+ {
+ "strict_filesize_constraint",
+ Option("bool", "Strict Filesize Constraint", options.strict_filesize_constraint)
+ },
+ {
+ "write_filename_on_metadata",
+ Option("bool", "Write Filename on Metadata", options.write_filename_on_metadata)
+ },
+ {
+ "target_filesize",
+ Option("int", "Target Filesize", options.target_filesize, filesizeOpts)
+ },
+ {
+ "crf",
+ Option("int", "CRF", options.crf, crfOpts)
+ },
+ {
+ "fps",
+ Option("list", "FPS", options.fps, fpsOpts)
+ },
+ {
+ "gif_dither",
+ Option("list", "GIF Dither Type", options.gif_dither, gifDitherOpts, function()
+ return self.options[1][2]:getValue() == "gif"
+ end)
+ },
+ {
+ "force_square_pixels",
+ Option("bool", "Force Square Pixels", options.force_square_pixels)
+ }
+ }
+ self.keybinds = {
+ ["LEFT"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.leftKey
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["RIGHT"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.rightKey
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["UP"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.prevOpt
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["DOWN"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.nextOpt
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ENTER"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.confirmOpts
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancelOpts
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ end,
+ __base = _base_0,
+ __name = "EncodeOptionsPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ EncodeOptionsPage = _class_0
+end
+local PreviewPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ prepare = function(self)
+ local vf = mp.get_property_native("vf")
+ vf[#vf + 1] = {
+ name = "sub"
+ }
+ if self.region:is_valid() then
+ vf[#vf + 1] = {
+ name = "crop",
+ params = {
+ w = tostring(self.region.w),
+ h = tostring(self.region.h),
+ x = tostring(self.region.x),
+ y = tostring(self.region.y)
+ }
+ }
+ end
+ mp.set_property_native("vf", vf)
+ if self.startTime > -1 and self.endTime > -1 then
+ mp.set_property_native("ab-loop-a", self.startTime)
+ mp.set_property_native("ab-loop-b", self.endTime)
+ mp.set_property_native("time-pos", self.startTime)
+ end
+ return mp.set_property_native("pause", false)
+ end,
+ dispose = function(self)
+ mp.set_property("ab-loop-a", "no")
+ mp.set_property("ab-loop-b", "no")
+ for prop, value in pairs(self.originalProperties) do
+ mp.set_property_native(prop, value)
+ end
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append("Press " .. tostring(bold('ESC')) .. " to exit preview.\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ cancel = function(self)
+ self:hide()
+ return self.callback()
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self, callback, region, startTime, endTime)
+ self.callback = callback
+ self.originalProperties = {
+ ["vf"] = mp.get_property_native("vf"),
+ ["time-pos"] = mp.get_property_native("time-pos"),
+ ["pause"] = mp.get_property_native("pause")
+ }
+ self.keybinds = {
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.cancel
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self.region = region
+ self.startTime = startTime
+ self.endTime = endTime
+ self.isLoop = false
+ end,
+ __base = _base_0,
+ __name = "PreviewPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ PreviewPage = _class_0
+end
+local MainPage
+do
+ local _class_0
+ local _parent_0 = Page
+ local _base_0 = {
+ setStartTime = function(self)
+ self.startTime = mp.get_property_number("time-pos")
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ setEndTime = function(self)
+ self.endTime = mp.get_property_number("time-pos")
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ setupStartAndEndTimes = function(self)
+ if mp.get_property_native("duration") then
+ self.startTime = 0
+ self.endTime = mp.get_property_native("duration")
+ else
+ self.startTime = -1
+ self.endTime = -1
+ end
+ if self.visible then
+ self:clear()
+ return self:draw()
+ end
+ end,
+ draw = function(self)
+ local window_w, window_h = mp.get_osd_size()
+ local ass = assdraw.ass_new()
+ ass:new_event()
+ self:setup_text(ass)
+ ass:append(tostring(bold('WebM maker')) .. "\\N\\N")
+ ass:append(tostring(bold('c:')) .. " crop\\N")
+ ass:append(tostring(bold('1:')) .. " set start time (current is " .. tostring(seconds_to_time_string(self.startTime)) .. ")\\N")
+ ass:append(tostring(bold('2:')) .. " set end time (current is " .. tostring(seconds_to_time_string(self.endTime)) .. ")\\N")
+ ass:append(tostring(bold('o:')) .. " change encode options\\N")
+ ass:append(tostring(bold('p:')) .. " preview\\N")
+ ass:append(tostring(bold('e:')) .. " encode\\N\\N")
+ ass:append(tostring(bold('ESC:')) .. " close\\N")
+ return mp.set_osd_ass(window_w, window_h, ass.text)
+ end,
+ show = function(self)
+ _class_0.__parent.show(self)
+ return emit_event("show-main-page")
+ end,
+ onUpdateCropRegion = function(self, updated, newRegion)
+ if updated then
+ self.region = newRegion
+ end
+ return self:show()
+ end,
+ crop = function(self)
+ self:hide()
+ local cropPage = CropPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onUpdateCropRegion
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(), self.region)
+ return cropPage:show()
+ end,
+ onOptionsChanged = function(self, updated)
+ return self:show()
+ end,
+ changeOptions = function(self)
+ self:hide()
+ local encodeOptsPage = EncodeOptionsPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onOptionsChanged
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)())
+ return encodeOptsPage:show()
+ end,
+ onPreviewEnded = function(self)
+ return self:show()
+ end,
+ preview = function(self)
+ self:hide()
+ local previewPage = PreviewPage((function()
+ local _base_1 = self
+ local _fn_0 = _base_1.onPreviewEnded
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(), self.region, self.startTime, self.endTime)
+ return previewPage:show()
+ end,
+ encode = function(self)
+ self:hide()
+ if self.startTime < 0 then
+ message("No start time, aborting")
+ return
+ end
+ if self.endTime < 0 then
+ message("No end time, aborting")
+ return
+ end
+ if self.startTime >= self.endTime then
+ message("Start time is ahead of end time, aborting")
+ return
+ end
+ return encode(self.region, self.startTime, self.endTime)
+ end
+ }
+ _base_0.__index = _base_0
+ setmetatable(_base_0, _parent_0.__base)
+ _class_0 = setmetatable({
+ __init = function(self)
+ self.keybinds = {
+ ["c"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.crop
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["1"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setStartTime
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["2"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.setEndTime
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["o"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.changeOptions
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["p"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.preview
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["e"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.encode
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)(),
+ ["ESC"] = (function()
+ local _base_1 = self
+ local _fn_0 = _base_1.hide
+ return function(...)
+ return _fn_0(_base_1, ...)
+ end
+ end)()
+ }
+ self.startTime = -1
+ self.endTime = -1
+ self.region = Region()
+ end,
+ __base = _base_0,
+ __name = "MainPage",
+ __parent = _parent_0
+ }, {
+ __index = function(cls, name)
+ local val = rawget(_base_0, name)
+ if val == nil then
+ local parent = rawget(cls, "__parent")
+ if parent then
+ return parent[name]
+ end
+ else
+ return val
+ end
+ end,
+ __call = function(cls, ...)
+ local _self_0 = setmetatable({}, _base_0)
+ cls.__init(_self_0, ...)
+ return _self_0
+ end
+ })
+ _base_0.__class = _class_0
+ if _parent_0.__inherited then
+ _parent_0.__inherited(_parent_0, _class_0)
+ end
+ MainPage = _class_0
+end
+monitor_dimensions()
+local mainPage = MainPage()
+mp.add_key_binding(options.keybind, "display-webm-encoder", (function()
+ local _base_0 = mainPage
+ local _fn_0 = _base_0.show
+ return function(...)
+ return _fn_0(_base_0, ...)
+ end
+end)(), {
+ repeatable = false
+})
+mp.register_event("file-loaded", (function()
+ local _base_0 = mainPage
+ local _fn_0 = _base_0.setupStartAndEndTimes
+ return function(...)
+ return _fn_0(_base_0, ...)
+ end
+end)())
+msg.verbose("Loaded mpv-webm script!")
+return emit_event("script-loaded")
]