├── LICENSE ├── README.md ├── TikTok Client ├── Assets │ └── tiktoklogo.png ├── UI │ ├── ClipEditor.ui │ ├── clipDownload.ui │ ├── clipUpload.ui │ ├── login.ui │ └── menu.ui ├── client.py ├── clientUI.py ├── config.ini ├── main.py ├── scriptwrapper.py └── settings.py ├── TikTok Server ├── Assets │ └── tiktoklogo.png ├── UI │ ├── clipPassiveDownload.ui │ └── clipTemplateHandler.ui ├── autodownloader.py ├── autodownloaderUI.py ├── config.ini ├── database.py ├── filtercreator.py ├── main.py ├── scriptwrapper.py ├── server.py ├── settings.py └── tiktok.py ├── TikTok Video Generator ├── Logo │ └── tiktoklogo.png ├── UI │ └── videoRendering.ui ├── config.ini ├── main.py ├── scriptwrapper.py ├── server.py ├── settings.py ├── vidGen.py └── vidgenUI.py ├── images └── logo.png └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 HA6Bots 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Contributors][contributors-shield]][contributors-url] 3 | [![Forks][forks-shield]][forks-url] 4 | [![Stargazers][stars-shield]][stars-url] 5 | [![Issues][issues-shield]][issues-url] 6 | [![MIT License][license-shield]][license-url] 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | 15 | Logo 16 | 17 | 18 |

TikTok Compilation Video Generator

19 | 20 |

21 | A system of bots that collects clips automatically via custom made filters, lets you easily browse these clips, and puts them together into a compilation video ready to be uploaded straight to any social media platform 22 |
23 | Explore the docs » 24 |
25 |
26 |

27 |

28 | 29 | 30 | 31 | 32 |
33 | Table of Contents 34 |
    35 |
  1. 36 | About The Project 37 | 43 |
  2. 44 |
  3. 45 | Getting Started 46 | 50 |
  4. 51 |
  5. Usage
  6. 52 |
  7. Roadmap
  8. 53 |
  9. License
  10. 54 |
  11. 55 | File System and Explanation 56 | 61 |
  12. 62 |
  13. 63 | Config Files and Explanation 64 | 69 |
  14. 70 |
71 |
72 | 73 | 74 | 75 | 76 | ## About The Project 77 | 78 | 79 | 80 | A system of bots that collects clips automatically via custom made filters, lets you easily browse these clips, and puts them together into a compilation video ready to be uploaded straight to any social media platform. Full VPS support is provided, along with an accounts system so multiple users can use the bot at once. This bot is split up into three separate programs. The server. The client. The video generator. These programs perform different functions that when combined creates a very powerful system for auto generating compilation videos. 81 | 82 | TikTok compilation videos are a new phenomena that are quickly taking over the internet. They wrack up a lot of views and can quickly grow a channels follower base. There’s quite literally an endless supply of TikTok videos to choose from so the potential for growing channels is limitless. However there are several challenges involved in creating compilation videos. This bot (or series of programs) addresses many of these issues. 83 | 84 | ### Full VPS Support and Account System 85 | Since the bot is split up into three different programs, communications between the programs uses a combination HTTP and FTP servers to move information from one program to the other. The FTP servers are used to move mp4 files around while the HTTP servers are for general information and usually are in the form of json. FTP requires authorisation for each client and therefore this provides the basis of the account system. You can add or remove users and set there password in the server program. This username and password combination is required in the video editor program. Therefore this works perfectly for a multi man operation as allows for multiple people to use the bot at once. 86 | 87 | ### What this bot does 88 | Passively downloads and stores clips from TikTok for any user created filter. The clips are automatically kept track of in a clip bin database. 89 | 90 | Provides a video editor interface connected directly to the clip bin database, allowing you to easily go through the clips. The interface is somewhat similar to that of tinder, where you can keep/skip a video clip. 91 | 92 | A video generator that compiles the clips from the video editor, generating a mp4 video where that you can upload to any platform. 93 | 94 | ### Bot Showcase Video 95 | * [Youtube](https://www.youtube.com/watch?v=-yXEDeiQBuk) 96 | 97 | There are three separate programs that make up the TikTok bot. 98 | 99 | ### Built With 100 | 101 | This section should list any major frameworks used in development. 102 | * [Python](https://www.python.org/) 103 | 104 | 105 | 106 | 107 | ## Getting Started 108 | 109 | This is an example of how you may give instructions on setting up your project locally. 110 | To get a local copy up and running follow these simple example steps. 111 | 112 | ### Prerequisites 113 | 114 | This is an example of how to list things you need to use the software and how to install them. 115 | * FFMPEG 116 | * [Download FFMPEG](https://www.ffmpeg.org/download.html) 117 | ``` 118 | Must be added to system path so can be called from command line. 119 | ``` 120 | * MySql 121 | * [Download MySql](https://dev.mysql.com/downloads/) 122 | 123 | ### Installation 124 | 125 | 1. Install Modules 126 | ```sh 127 | pip install -r requirements.txt 128 | ``` 129 | 2. Get TikTok Cookies 130 | * Go to [tiktok](https://www.tiktok.com/) 131 | * Login to you account 132 | * Go to your profile 133 | * Open Developer Tools (cltr+shift+i) 134 | * In Developer console go to apllication 135 | * Find cookies Folder . 136 | * In cookies folder find 's_v_web_id' and 'sid_ucp_v1' 137 | 3. Edit config file in `Tiktok server` 138 | * Add value of 's_v_web_id' and 'sid_ucp_v1' from cookie. 139 | * Edit database details. 140 | 3. Check and edit config file in `Tiktok Client` and `TikTok Video Generator` 141 | 142 | 143 | 144 | ## Usage 145 | 146 | 1. Start mysql database. 147 | 148 | 2. Go to `Tiktok Server` and start server. 149 | ```sh 150 | py main.py 151 | ``` 152 | * Add filter , make it live. 153 | * search and download Clips 154 | 155 | 3. Go to `Tiktok Client` and start. 156 | ```sh 157 | py main.py 158 | ``` 159 | *Edit clips 160 | *Change duration, Intro, Outro and Interval. 161 | 162 | 4. Go to `Tiktok Video Generator` and start. 163 | ```sh 164 | py main.py 165 | ``` 166 | *Select and Render Final Video file . 167 | 168 | 169 | ## Roadmap 170 | 171 | See the [open issues](https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/issues) for a list of proposed features (and known issues). 172 | 173 | 174 | 175 | 176 | 177 | ## License 178 | 179 | Distributed under the MIT License. See `LICENSE` for more information. 180 | 181 | 182 | 183 | 184 | 185 | ## FIle System 186 | 187 | 188 | ### Server Program 189 | 190 | Function: 191 | 192 | • Automatically downloads clips from TikTok for any categories that you select. 193 | 194 | • Can manage accounts for the client logins 195 | 196 | Automatic Downloader Notes 197 | 198 | The automatic downloader side of the server is designed in a way to get around the TikTok limitations for getting the highlight clips. 199 | 200 | It is split into two different processes: 201 | 202 | Finding the clip and obtaining it’s URL 203 | 204 | Downloading the clip via the URL 205 | 206 | There are no restrictions on downloading the clips and this is simple to do once a URL is obtained. However finding the top clips in the first place is another story. 207 | 208 | TikTok only gives you access to about 2000 clips for each request entered in the API call. This would not be a sufficient amount of clips if the find/download process is only initiated when the bot is used for video editing. Therefore it is recommended to run this process automatically to build up a large clip bin, preferably on a VPS. This is largely down to the usage of the bot - heavy usage will demand a large amount of clips, and therefore turning on the automated find/download process is recommended for this case. 209 | 210 | * main.py : start point 211 | 212 | * database.py : all the sql queries are written here 213 | 214 | * server.py : FTP and HTTP Servers are handled here. 215 | 216 | * settings.py : Data loaded in from config.ini 217 | 218 | * tiktok.py : Where the API calls are made to TikTok with the download/find methods. 219 | 220 | * autodownloader.py : A wrapper for the download/find process utilised in autodownloaderUI.py 221 | 222 | * autodownloaderUI.py : Where the UI is programmed 223 | 224 | scriptwrapper.py : Various wrappers for the TikTok clips here. Formatting of the video occurs here in the “reformatPartialJson” method 225 | 226 | ### Video Editor Program 227 | 228 | This is the actual user interface used to browse the clips in the server clip bin. This is a fairly simple process, for any one clip you have the option to keep or remove it. 229 | 230 | * main.py : start point 231 | 232 | * client.py : Communications with the server http and ftp occur here 233 | 234 | * settings.py : Data loaded in from config.ini 235 | 236 | * scriptwrapper.py : Various wrappers for tiktok clips / entire videos are stored here 237 | 238 | * clientUI.py : Where the UI is programmed 239 | 240 | ### Video Generator Program 241 | 242 | This actually puts together the clips into a compilation video. It also generates a credits text file with all the usernames of the TikToks used in the video. 243 | 244 | * main.py : start point 245 | 246 | * server.py : FTP and HTTP Servers are handled here. 247 | 248 | * settings.py : Data loaded in from config.ini 249 | 250 | * scriptwrapper.py : Various wrappers for tiktok clips / entire videos are stored here 251 | 252 | * vidGen.py : Methods for video rendering here see “renderVideo” 253 | 254 | * vidgenUI.py : Where the UI is programmed 255 | 256 | 257 | ## Config File Explanation 258 | 259 | Additional settings that only take effect on start-up are stored in a config file for each program. Any changes made require a restart to the particular program. 260 | 261 | ### Server Config 262 | 263 | [server_details] 264 | 265 | * address = 127.0.0.1 <-- Server Address 266 | 267 | * http_port = 8000 <-- Server HTTP Port 268 | 269 | * ftp_port = 2121 <-- Server FTP Port 270 | 271 | [video_generator_location] 272 | 273 | * address = 127.0.0.1 <-- Video Generator Address 274 | 275 | * http_port = 8001 <-- Video Generator HTTP Port 276 | 277 | * ftp_port = 2122 <-- Video Generator FTP Port 278 | 279 | * ftp_user = VidGen <-- Video Generator FTP Client name 280 | 281 | * ftp_password = password <-- Video Generator FTP Client password 282 | 283 | [tiktok] 284 | 285 | * language = en <-- Clip language 286 | 287 | * s_v_web_id = value <-- Get from TikTok Cookies , Read Above. 288 | 289 | * tt_webid = value <-- Get from TikTok Cookies , Read Above. 290 | 291 | [mysql_database] 292 | 293 | * databasehost = localhost <-- MySQL Server address 294 | 295 | * databaseuser = root <-- MySQL Server user 296 | 297 | * databasepassword = <-- MySQL Server user password 298 | 299 | ### Video Editor Config 300 | 301 | [server_location] 302 | 303 | * address = 127.0.0.1 <-- Server address 304 | 305 | * server_http_port = 8000 <-- Server HTTP port 306 | 307 | * server_ftp_port = 2121 <-- Server FTP port 308 | 309 | [auto_login] 310 | 311 | * username = admin <-- User registered in server 312 | 313 | * password = password <-- User registered in server’s password 314 | 315 | * auto_login = true <-- Insert the above details into the login window on startup 316 | 317 | [video_settings] 318 | 319 | * enforce_interval = True <-- Forces you to select a interval for your video 320 | 321 | * enforce_intro = True <-- Forces you to select a intro for your video 322 | 323 | * enforce_outro = True <-- Forces you to select a outro for your video 324 | 325 | * enforce_firstclip = True <-- Forces you to select a first clip for your video 326 | 327 | ### Video Generator Config 328 | 329 | [video_generator_details] 330 | 331 | * address = 127.0.0.1 <-- Video Generator Address 332 | 333 | * http_port = 8001 <-- Video Generator HTTP port 334 | 335 | * ftp_port = 2122 <-- Video Generator FTP port 336 | 337 | * ftp_user = VidGen <-- Video Generator FTP user 338 | 339 | * ftp_password = password <-- Video Generator FTP user’s password 340 | 341 | [server_location] 342 | 343 | * address = 127.0.0.1 <-- Server address 344 | 345 | * http_port = 8000 <-- Server HTTP port 346 | 347 | * ftp_port = 2121 <-- Server FTP port 348 | 349 | [rendering] 350 | 351 | * fps = 30 <-- FPS to render video at if useMinimumFps or useMaximumFps are both true 352 | 353 | * useMinimumFps = True <-- Sets all the individual clips FPS to the lowest FPS of the clips (recommended) 354 | 355 | * useMaximumFps = False <-- Sets all the individual clips FPS to the highest FPS of the clips 356 | 357 | * backupVideos = True <-- Will automatically backup each video send to the video generator. These can be rerendered or deleted via the UI 358 | 359 | 360 | 361 | 362 | [contributors-shield]: https://img.shields.io/github/contributors/HA6Bots/TikTok-Compilation-Video-Generator.svg?style=for-the-badge 363 | [contributors-url]: https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/graphs/contributors 364 | [forks-shield]: https://img.shields.io/github/forks/HA6Bots/TikTok-Compilation-Video-Generator.svg?style=for-the-badge 365 | [forks-url]: https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/network/members 366 | [stars-shield]: https://img.shields.io/github/stars/HA6Bots/TikTok-Compilation-Video-Generator.svg?style=for-the-badge 367 | [stars-url]: https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/stargazers 368 | [issues-shield]: https://img.shields.io/github/issues/HA6Bots/TikTok-Compilation-Video-Generator.svg?style=for-the-badge 369 | [issues-url]: https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/issues 370 | [license-shield]: https://img.shields.io/github/license/HA6Bots/TikTok-Compilation-Video-Generator.svg?style=for-the-badge 371 | [license-url]: https://github.com/HA6Bots/TikTok-Compilation-Video-Generator/blob/master/LICENSE 372 | 373 | [product-screenshot]: images/screenshot.png 374 | -------------------------------------------------------------------------------- /TikTok Client/Assets/tiktoklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/405bef46cbc7d8f949b4beecce64c62b4d583667/TikTok Client/Assets/tiktoklogo.png -------------------------------------------------------------------------------- /TikTok Client/UI/clipUpload.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | downloadClips 4 | 5 | 6 | 7 | 0 8 | 0 9 | 381 10 | 133 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 255 20 | 255 21 | 255 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 29 | 29 30 | 29 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 38 | 43 39 | 43 40 | 41 | 42 | 43 | 44 | 45 | 46 | 36 47 | 36 48 | 36 49 | 50 | 51 | 52 | 53 | 54 | 55 | 14 56 | 14 57 | 14 58 | 59 | 60 | 61 | 62 | 63 | 64 | 19 65 | 19 66 | 19 67 | 68 | 69 | 70 | 71 | 72 | 73 | 255 74 | 255 75 | 255 76 | 77 | 78 | 79 | 80 | 81 | 82 | 255 83 | 255 84 | 255 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 150 101 | 150 102 | 150 103 | 104 | 105 | 106 | 107 | 108 | 109 | 29 110 | 29 111 | 29 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 122 | 123 | 124 | 125 | 126 | 127 | 14 128 | 14 129 | 14 130 | 131 | 132 | 133 | 134 | 135 | 136 | 255 137 | 255 138 | 220 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 255 157 | 255 158 | 255 159 | 160 | 161 | 162 | 163 | 164 | 165 | 29 166 | 29 167 | 29 168 | 169 | 170 | 171 | 172 | 173 | 174 | 43 175 | 43 176 | 43 177 | 178 | 179 | 180 | 181 | 182 | 183 | 36 184 | 36 185 | 36 186 | 187 | 188 | 189 | 190 | 191 | 192 | 14 193 | 14 194 | 14 195 | 196 | 197 | 198 | 199 | 200 | 201 | 19 202 | 19 203 | 19 204 | 205 | 206 | 207 | 208 | 209 | 210 | 255 211 | 255 212 | 255 213 | 214 | 215 | 216 | 217 | 218 | 219 | 255 220 | 255 221 | 255 222 | 223 | 224 | 225 | 226 | 227 | 228 | 0 229 | 0 230 | 0 231 | 232 | 233 | 234 | 235 | 236 | 237 | 150 238 | 150 239 | 150 240 | 241 | 242 | 243 | 244 | 245 | 246 | 29 247 | 29 248 | 29 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 14 265 | 14 266 | 14 267 | 268 | 269 | 270 | 271 | 272 | 273 | 255 274 | 255 275 | 220 276 | 277 | 278 | 279 | 280 | 281 | 282 | 0 283 | 0 284 | 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 14 295 | 14 296 | 297 | 298 | 299 | 300 | 301 | 302 | 29 303 | 29 304 | 29 305 | 306 | 307 | 308 | 309 | 310 | 311 | 43 312 | 43 313 | 43 314 | 315 | 316 | 317 | 318 | 319 | 320 | 36 321 | 36 322 | 36 323 | 324 | 325 | 326 | 327 | 328 | 329 | 14 330 | 14 331 | 14 332 | 333 | 334 | 335 | 336 | 337 | 338 | 19 339 | 19 340 | 19 341 | 342 | 343 | 344 | 345 | 346 | 347 | 14 348 | 14 349 | 14 350 | 351 | 352 | 353 | 354 | 355 | 356 | 255 357 | 255 358 | 255 359 | 360 | 361 | 362 | 363 | 364 | 365 | 14 366 | 14 367 | 14 368 | 369 | 370 | 371 | 372 | 373 | 374 | 29 375 | 29 376 | 29 377 | 378 | 379 | 380 | 381 | 382 | 383 | 29 384 | 29 385 | 29 386 | 387 | 388 | 389 | 390 | 391 | 392 | 0 393 | 0 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 29 402 | 29 403 | 29 404 | 405 | 406 | 407 | 408 | 409 | 410 | 255 411 | 255 412 | 220 413 | 414 | 415 | 416 | 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 8 431 | 432 | 433 | 434 | Uploading Video 435 | 436 | 437 | 438 | 439 | 440 | 30 441 | 60 442 | 351 443 | 23 444 | 445 | 446 | 447 | 0 448 | 449 | 450 | 451 | 452 | 453 | 50 454 | 30 455 | 281 456 | 81 457 | 458 | 459 | 460 | 461 | 14 462 | 463 | 464 | 465 | Uploading Clips... 466 | 467 | 468 | Qt::AlignCenter 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /TikTok Client/UI/login.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | downloadClips 4 | 5 | 6 | 7 | 0 8 | 0 9 | 381 10 | 228 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 255 20 | 255 21 | 255 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 29 | 29 30 | 29 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 38 | 43 39 | 43 40 | 41 | 42 | 43 | 44 | 45 | 46 | 36 47 | 36 48 | 36 49 | 50 | 51 | 52 | 53 | 54 | 55 | 14 56 | 14 57 | 14 58 | 59 | 60 | 61 | 62 | 63 | 64 | 19 65 | 19 66 | 19 67 | 68 | 69 | 70 | 71 | 72 | 73 | 0 74 | 0 75 | 255 76 | 77 | 78 | 79 | 80 | 81 | 82 | 255 83 | 255 84 | 255 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 150 101 | 150 102 | 150 103 | 104 | 105 | 106 | 107 | 108 | 109 | 29 110 | 29 111 | 29 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 122 | 123 | 124 | 125 | 126 | 127 | 14 128 | 14 129 | 14 130 | 131 | 132 | 133 | 134 | 135 | 136 | 255 137 | 255 138 | 220 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 255 157 | 255 158 | 255 159 | 160 | 161 | 162 | 163 | 164 | 165 | 29 166 | 29 167 | 29 168 | 169 | 170 | 171 | 172 | 173 | 174 | 43 175 | 43 176 | 43 177 | 178 | 179 | 180 | 181 | 182 | 183 | 36 184 | 36 185 | 36 186 | 187 | 188 | 189 | 190 | 191 | 192 | 14 193 | 14 194 | 14 195 | 196 | 197 | 198 | 199 | 200 | 201 | 19 202 | 19 203 | 19 204 | 205 | 206 | 207 | 208 | 209 | 210 | 0 211 | 0 212 | 255 213 | 214 | 215 | 216 | 217 | 218 | 219 | 255 220 | 255 221 | 255 222 | 223 | 224 | 225 | 226 | 227 | 228 | 0 229 | 0 230 | 0 231 | 232 | 233 | 234 | 235 | 236 | 237 | 150 238 | 150 239 | 150 240 | 241 | 242 | 243 | 244 | 245 | 246 | 29 247 | 29 248 | 29 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 14 265 | 14 266 | 14 267 | 268 | 269 | 270 | 271 | 272 | 273 | 255 274 | 255 275 | 220 276 | 277 | 278 | 279 | 280 | 281 | 282 | 0 283 | 0 284 | 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 14 295 | 14 296 | 297 | 298 | 299 | 300 | 301 | 302 | 29 303 | 29 304 | 29 305 | 306 | 307 | 308 | 309 | 310 | 311 | 43 312 | 43 313 | 43 314 | 315 | 316 | 317 | 318 | 319 | 320 | 36 321 | 36 322 | 36 323 | 324 | 325 | 326 | 327 | 328 | 329 | 14 330 | 14 331 | 14 332 | 333 | 334 | 335 | 336 | 337 | 338 | 19 339 | 19 340 | 19 341 | 342 | 343 | 344 | 345 | 346 | 347 | 14 348 | 14 349 | 14 350 | 351 | 352 | 353 | 354 | 355 | 356 | 255 357 | 255 358 | 255 359 | 360 | 361 | 362 | 363 | 364 | 365 | 14 366 | 14 367 | 14 368 | 369 | 370 | 371 | 372 | 373 | 374 | 29 375 | 29 376 | 29 377 | 378 | 379 | 380 | 381 | 382 | 383 | 29 384 | 29 385 | 29 386 | 387 | 388 | 389 | 390 | 391 | 392 | 0 393 | 0 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 29 402 | 29 403 | 29 404 | 405 | 406 | 407 | 408 | 409 | 410 | 255 411 | 255 412 | 220 413 | 414 | 415 | 416 | 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 8 431 | 432 | 433 | 434 | Login 435 | 436 | 437 | 438 | 439 | 440 | 120 441 | 130 442 | 121 443 | 31 444 | 445 | 446 | 447 | Login 448 | 449 | 450 | 451 | 452 | 453 | 50 454 | 40 455 | 281 456 | 21 457 | 458 | 459 | 460 | username 461 | 462 | 463 | 464 | 465 | 466 | 50 467 | 80 468 | 281 469 | 21 470 | 471 | 472 | 473 | password 474 | 475 | 476 | 477 | 478 | 479 | 70 480 | 190 481 | 251 482 | 16 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 270 493 | 140 494 | 111 495 | 17 496 | 497 | 498 | 499 | Auto Login 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | -------------------------------------------------------------------------------- /TikTok Client/UI/menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | downloadClips 4 | 5 | 6 | 7 | 0 8 | 0 9 | 504 10 | 433 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 255 20 | 255 21 | 255 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 29 | 29 30 | 29 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 38 | 43 39 | 43 40 | 41 | 42 | 43 | 44 | 45 | 46 | 36 47 | 36 48 | 36 49 | 50 | 51 | 52 | 53 | 54 | 55 | 14 56 | 14 57 | 14 58 | 59 | 60 | 61 | 62 | 63 | 64 | 19 65 | 19 66 | 19 67 | 68 | 69 | 70 | 71 | 72 | 73 | 255 74 | 255 75 | 255 76 | 77 | 78 | 79 | 80 | 81 | 82 | 255 83 | 255 84 | 255 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 150 101 | 150 102 | 150 103 | 104 | 105 | 106 | 107 | 108 | 109 | 29 110 | 29 111 | 29 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 122 | 123 | 124 | 125 | 126 | 127 | 14 128 | 14 129 | 14 130 | 131 | 132 | 133 | 134 | 135 | 136 | 255 137 | 255 138 | 220 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 255 157 | 255 158 | 255 159 | 160 | 161 | 162 | 163 | 164 | 165 | 29 166 | 29 167 | 29 168 | 169 | 170 | 171 | 172 | 173 | 174 | 43 175 | 43 176 | 43 177 | 178 | 179 | 180 | 181 | 182 | 183 | 36 184 | 36 185 | 36 186 | 187 | 188 | 189 | 190 | 191 | 192 | 14 193 | 14 194 | 14 195 | 196 | 197 | 198 | 199 | 200 | 201 | 19 202 | 19 203 | 19 204 | 205 | 206 | 207 | 208 | 209 | 210 | 255 211 | 255 212 | 255 213 | 214 | 215 | 216 | 217 | 218 | 219 | 255 220 | 255 221 | 255 222 | 223 | 224 | 225 | 226 | 227 | 228 | 0 229 | 0 230 | 0 231 | 232 | 233 | 234 | 235 | 236 | 237 | 150 238 | 150 239 | 150 240 | 241 | 242 | 243 | 244 | 245 | 246 | 29 247 | 29 248 | 29 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 14 265 | 14 266 | 14 267 | 268 | 269 | 270 | 271 | 272 | 273 | 255 274 | 255 275 | 220 276 | 277 | 278 | 279 | 280 | 281 | 282 | 0 283 | 0 284 | 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 14 295 | 14 296 | 297 | 298 | 299 | 300 | 301 | 302 | 29 303 | 29 304 | 29 305 | 306 | 307 | 308 | 309 | 310 | 311 | 43 312 | 43 313 | 43 314 | 315 | 316 | 317 | 318 | 319 | 320 | 36 321 | 36 322 | 36 323 | 324 | 325 | 326 | 327 | 328 | 329 | 14 330 | 14 331 | 14 332 | 333 | 334 | 335 | 336 | 337 | 338 | 19 339 | 19 340 | 19 341 | 342 | 343 | 344 | 345 | 346 | 347 | 14 348 | 14 349 | 14 350 | 351 | 352 | 353 | 354 | 355 | 356 | 255 357 | 255 358 | 255 359 | 360 | 361 | 362 | 363 | 364 | 365 | 14 366 | 14 367 | 14 368 | 369 | 370 | 371 | 372 | 373 | 374 | 29 375 | 29 376 | 29 377 | 378 | 379 | 380 | 381 | 382 | 383 | 29 384 | 29 385 | 29 386 | 387 | 388 | 389 | 390 | 391 | 392 | 0 393 | 0 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 29 402 | 29 403 | 29 404 | 405 | 406 | 407 | 408 | 409 | 410 | 255 411 | 255 412 | 220 413 | 414 | 415 | 416 | 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 8 431 | 432 | 433 | 434 | Main Menu 435 | 436 | 437 | 438 | 439 | 440 | 140 441 | 95 442 | 221 443 | 61 444 | 445 | 446 | 447 | 448 | 16 449 | 450 | 451 | 452 | Start Editing 453 | 454 | 455 | 456 | 457 | 458 | 20 459 | 245 460 | 181 461 | 31 462 | 463 | 464 | 465 | Download Video 466 | 467 | 468 | 469 | 470 | 471 | 20 472 | 285 473 | 271 474 | 31 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 0 484 | 0 485 | 0 486 | 487 | 488 | 489 | 490 | 491 | 492 | 0 493 | 0 494 | 0 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 0 504 | 0 505 | 0 506 | 507 | 508 | 509 | 510 | 511 | 512 | 0 513 | 0 514 | 0 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 14 524 | 14 525 | 14 526 | 527 | 528 | 529 | 530 | 531 | 532 | 0 533 | 0 534 | 0 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 230 551 | 335 552 | 241 553 | 31 554 | 555 | 556 | 557 | 0 558 | 559 | 560 | 561 | 562 | 563 | 20 564 | 335 565 | 181 566 | 31 567 | 568 | 569 | 570 | Open Finished Videos Folder 571 | 572 | 573 | 574 | 575 | 576 | 310 577 | 285 578 | 41 579 | 31 580 | 581 | 582 | 583 | 584 | 15 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | -10 595 | 164 596 | 521 597 | 31 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 131 607 | 131 608 | 131 609 | 610 | 611 | 612 | 613 | 614 | 615 | 255 616 | 255 617 | 255 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 131 627 | 131 628 | 131 629 | 630 | 631 | 632 | 633 | 634 | 635 | 255 636 | 255 637 | 255 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 131 647 | 131 648 | 131 649 | 650 | 651 | 652 | 653 | 654 | 655 | 29 656 | 29 657 | 29 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | Qt::Horizontal 666 | 667 | 668 | 669 | 670 | 671 | 30 672 | 190 673 | 181 674 | 31 675 | 676 | 677 | 678 | Download Completed Videos 679 | 680 | 681 | 682 | 683 | 684 | 20 685 | 12 686 | 351 687 | 51 688 | 689 | 690 | 691 | 692 | 12 693 | 694 | 695 | 696 | Welcome 697 | 698 | 699 | 700 | 701 | 702 | 230 703 | 250 704 | 171 705 | 16 706 | 707 | 708 | 709 | 0 Completed Videos 710 | 711 | 712 | 713 | 714 | 715 | 230 716 | 390 717 | 241 718 | 31 719 | 720 | 721 | 722 | 0 723 | 724 | 725 | 726 | 727 | 728 | 20 729 | 380 730 | 181 731 | 16 732 | 733 | 734 | 735 | Video Generator Progress 736 | 737 | 738 | 739 | 740 | 741 | 20 742 | 400 743 | 181 744 | 16 745 | 746 | 747 | 748 | Render Message 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | -------------------------------------------------------------------------------- /TikTok Client/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import scriptwrapper 3 | import ftplib 4 | import settings 5 | import clientUI 6 | import traceback, sys 7 | 8 | settings.generateConfigFile() 9 | httpaddress = "%s:%s" % (settings.address, settings.HTTP_PORT) 10 | max_progress = None 11 | current_progress = None 12 | render_message = None 13 | music_categories = ["None"] 14 | mainMenuWindow = None 15 | 16 | from time import sleep 17 | 18 | def requestGames(): 19 | responsegames =requests.get(f'http://{httpaddress}/getgames') 20 | clientUI.games = responsegames.json()["games"] 21 | 22 | def requestClips(game, amount, window): 23 | r = requests.get(f'http://{httpaddress}/getclips', json={"game": game, "amount" : int(amount)}, headers={'Accept-Encoding': None}) 24 | clips = r.json()["clips"] 25 | clipwrappers = [] 26 | 27 | for clip in clips: 28 | id = clip["id"] 29 | mp4 = clip["mp4"] 30 | streamer = clip["author_name"] 31 | duration = clip["duration"] 32 | clip_title = clip["clip_title"] 33 | diggCount = clip["diggCount"] 34 | shareCount = clip["shareCount"] 35 | playCount = clip["playCount"] 36 | commentCount = clip["commentCount"] 37 | clipwrappers.append(scriptwrapper.DownloadedTwitchClipWrapper(id, streamer, clip_title, mp4, duration, diggCount, shareCount, playCount, commentCount)) 38 | 39 | 40 | window.set_max_progres_bar.emit(len(clips)) 41 | 42 | ftp = ftplib.FTP() 43 | ftp.connect(settings.address, settings.FTP_PORT) 44 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 45 | ftp.cwd('/VideoFiles/') 46 | bad_indexes = [] 47 | for i, clip in enumerate(clipwrappers): 48 | try: 49 | mp4 = clip.mp4 50 | print("Downloading %s/%s clips %s" % (i + 1, len(clipwrappers), mp4)) 51 | with open("TempClips/%s.mp4"%mp4, 'wb' ) as file: 52 | ftp.retrbinary('RETR %s.mp4' % mp4, file.write) 53 | window.update_progress_bar.emit(i + 1) 54 | except Exception as e: 55 | bad_indexes.append(i) 56 | print("Failed to download clip, will remove later.") 57 | print(e) 58 | 59 | for i in sorted(bad_indexes, reverse=True): 60 | del clipwrappers[i] 61 | 62 | vidwrapper = scriptwrapper.ScriptWrapper(clipwrappers) 63 | window.finished_downloading.emit(vidwrapper) 64 | 65 | def testFTPConnection(username, password): 66 | try: 67 | ftp = ftplib.FTP() 68 | ftp.connect(settings.address, settings.FTP_PORT) 69 | ftp.login(username, password) 70 | return True 71 | except Exception as e: 72 | return False 73 | 74 | 75 | 76 | def requestClipsWithoutClips(game, amount, clips, window): 77 | ids = [] 78 | for clip in clips: 79 | ids.append(str(clip.id)) 80 | 81 | r = requests.get(f'http://{httpaddress}/getclipswithoutids', json={"game": game, "amount" : int(amount), "ids" : ids}, headers={'Accept-Encoding': None}) 82 | clips = r.json()["clips"] 83 | clipwrappers = [] 84 | for clip in clips: 85 | id = clip["id"] 86 | mp4 = clip["mp4"] 87 | streamer = clip["author_name"] 88 | duration = clip["duration"] 89 | clip_title = clip["clip_title"] 90 | diggCount = clip["diggCount"] 91 | shareCount = clip["shareCount"] 92 | playCount = clip["playCount"] 93 | commentCount = clip["commentCount"] 94 | clipwrappers.append(scriptwrapper.DownloadedTwitchClipWrapper(id, streamer, clip_title, mp4, duration, diggCount, shareCount, playCount, commentCount)) 95 | 96 | window.set_max_progres_bar.emit(len(clips)) 97 | 98 | ftp = ftplib.FTP() 99 | ftp.connect(settings.address, settings.FTP_PORT) 100 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 101 | ftp.cwd('/VideoFiles/') 102 | bad_indexes = [] 103 | 104 | for i, clip in enumerate(clipwrappers): 105 | try: 106 | mp4 = clip.mp4 107 | print("Downloading %s/%s clips %s" % (i + 1, len(clipwrappers), mp4)) 108 | with open("TempClips/%s.mp4"%mp4, 'wb' ) as file : 109 | ftp.retrbinary('RETR %s.mp4' % mp4, file.write, blocksize=settings.block_size) 110 | window.update_progress_bar.emit(i + 1) 111 | except Exception as e: 112 | bad_indexes.append(i) 113 | print("Failed to download clip, will remove later.") 114 | print(e) 115 | 116 | for i in sorted(bad_indexes, reverse=True): 117 | del clipwrappers[i] 118 | 119 | vidwrapper = scriptwrapper.ScriptWrapper(clipwrappers) 120 | window.finished_downloading.emit(vidwrapper) 121 | 122 | def uploadFile(location, ftplocation, name): 123 | ftp = ftplib.FTP() 124 | ftp.connect(settings.address, settings.FTP_PORT) 125 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 126 | ftp.cwd('%s' % ftplocation) 127 | file = open(location,'rb') 128 | ftp.storbinary('STOR %s' % name, file, blocksize=262144) 129 | file.close() 130 | 131 | def VideoGeneratorRenderStatus(): 132 | global max_progress, current_progress, render_message, music_categories 133 | while True: 134 | if mainMenuWindow is not None: 135 | try: 136 | r = requests.get(f'http://{httpaddress}/getrenderinfo', headers={'Accept-Encoding': None}) 137 | renderData = r.json() 138 | mainMenuWindow.update_render_progress.emit(renderData) 139 | except Exception: 140 | print("server not online") 141 | traceback.print_exc(file=sys.stdout) 142 | 143 | sleep(5) 144 | 145 | 146 | def exportVideo(videowrapper, name, window): 147 | 148 | clips = videowrapper.final_clips 149 | 150 | introUpload = None 151 | vidClipUpload = None 152 | 153 | amount = 0 154 | for clip in clips: 155 | if clip.upload: 156 | amount += 1 157 | 158 | window.set_max_progres_bar.emit(amount) 159 | 160 | for clip in clips: 161 | if clip.upload: 162 | introUpload = clip.mp4 163 | name = len(clip.mp4.split("/")) 164 | new_name = (clip.mp4.split("/")[name-1]).replace(".mp4", "") 165 | clip.mp4 = "UploadedFiles/%s.mp4" % new_name 166 | uploadFile(introUpload, "/UploadedFiles/", "%s.mp4" % new_name) 167 | window.update_progress_bar.emit() 168 | continue 169 | 170 | 171 | clipInfo = [] 172 | 173 | for clip in clips: 174 | clipInfo.append({"id" : clip.id, 175 | "isIntro" : clip.isIntro, "isUpload" : clip.upload, "mp4" : clip.mp4, "duration" : clip.vid_duration, "audio" : clip.audio, "keep" : clip.isUsed, "isInterval" : clip.isInterval, "isOutro" : clip.isOutro}) 176 | 177 | window.update_progress_bar.emit() 178 | 179 | info = {"clips": clipInfo, "name" : name} 180 | r = requests.get(f'http://{httpaddress}/uploadvideo', json=info, headers={'Accept-Encoding': None}) 181 | sucess = r.json()["upload_success"] 182 | print("Uploaded Video!") 183 | window.finished_downloading.emit() 184 | 185 | 186 | def requestFinishedVideoList(window): 187 | r = requests.get(f'http://{httpaddress}/getfinishedvideoslist', headers={'Accept-Encoding': None}) 188 | videos = r.json()["videos"] 189 | window.download_finished_videos_names.emit(videos) 190 | 191 | def downloadFinishedVideo(name, window): 192 | ftp = ftplib.FTP() 193 | ftp.connect(settings.address, settings.FTP_PORT) 194 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 195 | ftp.cwd('/FinalVideos/') 196 | 197 | print("Downloading Video %s " % name) 198 | with open("Finished Videos/%s.mp4"%name, 'wb' ) as file : 199 | print('%s.mp4' % name) 200 | ftp.retrbinary('RETR %s.mp4' % name, file.write, blocksize=settings.block_size) 201 | window.update_progress_bar.emit(1) 202 | with open("Finished Videos/%s.txt"%name, 'wb' ) as file : 203 | ftp.retrbinary('RETR %s.txt' % name, file.write, blocksize=settings.block_size) 204 | window.update_progress_bar.emit(2) 205 | window.finish_downloading.emit() 206 | 207 | 208 | -------------------------------------------------------------------------------- /TikTok Client/config.ini: -------------------------------------------------------------------------------- 1 | [server_location] 2 | address = 127.0.0.1 3 | server_http_port = 8000 4 | server_ftp_port = 2121 5 | 6 | [auto_login] 7 | username = admin 8 | password = password 9 | auto_login = true 10 | 11 | [video_settings] 12 | enforce_interval = False 13 | enforce_intro = False 14 | enforce_outro = False 15 | enforce_firstclip = False 16 | -------------------------------------------------------------------------------- /TikTok Client/main.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | from threading import Thread 3 | import settings 4 | import clientUI 5 | import os 6 | import client 7 | import sys 8 | 9 | current_path = os.path.dirname(os.path.realpath(__file__)) 10 | script = None 11 | menu = None 12 | class App(): 13 | def __init__(self): 14 | global menu 15 | app = QtWidgets.QApplication(sys.argv) 16 | app.processEvents() 17 | 18 | login = clientUI.LoginWindow() 19 | login.show() 20 | 21 | 22 | Thread(target=client.VideoGeneratorRenderStatus).start() 23 | 24 | sys.exit(app.exec_()) 25 | 26 | def init(): 27 | app = App() 28 | 29 | 30 | 31 | sys._excepthook = sys.excepthook 32 | def exception_hook(exctype, value, traceback): 33 | print(exctype, value, traceback) 34 | sys._excepthook(exctype, value, traceback) 35 | sys.exit(1) 36 | sys.excepthook = exception_hook 37 | 38 | 39 | def getFileNames(file_path): 40 | files = [os.path.splitext(filename)[0] for filename in os.listdir(file_path)] 41 | return files 42 | 43 | 44 | 45 | def deleteAllFilesInPath(path): 46 | for file in os.listdir(path): 47 | file_path = os.path.join(path, file) 48 | try: 49 | if os.path.isfile(file_path): 50 | os.unlink(file_path) 51 | except Exception as e: 52 | print(e) 53 | 54 | if __name__ == "__main__": 55 | current_directory = os.path.dirname(os.path.realpath(__file__)) 56 | os.chdir(current_directory) 57 | settings.generateConfigFile() 58 | if not os.path.exists("TempClips"): 59 | os.mkdir("TempClips") 60 | os.mkdir("FirstClips") 61 | os.mkdir("Intros") 62 | os.mkdir("Outros") 63 | os.mkdir("Finished Videos") 64 | os.mkdir("Intervals") 65 | os.mkdir("Save Data") 66 | 67 | 68 | else: 69 | deleteAllFilesInPath("TempClips") 70 | 71 | client.requestGames() 72 | init() 73 | 74 | #requestGames() 75 | #requestClips("Warzone", 10) 76 | #connectFTP() 77 | 78 | pass 79 | # 80 | # while len(getFileNames(f'{current_path}/Assets/Music')) == 0: 81 | # print(f"No music files in directory: '{current_path}/Assets/Music'. Please add some!") 82 | # sleep(5) 83 | # 84 | # while len(getFileNames(f'{current_path}/Assets/Intros')) == 0: 85 | # print(f"No intro videos in directory: '{current_path}/Assets/Intros'. Please add some!") 86 | # sleep(5) 87 | # 88 | # while len(getFileNames(f'{current_path}/Assets/Intervals')) == 0: 89 | # print(f"No intro videos in directory: '{current_path}/Assets/Intervals'. Please add some!") 90 | # sleep(5) 91 | # 92 | #init() 93 | -------------------------------------------------------------------------------- /TikTok Client/scriptwrapper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import math 4 | import datetime 5 | current_path = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | 8 | 9 | class TwitchVideo(): 10 | def __init__(self, scriptwrapper): 11 | self.scriptWrapper = scriptwrapper 12 | self.final_clips = None 13 | 14 | 15 | class DownloadedTwitchClipWrapper(): 16 | def __init__(self, id, author_name, clip_title, mp4name, vid_duration, diggCount, shareCount, playCount, commentCount): 17 | 18 | self.id = id 19 | self.author_name = author_name 20 | self.mp4 = mp4name 21 | self.clip_name = clip_title 22 | self.vid_duration = vid_duration 23 | self.upload = False 24 | self.isIntro = False 25 | self.isOutro = False 26 | self.isInterval = False 27 | self.isUsed = False 28 | self.audio = 1 29 | self.diggCount = diggCount 30 | self.shareCount = shareCount 31 | self.playCount = playCount 32 | self.commentCount = commentCount 33 | #Getting duration of video clips to trim a percentage of the beginning off 34 | 35 | 36 | 37 | class ScriptWrapper(): 38 | def __init__(self, script): 39 | self.rawScript = script 40 | self.scriptMap = [] 41 | self.setupScriptMap() 42 | 43 | 44 | def addClipAtStart(self, clip): 45 | self.rawScript = [clip] + self.rawScript 46 | self.scriptMap = [True] + self.scriptMap 47 | 48 | 49 | def addScriptWrapper(self, scriptwrapper): 50 | self.rawScript = self.rawScript + scriptwrapper.rawScript 51 | self.scriptMap = self.scriptMap + scriptwrapper.scriptMap 52 | 53 | 54 | def moveDown(self, i): 55 | if i > 0: 56 | copy1 = self.scriptMap[i-1] 57 | copy2 = self.rawScript[i-1] 58 | 59 | self.scriptMap[i-1] = self.scriptMap[i] 60 | self.rawScript[i-1] = self.rawScript[i] 61 | 62 | self.scriptMap[i] = copy1 63 | self.rawScript[i] = copy2 64 | else: 65 | print("already at bottom!") 66 | 67 | def moveUp(self, i): 68 | if i < len(self.scriptMap) - 1: 69 | copy1 = self.scriptMap[i+1] 70 | copy2 = self.rawScript[i+1] 71 | 72 | self.scriptMap[i+1] = self.scriptMap[i] 73 | self.rawScript[i+1] = self.rawScript[i] 74 | 75 | self.scriptMap[i] = copy1 76 | self.rawScript[i] = copy2 77 | else: 78 | print("already at top!") 79 | 80 | def setupScriptMap(self): 81 | for mainComment in self.rawScript: 82 | line = False 83 | self.scriptMap.append(line) 84 | 85 | 86 | def keep(self, mainCommentIndex): 87 | self.scriptMap[mainCommentIndex] = True 88 | 89 | def skip(self, mainCommentIndex): 90 | self.scriptMap[mainCommentIndex] = False 91 | 92 | def setCommentStart(self, x, start): 93 | self.rawScript[x].start_cut = start 94 | 95 | def setCommentEnd(self, x, end): 96 | self.rawScript[x].end_cut = end 97 | 98 | def setCommentAudio(self, x, audio): 99 | self.rawScript[x].audio = audio 100 | 101 | def getCommentData(self, x, y): 102 | return self.rawScript[x][y] 103 | 104 | def getCommentAmount(self): 105 | return len(self.scriptMap) 106 | 107 | def getEditedCommentThreadsAmount(self): 108 | return len([commentThread for commentThread in self.scriptMap if commentThread[0] is True]) 109 | 110 | def getEditedCommentAmount(self): 111 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 112 | count = 0 113 | for commentThread in commentThreads: 114 | for comment in commentThread: 115 | if comment is True: 116 | count += 1 117 | return count 118 | 119 | def getEditedWordCount(self): 120 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 121 | word_count = 0 122 | for x, commentThread in enumerate(commentThreads): 123 | for y, comment in enumerate(commentThread): 124 | if comment is True: 125 | word_count += len(self.rawScript[x][y].text.split(" ")) 126 | return word_count 127 | 128 | def getEditedCharacterCount(self): 129 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 130 | word_count = 0 131 | for x, commentThread in enumerate(commentThreads): 132 | for y, comment in enumerate(commentThread): 133 | if comment is True: 134 | word_count += len(self.rawScript[x][y].text) 135 | return word_count 136 | 137 | 138 | def getCommentInformation(self, x): 139 | return self.rawScript[x] 140 | 141 | 142 | def getKeptClips(self): 143 | final_script = [] 144 | for i, clip in enumerate(self.scriptMap): 145 | if clip: 146 | final_script.append(self.rawScript[i]) 147 | return final_script 148 | 149 | 150 | def getFinalClips(self): 151 | final_script = [] 152 | for i, clip in enumerate(self.scriptMap): 153 | clipwrapper = self.rawScript[i] 154 | clipwrapper.isUsed = clip 155 | final_script.append(self.rawScript[i]) 156 | return final_script 157 | 158 | 159 | def getEstimatedVideoTime(self): 160 | time = 0 161 | for i, comment in enumerate(self.scriptMap): 162 | if comment is True: 163 | time += round(self.rawScript[i].vid_duration, 1) 164 | obj = datetime.timedelta(seconds=math.ceil(time)) 165 | return obj 166 | -------------------------------------------------------------------------------- /TikTok Client/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | from sys import platform 4 | 5 | currentPath = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | 8 | 9 | address = "127.0.0.1" 10 | FTP_PORT = 2121 11 | HTTP_PORT = 8000 12 | 13 | FTP_USER = "Tom" 14 | FTP_PASSWORD = "password" 15 | 16 | autoLogin = False 17 | 18 | block_size = 262144 19 | 20 | config = configparser.ConfigParser() 21 | 22 | configpath = None 23 | 24 | if platform == "linux" or platform == "linux2" or platform == "darwin": 25 | configpath = "%s/config.ini" % currentPath 26 | else: 27 | configpath = "%s\\config.ini" % currentPath 28 | 29 | enforceInterval = True 30 | enforceIntro = True 31 | enforceOutro = True 32 | enforceFirstClip = True 33 | 34 | def generateConfigFile(): 35 | if not os.path.isfile(configpath): 36 | print("Could not find config file in location %s, creating a new one" % configpath) 37 | config.add_section("server_location") 38 | config.set("server_location", 'address', '127.0.0.1') 39 | config.set("server_location", 'server_http_port', '8000') 40 | config.set("server_location", 'server_ftp_port', '2121') 41 | config.add_section("auto_login") 42 | config.set("auto_login", 'username', '') 43 | config.set("auto_login", 'password', '') 44 | config.set("auto_login", 'auto_login', 'False') 45 | config.add_section("video_settings") 46 | config.set("video_settings", "enforce_interval", "True") 47 | config.set("video_settings", "enforce_intro", "True") 48 | config.set("video_settings", "enforce_outro", "True") 49 | config.set("video_settings", "enforce_firstclip", "True") 50 | 51 | 52 | with open(configpath, 'w') as configfile: 53 | config.write(configfile) 54 | else: 55 | print("Found config in location %s" % configpath) 56 | loadValues() 57 | 58 | def loadValues(): 59 | global FTP_USER, FTP_PASSWORD, FTP_PORT, HTTP_PORT, address, autoLogin, \ 60 | enforceFirstClip, enforceInterval, enforceIntro, enforceOutro 61 | config = configparser.ConfigParser() 62 | config.read(configpath) 63 | address = config.get('server_location', 'address') 64 | HTTP_PORT = config.getint('server_location', 'server_http_port') 65 | FTP_PORT = config.getint('server_location', 'server_ftp_port') 66 | FTP_USER = config.get('auto_login', 'username') 67 | FTP_PASSWORD = config.get('auto_login', 'password') 68 | autoLogin = config.getboolean('auto_login', 'auto_login') 69 | 70 | enforceInterval = config.getboolean('video_settings', 'enforce_interval') 71 | enforceIntro = config.getboolean('video_settings', 'enforce_intro') 72 | enforceOutro = config.getboolean('video_settings', 'enforce_outro') 73 | enforceFirstClip = config.getboolean('video_settings', 'enforce_firstclip') 74 | -------------------------------------------------------------------------------- /TikTok Server/Assets/tiktoklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/405bef46cbc7d8f949b4beecce64c62b4d583667/TikTok Server/Assets/tiktoklogo.png -------------------------------------------------------------------------------- /TikTok Server/UI/clipTemplateHandler.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | downloadClips 4 | 5 | 6 | 7 | 0 8 | 0 9 | 505 10 | 358 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 255 20 | 255 21 | 255 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 29 | 29 30 | 29 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 38 | 43 39 | 43 40 | 41 | 42 | 43 | 44 | 45 | 46 | 36 47 | 36 48 | 36 49 | 50 | 51 | 52 | 53 | 54 | 55 | 14 56 | 14 57 | 14 58 | 59 | 60 | 61 | 62 | 63 | 64 | 19 65 | 19 66 | 19 67 | 68 | 69 | 70 | 71 | 72 | 73 | 0 74 | 0 75 | 255 76 | 77 | 78 | 79 | 80 | 81 | 82 | 255 83 | 255 84 | 255 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 150 101 | 150 102 | 150 103 | 104 | 105 | 106 | 107 | 108 | 109 | 29 110 | 29 111 | 29 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 122 | 123 | 124 | 125 | 126 | 127 | 14 128 | 14 129 | 14 130 | 131 | 132 | 133 | 134 | 135 | 136 | 255 137 | 255 138 | 220 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 255 157 | 255 158 | 255 159 | 160 | 161 | 162 | 163 | 164 | 165 | 29 166 | 29 167 | 29 168 | 169 | 170 | 171 | 172 | 173 | 174 | 43 175 | 43 176 | 43 177 | 178 | 179 | 180 | 181 | 182 | 183 | 36 184 | 36 185 | 36 186 | 187 | 188 | 189 | 190 | 191 | 192 | 14 193 | 14 194 | 14 195 | 196 | 197 | 198 | 199 | 200 | 201 | 19 202 | 19 203 | 19 204 | 205 | 206 | 207 | 208 | 209 | 210 | 0 211 | 0 212 | 255 213 | 214 | 215 | 216 | 217 | 218 | 219 | 255 220 | 255 221 | 255 222 | 223 | 224 | 225 | 226 | 227 | 228 | 0 229 | 0 230 | 0 231 | 232 | 233 | 234 | 235 | 236 | 237 | 150 238 | 150 239 | 150 240 | 241 | 242 | 243 | 244 | 245 | 246 | 29 247 | 29 248 | 29 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 14 265 | 14 266 | 14 267 | 268 | 269 | 270 | 271 | 272 | 273 | 255 274 | 255 275 | 220 276 | 277 | 278 | 279 | 280 | 281 | 282 | 0 283 | 0 284 | 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 14 295 | 14 296 | 297 | 298 | 299 | 300 | 301 | 302 | 29 303 | 29 304 | 29 305 | 306 | 307 | 308 | 309 | 310 | 311 | 43 312 | 43 313 | 43 314 | 315 | 316 | 317 | 318 | 319 | 320 | 36 321 | 36 322 | 36 323 | 324 | 325 | 326 | 327 | 328 | 329 | 14 330 | 14 331 | 14 332 | 333 | 334 | 335 | 336 | 337 | 338 | 19 339 | 19 340 | 19 341 | 342 | 343 | 344 | 345 | 346 | 347 | 14 348 | 14 349 | 14 350 | 351 | 352 | 353 | 354 | 355 | 356 | 255 357 | 255 358 | 255 359 | 360 | 361 | 362 | 363 | 364 | 365 | 14 366 | 14 367 | 14 368 | 369 | 370 | 371 | 372 | 373 | 374 | 29 375 | 29 376 | 29 377 | 378 | 379 | 380 | 381 | 382 | 383 | 29 384 | 29 385 | 29 386 | 387 | 388 | 389 | 390 | 391 | 392 | 0 393 | 0 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 29 402 | 29 403 | 29 404 | 405 | 406 | 407 | 408 | 409 | 410 | 255 411 | 255 412 | 220 413 | 414 | 415 | 416 | 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 8 431 | 432 | 433 | 434 | Create Filter 435 | 436 | 437 | 438 | 439 | 440 | 188 441 | 290 442 | 121 443 | 41 444 | 445 | 446 | 447 | 448 | 10 449 | 450 | 451 | 452 | Create Filter 453 | 454 | 455 | 456 | 457 | 458 | 130 459 | 40 460 | 281 461 | 22 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 0 471 | 0 472 | 0 473 | 474 | 475 | 476 | 477 | 478 | 479 | 255 480 | 255 481 | 255 482 | 483 | 484 | 485 | 486 | 487 | 488 | 33 489 | 33 490 | 33 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 0 500 | 0 501 | 0 502 | 503 | 504 | 505 | 506 | 507 | 508 | 255 509 | 255 510 | 255 511 | 512 | 513 | 514 | 515 | 516 | 517 | 33 518 | 33 519 | 33 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 14 529 | 14 530 | 14 531 | 532 | 533 | 534 | 535 | 536 | 537 | 29 538 | 29 539 | 29 540 | 541 | 542 | 543 | 544 | 545 | 546 | 0 547 | 120 548 | 215 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | Trending 558 | 559 | 560 | 561 | 562 | Hashtag 563 | 564 | 565 | 566 | 567 | Author 568 | 569 | 570 | 571 | 572 | 573 | 574 | 130 575 | 20 576 | 191 577 | 16 578 | 579 | 580 | 581 | Get Clip By: 582 | 583 | 584 | 585 | 586 | 587 | 128 588 | 120 589 | 141 590 | 17 591 | 592 | 593 | 594 | Minimum Like Count 595 | 596 | 597 | false 598 | 599 | 600 | 601 | 602 | 603 | 278 604 | 120 605 | 113 606 | 20 607 | 608 | 609 | 610 | 10000 611 | 612 | 613 | 614 | 615 | 616 | 278 617 | 150 618 | 113 619 | 20 620 | 621 | 622 | 623 | 10000 624 | 625 | 626 | 627 | 628 | 629 | 128 630 | 150 631 | 141 632 | 17 633 | 634 | 635 | 636 | Minimum Share Count 637 | 638 | 639 | false 640 | 641 | 642 | 643 | 644 | 645 | 278 646 | 180 647 | 113 648 | 20 649 | 650 | 651 | 652 | 10000 653 | 654 | 655 | 656 | 657 | 658 | 128 659 | 180 660 | 141 661 | 17 662 | 663 | 664 | 665 | Minimum Play Count 666 | 667 | 668 | false 669 | 670 | 671 | 672 | 673 | 674 | 278 675 | 210 676 | 113 677 | 20 678 | 679 | 680 | 681 | 10000 682 | 683 | 684 | 685 | 686 | 687 | 128 688 | 210 689 | 141 690 | 17 691 | 692 | 693 | 694 | Minimum Comment Count 695 | 696 | 697 | false 698 | 699 | 700 | 701 | 702 | 703 | 130 704 | 250 705 | 261 706 | 20 707 | 708 | 709 | 710 | Filter Name 711 | 712 | 713 | 714 | 715 | 716 | 20 717 | 70 718 | 191 719 | 41 720 | 721 | 722 | 723 | Input Text 724 | 725 | 726 | true 727 | 728 | 729 | 730 | 731 | 732 | 240 733 | 80 734 | 151 735 | 20 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | -------------------------------------------------------------------------------- /TikTok Server/autodownloader.py: -------------------------------------------------------------------------------- 1 | import tiktok 2 | import database 3 | from time import sleep 4 | from threading import Thread 5 | 6 | class AutoDownloader(): 7 | def __init__(self, window, downloadqueue): 8 | self.window = window 9 | self.autoDownloadQueue = downloadqueue 10 | self.clipIndex = 0 11 | self.auto = False 12 | 13 | 14 | def startAutoMode(self): 15 | self.auto = True 16 | self.findClips() 17 | 18 | def startDownloading(self): 19 | self.downloadClips() 20 | 21 | 22 | def startFinding(self): 23 | self.findClips() 24 | 25 | 26 | def stop(self): 27 | tiktok.forceStop = True 28 | 29 | 30 | def findClips(self): 31 | if self.clipIndex == 0: 32 | self.window.start_clip_search.emit() 33 | 34 | if not self.clipIndex == len(self.autoDownloadQueue): 35 | # Thread(target=tiktok.getAllClips, args=(self.autoDownloadQueue[self.clipIndex], int(self.window.bulkFindAmount.text()), self.window)).start() 36 | amount = len(tiktok.getAllClips(self.autoDownloadQueue[self.clipIndex], int(self.window.bulkFindAmount.text()), self.window)) 37 | self.clipIndex += 1 38 | self.window.update_log_found_total_clips.emit(self.autoDownloadQueue[self.clipIndex-1][0], amount) 39 | else: 40 | self.clipIndex = 0 41 | self.window.end_find_search.emit() 42 | if self.auto: 43 | self.downloadClips() 44 | 45 | def downloadClips(self): 46 | if self.clipIndex == 0: 47 | self.window.start_download_search.emit() 48 | if not self.clipIndex == len(self.autoDownloadQueue): 49 | filter = self.autoDownloadQueue[self.clipIndex] 50 | clips = database.getFoundClips(filter[0], int(self.window.bulkDownloadAmount.text())) 51 | # Thread(target=tiktok.autoDownloadClips, args=(filter[0], clips, self.window)).start() 52 | tiktok.autoDownloadClips(filter[0], clips, self.window) 53 | self.clipIndex += 1 54 | self.window.update_done_downloading_game.emit(filter[0], len(clips)) 55 | 56 | else: 57 | self.clipIndex = 0 58 | self.window.end_download_search.emit() 59 | if self.auto: 60 | self.findClips() 61 | -------------------------------------------------------------------------------- /TikTok Server/autodownloaderUI.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 2 | from PyQt5 import QtWidgets 3 | from filtercreator import FilterCreationWindow 4 | from PyQt5.QtCore import * 5 | from PyQt5 import QtGui 6 | import scriptwrapper 7 | from PyQt5.QtMultimedia import QMediaPlayer, QMediaPlaylist, QMediaContent 8 | from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSignal, QPoint, QRect, QObject 9 | from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QVideoFrame, QAbstractVideoSurface, QAbstractVideoBuffer, QVideoSurfaceFormat 10 | from PyQt5.QtWidgets import * 11 | import autodownloader 12 | import server 13 | import database 14 | import pickle 15 | import tiktok 16 | import os 17 | from time import sleep 18 | from threading import Thread 19 | import settings 20 | import sys 21 | from PyQt5.QtGui import QIcon 22 | 23 | current_path = os.path.dirname(os.path.realpath(__file__)) 24 | 25 | 26 | def cleanDatabase(): 27 | clips = database.getClipsByStatus("DOWNLOADED") 28 | 29 | print("Checking %s clips for MP4s" % len(clips)) 30 | 31 | for i, clip in enumerate(clips): 32 | filePath = f"{settings.vid_filepath}/%s.mp4" % clip.mp4 33 | print(f"Checking if clip ({i + 1}/{len(clips)}) exists") 34 | if not os.path.exists(f"{settings.vid_filepath}/%s.mp4" % clip.mp4): 35 | print(f"Clip does not exist {filePath}") 36 | database.updateStatus(clip.id, "MISSING") 37 | 38 | def deleteClipsForFilter(filter): 39 | clips = database.getFilterClipsByStatus(filter, "DOWNLOADED") 40 | 41 | print("Attemping to delete all clips for %s (%s downloaded found)" % (filter, len(clips))) 42 | for i, clip in enumerate(clips): 43 | filePath = f"{settings.vid_filepath}/%s.mp4" % clip.mp4 44 | print(f"Checking if clip ({i + 1}/{len(clips)}) exists") 45 | if not os.path.exists(f"{settings.vid_filepath}/%s.mp4" % clip.mp4): 46 | print(f"Clip does not exist {filePath}") 47 | database.updateStatus(clip.id, "FOUND") 48 | else: 49 | os.remove(f"{settings.vid_filepath}/%s.mp4" % clip.mp4) 50 | print(f"Clip exists, deleting it {filePath}") 51 | database.updateStatus(clip.id, "FOUND") 52 | 53 | 54 | 55 | 56 | class PassiveDownloaderWindow(QMainWindow): 57 | update_log_found_clips = pyqtSignal(str, int, str) 58 | update_log_found_total_clips = pyqtSignal(str, int) 59 | update_log_start_downloading_game = pyqtSignal(str, int) 60 | update_log_downloaded_clip = pyqtSignal(int) 61 | update_done_downloading_game = pyqtSignal(str, int) 62 | 63 | start_clip_search = pyqtSignal() 64 | start_download_search = pyqtSignal() 65 | update_combo_box_filter = pyqtSignal() 66 | 67 | end_find_search = pyqtSignal() 68 | end_download_search = pyqtSignal() 69 | 70 | 71 | def __init__(self, clipEditorWindow = None): 72 | QtWidgets.QWidget.__init__(self) 73 | uic.loadUi(f"{current_path}/UI/clipPassiveDownload.ui", self) 74 | try: 75 | self.setWindowIcon(QIcon('Assets/tiktoklogo.png')) 76 | except Exception as e: 77 | pass 78 | self.autoDownloadQueue = [] 79 | self.autoWrapper = autodownloader.AutoDownloader(self, self.autoDownloadQueue) 80 | 81 | self.loadGameQueue() 82 | self.addFilter.clicked.connect(self.addFilterToQueue) 83 | self.clearFilters.clicked.connect(self.clearFilterQueue) 84 | 85 | self.startFinding.clicked.connect(self.startFindingProcess) 86 | self.stopFinding.clicked.connect(self.stopFindingProcess) 87 | self.startDownloading.clicked.connect(self.startDownloadingProcess) 88 | self.stopDownloading.clicked.connect(self.stopDownloadingProcess) 89 | self.refreshFilterClips.clicked.connect(self.logGetAmountClips) 90 | self.deleteClips.clicked.connect(self.deleteClipsByGame) 91 | self.updateStatus.clicked.connect(self.cleanDatabase) 92 | self.addNewFilter.clicked.connect(self.addFilterPopup) 93 | 94 | 95 | self.update_log_found_clips.connect(self.logAddClipFoundInfo) 96 | self.update_log_found_total_clips.connect(self.logAddTotalClipFoundInfo) 97 | self.update_log_start_downloading_game.connect(self.logStartDownloadFilterInfo) 98 | self.update_log_downloaded_clip.connect(self.updateProgressBar) 99 | self.update_done_downloading_game.connect(self.logDoneDownloadingFilterInfo) 100 | 101 | self.start_clip_search.connect(self.logStartClipSearchInfo) 102 | self.end_find_search.connect(self.logCompletedClipSearchInfo) 103 | self.end_download_search.connect(self.logCompletedDownloadInfo) 104 | self.start_download_search.connect(self.logStartDownloadInfo) 105 | 106 | self.update_combo_box_filter.connect(self.populateComboBox) 107 | 108 | self.startAuto.clicked.connect(self.startAutoProcess) 109 | self.stopAuto.clicked.connect(self.stopAutoProcess) 110 | self.addNewUser.clicked.connect(self.addNewFTPUser) 111 | self.removeUser.clicked.connect(self.deleteFTPUser) 112 | self.finishVidDirectory.clicked.connect(self.openFinishedVids) 113 | self.clipBinDirectory.clicked.connect(self.openClipBin) 114 | self.startAuto.setEnabled(False) 115 | 116 | self.populateComboBox() 117 | 118 | self.clipEditorWindow = clipEditorWindow 119 | self.clipFindIndex = 0 120 | self.updateAccountInfo() 121 | 122 | def closeEvent(self, evnt): 123 | sys.exit() 124 | 125 | def openFinishedVids(self): 126 | os.startfile(settings.final_video_path) 127 | 128 | def openClipBin(self): 129 | os.startfile(settings.vid_filepath) 130 | 131 | def addNewFTPUser(self): 132 | username = self.username.text() 133 | password = self.password.text() 134 | if username == "" or password == "": 135 | self.userAddStatus.setText("Please enter a password or username") 136 | elif username in [i[0] for i in server.usersList]: 137 | self.userAddStatus.setText("Account already exists with this name!") 138 | else: 139 | self.userAddStatus.setText("Successfully added new user %s" % username) 140 | server.usersList.append((username, password)) 141 | self.updateAccountInfo() 142 | server.saveUsersTable() 143 | 144 | def deleteFTPUser(self): 145 | toRemove = self.userToRemove.currentText() 146 | index = [i for i in range(len(server.usersList)) if server.usersList[i][0] == toRemove] 147 | if toRemove: 148 | del server.usersList[index[0]] 149 | print("Successfully deleted user %s" % toRemove) 150 | else: 151 | print("Couldn't delete user %s" % toRemove) 152 | self.updateAccountInfo() 153 | server.saveUsersTable() 154 | 155 | 156 | 157 | def updateAccountInfo(self): 158 | self.accountInfo.clear() 159 | for user in server.usersList: 160 | username = user[0] 161 | password = user[1] 162 | 163 | self.accountInfo.append("User %s, password %s" % (username, password)) 164 | self.populateRemoveUserList() 165 | 166 | def populateRemoveUserList(self): 167 | self.userToRemove.clear() 168 | users = [] 169 | for user in server.usersList: 170 | if user[0] == settings.videoGeneratorFTPUser: 171 | continue 172 | users.append(user[0]) 173 | self.userToRemove.addItems(users) 174 | 175 | 176 | def deleteClipsByGame(self): 177 | game = self.gameSelectToDelete.currentText() 178 | deleteClipsForFilter(game) 179 | 180 | def cleanDatabase(self): 181 | cleanDatabase() 182 | 183 | 184 | def addFilterPopup(self): 185 | 186 | self.filter_window = FilterCreationWindow(self) 187 | self.filter_window.show() 188 | 189 | def populateComboBox(self): 190 | self.filterSelect.clear() 191 | self.gameSelectToDelete.clear() 192 | filters = [] 193 | saved_filters = database.getFilterNames() 194 | for filter in saved_filters: 195 | filters.append(filter) 196 | self.filterSelect.addItems(filters) 197 | self.gameSelectToDelete.addItems(filters) 198 | 199 | 200 | def startFindingProcess(self): 201 | self.refreshFilterClips.setEnabled(False) 202 | self.addFilter.setEnabled(False) 203 | self.clearFilters.setEnabled(False) 204 | self.startFinding.setEnabled(False) 205 | self.stopFinding.setEnabled(True) 206 | self.startAuto.setEnabled(False) 207 | self.stopAuto.setEnabled(False) 208 | 209 | self.autoWrapper.findClips() 210 | pass 211 | 212 | def stopFindingProcess(self): 213 | self.startFinding.setEnabled(True) 214 | self.startFinding.setEnabled(True) 215 | self.stopFinding.setEnabled(False) 216 | # self.startAuto.setEnabled(True) 217 | self.startAuto.setEnabled(False) 218 | self.stopAuto.setEnabled(False) 219 | self.autoWrapper.stop() 220 | pass 221 | 222 | def startDownloadingProcess(self): 223 | self.refreshFilterClips.setEnabled(False) 224 | self.addFilter.setEnabled(False) 225 | self.clearFilters.setEnabled(False) 226 | self.stopFinding.setEnabled(False) 227 | self.startAuto.setEnabled(False) 228 | self.stopAuto.setEnabled(False) 229 | self.startFinding.setEnabled(False) 230 | self.startDownloading.setEnabled(False) 231 | self.stopDownloading.setEnabled(True) 232 | 233 | self.autoWrapper.downloadClips() 234 | pass 235 | 236 | def stopDownloadingProcess(self): 237 | self.refreshFilterClips.setEnabled(True) 238 | self.addFilter.setEnabled(True) 239 | self.clearFilters.setEnabled(True) 240 | self.stopFinding.setEnabled(False) 241 | # self.startAuto.setEnabled(True) 242 | self.startAuto.setEnabled(False) 243 | self.stopAuto.setEnabled(False) 244 | self.startFinding.setEnabled(True) 245 | self.startDownloading.setEnabled(True) 246 | self.stopDownloading.setEnabled(False) 247 | 248 | self.autoWrapper.stop() 249 | pass 250 | 251 | 252 | def startAutoProcess(self): 253 | self.refreshFilterClips.setEnabled(False) 254 | self.addFilter.setEnabled(False) 255 | self.clearFilters.setEnabled(False) 256 | self.stopFinding.setEnabled(False) 257 | self.startFinding.setEnabled(True) 258 | self.startAuto.setEnabled(False) 259 | self.stopAuto.setEnabled(True) 260 | self.startFinding.setEnabled(False) 261 | self.startDownloading.setEnabled(False) 262 | 263 | self.autoWrapper.auto = True 264 | self.autoWrapper.startAutoMode() 265 | pass 266 | 267 | def stopAutoProcess(self): 268 | self.autoWrapper.auto = False 269 | self.autoWrapper.stop() 270 | 271 | def loadGameQueue(self): 272 | try: 273 | with open(f'{current_path}/autodownloaderfilters.save', 'rb') as pickle_file: 274 | self.autoDownloadQueue = pickle.load(pickle_file) 275 | self.autoWrapper.autoDownloadQueue = self.autoDownloadQueue 276 | except Exception: 277 | pass 278 | self.updateAutoDownloadQueue() 279 | self.logGetAmountClips() 280 | 281 | def addFilterToQueue(self): 282 | self.clearFilterQueue() 283 | filter = self.filterSelect.currentText() 284 | if filter not in [tempfilter[0] for tempfilter in self.autoDownloadQueue]: 285 | filterObject = database.getSavedFilterByName(filter) 286 | self.autoDownloadQueue.append([filter, filterObject]) 287 | 288 | with open(f'{current_path}/autodownloaderfilters.save', 'wb') as pickle_file: 289 | pickle.dump(self.autoDownloadQueue, pickle_file) 290 | 291 | self.autoWrapper.autoDownloadQueue = self.autoDownloadQueue 292 | self.logGetAmountClips() 293 | self.updateAutoDownloadQueue() 294 | 295 | def clearFilterQueue(self): 296 | self.autoDownloadQueue.clear() 297 | self.autoWrapper.autoDownloadQueue.clear() 298 | self.updateAutoDownloadQueue() 299 | 300 | def updateAutoDownloadQueue(self): 301 | self.autoDownloadInfo.clear() 302 | for filter in self.autoDownloadQueue: 303 | self.autoDownloadInfo.append(filter[0]) 304 | 305 | 306 | def logStartClipSearchInfo(self): # called in autodownloader 307 | self.downloadLog.append("Starting Clip Search for %s filters" % len(self.autoDownloadQueue)) 308 | 309 | def logAddClipFoundInfo(self, game_name, amount, period): # called in twitch 310 | self.downloadLog.append("Found %s for filter %s for period %s" % (amount, game_name, period)) 311 | 312 | def logAddTotalClipFoundInfo(self, game_name, amount): # called in twitch 313 | self.downloadLog.append("Found %s for filter %s" % (amount, game_name)) 314 | self.autoWrapper.findClips() 315 | 316 | def logCompletedClipSearchInfo(self): # called in autodownloader 317 | self.downloadLog.append("Completed clip search for %s filters" % len(self.autoDownloadQueue)) 318 | self.logGetAmountClips() 319 | self.refreshFilterClips.setEnabled(True) 320 | self.addFilter.setEnabled(True) 321 | self.clearFilters.setEnabled(True) 322 | self.startFinding.setEnabled(True) 323 | self.stopFinding.setEnabled(False) 324 | self.stopAuto.setEnabled(False) 325 | # self.startAuto.setEnabled(True) 326 | self.startAuto.setEnabled(False) 327 | 328 | 329 | def logGetAmountClips(self): # called here 330 | self.clipBinInformation.clear() 331 | filters = database.getAllSavedFilters() # get all saved filters 332 | total_downloaded = 0 333 | for filter in [i[0] for i in filters]: 334 | amount = database.getFilterClipCount(filter)[0][0] 335 | amount_downloaded = database.getFilterClipCountByStatus(filter, "DOWNLOADED")[0][0] 336 | amount_used = database.getFilterClipCountByStatus(filter, "USED")[0][0] 337 | total_downloaded += amount_downloaded 338 | self.clipBinInformation.append("Filter: %s amount clips %s (downloaded %s | used %s)" % (filter, amount, amount_downloaded, amount_used)) 339 | self.clipBinInformation.append("Total Downloaded: %s" % total_downloaded) 340 | 341 | def logStartDownloadInfo(self): # called in autodownloader 342 | self.downloadLog.append("Starting downloads for %s filters" % len(self.autoDownloadQueue)) 343 | 344 | def logStartDownloadFilterInfo(self, filter, amount): # called tiktok 345 | self.progressBar.setValue(0) 346 | self.progressBar.setMaximum(amount) 347 | self.downloadLog.append("Downloading %s clips for filter %s" % (amount, filter)) 348 | self.currentDownloadFilter.setText("Current Filter: %s" % filter) 349 | self.amountCurrentPass.setText("Amount in current pass: %s" % amount) 350 | 351 | def logDoneDownloadingFilterInfo(self, filter, amount): # called in tiktok 352 | self.downloadLog.append("Finished downloading %s clips for filter %s" % (amount, filter)) 353 | self.autoWrapper.downloadClips() 354 | 355 | def logCompletedDownloadInfo(self): # called in autodownloader 356 | self.downloadLog.append("Completed downloading %s clips" % len(self.autoDownloadQueue)) 357 | self.logGetAmountClips() 358 | self.refreshFilterClips.setEnabled(True) 359 | self.addFilter.setEnabled(True) 360 | self.clearFilters.setEnabled(True) 361 | self.startFinding.setEnabled(True) 362 | self.startDownloading.setEnabled(True) 363 | self.stopDownloading.setEnabled(False) 364 | self.stopFinding.setEnabled(False) 365 | 366 | def updateProgressBar(self, number): # called in twitch 367 | self.progressBar.setValue(number) 368 | self.downloadProgressAmount.setText("Download progress: %s" % number) 369 | 370 | 371 | -------------------------------------------------------------------------------- /TikTok Server/config.ini: -------------------------------------------------------------------------------- 1 | [server_details] 2 | address = 127.0.0.1 3 | http_port = 8000 4 | ftp_port = 2121 5 | 6 | [video_generator_location] 7 | address = 127.0.0.1 8 | http_port = 8001 9 | ftp_port = 2122 10 | ftp_user = VidGen 11 | ftp_password = password 12 | 13 | [tiktok] 14 | language = en 15 | s_v_web_id = 16 | tt_webid = 17 | 18 | [mysql_database] 19 | databasehost = localhost 20 | databaseuser = root 21 | databasepassword = 22 | 23 | 24 | -------------------------------------------------------------------------------- /TikTok Server/database.py: -------------------------------------------------------------------------------- 1 | import mysql.connector 2 | from mysql.connector import pooling 3 | from datetime import date 4 | import pickle 5 | import settings 6 | current_date = date.today() 7 | connection_pool = None 8 | 9 | def startDatabase(): 10 | beginDatabaseConnection() 11 | initDatabase() 12 | 13 | def initDatabase(): 14 | global connection_pool 15 | connection_object = connection_pool.get_connection() 16 | cursor = connection_object.cursor() 17 | cursor.execute("SET sql_notes = 0; ") 18 | cursor.execute("CREATE SCHEMA IF NOT EXISTS `tiktokdb` ;") 19 | cursor.execute("USE tiktokdb;") 20 | cursor.execute("SET sql_notes = 0;") 21 | cursor.execute("set global max_allowed_packet=67108864;") 22 | cursor.execute("create table IF NOT EXISTS clip_bin (clip_num int NOT NULL AUTO_INCREMENT, PRIMARY KEY (clip_num), clip_id varchar(100), date varchar(40), status varchar(100), clipwrapper BLOB, filter_name varchar(70));") 23 | 24 | cursor.execute("create table IF NOT EXISTS filters (num int NOT NULL AUTO_INCREMENT, PRIMARY KEY (num), name varchar(70), filterwrapper BLOB);") 25 | cursor.execute("SET sql_notes = 1; ") 26 | 27 | def beginDatabaseConnection(): 28 | global connection_pool 29 | connection_pool = pooling.MySQLConnectionPool( 30 | pool_size=32, 31 | pool_reset_session=True, 32 | host=settings.databasehost, 33 | user=settings.databaseuser, 34 | passwd=settings.databasepassword, 35 | ) 36 | print("Started database connection") 37 | 38 | 39 | def addFoundClip(tiktokclip, filterName): 40 | global connection_pool 41 | connection_object = connection_pool.get_connection() 42 | cursor = connection_object.cursor() 43 | cursor.execute("USE tiktokdb;") 44 | 45 | id = tiktokclip.id 46 | clipblob = pickle.dumps(tiktokclip) 47 | query = "INSERT INTO clip_bin(clip_id, date, filter_name, status, clipwrapper) VALUES(%s, %s, %s, 'FOUND', %s);" 48 | args = (id, current_date, filterName, clipblob) 49 | 50 | cursor.execute(query, args) 51 | 52 | connection_object.commit() 53 | cursor.close() 54 | connection_object.close() 55 | 56 | def getFoundClips(filter, limit): 57 | global connection_pool 58 | connection_object = connection_pool.get_connection() 59 | cursor = connection_object.cursor() 60 | cursor.execute("USE tiktokdb;") 61 | 62 | query = "select * FROM clip_bin WHERE filter_name = %s and status = 'FOUND' LIMIT %s;" 63 | args = (filter,limit) 64 | 65 | cursor.execute(query, args) 66 | result = cursor.fetchall() 67 | results = [] 68 | for res in result: 69 | results.append(pickle.loads(res[4])) 70 | connection_object.commit() 71 | cursor.close() 72 | connection_object.close() 73 | return results 74 | 75 | 76 | def addFilter(filter_name, filterobject): 77 | global connection_pool 78 | connection_object = connection_pool.get_connection() 79 | cursor = connection_object.cursor() 80 | cursor.execute("USE tiktokdb;") 81 | query = f"INSERT INTO filters(`name`, `filterwrapper`) VALUES(%s, %s);" 82 | filterobjectdumped = pickle.dumps(filterobject) 83 | args = (filter_name, filterobjectdumped) 84 | cursor.execute(query, args) 85 | connection_object.commit() 86 | cursor.close() 87 | connection_object.close() 88 | 89 | def getAllSavedFilters(): 90 | connection_object = connection_pool.get_connection() 91 | cursor = connection_object.cursor() 92 | cursor.execute("USE tiktokdb;") 93 | query = "SELECT name, filterwrapper FROM filters;" 94 | cursor.execute(query) 95 | result = cursor.fetchall() 96 | results = [] 97 | for res in result: 98 | results.append([res[0], pickle.loads(res[1])]) 99 | cursor.close() 100 | connection_object.close() 101 | return results 102 | 103 | def getSavedFilterByName(filterName): 104 | connection_object = connection_pool.get_connection() 105 | cursor = connection_object.cursor() 106 | cursor.execute("USE tiktokdb;") 107 | query = "SELECT filterwrapper FROM filters WHERE name = %s;" 108 | args = (filterName,) 109 | cursor.execute(query, args) 110 | result = cursor.fetchall() 111 | results = pickle.loads(result[0][0]) 112 | cursor.close() 113 | connection_object.close() 114 | return results 115 | 116 | def getFilterNames(): 117 | connection_object = connection_pool.get_connection() 118 | cursor = connection_object.cursor() 119 | cursor.execute("USE tiktokdb;") 120 | query = "SELECT name FROM filters;" 121 | cursor.execute(query) 122 | result = cursor.fetchall() 123 | results = [] 124 | for res in result: 125 | results.append(res[0]) 126 | cursor.close() 127 | connection_object.close() 128 | return results 129 | 130 | def getFilterClipCount(filter): 131 | connection_object = connection_pool.get_connection() 132 | cursor = connection_object.cursor() 133 | cursor.execute("USE tiktokdb;") 134 | query = "SELECT COUNT(*) FROM clip_bin WHERE filter_name = %s" 135 | args = (filter,) 136 | cursor.execute(query, args) 137 | result = cursor.fetchall() 138 | results = [] 139 | for res in result: 140 | results.append(res) 141 | cursor.close() 142 | connection_object.close() 143 | return results 144 | 145 | def getFilterClipCountByStatus(filter,status): 146 | connection_object = connection_pool.get_connection() 147 | cursor = connection_object.cursor() 148 | cursor.execute("USE tiktokdb;") 149 | query = "SELECT COUNT(*) FROM clip_bin WHERE filter_name = %s and status = %s" 150 | args = (filter,status) 151 | cursor.execute(query, args) 152 | result = cursor.fetchall() 153 | results = [] 154 | for res in result: 155 | results.append(res) 156 | cursor.close() 157 | connection_object.close() 158 | return results 159 | 160 | def getFilterClipsByStatusLimit(filterName, status, limit): 161 | connection_object = connection_pool.get_connection() 162 | cursor = connection_object.cursor() 163 | cursor.execute("USE tiktokdb;") 164 | query = "SELECT * FROM clip_bin WHERE filter_name = %s and status = %s LIMIT %s;" 165 | args = (filterName,status, limit) 166 | cursor.execute(query, args) 167 | result = cursor.fetchall() 168 | results = [] 169 | for res in result: 170 | results.append(pickle.loads(res[4])) 171 | cursor.close() 172 | connection_object.close() 173 | return results 174 | 175 | 176 | def geClipsByStatusWithoutIds(filterName, status, limit, idlist): 177 | connection_object = connection_pool.get_connection() 178 | cursor = connection_object.cursor() 179 | cursor.execute("USE tiktokdb;") 180 | format_strings = ','.join(["%s"] * len(idlist)) 181 | 182 | query = f"SELECT * FROM clip_bin WHERE filter_name = '{filterName}' and status = '{status}'" \ 183 | f" and clip_id not in ({format_strings})" \ 184 | f" LIMIT {int(limit)};" 185 | 186 | cursor.execute(query, tuple(idlist)) 187 | result = cursor.fetchall() 188 | results = [] 189 | for res in result: 190 | results.append(pickle.loads(res[4])) 191 | cursor.close() 192 | connection_object.close() 193 | return results 194 | 195 | 196 | def getClipById(id): 197 | connection_object = connection_pool.get_connection() 198 | cursor = connection_object.cursor() 199 | cursor.execute("USE tiktokdb;") 200 | query = "SELECT clipwrapper FROM clip_bin WHERE clip_id = %s;" 201 | args = (id, ) 202 | cursor.execute(query, args) 203 | result = cursor.fetchall() 204 | results = [] 205 | for res in result: 206 | results.append(pickle.loads(res[0])) 207 | cursor.close() 208 | connection_object.close() 209 | return results[0] 210 | 211 | def getClipsByStatus(status): 212 | connection_object = connection_pool.get_connection() 213 | cursor = connection_object.cursor() 214 | cursor.execute("USE tiktokdb;") 215 | query = "SELECT clipwrapper FROM clip_bin WHERE status = %s;" 216 | args = (status, ) 217 | cursor.execute(query, args) 218 | result = cursor.fetchall() 219 | results = [] 220 | for res in result: 221 | results.append(pickle.loads(res[0])) 222 | cursor.close() 223 | connection_object.close() 224 | return results 225 | 226 | def getFilterClipsByStatus(filterName, status): 227 | connection_object = connection_pool.get_connection() 228 | cursor = connection_object.cursor() 229 | cursor.execute("USE tiktokdb;") 230 | query = "SELECT clipwrapper FROM clip_bin WHERE status = %s and filter_name=%s;" 231 | args = (status, filterName) 232 | cursor.execute(query, args) 233 | result = cursor.fetchall() 234 | results = [] 235 | for res in result: 236 | results.append(pickle.loads(res[0])) 237 | cursor.close() 238 | connection_object.close() 239 | return results 240 | 241 | 242 | def getAllSavedClipIDs(): 243 | connection_object = connection_pool.get_connection() 244 | cursor = connection_object.cursor() 245 | cursor.execute("USE tiktokdb;") 246 | query = "SELECT clip_id FROM clip_bin;" 247 | cursor.execute(query) 248 | result = cursor.fetchall() 249 | results = [] 250 | for res in result: 251 | results.append(res) 252 | cursor.close() 253 | connection_object.close() 254 | return results 255 | 256 | def updateStatus(clip_id, status): 257 | connection_object = connection_pool.get_connection() 258 | cursor = connection_object.cursor() 259 | cursor.execute("USE tiktokdb;") 260 | query = "UPDATE clip_bin SET status = %s WHERE clip_id = %s;" 261 | args = (status, clip_id) 262 | cursor.execute(query, args) 263 | connection_object.commit() 264 | cursor.close() 265 | connection_object.close() 266 | 267 | def updateStatusWithClip(clip_id, status, clip): 268 | connection_object = connection_pool.get_connection() 269 | cursor = connection_object.cursor() 270 | cursor.execute("USE tiktokdb;") 271 | query = "UPDATE clip_bin SET status = %s, clipwrapper = %s WHERE clip_id = %s;" 272 | tiktokclip = pickle.dumps(clip) 273 | args = (status, tiktokclip, clip_id) 274 | cursor.execute(query, args) 275 | connection_object.commit() 276 | cursor.close() 277 | connection_object.close() 278 | 279 | -------------------------------------------------------------------------------- /TikTok Server/filtercreator.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 2 | from PyQt5 import QtWidgets 3 | from PyQt5.QtCore import * 4 | from PyQt5 import QtGui 5 | import scriptwrapper 6 | from PyQt5.QtMultimedia import QMediaPlayer, QMediaPlaylist, QMediaContent 7 | from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSignal, QPoint, QRect, QObject 8 | from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QVideoFrame, QAbstractVideoSurface, QAbstractVideoBuffer, QVideoSurfaceFormat 9 | from PyQt5.QtWidgets import * 10 | import database 11 | import os 12 | 13 | current_path = os.path.dirname(os.path.realpath(__file__)) 14 | 15 | class Filter(): 16 | def __init__(self, searchType, inputText, likeCount, shareCount, playCount, commentCount): 17 | 18 | # trending author hashtags 19 | self.searchType = searchType 20 | 21 | # string or list 22 | self.inputText = inputText 23 | 24 | self.likeCount = likeCount 25 | self.shareCount = shareCount 26 | self.playCount = playCount 27 | self.commentCount = commentCount 28 | 29 | 30 | 31 | class FilterCreationWindow(QMainWindow): 32 | # update_log_found_clips = pyqtSignal(str, int, str) 33 | 34 | 35 | 36 | def __init__(self, window): 37 | QtWidgets.QWidget.__init__(self) 38 | uic.loadUi(f"{current_path}/UI/clipTemplateHandler.ui", self) 39 | self.window = window 40 | self.likeFilter.stateChanged.connect(self.updateDisplay) 41 | self.shareFilter.stateChanged.connect(self.updateDisplay) 42 | self.amountFilter.stateChanged.connect(self.updateDisplay) 43 | self.commentFilter.stateChanged.connect(self.updateDisplay) 44 | self.createFilter.clicked.connect(self.attemptCreateFilter) 45 | self.category.currentTextChanged.connect(self.changeCategory) 46 | 47 | self.changeCategory() 48 | 49 | self.savedFilters = database.getFilterNames() 50 | 51 | 52 | def changeCategory(self): 53 | isTrending = True if self.category.currentText() == "Trending" else False 54 | isHashTag = True if self.category.currentText() == "Hashtag" else False 55 | isAuthor = True if self.category.currentText() == "Author" else False 56 | 57 | 58 | self.inputText.show() 59 | 60 | if isTrending: 61 | self.inputText.hide() 62 | self.inputTextLabel.setText("") 63 | 64 | if isHashTag: 65 | self.inputTextLabel.setText("Enter comma separated Hashtags (no #) e.g. memes, lol, funny") 66 | 67 | if isAuthor: 68 | self.inputTextLabel.setText("Enter comma separated author names e.g. charlidamelio, addisonre") 69 | 70 | 71 | def updateDisplay(self): 72 | 73 | useLikeFilter = True if self.likeFilter.isChecked() else False 74 | useShareFilter = True if self.shareFilter.isChecked() else False 75 | useAmountFilter = True if self.amountFilter.isChecked() else False 76 | useCommentFilter = True if self.commentFilter.isChecked() else False 77 | 78 | self.likeAmount.setEnabled(useLikeFilter) 79 | self.shareAmount.setEnabled(useShareFilter) 80 | self.playAmount.setEnabled(useAmountFilter) 81 | self.commentAmount.setEnabled(useCommentFilter) 82 | 83 | def attemptCreateFilter(self): 84 | 85 | useLikeFilter = True if self.likeFilter.isChecked() else False 86 | useShareFilter = True if self.shareFilter.isChecked() else False 87 | useAmountFilter = True if self.amountFilter.isChecked() else False 88 | useCommentFilter = True if self.commentFilter.isChecked() else False 89 | 90 | isTrending = True if self.category.currentText() == "Trending" else False 91 | isHashTag = True if self.category.currentText() == "Hashtag" else False 92 | isAuthor = True if self.category.currentText() == "Author" else False 93 | 94 | filterType = self.category.currentText() 95 | inputText = None 96 | 97 | likeAmount = None 98 | shareAmount = None 99 | playAmount = None 100 | commentAmount = None 101 | 102 | try: 103 | if useLikeFilter: 104 | likeAmount = int(self.likeAmount.text()) 105 | if useShareFilter: 106 | shareAmount = int(self.shareAmount.text()) 107 | if useAmountFilter: 108 | playAmount = int(self.playAmount.text()) 109 | if useCommentFilter: 110 | commentAmount = int(self.commentAmount.text()) 111 | except Exception: 112 | QMessageBox.information(self, 'Could not create filter!', "Make sure that the filter inputs are all numbers!", QMessageBox.Ok) 113 | return 114 | 115 | if isHashTag or isAuthor: 116 | if self.inputText.text() == "" or self.inputText.text().replace(" ", "") == "": 117 | QMessageBox.information(self, '%s type specified but no input text!' % filterType, "Enter a value into the input box! Add commas if you want multiple!", QMessageBox.Ok) 118 | return 119 | else: 120 | inputText = self.inputText.text().replace(" ", "") 121 | inputText = inputText.split(",") 122 | 123 | filterName = self.filterName.text() 124 | 125 | if filterName == "" or filterName.replace(" ", "") == "": 126 | QMessageBox.information(self, 'Please enter a filter name!', "Filter name needed!", QMessageBox.Ok) 127 | return 128 | 129 | if filterName in self.savedFilters: 130 | QMessageBox.information(self, 'Filter already exists with name!', "Please use a different name for this filter! This name (%s) is already registered." % filterName, QMessageBox.Ok) 131 | return 132 | 133 | filter = Filter(filterType, inputText, likeAmount, shareAmount, playAmount, commentAmount) 134 | database.addFilter(filterName, filter) 135 | self.window.update_combo_box_filter.emit() 136 | self.close() 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /TikTok Server/main.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | from threading import Thread 3 | #import vidGen 4 | import server 5 | import autodownloaderUI 6 | import os 7 | import database 8 | import settings 9 | import sys 10 | 11 | 12 | 13 | class App(): 14 | def __init__(self): 15 | app = QtWidgets.QApplication(sys.argv) 16 | app.processEvents() 17 | 18 | #filter_window = FilterCreationWindow(self) 19 | #filter_window.show() 20 | 21 | autodownloader = autodownloaderUI.PassiveDownloaderWindow() 22 | autodownloader.show() 23 | 24 | 25 | 26 | Thread(target=server.VideoGeneratorCommunications).start() 27 | Thread(target=server.VideoGeneratorRenderStatus).start() 28 | 29 | 30 | 31 | sys.exit(app.exec_()) 32 | 33 | def init(): 34 | app = App() 35 | 36 | 37 | 38 | sys._excepthook = sys.excepthook 39 | def exception_hook(exctype, value, traceback): 40 | sys._excepthook(exctype, value, traceback) 41 | sys.exit(1) 42 | sys.excepthook = exception_hook 43 | 44 | 45 | def getFileNames(file_path): 46 | files = [os.path.splitext(filename)[0] for filename in os.listdir(file_path)] 47 | return files 48 | 49 | 50 | if __name__ == "__main__": 51 | 52 | 53 | 54 | current_directory = os.path.dirname(os.path.realpath(__file__)) 55 | os.chdir(current_directory) 56 | settings.generateConfigFile() 57 | if not os.path.exists("UploadedFiles"): 58 | os.mkdir("UploadedFiles") 59 | 60 | if not os.path.exists(settings.vid_filepath): 61 | os.mkdir(settings.vid_filepath) 62 | 63 | if not os.path.exists(settings.final_video_path): 64 | os.mkdir(settings.final_video_path) 65 | 66 | if not os.path.exists(settings.video_data_path): 67 | os.mkdir(settings.video_data_path) 68 | 69 | if not os.path.exists(settings.backup_path): 70 | os.mkdir(settings.backup_path) 71 | 72 | if not os.path.exists(settings.asset_file_path): 73 | os.mkdir(settings.asset_file_path) 74 | os.mkdir(f"{settings.asset_file_path}/Fonts") 75 | os.mkdir(f"{settings.asset_file_path}/Intervals") 76 | os.mkdir(f"{settings.asset_file_path}/Intros") 77 | os.mkdir(f"{settings.asset_file_path}/Music") 78 | 79 | database.startDatabase() 80 | server.init() 81 | 82 | App() 83 | 84 | -------------------------------------------------------------------------------- /TikTok Server/scriptwrapper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import math 4 | import datetime 5 | import pickle 6 | import database 7 | import random 8 | import settings 9 | current_path = os.path.dirname(os.path.realpath(__file__)) 10 | 11 | 12 | def reformatPartialJson(videojson): 13 | final_clips = [] 14 | 15 | clips = videojson["clips"] 16 | name = videojson["name"] 17 | 18 | intervalClip = None 19 | outroClip = None 20 | for clip in clips: 21 | id = clip["id"] 22 | 23 | isUpload = clip["isUpload"] 24 | isIntro = clip["isIntro"] 25 | isOutro = clip["isOutro"] 26 | uploadMp4 = clip["mp4"] 27 | used = clip["keep"] 28 | isInterval = clip["isInterval"] 29 | 30 | 31 | 32 | if not used: 33 | mp4path = "%s/%s.mp4" % (settings.vid_filepath, uploadMp4) 34 | print("Clip %s is not used, deleting" % mp4path) 35 | os.remove(mp4path) 36 | 37 | if not isUpload: 38 | oldwrapper = database.getClipById(id) 39 | database.updateStatus(id, "USED") 40 | clip["author_name"] = oldwrapper.author_name 41 | final_clips.append(clip) 42 | else: 43 | streamer_name = "" 44 | title = "" 45 | 46 | if not isIntro: 47 | name = len(uploadMp4.split("/")) 48 | new_name = (uploadMp4.split("/")[name-1]).replace(".mp4", "") 49 | 50 | channel_url = f"https://www.twitch.tv/{new_name}" 51 | streamer_name = new_name 52 | 53 | clip["author_name"] = streamer_name 54 | clip["title"] = title 55 | 56 | if isOutro: 57 | clip["author_name"] = "" 58 | outroClip = clip 59 | continue 60 | 61 | final_clips.append(clip) 62 | if isInterval: 63 | clip["author_name"] = "" 64 | intervalClip = clip 65 | if not isUpload and intervalClip is not None and not isIntro and used and not isOutro: 66 | final_clips.append(intervalClip) 67 | 68 | if outroClip is not None: 69 | final_clips.append(outroClip) 70 | 71 | #print(final_clips) 72 | videojson["clips"] = final_clips 73 | videojson["name"] = name 74 | #print(videojson) 75 | return videojson 76 | 77 | 78 | def createTwitchVideoFromJSON(videojson): 79 | final_clips = [] 80 | 81 | clips = videojson["clips"] 82 | 83 | 84 | for clip in clips: 85 | print(clip) 86 | id = clip["id"] 87 | audio = clip["audio"] 88 | used = clip["keep"] 89 | 90 | isUpload = clip["isUpload"] 91 | isIntro = clip["isIntro"] 92 | uploadMp4 = clip["mp4"] 93 | uploadDuration = clip["duration"] 94 | 95 | if not isUpload: 96 | oldwrapper = database.getClipById(id) 97 | oldwrapper.audio = audio 98 | oldwrapper.isUsed = used 99 | 100 | final_clips.append(oldwrapper) 101 | database.updateStatus(id, "USED") 102 | else: 103 | id = "na" 104 | url = "na" 105 | streamer_name = "na" 106 | title = "na" 107 | channel_url = "na" 108 | 109 | if not isIntro: 110 | name = len(uploadMp4.split("/")) 111 | new_name = (uploadMp4.split("/")[name-1]).replace(".mp4", "") 112 | 113 | channel_url = f"https://www.twitch.tv/{new_name}" 114 | streamer_name = new_name 115 | 116 | wrapper = ClipWrapper(id, url, streamer_name, title, channel_url) 117 | wrapper.mp4 = uploadMp4 118 | wrapper.vid_duration = uploadDuration 119 | wrapper.isIntro = isIntro 120 | wrapper.audio = audio 121 | wrapper.isUsed = used 122 | 123 | final_clips.append(wrapper) 124 | 125 | video = TikTokVideo(final_clips) 126 | return video 127 | 128 | 129 | def saveTwitchVideo(video): 130 | random_name = str(random.randint(0, 100000)) 131 | print(f'VideoData/vid{random_name}.save') 132 | with open(f'VideoData/vid{random_name}.save' 'wb') as pickle_file: 133 | pickle.dump(video, pickle_file) 134 | 135 | 136 | 137 | class TikTokVideo(): 138 | def __init__(self, clips): 139 | self.clips = clips 140 | 141 | 142 | class ClipWrapper(): 143 | 144 | def __init__(self, id, url, author_name, createTime, text, diggCount, shareCount, playCount, commentCount, duration): 145 | self.id = id 146 | self.url = url 147 | self.author_name = author_name 148 | self.audio = 1 149 | self.isUsed = False 150 | self.mp4 = "%s-%s" % (author_name, id) 151 | self.isIntro = False 152 | self.vid_duration = None 153 | self.createTime = createTime 154 | self.text = text 155 | self.diggCount = diggCount 156 | self.shareCount = shareCount 157 | self.playCount = playCount 158 | self.commentCount = commentCount 159 | self.estDuration = duration 160 | 161 | 162 | #Getting duration of video clips to trim a percentage of the beginning off 163 | 164 | 165 | 166 | 167 | class ScriptWrapper(): 168 | def __init__(self, script): 169 | self.rawScript = script 170 | self.scriptMap = [] 171 | self.setupScriptMap() 172 | 173 | 174 | def addClipAtStart(self, clip): 175 | self.rawScript = [clip] + self.rawScript 176 | self.scriptMap = [True] + self.scriptMap 177 | 178 | 179 | def addScriptWrapper(self, scriptwrapper): 180 | self.rawScript = self.rawScript + scriptwrapper.rawScript 181 | self.scriptMap = self.scriptMap + scriptwrapper.scriptMap 182 | 183 | 184 | def moveDown(self, i): 185 | if i > 0: 186 | copy1 = self.scriptMap[i-1] 187 | copy2 = self.rawScript[i-1] 188 | 189 | self.scriptMap[i-1] = self.scriptMap[i] 190 | self.rawScript[i-1] = self.rawScript[i] 191 | 192 | self.scriptMap[i] = copy1 193 | self.rawScript[i] = copy2 194 | else: 195 | print("already at bottom!") 196 | 197 | def moveUp(self, i): 198 | if i < len(self.scriptMap) - 1: 199 | copy1 = self.scriptMap[i+1] 200 | copy2 = self.rawScript[i+1] 201 | 202 | self.scriptMap[i+1] = self.scriptMap[i] 203 | self.rawScript[i+1] = self.rawScript[i] 204 | 205 | self.scriptMap[i] = copy1 206 | self.rawScript[i] = copy2 207 | else: 208 | print("already at top!") 209 | 210 | def setupScriptMap(self): 211 | for mainComment in self.rawScript: 212 | line = False 213 | self.scriptMap.append(line) 214 | 215 | 216 | def keep(self, mainCommentIndex): 217 | self.scriptMap[mainCommentIndex] = True 218 | 219 | def skip(self, mainCommentIndex): 220 | self.scriptMap[mainCommentIndex] = False 221 | 222 | def setCommentStart(self, x, start): 223 | self.rawScript[x].start_cut = start 224 | 225 | def setCommentEnd(self, x, end): 226 | self.rawScript[x].end_cut = end 227 | 228 | def getCommentData(self, x, y): 229 | return self.rawScript[x][y] 230 | 231 | def getCommentAmount(self): 232 | return len(self.scriptMap) 233 | 234 | def getEditedCommentThreadsAmount(self): 235 | return len([commentThread for commentThread in self.scriptMap if commentThread[0] is True]) 236 | 237 | def getEditedCommentAmount(self): 238 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 239 | count = 0 240 | for commentThread in commentThreads: 241 | for comment in commentThread: 242 | if comment is True: 243 | count += 1 244 | return count 245 | 246 | def getEditedWordCount(self): 247 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 248 | word_count = 0 249 | for x, commentThread in enumerate(commentThreads): 250 | for y, comment in enumerate(commentThread): 251 | if comment is True: 252 | word_count += len(self.rawScript[x][y].text.split(" ")) 253 | return word_count 254 | 255 | def getEditedCharacterCount(self): 256 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 257 | word_count = 0 258 | for x, commentThread in enumerate(commentThreads): 259 | for y, comment in enumerate(commentThread): 260 | if comment is True: 261 | word_count += len(self.rawScript[x][y].text) 262 | return word_count 263 | 264 | 265 | def getCommentInformation(self, x): 266 | return self.rawScript[x] 267 | 268 | 269 | def getKeptClips(self): 270 | final_script = [] 271 | for i, clip in enumerate(self.scriptMap): 272 | if clip: 273 | final_script.append(self.rawScript[i]) 274 | return final_script 275 | 276 | 277 | -------------------------------------------------------------------------------- /TikTok Server/server.py: -------------------------------------------------------------------------------- 1 | from pyftpdlib.authorizers import DummyAuthorizer 2 | from pyftpdlib.handlers import FTPHandler 3 | from pyftpdlib.servers import FTPServer 4 | from threading import Thread 5 | import http.server 6 | import socketserver 7 | import json 8 | import cgi 9 | import database 10 | import scriptwrapper 11 | from copy import deepcopy 12 | import random 13 | import pickle 14 | import os 15 | import settings 16 | import ftplib 17 | import requests 18 | from time import sleep 19 | import traceback, sys 20 | 21 | current_path = os.path.dirname(os.path.realpath(__file__)) 22 | 23 | usersList = [] 24 | 25 | max_progress = None 26 | current_progress = None 27 | render_message = None 28 | 29 | 30 | 31 | settings.generateConfigFile() 32 | vidgenhttpaddress = "%s:%s" % (settings.videoGeneratorAddress, settings.videoGeneratorHTTPPort) 33 | 34 | 35 | # The directory the FTP user will have full read/write access to. 36 | FTP_DIRECTORY = current_path 37 | 38 | 39 | def getGames(): 40 | filters = database.getAllSavedFilters() 41 | formatted = [] 42 | for filter in filters: 43 | formatted.append(filter[0]) 44 | return formatted 45 | 46 | 47 | def getClips(filter, amount): 48 | clips = database.getFilterClipsByStatusLimit(filter, "DOWNLOADED", amount) 49 | files = [] 50 | for clip in clips: 51 | info = {"id" : clip.id, "mp4" : clip.mp4, "author_name" : clip.author_name, "duration" : clip.estDuration, "clip_title" : clip.text, 52 | "diggCount" : clip.diggCount, "shareCount" : clip.shareCount, "playCount" : clip.playCount, "commentCount" : clip.commentCount} 53 | files.append(info) 54 | return files 55 | #print(files) 56 | 57 | def getClipsWithoutIds(game, amount, ids): 58 | clips = database.geClipsByStatusWithoutIds(game, "DOWNLOADED", amount, ids) 59 | files = [] 60 | for clip in clips: 61 | info = {"id" : clip.id, "mp4" : clip.mp4, "author_name" : clip.author_name, "duration" : clip.estDuration, "clip_title" : clip.text, 62 | "diggCount" : clip.diggCount, "shareCount" : clip.shareCount, "playCount" : clip.playCount, "commentCount" : clip.commentCount} 63 | files.append(info) 64 | return files 65 | 66 | def getFinishedVideosList(): 67 | finished = [] 68 | for file in os.listdir(settings.final_video_path): 69 | name = file.replace(".txt", "").replace(".mp4", "") 70 | if name not in finished: 71 | finished.append(name) 72 | 73 | return finished 74 | 75 | 76 | 77 | def uploadVideo(message): 78 | # just marks them as used clips in database 79 | new_json = scriptwrapper.reformatPartialJson(message) 80 | 81 | random_name = str(random.randint(0, 100000)) 82 | print(f'VideoData/vid{random_name}.save') 83 | with open(f'VideoData/vid{random_name}.json', 'w') as f: 84 | json.dump(new_json, f) 85 | print(new_json) 86 | return True 87 | 88 | 89 | 90 | def getFileNames(file_path): 91 | files = [os.path.splitext(filename)[0] for filename in os.listdir(file_path)] 92 | return files 93 | 94 | def sendVideoContentToVidGenerator(file): 95 | try: 96 | ftp = ftplib.FTP() 97 | ftp.connect(settings.videoGeneratorAddress, settings.videoGeneratorFTPPort) 98 | ftp.login(settings.videoGeneratorFTPUser, settings.videoGeneratorFTPPassword) 99 | ftp.cwd('Temp') 100 | folder_name = file.replace(".json", "") 101 | print("Sending %s to video generator" % folder_name) 102 | saveName = file 103 | try: 104 | ftp.mkd(folder_name) 105 | except ftplib.error_perm as e: 106 | print(e) 107 | ftp.cwd('/Temp/%s/' % folder_name) 108 | jsonFile = None 109 | jsonFileCopy = None 110 | with open(f'{settings.video_data_path}/{file}.json') as json_file: 111 | jsonFile = (json.load(json_file)) 112 | jsonFileCopy = deepcopy(jsonFile) 113 | 114 | kept_clips = [] 115 | for clip in jsonFile["clips"]: 116 | if clip["keep"]: 117 | mp4 = clip["mp4"] 118 | if ".mp4" not in mp4: 119 | mp4 = "%s/%s.mp4"%(settings.vid_filepath, mp4) 120 | file = open(mp4,'rb') 121 | name = len(mp4.split("/")) 122 | mp4name = mp4.split("/")[name-1] 123 | 124 | ftp.storbinary('STOR %s' % mp4name, file, blocksize=262144) 125 | file.close() 126 | clip["mp4"] = '/Temp/%s/%s' % (folder_name, mp4name) 127 | 128 | kept_clips.append(clip) 129 | 130 | jsonFile["clips"] = kept_clips 131 | jsonFile["vid_folder"] = folder_name 132 | r = requests.get(f'http://{vidgenhttpaddress}/sendscript', json=jsonFile, headers={'Accept-Encoding': None}) 133 | sucess = r.json()["received"] 134 | os.remove(settings.video_data_path + "/" + str(saveName) + ".json") 135 | print("Done sending %s" % folder_name) 136 | 137 | for clip in jsonFileCopy["clips"]: 138 | mp4 = clip["mp4"] 139 | try: 140 | if "UploadedFiles" in mp4: 141 | print("Deleting %s" % mp4) 142 | os.remove(mp4) 143 | else: 144 | print("deleting %s/%s.mp4" % (settings.vid_filepath, mp4)) 145 | os.remove("%s/%s.mp4" % (settings.vid_filepath, mp4)) 146 | except Exception as e: 147 | print("could not delete mp4 %s" % mp4) 148 | 149 | 150 | #print(clip["mp4"]) 151 | #print(clip["keep"]) 152 | #print(ftp.nlst()) 153 | except ConnectionRefusedError: 154 | print("Video Generator is offline") 155 | 156 | def VideoGeneratorCommunications(): 157 | while True: 158 | savedFiles = getFileNames(f'{settings.video_data_path}') 159 | saved_videos = [] 160 | for file in savedFiles: 161 | with open(f'{settings.video_data_path}/{file}.json') as json_file: 162 | saved_videos.append(json.load(json_file)) 163 | 164 | for name in savedFiles: 165 | sendVideoContentToVidGenerator(name) 166 | sleep(5) 167 | 168 | 169 | def VideoGeneratorRenderStatus(): 170 | global max_progress, current_progress, render_message 171 | while True: 172 | sleep(5) 173 | try: 174 | r = requests.get(f'http://{vidgenhttpaddress}/getrenderinfo', headers={'Accept-Encoding': None}) 175 | renderData = r.json() 176 | max_progress = renderData["max_progress"] 177 | current_progress = renderData["current_progress"] 178 | render_message = renderData["render_message"] 179 | 180 | except Exception: 181 | print("video generator offline") 182 | 183 | 184 | def startFTPServer(): 185 | authorizer = DummyAuthorizer() 186 | 187 | for user in usersList: 188 | authorizer.add_user(user[0], user[1], FTP_DIRECTORY, perm='elradfmw') 189 | 190 | 191 | 192 | handler = FTPHandler 193 | handler.authorizer = authorizer 194 | 195 | handler.banner = "pyftpdlib based ftpd ready." 196 | 197 | address = (settings.serveraddress, settings.FTP_PORT) 198 | server = FTPServer(address, handler) 199 | 200 | server.max_cons = 256 201 | server.max_cons_per_ip = 5 202 | 203 | server.serve_forever() 204 | 205 | 206 | class HTTPHandler(http.server.BaseHTTPRequestHandler): 207 | def _set_headers(self): 208 | self.send_response(200) 209 | self.send_header('Content-type', 'application/json') 210 | self.end_headers() 211 | 212 | def do_HEAD(self): 213 | self._set_headers() 214 | 215 | def do_GET(self): 216 | self._set_headers() 217 | try: 218 | if "/getgames" == self.path: 219 | games = getGames() 220 | self.wfile.write(json.dumps({'games': games}).encode()) 221 | elif "/getclips" == self.path: 222 | length = int(self.headers.get('content-length')) 223 | message = json.loads(self.rfile.read(length)) 224 | game = message["game"] 225 | amount = message["amount"] 226 | clips = getClips(game, amount) 227 | self.wfile.write(json.dumps({'clips': clips}).encode()) 228 | elif "/getclipswithoutids" == self.path: 229 | length = int(self.headers.get('content-length')) 230 | message = json.loads(self.rfile.read(length)) 231 | game = message["game"] 232 | amount = message["amount"] 233 | ids = message["ids"] 234 | clips = getClipsWithoutIds(game, amount, ids) 235 | self.wfile.write(json.dumps({'clips': clips}).encode()) 236 | elif "/uploadvideo" == self.path: 237 | length = int(self.headers.get('content-length')) 238 | message = json.loads(self.rfile.read(length)) 239 | success = uploadVideo(message) 240 | self.wfile.write(json.dumps({'upload_success': success}).encode()) 241 | elif "/getfinishedvideoslist" == self.path: 242 | finishedvideos = getFinishedVideosList() 243 | self.wfile.write(json.dumps({'videos': finishedvideos}).encode()) 244 | elif "/getrenderinfo" == self.path: 245 | 246 | 247 | render_data = {'max_progress': max_progress, 248 | "current_progress" : current_progress, 249 | "render_message" : render_message} 250 | self.wfile.write(json.dumps(render_data).encode()) 251 | except Exception as e: 252 | print(e) 253 | traceback.print_exc(file=sys.stdout) 254 | #self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}).encode()) 255 | 256 | # POST echoes the message adding a JSON field 257 | def do_POST(self): 258 | ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) 259 | 260 | # refuse to receive non-json content 261 | if ctype != 'application/json': 262 | self.send_response(400) 263 | self.end_headers() 264 | return 265 | 266 | # read the message and convert it into a python dictionary 267 | length = int(self.headers.getheader('content-length')) 268 | message = json.loads(self.rfile.read(length)) 269 | 270 | # add a property to the object, just to mess with data 271 | message['received'] = 'ok' 272 | 273 | # send the message back 274 | self._set_headers() 275 | self.wfile.write(json.dumps(message)) 276 | 277 | 278 | def startHTTPServer(): 279 | with socketserver.TCPServer((settings.serveraddress, settings.HTTP_PORT), HTTPHandler) as httpd: 280 | print("serving at port", settings.HTTP_PORT) 281 | httpd.serve_forever() 282 | 283 | 284 | def createDefaultUserTable(): 285 | return [(settings.videoGeneratorFTPUser, settings.videoGeneratorFTPPassword)] 286 | 287 | def saveUsersTable(): 288 | print(f'Saving users table') 289 | with open(f"{current_path}/usertable.save", 'wb') as pickle_file: 290 | pickle.dump(usersList, pickle_file) 291 | 292 | 293 | def init(): 294 | global usersList 295 | if not os.path.exists(f"{current_path}/usertable.save"): 296 | print("Didn't find users table creating new one.") 297 | usersList = createDefaultUserTable() 298 | saveUsersTable() 299 | else: 300 | with open(f'{current_path}/usertable.save', 'rb') as pickle_file: 301 | print("Found users table.") 302 | usersList = pickle.load(pickle_file) 303 | 304 | Thread(target=startFTPServer).start() 305 | Thread(target=startHTTPServer).start() 306 | 307 | 308 | -------------------------------------------------------------------------------- /TikTok Server/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | from sys import platform 4 | 5 | currentPath = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | vid_filepath = "VideoFiles" 8 | final_video_path = "FinalVideos" 9 | asset_file_path = "Assets" 10 | video_data_path = "VideoData" 11 | backup_path = "Backup" 12 | 13 | serveraddress = "127.0.0.1" 14 | # The port the FTP server will listen on. 15 | # This must be greater than 1023 unless you run this script as root. 16 | FTP_PORT = 2121 17 | HTTP_PORT = 8000 18 | 19 | 20 | temp_path = "Temp" 21 | 22 | first_clip_name = '' 23 | 24 | 25 | videoGeneratorAddress = "127.0.0.1" 26 | videoGeneratorFTPPort = 2122 27 | videoGeneratorHTTPPort = 8001 28 | videoGeneratorFTPUser = "VidGen" 29 | videoGeneratorFTPPassword = "password" 30 | 31 | 32 | databasehost = "localhost" 33 | databaseuser = "root" 34 | databasepassword = "" 35 | s_v_web_id = "" 36 | tt_webid = "" 37 | 38 | language = "en" 39 | 40 | config = configparser.RawConfigParser() 41 | 42 | configpath = None 43 | 44 | if platform == "linux" or platform == "linux2" or platform == "darwin": 45 | configpath = "%s/config.ini" % currentPath 46 | else: 47 | configpath = "%s\\config.ini" % currentPath 48 | 49 | def generateConfigFile(): 50 | if not os.path.isfile(configpath): 51 | print("Could not find config file in location %s, creating a new one" % configpath) 52 | config.add_section("server_details") 53 | config.set("server_details", 'address', '127.0.0.1') 54 | config.set("server_details", 'http_port', '8000') 55 | config.set("server_details", 'ftp_port', '2121') 56 | config.add_section("video_generator_location") 57 | config.set("video_generator_location", 'address', '127.0.0.1') 58 | config.set("video_generator_location", 'http_port', '8001') 59 | config.set("video_generator_location", 'ftp_port', '2122') 60 | config.set("video_generator_location", 'ftp_user', 'VidGen') 61 | config.set("video_generator_location", 'ftp_password', 'password') 62 | config.add_section("tiktok") 63 | config.set("tiktok", 'language', 'en') 64 | config.set("tiktok", 's_v_web_id', '') 65 | config.set("tiktok", 'tt_webid', '') 66 | config.add_section("mysql_database") 67 | config.set("mysql_database", 'databasehost', 'localhost') 68 | config.set("mysql_database", 'databaseuser', 'root') 69 | config.set("mysql_database", 'databasepassword', '') 70 | 71 | with open(configpath, 'w') as configfile: 72 | config.write(configfile) 73 | else: 74 | print("Found config in location %s" % configpath) 75 | loadValues() 76 | 77 | def loadValues(): 78 | global FTP_PORT, HTTP_PORT, serveraddress, videoGeneratorAddress, videoGeneratorFTPPort, videoGeneratorHTTPPort, videoGeneratorFTPUser,\ 79 | videoGeneratorFTPPassword, language, databasehost, databasepassword, databaseuser, s_v_web_id, tt_webid 80 | config = configparser.RawConfigParser() 81 | config.read(configpath) 82 | serveraddress = config.get('server_details', 'address') 83 | HTTP_PORT = config.getint('server_details', 'http_port') 84 | FTP_PORT = config.getint('server_details', 'ftp_port') 85 | videoGeneratorAddress = config.get('video_generator_location', 'address') 86 | videoGeneratorHTTPPort = config.getint('video_generator_location', 'http_port') 87 | videoGeneratorFTPPort = config.getint('video_generator_location', 'ftp_port') 88 | videoGeneratorFTPUser = config.get('video_generator_location', 'ftp_user') 89 | videoGeneratorFTPPassword = config.get('video_generator_location', 'ftp_password') 90 | language = config.get('tiktok', 'language') 91 | s_v_web_id = config.get('tiktok', 's_v_web_id') 92 | tt_webid = config.get('tiktok', 'tt_webid') 93 | databasehost = config.get("mysql_database", 'databasehost') 94 | databaseuser = config.get("mysql_database", 'databaseuser') 95 | databasepassword = config.get("mysql_database", 'databasepassword') 96 | -------------------------------------------------------------------------------- /TikTok Server/tiktok.py: -------------------------------------------------------------------------------- 1 | 2 | import database 3 | import scriptwrapper 4 | from TikTokAPI import TikTokAPI 5 | import traceback, sys 6 | import settings 7 | from time import sleep 8 | from pymediainfo import MediaInfo 9 | 10 | 11 | cookie = { 12 | "s_v_web_id": settings.s_v_web_id, 13 | "tt_webid": settings.tt_webid 14 | } 15 | 16 | api = TikTokAPI(cookie=cookie) 17 | 18 | forceStop = False 19 | 20 | 21 | 22 | def getAllClips(filter, amount, window): 23 | global all_clips_found, forceStop 24 | # format the saved_ids by taking it out of tuple form 25 | 26 | bad_ids = [] 27 | 28 | for id in database.getAllSavedClipIDs(): 29 | bad_ids.append(id[0]) 30 | 31 | clips = [] 32 | 33 | filterName = filter[0] 34 | filterObject = filter[1] 35 | 36 | print(f"Looking for all clips for filter {filterName}") 37 | 38 | 39 | oldIds = [] 40 | 41 | def attemptAddScripts(results, typeofrequest): 42 | try: 43 | newIds = [] 44 | amountJustAdded = 0 45 | parseType = "itemList" if typeofrequest == "Hashtag" else "items" 46 | for i, tiktok in enumerate(results[parseType]): 47 | #print("downloading %s/%s" % (i+1, len(result))) 48 | # Prints the text of the tiktok 49 | videoURL = None 50 | author = None 51 | vidId = None 52 | createTime = None 53 | text = None 54 | diggCount = None 55 | shareCount = None 56 | playCount = None 57 | commentCount = None 58 | duration = None 59 | hashtags = None 60 | 61 | try: 62 | videoURL = tiktok["video"]["downloadAddr"] 63 | author = tiktok["music"]["authorName"] 64 | vidId = tiktok["id"] 65 | createTime = tiktok["createTime"] 66 | text = tiktok["desc"] 67 | diggCount = tiktok["stats"]["diggCount"] 68 | shareCount = tiktok["stats"]["shareCount"] 69 | playCount = tiktok["stats"]["playCount"] 70 | commentCount = tiktok["stats"]["commentCount"] 71 | duration = tiktok["video"]["duration"] 72 | except Exception as e: 73 | print("error parsing data") 74 | continue 75 | 76 | newIds.append(vidId) 77 | 78 | if vidId in bad_ids: 79 | continue 80 | 81 | if vidId in [newclip.id for newclip in clips]: 82 | continue 83 | 84 | if filterObject.likeCount is not None: 85 | if diggCount < filterObject.likeCount: 86 | bad_ids.append(vidId) 87 | continue 88 | 89 | if filterObject.shareCount is not None: 90 | if shareCount < filterObject.shareCount: 91 | bad_ids.append(vidId) 92 | continue 93 | 94 | if filterObject.playCount is not None: 95 | if playCount < filterObject.playCount: 96 | bad_ids.append(vidId) 97 | continue 98 | 99 | if filterObject.commentCount is not None: 100 | if commentCount < filterObject.commentCount: 101 | bad_ids.append(vidId) 102 | continue 103 | 104 | tiktok_clip = scriptwrapper.ClipWrapper(vidId, videoURL, author, createTime, text, diggCount, shareCount, playCount, commentCount, duration) 105 | clips.append(tiktok_clip) 106 | amountJustAdded += 1 107 | 108 | return newIds 109 | except Exception: 110 | print("error occurred parsing: %s" % results) 111 | return None 112 | 113 | searchAmount = amount 114 | 115 | while True: 116 | try: 117 | # The Number of trending TikToks you want to be displayed 118 | 119 | new_ids = [] 120 | 121 | if filterObject.searchType == "Hashtag": 122 | hashtags = filterObject.inputText 123 | count = int(searchAmount / len(hashtags)) 124 | for hashtag in hashtags: 125 | print("Looking for %s clips for hashtag %s" % (count, hashtag)) 126 | results = api.getVideosByHashTag(hashtag, count) 127 | 128 | new = attemptAddScripts(results, "Trending") 129 | if new is None: 130 | break 131 | new_ids.append(new) 132 | 133 | elif filterObject.searchType == "Author": 134 | authors = filterObject.inputText 135 | count = int(searchAmount / len(authors)) 136 | 137 | 138 | for author in authors: 139 | print("Looking for %s clips for author %s" % (count, author)) 140 | results = api.getVideosByUserName(author, count) 141 | 142 | new = attemptAddScripts(results, "Trending") 143 | if new is None: 144 | break 145 | new_ids.append(new) 146 | elif filterObject.searchType == "Trending": 147 | print("Looking for %s trending clips" % searchAmount) 148 | 149 | results = api.getTrending(searchAmount) 150 | 151 | 152 | new = attemptAddScripts(results, "Trending") 153 | if new is None: 154 | break 155 | new_ids.append(new) 156 | 157 | if new_ids == oldIds: 158 | print("Found exactly the same ids in two consecutive searches. Terminating search process") 159 | break 160 | 161 | oldIds = new_ids 162 | 163 | 164 | print(f"{len(clips)} unique {filterName} clips found") 165 | 166 | 167 | if len(clips) >= amount: 168 | print("done") 169 | break 170 | else: 171 | searchAmount *= 2 172 | 173 | 174 | if forceStop: 175 | print("Forced Stop Finding Process") 176 | forceStop = False 177 | break 178 | except Exception as e: 179 | traceback.print_exc(file=sys.stdout) 180 | 181 | print(e) 182 | print("exception occured downloading. waiting and retrying") 183 | sleep(5) 184 | 185 | print(f"Found {len(clips)} unique clips") 186 | for clip in clips: 187 | database.addFoundClip(clip, filterName) 188 | 189 | print("DONE!") 190 | return clips 191 | 192 | 193 | 194 | def autoDownloadClips(filterName, clips, window): 195 | global forceStop 196 | #Downloading the clips with custom naming scheme 197 | #window.update_log_start_downloading_game.emit(filterName, len(clips)) 198 | print('Downloading...') 199 | for i, clip in enumerate(clips): 200 | print("Downloading Clip %s/%s" % (i + 1, len(clips))) 201 | try: 202 | api.downloadVideoById(clip.id, f"{settings.vid_filepath}/{clip.author_name}-{clip.id}.mp4") 203 | 204 | media_info = MediaInfo.parse(f"{settings.vid_filepath}/{clip.author_name}-{clip.id}.mp4") 205 | duration = media_info.tracks[0].duration 206 | clip.vid_duration = float(duration) / 1000 207 | database.updateStatusWithClip(clip.id, "DOWNLOADED", clip) 208 | except Exception as e: 209 | print(e) 210 | print("Error downloading clip") 211 | database.updateStatusWithClip(clip.id, "BAD", clip) 212 | 213 | window.update_log_downloaded_clip.emit(i + 1) 214 | if forceStop: 215 | print("Forced Stop Downloading Process") 216 | forceStop = False 217 | break 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /TikTok Video Generator/Logo/tiktoklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/405bef46cbc7d8f949b4beecce64c62b4d583667/TikTok Video Generator/Logo/tiktoklogo.png -------------------------------------------------------------------------------- /TikTok Video Generator/UI/videoRendering.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 708 10 | 575 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 255 20 | 255 21 | 255 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 29 | 29 30 | 29 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 38 | 43 39 | 43 40 | 41 | 42 | 43 | 44 | 45 | 46 | 36 47 | 36 48 | 36 49 | 50 | 51 | 52 | 53 | 54 | 55 | 14 56 | 14 57 | 14 58 | 59 | 60 | 61 | 62 | 63 | 64 | 19 65 | 19 66 | 19 67 | 68 | 69 | 70 | 71 | 72 | 73 | 255 74 | 255 75 | 255 76 | 77 | 78 | 79 | 80 | 81 | 82 | 255 83 | 255 84 | 255 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 99 | 100 | 0 101 | 0 102 | 0 103 | 104 | 105 | 106 | 107 | 108 | 109 | 29 110 | 29 111 | 29 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 0 120 | 0 121 | 122 | 123 | 124 | 125 | 126 | 127 | 14 128 | 14 129 | 14 130 | 131 | 132 | 133 | 134 | 135 | 136 | 255 137 | 255 138 | 220 139 | 140 | 141 | 142 | 143 | 144 | 145 | 0 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 255 157 | 255 158 | 255 159 | 160 | 161 | 162 | 163 | 164 | 165 | 29 166 | 29 167 | 29 168 | 169 | 170 | 171 | 172 | 173 | 174 | 43 175 | 43 176 | 43 177 | 178 | 179 | 180 | 181 | 182 | 183 | 36 184 | 36 185 | 36 186 | 187 | 188 | 189 | 190 | 191 | 192 | 14 193 | 14 194 | 14 195 | 196 | 197 | 198 | 199 | 200 | 201 | 19 202 | 19 203 | 19 204 | 205 | 206 | 207 | 208 | 209 | 210 | 255 211 | 255 212 | 255 213 | 214 | 215 | 216 | 217 | 218 | 219 | 255 220 | 255 221 | 255 222 | 223 | 224 | 225 | 226 | 227 | 228 | 0 229 | 0 230 | 0 231 | 232 | 233 | 234 | 235 | 236 | 237 | 0 238 | 0 239 | 0 240 | 241 | 242 | 243 | 244 | 245 | 246 | 29 247 | 29 248 | 29 249 | 250 | 251 | 252 | 253 | 254 | 255 | 0 256 | 0 257 | 0 258 | 259 | 260 | 261 | 262 | 263 | 264 | 14 265 | 14 266 | 14 267 | 268 | 269 | 270 | 271 | 272 | 273 | 255 274 | 255 275 | 220 276 | 277 | 278 | 279 | 280 | 281 | 282 | 0 283 | 0 284 | 0 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 14 294 | 14 295 | 14 296 | 297 | 298 | 299 | 300 | 301 | 302 | 29 303 | 29 304 | 29 305 | 306 | 307 | 308 | 309 | 310 | 311 | 43 312 | 43 313 | 43 314 | 315 | 316 | 317 | 318 | 319 | 320 | 36 321 | 36 322 | 36 323 | 324 | 325 | 326 | 327 | 328 | 329 | 14 330 | 14 331 | 14 332 | 333 | 334 | 335 | 336 | 337 | 338 | 19 339 | 19 340 | 19 341 | 342 | 343 | 344 | 345 | 346 | 347 | 14 348 | 14 349 | 14 350 | 351 | 352 | 353 | 354 | 355 | 356 | 255 357 | 255 358 | 255 359 | 360 | 361 | 362 | 363 | 364 | 365 | 14 366 | 14 367 | 14 368 | 369 | 370 | 371 | 372 | 373 | 374 | 29 375 | 29 376 | 29 377 | 378 | 379 | 380 | 381 | 382 | 383 | 29 384 | 29 385 | 29 386 | 387 | 388 | 389 | 390 | 391 | 392 | 0 393 | 0 394 | 0 395 | 396 | 397 | 398 | 399 | 400 | 401 | 29 402 | 29 403 | 29 404 | 405 | 406 | 407 | 408 | 409 | 410 | 255 411 | 255 412 | 220 413 | 414 | 415 | 416 | 417 | 418 | 419 | 0 420 | 0 421 | 0 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | Video Generator 430 | 431 | 432 | 433 | 434 | 70 435 | 90 436 | 611 437 | 321 438 | 439 | 440 | 441 | 442 | 443 | 444 | 90 445 | 50 446 | 271 447 | 16 448 | 449 | 450 | 451 | Video Queue 452 | 453 | 454 | 455 | 456 | 457 | 200 458 | 440 459 | 191 460 | 21 461 | 462 | 463 | 464 | Video Being Rendered 465 | 466 | 467 | 468 | 469 | 470 | 70 471 | 470 472 | 431 473 | 23 474 | 475 | 476 | 477 | 0 478 | 479 | 480 | 481 | 482 | 483 | 110 484 | 520 485 | 141 486 | 31 487 | 488 | 489 | 490 | Test Server Connection 491 | 492 | 493 | 494 | 495 | 496 | 270 497 | 510 498 | 241 499 | 61 500 | 501 | 502 | 503 | 504 | 505 | 506 | true 507 | 508 | 509 | 510 | 511 | 512 | 510 513 | 460 514 | 161 515 | 22 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 0 525 | 0 526 | 255 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 0 536 | 0 537 | 255 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 14 547 | 14 548 | 14 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 510 560 | 430 561 | 161 562 | 16 563 | 564 | 565 | 566 | Backup video files 567 | 568 | 569 | 570 | 571 | 572 | 510 573 | 500 574 | 161 575 | 23 576 | 577 | 578 | 579 | Render Backup 580 | 581 | 582 | 583 | 584 | 585 | 510 586 | 540 587 | 161 588 | 23 589 | 590 | 591 | 592 | Delete Backup 593 | 594 | 595 | 596 | 597 | 598 | 599 | -------------------------------------------------------------------------------- /TikTok Video Generator/config.ini: -------------------------------------------------------------------------------- 1 | [video_generator_details] 2 | address = 127.0.0.1 3 | http_port = 8001 4 | ftp_port = 2122 5 | ftp_user = VidGen 6 | ftp_password = password 7 | 8 | [server_location] 9 | address = 127.0.0.1 10 | http_port = 8000 11 | ftp_port = 2121 12 | 13 | [rendering] 14 | fps = 30 15 | useMinimumFps = True 16 | useMaximumFps = False 17 | backupVideos = True 18 | 19 | -------------------------------------------------------------------------------- /TikTok Video Generator/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PyQt5 import QtWidgets 3 | import settings 4 | import sys 5 | import vidgenUI 6 | import server 7 | import vidGen 8 | from threading import Thread 9 | 10 | class App(): 11 | def __init__(self): 12 | app = QtWidgets.QApplication(sys.argv) 13 | app.processEvents() 14 | renderingScreen = vidgenUI.renderingScreen() 15 | renderingScreen.show() 16 | Thread(target=vidGen.renderThread, args = (renderingScreen, )).start() 17 | Thread(target=server.sendThread).start() 18 | 19 | sys.exit(app.exec_()) 20 | 21 | if __name__ == "__main__": 22 | current_directory = os.path.dirname(os.path.realpath(__file__)) 23 | os.chdir(current_directory) 24 | settings.generateConfigFile() 25 | 26 | if not os.path.exists(settings.temp_path): 27 | os.mkdir(f"{settings.temp_path}") 28 | 29 | if not os.path.exists(settings.final_video_path): 30 | os.mkdir(f"{settings.final_video_path}") 31 | 32 | if not os.path.exists(settings.vid_finishedvids): 33 | os.mkdir(f"{settings.vid_finishedvids}") 34 | 35 | if not os.path.exists(settings.backup_path): 36 | os.mkdir(f"{settings.backup_path}") 37 | 38 | start = True 39 | if settings.useMaximumFps and settings.useMinimumFps: 40 | print("Selected max fps and minimum fps in the config file. Please only set one to true!") 41 | start = False 42 | 43 | if start: 44 | vidGen.deleteAllFilesInPath(settings.vid_finishedvids) 45 | server.init() 46 | App() 47 | -------------------------------------------------------------------------------- /TikTok Video Generator/scriptwrapper.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import math 4 | import datetime 5 | import pickle 6 | import random 7 | current_path = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | 10 | 11 | def createTwitchVideoFromJSON(videojson): 12 | #print(videojson) 13 | final_clips = [] 14 | 15 | clips = videojson["clips"] 16 | name = videojson["name"] 17 | 18 | 19 | for clip in clips: 20 | id = clip["id"] 21 | audio = clip["audio"] 22 | used = clip["keep"] 23 | 24 | isUpload = clip["isUpload"] 25 | isIntro = clip["isIntro"] 26 | isInterval = clip["isInterval"] 27 | uploadMp4 = clip["mp4"] 28 | uploadDuration = clip["duration"] 29 | author_name = clip["author_name"] 30 | 31 | 32 | 33 | wrapper = ClipWrapper(id, author_name) 34 | wrapper.mp4 = uploadMp4 35 | wrapper.vid_duration = uploadDuration 36 | wrapper.isUpload = isUpload 37 | wrapper.isInterval = isInterval 38 | wrapper.isIntro = isIntro 39 | wrapper.audio = audio 40 | wrapper.isUsed = used 41 | 42 | final_clips.append(wrapper) 43 | 44 | video = TikTokVideo(final_clips, name) 45 | #print(final_clips) 46 | return video 47 | 48 | 49 | def saveTwitchVideo(folderName, video): 50 | print(f'Saved to Temp/%s/vid.data' % folderName) 51 | with open(f'Temp/%s/vid.data' % folderName, 'wb') as pickle_file: 52 | pickle.dump(video, pickle_file) 53 | 54 | 55 | 56 | class TikTokVideo(): 57 | def __init__(self, clips, name): 58 | self.clips = clips 59 | self.name = name 60 | 61 | 62 | 63 | class ClipWrapper(): 64 | def __init__(self, id, author_name): 65 | self.id = id 66 | self.author_name = author_name 67 | self.audio = 1 68 | self.isUsed = False 69 | self.isInterval = False 70 | self.isUpload = False 71 | self.mp4 = "AndreasGreenLive-702952046" 72 | self.isIntro = False 73 | # result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", 74 | # "format=duration", "-of", 75 | # "default=noprint_wrappers=1:nokey=1", f"{current_path}\VideoFiles\AndreasGreenLive-702952046.mp4"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 76 | self.vid_duration = None 77 | 78 | #Getting duration of video clips to trim a percentage of the beginning off 79 | 80 | 81 | 82 | 83 | class ScriptWrapper(): 84 | def __init__(self, script): 85 | self.rawScript = script 86 | self.scriptMap = [] 87 | self.setupScriptMap() 88 | 89 | 90 | def addClipAtStart(self, clip): 91 | self.rawScript = [clip] + self.rawScript 92 | self.scriptMap = [True] + self.scriptMap 93 | 94 | 95 | def addScriptWrapper(self, scriptwrapper): 96 | self.rawScript = self.rawScript + scriptwrapper.rawScript 97 | self.scriptMap = self.scriptMap + scriptwrapper.scriptMap 98 | 99 | 100 | def moveDown(self, i): 101 | if i > 0: 102 | copy1 = self.scriptMap[i-1] 103 | copy2 = self.rawScript[i-1] 104 | 105 | self.scriptMap[i-1] = self.scriptMap[i] 106 | self.rawScript[i-1] = self.rawScript[i] 107 | 108 | self.scriptMap[i] = copy1 109 | self.rawScript[i] = copy2 110 | else: 111 | print("already at bottom!") 112 | 113 | def moveUp(self, i): 114 | if i < len(self.scriptMap) - 1: 115 | copy1 = self.scriptMap[i+1] 116 | copy2 = self.rawScript[i+1] 117 | 118 | self.scriptMap[i+1] = self.scriptMap[i] 119 | self.rawScript[i+1] = self.rawScript[i] 120 | 121 | self.scriptMap[i] = copy1 122 | self.rawScript[i] = copy2 123 | else: 124 | print("already at top!") 125 | 126 | def setupScriptMap(self): 127 | for mainComment in self.rawScript: 128 | line = False 129 | self.scriptMap.append(line) 130 | 131 | 132 | def keep(self, mainCommentIndex): 133 | self.scriptMap[mainCommentIndex] = True 134 | 135 | def skip(self, mainCommentIndex): 136 | self.scriptMap[mainCommentIndex] = False 137 | 138 | def setCommentStart(self, x, start): 139 | self.rawScript[x].start_cut = start 140 | 141 | def setCommentEnd(self, x, end): 142 | self.rawScript[x].end_cut = end 143 | 144 | def getCommentData(self, x, y): 145 | return self.rawScript[x][y] 146 | 147 | def getCommentAmount(self): 148 | return len(self.scriptMap) 149 | 150 | def getEditedCommentThreadsAmount(self): 151 | return len([commentThread for commentThread in self.scriptMap if commentThread[0] is True]) 152 | 153 | def getEditedCommentAmount(self): 154 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 155 | count = 0 156 | for commentThread in commentThreads: 157 | for comment in commentThread: 158 | if comment is True: 159 | count += 1 160 | return count 161 | 162 | def getEditedWordCount(self): 163 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 164 | word_count = 0 165 | for x, commentThread in enumerate(commentThreads): 166 | for y, comment in enumerate(commentThread): 167 | if comment is True: 168 | word_count += len(self.rawScript[x][y].text.split(" ")) 169 | return word_count 170 | 171 | def getEditedCharacterCount(self): 172 | commentThreads = ([commentThread for commentThread in self.scriptMap]) 173 | word_count = 0 174 | for x, commentThread in enumerate(commentThreads): 175 | for y, comment in enumerate(commentThread): 176 | if comment is True: 177 | word_count += len(self.rawScript[x][y].text) 178 | return word_count 179 | 180 | 181 | def getCommentInformation(self, x): 182 | return self.rawScript[x] 183 | 184 | 185 | def getKeptClips(self): 186 | final_script = [] 187 | for i, clip in enumerate(self.scriptMap): 188 | if clip: 189 | final_script.append(self.rawScript[i]) 190 | return final_script 191 | 192 | def getEstimatedVideoTime(self): 193 | time = 0 194 | for i, comment in enumerate(self.scriptMap): 195 | if comment is True: 196 | time += round(self.rawScript[i].vid_duration - (self.rawScript[i].start_cut / 1000) - (self.rawScript[i].end_cut / 1000), 1) 197 | obj = datetime.timedelta(seconds=math.ceil(time)) 198 | return obj 199 | 200 | -------------------------------------------------------------------------------- /TikTok Video Generator/server.py: -------------------------------------------------------------------------------- 1 | from pyftpdlib.authorizers import DummyAuthorizer 2 | from pyftpdlib.handlers import FTPHandler 3 | from pyftpdlib.servers import FTPServer 4 | from threading import Thread 5 | import http.server 6 | import socketserver 7 | import json 8 | import cgi 9 | from time import sleep 10 | import scriptwrapper 11 | import random 12 | import vidGen 13 | import traceback, sys 14 | import pickle 15 | import os 16 | import settings 17 | import ftplib 18 | 19 | current_path = os.path.dirname(os.path.realpath(__file__)) 20 | 21 | 22 | # The directory the FTP user will have full read/write access to. 23 | FTP_DIRECTORY = current_path 24 | 25 | 26 | def testFTPConnection(): 27 | try: 28 | ftp = ftplib.FTP() 29 | ftp.connect(settings.server_address, settings.serverFTPPort) 30 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 31 | return True 32 | except Exception as e: 33 | return False 34 | 35 | 36 | 37 | def getFileNames(file_path): 38 | files = [os.path.splitext(filename)[0] for filename in os.listdir(file_path)] 39 | return files 40 | 41 | def uploadCompleteVideo(name): 42 | try: 43 | if os.path.exists("%s/%s.txt" % (settings.final_video_path, name)): 44 | ftp = ftplib.FTP() 45 | ftp.connect(settings.server_address, settings.serverFTPPort) 46 | ftp.login(settings.FTP_USER, settings.FTP_PASSWORD) 47 | ftp.cwd("FinalVideos") 48 | sleep(10) 49 | print("Uploading %s.mp4" % name) 50 | filemp4 = open("%s/%s.mp4" % (settings.final_video_path, name),'rb') 51 | ftp.storbinary('STOR %s.mp4' % name, filemp4, blocksize=262144) 52 | filemp4.close() 53 | print("Uploading %s.txt" % name) 54 | filetxt = open("%s/%s.txt" % (settings.final_video_path, name),'rb') 55 | ftp.storbinary('STOR %s.txt' % name, filetxt, blocksize=262144) 56 | filetxt.close() 57 | print("Done Uploading %s" % name) 58 | os.remove(f"{settings.final_video_path}/%s.mp4" % name) 59 | os.remove(f"{settings.final_video_path}/%s.txt" % name) 60 | else: 61 | pass 62 | except Exception as e: 63 | print(e) 64 | 65 | 66 | 67 | 68 | def sendThread(): 69 | while True: 70 | sleep(5) 71 | savedFilesDuplicates = getFileNames(f'{settings.final_video_path}') 72 | savedFiles = list(dict.fromkeys(savedFilesDuplicates)) 73 | for file in savedFiles: 74 | uploadCompleteVideo(file) 75 | 76 | 77 | 78 | 79 | def startFTPServer(): 80 | authorizer = DummyAuthorizer() 81 | 82 | authorizer.add_user(settings.FTP_USER, settings.FTP_PASSWORD, FTP_DIRECTORY, perm='elradfmw') 83 | 84 | handler = FTPHandler 85 | handler.authorizer = authorizer 86 | 87 | handler.banner = "pyftpdlib based ftpd ready." 88 | 89 | address = (settings.videogeneratoraddress, settings.FTP_PORT) 90 | server = FTPServer(address, handler) 91 | 92 | server.max_cons = 256 93 | server.max_cons_per_ip = 5 94 | 95 | server.serve_forever() 96 | 97 | 98 | class HTTPHandler(http.server.BaseHTTPRequestHandler): 99 | def _set_headers(self): 100 | self.send_response(200) 101 | self.send_header('Content-type', 'application/json') 102 | self.end_headers() 103 | 104 | def do_HEAD(self): 105 | self._set_headers() 106 | 107 | # GET sends back a Hello world message 108 | def do_GET(self): 109 | 110 | self._set_headers() 111 | try: 112 | if "/sendscript" == self.path: 113 | length = int(self.headers.get('content-length')) 114 | message = json.loads(self.rfile.read(length)) 115 | video = scriptwrapper.createTwitchVideoFromJSON(message) 116 | folder = message["vid_folder"] 117 | scriptwrapper.saveTwitchVideo(folder, video) 118 | self.wfile.write(json.dumps({'received': True}).encode()) 119 | pass 120 | if "/getrenderinfo" == self.path: 121 | 122 | 123 | render_data = {'max_progress': vidGen.render_max_progress, 124 | "current_progress" : vidGen.render_current_progress, 125 | "render_message" : vidGen.render_message, "music" : None} 126 | self.wfile.write(json.dumps(render_data).encode()) 127 | pass 128 | except Exception as e: 129 | traceback.print_exc(file=sys.stdout) 130 | 131 | print(e) 132 | print("Error occured with http requests") 133 | #self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}).encode()) 134 | 135 | # POST echoes the message adding a JSON field 136 | def do_POST(self): 137 | ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) 138 | 139 | # refuse to receive non-json content 140 | if ctype != 'application/json': 141 | self.send_response(400) 142 | self.end_headers() 143 | return 144 | 145 | # read the message and convert it into a python dictionary 146 | length = int(self.headers.getheader('content-length')) 147 | message = json.loads(self.rfile.read(length)) 148 | 149 | # add a property to the object, just to mess with data 150 | message['received'] = 'ok' 151 | 152 | # send the message back 153 | self._set_headers() 154 | self.wfile.write(json.dumps(message)) 155 | 156 | 157 | def startHTTPServer(): 158 | with socketserver.TCPServer((settings.videogeneratoraddress, settings.HTTP_PORT), HTTPHandler) as httpd: 159 | print("serving at port", settings.HTTP_PORT) 160 | httpd.serve_forever() 161 | 162 | 163 | def init(): 164 | Thread(target=startFTPServer).start() 165 | Thread(target=startHTTPServer).start() 166 | 167 | 168 | -------------------------------------------------------------------------------- /TikTok Video Generator/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | from sys import platform 4 | currentPath = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | 7 | server_address = "127.0.0.1" 8 | serverFTPPort = 2121 9 | 10 | videogeneratoraddress = "127.0.0.1" 11 | 12 | # The port the FTP server will listen on. 13 | # This must be greater than 1023 unless you run this script as root. 14 | FTP_PORT = 2122 15 | HTTP_PORT = 8001 16 | 17 | FTP_USER = "VidGen" 18 | FTP_PASSWORD = "password" 19 | 20 | 21 | vid_finishedvids = "FinishedVids" 22 | vid_filepath = "VideoFiles" 23 | final_video_path = "FinalVideos" 24 | old_clip_path = "OldClips" 25 | video_data_path = "VideoData" 26 | backup_path = "Backup" 27 | 28 | 29 | server_port = 8000 30 | 31 | fps = 15 32 | 33 | temp_path = "Temp" 34 | 35 | first_clip_name = '' 36 | 37 | useMinimumFps = False 38 | useMaximumFps = False 39 | 40 | backupVideos = False 41 | 42 | 43 | config = configparser.ConfigParser() 44 | 45 | configpath = None 46 | 47 | if platform == "linux" or platform == "linux2" or platform == "darwin": 48 | configpath = "%s/config.ini" % currentPath 49 | else: 50 | configpath = "%s\\config.ini" % currentPath 51 | 52 | 53 | def generateConfigFile(): 54 | if not os.path.isfile(configpath): 55 | print("Could not find config file in location %s, creating a new one" % configpath) 56 | config.add_section("video_generator_details") 57 | config.set("video_generator_details", 'address', '127.0.0.1') 58 | config.set("video_generator_details", 'http_port', '8001') 59 | config.set("video_generator_details", 'ftp_port', '2122') 60 | config.set("video_generator_details", 'FTP_USER', 'VidGen') 61 | config.set("video_generator_details", 'FTP_PASSWORD', 'password') 62 | config.add_section("server_location") 63 | config.set("server_location", 'address', '127.0.0.1') 64 | config.set("server_location", 'http_port', '8000') 65 | config.set("server_location", 'ftp_port', '2122') 66 | 67 | config.add_section("rendering") 68 | config.set("rendering", 'fps', '30') 69 | config.set("rendering", 'useMinimumFps', 'True') 70 | config.set("rendering", 'useMaximumFps', 'False') 71 | config.set("rendering", 'backupVideos', 'True') 72 | 73 | 74 | with open(configpath, 'w') as configfile: 75 | config.write(configfile) 76 | else: 77 | print("Found config in location %s" % configpath) 78 | loadValues() 79 | 80 | def loadValues(): 81 | global server_address, serverFTPPort, videogeneratoraddress, FTP_PORT, HTTP_PORT, FTP_USER, FTP_PASSWORD, fps, server_port, useMinimumFps, useMaximumFps, backupVideos 82 | config = configparser.ConfigParser() 83 | config.read(configpath) 84 | videogeneratoraddress = config.get('video_generator_details', 'address') 85 | FTP_PORT = config.getint('video_generator_details', 'ftp_port') 86 | HTTP_PORT = config.getint('video_generator_details', 'http_port') 87 | FTP_USER = config.get('video_generator_details', 'FTP_USER') 88 | FTP_PASSWORD = config.get('video_generator_details', 'FTP_PASSWORD') 89 | 90 | 91 | server_address = config.get('server_location', 'address') 92 | server_port = config.get('server_location', 'http_port') 93 | serverFTPPort = config.getint('server_location', 'ftp_port') 94 | fps = config.getint('rendering', 'fps') 95 | useMinimumFps = config.getboolean('rendering', 'useMinimumFps') 96 | useMaximumFps = config.getboolean('rendering', 'useMaximumFps') 97 | backupVideos = config.getboolean('rendering', 'backupVideos') 98 | 99 | -------------------------------------------------------------------------------- /TikTok Video Generator/vidGen.py: -------------------------------------------------------------------------------- 1 | import random 2 | import os 3 | import time 4 | import shutil 5 | import subprocess 6 | import re 7 | import cv2 8 | from time import sleep 9 | import datetime 10 | from distutils.dir_util import copy_tree 11 | import pickle 12 | import settings 13 | 14 | #File Paths 15 | 16 | 17 | 18 | #Creating file paths that are needed 19 | 20 | 21 | saved_videos = None 22 | render_current_progress = None 23 | render_max_progress = None 24 | render_message = None 25 | 26 | 27 | #------------------------------------------C O M P I L A T I O N G E N E R A T O R------------------------------------------ 28 | 29 | #Getting Filename without extension and storing it into a list 30 | def getFileNames(file_path): 31 | files = [os.path.splitext(filename)[0] for filename in os.listdir(file_path)] 32 | return files 33 | 34 | def deleteSkippedClips(clips): 35 | for clip in clips: 36 | os.remove(f'{clip}') 37 | 38 | def deleteAllFilesInPath(path): 39 | for file in os.listdir(path): 40 | file_path = os.path.join(path, file) 41 | try: 42 | if os.path.isfile(file_path): 43 | os.unlink(file_path) 44 | except Exception as e: 45 | print(e) 46 | 47 | 48 | def renderThread(renderingScreen): 49 | global saved_videos 50 | while True: 51 | time.sleep(5) 52 | savedFiles = getFileNames(f'{settings.temp_path}') 53 | saved_videos = [] 54 | save_names = [] 55 | for file in savedFiles: 56 | try: 57 | with open(f'{settings.temp_path}/{file}/vid.data', 'rb') as pickle_file: 58 | script = pickle.load(pickle_file) 59 | saved_videos.append(script) 60 | save_names.append(f'{settings.temp_path}/{file}') 61 | except FileNotFoundError: 62 | pass 63 | #print("No vid.data file in %s" % file) 64 | renderingScreen.script_queue_update.emit() 65 | 66 | for i, video in enumerate(saved_videos): 67 | print(f'Rendering script {i + 1}/{len(saved_videos)}') 68 | 69 | t0 = datetime.datetime.now() 70 | renderVideo(video, renderingScreen) 71 | t1 = datetime.datetime.now() 72 | 73 | total = t1-t0 74 | print("Rendering Time %s" % total) 75 | 76 | if settings.backupVideos: 77 | backupName = save_names[i].replace(settings.temp_path, settings.backup_path) 78 | if os.path.exists(backupName): 79 | print("Backup for video %s already exists" % backupName) 80 | else: 81 | print("Making backup of video to %s" % backupName) 82 | copy_tree(save_names[i], backupName) 83 | 84 | 85 | print(f"Deleting video folder {save_names[i]}") 86 | shutil.rmtree(save_names[i]) 87 | renderingScreen.update_backups.emit() 88 | # delete all the temp videos 89 | try: 90 | deleteAllFilesInPath(settings.vid_finishedvids) 91 | except Exception as e: 92 | print(e) 93 | print("Couldn't delete clips") 94 | 95 | 96 | 97 | #Adding Streamer's name to the video clip 98 | def renderVideo(video, rendering_screen): 99 | global render_current_progress, render_max_progress, render_message 100 | t0 = datetime.datetime.now() 101 | 102 | clips = video.clips 103 | videoName = video.name 104 | 105 | subprocess._cleanup = lambda: None 106 | credits = [] 107 | streamers_in_cred = [] 108 | 109 | render_current_progress = 0 110 | # see where render_current_progress += 1 111 | 112 | amount = 0 113 | for clip in clips: 114 | if clip.isUsed: 115 | amount += 1 116 | 117 | render_max_progress = amount * 2 + 1 + 1 118 | render_message = "Beginning Rendering" 119 | rendering_screen.render_progress.emit() 120 | 121 | current_date = datetime.datetime.today().strftime("%m-%d-%Y__%H-%M-%S") 122 | 123 | toCombine = [] 124 | 125 | 126 | fpsList = [] 127 | 128 | for i, clip in enumerate(clips): 129 | mp4 = clip.mp4 130 | mp4name = mp4 131 | mp4path = f"{mp4}.mp4" 132 | 133 | if len(mp4.split("/")) > 2: 134 | name = len(mp4.split("/")) 135 | mp4name = mp4.split("/")[name-1].replace(".mp4", "") 136 | mp4path = mp4[1:] 137 | cap=cv2.VideoCapture(mp4path) 138 | fps = cap.get(cv2.CAP_PROP_FPS) 139 | fpsList.append(fps) 140 | 141 | chosenFps = settings.fps 142 | if settings.useMinimumFps: 143 | chosenFps = int(min(fpsList)) 144 | 145 | if settings.useMaximumFps: 146 | chosenFps = int(max(fpsList)) 147 | 148 | print("Using Fps %s" % chosenFps) 149 | 150 | # render progress 1 151 | for i, clip in enumerate(clips): 152 | if clip.isUsed: 153 | name = clip.author_name 154 | mp4 = clip.mp4 155 | 156 | if name is not None and name not in streamers_in_cred and not clip.isUpload: 157 | credits.append(f"{clip.author_name}") 158 | streamers_in_cred.append(clip.author_name) 159 | 160 | 161 | final_duration = round(clip.vid_duration, 1) 162 | 163 | 164 | print(f"Rendering video ({i + 1}/{len(clips)}) to \"{settings.vid_finishedvids}\"/{mp4}_finished.mp4") 165 | 166 | 167 | mp4name = mp4 168 | mp4path = f"{mp4}.mp4" 169 | 170 | if len(mp4.split("/")) > 2: 171 | name = len(mp4.split("/")) 172 | mp4name = mp4.split("/")[name-1].replace(".mp4", "") 173 | mp4path = mp4[1:] 174 | 175 | if not clip.isInterval and not clip.isIntro: 176 | os.system(f"ffmpeg -i \"{mp4path}\" -vf \"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1\" \"{settings.vid_finishedvids}/{mp4name}temp.mp4\"") 177 | os.system(f"ffmpeg -i \"{settings.vid_finishedvids}/{mp4name}temp.mp4\" -filter:v fps=fps={chosenFps} \"{settings.vid_finishedvids}/{mp4name}_finished.mp4\"") 178 | path = f"'{os.path.dirname(os.path.realpath(__file__))}/{settings.vid_finishedvids}/{mp4name}_finished.mp4'" 179 | path = path.replace("\\", "/") 180 | 181 | #path = f"'{mp4path}'" 182 | #path = path.replace("\\", "/") 183 | 184 | 185 | toCombine.append(path) 186 | #os.system(f"ffmpeg -y -fflags genpts -i \"{mp4path}\" -vf \"ass=subtitleFile.ass, scale=1920:1080\" \"{settings.vid_finishedvids}/{mp4name}_finished.mp4\"") 187 | 188 | render_current_progress += 1 189 | render_message = f"Done Adding text to video ({i + 1}/{len(clips)})" 190 | rendering_screen.render_progress.emit() 191 | 192 | 193 | render_message = f"Adding clip to list ({i + 1}/{len(clips)})" 194 | rendering_screen.render_progress.emit() 195 | 196 | 197 | render_current_progress += 1 198 | render_message = f"Done Adding clip to list ({i + 1}/{len(clips)})" 199 | rendering_screen.render_progress.emit() 200 | 201 | 202 | 203 | # render progress 2 204 | render_message = "Creating audio loop" 205 | rendering_screen.render_progress.emit() 206 | #audio = AudioFileClip(f'{settings.asset_file_path}/Music/{musicFiles[0]}.mp3').fx(afx.volumex, float(video.background_volume)) 207 | 208 | 209 | 210 | 211 | render_current_progress += 1 212 | render_message = "Done Creating audio loop" 213 | rendering_screen.render_progress.emit() 214 | # render progress 3 215 | render_message = "Writing final video" 216 | rendering_screen.render_progress.emit() 217 | 218 | 219 | sleep(5) 220 | 221 | vid_concat = open("concat.txt", "a") 222 | #Adding comment thread video clips and interval video file paths to text file for concatenating 223 | for files in toCombine: 224 | vid_concat.write(f"file {files}\n") 225 | vid_concat.close() 226 | 227 | 228 | 229 | 230 | os.system(f"ffmpeg -safe 0 -f concat -segment_time_metadata 1 -i concat.txt -vf select=concatdec_select -af aselect=concatdec_select,aresample=async=1 \"{settings.final_video_path}/{videoName}_{current_date}.mp4\"") 231 | #os.system(f"ffmpeg -f concat -safe 0 -i concat.txt -s 1920x1080 -c copy {settings.final_video_path}/TikTokMoments_{current_date}.mp4") 232 | 233 | open("concat.txt", 'w').close() 234 | 235 | 236 | #final_vid_with_music.write_videofile(f'{settings.final_video_path}/TikTokMoments_{current_date}.mp4', fps=settings.fps, threads=16) 237 | render_current_progress += 1 238 | t1 = datetime.datetime.now() 239 | total = t1-t0 240 | render_message = "Done writing final video (%s)" % total 241 | rendering_screen.render_progress.emit() 242 | 243 | f= open(f"{settings.final_video_path}/{videoName}_{current_date}.txt","w+") 244 | f.write("A special thanks to the following: \n\n") 245 | for cred in credits: 246 | f.write(cred + "\n") 247 | f.close() 248 | sleep(10) 249 | 250 | -------------------------------------------------------------------------------- /TikTok Video Generator/vidgenUI.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSignal, QPoint, QRect, QObject 2 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 3 | from PyQt5.QtWidgets import * 4 | import settings 5 | import vidGen 6 | import pickle 7 | import sys 8 | import server 9 | import shutil 10 | import traceback, sys 11 | from PyQt5.QtGui import QIcon 12 | from distutils.dir_util import copy_tree 13 | 14 | class renderingScreen(QDialog): 15 | script_queue_update = pyqtSignal() 16 | render_progress = pyqtSignal() 17 | 18 | update_backups = pyqtSignal() 19 | 20 | 21 | def __init__(self): 22 | QtWidgets.QWidget.__init__(self) 23 | uic.loadUi(f"UI/videoRendering.ui", self) 24 | 25 | try: 26 | self.setWindowIcon(QIcon('Logo/tiktoklogo.png')) 27 | except Exception as e: 28 | pass 29 | 30 | self.script_queue_update.connect(self.updateScriptScreen) 31 | self.render_progress.connect(self.updateRenderProgress) 32 | self.update_backups.connect(self.populateComboBox) 33 | 34 | self.renderBackup.clicked.connect(self.renderBackupFromName) 35 | self.deleteBackup.clicked.connect(self.deleteBackupFromName) 36 | 37 | self.testServerFTP() 38 | self.testServerConnection.clicked.connect(self.testServerFTP) 39 | 40 | self.populateComboBox() 41 | 42 | def populateComboBox(self): 43 | self.backupSelection.clear() 44 | savedFiles = vidGen.getFileNames(f'{settings.backup_path}') 45 | saved_names = [] 46 | for file in savedFiles: 47 | try: 48 | with open(f'{settings.backup_path}/{file}/vid.data', 'rb') as pickle_file: 49 | script = pickle.load(pickle_file) 50 | saved_names.append(script.name) 51 | except FileNotFoundError: 52 | pass 53 | 54 | self.backupSelection.addItems(saved_names) 55 | 56 | def renderBackupFromName(self): 57 | try: 58 | backupName = self.backupSelection.currentText() 59 | 60 | backupPath = None 61 | 62 | savedFiles = vidGen.getFileNames(f'{settings.backup_path}') 63 | for file in savedFiles: 64 | try: 65 | with open(f'{settings.backup_path}/{file}/vid.data', 'rb') as pickle_file: 66 | script = pickle.load(pickle_file) 67 | if script.name == backupName: 68 | backupPath = f"{settings.backup_path}/{file}" 69 | break 70 | except FileNotFoundError: 71 | pass 72 | 73 | if backupPath is not None: 74 | copy_tree(backupPath, backupPath.replace(settings.backup_path, settings.temp_path)) 75 | except Exception: 76 | traceback.print_exc(file=sys.stdout) 77 | 78 | 79 | def deleteBackupFromName(self): 80 | try: 81 | backupName = self.backupSelection.currentText() 82 | 83 | backupPath = None 84 | 85 | savedFiles = vidGen.getFileNames(f'{settings.backup_path}') 86 | for file in savedFiles: 87 | try: 88 | with open(f'{settings.backup_path}/{file}/vid.data', 'rb') as pickle_file: 89 | script = pickle.load(pickle_file) 90 | if script.name == backupName: 91 | backupPath = f"{settings.backup_path}/{file}" 92 | break 93 | except FileNotFoundError: 94 | pass 95 | 96 | if backupPath is not None: 97 | shutil.rmtree(backupPath) 98 | self.populateComboBox() 99 | 100 | except Exception: 101 | traceback.print_exc(file=sys.stdout) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | def closeEvent(self, evnt): 111 | sys.exit() 112 | 113 | 114 | def testServerFTP(self): 115 | success = server.testFTPConnection() 116 | if success: 117 | self.connectionStatus.setText("Server connection fine!") 118 | else: 119 | self.connectionStatus.setText("Could not connect to server! Ensure it is online and FTP username/password are correct in config.ini.") 120 | 121 | 122 | def updateScriptScreen(self): 123 | self.scriptQueue.clear() 124 | for i, script in enumerate(vidGen.saved_videos): 125 | amount_clips = len(script.clips) 126 | self.scriptQueue.append(f'({i + 1}/{len(vidGen.saved_videos)}) clips: {amount_clips}') 127 | 128 | def updateRenderProgress(self): 129 | self.renderStatus.setText(vidGen.render_message) 130 | self.progressBar.setMaximum(vidGen.render_max_progress) 131 | self.progressBar.setValue(vidGen.render_current_progress) -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/405bef46cbc7d8f949b4beecce64c62b4d583667/images/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | PyQt5 3 | Pymediainfo 4 | Opencv-python 5 | Pyftpdlib 6 | Pydub 7 | mysql-connector-python 8 | PyTikTokAPI --------------------------------------------------------------------------------