├── 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 |
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 | -
36 | About The Project
37 |
43 |
44 | -
45 | Getting Started
46 |
50 |
51 | - Usage
52 | - Roadmap
53 | - License
54 | -
55 | File System and Explanation
56 |
61 |
62 | -
63 | Config Files and Explanation
64 |
69 |
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
--------------------------------------------------------------------------------