├── .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. --------------------------------------------------------------------------------