├── images
└── logo.png
├── requirements.txt
├── TikTok Client
├── Assets
│ └── tiktoklogo.png
├── config.ini
├── main.py
├── settings.py
├── scriptwrapper.py
├── client.py
└── UI
│ ├── clipUpload.ui
│ ├── login.ui
│ └── menu.ui
├── TikTok Server
├── Assets
│ └── tiktoklogo.png
├── config.ini
├── autodownloader.py
├── main.py
├── settings.py
├── filtercreator.py
├── tiktok.py
├── scriptwrapper.py
├── database.py
├── server.py
├── autodownloaderUI.py
└── UI
│ └── clipTemplateHandler.ui
├── TikTok Video Generator
├── Logo
│ └── tiktoklogo.png
├── config.ini
├── main.py
├── settings.py
├── vidgenUI.py
├── server.py
├── scriptwrapper.py
├── vidGen.py
└── UI
│ └── videoRendering.ui
├── LICENSE
└── README.md
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/HEAD/images/logo.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | PyQt5
3 | Pymediainfo
4 | Opencv-python
5 | Pyftpdlib
6 | Pydub
7 | mysql-connector-python
8 | PyTikTokAPI
--------------------------------------------------------------------------------
/TikTok Client/Assets/tiktoklogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/HEAD/TikTok Client/Assets/tiktoklogo.png
--------------------------------------------------------------------------------
/TikTok Server/Assets/tiktoklogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/HEAD/TikTok Server/Assets/tiktoklogo.png
--------------------------------------------------------------------------------
/TikTok Video Generator/Logo/tiktoklogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HA6Bots/TikTok-Compilation-Video-Generator/HEAD/TikTok Video Generator/Logo/tiktoklogo.png
--------------------------------------------------------------------------------
/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 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 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 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/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 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/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 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 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 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)
--------------------------------------------------------------------------------
/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 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 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 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 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 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 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 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 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/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 Client/UI/clipUpload.ui:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |