Spaces:
Sleeping
Sleeping
Upload 85 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +11 -0
- LICENSE +674 -0
- README.md +154 -13
- README_en.md +155 -0
- ViralCutter.ipynb +253 -0
- abrir_cmd.bat +16 -0
- api_config.json +12 -0
- changelog.md +94 -0
- check_landmarks.py +49 -0
- i18n/__pycache__/i18n.cpython-310.pyc +0 -0
- i18n/__pycache__/i18n.cpython-311.pyc +0 -0
- i18n/i18n.py +27 -0
- i18n/locale/en_US.json +211 -0
- i18n/locale/pt_BR.json +211 -0
- i18n/locale_diff.py +47 -0
- i18n/scan_i18n.py +75 -0
- install_dependencies.bat +27 -0
- main_improved.py +695 -0
- models/.local-model-here +0 -0
- prompt.txt +42 -0
- requirements.txt +23 -0
- run.bat +9 -0
- run_webui.bat +4 -0
- scripts/__pycache__/adjust_subtitles.cpython-310.pyc +0 -0
- scripts/__pycache__/adjust_subtitles.cpython-311.pyc +0 -0
- scripts/__pycache__/burn_subtitles.cpython-310.pyc +0 -0
- scripts/__pycache__/burn_subtitles.cpython-311.pyc +0 -0
- scripts/__pycache__/create_viral_segments.cpython-310.pyc +0 -0
- scripts/__pycache__/create_viral_segments.cpython-311.pyc +0 -0
- scripts/__pycache__/cut_json.cpython-310.pyc +0 -0
- scripts/__pycache__/cut_json.cpython-311.pyc +0 -0
- scripts/__pycache__/cut_segments.cpython-310.pyc +0 -0
- scripts/__pycache__/cut_segments.cpython-311.pyc +0 -0
- scripts/__pycache__/download_video.cpython-310.pyc +0 -0
- scripts/__pycache__/download_video.cpython-311.pyc +0 -0
- scripts/__pycache__/edit_video.cpython-310.pyc +0 -0
- scripts/__pycache__/edit_video.cpython-311.pyc +0 -0
- scripts/__pycache__/face_detection_insightface.cpython-310.pyc +0 -0
- scripts/__pycache__/face_detection_insightface.cpython-311.pyc +0 -0
- scripts/__pycache__/generate_premiere_xml.cpython-311.pyc +0 -0
- scripts/__pycache__/one_face.cpython-310.pyc +0 -0
- scripts/__pycache__/one_face.cpython-311.pyc +0 -0
- scripts/__pycache__/organize_output.cpython-310.pyc +0 -0
- scripts/__pycache__/organize_output.cpython-311.pyc +0 -0
- scripts/__pycache__/save_json.cpython-310.pyc +0 -0
- scripts/__pycache__/save_json.cpython-311.pyc +0 -0
- scripts/__pycache__/transcribe_cuts.cpython-310.pyc +0 -0
- scripts/__pycache__/transcribe_cuts.cpython-311.pyc +0 -0
- scripts/__pycache__/transcribe_video.cpython-310.pyc +0 -0
- scripts/__pycache__/transcribe_video.cpython-311.pyc +0 -0
.gitignore
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
env
|
| 2 |
+
.gitignore
|
| 3 |
+
scripts\__pycache__\*
|
| 4 |
+
i18n\__pycache__\*
|
| 5 |
+
*.pyc
|
| 6 |
+
VIRALS
|
| 7 |
+
*.7z
|
| 8 |
+
*.zip
|
| 9 |
+
api_config.json
|
| 10 |
+
versions
|
| 11 |
+
*.gguf
|
LICENSE
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
GNU GENERAL PUBLIC LICENSE
|
| 2 |
+
Version 3, 29 June 2007
|
| 3 |
+
|
| 4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
| 5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
| 6 |
+
of this license document, but changing it is not allowed.
|
| 7 |
+
|
| 8 |
+
Preamble
|
| 9 |
+
|
| 10 |
+
The GNU General Public License is a free, copyleft license for
|
| 11 |
+
software and other kinds of works.
|
| 12 |
+
|
| 13 |
+
The licenses for most software and other practical works are designed
|
| 14 |
+
to take away your freedom to share and change the works. By contrast,
|
| 15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
| 16 |
+
share and change all versions of a program--to make sure it remains free
|
| 17 |
+
software for all its users. We, the Free Software Foundation, use the
|
| 18 |
+
GNU General Public License for most of our software; it applies also to
|
| 19 |
+
any other work released this way by its authors. You can apply it to
|
| 20 |
+
your programs, too.
|
| 21 |
+
|
| 22 |
+
When we speak of free software, we are referring to freedom, not
|
| 23 |
+
price. Our General Public Licenses are designed to make sure that you
|
| 24 |
+
have the freedom to distribute copies of free software (and charge for
|
| 25 |
+
them if you wish), that you receive source code or can get it if you
|
| 26 |
+
want it, that you can change the software or use pieces of it in new
|
| 27 |
+
free programs, and that you know you can do these things.
|
| 28 |
+
|
| 29 |
+
To protect your rights, we need to prevent others from denying you
|
| 30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
| 31 |
+
certain responsibilities if you distribute copies of the software, or if
|
| 32 |
+
you modify it: responsibilities to respect the freedom of others.
|
| 33 |
+
|
| 34 |
+
For example, if you distribute copies of such a program, whether
|
| 35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
| 36 |
+
freedoms that you received. You must make sure that they, too, receive
|
| 37 |
+
or can get the source code. And you must show them these terms so they
|
| 38 |
+
know their rights.
|
| 39 |
+
|
| 40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
| 41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
| 42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
| 43 |
+
|
| 44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
| 45 |
+
that there is no warranty for this free software. For both users' and
|
| 46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
| 47 |
+
changed, so that their problems will not be attributed erroneously to
|
| 48 |
+
authors of previous versions.
|
| 49 |
+
|
| 50 |
+
Some devices are designed to deny users access to install or run
|
| 51 |
+
modified versions of the software inside them, although the manufacturer
|
| 52 |
+
can do so. This is fundamentally incompatible with the aim of
|
| 53 |
+
protecting users' freedom to change the software. The systematic
|
| 54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
| 55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
| 56 |
+
have designed this version of the GPL to prohibit the practice for those
|
| 57 |
+
products. If such problems arise substantially in other domains, we
|
| 58 |
+
stand ready to extend this provision to those domains in future versions
|
| 59 |
+
of the GPL, as needed to protect the freedom of users.
|
| 60 |
+
|
| 61 |
+
Finally, every program is threatened constantly by software patents.
|
| 62 |
+
States should not allow patents to restrict development and use of
|
| 63 |
+
software on general-purpose computers, but in those that do, we wish to
|
| 64 |
+
avoid the special danger that patents applied to a free program could
|
| 65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
| 66 |
+
patents cannot be used to render the program non-free.
|
| 67 |
+
|
| 68 |
+
The precise terms and conditions for copying, distribution and
|
| 69 |
+
modification follow.
|
| 70 |
+
|
| 71 |
+
TERMS AND CONDITIONS
|
| 72 |
+
|
| 73 |
+
0. Definitions.
|
| 74 |
+
|
| 75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
| 76 |
+
|
| 77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
| 78 |
+
works, such as semiconductor masks.
|
| 79 |
+
|
| 80 |
+
"The Program" refers to any copyrightable work licensed under this
|
| 81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
| 82 |
+
"recipients" may be individuals or organizations.
|
| 83 |
+
|
| 84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
| 85 |
+
in a fashion requiring copyright permission, other than the making of an
|
| 86 |
+
exact copy. The resulting work is called a "modified version" of the
|
| 87 |
+
earlier work or a work "based on" the earlier work.
|
| 88 |
+
|
| 89 |
+
A "covered work" means either the unmodified Program or a work based
|
| 90 |
+
on the Program.
|
| 91 |
+
|
| 92 |
+
To "propagate" a work means to do anything with it that, without
|
| 93 |
+
permission, would make you directly or secondarily liable for
|
| 94 |
+
infringement under applicable copyright law, except executing it on a
|
| 95 |
+
computer or modifying a private copy. Propagation includes copying,
|
| 96 |
+
distribution (with or without modification), making available to the
|
| 97 |
+
public, and in some countries other activities as well.
|
| 98 |
+
|
| 99 |
+
To "convey" a work means any kind of propagation that enables other
|
| 100 |
+
parties to make or receive copies. Mere interaction with a user through
|
| 101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
| 102 |
+
|
| 103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
| 104 |
+
to the extent that it includes a convenient and prominently visible
|
| 105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
| 106 |
+
tells the user that there is no warranty for the work (except to the
|
| 107 |
+
extent that warranties are provided), that licensees may convey the
|
| 108 |
+
work under this License, and how to view a copy of this License. If
|
| 109 |
+
the interface presents a list of user commands or options, such as a
|
| 110 |
+
menu, a prominent item in the list meets this criterion.
|
| 111 |
+
|
| 112 |
+
1. Source Code.
|
| 113 |
+
|
| 114 |
+
The "source code" for a work means the preferred form of the work
|
| 115 |
+
for making modifications to it. "Object code" means any non-source
|
| 116 |
+
form of a work.
|
| 117 |
+
|
| 118 |
+
A "Standard Interface" means an interface that either is an official
|
| 119 |
+
standard defined by a recognized standards body, or, in the case of
|
| 120 |
+
interfaces specified for a particular programming language, one that
|
| 121 |
+
is widely used among developers working in that language.
|
| 122 |
+
|
| 123 |
+
The "System Libraries" of an executable work include anything, other
|
| 124 |
+
than the work as a whole, that (a) is included in the normal form of
|
| 125 |
+
packaging a Major Component, but which is not part of that Major
|
| 126 |
+
Component, and (b) serves only to enable use of the work with that
|
| 127 |
+
Major Component, or to implement a Standard Interface for which an
|
| 128 |
+
implementation is available to the public in source code form. A
|
| 129 |
+
"Major Component", in this context, means a major essential component
|
| 130 |
+
(kernel, window system, and so on) of the specific operating system
|
| 131 |
+
(if any) on which the executable work runs, or a compiler used to
|
| 132 |
+
produce the work, or an object code interpreter used to run it.
|
| 133 |
+
|
| 134 |
+
The "Corresponding Source" for a work in object code form means all
|
| 135 |
+
the source code needed to generate, install, and (for an executable
|
| 136 |
+
work) run the object code and to modify the work, including scripts to
|
| 137 |
+
control those activities. However, it does not include the work's
|
| 138 |
+
System Libraries, or general-purpose tools or generally available free
|
| 139 |
+
programs which are used unmodified in performing those activities but
|
| 140 |
+
which are not part of the work. For example, Corresponding Source
|
| 141 |
+
includes interface definition files associated with source files for
|
| 142 |
+
the work, and the source code for shared libraries and dynamically
|
| 143 |
+
linked subprograms that the work is specifically designed to require,
|
| 144 |
+
such as by intimate data communication or control flow between those
|
| 145 |
+
subprograms and other parts of the work.
|
| 146 |
+
|
| 147 |
+
The Corresponding Source need not include anything that users
|
| 148 |
+
can regenerate automatically from other parts of the Corresponding
|
| 149 |
+
Source.
|
| 150 |
+
|
| 151 |
+
The Corresponding Source for a work in source code form is that
|
| 152 |
+
same work.
|
| 153 |
+
|
| 154 |
+
2. Basic Permissions.
|
| 155 |
+
|
| 156 |
+
All rights granted under this License are granted for the term of
|
| 157 |
+
copyright on the Program, and are irrevocable provided the stated
|
| 158 |
+
conditions are met. This License explicitly affirms your unlimited
|
| 159 |
+
permission to run the unmodified Program. The output from running a
|
| 160 |
+
covered work is covered by this License only if the output, given its
|
| 161 |
+
content, constitutes a covered work. This License acknowledges your
|
| 162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
| 163 |
+
|
| 164 |
+
You may make, run and propagate covered works that you do not
|
| 165 |
+
convey, without conditions so long as your license otherwise remains
|
| 166 |
+
in force. You may convey covered works to others for the sole purpose
|
| 167 |
+
of having them make modifications exclusively for you, or provide you
|
| 168 |
+
with facilities for running those works, provided that you comply with
|
| 169 |
+
the terms of this License in conveying all material for which you do
|
| 170 |
+
not control copyright. Those thus making or running the covered works
|
| 171 |
+
for you must do so exclusively on your behalf, under your direction
|
| 172 |
+
and control, on terms that prohibit them from making any copies of
|
| 173 |
+
your copyrighted material outside their relationship with you.
|
| 174 |
+
|
| 175 |
+
Conveying under any other circumstances is permitted solely under
|
| 176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
| 177 |
+
makes it unnecessary.
|
| 178 |
+
|
| 179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
| 180 |
+
|
| 181 |
+
No covered work shall be deemed part of an effective technological
|
| 182 |
+
measure under any applicable law fulfilling obligations under article
|
| 183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
| 184 |
+
similar laws prohibiting or restricting circumvention of such
|
| 185 |
+
measures.
|
| 186 |
+
|
| 187 |
+
When you convey a covered work, you waive any legal power to forbid
|
| 188 |
+
circumvention of technological measures to the extent such circumvention
|
| 189 |
+
is effected by exercising rights under this License with respect to
|
| 190 |
+
the covered work, and you disclaim any intention to limit operation or
|
| 191 |
+
modification of the work as a means of enforcing, against the work's
|
| 192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
| 193 |
+
technological measures.
|
| 194 |
+
|
| 195 |
+
4. Conveying Verbatim Copies.
|
| 196 |
+
|
| 197 |
+
You may convey verbatim copies of the Program's source code as you
|
| 198 |
+
receive it, in any medium, provided that you conspicuously and
|
| 199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
| 200 |
+
keep intact all notices stating that this License and any
|
| 201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
| 202 |
+
keep intact all notices of the absence of any warranty; and give all
|
| 203 |
+
recipients a copy of this License along with the Program.
|
| 204 |
+
|
| 205 |
+
You may charge any price or no price for each copy that you convey,
|
| 206 |
+
and you may offer support or warranty protection for a fee.
|
| 207 |
+
|
| 208 |
+
5. Conveying Modified Source Versions.
|
| 209 |
+
|
| 210 |
+
You may convey a work based on the Program, or the modifications to
|
| 211 |
+
produce it from the Program, in the form of source code under the
|
| 212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
| 213 |
+
|
| 214 |
+
a) The work must carry prominent notices stating that you modified
|
| 215 |
+
it, and giving a relevant date.
|
| 216 |
+
|
| 217 |
+
b) The work must carry prominent notices stating that it is
|
| 218 |
+
released under this License and any conditions added under section
|
| 219 |
+
7. This requirement modifies the requirement in section 4 to
|
| 220 |
+
"keep intact all notices".
|
| 221 |
+
|
| 222 |
+
c) You must license the entire work, as a whole, under this
|
| 223 |
+
License to anyone who comes into possession of a copy. This
|
| 224 |
+
License will therefore apply, along with any applicable section 7
|
| 225 |
+
additional terms, to the whole of the work, and all its parts,
|
| 226 |
+
regardless of how they are packaged. This License gives no
|
| 227 |
+
permission to license the work in any other way, but it does not
|
| 228 |
+
invalidate such permission if you have separately received it.
|
| 229 |
+
|
| 230 |
+
d) If the work has interactive user interfaces, each must display
|
| 231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
| 232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
| 233 |
+
work need not make them do so.
|
| 234 |
+
|
| 235 |
+
A compilation of a covered work with other separate and independent
|
| 236 |
+
works, which are not by their nature extensions of the covered work,
|
| 237 |
+
and which are not combined with it such as to form a larger program,
|
| 238 |
+
in or on a volume of a storage or distribution medium, is called an
|
| 239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
| 240 |
+
used to limit the access or legal rights of the compilation's users
|
| 241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
| 242 |
+
in an aggregate does not cause this License to apply to the other
|
| 243 |
+
parts of the aggregate.
|
| 244 |
+
|
| 245 |
+
6. Conveying Non-Source Forms.
|
| 246 |
+
|
| 247 |
+
You may convey a covered work in object code form under the terms
|
| 248 |
+
of sections 4 and 5, provided that you also convey the
|
| 249 |
+
machine-readable Corresponding Source under the terms of this License,
|
| 250 |
+
in one of these ways:
|
| 251 |
+
|
| 252 |
+
a) Convey the object code in, or embodied in, a physical product
|
| 253 |
+
(including a physical distribution medium), accompanied by the
|
| 254 |
+
Corresponding Source fixed on a durable physical medium
|
| 255 |
+
customarily used for software interchange.
|
| 256 |
+
|
| 257 |
+
b) Convey the object code in, or embodied in, a physical product
|
| 258 |
+
(including a physical distribution medium), accompanied by a
|
| 259 |
+
written offer, valid for at least three years and valid for as
|
| 260 |
+
long as you offer spare parts or customer support for that product
|
| 261 |
+
model, to give anyone who possesses the object code either (1) a
|
| 262 |
+
copy of the Corresponding Source for all the software in the
|
| 263 |
+
product that is covered by this License, on a durable physical
|
| 264 |
+
medium customarily used for software interchange, for a price no
|
| 265 |
+
more than your reasonable cost of physically performing this
|
| 266 |
+
conveying of source, or (2) access to copy the
|
| 267 |
+
Corresponding Source from a network server at no charge.
|
| 268 |
+
|
| 269 |
+
c) Convey individual copies of the object code with a copy of the
|
| 270 |
+
written offer to provide the Corresponding Source. This
|
| 271 |
+
alternative is allowed only occasionally and noncommercially, and
|
| 272 |
+
only if you received the object code with such an offer, in accord
|
| 273 |
+
with subsection 6b.
|
| 274 |
+
|
| 275 |
+
d) Convey the object code by offering access from a designated
|
| 276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
| 277 |
+
Corresponding Source in the same way through the same place at no
|
| 278 |
+
further charge. You need not require recipients to copy the
|
| 279 |
+
Corresponding Source along with the object code. If the place to
|
| 280 |
+
copy the object code is a network server, the Corresponding Source
|
| 281 |
+
may be on a different server (operated by you or a third party)
|
| 282 |
+
that supports equivalent copying facilities, provided you maintain
|
| 283 |
+
clear directions next to the object code saying where to find the
|
| 284 |
+
Corresponding Source. Regardless of what server hosts the
|
| 285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
| 286 |
+
available for as long as needed to satisfy these requirements.
|
| 287 |
+
|
| 288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
| 289 |
+
you inform other peers where the object code and Corresponding
|
| 290 |
+
Source of the work are being offered to the general public at no
|
| 291 |
+
charge under subsection 6d.
|
| 292 |
+
|
| 293 |
+
A separable portion of the object code, whose source code is excluded
|
| 294 |
+
from the Corresponding Source as a System Library, need not be
|
| 295 |
+
included in conveying the object code work.
|
| 296 |
+
|
| 297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
| 298 |
+
tangible personal property which is normally used for personal, family,
|
| 299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
| 300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
| 301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
| 302 |
+
product received by a particular user, "normally used" refers to a
|
| 303 |
+
typical or common use of that class of product, regardless of the status
|
| 304 |
+
of the particular user or of the way in which the particular user
|
| 305 |
+
actually uses, or expects or is expected to use, the product. A product
|
| 306 |
+
is a consumer product regardless of whether the product has substantial
|
| 307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
| 308 |
+
the only significant mode of use of the product.
|
| 309 |
+
|
| 310 |
+
"Installation Information" for a User Product means any methods,
|
| 311 |
+
procedures, authorization keys, or other information required to install
|
| 312 |
+
and execute modified versions of a covered work in that User Product from
|
| 313 |
+
a modified version of its Corresponding Source. The information must
|
| 314 |
+
suffice to ensure that the continued functioning of the modified object
|
| 315 |
+
code is in no case prevented or interfered with solely because
|
| 316 |
+
modification has been made.
|
| 317 |
+
|
| 318 |
+
If you convey an object code work under this section in, or with, or
|
| 319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
| 320 |
+
part of a transaction in which the right of possession and use of the
|
| 321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
| 322 |
+
fixed term (regardless of how the transaction is characterized), the
|
| 323 |
+
Corresponding Source conveyed under this section must be accompanied
|
| 324 |
+
by the Installation Information. But this requirement does not apply
|
| 325 |
+
if neither you nor any third party retains the ability to install
|
| 326 |
+
modified object code on the User Product (for example, the work has
|
| 327 |
+
been installed in ROM).
|
| 328 |
+
|
| 329 |
+
The requirement to provide Installation Information does not include a
|
| 330 |
+
requirement to continue to provide support service, warranty, or updates
|
| 331 |
+
for a work that has been modified or installed by the recipient, or for
|
| 332 |
+
the User Product in which it has been modified or installed. Access to a
|
| 333 |
+
network may be denied when the modification itself materially and
|
| 334 |
+
adversely affects the operation of the network or violates the rules and
|
| 335 |
+
protocols for communication across the network.
|
| 336 |
+
|
| 337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
| 338 |
+
in accord with this section must be in a format that is publicly
|
| 339 |
+
documented (and with an implementation available to the public in
|
| 340 |
+
source code form), and must require no special password or key for
|
| 341 |
+
unpacking, reading or copying.
|
| 342 |
+
|
| 343 |
+
7. Additional Terms.
|
| 344 |
+
|
| 345 |
+
"Additional permissions" are terms that supplement the terms of this
|
| 346 |
+
License by making exceptions from one or more of its conditions.
|
| 347 |
+
Additional permissions that are applicable to the entire Program shall
|
| 348 |
+
be treated as though they were included in this License, to the extent
|
| 349 |
+
that they are valid under applicable law. If additional permissions
|
| 350 |
+
apply only to part of the Program, that part may be used separately
|
| 351 |
+
under those permissions, but the entire Program remains governed by
|
| 352 |
+
this License without regard to the additional permissions.
|
| 353 |
+
|
| 354 |
+
When you convey a copy of a covered work, you may at your option
|
| 355 |
+
remove any additional permissions from that copy, or from any part of
|
| 356 |
+
it. (Additional permissions may be written to require their own
|
| 357 |
+
removal in certain cases when you modify the work.) You may place
|
| 358 |
+
additional permissions on material, added by you to a covered work,
|
| 359 |
+
for which you have or can give appropriate copyright permission.
|
| 360 |
+
|
| 361 |
+
Notwithstanding any other provision of this License, for material you
|
| 362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
| 363 |
+
that material) supplement the terms of this License with terms:
|
| 364 |
+
|
| 365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
| 366 |
+
terms of sections 15 and 16 of this License; or
|
| 367 |
+
|
| 368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
| 369 |
+
author attributions in that material or in the Appropriate Legal
|
| 370 |
+
Notices displayed by works containing it; or
|
| 371 |
+
|
| 372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
| 373 |
+
requiring that modified versions of such material be marked in
|
| 374 |
+
reasonable ways as different from the original version; or
|
| 375 |
+
|
| 376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
| 377 |
+
authors of the material; or
|
| 378 |
+
|
| 379 |
+
e) Declining to grant rights under trademark law for use of some
|
| 380 |
+
trade names, trademarks, or service marks; or
|
| 381 |
+
|
| 382 |
+
f) Requiring indemnification of licensors and authors of that
|
| 383 |
+
material by anyone who conveys the material (or modified versions of
|
| 384 |
+
it) with contractual assumptions of liability to the recipient, for
|
| 385 |
+
any liability that these contractual assumptions directly impose on
|
| 386 |
+
those licensors and authors.
|
| 387 |
+
|
| 388 |
+
All other non-permissive additional terms are considered "further
|
| 389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
| 390 |
+
received it, or any part of it, contains a notice stating that it is
|
| 391 |
+
governed by this License along with a term that is a further
|
| 392 |
+
restriction, you may remove that term. If a license document contains
|
| 393 |
+
a further restriction but permits relicensing or conveying under this
|
| 394 |
+
License, you may add to a covered work material governed by the terms
|
| 395 |
+
of that license document, provided that the further restriction does
|
| 396 |
+
not survive such relicensing or conveying.
|
| 397 |
+
|
| 398 |
+
If you add terms to a covered work in accord with this section, you
|
| 399 |
+
must place, in the relevant source files, a statement of the
|
| 400 |
+
additional terms that apply to those files, or a notice indicating
|
| 401 |
+
where to find the applicable terms.
|
| 402 |
+
|
| 403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
| 404 |
+
form of a separately written license, or stated as exceptions;
|
| 405 |
+
the above requirements apply either way.
|
| 406 |
+
|
| 407 |
+
8. Termination.
|
| 408 |
+
|
| 409 |
+
You may not propagate or modify a covered work except as expressly
|
| 410 |
+
provided under this License. Any attempt otherwise to propagate or
|
| 411 |
+
modify it is void, and will automatically terminate your rights under
|
| 412 |
+
this License (including any patent licenses granted under the third
|
| 413 |
+
paragraph of section 11).
|
| 414 |
+
|
| 415 |
+
However, if you cease all violation of this License, then your
|
| 416 |
+
license from a particular copyright holder is reinstated (a)
|
| 417 |
+
provisionally, unless and until the copyright holder explicitly and
|
| 418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
| 419 |
+
holder fails to notify you of the violation by some reasonable means
|
| 420 |
+
prior to 60 days after the cessation.
|
| 421 |
+
|
| 422 |
+
Moreover, your license from a particular copyright holder is
|
| 423 |
+
reinstated permanently if the copyright holder notifies you of the
|
| 424 |
+
violation by some reasonable means, this is the first time you have
|
| 425 |
+
received notice of violation of this License (for any work) from that
|
| 426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
| 427 |
+
your receipt of the notice.
|
| 428 |
+
|
| 429 |
+
Termination of your rights under this section does not terminate the
|
| 430 |
+
licenses of parties who have received copies or rights from you under
|
| 431 |
+
this License. If your rights have been terminated and not permanently
|
| 432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
| 433 |
+
material under section 10.
|
| 434 |
+
|
| 435 |
+
9. Acceptance Not Required for Having Copies.
|
| 436 |
+
|
| 437 |
+
You are not required to accept this License in order to receive or
|
| 438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
| 439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
| 440 |
+
to receive a copy likewise does not require acceptance. However,
|
| 441 |
+
nothing other than this License grants you permission to propagate or
|
| 442 |
+
modify any covered work. These actions infringe copyright if you do
|
| 443 |
+
not accept this License. Therefore, by modifying or propagating a
|
| 444 |
+
covered work, you indicate your acceptance of this License to do so.
|
| 445 |
+
|
| 446 |
+
10. Automatic Licensing of Downstream Recipients.
|
| 447 |
+
|
| 448 |
+
Each time you convey a covered work, the recipient automatically
|
| 449 |
+
receives a license from the original licensors, to run, modify and
|
| 450 |
+
propagate that work, subject to this License. You are not responsible
|
| 451 |
+
for enforcing compliance by third parties with this License.
|
| 452 |
+
|
| 453 |
+
An "entity transaction" is a transaction transferring control of an
|
| 454 |
+
organization, or substantially all assets of one, or subdividing an
|
| 455 |
+
organization, or merging organizations. If propagation of a covered
|
| 456 |
+
work results from an entity transaction, each party to that
|
| 457 |
+
transaction who receives a copy of the work also receives whatever
|
| 458 |
+
licenses to the work the party's predecessor in interest had or could
|
| 459 |
+
give under the previous paragraph, plus a right to possession of the
|
| 460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
| 461 |
+
the predecessor has it or can get it with reasonable efforts.
|
| 462 |
+
|
| 463 |
+
You may not impose any further restrictions on the exercise of the
|
| 464 |
+
rights granted or affirmed under this License. For example, you may
|
| 465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
| 466 |
+
rights granted under this License, and you may not initiate litigation
|
| 467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
| 468 |
+
any patent claim is infringed by making, using, selling, offering for
|
| 469 |
+
sale, or importing the Program or any portion of it.
|
| 470 |
+
|
| 471 |
+
11. Patents.
|
| 472 |
+
|
| 473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
| 474 |
+
License of the Program or a work on which the Program is based. The
|
| 475 |
+
work thus licensed is called the contributor's "contributor version".
|
| 476 |
+
|
| 477 |
+
A contributor's "essential patent claims" are all patent claims
|
| 478 |
+
owned or controlled by the contributor, whether already acquired or
|
| 479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
| 480 |
+
by this License, of making, using, or selling its contributor version,
|
| 481 |
+
but do not include claims that would be infringed only as a
|
| 482 |
+
consequence of further modification of the contributor version. For
|
| 483 |
+
purposes of this definition, "control" includes the right to grant
|
| 484 |
+
patent sublicenses in a manner consistent with the requirements of
|
| 485 |
+
this License.
|
| 486 |
+
|
| 487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
| 488 |
+
patent license under the contributor's essential patent claims, to
|
| 489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
| 490 |
+
propagate the contents of its contributor version.
|
| 491 |
+
|
| 492 |
+
In the following three paragraphs, a "patent license" is any express
|
| 493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
| 494 |
+
(such as an express permission to practice a patent or covenant not to
|
| 495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
| 496 |
+
party means to make such an agreement or commitment not to enforce a
|
| 497 |
+
patent against the party.
|
| 498 |
+
|
| 499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
| 500 |
+
and the Corresponding Source of the work is not available for anyone
|
| 501 |
+
to copy, free of charge and under the terms of this License, through a
|
| 502 |
+
publicly available network server or other readily accessible means,
|
| 503 |
+
then you must either (1) cause the Corresponding Source to be so
|
| 504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
| 505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
| 506 |
+
consistent with the requirements of this License, to extend the patent
|
| 507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
| 508 |
+
actual knowledge that, but for the patent license, your conveying the
|
| 509 |
+
covered work in a country, or your recipient's use of the covered work
|
| 510 |
+
in a country, would infringe one or more identifiable patents in that
|
| 511 |
+
country that you have reason to believe are valid.
|
| 512 |
+
|
| 513 |
+
If, pursuant to or in connection with a single transaction or
|
| 514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
| 515 |
+
covered work, and grant a patent license to some of the parties
|
| 516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
| 517 |
+
or convey a specific copy of the covered work, then the patent license
|
| 518 |
+
you grant is automatically extended to all recipients of the covered
|
| 519 |
+
work and works based on it.
|
| 520 |
+
|
| 521 |
+
A patent license is "discriminatory" if it does not include within
|
| 522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
| 523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
| 524 |
+
specifically granted under this License. You may not convey a covered
|
| 525 |
+
work if you are a party to an arrangement with a third party that is
|
| 526 |
+
in the business of distributing software, under which you make payment
|
| 527 |
+
to the third party based on the extent of your activity of conveying
|
| 528 |
+
the work, and under which the third party grants, to any of the
|
| 529 |
+
parties who would receive the covered work from you, a discriminatory
|
| 530 |
+
patent license (a) in connection with copies of the covered work
|
| 531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
| 532 |
+
for and in connection with specific products or compilations that
|
| 533 |
+
contain the covered work, unless you entered into that arrangement,
|
| 534 |
+
or that patent license was granted, prior to 28 March 2007.
|
| 535 |
+
|
| 536 |
+
Nothing in this License shall be construed as excluding or limiting
|
| 537 |
+
any implied license or other defenses to infringement that may
|
| 538 |
+
otherwise be available to you under applicable patent law.
|
| 539 |
+
|
| 540 |
+
12. No Surrender of Others' Freedom.
|
| 541 |
+
|
| 542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
| 543 |
+
otherwise) that contradict the conditions of this License, they do not
|
| 544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
| 545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
| 546 |
+
License and any other pertinent obligations, then as a consequence you may
|
| 547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
| 548 |
+
to collect a royalty for further conveying from those to whom you convey
|
| 549 |
+
the Program, the only way you could satisfy both those terms and this
|
| 550 |
+
License would be to refrain entirely from conveying the Program.
|
| 551 |
+
|
| 552 |
+
13. Use with the GNU Affero General Public License.
|
| 553 |
+
|
| 554 |
+
Notwithstanding any other provision of this License, you have
|
| 555 |
+
permission to link or combine any covered work with a work licensed
|
| 556 |
+
under version 3 of the GNU Affero General Public License into a single
|
| 557 |
+
combined work, and to convey the resulting work. The terms of this
|
| 558 |
+
License will continue to apply to the part which is the covered work,
|
| 559 |
+
but the special requirements of the GNU Affero General Public License,
|
| 560 |
+
section 13, concerning interaction through a network will apply to the
|
| 561 |
+
combination as such.
|
| 562 |
+
|
| 563 |
+
14. Revised Versions of this License.
|
| 564 |
+
|
| 565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
| 566 |
+
the GNU General Public License from time to time. Such new versions will
|
| 567 |
+
be similar in spirit to the present version, but may differ in detail to
|
| 568 |
+
address new problems or concerns.
|
| 569 |
+
|
| 570 |
+
Each version is given a distinguishing version number. If the
|
| 571 |
+
Program specifies that a certain numbered version of the GNU General
|
| 572 |
+
Public License "or any later version" applies to it, you have the
|
| 573 |
+
option of following the terms and conditions either of that numbered
|
| 574 |
+
version or of any later version published by the Free Software
|
| 575 |
+
Foundation. If the Program does not specify a version number of the
|
| 576 |
+
GNU General Public License, you may choose any version ever published
|
| 577 |
+
by the Free Software Foundation.
|
| 578 |
+
|
| 579 |
+
If the Program specifies that a proxy can decide which future
|
| 580 |
+
versions of the GNU General Public License can be used, that proxy's
|
| 581 |
+
public statement of acceptance of a version permanently authorizes you
|
| 582 |
+
to choose that version for the Program.
|
| 583 |
+
|
| 584 |
+
Later license versions may give you additional or different
|
| 585 |
+
permissions. However, no additional obligations are imposed on any
|
| 586 |
+
author or copyright holder as a result of your choosing to follow a
|
| 587 |
+
later version.
|
| 588 |
+
|
| 589 |
+
15. Disclaimer of Warranty.
|
| 590 |
+
|
| 591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
| 592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
| 593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
| 594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
| 595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
| 596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
| 597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
| 598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
| 599 |
+
|
| 600 |
+
16. Limitation of Liability.
|
| 601 |
+
|
| 602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
| 603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
| 604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
| 605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
| 606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
| 607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
| 608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
| 609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
| 610 |
+
SUCH DAMAGES.
|
| 611 |
+
|
| 612 |
+
17. Interpretation of Sections 15 and 16.
|
| 613 |
+
|
| 614 |
+
If the disclaimer of warranty and limitation of liability provided
|
| 615 |
+
above cannot be given local legal effect according to their terms,
|
| 616 |
+
reviewing courts shall apply local law that most closely approximates
|
| 617 |
+
an absolute waiver of all civil liability in connection with the
|
| 618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
| 619 |
+
copy of the Program in return for a fee.
|
| 620 |
+
|
| 621 |
+
END OF TERMS AND CONDITIONS
|
| 622 |
+
|
| 623 |
+
How to Apply These Terms to Your New Programs
|
| 624 |
+
|
| 625 |
+
If you develop a new program, and you want it to be of the greatest
|
| 626 |
+
possible use to the public, the best way to achieve this is to make it
|
| 627 |
+
free software which everyone can redistribute and change under these terms.
|
| 628 |
+
|
| 629 |
+
To do so, attach the following notices to the program. It is safest
|
| 630 |
+
to attach them to the start of each source file to most effectively
|
| 631 |
+
state the exclusion of warranty; and each file should have at least
|
| 632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
| 633 |
+
|
| 634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
| 635 |
+
Copyright (C) <year> <name of author>
|
| 636 |
+
|
| 637 |
+
This program is free software: you can redistribute it and/or modify
|
| 638 |
+
it under the terms of the GNU General Public License as published by
|
| 639 |
+
the Free Software Foundation, either version 3 of the License, or
|
| 640 |
+
(at your option) any later version.
|
| 641 |
+
|
| 642 |
+
This program is distributed in the hope that it will be useful,
|
| 643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 645 |
+
GNU General Public License for more details.
|
| 646 |
+
|
| 647 |
+
You should have received a copy of the GNU General Public License
|
| 648 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
| 649 |
+
|
| 650 |
+
Also add information on how to contact you by electronic and paper mail.
|
| 651 |
+
|
| 652 |
+
If the program does terminal interaction, make it output a short
|
| 653 |
+
notice like this when it starts in an interactive mode:
|
| 654 |
+
|
| 655 |
+
<program> Copyright (C) <year> <name of author>
|
| 656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
| 657 |
+
This is free software, and you are welcome to redistribute it
|
| 658 |
+
under certain conditions; type `show c' for details.
|
| 659 |
+
|
| 660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
| 661 |
+
parts of the General Public License. Of course, your program's commands
|
| 662 |
+
might be different; for a GUI interface, you would use an "about box".
|
| 663 |
+
|
| 664 |
+
You should also get your employer (if you work as a programmer) or school,
|
| 665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
| 666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
| 667 |
+
<https://www.gnu.org/licenses/>.
|
| 668 |
+
|
| 669 |
+
The GNU General Public License does not permit incorporating your program
|
| 670 |
+
into proprietary programs. If your program is a subroutine library, you
|
| 671 |
+
may consider it more useful to permit linking proprietary applications with
|
| 672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
| 673 |
+
Public License instead of this License. But first, please read
|
| 674 |
+
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
README.md
CHANGED
|
@@ -1,13 +1,154 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ViralCutter: Gerador de Vídeos Virais
|
| 2 |
+
[  ](https://discord.gg/aihubbrasil) <br>
|
| 3 |
+
[English](https://github.com/RafaelGodoyEbert/ViralCutter/blob/main/README_en.md) | [Português](https://github.com/RafaelGodoyEbert/ViralCutter/blob/main/README.md)
|
| 4 |
+
|
| 5 |
+
## **Descrição**
|
| 6 |
+
ViralCutter é uma ferramenta inovadora para gerar vídeos virais a partir de conteúdo existente. Com técnicas avançadas de processamento de vídeo e áudio, o ViralCutter corta e edita segmentos de vídeo que são perfeitos para compartilhamento em redes sociais. Utilizando o modelo WhisperX para transcrição e geração de legendas automáticas, ele adapta os vídeos para o formato 9:16 (vertical), ideal para plataformas como TikTok e Instagram com Reels e Youtube com Shorts.
|
| 7 |
+
|
| 8 |
+
## **Novidades e Atualizações (Changelog)**
|
| 9 |
+
|
| 10 |
+
Confira as melhorias mais recentes:
|
| 11 |
+
|
| 12 |
+
- **Nova WebUI (Gradio)**: Interface gráfica moderna inspirada no OpusClip, facilitando o uso de todas as ferramentas.
|
| 13 |
+
- **Instalação Rápida (UV)**: Novo script `.bat` que utiliza o `uv` para instalar dependências de forma muito mais rápida.
|
| 14 |
+
- **Otimização de Performance**: "Slicing" de transcrição implementado. O vídeo é transcrito apenas uma vez, e os cortes reutilizam os dados, eliminando reprocessamento.
|
| 15 |
+
- **Suporte a IA Flexível**: Integração nativa com **Gemini API** e suporte experimental ao **G4F** (GPT-4 Free), além de modo Manual.
|
| 16 |
+
- **Configuração Externa**: Arquivos `api_config.json` e `prompt.txt` para fácil personalização sem mexer no código.
|
| 17 |
+
- **Correção de Rostos**: Fix no MediaPipe para rastreamento de rostos preciso sem depender de "Center Crop".
|
| 18 |
+
- **Melhorias em Legendas**: Posicionamento inteligente para vídeos com 2 faces (split screen) e correções de estilo.
|
| 19 |
+
|
| 20 |
+
*(Veja o [changelog.md](changelog.md) para detalhes completos)*
|
| 21 |
+
|
| 22 |
+
## **Interface**
|
| 23 |
+

|
| 24 |
+
*Tela Principal: Ajustes de IA, legendas e visualização de logs*
|
| 25 |
+
|
| 26 |
+

|
| 27 |
+
*Biblioteca: Galeria estilo OpusClip e controles intuitivos*
|
| 28 |
+
|
| 29 |
+
## **Funcionalidades**
|
| 30 |
+
|
| 31 |
+
- **Download de Vídeos**: Baixa vídeos do YouTube através de uma URL fornecida.
|
| 32 |
+
- **Transcrição de Áudio**: Converte áudio em texto utilizando o modelo WhisperX.
|
| 33 |
+
- **Identificação de Segmentos Virais**: Utiliza IA para detectar partes do vídeo com alto potencial de viralização.
|
| 34 |
+
- **Corte e Ajuste de Formato**: Corta os segmentos selecionados e ajusta a proporção para 9:16.
|
| 35 |
+
- **Recorte Inteligente**: Mantém o falante em foco (Face Tracking) ou utiliza modo Split Screen (2-Faces) automático.
|
| 36 |
+
- **Mesclagem de Áudio e Vídeo**: Combina o áudio transcrito com os clipes de vídeo processados.
|
| 37 |
+
- **Exportação em Lote**: Gera um arquivo ZIP com todos os vídeos virais criados, facilitando o download e compartilhamento.
|
| 38 |
+
- **Legenda personalizada**: Você cria uma legenda personalizada com cores, highlight, sem highlight ou palavra por palavra, tendo uma ampla possibilidade de edição.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
## **Como Usar**
|
| 42 |
+
<!--
|
| 43 |
+
Entre no link e siga os passos na ordem:<br> [](https://colab.research.google.com/drive/1gcxImzBt0ObWLfW3ThEcwqKhasB4WpgX?usp=sharing)
|
| 44 |
+
HF []()
|
| 45 |
+
-->
|
| 46 |
+
|
| 47 |
+
- Entre no link e siga os passos na ordem: [](https://colab.research.google.com/drive/1UZKzeqjIeEyvq9nPx7s_4mU6xlkZQn_R?usp=sharing#scrollTo=pa36OeArowme) <br>
|
| 48 |
+
|
| 49 |
+
## **Limitações**
|
| 50 |
+
|
| 51 |
+
- A qualidade dos vídeos gerados pode variar com base na qualidade do vídeo original.
|
| 52 |
+
- O tempo de processamento depende fortemente da sua GPU.
|
| 53 |
+
- O modelo **G4F** pode ser instável ou ter limites de requisição. Use **Gemini** para maior estabilidade mas precisa de api_key.
|
| 54 |
+
|
| 55 |
+
## Inspiração:
|
| 56 |
+
Este projeto foi inspirado nos seguintes repositórios:
|
| 57 |
+
|
| 58 |
+
* [Reels Clips Automator](https://github.com/eddieoz/reels-clips-automator)
|
| 59 |
+
* [YoutubeVideoToAIPoweredShorts](https://github.com/Fitsbit/YoutubeVideoToAIPoweredShorts)
|
| 60 |
+
|
| 61 |
+
## TODO📝
|
| 62 |
+
- [x] Release code
|
| 63 |
+
- [ ] Huggingface SpaceDemo
|
| 64 |
+
- [x] Two face in the cut
|
| 65 |
+
- [x] Custom caption and burn
|
| 66 |
+
- [x] Make the code faster
|
| 67 |
+
- [ ] More types of framing beyond 9:16
|
| 68 |
+
- [x] The cut follows the face as it moves
|
| 69 |
+
- [ ] Automatic translation
|
| 70 |
+
- [ ] Satisfactory video on the side
|
| 71 |
+
- [ ] Background music
|
| 72 |
+
- [ ] watermark at user's choice
|
| 73 |
+
- [ ] Upload directly to YouTube channel
|
| 74 |
+
|
| 75 |
+
## Exemplos
|
| 76 |
+
### Exmplo de vídeo viral ``com highlight ativo`` [comprimido pra caber no github]
|
| 77 |
+
https://github.com/user-attachments/assets/dd9a7039-e0f3-427a-a6e1-f50ab5029082
|
| 78 |
+
|
| 79 |
+
### Exemplo Opus Clip vs ViralCutter [comprimido pra caber no github]
|
| 80 |
+
https://github.com/user-attachments/assets/12916792-dc0e-4f63-a76b-5698946f50f4
|
| 81 |
+
|
| 82 |
+
### Exemplo 2 faces [comprimido pra caber no github]
|
| 83 |
+
https://github.com/user-attachments/assets/ca7ebb9c-52ba-4171-a513-625bef690a2b
|
| 84 |
+
|
| 85 |
+
## **Instalação e Uso Local**
|
| 86 |
+
|
| 87 |
+
### Pré-requisitos
|
| 88 |
+
- Python 3.10+
|
| 89 |
+
- FFmpeg instalado e no PATH do sistema.
|
| 90 |
+
- GPU NVIDIA recomendada (com CUDA instalado) para o WhisperX.
|
| 91 |
+
|
| 92 |
+
### Configuração
|
| 93 |
+
1. **Instale as dependências**:
|
| 94 |
+
|
| 95 |
+
**Opção A (Recomendada - Rápida)**:
|
| 96 |
+
Execute o arquivo `install_dependencies.bat`. Ele usará o `uv` para instalar tudo rapidamente.
|
| 97 |
+
|
| 98 |
+
**Opção B (Manual)**:
|
| 99 |
+
```bash
|
| 100 |
+
pip install -r requirements.txt
|
| 101 |
+
```
|
| 102 |
+
*(Nota: WhisperX e Torch podem exigir instalação específica para sua versão de CUDA)*.
|
| 103 |
+
|
| 104 |
+
2. **Configure a API (Opcional mas Recomendado)**:
|
| 105 |
+
Edite o arquivo `api_config.json` na pasta raiz:
|
| 106 |
+
```json
|
| 107 |
+
{
|
| 108 |
+
"selected_api": "gemini",
|
| 109 |
+
"gemini": {
|
| 110 |
+
"api_key": "SUA_CHAVE_AQUI"
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### Executando
|
| 116 |
+
|
| 117 |
+
#### Interface Gráfica (WebUI)
|
| 118 |
+
Para usar a nova interface visual:
|
| 119 |
+
Dê um duplo clique em `run_webui.bat` ou execute:
|
| 120 |
+
```bash
|
| 121 |
+
.\run_webui.bat
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
#### Modo Interativo (Simples)
|
| 125 |
+
Basta rodar o script e seguir as instruções na tela:
|
| 126 |
+
```bash
|
| 127 |
+
python main_improved.py
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
#### Modo CLI (Avançado)
|
| 131 |
+
Você pode passar todos os argumentos via linha de comando para automação:
|
| 132 |
+
|
| 133 |
+
```bash
|
| 134 |
+
python main_improved.py --url "https://youtu.be/EXEMPLO" --segments 3 --ai-backend gemini --model large-v3-turbo
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
**Argumentos Principais:**
|
| 138 |
+
- `--url`: URL do vídeo do YouTube.
|
| 139 |
+
- `--segments`: Número de cortes a gerar.
|
| 140 |
+
- `--ai-backend`: `gemini` (Recomendado), `g4f` ou `manual`.
|
| 141 |
+
- `--viral`: Ativa modo de busca viral automática.
|
| 142 |
+
- `--face-mode`: `auto`, `1` (uma cara), ou `2` (duas caras/split).
|
| 143 |
+
- `--workflow`: `1` (Completo) ou `2` (Apenas Corte, sem legendas/crop).
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## **Contribuições**
|
| 148 |
+
Quer ajudar a tornar o ViralCutter ainda melhor? Se você tiver sugestões ou quiser contribuir com o código, fique à vontade para abrir uma issue ou enviar um pull request no nosso repositório do GitHub.
|
| 149 |
+
|
| 150 |
+
## **Versão**
|
| 151 |
+
`0.7v Alpha`
|
| 152 |
+
Uma alternativa gratuita ao `opus.pro` e ao `vidyo.ai`.
|
| 153 |
+
|
| 154 |
+
---
|
README_en.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ViralCutter: Viral Video Generator
|
| 2 |
+
[  ](https://discord.gg/aihubbrasil) <br>
|
| 3 |
+
[English](https://github.com/RafaelGodoyEbert/ViralCutter/blob/main/README_en.md) | [Português](https://github.com/RafaelGodoyEbert/ViralCutter/blob/main/README.md)
|
| 4 |
+
|
| 5 |
+
## **Description**
|
| 6 |
+
ViralCutter is an innovative tool designed to generate viral videos from existing content. With advanced video and audio processing techniques, ViralCutter cuts and edits video segments that are perfect for sharing on social media. Using the WhisperX model for transcription and automatic caption generation, it adapts videos to the 9:16 (vertical) format, ideal for platforms like TikTok, Instagram Reels, and YouTube Shorts.
|
| 7 |
+
|
| 8 |
+
## **What's New & Updates (Changelog)**
|
| 9 |
+
|
| 10 |
+
Check out the latest improvements:
|
| 11 |
+
|
| 12 |
+
- **New WebUI (Gradio)**: Modern graphical interface inspired by OpusClip, making it easier to use all tools.
|
| 13 |
+
- **Fast Installation (UV)**: New `.bat` script that uses `uv` to install dependencies much faster.
|
| 14 |
+
- **Performance Optimization**: Transcription "slicing" implemented. The video is transcribed only once, and cuts reuse the data, eliminating reprocessing.
|
| 15 |
+
- **Flexible AI Support**: Native integration with **Gemini API** and experimental support for **G4F** (GPT-4 Free), plus a Manual mode.
|
| 16 |
+
- **External Configuration**: `api_config.json` and `prompt.txt` files for easy customization without touching the code.
|
| 17 |
+
- **Face Fix**: MediaPipe fix for precise face tracking without relying on "Center Crop".
|
| 18 |
+
- **Subtitle Improvements**: Smart positioning for 2-face videos (split screen) and style corrections.
|
| 19 |
+
|
| 20 |
+
*(See [changelog.md](changelog.md) for full details)*
|
| 21 |
+
|
| 22 |
+
## **Interface**
|
| 23 |
+

|
| 24 |
+
*Main Screen: OpusClip-style gallery and intuitive controls*
|
| 25 |
+
|
| 26 |
+

|
| 27 |
+
*Settings: AI adjustment, captions, and log viewer*
|
| 28 |
+
|
| 29 |
+
## **Features**
|
| 30 |
+
|
| 31 |
+
- **Video Download**: Downloads YouTube videos via a provided URL.
|
| 32 |
+
- **Audio Transcription**: Converts audio to text using the WhisperX model.
|
| 33 |
+
- **Viral Segment Identification**: Uses AI to detect parts of the video with high viral potential.
|
| 34 |
+
- **Cutting & Formatting**: Cuts selected segments and adjusts the aspect ratio to 9:16.
|
| 35 |
+
- **Smart Cropping**: Keeps the speaker in focus (Face Tracking) or uses automatic Split Screen (2-Faces) mode.
|
| 36 |
+
- **Audio/Video Merging**: Combines transcribed audio with processed video clips.
|
| 37 |
+
- **Batch Export**: Generates a ZIP file with all created viral videos, facilitating download and sharing.
|
| 38 |
+
- **Custom Captions**: Create custom captions with colors, highlights, no highlights, or word-by-word styles, offering extensive editing possibilities.
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
## **How to Use**
|
| 42 |
+
<!--
|
| 43 |
+
Open the link and follow the steps in order:<br> [](https://colab.research.google.com/drive/1gcxImzBt0ObWLfW3ThEcwqKhasB4WpgX?usp=sharing)
|
| 44 |
+
HF []()
|
| 45 |
+
-->
|
| 46 |
+
|
| 47 |
+
- Open the link and follow the steps in order(Only Portuguese, sorry): [](https://colab.research.google.com/drive/1UZKzeqjIeEyvq9nPx7s_4mU6xlkZQn_R?usp=sharing#scrollTo=pa36OeArowme) <br>
|
| 48 |
+
- I couldn't get the video to download/play correctly on Gradio in Google Colab, but it's functional.
|
| 49 |
+
|
| 50 |
+
## **Limitations**
|
| 51 |
+
|
| 52 |
+
- The quality of generated videos may vary based on the quality of the original video.
|
| 53 |
+
- Processing time depends heavily on your GPU.
|
| 54 |
+
- The **G4F** model may be unstable or have request limits. Use **Gemini** for greater stability (requires an api_key).
|
| 55 |
+
|
| 56 |
+
## Inspiration
|
| 57 |
+
This project was inspired by the following repositories:
|
| 58 |
+
|
| 59 |
+
* [Reels Clips Automator](https://github.com/eddieoz/reels-clips-automator)
|
| 60 |
+
* [YoutubeVideoToAIPoweredShorts](https://github.com/Fitsbit/YoutubeVideoToAIPoweredShorts)
|
| 61 |
+
|
| 62 |
+
## TODO📝
|
| 63 |
+
- [x] Release code
|
| 64 |
+
- [ ] Huggingface SpaceDemo
|
| 65 |
+
- [x] Two face in the cut
|
| 66 |
+
- [x] Custom caption and burn
|
| 67 |
+
- [x] Make the code faster
|
| 68 |
+
- [ ] More types of framing beyond 9:16
|
| 69 |
+
- [x] The cut follows the face as it moves
|
| 70 |
+
- [ ] Automatic translation
|
| 71 |
+
- [ ] Satisfying video on the side
|
| 72 |
+
- [ ] Background music
|
| 73 |
+
- [ ] Watermark at user's choice
|
| 74 |
+
- [ ] Upload directly to YouTube channel
|
| 75 |
+
|
| 76 |
+
## Examples
|
| 77 |
+
### Viral video example `with active highlight` [compressed to fit GitHub]
|
| 78 |
+
https://github.com/user-attachments/assets/dd9a7039-e0f3-427a-a6e1-f50ab5029082
|
| 79 |
+
|
| 80 |
+
### Opus Clip vs ViralCutter example [compressed to fit GitHub]
|
| 81 |
+
https://github.com/user-attachments/assets/12916792-dc0e-4f63-a76b-5698946f50f4
|
| 82 |
+
|
| 83 |
+
### 2-Face example [compressed to fit GitHub]
|
| 84 |
+
https://github.com/user-attachments/assets/ca7ebb9c-52ba-4171-a513-625bef690a2b
|
| 85 |
+
|
| 86 |
+
## **Installation and Local Usage**
|
| 87 |
+
|
| 88 |
+
### Prerequisites
|
| 89 |
+
- Python 3.10+
|
| 90 |
+
- FFmpeg installed and in the system PATH.
|
| 91 |
+
- NVIDIA GPU recommended (with CUDA installed) for WhisperX.
|
| 92 |
+
|
| 93 |
+
### Configuration
|
| 94 |
+
1. **Install dependencies**:
|
| 95 |
+
|
| 96 |
+
**Option A (Recommended - Fast)**:
|
| 97 |
+
Run the `install_dependencies.bat` file. It will use `uv` to install everything quickly.
|
| 98 |
+
|
| 99 |
+
**Option B (Manual)**:
|
| 100 |
+
```bash
|
| 101 |
+
pip install -r requirements.txt
|
| 102 |
+
```
|
| 103 |
+
*(Note: WhisperX and Torch may require specific installation instructions for your CUDA version)*.
|
| 104 |
+
|
| 105 |
+
2. **Configure API (Optional but Recommended)**:
|
| 106 |
+
Edit the `api_config.json` file in the root folder:
|
| 107 |
+
```json
|
| 108 |
+
{
|
| 109 |
+
"selected_api": "gemini",
|
| 110 |
+
"gemini": {
|
| 111 |
+
"api_key": "YOUR_KEY_HERE"
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### Running
|
| 117 |
+
|
| 118 |
+
#### Graphical Interface (WebUI)
|
| 119 |
+
To use the new visual interface:
|
| 120 |
+
Double-click `run_webui.bat` or run:
|
| 121 |
+
```bash
|
| 122 |
+
.\run_webui.bat
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
#### Interactive Mode (Simple)
|
| 126 |
+
Just run the script and follow the on-screen instructions:
|
| 127 |
+
```bash
|
| 128 |
+
python main_improved.py
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
#### CLI Mode (Advanced)
|
| 132 |
+
You can pass all arguments via command line for automation:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
python main_improved.py --url "https://youtu.be/EXAMPLE" --segments 3 --ai-backend gemini --model large-v3-turbo
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
**Main Arguments:**
|
| 139 |
+
- `--url`: YouTube video URL.
|
| 140 |
+
- `--segments`: Number of cuts to generate.
|
| 141 |
+
- `--ai-backend`: `gemini` (Recommended), `g4f`, or `manual`.
|
| 142 |
+
- `--viral`: Activates automatic viral search mode.
|
| 143 |
+
- `--face-mode`: `auto`, `1` (one face), or `2` (two faces/split).
|
| 144 |
+
- `--workflow`: `1` (Full) or `2` (Cut Only, no captions/crop).
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## **Contributions**
|
| 149 |
+
Want to help make ViralCutter even better? If you have suggestions or want to contribute to the code, feel free to open an issue or submit a pull request on our GitHub repository.
|
| 150 |
+
|
| 151 |
+
## **Version**
|
| 152 |
+
`0.7v Alpha`
|
| 153 |
+
A free alternative to `opus.pro` and `vidyo.ai`.
|
| 154 |
+
|
| 155 |
+
---
|
ViralCutter.ipynb
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"source": [
|
| 6 |
+
"# ViralCutter\n",
|
| 7 |
+
"Uma alternativa gratuita ao `opus.pro` e ao `vidyo.ai`"
|
| 8 |
+
],
|
| 9 |
+
"metadata": {
|
| 10 |
+
"id": "pa36OeArowme"
|
| 11 |
+
}
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
"cell_type": "markdown",
|
| 15 |
+
"source": [
|
| 16 |
+
"# Suporte em:\n",
|
| 17 |
+
"[](https://discord.gg/tAdPHFAbud)"
|
| 18 |
+
],
|
| 19 |
+
"metadata": {
|
| 20 |
+
"id": "6Q-ljfsw1unE"
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"cell_type": "markdown",
|
| 25 |
+
"source": [
|
| 26 |
+
"# TODO📝\n",
|
| 27 |
+
"- [x] Release code\n",
|
| 28 |
+
"- [ ] Huggingface SpaceDemo\n",
|
| 29 |
+
"- [x] Two face in the cut\n",
|
| 30 |
+
"- [x] Custom caption and burn\n",
|
| 31 |
+
"- [x] Make the code faster\n",
|
| 32 |
+
"- [ ] More types of framing beyond 9:16"
|
| 33 |
+
],
|
| 34 |
+
"metadata": {
|
| 35 |
+
"id": "0o3KcBT5wzws"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"cell_type": "code",
|
| 40 |
+
"execution_count": 1,
|
| 41 |
+
"metadata": {
|
| 42 |
+
"id": "e76jiRnjONmj",
|
| 43 |
+
"cellView": "form",
|
| 44 |
+
"colab": {
|
| 45 |
+
"base_uri": "https://localhost:8080/"
|
| 46 |
+
},
|
| 47 |
+
"outputId": "6e8baa39-5e40-45d7-e247-a70eddbc1a79"
|
| 48 |
+
},
|
| 49 |
+
"outputs": [
|
| 50 |
+
{
|
| 51 |
+
"output_type": "stream",
|
| 52 |
+
"name": "stdout",
|
| 53 |
+
"text": [
|
| 54 |
+
"✅ Instalação V7 Finalizada!\n",
|
| 55 |
+
"- Transformers 4.46.3 (Compatível com Alinhamento): INSTALADO\n",
|
| 56 |
+
"- Torch 2.3.1: ATIVO\n"
|
| 57 |
+
]
|
| 58 |
+
}
|
| 59 |
+
],
|
| 60 |
+
"source": [
|
| 61 |
+
"#@title 🛠️ Instalação\n",
|
| 62 |
+
"import os\n",
|
| 63 |
+
"import subprocess\n",
|
| 64 |
+
"import shutil\n",
|
| 65 |
+
"from IPython.display import clear_output\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"# 1. Limpeza TOTAL\n",
|
| 68 |
+
"print(\"🧹 Limpando instalação anterior...\")\n",
|
| 69 |
+
"%cd /content\n",
|
| 70 |
+
"if os.path.exists(\"ViralCutter\"):\n",
|
| 71 |
+
" shutil.rmtree(\"ViralCutter\")\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"!git clone -b dev https://github.com/RafaelGodoyEbert/ViralCutter.git\n",
|
| 74 |
+
"%cd /content/ViralCutter\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"print(\"⏳ Instalando gerenciador UV e drivers do sistema...\")\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"# 2. Instalar UV e Drivers Linux\n",
|
| 79 |
+
"subprocess.run(['pip', 'install', 'uv'], check=True)\n",
|
| 80 |
+
"subprocess.run('sudo apt update -y && sudo apt install -y libcudnn8 ffmpeg xvfb', shell=True, check=True)\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"# 3. Criar Ambiente Virtual\n",
|
| 83 |
+
"print(\"⏳ Criando ambiente virtual...\")\n",
|
| 84 |
+
"subprocess.run(['uv', 'venv', '.venv'], check=True)\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"# 4. INSTALAÇÃO DAS DEPENDÊNCIAS\n",
|
| 87 |
+
"print(\"⏳ Instalando Bibliotecas...\")\n",
|
| 88 |
+
"\n",
|
| 89 |
+
"# Passo A: WhisperX e Requisitos Básicos (Deixe instalar o que quiserem)\n",
|
| 90 |
+
"cmds_fase_1 = [\n",
|
| 91 |
+
" \"uv pip install --python .venv git+https://github.com/m-bain/whisperx.git\",\n",
|
| 92 |
+
" \"uv pip install --python .venv -r requirements.txt\",\n",
|
| 93 |
+
" \"uv pip install --python .venv yt-dlp pytubefix\",\n",
|
| 94 |
+
" \"uv pip install yt_dlp\"\n",
|
| 95 |
+
"]\n",
|
| 96 |
+
"\n",
|
| 97 |
+
"for cmd in cmds_fase_1:\n",
|
| 98 |
+
" subprocess.run(cmd, shell=True, check=True)\n",
|
| 99 |
+
"\n",
|
| 100 |
+
"# Passo B: CORREÇÃO DE ALINHAMENTO E GEMINI\n",
|
| 101 |
+
"# - google-generativeai: Para o Gemini funcionar\n",
|
| 102 |
+
"# - pandas: Para separar palavras\n",
|
| 103 |
+
"# - transformers==4.46.3: VERSÃO CRÍTICA. Versões mais novas exigem Torch 2.6 e quebram o alinhamento.\n",
|
| 104 |
+
"# - accelerate: Ajuda no carregamento do modelo\n",
|
| 105 |
+
"print(\"🔨 Aplicando downgrade estratégico no Transformers...\")\n",
|
| 106 |
+
"extra_libs = [\n",
|
| 107 |
+
" \"uv pip install --python .venv google-generativeai\",\n",
|
| 108 |
+
" \"uv pip install --python .venv pandas\",\n",
|
| 109 |
+
" \"uv pip install --python .venv onnxruntime-gpu\",\n",
|
| 110 |
+
" \"uv pip install --python .venv transformers==4.46.3 accelerate>=0.26.0\"\n",
|
| 111 |
+
"]\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"for cmd in extra_libs:\n",
|
| 114 |
+
" subprocess.run(cmd, shell=True, check=True)\n",
|
| 115 |
+
"\n",
|
| 116 |
+
"# Passo C: O MARTELO FINAL (Torch 2.3.1 Estável)\n",
|
| 117 |
+
"# Reinstalamos por último para garantir que nada atualizou ele sem querer\n",
|
| 118 |
+
"print(\"🔨 Forçando versão estável do Torch (2.3.1)...\")\n",
|
| 119 |
+
"cmd_fix_torch = (\n",
|
| 120 |
+
" \"uv pip install --python .venv \"\n",
|
| 121 |
+
" \"torch==2.3.1+cu121 torchvision==0.18.1+cu121 torchaudio==2.3.1+cu121 \"\n",
|
| 122 |
+
" \"--index-url https://download.pytorch.org/whl/cu121\"\n",
|
| 123 |
+
")\n",
|
| 124 |
+
"subprocess.run(cmd_fix_torch, shell=True, check=True)\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"# Passo D: Trava do Numpy\n",
|
| 127 |
+
"print(\"🔨 Travando Numpy...\")\n",
|
| 128 |
+
"subprocess.run(\"uv pip install --python .venv 'numpy<2.0' setuptools==69.5.1\", shell=True, check=True)\n",
|
| 129 |
+
"\n",
|
| 130 |
+
"# Passo E: Correção de Visão Computacional (CRÍTICO)\n",
|
| 131 |
+
"print(\"🔨 Corrigindo InsightFace e MediaPipe...\")\n",
|
| 132 |
+
"\n",
|
| 133 |
+
"# 1. Garante que o InsightFace tenha o motor necessário (evita que ele falhe)\n",
|
| 134 |
+
"subprocess.run(\"uv pip install --python .venv insightface onnxruntime-gpu\", shell=True, check=True)\n",
|
| 135 |
+
"\n",
|
| 136 |
+
"# 2. Limpeza Radical e Reinstalação do MediaPipe\n",
|
| 137 |
+
"# Remove versões quebradas\n",
|
| 138 |
+
"subprocess.run(\"uv pip uninstall --python .venv mediapipe protobuf flatbuffers\", shell=True)\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"# 3. Instala versão FLEXÍVEL respeitando limites críticos\n",
|
| 141 |
+
"# mediapipe>=0.10.0: Pega a versão mais recente compatível com Py3.12\n",
|
| 142 |
+
"# protobuf>=3.20,<5.0: Garante compatibilidade sem quebrar o sistema antigo\n",
|
| 143 |
+
"subprocess.run(\"uv pip install --python .venv 'mediapipe>=0.10.0' 'protobuf>=3.20,<5.0' 'flatbuffers>=2.0'\", shell=True, check=True)\n",
|
| 144 |
+
"\n",
|
| 145 |
+
"# 5. Configurar Monitor\n",
|
| 146 |
+
"os.system('Xvfb :1 -screen 0 2560x1440x8 &')\n",
|
| 147 |
+
"os.environ['DISPLAY'] = ':1.0'\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"clear_output()\n",
|
| 150 |
+
"print(\"✅ Instalação V7 Finalizada!\")\n",
|
| 151 |
+
"print(\"- Transformers 4.46.3 (Compatível com Alinhamento): INSTALADO\")\n",
|
| 152 |
+
"print(\"- Torch 2.3.1: ATIVO\")"
|
| 153 |
+
]
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"cell_type": "code",
|
| 157 |
+
"source": [
|
| 158 |
+
"#@title 🚀 Configuração e Execução\n",
|
| 159 |
+
"import os\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"%cd /content/ViralCutter\n",
|
| 162 |
+
"\n",
|
| 163 |
+
"# 1. Configura o \"Display\" falso (para evitar erros de interface gráfica)\n",
|
| 164 |
+
"os.system('Xvfb :1 -screen 0 2560x1440x8 &')\n",
|
| 165 |
+
"os.environ['DISPLAY'] = ':1.0'\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"# 2. CORREÇÃO DO ERRO ATUAL:\n",
|
| 168 |
+
"# Forçamos o Matplotlib a usar o backend 'Agg' (modo sem cabeça/headless)\n",
|
| 169 |
+
"# Isso ignora o 'module://matplotlib_inline.backend_inline' que o Colab força e quebra o script.\n",
|
| 170 |
+
"os.environ['MPLBACKEND'] = 'Agg'\n",
|
| 171 |
+
"\n",
|
| 172 |
+
"print(\"🚀 Iniciando ViralCutter usando o ambiente virtual...\")\n",
|
| 173 |
+
"print(\"⚠️ Ignore os avisos de 'UserWarning', aguarde o link public URL.\")\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"# Executa usando o Python do ambiente virtual\n",
|
| 176 |
+
"!/content/ViralCutter/.venv/bin/python webui/app.py --colab"
|
| 177 |
+
],
|
| 178 |
+
"metadata": {
|
| 179 |
+
"id": "DNn8ZKJdD5XG",
|
| 180 |
+
"colab": {
|
| 181 |
+
"base_uri": "https://localhost:8080/"
|
| 182 |
+
},
|
| 183 |
+
"outputId": "fd4689bd-f036-42de-fc7e-67854af40d81"
|
| 184 |
+
},
|
| 185 |
+
"execution_count": null,
|
| 186 |
+
"outputs": [
|
| 187 |
+
{
|
| 188 |
+
"output_type": "stream",
|
| 189 |
+
"name": "stdout",
|
| 190 |
+
"text": [
|
| 191 |
+
"/content/ViralCutter\n",
|
| 192 |
+
"🚀 Iniciando ViralCutter usando o ambiente virtual...\n",
|
| 193 |
+
"⚠️ Ignore os avisos de 'UserWarning', aguarde o link public URL.\n",
|
| 194 |
+
"/content/ViralCutter/webui/app.py:204: UserWarning: The parameters have been moved from the Blocks constructor to the launch() method in Gradio 6.0: theme, css. Please pass these parameters to launch() instead.\n",
|
| 195 |
+
" with gr.Blocks(title=i18n(\"ViralCutter WebUI\"), theme=gr.themes.Default(primary_hue=\"blue\", neutral_hue=\"slate\"), css=css) as demo:\n",
|
| 196 |
+
"Running in Colab mode. Generating public link...\n",
|
| 197 |
+
"* Running on local URL: http://127.0.0.1:7860\n",
|
| 198 |
+
"* Running on public URL: https://7f3b04ec670707105b.gradio.live\n",
|
| 199 |
+
"\n",
|
| 200 |
+
"This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n"
|
| 201 |
+
]
|
| 202 |
+
}
|
| 203 |
+
]
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"cell_type": "markdown",
|
| 207 |
+
"metadata": {
|
| 208 |
+
"id": "nEIsMZLwJ5kD"
|
| 209 |
+
},
|
| 210 |
+
"source": [
|
| 211 |
+
"#Créditos\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"Inspirado no [reels clips automator](https://github.com/eddieoz/reels-clips-automator) e no [YoutubeVideoToAIPoweredShorts](https://github.com/Fitsbit/YoutubeVideoToAIPoweredShorts)<br>\n",
|
| 214 |
+
"\n",
|
| 215 |
+
"---\n",
|
| 216 |
+
"\n",
|
| 217 |
+
"\n",
|
| 218 |
+
"Desenvolvido por **Rafa.Godoy**<br>\n",
|
| 219 |
+
"[  ](https://github.com/rafaelGodoyEbert)<br>\n",
|
| 220 |
+
"[  ](https://twitter.com/GodoyEbert)<br>\n",
|
| 221 |
+
"[Instagram](https://www.instagram.com/rafael.godoy.ebert/)<br>\n",
|
| 222 |
+
"[  ](https://discord.gg/aihubbrasil)"
|
| 223 |
+
]
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"cell_type": "markdown",
|
| 227 |
+
"source": [
|
| 228 |
+
"`0.1v Alpha`<br>\n",
|
| 229 |
+
"\n",
|
| 230 |
+
"Apenas uma alternativa gratuita ao `opus.pro` e ao `vidyo.ai`<br>\n"
|
| 231 |
+
],
|
| 232 |
+
"metadata": {
|
| 233 |
+
"id": "tpLJmPqGT5_u"
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
],
|
| 237 |
+
"metadata": {
|
| 238 |
+
"accelerator": "GPU",
|
| 239 |
+
"colab": {
|
| 240 |
+
"gpuType": "T4",
|
| 241 |
+
"provenance": []
|
| 242 |
+
},
|
| 243 |
+
"kernelspec": {
|
| 244 |
+
"display_name": "Python 3",
|
| 245 |
+
"name": "python3"
|
| 246 |
+
},
|
| 247 |
+
"language_info": {
|
| 248 |
+
"name": "python"
|
| 249 |
+
}
|
| 250 |
+
},
|
| 251 |
+
"nbformat": 4,
|
| 252 |
+
"nbformat_minor": 0
|
| 253 |
+
}
|
abrir_cmd.bat
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
|
| 3 |
+
REM Vai para a pasta onde está o .bat
|
| 4 |
+
cd /d "%~dp0"
|
| 5 |
+
|
| 6 |
+
REM Ativa o ambiente virtual criado pelo uv
|
| 7 |
+
if exist ".venv\Scripts\activate.bat" (
|
| 8 |
+
call ".venv\Scripts\activate.bat"
|
| 9 |
+
echo Ambiente virtual .venv ativado.
|
| 10 |
+
) else (
|
| 11 |
+
echo AVISO: Ambiente virtual .venv nao encontrado.
|
| 12 |
+
echo Execute install_dependencies.bat primeiro.
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
REM Abre um CMD interativo e mantém aberto
|
| 16 |
+
cmd /k
|
api_config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"selected_api": "gemini",
|
| 3 |
+
"gemini": {
|
| 4 |
+
"api_key": "",
|
| 5 |
+
"model": "gemini-2.5-flash-lite-preview-09-2025",
|
| 6 |
+
"chunk_size": 20000
|
| 7 |
+
},
|
| 8 |
+
"g4f": {
|
| 9 |
+
"model": "gpt-4o-mini",
|
| 10 |
+
"chunk_size": 2000
|
| 11 |
+
}
|
| 12 |
+
}
|
changelog.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
## Suporte a GGUF e Ajustes de Link
|
| 4 |
+
|
| 5 |
+
### Novidades
|
| 6 |
+
- **Suporte a GGUF**: colocado suporte a gguf para llm local.
|
| 7 |
+
- **Link Público**: ajustado diretórios de link público.
|
| 8 |
+
|
| 9 |
+
## Melhorias de Qualidade de Vídeo, Legendas e Processamento
|
| 10 |
+
|
| 11 |
+
### Novidades
|
| 12 |
+
|
| 13 |
+
- **Aprimoramento de prompt para LLM**: melhorias no prompt para permitir que o modelo de linguagem compreenda melhor o contexto do conteúdo.
|
| 14 |
+
- **Aprimoramento na detecção facial**: melhorias na identificação de rostos quando várias pessoas estão falando simultaneamente.
|
| 15 |
+
- **Seleção de Qualidade de Vídeo**: agora é possível escolher a qualidade desejada para download de vídeos (Melhor, 1080p, 720p, 480p) diretamente pela WebUI ou CLI, permitindo otimizar entre velocidade e uso de armazenamento.
|
| 16 |
+
- **Controle de Legendas do YouTube**: adicionada a opção de ignorar o download de legendas oficiais do YouTube, permitindo forçar uma nova transcrição via Whisper, se desejado.
|
| 17 |
+
- **Suporte a VTT**: o script de transcrição foi aprimorado para oferecer suporte a arquivos de legenda `.vtt` para alinhamento, garantindo maior compatibilidade.
|
| 18 |
+
- **Tradução de legendas em JSON com destaque palavra por palavra**: adicionada a tradução de legendas no formato JSON, permitindo highlight e sincronização word-by-word em outro idioma durante a exibição.
|
| 19 |
+
|
| 20 |
+
### Melhorias e Otimizações
|
| 21 |
+
|
| 22 |
+
- **yt-dlp mais robusto**: corrigidos problemas em que downloads de vídeo estavam sendo salvos como “Unknown_Video” e exibiam progresso incorreto. Também foram adicionados logs de progresso mais precisos e suporte aprimorado ao download de legendas.
|
| 23 |
+
- **Otimização de Legendas do YouTube**: quando legendas do YouTube estão disponíveis, o sistema agora faz o download automático e as utiliza apenas para alinhamento, pulando o processo pesado e demorado de transcrição. Isso acelera significativamente o processamento de vídeos que já possuem legendas.
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
## Active Speaker & Face Controls
|
| 27 |
+
|
| 28 |
+
### Controles Avançados de Face e Falante Ativo
|
| 29 |
+
- **Filtros de Face**: Controle granular para ignorar rostos pequenos, definir limite de confiança minimiza falsos positivos e "Zona Morta" para estabilizar a câmera.
|
| 30 |
+
- **Experimental: Active Speaker**: Novo modo experimental que tenta focar na pessoa que está falando (detecção de boca aberta e movimento), em vez de sempre dividir a tela.
|
| 31 |
+
- **Legendas**: Opção para remover pontuação automaticamente.
|
| 32 |
+
|
| 33 |
+
## Editor de Legenda JSON
|
| 34 |
+
|
| 35 |
+
### Funcionalidades
|
| 36 |
+
- **Editor de Legendas**: Adicionado um editor de legendas simples, dentro das limitações do Gradio, para corrigir erros de ortografia ocorridos durante o uso do WhisperX.
|
| 37 |
+
|
| 38 |
+
### Correções
|
| 39 |
+
- **Geral**: Alguns Fix Colab e melhorias na geração de viral segments.
|
| 40 |
+
|
| 41 |
+
## Gradio WebUI & UV Installation
|
| 42 |
+
|
| 43 |
+
### Nova Interface Web (Gradio)
|
| 44 |
+
- **OpusClip Inspired**: Nova interface gráfica construída com Gradio, inspirada no design do OpusClip, oferecendo uma experiência de usuário moderna e intuitiva.
|
| 45 |
+
- **Funcionalidades da UI**: Ajustes completos para garantir que todas as funcionalidades da ferramenta estejam acessíveis e operantes através da nova interface.
|
| 46 |
+
|
| 47 |
+
### Instalação e Infraestrutura
|
| 48 |
+
- **Instalação via UV**: Criação de script `.bat` para instalação otimizada de dependências utilizando o `uv`, acelerando o processo de setup.
|
| 49 |
+
- **Fixes Gerais**: Correções em diversos componentes que estavam quebrados ou instáveis, garantindo maior estabilidade na execução via UI.
|
| 50 |
+
|
| 51 |
+
## WebUI 2.0 & Enhanced Configuration
|
| 52 |
+
|
| 53 |
+
### WebUI Overhaul
|
| 54 |
+
- **Dark & Modern UI**: Interface completamente redesenhada com tema escuro e layout em grid responsivo (estilo Opus.pro) para a galeria de vídeos.
|
| 55 |
+
- **Dynamic Configuration**: Componentes da interface agora reagem dinamicamente à escolha do Backend de IA, atualizando automaticamente a lista de modelos disponíveis e o tamanho sugerido de chunk.
|
| 56 |
+
- **Improved Controls**: Controle granular sobre `Face Detect Interval`, `Skip Prompts`, e `Chunk Size` diretamente na interface web.
|
| 57 |
+
- **Refactoring**: Código da WebUI refatorado e modularizado (`library.py` separado do `app.py`) para melhor manutenção.
|
| 58 |
+
|
| 59 |
+
### Core & CLI
|
| 60 |
+
- **Arguments Expansion**: `main_improved.py` agora aceita argumentos de linha de comando para `--chunk-size` e `--ai-model-name`, permitindo override total da configuração.
|
| 61 |
+
- **Script Update**: `create_viral_segments.py` atualizado para respeitar os parâmetros passados via CLI, priorizando-os sobre o arquivo de configuração.
|
| 62 |
+
|
| 63 |
+
## Fix 2 faces
|
| 64 |
+
|
| 65 |
+
### Melhorias na Detecção Facial e Layout
|
| 66 |
+
- **Consistência Visual (2 Faces)**: Implementada lógica para "travar" a identidade dos rostos nas posições superior e inferior, impedindo que os participantes troquem de lugar durante o vídeo.
|
| 67 |
+
- **Lógica de Fallback Inteligente**: Caso o rosto não seja detectado no frame atual, o sistema agora tenta recuperar a posição baseada no frame anterior, posterior ou na última coordenada válida conhecida.
|
| 68 |
+
- **Intervalo de Detecção Personalizável**: Adicionada configuração para o usuário escolher a frequência da varredura facial, permitindo otimizar o tempo de renderização.
|
| 69 |
+
|
| 70 |
+
### Correções de Legendas
|
| 71 |
+
- **Correção de Sobreposição**: Resolvido bug onde legendas apareciam sobrepostas em momentos de fala rápida.
|
| 72 |
+
- **Refinamento de Centralização (2 Faces)**: Ajustes adicionais no cálculo de posição para garantir que a legenda fique perfeitamente centralizada no modo dividido.
|
| 73 |
+
|
| 74 |
+
## Atualizações Anteriores
|
| 75 |
+
|
| 76 |
+
### Refatoração e Melhorias de Código
|
| 77 |
+
- **Refatoração do Script Principal**: Criação e aprimoramento do `main_improved.py` para melhorar a estrutura e manutenibilidade do pipeline de processamento.
|
| 78 |
+
- **Padronização de Código (Inglês)**: Tradução completa de nomes de variáveis, funções e comentários internos para inglês, visando compatibilidade com padrões internacionais e colaboração open-source, mantendo logs de saída com suporte a i18n (`en_US`/`pt_BR`).
|
| 79 |
+
- **Ajuste de Diretórios**: Reorganização da estrutura de pastas e caminhos de saída para maior organização dos arquivos gerados.
|
| 80 |
+
|
| 81 |
+
### Configuração e IA
|
| 82 |
+
- **Integração Multi-LLM**: Implementação de suporte ao **g4f** (GPT-4 Free) e **Google Gemini**.
|
| 83 |
+
- **API Config**: Centralização das chaves e seleção de modelos no novo arquivo `api_config.json`, permitindo troca rápida de provedor de IA sem alterar o código.
|
| 84 |
+
- **Gerenciamento de Prompts**: Criação do arquivo `prompt.txt` para edição fácil do prompt do sistema.
|
| 85 |
+
|
| 86 |
+
### Legendas e Transcrição (Whisper)
|
| 87 |
+
- **Correções no Whisper**: Solução robusta para erros de `unpickling`, conflitos de DLLs (`libprotobuf`, `torchaudio`) e detecção de GPU.
|
| 88 |
+
- **Otimização do Fluxo (Slicing)**: O vídeo original é transcrito apenas uma vez. Os cortes reutilizam o JSON original, eliminando a re-transcrição e acelerando o processo.
|
| 89 |
+
- **Posicionamento de Legendas**: Correção da lógica de alinhamento para centralização no modo "2-face".
|
| 90 |
+
|
| 91 |
+
### Processamento de Vídeo e Detecção Facial
|
| 92 |
+
- **Novo Motor: InsightFace**: Adição da biblioteca `InsightFace` como motor de detecção facial de alta precisão.
|
| 93 |
+
- **MediaPipe**: Manutenção e correção de erros no fallback para o MediaPipe.
|
| 94 |
+
- **Limpeza de Logs**: Redução da verbosidade dos logs do FFmpeg no console.
|
check_landmarks.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# Adjust path to import scripts
|
| 6 |
+
sys.path.append(os.getcwd())
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from scripts.face_detection_insightface import init_insightface, suppress_stdout_stderr
|
| 10 |
+
|
| 11 |
+
app = init_insightface()
|
| 12 |
+
|
| 13 |
+
# Create a dummy frame (black) or try to read a real one if possible
|
| 14 |
+
# We just want to check the attributes of the returned face object
|
| 15 |
+
dummy = cv2.imread("C:/Users/rafam/.gemini/antigravity/brain/9bfedba9-72ff-4a40-a0d3-b77ea033233e/uploaded_image_1766002859958.png") # User uploaded image path from metadata?
|
| 16 |
+
# Actually I don't have access to the user's uploaded image path on disk straightforwardly unless I guess, but I can use a black frame.
|
| 17 |
+
# Wait, the user has processed videos in VIRALS folder.
|
| 18 |
+
|
| 19 |
+
# Let's just use a black frame 640x640 containing a drawn white face square to trigger detection? Unlikely to work with InsightFace (needs features).
|
| 20 |
+
# I will try to read one frame from a video in the workspace if exists.
|
| 21 |
+
|
| 22 |
+
video_path = "c:\\Users\\rafam\\Downloads\\ViralCutter\\output000_original_scale.mp4" # Guessing from edit_video
|
| 23 |
+
# Or search for mp4
|
| 24 |
+
|
| 25 |
+
import glob
|
| 26 |
+
videos = glob.glob("c:\\Users\\rafam\\Downloads\\ViralCutter\\**\\*.mp4", recursive=True)
|
| 27 |
+
if videos:
|
| 28 |
+
cap = cv2.VideoCapture(videos[0])
|
| 29 |
+
ret, frame = cap.read()
|
| 30 |
+
cap.release()
|
| 31 |
+
|
| 32 |
+
if ret:
|
| 33 |
+
faces = app.get(frame)
|
| 34 |
+
if faces:
|
| 35 |
+
print("Face Keys:", faces[0].keys())
|
| 36 |
+
if hasattr(faces[0], 'landmark_2d_106'):
|
| 37 |
+
print("Has 106 landmarks:", faces[0].landmark_2d_106 is not None)
|
| 38 |
+
if hasattr(faces[0], 'landmark_3d_68'):
|
| 39 |
+
print("Has 68 landmarks:", faces[0].landmark_3d_68 is not None)
|
| 40 |
+
print("Has kps:", faces[0].kps is not None)
|
| 41 |
+
else:
|
| 42 |
+
print("No faces found in sample frame.")
|
| 43 |
+
else:
|
| 44 |
+
print("Could not read frame.")
|
| 45 |
+
else:
|
| 46 |
+
print("No videos found to test.")
|
| 47 |
+
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Error: {e}")
|
i18n/__pycache__/i18n.cpython-310.pyc
ADDED
|
Binary file (1.2 kB). View file
|
|
|
i18n/__pycache__/i18n.cpython-311.pyc
ADDED
|
Binary file (1.87 kB). View file
|
|
|
i18n/i18n.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import locale
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def load_language_list(language):
|
| 7 |
+
with open(f"./i18n/locale/{language}.json", "r", encoding="utf-8") as f:
|
| 8 |
+
language_list = json.load(f)
|
| 9 |
+
return language_list
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class I18nAuto:
|
| 13 |
+
def __init__(self, language=None):
|
| 14 |
+
if language in ["Auto", None]:
|
| 15 |
+
language = locale.getdefaultlocale()[
|
| 16 |
+
0
|
| 17 |
+
] # getlocale can't identify the system's language ((None, None))
|
| 18 |
+
if not os.path.exists(f"./i18n/locale/{language}.json"):
|
| 19 |
+
language = "en_US"
|
| 20 |
+
self.language = language
|
| 21 |
+
self.language_map = load_language_list(language)
|
| 22 |
+
|
| 23 |
+
def __call__(self, key):
|
| 24 |
+
return self.language_map.get(key, key)
|
| 25 |
+
|
| 26 |
+
def __repr__(self):
|
| 27 |
+
return "Use Language: " + self.language
|
i18n/locale/en_US.json
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"main": "main",
|
| 3 |
+
"Burn only mode activated. Skipping to subtitle burning...": "Burn only mode activated. Skipping to subtitle burning...",
|
| 4 |
+
"Subtitle burning completed.": "Subtitle burning completed.",
|
| 5 |
+
"Enter the YouTube video URL (or press Enter to use latest project): ": "Enter the YouTube video URL (or press Enter to use latest project): ",
|
| 6 |
+
"Enter the number of viral segments to create: ": "Enter the number of viral segments to create: ",
|
| 7 |
+
"\nError: Number must be greater than 0.": "\nError: Number must be greater than 0.",
|
| 8 |
+
"Error: The value you entered is not an integer. Please try again.": "Error: The value you entered is not an integer. Please try again.",
|
| 9 |
+
"Do you want viral mode? (yes/no): ": "Do you want viral mode? (yes/no): ",
|
| 10 |
+
"Enter themes (comma-separated, leave blank if viral mode is True): ": "Enter themes (comma-separated, leave blank if viral mode is True): ",
|
| 11 |
+
"Subtitle burning skipped.": "Subtitle burning skipped.",
|
| 12 |
+
"Process completed successfully!": "Process completed successfully!",
|
| 13 |
+
"Using latest project: {}": "Using latest project: {}",
|
| 14 |
+
"Latest project found but 'input.mp4' is missing.": "Latest project found but 'input.mp4' is missing.",
|
| 15 |
+
"No existing projects found in VIRALS folder.": "No existing projects found in VIRALS folder.",
|
| 16 |
+
"VIRALS folder not found. Cannot load latest project.": "VIRALS folder not found. Cannot load latest project.",
|
| 17 |
+
"\nExisting viral segments found: {}": "\nExisting viral segments found: {}",
|
| 18 |
+
"Use existing viral segments? (yes/no) [default: yes]: ": "Use existing viral segments? (yes/no) [default: yes]: ",
|
| 19 |
+
"Loaded existing viral segments. Skipping configuration prompts.": "Loaded existing viral segments. Skipping configuration prompts.",
|
| 20 |
+
"Error loading JSON: {}.": "Error loading JSON: {}.",
|
| 21 |
+
"\nCurrent duration settings: {}s - {}s": "\nCurrent duration settings: {}s - {}s",
|
| 22 |
+
"Change duration? (y/n) [default: n]: ": "Change duration? (y/n) [default: n]: ",
|
| 23 |
+
"Minimum duration [{}]: ": "Minimum duration [{}]: ",
|
| 24 |
+
"Maximum duration [{}]: ": "Maximum duration [{}]: ",
|
| 25 |
+
"Invalid number. Using previous values.": "Invalid number. Using previous values.",
|
| 26 |
+
"Using AI Backend from config: {}": "Using AI Backend from config: {}",
|
| 27 |
+
"Select AI Backend for Viral Analysis:": "Select AI Backend for Viral Analysis:",
|
| 28 |
+
"1. Gemini API (Best / Recommended)": "1. Gemini API (Best / Recommended)",
|
| 29 |
+
"2. G4F (Free / Experimental)": "2. G4F (Free / Experimental)",
|
| 30 |
+
"3. Manual (Copy/Paste Prompt)": "3. Manual (Copy/Paste Prompt)",
|
| 31 |
+
"Choose (1/2/3): ": "Choose (1/2/3): ",
|
| 32 |
+
"Gemini API Key not found in api_config.json or arguments.": "Gemini API Key not found in api_config.json or arguments.",
|
| 33 |
+
"Enter your Gemini API Key: ": "Enter your Gemini API Key: ",
|
| 34 |
+
"Select Processing Workflow:": "Select Processing Workflow:",
|
| 35 |
+
"1. Full Processing (Face Crop 9:16 + Subtitles) [Default]": "1. Full Processing (Face Crop 9:16 + Subtitles) [Default]",
|
| 36 |
+
"2. Cut Only (Keep Original Size, No Subtitles)": "2. Cut Only (Keep Original Size, No Subtitles)",
|
| 37 |
+
"Choose (1/2): ": "Choose (1/2): ",
|
| 38 |
+
"Select Face Detection Model:": "Select Face Detection Model:",
|
| 39 |
+
"1. InsightFace (Recommended - High Accuracy, GPU Support) [Default]": "1. InsightFace (Recommended - High Accuracy, GPU Support) [Default]",
|
| 40 |
+
"2. MediaPipe (Google - Fast, lightweight)": "2. MediaPipe (Google - Fast, lightweight)",
|
| 41 |
+
"Select Face Tracking Mode:": "Select Face Tracking Mode:",
|
| 42 |
+
"1. Auto (Detect 1 or 2 faces dynamically) [Default]": "1. Auto (Detect 1 or 2 faces dynamically) [Default]",
|
| 43 |
+
"2. 1 Face (Focus on largest face)": "2. 1 Face (Focus on largest face)",
|
| 44 |
+
"3. 2 Faces (Split Screen)": "3. 2 Faces (Split Screen)",
|
| 45 |
+
"Error: No URL provided and no existing video selected.": "Error: No URL provided and no existing video selected.",
|
| 46 |
+
"Starting download...": "Starting download...",
|
| 47 |
+
"Transcribing with model {}...": "Transcribing with model {}...",
|
| 48 |
+
"Creating viral segments using {}...": "Creating viral segments using {}...",
|
| 49 |
+
"Cuts already exist. Cut again? (yes/no) [default: no]: ": "Cuts already exist. Cut again? (yes/no) [default: no]: ",
|
| 50 |
+
"Skipping Video Rendering (using existing cuts), but updating Subtitle JSONs...": "Skipping Video Rendering (using existing cuts), but updating Subtitle JSONs...",
|
| 51 |
+
"Cutting segments...": "Cutting segments...",
|
| 52 |
+
"Cut Only selected. Skipping Face Crop and Subtitles.": "Cut Only selected. Skipping Face Crop and Subtitles.",
|
| 53 |
+
"Process completed! Check your results in: {}": "Process completed! Check your results in: {}",
|
| 54 |
+
"Editing video with {} (Mode: {})...": "Editing video with {} (Mode: {})...",
|
| 55 |
+
"Processing subtitles...": "Processing subtitles...",
|
| 56 |
+
"Configuring subtitles for Split Screen (Center Position)...": "Configuring subtitles for Split Screen (Center Position)...",
|
| 57 |
+
"\nExisting cuts found in: {}": "\nExisting cuts found in: {}",
|
| 58 |
+
"\nAn error occurred: {}": "\nAn error occurred: {}",
|
| 59 |
+
"Process terminated.": "Process terminated.",
|
| 60 |
+
"Error terminating process: {}": "Error terminating process: {}",
|
| 61 |
+
"No process running.": "No process running.",
|
| 62 |
+
"Running...": "Running...",
|
| 63 |
+
"Error: No project selected.": "Error: No project selected.",
|
| 64 |
+
"Start Processing": "Start Processing",
|
| 65 |
+
"ViralCutter WebUI": "ViralCutter WebUI",
|
| 66 |
+
"Create New": "Create New",
|
| 67 |
+
"Input Source": "Input Source",
|
| 68 |
+
"YouTube URL": "YouTube URL",
|
| 69 |
+
"Existing Project": "Existing Project",
|
| 70 |
+
"Select Project": "Select Project",
|
| 71 |
+
"Segments": "Segments",
|
| 72 |
+
"Viral Mode": "Viral Mode",
|
| 73 |
+
"Themes": "Themes",
|
| 74 |
+
"Min Duration (s)": "Min Duration (s)",
|
| 75 |
+
"Max Duration (s)": "Max Duration (s)",
|
| 76 |
+
"AI Backend": "AI Backend",
|
| 77 |
+
"Gemini API Key": "Gemini API Key",
|
| 78 |
+
"AI Model": "AI Model",
|
| 79 |
+
"Chunk Size": "Chunk Size",
|
| 80 |
+
"Whisper Model": "Whisper Model",
|
| 81 |
+
"Workflow": "Workflow",
|
| 82 |
+
"Face Model": "Face Model",
|
| 83 |
+
"Face Mode": "Face Mode",
|
| 84 |
+
"Face Det. Interval": "Face Det. Interval",
|
| 85 |
+
"Subtitle Settings (alpha)": "Subtitle Settings (alpha)",
|
| 86 |
+
"Quick Presets": "Quick Presets",
|
| 87 |
+
"Enable Subtitle Customization (Includes Preset)": "Enable Subtitle Customization (Includes Preset)",
|
| 88 |
+
"Select options or preset to preview": "Select options or preset to preview",
|
| 89 |
+
"🎬 Render Animated Preview (Slow)": "🎬 Render Animated Preview (Slow)",
|
| 90 |
+
"Animated Preview": "Animated Preview",
|
| 91 |
+
"Advanced Settings": "Advanced Settings",
|
| 92 |
+
"Appearance": "Appearance",
|
| 93 |
+
"Font Name": "Font Name",
|
| 94 |
+
"Font Size (Base)": "Font Size (Base)",
|
| 95 |
+
"Highlight Size": "Highlight Size",
|
| 96 |
+
"Base Color": "Base Color",
|
| 97 |
+
"Highlight Color": "Highlight Color",
|
| 98 |
+
"Outline Color": "Outline Color",
|
| 99 |
+
"Shadow Color": "Shadow Color",
|
| 100 |
+
"Styling & Effects": "Styling & Effects",
|
| 101 |
+
"Outline Thickness": "Outline Thickness",
|
| 102 |
+
"Shadow Size": "Shadow Size",
|
| 103 |
+
"Border Style": "Border Style",
|
| 104 |
+
"Bold": "Bold",
|
| 105 |
+
"Italic": "Italic",
|
| 106 |
+
"Uppercase": "Uppercase",
|
| 107 |
+
"Underline": "Underline",
|
| 108 |
+
"Strikeout": "Strikeout",
|
| 109 |
+
"Positioning & Layout": "Positioning & Layout",
|
| 110 |
+
"V-Pos (Margin V)": "V-Pos (Margin V)",
|
| 111 |
+
"Alignment": "Alignment",
|
| 112 |
+
"Gap Limit": "Gap Limit",
|
| 113 |
+
"Mode": "Mode",
|
| 114 |
+
"Words per Block": "Words per Block",
|
| 115 |
+
"Stop": "Stop",
|
| 116 |
+
"Logs": "Logs",
|
| 117 |
+
"Results": "Results",
|
| 118 |
+
"Library": "Library",
|
| 119 |
+
"Existing Projects": "Existing Projects",
|
| 120 |
+
"Refresh List": "Refresh List",
|
| 121 |
+
"Error starting server: {}": "Error starting server: {}",
|
| 122 |
+
"No project selected.": "No project selected.",
|
| 123 |
+
"Project path not found: {}": "Project path not found: {}",
|
| 124 |
+
"Segment": "Segment",
|
| 125 |
+
"No description available.": "No description available.",
|
| 126 |
+
"External Video": "External Video",
|
| 127 |
+
"Not Found": "Not Found",
|
| 128 |
+
"No viral segments found.": "No viral segments found.",
|
| 129 |
+
"Error loading gallery: {}": "Error loading gallery: {}",
|
| 130 |
+
"PREVIEW": "PREVIEW",
|
| 131 |
+
"This is a PREVIEW of your subtitles": "This is a PREVIEW of your subtitles",
|
| 132 |
+
"Download": "Download",
|
| 133 |
+
"Error: {}": "Error: {}",
|
| 134 |
+
"This is a {} of your subtitles": "This is a {} of your subtitles",
|
| 135 |
+
"Manual": "Manual",
|
| 136 |
+
"Gemini": "Gemini",
|
| 137 |
+
"G4F": "G4F",
|
| 138 |
+
"Full": "Full",
|
| 139 |
+
"Cut Only": "Cut Only",
|
| 140 |
+
"Subtitles Only": "Subtitles Only",
|
| 141 |
+
"Outline": "Outline",
|
| 142 |
+
"Opaque Box": "Opaque Box",
|
| 143 |
+
"Left": "Left",
|
| 144 |
+
"Center": "Center",
|
| 145 |
+
"Right": "Right",
|
| 146 |
+
"Highlight": "Highlight",
|
| 147 |
+
"Word by Word": "Word by Word",
|
| 148 |
+
"No Highlight": "No Highlight",
|
| 149 |
+
"Auto": "Auto",
|
| 150 |
+
"funny, sad...": "funny, sad...",
|
| 151 |
+
"Bem-vindo ao ViralCutter! A ferramenta definitiva para transformar vídeos longos em clipes virais com o poder da IA.": "Welcome to ViralCutter! The ultimate tool to transform long videos into viral clips with the power of AI.",
|
| 152 |
+
"Aqui você pode:": "Here you can:",
|
| 153 |
+
"Cortes Automáticos": "Automatic Cuts",
|
| 154 |
+
"Identifique e corte os melhores momentos baseado em viralidade.": "Identify and cut the best moments based on virality.",
|
| 155 |
+
"Legendas Dinâmicas": "Dynamic Subtitles",
|
| 156 |
+
"Crie legendas estéticas (Estilo Hormozi) automaticamente.": "Create aesthetic subtitles (Hormozi Style) automatically.",
|
| 157 |
+
"IA Avançada": "Advanced AI",
|
| 158 |
+
"Suporte integrado para": "Integrated support for",
|
| 159 |
+
"Foco em Vertical": "Vertical Focus",
|
| 160 |
+
"Detecção facial inteligente para vídeos verticais (TikTok/Shorts/Reels).": "Smart face detection for vertical videos (TikTok/Shorts/Reels).",
|
| 161 |
+
"Este projeto foi desenvolvido para a comunidade do AI HUB Brasil.": "This project was developed for the AI HUB Brazil community.",
|
| 162 |
+
"Desenvolvido por Rafael Godoy": "Developed by Rafael Godoy",
|
| 163 |
+
"Apoie o projeto, qualquer valor é bem-vindo:": "Support the project, any amount is welcome:",
|
| 164 |
+
"Apoiar via PIX": "Support via PIX",
|
| 165 |
+
"Ignore Small Faces (0.0 - 1.0)": "Ignore Small Faces (0.0 - 1.0)",
|
| 166 |
+
"Relative size to ignore background.": "Relative size to ignore background.",
|
| 167 |
+
"Threshold for 2 Faces (0.0 - 1.0)": "Threshold for 2 Faces (0.0 - 1.0)",
|
| 168 |
+
"Size of 2nd face to activate split mode.": "Size of 2nd face to activate split mode.",
|
| 169 |
+
"Minimum Confidence (0.0 - 1.0)": "Minimum Confidence (0.0 - 1.0)",
|
| 170 |
+
"Ignore detections with low confidence.": "Ignore detections with low confidence.",
|
| 171 |
+
"Dead Zone (Stabilization)": "Dead Zone (Stabilization)",
|
| 172 |
+
"Movement pixels to ignore.": "Movement pixels to ignore.",
|
| 173 |
+
"Experimental: Focus on Speaker": "Experimental: Focus on Speaker",
|
| 174 |
+
"Tries to focus only on the speaking person instead of split screen.": "Tries to focus only on the speaking person instead of split screen.",
|
| 175 |
+
"MAR Threshold (Mouth Open)": "MAR Threshold (Mouth Open)",
|
| 176 |
+
"Mouth open sensitivity.": "Mouth open sensitivity.",
|
| 177 |
+
"Score Difference": "Score Difference",
|
| 178 |
+
"Minimum difference to focus on 1 face.": "Minimum difference to focus on 1 face.",
|
| 179 |
+
"Consider Motion": "Consider Motion",
|
| 180 |
+
"Increases score with motion (gestures).": "Increases score with motion (gestures).",
|
| 181 |
+
"Motion Dead Zone": "Motion Dead Zone",
|
| 182 |
+
"Pixels ignored.": "Pixels ignored.",
|
| 183 |
+
"Motion Sensitivity": "Motion Sensitivity",
|
| 184 |
+
"Points per pixel.": "Points per pixel.",
|
| 185 |
+
"Switch Speed": "Switch Speed",
|
| 186 |
+
"Speed to lose focus.": "Speed to lose focus.",
|
| 187 |
+
"Remove Punctuation": "Remove Punctuation",
|
| 188 |
+
"Advanced Face Settings": "Advanced Face Settings",
|
| 189 |
+
"Configuration Presets": "Configuration Presets",
|
| 190 |
+
"Download and use official subtitles if available. (Recommended, it speeds up the process)": "Download and use official subtitles if available. (Recommended, it speeds up the process)",
|
| 191 |
+
"Edit Subtitles (Smart Mode)": "Edit Subtitles (Smart Mode)",
|
| 192 |
+
"Error: Project folder could not be determined from logs.": "Error: Project folder could not be determined from logs.",
|
| 193 |
+
"Experimental: Active Speaker & Motion": "Experimental: Active Speaker & Motion",
|
| 194 |
+
"Load Subtitles": "Load Subtitles",
|
| 195 |
+
"Loaded {} segments.": "Loaded {} segments.",
|
| 196 |
+
"Local (GGUF)": "Local (GGUF)",
|
| 197 |
+
"No file loaded.": "No file loaded.",
|
| 198 |
+
"Please select project and file.": "Please select project and file.",
|
| 199 |
+
"Refresh": "Refresh",
|
| 200 |
+
"Render All started in background... Check terminal/logs.": "Render All started in background... Check terminal/logs.",
|
| 201 |
+
"Select Subtitle File": "Select Subtitle File",
|
| 202 |
+
"Status": "Status",
|
| 203 |
+
"Subtitle Editor": "Subtitle Editor",
|
| 204 |
+
"Subtitle Segments": "Subtitle Segments",
|
| 205 |
+
"Translate Subtitles To": "Translate Subtitles To",
|
| 206 |
+
"Use YouTube Subs": "Use YouTube Subs",
|
| 207 |
+
"Video Quality": "Video Quality",
|
| 208 |
+
"⚡ Render This Segment (Very-Fast)": "⚡ Render This Segment (Very-Fast)",
|
| 209 |
+
"🎬 Render All (Fast)": "🎬 Render All (Fast)",
|
| 210 |
+
"💾 Save Changes": "💾 Save Changes"
|
| 211 |
+
}
|
i18n/locale/pt_BR.json
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"main": "main",
|
| 3 |
+
"Burn only mode activated. Skipping to subtitle burning...": "Modo de apenas queimar legendas ativado. Pulando para a queima de legendas...",
|
| 4 |
+
"Subtitle burning completed.": "Queima de legendas concluída.",
|
| 5 |
+
"Enter the YouTube video URL (or press Enter to use latest project): ": "Digite a URL do vídeo do YouTube (ou pressione Enter para usar o projeto mais recente): ",
|
| 6 |
+
"Enter the number of viral segments to create: ": "Digite o número de segmentos virais para criar: ",
|
| 7 |
+
"\nError: Number must be greater than 0.": "\nErro: O número deve ser maior que 0.",
|
| 8 |
+
"Error: The value you entered is not an integer. Please try again.": "Erro: O valor que você digitou não é um número inteiro. Tente novamente.",
|
| 9 |
+
"Do you want viral mode? (yes/no): ": "Você quer o modo viral? (sim/não): ",
|
| 10 |
+
"Enter themes (comma-separated, leave blank if viral mode is True): ": "Digite temas (separados por vírgula, deixe em branco se o modo viral for Verdadeiro): ",
|
| 11 |
+
"Subtitle burning skipped.": "Queima de legendas ignorada.",
|
| 12 |
+
"Process completed successfully!": "Processo concluído com sucesso!",
|
| 13 |
+
"Using latest project: {}": "Usando o projeto mais recente: {}",
|
| 14 |
+
"Latest project found but 'input.mp4' is missing.": "Projeto mais recente encontrado, mas 'input.mp4' está faltando.",
|
| 15 |
+
"No existing projects found in VIRALS folder.": "Nenhum projeto existente encontrado na pasta VIRALS.",
|
| 16 |
+
"VIRALS folder not found. Cannot load latest project.": "Pasta VIRALS não encontrada. Não é possível carregar o projeto mais recente.",
|
| 17 |
+
"\nExisting viral segments found: {}": "\nSegmentos virais existentes encontrados: {}",
|
| 18 |
+
"Use existing viral segments? (yes/no) [default: yes]: ": "Usar segmentos virais existentes? (sim/não) [padrão: sim]: ",
|
| 19 |
+
"Loaded existing viral segments. Skipping configuration prompts.": "Segmentos virais existentes carregados. Pulando prompts de configuração.",
|
| 20 |
+
"Error loading JSON: {}.": "Erro ao carregar JSON: {}.",
|
| 21 |
+
"\nCurrent duration settings: {}s - {}s": "\nConfigurações de duração atuais: {}s - {}s",
|
| 22 |
+
"Change duration? (y/n) [default: n]: ": "Alterar duração? (s/n) [padrão: n]: ",
|
| 23 |
+
"Minimum duration [{}]: ": "Duração mínima [{}]: ",
|
| 24 |
+
"Maximum duration [{}]: ": "Duração máxima [{}]: ",
|
| 25 |
+
"Invalid number. Using previous values.": "Número inválido. Usando valores anteriores.",
|
| 26 |
+
"Using AI Backend from config: {}": "Usando Backend de IA da configuração: {}",
|
| 27 |
+
"Select AI Backend for Viral Analysis:": "Selecione o Backend de IA para Análise Viral:",
|
| 28 |
+
"1. Gemini API (Best / Recommended)": "1. Gemini API (Melhor / Recomendado)",
|
| 29 |
+
"2. G4F (Free / Experimental)": "2. G4F (Grátis / Experimental)",
|
| 30 |
+
"3. Manual (Copy/Paste Prompt)": "3. Manual (Copiar/Colar Prompt)",
|
| 31 |
+
"Choose (1/2/3): ": "Escolha (1/2/3): ",
|
| 32 |
+
"Gemini API Key not found in api_config.json or arguments.": "Chave da API Gemini não encontrada em api_config.json ou argumentos.",
|
| 33 |
+
"Enter your Gemini API Key: ": "Digite sua chave de API Gemini: ",
|
| 34 |
+
"Select Processing Workflow:": "Selecione o Fluxo de Trabalho de Processamento:",
|
| 35 |
+
"1. Full Processing (Face Crop 9:16 + Subtitles) [Default]": "1. Processamento Completo (Recorte de Rosto 9:16 + Legendas) [Padrão]",
|
| 36 |
+
"2. Cut Only (Keep Original Size, No Subtitles)": "2. Apenas Cortar (Manter Tamanho Original, Sem Legendas)",
|
| 37 |
+
"Choose (1/2): ": "Escolha (1/2): ",
|
| 38 |
+
"Select Face Detection Model:": "Selecione o Modelo de Detecção de Rosto:",
|
| 39 |
+
"1. InsightFace (Recommended - High Accuracy, GPU Support) [Default]": "1. InsightFace (Recomendado - Alta Precisão, Suporte a GPU) [Padrão]",
|
| 40 |
+
"2. MediaPipe (Google - Fast, lightweight)": "2. MediaPipe (Google - Rápido, leve)",
|
| 41 |
+
"Select Face Tracking Mode:": "Selecione o Modo de Rastreamento de Rosto:",
|
| 42 |
+
"1. Auto (Detect 1 or 2 faces dynamically) [Default]": "1. Auto (Detectar 1 ou 2 rostos dinamicamente) [Padrão]",
|
| 43 |
+
"2. 1 Face (Focus on largest face)": "2. 1 Rosto (Focar no maior rosto)",
|
| 44 |
+
"3. 2 Faces (Split Screen)": "3. 2 Rostos (Tela Dividida)",
|
| 45 |
+
"Error: No URL provided and no existing video selected.": "Erro: Nenhuma URL fornecida e nenhum vídeo existente selecionado.",
|
| 46 |
+
"Starting download...": "Iniciando download...",
|
| 47 |
+
"Transcribing with model {}...": "Transcrevendo com modelo {}...",
|
| 48 |
+
"Creating viral segments using {}...": "Criando segmentos virais usando {}...",
|
| 49 |
+
"Cuts already exist. Cut again? (yes/no) [default: no]: ": "Cortes já existem. Cortar novamente? (sim/não) [padrão: não]: ",
|
| 50 |
+
"Skipping Video Rendering (using existing cuts), but updating Subtitle JSONs...": "Pulando Renderização de Vídeo (usando cortes existentes), mas atualizando JSONs de Legenda...",
|
| 51 |
+
"Cutting segments...": "Cortando segmentos...",
|
| 52 |
+
"Cut Only selected. Skipping Face Crop and Subtitles.": "Selecionado Apenas Cortar. Pulando Recorte de Rosto e Legendas.",
|
| 53 |
+
"Process completed! Check your results in: {}": "Processo concluído! Verifique seus resultados em: {}",
|
| 54 |
+
"Editing video with {} (Mode: {})...": "Editando vídeo com {} (Modo: {})...",
|
| 55 |
+
"Processing subtitles...": "Processando legendas...",
|
| 56 |
+
"Configuring subtitles for Split Screen (Center Position)...": "Configurando legendas para Tela Dividida (Posição Central)...",
|
| 57 |
+
"\nExisting cuts found in: {}": "\nCortes existentes encontrados em: {}",
|
| 58 |
+
"\nAn error occurred: {}": "\nOcorreu um erro: {}",
|
| 59 |
+
"Process terminated.": "Processo terminado.",
|
| 60 |
+
"Error terminating process: {}": "Erro ao terminar processo: {}",
|
| 61 |
+
"No process running.": "Nenhum processo em execução.",
|
| 62 |
+
"Running...": "Executando...",
|
| 63 |
+
"Error: No project selected.": "Erro: Nenhum projeto selecionado.",
|
| 64 |
+
"Start Processing": "Iniciar Processamento",
|
| 65 |
+
"ViralCutter WebUI": "ViralCutter WebUI",
|
| 66 |
+
"Create New": "Criar Novo",
|
| 67 |
+
"Input Source": "Fonte de Entrada",
|
| 68 |
+
"YouTube URL": "URL do YouTube",
|
| 69 |
+
"Existing Project": "Projeto Existente",
|
| 70 |
+
"Select Project": "Selecionar Projeto",
|
| 71 |
+
"Segments": "Segmentos",
|
| 72 |
+
"Viral Mode": "Modo Viral",
|
| 73 |
+
"Themes": "Temas",
|
| 74 |
+
"Min Duration (s)": "Duração Mín (s)",
|
| 75 |
+
"Max Duration (s)": "Duração Máx (s)",
|
| 76 |
+
"AI Backend": "Backend de IA",
|
| 77 |
+
"Gemini API Key": "Chave API Gemini",
|
| 78 |
+
"AI Model": "Modelo de IA",
|
| 79 |
+
"Chunk Size": "Tamanho do Chunk",
|
| 80 |
+
"Whisper Model": "Modelo Whisper",
|
| 81 |
+
"Workflow": "Fluxo de Trabalho",
|
| 82 |
+
"Face Model": "Modelo de Face",
|
| 83 |
+
"Face Mode": "Modo de Face",
|
| 84 |
+
"Face Det. Interval": "Intervalo Det. Face",
|
| 85 |
+
"Subtitle Settings (alpha)": "Configurações de Legenda (alfa)",
|
| 86 |
+
"Quick Presets": "Predefinições Rápidas",
|
| 87 |
+
"Enable Subtitle Customization (Includes Preset)": "Habilitar Personalização de Legenda",
|
| 88 |
+
"Select options or preset to preview": "Selecione opções ou predefinição para visualizar",
|
| 89 |
+
"🎬 Render Animated Preview (Slow)": "🎬 Renderizar Prévia Animada (Lento)",
|
| 90 |
+
"Animated Preview": "Prévia Animada",
|
| 91 |
+
"Advanced Settings": "Configurações Avançadas",
|
| 92 |
+
"Appearance": "Aparência",
|
| 93 |
+
"Font Name": "Nome da Fonte",
|
| 94 |
+
"Font Size (Base)": "Tamanho da Fonte (Base)",
|
| 95 |
+
"Highlight Size": "Tamanho do Destaque",
|
| 96 |
+
"Base Color": "Cor Base",
|
| 97 |
+
"Highlight Color": "Cor do Destaque",
|
| 98 |
+
"Outline Color": "Cor do Contorno",
|
| 99 |
+
"Shadow Color": "Cor da Sombra",
|
| 100 |
+
"Styling & Effects": "Estilo & Efeitos",
|
| 101 |
+
"Outline Thickness": "Espessura do Contorno",
|
| 102 |
+
"Shadow Size": "Tamanho da Sombra",
|
| 103 |
+
"Border Style": "Estilo da Borda",
|
| 104 |
+
"Bold": "Negrito",
|
| 105 |
+
"Italic": "Itálico",
|
| 106 |
+
"Uppercase": "Maiúsculas",
|
| 107 |
+
"Underline": "Sublinhado",
|
| 108 |
+
"Strikeout": "Tachado",
|
| 109 |
+
"Positioning & Layout": "Posicionamento & Layout",
|
| 110 |
+
"V-Pos (Margin V)": "V-Pos (Margem V)",
|
| 111 |
+
"Alignment": "Alinhamento",
|
| 112 |
+
"Gap Limit": "Limite de Lacuna",
|
| 113 |
+
"Mode": "Modo",
|
| 114 |
+
"Words per Block": "Palavras por Bloco",
|
| 115 |
+
"Stop": "Parar",
|
| 116 |
+
"Logs": "Logs",
|
| 117 |
+
"Results": "Resultados",
|
| 118 |
+
"Library": "Biblioteca",
|
| 119 |
+
"Existing Projects": "Projetos Existentes",
|
| 120 |
+
"Refresh List": "Atualizar Lista",
|
| 121 |
+
"Error starting server: {}": "Erro ao iniciar servidor: {}",
|
| 122 |
+
"No project selected.": "Nenhum projeto selecionado.",
|
| 123 |
+
"Project path not found: {}": "Caminho do projeto não encontrado: {}",
|
| 124 |
+
"Segment": "Segmento",
|
| 125 |
+
"No description available.": "Nenhuma descrição disponível.",
|
| 126 |
+
"External Video": "Vídeo Externo",
|
| 127 |
+
"Not Found": "Não Encontrado",
|
| 128 |
+
"No viral segments found.": "Nenhum segmento viral encontrado.",
|
| 129 |
+
"Error loading gallery: {}": "Erro ao carregar galeria: {}",
|
| 130 |
+
"PREVIEW": "PRÉVIA",
|
| 131 |
+
"This is a PREVIEW of your subtitles": "Esta é uma PRÉVIA das suas legendas",
|
| 132 |
+
"Download": "Baixar",
|
| 133 |
+
"Error: {}": "Erro: {}",
|
| 134 |
+
"This is a {} of your subtitles": "Esta é uma {} das suas legendas",
|
| 135 |
+
"Manual": "Manual",
|
| 136 |
+
"Gemini": "Gemini",
|
| 137 |
+
"G4F": "G4F",
|
| 138 |
+
"Full": "Completo",
|
| 139 |
+
"Cut Only": "Apenas Corte",
|
| 140 |
+
"Subtitles Only": "Apenas Legendas",
|
| 141 |
+
"Outline": "Contorno",
|
| 142 |
+
"Opaque Box": "Caixa Opaca",
|
| 143 |
+
"Left": "Esquerda",
|
| 144 |
+
"Center": "Centro",
|
| 145 |
+
"Right": "Direita",
|
| 146 |
+
"Highlight": "Destacar",
|
| 147 |
+
"Word by Word": "Palavra por Palavra",
|
| 148 |
+
"No Highlight": "Sem Destaque",
|
| 149 |
+
"Auto": "Auto",
|
| 150 |
+
"funny, sad...": "engraçado, triste...",
|
| 151 |
+
"Bem-vindo ao ViralCutter! A ferramenta definitiva para transformar vídeos longos em clipes virais com o poder da IA.": "Bem-vindo ao ViralCutter! A ferramenta definitiva para transformar vídeos longos em clipes virais com o poder da IA.",
|
| 152 |
+
"Aqui você pode:": "Aqui você pode:",
|
| 153 |
+
"Cortes Automáticos": "Cortes Automáticos",
|
| 154 |
+
"Identifique e corte os melhores momentos baseado em viralidade.": "Identifique e corte os melhores momentos baseado em viralidade.",
|
| 155 |
+
"Legendas Dinâmicas": "Legendas Dinâmicas",
|
| 156 |
+
"Crie legendas estéticas (Estilo Hormozi) automaticamente.": "Crie legendas estéticas (Estilo Hormozi) automaticamente.",
|
| 157 |
+
"IA Avançada": "IA Avançada",
|
| 158 |
+
"Suporte integrado para": "Suporte integrado para",
|
| 159 |
+
"Foco em Vertical": "Foco em Vertical",
|
| 160 |
+
"Detecção facial inteligente para vídeos verticais (TikTok/Shorts/Reels).": "Detecção facial inteligente para vídeos verticais (TikTok/Shorts/Reels).",
|
| 161 |
+
"Este projeto foi desenvolvido para a comunidade do AI HUB Brasil.": "Este projeto foi desenvolvido para a comunidade do AI HUB Brasil.",
|
| 162 |
+
"Desenvolvido por Rafael Godoy": "Desenvolvido por Rafael Godoy",
|
| 163 |
+
"Apoie o projeto, qualquer valor é bem-vindo:": "Apoie o projeto, qualquer valor é bem-vindo:",
|
| 164 |
+
"Apoiar via PIX": "Apoiar via PIX",
|
| 165 |
+
"Ignore Small Faces (0.0 - 1.0)": "Ignorar Rostos Pequenos (0.0 - 1.0)",
|
| 166 |
+
"Relative size to ignore background.": "Tamanho relativo para ignorar fundo.",
|
| 167 |
+
"Threshold for 2 Faces (0.0 - 1.0)": "Limite para 2 Rostos (0.0 - 1.0)",
|
| 168 |
+
"Size of 2nd face to activate split mode.": "Tamanho do 2º rosto para ativar modo duplo.",
|
| 169 |
+
"Minimum Confidence (0.0 - 1.0)": "Confiança Mínima (0.0 - 1.0)",
|
| 170 |
+
"Ignore detections with low confidence.": "Ignorar detecções com baixa confiança.",
|
| 171 |
+
"Dead Zone (Stabilization)": "Zona Morta (Estabilização)",
|
| 172 |
+
"Movement pixels to ignore.": "Pixels de movimento para ignorar.",
|
| 173 |
+
"Experimental: Focus on Speaker": "Experimental: Focar em quem fala",
|
| 174 |
+
"Tries to focus only on the speaking person instead of split screen.": "Tenta focar apenas na pessoa falando em vez de dividir a tela.",
|
| 175 |
+
"MAR Threshold (Mouth Open)": "MAR Threshold (Boca Aberta)",
|
| 176 |
+
"Mouth open sensitivity.": "Sensibilidade da boca aberta.",
|
| 177 |
+
"Score Difference": "Diferença de Score",
|
| 178 |
+
"Minimum difference to focus on 1 face.": "Diferença mínima para focar em 1 rosto.",
|
| 179 |
+
"Consider Motion": "Considerar Movimento",
|
| 180 |
+
"Increases score with motion (gestures).": "Aumenta score com movimento (gestos).",
|
| 181 |
+
"Motion Dead Zone": "Zona Morta de Movimento",
|
| 182 |
+
"Pixels ignored.": "Pixels ignorados.",
|
| 183 |
+
"Motion Sensitivity": "Sensibilidade de Movimento",
|
| 184 |
+
"Points per pixel.": "Pontos por pixel.",
|
| 185 |
+
"Switch Speed": "Velocidade de Troca",
|
| 186 |
+
"Speed to lose focus.": "Rapidez para perder o foco.",
|
| 187 |
+
"Remove Punctuation": "Remover Pontuação",
|
| 188 |
+
"Advanced Face Settings": "Configurações Avançadas de Rosto",
|
| 189 |
+
"Configuration Presets": "Predefinições de Configuração",
|
| 190 |
+
"Download and use official subtitles if available. (Recommended, it speeds up the process)": "Baixar e usar legendas oficiais se disponíveis. (Recomendado, acelera o processo)",
|
| 191 |
+
"Edit Subtitles (Smart Mode)": "Editar Legendas (Modo Inteligente)",
|
| 192 |
+
"Error: Project folder could not be determined from logs.": "Erro: Pasta do projeto não pôde ser determinada a partir dos logs.",
|
| 193 |
+
"Experimental: Active Speaker & Motion": "Experimental: Falante Ativo & Movimento",
|
| 194 |
+
"Load Subtitles": "Carregar Legendas",
|
| 195 |
+
"Loaded {} segments.": "Carregado {} segmentos.",
|
| 196 |
+
"Local (GGUF)": "Local (GGUF)",
|
| 197 |
+
"No file loaded.": "Nenhum arquivo carregado.",
|
| 198 |
+
"Please select project and file.": "Por favor selecione projeto e arquivo.",
|
| 199 |
+
"Refresh": "Atualizar",
|
| 200 |
+
"Render All started in background... Check terminal/logs.": "Renderizar Tudo iniciado em segundo plano... Verifique terminal/logs.",
|
| 201 |
+
"Select Subtitle File": "Selecionar Arquivo de Legenda",
|
| 202 |
+
"Status": "Status",
|
| 203 |
+
"Subtitle Editor": "Editor de Legendas",
|
| 204 |
+
"Subtitle Segments": "Segmentos de Legenda",
|
| 205 |
+
"Translate Subtitles To": "Traduzir Legendas Para",
|
| 206 |
+
"Use YouTube Subs": "Usar Legendas do YouTube",
|
| 207 |
+
"Video Quality": "Qualidade de Vídeo",
|
| 208 |
+
"⚡ Render This Segment (Very-Fast)": "⚡ Renderizar Este Segmento (Muito Rápido)",
|
| 209 |
+
"🎬 Render All (Fast)": "🎬 Renderizar Tudo (Rápido)",
|
| 210 |
+
"💾 Save Changes": "💾 Salvar Alterações"
|
| 211 |
+
}
|
i18n/locale_diff.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
|
| 5 |
+
# Define the standard file name
|
| 6 |
+
standard_file = "locale/zh_CN.json"
|
| 7 |
+
|
| 8 |
+
# Find all JSON files in the directory
|
| 9 |
+
dir_path = "locale/"
|
| 10 |
+
languages = [
|
| 11 |
+
os.path.join(dir_path, f)
|
| 12 |
+
for f in os.listdir(dir_path)
|
| 13 |
+
if f.endswith(".json") and f != standard_file
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
# Load the standard file
|
| 17 |
+
with open(standard_file, "r", encoding="utf-8") as f:
|
| 18 |
+
standard_data = json.load(f, object_pairs_hook=OrderedDict)
|
| 19 |
+
|
| 20 |
+
# Loop through each language file
|
| 21 |
+
for lang_file in languages:
|
| 22 |
+
# Load the language file
|
| 23 |
+
with open(lang_file, "r", encoding="utf-8") as f:
|
| 24 |
+
lang_data = json.load(f, object_pairs_hook=OrderedDict)
|
| 25 |
+
|
| 26 |
+
# Find the difference between the language file and the standard file
|
| 27 |
+
diff = set(standard_data.keys()) - set(lang_data.keys())
|
| 28 |
+
|
| 29 |
+
miss = set(lang_data.keys()) - set(standard_data.keys())
|
| 30 |
+
|
| 31 |
+
# Add any missing keys to the language file
|
| 32 |
+
for key in diff:
|
| 33 |
+
lang_data[key] = key
|
| 34 |
+
|
| 35 |
+
# Del any extra keys to the language file
|
| 36 |
+
for key in miss:
|
| 37 |
+
del lang_data[key]
|
| 38 |
+
|
| 39 |
+
# Sort the keys of the language file to match the order of the standard file
|
| 40 |
+
lang_data = OrderedDict(
|
| 41 |
+
sorted(lang_data.items(), key=lambda x: list(standard_data.keys()).index(x[0]))
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# Save the updated language file
|
| 45 |
+
with open(lang_file, "w", encoding="utf-8") as f:
|
| 46 |
+
json.dump(lang_data, f, ensure_ascii=False, indent=4, sort_keys=True)
|
| 47 |
+
f.write("\n")
|
i18n/scan_i18n.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import glob
|
| 3 |
+
import json
|
| 4 |
+
from collections import OrderedDict
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def extract_i18n_strings(node):
|
| 8 |
+
i18n_strings = []
|
| 9 |
+
|
| 10 |
+
if (
|
| 11 |
+
isinstance(node, ast.Call)
|
| 12 |
+
and isinstance(node.func, ast.Name)
|
| 13 |
+
and node.func.id == "i18n"
|
| 14 |
+
):
|
| 15 |
+
for arg in node.args:
|
| 16 |
+
if isinstance(arg, ast.Str):
|
| 17 |
+
i18n_strings.append(arg.s)
|
| 18 |
+
|
| 19 |
+
for child_node in ast.iter_child_nodes(node):
|
| 20 |
+
i18n_strings.extend(extract_i18n_strings(child_node))
|
| 21 |
+
|
| 22 |
+
return i18n_strings
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# scan the directory for all .py files (recursively)
|
| 26 |
+
# for each file, parse the code into an AST
|
| 27 |
+
# for each AST, extract the i18n strings
|
| 28 |
+
|
| 29 |
+
strings = []
|
| 30 |
+
for filename in glob.iglob("**/*.py", recursive=True):
|
| 31 |
+
with open(filename, "r") as f:
|
| 32 |
+
code = f.read()
|
| 33 |
+
if "I18nAuto" in code:
|
| 34 |
+
tree = ast.parse(code)
|
| 35 |
+
i18n_strings = extract_i18n_strings(tree)
|
| 36 |
+
print(filename, len(i18n_strings))
|
| 37 |
+
strings.extend(i18n_strings)
|
| 38 |
+
code_keys = set(strings)
|
| 39 |
+
"""
|
| 40 |
+
n_i18n.py
|
| 41 |
+
gui_v1.py 26
|
| 42 |
+
app.py 16
|
| 43 |
+
infer-web.py 147
|
| 44 |
+
scan_i18n.py 0
|
| 45 |
+
i18n.py 0
|
| 46 |
+
lib/train/process_ckpt.py 1
|
| 47 |
+
"""
|
| 48 |
+
print()
|
| 49 |
+
print("Total unique:", len(code_keys))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
standard_file = "i18n/locale/zh_CN.json"
|
| 53 |
+
with open(standard_file, "r", encoding="utf-8") as f:
|
| 54 |
+
standard_data = json.load(f, object_pairs_hook=OrderedDict)
|
| 55 |
+
standard_keys = set(standard_data.keys())
|
| 56 |
+
|
| 57 |
+
# Define the standard file name
|
| 58 |
+
unused_keys = standard_keys - code_keys
|
| 59 |
+
print("Unused keys:", len(unused_keys))
|
| 60 |
+
for unused_key in unused_keys:
|
| 61 |
+
print("\t", unused_key)
|
| 62 |
+
|
| 63 |
+
missing_keys = code_keys - standard_keys
|
| 64 |
+
print("Missing keys:", len(missing_keys))
|
| 65 |
+
for missing_key in missing_keys:
|
| 66 |
+
print("\t", missing_key)
|
| 67 |
+
|
| 68 |
+
code_keys_dict = OrderedDict()
|
| 69 |
+
for s in strings:
|
| 70 |
+
code_keys_dict[s] = s
|
| 71 |
+
|
| 72 |
+
# write back
|
| 73 |
+
with open(standard_file, "w", encoding="utf-8") as f:
|
| 74 |
+
json.dump(code_keys_dict, f, ensure_ascii=False, indent=4, sort_keys=True)
|
| 75 |
+
f.write("\n")
|
install_dependencies.bat
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo ==========================================
|
| 3 |
+
echo Instalando uv (Gerenciador de pacotes rapido Python)...
|
| 4 |
+
echo ==========================================
|
| 5 |
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
| 6 |
+
|
| 7 |
+
echo.
|
| 8 |
+
echo ==========================================
|
| 9 |
+
echo Criando ambiente virtual (.venv)...
|
| 10 |
+
echo ==========================================
|
| 11 |
+
:: Tenta usar o uv do PATH. Se falhar, pode ser necessario reiniciar o terminal.
|
| 12 |
+
uv venv
|
| 13 |
+
|
| 14 |
+
echo.
|
| 15 |
+
echo ==========================================
|
| 16 |
+
echo Instalando dependencias do requirements.txt...
|
| 17 |
+
echo ==========================================
|
| 18 |
+
:: Ativa o venv temporariamente para o install (uv gerencia isso automaticamente se detectar o venv, mas vamos garantir)
|
| 19 |
+
:: Se o uv venv criou a pasta .venv, o uv pip install vai usar ela por padrao se estiver na raiz.
|
| 20 |
+
uv pip install -r requirements.txt
|
| 21 |
+
uv pip install auto-editor
|
| 22 |
+
|
| 23 |
+
echo.
|
| 24 |
+
echo ==========================================
|
| 25 |
+
echo Concluido!
|
| 26 |
+
echo ==========================================
|
| 27 |
+
pause
|
main_improved.py
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
# Suppress unnecessary logs before importing heavy libs
|
| 5 |
+
os.environ["ORT_LOGGING_LEVEL"] = "3"
|
| 6 |
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
| 7 |
+
|
| 8 |
+
import warnings
|
| 9 |
+
warnings.filterwarnings("ignore")
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import shutil
|
| 13 |
+
import subprocess
|
| 14 |
+
import argparse
|
| 15 |
+
import time
|
| 16 |
+
from scripts import (
|
| 17 |
+
download_video,
|
| 18 |
+
transcribe_video,
|
| 19 |
+
create_viral_segments,
|
| 20 |
+
cut_segments,
|
| 21 |
+
edit_video,
|
| 22 |
+
transcribe_cuts,
|
| 23 |
+
adjust_subtitles,
|
| 24 |
+
burn_subtitles,
|
| 25 |
+
save_json,
|
| 26 |
+
organize_output,
|
| 27 |
+
translate_json,
|
| 28 |
+
)
|
| 29 |
+
from i18n.i18n import I18nAuto
|
| 30 |
+
|
| 31 |
+
# Inicializa sistema de tradução
|
| 32 |
+
i18n = I18nAuto()
|
| 33 |
+
#
|
| 34 |
+
# Configurações de Legenda (ASS Style)
|
| 35 |
+
# Cores no formato BGR (Blue-Green-Red) para o ASS
|
| 36 |
+
COLORS = {
|
| 37 |
+
"red": "0000FF", # Red
|
| 38 |
+
"yellow": "00FFFF", # Yellow
|
| 39 |
+
"green": "00FF00", # Green
|
| 40 |
+
"white": "FFFFFF", # White
|
| 41 |
+
"black": "000000", # Black
|
| 42 |
+
"grey": "808080", # Grey
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
def get_subtitle_config(config_path=None):
|
| 46 |
+
"""
|
| 47 |
+
Returns the subtitle configuration dictionary.
|
| 48 |
+
Can be expanded to load from a JSON/YAML file in the future.
|
| 49 |
+
"""
|
| 50 |
+
# Default Config
|
| 51 |
+
base_color_transparency = "00"
|
| 52 |
+
outline_transparency = "FF"
|
| 53 |
+
highlight_color_transparency = "00"
|
| 54 |
+
shadow_color_transparency = "00"
|
| 55 |
+
|
| 56 |
+
config = {
|
| 57 |
+
"font": "Montserrat-Regular",
|
| 58 |
+
"base_size": 30,
|
| 59 |
+
"base_color": f"&H{base_color_transparency}{COLORS['white']}&",
|
| 60 |
+
"highlight_size": 35,
|
| 61 |
+
"words_per_block": 3,
|
| 62 |
+
"gap_limit": 0.5,
|
| 63 |
+
"mode": 'highlight', # Options: 'no_highlight', 'word_by_word', 'highlight'
|
| 64 |
+
"highlight_color": f"&H{highlight_color_transparency}{COLORS['green']}&",
|
| 65 |
+
"vertical_position": 210, # 1=170(top), ... 4=60(default)
|
| 66 |
+
"alignment": 2, # 2=Center
|
| 67 |
+
"bold": 0,
|
| 68 |
+
"italic": 0,
|
| 69 |
+
"underline": 0,
|
| 70 |
+
"strikeout": 0,
|
| 71 |
+
"border_style": 2, # 1=outline, 3=box
|
| 72 |
+
"outline_thickness": 1.5,
|
| 73 |
+
"outline_color": f"&H{outline_transparency}{COLORS['grey']}&",
|
| 74 |
+
"shadow_size": 2,
|
| 75 |
+
"shadow_color": f"&H{shadow_color_transparency}{COLORS['black']}&",
|
| 76 |
+
"remove_punctuation": True,
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if config_path and os.path.exists(config_path):
|
| 80 |
+
try:
|
| 81 |
+
with open(config_path, 'r', encoding='utf-8') as f:
|
| 82 |
+
loaded_config = json.load(f)
|
| 83 |
+
config.update(loaded_config)
|
| 84 |
+
print(f"Loaded subtitle config from {config_path}")
|
| 85 |
+
except Exception as e:
|
| 86 |
+
print(f"Error loading subtitle config: {e}. Using defaults.")
|
| 87 |
+
|
| 88 |
+
return config
|
| 89 |
+
|
| 90 |
+
def interactive_input_int(prompt_text):
|
| 91 |
+
"""Solicita um inteiro ao usuário via terminal."""
|
| 92 |
+
while True:
|
| 93 |
+
try:
|
| 94 |
+
value = int(input(i18n(prompt_text)))
|
| 95 |
+
if value > 0:
|
| 96 |
+
return value
|
| 97 |
+
print(i18n("\nError: Number must be greater than 0."))
|
| 98 |
+
except ValueError:
|
| 99 |
+
print(i18n("\nError: The value you entered is not an integer. Please try again."))
|
| 100 |
+
|
| 101 |
+
def main():
|
| 102 |
+
# Configuração de Argumentos via Linha de Comando (CLI)
|
| 103 |
+
parser = argparse.ArgumentParser(description="ViralCutter CLI")
|
| 104 |
+
parser.add_argument("--url", help="YouTube Video URL")
|
| 105 |
+
parser.add_argument("--segments", type=int, help="Number of segments to create")
|
| 106 |
+
parser.add_argument("--viral", action="store_true", help="Enable viral mode")
|
| 107 |
+
parser.add_argument("--themes", help="Comma-separated themes (if not viral mode)")
|
| 108 |
+
parser.add_argument("--burn-only", action="store_true", help="Skip processing and only burn subtitles")
|
| 109 |
+
parser.add_argument("--min-duration", type=int, default=15, help="Minimum segment duration (seconds)")
|
| 110 |
+
parser.add_argument("--max-duration", type=int, default=90, help="Maximum segment duration (seconds)")
|
| 111 |
+
parser.add_argument("--model", default="large-v3-turbo", help="Whisper model to use")
|
| 112 |
+
|
| 113 |
+
parser.add_argument("--ai-backend", choices=["manual", "gemini", "g4f", "local"], help="AI backend for viral analysis")
|
| 114 |
+
parser.add_argument("--api-key", help="Gemini API Key (required if ai-backend is gemini)")
|
| 115 |
+
|
| 116 |
+
parser.add_argument("--chunk-size", help="Override Chunk Size")
|
| 117 |
+
parser.add_argument("--ai-model-name", help="Override AI Model Name")
|
| 118 |
+
|
| 119 |
+
parser.add_argument("--project-path", help="Path to existing project folder (overrides URL/Latest)")
|
| 120 |
+
parser.add_argument("--workflow", choices=["1", "2", "3"], default="1", help="Workflow choice: 1=Full, 2=Cut Only, 3=Subtitles Only")
|
| 121 |
+
parser.add_argument("--face-model", choices=["insightface", "mediapipe"], default="insightface", help="Face detection model")
|
| 122 |
+
parser.add_argument("--face-mode", choices=["auto", "1", "2"], default="auto", help="Face tracking mode: auto, 1, 2")
|
| 123 |
+
parser.add_argument("--subtitle-config", help="Path to subtitle configuration JSON file")
|
| 124 |
+
parser.add_argument("--face-detect-interval", type=str, default="0.17,1.0", help="Face detection interval in seconds. Single value or 'interval_1face,interval_2face'")
|
| 125 |
+
parser.add_argument("--face-filter-threshold", type=float, default=0.35, help="Relative area threshold to ignore background faces (default: 0.35)")
|
| 126 |
+
parser.add_argument("--face-two-threshold", type=float, default=0.60, help="Relative area threshold to trigger 2-face mode (default: 0.60)")
|
| 127 |
+
parser.add_argument("--face-confidence-threshold", type=float, default=0.30, help="Face detection confidence threshold (0.0 - 1.0) (default: 0.30)")
|
| 128 |
+
parser.add_argument("--face-dead-zone", type=str, default="40", help="Camera movement dead zone in pixels (default: 40)") # str to support future "auto"
|
| 129 |
+
parser.add_argument("--focus-active-speaker", action="store_true", help="Enable experimental active speaker focus (InsightFace only)")
|
| 130 |
+
parser.add_argument("--active-speaker-mar", type=float, default=0.03, help="Mouth Aspect Ratio threshold for active speaker (0.0 - 1.0) (default: 0.03)")
|
| 131 |
+
parser.add_argument("--active-speaker-score-diff", type=float, default=1.5, help="Score difference to focus on active speaker (default: 1.5)")
|
| 132 |
+
parser.add_argument("--include-motion", action="store_true", help="Include motion (body/head movement) in activity score")
|
| 133 |
+
parser.add_argument("--active-speaker-motion-threshold", type=float, default=3.0, help="Motion deadzone in pixels (default: 3.0)")
|
| 134 |
+
parser.add_argument("--active-speaker-motion-sensitivity", type=float, default=0.05, help="Motion sensitivity multiplier (default: 0.05)")
|
| 135 |
+
parser.add_argument("--active-speaker-decay", type=float, default=2.0, help="Activity score decay rate (default: 2.0)")
|
| 136 |
+
parser.add_argument("--skip-prompts", action="store_true", help="Skip interactive prompts and use defaults/existing files")
|
| 137 |
+
parser.add_argument("--video-quality", choices=["best", "1080p", "720p", "480p"], default="best", help="Video download quality")
|
| 138 |
+
parser.add_argument("--skip-youtube-subs", action="store_true", help="Skip downloading YouTube subtitles")
|
| 139 |
+
parser.add_argument("--translate-target", help="Target language code for subtitle translation (e.g. 'pt', 'en').")
|
| 140 |
+
|
| 141 |
+
args = parser.parse_args()
|
| 142 |
+
|
| 143 |
+
# Workflow Logic
|
| 144 |
+
workflow_choice = args.workflow
|
| 145 |
+
|
| 146 |
+
# If Subtitles Only, checking project path
|
| 147 |
+
if workflow_choice == "3" and not args.project_path and not args.url and not args.skip_prompts:
|
| 148 |
+
# Prompt for project path or use latest if not provided?
|
| 149 |
+
pass # Will handle in main flow
|
| 150 |
+
|
| 151 |
+
# Modo Apenas Queimar Legenda (Legacy support, mapped to Workflow 3 internally if burn-only is set)
|
| 152 |
+
# Verifica o argumento CLI ou uma variável local hardcoded (para compatibilidade)
|
| 153 |
+
burn_only_mode = args.burn_only
|
| 154 |
+
|
| 155 |
+
if burn_only_mode:
|
| 156 |
+
print(i18n("Burn only mode activated. Switching to Workflow 3..."))
|
| 157 |
+
workflow_choice = "3"
|
| 158 |
+
|
| 159 |
+
# Obtenção de Inputs (CLI ou Interativo)
|
| 160 |
+
url = args.url
|
| 161 |
+
project_path_arg = args.project_path
|
| 162 |
+
input_video = None
|
| 163 |
+
|
| 164 |
+
# Se project_path for fornecido, ignoramos URL
|
| 165 |
+
if project_path_arg:
|
| 166 |
+
if os.path.exists(project_path_arg):
|
| 167 |
+
print(i18n("Using provided project path: {}").format(project_path_arg))
|
| 168 |
+
# Tentar achar o input.mp4 pra manter compatibilidade de variaveis, embora Workflow 3 não precise de download
|
| 169 |
+
possible_input = os.path.join(project_path_arg, "input.mp4")
|
| 170 |
+
if os.path.exists(possible_input):
|
| 171 |
+
input_video = possible_input
|
| 172 |
+
else:
|
| 173 |
+
# Se não tiver input.mp4, tudo bem para workflow 3, mas definimos um dummy para não quebrar logica
|
| 174 |
+
input_video = os.path.join(project_path_arg, "dummy_input.mp4")
|
| 175 |
+
|
| 176 |
+
# Se for workflow 3, não precisamos de URL
|
| 177 |
+
else:
|
| 178 |
+
print(i18n("Error: Provided project path does not exist."))
|
| 179 |
+
sys.exit(1)
|
| 180 |
+
|
| 181 |
+
# Se não temos URL via CLI nem Project Path, pedimos agora
|
| 182 |
+
if not url and not project_path_arg:
|
| 183 |
+
if args.skip_prompts:
|
| 184 |
+
print(i18n("No URL provided and skipping prompts. Trying to load latest project..."))
|
| 185 |
+
# Fallthrough to project loading logic
|
| 186 |
+
else:
|
| 187 |
+
user_input = input(i18n("Enter the YouTube video URL (or press Enter to use latest project): ")).strip()
|
| 188 |
+
if user_input:
|
| 189 |
+
url = user_input
|
| 190 |
+
|
| 191 |
+
if not url and not input_video:
|
| 192 |
+
# Usuário apertou Enter (Vazio) -> Tentar pegar último projeto
|
| 193 |
+
base_virals = "VIRALS"
|
| 194 |
+
if os.path.exists(base_virals):
|
| 195 |
+
subdirs = [os.path.join(base_virals, d) for d in os.listdir(base_virals) if os.path.isdir(os.path.join(base_virals, d))]
|
| 196 |
+
if subdirs:
|
| 197 |
+
latest_project = max(subdirs, key=os.path.getmtime)
|
| 198 |
+
detected_video = os.path.join(latest_project, "input.mp4")
|
| 199 |
+
if os.path.exists(detected_video):
|
| 200 |
+
input_video = detected_video
|
| 201 |
+
print(i18n("Using latest project: {}").format(latest_project))
|
| 202 |
+
else:
|
| 203 |
+
print(i18n("Latest project found but 'input.mp4' is missing."))
|
| 204 |
+
sys.exit(1)
|
| 205 |
+
else:
|
| 206 |
+
print(i18n("No existing projects found in VIRALS folder."))
|
| 207 |
+
sys.exit(1)
|
| 208 |
+
else:
|
| 209 |
+
print(i18n("VIRALS folder not found. Cannot load latest project."))
|
| 210 |
+
sys.exit(1)
|
| 211 |
+
|
| 212 |
+
# -------------------------------------------------------------------------
|
| 213 |
+
# Checagem Antecipada de Segmentos Virais (Para pular configurações se já existirem)
|
| 214 |
+
# -------------------------------------------------------------------------
|
| 215 |
+
viral_segments = None
|
| 216 |
+
project_folder_anticipated = None
|
| 217 |
+
|
| 218 |
+
if input_video:
|
| 219 |
+
# Se já temos o vídeo, podemos deduzir a pasta
|
| 220 |
+
project_folder_anticipated = os.path.dirname(input_video)
|
| 221 |
+
viral_segments_file = os.path.join(project_folder_anticipated, "viral_segments.txt")
|
| 222 |
+
|
| 223 |
+
if os.path.exists(viral_segments_file):
|
| 224 |
+
print(i18n("\nExisting viral segments found: {}").format(viral_segments_file))
|
| 225 |
+
if args.skip_prompts:
|
| 226 |
+
use_existing_json = 'yes'
|
| 227 |
+
else:
|
| 228 |
+
use_existing_json = input(i18n("Use existing viral segments? (yes/no) [default: yes]: ")).strip().lower()
|
| 229 |
+
|
| 230 |
+
if use_existing_json in ['', 'y', 'yes']:
|
| 231 |
+
try:
|
| 232 |
+
with open(viral_segments_file, 'r', encoding='utf-8') as f:
|
| 233 |
+
viral_segments = json.load(f)
|
| 234 |
+
print(i18n("Loaded existing viral segments. Skipping configuration prompts."))
|
| 235 |
+
if viral_segments and "segments" in viral_segments:
|
| 236 |
+
print(f"DEBUG: Loaded {len(viral_segments['segments'])} segments from file.")
|
| 237 |
+
else:
|
| 238 |
+
print("DEBUG: Loaded JSON but 'segments' key is missing or empty.")
|
| 239 |
+
except Exception as e:
|
| 240 |
+
print(i18n("Error loading JSON: {}.").format(e))
|
| 241 |
+
|
| 242 |
+
# Variaveis de config de IA (só necessárias se não tivermos os segmentos)
|
| 243 |
+
num_segments = None
|
| 244 |
+
viral_mode = False
|
| 245 |
+
themes = ""
|
| 246 |
+
ai_backend = "manual" # default
|
| 247 |
+
api_key = None
|
| 248 |
+
|
| 249 |
+
if not viral_segments:
|
| 250 |
+
num_segments = args.segments
|
| 251 |
+
if not num_segments:
|
| 252 |
+
if args.skip_prompts:
|
| 253 |
+
print("No segments count provided and skip-prompts is ON. Using default 3.")
|
| 254 |
+
num_segments = 3
|
| 255 |
+
else:
|
| 256 |
+
num_segments = interactive_input_int("Enter the number of viral segments to create: ")
|
| 257 |
+
|
| 258 |
+
viral_mode = args.viral
|
| 259 |
+
if not args.viral and not args.themes:
|
| 260 |
+
if args.skip_prompts:
|
| 261 |
+
print("Viral mode not set, defaulting to True.")
|
| 262 |
+
viral_mode = True
|
| 263 |
+
else:
|
| 264 |
+
response = input(i18n("Do you want viral mode? (yes/no): ")).lower()
|
| 265 |
+
viral_mode = response in ['yes', 'y']
|
| 266 |
+
|
| 267 |
+
themes = args.themes if args.themes else ""
|
| 268 |
+
if not viral_mode and not themes:
|
| 269 |
+
if not args.skip_prompts:
|
| 270 |
+
themes = input(i18n("Enter themes (comma-separated, leave blank if viral mode is True): "))
|
| 271 |
+
|
| 272 |
+
# Duration Config
|
| 273 |
+
print(i18n("\nCurrent duration settings: {}s - {}s").format(args.min_duration, args.max_duration))
|
| 274 |
+
if not args.skip_prompts:
|
| 275 |
+
change_dur = input(i18n("Change duration? (y/n) [default: n]: ")).strip().lower()
|
| 276 |
+
if change_dur in ['y', 'yes']:
|
| 277 |
+
try:
|
| 278 |
+
min_d = input(i18n("Minimum duration [{}]: ").format(args.min_duration)).strip()
|
| 279 |
+
if min_d: args.min_duration = int(min_d)
|
| 280 |
+
|
| 281 |
+
max_d = input(i18n("Maximum duration [{}]: ").format(args.max_duration)).strip()
|
| 282 |
+
if max_d: args.max_duration = int(max_d)
|
| 283 |
+
except ValueError:
|
| 284 |
+
print(i18n("Invalid number. Using previous values."))
|
| 285 |
+
|
| 286 |
+
# Load API Config
|
| 287 |
+
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'api_config.json')
|
| 288 |
+
api_config = {}
|
| 289 |
+
if os.path.exists(config_path):
|
| 290 |
+
try:
|
| 291 |
+
with open(config_path, 'r', encoding='utf-8') as f:
|
| 292 |
+
api_config = json.load(f)
|
| 293 |
+
except:
|
| 294 |
+
pass
|
| 295 |
+
|
| 296 |
+
# Seleção do Backend de IA
|
| 297 |
+
ai_backend = args.ai_backend
|
| 298 |
+
|
| 299 |
+
# Try to load backend from config if not in args
|
| 300 |
+
if not ai_backend and api_config.get("selected_api"):
|
| 301 |
+
ai_backend = api_config.get("selected_api")
|
| 302 |
+
print(i18n("Using AI Backend from config: {}").format(ai_backend))
|
| 303 |
+
|
| 304 |
+
if not ai_backend:
|
| 305 |
+
if args.skip_prompts:
|
| 306 |
+
print("No AI backend selected, defaulting to Manual.")
|
| 307 |
+
ai_backend = "manual"
|
| 308 |
+
else:
|
| 309 |
+
print("\n" + i18n("Select AI Backend for Viral Analysis:"))
|
| 310 |
+
print(i18n("1. Gemini API (Best / Recommended)"))
|
| 311 |
+
print(i18n("2. G4F (Free / Experimental)"))
|
| 312 |
+
print(i18n("3. Local (GGUF via llama.cpp)"))
|
| 313 |
+
print(i18n("4. Manual (Copy/Paste Prompt)"))
|
| 314 |
+
choice = input(i18n("Choose (1-4): ")).strip()
|
| 315 |
+
|
| 316 |
+
if choice == "1":
|
| 317 |
+
ai_backend = "gemini"
|
| 318 |
+
elif choice == "2":
|
| 319 |
+
ai_backend = "g4f"
|
| 320 |
+
elif choice == "3":
|
| 321 |
+
ai_backend = "local"
|
| 322 |
+
# Interactive model selection for local
|
| 323 |
+
# List models
|
| 324 |
+
models_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")
|
| 325 |
+
if not os.path.exists(models_dir): os.makedirs(models_dir)
|
| 326 |
+
models = [f for f in os.listdir(models_dir) if f.endswith(".gguf")]
|
| 327 |
+
|
| 328 |
+
if not models:
|
| 329 |
+
print(i18n("\nNo .gguf models found in 'models' directory."))
|
| 330 |
+
print(i18n("Please place a module file in: {}").format(models_dir))
|
| 331 |
+
print(i18n("Falling back to Manual..."))
|
| 332 |
+
ai_backend = "manual"
|
| 333 |
+
else:
|
| 334 |
+
print(i18n("\nAvailable Models:"))
|
| 335 |
+
for idx, m in enumerate(models):
|
| 336 |
+
print(f"{idx+1}. {m}")
|
| 337 |
+
|
| 338 |
+
try:
|
| 339 |
+
m_idx = int(input(i18n("Select Model (Number): "))) - 1
|
| 340 |
+
if 0 <= m_idx < len(models):
|
| 341 |
+
args.ai_model_name = models[m_idx] # Set global arg
|
| 342 |
+
else:
|
| 343 |
+
print("Invalid selection. Using first model.")
|
| 344 |
+
args.ai_model_name = models[0]
|
| 345 |
+
except:
|
| 346 |
+
print("Invalid input. Using first model.")
|
| 347 |
+
args.ai_model_name = models[0]
|
| 348 |
+
|
| 349 |
+
else:
|
| 350 |
+
ai_backend = "manual"
|
| 351 |
+
|
| 352 |
+
api_key = args.api_key
|
| 353 |
+
# Check config for API Key if using Gemini
|
| 354 |
+
if ai_backend == "gemini" and not api_key:
|
| 355 |
+
cfg_key = api_config.get("gemini", {}).get("api_key", "")
|
| 356 |
+
if cfg_key and cfg_key != "SUA_KEY_AQUI":
|
| 357 |
+
api_key = cfg_key
|
| 358 |
+
|
| 359 |
+
if ai_backend == "gemini" and not api_key:
|
| 360 |
+
if args.skip_prompts:
|
| 361 |
+
print("Gemini API key missing, but skip-prompts is ON. Might fail.")
|
| 362 |
+
else:
|
| 363 |
+
print(i18n("Gemini API Key not found in api_config.json or arguments."))
|
| 364 |
+
api_key = input(i18n("Enter your Gemini API Key: ")).strip()
|
| 365 |
+
|
| 366 |
+
# Workflow & Face Config Inputs
|
| 367 |
+
workflow_choice = args.workflow
|
| 368 |
+
face_model = args.face_model
|
| 369 |
+
face_mode = args.face_mode
|
| 370 |
+
|
| 371 |
+
# If args weren't provided and we are not skipping prompts, ask user
|
| 372 |
+
# Note: argparse defaults are set, so they "are provided" effectively.
|
| 373 |
+
# To truly detect "not provided", request default=None in argparse.
|
| 374 |
+
# But for "Simplified Mode", defaults are good.
|
| 375 |
+
# Advanced users use params.
|
| 376 |
+
# We will assume CLI defaults are what we want if skip_prompts is on.
|
| 377 |
+
|
| 378 |
+
# Logic for detection intervals (Moved out of interactive block to support CLI/WebUI)
|
| 379 |
+
detection_intervals = None
|
| 380 |
+
if args.face_detect_interval:
|
| 381 |
+
try:
|
| 382 |
+
parts = args.face_detect_interval.split(',')
|
| 383 |
+
if len(parts) == 1:
|
| 384 |
+
val = float(parts[0])
|
| 385 |
+
detection_intervals = {'1': val, '2': val}
|
| 386 |
+
elif len(parts) >= 2:
|
| 387 |
+
val1 = float(parts[0])
|
| 388 |
+
val2 = float(parts[1])
|
| 389 |
+
detection_intervals = {'1': val1, '2': val2}
|
| 390 |
+
except ValueError:
|
| 391 |
+
pass
|
| 392 |
+
|
| 393 |
+
if not args.burn_only and not args.skip_prompts:
|
| 394 |
+
# Interactive Face Config
|
| 395 |
+
print(i18n("\n--- Face Detection Settings ---"))
|
| 396 |
+
print(i18n("Current Face Model: {} | Mode: {}").format(face_model, face_mode))
|
| 397 |
+
|
| 398 |
+
if detection_intervals:
|
| 399 |
+
print(i18n("Custom detection intervals: {}").format(detection_intervals))
|
| 400 |
+
else:
|
| 401 |
+
print(i18n("Using dynamic intervals: 1s for 2-face, ~0.16s for 1-face."))
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
# Pipeline Execution
|
| 405 |
+
try:
|
| 406 |
+
# 1. Download & Project Setup
|
| 407 |
+
print(f"DEBUG: Checking input_video state. input_video={input_video}")
|
| 408 |
+
|
| 409 |
+
if not input_video:
|
| 410 |
+
if not url:
|
| 411 |
+
print(i18n("Error: No URL provided and no existing video selected."))
|
| 412 |
+
sys.exit(1)
|
| 413 |
+
|
| 414 |
+
print(i18n("Starting download..."))
|
| 415 |
+
download_subs = not args.skip_youtube_subs
|
| 416 |
+
download_result = download_video.download(url, download_subs=download_subs, quality=args.video_quality)
|
| 417 |
+
|
| 418 |
+
if isinstance(download_result, tuple):
|
| 419 |
+
input_video, project_folder = download_result
|
| 420 |
+
else:
|
| 421 |
+
input_video = download_result
|
| 422 |
+
project_folder = os.path.dirname(input_video)
|
| 423 |
+
|
| 424 |
+
print(f"DEBUG: Download finished. input_video={input_video}, project_folder={project_folder}")
|
| 425 |
+
|
| 426 |
+
else:
|
| 427 |
+
# Reuso de video existente
|
| 428 |
+
print("DEBUG: Using existing video logic.")
|
| 429 |
+
project_folder = os.path.dirname(input_video)
|
| 430 |
+
|
| 431 |
+
print(f"Project Folder: {project_folder}")
|
| 432 |
+
|
| 433 |
+
# 2. Transcribe
|
| 434 |
+
if workflow_choice == "3":
|
| 435 |
+
print(i18n("Workflow 3: Skipping Transcribe."))
|
| 436 |
+
# We assume transcription exists (SRT/JSON) or we won't need it for 'adjust_subtitles' if it uses 'subs/*.json' which are created by 'cut_segments'
|
| 437 |
+
# Actually 'adjust_subtitles' reads from 'project_folder/subs'.
|
| 438 |
+
# viral_segments = True # Removed to avoid overwritting dict loaded earlier
|
| 439 |
+
else:
|
| 440 |
+
print(i18n("Transcribing with model {}...").format(args.model))
|
| 441 |
+
# Se skip config, args.model é default
|
| 442 |
+
srt_file, tsv_file = transcribe_video.transcribe(input_video, args.model, project_folder=project_folder)
|
| 443 |
+
|
| 444 |
+
# 3. Create Viral Segments
|
| 445 |
+
if workflow_choice != "3":
|
| 446 |
+
# Se não carregamos 'viral_segments' lá em cima (ou se era download novo), checamos agora ou criamos
|
| 447 |
+
if not viral_segments:
|
| 448 |
+
# Checagem tardia para downloads novos que por acaso ja tenham json (Ex: URL repetida)
|
| 449 |
+
viral_segments_file_late = os.path.join(project_folder, "viral_segments.txt")
|
| 450 |
+
if os.path.exists(viral_segments_file_late):
|
| 451 |
+
print(i18n("Found existing viral segments file at {}").format(viral_segments_file_late))
|
| 452 |
+
if args.skip_prompts:
|
| 453 |
+
print(i18n("Skipping prompts enabled. Loading existing segments."))
|
| 454 |
+
try:
|
| 455 |
+
with open(viral_segments_file_late, 'r', encoding='utf-8') as f:
|
| 456 |
+
viral_segments = json.load(f)
|
| 457 |
+
except Exception as e:
|
| 458 |
+
print(i18n("Error loading existing JSON: {}. Proceeding to create new segments.").format(e))
|
| 459 |
+
else:
|
| 460 |
+
print(i18n("Loading existing viral segments found at {}").format(viral_segments_file_late))
|
| 461 |
+
try:
|
| 462 |
+
with open(viral_segments_file_late, 'r', encoding='utf-8') as f:
|
| 463 |
+
viral_segments = json.load(f)
|
| 464 |
+
except Exception as e:
|
| 465 |
+
print(i18n("Error loading existing JSON: {}.").format(e))
|
| 466 |
+
|
| 467 |
+
if not viral_segments:
|
| 468 |
+
print(i18n("Creating viral segments using {}...").format(ai_backend.upper()))
|
| 469 |
+
viral_segments = create_viral_segments.create(
|
| 470 |
+
num_segments,
|
| 471 |
+
viral_mode,
|
| 472 |
+
themes,
|
| 473 |
+
args.min_duration,
|
| 474 |
+
args.max_duration,
|
| 475 |
+
ai_mode=ai_backend,
|
| 476 |
+
api_key=api_key,
|
| 477 |
+
project_folder=project_folder,
|
| 478 |
+
chunk_size_arg=args.chunk_size,
|
| 479 |
+
model_name_arg=args.ai_model_name
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
if not viral_segments or not viral_segments.get("segments"):
|
| 483 |
+
print(i18n("Error: No viral segments were generated."))
|
| 484 |
+
print(i18n("Possible reasons: API error, Model not found, or empty response."))
|
| 485 |
+
print(i18n("Stopping execution."))
|
| 486 |
+
sys.exit(1)
|
| 487 |
+
|
| 488 |
+
save_json.save_viral_segments(viral_segments, project_folder=project_folder)
|
| 489 |
+
|
| 490 |
+
# 4. Cut Segments
|
| 491 |
+
# Se workflow for 3, pulamos corte
|
| 492 |
+
if workflow_choice == "3":
|
| 493 |
+
print(i18n("Workflow 3 (Subtitles Only): Skipping Cut and Edit."))
|
| 494 |
+
# Deduzir cuts folder apenas para log
|
| 495 |
+
cuts_folder = os.path.join(project_folder, "cuts")
|
| 496 |
+
else:
|
| 497 |
+
cuts_folder = os.path.join(project_folder, "cuts")
|
| 498 |
+
skip_cutting = False
|
| 499 |
+
|
| 500 |
+
if os.path.exists(cuts_folder) and os.listdir(cuts_folder):
|
| 501 |
+
print(i18n("\nExisting cuts found in: {}").format(cuts_folder))
|
| 502 |
+
if args.skip_prompts:
|
| 503 |
+
cut_again_resp = 'no'
|
| 504 |
+
else:
|
| 505 |
+
cut_again_resp = input(i18n("Cuts already exist. Cut again? (yes/no) [default: no]: ")).strip().lower()
|
| 506 |
+
|
| 507 |
+
# Default is no (skip) if they just press enter or say no
|
| 508 |
+
if cut_again_resp not in ['y', 'yes']:
|
| 509 |
+
skip_cutting = True
|
| 510 |
+
|
| 511 |
+
if skip_cutting:
|
| 512 |
+
print(i18n("Skipping Video Rendering (using existing cuts), but updating Subtitle JSONs..."))
|
| 513 |
+
else:
|
| 514 |
+
print(i18n("Cutting segments..."))
|
| 515 |
+
|
| 516 |
+
cut_segments.cut(viral_segments, project_folder=project_folder, skip_video=skip_cutting)
|
| 517 |
+
|
| 518 |
+
# 5. Workflow Check
|
| 519 |
+
if workflow_choice == "2":
|
| 520 |
+
print(i18n("Cut Only selected. Skipping Face Crop and Subtitles."))
|
| 521 |
+
print(i18n(f"Process completed! Check your results in: {project_folder}"))
|
| 522 |
+
sys.exit(0)
|
| 523 |
+
|
| 524 |
+
# 5. Edit Video (Face Crop)
|
| 525 |
+
if workflow_choice != "3":
|
| 526 |
+
print(i18n("Editing video with {} (Mode: {})...").format(face_model, face_mode))
|
| 527 |
+
|
| 528 |
+
# Parse dead zone safely
|
| 529 |
+
try:
|
| 530 |
+
dead_zone_val = float(args.face_dead_zone)
|
| 531 |
+
except:
|
| 532 |
+
dead_zone_val = 40.0
|
| 533 |
+
|
| 534 |
+
edit_video.edit(
|
| 535 |
+
project_folder=project_folder,
|
| 536 |
+
face_model=face_model,
|
| 537 |
+
face_mode=face_mode,
|
| 538 |
+
detection_period=detection_intervals,
|
| 539 |
+
filter_threshold=args.face_filter_threshold,
|
| 540 |
+
two_face_threshold=args.face_two_threshold,
|
| 541 |
+
confidence_threshold=args.face_confidence_threshold,
|
| 542 |
+
dead_zone=dead_zone_val,
|
| 543 |
+
focus_active_speaker=args.focus_active_speaker,
|
| 544 |
+
active_speaker_mar=args.active_speaker_mar,
|
| 545 |
+
active_speaker_score_diff=args.active_speaker_score_diff,
|
| 546 |
+
include_motion=args.include_motion,
|
| 547 |
+
active_speaker_motion_deadzone=args.active_speaker_motion_threshold,
|
| 548 |
+
active_speaker_motion_sensitivity=args.active_speaker_motion_sensitivity,
|
| 549 |
+
active_speaker_decay=args.active_speaker_decay,
|
| 550 |
+
segments_data=viral_segments.get("segments", []) if viral_segments else None
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
else:
|
| 555 |
+
print(i18n("Workflow 3: Skipping Face Crop."))
|
| 556 |
+
# Rename existing files if viral_segments available (since edit_video didn't run)
|
| 557 |
+
if viral_segments and "segments" in viral_segments:
|
| 558 |
+
segments_data = viral_segments.get("segments", [])
|
| 559 |
+
final_folder = os.path.join(project_folder, "final")
|
| 560 |
+
subs_folder = os.path.join(project_folder, "subs")
|
| 561 |
+
|
| 562 |
+
print("Renaming existing files with titles...")
|
| 563 |
+
for idx, segment in enumerate(segments_data):
|
| 564 |
+
title = segment.get("title", f"Segment_{idx}")
|
| 565 |
+
safe_title = "".join([c for c in title if c.isalnum() or c in " _-"]).strip()
|
| 566 |
+
safe_title = safe_title.replace(" ", "_")[:60]
|
| 567 |
+
|
| 568 |
+
new_base_name = f"{idx:03d}_{safe_title}"
|
| 569 |
+
|
| 570 |
+
# 1. MP4
|
| 571 |
+
old_mp4_name = f"final-output{idx:03d}_processed.mp4"
|
| 572 |
+
old_mp4_path = os.path.join(final_folder, old_mp4_name)
|
| 573 |
+
new_mp4_path = os.path.join(final_folder, f"{new_base_name}.mp4")
|
| 574 |
+
if os.path.exists(old_mp4_path) and not os.path.exists(new_mp4_path):
|
| 575 |
+
os.rename(old_mp4_path, new_mp4_path)
|
| 576 |
+
print(f"Renamed (Workflow 3): {old_mp4_name} -> {new_base_name}.mp4")
|
| 577 |
+
|
| 578 |
+
# 2. JSON Sub
|
| 579 |
+
old_json_name = f"final-output{idx:03d}_processed.json"
|
| 580 |
+
old_json_path = os.path.join(subs_folder, old_json_name)
|
| 581 |
+
new_json_path = os.path.join(subs_folder, f"{new_base_name}_processed.json")
|
| 582 |
+
if os.path.exists(old_json_path) and not os.path.exists(new_json_path):
|
| 583 |
+
os.rename(old_json_path, new_json_path)
|
| 584 |
+
print(f"Renamed (Workflow 3): {old_json_name} -> {new_base_name}_processed.json")
|
| 585 |
+
|
| 586 |
+
# 3. Timeline
|
| 587 |
+
old_tl_name = f"temp_video_no_audio_{idx}_timeline.json"
|
| 588 |
+
old_tl_path = os.path.join(final_folder, old_tl_name)
|
| 589 |
+
new_tl_path = os.path.join(final_folder, f"{new_base_name}_timeline.json")
|
| 590 |
+
if os.path.exists(old_tl_path) and not os.path.exists(new_tl_path):
|
| 591 |
+
os.rename(old_tl_path, new_tl_path)
|
| 592 |
+
print(f"Renamed (Workflow 3): {old_tl_name} -> {new_base_name}_timeline.json")
|
| 593 |
+
|
| 594 |
+
# 6. Subtitles
|
| 595 |
+
burn_subtitles_option = True
|
| 596 |
+
if burn_subtitles_option:
|
| 597 |
+
print(i18n("Processing subtitles..."))
|
| 598 |
+
# transcribe_cuts removido: JSON de legenda já é gerado no corte
|
| 599 |
+
# transcribe_cuts.transcribe(project_folder=project_folder)
|
| 600 |
+
|
| 601 |
+
# --- Translation Integration ---
|
| 602 |
+
if args.translate_target and args.translate_target.lower() != "none":
|
| 603 |
+
print(i18n("Translating subtitles to: {}").format(args.translate_target))
|
| 604 |
+
import asyncio
|
| 605 |
+
try:
|
| 606 |
+
asyncio.run(translate_json.translate_project_subs(project_folder, args.translate_target))
|
| 607 |
+
except Exception as e:
|
| 608 |
+
print(i18n("Translation failed: {}").format(e))
|
| 609 |
+
# -------------------------------
|
| 610 |
+
|
| 611 |
+
sub_config = get_subtitle_config(args.subtitle_config)
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
# Passa o dicionário desempacotado como argumentos, mais o project_folder
|
| 616 |
+
try:
|
| 617 |
+
adjust_subtitles.adjust(project_folder=project_folder, **sub_config)
|
| 618 |
+
burn_subtitles.burn(project_folder=project_folder)
|
| 619 |
+
except FileNotFoundError as fnf_error:
|
| 620 |
+
print(i18n("\n[ERROR] Subtitle processing failed: {}").format(str(fnf_error)))
|
| 621 |
+
print(i18n("Tip: If you are using Workflow 3 (Subtitles Only), ensure the 'subs' folder exists and contains valid JSON files."))
|
| 622 |
+
sys.exit(1)
|
| 623 |
+
except Exception as e:
|
| 624 |
+
print(i18n("\n[ERROR] Unexpected error during subtitle processing: {}").format(str(e)))
|
| 625 |
+
raise e
|
| 626 |
+
else:
|
| 627 |
+
print(i18n("Subtitle burning skipped."))
|
| 628 |
+
|
| 629 |
+
# Organização Final (Opcional, pois agora já está tudo em project_folder)
|
| 630 |
+
# organize_output.organize(project_folder=project_folder)
|
| 631 |
+
|
| 632 |
+
# --- Save Processing Configuration ---
|
| 633 |
+
try:
|
| 634 |
+
# Determine AI Model used
|
| 635 |
+
used_ai_model = args.ai_model_name
|
| 636 |
+
if not used_ai_model and ai_backend != "manual":
|
| 637 |
+
if ai_backend == "gemini":
|
| 638 |
+
used_ai_model = api_config.get("gemini", {}).get("model", "default")
|
| 639 |
+
elif ai_backend == "g4f":
|
| 640 |
+
used_ai_model = api_config.get("g4f", {}).get("model", "default")
|
| 641 |
+
|
| 642 |
+
# Ensure sub_config exists
|
| 643 |
+
current_sub_config = sub_config if 'sub_config' in locals() else get_subtitle_config(args.subtitle_config)
|
| 644 |
+
|
| 645 |
+
final_config = {
|
| 646 |
+
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 647 |
+
"workflow": workflow_choice,
|
| 648 |
+
"ai_config": {
|
| 649 |
+
"backend": ai_backend,
|
| 650 |
+
"model_name": used_ai_model,
|
| 651 |
+
"viral_mode": viral_mode,
|
| 652 |
+
"themes": themes,
|
| 653 |
+
"num_segments": num_segments,
|
| 654 |
+
"chunk_size": args.chunk_size
|
| 655 |
+
},
|
| 656 |
+
"face_config": {
|
| 657 |
+
"model": face_model,
|
| 658 |
+
"mode": face_mode,
|
| 659 |
+
"detect_interval": args.face_detect_interval,
|
| 660 |
+
"filter_threshold": args.face_filter_threshold,
|
| 661 |
+
"two_face_threshold": args.face_two_threshold,
|
| 662 |
+
"confidence_threshold": args.face_confidence_threshold,
|
| 663 |
+
"dead_zone": args.face_dead_zone,
|
| 664 |
+
"focus_active_speaker": args.focus_active_speaker,
|
| 665 |
+
"active_speaker_mar": args.active_speaker_mar,
|
| 666 |
+
"active_speaker_score_diff": args.active_speaker_score_diff,
|
| 667 |
+
"include_motion": args.include_motion
|
| 668 |
+
},
|
| 669 |
+
"video_config": {
|
| 670 |
+
"min_duration": args.min_duration,
|
| 671 |
+
"max_duration": args.max_duration,
|
| 672 |
+
"whisper_model": args.model
|
| 673 |
+
},
|
| 674 |
+
"subtitle_config": current_sub_config
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
config_save_path = os.path.join(project_folder, "process_config.json")
|
| 678 |
+
with open(config_save_path, "w", encoding="utf-8") as f:
|
| 679 |
+
json.dump(final_config, f, indent=4, ensure_ascii=False)
|
| 680 |
+
print(i18n("Configuration saved to: {}").format(config_save_path))
|
| 681 |
+
|
| 682 |
+
except Exception as e:
|
| 683 |
+
print(i18n("Error saving configuration JSON: {}").format(e))
|
| 684 |
+
# -------------------------------------
|
| 685 |
+
|
| 686 |
+
print(i18n("Process completed! Check your results in: {}").format(project_folder))
|
| 687 |
+
|
| 688 |
+
except Exception as e:
|
| 689 |
+
print(i18n("\nAn error occurred: {}").format(str(e)))
|
| 690 |
+
import traceback
|
| 691 |
+
traceback.print_exc()
|
| 692 |
+
sys.exit(1)
|
| 693 |
+
|
| 694 |
+
if __name__ == "__main__":
|
| 695 |
+
main()
|
models/.local-model-here
ADDED
|
File without changes
|
prompt.txt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
You are a World-Class Viral Video Editor and Storyteller. You are an expert at finding "gold nuggets" in long transcripts that can stand alone as viral TikToks/Reels/Shorts.
|
| 3 |
+
|
| 4 |
+
Your goal is to extract segments that have **Perfect Narrative Completeness** (Start, Middle, End) while respecting time constraints.
|
| 5 |
+
|
| 6 |
+
### INPUT FORMAT EXPLAINED
|
| 7 |
+
The transcript below is a continuous text stream with embedded **Time Tags** like `(12s)`.
|
| 8 |
+
- Example: `"Hello world (0s). Today we are going to (3s) fly to the moon."`
|
| 9 |
+
- These tags represent the approximate timestamp of the *preceding* text.
|
| 10 |
+
- Use them to calculate duration. Duration = (End Tag - Start Tag).
|
| 11 |
+
|
| 12 |
+
### STRICT VIRAL RULES (The "ViralCutter Standard"):
|
| 13 |
+
1. **NO "GARBAGE" STARTS:**
|
| 14 |
+
- NEVER start with filler words ("Um", "Uh", "So...", "And then").
|
| 15 |
+
- NEVER start with low-energy intros ("Hi guys, welcome back").
|
| 16 |
+
- **ALWAYS** start with a Hook: A strong statement, a question, or an action verb.
|
| 17 |
+
- *Bad Start:* "(10s) So, I was thinking..."
|
| 18 |
+
- *Good Start:* "(12s) I almost died yesterday."
|
| 19 |
+
|
| 20 |
+
2. **THE "STANDALONE" TEST:**
|
| 21 |
+
- Can this segment be shown to a stranger without ANY context?
|
| 22 |
+
- If it contains "That's why *he* said that" (and "he" is unknown), it FAILS. Eliminate or expand context.
|
| 23 |
+
|
| 24 |
+
3. **NARRATIVE ARC:**
|
| 25 |
+
- **Start:** Hook (0-3s).
|
| 26 |
+
- **Middle:** Value/Story (Retain attention).
|
| 27 |
+
- **End:** Punchline/Conclusion (Satisfying ending). NEVER cut mid-sentence.
|
| 28 |
+
|
| 29 |
+
4. **DURATION MATH:**
|
| 30 |
+
- Use the `(XXs)` tags to estimate duration.
|
| 31 |
+
- Target: {min_duration}s to {max_duration}s.
|
| 32 |
+
|
| 33 |
+
### YOUR TASK:
|
| 34 |
+
Analyze the transcript below. Find {amount} potential viral segments.
|
| 35 |
+
|
| 36 |
+
TRANSCRIPT:
|
| 37 |
+
{transcript_chunk}
|
| 38 |
+
|
| 39 |
+
### OUTPUT FORMAT (JSON ONLY):
|
| 40 |
+
{json_template}
|
| 41 |
+
|
| 42 |
+
Return ONLY VALID JSON. No markdown, no commentary.
|
requirements.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
g4f[all]
|
| 2 |
+
yt-dlp
|
| 3 |
+
ffmpeg-python
|
| 4 |
+
whisperx
|
| 5 |
+
mediapipe
|
| 6 |
+
google-genai
|
| 7 |
+
insightface
|
| 8 |
+
onnxruntime-gpu
|
| 9 |
+
gradio
|
| 10 |
+
opencv-python
|
| 11 |
+
numpy
|
| 12 |
+
psutil
|
| 13 |
+
fastapi
|
| 14 |
+
uvicorn
|
| 15 |
+
torch
|
| 16 |
+
deep-translator
|
| 17 |
+
tqdm
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Local LLM Support with CUDA 12.4
|
| 22 |
+
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu124
|
| 23 |
+
llama-cpp-python
|
run.bat
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
title ViralCutter
|
| 4 |
+
|
| 5 |
+
cd /d "%~dp0"
|
| 6 |
+
call .venv\Scripts\activate.bat
|
| 7 |
+
python main_improved.py
|
| 8 |
+
echo.
|
| 9 |
+
pause
|
run_webui.bat
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
call .venv\Scripts\activate.bat
|
| 3 |
+
python webui\app.py
|
| 4 |
+
pause
|
scripts/__pycache__/adjust_subtitles.cpython-310.pyc
ADDED
|
Binary file (5.29 kB). View file
|
|
|
scripts/__pycache__/adjust_subtitles.cpython-311.pyc
ADDED
|
Binary file (13.1 kB). View file
|
|
|
scripts/__pycache__/burn_subtitles.cpython-310.pyc
ADDED
|
Binary file (1.72 kB). View file
|
|
|
scripts/__pycache__/burn_subtitles.cpython-311.pyc
ADDED
|
Binary file (5.22 kB). View file
|
|
|
scripts/__pycache__/create_viral_segments.cpython-310.pyc
ADDED
|
Binary file (7.59 kB). View file
|
|
|
scripts/__pycache__/create_viral_segments.cpython-311.pyc
ADDED
|
Binary file (30.9 kB). View file
|
|
|
scripts/__pycache__/cut_json.cpython-310.pyc
ADDED
|
Binary file (1.66 kB). View file
|
|
|
scripts/__pycache__/cut_json.cpython-311.pyc
ADDED
|
Binary file (3.34 kB). View file
|
|
|
scripts/__pycache__/cut_segments.cpython-310.pyc
ADDED
|
Binary file (3.3 kB). View file
|
|
|
scripts/__pycache__/cut_segments.cpython-311.pyc
ADDED
|
Binary file (7.34 kB). View file
|
|
|
scripts/__pycache__/download_video.cpython-310.pyc
ADDED
|
Binary file (2.12 kB). View file
|
|
|
scripts/__pycache__/download_video.cpython-311.pyc
ADDED
|
Binary file (13.3 kB). View file
|
|
|
scripts/__pycache__/edit_video.cpython-310.pyc
ADDED
|
Binary file (14.4 kB). View file
|
|
|
scripts/__pycache__/edit_video.cpython-311.pyc
ADDED
|
Binary file (50.4 kB). View file
|
|
|
scripts/__pycache__/face_detection_insightface.cpython-310.pyc
ADDED
|
Binary file (2.73 kB). View file
|
|
|
scripts/__pycache__/face_detection_insightface.cpython-311.pyc
ADDED
|
Binary file (5.59 kB). View file
|
|
|
scripts/__pycache__/generate_premiere_xml.cpython-311.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
scripts/__pycache__/one_face.cpython-310.pyc
ADDED
|
Binary file (2.79 kB). View file
|
|
|
scripts/__pycache__/one_face.cpython-311.pyc
ADDED
|
Binary file (5.7 kB). View file
|
|
|
scripts/__pycache__/organize_output.cpython-310.pyc
ADDED
|
Binary file (2.35 kB). View file
|
|
|
scripts/__pycache__/organize_output.cpython-311.pyc
ADDED
|
Binary file (5.1 kB). View file
|
|
|
scripts/__pycache__/save_json.cpython-310.pyc
ADDED
|
Binary file (1.23 kB). View file
|
|
|
scripts/__pycache__/save_json.cpython-311.pyc
ADDED
|
Binary file (2.4 kB). View file
|
|
|
scripts/__pycache__/transcribe_cuts.cpython-310.pyc
ADDED
|
Binary file (1.79 kB). View file
|
|
|
scripts/__pycache__/transcribe_cuts.cpython-311.pyc
ADDED
|
Binary file (3.54 kB). View file
|
|
|
scripts/__pycache__/transcribe_video.cpython-310.pyc
ADDED
|
Binary file (3.93 kB). View file
|
|
|
scripts/__pycache__/transcribe_video.cpython-311.pyc
ADDED
|
Binary file (15.3 kB). View file
|
|
|