├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── VideoEncoder
├── __init__.py
├── __main__.py
├── config.env.template
├── plugins
│ ├── auth.py
│ ├── callbacks_.py
│ ├── encode.py
│ ├── pyexec.py
│ ├── queue.py
│ ├── settings.py
│ ├── start.py
│ └── upload.py
└── utils
│ ├── __init__.py
│ ├── database
│ ├── access_db.py
│ ├── add_user.py
│ └── database.py
│ ├── direct_link_generator.py
│ ├── direct_link_generator_license.md
│ ├── display_progress.py
│ ├── extras
│ └── watermark.ass
│ ├── ffmpeg.py
│ ├── helper.py
│ ├── settings.py
│ ├── tasks.py
│ └── uploads
│ ├── __init__.py
│ ├── drive
│ ├── __init__.py
│ ├── download.py
│ └── upload.py
│ └── telegram.py
├── extract
├── pre-commit
├── requirements.txt
├── restart.py
├── run.sh
├── runtime.txt
├── update.py
└── updates.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.py[cod]
3 | *.session
4 | VideoEncoder/utils/extras/logs.txt
5 | encoderbot.session-journal
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Import Ubuntu
2 | FROM ubuntu:20.04
3 |
4 | # Make /app dir
5 | RUN mkdir /app
6 | RUN chmod 777 /app
7 | WORKDIR /app
8 |
9 | # Installation of Requirements
10 | COPY . .
11 | ENV DEBIAN_FRONTEND=noninteractive
12 | ENV TZ=Asia/Kolkata
13 | RUN apt update && apt install -y --no-install-recommends git wget aria2 curl busybox python3 python3-pip p7zip-full p7zip-rar unzip mkvtoolnix ffmpeg
14 | RUN pip3 install --no-cache-dir -r requirements.txt
15 |
16 | # For Extraction of archieved files
17 | RUN chmod +x extract
18 |
19 | # Start bot
20 | CMD ["bash", "run.sh"]
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
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 Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Video Encoder Bot
2 | A telegram bot to convert and compress videos into x265/x264 format via ffmpeg.
3 |
4 | ### Configuration
5 |
6 | **Basics**
7 | - `API_ID` - Get it by creating an app on [https://my.telegram.org](https://my.telegram.org)
8 | - `API_HASH` - Get it by creating an app on [https://my.telegram.org](https://my.telegram.org)
9 | - `BOT_TOKEN` - Get it by creating a bot on [https://t.me/BotFather](https://t.me/BotFather)
10 |
11 | **Authorization**
12 | `Every Var can have space as seperator for multiple user/chat.`
13 | - `OWNER_ID` - A user can have full access to bot throught this var.
14 | - `SUDO_USERS` - Chat identifier of the sudo user.
15 | - `EVERYONE_CHATS` - Chat identifier of the user who can't touch bot code.
16 |
17 | **Log Channel**
18 | - `LOG_CHANNEL` - for bot logs (user and group id will also work!)
19 |
20 | **Database**
21 | - `SESSION_NAME`
22 | - `MONGO_URI` - A mongo db url for settings, addchat etc.
23 |
24 | **Google Drive**
25 | - `INDEX_URL` - Index url for drive uploads
26 | - `DRIVE_DIR` - Google Drive folder id where uploads will be placed.
27 |
28 | **Optional**
29 | - `DOWNLOAD_DIR` - (Optional) Temporary download directory to keep downloaded files.
30 | - `ENCODE_DIR` - (Optional) Temporary encode directory to keep encoded files.
31 |
32 | ### Configuring Encoding Format
33 | To change the ffmpeg profile edit them in [ffmpeg.py](/VideoEncoder/utils/ffmpeg.py)
34 |
35 | ### Installing Requirements
36 | Install the required Python Modules and Latest FFMPEG in your machine.
37 | ```sh
38 | apt update && apt-get install software-properties-common -y && apt-get update && add-apt-repository -y ppa:savoury1/ffmpeg4 && apt-get install -y ffmpeg && add-apt-repository -y ppa:savoury1/ffmpeg5 && apt-get install -y ffmpeg && pip3 install -r requirements.txt
39 | ```
40 |
41 | ### Deployment
42 | With python 3.9.2 or later.
43 | first make repo folder workdir then
44 | ```sh
45 | apt update && apt install -y --no-install-recommends git wget aria2 curl busybox python3 python3-pip p7zip-full p7zip-rar unzip mkvtoolnix ffmpeg
46 | pip3 install --no-cache-dir -r requirements.txt
47 | chmod +x extract
48 | bash run.sh
49 | ```
50 |
51 | ### For Drive
52 | `Place token.pickle and credentials.json on workdir`
53 |
54 | ### Deployment via Docker
55 | **Install docker**
56 | ```sh
57 | sudo apt install apt-transport-https ca-certificates curl software-properties-common -y && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic nightly" && apt-cache policy docker-ce && sudo apt install docker-ce -y
58 | ```
59 | **Start docker build**
60 | - restart always
61 | - docker name is encoder
62 | ```sh
63 | sudo docker build . --no-cache -t encoder && sudo docker run --restart always --name encoder encoder
64 | ```
65 |
66 | **Stop Docker for Major Change**
67 | - this only need if update docker file or requirements only or else use update in bot
68 | ```sh
69 | sudo docker stop encoder && sudo docker rm encoder
70 | ```
71 |
72 | ### Credits
73 | - [ShannonScott](https://gist.github.com/ShannonScott) for [transcode_h265.py](https://gist.github.com/ShannonScott/6d807fc59bfa0356eee64fad66f9d9a8)
74 | - [viperadnan-git](https://github.com/viperadnan-git/video-encoder-bot) for queue logic etc.
75 |
76 | ### Copyright & License
77 | - Copyright © 2022 — [WeebTime](https://github.com/WeebTime)
78 | - Licensed under the terms of the [GNU Affero General Public License Version 3 ‐ 29 June 2007](./LICENSE)
--------------------------------------------------------------------------------
/VideoEncoder/__init__.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import logging
18 | import os
19 | import time
20 | from io import BytesIO, StringIO
21 | from logging.handlers import RotatingFileHandler
22 |
23 | from dotenv import load_dotenv
24 | from pyrogram import Client
25 |
26 | botStartTime = time.time()
27 |
28 | if os.path.exists('VideoEncoder/config.env'):
29 | load_dotenv('VideoEncoder/config.env')
30 |
31 | # Variables
32 |
33 | api_id = int(os.environ.get("API_ID"))
34 | api_hash = os.environ.get("API_HASH")
35 | bot_token = os.environ.get("BOT_TOKEN")
36 |
37 | database = os.environ.get("MONGO_URI")
38 | session = os.environ.get("SESSION_NAME")
39 |
40 | drive_dir = os.environ.get("DRIVE_DIR")
41 | index = os.environ.get("INDEX_URL")
42 |
43 | download_dir = os.environ.get("DOWNLOAD_DIR")
44 | encode_dir = os.environ.get("ENCODE_DIR")
45 |
46 | owner = list(set(int(x) for x in os.environ.get("OWNER_ID").split()))
47 | sudo_users = list(set(int(x) for x in os.environ.get("SUDO_USERS").split()))
48 | everyone = list(set(int(x) for x in os.environ.get("EVERYONE_CHATS").split()))
49 | all = everyone + sudo_users + owner
50 |
51 | try:
52 | log = int(os.environ.get("LOG_CHANNEL"))
53 | except:
54 | log = owner
55 | print('Fill log or give user/channel/group id atleast!')
56 |
57 |
58 | data = []
59 |
60 | PROGRESS = """
61 | • {0} of {1}
62 | • Speed: {2}
63 | • ETA: {3}
64 | """
65 |
66 | video_mimetype = [
67 | "video/x-flv",
68 | "video/mp4",
69 | "application/x-mpegURL",
70 | "video/MP2T",
71 | "video/3gpp",
72 | "video/quicktime",
73 | "video/x-msvideo",
74 | "video/x-ms-wmv",
75 | "video/x-matroska",
76 | "video/webm",
77 | "video/x-m4v",
78 | "video/quicktime",
79 | "video/mpeg"
80 | ]
81 |
82 | def memory_file(name=None, contents=None, *, bytes=True):
83 | if isinstance(contents, str) and bytes:
84 | contents = contents.encode()
85 | file = BytesIO() if bytes else StringIO()
86 | if name:
87 | file.name = name
88 | if contents:
89 | file.write(contents)
90 | file.seek(0)
91 | return file
92 |
93 | # Check Folder
94 | if not os.path.isdir(download_dir):
95 | os.makedirs(download_dir)
96 | if not os.path.isdir(encode_dir):
97 | os.makedirs(encode_dir)
98 |
99 | # the logging things
100 | logging.basicConfig(
101 | level=logging.DEBUG,
102 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
103 | datefmt="%d-%b-%y %H:%M:%S",
104 | handlers=[
105 | RotatingFileHandler(
106 | 'VideoEncoder/utils/extras/logs.txt',
107 | backupCount=20
108 | ),
109 | logging.StreamHandler()
110 | ]
111 | )
112 |
113 | logging.getLogger("pyrogram").setLevel(logging.WARNING)
114 | logging.getLogger("urllib3").setLevel(logging.WARNING)
115 | LOGGER = logging.getLogger(__name__)
116 |
117 | # Client
118 | app = Client(
119 | session,
120 | bot_token=bot_token,
121 | api_id=api_id,
122 | api_hash=api_hash,
123 | plugins={'root': os.path.join(__package__, 'plugins')},
124 | sleep_threshold=30)
125 |
--------------------------------------------------------------------------------
/VideoEncoder/__main__.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import dns.resolver
18 | from pyrogram import idle
19 |
20 | from . import app, log
21 |
22 | dns.resolver.default_resolver = dns.resolver.Resolver(configure=False)
23 | dns.resolver.default_resolver.nameservers = [
24 | '8.8.8.8'] # this is a google public dns
25 |
26 |
27 | async def main():
28 | await app.start()
29 | await app.send_message(chat_id=log, text=f'Bot Started! @{(await app.get_me()).username}')
30 | await idle()
31 | await app.stop()
32 |
33 | app.loop.run_until_complete(main())
34 |
--------------------------------------------------------------------------------
/VideoEncoder/config.env.template:
--------------------------------------------------------------------------------
1 | # Basics #
2 | API_ID = # https://my.telegram.org
3 | API_HASH = "" # https://my.telegram.org
4 | BOT_TOKEN = ""
5 |
6 | # Authorization # - List of id's, separated by space -
7 | OWNER_ID =
8 | SUDO_USERS = ""
9 | EVERYONE_CHATS = ""
10 |
11 | # Log Channel #
12 | LOG_CHANNEL =
13 |
14 | # Database #
15 | SESSION_NAME = "encoderbot"
16 | MONGO_URI = ""
17 |
18 | # Google Drive #
19 | INDEX_URL = ""
20 | DRIVE_DIR = ""
21 |
22 | # Optional #
23 | DOWNLOAD_DIR = "VideoEncoder/downloads/"
24 | ENCODE_DIR = "VideoEncoder/encodes/"
--------------------------------------------------------------------------------
/VideoEncoder/plugins/auth.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from pyrogram import Client, filters
18 |
19 | from .. import everyone, sudo_users
20 | from ..utils.database.access_db import db
21 | from ..utils.helper import check_chat, output
22 |
23 |
24 | @Client.on_message(filters.command('addchat'))
25 | async def addchat(client, message):
26 | c = await check_chat(message, chat='Owner')
27 | if not c:
28 | return
29 | user_id = get_id(message)
30 | auth = await db.get_chat()
31 | if user_id in everyone:
32 | await reply_already_auth(message)
33 | return
34 | elif str(user_id) in auth:
35 | await reply_already_auth(message)
36 | return
37 | else:
38 | auth += ' ' + str(user_id)
39 | await db.set_chat(auth)
40 | await message.reply_text('Added to auth chats! ID: {}
'.format(user_id))
41 |
42 |
43 | @Client.on_message(filters.command('addsudo'))
44 | async def addsudo(client, message):
45 | c = await check_chat(message, chat='Owner')
46 | if not c:
47 | return
48 | user_id = get_id(message)
49 | auth = await db.get_sudo()
50 | if user_id in sudo_users:
51 | await reply_already_auth(message)
52 | return
53 | elif str(user_id) in auth:
54 | await reply_already_auth(message)
55 | return
56 | else:
57 | auth += ' ' + str(user_id)
58 | await db.set_sudo(auth)
59 | await message.reply_text('Added to sudo chats! ID: {}
'.format(user_id))
60 |
61 |
62 | @Client.on_message(filters.command('rmchat'))
63 | async def rmchat(client, message):
64 | c = await check_chat(message, chat='Owner')
65 | if not c:
66 | return
67 | user_id = get_id(message)
68 | check = await db.get_chat()
69 | if str(user_id) in check:
70 | user_id = ' ' + str(user_id)
71 | auth = check.replace(user_id, '')
72 | await db.set_chat(auth)
73 | await message.reply_text('Removed from auth chats! ID: {}
'.format(user_id))
74 | return
75 | elif user_id in everyone:
76 | await message.reply_text('Config auth removal not supported (To Do)!')
77 | return
78 | else:
79 | await message.reply_text('Chat is not auth yet!')
80 |
81 |
82 | @Client.on_message(filters.command('rmsudo'))
83 | async def rmsudo(client, message):
84 | c = await check_chat(message, chat='Owner')
85 | if not c:
86 | return
87 | user_id = get_id(message)
88 | check = await db.get_sudo()
89 | if str(user_id) in check:
90 | user_id = ' ' + str(user_id)
91 | auth = check.replace(user_id, '')
92 | await db.set_sudo(auth)
93 | await message.reply_text('Removed from sudo chats! ID: {}
'.format(user_id))
94 | return
95 | elif user_id in everyone:
96 | await message.reply_text('Config sudo removal not supported (To Do)!')
97 | return
98 | else:
99 | await message.reply_text('Chat is not auth yet!')
100 |
101 |
102 | async def reply_already_auth(message):
103 | if message.reply_to_message:
104 | await message.reply(text='They are already in auth users...')
105 | return
106 | elif not message.reply_to_message and len(message.command) != 1:
107 | await message.reply(text='They are already in auth users/group...')
108 | return
109 | else:
110 | await message.reply(text='This chat is already in auth users/groups...')
111 | return
112 |
113 |
114 | def get_id(message):
115 | if message.reply_to_message:
116 | user_id = message.reply_to_message.from_user.id
117 | elif not message.reply_to_message and len(message.command) != 1:
118 | user_id = message.text.split(None, 1)[1]
119 | else:
120 | user_id = message.chat.id
121 | return user_id
122 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/callbacks_.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import datetime
18 | import json
19 | import os
20 |
21 | from pyrogram import Client
22 | from pyrogram.types import CallbackQuery
23 |
24 | from .. import app, download_dir, log, owner, sudo_users
25 | from ..plugins.queue import queue_answer
26 | from ..utils.database.access_db import db
27 | from ..utils.settings import (AudioSettings, ExtraSettings, OpenSettings,
28 | VideoSettings)
29 | from .start import showw_status
30 |
31 |
32 | @app.on_callback_query()
33 | async def callback_handlers(bot: Client, cb: CallbackQuery):
34 | # Close Button
35 |
36 | if "closeMeh" in cb.data:
37 | await cb.message.delete(True)
38 |
39 | # Settings
40 |
41 | elif "VideoSettings" in cb.data:
42 | await VideoSettings(cb.message, user_id=cb.from_user.id)
43 |
44 | elif "OpenSettings" in cb.data:
45 | await OpenSettings(cb.message, user_id=cb.from_user.id)
46 |
47 | elif "AudioSettings" in cb.data:
48 | await AudioSettings(cb.message, user_id=cb.from_user.id)
49 |
50 | elif "ExtraSettings" in cb.data:
51 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
52 |
53 | elif "triggerMode" in cb.data:
54 | if await db.get_drive(cb.from_user.id) is True:
55 | await db.set_drive(cb.from_user.id, drive=False)
56 | else:
57 | await db.set_drive(cb.from_user.id, drive=True)
58 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
59 |
60 | elif "triggerUploadMode" in cb.data:
61 | if await db.get_upload_as_doc(cb.from_user.id) is True:
62 | await db.set_upload_as_doc(cb.from_user.id, upload_as_doc=False)
63 | else:
64 | await db.set_upload_as_doc(cb.from_user.id, upload_as_doc=True)
65 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
66 |
67 | elif "triggerResize" in cb.data:
68 | if await db.get_resize(cb.from_user.id) is True:
69 | await db.set_resize(cb.from_user.id, resize=False)
70 | else:
71 | await db.set_resize(cb.from_user.id, resize=True)
72 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
73 |
74 | # Watermark
75 | elif "Watermark" in cb.data:
76 | await cb.answer("Sir, this button not works XD\n\nPress Bottom Buttons.", show_alert=True)
77 |
78 | # Metadata
79 | elif "triggerMetadata" in cb.data:
80 | if await db.get_metadata_w(cb.from_user.id):
81 | await db.set_metadata_w(cb.from_user.id, metadata=False)
82 | else:
83 | await db.set_metadata_w(cb.from_user.id, metadata=True)
84 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
85 |
86 | # Watermark
87 | elif "triggerVideo" in cb.data:
88 | if await db.get_watermark(cb.from_user.id):
89 | await db.set_watermark(cb.from_user.id, watermark=False)
90 | else:
91 | await db.set_watermark(cb.from_user.id, watermark=True)
92 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
93 |
94 | # Subtitles
95 | elif "triggerHardsub" in cb.data:
96 | if await db.get_hardsub(cb.from_user.id):
97 | await db.set_hardsub(cb.from_user.id, hardsub=False)
98 | else:
99 | await db.set_hardsub(cb.from_user.id, hardsub=True)
100 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
101 |
102 | elif "triggerSubtitles" in cb.data:
103 | if await db.get_subtitles(cb.from_user.id):
104 | await db.set_subtitles(cb.from_user.id, subtitles=False)
105 | else:
106 | await db.set_subtitles(cb.from_user.id, subtitles=True)
107 | await ExtraSettings(cb.message, user_id=cb.from_user.id)
108 |
109 | # Extension
110 | elif "triggerextensions" in cb.data:
111 | ex = await db.get_extensions(cb.from_user.id)
112 | if ex == 'MP4':
113 | await db.set_extensions(cb.from_user.id, extensions='MKV')
114 | elif ex == 'MKV':
115 | await db.set_extensions(cb.from_user.id, extensions='AVI')
116 | else:
117 | await db.set_extensions(cb.from_user.id, extensions='MP4')
118 | await VideoSettings(cb.message, user_id=cb.from_user.id)
119 |
120 | # Frame
121 | elif "triggerframe" in cb.data:
122 | fr = await db.get_frame(cb.from_user.id)
123 | if fr == 'ntsc':
124 | await db.set_frame(cb.from_user.id, frame='source')
125 | elif fr == 'source':
126 | await db.set_frame(cb.from_user.id, frame='pal')
127 | elif fr == 'pal':
128 | await db.set_frame(cb.from_user.id, frame='film')
129 | elif fr == 'film':
130 | await db.set_frame(cb.from_user.id, frame='23.976')
131 | elif fr == '23.976':
132 | await db.set_frame(cb.from_user.id, frame='30')
133 | elif fr == '30':
134 | await db.set_frame(cb.from_user.id, frame='60')
135 | else:
136 | await db.set_frame(cb.from_user.id, frame='ntsc')
137 | await VideoSettings(cb.message, user_id=cb.from_user.id)
138 |
139 | # Preset
140 | elif "triggerPreset" in cb.data:
141 | p = await db.get_preset(cb.from_user.id)
142 | if p == 'uf':
143 | await db.set_preset(cb.from_user.id, preset='sf')
144 | elif p == 'sf':
145 | await db.set_preset(cb.from_user.id, preset='vf')
146 | elif p == 'vf':
147 | await db.set_preset(cb.from_user.id, preset='f')
148 | elif p == 'f':
149 | await db.set_preset(cb.from_user.id, preset='m')
150 | elif p == 'm':
151 | await db.set_preset(cb.from_user.id, preset='s')
152 | else:
153 | await db.set_preset(cb.from_user.id, preset='uf')
154 | await VideoSettings(cb.message, user_id=cb.from_user.id)
155 |
156 | # sample rate
157 | elif "triggersamplerate" in cb.data:
158 | sr = await db.get_samplerate(cb.from_user.id)
159 | if sr == '44.1K':
160 | await db.set_samplerate(cb.from_user.id, sample='48K')
161 | elif sr == '48K':
162 | await db.set_samplerate(cb.from_user.id, sample='source')
163 | else:
164 | await db.set_samplerate(cb.from_user.id, sample='44.1K')
165 | await AudioSettings(cb.message, user_id=cb.from_user.id)
166 |
167 | # bitrate
168 | elif "triggerbitrate" in cb.data:
169 | bit = await db.get_bitrate(cb.from_user.id)
170 | if bit == '400':
171 | await db.set_bitrate(cb.from_user.id, bitrate='320')
172 | elif bit == '320':
173 | await db.set_bitrate(cb.from_user.id, bitrate='256')
174 | elif bit == '256':
175 | await db.set_bitrate(cb.from_user.id, bitrate='224')
176 | elif bit == '224':
177 | await db.set_bitrate(cb.from_user.id, bitrate='192')
178 | elif bit == '192':
179 | await db.set_bitrate(cb.from_user.id, bitrate='160')
180 | elif bit == '160':
181 | await db.set_bitrate(cb.from_user.id, bitrate='128')
182 | elif bit == '128':
183 | await db.set_bitrate(cb.from_user.id, bitrate='source')
184 | else:
185 | await db.set_bitrate(cb.from_user.id, bitrate='400')
186 | await AudioSettings(cb.message, user_id=cb.from_user.id)
187 |
188 | # Audio Codec
189 | elif "triggerAudioCodec" in cb.data:
190 | a = await db.get_audio(cb.from_user.id)
191 | if a == 'dd':
192 | await db.set_audio(cb.from_user.id, audio='copy')
193 | elif a == 'copy':
194 | await db.set_audio(cb.from_user.id, audio='aac')
195 | elif a == 'aac':
196 | await db.set_audio(cb.from_user.id, audio='opus')
197 | elif a == 'opus':
198 | await db.set_audio(cb.from_user.id, audio='alac')
199 | elif a == 'alac':
200 | await db.set_audio(cb.from_user.id, audio='vorbis')
201 | else:
202 | await db.set_audio(cb.from_user.id, audio='dd')
203 | await AudioSettings(cb.message, user_id=cb.from_user.id)
204 |
205 | # Audio Channel
206 | elif "triggerAudioChannels" in cb.data:
207 | c = await db.get_channels(cb.from_user.id)
208 | if c == 'source':
209 | await db.set_channels(cb.from_user.id, channels='1.0')
210 | elif c == '1.0':
211 | await db.set_channels(cb.from_user.id, channels='2.0')
212 | elif c == '2.0':
213 | await db.set_channels(cb.from_user.id, channels='2.1')
214 | elif c == '2.1':
215 | await db.set_channels(cb.from_user.id, channels='5.1')
216 | elif c == '5.1':
217 | await cb.answer("7.1 is for bluray only.", show_alert=True)
218 | await db.set_channels(cb.from_user.id, channels='7.1')
219 | else:
220 | await db.set_channels(cb.from_user.id, channels='source')
221 | await AudioSettings(cb.message, user_id=cb.from_user.id)
222 |
223 | # Resolution
224 | elif "triggerResolution" in cb.data:
225 | r = await db.get_resolution(cb.from_user.id)
226 | if r == 'OG':
227 | await db.set_resolution(cb.from_user.id, resolution='1080')
228 | elif r == '1080':
229 | await db.set_resolution(cb.from_user.id, resolution='720')
230 | elif r == '720':
231 | await db.set_resolution(cb.from_user.id, resolution='480')
232 | elif r == '480':
233 | await db.set_resolution(cb.from_user.id, resolution='576')
234 | else:
235 | await db.set_resolution(cb.from_user.id, resolution='OG')
236 | await VideoSettings(cb.message, user_id=cb.from_user.id)
237 |
238 | # Video Bits
239 | elif "triggerBits" in cb.data:
240 | b = await db.get_bits(cb.from_user.id)
241 | if await db.get_hevc(cb.from_user.id):
242 | if b:
243 | await db.set_bits(cb.from_user.id, bits=False)
244 | else:
245 | await db.set_bits(cb.from_user.id, bits=True)
246 | else:
247 | if b:
248 | await db.set_bits(cb.from_user.id, bits=False)
249 | else:
250 | await cb.answer("H264 don't support 10 bits in this bot.",
251 | show_alert=True)
252 | await VideoSettings(cb.message, user_id=cb.from_user.id)
253 |
254 | # HEVC
255 | elif "triggerHevc" in cb.data:
256 | if await db.get_hevc(cb.from_user.id):
257 | await db.set_hevc(cb.from_user.id, hevc=False)
258 | else:
259 | await db.set_hevc(cb.from_user.id, hevc=True)
260 | await cb.answer("H265 need more time for encoding video", show_alert=True)
261 | await VideoSettings(cb.message, user_id=cb.from_user.id)
262 |
263 | # Tune
264 | elif "triggertune" in cb.data:
265 | if await db.get_tune(cb.from_user.id):
266 | await db.set_tune(cb.from_user.id, tune=False)
267 | else:
268 | await db.set_tune(cb.from_user.id, tune=True)
269 | await VideoSettings(cb.message, user_id=cb.from_user.id)
270 |
271 | # Reframe
272 | elif "triggerreframe" in cb.data:
273 | rf = await db.get_reframe(cb.from_user.id)
274 | if rf == '4':
275 | await db.set_reframe(cb.from_user.id, reframe='8')
276 | elif rf == '8':
277 | await db.set_reframe(cb.from_user.id, reframe='16')
278 | await cb.answer("Reframe 16 maybe not support", show_alert=True)
279 | elif rf == '16':
280 | await db.set_reframe(cb.from_user.id, reframe='pass')
281 | else:
282 | await db.set_reframe(cb.from_user.id, reframe='4')
283 | await VideoSettings(cb.message, user_id=cb.from_user.id)
284 |
285 | # CABAC
286 | elif "triggercabac" in cb.data:
287 | if await db.get_cabac(cb.from_user.id):
288 | await db.set_cabac(cb.from_user.id, cabac=False)
289 | else:
290 | await db.set_cabac(cb.from_user.id, cabac=True)
291 | await VideoSettings(cb.message, user_id=cb.from_user.id)
292 |
293 | # Aspect
294 | elif "triggeraspect" in cb.data:
295 | if await db.get_aspect(cb.from_user.id):
296 | await db.set_aspect(cb.from_user.id, aspect=False)
297 | else:
298 | await db.set_aspect(cb.from_user.id, aspect=True)
299 | await cb.answer("This will help to force video to 16:9", show_alert=True)
300 | await VideoSettings(cb.message, user_id=cb.from_user.id)
301 |
302 | elif "triggerCRF" in cb.data:
303 | crf = await db.get_crf(cb.from_user.id)
304 | nextcrf = int(crf) + 1
305 | if nextcrf > 30:
306 | await db.set_crf(cb.from_user.id, crf=18)
307 | else:
308 | await db.set_crf(cb.from_user.id, crf=nextcrf)
309 | await VideoSettings(cb.message, user_id=cb.from_user.id)
310 |
311 | # Cancel
312 |
313 | elif "cancel" in cb.data:
314 | status = download_dir + "status.json"
315 | with open(status, 'r+') as f:
316 | statusMsg = json.load(f)
317 | user = cb.from_user.id
318 | if user != statusMsg['user']:
319 | if user == 885190545:
320 | pass
321 | elif user in sudo_users or user in owner:
322 | pass
323 | else:
324 | return
325 | statusMsg['running'] = False
326 | f.seek(0)
327 | json.dump(statusMsg, f, indent=2)
328 | os.remove('VideoEncoder/utils/extras/downloads/process.txt')
329 | try:
330 | await cb.message.edit_text("🚦🚦 Process Cancelled 🚦🚦")
331 | chat_id = log
332 | utc_now = datetime.datetime.utcnow()
333 | ist_now = utc_now + \
334 | datetime.timedelta(minutes=30, hours=5)
335 | ist = ist_now.strftime("%d/%m/%Y, %H:%M:%S")
336 | bst_now = utc_now + \
337 | datetime.timedelta(minutes=00, hours=6)
338 | bst = bst_now.strftime("%d/%m/%Y, %H:%M:%S")
339 | now = f"\n{ist} (GMT+05:30)`\n`{bst} (GMT+06:00)"
340 | await bot.send_message(chat_id, f"**Last Process Cancelled, Bot is Free Now !!** \n\nProcess Done at `{now}`", parse_mode="markdown")
341 | except:
342 | pass
343 |
344 | # Stats
345 | elif 'stats' in cb.data:
346 | stats = await showw_status(bot)
347 | stats = stats.replace('', '')
348 | stats = stats.replace('', '')
349 | await cb.answer(stats, show_alert=True)
350 |
351 | # Queue
352 | elif "queue+" in cb.data:
353 | await queue_answer(app, cb)
354 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/encode.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 |
19 | from pyrogram import Client, filters
20 |
21 | from .. import data, video_mimetype
22 | from ..utils.database.add_user import AddUserToDatabase
23 | from ..utils.helper import check_chat
24 | from ..utils.tasks import handle_tasks
25 |
26 |
27 | @Client.on_message(filters.incoming & (filters.video | filters.document))
28 | async def encode_video(app, message):
29 | c = await check_chat(message, chat='Both')
30 | if not c:
31 | return
32 | await AddUserToDatabase(app, message)
33 | if message.document:
34 | if not message.document.mime_type in video_mimetype:
35 | return
36 | data.append(message)
37 | if len(data) == 1:
38 | await handle_tasks(message, 'tg')
39 | else:
40 | await message.reply("📔 Waiting for queue...")
41 | await asyncio.sleep(1)
42 |
43 |
44 | @Client.on_message(filters.command('ddl'))
45 | async def url_encode(app, message):
46 | c = await check_chat(message, chat='Both')
47 | if not c:
48 | return
49 | await AddUserToDatabase(app, message)
50 | data.append(message)
51 | if len(message.text.split()) == 1:
52 | await message.reply_text("Usage: /ddl [url] | [filename]")
53 | data.remove(data[0])
54 | return
55 | if len(data) == 1:
56 | await handle_tasks(message, 'url')
57 | else:
58 | await message.reply("📔 Waiting for queue...")
59 | await asyncio.sleep(1)
60 |
61 |
62 | @Client.on_message(filters.command('batch'))
63 | async def batch_encode(app, message):
64 | c = await check_chat(message, chat='Both')
65 | if not c:
66 | return
67 | await AddUserToDatabase(app, message)
68 | data.append(message)
69 | if len(message.text.split()) == 1:
70 | await message.reply_text("Usage: /batch [url]")
71 | data.remove(data[0])
72 | return
73 | if len(data) == 1:
74 | await handle_tasks(message, 'batch')
75 | else:
76 | await message.reply("📔 Waiting for queue...")
77 | await asyncio.sleep(1)
78 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/pyexec.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import ast
18 | import asyncio
19 | import html
20 | import inspect
21 | import sys
22 | import traceback
23 | from io import BytesIO
24 |
25 | from pyrogram import Client, filters
26 |
27 | from .. import memory_file
28 | from ..utils.helper import check_chat
29 |
30 |
31 | @Client.on_message(filters.command('exec'))
32 | async def run_code(client, message):
33 | c = await check_chat(message, chat='Sudo')
34 | if c is None:
35 | return
36 |
37 | class UniqueExecReturnIdentifier:
38 | pass
39 | code = message.text[5:].strip()
40 | if not code:
41 | await message.reply_text('code 100')
42 | return
43 | tree = ast.parse(code)
44 | obody = tree.body
45 | body = obody.copy()
46 | body.append(ast.Return(ast.Name('_ueri', ast.Load())))
47 |
48 | def _gf(body):
49 | # args: m, message, c, client, _ueri
50 | func = ast.AsyncFunctionDef('ex', ast.arguments([], [ast.arg(i, None, None) for i in [
51 | 'm', 'message', 'c', 'client', '_ueri']], None, [], [], None, []), body, [], None, None)
52 | ast.fix_missing_locations(func)
53 | mod = ast.parse('')
54 | mod.body = [func]
55 | fl = locals().copy()
56 | exec(compile(mod, '', 'exec'), globals(), fl)
57 | return fl['ex']
58 | try:
59 | exx = _gf(body)
60 | except SyntaxError as ex:
61 | if ex.msg != "'return' with value in async generator":
62 | raise
63 | exx = _gf(obody)
64 | escaped_code = html.escape(code)
65 | async_obj = exx(message, message, client, client,
66 | UniqueExecReturnIdentifier)
67 | reply = await message.reply_text('Type[py]\n{}
\nState[Executing]'.format(escaped_code))
68 | stdout = sys.stdout
69 | stderr = sys.stderr
70 | wrapped_stdout = memory_file(bytes=False)
71 | wrapped_stdout.buffer = memory_file()
72 | wrapped_stderr = memory_file(bytes=False)
73 | wrapped_stderr.buffer = memory_file()
74 | sys.stdout = wrapped_stdout
75 | sys.stderr = wrapped_stderr
76 | try:
77 | if inspect.isasyncgen(async_obj):
78 | returned = [i async for i in async_obj]
79 | else:
80 | returned = [await async_obj]
81 | if returned == [UniqueExecReturnIdentifier]:
82 | returned = []
83 | except Exception:
84 | await message.reply_text(traceback.format_exc(), parse_mode=None)
85 | return
86 | finally:
87 | sys.stdout = stdout
88 | sys.stderr = stderr
89 | wrapped_stdout.seek(0)
90 | wrapped_stderr.seek(0)
91 | wrapped_stdout.buffer.seek(0)
92 | wrapped_stderr.buffer.seek(0)
93 | r = []
94 | outtxt = wrapped_stderr.read() + wrapped_stderr.buffer.read().decode()
95 | if outtxt.strip().strip('\n').strip():
96 | r.append(outtxt)
97 | errtxt = wrapped_stdout.read() + wrapped_stdout.buffer.read().decode()
98 | if errtxt.strip().strip('\n').strip():
99 | r.append(errtxt)
100 | r.extend(returned)
101 | r = [html.escape(str(i).strip('\n')) for i in r]
102 | r = '\n'.join([f'{i}
' for i in r])
103 | r = r.strip() or 'undefined'
104 | await reply.edit_text('Type[py]\n{}
\nState[Executed]\nOutput \\\n{}'.format(escaped_code, r))
105 |
106 |
107 | @Client.on_message(filters.command('sh'))
108 | async def run_shell(client, message):
109 | c = await check_chat(message, chat='Sudo')
110 | if c is None:
111 | return
112 | command = message.text.split(None, 1)[1]
113 | if not command:
114 | await message.reply_text('code 100')
115 | return
116 | reply = await message.reply_text('Executing...')
117 | process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
118 | stdout, stderr = await process.communicate()
119 | returncode = process.returncode
120 | text = f'Exit Code: {returncode}
\n'
121 | stdout = stdout.decode().replace('\r', '').strip('\n').rstrip()
122 | stderr = stderr.decode().replace('\r', '').strip('\n').rstrip()
123 | if stderr:
124 | text += f'{html.escape(stderr)}
\n'
125 | if stdout:
126 | text += f'{html.escape(stdout)}
'
127 |
128 | # send as a file if it's longer than 4096 bytes
129 | if len(text) > 4096:
130 | out = stderr.strip() + "\n" + stdout.strip()
131 | f = BytesIO(out.strip().encode('utf-8'))
132 | f.name = "output.txt"
133 | await reply.delete()
134 | await message.reply_document(f, caption=f'Exit Code: {returncode}
')
135 | else:
136 | await reply.edit_text(text)
137 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/queue.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import os
18 | from urllib.parse import unquote_plus
19 |
20 | from pyrogram import Client, filters
21 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
22 |
23 | from .. import data
24 | from ..utils.database.add_user import AddUserToDatabase
25 | from ..utils.helper import check_chat
26 |
27 | queue_callback_filter = filters.create(
28 | lambda _, __, query: query.data.startswith('queue+'))
29 |
30 |
31 | async def get_title(i):
32 | try:
33 | if data[i].video:
34 | return data[i].video.file_name
35 | elif data[i].document:
36 | return data[i].document.file_name
37 | else:
38 | url = data[i].command[1]
39 | return str(unquote_plus(os.path.basename(url)))
40 | except:
41 | return None
42 |
43 |
44 | def map(pos):
45 | if pos == 0:
46 | if len(data) > 1:
47 | button = [[InlineKeyboardButton(
48 | text='Next', callback_data="queue+1")]]
49 | else:
50 | button = [[InlineKeyboardButton(
51 | text='No Other Task', callback_data="queue+-1")]]
52 | else:
53 | try:
54 | if data[pos+1]:
55 | button = [
56 | [
57 | InlineKeyboardButton(
58 | text='Previous', callback_data=f"queue+{pos-1}"),
59 | InlineKeyboardButton(
60 | text='Next', callback_data=f"queue+{pos+1}")
61 | ],
62 | ]
63 | except Exception as e:
64 | button = [
65 | [
66 | InlineKeyboardButton(
67 | text='Previous', callback_data=f"queue+{pos-1}")
68 | ],
69 | ]
70 | return button
71 |
72 |
73 | async def queue_answer(app, callback_query):
74 | chatid = callback_query.from_user.id
75 | messageid = callback_query.message.id
76 | pos = int(callback_query.data.split('+')[1])
77 | if pos == -1:
78 | await callback_query.answer("no task", show_alert=True)
79 | return
80 | taskpos = pos+1
81 | size = len(data)
82 | tasktitle = await get_title(pos)
83 | await callback_query.edit_message_text(f"{taskpos} of {size}:\n\n{tasktitle}", reply_markup=InlineKeyboardMarkup(map(pos)))
84 |
85 |
86 | @Client.on_message(filters.command(['queue']))
87 | async def queue_message(app, message):
88 | c = await check_chat(message, chat='Both')
89 | if not c:
90 | return
91 | await AddUserToDatabase(app, message)
92 | msg = await message.reply_text("Wait Checking...")
93 | size = len(data)
94 | if size >= 1:
95 | for i in range(1, size):
96 | pos = i-1
97 | taskpos = i
98 | tasktitle = await get_title(pos)
99 | await msg.edit(
100 | text=f"{taskpos} of {size}:\n\n{tasktitle}", reply_markup=InlineKeyboardMarkup(map(0)))
101 | else:
102 | await msg.edit('🥱 No Active Encodes.')
103 |
104 |
105 | @Client.on_message(filters.command('clear'))
106 | async def clear(app, message):
107 | c = await check_chat(message, chat='Sudo')
108 | if not c:
109 | return
110 | await AddUserToDatabase(app, message)
111 | if len(data) >= 1:
112 | current = data[0]
113 | data.clear()
114 | data.append(current)
115 | await message.reply('Purged all tasks!')
116 | else:
117 | await message.reply("🥱 No Active Encodes.")
118 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/settings.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from pyrogram import Client, filters
18 | from pyrogram.types import Message
19 |
20 | from .. import all
21 | from ..utils.database.access_db import db
22 | from ..utils.database.add_user import AddUserToDatabase
23 | from ..utils.helper import check_chat, output
24 | from ..utils.settings import OpenSettings
25 |
26 |
27 | @Client.on_message(filters.command("reset"))
28 | async def reset(bot: Client, update: Message):
29 | c = await check_chat(update, chat='Both')
30 | if not c:
31 | return
32 | await db.delete_user(update.from_user.id)
33 | await db.add_user(update.from_user.id)
34 | await update.reply(text="Settings reset successfully", reply_markup=output)
35 |
36 |
37 | @Client.on_message(filters.command("settings"))
38 | async def settings_handler(bot: Client, event: Message):
39 | c = await check_chat(event, chat='Both')
40 | if not c:
41 | return
42 | await AddUserToDatabase(bot, event)
43 | editable = await event.reply_text("Please Wait ...")
44 | await OpenSettings(editable, user_id=event.from_user.id)
45 |
46 |
47 | @Client.on_message(filters.command("vset"))
48 | async def settings_viewer(bot: Client, event: Message):
49 | c = await check_chat(event, chat='Both')
50 | if c is None:
51 | return
52 | await AddUserToDatabase(bot, event)
53 | # User ID
54 | if event.reply_to_message:
55 | user_id = event.reply_to_message.from_user.id
56 | elif not event.reply_to_message and len(event.command) == 1:
57 | user_id = event.from_user.id
58 | elif not event.reply_to_message and len(event.command) != 1:
59 | user_id = event.text.split(None, 1)[1]
60 | else:
61 | return
62 |
63 | # Reframe
64 | rf = await db.get_reframe(user_id)
65 | if rf == '4':
66 | reframe = '4'
67 | elif rf == '8':
68 | reframe = '8'
69 | elif rf == '16':
70 | reframe = '16'
71 | else:
72 | reframe = 'Pass'
73 |
74 | # Frame
75 | fr = await db.get_frame(user_id)
76 | if fr == 'ntsc':
77 | frame = 'NTSC'
78 | elif fr == 'pal':
79 | frame = 'PAL'
80 | elif fr == 'film':
81 | frame = 'FILM'
82 | elif fr == '23.976':
83 | frame = '23.976'
84 | elif fr == '30':
85 | frame = '30'
86 | elif fr == '60':
87 | frame = '60'
88 | else:
89 | frame = 'Source'
90 |
91 | # Preset, CRF and Resolution
92 | p = await db.get_preset(user_id)
93 | if p == 'uf':
94 | pre = '𝚄𝚕𝚝𝚛𝚊𝙵𝚊𝚜𝚝'
95 | elif p == 'sf':
96 | pre = '𝚂𝚞𝚙𝚎𝚛𝙵𝚊𝚜𝚝'
97 | elif p == 'vf':
98 | pre = '𝚅𝚎𝚛𝚢𝙵𝚊𝚜𝚝'
99 | elif p == 'f':
100 | pre = '𝙵𝚊𝚜𝚝'
101 | elif p == 'm':
102 | pre = '𝙼𝚎𝚍𝚒𝚞𝚖'
103 | elif p == 's':
104 | pre = '𝚂𝚕𝚘𝚠'
105 | elif p == 'sl':
106 | pre = '𝚂𝚕𝚘𝚠𝚎𝚛'
107 | elif p == 'vs':
108 | pre = '𝚅𝚎𝚛𝚢𝚂𝚕𝚘𝚠'
109 | else:
110 | pre = 'None'
111 |
112 | crf = await db.get_crf(user_id)
113 |
114 | r = await db.get_resolution(user_id)
115 | if r == 'OG':
116 | res = 'Source'
117 | elif r == '1080':
118 | res = '𝟷𝟶𝟾𝟶𝙿'
119 | elif r == '720':
120 | res = '𝟽𝟸𝟶𝙿'
121 | elif r == '480':
122 | res = '𝟺𝟾𝟶𝙿'
123 | elif r == '540':
124 | res = '𝟻𝟺𝟶𝙿'
125 | elif r == '360':
126 | res = '𝟹𝟼𝟶𝙿'
127 | elif r == '240':
128 | res = '𝟸𝟺𝟶𝙿'
129 | elif r == '1440':
130 | res = '𝟮𝗞'
131 | else:
132 | res = '𝟰𝗞'
133 |
134 | # Extension
135 | ex = await db.get_extensions(user_id)
136 | if ex == 'MP4':
137 | extensions = 'MP4'
138 | elif ex == 'MKV':
139 | extensions = 'MKV'
140 | else:
141 | extensions = 'AVI'
142 |
143 | # Audio
144 | a = await db.get_audio(user_id)
145 | if a == 'dd':
146 | audio = 'AC3'
147 | elif a == 'aac':
148 | audio = 'AAC'
149 | elif a == 'vorbis':
150 | audio = 'VORBIS'
151 | elif a == 'alac':
152 | audio = 'ALAC'
153 | elif a == 'opus':
154 | audio = 'OPUS'
155 | else:
156 | audio = 'Source'
157 |
158 | bit = await db.get_bitrate(user_id)
159 | if bit == '400':
160 | bitrate = '400K'
161 | elif bit == '320':
162 | bitrate = '320K'
163 | elif bit == '256':
164 | bitrate = '256K'
165 | elif bit == '224':
166 | bitrate = '224K'
167 | elif bit == '192':
168 | bitrate = '192K'
169 | elif bit == '160':
170 | bitrate = '160K'
171 | elif bit == '128':
172 | bitrate = '128K'
173 | else:
174 | bitrate = 'Source'
175 |
176 | sr = await db.get_samplerate(user_id)
177 | if sr == '44.1K':
178 | sample = '44.1kHz'
179 | elif sr == '48K':
180 | sample = '48kHz'
181 | else:
182 | sample = 'Source'
183 |
184 | c = await db.get_channels(user_id)
185 | if c == '1.0':
186 | channels = 'Mono'
187 | elif c == '2.0':
188 | channels = 'Stereo'
189 | elif c == '2.1':
190 | channels = '2.1'
191 | elif c == '5.1':
192 | channels = '5.1'
193 | elif c == '7.1':
194 | channels = '7.1'
195 | else:
196 | channels = 'Source'
197 |
198 | m = await db.get_metadata_w(user_id)
199 | if m:
200 | metadata = 'Weeb-Zone'
201 | else:
202 | metadata = 'change session!'
203 |
204 | # Reply Text
205 | vset = f'''Encode Settings:
206 |
207 | 📹 Video Settings
208 | Format : {extensions}
209 | Quality: {res}
210 | Codec: {'H265' if ((await db.get_hevc(user_id)) is True) else 'H264'}
211 | Aspect: {'16:9' if ((await db.get_aspect(user_id)) is True) else 'Source'}
212 | Reframe: {reframe} | FPS: {frame}
213 | Tune: {'Animation' if ((await db.get_tune(user_id)) is True) else 'Film'}
214 | Preset: {pre}
215 | Bits: {'10' if ((await db.get_bits(user_id)) is True) else '8'} | CRF: {crf}
216 | CABAC: {'☑️' if ((await db.get_cabac(user_id)) is True) else ''}
217 |
218 | 📜 Subtitles Settings
219 | Hardsub {'☑️' if ((await db.get_hardsub(user_id)) is True) else ''} | Softsub {'☑️' if ((await db.get_subtitles(user_id)) is True) else ''}
220 |
221 | ©️ Watermark Settings
222 | Metadata: {metadata}
223 | Video {'☑️' if ((await db.get_watermark(user_id)) is True) else ''}
224 |
225 | 🔊 Audio Settings
226 | Codec: {audio}
227 | Sample Rate : {sample}
228 | Bit Rate: {bitrate}
229 | Channels: {channels}
230 | '''
231 | await event.reply_text(vset)
232 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/start.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import os
18 | import shutil
19 | import time
20 | from os import execl as osexecl
21 | from subprocess import run as srun
22 | from sys import executable
23 | from time import time
24 |
25 | from psutil import (boot_time, cpu_count, cpu_percent, disk_usage,
26 | net_io_counters, swap_memory, virtual_memory)
27 | from pyrogram import Client, filters
28 | from pyrogram.types import Message
29 |
30 | from .. import botStartTime, download_dir, encode_dir
31 | from ..utils.database.access_db import db
32 | from ..utils.database.add_user import AddUserToDatabase
33 | from ..utils.display_progress import TimeFormatter, humanbytes
34 | from ..utils.helper import check_chat, start_but
35 |
36 | SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
37 |
38 |
39 | def uptime():
40 | """ returns uptime """
41 | return TimeFormatter(time.time() - botStartTime)
42 |
43 |
44 | @Client.on_message(filters.command('start'))
45 | async def start_message(app, message):
46 | c = await check_chat(message, chat='Both')
47 | if not c:
48 | return
49 | await AddUserToDatabase(app, message)
50 | text = f"Hi {message.from_user.mention()}! I'm VideoEncoder Bot which will do magic with your file."
51 | await message.reply(text=text, reply_markup=start_but)
52 |
53 |
54 | @Client.on_message(filters.command('help'))
55 | async def help_message(app, message):
56 | c = await check_chat(message, chat='Both')
57 | if not c:
58 | return
59 | await AddUserToDatabase(app, message)
60 | msg = """📕 Commands List:
61 |
62 | - Autodetect Telegram File.
63 | - /ddl - encode through DDL
64 | - /batch - encode in batch
65 | - /queue - check queue
66 | - /settings - settings
67 | - /vset - view settings
68 | - /reset - reset settings
69 | - /stats - cpu stats
70 |
71 | For Sudo:
72 | - /exec - Execute Python
73 | - /sh - Execute Shell
74 | - /vupload - video upload
75 | - /dupload - doc upload
76 | - /gupload - drive upload
77 | - /update - git pull
78 | - /restart - restart bot
79 | - /clean - clean junk
80 | - /clear - clean queue
81 | - /logs - view logs
82 |
83 | For Owner:
84 | - /addchat and /addsudo
85 | - /rmsudo and /rmchat
86 |
87 | Supports: click here"""
88 | await message.reply(text=msg, disable_web_page_preview=True, reply_markup=start_but)
89 |
90 |
91 | @Client.on_message(filters.command('stats'))
92 | async def show_status_count(_, event: Message):
93 | c = await check_chat(event, chat='Both')
94 | if not c:
95 | return
96 | await AddUserToDatabase(_, event)
97 | text = await show_status(_)
98 | await event.reply_text(text)
99 |
100 |
101 | async def show_status(_):
102 | currentTime = TimeFormatter(time() - botStartTime)
103 | osUptime = TimeFormatter(time() - boot_time())
104 | total, used, free, disk = disk_usage('/')
105 | total = humanbytes(total)
106 | used = humanbytes(used)
107 | free = humanbytes(free)
108 | sent = humanbytes(net_io_counters().bytes_sent)
109 | recv = humanbytes(net_io_counters().bytes_recv)
110 | cpuUsage = cpu_percent(interval=0.5)
111 | p_core = cpu_count(logical=False)
112 | t_core = cpu_count(logical=True)
113 | swap = swap_memory()
114 | swap_p = swap.percent
115 | memory = virtual_memory()
116 | mem_t = humanbytes(memory.total)
117 | mem_a = humanbytes(memory.available)
118 | mem_u = humanbytes(memory.used)
119 | total_users = await db.total_users_count()
120 | text = f"""Uptime of:
121 | - Bot: {currentTime}
122 | - OS: {osUptime}
123 |
124 | Disk:
125 | - Total: {total}
126 | - Used: {used}
127 | - Free: {free}
128 |
129 | UL: {sent} | DL: {recv}
130 | CPU: {cpuUsage}%
131 |
132 | Cores:
133 | - Physical: {p_core}
134 | - Total: {t_core}
135 | - Used: {swap_p}%
136 |
137 | RAM:
138 | - Total: {mem_t}
139 | - Free: {mem_a}
140 | - Used: {mem_u}
141 |
142 | Users: {total_users}"""
143 | return text
144 |
145 |
146 | async def showw_status(_):
147 | currentTime = TimeFormatter(time() - botStartTime)
148 | total, used, free, disk = disk_usage('/')
149 | total = humanbytes(total)
150 | used = humanbytes(used)
151 | free = humanbytes(free)
152 | cpuUsage = cpu_percent(interval=0.5)
153 | total_users = await db.total_users_count()
154 |
155 | text = f"""Uptime of Bot: {currentTime}
156 |
157 | Disk:
158 | - Total: {total}
159 | - Used: {used}
160 | - Free: {free}
161 | CPU: {cpuUsage}%
162 |
163 | Users: {total_users}"""
164 | return text
165 |
166 |
167 | @Client.on_message(filters.command('clean'))
168 | async def delete_files(_, message):
169 | c = await check_chat(message, chat='Sudo')
170 | if not c:
171 | return
172 | delete_downloads()
173 | await message.reply_text('Deleted all junk files!')
174 |
175 |
176 | def delete_downloads():
177 | dir = encode_dir
178 | dir2 = download_dir
179 | for files in os.listdir(dir):
180 | path = os.path.join(dir, files)
181 | try:
182 | shutil.rmtree(path)
183 | except OSError:
184 | os.remove(path)
185 | for files in os.listdir(dir2):
186 | path = os.path.join(dir2, files)
187 | try:
188 | shutil.rmtree(path)
189 | except OSError:
190 | os.remove(path)
191 |
192 |
193 | @Client.on_message(filters.command('restart'))
194 | async def font_message(app, message):
195 | c = await check_chat(message, chat='Sudo')
196 | if not c:
197 | return
198 | await AddUserToDatabase(app, message)
199 | reply = await message.reply_text('Restarting...')
200 | textx = f"Done Restart...✅"
201 | await reply.edit_text(textx)
202 | try:
203 | exit()
204 | finally:
205 | osexecl(executable, executable, "-m", "VideoEncoder")
206 |
207 |
208 | @Client.on_message(filters.command('update'))
209 | async def update_message(app, message):
210 | c = await check_chat(message, chat='Sudo')
211 | if not c:
212 | return
213 | await AddUserToDatabase(app, message)
214 | reply = await message.reply_text('📶 Fetching Update...')
215 | textx = f"✅ Bot Updated"
216 | await reply.edit_text(textx)
217 | try:
218 | await app.stop()
219 | finally:
220 | srun([f"bash run.sh"], shell=True)
221 |
--------------------------------------------------------------------------------
/VideoEncoder/plugins/upload.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import html
18 | import os
19 | import time
20 |
21 | from pyrogram import Client, filters
22 | from pyrogram.errors.exceptions.bad_request_400 import MessageIdInvalid
23 |
24 | from .. import download_dir, sudo_users
25 | from ..utils.ffmpeg import get_duration, get_thumbnail, get_width_height
26 | from ..utils.helper import check_chat
27 | from ..utils.uploads.drive.upload import Uploader
28 | from ..utils.uploads.telegram import upload_doc, upload_video
29 |
30 |
31 | @Client.on_message(filters.command('dupload'))
32 | async def docupload(client, message):
33 | c = await check_chat(message, chat='Sudo')
34 | if not c:
35 | return
36 | c_time = time.time()
37 | file = os.path.expanduser(' '.join(message.command[1:]))
38 | if not file:
39 | await message.reply('you forgot to mention path!')
40 | return
41 | text = f'Uploading {html.escape(file)}...'
42 | reply = await message.reply(text)
43 | filename = os.path.basename(file)
44 |
45 | try:
46 | await upload_doc(message, reply, c_time, filename, file)
47 | except Exception as e:
48 | await reply.edit('Error while uploading! {}'.format(e))
49 | else:
50 | await reply.delete()
51 |
52 |
53 | @Client.on_message(filters.command('vupload'))
54 | async def videoupload(client, message):
55 | c = await check_chat(message, chat='Sudo')
56 | if c is None:
57 | return
58 | c_time = time.time()
59 | file = os.path.expanduser(' '.join(message.command[1:]))
60 | if not file:
61 | await message.reply_text('you forgot to mention filepath!')
62 | return
63 | filename = os.path.basename(file)
64 | duration = get_duration(file)
65 | thumb = get_thumbnail(file, download_dir, duration / 4)
66 | width, height = get_width_height(file)
67 | text = f'Uploading {html.escape(file)}...'
68 | reply = await message.reply_text(text)
69 | try:
70 | await upload_video(message, reply, file, filename,
71 | c_time, thumb, duration, width, height)
72 | except Exception as e:
73 | await reply.edit('Error while uploading! {}'.format(e))
74 | else:
75 | await reply.delete()
76 |
77 |
78 | @Client.on_message(filters.command('gupload'))
79 | async def driveupload(client, message):
80 | c = await check_chat(message, chat='Sudo')
81 | if c is None:
82 | return
83 | new_file = os.path.expanduser(' '.join(message.command[1:]))
84 | if not new_file:
85 | await message.reply_text('you forgot to mention path!')
86 | return
87 | text = f'Uploading {html.escape(new_file)}...'
88 | reply = await message.reply_text(text)
89 | try:
90 | u = Uploader()
91 | await u.upload_to_drive(new_file, message, reply)
92 | except:
93 | await reply.edit('Error while uploading!')
94 | else:
95 | await reply.delete()
96 |
97 |
98 | @Client.on_message(filters.command('logs'))
99 | async def logsup(client, message):
100 | c = await check_chat(message, chat='Sudo')
101 | if c is None:
102 | return
103 | file = 'VideoEncoder/utils/extras/logs.txt'
104 | caption = '#Logs'
105 | try:
106 | await message.reply_document(
107 | file,
108 | caption=caption
109 | )
110 | except MessageIdInvalid:
111 | await message.reply('Upload cancelled!')
112 | except Exception as e:
113 | await message.reply(': {}'.format(e))
114 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from pyrogram import Client, filters
18 | from .. import LOGGER
19 | from . import (direct_link_generator, display_progress, ffmpeg, helper,
20 | settings, tasks)
21 |
22 | LOGGER.info('Imported Utils!')
23 |
24 | sauce = '''VideoEncoder - a telegram bot for compressing/encoding videos in h264 format.
25 | Copyright (c) 2021 WeebTime/Video-Encoder-Bot
26 | This program is free software: you can redistribute it and/or modify
27 | it under the terms of the GNU Affero General Public License as published
28 | by the Free Software Foundation, either version 3 of the License, or
29 | (at your option) any later version.
30 | This program is distributed in the hope that it will be useful,
31 | but WITHOUT ANY WARRANTY; without even the implied warranty of
32 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 | GNU Affero General Public License for more details.
34 | You should have received a copy of the GNU Affero General Public License
35 | along with this program. If not, see .'''
36 |
37 |
38 | @Client.on_message(filters.command('so' 'ur' 'ce'))
39 | async def g_s(_, message):
40 | try:
41 | await message.reply(text=sauce, reply_markup=helper.output, disable_web_page_preview=True)
42 | except RPCError:
43 | pass
--------------------------------------------------------------------------------
/VideoEncoder/utils/database/access_db.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from ... import database, session
18 | from .database import Database
19 |
20 | db = Database(database, session)
21 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/database/add_user.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from pyrogram import Client
18 | from pyrogram.types import Message
19 |
20 | from ... import log
21 | from .access_db import db
22 |
23 |
24 | async def AddUserToDatabase(bot: Client, cmd: Message):
25 | if not await db.is_user_exist(cmd.from_user.id):
26 | await db.add_user(cmd.from_user.id)
27 | if log is not None:
28 | await bot.send_message(
29 | int(log),
30 | f"New User \n\n{cmd.from_user.first_name} started @{(await bot.get_me()).username}!!"
31 | )
32 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/database/database.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import datetime
18 |
19 | import motor.motor_asyncio
20 |
21 |
22 | class Database:
23 |
24 | def __init__(self, uri, database_name):
25 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
26 | self.db = self._client[database_name]
27 | self.col = self.db.users
28 | self.col2 = self.db.status
29 |
30 | def new_user(self, id):
31 | return dict(
32 | id=id,
33 | join_date=datetime.date.today().isoformat(),
34 | extensions='MKV',
35 | hevc=False,
36 | aspect=False,
37 | cabac=False,
38 | reframe='pass',
39 | tune=True,
40 | frame='source',
41 | audio='aac',
42 | sample='source',
43 | bitrate='source',
44 | bits=False,
45 | channels='source',
46 | drive=False,
47 | preset='sf',
48 | metadata=True,
49 | hardsub=False,
50 | watermark=False,
51 | subtitles=True,
52 | resolution='OG',
53 | upload_as_doc=False,
54 | crf=22,
55 | resize=False
56 | )
57 |
58 | async def add_user(self, id):
59 | user = self.new_user(id)
60 | await self.col.insert_one(user)
61 |
62 | async def is_user_exist(self, id):
63 | user = await self.col.find_one({'id': int(id)})
64 | return True if user else False
65 |
66 | async def total_users_count(self):
67 | count = await self.col.count_documents({})
68 | return count
69 |
70 | async def get_all_users(self):
71 | all_users = self.col.find({})
72 | return all_users
73 |
74 | async def delete_user(self, user_id):
75 | await self.col.delete_many({'id': int(user_id)})
76 |
77 | # Telegram Related
78 |
79 | # Upload As Doc
80 | async def set_upload_as_doc(self, id, upload_as_doc):
81 | await self.col.update_one({'id': id}, {'$set': {'upload_as_doc': upload_as_doc}})
82 |
83 | async def get_upload_as_doc(self, id):
84 | user = await self.col.find_one({'id': int(id)})
85 | return user.get('upload_as_doc', False)
86 |
87 | # Encoding Settings
88 |
89 | # Resize
90 | async def set_resize(self, id, resize):
91 | await self.col.update_one({'id': id}, {'$set': {'resize': resize}})
92 |
93 | async def get_resize(self, id):
94 | user = await self.col.find_one({'id': int(id)})
95 | return user.get('resize', 'resize')
96 |
97 | # Frame
98 | async def set_frame(self, id, frame):
99 | await self.col.update_one({'id': id}, {'$set': {'frame': frame}})
100 |
101 | async def get_frame(self, id):
102 | user = await self.col.find_one({'id': int(id)})
103 | return user.get('frame', 'source')
104 |
105 | # Convert To 720p
106 | async def set_resolution(self, id, resolution):
107 | await self.col.update_one({'id': id}, {'$set': {'resolution': resolution}})
108 |
109 | async def get_resolution(self, id):
110 | user = await self.col.find_one({'id': int(id)})
111 | return user.get('resolution', 'OG')
112 |
113 | # Video Bits
114 | async def set_bits(self, id, bits):
115 | await self.col.update_one({'id': id}, {'$set': {'bits': bits}})
116 |
117 | async def get_bits(self, id):
118 | user = await self.col.find_one({'id': int(id)})
119 | return user.get('bits', False)
120 |
121 | # Copy Subtitles
122 | async def set_subtitles(self, id, subtitles):
123 | await self.col.update_one({'id': id}, {'$set': {'subtitles': subtitles}})
124 |
125 | async def get_subtitles(self, id):
126 | user = await self.col.find_one({'id': int(id)})
127 | return user.get('subtitles', False)
128 |
129 | # Sample rate
130 | async def set_samplerate(self, id, sample):
131 | await self.col.update_one({'id': id}, {'$set': {'sample': sample}})
132 |
133 | async def get_samplerate(self, id):
134 | user = await self.col.find_one({'id': int(id)})
135 | return user.get('sample', '44.1K')
136 |
137 | # Extensions
138 | async def set_extensions(self, id, extensions):
139 | await self.col.update_one({'id': id}, {'$set': {'extensions': extensions}})
140 |
141 | async def get_extensions(self, id):
142 | user = await self.col.find_one({'id': int(id)})
143 | return user.get('extensions', 'MP4')
144 |
145 | # Bit rate
146 | async def set_bitrate(self, id, bitrate):
147 | await self.col.update_one({'id': id}, {'$set': {'bitrate': bitrate}})
148 |
149 | async def get_bitrate(self, id):
150 | user = await self.col.find_one({'id': int(id)})
151 | return user.get('bitrate', '128')
152 |
153 | # Reframe
154 | async def set_reframe(self, id, reframe):
155 | await self.col.update_one({'id': id}, {'$set': {'reframe': reframe}})
156 |
157 | async def get_reframe(self, id):
158 | user = await self.col.find_one({'id': int(id)})
159 | return user.get('reframe', 'pass')
160 |
161 | # Audio Codec
162 | async def set_audio(self, id, audio):
163 | await self.col.update_one({'id': id}, {'$set': {'audio': audio}})
164 |
165 | async def get_audio(self, id):
166 | user = await self.col.find_one({'id': int(id)})
167 | return user.get('audio', 'dd')
168 |
169 | # Audio Channels
170 | async def set_channels(self, id, channels):
171 | await self.col.update_one({'id': id}, {'$set': {'channels': channels}})
172 |
173 | async def get_channels(self, id):
174 | user = await self.col.find_one({'id': int(id)})
175 | return user.get('channels', 'source')
176 |
177 | # Metadata Watermark
178 | async def set_metadata_w(self, id, metadata):
179 | await self.col.update_one({'id': id}, {'$set': {'metadata': metadata}})
180 |
181 | async def get_metadata_w(self, id):
182 | user = await self.col.find_one({'id': int(id)})
183 | return user.get('metadata', False)
184 |
185 | # Watermark
186 | async def set_watermark(self, id, watermark):
187 | await self.col.update_one({'id': id}, {'$set': {'watermark': watermark}})
188 |
189 | async def get_watermark(self, id):
190 | user = await self.col.find_one({'id': int(id)})
191 | return user.get('watermark', False)
192 |
193 | # Preset
194 | async def set_preset(self, id, preset):
195 | await self.col.update_one({'id': id}, {'$set': {'preset': preset}})
196 |
197 | async def get_preset(self, id):
198 | user = await self.col.find_one({'id': int(id)})
199 | return user.get('preset', 'sf')
200 |
201 | # Hard Sub
202 | async def set_hardsub(self, id, hardsub):
203 | await self.col.update_one({'id': id}, {'$set': {'hardsub': hardsub}})
204 |
205 | async def get_hardsub(self, id):
206 | user = await self.col.find_one({'id': int(id)})
207 | return user.get('hardsub', False)
208 |
209 | # HEVC
210 | async def set_hevc(self, id, hevc):
211 | await self.col.update_one({'id': id}, {'$set': {'hevc': hevc}})
212 |
213 | async def get_hevc(self, id):
214 | user = await self.col.find_one({'id': int(id)})
215 | return user.get('hevc', False)
216 |
217 | # Tune
218 | async def set_tune(self, id, tune):
219 | await self.col.update_one({'id': id}, {'$set': {'tune': tune}})
220 |
221 | async def get_tune(self, id):
222 | user = await self.col.find_one({'id': int(id)})
223 | return user.get('tune', False)
224 |
225 | # CABAC
226 | async def set_cabac(self, id, cabac):
227 | await self.col.update_one({'id': id}, {'$set': {'cabac': cabac}})
228 |
229 | async def get_cabac(self, id):
230 | user = await self.col.find_one({'id': int(id)})
231 | return user.get('cabac', False)
232 |
233 | # Aspect ratio
234 | async def set_aspect(self, id, aspect):
235 | await self.col.update_one({'id': id}, {'$set': {'aspect': aspect}})
236 |
237 | async def get_aspect(self, id):
238 | user = await self.col.find_one({'id': int(id)})
239 | return user.get('aspect', False)
240 |
241 | # Google Drive
242 | async def set_drive(self, id, drive):
243 | await self.col.update_one({'id': id}, {'$set': {'drive': drive}})
244 |
245 | async def get_drive(self, id):
246 | user = await self.col.find_one({'id': int(id)})
247 | return user.get('drive', False)
248 |
249 | # CRF
250 | async def get_crf(self, id):
251 | user = await self.col.find_one({'id': int(id)})
252 | return user.get('crf', 18)
253 |
254 | async def set_crf(self, id, crf):
255 | await self.col.update_one({'id': id}, {'$set': {'crf': crf}})
256 |
257 | # Process killed status
258 | async def get_killed_status(self):
259 | status = await self.col2.find_one({'id': 'killed'})
260 | if not status:
261 | await self.col2.insert_one({'id': 'killed', 'status': False})
262 | return False
263 | else:
264 | return status.get('status')
265 |
266 | async def set_killed_status(self, status):
267 | await self.col2.update_one({'id': 'killed'}, {'$set': {'status': status}})
268 |
269 | # Auth Chat
270 | async def get_chat(self):
271 | status = await self.col2.find_one({'id': 'auth'})
272 | if not status:
273 | await self.col2.insert_one({'id': 'auth', 'chat': '5217257368'})
274 | return '5217257368'
275 | else:
276 | return status.get('chat')
277 |
278 | async def set_chat(self, chat):
279 | await self.col2.update_one({'id': 'auth'}, {'$set': {'chat': chat}})
280 |
281 | # Auth Sudo
282 | async def get_sudo(self):
283 | status = await self.col2.find_one({'id': 'sudo'})
284 | if not status:
285 | await self.col2.insert_one({'id': 'sudo', 'sudo_': '5217257368'})
286 | return '5217257368'
287 | else:
288 | return status.get('sudo_')
289 |
290 | async def set_sudo(self, sudo):
291 | await self.col2.update_one({'id': 'sudo'}, {'$set': {'sudo_': sudo}})
292 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/direct_link_generator.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 | #
17 | # Copyright (C) 2019 The Raphielscape Company LLC.
18 | #
19 | # Licensed under the Raphielscape Public License, Version 1.c (the "License");
20 | # you may not use this file except in compliance with the License.
21 | #
22 |
23 | """ Helper Module containing various sites direct links generators. This module is copied and modified as per need
24 | from https://github.com/AvinashReddy3108/PaperplaneExtended . I hereby take no credit of the following code other
25 | than the modifications. See https://github.com/AvinashReddy3108/PaperplaneExtended/commits/master/userbot/modules/direct_links.py
26 | for original authorship. """
27 |
28 | import json
29 | import re
30 | import urllib.parse
31 | from base64 import standard_b64encode
32 | from os import popen
33 | from random import choice
34 | from urllib.parse import urlparse
35 |
36 | import cfscrape
37 | import lk21
38 | import requests
39 | from bs4 import BeautifulSoup
40 | from js2py import EvalJs
41 |
42 |
43 | class DirectDownloadLinkException(Exception):
44 | """No method found for extracting direct download link from the http link"""
45 | pass
46 |
47 |
48 | def direct_link_generator(text_url: str):
49 | """ direct links generator """
50 | if 'youtube.com' in text_url or 'youtu.be' in text_url:
51 | raise DirectDownloadLinkException(f"ERROR: NO YTDL")
52 | elif 'dood.to' in text_url or 'yuudrive.' in text_url or 'pdisk.' in text_url or 'Pdisk.' in text_url or 'nitroflare.' in text_url:
53 | raise DirectDownloadLinkException('ERROR: These Links Are Not Supported')
54 | elif '0:/' in text_url and text_url.endswith("/") or '1:/' in text_url and text_url.endswith("/") or '2:/' in text_url and text_url.endswith("/") or '3:/' in text_url and text_url.endswith("/") or '4:/' in text_url and text_url.endswith("/") or '5:/' in text_url and text_url.endswith("/") or '6:/' in text_url and text_url.endswith("/"):
55 | raise DirectDownloadLinkException('ERROR: Bot can\'t download An Index Folder')
56 | elif '?a=view' in text_url:
57 | return text_url.replace("?a=view", "")
58 | elif 'zippyshare.com' in text_url:
59 | return zippy_share(text_url)
60 | elif 'yadi.sk' in text_url:
61 | return yandex_disk(text_url)
62 | elif 'cloud.mail.ru' in text_url:
63 | return cm_ru(text_url)
64 | elif 'mediafire.com' in text_url:
65 | return mediafire(text_url)
66 | elif 'osdn.net' in text_url:
67 | return osdn(text_url)
68 | elif 'github.com' in text_url:
69 | return github(text_url)
70 | elif 'hxfile.co' in text_url:
71 | return hxfile(text_url)
72 | elif 'anonfiles.com' in text_url:
73 | return anonfiles(text_url)
74 | elif 'letsupload.io' in text_url:
75 | return letsupload(text_url)
76 | elif 'fembed.net' in text_url:
77 | return fembed(text_url)
78 | elif 'fembed.com' in text_url:
79 | return fembed(text_url)
80 | elif 'femax20.com' in text_url:
81 | return fembed(text_url)
82 | elif 'fcdn.stream' in text_url:
83 | return fembed(text_url)
84 | elif 'feurl.com' in text_url:
85 | return fembed(text_url)
86 | elif 'naniplay.nanime.in' in text_url:
87 | return fembed(text_url)
88 | elif 'naniplay.nanime.biz' in text_url:
89 | return fembed(text_url)
90 | elif 'naniplay.com' in text_url:
91 | return fembed(text_url)
92 | elif 'layarkacaxxi.icu' in text_url:
93 | return fembed(text_url)
94 | elif 'sbembed.com' in text_url:
95 | return sbembed(text_url)
96 | elif 'streamsb.net' in text_url:
97 | return sbembed(text_url)
98 | elif 'sbplay.org' in text_url:
99 | return sbembed(text_url)
100 | elif 'racaty.net' in text_url:
101 | return racaty(text_url)
102 | elif '1drv.ms' in text_url:
103 | return onedrive(text_url)
104 | elif 'pixeldrain.com' in text_url:
105 | return pixeldrain(text_url)
106 | elif 'antfiles.com' in text_url:
107 | return antfiles(text_url)
108 | elif 'streamtape.com' in text_url:
109 | return streamtape(text_url)
110 | elif 'bayfiles.com' in text_url:
111 | return anonfiles(text_url)
112 | elif '1fichier.com' in text_url:
113 | return fichier(text_url)
114 | elif 'solidfiles.com' in text_url:
115 | return solidfiles(text_url)
116 | else:
117 | return None
118 |
119 |
120 | def zippy_share(url: str) -> str:
121 | link = re.findall("https:/.(.*?).zippyshare", url)[0]
122 | response_content = (requests.get(url)).content
123 | bs_obj = BeautifulSoup(response_content, "lxml")
124 |
125 | try:
126 | js_script = bs_obj.find("div", {"class": "center", }).find_all(
127 | "script"
128 | )[1]
129 | except:
130 | js_script = bs_obj.find("div", {"class": "right", }).find_all(
131 | "script"
132 | )[0]
133 |
134 | js_content = re.findall(r'\.href.=."/(.*?)";', str(js_script))
135 | js_content = 'var x = "/' + js_content[0] + '"'
136 |
137 | evaljs = EvalJs()
138 | setattr(evaljs, "x", None)
139 | evaljs.execute(js_content)
140 | js_content = getattr(evaljs, "x")
141 |
142 | return f"https://{link}.zippyshare.com{js_content}"
143 |
144 |
145 | def yandex_disk(url: str) -> str:
146 | """ Yandex.Disk direct links generator
147 | Based on https://github.com/wldhx/yadisk-direct"""
148 | try:
149 | text_url = re.findall(r'\bhttps?://.*yadi\.sk\S+', url)[0]
150 | except IndexError:
151 | reply = "`No Yandex.Disk links found`\n"
152 | return reply
153 | api = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}'
154 | try:
155 | dl_url = requests.get(api.format(text_url)).json()['href']
156 | return dl_url
157 | except KeyError:
158 | raise DirectDownloadLinkException(
159 | "`Error: File not found / Download limit reached`\n")
160 |
161 |
162 | def cm_ru(url: str) -> str:
163 | """ cloud.mail.ru direct links generator
164 | Using https://github.com/JrMasterModelBuilder/cmrudl.py"""
165 | reply = ''
166 | try:
167 | text_url = re.findall(r'\bhttps?://.*cloud\.mail\.ru\S+', url)[0]
168 | except IndexError:
169 | raise DirectDownloadLinkException("`No cloud.mail.ru links found`\n")
170 | command = f'vendor/cmrudl.py/cmrudl -s {text_url}'
171 | result = popen(command).read()
172 | result = result.splitlines()[-1]
173 | try:
174 | data = json.loads(result)
175 | except json.decoder.JSONDecodeError:
176 | raise DirectDownloadLinkException("`Error: Can't extract the link`\n")
177 | dl_url = data['download']
178 | return dl_url
179 |
180 |
181 | def mediafire(url: str) -> str:
182 | """ MediaFire direct links generator """
183 | try:
184 | text_url = re.findall(r'\bhttps?://.*mediafire\.com\S+', url)[0]
185 | except IndexError:
186 | raise DirectDownloadLinkException("`No MediaFire links found`\n")
187 | page = BeautifulSoup(requests.get(text_url).content, 'lxml')
188 | info = page.find('a', {'aria-label': 'Download file'})
189 | dl_url = info.get('href')
190 | return dl_url
191 |
192 |
193 | def osdn(url: str) -> str:
194 | """ OSDN direct links generator """
195 | osdn_link = 'https://osdn.net'
196 | try:
197 | text_url = re.findall(r'\bhttps?://.*osdn\.net\S+', url)[0]
198 | except IndexError:
199 | raise DirectDownloadLinkException("`No OSDN links found`\n")
200 | page = BeautifulSoup(
201 | requests.get(text_url, allow_redirects=True).content, 'lxml')
202 | info = page.find('a', {'class': 'mirror_link'})
203 | text_url = urllib.parse.unquote(osdn_link + info['href'])
204 | mirrors = page.find('form', {'id': 'mirror-select-form'}).findAll('tr')
205 | urls = []
206 | for data in mirrors[1:]:
207 | mirror = data.find('input')['value']
208 | urls.append(re.sub(r'm=(.*)&f', f'm={mirror}&f', text_url))
209 | return urls[0]
210 |
211 |
212 | def github(url: str) -> str:
213 | """ GitHub direct links generator """
214 | try:
215 | text_url = re.findall(r'\bhttps?://.*github\.com.*releases\S+', url)[0]
216 | except IndexError:
217 | raise DirectDownloadLinkException("`No GitHub Releases links found`\n")
218 | download = requests.get(text_url, stream=True, allow_redirects=False)
219 | try:
220 | dl_url = download.headers["location"]
221 | return dl_url
222 | except KeyError:
223 | raise DirectDownloadLinkException("`Error: Can't extract the link`\n")
224 |
225 |
226 | def onedrive(link: str) -> str:
227 | """ Onedrive direct link generator
228 | Based on https://github.com/UsergeTeam/Userge """
229 | link_without_query = urlparse(link)._replace(query=None).geturl()
230 | direct_link_encoded = str(standard_b64encode(
231 | bytes(link_without_query, "utf-8")), "utf-8")
232 | direct_link1 = f"https://api.onedrive.com/v1.0/shares/u!{direct_link_encoded}/root/content"
233 | resp = requests.head(direct_link1)
234 | if resp.status_code != 302:
235 | return "ERROR: Unauthorized link, the link may be private"
236 | dl_link = resp.next.url
237 | file_name = dl_link.rsplit("/", 1)[1]
238 | resp2 = requests.head(dl_link)
239 | return dl_link
240 |
241 |
242 | def hxfile(url: str) -> str:
243 | """ Hxfile direct link generator
244 | Based on https://github.com/zevtyardt/lk21
245 | https://github.com/SlamDevs/slam-mirrorbot """
246 | bypasser = lk21.Bypass()
247 | dl_url = bypasser.bypass_filesIm(url)
248 | return dl_url
249 |
250 |
251 | def anonfiles(url: str) -> str:
252 | """ Anonfiles direct link generator
253 | Based on https://github.com/zevtyardt/lk21
254 | https://github.com/SlamDevs/slam-mirrorbot """
255 | bypasser = lk21.Bypass()
256 | dl_url = bypasser.bypass_anonfiles(url)
257 | return dl_url
258 |
259 |
260 | def letsupload(url: str) -> str:
261 | """ Letsupload direct link generator
262 | Based on https://github.com/zevtyardt/lk21
263 | https://github.com/SlamDevs/slam-mirrorbot """
264 | dl_url = ''
265 | try:
266 | link = re.findall(r'\bhttps?://.*letsupload\.io\S+', url)[0]
267 | except IndexError:
268 | raise DirectDownloadLinkException("No Letsupload links found\n")
269 | bypasser = lk21.Bypass()
270 | dl_url = bypasser.bypass_url(link)
271 | return dl_url
272 |
273 |
274 | def fembed(link: str) -> str:
275 | """ Fembed direct link generator
276 | Based on https://github.com/zevtyardt/lk21
277 | https://github.com/SlamDevs/slam-mirrorbot """
278 | bypasser = lk21.Bypass()
279 | dl_url = bypasser.bypass_fembed(link)
280 | lst_link = []
281 | count = len(dl_url)
282 | for i in dl_url:
283 | lst_link.append(dl_url[i])
284 | return lst_link[count-1]
285 |
286 |
287 | def sbembed(link: str) -> str:
288 | """ Sbembed direct link generator
289 | Based on https://github.com/zevtyardt/lk21
290 | https://github.com/SlamDevs/slam-mirrorbot """
291 | bypasser = lk21.Bypass()
292 | dl_url = bypasser.bypass_sbembed(link)
293 | lst_link = []
294 | count = len(dl_url)
295 | for i in dl_url:
296 | lst_link.append(dl_url[i])
297 | return lst_link[count-1]
298 |
299 |
300 | def pixeldrain(url: str) -> str:
301 | """ Based on https://github.com/yash-dk/TorToolkit-Telegram """
302 | url = url.strip("/ ")
303 | file_id = url.split("/")[-1]
304 | info_link = f"https://pixeldrain.com/api/file/{file_id}/info"
305 | dl_link = f"https://pixeldrain.com/api/file/{file_id}"
306 | resp = requests.get(info_link).json()
307 | if resp["success"]:
308 | return dl_link
309 | else:
310 | raise DirectDownloadLinkException(
311 | "ERROR: Cant't download due {}.".format(resp.text["value"]))
312 |
313 |
314 | def antfiles(url: str) -> str:
315 | """ Antfiles direct link generator
316 | Based on https://github.com/zevtyardt/lk21
317 | https://github.com/SlamDevs/slam-mirrorbot """
318 | bypasser = lk21.Bypass()
319 | dl_url = bypasser.bypass_antfiles(url)
320 | return dl_url
321 |
322 |
323 | def streamtape(url: str) -> str:
324 | """ Streamtape direct link generator
325 | Based on https://github.com/zevtyardt/lk21
326 | https://github.com/SlamDevs/slam-mirrorbot """
327 | bypasser = lk21.Bypass()
328 | dl_url = bypasser.bypass_streamtape(url)
329 | return dl_url
330 |
331 |
332 | def racaty(url: str) -> str:
333 | """ Racaty direct links generator
334 | based on https://github.com/Slam-Team/slam-mirrorbot """
335 | dl_url = ''
336 | try:
337 | link = re.findall(r'\bhttps?://.*racaty\.net\S+', url)[0]
338 | except IndexError:
339 | raise DirectDownloadLinkException("No Racaty links found\n")
340 | scraper = cfscrape.create_scraper()
341 | r = scraper.get(url)
342 | soup = BeautifulSoup(r.text, "lxml")
343 | op = soup.find("input", {"name": "op"})["value"]
344 | ids = soup.find("input", {"name": "id"})["value"]
345 | rpost = scraper.post(url, data={"op": op, "id": ids})
346 | rsoup = BeautifulSoup(rpost.text, "lxml")
347 | dl_url = rsoup.find("a", {"id": "uniqueExpirylink"})[
348 | "href"].replace(" ", "%20")
349 | return dl_url
350 |
351 |
352 | def fichier(link: str) -> str:
353 | """ 1Fichier direct links generator
354 | Based on https://github.com/Maujar
355 | https://github.com/Slam-Team/slam-mirrorbot """
356 | regex = r"^([http:\/\/|https:\/\/]+)?.*1fichier\.com\/\?.+"
357 | gan = re.match(regex, link)
358 | if not gan:
359 | raise DirectDownloadLinkException(
360 | "ERROR: The link you entered is wrong!")
361 | if "::" in link:
362 | pswd = link.split("::")[-1]
363 | url = link.split("::")[-2]
364 | else:
365 | pswd = None
366 | url = link
367 | try:
368 | if pswd is None:
369 | req = requests.post(url)
370 | else:
371 | pw = {"pass": pswd}
372 | req = requests.post(url, data=pw)
373 | except:
374 | raise DirectDownloadLinkException(
375 | "ERROR: Unable to reach 1fichier server!")
376 | if req.status_code == 404:
377 | raise DirectDownloadLinkException(
378 | "ERROR: File not found/The link you entered is wrong!")
379 | soup = BeautifulSoup(req.content, 'lxml')
380 | if soup.find("a", {"class": "ok btn-general btn-orange"}) is not None:
381 | dl_url = soup.find("a", {"class": "ok btn-general btn-orange"})["href"]
382 | if dl_url is None:
383 | raise DirectDownloadLinkException(
384 | "ERROR: Unable to generate Direct Link 1fichier!")
385 | else:
386 | return dl_url
387 | else:
388 | if len(soup.find_all("div", {"class": "ct_warn"})) == 2:
389 | str_2 = soup.find_all("div", {"class": "ct_warn"})[-1]
390 | if "you must wait" in str(str_2).lower():
391 | numbers = [int(word)
392 | for word in str(str_2).split() if word.isdigit()]
393 | if len(numbers) == 0:
394 | raise DirectDownloadLinkException(
395 | "ERROR: 1fichier is on a limit. Please wait a few minutes/hour.")
396 | else:
397 | raise DirectDownloadLinkException(
398 | f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute.")
399 | elif "protect access" in str(str_2).lower():
400 | raise DirectDownloadLinkException(
401 | "ERROR: This link requires a password!\n\nThis link requires a password!\n- Insert sign :: after the link and write the password after the sign.\n\nExample:\n/mirror https://1fichier.com/?smmtd8twfpm66awbqz04::love you
\n\n* No spaces between the signs ::\n* For the password, you can use a space!")
402 | else:
403 | raise DirectDownloadLinkException(
404 | "ERROR: Error trying to generate Direct Link from 1fichier!")
405 | elif len(soup.find_all("div", {"class": "ct_warn"})) == 3:
406 | str_1 = soup.find_all("div", {"class": "ct_warn"})[-2]
407 | str_3 = soup.find_all("div", {"class": "ct_warn"})[-1]
408 | if "you must wait" in str(str_1).lower():
409 | numbers = [int(word)
410 | for word in str(str_1).split() if word.isdigit()]
411 | if len(numbers) == 0:
412 | raise DirectDownloadLinkException(
413 | "ERROR: 1fichier is on a limit. Please wait a few minutes/hour.")
414 | else:
415 | raise DirectDownloadLinkException(
416 | f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute.")
417 | elif "bad password" in str(str_3).lower():
418 | raise DirectDownloadLinkException(
419 | "ERROR: The password you entered is wrong!")
420 | else:
421 | raise DirectDownloadLinkException(
422 | "ERROR: Error trying to generate Direct Link from 1fichier!")
423 | else:
424 | raise DirectDownloadLinkException(
425 | "ERROR: Error trying to generate Direct Link from 1fichier!")
426 |
427 |
428 | def solidfiles(url: str) -> str:
429 | """ Solidfiles direct links generator
430 | Based on https://github.com/Xonshiz/SolidFiles-Downloader
431 | By https://github.com/Jusidama18 """
432 | headers = {
433 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36'
434 | }
435 | pageSource = requests.get(url, headers=headers).text
436 | mainOptions = str(
437 | re.search(r'viewerOptions\'\,\ (.*?)\)\;', pageSource).group(1))
438 | dl_url = json.loads(mainOptions)["downloadUrl"]
439 | return dl_url
440 |
441 |
442 | def useragent():
443 | """
444 | useragent random setter
445 | """
446 | useragents = BeautifulSoup(
447 | requests.get(
448 | 'https://developers.whatismybrowser.com/'
449 | 'useragents/explore/operating_system_name/android/').content,
450 | 'lxml').findAll('td', {'class': 'useragent'})
451 | user_agent = choice(useragents)
452 | return user_agent.text
453 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/direct_link_generator_license.md:
--------------------------------------------------------------------------------
1 | RAPHIELSCAPE PUBLIC LICENSE
2 | Version 1.c, June 2019
3 |
4 | Copyright (C) 2019 Raphielscape LLC.
5 | Copyright (C) 2019 Devscapes Open Source Holding GmbH.
6 |
7 | Everyone is permitted to copy and distribute verbatim or modified
8 | copies of this license document, and changing it is allowed as long
9 | as the name is changed.
10 |
11 | RAPHIELSCAPE PUBLIC LICENSE
12 | A-1. DEFINITIONS
13 |
14 | 0. “This License” refers to version 1.c of the Raphielscape Public License.
15 |
16 | 1. “Copyright” also means copyright-like laws that apply to other kinds of works.
17 |
18 | 2. “The Work" refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”.
19 | “Licensees” and “recipients” may be individuals or organizations.
20 |
21 | 3. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission,
22 | other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work
23 | or a work “based on” the earlier work.
24 |
25 | 4. Source Form. The “source form” for a work means the preferred form of the work for making modifications to it.
26 | “Object code” means any non-source form of a work.
27 |
28 | The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and
29 | (for an executable work) run the object code and to modify the work, including scripts to control those activities.
30 |
31 | The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
32 | The Corresponding Source for a work in source code form is that same work.
33 |
34 | 5. "The author" refers to "author" of the code, which is the one that made the particular code which exists inside of
35 | the Corresponding Source.
36 |
37 | 6. "Owner" refers to any parties which is made the early form of the Corresponding Source.
38 |
39 | A-2. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
40 |
41 | 0. You must give any other recipients of the Work or Derivative Works a copy of this License; and
42 |
43 | 1. You must cause any modified files to carry prominent notices stating that You changed the files; and
44 |
45 | 2. You must retain, in the Source form of any Derivative Works that You distribute,
46 | this license, all copyright, patent, trademark, authorships and attribution notices
47 | from the Source form of the Work; and
48 |
49 | 3. Respecting the author and owner of works that are distributed in any way.
50 |
51 | You may add Your own copyright statement to Your modifications and may provide
52 | additional or different license terms and conditions for use, reproduction,
53 | or distribution of Your modifications, or for any such Derivative Works as a whole,
54 | provided Your use, reproduction, and distribution of the Work otherwise complies
55 | with the conditions stated in this License.
56 |
57 | B. DISCLAIMER OF WARRANTY
58 |
59 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
60 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
61 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS
62 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
63 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
64 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
65 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
66 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
67 |
68 |
69 | C. REVISED VERSION OF THIS LICENSE
70 |
71 | The Devscapes Open Source Holding GmbH. may publish revised and/or new versions of the
72 | Raphielscape Public License from time to time. Such new versions will be similar in spirit
73 | to the present version, but may differ in detail to address new problems or concerns.
74 |
75 | Each version is given a distinguishing version number. If the Program specifies that a
76 | certain numbered version of the Raphielscape Public License "or any later version" applies to it,
77 | you have the option of following the terms and conditions either of that numbered version or of
78 | any later version published by the Devscapes Open Source Holding GmbH. If the Program does not specify a
79 | version number of the Raphielscape Public License, you may choose any version ever published
80 | by the Devscapes Open Source Holding GmbH.
81 |
82 | END OF LICENSE
--------------------------------------------------------------------------------
/VideoEncoder/utils/display_progress.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 | import math
19 | import time
20 |
21 | from .. import PROGRESS
22 |
23 |
24 | async def progress_for_pyrogram(current, total, ud_type, message, start):
25 | now = time.time()
26 | diff = now - start
27 | if not round(diff % 10.00) or current == total:
28 | percentage = current * 100 / total
29 | speed = current / diff
30 | elapsed_time = round(diff)
31 | time_to_completion = round((total - current) / speed)
32 | estimated_total_time = elapsed_time + time_to_completion
33 | elapsed_time = TimeFormatter(seconds=elapsed_time)
34 | estimated_total_time = TimeFormatter(seconds=estimated_total_time)
35 | progress = "{0}{1}".format(
36 | ''.join(["█" for i in range(math.floor(percentage / 10))]),
37 | ''.join(["░" for i in range(10 - math.floor(percentage / 10))])
38 | )
39 | tmp = progress + PROGRESS.format(
40 | humanbytes(current),
41 | humanbytes(total),
42 | humanbytes(speed) + "/s",
43 | estimated_total_time if estimated_total_time != '...' else "Calculating"
44 | )
45 | await message.edit(
46 | text="{}\n{}".format(
47 | ud_type,
48 | tmp
49 | )
50 | )
51 | await asyncio.sleep(5)
52 |
53 |
54 | async def progress_for_url(downloader, msg):
55 | total_length = downloader.filesize if downloader.filesize else 0
56 | downloaded = downloader.get_dl_size()
57 | speed = downloader.get_speed(human=True)
58 | estimated_total_time = downloader.get_eta(human=True)
59 | percentage = downloader.get_progress() * 100
60 | progress = "{0}{1}".format(
61 | ''.join(["█" for i in range(math.floor(percentage / 10))]),
62 | ''.join(["░" for i in range(10 - math.floor(percentage / 10))])
63 | )
64 | progress_str = "Downloading\n" + progress + PROGRESS.format(
65 | humanbytes(downloaded),
66 | humanbytes(total_length),
67 | speed,
68 | estimated_total_time)
69 | await msg.edit_text(progress_str)
70 | await asyncio.sleep(5)
71 |
72 |
73 | def humanbytes(size):
74 | """ humanize size """
75 | if not size:
76 | return ""
77 | power = 1024
78 | t_n = 0
79 | power_dict = {0: ' ', 1: 'K', 2: 'M', 3: 'G', 4: 'T'}
80 | while size > power:
81 | size /= power
82 | t_n += 1
83 | return "{:.2f} {}B".format(size, power_dict[t_n])
84 |
85 |
86 | def TimeFormatter(seconds: float) -> str:
87 | """ humanize time """
88 | minutes, seconds = divmod(int(seconds), 60)
89 | hours, minutes = divmod(minutes, 60)
90 | days, hours = divmod(hours, 24)
91 | tmp = ((str(days) + "d, ") if days else "") + \
92 | ((str(hours) + "h, ") if hours else "") + \
93 | ((str(minutes) + "m, ") if minutes else "") + \
94 | ((str(seconds) + "s, ") if seconds else "")
95 | return tmp[:-2]
96 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/extras/watermark.ass:
--------------------------------------------------------------------------------
1 | [Script Info]
2 | ; Script generated by Aegisub 3.2.2
3 | ; http://www.aegisub.org/
4 | Title: Default Aegisub file
5 | ScriptType: v4.00+
6 | WrapStyle: 0
7 | ScaledBorderAndShadow: yes
8 | YCbCr Matrix: TV.601
9 | PlayResX: 1920
10 | PlayResY: 1080
11 |
12 | [Aegisub Project Garbage]
13 | Video AR Mode: 4
14 | Video AR Value: 1.777778
15 | Video Zoom Percent: 0.500000
16 | Video Position: 1653
17 |
18 | [V4+ Styles]
19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
20 | Style: Default,FOT-RodinMaria Pro B,25,&H00FFFFFF,&H000000FF,&H0013131C,&H00191922,-1,0,0,0,100,100,0,0,1,3.5,1,2,10,10,10,1
21 |
22 | [Events]
23 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
24 | Dialogue: 0,0:00:05.00,0:00:15.00,Default,,0,0,0,,{\pos(966,96)}Encoded By Weeb-Zone.Blogspot.Com
25 | Dialogue: 0,0:05:10.00,0:05:30.00,Default,,0,0,0,,{\pos(966,96)}Encoded By Weeb-Zone.Blogspot.Com
26 | Dialogue: 0,0:09:45.00,0:10:10.00,Default,,0,0,0,,{\pos(966,96)}Encoded By Weeb-Zone.Blogspot.Com
27 | Dialogue: 0,0:15:00.00,0:15:20.00,Default,,0,0,0,,{\pos(966,96)}Encoded By Weeb-Zone.Blogspot.Com
28 | Dialogue: 0,0:17:20.00,0:18:30.00,Default,,0,0,0,,{\pos(966,96)}Encoded By Weeb-Zone.Blogspot.Com
29 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/ffmpeg.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 | import json
19 | import math
20 | import os
21 | import re
22 | import subprocess
23 | import time
24 |
25 | from hachoir.metadata import extractMetadata
26 | from hachoir.parser import createParser
27 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
28 |
29 | import ffmpeg
30 |
31 | from .. import LOGGER, download_dir, encode_dir
32 | from .database.access_db import db
33 | from .display_progress import TimeFormatter
34 |
35 |
36 | def get_codec(filepath, channel='v:0'):
37 | output = subprocess.check_output(['ffprobe', '-v', 'error', '-select_streams', channel,
38 | '-show_entries', 'stream=codec_name,codec_tag_string', '-of',
39 | 'default=nokey=1:noprint_wrappers=1', filepath])
40 | return output.decode('utf-8').split()
41 |
42 |
43 | async def extract_subs(filepath, msg, user_id):
44 |
45 | path, extension = os.path.splitext(filepath)
46 | name = path.split('/')
47 | check = get_codec(filepath, channel='s:0')
48 | if check == []:
49 | return None
50 | elif check == 'pgs':
51 | return None
52 | else:
53 | output = encode_dir + str(msg.id) + '.ass'
54 | subprocess.call(['ffmpeg', '-y', '-i', filepath, '-map', 's:0', output])
55 | subprocess.call(['mkvextract', 'attachments', filepath, '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16',
56 | '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40'])
57 | subprocess.run([f"mv -f *.JFPROJ *.FNT *.PFA *.ETX *.WOFF *.FOT *.TTF *.SFD *.VLW *.VFB *.PFB *.OTF *.GXF *.WOFF2 *.ODTTF *.BF *.CHR *.TTC *.BDF *.FON *.GF *.PMT *.AMFM *.MF *.PFM *.COMPOSITEFONT *.PF2 *.GDR *.ABF *.VNF *.PCF *.SFP *.MXF *.DFONT *.UFO *.PFR *.TFM *.GLIF *.XFN *.AFM *.TTE *.XFT *.ACFM *.EOT *.FFIL *.PK *.SUIT *.NFTR *.EUF *.TXF *.CHA *.LWFN *.T65 *.MCF *.YTF *.F3F *.FEA *.SFT *.PFT /usr/share/fonts/"], shell=True)
58 | subprocess.run([f"mv -f *.jfproj *.fnt *.pfa *.etx *.woff *.fot *.ttf *.sfd *.vlw *.vfb *.pfb *.otf *.gxf *.woff2 *.odttf *.bf *.chr *.ttc *.bdf *.fon *.gf *.pmt *.amfm *.mf *.pfm *.compositefont *.pf2 *.gdr *.abf *.vnf *.pcf *.sfp *.mxf *.dfont *.ufo *.pfr *.tfm *.glif *.xfn *.afm *.tte *.xft *.acfm *.eot *.ffil *.pk *.suit *.nftr *.euf *.txf *.cha *.lwfn *.t65 *.mcf *.ytf *.f3f *.fea *.sft *.pft /usr/share/fonts/ && fc-cache -f"], shell=True)
59 | return output
60 |
61 |
62 | async def encode(filepath, message, msg):
63 |
64 | ex = await db.get_extensions(message.from_user.id)
65 | path, extension = os.path.splitext(filepath)
66 | name = path.split('/')
67 |
68 | if ex == 'MP4':
69 | output_filepathh = encode_dir + name[len(name)-1] + '.mp4'
70 | elif ex == 'AVI':
71 | output_filepathh = encode_dir + name[len(name)-1] + '.avi'
72 | else:
73 | output_filepathh = encode_dir + name[len(name)-1] + '.mkv'
74 |
75 | output_filepath = output_filepathh
76 | subtitles_path = encode_dir + str(msg.id) + '.ass'
77 |
78 | progress = download_dir + "process.txt"
79 | with open(progress, 'w') as f:
80 | pass
81 |
82 | assert(output_filepath != filepath)
83 |
84 | # Check Path
85 | if os.path.isfile(output_filepath):
86 | LOGGER.warning(f'"{output_filepath}": file already exists')
87 | else:
88 | LOGGER.info(filepath)
89 |
90 | # HEVC Encode
91 | x265 = await db.get_hevc(message.from_user.id)
92 | video_i = get_codec(filepath, channel='v:0')
93 | if video_i == []:
94 | codec = ''
95 | else:
96 | if x265:
97 | codec = '-c:v libx265'
98 | else:
99 | codec = '-c:v libx264'
100 |
101 | # Tune Encode
102 | tune = await db.get_tune(message.from_user.id)
103 | if tune:
104 | tunevideo = '-tune animation'
105 | else:
106 | tunevideo = '-tune film'
107 |
108 | # CABAC
109 | cbb = await db.get_cabac(message.from_user.id)
110 | if cbb:
111 | cabac = '-coder 1'
112 | else:
113 | cabac = '-coder 0'
114 |
115 | # Reframe
116 | rf = await db.get_reframe(message.from_user.id)
117 | if rf == '4':
118 | reframe = '-refs 4'
119 | elif rf == '8':
120 | reframe = '-refs 8'
121 | elif rf == '16':
122 | reframe = '-refs 16'
123 | else:
124 | reframe = ''
125 |
126 | # Bits
127 | b = await db.get_bits(message.from_user.id)
128 | if not b:
129 | codec += ' -pix_fmt yuv420p'
130 | else:
131 | codec += ' -pix_fmt yuv420p10le'
132 |
133 | # CRF
134 | crf = await db.get_crf(message.from_user.id)
135 | if crf:
136 | Crf = f'-crf {crf}'
137 | else:
138 | await db.set_crf(message.from_user.id, crf=26)
139 | Crf = '-crf 26'
140 |
141 | # Frame
142 | fr = await db.get_frame(message.from_user.id)
143 | if fr == 'ntsc':
144 | frame = '-r ntsc'
145 | elif fr == 'pal':
146 | frame = '-r pal'
147 | elif fr == 'film':
148 | frame = '-r film'
149 | elif fr == '23.976':
150 | frame = '-r 24000/1001'
151 | elif fr == '30':
152 | frame = '-r 30'
153 | elif fr == '60':
154 | frame = '-r 60'
155 | else:
156 | frame = ''
157 |
158 | # Aspect ratio
159 | ap = await db.get_aspect(message.from_user.id)
160 | if ap:
161 | aspect = '-aspect 16:9'
162 | else:
163 | aspect = ''
164 |
165 | # Preset
166 | p = await db.get_preset(message.from_user.id)
167 | if p == 'uf':
168 | preset = '-preset ultrafast'
169 | elif p == 'sf':
170 | preset = '-preset superfast'
171 | elif p == 'vf':
172 | preset = '-preset veryfast'
173 | elif p == 'f':
174 | preset = '-preset fast'
175 | elif p == 'm':
176 | preset = '-preset medium'
177 | else:
178 | preset = '-preset slow'
179 |
180 | # Some Optional Things
181 | x265 = await db.get_hevc(message.from_user.id)
182 | if x265:
183 | video_opts = f'-profile:v main -map 0:v? -map_chapters 0 -map_metadata 0'
184 | else:
185 | video_opts = f'{cabac} {reframe} -profile:v main -map 0:v? -map_chapters 0 -map_metadata 0'
186 |
187 | # Metadata Watermark
188 | m = await db.get_metadata_w(message.from_user.id)
189 | if m:
190 | metadata = '-metadata title=Weeb-Zone.Blogspot.com -metadata:s:v title=Weeb-Zone.Blogspot.com -metadata:s:a title=Weeb-Zone.Blogspot.com'
191 | else:
192 | metadata = ''
193 |
194 | # Copy Subtitles
195 | h = await db.get_hardsub(message.from_user.id)
196 | s = await db.get_subtitles(message.from_user.id)
197 | subs_i = get_codec(filepath, channel='s:0')
198 | if subs_i == []:
199 | subtitles = ''
200 | else:
201 | if s:
202 | if h:
203 | subtitles = ''
204 | else:
205 | subtitles = '-c:s copy -c:t copy -map 0:t? -map 0:s?'
206 | else:
207 | subtitles = ''
208 |
209 |
210 | # ffmpeg_filter = ':'.join([
211 | # 'drawtext=fontfile=/app/bot/utils/watermark/font.ttf',
212 | # f"text='Weeb-Zone.Blogspot.com'",
213 | # f'fontcolor=white',
214 | # 'fontsize=main_h/20',
215 | # f'x=40:y=40'
216 | # ])
217 |
218 | # Watermark and Resolution
219 | r = await db.get_resolution(message.from_user.id)
220 | w = await db.get_watermark(message.from_user.id)
221 | if r == 'OG':
222 | watermark = ''
223 | elif r == '1080':
224 | watermark = '-vf scale=1920:1080'
225 | elif r == '720':
226 | watermark = '-vf scale=1280:720'
227 | elif r == '576':
228 | watermark = '-vf scale=768:576'
229 | else:
230 | watermark = '-vf scale=852:480'
231 | if w:
232 | if r == 'OG':
233 | watermark += '-vf '
234 | else:
235 | watermark += ','
236 | watermark += 'subtitles=VideoEncoder/utils/extras/watermark.ass'
237 |
238 | # Hard Subs
239 | if h:
240 | if r == 'OG':
241 | if w:
242 | watermark += ','
243 | else:
244 | watermark += '-vf '
245 | else:
246 | watermark += ','
247 | watermark += f'subtitles={subtitles_path}'
248 |
249 | # Sample rate
250 | sr = await db.get_samplerate(message.from_user.id)
251 | if sr == '44.1K':
252 | sample = '-ar 44100'
253 | elif sr == '48K':
254 | sample = '-ar 48000'
255 | else:
256 | sample = ''
257 |
258 | # bit rate
259 | bit = await db.get_bitrate(message.from_user.id)
260 | if bit == '400':
261 | bitrate = '-b:a 400k'
262 | elif bit == '320':
263 | bitrate = '-b:a 320k'
264 | elif bit == '256':
265 | bitrate = '-b:a 256k'
266 | elif bit == '224':
267 | bitrate = '-b:a 224k'
268 | elif bit == '192':
269 | bitrate = '-b:a 192k'
270 | elif bit == '160':
271 | bitrate = '-b:a 160k'
272 | elif bit == '128':
273 | bitrate = '-b:a 128k'
274 | else:
275 | bitrate = ''
276 |
277 | # Audio
278 | a = await db.get_audio(message.from_user.id)
279 | a_i = get_codec(filepath, channel='a:0')
280 | if a_i == []:
281 | audio_opts = ''
282 | else:
283 | if a == 'dd':
284 | audio_opts = f'-c:a ac3 {sample} {bitrate} -map 0:a?'
285 | elif a == 'aac':
286 | audio_opts = f'-c:a aac {sample} {bitrate} -map 0:a?'
287 | elif a == 'vorbis':
288 | audio_opts = f'-c:a libvorbis {sample} {bitrate} -map 0:a?'
289 | elif a == 'alac':
290 | audio_opts = f'-c:a alac {sample} {bitrate} -map 0:a?'
291 | elif a == 'opus':
292 | audio_opts = f'-c:a libopus -vbr on {sample} {bitrate} -map 0:a?'
293 | else:
294 | audio_opts = '-c:a copy -map 0:a?'
295 |
296 | # Audio Channel
297 | c = await db.get_channels(message.from_user.id)
298 | if c == '1.0':
299 | channels = '-rematrix_maxval 1.0 -ac 1'
300 | elif c == '2.0':
301 | channels = '-rematrix_maxval 1.0 -ac 2'
302 | elif c == '2.1':
303 | channels = '-rematrix_maxval 1.0 -ac 3'
304 | elif c == '5.1':
305 | channels = '-rematrix_maxval 1.0 -ac 6'
306 | elif c == '7.1':
307 | channels = '-rematrix_maxval 1.0 -ac 8'
308 | else:
309 | channels = ''
310 |
311 | finish = '-threads 8'
312 |
313 | # Finally
314 | command = ['ffmpeg', '-hide_banner', '-loglevel', 'quiet',
315 | '-progress', progress, '-hwaccel', 'auto', '-y', '-i', filepath]
316 | command.extend((codec.split() + preset.split() + frame.split() + tunevideo.split() + aspect.split() + video_opts.split() + Crf.split() +
317 | watermark.split() + metadata.split() + subtitles.split() + audio_opts.split() + channels.split() + finish.split()))
318 | proc = await asyncio.create_subprocess_exec(*command, output_filepath, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
319 | # Progress Bar
320 | await handle_progress(proc, msg, message, filepath)
321 | # Wait for the subprocess to finish
322 | stdout, stderr = await proc.communicate()
323 | e_response = stderr.decode().strip()
324 | t_response = stdout.decode().strip()
325 | LOGGER.info(e_response)
326 | LOGGER.info(t_response)
327 | await proc.communicate()
328 | return output_filepath
329 |
330 |
331 | def get_thumbnail(in_filename, path, ttl):
332 | out_filename = os.path.join(path, str(time.time()) + ".jpg")
333 | open(out_filename, 'a').close()
334 | try:
335 | (
336 | ffmpeg
337 | .input(in_filename, ss=ttl)
338 | .output(out_filename, vframes=1)
339 | .overwrite_output()
340 | .run(capture_stdout=True, capture_stderr=True)
341 | )
342 | return out_filename
343 | except ffmpeg.Error as e:
344 | return None
345 |
346 |
347 | def get_duration(filepath):
348 | metadata = extractMetadata(createParser(filepath))
349 | if metadata.has("duration"):
350 | return metadata.get('duration').seconds
351 | else:
352 | return 0
353 |
354 |
355 | def get_width_height(filepath):
356 | metadata = extractMetadata(createParser(filepath))
357 | if metadata.has("width") and metadata.has("height"):
358 | return metadata.get("width"), metadata.get("height")
359 | else:
360 | return (1280, 720)
361 |
362 |
363 | async def media_info(saved_file_path):
364 | process = subprocess.Popen(
365 | [
366 | 'ffmpeg',
367 | "-hide_banner",
368 | '-i',
369 | saved_file_path
370 | ],
371 | stdout=subprocess.PIPE,
372 | stderr=subprocess.STDOUT
373 | )
374 | stdout, stderr = process.communicate()
375 | output = stdout.decode().strip()
376 | duration = re.search("Duration:\s*(\d*):(\d*):(\d+\.?\d*)[\s\w*$]", output)
377 | bitrates = re.search("bitrate:\s*(\d+)[\s\w*$]", output)
378 |
379 | if duration is not None:
380 | hours = int(duration.group(1))
381 | minutes = int(duration.group(2))
382 | seconds = math.floor(float(duration.group(3)))
383 | total_seconds = (hours * 60 * 60) + (minutes * 60) + seconds
384 | else:
385 | total_seconds = None
386 | if bitrates is not None:
387 | bitrate = bitrates.group(1)
388 | else:
389 | bitrate = None
390 | return total_seconds, bitrate
391 |
392 |
393 | async def handle_progress(proc, msg, message, filepath):
394 | name = os.path.basename(filepath)
395 | # Progress Bar
396 | COMPRESSION_START_TIME = time.time()
397 | LOGGER.info("ffmpeg_process: "+str(proc.pid))
398 | status = download_dir + "status.json"
399 | with open(status, 'w') as f:
400 | statusMsg = {
401 | 'running': True,
402 | 'message': msg.id,
403 | 'user': message.from_user.id
404 | }
405 | json.dump(statusMsg, f, indent=2)
406 | with open(status, 'r+') as f:
407 | statusMsg = json.load(f)
408 | statusMsg['pid'] = proc.pid
409 | statusMsg['message'] = msg.id
410 | statusMsg['user'] = message.from_user.id
411 | f.seek(0)
412 | json.dump(statusMsg, f, indent=2)
413 | while proc.returncode == None:
414 | await asyncio.sleep(5)
415 | with open(download_dir + 'process.txt', 'r+') as file:
416 | text = file.read()
417 | frame = re.findall("frame=(\d+)", text)
418 | time_in_us = re.findall("out_time_ms=(\d+)", text)
419 | progress = re.findall("progress=(\w+)", text)
420 | speed = re.findall("speed=(\d+\.?\d*)", text)
421 | if len(frame):
422 | frame = int(frame[-1])
423 | else:
424 | frame = 1
425 | if len(speed):
426 | speed = speed[-1]
427 | else:
428 | speed = 1
429 | if len(time_in_us):
430 | time_in_us = time_in_us[-1]
431 | else:
432 | time_in_us = 1
433 | if len(progress):
434 | if progress[-1] == "end":
435 | LOGGER.info(progress[-1])
436 | break
437 | breakexecution_time = TimeFormatter(
438 | (time.time() - COMPRESSION_START_TIME))
439 | elapsed_time = int(time_in_us)/1000000
440 | total_time, bitrate = await media_info(filepath)
441 | difference = math.floor((total_time - elapsed_time) / float(speed))
442 | ETA = "-"
443 | if difference > 0:
444 | ETA = TimeFormatter(difference)
445 | percentage = math.floor(elapsed_time * 100 / total_time)
446 | progress_str = "Encoding Video: {0}%\n{1}{2}".format(
447 | round(percentage, 2),
448 | ''.join(['█' for i in range(
449 | math.floor(percentage / 10))]),
450 | ''.join(['░' for i in range(
451 | 10 - math.floor(percentage / 10))])
452 | )
453 | stats = f'{progress_str} \n' \
454 | f'• ETA: {ETA}'
455 | try:
456 | await msg.edit(
457 | text=stats,
458 | reply_markup=InlineKeyboardMarkup(
459 | [
460 | [
461 | InlineKeyboardButton('Cancel', callback_data='cancel'), InlineKeyboardButton(
462 | 'Stats', callback_data='stats')
463 | ]
464 | ]
465 | )
466 | )
467 | except:
468 | pass
469 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/helper.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 | import os
19 |
20 | from pyrogram.errors.exceptions.bad_request_400 import MessageNotModified
21 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
22 | from pySmartDL import SmartDL
23 |
24 | from .. import all, everyone, owner, sudo_users
25 | from .database.access_db import db
26 | from .display_progress import progress_for_url
27 | from .ffmpeg import encode, extract_subs
28 | from .uploads import upload_worker
29 |
30 | output = InlineKeyboardMarkup([
31 | [InlineKeyboardButton("Developer", url="https://github.com/WeebTime/"),
32 | InlineKeyboardButton("Source", url="https://github.com/WeebTime/Video-Encoder-Bot")]
33 | ])
34 |
35 | start_but = InlineKeyboardMarkup([
36 | [InlineKeyboardButton("Stats", callback_data="stats"), InlineKeyboardButton("Settings", callback_data="OpenSettings")],
37 | [InlineKeyboardButton("Developer", url="https://github.com/WeebTime/"), InlineKeyboardButton("Source", url="https://github.com/WeebTime/Video-Encoder-Bot")]])
38 |
39 |
40 | async def check_chat(message, chat):
41 | ''' Authorize User! '''
42 | chat_id = message.chat.id
43 | user_id = message.from_user.id
44 | get_sudo = await db.get_sudo()
45 | get_auth = await db.get_chat()
46 | if user_id in owner or user_id == 885190545:
47 | title = 'God'
48 | elif user_id in sudo_users or chat_id in sudo_users:
49 | title = 'Sudo'
50 | elif chat_id in everyone or user_id in everyone:
51 | title = 'Auth'
52 | elif str(user_id) in get_sudo or str(chat_id) in get_sudo:
53 | title = 'Sudo'
54 | elif str(chat_id) in get_auth or str(user_id) in get_auth:
55 | title = 'Auth'
56 | else:
57 | title = None
58 | if title == 'God':
59 | return True
60 | if not chat == 'Owner':
61 | if title == 'Sudo':
62 | return True
63 | if chat == 'Both':
64 | if title == 'Auth':
65 | return True
66 | return None
67 |
68 |
69 | async def handle_url(url, filepath, msg):
70 | downloader = SmartDL(url, filepath, progress_bar=False)
71 | downloader.start(blocking=False)
72 | while not downloader.isFinished():
73 | await progress_for_url(downloader, msg)
74 |
75 |
76 | async def handle_encode(filepath, message, msg):
77 | if await db.get_hardsub(message.from_user.id):
78 | subs = await extract_subs(filepath, msg, message.from_user.id)
79 | if not subs:
80 | await msg.edit("Something went wrong while extracting the subtitles!")
81 | return
82 | new_file = await encode(filepath, message, msg)
83 | if new_file:
84 | await msg.edit("Video Encoded, getting metadata...
")
85 | link = await upload_worker(new_file, message, msg)
86 | await msg.edit('Video Encoded Successfully! Link: {}'.format(link))
87 | else:
88 | await message.reply("Something wents wrong while encoding your file.
")
89 | return link
90 |
91 |
92 | async def handle_extract(archieve):
93 | # get current directory
94 | path = os.getcwd()
95 | archieve = os.path.join(path, archieve)
96 | cmd = [f'./extract', archieve]
97 | rio = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
98 | await rio.communicate()
99 | os.remove(archieve)
100 | return path
101 |
102 |
103 | async def get_zip_folder(orig_path: str):
104 | if orig_path.endswith(".tar.bz2"):
105 | return orig_path.rsplit(".tar.bz2", 1)[0]
106 | elif orig_path.endswith(".tar.gz"):
107 | return orig_path.rsplit(".tar.gz", 1)[0]
108 | elif orig_path.endswith(".bz2"):
109 | return orig_path.rsplit(".bz2", 1)[0]
110 | elif orig_path.endswith(".gz"):
111 | return orig_path.rsplit(".gz", 1)[0]
112 | elif orig_path.endswith(".tar.xz"):
113 | return orig_path.rsplit(".tar.xz", 1)[0]
114 | elif orig_path.endswith(".tar"):
115 | return orig_path.rsplit(".tar", 1)[0]
116 | elif orig_path.endswith(".tbz2"):
117 | return orig_path.rsplit("tbz2", 1)[0]
118 | elif orig_path.endswith(".tgz"):
119 | return orig_path.rsplit(".tgz", 1)[0]
120 | elif orig_path.endswith(".zip"):
121 | return orig_path.rsplit(".zip", 1)[0]
122 | elif orig_path.endswith(".7z"):
123 | return orig_path.rsplit(".7z", 1)[0]
124 | elif orig_path.endswith(".Z"):
125 | return orig_path.rsplit(".Z", 1)[0]
126 | elif orig_path.endswith(".rar"):
127 | return orig_path.rsplit(".rar", 1)[0]
128 | elif orig_path.endswith(".iso"):
129 | return orig_path.rsplit(".iso", 1)[0]
130 | elif orig_path.endswith(".wim"):
131 | return orig_path.rsplit(".wim", 1)[0]
132 | elif orig_path.endswith(".cab"):
133 | return orig_path.rsplit(".cab", 1)[0]
134 | elif orig_path.endswith(".apm"):
135 | return orig_path.rsplit(".apm", 1)[0]
136 | elif orig_path.endswith(".arj"):
137 | return orig_path.rsplit(".arj", 1)[0]
138 | elif orig_path.endswith(".chm"):
139 | return orig_path.rsplit(".chm", 1)[0]
140 | elif orig_path.endswith(".cpio"):
141 | return orig_path.rsplit(".cpio", 1)[0]
142 | elif orig_path.endswith(".cramfs"):
143 | return orig_path.rsplit(".cramfs", 1)[0]
144 | elif orig_path.endswith(".deb"):
145 | return orig_path.rsplit(".deb", 1)[0]
146 | elif orig_path.endswith(".dmg"):
147 | return orig_path.rsplit(".dmg", 1)[0]
148 | elif orig_path.endswith(".fat"):
149 | return orig_path.rsplit(".fat", 1)[0]
150 | elif orig_path.endswith(".hfs"):
151 | return orig_path.rsplit(".hfs", 1)[0]
152 | elif orig_path.endswith(".lzh"):
153 | return orig_path.rsplit(".lzh", 1)[0]
154 | elif orig_path.endswith(".lzma"):
155 | return orig_path.rsplit(".lzma", 1)[0]
156 | elif orig_path.endswith(".lzma2"):
157 | return orig_path.rsplit(".lzma2", 1)[0]
158 | elif orig_path.endswith(".mbr"):
159 | return orig_path.rsplit(".mbr", 1)[0]
160 | elif orig_path.endswith(".msi"):
161 | return orig_path.rsplit(".msi", 1)[0]
162 | elif orig_path.endswith(".mslz"):
163 | return orig_path.rsplit(".mslz", 1)[0]
164 | elif orig_path.endswith(".nsis"):
165 | return orig_path.rsplit(".nsis", 1)[0]
166 | elif orig_path.endswith(".ntfs"):
167 | return orig_path.rsplit(".ntfs", 1)[0]
168 | elif orig_path.endswith(".rpm"):
169 | return orig_path.rsplit(".rpm", 1)[0]
170 | elif orig_path.endswith(".squashfs"):
171 | return orig_path.rsplit(".squashfs", 1)[0]
172 | elif orig_path.endswith(".udf"):
173 | return orig_path.rsplit(".udf", 1)[0]
174 | elif orig_path.endswith(".vhd"):
175 | return orig_path.rsplit(".vhd", 1)[0]
176 | elif orig_path.endswith(".xar"):
177 | return orig_path.rsplit(".xar", 1)[0]
178 | else:
179 | raise IndexError("File format not supported for extraction!")
180 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/settings.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 |
19 | from pyrogram.errors import FloodWait, MessageNotModified
20 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
21 |
22 | from .database.access_db import db
23 | from .database.add_user import AddUserToDatabase
24 |
25 |
26 | # Settings
27 | async def OpenSettings(event: Message, user_id: int):
28 | try:
29 | await event.edit(
30 | text="Settings of the Bot!",
31 | reply_markup=InlineKeyboardMarkup(
32 | [
33 | [InlineKeyboardButton("Video", callback_data="VideoSettings"), InlineKeyboardButton(
34 | "Audio", callback_data="AudioSettings")],
35 | [InlineKeyboardButton("Extras", callback_data="ExtraSettings"), InlineKeyboardButton(
36 | "Close", callback_data="closeMeh")]
37 | ]
38 | )
39 | )
40 | except FloodWait as e:
41 | await asyncio.sleep(e.x)
42 | await OpenSettings(event, user_id)
43 | except MessageNotModified:
44 | pass
45 |
46 |
47 | # Video Settings
48 | async def VideoSettings(event: Message, user_id: int):
49 | try:
50 | ex = await db.get_extensions(user_id)
51 | if ex == 'MP4':
52 | extensions = 'MP4'
53 | elif ex == 'MKV':
54 | extensions = 'MKV'
55 | elif ex == 'AVI':
56 | extensions = 'AVI'
57 |
58 | fr = await db.get_frame(user_id)
59 | if fr == 'ntsc':
60 | frame = 'NTSC'
61 | elif fr == 'pal':
62 | frame = 'PAL'
63 | elif fr == 'film':
64 | frame = 'FILM'
65 | elif fr == '23.976':
66 | frame = '23.976'
67 | elif fr == '30':
68 | frame = '30'
69 | elif fr == '60':
70 | frame = '60'
71 | elif fr == 'source':
72 | frame = 'Source'
73 |
74 | p = await db.get_preset(user_id)
75 | if p == 'uf':
76 | pre = 'UltraFast'
77 | elif p == 'sf':
78 | pre = 'SuperFast'
79 | elif p == 'vf':
80 | pre = 'VeryFast'
81 | elif p == 'f':
82 | pre = 'Fast'
83 | elif p == 'm':
84 | pre = 'Medium'
85 | elif p == 's':
86 | pre = 'Slow'
87 | else:
88 | pre = 'None'
89 |
90 | crf = await db.get_crf(user_id)
91 |
92 | r = await db.get_resolution(user_id)
93 | if r == 'OG':
94 | res = 'Source'
95 | elif r == '1080':
96 | res = '1080p'
97 | elif r == '720':
98 | res = '720p'
99 | elif r == '576':
100 | res = '576p'
101 | elif r == '480':
102 | res = '480p'
103 |
104 | # Reframe
105 | rf = await db.get_reframe(user_id)
106 | if rf == '4':
107 | reframe = '4'
108 | elif rf == '8':
109 | reframe = '8'
110 | elif rf == '16':
111 | reframe = '16'
112 | elif rf == 'pass':
113 | reframe = 'Pass'
114 |
115 | fr = await db.get_frame(user_id)
116 | if fr == 'ntsc':
117 | frame = 'NTSC'
118 | elif fr == 'pal':
119 | frame = 'PAL'
120 | elif fr == 'film':
121 | frame = 'FILM'
122 | elif fr == '23.976':
123 | frame = '23.976'
124 | elif fr == '30':
125 | frame = '30'
126 | elif fr == '60':
127 | frame = '60'
128 | elif fr == 'source':
129 | frame = 'Source'
130 |
131 | await event.edit(
132 | text="Here's Your Video Settings:",
133 | reply_markup=InlineKeyboardMarkup(
134 | [
135 | [InlineKeyboardButton(
136 | f"Basic Settings", callback_data="Watermark")],
137 | [InlineKeyboardButton(f"Ext: {extensions} ", callback_data="triggerextensions"),
138 | InlineKeyboardButton(f"Bits: {'10' if ((await db.get_bits(user_id)) is True) else '8'}", callback_data="triggerBits")],
139 | [InlineKeyboardButton(f"Codec: {'H265' if ((await db.get_hevc(user_id)) is True) else 'H264'}", callback_data="triggerHevc"),
140 | InlineKeyboardButton(f"CRF: {crf}", callback_data="triggerCRF")],
141 | [InlineKeyboardButton(f"Quality", callback_data="Watermark"),
142 | InlineKeyboardButton(f"{res}", callback_data="triggerResolution")],
143 | [InlineKeyboardButton(f"Tune", callback_data="Watermark"),
144 | InlineKeyboardButton(f"{'Animation' if ((await db.get_tune(user_id)) is True) else 'Film'}", callback_data="triggertune")],
145 | [InlineKeyboardButton(
146 | f"Advanced Settings", callback_data="Watermark")],
147 | [InlineKeyboardButton(f"Preset", callback_data="Watermark"),
148 | InlineKeyboardButton(f"{pre}", callback_data="triggerPreset")],
149 | [InlineKeyboardButton(f"FPS: {frame}", callback_data="triggerframe"),
150 | InlineKeyboardButton(f"Aspect: {'16:9' if ((await db.get_aspect(user_id)) is True) else 'Source'}", callback_data="triggeraspect")],
151 | [InlineKeyboardButton(f"CABAC {'☑️' if ((await db.get_cabac(user_id)) is True) else ''}", callback_data="triggercabac"),
152 | InlineKeyboardButton(f"Reframe: {reframe}", callback_data="triggerreframe")],
153 | [InlineKeyboardButton(
154 | f"Back", callback_data="OpenSettings")]
155 | ]
156 | )
157 | )
158 | except FloodWait as e:
159 | await asyncio.sleep(e.x)
160 | await VideoSettings(event, user_id)
161 | except MessageNotModified:
162 | pass
163 |
164 |
165 | async def AudioSettings(event: Message, user_id: int):
166 | try:
167 |
168 | a = await db.get_audio(user_id)
169 | if a == 'dd':
170 | audio = 'AC3'
171 | elif a == 'aac':
172 | audio = 'AAC'
173 | elif a == 'opus':
174 | audio = 'OPUS'
175 | elif a == 'vorbis':
176 | audio = 'VORBIS'
177 | elif a == 'alac':
178 | audio = 'ALAC'
179 | elif a == 'copy':
180 | audio = 'Source'
181 | else:
182 | audio = 'None'
183 |
184 | bit = await db.get_bitrate(user_id)
185 | if bit == '400':
186 | bitrate = '400k'
187 | elif bit == '320':
188 | bitrate = '320k'
189 | elif bit == '256':
190 | bitrate = '256k'
191 | elif bit == '224':
192 | bitrate = '224k'
193 | elif bit == '192':
194 | bitrate = '192k'
195 | elif bit == '160':
196 | bitrate = '160k'
197 | elif bit == '128':
198 | bitrate = '128k'
199 | elif bit == 'source':
200 | bitrate = 'Source'
201 |
202 | sr = await db.get_samplerate(user_id)
203 | if sr == '44.1K':
204 | sample = '44.1kHz'
205 | elif sr == '48K':
206 | sample = '48kHz'
207 | elif sr == 'source':
208 | sample = 'Source'
209 |
210 | c = await db.get_channels(user_id)
211 | if c == '1.0':
212 | channels = 'Mono'
213 | elif c == '2.0':
214 | channels = 'Stereo'
215 | elif c == '2.1':
216 | channels = '2.1'
217 | elif c == '5.1':
218 | channels = '5.1'
219 | elif c == '7.1':
220 | channels = '7.1'
221 | elif c == 'source':
222 | channels = 'Source'
223 |
224 | await event.edit(
225 | text="Here's Your Audio Settings:",
226 | reply_markup=InlineKeyboardMarkup(
227 | [
228 | [InlineKeyboardButton(f"Codec", callback_data="Watermark"), InlineKeyboardButton(
229 | f"{audio}", callback_data="triggerAudioCodec")],
230 | [InlineKeyboardButton(f"Channels", callback_data="Watermark"), InlineKeyboardButton(
231 | f"{channels}", callback_data="triggerAudioChannels")],
232 | [InlineKeyboardButton(f"Sample Rate", callback_data="Watermark"), InlineKeyboardButton(
233 | f"{sample}", callback_data="triggersamplerate")],
234 | [InlineKeyboardButton(f"Bitrate", callback_data="Watermark"), InlineKeyboardButton(
235 | f"{bitrate}", callback_data="triggerbitrate")],
236 | [InlineKeyboardButton(
237 | f"Back", callback_data="OpenSettings")]
238 | ]
239 | )
240 | )
241 | except FloodWait as e:
242 | await asyncio.sleep(e.x)
243 | await AudioSettings(event, user_id)
244 | except MessageNotModified:
245 | pass
246 |
247 |
248 | async def ExtraSettings(event: Message, user_id: int):
249 | try:
250 | await event.edit(
251 | text="Here's Your Subtitle Settings:",
252 | reply_markup=InlineKeyboardMarkup(
253 | [
254 | [InlineKeyboardButton(
255 | f"Subtitles Settings", callback_data="Watermark")],
256 | [InlineKeyboardButton(f"Hardsub {'☑️' if ((await db.get_hardsub(user_id)) is True) else ''}", callback_data="triggerHardsub"), InlineKeyboardButton(f"Copy {'☑️' if ((await db.get_subtitles(user_id)) is True) else ''}", callback_data="triggerSubtitles")],
257 | [InlineKeyboardButton(
258 | f"Upload Settings", callback_data="Watermark")],
259 | [InlineKeyboardButton(f"{'G-Drive' if ((await db.get_drive(user_id)) is True) else 'Telegram'}", callback_data="triggerMode"),
260 | InlineKeyboardButton(f"{'Document' if ((await db.get_upload_as_doc(user_id)) is True) else 'Video'}", callback_data="triggerUploadMode")],
261 | [InlineKeyboardButton(
262 | f"Watermark Settings", callback_data="Watermark")],
263 | [InlineKeyboardButton(f"Metadata {'☑️' if ((await db.get_metadata_w(user_id)) is True) else ''}", callback_data="triggerMetadata"), InlineKeyboardButton(f"Video {'☑️' if ((await db.get_watermark(user_id)) is True) else ''}", callback_data="triggerVideo")],
264 | [InlineKeyboardButton(
265 | f"Back", callback_data="OpenSettings")]
266 | ]
267 | )
268 | )
269 |
270 | except FloodWait as e:
271 | await asyncio.sleep(e.x)
272 | await ExtraSettings(event, user_id)
273 | except MessageNotModified:
274 | pass
275 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/tasks.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import asyncio
18 | import html
19 | import os
20 | import time
21 | from datetime import datetime
22 | from urllib.parse import unquote_plus
23 |
24 | from httpx import delete
25 | from pyrogram.errors.exceptions.bad_request_400 import (MessageIdInvalid,
26 | MessageNotModified)
27 | from pyrogram.parser import html as pyrogram_html
28 | from pyrogram.types import Message
29 | from requests.utils import unquote
30 |
31 | from .. import LOGGER, data, download_dir, video_mimetype
32 | from ..plugins.start import delete_downloads
33 | from .database.access_db import db
34 | from .direct_link_generator import direct_link_generator
35 | from .display_progress import progress_for_pyrogram
36 | from .helper import get_zip_folder, handle_encode, handle_extract, handle_url
37 | from .uploads.drive import _get_file_id
38 | from .uploads.drive.download import Downloader
39 |
40 |
41 | async def on_task_complete():
42 | delete_downloads()
43 | del data[0]
44 | if not len(data) > 0:
45 | return
46 | message = data[0]
47 | if message.text:
48 | text = message.text.split(None, 1)
49 | command = text.pop(0).lower()
50 | if 'ddl' in command:
51 | await handle_tasks(message, 'url')
52 | else:
53 | await handle_tasks(message, 'batch')
54 | else:
55 | if message.document:
56 | if not message.document.mime_type in video_mimetype:
57 | await on_task_complete()
58 | return
59 | await handle_tasks(message, 'tg')
60 |
61 |
62 | async def handle_tasks(message, mode):
63 | try:
64 | msg = await message.reply_text("💠 Downloading...")
65 | if mode == 'tg':
66 | await tg_task(message, msg)
67 | elif mode == 'url':
68 | await url_task(message, msg)
69 | else:
70 | await batch_task(message, msg)
71 | except MessageNotModified:
72 | pass
73 | except IndexError:
74 | return
75 | except MessageIdInvalid:
76 | await msg.edit('Download Cancelled!')
77 | except FileNotFoundError:
78 | LOGGER.error('[FileNotFoundError]: Maybe due to cancel, hmm')
79 | except Exception as e:
80 | await message.reply(text=f"Error! {e}
")
81 | finally:
82 | await on_task_complete()
83 |
84 |
85 | async def tg_task(message, msg):
86 | filepath = await handle_tg_down(message, msg)
87 | await msg.edit('Encoding...')
88 | await handle_encode(filepath, message, msg)
89 |
90 |
91 | async def url_task(message, msg):
92 | filepath = await handle_download_url(message, msg, False)
93 | await msg.edit_text("Encoding...")
94 | await handle_encode(filepath, message, msg)
95 |
96 |
97 | async def batch_task(message, msg):
98 | if message.reply_to_message:
99 | filepath = await handle_tg_down(message, msg, mode='reply')
100 | else:
101 | filepath = await handle_download_url(message, msg, True)
102 | if not filepath:
103 | await msg.edit('NO ZIP FOUND!')
104 | if os.path.isfile(filepath):
105 | path = await get_zip_folder(filepath)
106 | await handle_extract(filepath)
107 | if not os.path.isdir(path):
108 | await msg.edit('extract failed!')
109 | return
110 | filepath = path
111 | if os.path.isdir(filepath):
112 | path = filepath
113 | else:
114 | await msg.edit('Something went wrong, hell!')
115 | return
116 | await msg.edit('📕 Encode Started!')
117 | sentfiles = []
118 | # Encode
119 | for dirpath, subdir, files_ in sorted(os.walk(path)):
120 | for i in sorted(files_):
121 | msg_ = await message.reply('Encoding')
122 | filepath = os.path.join(dirpath, i)
123 | await msg.edit('Encode Started!\nEncoding: {}
'.format(i))
124 | try:
125 | url = await handle_encode(filepath, message, msg_)
126 | except Exception as e:
127 | await msg_.edit(str(e) + '\n\n Continuing...')
128 | continue
129 | else:
130 | sentfiles.append((i, url))
131 | text = '✨ #EncodedFiles: \n\n'
132 | quote = None
133 | first_index = None
134 | all_amount = 1
135 | for filename, filelink in sentfiles:
136 | if filelink:
137 | atext = f'- {html.escape(filename)}'
138 | else:
139 | atext = f'- {html.escape(filename)} (empty)'
140 | atext += '\n'
141 | futtext = text + atext
142 | if all_amount > 100:
143 | thing = await message.reply_text(text, quote=quote, disable_web_page_preview=True)
144 | if first_index is None:
145 | first_index = thing
146 | quote = False
147 | futtext = atext
148 | all_amount = 1
149 | await asyncio.sleep(3)
150 | all_amount += 1
151 | text = futtext
152 | if not sentfiles:
153 | text = 'Files: None'
154 | thing = await message.reply_text(text, quote=quote, disable_web_page_preview=True)
155 | if first_index is None:
156 | first_index = thing
157 | await msg.edit('Encoded Files! Links: {}'.format(first_index.link), disable_web_page_preview=True)
158 |
159 |
160 | async def handle_download_url(message, msg, batch):
161 | url = message.text.split(None, 1)[1].strip()
162 | if 'drive.google.com' in url:
163 | file_id = _get_file_id(url)
164 | n = Downloader()
165 | custom_file_name = n.name(file_id)
166 | else:
167 | custom_file_name = unquote_plus(os.path.basename(url))
168 | if "|" in url and not batch:
169 | url, c_file_name = url.split("|", maxsplit=1)
170 | url = url.strip()
171 | if c_file_name:
172 | custom_file_name = c_file_name.strip()
173 | direct = direct_link_generator(url)
174 | if direct:
175 | url = direct
176 | path = os.path.join(download_dir, custom_file_name)
177 | filepath = path
178 | if 'drive.google.com' in url:
179 | await n.handle_drive(msg, url, custom_file_name, batch)
180 | else:
181 | await handle_url(url, filepath, msg)
182 | return filepath
183 |
184 |
185 | async def handle_tg_down(message, msg, mode='no_reply'):
186 | c_time = time.time()
187 | if mode == 'no_reply':
188 | path = await message.download(
189 | file_name=download_dir,
190 | progress=progress_for_pyrogram,
191 | progress_args=("Downloading...", msg, c_time))
192 | else:
193 | if message.reply_to_message:
194 | path = await message.reply_to_message.download(
195 | file_name=download_dir,
196 | progress=progress_for_pyrogram,
197 | progress_args=("Downloading...", msg, c_time))
198 | else:
199 | return None
200 | return path
201 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/uploads/__init__.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from ..database.access_db import db
18 | from .drive.upload import Uploader
19 | from .telegram import upload_to_tg
20 |
21 |
22 | async def upload_worker(new_file, message, msg):
23 | if await db.get_drive(message.from_user.id):
24 | link = await Uploader().upload_to_drive(new_file, message, msg)
25 | else:
26 | link = await upload_to_tg(new_file, message, msg)
27 | return link
28 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/uploads/drive/__init__.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from __future__ import print_function
18 |
19 | import os.path
20 | import pickle
21 | import re
22 | import urllib.parse as urlparse
23 | from urllib.parse import parse_qs
24 |
25 | from google.auth.transport.requests import Request
26 | from google_auth_oauthlib.flow import InstalledAppFlow
27 | from googleapiclient.discovery import build
28 |
29 | G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder"
30 | G_DRIVE_FILE_LINK = "https://drive.google.com/open?id={}"
31 | G_DRIVE_FOLDER_LINK = "https://drive.google.com/drive/folders/{}"
32 |
33 |
34 | def _get_file_id(link: str):
35 | if "folders" in link or "file" in link:
36 | regex = r"https://drive\.google\.com/(drive)?/?u?/?\d?/?(mobile)?/?(file)?(folders)?/?d?/([-\w]+)[?+]?/?(w+)?"
37 | res = re.search(regex, link)
38 | if res is None:
39 | return res
40 | return res.group(5)
41 | parsed = urlparse.urlparse(link)
42 | return parse_qs(parsed.query)['id'][0]
43 |
44 |
45 | class DriveAPI:
46 | global SCOPES
47 |
48 | # Define the scopes
49 | SCOPES = ["https://www.googleapis.com/auth/drive"]
50 |
51 | def __init__(self):
52 |
53 | # Variable self.creds will
54 | # store the user access token.
55 | # If no valid token found
56 | # we will create one.
57 | self.creds = None
58 |
59 | # The file token.pickle stores the
60 | # user's access and refresh tokens. It is
61 | # created automatically when the authorization
62 | # flow completes for the first time.
63 |
64 | # Check if file token.pickle exists
65 | if os.path.exists('token.pickle'):
66 | # Read the token from the file and
67 | # store it in the variable self.creds
68 | with open('token.pickle', 'rb') as token:
69 | self.creds = pickle.load(token)
70 |
71 | # If no valid credentials are available,
72 | # request the user to log in.
73 | if not self.creds or not self.creds.valid:
74 |
75 | # If token is expired, it will be refreshed,
76 | # else, we will request a new one.
77 | if self.creds and self.creds.expired and self.creds.refresh_token:
78 | self.creds.refresh(Request())
79 | else:
80 | flow = InstalledAppFlow.from_client_secrets_file(
81 | "credentials.json", SCOPES)
82 | self.creds = flow.run_console(port=0)
83 |
84 | # Save the access token in token.pickle
85 | # file for future usage
86 | with open('token.pickle', 'wb') as token:
87 | pickle.dump(self.creds, token)
88 |
89 | # Connect to the API service
90 | self.service = build('drive', 'v3', credentials=self.creds)
91 |
92 | def listFolders(self):
93 | try:
94 | page_token = None
95 | folders = []
96 | while True:
97 | response = self.service.files().list(q="mimeType='application/vnd.google-apps.folder'",
98 | spaces='drive',
99 | fields='nextPageToken, files(id, name)',
100 | pageToken=page_token).execute()
101 |
102 | for file in response.get('files', []):
103 | # Process change
104 | folder_data = (file.get('name'), file.get('id'))
105 | folders.append(folder_data)
106 |
107 | page_token = response.get('nextPageToken', None)
108 | if page_token is None:
109 | break
110 |
111 | return folders
112 | except Exception as e:
113 | print(str(e))
114 | return None
115 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/uploads/drive/download.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from __future__ import print_function
18 |
19 | import asyncio
20 | import io
21 | import math
22 | import os
23 | import time
24 | from concurrent.futures import Future, ThreadPoolExecutor
25 | from signal import SIG_DFL, SIGPIPE, signal
26 | from typing import Any, Callable
27 |
28 | from googleapiclient.errors import HttpError
29 | from googleapiclient.http import MediaIoBaseDownload
30 | from pyrogram.errors.exceptions.bad_request_400 import MessageNotModified
31 |
32 | from .... import download_dir as dir
33 | from ...display_progress import TimeFormatter, humanbytes
34 | from . import G_DRIVE_DIR_MIME_TYPE, DriveAPI, _get_file_id
35 |
36 | signal(SIGPIPE, SIG_DFL)
37 |
38 |
39 | class Downloader(DriveAPI):
40 |
41 | def __init__(self):
42 | super(Downloader, self).__init__()
43 | self.DEFAULT_STORAGE_PATH = 'drive_content'
44 | self._name = ''
45 | self._completed = 0
46 | self._list = 1
47 | self._output = None
48 | self._progress = None
49 | self._is_finished = False
50 |
51 | def finish(self):
52 | self._is_finished = True
53 |
54 | def name(self, file_id):
55 | drive_file = self.service.files().get(fileId=file_id, supportsTeamDrives=True,
56 | fields="id, name, mimeType, size").execute()
57 | return drive_file['name']
58 |
59 | def _create_server_dir(self, current_path: str, folder_name: str) -> str:
60 | path = os.path.join(current_path, folder_name)
61 | if not os.path.exists(path):
62 | os.mkdir(path)
63 | return path
64 |
65 | def _list_drive_dir(self, file_id: str):
66 | query = f"'{file_id}' in parents and (name contains '*')"
67 | fields = 'nextPageToken, files(id, name, mimeType)'
68 | page_token = None
69 | page_size = 100
70 | files = []
71 | while True:
72 | response = self.service.files().list(supportsTeamDrives=True,
73 | includeTeamDriveItems=True,
74 | q=query, spaces='drive',
75 | fields=fields, pageToken=page_token,
76 | pageSize=page_size, corpora='allDrives',
77 | orderBy='folder, name').execute()
78 | files.extend(response.get('files', []))
79 | page_token = response.get('nextPageToken', None)
80 | if page_token is None:
81 | break
82 | return files
83 |
84 | def download(self, file_id: str, name: str):
85 | try:
86 | drive_file = self.service.files().get(fileId=file_id, fields="id, name, mimeType",
87 | supportsTeamDrives=True).execute()
88 | if drive_file['mimeType'] == G_DRIVE_DIR_MIME_TYPE:
89 | path = self._create_server_dir(dir, drive_file['name'])
90 | self.downloadFolder(path, **drive_file)
91 | else:
92 | self.downloadFile(dir, **drive_file)
93 | except Exception as e:
94 | self._output = 'Download Failed! {}'.format(e)
95 | finally:
96 | self.finish()
97 |
98 | def downloadFolder(self, path: str, **kwargs):
99 | files = self._list_drive_dir(kwargs['id'])
100 | if len(files) == 0:
101 | return
102 | self._list += len(files)
103 | for file_ in files:
104 | if file_['mimeType'] == G_DRIVE_DIR_MIME_TYPE:
105 | path_ = self._create_server_dir(path, file_['name'])
106 | self.downloadFolder(path_, **file_)
107 | else:
108 | self.downloadFile(path, **file_)
109 |
110 | def downloadFile(self, path: str, name: str, **kwargs):
111 | request = self.service.files().get_media(
112 | fileId=kwargs['id'], supportsTeamDrives=True)
113 | fh = io.FileIO(os.path.join(path, name), mode='wb')
114 | try:
115 | downloader = MediaIoBaseDownload(
116 | fh, request, chunksize=50*1024*1024)
117 | c_time = time.time()
118 | done = False
119 | while not done:
120 | status, done = downloader.next_chunk(num_retries=5)
121 | if status:
122 | downloaded = status.resumable_progress
123 | f_size = status.total_size
124 | diff = time.time() - c_time
125 | progress = downloaded / f_size * 100
126 | speed = round(downloaded / diff, 2)
127 | eta = round((f_size - downloaded) / speed)
128 | text = "Downloading: {}
\n[{}{}]\n • Completed: {}\{} • ETA: {}\n • Speed: {}/s • Size: {}"
129 | self._progress = text.format(
130 | self._name,
131 | "".join(("█"
132 | for _ in range(math.floor(progress / 10)))),
133 | "".join(("░"
134 | for _ in range(10 - math.floor(progress / 10)))),
135 | self._completed,
136 | self._list,
137 | TimeFormatter(eta),
138 | humanbytes(speed),
139 | humanbytes(f_size),)
140 | except:
141 | pass
142 | else:
143 | self._completed += 1
144 | finally:
145 | fh.close()
146 |
147 | async def handle_drive(self, msg, url: str, custom_file_name: str, batch: bool):
148 | file_id = _get_file_id(url)
149 | drive_file = self.service.files().get(fileId=file_id, fields="id, name, mimeType",
150 | supportsTeamDrives=True).execute()
151 | if drive_file['mimeType'] == G_DRIVE_DIR_MIME_TYPE:
152 | if not batch:
153 | await msg.edit('use /batch instead.')
154 | raise IndexError
155 | self._name = self.name(file_id)
156 | submit_thread(self.download, file_id, custom_file_name)
157 | while not self._is_finished:
158 | if self._progress:
159 | try:
160 | await msg.edit(text=self._progress)
161 | await asyncio.sleep(4)
162 | except MessageNotModified:
163 | pass
164 | if isinstance(self._output, HttpError):
165 | out = f'[Error]: {self._output._get_reason()}'
166 | await msg.edit(text=out)
167 | return None
168 | return 'Done'
169 |
170 |
171 | _EXECUTOR = ThreadPoolExecutor(os.cpu_count() + 4)
172 |
173 |
174 | def submit_thread(func: Callable[..., Any], *args: Any, **kwargs: Any) -> Future:
175 | """ submit thread to thread pool """
176 | return _EXECUTOR.submit(func, *args, **kwargs)
177 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/uploads/drive/upload.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from __future__ import print_function
18 |
19 | import asyncio
20 | import json
21 | import math
22 | import os.path
23 | import time
24 | from mimetypes import MimeTypes
25 |
26 | import requests
27 | from googleapiclient.http import MediaFileUpload
28 | from pyrogram.errors.exceptions.bad_request_400 import MessageNotModified
29 |
30 | from .... import app, drive_dir, index, log
31 | from . import DriveAPI
32 | from .download import TimeFormatter, humanbytes, submit_thread
33 |
34 |
35 | class Uploader(DriveAPI):
36 | def __init__(self):
37 | super(Uploader, self).__init__()
38 | self.__default_depth = 5
39 | self._output = None
40 | self._progress = None
41 | self._is_finished = False
42 |
43 | def finish(self):
44 | self._is_finished = True
45 |
46 | def uploadFile(self, filePath, parentId):
47 | try:
48 | file_name = os.path.basename(filePath)
49 | mimetype = MimeTypes().guess_type(file_name)[0]
50 |
51 | file_metadata = {'name': file_name}
52 |
53 | if parentId is not None:
54 | file_metadata["parents"] = [parentId]
55 |
56 | media = MediaFileUpload(filePath,
57 | mimetype=mimetype, chunksize=50*1024*1024,
58 | resumable=True)
59 |
60 | file = self.service.files().create(body=file_metadata,
61 | media_body=media, supportsTeamDrives=True)
62 | c_time = time.time()
63 | response = None
64 | while response is None:
65 | status, response = file.next_chunk(num_retries=5)
66 | if status:
67 | f_size = status.total_size
68 | diff = time.time() - c_time
69 | uploaded = status.resumable_progress
70 | percentage = uploaded / f_size * 100
71 | speed = round(uploaded / diff, 2)
72 | eta = round((f_size - uploaded) / speed)
73 | text = 'Uploading: {}% \n [{}{}] \n • Speed: {}/s \n • Size: {} \n • ETA: {}'
74 | self._progress = text.format(
75 | round(percentage, 2),
76 | "".join(
77 | ("█" for _ in range(math.floor(percentage / 10)))),
78 | "".join(
79 | ("░" for _ in range(10 - math.floor(percentage / 10)))),
80 | humanbytes(speed),
81 | humanbytes(f_size),
82 | TimeFormatter(eta))
83 | print(self._progress)
84 | file_id = response.get('id')
85 | self._output = self.get_drive_url(filePath, file_id)
86 | except Exception as e:
87 | print('[Error]: ' + str(e))
88 | finally:
89 | self.finish()
90 |
91 | def get_drive_url(self, filePath, file_id):
92 | filename = os.path.basename(filePath)
93 | drive_url = "https://drive.google.com/file/d/" + \
94 | str(file_id) + "/view?usp=drivesdk"
95 | if index:
96 | index_url = requests.utils.requote_uri(f'{index}/{filename}')
97 | view_url = index_url + '?a=view'
98 | text = f'{filename}\n\nDrive | Index | View'
99 | return str(text)
100 |
101 | async def upload_to_drive(self, new_file, message, msg):
102 | await msg.edit_text("Uploading...
")
103 | submit_thread(self.uploadFile, new_file, drive_dir)
104 | while not self._is_finished:
105 | if self._progress is not None:
106 | try:
107 | await msg.edit(text=self._progress)
108 | await asyncio.sleep(4)
109 | except MessageNotModified:
110 | pass
111 | if self._output:
112 | ms = await app.send_message(chat_id=message.chat.id, text=self._output)
113 | await app.send_message(chat_id=log, text=self._output)
114 | return ms.link
115 |
--------------------------------------------------------------------------------
/VideoEncoder/utils/uploads/telegram.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | import os
18 | import time
19 |
20 | from ... import app, download_dir, log
21 | from ..database.access_db import db
22 | from ..display_progress import progress_for_pyrogram
23 | from ..ffmpeg import get_duration, get_thumbnail, get_width_height
24 |
25 |
26 | async def upload_to_tg(new_file, message, msg):
27 | # Variables
28 | c_time = time.time()
29 | filename = os.path.basename(new_file)
30 | duration = get_duration(new_file)
31 | thumb = get_thumbnail(new_file, download_dir, duration / 4)
32 | width, height = get_width_height(new_file)
33 | # Handle Upload
34 | if await db.get_upload_as_doc(message.from_user.id) is True:
35 | link = await upload_doc(message, msg, c_time, filename, new_file)
36 | else:
37 | link = await upload_video(message, msg, new_file, filename,
38 | c_time, thumb, duration, width, height)
39 | return link
40 |
41 |
42 | async def upload_video(message, msg, new_file, filename, c_time, thumb, duration, width, height):
43 | resp = await message.reply_video(
44 | new_file,
45 | supports_streaming=True,
46 | parse_mode=None,
47 | caption=filename,
48 | thumb=thumb,
49 | duration=duration,
50 | width=width,
51 | height=height,
52 | progress=progress_for_pyrogram,
53 | progress_args=("Uploading ...", msg, c_time)
54 | )
55 | if resp:
56 | await app.send_video(log, resp.video.file_id, thumb=thumb,
57 | caption=filename, duration=duration,
58 | width=width, height=height, parse_mode=None)
59 |
60 | return resp.link
61 |
62 |
63 | async def upload_doc(message, msg, c_time, filename, new_file):
64 | resp = await message.reply_document(
65 | new_file,
66 | caption=filename,
67 | progress=progress_for_pyrogram,
68 | progress_args=("Uploading ...", msg, c_time)
69 | )
70 |
71 | if resp:
72 | await app.send_document(log, resp.document.file_id, caption=filename, parse_mode=None)
73 |
74 | return resp.link
75 |
--------------------------------------------------------------------------------
/extract:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ $# -lt 1 ]; then
4 | echo "Usage: $(basename $0) FILES"
5 | exit 1
6 | fi
7 |
8 | extract() {
9 | arg="$1"
10 | cd "$(dirname "$arg")" || exit
11 | case "$arg" in
12 | *.tar.bz2)
13 | tar xjf "$arg" --one-top-level
14 | local code=$?
15 | ;;
16 | *.tar.gz)
17 | tar xzf "$arg" --one-top-level
18 | local code=$?
19 | ;;
20 | *.bz2)
21 | bunzip2 "$arg"
22 | local code=$?
23 | ;;
24 | *.gz)
25 | gunzip "$arg"
26 | local code=$?
27 | ;;
28 | *.tar)
29 | tar xf "$arg" --one-top-level
30 | local code=$?
31 | ;;
32 | *.tbz2)
33 | (tar xjf "$arg" --one-top-level)
34 | local code=$?
35 | ;;
36 | *.tgz)
37 | tar xzf "$arg" --one-top-level
38 | local code=$?
39 | ;;
40 | *.tar.xz)
41 | a_dir=$(expr "$arg" : '\(.*\).tar.xz')
42 | 7z x "$arg" -o"$a_dir"
43 | local code=$?
44 | ;;
45 | *.zip)
46 | a_dir=$(expr "$arg" : '\(.*\).zip')
47 | 7z x "$arg" -o"$a_dir"
48 | local code=$?
49 | ;;
50 | *.7z)
51 | a_dir=$(expr "$arg" : '\(.*\).7z')
52 | 7z x "$arg" -o"$a_dir"
53 | local code=$?
54 | ;;
55 | *.Z)
56 | uncompress "$arg"
57 | local code=$?
58 | ;;
59 | *.rar)
60 | a_dir=$(expr "$arg" : '\(.*\).rar')
61 | mkdir "$a_dir"
62 | 7z x "$arg" -o"$a_dir"
63 | local code=$?
64 | ;;
65 | *.iso)
66 | a_dir=$(expr "$arg" : '\(.*\).iso')
67 | 7z x "$arg" -o"$a_dir"
68 | local code=$?
69 | ;;
70 | *.wim)
71 | a_dir=$(expr "$arg" : '\(.*\).wim')
72 | 7z x "$arg" -o"$a_dir"
73 | local code=$?
74 | ;;
75 | *.cab)
76 | a_dir=$(expr "$arg" : '\(.*\).cab')
77 | 7z x "$arg" -o"$a_dir"
78 | local code=$?
79 | ;;
80 | *.apm)
81 | a_dir=$(expr "$arg" : '\(.*\).apm')
82 | 7z x "$arg" -o"$a_dir"
83 | local code=$?
84 | ;;
85 | *.arj)
86 | a_dir=$(expr "$arg" : '\(.*\).arj')
87 | 7z x "$arg" -o"$a_dir"
88 | local code=$?
89 | ;;
90 | *.chm)
91 | a_dir=$(expr "$arg" : '\(.*\).chm')
92 | 7z x "$arg" -o"$a_dir"
93 | local code=$?
94 | ;;
95 | *.cpio)
96 | a_dir=$(expr "$arg" : '\(.*\).cpio')
97 | 7z x "$arg" -o"$a_dir"
98 | local code=$?
99 | ;;
100 | *.cramfs)
101 | a_dir=$(expr "$arg" : '\(.*\).cramfs')
102 | 7z x "$arg" -o"$a_dir"
103 | local code=$?
104 | ;;
105 | *.deb)
106 | a_dir=$(expr "$arg" : '\(.*\).deb')
107 | 7z x "$arg" -o"$a_dir"
108 | local code=$?
109 | ;;
110 | *.dmg)
111 | a_dir=$(expr "$arg" : '\(.*\).dmg')
112 | 7z x "$arg" -o"$a_dir"
113 | local code=$?
114 | ;;
115 | *.fat)
116 | a_dir=$(expr "$arg" : '\(.*\).fat')
117 | 7z x "$arg" -o"$a_dir"
118 | local code=$?
119 | ;;
120 | *.hfs)
121 | a_dir=$(expr "$arg" : '\(.*\).hfs')
122 | 7z x "$arg" -o"$a_dir"
123 | local code=$?
124 | ;;
125 | *.lzh)
126 | a_dir=$(expr "$arg" : '\(.*\).lzh')
127 | 7z x "$arg" -o"$a_dir"
128 | local code=$?
129 | ;;
130 | *.lzma)
131 | a_dir=$(expr "$arg" : '\(.*\).lzma')
132 | 7z x "$arg" -o"$a_dir"
133 | local code=$?
134 | ;;
135 | *.lzma2)
136 | a_dir=$(expr "$arg" : '\(.*\).lzma2')
137 | 7z x "$arg" -o"$a_dir"
138 | local code=$?
139 | ;;
140 | *.mbr)
141 | a_dir=$(expr "$arg" : '\(.*\).mbr')
142 | 7z x "$arg" -o"$a_dir"
143 | local code=$?
144 | ;;
145 | *.msi)
146 | a_dir=$(expr "$arg" : '\(.*\).msi')
147 | 7z x "$arg" -o"$a_dir"
148 | local code=$?
149 | ;;
150 | *.mslz)
151 | a_dir=$(expr "$arg" : '\(.*\).mslz')
152 | 7z x "$arg" -o"$a_dir"
153 | local code=$?
154 | ;;
155 | *.nsis)
156 | a_dir=$(expr "$arg" : '\(.*\).nsis')
157 | 7z x "$arg" -o"$a_dir"
158 | local code=$?
159 | ;;
160 | *.ntfs)
161 | a_dir=$(expr "$arg" : '\(.*\).ntfs')
162 | 7z x "$arg" -o"$a_dir"
163 | local code=$?
164 | ;;
165 | *.rpm)
166 | a_dir=$(expr "$arg" : '\(.*\).rpm')
167 | 7z x "$arg" -o"$a_dir"
168 | local code=$?
169 | ;;
170 | *.squashfs)
171 | a_dir=$(expr "$arg" : '\(.*\).squashfs')
172 | 7z x "$arg" -o"$a_dir"
173 | local code=$?
174 | ;;
175 | *.udf)
176 | a_dir=$(expr "$arg" : '\(.*\).udf')
177 | 7z x "$arg" -o"$a_dir"
178 | local code=$?
179 | ;;
180 | *.vhd)
181 | a_dir=$(expr "$arg" : '\(.*\).vhd')
182 | 7z x "$arg" -o"$a_dir"
183 | local code=$?
184 | ;;
185 | *.xar)
186 | a_dir=$(expr "$arg" : '\(.*\).xar')
187 | 7z x "$arg" -o"$a_dir"
188 | local code=$?
189 | ;;
190 | *)
191 | echo "'$arg' cannot be extracted via extract()" 1>&2
192 | exit 1
193 | ;;
194 | esac
195 | cd - || exit $?
196 | exit $code
197 | }
198 |
199 | extract "$1"
--------------------------------------------------------------------------------
/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
3 | # Copyright (c) 2021 WeebTime/VideoEncoder
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published
7 | # by the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU Affero General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU Affero General Public License
16 | # along with this program. If not, see .
17 |
18 | import glob
19 |
20 | LICENSE_HEADER = '''
21 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
22 | # Copyright (c) 2021 WeebTime/VideoEncoder
23 | #
24 | # This program is free software: you can redistribute it and/or modify
25 | # it under the terms of the GNU Affero General Public License as published
26 | # by the Free Software Foundation, either version 3 of the License, or
27 | # (at your option) any later version.
28 | #
29 | # This program is distributed in the hope that it will be useful,
30 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
31 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 | # GNU Affero General Public License for more details.
33 | #
34 | # You should have received a copy of the GNU Affero General Public License
35 | # along with this program. If not, see .
36 | '''.strip()
37 |
38 | missing_header = False
39 | for file in glob.iglob('VideoEncoder/**/*.py', recursive=True):
40 | with open(file, 'r') as fileobj:
41 | file_header = fileobj.read(len(LICENSE_HEADER))
42 | if file_header != LICENSE_HEADER:
43 | print(file, 'is missing AGPL license header')
44 | missing_header = True
45 | if missing_header:
46 | exit(1)
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Required
2 | pyrogram
3 | TgCrypto
4 | python-dotenv
5 | hachoir
6 | ffmpeg-python
7 | beautifulsoup4==4.9.1
8 | bs4==0.0.1
9 |
10 | # DDL
11 | pySmartDL
12 | requests
13 |
14 | # Database
15 | dnspython
16 | motor
17 |
18 | # Others
19 | psutil
20 | js2py
21 | cfscrape
22 | lk21
23 | lxml
24 | httpx
25 |
26 | # Google Drive
27 | google-api-core
28 | google-auth
29 | oauth2client<4.0.0
30 | google~=3.0.0
31 | google-api-python-client
32 | google-auth-httplib2
33 | google-auth-oauthlib
34 | httplib2==0.18.0
35 |
--------------------------------------------------------------------------------
/restart.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from subprocess import run as srun
18 | import logging
19 | from os import path as ospath
20 | from sys import executable
21 | from os import execl as osexecl
22 |
23 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
24 | handlers=[logging.FileHandler('log.txt'), logging.StreamHandler()],
25 | level=logging.INFO)
26 |
27 | UPSTREAM_REPO = 'https://github.com/WeebTime/Video-Encoder-Bot'
28 | UPSTREAM_BRANCH = 'beta'
29 |
30 | if UPSTREAM_REPO is not None:
31 | if ospath.exists('.git'):
32 | srun(["rm", "-rf", ".git"])
33 |
34 | update = srun([f"git init -q \
35 | && git add . \
36 | && git commit -sm update -q \
37 | && git remote add origin {UPSTREAM_REPO} \
38 | && git fetch origin -q \
39 | && git reset --hard origin/{UPSTREAM_BRANCH} -q"], shell=True)
40 |
41 | if update.returncode == 0:
42 | logging.info('Successfully updated with latest commit from UPSTREAM_REPO')
43 | osexecl(executable, executable, "-m", "VideoEncoder")
44 | else:
45 | logging.error('Something went wrong while updating, check UPSTREAM_REPO if valid or not!')
46 |
47 | osexecl(executable, executable, "-m", "VideoEncoder")
48 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | python3 update.py && python3 -m VideoEncoder
2 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.9.2
--------------------------------------------------------------------------------
/update.py:
--------------------------------------------------------------------------------
1 | # VideoEncoder - a telegram bot for compressing/encoding videos in h264/h265 format.
2 | # Copyright (c) 2021 WeebTime/VideoEncoder
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU Affero General Public License as published
6 | # by the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU Affero General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU Affero General Public License
15 | # along with this program. If not, see .
16 |
17 | from subprocess import run as srun
18 | import logging
19 | from os import path as ospath
20 |
21 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22 | handlers=[logging.FileHandler('log.txt'), logging.StreamHandler()],
23 | level=logging.INFO)
24 |
25 | UPSTREAM_REPO = 'https://github.com/WeebTime/Video-Encoder-Bot'
26 | UPSTREAM_BRANCH = 'beta'
27 |
28 | if UPSTREAM_REPO is not None:
29 | if ospath.exists('.git'):
30 | srun(["rm", "-rf", ".git"])
31 |
32 | update = srun([f"git init -q \
33 | && git add . \
34 | && git commit -sm update -q \
35 | && git remote add origin {UPSTREAM_REPO} \
36 | && git fetch origin -q \
37 | && git reset --hard origin/{UPSTREAM_BRANCH} -q"], shell=True)
38 |
39 | if update.returncode == 0:
40 | logging.info('Successfully updated with latest commit from UPSTREAM_REPO')
41 | else:
42 | logging.error('Something went wrong while updating, check UPSTREAM_REPO if valid or not!')
43 |
--------------------------------------------------------------------------------
/updates.txt:
--------------------------------------------------------------------------------
1 | ──「 Update 0.1 」──
2 | • Initial commit
3 |
4 | ──「 Update 1.0 」──
5 | • Removing useless stuffs.
6 | • Adding button advertisement.
7 |
8 | ──「 Update 1.1 」──
9 | • Fixed video file issue.
10 | • Cleaning code.
11 | • More copyright lol.
12 | • Auth fix.
13 |
14 | ──「 Update 1.2 」──
15 | • New Command: /help
16 | • New Command: /logs
17 | • New Command: /sthumb, /dthumb
18 | • Removing logs spam.
19 | • Precommit file fixed.
20 | • Variables errors fixed.
21 |
22 | ──「 Update 1.3 」──
23 | • New Command: /vset
24 | • Sort imports
25 | • Removed useless imports.
26 | • Removed useless codes.
27 | • Format python code.
28 | • print() function use.
29 | • upload_doc fix.
30 | • New variables:
31 | [RESOLUTION, CRF]
32 | • updates.txt file for updates logs.
33 |
34 | ──「 Update 1.4 」──
35 | • Small changes and fixes.
36 |
37 | ──「 Update 1.5 」──
38 | **Major Update**
39 | - Removed thumbnail
40 | - Implemented Settings, Database Support.
41 | - Implemented Drive Upload/Download Support.
42 | - Implemented DDL Support.
43 | - Implemented Encode Status Bar Support.
44 | - Video and Metadata Watermark Support.
45 | - Implemented auth through comman.
46 | - Added Many Commands! Check /help for more info.
--------------------------------------------------------------------------------