summaryrefslogtreecommitdiff
path: root/config/mpv/scripts/subs2srsa
diff options
context:
space:
mode:
Diffstat (limited to 'config/mpv/scripts/subs2srsa')
-rw-r--r--config/mpv/scripts/subs2srsa/.gitignore5
-rw-r--r--config/mpv/scripts/subs2srsa/LICENSE674
-rw-r--r--config/mpv/scripts/subs2srsa/Makefile36
-rw-r--r--config/mpv/scripts/subs2srsa/README.md478
-rw-r--r--config/mpv/scripts/subs2srsa/ankiconnect.lua241
-rw-r--r--config/mpv/scripts/subs2srsa/cfg_mgr.lua240
-rw-r--r--config/mpv/scripts/subs2srsa/encoder/codec_support.lua40
-rw-r--r--config/mpv/scripts/subs2srsa/encoder/encoder.lua729
-rwxr-xr-xconfig/mpv/scripts/subs2srsa/find_anki_col.sh5
-rw-r--r--config/mpv/scripts/subs2srsa/helpers.lua280
-rw-r--r--config/mpv/scripts/subs2srsa/howto/add_dialog.md9
-rw-r--r--config/mpv/scripts/subs2srsa/howto/create_card.md14
-rw-r--r--config/mpv/scripts/subs2srsa/howto/create_quick_card.md31
-rw-r--r--config/mpv/scripts/subs2srsa/howto/flatpak.md27
-rw-r--r--config/mpv/scripts/subs2srsa/howto/goldendict.md45
-rw-r--r--config/mpv/scripts/subs2srsa/howto/yomichan.md24
-rw-r--r--config/mpv/scripts/subs2srsa/main.lua1
-rw-r--r--config/mpv/scripts/subs2srsa/menu.lua77
-rw-r--r--config/mpv/scripts/subs2srsa/osd_styler.lua97
-rw-r--r--config/mpv/scripts/subs2srsa/platform/init.lua14
-rw-r--r--config/mpv/scripts/subs2srsa/platform/nix.lua49
-rw-r--r--config/mpv/scripts/subs2srsa/platform/win.lua72
-rw-r--r--config/mpv/scripts/subs2srsa/subs2srs.lua746
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/observer.lua344
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua196
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/sub_list.lua74
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/subtitle.lua56
-rw-r--r--config/mpv/scripts/subs2srsa/test.lua32
-rw-r--r--config/mpv/scripts/subs2srsa/utils/base64.lua46
-rw-r--r--config/mpv/scripts/subs2srsa/utils/filename_factory.lua89
-rw-r--r--config/mpv/scripts/subs2srsa/utils/forvo.lua145
-rw-r--r--config/mpv/scripts/subs2srsa/utils/pause_timer.lua33
-rw-r--r--config/mpv/scripts/subs2srsa/utils/play_control.lua61
-rw-r--r--config/mpv/scripts/subs2srsa/utils/switch.lua38
-rw-r--r--config/mpv/scripts/subs2srsa/utils/timings.lua28
35 files changed, 0 insertions, 5076 deletions
diff --git a/config/mpv/scripts/subs2srsa/.gitignore b/config/mpv/scripts/subs2srsa/.gitignore
deleted file mode 100644
index 850b67c..0000000
--- a/config/mpv/scripts/subs2srsa/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.idea
-.vscode
-/.github/RELEASE/*.zip
-/.github/RELEASE/*.html
-*TODO*
diff --git a/config/mpv/scripts/subs2srsa/LICENSE b/config/mpv/scripts/subs2srsa/LICENSE
deleted file mode 100644
index f288702..0000000
--- a/config/mpv/scripts/subs2srsa/LICENSE
+++ /dev/null
@@ -1,674 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
- 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.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- 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 <https://www.gnu.org/licenses/>.
-
-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:
-
- <program> Copyright (C) <year> <name of author>
- 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
-<https://www.gnu.org/licenses/>.
-
- 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
-<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/config/mpv/scripts/subs2srsa/Makefile b/config/mpv/scripts/subs2srsa/Makefile
deleted file mode 100644
index fc346ce..0000000
--- a/config/mpv/scripts/subs2srsa/Makefile
+++ /dev/null
@@ -1,36 +0,0 @@
-PROJECT := mpvacious
-PREFIX ?= /etc/mpv/
-BRANCH ?= remotes/origin/master
-VERSION := $(shell git describe --tags $(BRANCH))
-RELEASE_DIR := .github/RELEASE
-ZIP := $(RELEASE_DIR)/$(PROJECT)_$(VERSION).zip
-DOCS := $(RELEASE_DIR)/README_$(VERSION).html
-MD2HTML = md2html --github --full-html
-
-.PHONY: all clean docs install uninstall
-
-all: $(ZIP)
-docs: $(DOCS)
-
-$(ZIP):
- git archive \
- --prefix=$(PROJECT)_$(VERSION)/ \
- --format=zip \
- -o $@ \
- $(BRANCH) \
-
-$(DOCS):
- git show "$(BRANCH):README.md" | $(MD2HTML) -o $@
-
-install:
- find . -type f -iname '*.lua' | while read -r file; do \
- install -Dm644 "$$file" "$(PREFIX)/scripts/$(PROJECT)/$$file"; \
- done
- install -Dm644 $(RELEASE_DIR)/subs2srs.conf "$(PREFIX)/script-opts/subs2srs.conf"
-
-uninstall:
- rm -rf -- "$(PREFIX)/scripts/$(PROJECT)"
- rm -- "$(PREFIX)/script-opts/subs2srs.conf"
-
-clean:
- rm -- $(ZIP) $(DOCS)
diff --git a/config/mpv/scripts/subs2srsa/README.md b/config/mpv/scripts/subs2srsa/README.md
deleted file mode 100644
index deefe3c..0000000
--- a/config/mpv/scripts/subs2srsa/README.md
+++ /dev/null
@@ -1,478 +0,0 @@
-<p align="center">
-<img src="https://user-images.githubusercontent.com/69171671/117440218-4ae26800-af23-11eb-87b4-1d9026fc953f.png"/>
-</p>
-
-# mpvacious
-
-[![AUR](https://img.shields.io/badge/AUR-install-blue.svg)](https://aur.archlinux.org/packages/mpv-mpvacious/)
-[![Chat](https://img.shields.io/badge/chat-join-green.svg)](https://tatsumoto-ren.github.io/blog/join-our-community.html)
-![GitHub](https://img.shields.io/github/license/Ajatt-Tools/mpvacious)
-[![Donate](https://img.shields.io/badge/support-developer-orange)](https://tatsumoto.neocities.org/blog/donating-to-tatsumoto.html)
-
-mpvacious is your semi-automatic subs2srs for mpv.
-It supports multiple workflows and allows you to quickly create Anki cards
-while watching your favorite TV show.
-**[Video demonstration](https://redirect.invidious.io/watch?v=vU85ramvyo4)**.
-
-## Requirements
-
-<table>
-<tr>
- <th><a href="https://www.gnu.org/gnu/about-gnu.html">GNU/Linux</a></th>
- <th><a href="https://www.gnu.org/proprietary/malware-microsoft.en.html">Windows 10+</a></th>
- <th><a href="https://www.gnu.org/proprietary/malware-apple.en.html">macOS</a></th>
- <th>Comments</th>
-</tr>
-<tr>
- <td><a href="https://wiki.archlinux.org/index.php/Mpv">mpv</a></td>
- <td><a href="https://sourceforge.net/projects/mpv-player-windows/files">mpv</a></td>
- <td><a href="https://mpv.io/installation/">mpv</a></td>
- <td>v0.32.0 or newer.</td>
-</tr>
-<tr>
- <td><a href="https://wiki.archlinux.org/index.php/Anki">Anki</a></td>
- <td colspan="2" align="center"><a href="https://apps.ankiweb.net/">Anki</a></td>
- <td></td>
-</tr>
-<tr>
- <td colspan="3" align="center"><a href="https://ankiweb.net/shared/info/2055492159">AnkiConnect</a></td>
- <td>Install from AnkiWeb.</td>
-</tr>
-<tr>
- <td><a href="https://www.archlinux.org/packages/core/x86_64/curl/">curl</a></td>
- <td colspan="2" align="center"><a href="https://curl.haxx.se/">curl</a></td>
- <td>Installed by default on all platforms except Windows 7.</td>
-</tr>
-<tr>
- <td><a href="https://www.archlinux.org/packages/extra/x86_64/xclip/">xclip</a> or <a href="https://archlinux.org/packages/extra/x86_64/wl-clipboard">wl-copy</a></td>
- <td></td>
- <td>pbcopy</td>
- <td>To copy subtitle text to clipboard.</td>
-</tr>
-</table>
-
-Install all dependencies at once (on [Arch-based](https://www.parabola.nu/)
-[distros](https://www.gnu.org/distros/free-distros.en.html)):
-
-```
-$ sudo pacman -Syu mpv anki curl xclip --needed
-```
-
-## Prerequisites
-
-* A guide on how to set up Anki can be found [on our site](https://tatsumoto.neocities.org/blog/setting-up-anki.html).
-* If you're on a [Windows](https://www.fsf.org/windows) or a [Windows-like](https://reactos.org/) machine,
- a mpv build by `shinchiro` is recommended.
-* **macOS** users are advised to use [homebrew](https://brew.sh/) or manually add `mpv` to `PATH`.
-* Note that it is not recommended to use FlatPak or similar containers.
- If you still want to, [read this](howto/flatpak.md).
-* Make sure that your build of mpv supports encoding of audio and images.
- This shell command can be used to test it.
-
- ```
- $ mpv 'test_video.mkv' --loop-file=no --frames=1 -o='test_image.jpg'
- ```
-
- If the command fails, find a compatible build on the [mpv website](https://mpv.io/installation/)
- or instead install FFmpeg and [enable FFmpeg support](#configuration) in `mpvacious`'s config file.
-* Most problems with adding audio or images to Anki cards can be fixed
- by installing FFmpeg and enabling it settings.
-
-## Installation
-
-There are multiple ways you can install `mpvacious`.
-I recommend installing with `git` so that you can easily update on demand.
-
-`mpvacious` is a user-script for mpv,
-so it has to be installed in the directory `mpv` reads its user-scripts from.
-
-| OS | Location |
-|-----------|--------------------------------------------------|
-| GNU/Linux | `~/.config/mpv/scripts/` |
-| Windows | `C:/Users/Username/AppData/Roaming/mpv/scripts/` |
-
-Windows is not recommended,
-but we acknowledge that some people haven't switched to GNU/Linux yet.
-
-### Using git
-
-Clone the repo to the `scripts` directory.
-
-```
-mkdir -p ~/.config/mpv/scripts/
-git clone 'https://github.com/Ajatt-Tools/mpvacious.git' ~/.config/mpv/scripts/subs2srs
-```
-
-To update, run the following command.
-
-```
-cd ~/.config/mpv/scripts/subs2srs && git pull
-```
-
-### From the AUR
-
-`mpvacious` can be installed with the [mpv-mpvacious](https://aur.archlinux.org/packages/mpv-mpvacious/) package.
-
-### Manually
-
-This way is not recommended because it's easy to make a mistake during the process
-and end up with a broken install.
-
-Download
-[the repository](https://github.com/Ajatt-Tools/mpvacious/archive/refs/heads/master.zip)
-or
-[the latest release](https://github.com/Ajatt-Tools/mpvacious/releases)
-and extract the folder containing
-[subs2srs.lua](https://raw.githubusercontent.com/Ajatt-Tools/mpvacious/master/subs2srs.lua)
-to your [mpv scripts](https://github.com/mpv-player/mpv/wiki/User-Scripts) directory.
-
-<details>
-
-<summary>Expected directory tree</summary>
-
-```
-~/.config/mpv/scripts
-|-- other script 1
-|-- other script 2
-|-- subs2srs
-| |-- main.lua
-| |-- subs2srs.lua
-| `-- other files
-`-- other script 3
-```
-
-</details>
-
-<details>
-
-<summary>A note for mpv v0.32 and older</summary>
-
-Older versions of `mpv` don't know how to handle user-scripts in subdirectories.
-You need to tell mpv where to look for `mpvacious`.
-
-Open or create `~/.config/mpv/scripts/modules.lua` and add these lines:
-```
-local mpv_scripts_dir_path = os.getenv("HOME") .. "/.config/mpv/scripts/"
-package.path = package.path .. ';' .. os.getenv("HOME") .. '/.config/mpv/scripts/subs2srs/?.lua'
-function load(relative_path) dofile(mpv_scripts_dir_path .. relative_path) end
-load("subs2srs/subs2srs.lua")
-```
-
-</details>
-
-**Note:** in [Celluloid](https://www.archlinux.org/packages/community/x86_64/celluloid/)
-user scripts are installed in `/.config/celluloid/scripts/`.
-When following the instructions above, replace `.config/mpv` with `.config/celluloid`
-and optionally `subs2srs` with the name of the folder mpvacious is cloned into.
-
-## Configuration
-
-The config file should be created by the user, if needed.
-
-| OS | Config location |
-|--------------------|-------------------------------------------------------------------|
-| GNU/Linux | `~/.config/mpv/script-opts/subs2srs.conf` |
-| Windows | `C:/Users/Username/AppData/Roaming/mpv/script-opts/subs2srs.conf` |
-| Windows (portable) | `mpv.exeフォルダ/portable_config/script-opts/subs2srs.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 `=`.
-
-<p align="center">
- <a href="https://github.com/Ajatt-Tools/mpvacious/blob/master/.github/RELEASE/subs2srs.conf">Example configuration file</a>
-</p>
-
-If the first field is empty, it will be set contain the string `[empty]`.
-Otherwise, Anki won't allow mpvacious to add new notes.
-This won't happen if the sentence field is first in the note type settings.
-
-**Tip**: Try [our official note type](https://ankiweb.net/shared/info/1557722832)
-if you don't want to configure note fields yourself.
-Alternatively, we have a collection of user-created note types, which you can browse
-[here](https://github.com/Ajatt-Tools/AnkiNoteTypes).
-
-If you are having problems playing media files on older mobile devices,
-set `audio_format` to `mp3` and/or `snapshot_format` to `jpg`.
-Otherwise, I recommend sticking with `opus` for audio,
-and `avif` or `webp` for images,
-as they greatly reduce the size of the generated files.
-
-If you still use AnkiMobile (the [proprietary](https://www.gnu.org/proprietary/) Anki app),
-set `opus_container` to `m4a` or `webm`. I'll allow iOS to play Opus files, while still maintaining
-compatibility with non-Apple devices. For really old iOS devices, set `opus_container` to
-[`caf`](https://en.wikipedia.org/wiki/Core_Audio_Format). CAF plays only on Anki Desktop,
-AnkiWeb in Safari and AnkiMobile, and is really not recommended. (Please note that
-[Lockdown Mode](https://support.apple.com/en-us/105120) completely disables Opus and AVIF support,
-though you may try to add an exception for AnkiMobile.)
-
-If no matter what mpvacious fails to create audio clips and/or snapshots,
-change `use_ffmpeg` to `yes`.
-By using ffmpeg instead of the encoder built in mpv you can work around most encoder issues.
-You need to have ffmpeg installed for this to work.
-
-### Key bindings
-
-The user may change some global key bindings, though this step is not necessary.
-See [Usage](#usage) for the explanation of what they do.
-
-| OS | Config location |
-|-----------|----------------------------------------------------|
-| GNU/Linux | `~/.config/mpv/input.conf` |
-| Windows | `C:/Users/Username/AppData/Roaming/mpv/input.conf` |
-
-Default bindings:
-
-```
-a script-binding mpvacious-menu-open
-
-Ctrl+g script-binding mpvacious-animated-snapshot-toggle
-
-Ctrl+n script-binding mpvacious-export-note
-
-Ctrl+m script-binding mpvacious-update-last-note
-Ctrl+M script-binding mpvacious-overwrite-last-note
-
-g script-binding mpvacious-quick-card-menu-open
-Alt+g script-binding mpvacious-quick-card-sel-menu-open
-
-Ctrl+c script-binding mpvacious-copy-primary-sub-to-clipboard
-Ctrl+C script-binding mpvacious-copy-secondary-sub-to-clipboard
-Ctrl+t script-binding mpvacious-autocopy-toggle
-
-H script-binding mpvacious-sub-seek-back
-L script-binding mpvacious-sub-seek-forward
-
-Alt+h script-binding mpvacious-sub-seek-back-pause
-Alt+l script-binding mpvacious-sub-seek-forward-pause
-
-Ctrl+h script-binding mpvacious-sub-rewind
-Ctrl+H script-binding mpvacious-sub-replay
-Ctrl+L script-binding mpvacious-sub-play-up-to-next
-
-Ctrl+v script-binding mpvacious-secondary-sid-toggle
-Ctrl+k script-binding mpvacious-secondary-sid-prev
-Ctrl+j script-binding mpvacious-secondary-sid-next
-```
-
-**Note:** A capital letter means that you need to press Shift in order to activate the corresponding binding.
-For example, <kbd>Ctrl+M</kbd> actually means <kbd>Ctrl+Shift+m</kbd>.
-mpv accepts both variants in `input.conf`.
-
-## Usage
-
-* [Create a card](howto/create_card.md)
-* [Quick card creation](howto/create_quick_card.md)
-* [Open the "Add" dialog](howto/add_dialog.md)
-* [Usage with Rikaitan](howto/yomichan.md)
-* [Usage with GoldenDict](howto/goldendict.md)
-
-### Global bindings
-
-**Menu:**
-
-* <kbd>a</kbd> - Open `advanced menu`.
-
-**Enable\Disable animation:**
-
-* <kbd>Ctrl+g</kbd> - If animation is enabled, animated snapshots will be generated instead of static images.
- Animated snapshot are like GIFs (just in a different format)
- and will capture the video from the start to the end times selected.
-
-**Make a card:**
-
-* <kbd>Ctrl+n</kbd> - Export a card with the currently visible subtitle line on the front.
-Use this when your subs are well-timed,
-and the target sentence doesn't span multiple subs.
-
-**Quick card creation:**
-
-* <kbd>g</kbd> - Quick card creation menu.
-* <kbd>Alt+g</kbd> - Quick card creation, card selection menu.
-
-**Update the last card:**
-
-* <kbd>Ctrl+m</kbd> - Append to the media fields of the newly added Anki card.
-* <kbd>Ctrl+Shift+m</kbd> - Overwrite media fields of the newly added Anki card.
-
-**Clipboard:**
-
-* <kbd>Ctrl+c</kbd> - Copy current subtitle string to the system clipboard.
-* <kbd>Ctrl+t</kbd> - Toggle automatic copying of subtitles to the clipboard.
-
-**Seeking:**
-
-* <kbd>Shift+h</kbd> and <kbd>Shift+l</kbd> - Seek to the previous or the next subtitle.
-* <kbd>Alt+h</kbd> and <kbd>Alt+l</kbd> - Seek to the previous, or the next subtitle, and pause.
-* <kbd>Ctrl+h</kbd> - Seek to the start of the currently visible subtitle. Use it if you missed something.
-* <kbd>Ctrl+Shift+h</kbd> - Replay current subtitle line, and pause.
-* <kbd>Ctrl+Shift+l</kbd> - Play until the end of the next subtitle, and pause. Useful for beginners who need
- to look up words in each and every dialogue line.
-
-**Secondary subtitles:**
-
-* <kbd>Ctrl+v</kbd> - Toggle visibility.
-* <kbd>Ctrl+k</kbd> - Switch to the previous subtitle if it's not already selected.
-* <kbd>Ctrl+j</kbd> - Switch to the next subtitle if it's not already selected.
-
-### Menu options
-
-Advanced menu has the following options:
-
-* <kbd>f</kbd> - Increment number of cards to update. Only affects note updating, including quick card creation. The number of cards to update is reset to 1 upon updating a note.
-* <kbd>shift+f</kbd> - Decrement number of cards to update.
-
-* <kbd>c</kbd> - Interactive subtitle selection.
- The range of the currently displayed subtitle line is selected. The selection then grows both ways based on the following displayed lines.
- It does nothing if there are no subs on screen.
-
-* <kbd>shift+s</kbd> - Set the start time to the current sub. The selection then grows forward based on the following displayed lines.
- The default selection spans across the range of the currently displayed subtitle line.
-* <kbd>shift+e</kbd> - Set the end time to the current sub. The selection then grows backward based on the following displayed lines.
- The default selection spans across the range of the currently displayed subtitle line.
-
-Then seek with <kbd>Shift+h</kbd> and <kbd>Shift+l</kbd> to the previous/next line that you want to add.
-Press <kbd>n</kbd> to make the card.
-
-* <kbd>r</kbd> - Forget all previously saved timings and associated dialogs.
-
-* <kbd>z</kbd> and <kbd>Shift+z</kbd> - Adjust subtitle delay.
-
-If above fails, you have to manually set timings.
-* <kbd>s</kbd> - Set the start time. The selection then grows forward based on the following displayed lines.
-The default selection spans across the selected start point and the end of the subtitle line.
-* <kbd>e</kbd> - Set the end time. The selection then grows backward based on the following displayed lines.
-The default selection spans across the selected end point and the start of the subtitle line.
-
-Then, as earlier, press <kbd>n</kbd> to make the card.
-
-**Tip**: change playback speed by pressing <kbd>[</kbd> and <kbd>]</kbd>
-to precisely mark start and end of the phrase.
-
-### My subtitles are not in sync
-
-If subs are badly timed, first, you could try to re-time them.
-Read [Retiming subtitles](https://tatsumoto.neocities.org/blog/retiming-subtitles).
-Or shift timings using key bindings provided by mpv (usually <kbd>z</kbd> and <kbd>Shift+z</kbd>).
-
-### Example sentence card
-
-With the addon you can make cards like this in just a few seconds.
-
-![card-example](https://user-images.githubusercontent.com/69171671/92900057-e102d480-f40e-11ea-8cfc-b00848ca66ff.png)
-
-### Audio cards
-
-It is possible to make a card with just audio, and a picture
-when subtitles for the show you are watching aren't available, for example.
-mpv by default allows you to do a `1` second exact seek by pressing <kbd>Shift+LEFT</kbd> and <kbd>Shift+RIGHT</kbd>.
-Open the mpvacious menu by pressing <kbd>a</kbd>, seek to the position you need, and set the timings.
-Then press <kbd>g</kbd> to invoke the `Add Cards` dialog.
-Here's a [video demonstration](https://redirect.invidious.io/watch?v=BXhyckdHPGE).
-
-If the show is hard-subbed, you can use
-[transformers-ocr](https://tatsumoto.neocities.org/blog/mining-from-manga.html)
-to recognize and add text to the card.
-
-### Secondary subtitles
-
-If you want to add a translation to your cards, and you have the subtitles in that language,
-you can add them as secondary subtitles if you run `mpv` with `--secondary-sid=<sid>` parameter,
-`sid` being the track identifier for the subtitle.
-
-You also need to specify `secondary_field` in the [config file](#Configuration)
-if it is different from the default.
-
-If you want to load secondary subtitles **automatically**, don't modify the run parameters
-and instead set the desired languages in the config file (`secondary_sub_lang` option).
-
-Secondary subtitles will be visible when hovering over the top part of the `mpv` window.
-
-https://user-images.githubusercontent.com/69171671/188492261-909ba3e8-b82c-493f-88cf-0ec953dfcfe1.mp4
-
-By pressing <kbd>Ctrl</kbd>+<kbd>v</kbd> you can control secondary sid visibility without using the mouse.
-
-### Other tools
-
-If you don't like the default Rikaitan Search tool, try:
-
-* Clipboard Inserter browser add-on
-([chrome](https://chrome.google.com/webstore/detail/clipboard-inserter/deahejllghicakhplliloeheabddjajm))
-([firefox](https://addons.mozilla.org/ja/firefox/addon/clipboard-inserter/))
-* A html page ([1](https://pastebin.com/zDY6s3NK)) ([2](https://pastebin.com/hZ4sawL4))
-to paste the contents of your clipboard to
-
-You can use any html page as long as it has \<body\>\</body\> in it.
-
-### Additional mpv key bindings
-
-I recommend adding these lines to your [input.conf](#key-bindings) for smoother experience.
-```
-# vim-like seeking
-l seek 5
-h seek -5
-j seek -60
-k seek 60
-
-# Cycle between subtitle files
-K cycle sub
-J cycle sub down
-
-# Add/subtract 50 ms delay from subs
-Z add sub-delay +0.05
-z add sub-delay -0.05
-
-# Adjust timing to previous/next subtitle
-X sub-step 1
-x sub-step -1
-```
-
-## Profiles
-
-Mpvacious supports config profiles.
-To make use of them, create a new config file called `subs2srs_profiles.conf`
-in the same folder as your [subs2srs.conf](#Configuration).
-Inside the file, define available profile names (without `.conf`) and the name of the active profile:
-
-```
-profiles=subs2srs,english,german
-active=subs2srs
-```
-
-In the example above, I have three profiles.
-The first one is the default,
-the second one is for learning English,
-the third one is for learning German.
-
-Then in the same folder create config files for each of the defined profiles.
-For example, below is the contents of my `english.conf` file:
-
-```
-deck_name=English sentence mining
-model_name=General
-sentence_field=Question
-audio_field=Audio
-image_field=Extra
-```
-
-You don't have to redefine all settings in the new profile.
-Specify only the ones you want to be different from the default.
-
-To cycle profiles, open the advanced menu by pressing <kbd>a</kbd> and then press <kbd>p</kbd>.
-At any time you can see what profile is active in the menu's status bar.
-
-## Hacking
-
-If you want to modify this script
-or make an entirely new one from scratch,
-these links may help.
-
-* https://mpv.io/manual/master/#lua-scripting
-* https://github.com/mpv-player/mpv/blob/master/player/lua/defaults.lua
-* https://github.com/SenneH/mpv2anki
-* https://github.com/kelciour/mpv-scripts/blob/master/subs2srs.lua
-* https://pastebin.com/M2gBksHT
-* https://pastebin.com/NBudhMUk
-* https://pastebin.com/W5YV1A9q
-* https://github.com/ayuryshev/subs2srs
-* https://github.com/erjiang/subs2srs
diff --git a/config/mpv/scripts/subs2srsa/ankiconnect.lua b/config/mpv/scripts/subs2srsa/ankiconnect.lua
deleted file mode 100644
index f9c87d8..0000000
--- a/config/mpv/scripts/subs2srsa/ankiconnect.lua
+++ /dev/null
@@ -1,241 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-AnkiConnect requests
-]]
-
-local utils = require('mp.utils')
-local msg = require('mp.msg')
-local h = require('helpers')
-local self = {}
-
-self.execute = function(request, completion_fn)
- -- utils.format_json returns a string
- -- On error, request_json will contain "null", not nil.
- local request_json, error = utils.format_json(request)
-
- if error ~= nil or request_json == "null" then
- return completion_fn and completion_fn()
- else
- return self.platform.curl_request(self.config.ankiconnect_url, request_json, completion_fn)
- end
-end
-
-self.parse_result = function(curl_output)
- -- there are two values that we actually care about: result and error
- -- but we need to crawl inside to get them.
-
- if curl_output == nil then
- return nil, "Failed to format json or no args passed"
- end
-
- if curl_output.status ~= 0 then
- return nil, "Ankiconnect isn't running"
- end
-
- local stdout_json = utils.parse_json(curl_output.stdout)
-
- if stdout_json == nil then
- return nil, "Fatal error from Ankiconnect"
- end
-
- if stdout_json.error ~= nil then
- return nil, tostring(stdout_json.error)
- end
-
- return stdout_json.result, nil
-end
-
-self.get_media_dir_path = function()
- -- Ask AnkiConnect where to store media files.
- -- If AnkiConnect isn't running, returns nil.
-
- local ret = self.execute({
- action = "getMediaDirPath",
- version = 6,
- })
- local dir_path, error = self.parse_result(ret)
- if not error then
- return dir_path
- else
- msg.error(string.format("Couldn't retrieve path to collection.media folder: %s", error))
- return nil
- end
-end
-
-self.create_deck = function(deck_name)
- local args = {
- action = "changeDeck",
- version = 6,
- params = {
- cards = {},
- deck = deck_name
- }
- }
- local result_notify = function(_, result, _)
- local _, error = self.parse_result(result)
- if not error then
- msg.info(string.format("Deck %s: check completed.", deck_name))
- else
- msg.warn(string.format("Deck %s: check failed. Reason: %s.", deck_name, error))
- end
- end
- self.execute(args, result_notify)
-end
-
-self.add_note = function(note_fields, tag, gui)
- local action = gui and 'guiAddCards' or 'addNote'
- local args = {
- action = action,
- version = 6,
- params = {
- note = {
- deckName = self.config.deck_name,
- modelName = self.config.model_name,
- fields = note_fields,
- options = {
- allowDuplicate = self.config.allow_duplicates,
- duplicateScope = "deck",
- },
- tags = h.is_empty(tag) and {} or { tag, },
- }
- }
- }
- local result_notify = function(_, result, _)
- local note_id, error = self.parse_result(result)
- if not error then
- h.notify(string.format("Note added. ID = %s.", note_id))
- else
- h.notify(string.format("Error: %s.", error), "error", 2)
- end
- end
- self.execute(args, result_notify)
-end
-
-self.get_last_note_ids = function(n_cards)
- local ret = self.execute {
- action = "findNotes",
- version = 6,
- params = {
- query = "added:1" -- find all notes added today
- }
- }
-
- local note_ids, _ = self.parse_result(ret)
-
- if not h.is_empty(note_ids) then
- return h.get_last_n_added_notes(note_ids, n_cards)
- else
- return {}
- end
-end
-
-self.get_note_fields = function(note_id)
- local ret = self.execute {
- action = "notesInfo",
- version = 6,
- params = {
- notes = { note_id }
- }
- }
-
- local result, error = self.parse_result(ret)
-
- if error == nil then
- result = result[1].fields
- for key, value in pairs(result) do
- result[key] = value.value
- end
- return result
- else
- return nil
- end
-end
-
-self.get_first_field = function(model_name)
- local ret = self.execute {
- action = "findModelsByName",
- version = 6,
- params = {
- modelNames = { model_name }
- }
- }
-
- local result, error = self.parse_result(ret)
-
- if error == nil then
- for _, field in pairs(result[1].flds) do
- if field.ord == 0 then
- return field.name
- end
- end
- else
- msg.error(string.format("Couldn't retrieve the first field's name of note type %s: %s", model_name, error))
- return nil
- end
-end
-
-self.gui_browse = function(query)
- if not self.config.disable_gui_browse then
- self.execute {
- action = 'guiBrowse',
- version = 6,
- params = {
- query = query
- }
- }
- end
-end
-
-self.add_tag = function(note_id, tag)
- if not h.is_empty(tag) then
- self.execute {
- action = 'addTags',
- version = 6,
- params = {
- notes = { note_id },
- tags = tag
- }
- }
- end
-end
-
-self.append_media = function(note_id, fields, create_media_fn, tag)
- -- AnkiConnect will fail to update the note if it's selected in the Anki Browser.
- -- https://github.com/FooSoft/anki-connect/issues/82
- -- Switch focus from the current note to avoid it.
- self.gui_browse("nid:1") -- impossible nid
-
- local args = {
- action = "updateNoteFields",
- version = 6,
- params = {
- note = {
- id = note_id,
- fields = fields,
- }
- }
- }
-
- local on_finish = function(_, result, _)
- local _, error = self.parse_result(result)
- if not error then
- create_media_fn()
- self.add_tag(note_id, tag)
- self.gui_browse(string.format("nid:%s", note_id)) -- select the updated note in the card browser
- h.notify(string.format("Note #%s updated.", note_id))
- else
- h.notify(string.format("Error: %s.", error), "error", 2)
- end
- end
-
- self.execute(args, on_finish)
-end
-
-self.init = function(config, platform)
- self.config = config
- self.platform = platform
-end
-
-return self
diff --git a/config/mpv/scripts/subs2srsa/cfg_mgr.lua b/config/mpv/scripts/subs2srsa/cfg_mgr.lua
deleted file mode 100644
index 076c0d9..0000000
--- a/config/mpv/scripts/subs2srsa/cfg_mgr.lua
+++ /dev/null
@@ -1,240 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Config management, validation, loading.
-]]
-
-local mp = require('mp')
-local mpopt = require('mp.options')
-local msg = require('mp.msg')
-local h = require('helpers')
-local utils = require('mp.utils')
-
-local min_side_px = 42
-local max_side_px = 640
-local default_height_px = 200
-
--- This constant should be used in place of width and/or height in the config file.
--- It tells the encoder to preserve aspect ratio when downscaling snapshots.
--- The user almost always wants to set either width or height to this value.
--- Note: If set to -1, encoding will fail with the "height/width not divisible by 2" error.
-local preserve_aspect_ratio = -2
-
-local self = {
- config = nil,
- profiles = nil,
- initial_config = {}
-}
-
-local default_profile_filename = 'subs2srs'
-local profiles_filename = 'subs2srs_profiles'
-
-local function set_file_extension_for_opus()
- -- Default to OGG, then change if an extension is supported.
- -- https://en.wikipedia.org/wiki/Core_Audio_Format
- self.config.audio_extension = '.ogg'
- for _, extension in ipairs({ 'opus', 'm4a', 'webm', 'caf' }) do
- if extension == self.config.opus_container then
- self.config.audio_extension = '.' .. self.config.opus_container
- break
- end
- end
-end
-
-local function set_audio_format()
- if self.config.audio_format == 'opus' then
- -- https://opus-codec.org/
- self.config.audio_codec = 'libopus'
- set_file_extension_for_opus()
- else
- self.config.audio_codec = 'libmp3lame'
- self.config.audio_extension = '.mp3'
- end
-end
-
-local function set_video_format()
- if self.config.snapshot_format == 'avif' then
- self.config.snapshot_extension = '.avif'
- self.config.snapshot_codec = 'libaom-av1'
- elseif self.config.snapshot_format == 'webp' then
- self.config.snapshot_extension = '.webp'
- self.config.snapshot_codec = 'libwebp'
- else
- self.config.snapshot_extension = '.jpg'
- self.config.snapshot_codec = 'mjpeg'
- end
-
- -- Animated webp images can only have .webp extension.
- -- The user has no choice on this. Same logic for avif.
- if self.config.animated_snapshot_format == 'avif' then
- self.config.animated_snapshot_extension = '.avif'
- self.config.animated_snapshot_codec = 'libaom-av1'
- else
- self.config.animated_snapshot_extension = '.webp'
- self.config.animated_snapshot_codec = 'libwebp'
- end
-end
-
-local function ensure_in_range(dimension)
- self.config[dimension] = self.config[dimension] < min_side_px and preserve_aspect_ratio or self.config[dimension]
- self.config[dimension] = self.config[dimension] > max_side_px and max_side_px or self.config[dimension]
-end
-
-local function conditionally_set_defaults(width, height, quality)
- if self.config[width] < 1 and self.config[height] < 1 then
- self.config[width] = preserve_aspect_ratio
- self.config[height] = default_height_px
- end
- if self.config[quality] < 0 or self.config[quality] > 100 then
- self.config[quality] = 15
- end
-end
-
-local function check_image_settings()
- ensure_in_range('snapshot_width')
- ensure_in_range('snapshot_height')
- conditionally_set_defaults('snapshot_width', 'snapshot_height', 'snapshot_quality')
-end
-
-local function ensure_correct_fps()
- if self.config.animated_snapshot_fps == nil or self.config.animated_snapshot_fps <= 0 or self.config.animated_snapshot_fps > 30 then
- self.config.animated_snapshot_fps = 10
- end
-end
-
-local function check_animated_snapshot_settings()
- ensure_in_range('animated_snapshot_width')
- ensure_in_range('animated_snapshot_height')
- conditionally_set_defaults('animated_snapshot_width', 'animated_snapshot_height', 'animated_snapshot_quality')
- ensure_correct_fps()
-end
-
-local function validate_config()
- set_audio_format()
- set_video_format()
- check_image_settings()
- check_animated_snapshot_settings()
-end
-
-local function remember_initial_config()
- if h.is_empty(self.initial_config) then
- for key, value in pairs(self.config) do
- self.initial_config[key] = value
- end
- else
- msg.fatal("Ignoring. Initial config has been read already.")
- end
-end
-
-local function restore_initial_config()
- for key, value in pairs(self.initial_config) do
- self.config[key] = value
- end
-end
-
-local function read_profile_list()
- mpopt.read_options(self.profiles, profiles_filename)
- msg.info("Read profile list. Defined profiles: " .. self.profiles.profiles)
-end
-
-local function read_profile(profile_name)
- mpopt.read_options(self.config, profile_name)
- msg.info("Read config file: " .. profile_name)
-end
-
-local function read_default_config()
- read_profile(default_profile_filename)
-end
-
-local function reload_from_disk()
- --- Loads default config file (subs2srs.conf), then overwrites it with current profile.
- if not h.is_empty(self.config) and not h.is_empty(self.profiles) then
- restore_initial_config()
- read_default_config()
- if self.profiles.active ~= default_profile_filename then
- read_profile(self.profiles.active)
- end
- validate_config()
- else
- msg.fatal("Attempt to load config when init hasn't been done.")
- end
-end
-
-local function next_profile()
- local first, next, new
- for profile in string.gmatch(self.profiles.profiles, '[^,]+') do
- if not first then
- first = profile
- end
- if profile == self.profiles.active then
- next = true
- elseif next then
- next = false
- new = profile
- end
- end
- if next == true or not new then
- new = first
- end
- self.profiles.active = new
- reload_from_disk()
-end
-
-local function create_config_file()
- local name = default_profile_filename
- -- ~/.config/mpv/scripts/ and the mpvacious dir
- local parent, child = utils.split_path(mp.get_script_directory())
- -- ~/.config/mpv/ and "scripts"
- parent, child = utils.split_path(parent:gsub("/$", ""))
- -- ~/.config/mpv/script-opts/subs2srs.conf
- local config_filepath = utils.join_path(utils.join_path(parent, "script-opts"), string.format('%s.conf', name))
- local example_config_filepath = utils.join_path(mp.get_script_directory(), ".github/RELEASE/subs2srs.conf")
-
- local file_info = utils.file_info(config_filepath)
- if file_info and file_info.is_file then
- print("config already exists")
- return
- end
-
- local handle = io.open(example_config_filepath, 'r')
- if handle == nil then
- return
- end
-
- local content = handle:read("*a")
- handle:close()
-
- handle = io.open(config_filepath, 'w')
- if handle == nil then
- h.notify(string.format("Couldn't open %s.", config_filepath), "error", 4)
- return
- end
-
- handle:write(string.format("# Written by %s on %s.\n", name, os.date()))
- handle:write(content)
- handle:close()
- h.notify("Settings saved.", "info", 2)
-end
-
-local function init(config_table, profiles_table)
- create_config_file()
- self.config, self.profiles = config_table, profiles_table
- -- 'subs2srs' is the main profile, it is always loaded. 'active profile' overrides it afterwards.
- -- initial state is saved to another table to maintain consistency when cycling through incomplete profiles.
- read_profile_list()
- read_default_config()
- remember_initial_config()
- if self.profiles.active ~= default_profile_filename then
- read_profile(self.profiles.active)
- end
- validate_config()
-end
-
-return {
- reload_from_disk = reload_from_disk,
- init = init,
- next_profile = next_profile,
- default_height_px = default_height_px,
- preserve_aspect_ratio = preserve_aspect_ratio,
-}
diff --git a/config/mpv/scripts/subs2srsa/encoder/codec_support.lua b/config/mpv/scripts/subs2srsa/encoder/codec_support.lua
deleted file mode 100644
index 26cd91a..0000000
--- a/config/mpv/scripts/subs2srsa/encoder/codec_support.lua
+++ /dev/null
@@ -1,40 +0,0 @@
---[[
-Copyright: Ajatt-Tools and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Check what codecs are supported by mpv.
-If a desired codec is not supported, set the "use_ffmpeg" config option to "yes".
-]]
-
-local mp = require('mp')
-local h = require('helpers')
-
-local ovc_help = h.subprocess { 'mpv', '--ovc=help' }
-local oac_help = h.subprocess { 'mpv', '--oac=help' }
-
-local function is_audio_supported(codec)
- return oac_help.status == 0 and oac_help.stdout:find('--oac=' .. codec, 1, true) ~= nil
-end
-
-local function is_image_supported(codec)
- return ovc_help.status == 0 and ovc_help.stdout:find('--ovc=' .. codec, 1, true) ~= nil
-end
-
-local inspection_result = {
- snapshot = {
- ['libaom-av1'] = is_image_supported('libaom-av1'),
- libwebp = is_image_supported('libwebp'),
- mjpeg = is_image_supported('mjpeg'),
- },
- audio = {
- libmp3lame = is_audio_supported('libmp3lame'),
- libopus = is_audio_supported('libopus'),
- },
-}
-for type, codecs in pairs(inspection_result) do
- for codec, supported in pairs(codecs) do
- mp.msg.info(string.format("mpv supports %s codec %s: %s", type, codec, tostring(supported)))
- end
-end
-
-return inspection_result
diff --git a/config/mpv/scripts/subs2srsa/encoder/encoder.lua b/config/mpv/scripts/subs2srsa/encoder/encoder.lua
deleted file mode 100644
index b05c3db..0000000
--- a/config/mpv/scripts/subs2srsa/encoder/encoder.lua
+++ /dev/null
@@ -1,729 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Encoder creates audio clips and snapshots, both animated and static.
-]]
-
-local mp = require('mp')
-local utils = require('mp.utils')
-local h = require('helpers')
-local filename_factory = require('utils.filename_factory')
-local msg = require('mp.msg')
-
---Contains the state of the module
-local self = {
- snapshot = {},
- audio = {},
- config = nil,
- store_fn = nil,
- platform = nil,
- encoder = nil,
- output_dir_path = nil,
-}
-
-------------------------------------------------------------
--- utility functions
-
-local function pad_timings(padding, start_time, end_time)
- local video_duration = mp.get_property_number('duration')
- start_time = start_time - padding
- end_time = end_time + padding
-
- if start_time < 0 then
- start_time = 0
- end
-
- if end_time > video_duration then
- end_time = video_duration
- end
-
- return start_time, end_time
-end
-
-local function alt_path_dirs()
- return {
- '/opt/homebrew/bin',
- '/usr/local/bin',
- utils.join_path(os.getenv("HOME") or "~", '.local/bin'),
- }
-end
-
-local function find_exec(name)
- local path, info
- for _, alt_dir in pairs(alt_path_dirs()) do
- path = utils.join_path(alt_dir, name)
- info = utils.file_info(path)
- if info and info.is_file then
- return path
- end
- end
- return name
-end
-
-local function toms(timestamp)
- --- Trim timestamp down to milliseconds.
- return string.format("%.3f", timestamp)
-end
-
-local function fit_quality_percentage_to_range(quality, worst_val, best_val)
- local scaled = worst_val + (best_val - worst_val) * quality / 100
- -- Round to the nearest integer that's better in quality.
- if worst_val > best_val then
- return math.floor(scaled)
- end
- return math.ceil(scaled)
-end
-
-local function quality_to_crf_avif(quality_value)
- -- Quality is from 0 to 100. For avif images CRF is from 0 to 63 and reversed.
- local worst_avif_crf = 63
- local best_avif_crf = 0
- return fit_quality_percentage_to_range(quality_value, worst_avif_crf, best_avif_crf)
-end
-
-local function quality_to_jpeg_qscale(quality_value)
- local worst_jpeg_quality = 31
- local best_jpeg_quality = 2
- return fit_quality_percentage_to_range(quality_value, worst_jpeg_quality, best_jpeg_quality)
-end
-
-------------------------------------------------------------
--- ffmpeg encoder
-
-local ffmpeg = {}
-
-ffmpeg.exec = find_exec("ffmpeg")
-
-ffmpeg.prepend = function(...)
- return {
- ffmpeg.exec, "-hide_banner", "-nostdin", "-y", "-loglevel", "quiet", "-sn",
- ...,
- }
-end
-
-local function make_scale_filter(algorithm, width, height)
- -- algorithm is either "sinc" or "lanczos"
- -- Static image scaling uses "sinc", which is the best downscaling algorithm: https://stackoverflow.com/a/6171860
- -- Animated images use Lanczos, which is faster.
- return string.format(
- "scale='min(%d,iw)':'min(%d,ih)':flags=%s+accurate_rnd",
- width, height, algorithm
- )
-end
-
-local function static_scale_filter()
- return make_scale_filter('sinc', self.config.snapshot_width, self.config.snapshot_height)
-end
-
-local function animated_scale_filter()
- return make_scale_filter(
- 'lanczos',
- self.config.animated_snapshot_width,
- self.config.animated_snapshot_height
- )
-end
-
-ffmpeg.make_static_snapshot_args = function(source_path, output_path, timestamp)
- local encoder_args
- if self.config.snapshot_format == 'avif' then
- encoder_args = {
- '-c:v', 'libaom-av1',
- -- cpu-used < 6 can take a lot of time to encode.
- '-cpu-used', '6',
- -- Avif quality can be controlled with crf.
- '-crf', tostring(quality_to_crf_avif(self.config.snapshot_quality)),
- '-still-picture', '1',
- }
- elseif self.config.snapshot_format == 'webp' then
- encoder_args = {
- '-c:v', 'libwebp',
- '-compression_level', '6',
- '-quality', tostring(self.config.snapshot_quality),
- }
- else
- encoder_args = {
- '-c:v', 'mjpeg',
- '-q:v', tostring(quality_to_jpeg_qscale(self.config.snapshot_quality)),
- }
- end
-
- local args = ffmpeg.prepend(
- '-an',
- '-ss', toms(timestamp),
- '-i', source_path,
- '-map_metadata', '-1',
- '-vf', static_scale_filter(),
- '-frames:v', '1',
- h.unpack(encoder_args)
- )
- table.insert(args, output_path)
- return args
-end
-
-ffmpeg.make_animated_snapshot_args = function(source_path, output_path, start_timestamp, end_timestamp)
- local encoder_args
- if self.config.animated_snapshot_format == 'avif' then
- encoder_args = {
- '-c:v', 'libaom-av1',
- -- cpu-used < 6 can take a lot of time to encode.
- '-cpu-used', '6',
- -- Avif quality can be controlled with crf.
- '-crf', tostring(quality_to_crf_avif(self.config.animated_snapshot_quality)),
- }
- else
- -- Documentation: https://www.ffmpeg.org/ffmpeg-all.html#libwebp
- encoder_args = {
- '-c:v', 'libwebp',
- '-compression_level', '6',
- '-quality', tostring(self.config.animated_snapshot_quality),
- }
- end
-
- local args = ffmpeg.prepend(
- '-an',
- '-ss', toms(start_timestamp),
- '-to', toms(end_timestamp),
- '-i', source_path,
- '-map_metadata', '-1',
- '-loop', '0',
- '-vf', string.format(
- 'fps=%d,%s',
- self.config.animated_snapshot_fps,
- animated_scale_filter()
- ),
- h.unpack(encoder_args)
- )
- table.insert(args, output_path)
- return args
-end
-
-local function make_loudnorm_targets()
- return string.format(
- 'loudnorm=I=%s:LRA=%s:TP=%s:dual_mono=true',
- self.config.loudnorm_target,
- self.config.loudnorm_range,
- self.config.loudnorm_peak
- )
-end
-
-local function parse_loudnorm(loudnorm_targets, json_extractor, loudnorm_consumer)
- local function warn()
- msg.warn('Failed to measure loudnorm stats, falling back on dynamic loudnorm.')
- end
-
- return function(success, result)
- local json
- if success and result.status == 0 then
- json = json_extractor(result.stdout, result.stderr)
- end
-
- if json == nil then
- warn()
- loudnorm_consumer(loudnorm_targets)
- return
- end
-
- local loudnorm_args = { loudnorm_targets }
- local function add_arg(name, val)
- -- loudnorm sometimes fails to gather stats for extremely short inputs.
- -- Simply omit the stat to fall back on dynamic loudnorm.
- if val ~= '-inf' and val ~= 'inf' then
- table.insert(loudnorm_args, string.format('%s=%s', name, val))
- else
- warn()
- end
- end
-
- local stats = utils.parse_json(json)
- add_arg('measured_I', stats.input_i)
- add_arg('measured_LRA', stats.input_lra)
- add_arg('measured_TP', stats.input_tp)
- add_arg('measured_thresh', stats.input_thresh)
- add_arg('offset', stats.target_offset)
-
- loudnorm_consumer(table.concat(loudnorm_args, ':'))
- end
-end
-
-local function add_filter(filters, filter)
- if #filters == 0 then
- filters = filter
- else
- filters = string.format('%s,%s', filters, filter)
- end
-end
-
-local function separate_filters(filters, new_args, args)
- -- Would've strongly preferred
- -- if args[i] == '-af' or args[i] == '-filter:a' then
- -- i = i + 1
- -- add_filter(args[i])
- -- but https://lua.org/manual/5.4/manual.html#3.3.5 says that
- -- "You should not change the value of the control variable during the loop."
- local expect_filter = false
- for i = 1, #args do
- if args[i] == '-af' or args[i] == '-filter:a' then
- expect_filter = true
- else
- if expect_filter then
- add_filter(filters, args[i])
- else
- table.insert(new_args, args[i])
- end
- expect_filter = false
- end
- end
-end
-
-ffmpeg.append_user_audio_args = function(args)
- local new_args = {}
- local filters = ''
-
- separate_filters(filters, new_args, args)
- if self.config.tie_volumes then
- add_filter(filters, string.format("volume=%.1f", mp.get_property_native('volume') / 100.0))
- end
-
- local user_args = {}
- for arg in string.gmatch(self.config.ffmpeg_audio_args, "%S+") do
- table.insert(user_args, arg)
- end
- separate_filters(filters, new_args, user_args)
-
- if #filters > 0 then
- table.insert(new_args, '-af')
- table.insert(new_args, filters)
- end
- return new_args
-end
-
-ffmpeg.make_audio_args = function(
- source_path, output_path, start_timestamp, end_timestamp, args_consumer
-)
- local audio_track = h.get_active_track('audio')
- local audio_track_id = audio_track['ff-index']
-
- if audio_track and audio_track.external == true then
- source_path = audio_track['external-filename']
- audio_track_id = 'a'
- end
-
- local function make_ffargs(...)
- return ffmpeg.append_user_audio_args(
- ffmpeg.prepend(
- '-vn',
- '-ss', toms(start_timestamp),
- '-to', toms(end_timestamp),
- '-i', source_path,
- '-map_metadata', '-1',
- '-map_chapters', '-1',
- '-map', string.format("0:%s", tostring(audio_track_id)),
- '-ac', '1',
- ...
- )
- )
- end
-
- local function make_encoding_args(loudnorm_args)
- local encoder_args
- if self.config.audio_format == 'opus' then
- encoder_args = {
- '-c:a', 'libopus',
- '-application', 'voip',
- '-apply_phase_inv', '0', -- Improves mono audio.
- }
- if self.config.opus_container == 'm4a' then
- table.insert(encoder_args, '-f')
- table.insert(encoder_args, 'mp4')
- end
- else
- -- https://wiki.hydrogenaud.io/index.php?title=LAME#Recommended_encoder_settings:
- -- "For very low bitrates, up to 100kbps, ABR is most often the best solution."
- encoder_args = {
- '-c:a', 'libmp3lame',
- '-compression_level', '0',
- '-abr', '1',
- }
- end
-
- encoder_args = { '-b:a', tostring(self.config.audio_bitrate), h.unpack(encoder_args) }
- if loudnorm_args then
- table.insert(encoder_args, '-af')
- table.insert(encoder_args, loudnorm_args)
- end
- local args = make_ffargs(h.unpack(encoder_args))
- table.insert(args, output_path)
- args_consumer(args)
- end
-
- if not self.config.loudnorm then
- make_encoding_args(nil)
- return
- end
-
- local loudnorm_targets = make_loudnorm_targets()
- local args = make_ffargs(
- '-loglevel', 'info',
- '-af', loudnorm_targets .. ':print_format=json'
- )
- table.insert(args, '-f')
- table.insert(args, 'null')
- table.insert(args, '-')
- h.subprocess(
- args,
- parse_loudnorm(
- loudnorm_targets,
- function(stdout, stderr)
- local start, stop, json = string.find(stderr, '%[Parsed_loudnorm_0.-({.-})')
- return json
- end,
- make_encoding_args
- )
- )
-end
-
-------------------------------------------------------------
--- mpv encoder
-
-local mpv = { }
-
-mpv.exec = find_exec("mpv")
-
-mpv.prepend_common_args = function(source_path, ...)
- return {
- mpv.exec,
- source_path,
- '--no-config',
- '--loop-file=no',
- '--keep-open=no',
- '--no-sub',
- '--no-ocopy-metadata',
- ...,
- }
-end
-
-mpv.make_static_snapshot_args = function(source_path, output_path, timestamp)
- local encoder_args
- if self.config.snapshot_format == 'avif' then
- encoder_args = {
- '--ovc=libaom-av1',
- -- cpu-used < 6 can take a lot of time to encode.
- '--ovcopts-add=cpu-used=6',
- string.format('--ovcopts-add=crf=%d', quality_to_crf_avif(self.config.snapshot_quality)),
- '--ovcopts-add=still-picture=1',
- }
- elseif self.config.snapshot_format == 'webp' then
- encoder_args = {
- '--ovc=libwebp',
- '--ovcopts-add=compression_level=6',
- string.format('--ovcopts-add=quality=%d', self.config.snapshot_quality),
- }
- else
- encoder_args = {
- '--ovc=mjpeg',
- '--vf-add=scale=out_range=jpeg',
- string.format(
- '--ovcopts=global_quality=%d*QP2LAMBDA,flags=+qscale',
- quality_to_jpeg_qscale(self.config.snapshot_quality)
- ),
- }
- end
-
- return mpv.prepend_common_args(
- source_path,
- '--audio=no',
- '--frames=1',
- '--start=' .. toms(timestamp),
- string.format('--vf-add=lavfi=[%s]', static_scale_filter()),
- '-o=' .. output_path,
- h.unpack(encoder_args)
- )
-end
-
-mpv.make_animated_snapshot_args = function(source_path, output_path, start_timestamp, end_timestamp)
- local encoder_args
- if self.config.animated_snapshot_format == 'avif' then
- encoder_args = {
- '--ovc=libaom-av1',
- -- cpu-used < 6 can take a lot of time to encode.
- '--ovcopts-add=cpu-used=6',
- string.format('--ovcopts-add=crf=%d', quality_to_crf_avif(self.config.animated_snapshot_quality)),
- }
- else
- encoder_args = {
- '--ovc=libwebp',
- '--ovcopts-add=compression_level=6',
- string.format('--ovcopts-add=quality=%d', self.config.animated_snapshot_quality),
- }
- end
-
- return mpv.prepend_common_args(
- source_path,
- '--audio=no',
- '--start=' .. toms(start_timestamp),
- '--end=' .. toms(end_timestamp),
- '--ofopts-add=loop=0',
- string.format('--vf-add=fps=%d', self.config.animated_snapshot_fps),
- string.format('--vf-add=lavfi=[%s]', animated_scale_filter()),
- '-o=' .. output_path,
- h.unpack(encoder_args)
- )
-end
-
-mpv.make_audio_args = function(source_path, output_path,
- start_timestamp, end_timestamp, args_consumer)
- local audio_track = h.get_active_track('audio')
- local audio_track_id = mp.get_property("aid")
-
- if audio_track and audio_track.external == true then
- source_path = audio_track['external-filename']
- audio_track_id = 'auto'
- end
-
- local function make_mpvargs(...)
- local args = mpv.prepend_common_args(
- source_path,
- '--video=no',
- '--aid=' .. audio_track_id,
- '--audio-channels=mono',
- '--start=' .. toms(start_timestamp),
- '--end=' .. toms(end_timestamp),
- string.format(
- '--volume=%d',
- self.config.tie_volumes and mp.get_property('volume') or 100
- ),
- ...
- )
- for arg in string.gmatch(self.config.mpv_audio_args, "%S+") do
- table.insert(args, arg)
- end
- return args
- end
-
- local function make_encoding_args(loudnorm_args)
- local encoder_args
- if self.config.audio_format == 'opus' then
- encoder_args = {
- '--oac=libopus',
- '--oacopts-add=application=voip',
- '--oacopts-add=apply_phase_inv=0', -- Improves mono audio.
- }
- if self.config.opus_container == 'm4a' then
- table.insert(encoder_args, '--of=mp4')
- end
- else
- -- https://wiki.hydrogenaud.io/index.php?title=LAME#Recommended_encoder_settings:
- -- "For very low bitrates, up to 100kbps, ABR is most often the best solution."
- encoder_args = {
- '--oac=libmp3lame',
- '--oacopts-add=compression_level=0',
- '--oacopts-add=abr=1',
- }
- end
-
- local args = make_mpvargs(
- '--oacopts-add=b=' .. self.config.audio_bitrate,
- '-o=' .. output_path,
- h.unpack(encoder_args)
- )
- if loudnorm_args then
- table.insert(args, '--af-append=' .. loudnorm_args)
- end
- args_consumer(args)
- end
-
- if not self.config.loudnorm then
- make_encoding_args(nil)
- return
- end
-
- local loudnorm_targets = make_loudnorm_targets()
- h.subprocess(
- make_mpvargs(
- '-v',
- '--af-append=' .. loudnorm_targets .. ':print_format=json',
- '--ao=null',
- '--of=null'
- ),
- parse_loudnorm(
- loudnorm_targets,
- function(stdout, stderr)
- local start, stop, json = string.find(stdout, '%[ffmpeg%] ({.-})')
- if json then
- json = string.gsub(json, '%[ffmpeg%]', '')
- end
- return json
- end,
- make_encoding_args
- )
- )
-end
-
-------------------------------------------------------------
--- main interface
-
-local create_animated_snapshot = function(start_timestamp, end_timestamp, source_path, output_path, on_finish_fn)
- -- Creates the animated snapshot and then calls on_finish_fn
- local args = self.encoder.make_animated_snapshot_args(source_path, output_path, start_timestamp, end_timestamp)
- h.subprocess(args, on_finish_fn)
-end
-
-local create_static_snapshot = function(timestamp, source_path, output_path, on_finish_fn)
- -- Creates a static snapshot, in other words an image, and then calls on_finish_fn
- if not self.config.screenshot then
- local args = self.encoder.make_static_snapshot_args(source_path, output_path, timestamp)
- h.subprocess(args, on_finish_fn)
- else
- local args = { 'screenshot-to-file', output_path, 'video', }
- mp.command_native_async(args, on_finish_fn)
- end
-
-end
-
-local report_creation_result = function(file_path)
- return function(success, result)
- -- result is nil on success for screenshot-to-file.
- if success and (result == nil or result.status == 0) and h.file_exists(file_path) then
- msg.info(string.format("Created file: %s", file_path))
- return true
- else
- msg.error(string.format("Couldn't create file: %s", file_path))
- return false
- end
- end
-end
-
-local create_snapshot = function(start_timestamp, end_timestamp, current_timestamp, filename)
- if h.is_empty(self.output_dir_path) then
- return msg.error("Output directory wasn't provided. Image file will not be created.")
- end
-
- -- Calls the proper function depending on whether or not the snapshot should be animated
- if not h.is_empty(self.config.image_field) then
- local source_path = mp.get_property("path")
- local output_path = utils.join_path(self.output_dir_path, filename)
-
- local on_finish = report_creation_result(output_path)
- if self.config.animated_snapshot_enabled then
- create_animated_snapshot(start_timestamp, end_timestamp, source_path, output_path, on_finish)
- else
- create_static_snapshot(current_timestamp, source_path, output_path, on_finish)
- end
- else
- print("Snapshot will not be created.")
- end
-end
-
-local background_play = function(file_path, on_finish)
- return h.subprocess(
- { mpv.exec, '--audio-display=no', '--force-window=no', '--keep-open=no', '--really-quiet', file_path },
- on_finish
- )
-end
-
-local create_audio = function(start_timestamp, end_timestamp, filename, padding)
- if h.is_empty(self.output_dir_path) then
- return msg.error("Output directory wasn't provided. Audio file will not be created.")
- end
-
- if not h.is_empty(self.config.audio_field) then
- local source_path = mp.get_property("path")
- local output_path = utils.join_path(self.output_dir_path, filename)
-
- if padding > 0 then
- start_timestamp, end_timestamp = pad_timings(padding, start_timestamp, end_timestamp)
- end
-
- local function start_encoding(args)
- local on_finish = function(success, result)
- local conversion_check = report_creation_result(output_path)
- if conversion_check(success, result) and self.config.preview_audio then
- background_play(output_path, function()
- print("Played file: " .. output_path)
- end)
- end
- end
-
- h.subprocess(args, on_finish)
- end
-
- self.encoder.make_audio_args(
- source_path, output_path, start_timestamp, end_timestamp, start_encoding
- )
- else
- print("Audio will not be created.")
- end
-end
-
-local make_snapshot_filename = function(start_time, end_time, timestamp)
- -- Generate a filename for the snapshot, taking care of its extension and whether it's animated or static
- if self.config.animated_snapshot_enabled then
- return filename_factory.make_filename(start_time, end_time, self.config.animated_snapshot_extension)
- else
- return filename_factory.make_filename(timestamp, self.config.snapshot_extension)
- end
-end
-
-local make_audio_filename = function(start_time, end_time)
- -- Generates a filename for the audio
- return filename_factory.make_filename(start_time, end_time, self.config.audio_extension)
-end
-
-local toggle_animation = function()
- -- Toggles on and off animated snapshot generation at runtime. It is called whenever ctrl+g is pressed
- self.config.animated_snapshot_enabled = not self.config.animated_snapshot_enabled
- h.notify("Animation " .. (self.config.animated_snapshot_enabled and "enabled" or "disabled"), "info", 2)
-end
-
-local init = function(config)
- -- Sets the module to its preconfigured status
- self.config = config
- self.encoder = config.use_ffmpeg and ffmpeg or mpv
-end
-
-local set_output_dir = function(dir_path)
- -- Set directory where media files should be saved.
- -- This function is called every time a card is created or updated.
- self.output_dir_path = dir_path
-end
-
-local create_job = function(type, sub, audio_padding)
- local filename, run_async, current_timestamp
- if type == 'snapshot' and h.has_video_track() then
- current_timestamp = mp.get_property_number("time-pos", 0)
- filename = make_snapshot_filename(sub['start'], sub['end'], current_timestamp)
- run_async = function()
- create_snapshot(sub['start'], sub['end'], current_timestamp, filename)
- end
- elseif type == 'audioclip' and h.has_audio_track() then
- filename = make_audio_filename(sub['start'], sub['end'])
- run_async = function()
- create_audio(sub['start'], sub['end'], filename, audio_padding)
- end
- else
- run_async = function()
- print(type .. " will not be created.")
- end
- end
- return {
- filename = filename,
- run_async = run_async,
- }
-end
-
-return {
- init = init,
- set_output_dir = set_output_dir,
- snapshot = {
- create_job = function(sub)
- return create_job('snapshot', sub)
- end,
- toggle_animation = toggle_animation,
- },
- audio = {
- create_job = function(sub, padding)
- return create_job('audioclip', sub, padding)
- end,
- },
-}
diff --git a/config/mpv/scripts/subs2srsa/find_anki_col.sh b/config/mpv/scripts/subs2srsa/find_anki_col.sh
deleted file mode 100755
index a98199b..0000000
--- a/config/mpv/scripts/subs2srsa/find_anki_col.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-# Find full path to an opened Anki collection.
-
-readlink -f -- /proc/$(pgrep '^anki$')/fd/* | grep 'collection.anki2$'
diff --git a/config/mpv/scripts/subs2srsa/helpers.lua b/config/mpv/scripts/subs2srsa/helpers.lua
deleted file mode 100644
index e8f911e..0000000
--- a/config/mpv/scripts/subs2srsa/helpers.lua
+++ /dev/null
@@ -1,280 +0,0 @@
---[[
-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 msg = require('mp.msg')
-local utils = require('mp.utils')
-local this = {}
-
-this.unpack = unpack and unpack or table.unpack
-
-this.remove_all_spaces = function(str)
- return str:gsub('%s*', '')
-end
-
-this.table_get = function(table, key, default)
- if table[key] == nil then
- return default or 'nil'
- else
- return table[key]
- end
-end
-
-this.max_num = function(table)
- local max = table[1]
- for _, value in ipairs(table) do
- if value > max then
- max = value
- end
- end
- return max
-end
-
-this.get_last_n_added_notes = function(note_ids, n)
- table.sort(note_ids)
- return { this.unpack(note_ids, math.max(#note_ids - n + 1, 1), #note_ids) }
-end
-
-this.contains = function(table, element)
- for _, value in pairs(table) do
- if value == element then
- return true
- end
- end
- return false
-end
-
-this.minutes_ago = function(m)
- return (os.time() - 60 * m) * 1000
-end
-
-this.is_wayland = function()
- return os.getenv('WAYLAND_DISPLAY') ~= nil
-end
-
-this.is_win = function()
- return mp.get_property('options/vo-mmcss-profile') ~= nil
-end
-
-this.is_mac = function()
- return mp.get_property('options/macos-force-dedicated-gpu') ~= nil
-end
-
-local function map(tab, func)
- local t = {}
- for k, v in pairs(tab) do
- t[k] = func(v)
- end
- return t
-end
-
-local function args_as_str(args)
- return table.concat(map(args, function(str) return string.format("'%s'", str) end), " ")
-end
-
-this.subprocess = function(args, completion_fn, override_settings)
- -- if `completion_fn` is passed, the command is ran asynchronously,
- -- and upon completion, `completion_fn` is called to process the results.
- msg.info("Executing: " .. args_as_str(args))
- local command_native = type(completion_fn) == 'function' and mp.command_native_async or mp.command_native
- local command_table = {
- name = "subprocess",
- playback_only = false,
- capture_stdout = true,
- capture_stderr = true,
- args = args
- }
- if not this.is_empty(override_settings) then
- for k,v in pairs(override_settings) do
- command_table[k] = v
- end
- end
- return command_native(command_table, completion_fn)
-end
-
-this.subprocess_detached = function(args, completion_fn)
- local overwrite_settings = {
- detach=true,
- capture_stdout = false,
- capture_stderr = false,
- }
- return this.subprocess(args, completion_fn, overwrite_settings)
-end
-
-this.is_empty = function(var)
- return var == nil or var == '' or (type(var) == 'table' and next(var) == nil)
-end
-
-this.contains_non_latin_letters = function(str)
- return str:match("[^%c%p%s%w—]")
-end
-
-this.capitalize_first_letter = function(string)
- return string:gsub("^%l", string.upper)
-end
-
-this.remove_leading_trailing_spaces = function(str)
- return str:gsub('^%s*(.-)%s*$', '%1')
-end
-
-this.remove_leading_trailing_dashes = function(str)
- return str:gsub('^[%-_]*(.-)[%-_]*$', '%1')
-end
-
-this.remove_text_in_parentheses = function(str)
- -- Remove text like (泣き声) or (ドアの開く音)
- -- No deletion is performed if there's no text after the parentheses.
- -- Note: the modifier `-´ matches zero or more occurrences.
- -- However, instead of matching the longest sequence, it matches the shortest one.
- return str:gsub('(%b())(.)', '%2'):gsub('((.-))(.)', '%2')
-end
-
-this.remove_newlines = function(str)
- return str:gsub('[\n\r]+', ' ')
-end
-
-this.trim = function(str)
- str = this.remove_leading_trailing_spaces(str)
- str = this.remove_text_in_parentheses(str)
- str = this.remove_newlines(str)
- return str
-end
-
-this.escape_special_characters = (function()
- local entities = {
- ['&'] = '&amp;',
- ['"'] = '&quot;',
- ["'"] = '&apos;',
- ['<'] = '&lt;',
- ['>'] = '&gt;',
- }
- return function(s)
- return s:gsub('[&"\'<>]', entities)
- end
-end)()
-
-this.remove_extension = function(filename)
- return filename:gsub('%.%w+$', '')
-end
-
-this.remove_special_characters = function(str)
- return str:gsub('[%c%p%s]', ''):gsub(' ', '')
-end
-
-this.remove_text_in_brackets = function(str)
- return str:gsub('%b[]', ''):gsub('【.-】', '')
-end
-
-this.remove_filename_text_in_parentheses = function(str)
- return str:gsub('%b()', ''):gsub('(.-)', '')
-end
-
-this.remove_common_resolutions = function(str)
- -- Also removes empty leftover parentheses and brackets.
- return str:gsub("2160p", ""):gsub("1080p", ""):gsub("720p", ""):gsub("576p", ""):gsub("480p", ""):gsub("%(%)", ""):gsub("%[%]", "")
-end
-
-this.human_readable_time = function(seconds)
- if type(seconds) ~= 'number' or seconds < 0 then
- return 'empty'
- end
-
- local parts = {
- h = math.floor(seconds / 3600),
- m = math.floor(seconds / 60) % 60,
- s = math.floor(seconds % 60),
- 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.get_episode_number = function(filename)
- -- Reverses the filename to start the search from the end as the media title might contain similar numbers.
- local filename_reversed = filename:reverse()
-
- local ep_num_patterns = {
- "[%s_](%d?%d?%d)[pP]?[eE]", -- Starting with E or EP (case-insensitive). "Example Series S01E01 [94Z295D1]"
- "^(%d?%d?%d)[pP]?[eE]", -- Starting with E or EP (case-insensitive) at the end of filename. "Example Series S01E01"
- "%)(%d?%d?%d)%(", -- Surrounded by parentheses. "Example Series (12)"
- "%](%d?%d?%d)%[", -- Surrounded by brackets. "Example Series [01]"
- "%s(%d?%d?%d)%s", -- Surrounded by whitespace. "Example Series 124 [1080p 10-bit]"
- "_(%d?%d?%d)_", -- Surrounded by underscores. "Example_Series_04_1080p"
- "^(%d?%d?%d)[%s_]", -- Ending to the episode number. "Example Series 124"
- "(%d?%d?%d)%-edosipE", -- Prepended by "Episode-". "Example Episode-165"
- }
-
- local s, e, episode_num
- for _, pattern in pairs(ep_num_patterns) do
- s, e, episode_num = string.find(filename_reversed, pattern)
- if not this.is_empty(episode_num) then
- return #filename - e, #filename - s, episode_num:reverse()
- end
- end
-end
-
-this.notify = function(message, level, duration)
- level = level or 'info'
- duration = duration or 1
- msg[level](message)
- mp.osd_message(message, duration)
-end
-
-this.get_active_track = function(track_type)
- -- track_type == audio|sub
- for _, track in pairs(mp.get_property_native('track-list')) do
- if track.type == track_type and track.selected == true then
- return track
- end
- end
- return nil
-end
-
-this.has_video_track = function()
- return mp.get_property_native('vid') ~= false
-end
-
-this.has_audio_track = function()
- return mp.get_property_native('aid') ~= false
-end
-
-this.str_contains = function(s, pattern)
- return not this.is_empty(s) and string.find(string.lower(s), string.lower(pattern)) ~= nil
-end
-
-this.filter = function(arr, func)
- local filtered = {}
- for _, elem in ipairs(arr) do
- if func(elem) == true then
- table.insert(filtered, elem)
- end
- end
- return filtered
-end
-
-this.file_exists = function(filepath)
- if not this.is_empty(filepath) then
- local info = utils.file_info(filepath)
- if info and info.is_file and info.size > 0 then
- return true
- end
- end
- return false
-end
-
-this.get_loaded_tracks = function(track_type)
- --- Return all sub tracks, audio tracks, etc.
- return this.filter(mp.get_property_native('track-list'), function(track) return track.type == track_type end)
-end
-
-return this
diff --git a/config/mpv/scripts/subs2srsa/howto/add_dialog.md b/config/mpv/scripts/subs2srsa/howto/add_dialog.md
deleted file mode 100644
index 22093c6..0000000
--- a/config/mpv/scripts/subs2srsa/howto/add_dialog.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Open the "Add" dialog
-
-1) Open a video in `mpv`.
-1) Press <kbd>a</kbd> to open advanced menu.
-1) Optionally, press <kbd>c</kbd> and select the desired subtitle lines with the interactive selection.
-1) Press <kbd>g</kbd> to open the Add dialog in Anki.
-1) Add dictionary definitions using software like GoldenDict, Qolibri, etc.
-
-After the card is created, you can find it by typing `added:1` in the Anki Browser.
diff --git a/config/mpv/scripts/subs2srsa/howto/create_card.md b/config/mpv/scripts/subs2srsa/howto/create_card.md
deleted file mode 100644
index e92f77d..0000000
--- a/config/mpv/scripts/subs2srsa/howto/create_card.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Make a simple sentence card
-
-To make a card from the currently visible subtitle line, press <kbd>Ctrl+n</kbd>.
-
-To make a card from two or more subtitle lines:
-
-1) Press <kbd>a</kbd> to open advanced menu.
-2) Press <kbd>c</kbd> to start interactive selection.
-3) Seek to the previous/next subtitle with <kbd>Shift+h</kbd> and <kbd>Shift+l</kbd>.
-4) Press <kbd>n</kbd> to create a new card.
-
-After the card is created, you can find it by typing `added:1` in the Anki Browser.
-The card doesn't contain dictionary definitions.
-You need to add them yourself afterward, using software like GoldenDict, Qolibri, etc.
diff --git a/config/mpv/scripts/subs2srsa/howto/create_quick_card.md b/config/mpv/scripts/subs2srsa/howto/create_quick_card.md
deleted file mode 100644
index 619794a..0000000
--- a/config/mpv/scripts/subs2srsa/howto/create_quick_card.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# Quick Card Creation
-
-The goal of the quick card creation menu is to streamline a specific flow for media with **well-timed subtitles**:
-
-1) Add a new note to Anki via
- [Rikaitan](https://tatsumoto.neocities.org/blog/setting-up-yomichan.html)
- or any other tool that works with AnkiConnect.
-2) Update the note via mpvacious.
-
-To update the most recently added card from the currently visible subtitle line, press <kbd>gg</kbd>.
-
-To make a card from two or more subtitle lines:
-
-1) Press <kbd>g</kbd> to open the quick card creation menu.
-2) Press any number <kbd>[2-9]</kbd>. This number corresponds to the number of lines to create the card from.
-
-Note: <kbd>g1</kbd> is also valid.
-However, <kbd>gg</kbd> is an additional bind to further streamline the most common scenario.
-
-For example,
-<kbd>g2</kbd> creates a card using 2 subtitle lines.
-
-Like the advanced menu, you can also update multiple cards:
-
-1) Press <kbd>Alt+g</kbd> to select the number of cards for quick card creation.
-2) Press any number <kbd>[2-9]</kbd>. This number corresponds to the number of cards to update.
-3) Press any number <kbd>[1-9]</kbd> again. This is the number of lines.
-
-Note: upon completing the note update, the selected number of cards resets back to the default of one.
-
-For example, <kbd>(Alt+g)22</kbd> would update the last 2 notes using 2 subtitle lines.
diff --git a/config/mpv/scripts/subs2srsa/howto/flatpak.md b/config/mpv/scripts/subs2srsa/howto/flatpak.md
deleted file mode 100644
index 46a5787..0000000
--- a/config/mpv/scripts/subs2srsa/howto/flatpak.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Flatpak notes
-
-We think it's best to never use Flatpak.
-Specifically, try not to use Flatpak to install `mpv` and `anki`.
-Install packages from the official repositories of your distro or from the AUR.
-
-Read the following notes if you still decide to use Flatpak.
-
-Make these changes in Flatseal:
-
-* Enable "Filesystem > All system files"
- so it could see `wl-copy`.
- Unfortunately, there's no option to provide only a specific system file.
-* Add `~/.var/app/net.ankiweb.Anki` to "Filesystem > Other Files"
- so mpvacious could add encoded snapshots and audio to Anki.
-* Add `PATH=/home/USERNAME/.local/bin:/home/USERNAME/bin:/app/bin:/usr/bin:/run/host/usr/bin` to "Environment > Variables".
- There's no option to add a path to `PATH` in Flatseal,
- so I opened container,
- saved it's PATH and added `/run/host/usr/bin`
- so mpvacuous could access `wl-copy`.
-* Enable "Shared > Network".
- It's enabled by default, but anyway.
-
-The mpv config root is `~/.var/app/io.mpv.Mpv/config/mpv`
-
-* `~/.var/app/io.mpv.Mpv/config/mpv/scripts`
-* `~/.var/app/io.mpv.Mpv/config/mpv/script-opts`
diff --git a/config/mpv/scripts/subs2srsa/howto/goldendict.md b/config/mpv/scripts/subs2srsa/howto/goldendict.md
deleted file mode 100644
index 7f355c6..0000000
--- a/config/mpv/scripts/subs2srsa/howto/goldendict.md
+++ /dev/null
@@ -1,45 +0,0 @@
-# Modifying cards added with GoldenDict
-
-You can add a card first using GoldenDict,
-and then append an audio clip and a picture to it.
-
-**Note:** the only version of GoldenDict that can create Anki cards with configurable fields is
-[xiaoyifang's goldendict](https://github.com/xiaoyifang/goldendict-ng).
-Read [Setting up GoldenDict](https://tatsumoto-ren.github.io/blog/setting-up-goldendict.html) and
-[How to connect with Anki](https://github.com/xiaoyifang/goldendict-ng/blob/staged/website/docs/topic_anki.md)
-if you are new to GoldenDict.
-
-To send subtitles from `mpv` directly to GoldenDict,
-append the following line to `subs2srs.conf`:
-
-```
-autoclip_method=goldendict
-```
-
-**Note:** If `goldendict` is not in the PATH,
-you have to [add it to the PATH](https://wiki.archlinux.org/title/Environment_variables#Per_user).
-
-1) Press <kbd>a</kbd> to open `advanced menu`.
-2) Press <kbd>t</kbd> to toggle the `autoclip` option.
-
-Now as subtitles appear on the screen,
-they will be immediately sent to GoldenDict instead of the system clipboard.
-
-1) Open GoldenDict.
-2) Play a video in `mpv`.
-3) When you find an unknown word,
- select the definition text,
- right-click and select "send word to anki" to make a card,
- or press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>N</kbd>.
-4) Go back to mpv and add an image and an audio clip
- to the card you've just made by pressing <kbd>m</kbd> while the `advanced menu` is open.
- Pressing <kbd>Shift+m</kbd> will overwrite any existing data in media fields.
-
-https://github.com/Ajatt-Tools/mpvacious/assets/69171671/0fc02d24-d320-4d2c-b7a9-cb478e9f0067
-
-Don't forget to set the right timings and join lines together
-if the sentence is split between multiple subs.
-To do it, enter interactive selection by pressing <kbd>c</kbd>
-and seek to the next or previous subtitle.
-
-To pair Mecab and GoldenDict, install [gd-tools](https://github.com/Ajatt-Tools/gd-tools).
diff --git a/config/mpv/scripts/subs2srsa/howto/yomichan.md b/config/mpv/scripts/subs2srsa/howto/yomichan.md
deleted file mode 100644
index 9ea16d6..0000000
--- a/config/mpv/scripts/subs2srsa/howto/yomichan.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Modifying cards added with Rikaitan
-
-You can add a card first using
-[Rikaitan](https://tatsumoto.neocities.org/blog/setting-up-yomichan.html),
-and then append an audio clip and a picture to it.
-
-1) Press <kbd>a</kbd> to open `advanced menu`.
-1) Press <kbd>t</kbd> to toggle the `autoclip` option.
-
-Now as subtitles appear on the screen, they will be immediately copied to the clipboard.
-You can use it in combination with clipboard monitor.
-
-1) Open [Rikaitan Search](https://tatsumoto.neocities.org/blog/what-is-yomichan-search)
- by pressing <kbd>Alt+Insert</kbd> in your web browser.
-1) Play a video in `mpv`.
-1) When you find an unknown word, click the <kbd>+</kbd> button to make a card for it.
-4) Go back to mpv and add an image and an audio clip
- to the card you've just made by pressing <kbd>m</kbd> while the `advanced menu` is open.
- Pressing <kbd>Shift+m</kbd> will overwrite any existing data in media fields.
-
-Don't forget to set the right timings and join lines together
-if the sentence is split between multiple subs.
-To do it, enter interactive selection by pressing <kbd>c</kbd>
-and seek to the next or previous subtitle.
diff --git a/config/mpv/scripts/subs2srsa/main.lua b/config/mpv/scripts/subs2srsa/main.lua
deleted file mode 100644
index 6817586..0000000
--- a/config/mpv/scripts/subs2srsa/main.lua
+++ /dev/null
@@ -1 +0,0 @@
-require('subs2srs')
diff --git a/config/mpv/scripts/subs2srsa/menu.lua b/config/mpv/scripts/subs2srsa/menu.lua
deleted file mode 100644
index 4d34937..0000000
--- a/config/mpv/scripts/subs2srsa/menu.lua
+++ /dev/null
@@ -1,77 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Menu for mpvacious
-]]
-
-local mp = require('mp')
-local msg = require('mp.msg')
-local h = require('helpers')
-
-local Menu = {
- active = false,
- keybindings = {},
- overlay = mp.create_osd_overlay and mp.create_osd_overlay('ass-events'),
-}
-
-function Menu:new(o)
- o = o or {}
- setmetatable(o, self)
- self.__index = self
- return o
-end
-
-function Menu:with_update(params)
- return function()
- local status, error = pcall(h.unpack(params))
- if not status then
- msg['error'](error)
- end
- self:update()
- end
-end
-
-function Menu:make_osd()
- return nil
-end
-
-function Menu:update()
- if self.active == false then return end
- self.overlay.data = self:make_osd():get_text()
- self.overlay:update()
-end
-
-function Menu:open()
- if self.overlay == nil then
- h.notify("OSD overlay is not supported in " .. mp.get_property("mpv-version"), "error", 5)
- return
- end
-
- if self.active == true then
- self:close()
- return
- end
-
- for _, val in pairs(self.keybindings) do
- mp.add_forced_key_binding(val.key, val.key, val.fn)
- end
-
- self.active = true
- self:update()
-end
-
-function Menu:close()
- if self.active == false then
- return
- end
-
- for _, val in pairs(self.keybindings) do
- mp.remove_key_binding(val.key)
- end
-
- self.overlay:remove()
- self.active = false
-end
-
-return Menu
diff --git a/config/mpv/scripts/subs2srsa/osd_styler.lua b/config/mpv/scripts/subs2srsa/osd_styler.lua
deleted file mode 100644
index 3a865dc..0000000
--- a/config/mpv/scripts/subs2srsa/osd_styler.lua
+++ /dev/null
@@ -1,97 +0,0 @@
---[[
-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 <https://www.gnu.org/licenses/>.
-]]
-
-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: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('ffe1d0'):bold(text):color('ffffff')
-end
-
-function OSD:item(text)
- return self:color('fef6dd'):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/config/mpv/scripts/subs2srsa/platform/init.lua b/config/mpv/scripts/subs2srsa/platform/init.lua
deleted file mode 100644
index 825d8d4..0000000
--- a/config/mpv/scripts/subs2srsa/platform/init.lua
+++ /dev/null
@@ -1,14 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Platform-specific functions.
-]]
-
-local h = require('helpers')
-
-if h.is_win() then
- return require('platform.win')
-else
- return require('platform.nix')
-end
diff --git a/config/mpv/scripts/subs2srsa/platform/nix.lua b/config/mpv/scripts/subs2srsa/platform/nix.lua
deleted file mode 100644
index cbf6c85..0000000
--- a/config/mpv/scripts/subs2srsa/platform/nix.lua
+++ /dev/null
@@ -1,49 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Platform-specific functions for *nix systems.
-]]
-
-local h = require('helpers')
-local self = { healthy = true, clip_util = "", clip_cmd = "", }
-
-if h.is_mac() then
- self.clip_util = "pbcopy"
- self.clip_cmd = "LANG=en_US.UTF-8 " .. self.clip_util
-elseif h.is_wayland() then
- local function is_wl_copy_installed()
- local handle = h.subprocess { 'wl-copy', '--version' }
- return handle.status == 0 and handle.stdout:match("wl%-clipboard") ~= nil
- end
-
- self.clip_util = "wl-copy"
- self.clip_cmd = self.clip_util
- self.healthy = is_wl_copy_installed()
-else
- local function is_xclip_installed()
- local handle = h.subprocess { 'xclip', '-version' }
- return handle.status == 0 and handle.stderr:match("xclip version") ~= nil
- end
-
- self.clip_util = "xclip"
- self.clip_cmd = self.clip_util .. " -i -selection clipboard"
- self.healthy = is_xclip_installed()
-end
-
-self.tmp_dir = function()
- return os.getenv("TMPDIR") or '/tmp'
-end
-
-self.copy_to_clipboard = function(text)
- local handle = io.popen(self.clip_cmd, 'w')
- handle:write(text)
- handle:close()
-end
-
-self.curl_request = function(url, request_json, completion_fn)
- local args = { 'curl', '-s', url, '-X', 'POST', '-d', request_json }
- return h.subprocess(args, completion_fn)
-end
-
-return self
diff --git a/config/mpv/scripts/subs2srsa/platform/win.lua b/config/mpv/scripts/subs2srsa/platform/win.lua
deleted file mode 100644
index e40dd7a..0000000
--- a/config/mpv/scripts/subs2srsa/platform/win.lua
+++ /dev/null
@@ -1,72 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Platform-specific functions for Windows.
-]]
-
-local mp = require('mp')
-local h = require('helpers')
-local utils = require('mp.utils')
-local base64 = require('utils.base64')
-local self = { windows = true, healthy = true, clip_util = "cmd", }
-local tmp_files = {}
-
-mp.register_event('shutdown', function()
- for _, file in ipairs(tmp_files) do
- os.remove(file)
- end
-end)
-
-self.tmp_dir = function()
- return os.getenv('TEMP')
-end
-
-self.copy_to_clipboard = function(text)
- local args = {
- "powershell", "-NoLogo", "-NoProfile", "-WindowStyle", "Hidden", "-Command",
- string.format(
- "Set-Clipboard ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('%s')))",
- base64.enc(text)
- )
- }
- return h.subprocess_detached(
- args,
- function()
- end
- )
-end
-
-self.gen_random_tmp_file_path = function()
- return utils.join_path(self.tmp_dir(), string.format('curl_tmp_%d.txt', math.random(10 ^ 9)))
-end
-
-self.gen_unique_tmp_file_path = function()
- local curl_tmpfile_path = self.gen_random_tmp_file_path()
- while h.file_exists(curl_tmpfile_path) do
- curl_tmpfile_path = self.gen_random_tmp_file_path()
- end
- return curl_tmpfile_path
-end
-
-self.curl_request = function(url, request_json, completion_fn)
- local curl_tmpfile_path = self.gen_unique_tmp_file_path()
- local handle = io.open(curl_tmpfile_path, "w")
- handle:write(request_json)
- handle:close()
- table.insert(tmp_files, curl_tmpfile_path)
- local args = {
- 'curl',
- '-s',
- url,
- '-H',
- 'Content-Type: application/json; charset=UTF-8',
- '-X',
- 'POST',
- '--data-binary',
- table.concat { '@', curl_tmpfile_path }
- }
- return h.subprocess(args, completion_fn)
-end
-
-return self
diff --git a/config/mpv/scripts/subs2srsa/subs2srs.lua b/config/mpv/scripts/subs2srsa/subs2srs.lua
deleted file mode 100644
index 34c79a9..0000000
--- a/config/mpv/scripts/subs2srsa/subs2srs.lua
+++ /dev/null
@@ -1,746 +0,0 @@
---[[
-Copyright (C) 2020-2022 Ren Tatsumoto and contributors
-
-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 <https://www.gnu.org/licenses/>.
-
-Requirements:
-* mpv >= 0.32.0
-* AnkiConnect
-* curl
-* xclip (when running X11)
-* wl-copy (when running Wayland)
-
-Usage:
-1. Change `config` according to your needs
-* Config path: ~/.config/mpv/script-opts/subs2srs.conf
-* Config file isn't created automatically.
-
-2. Open a video
-
-3. Use key bindings to manipulate the script
-* Open mpvacious menu - `a`
-* Create a note from the current subtitle line - `Ctrl + n`
-
-For complete usage guide, see <https://github.com/Ajatt-Tools/mpvacious/blob/master/README.md>
-]]
-
-local mp = require('mp')
-local OSD = require('osd_styler')
-local cfg_mgr = require('cfg_mgr')
-local encoder = require('encoder.encoder')
-local h = require('helpers')
-local Menu = require('menu')
-local ankiconnect = require('ankiconnect')
-local switch = require('utils.switch')
-local play_control = require('utils.play_control')
-local secondary_sid = require('subtitles.secondary_sid')
-local platform = require('platform.init')
-local forvo = require('utils.forvo')
-local subs_observer = require('subtitles.observer')
-local codec_support = require('encoder.codec_support')
-
-local menu, quick_menu, quick_menu_card
-local quick_creation_opts = {
- _n_lines = nil,
- _n_cards = 1,
- set_cards = function(self, n)
- self._n_cards = math.max(0, n)
- end,
- set_lines = function(self, n)
- self._n_lines = math.max(0, n)
- end,
- get_cards = function(self)
- return self._n_cards
- end,
- get_lines = function(self)
- return self._n_lines
- end,
- increment_cards = function(self)
- self:set_cards(self._n_cards + 1)
- end,
- decrement_cards = function(self)
- self:set_cards(self._n_cards - 1)
- end,
- clear_options = function(self)
- self._n_lines = nil
- self._n_cards = 1
- end
-}
-------------------------------------------------------------
--- default config
-
-local config = {
- -- The user should not modify anything below.
-
- -- Common
- nuke_spaces = false, -- remove all spaces from the primary subtitles on exported anki cards and when copying text to clipboard.
- clipboard_trim_enabled = true, -- remove unnecessary characters from strings before copying to the clipboard
- use_ffmpeg = false, -- if set to true, use ffmpeg to create audio clips and snapshots. by default use mpv.
- reload_config_before_card_creation = true, -- for convenience, read config file from disk before a card is made.
-
- -- Clipboard and external communication
- autoclip = false, -- enable copying subs to the clipboard when mpv starts
- autoclip_method = "clipboard", -- one of the methods
- autoclip_custom_args = "", -- command to run when autoclip is triggered and autoclip_method and set to "custom_command".
-
- -- Secondary subtitle
- secondary_sub_auto_load = true, -- Automatically load secondary subtitle track when a video file is opened.
- secondary_sub_lang = 'eng,en,rus,ru,jp,jpn,ja', -- Language of secondary subs that should be automatically loaded.
- secondary_sub_area = 0.15, -- Hover area. Fraction of the window from the top.
- secondary_sub_visibility = 'auto', -- One of: 'auto', 'never', 'always'. Controls secondary_sid visibility. Ctrl+V to cycle.
-
- -- Snapshots
- snapshot_format = "avif", -- avif, webp or jpg
- snapshot_quality = 15, -- from 0=lowest to 100=highest
- snapshot_width = cfg_mgr.preserve_aspect_ratio, -- a positive integer or -2 for auto
- snapshot_height = cfg_mgr.default_height_px, -- same
- screenshot = false, -- create a screenshot instead of a snapshot; see example config.
-
- -- Animations
- animated_snapshot_enabled = false, -- if enabled captures the selected segment of the video, instead of just a frame
- animated_snapshot_format = "avif", -- avif or webp
- animated_snapshot_fps = 10, -- positive integer between 0 and 30 (30 included)
- animated_snapshot_width = cfg_mgr.preserve_aspect_ratio, -- positive integer or -2 to scale it maintaining ratio (height must not be -2 in that case)
- animated_snapshot_height = cfg_mgr.default_height_px, -- positive integer or -2 to scale it maintaining ratio (width must not be -2 in that case)
- animated_snapshot_quality = 5, -- positive integer between 0 and 100 (100 included)
-
- -- Audio clips
- audio_format = "opus", -- opus or mp3
- opus_container = "ogg", -- ogg, opus, m4a, webm or caf
- audio_bitrate = "18k", -- from 16k to 32k
- audio_padding = 0.12, -- Set a pad to the dialog timings. 0.5 = audio is padded by .5 seconds. 0 = disable.
- tie_volumes = false, -- if set to true, the volume of the outputted audio file depends on the volume of the player at the time of export
- preview_audio = false, -- play created audio clips in background.
-
- -- Menu
- menu_font_name = "Noto Sans CJK JP",
- menu_font_size = 25,
- show_selected_text = true,
-
- -- Make sure to remove loudnorm from ffmpeg_audio_args and mpv_audio_args before enabling.
- loudnorm = false,
- loudnorm_target = -16,
- loudnorm_range = 11,
- loudnorm_peak = -1.5,
-
- -- Custom encoding args
- -- Defaults are for backward compatibility, in case someone
- -- updates mpvacious without updating their config.
- -- Better to remove loudnorm from custom args and enable two-pass loudnorm.
- -- Enabling loudnorm both through the separate switch and through custom args
- -- can lead to unpredictable results.
- ffmpeg_audio_args = '-af loudnorm=I=-16:TP=-1.5:LRA=11:dual_mono=true',
- mpv_audio_args = '--af-append=loudnorm=I=-16:TP=-1.5:LRA=11:dual_mono=true',
-
- -- Anki
- create_deck = false, -- automatically create a deck for new cards
- allow_duplicates = false, -- allow making notes with the same sentence field
- deck_name = "Learning", -- name of the deck for new cards
- model_name = "Japanese sentences", -- Tools -> Manage note types
- sentence_field = "SentKanji",
- secondary_field = "SentEng",
- audio_field = "SentAudio",
- audio_template = '[sound:%s]',
- image_field = "Image",
- image_template = '<img alt="snapshot" src="%s">',
- append_media = true, -- True to append video media after existing data, false to insert media before
- disable_gui_browse = false, -- Lets you disable anki browser manipulation by mpvacious.
- ankiconnect_url = '127.0.0.1:8765',
-
- -- Note tagging
- -- The tag(s) added to new notes. Spaces separate multiple tags.
- -- Change to "" to disable tagging completely.
- -- The following substitutions are supported:
- -- %n - the name of the video
- -- %t - timestamp
- -- %d - episode number (if none found, returns nothing)
- -- %e - SUBS2SRS_TAGS environment variable
- note_tag = "subs2srs %n",
- tag_nuke_brackets = true, -- delete all text inside brackets before substituting filename into tag
- tag_nuke_parentheses = false, -- delete all text inside parentheses before substituting filename into tag
- tag_del_episode_num = true, -- delete the episode number if found
- tag_del_after_episode_num = true, -- delete everything after the found episode number (does nothing if tag_del_episode_num is disabled)
- tag_filename_lowercase = false, -- convert filename to lowercase for tagging.
-
- -- Misc info
- miscinfo_enable = true,
- miscinfo_field = "Notes", -- misc notes and source information field
- miscinfo_format = "%n EP%d (%t)", -- format string to use for the miscinfo_field, accepts note_tag-style format strings
-
- -- Forvo support
- use_forvo = "yes", -- 'yes', 'no', 'always'
- vocab_field = "VocabKanji", -- target word field
- vocab_audio_field = "VocabAudio", -- target word audio
-}
-
--- Defines config profiles
--- Each name references a file in ~/.config/mpv/script-opts/*.conf
--- Profiles themselves are defined in ~/.config/mpv/script-opts/subs2srs_profiles.conf
-local profiles = {
- profiles = "subs2srs,subs2srs_english",
- active = "subs2srs",
-}
-
-------------------------------------------------------------
--- utility functions
-local function _(params)
- return function()
- return pcall(h.unpack(params))
- end
-end
-
-local function escape_for_osd(str)
- str = h.trim(str)
- str = str:gsub('[%[%]{}]', '')
- return str
-end
-
-local function ensure_deck()
- if config.create_deck == true then
- ankiconnect.create_deck(config.deck_name)
- end
-end
-
-local function load_next_profile()
- cfg_mgr.next_profile()
- ensure_deck()
- h.notify("Loaded profile " .. profiles.active)
-end
-
-local function tag_format(filename)
- filename = h.remove_extension(filename)
- filename = h.remove_common_resolutions(filename)
-
- local s, e, episode_num = h.get_episode_number(filename)
-
- if config.tag_del_episode_num == true and not h.is_empty(s) then
- if config.tag_del_after_episode_num == true then
- -- Removing everything (e.g. episode name) after the episode number including itself.
- filename = filename:sub(1, s)
- else
- -- Removing the first found instance of the episode number.
- filename = filename:sub(1, s) .. filename:sub(e + 1, -1)
- end
- end
-
- if config.tag_nuke_brackets == true then
- filename = h.remove_text_in_brackets(filename)
- end
- if config.tag_nuke_parentheses == true then
- filename = h.remove_filename_text_in_parentheses(filename)
- end
-
- if config.tag_filename_lowercase == true then
- filename = filename:lower()
- end
-
- filename = h.remove_leading_trailing_spaces(filename)
- filename = filename:gsub(" ", "_")
- filename = filename:gsub("_%-_", "_") -- Replaces garbage _-_ substrings with a underscore
- filename = h.remove_leading_trailing_dashes(filename)
- return filename, episode_num or ''
-end
-
-local substitute_fmt = (function()
- local function substitute_filename(tag, filename)
- return tag:gsub("%%n", filename)
- end
-
- local function substitute_episode_number(tag, episode)
- return tag:gsub("%%d", episode)
- end
-
- local function substitute_time_pos(tag)
- local time_pos = h.human_readable_time(mp.get_property_number('time-pos'))
- return tag:gsub("%%t", time_pos)
- end
-
- local function substitute_envvar(tag)
- local env_tags = os.getenv('SUBS2SRS_TAGS') or ''
- return tag:gsub("%%e", env_tags)
- end
-
- return function(tag)
- if not h.is_empty(tag) then
- local filename, episode = tag_format(mp.get_property("filename"))
- tag = substitute_filename(tag, filename)
- tag = substitute_episode_number(tag, episode)
- tag = substitute_time_pos(tag)
- tag = substitute_envvar(tag)
- tag = h.remove_leading_trailing_spaces(tag)
- end
- return tag
- end
-end)()
-
-local function prepare_for_exporting(sub_text)
- if not h.is_empty(sub_text) then
- sub_text = h.trim(sub_text)
- sub_text = h.escape_special_characters(sub_text)
- end
- return sub_text
-end
-
-local function construct_note_fields(sub_text, secondary_text, snapshot_filename, audio_filename)
- local ret = {
- [config.sentence_field] = subs_observer.maybe_remove_all_spaces(prepare_for_exporting(sub_text)),
- }
- if not h.is_empty(config.secondary_field) then
- ret[config.secondary_field] = prepare_for_exporting(secondary_text)
- end
- if not h.is_empty(config.image_field) and not h.is_empty(snapshot_filename) then
- ret[config.image_field] = string.format(config.image_template, snapshot_filename)
- end
- if not h.is_empty(config.audio_field) and not h.is_empty(audio_filename) then
- ret[config.audio_field] = string.format(config.audio_template, audio_filename)
- end
- if config.miscinfo_enable == true then
- ret[config.miscinfo_field] = substitute_fmt(config.miscinfo_format)
- end
- return ret
-end
-
-local function join_media_fields(new_data, stored_data)
- for _, field in pairs { config.audio_field, config.image_field, config.miscinfo_field } do
- if not h.is_empty(field) then
- new_data[field] = h.table_get(stored_data, field, "") .. h.table_get(new_data, field, "")
- end
- end
- return new_data
-end
-
-local function update_sentence(new_data, stored_data)
- -- adds support for TSCs
- -- https://tatsumoto-ren.github.io/blog/discussing-various-card-templates.html#targeted-sentence-cards-or-mpvacious-cards
- -- if the target word was marked by yomichan, this function makes sure that the highlighting doesn't get erased.
-
- if h.is_empty(stored_data[config.sentence_field]) then
- -- sentence field is empty. can't continue.
- return new_data
- elseif h.is_empty(new_data[config.sentence_field]) then
- -- *new* sentence field is empty, but old one contains data. don't delete the existing sentence.
- new_data[config.sentence_field] = stored_data[config.sentence_field]
- return new_data
- end
-
- local _, opentag, target, closetag, _ = stored_data[config.sentence_field]:match('^(.-)(<[^>]+>)(.-)(</[^>]+>)(.-)$')
- if target then
- local prefix, _, suffix = new_data[config.sentence_field]:match(table.concat { '^(.-)(', target, ')(.-)$' })
- if prefix and suffix then
- new_data[config.sentence_field] = table.concat { prefix, opentag, target, closetag, suffix }
- end
- end
- return new_data
-end
-
-local function audio_padding()
- local video_duration = mp.get_property_number('duration')
- if config.audio_padding == 0.0 or not video_duration then
- return 0.0
- end
- if subs_observer.user_altered() then
- return 0.0
- end
- return config.audio_padding
-end
-
-------------------------------------------------------------
--- front for adding and updating notes
-
-local function maybe_reload_config()
- if config.reload_config_before_card_creation then
- cfg_mgr.reload_from_disk()
- end
-end
-
-local function get_anki_media_dir_path()
- return ankiconnect.get_media_dir_path()
-end
-
-local function export_to_anki(gui)
- maybe_reload_config()
- local sub = subs_observer.collect_from_current()
-
- if not sub:is_valid() then
- return h.notify("Nothing to export.", "warn", 1)
- end
-
- if not gui and h.is_empty(sub['text']) then
- sub['text'] = string.format("mpvacious wasn't able to grab subtitles (%s)", os.time())
- end
-
- encoder.set_output_dir(get_anki_media_dir_path())
- local snapshot = encoder.snapshot.create_job(sub)
- local audio = encoder.audio.create_job(sub, audio_padding())
-
- snapshot.run_async()
- audio.run_async()
-
- local first_field = ankiconnect.get_first_field(config.model_name)
- local note_fields = construct_note_fields(sub['text'], sub['secondary'], snapshot.filename, audio.filename)
-
- if not h.is_empty(first_field) and h.is_empty(note_fields[first_field]) then
- note_fields[first_field] = "[empty]"
- end
-
- ankiconnect.add_note(note_fields, substitute_fmt(config.note_tag), gui)
- subs_observer.clear()
-end
-
-local function update_last_note(overwrite)
- maybe_reload_config()
- local sub
- local n_lines = quick_creation_opts:get_lines()
- local n_cards = quick_creation_opts:get_cards()
- if n_lines then
- sub = subs_observer.collect_from_all_dialogues(n_lines)
- else
- sub = subs_observer.collect_from_current()
- end
- -- this now returns a table
- local last_note_ids = ankiconnect.get_last_note_ids(n_cards)
- n_cards = #last_note_ids
-
- if not sub:is_valid() then
- return h.notify("Nothing to export. Have you set the timings?", "warn", 2)
- end
-
- if h.is_empty(sub['text']) then
- -- In this case, don't modify whatever existing text there is and just
- -- modify the other fields we can. The user might be trying to add
- -- audio to a card which they've manually transcribed (either the video
- -- has no subtitles or it has image subtitles).
- sub['text'] = nil
- end
-
- --first element is the earliest
-
- if h.is_empty(last_note_ids) or last_note_ids[1] < h.minutes_ago(10) then
- return h.notify("Couldn't find the target note.", "warn", 2)
- end
-
- local anki_media_dir = get_anki_media_dir_path()
- encoder.set_output_dir(anki_media_dir)
- local snapshot = encoder.snapshot.create_job(sub)
- local audio = encoder.audio.create_job(sub, audio_padding())
-
- local create_media = function()
- snapshot.run_async()
- audio.run_async()
- end
- for i = 1, n_cards do
- local new_data = construct_note_fields(sub['text'], sub['secondary'], snapshot.filename, audio.filename)
- local stored_data = ankiconnect.get_note_fields(last_note_ids[i])
- if stored_data then
- forvo.set_output_dir(anki_media_dir)
- new_data = forvo.append(new_data, stored_data)
- new_data = update_sentence(new_data, stored_data)
- if not overwrite then
- if config.append_media then
- new_data = join_media_fields(new_data, stored_data)
- else
- new_data = join_media_fields(stored_data, new_data)
- end
- end
- end
-
- -- If the text is still empty, put some dummy text to let the user know why
- -- there's no text in the sentence field.
- if h.is_empty(new_data[config.sentence_field]) then
- new_data[config.sentence_field] = string.format("mpvacious wasn't able to grab subtitles (%s)", os.time())
- end
-
- ankiconnect.append_media(last_note_ids[i], new_data, create_media, substitute_fmt(config.note_tag))
- end
- subs_observer.clear()
- quick_creation_opts:clear_options()
-end
-
-------------------------------------------------------------
--- main menu
-
-menu = Menu:new {
- hints_state = switch.new { 'basic', 'menu', 'global', 'hidden', },
-}
-
-menu.keybindings = {
- { key = 'S', fn = menu:with_update { subs_observer.set_manual_timing_to_sub, 'start' } },
- { key = 'E', fn = menu:with_update { subs_observer.set_manual_timing_to_sub, 'end' } },
- { key = 's', fn = menu:with_update { subs_observer.set_manual_timing, 'start' } },
- { key = 'e', fn = menu:with_update { subs_observer.set_manual_timing, 'end' } },
- { key = 'c', fn = menu:with_update { subs_observer.set_to_current_sub } },
- { key = 'r', fn = menu:with_update { subs_observer.clear_and_notify } },
- { key = 'g', fn = menu:with_update { export_to_anki, true } },
- { key = 'n', fn = menu:with_update { export_to_anki, false } },
- { key = 'm', fn = menu:with_update { update_last_note, false } },
- { key = 'M', fn = menu:with_update { update_last_note, true } },
- { key = 'f', fn = menu:with_update { function()
- quick_creation_opts:increment_cards()
- end } },
- { key = 'F', fn = menu:with_update { function()
- quick_creation_opts:decrement_cards()
- end } },
- { key = 't', fn = menu:with_update { subs_observer.toggle_autocopy } },
- { key = 'T', fn = menu:with_update { subs_observer.next_autoclip_method } },
- { key = 'i', fn = menu:with_update { menu.hints_state.bump } },
- { key = 'p', fn = menu:with_update { load_next_profile } },
- { key = 'ESC', fn = function()
- menu:close()
- end },
- { key = 'q', fn = function()
- menu:close()
- end },
-}
-
-function menu:print_header(osd)
- if self.hints_state.get() == 'hidden' then
- return
- end
- osd:submenu('mpvacious options'):newline()
- osd:item('Timings: '):text(h.human_readable_time(subs_observer.get_timing('start')))
- osd:item(' to '):text(h.human_readable_time(subs_observer.get_timing('end'))):newline()
- osd:item('Clipboard autocopy: '):text(subs_observer.autocopy_status_str()):newline()
- osd:item('Active profile: '):text(profiles.active):newline()
- osd:item('Deck: '):text(config.deck_name):newline()
- osd:item('# cards: '):text(quick_creation_opts:get_cards()):newline()
-end
-
-function menu:print_bindings(osd)
- if self.hints_state.get() == 'global' then
- osd:submenu('Global bindings'):newline()
- osd:tab():item('ctrl+c: '):text('Copy current subtitle to clipboard'):newline()
- osd:tab():item('ctrl+h: '):text('Seek to the start of the line'):newline()
- osd:tab():item('ctrl+g: '):text('Toggle animated snapshots'):newline()
- osd:tab():item('ctrl+shift+h: '):text('Replay current subtitle'):newline()
- osd:tab():item('shift+h/l: '):text('Seek to the previous/next subtitle'):newline()
- osd:tab():item('alt+h/l: '):text('Seek to the previous/next subtitle and pause'):newline()
- osd:italics("Press "):item('i'):italics(" to hide mpvacious options."):newline()
- elseif self.hints_state.get() == 'menu' then
- osd:submenu('Menu bindings'):newline()
- osd:tab():item('c: '):text('Set timings to the current sub'):newline()
- osd:tab():item('s: '):text('Set start time to current position'):newline()
- osd:tab():item('e: '):text('Set end time to current position'):newline()
- osd:tab():item('shift+s: '):text('Set start time to current subtitle'):newline()
- osd:tab():item('shift+e: '):text('Set end time to current subtitle'):newline()
- osd:tab():item('f: '):text('Increment # cards to update '):italics('(+shift to decrement)'):newline()
- osd:tab():item('r: '):text('Reset timings'):newline()
- osd:tab():item('n: '):text('Export note'):newline()
- osd:tab():item('g: '):text('GUI export'):newline()
- osd:tab():item('m: '):text('Update the last added note '):italics('(+shift to overwrite)'):newline()
- osd:tab():item('t: '):text('Toggle clipboard autocopy'):newline()
- osd:tab():item('T: '):text('Switch to the next clipboard method'):newline()
- osd:tab():item('p: '):text('Switch to next profile'):newline()
- osd:tab():item('ESC: '):text('Close'):newline()
- osd:italics("Press "):item('i'):italics(" to show global bindings."):newline()
- elseif self.hints_state.get() == 'hidden' then
- -- Menu bindings are active but hidden
- else
- osd:italics("Press "):item('i'):italics(" to show menu bindings."):newline()
- end
-end
-
-function menu:warn_formats(osd)
- if config.use_ffmpeg then
- return
- end
- for type, codecs in pairs(codec_support) do
- for codec, supported in pairs(codecs) do
- if not supported and config[type .. '_codec'] == codec then
- osd:red('warning: '):newline()
- osd:tab():text(string.format("your version of mpv does not support %s.", codec)):newline()
- osd:tab():text(string.format("mpvacious won't be able to create %s files.", type)):newline()
- end
- end
- end
-end
-
-function menu:warn_clipboard(osd)
- if subs_observer.autocopy_current_method() == "clipboard" and platform.healthy == false then
- osd:red('warning: '):text(string.format("%s is not installed.", platform.clip_util)):newline()
- end
-end
-
-function menu:print_legend(osd)
- osd:new_layer():size(config.menu_font_size):font(config.menu_font_name):align(4)
- self:print_header(osd)
- self:print_bindings(osd)
- self:warn_formats(osd)
- self:warn_clipboard(osd)
-end
-
-function menu:print_selection(osd)
- if subs_observer.is_appending() and config.show_selected_text then
- osd:new_layer():size(config.menu_font_size):font(config.menu_font_name):align(6)
- osd:submenu("Primary text"):newline()
- for _, s in ipairs(subs_observer.recorded_subs()) do
- osd:text(escape_for_osd(s['text'])):newline()
- end
- if not h.is_empty(config.secondary_field) then
- -- If the user wants to add secondary subs to Anki,
- -- it's okay to print them on the screen.
- osd:submenu("Secondary text"):newline()
- for _, s in ipairs(subs_observer.recorded_secondary_subs()) do
- osd:text(escape_for_osd(s['text'])):newline()
- end
- end
- end
-end
-
-function menu:make_osd()
- local osd = OSD:new()
- self:print_legend(osd)
- self:print_selection(osd)
- return osd
-end
-
-------------------------------------------------------------
---quick_menu line selection
-local choose_cards = function(i)
- quick_creation_opts:set_cards(i)
- quick_menu_card:close()
- quick_menu:open()
-end
-local choose_lines = function(i)
- quick_creation_opts:set_lines(i)
- update_last_note(true)
- quick_menu:close()
-end
-
-quick_menu = Menu:new()
-quick_menu.keybindings = {}
-for i = 1, 9 do
- table.insert(quick_menu.keybindings, { key = tostring(i), fn = function()
- choose_lines(i)
- end })
-end
-table.insert(quick_menu.keybindings, { key = 'g', fn = function()
- choose_lines(1)
-end })
-table.insert(quick_menu.keybindings, { key = 'ESC', fn = function()
- quick_menu:close()
-end })
-table.insert(quick_menu.keybindings, { key = 'q', fn = function()
- quick_menu:close()
-end })
-function quick_menu:print_header(osd)
- osd:submenu('quick card creation: line selection'):newline()
- osd:item('# lines: '):text('Enter 1-9'):newline()
-end
-function quick_menu:print_legend(osd)
- osd:new_layer():size(config.menu_font_size):font(config.menu_font_name):align(4)
- self:print_header(osd)
- menu:warn_formats(osd)
-end
-function quick_menu:make_osd()
- local osd = OSD:new()
- self:print_legend(osd)
- return osd
-end
-
--- quick_menu card selection
-quick_menu_card = Menu:new()
-quick_menu_card.keybindings = {}
-for i = 1, 9 do
- table.insert(quick_menu_card.keybindings, { key = tostring(i), fn = function()
- choose_cards(i)
- end })
-end
-table.insert(quick_menu_card.keybindings, { key = 'ESC', fn = function()
- quick_menu_card:close()
-end })
-table.insert(quick_menu_card.keybindings, { key = 'q', fn = function()
- quick_menu_card:close()
-end })
-function quick_menu_card:print_header(osd)
- osd:submenu('quick card creation: card selection'):newline()
- osd:item('# cards: '):text('Enter 1-9'):newline()
-end
-function quick_menu_card:print_legend(osd)
- osd:new_layer():size(config.menu_font_size):font(config.menu_font_name):align(4)
- self:print_header(osd)
- menu:warn_formats(osd)
-end
-function quick_menu_card:make_osd()
- local osd = OSD:new()
- self:print_legend(osd)
- return osd
-end
-
-------------------------------------------------------------
--- main
-
-local main = (function()
- local main_executed = false
- return function()
- if main_executed then
- subs_observer.clear_all_dialogs()
- return
- else
- main_executed = true
- end
-
- cfg_mgr.init(config, profiles)
- ankiconnect.init(config, platform)
- forvo.init(config, platform)
- encoder.init(config)
- secondary_sid.init(config)
- ensure_deck()
- subs_observer.init(menu, config)
-
- -- Key bindings
- mp.add_forced_key_binding("Ctrl+c", "mpvacious-copy-sub-to-clipboard", subs_observer.copy_current_primary_to_clipboard)
- mp.add_key_binding("Ctrl+C", "mpvacious-copy-secondary-sub-to-clipboard", subs_observer.copy_current_secondary_to_clipboard)
- mp.add_key_binding("Ctrl+t", "mpvacious-autocopy-toggle", subs_observer.toggle_autocopy)
- mp.add_key_binding("Ctrl+g", "mpvacious-animated-snapshot-toggle", encoder.snapshot.toggle_animation)
-
- -- Secondary subtitles
- mp.add_key_binding("Ctrl+v", "mpvacious-secondary-sid-toggle", secondary_sid.change_visibility)
- mp.add_key_binding("Ctrl+k", "mpvacious-secondary-sid-prev", secondary_sid.select_previous)
- mp.add_key_binding("Ctrl+j", "mpvacious-secondary-sid-next", secondary_sid.select_next)
-
- -- Open advanced menu
- mp.add_key_binding("a", "mpvacious-menu-open", function()
- menu:open()
- end)
-
- -- Add note
- mp.add_forced_key_binding("Ctrl+n", "mpvacious-export-note", menu:with_update { export_to_anki, false })
-
- -- Note updating
- mp.add_key_binding("Ctrl+m", "mpvacious-update-last-note", menu:with_update { update_last_note, false })
- mp.add_key_binding("Ctrl+M", "mpvacious-overwrite-last-note", menu:with_update { update_last_note, true })
-
- mp.add_key_binding("g", "mpvacious-quick-card-menu-open", function()
- quick_menu:open()
- end)
- mp.add_key_binding("Alt+g", "mpvacious-quick-card-sel-menu-open", function()
- quick_menu_card:open()
- end)
-
- -- Vim-like seeking between subtitle lines
- mp.add_key_binding("H", "mpvacious-sub-seek-back", _ { play_control.sub_seek, 'backward' })
- mp.add_key_binding("L", "mpvacious-sub-seek-forward", _ { play_control.sub_seek, 'forward' })
-
- mp.add_key_binding("Alt+h", "mpvacious-sub-seek-back-pause", _ { play_control.sub_seek, 'backward', true })
- mp.add_key_binding("Alt+l", "mpvacious-sub-seek-forward-pause", _ { play_control.sub_seek, 'forward', true })
-
- mp.add_key_binding("Ctrl+h", "mpvacious-sub-rewind", _ { play_control.sub_rewind })
- mp.add_key_binding("Ctrl+H", "mpvacious-sub-replay", _ { play_control.play_till_sub_end })
- mp.add_key_binding("Ctrl+L", "mpvacious-sub-play-up-to-next", _ { play_control.play_till_next_sub_end })
-
- mp.msg.warn("Press 'a' to open the mpvacious menu.")
- end
-end)()
-
-mp.register_event("file-loaded", main)
diff --git a/config/mpv/scripts/subs2srsa/subtitles/observer.lua b/config/mpv/scripts/subs2srsa/subtitles/observer.lua
deleted file mode 100644
index d9284c5..0000000
--- a/config/mpv/scripts/subs2srsa/subtitles/observer.lua
+++ /dev/null
@@ -1,344 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Observer waits for subtitles to appear on the screen and adds them to a list.
-]]
-
-local h = require('helpers')
-local timings = require('utils.timings')
-local sub_list = require('subtitles.sub_list')
-local Subtitle = require('subtitles.subtitle')
-local mp = require('mp')
-local platform = require('platform.init')
-local switch = require('utils.switch')
-
-local self = {}
-
-local dialogs = sub_list.new()
-local secondary_dialogs = sub_list.new()
-local all_dialogs = sub_list.new()
-local all_secondary_dialogs = sub_list.new()
-local user_timings = timings.new()
-
-local append_dialogue = false
-local autoclip_enabled = false
-local autoclip_method = {}
-
-
-------------------------------------------------------------
--- private
-
-local function copy_primary_sub()
- if autoclip_enabled then
- autoclip_method.call()
- end
-end
-
-local function append_primary_sub()
- local current_sub = Subtitle:now()
- all_dialogs.insert(current_sub)
- if append_dialogue and dialogs.insert(current_sub) then
- self.menu:update()
- end
-end
-
-local function append_secondary_sub()
- local current_secondary = Subtitle:now('secondary')
- all_secondary_dialogs.insert(current_secondary)
- if append_dialogue and secondary_dialogs.insert(Subtitle:now('secondary')) then
- self.menu:update()
- end
-end
-
-local function start_appending()
- append_dialogue = true
- append_primary_sub()
- append_secondary_sub()
-end
-
-local function handle_primary_sub()
- append_primary_sub()
- copy_primary_sub()
-end
-
-local function handle_secondary_sub()
- append_secondary_sub()
-end
-
-local function on_external_finish(success, result, error)
- if success ~= true or error ~= nil then
- h.notify("Command failed: " .. table.concat(result))
- end
-end
-
-local function external_command_args(cur_lines)
- local args = {}
- for arg in string.gmatch(self.config.autoclip_custom_args, "%S+") do
- if arg == '%MPV_PRIMARY%' then
- arg = cur_lines.primary
- elseif arg == '%MPV_SECONDARY%' then
- arg = cur_lines.secondary
- end
- table.insert(args, arg)
- end
- return args
-end
-
-local function call_external_command(cur_lines)
- if not h.is_empty(self.config.autoclip_custom_args) then
- h.subprocess(external_command_args(cur_lines), on_external_finish)
- end
-end
-
-local function current_subtitle_lines()
- local primary = dialogs.get_text()
-
- if h.is_empty(primary) then
- primary = mp.get_property("sub-text")
- end
-
- if h.is_empty(primary) then
- return nil
- end
-
- local secondary = secondary_dialogs.get_text()
-
- if h.is_empty(secondary) then
- secondary = mp.get_property("secondary-sub-text") or ""
- end
-
- return { primary = self.clipboard_prepare(primary), secondary = secondary }
-end
-
-local function ensure_goldendict_running()
- --- Ensure that goldendict is running and is disowned by mpv.
- --- Avoid goldendict getting killed when mpv exits.
- if autoclip_enabled and self.autocopy_current_method() == "goldendict" then
- os.execute("setsid -f goldendict")
- end
-end
-
-------------------------------------------------------------
--- autoclip methods
-
-autoclip_method = (function()
- local methods = { 'clipboard', 'goldendict', 'custom_command', }
- local current_method = switch.new(methods)
-
- local function call()
- local cur_lines = current_subtitle_lines()
- if h.is_empty(cur_lines) then
- return
- end
-
- if current_method.get() == 'clipboard' then
- self.copy_to_clipboard("autocopy action", cur_lines.primary)
- elseif current_method.get() == 'goldendict' then
- h.subprocess_detached({ 'goldendict', cur_lines.primary }, on_external_finish)
- elseif current_method.get() == 'custom_command' then
- call_external_command(cur_lines)
- end
- end
-
- return {
- call = call,
- get = current_method.get,
- bump = current_method.bump,
- set = current_method.set,
- }
-end)()
-
-local function copy_subtitle(subtitle_id)
- self.copy_to_clipboard("copy-on-demand", mp.get_property(subtitle_id))
-end
-
-------------------------------------------------------------
--- public
-
-self.copy_to_clipboard = function(_, text)
- if platform.healthy == false then
- h.notify(platform.clip_util .. " is not installed.", "error", 5)
- end
- if not h.is_empty(text) then
- platform.copy_to_clipboard(self.clipboard_prepare(text))
- end
-end
-
-self.clipboard_prepare = function(text)
- text = self.config.clipboard_trim_enabled and h.trim(text) or h.remove_newlines(text)
- text = self.maybe_remove_all_spaces(text)
- return text
-end
-
-self.maybe_remove_all_spaces = function(str)
- if self.config.nuke_spaces == true and h.contains_non_latin_letters(str) then
- return h.remove_all_spaces(str)
- else
- return str
- end
-end
-
-self.copy_current_primary_to_clipboard = function()
- copy_subtitle("sub-text")
-end
-
-self.copy_current_secondary_to_clipboard = function()
- copy_subtitle("secondary-sub-text")
-end
-
-self.user_altered = function()
- --- Return true if the user manually set at least start or end.
- return user_timings.is_set('start') or user_timings.is_set('end')
-end
-
-self.get_timing = function(position)
- if user_timings.is_set(position) then
- return user_timings.get(position)
- elseif not dialogs.is_empty() then
- return dialogs.get_time(position)
- end
- return -1
-end
-
-self.collect_from_all_dialogues = function(n_lines)
- local current_sub = Subtitle:now()
- local current_secondary_sub = Subtitle:now('secondary')
- all_dialogs.insert(current_sub)
- all_secondary_dialogs.insert(current_secondary_sub)
- if current_sub == nil then
- return Subtitle:new() -- return a default empty new Subtitle to let consumer handle
- end
- local text, end_sub = all_dialogs.get_n_text(current_sub, n_lines)
- local secondary_text, _
- if current_secondary_sub == nil then
- secondary_text = ''
- else
- secondary_text, _ = all_secondary_dialogs.get_n_text(current_secondary_sub, n_lines) -- we'll use main sub's timing
- end
- return Subtitle:new {
- ['text'] = text,
- ['secondary'] = secondary_text,
- ['start'] = current_sub['start'],
- ['end'] = end_sub['end'],
- }
-end
-
-self.collect_from_current = function()
- --- Return all recorded subtitle lines as one subtitle object.
- --- The caller has to call subs_observer.clear() afterwards.
- if dialogs.is_empty() then
- dialogs.insert(Subtitle:now())
- end
- if secondary_dialogs.is_empty() then
- secondary_dialogs.insert(Subtitle:now('secondary'))
- end
- return Subtitle:new {
- ['text'] = dialogs.get_text(),
- ['secondary'] = secondary_dialogs.get_text(),
- ['start'] = self.get_timing('start'),
- ['end'] = self.get_timing('end'),
- }
-end
-
-self.set_manual_timing = function(position)
- user_timings.set(position, mp.get_property_number('time-pos') - mp.get_property("audio-delay"))
- h.notify(h.capitalize_first_letter(position) .. " time has been set.")
- start_appending()
-end
-
-self.set_manual_timing_to_sub = function(position)
- local sub = Subtitle:now()
- if sub then
- user_timings.set(position, sub[position] - mp.get_property("audio-delay"))
- h.notify(h.capitalize_first_letter(position) .. " time has been set.")
- start_appending()
- else
- h.notify("There's no visible subtitle.", "info", 2)
- end
-end
-
-self.set_to_current_sub = function()
- self.clear()
- if Subtitle:now() then
- start_appending()
- h.notify("Timings have been set to the current sub.", "info", 2)
- else
- h.notify("There's no visible subtitle.", "info", 2)
- end
-end
-
-self.clear = function()
- append_dialogue = false
- dialogs = sub_list.new()
- secondary_dialogs = sub_list.new()
- user_timings = timings.new()
-end
-
-self.clear_all_dialogs = function()
- all_dialogs = sub_list.new()
- all_secondary_dialogs = sub_list.new()
-end
-
-self.clear_and_notify = function()
- --- Clear then notify the user.
- --- Called by the OSD menu when the user presses a button to drop recorded subtitles.
- self.clear()
- h.notify("Timings have been reset.", "info", 2)
-end
-
-self.is_appending = function()
- return append_dialogue
-end
-
-self.recorded_subs = function()
- return dialogs.get_subs_list()
-end
-
-self.recorded_secondary_subs = function()
- return secondary_dialogs.get_subs_list()
-end
-
-self.autocopy_status_str = function()
- return string.format(
- "%s (%s)",
- (autoclip_enabled and 'enabled' or 'disabled'),
- autoclip_method.get():gsub('_', ' ')
- )
-end
-
-self.autocopy_current_method = function()
- return autoclip_method.get()
-end
-
-local function notify_autocopy()
- if autoclip_enabled then
- copy_primary_sub()
- end
- h.notify(string.format("Clipboard autocopy has been %s.", self.autocopy_status_str()))
-end
-
-self.toggle_autocopy = function()
- autoclip_enabled = not autoclip_enabled
- notify_autocopy()
-end
-
-self.next_autoclip_method = function()
- autoclip_method.bump()
- notify_autocopy()
-end
-
-self.init = function(menu, config)
- self.menu = menu
- self.config = config
-
- -- The autoclip state is copied as a local value
- -- to prevent it from being reset when the user reloads the config file.
- autoclip_enabled = self.config.autoclip
- autoclip_method.set(self.config.autoclip_method)
-
- mp.observe_property("sub-text", "string", handle_primary_sub)
- mp.observe_property("secondary-sub-text", "string", handle_secondary_sub)
-end
-
-return self
diff --git a/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua b/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua
deleted file mode 100644
index 21576e8..0000000
--- a/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua
+++ /dev/null
@@ -1,196 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-This module automatically finds and sets secondary sid if it's not already set.
-Secondary sid will be shown when mouse is moved to the top part of the mpv window.
-]]
-
-local mp = require('mp')
-local h = require('helpers')
-
-local self = {
- visibility = 'auto',
- visibility_states = { auto = true, never = true, always = true, },
-}
-
-local function is_accepted_language(sub_lang)
- -- for missing keys compares nil to true
- return self.accepted_languages[sub_lang] == true
-end
-
-local function is_selected_language(track, active_track)
- return track.id == mp.get_property_native('sid') or (active_track and active_track.lang == track.lang)
-end
-
-local function is_full(track)
- return h.str_contains(track.title, 'full')
-end
-
-local function is_garbage(track)
- for _, keyword in pairs({ 'song', 'sign', 'caption', 'commentary' }) do
- if h.str_contains(track.title, keyword) then
- return true
- end
- end
- return false
-end
-
-local function prioritize_full_subs(tracks_list)
- return table.sort(tracks_list, function(first, second)
- return (is_full(first) and not is_full(second)) or (is_garbage(second) and not is_garbage(first))
- end)
-end
-
-local function find_best_secondary_sid()
- local active_track = h.get_active_track('sub')
- local sub_tracks = h.get_loaded_tracks('sub')
- prioritize_full_subs(sub_tracks)
- for _, track in ipairs(sub_tracks) do
- if is_accepted_language(track.lang) and not is_selected_language(track, active_track) then
- return track.id
- end
- end
- return nil
-end
-
-local function window_height()
- return mp.get_property_native('osd-dimensions/h')
-end
-
-local function get_accepted_sub_langs()
- local languages = {}
- for lang in self.config.secondary_sub_lang:gmatch('[a-zA-Z-]+') do
- languages[lang] = true
- end
- return languages
-end
-
-local function on_mouse_move(_, state)
- -- state = {x=int,y=int, hover=true|false, }
- if mp.get_property_native('secondary-sid') and self.visibility == 'auto' and state ~= nil then
- mp.set_property_bool(
- 'secondary-sub-visibility',
- state.hover and (state.y / window_height()) < self.config.secondary_sub_area
- )
- end
-end
-
-local function on_file_loaded()
- -- If secondary sid is not already set, try to find and set it.
- local secondary_sid = mp.get_property_native('secondary-sid')
- if secondary_sid == false and self.config.secondary_sub_auto_load == true then
- secondary_sid = find_best_secondary_sid()
- if secondary_sid ~= nil then
- mp.set_property_native('secondary-sid', secondary_sid)
- end
- end
-end
-
-local function update_visibility()
- mp.set_property_bool('secondary-sub-visibility', self.visibility == 'always')
-end
-
-local function init(config)
- self.config = config
- self.visibility = config.secondary_sub_visibility
- self.accepted_languages = get_accepted_sub_langs()
- mp.register_event('file-loaded', on_file_loaded)
- if config.secondary_sub_area > 0 then
- mp.observe_property('mouse-pos', 'native', on_mouse_move)
- end
- update_visibility()
-end
-
-local function change_visibility()
- while true do
- self.visibility = next(self.visibility_states, self.visibility)
- if self.visibility ~= nil then
- break
- end
- end
- update_visibility()
- h.notify("Secondary sid visibility: " .. self.visibility)
-end
-
-local function compare_by_preference_then_id(track1, track2)
- if is_accepted_language(track1.lang) and not is_accepted_language(track2.lang) then
- return true
- elseif not is_accepted_language(track1.lang) and is_accepted_language(track2.lang) then
- return false
- else
- return (track1.id < track2.id)
- end
-end
-
-local function split_before_after(previous_tracks, next_tracks, all_tracks, current_track_id)
- -- works like take_while() and drop_while() combined
- local prev = true
- for _, track in ipairs(all_tracks) do
- if prev == true and track.id == current_track_id then
- prev = false
- end
- if track.id ~= current_track_id then
- if prev then
- table.insert(previous_tracks, track)
- else
- table.insert(next_tracks, track)
- end
- end
- end
-end
-
-local function not_primary_sid(track)
- return mp.get_property_native('sid') ~= track.id
-end
-
-local function find_new_secondary_sub(direction)
- local subtitle_tracks = h.filter(h.get_loaded_tracks('sub'), not_primary_sid)
- table.sort(subtitle_tracks, compare_by_preference_then_id)
-
- local secondary_sid = mp.get_property_native('secondary-sid')
- local new_secondary_sub = { id = false, title = "removed" }
-
- if #subtitle_tracks > 0 then
- if not secondary_sid then
- new_secondary_sub = (direction == 'prev') and subtitle_tracks[#subtitle_tracks] or subtitle_tracks[1]
- else
- local previous_tracks = {}
- local next_tracks = {}
- split_before_after(previous_tracks, next_tracks, subtitle_tracks, secondary_sid)
- if direction == 'prev' and #previous_tracks > 0 then
- new_secondary_sub = previous_tracks[#previous_tracks]
- elseif direction == 'next' and #next_tracks > 0 then
- new_secondary_sub = next_tracks[1]
- end
- end
- end
- return new_secondary_sub
-end
-
-local function switch_secondary_sid(direction)
- local new_secondary_sub = find_new_secondary_sub(direction)
-
- mp.set_property_native('secondary-sid', new_secondary_sub.id)
- if new_secondary_sub.id == false then
- h.notify("Removed secondary sid.")
- else
- h.notify(string.format(
- "Secondary #%d: %s (%s)",
- new_secondary_sub.id,
- new_secondary_sub.title or "No title",
- new_secondary_sub.lang or "Unknown"
- ))
- end
-end
-
-return {
- init = init,
- change_visibility = change_visibility,
- select_previous = function()
- switch_secondary_sid('prev')
- end,
- select_next = function()
- switch_secondary_sid('next')
- end,
-}
diff --git a/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua b/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua
deleted file mode 100644
index de7e1c2..0000000
--- a/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua
+++ /dev/null
@@ -1,74 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Subtitle list remembers selected subtitle lines.
-]]
-
-local h = require('helpers')
-
-local new_sub_list = function()
- local subs_list = {}
-
- local find_i = function(sub)
- for i, v in ipairs(subs_list) do
- if sub < v then
- return i
- end
- end
- return #subs_list + 1
- end
- local get_time = function(position)
- local i = position == 'start' and 1 or #subs_list
- return subs_list[i][position]
- end
- local get_text = function()
- local speech = {}
- for _, sub in ipairs(subs_list) do
- table.insert(speech, sub['text'])
- end
- return table.concat(speech, ' ')
- end
- local get_n_text = function(sub, n_lines)
- local speech = {}
- local end_sub = sub
- for _, v in ipairs(subs_list) do
- if v['start'] - end_sub['end'] >= 20 then
- break
- end
- if v >= sub and #speech < n_lines then
- table.insert(speech, v['text'])
- end_sub = v
- end
- end
- return table.concat(speech, ' '), end_sub
- end
- local insert = function(sub)
- if sub ~= nil and not h.contains(subs_list, sub) then
- table.insert(subs_list, find_i(sub), sub)
- return true
- end
- return false
- end
- local get_subs_list = function()
- local copy = {}
- for key, value in pairs(subs_list) do
- copy[key] = value
- end
- return copy
- end
- return {
- get_subs_list = get_subs_list,
- get_time = get_time,
- get_text = get_text,
- get_n_text = get_n_text,
- insert = insert,
- is_empty = function()
- return h.is_empty(subs_list)
- end,
- }
-end
-
-return {
- new = new_sub_list,
-}
diff --git a/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua b/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua
deleted file mode 100644
index 4e8df41..0000000
--- a/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua
+++ /dev/null
@@ -1,56 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Subtitle class provides methods for storing and comparing subtitle lines.
-]]
-
-local mp = require('mp')
-
-local Subtitle = {
- ['text'] = '',
- ['secondary'] = '',
- ['start'] = -1,
- ['end'] = -1,
-}
-
-function Subtitle:new(o)
- o = o or {}
- setmetatable(o, self)
- self.__index = self
- return o
-end
-
-function Subtitle:now(secondary)
- local prefix = secondary and "secondary-" or ""
- local this = self:new {
- ['text'] = mp.get_property(prefix .. "sub-text"),
- ['start'] = mp.get_property_number(prefix .. "sub-start"),
- ['end'] = mp.get_property_number(prefix .. "sub-end"),
- }
- if this:is_valid() then
- return this:delay(mp.get_property_native("sub-delay") - mp.get_property_native("audio-delay"))
- else
- return nil
- end
-end
-
-function Subtitle:delay(delay)
- self['start'] = self['start'] + delay
- self['end'] = self['end'] + delay
- return self
-end
-
-function Subtitle:is_valid()
- return self['start'] and self['end'] and self['start'] >= 0 and self['end'] > self['start']
-end
-
-Subtitle.__eq = function(lhs, rhs)
- return lhs['text'] == rhs['text']
-end
-
-Subtitle.__lt = function(lhs, rhs)
- return lhs['start'] < rhs['start']
-end
-
-return Subtitle
diff --git a/config/mpv/scripts/subs2srsa/test.lua b/config/mpv/scripts/subs2srsa/test.lua
deleted file mode 100644
index d1291d2..0000000
--- a/config/mpv/scripts/subs2srsa/test.lua
+++ /dev/null
@@ -1,32 +0,0 @@
-local helpers = require('helpers')
-
-local function assert_equals(expected, actual)
- if expected ~= actual then
- error(string.format("TEST FAILED: Expected '%s', got '%s'", expected, actual))
- end
-end
-
-local function test_get_episode_number()
- local test_cases = {
- { nil, "A Whisker Away.mkv" },
- { nil, "[Placeholder] Gekijouban SHIROBAKO [Ma10p_1080p][x265_flac]" },
- { "06", "[Placeholder] Sono Bisque Doll wa Koi wo Suru - 06 [54E495D0]" },
- { "02", "(Hi10)_Kobayashi-san_Chi_no_Maid_Dragon_-_02_(BD_1080p)_(Placeholder)_(12C5D2B4)" },
- { "01", "[Placeholder] Koi to Yobu ni wa Kimochi Warui - 01 (1080p) [D517C9F0]" },
- { "01", "[Placeholder] Tsukimonogatari 01 [BD 1080p x264 10-bit FLAC] [5CD88145]" },
- { "01", "[Placeholder] 86 - Eighty Six - 01 (1080p) [1B13598F]" },
- { "00", "[Placeholder] Fate Stay Night - Unlimited Blade Works - 00 (BD 1080p Hi10 FLAC) [95590B7F]" },
- { "01", "House, M.D. S01E01 Pilot - Everybody Lies (1080p x265 Placeholder)" },
- { "165", "A Generic Episode-165" }
- }
-
- for _, case in pairs(test_cases) do
- local _, _, episode_num = helpers.get_episode_number(case[2])
- assert_equals(case[1], episode_num)
- end
-end
-
--- Runs tests
-test_get_episode_number()
-
-os.exit(print("Tests passed"))
diff --git a/config/mpv/scripts/subs2srsa/utils/base64.lua b/config/mpv/scripts/subs2srsa/utils/base64.lua
deleted file mode 100644
index 0fe2d06..0000000
--- a/config/mpv/scripts/subs2srsa/utils/base64.lua
+++ /dev/null
@@ -1,46 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Encoding and decoding in base64
-]]
-
--- http://lua-users.org/wiki/BaseSixtyFour
-
--- character table string
-local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
-
--- encoding
-local function enc(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 b:sub(c+1,c+1)
- end)..({ '', '==', '=' })[#data%3+1])
-end
-
--- decoding
-local function dec(data)
- data = string.gsub(data, '[^'..b..'=]', '')
- return (data:gsub('.', function(x)
- if (x == '=') then return '' end
- local r,f='',(b: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
-
-return {
- enc = enc,
- dec = dec,
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/filename_factory.lua b/config/mpv/scripts/subs2srsa/utils/filename_factory.lua
deleted file mode 100644
index a794fc7..0000000
--- a/config/mpv/scripts/subs2srsa/utils/filename_factory.lua
+++ /dev/null
@@ -1,89 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Creates image and audio filenames compatible with Anki.
-]]
-
-local mp = require('mp')
-local h = require('helpers')
-
-local filename
-
-local anki_compatible_length = (function()
- -- Anki forcibly mutilates all filenames longer than 119 bytes when you run `Tools->Check Media...`.
- local allowed_bytes = 119
- local timestamp_bytes = #'_99h99m99s999ms-99h99m99s999ms.webp'
-
- return function(str, timestamp)
- -- if timestamp provided, recalculate limit_bytes
- local limit_bytes = allowed_bytes - (timestamp and #timestamp or timestamp_bytes)
-
- if #str <= limit_bytes then
- return str
- end
-
- local bytes_per_char = h.contains_non_latin_letters(str) and #'車' or #'z'
- local limit_chars = math.floor(limit_bytes / bytes_per_char)
-
- if limit_chars == limit_bytes then
- return str:sub(1, limit_bytes)
- end
-
- local ret = h.subprocess {
- 'awk',
- '-v', string.format('str=%s', str),
- '-v', string.format('limit=%d', limit_chars),
- 'BEGIN{print substr(str, 1, limit); exit}'
- }
-
- if ret.status == 0 then
- ret.stdout = h.remove_newlines(ret.stdout)
- ret.stdout = h.remove_leading_trailing_spaces(ret.stdout)
- return ret.stdout
- else
- return 'subs2srs_' .. os.time()
- end
- end
-end)()
-
-local make_media_filename = function()
- filename = mp.get_property("filename") -- filename without path
- filename = h.remove_extension(filename)
- filename = h.remove_filename_text_in_parentheses(filename)
- filename = h.remove_text_in_brackets(filename)
- filename = h.remove_special_characters(filename)
-end
-
-local function timestamp_range(start_timestamp, end_timestamp, extension)
- -- Generates a filename suffix of the form: _00h00m00s000ms-99h99m99s999ms.extension
- -- Extension must already contain the dot.
- return string.format(
- '_%s_%s%s',
- h.human_readable_time(start_timestamp),
- h.human_readable_time(end_timestamp),
- extension
- )
-end
-
-local function timestamp_static(timestamp, extension)
- -- Generates a filename suffix of the form: _00h00m00s000ms.extension
- -- Extension must already contain the dot.
- return string.format(
- '_%s%s',
- h.human_readable_time(timestamp),
- extension
- )
-end
-
-local make_filename = function(...)
- local args = {...}
- local timestamp = #args < 3 and timestamp_static(...) or timestamp_range(...)
- return string.lower(anki_compatible_length(filename, timestamp) .. timestamp)
-end
-
-mp.register_event("file-loaded", make_media_filename)
-
-return {
- make_filename = make_filename,
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/forvo.lua b/config/mpv/scripts/subs2srsa/utils/forvo.lua
deleted file mode 100644
index 09bc596..0000000
--- a/config/mpv/scripts/subs2srsa/utils/forvo.lua
+++ /dev/null
@@ -1,145 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Utils for downloading pronunciations from Forvo
-]]
-
-local utils = require('mp.utils')
-local msg = require('mp.msg')
-local h = require('helpers')
-local base64 = require('utils.base64')
-local self = {
- output_dir_path = nil,
-}
-
-local function url_encode(url)
- -- https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
- local char_to_hex = function(c)
- return string.format("%%%02X", string.byte(c))
- end
- if url == nil then
- return
- end
- url = url:gsub("\n", "\r\n")
- url = url:gsub("([^%w _%%%-%.~])", char_to_hex)
- url = url:gsub(" ", "+")
- return url
-end
-
-local function reencode(source_path, dest_path)
- local args = {
- 'mpv',
- source_path,
- '--loop-file=no',
- '--keep-open=no',
- '--video=no',
- '--no-ocopy-metadata',
- '--no-sub',
- '--audio-channels=mono',
- '--oacopts-add=vbr=on',
- '--oacopts-add=application=voip',
- '--oacopts-add=compression_level=10',
- '--af-append=silenceremove=1:0:-50dB',
- table.concat { '--oac=', self.config.audio_codec },
- table.concat { '--of=', self.config.audio_format },
- table.concat { '--oacopts-add=b=', self.config.audio_bitrate },
- table.concat { '-o=', dest_path }
- }
- return h.subprocess(args)
-end
-
-local function reencode_and_store(source_path, filename)
- local reencoded_path = utils.join_path(self.output_dir_path, filename)
- local result = reencode(source_path, reencoded_path)
- return result.status == 0
-end
-
-local function curl_save(source_url, save_location)
- local curl_args = { 'curl', source_url, '-s', '-L', '-o', save_location }
- return h.subprocess(curl_args).status == 0
-end
-
-local function get_pronunciation_url(word)
- local file_format = self.config.audio_extension:sub(2)
- local forvo_page = h.subprocess { 'curl', '-s', string.format('https://forvo.com/search/%s/ja', url_encode(word)) }.stdout
- local play_params = string.match(forvo_page, "Play%((.-)%);")
-
- if play_params then
- local iter = string.gmatch(play_params, "'(.-)'")
- local formats = { mp3 = iter(), ogg = iter() }
- return string.format('https://audio00.forvo.com/%s/%s', file_format, base64.dec(formats[file_format]))
- end
-end
-
-local function make_forvo_filename(word)
- return string.format('forvo_%s%s', self.platform.windows and os.time() or word, self.config.audio_extension)
-end
-
-local function get_forvo_pronunciation(word)
- local audio_url = get_pronunciation_url(word)
-
- if h.is_empty(audio_url) then
- msg.warn(string.format("Seems like Forvo doesn't have audio for word %s.", word))
- return
- end
-
- local filename = make_forvo_filename(word)
- local tmp_filepath = utils.join_path(self.platform.tmp_dir(), filename)
-
- local result
- if curl_save(audio_url, tmp_filepath) and reencode_and_store(tmp_filepath, filename) then
- result = string.format(self.config.audio_template, filename)
- else
- msg.warn(string.format("Couldn't download audio for word %s from Forvo.", word))
- end
-
- os.remove(tmp_filepath)
- return result
-end
-
-local append = function(new_data, stored_data)
- if self.config.use_forvo == 'no' then
- -- forvo functionality was disabled in the config file
- return new_data
- end
-
- if type(stored_data[self.config.vocab_audio_field]) ~= 'string' then
- -- there is no field configured to store forvo pronunciation
- return new_data
- end
-
- if h.is_empty(stored_data[self.config.vocab_field]) then
- -- target word field is empty. can't continue.
- return new_data
- end
-
- if self.config.use_forvo == 'always' or h.is_empty(stored_data[self.config.vocab_audio_field]) then
- local forvo_pronunciation = get_forvo_pronunciation(stored_data[self.config.vocab_field])
- if not h.is_empty(forvo_pronunciation) then
- if self.config.vocab_audio_field == self.config.audio_field then
- -- improperly configured fields. don't lose sentence audio
- new_data[self.config.audio_field] = forvo_pronunciation .. new_data[self.config.audio_field]
- else
- new_data[self.config.vocab_audio_field] = forvo_pronunciation
- end
- end
- end
-
- return new_data
-end
-
-local set_output_dir = function(dir_path)
- self.output_dir_path = dir_path
-end
-
-local function init(config, platform)
- self.config = config
- self.platform = platform
-end
-
-return {
- append = append,
- init = init,
- set_output_dir = set_output_dir,
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/pause_timer.lua b/config/mpv/scripts/subs2srsa/utils/pause_timer.lua
deleted file mode 100644
index e37b0ea..0000000
--- a/config/mpv/scripts/subs2srsa/utils/pause_timer.lua
+++ /dev/null
@@ -1,33 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Pause timer stops playback when reaching a set timing.
-]]
-
-local mp = require('mp')
-local stop_time = -1
-local check_stop
-
-local set_stop_time = function(time)
- stop_time = time
- mp.observe_property("time-pos", "number", check_stop)
-end
-
-local stop = function()
- mp.unobserve_property(check_stop)
- stop_time = -1
-end
-
-check_stop = function(_, time)
- if time > stop_time then
- stop()
- mp.set_property("pause", "yes")
- end
-end
-
-return {
- set_stop_time = set_stop_time,
- check_stop = check_stop,
- stop = stop,
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/play_control.lua b/config/mpv/scripts/subs2srsa/utils/play_control.lua
deleted file mode 100644
index 901377d..0000000
--- a/config/mpv/scripts/subs2srsa/utils/play_control.lua
+++ /dev/null
@@ -1,61 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Provides additional methods for controlling playback.
-]]
-
-local mp = require('mp')
-local h = require('helpers')
-local pause_timer = require('utils.pause_timer')
-local Subtitle = require('subtitles.subtitle')
-
-local current_sub
-
-local function stop_at_the_end(sub)
- pause_timer.set_stop_time(sub['end'] - 0.050)
- h.notify("Playing till the end of the sub...", "info", 3)
-end
-
-local function play_till_sub_end()
- local sub = Subtitle:now()
- mp.commandv('seek', sub['start'], 'absolute')
- mp.set_property("pause", "no")
- stop_at_the_end(sub)
-end
-
-local function sub_seek(direction, pause)
- mp.commandv("sub_seek", direction == 'backward' and '-1' or '1')
- mp.commandv("seek", "0.015", "relative+exact")
- if pause then
- mp.set_property("pause", "yes")
- end
- pause_timer.stop()
-end
-
-local function sub_rewind()
- mp.commandv('seek', Subtitle:now()['start'] + 0.015, 'absolute')
- pause_timer.stop()
-end
-
-local function check_sub()
- local sub = Subtitle:now()
- if sub and sub ~= current_sub then
- mp.unobserve_property(check_sub)
- stop_at_the_end(sub)
- end
-end
-
-local function play_till_next_sub_end()
- current_sub = Subtitle:now()
- mp.observe_property("sub-text", "string", check_sub)
- mp.set_property("pause", "no")
- h.notify("Waiting till next sub...", "info", 10)
-end
-
-return {
- play_till_sub_end = play_till_sub_end,
- play_till_next_sub_end = play_till_next_sub_end,
- sub_seek = sub_seek,
- sub_rewind = sub_rewind,
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/switch.lua b/config/mpv/scripts/subs2srsa/utils/switch.lua
deleted file mode 100644
index 5dac1c6..0000000
--- a/config/mpv/scripts/subs2srsa/utils/switch.lua
+++ /dev/null
@@ -1,38 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Switch cycles between values in a table.
-]]
-
-local make_switch = function(states)
- local self = {
- states = states,
- current_state = 1
- }
- local bump = function()
- self.current_state = self.current_state + 1
- if self.current_state > #self.states then
- self.current_state = 1
- end
- end
- local get = function()
- return self.states[self.current_state]
- end
- local set = function(new_state)
- for idx, value in ipairs(self.states) do
- if value == new_state then
- self.current_state = idx
- end
- end
- end
- return {
- bump = bump,
- get = get,
- set = set,
- }
-end
-
-return {
- new = make_switch
-}
diff --git a/config/mpv/scripts/subs2srsa/utils/timings.lua b/config/mpv/scripts/subs2srsa/utils/timings.lua
deleted file mode 100644
index d2408d6..0000000
--- a/config/mpv/scripts/subs2srsa/utils/timings.lua
+++ /dev/null
@@ -1,28 +0,0 @@
---[[
-Copyright: Ren Tatsumoto and contributors
-License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html
-
-Object that remembers manually set timings.
-]]
-
-local new_timings = function()
- local self = { ['start'] = -1, ['end'] = -1, }
- local is_set = function(position)
- return self[position] >= 0
- end
- local set = function(position, time)
- self[position] = time
- end
- local get = function(position)
- return self[position]
- end
- return {
- is_set = is_set,
- set = set,
- get = get,
- }
-end
-
-return {
- new = new_timings,
-}