iricardoxd commited on
Commit
fde6ce9
·
1 Parent(s): c602fc5
app.py CHANGED
@@ -1,35 +1,293 @@
 
 
 
 
 
 
1
  import gradio as gr
2
- from selenium import webdriver
3
- from selenium.common.exceptions import WebDriverException
4
- from PIL import Image
5
- from io import BytesIO
6
 
7
- def take_screenshot(url):
8
- options = webdriver.ChromeOptions()
9
- options.add_argument('--headless')
10
- options.add_argument('--no-sandbox')
11
- options.add_argument('--disable-dev-shm-usage')
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  try:
14
- wd = webdriver.Chrome(options=options)
15
- wd.set_window_size(1080, 720) # Adjust the window size here
16
- wd.get(url)
17
- wd.implicitly_wait(10)
18
- screenshot = wd.get_screenshot_as_png()
19
- except WebDriverException as e:
20
- return Image.new('RGB', (1, 1))
21
- finally:
22
- if wd:
23
- wd.quit()
24
-
25
- return Image.open(BytesIO(screenshot))
26
-
27
- iface = gr.Interface(
28
- fn=take_screenshot,
29
- inputs=gr.inputs.Textbox(label="Website URL", default="https://kargaranamir.github.io"),
30
- outputs=gr.Image(type="pil", height=360, width=540), # Adjust the image size here
31
- title="Website Screenshot",
32
- description="Take a screenshot of a website.",
33
- )
34
-
35
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pixabay.core
2
+ import glob
3
+ from moviepy.editor import VideoFileClip, ImageClip, concatenate_videoclips, AudioFileClip,CompositeVideoClip, TextClip
4
+ import requests
5
+ import random
6
+ import moviepy.editor as mp
7
  import gradio as gr
8
+ import time
9
+ import re
10
+ import os
 
11
 
12
+ from googletrans import Translator
 
 
 
 
13
 
14
+ import ffmpeg
15
+
16
+ import json
17
+ import openai
18
+
19
+
20
+ openai.api_key = os.environ["API_KEY"] # 首先要有apikey
21
+
22
+ #openai.api_key = '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'
23
+
24
+ messages = [{"role": "system", "content": "eres una mano derecha"}]
25
+
26
+
27
+ def chatgpt(content, temperature=0.8):
28
+ global messages
29
+ print(f'ask:{content}')
30
+ messages.append({"role": "user", "content": content})
31
+ time.sleep(1)
32
  try:
33
+ response = openai.ChatCompletion.create(
34
+ model="gpt-3.5-turbo-0301", # 或者 gpt-3.5-turbo-0301
35
+ messages=messages,
36
+ temperature=temperature,
37
+ max_tokens=1000,
38
+ top_p=1,
39
+ frequency_penalty=0,
40
+ presence_penalty=0,
41
+ )
42
+ except Exception as e:
43
+ messages = [{"role": "system", "content": "eres una mano derecha"}]
44
+ return 'Este mensaje interactivo ha alcanzado el límite superior, el token se ha borrado, vuelva a ingresar'
45
+ messages.append(json.loads(str(response.choices[0].message)))
46
+ print(f'answer:{response.choices[0].message.content}')
47
+ resulado = response.choices[0].message.content
48
+ resulado = resulado.replace('.','')
49
+ return resulado
50
+
51
+
52
+
53
+ pixabayapi = ''
54
+ def generaraudio2(texto):
55
+ nombre = s=re.sub('[^A-Za-z0-9]+', '',texto)
56
+ nombre = nombre.strip()
57
+ archivo = 'es_'+nombre+'.wav'
58
+ print(archivo)
59
+ existe = glob.glob(archivo)
60
+ texto = texto.lower()
61
+ texto = texto.replace('html5','hache te eme ele cinco')
62
+ texto = texto.replace('html','hache te eme ele')
63
+ texto = texto.replace('pdf','pe de eefe')
64
+ texto = texto.replace('bit.ly','bit punto li!, eslach,')
65
+ texto = texto.replace('.',',')
66
+ texto = texto.replace('1','uno')
67
+ texto = texto.replace('2','dos')
68
+ texto = texto.replace('3','tres')
69
+ texto = texto.replace('4','cuatro')
70
+ texto = texto.replace('5','cinco')
71
+ texto = texto.replace('6','seis')
72
+ texto = texto.replace('7','siete')
73
+ texto = texto.replace('8','ocho')
74
+ texto = texto.replace('9','nueve')
75
+ if not existe:
76
+ response = requests.post("https://iricardoxd-texto-a-voz-aws.hf.space/api/predict", json={
77
+ "data": [
78
+ texto
79
+ ]},headers={'Content-Type':'application/json','Authorization': 'Bearer {}'.format('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')}).json()
80
+ data = response["data"]
81
+ print(response['data'][0]['name'])
82
+ time.sleep(5)
83
+ response = requests.get("https://iricardoxd-texto-a-voz-aws.hf.space/file="+response['data'][0]['name'],headers={'Content-Type':'application/json','Authorization': 'Bearer {}'.format('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')})
84
+ if response.status_code == 200:
85
+ open(archivo, 'wb').write(response.content)
86
+ print('downloaded')
87
+ else:
88
+ print(response.status_code)
89
+ return archivo
90
+ import requests
91
+ import shutil
92
+ def buscar(palabra,category):
93
+ xd = palabra.split()
94
+ xdfile = '_'.join(xd)
95
+ xdfile = xdfile.replace('/','').replace('"','').replace('.','').replace('!','').replace('@','').replace('\\','').replace('\n','').replace('\rn','').replace(':','').replace(',','')
96
+ archivo = category+'_'+xdfile+".mp4"
97
+ #existe = glob.glob(archivo)
98
+ if True:#not existe:
99
+ # Establece tu clave de API de Pexels
100
+ PEXELS_API_KEY = '563492ad6f917000010000011b2f873942744900a033f732a56ae4b7'
101
+ translator = Translator()
102
+ search_term = translator.translate(palabra, dest='en').text#blob.translate(to='en')
103
+ print(search_term)
104
+ # Crea el URL de la API
105
+ api_url = f'https://api.pexels.com/videos/search?query={search_term}&orientation=portrait&per_page=3&locale=en-US'
106
+ print(api_url)
107
+ # Establece los encabezados de la solicitud
108
+ headers = {'Authorization': PEXELS_API_KEY}
109
+
110
+ # Realiza la solicitud a la API
111
+ response = requests.get(api_url, headers=headers)
112
+
113
+ # Verifica si la solicitud fue exitosa
114
+ if response.status_code == 200:
115
+ # Obtiene los resultados de la búsqueda
116
+ results = response.json()['videos']
117
+ #print(results)
118
+ # Itera sobre los resultados
119
+ if len(results)>0:
120
+ ancho = 1
121
+ ancho = ancho - 1
122
+ numero = random.randint(0,ancho)
123
+ print('el numero es ',numero)
124
+ # Obtiene el URL del video
125
+ for videox in results[numero]['video_files']:
126
+ if videox['height']==1920:
127
+ video_url = videox['link']
128
+ else:
129
+ if videox['quality']=='hd':
130
+ video_url = videox['link']
131
+ else:
132
+ video_url = results[numero]['video_files'][0]['link']
133
+ print('el video es',video_url)
134
+
135
+ # Descarga el video
136
+ with requests.get(video_url, stream=True) as r:
137
+ with open(archivo, 'wb') as f:
138
+ shutil.copyfileobj(r.raw, f)
139
+
140
+ print(f'Descargado video {numero} de {len(results)}')
141
+ else:
142
+ api_url2 = f'https://api.pexels.com/v1/search?query={search_term}&per_page=1&orientation=portrait&locale=en-US'
143
+ print(api_url2)
144
+ response2 = requests.get(api_url2, headers=headers)
145
+ if response2.status_code == 200:
146
+ # Obtiene los resultados de la búsqueda
147
+ results2 = response2.json()['photos']
148
+ # Itera sobre los resultados
149
+ if len(results2)>0:
150
+ ancho = 1
151
+ ancho = ancho - 1
152
+ numero = random.randint(0,ancho)
153
+ print('el numero es ',numero)
154
+ # Obtiene el URL del video
155
+ video_url = results2[numero]['src']['portrait']
156
+ imagen = results2[numero]['src']['original']
157
+ filex = imagen.split('/')
158
+ print(' imagen es',video_url)
159
+ x=len(filex)-1
160
+ archivo=filex[x]
161
+ print(' imagen es x ',archivo)
162
+ # Descarga el video
163
+ with requests.get(video_url, stream=True) as r:
164
+ with open(archivo, 'wb') as f:
165
+ shutil.copyfileobj(r.raw, f)
166
+
167
+ print(f'Descargado imagen {numero} de {len(results)}')
168
+ else:
169
+ archivo='doctor.mp4'
170
+ else:
171
+ print('Error al realizar la solicitud a la API')
172
+ return archivo
173
+ def buscarsustantivo(frase):
174
+ if frase !='':
175
+ prompt ='Respond only with one word of videos availables in pixels.com that best expresses the English phrase: '+frase
176
+ return chatgpt(prompt)
177
+
178
+ def combinar(frase, api_pixabay):
179
+ global pixabayapi
180
+ pixabayapi = '38938115-4ffafdfccf94d14e4d35899f5'
181
+ category="travel"
182
+ clips = []
183
+ palabras = frase.split(' ')
184
+ palabras_frases = frase.split('.')
185
+ frases = []
186
+ i = 0
187
+ linea = ''
188
+ for palabra in palabras:
189
+ i = i + 1
190
+ linea = linea + ' ' + palabra
191
+ escena_palabras = random.randint(1, 5)
192
+ if i>escena_palabras or '.' in linea or '?' in linea or '!' in linea:
193
+ i=0
194
+ frases.append(linea)
195
+ linea = ''
196
+ if linea !='':
197
+ frases.append(linea)
198
+ duracionTotal = 0
199
+ for lineaf in frases:
200
+ tema = buscarsustantivo(lineaf)
201
+ palabras_de_tema = tema.split(' ')
202
+ if len(palabras_de_tema)>1:
203
+ tema = palabras_de_tema[0]
204
+ #for tema in temas:
205
+ if len(tema)>0:
206
+ palabraclave = tema
207
+ else:
208
+ palabraclave = frases[0]
209
+ print(palabraclave)
210
+ resultado = buscar(palabraclave,category)
211
+ if(resultado=='lv_0_20230901171552.mp4'):
212
+ tema = buscarsustantivo(lineaf)
213
+ palabras_de_tema = tema.split(' ')
214
+ if len(palabras_de_tema)>1:
215
+ tema = palabras_de_tema[0]
216
+ #for tema in temas:
217
+ if len(tema)>0:
218
+ palabraclave = tema
219
+ else:
220
+ palabraclave = frases[0]
221
+ print(palabraclave)
222
+ resultado = buscar(palabraclave,category)
223
+ if resultado!=None:
224
+ velocidadhabla = 15#catacteres por segundos 18 teresa, 22 huggie
225
+ print(len(lineaf))
226
+ duracion = len(lineaf)/velocidadhabla
227
+ print(resultado)
228
+ try:
229
+ audiofile = generaraudio2(lineaf)
230
+ clip1 = VideoFileClip(generate_subtitled_video(resultado,lineaf))
231
+ audio = AudioFileClip(audiofile)
232
+ #duracion = audio.duration
233
+ duracionTotal = duracionTotal + duracion
234
+ subclip = clip1.subclip(0, duracion)
235
+ resized_clip = subclip.resize((1080, 1920))
236
+ video = resized_clip.resize(height=1920)
237
+ final_clip = video#final_clip = video.crop(x1=1166.6,y1=0,x2=2246.6,y2=1920)
238
+ final_clip.audio = audio
239
+ clips.append(final_clip)
240
+ except Exception as e:
241
+ print(f'Ocurrió un error: {e}')
242
+ clip = ImageClip(resultado)
243
+ # Establece la duración del clip en segundos
244
+ clip = clip.set_duration(5)
245
+ # Guarda el clip como un archivo de video
246
+ clip.write_videofile('videoxd.mp4', fps=24)
247
+ audiofile = generaraudio2(lineaf)
248
+ clip1 = VideoFileClip(generate_subtitled_video('videoxd.mp4',lineaf))#generate_subtitled_video('videoxd.mp4',audiofile,lineaf))
249
+ audio = AudioFileClip(audiofile)
250
+ #duracion = audio.duration
251
+ duracionTotal = duracionTotal + duracion
252
+ subclip = clip1.subclip(0, duracion)
253
+ resized_clip = subclip.resize((1080, 1920))
254
+ video = resized_clip.resize(height=1920)
255
+ final_clip = video#final_clip = video.crop(x1=1166.6,y1=0,x2=2246.6,y2=1920)
256
+ #final_clip.audio = audio
257
+ clips.append(final_clip)
258
+ filename=palabras_frases[0]+'.mp4'
259
+ if len(clips)>0:
260
+ audiofilefinal = generaraudio2(frase)
261
+ final_clip = concatenate_videoclips(clips)
262
+ final_clip.audio = AudioFileClip(audiofilefinal)
263
+ final_clip.write_videofile(filename)
264
+ print('listo video')
265
+ final = filename
266
+ subir(filename,palabras_frases[0])
267
+ html= '<a href="file/'+final+'" download>Descargar video</a><a href="file/final.mp4" download>Descargar video</a>'
268
+ return (final,html)
269
+ def generate_subtitled_video(video, transcript):
270
+ input = ffmpeg.input(video)
271
+ output = ffmpeg.output(input, 'srt'+video, vf="drawtext=text='"+transcript+"':fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=((h-text_h-line_h)/9)*10")
272
+ output.run(quiet=True, overwrite_output=True)
273
+ video_with_subs = 'srt'+video
274
+ print(video_with_subs)
275
+ return video_with_subs
276
+ def subir(video,descripcion):
277
+ from upload import upload_videos
278
+ from auth import AuthBackend
279
+
280
+ videos = [
281
+ {
282
+ 'video': video,
283
+ 'description': descripcion
284
+ }
285
+ ]
286
+
287
+ auth = AuthBackend(cookies='cookies.txt')
288
+ failed_videos = upload_videos(videos=videos, auth=auth)
289
+
290
+ for video in failed_videos: # each input video object which failed
291
+ print(f'{video["video"]} with description "{video["description"]}" failed')
292
+ demo = gr.Interface(fn=combinar, inputs=["text","text"], outputs=["video","html"])
293
+ demo.launch()
auth.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Handles authentication for TikTokUploader"""
2
+ from http import cookiejar
3
+ from time import time, sleep
4
+
5
+ from selenium.webdriver.common.by import By
6
+
7
+ from selenium.webdriver.support.ui import WebDriverWait
8
+ from selenium.webdriver.support import expected_conditions as EC
9
+
10
+ from tiktok_uploader import config, logger
11
+ from tiktok_uploader.browsers import get_browser
12
+ from tiktok_uploader.utils import green
13
+
14
+ class AuthBackend:
15
+ """
16
+ Handles authentication for TikTokUploader
17
+ """
18
+ username: str
19
+ password: str
20
+ cookies: list
21
+
22
+ def __init__(self, username: str = '', password: str = '',
23
+ cookies_list: list = None, cookies=None, cookies_str=None, sessionid: str = None):
24
+ """
25
+ Creates the authenticaiton backend
26
+
27
+ Keyword arguments:
28
+ - username -> the accounts's username or email
29
+ - password -> the account's password
30
+
31
+ - cookies -> a list of cookie dictionaries of cookies which is Selenium-compatable
32
+ """
33
+ if (username and not password) or (password and not username):
34
+ raise InsufficientAuth()
35
+
36
+ self.cookies = self.get_cookies(path=cookies) if cookies else []
37
+ self.cookies += self.get_cookies(cookies_str=cookies_str) if cookies_str else []
38
+ self.cookies += cookies_list if cookies_list else []
39
+ self.cookies += [{'name': 'sessionid', 'value': sessionid}] if sessionid else []
40
+
41
+ if not (self.cookies or (username and password)):
42
+ raise InsufficientAuth()
43
+
44
+ self.username = username
45
+ self.password = password
46
+
47
+ if cookies:
48
+ logger.debug(green("Authenticating browser with cookies"))
49
+ elif username and password:
50
+ logger.debug(green("Authenticating browser with username and password"))
51
+ elif sessionid:
52
+ logger.debug(green("Authenticating browser with sessionid"))
53
+ elif cookies_list:
54
+ logger.debug(green("Authenticating browser with cookies_list"))
55
+
56
+
57
+ def authenticate_agent(self, driver):
58
+ """
59
+ Authenticates the agent using the browser backend
60
+ """
61
+ # tries to use cookies
62
+ if not self.cookies and self.username and self.password:
63
+ self.cookies = login(driver, username=self.username, password=self.password)
64
+
65
+ logger.debug(green("Authenticating browser with cookies"))
66
+
67
+ driver.get(config['paths']['main'])
68
+
69
+ WebDriverWait(driver, config['explicit_wait']).until(EC.title_contains("TikTok"))
70
+
71
+ for cookie in self.cookies:
72
+ try:
73
+ driver.add_cookie(cookie)
74
+ except Exception as _:
75
+ logger.error('Failed to add cookie %s', cookie)
76
+
77
+ return driver
78
+
79
+
80
+ def get_cookies(self, path: str = None, cookies_str: str = None) -> dict:
81
+ """
82
+ Gets cookies from the passed file using the netscape standard
83
+ """
84
+ if path:
85
+ with open(path, "r", encoding="utf-8") as file:
86
+ lines = file.read().split("\n")
87
+ else:
88
+ lines = cookies_str.split("\n")
89
+
90
+ return_cookies = []
91
+ for line in lines:
92
+ split = line.split('\t')
93
+ if len(split) < 6:
94
+ continue
95
+
96
+ split = [x.strip() for x in split]
97
+
98
+ try:
99
+ split[4] = int(split[4])
100
+ except ValueError:
101
+ split[4] = None
102
+
103
+ return_cookies.append({
104
+ 'name': split[5],
105
+ 'value': split[6],
106
+ 'domain': split[0],
107
+ 'path': split[2],
108
+ })
109
+
110
+ if split[4]:
111
+ return_cookies[-1]['expiry'] = split[4]
112
+ return return_cookies
113
+
114
+
115
+ def login_accounts(driver=None, accounts=[(None, None)], *args, **kwargs) -> list:
116
+ """
117
+ Authenticates the accounts using the browser backend and saves the required credentials
118
+
119
+ Keyword arguments:
120
+ - driver -> the webdriver to use
121
+ - accounts -> a list of tuples of the form (username, password)
122
+ """
123
+ driver = driver or get_browser(headless=True, *args, **kwargs)
124
+
125
+ cookies = {}
126
+ for account in accounts:
127
+ username, password = get_username_and_password(account)
128
+
129
+ cookies[username] = login(driver, username, password)
130
+
131
+ return cookies
132
+
133
+
134
+ def login(driver, username: str, password: str):
135
+ """
136
+ Logs in the user using the email and password
137
+ """
138
+ assert username and password, "Username and password are required"
139
+
140
+ # checks if the browser is on TikTok
141
+ if not config['paths']['main'] in driver.current_url:
142
+ driver.get(config['paths']['main'])
143
+
144
+ # checks if the user is already logged in
145
+ if driver.get_cookie(config['selectors']['login']['cookie_of_interest']):
146
+ # clears the existing cookies
147
+ driver.delete_all_cookies()
148
+
149
+ # goes to the login site
150
+ driver.get(config['paths']['login'])
151
+
152
+ # selects and fills the login and the password
153
+ username_field = WebDriverWait(driver, config['explicit_wait']).until(
154
+ EC.presence_of_element_located((By.XPATH, config['selectors']['login']['username_field']))
155
+ )
156
+ username_field.clear()
157
+ username_field.send_keys(username)
158
+
159
+ password_field = driver.find_element(By.XPATH, config['selectors']['login']['password_field'])
160
+ password_field.clear()
161
+ password_field.send_keys(password)
162
+
163
+ # submits the form
164
+ submit = driver.find_element(By.XPATH, config['selectors']['login']['login_button'])
165
+ submit.click()
166
+
167
+ print(f'Complete the captcha for {username}')
168
+
169
+ # Wait until the session id cookie is set
170
+ start_time = time()
171
+ while not driver.get_cookie(config['selectors']['login']['cookie_of_interest']):
172
+ sleep(0.5)
173
+ if time() - start_time > config['explicit_wait']:
174
+ raise InsufficientAuth() # TODO: Make this something more real
175
+
176
+ # wait until the url changes
177
+ WebDriverWait(driver, config['explicit_wait']).until(EC.url_changes(config['paths']['login']))
178
+
179
+ return driver.get_cookies()
180
+
181
+
182
+ def get_username_and_password(login_info: tuple or dict):
183
+ """
184
+ Parses the input into a username and password
185
+ """
186
+ if not isinstance(login_info, dict):
187
+ return login_info[0], login_info[1]
188
+
189
+ # checks if they used email or username
190
+ if 'email' in login_info:
191
+ return login_info['email'], login_info['password']
192
+ elif 'username' in login_info:
193
+ return login_info['username'], login_info['password']
194
+
195
+ raise InsufficientAuth()
196
+
197
+
198
+ def save_cookies(path, cookies: list):
199
+ """
200
+ Saves the cookies to a netscape file
201
+ """
202
+ # saves the cookies to a file
203
+ cookie_jar = cookiejar.MozillaCookieJar(path)
204
+ cookie_jar.load()
205
+
206
+ for cookie in cookies:
207
+ cookie_jar.set_cookie(cookie)
208
+
209
+ cookie_jar.save()
210
+
211
+
212
+ class InsufficientAuth(Exception):
213
+ """
214
+ Insufficient authentication:
215
+
216
+ > TikTok uses cookies to keep track of the user's authentication or session.
217
+
218
+ Either:
219
+ - Use a cookies file passed as the `cookies` argument
220
+ - easily obtained using https://github.com/kairi003/Get-cookies.txt-LOCALLY
221
+ - Use a cookies list passed as the `cookies_list` argument
222
+ - can be obtained from your browser's developer tools under storage -> cookies
223
+ - only the `sessionid` cookie is required
224
+ """
225
+
226
+ def __init__(self, message=None):
227
+ super().__init__(message or self.__doc__)
config.toml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TikTok Uploader Default Configuation File
2
+
3
+ headless = true
4
+ quit_on_end = true
5
+
6
+ # Messing around with inputs
7
+ valid_path_names = ["path", "filename", "video", "video_path"]
8
+ valid_descriptions = ["description", "desc", "caption"]
9
+
10
+ # Selenium Webdriver Waits
11
+ implicit_wait = 5 # seconds
12
+ explicit_wait = 60 # seconds
13
+
14
+ supported_file_types = ["mp4", "mov", "avi", "wmv", "flv", "webm", "mkv", "m4v", "3gp", "3g2", "gif"]
15
+
16
+ max_description_length = 150 # characters
17
+
18
+ [paths]
19
+ main = "https://www.tiktok.com/"
20
+ login = "https://www.tiktok.com/login/phone-or-email/email"
21
+ upload = "https://www.tiktok.com/creator-center/upload?from=upload"
22
+
23
+ [disguising]
24
+ user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
25
+
26
+ [selectors] # Selenium XPATH selectors
27
+
28
+ [selectors.login]
29
+ username_field = "//input[@name=\"username\"]"
30
+ password_field = "//input[@type=\"password\"]"
31
+ login_button = "//button[@type=\"submit\"]"
32
+
33
+ alert_user_if_failed = true # sends an alert and waits instead of failing
34
+
35
+ cookie_of_interest = "sessionid" # cookie to check if login was successful
36
+
37
+ [selectors.upload]
38
+ iframe = "//iframe"
39
+
40
+ upload_video = "//input[@type='file']"
41
+ upload_in_progress = "//*[.='Cancelar']"
42
+ upload_confirmation = "//video"
43
+ process_confirmation = "//img[@draggable='false']"
44
+
45
+ description = "//div[@contenteditable='true']"
46
+
47
+ visibility = "//div[@class='tiktok-select-selector']"
48
+ options = ["Public", "Friends", "Private"]
49
+
50
+ hashtags = "//div[@class='mentionSuggestions']//*[contains(text(), '{}')]"
51
+ mentions = "//div[contains(concat(' ', normalize-space(@class), ' '), 'user-id') and .='{}']/.."
52
+
53
+ mention_box = "//input[contains(concat(' ', normalize-space(@class), ' '), 'search-friends')]"
54
+
55
+ comment = "//label[.='Comment']/following-sibling::div/input"
56
+ duet = "//label[.='Duet']/following-sibling::div/input"
57
+ stitch = "//label[.='Stitch']/following-sibling::div/input"
58
+
59
+ post = "//div[contains(@class, 'btn-post')]"
60
+ post_confirmation = "//div[.='Your videos are being uploaded to TikTok!']"
61
+
62
+ [selectors.schedule]
63
+ switch = "//*[@id='tux-3']"
64
+
65
+ date_picker = "//div[contains(@class, 'date-picker-input')]"
66
+ calendar = "//div[contains(@class, 'calendar-wrapper')]"
67
+ calendar_month = "//span[contains(@class, 'month-title')]"
68
+ calendar_valid_days = "//div[@class='jsx-4172176419 days-wrapper']//span[contains(@class, 'day') and contains(@class, 'valid')]"
69
+ calendar_arrows = "//span[contains(@class, 'arrow')]" # first last, second next
70
+
71
+ time_picker = "//div[contains(@class, 'time-picker-input')]"
72
+ time_picker_text = "//div[contains(@class, 'time-picker-input')]/*[1]"
73
+ time_picker_container = "//div[@class='tiktok-timepicker-time-picker-container']"
74
+ timepicker_hours = "//span[contains(@class, 'tiktok-timepicker-left')]"
75
+ timepicker_minutes = "//span[contains(@class, 'tiktok-timepicker-right')]"
cookies.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Netscape HTTP Cookie File
2
+ # http://curl.haxx.se/rfc/cookie_spec.html
3
+ # This is a generated file! Do not edit.
4
+
5
+ .www.tiktok.com TRUE / TRUE 1696204457 tiktok_webapp_theme light
6
+ .tiktok.com TRUE / TRUE 1700680950 passport_csrf_token 2fcbe4468644121124e160b2bd58f363
7
+ .tiktok.com TRUE / FALSE 1700680950 passport_csrf_token_default 2fcbe4468644121124e160b2bd58f363
8
+ .tiktok.com TRUE / TRUE 1711151649 tt_chain_token Rt2x5VfQLupBGHX2zAaeKQ==
9
+ .www.tiktok.com TRUE / FALSE 1696204455 __tea_cache_tokens_1988 {%22_type_%22:%22default%22%2C%22user_unique_id%22:%227282105198122092075%22%2C%22timestamp%22:1695497380126}
10
+ .tiktok.com TRUE / TRUE 1700681741 multi_sids 7282105716742734890%3A747d8454c7769596ecaf4ae3d1579662
11
+ .tiktok.com TRUE / TRUE 1700681741 cmpl_token AgQQAPNSF-RO0rUx0janeB0T_26lK8CI_6nZYMy3Aw
12
+ .tiktok.com TRUE / TRUE 1711049741 sid_guard 747d8454c7769596ecaf4ae3d1579662%7C1695497740%7C15552000%7CThu%2C+21-Mar-2024+19%3A35%3A40+GMT
13
+ .tiktok.com TRUE / TRUE 1711049741 uid_tt 16c13984b11d11e7bcbf81c40c4fc99d4589d115466d2c20f7579350a6ffedd5
14
+ .tiktok.com TRUE / TRUE 1711049741 uid_tt_ss 16c13984b11d11e7bcbf81c40c4fc99d4589d115466d2c20f7579350a6ffedd5
15
+ .tiktok.com TRUE / TRUE 1711049741 sid_tt 747d8454c7769596ecaf4ae3d1579662
16
+ .tiktok.com TRUE / TRUE 1711049741 sessionid 747d8454c7769596ecaf4ae3d1579662
17
+ .tiktok.com TRUE / TRUE 1711049741 sessionid_ss 747d8454c7769596ecaf4ae3d1579662
18
+ .tiktok.com TRUE / TRUE 1711049741 sid_ucp_v1 1.0.0-KDcyZWZmZjFjMWViNTgwMWMzZDQ3NjRhNTU1NzRjMGIxY2QyM2JkMTUKIAiqiKv6kpLPh2UQjPy8qAYYswsgDDCf-byoBjgBQOsHEAQaB3VzZWFzdDUiIDc0N2Q4NDU0Yzc3Njk1OTZlY2FmNGFlM2QxNTc5NjYy
19
+ .tiktok.com TRUE / TRUE 1711049741 ssid_ucp_v1 1.0.0-KDcyZWZmZjFjMWViNTgwMWMzZDQ3NjRhNTU1NzRjMGIxY2QyM2JkMTUKIAiqiKv6kpLPh2UQjPy8qAYYswsgDDCf-byoBjgBQOsHEAQaB3VzZWFzdDUiIDc0N2Q4NDU0Yzc3Njk1OTZlY2FmNGFlM2QxNTc5NjYy
20
+ .tiktok.com TRUE / FALSE 1711049740 store-idc useast5
21
+ .tiktok.com TRUE / FALSE 1711049740 store-country-code us
22
+ .tiktok.com TRUE / FALSE 1711049740 store-country-code-src uid
23
+ .tiktok.com TRUE / FALSE 1711049740 tt-target-idc useast5
24
+ .tiktok.com TRUE / FALSE 1711049754 tt-target-idc-sign 3G_umYol_pR5-9ql6JlmS_vAG-oxfye8T1PW1DwPhJstsWwuBM2naJyAcfB0zdi3iVioRSpKo9S7EMPiSOtqiUbUo8-iB_LMGkSkCCB8NjPZxq3XvWw4TuTHJrAQE-KKAWVvLMPRpGHhmPYaiaVS3V6rRCKbGyZXvw26F9jfVNOPTrobntczrQMSR7wnwLPfyIKmKLsqZ3FZUS8u8ZakrgZhVK1c9P9wRHo0rdrWJ8p871cqBu9lVYoeVFL-eT8LaYN3EjWJK_S3ySqXuIJTsABbn9RokzBI4kEIx7Rysds1bBidJPPC6N29PkJbwSnaniSy4Q1kRu9oQNl-d80LsqMcRaP6gj19zVkmwoTBQ7bXaIJoo1-nq2WGA7EhlTroDhbxsS_mnV1e12UASiigajxfccDY8k3bU4qej4FQj0_73OEQVpRayxr9jK34w9CgGozzxVRFfyIAtAnTkpwZmTzqxrOhYn5JW3mI4HaK-xtAeI88l34FC6uXMw2Yg0aH
25
+ .tiktok.com TRUE / TRUE 0 tt_csrf_token YVpr7bGu-pagI4x0nJWqcHju8QT9akkzF-K4
26
+ .www.tiktok.com TRUE / FALSE 0 passport_fe_beating_status true
27
+ .tiktok.com TRUE / TRUE 1711151664 ttwid 1%7CBffw97Kt4MoFtptEg0otV7ohXh9iyAh9ShYIEX2fUug%7C1695599664%7Cf95f7655258ee6bd0a66af03e4bb410e33f3fc54711a8e9e03f1f55507ae47a8
28
+ .tiktok.com TRUE / FALSE 1711151665 odin_tt 9c735b8191086bf88a56b57173f76d2b543edca8c95410eee89502a1f33861ebe6c9b08c2e01e2eb2e9c21cddc0eb82fadfdd2a9a8b738b16202e183f3ae0003ab015dc977f2a30bc485784adf36040a
29
+ www.tiktok.com FALSE / TRUE 0 csrf_session_id 91570471488ad14ddabf4f3f567cf8b4
30
+ .tiktok.com TRUE / TRUE 1696463793 msToken ULaJAPtAQlIhCv-aMXKoBMeeLfw0h_LYPD4nyD6nifbdb8c5rb-72ohf2PRIpn3DUwz8aD4HX-RTr6lB946CB2qqV3ulguPmFOJ7yWC9i4nHCA83Z3kaChw71JJr5Yif_iFbbIe-Cegg
31
+ www.tiktok.com FALSE / FALSE 1696204593 msToken ULaJAPtAQlIhCv-aMXKoBMeeLfw0h_LYPD4nyD6nifbdb8c5rb-72ohf2PRIpn3DUwz8aD4HX-RTr6lB946CB2qqV3ulguPmFOJ7yWC9i4nHCA83Z3kaChw71JJr5Yif_iFbbIe-Cegg
proxy_auth_extension/__init__.py ADDED
File without changes
proxy_auth_extension/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (141 Bytes). View file
 
proxy_auth_extension/__pycache__/proxy_auth_extension.cpython-310.pyc ADDED
Binary file (2.15 kB). View file
 
proxy_auth_extension/background.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var config = {
2
+ mode: "fixed_servers",
3
+ rules: {
4
+ singleProxy: {
5
+ scheme: "http",
6
+ host: "{{ proxy_host }}",
7
+ port: parseInt("{{ proxy_port }}")
8
+ },
9
+ bypassList: ["localhost"]
10
+ }
11
+ };
12
+
13
+ chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
14
+
15
+ function callbackFn(details) {
16
+ return {
17
+ authCredentials: {
18
+ username: "{{ proxy_user }}",
19
+ password: "{{ proxy_pass }}"
20
+ }
21
+ };
22
+ }
23
+
24
+ chrome.webRequest.onAuthRequired.addListener(
25
+ callbackFn,
26
+ {urls: ["<all_urls>"]},
27
+ ['blocking']
28
+ );
proxy_auth_extension/manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0.0",
3
+ "manifest_version": 2,
4
+ "name": "Chrome Proxy",
5
+ "permissions": [
6
+ "proxy",
7
+ "tabs",
8
+ "unlimitedStorage",
9
+ "storage",
10
+ "<all_urls>",
11
+ "webRequest",
12
+ "webRequestBlocking"
13
+ ],
14
+ "background": {
15
+ "scripts": ["background.js"]
16
+ },
17
+ "minimum_chrome_version":"22.0.0"
18
+ }
proxy_auth_extension/proxy_auth_extension.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import zipfile
2
+ import os
3
+ import json
4
+ from selenium.webdriver.common.by import By
5
+
6
+
7
+ def replace_variables_in_js(js_content: str, variables_dict: dict):
8
+ for variable, value in variables_dict.items():
9
+ js_content = js_content.replace('{{ ' + variable + ' }}', value)
10
+ return js_content
11
+
12
+
13
+ def generate_proxy_auth_extension(
14
+ proxy_host: str, proxy_port: str, proxy_user: str, proxy_pass: str,
15
+ extension_file: str):
16
+ """Generate a Chrome extension that modify proxy settings based on desired host, port, username and password.
17
+
18
+ If you are using --headless in chromedriver, you must use --headless=new to support extensions in headless mode.
19
+ """
20
+ current_dir = os.path.dirname(os.path.abspath(__file__))
21
+ manifest_json_path = os.path.join(current_dir, 'manifest.json')
22
+ background_js_path = os.path.join(current_dir, 'background.js')
23
+ with open(manifest_json_path, 'r', encoding='utf-8') as f:
24
+ manifest_json = f.read()
25
+ with open(background_js_path, 'r', encoding='utf-8') as f:
26
+ background_js = f.read()
27
+
28
+ variables_dict = {
29
+ 'proxy_host': proxy_host,
30
+ 'proxy_port': proxy_port,
31
+ 'proxy_user': proxy_user,
32
+ 'proxy_pass': proxy_pass
33
+ }
34
+ background_js = replace_variables_in_js(background_js, variables_dict)
35
+
36
+ with zipfile.ZipFile(extension_file, 'w') as zp:
37
+ zp.writestr('manifest.json', manifest_json)
38
+ zp.writestr('background.js', background_js)
39
+
40
+
41
+ def get_my_ip(driver):
42
+ origin_tab = driver.current_window_handle
43
+ driver.execute_script("window.open('', '_blank');")
44
+ driver.switch_to.window(driver.window_handles[-1])
45
+
46
+ driver.get('https://api.ipify.org/?format=json')
47
+
48
+ ip_row = driver.find_element(By.XPATH, '//body').text
49
+ ip = json.loads(ip_row)['ip']
50
+
51
+ driver.close()
52
+ driver.switch_to.window(origin_tab)
53
+
54
+ return ip
55
+
56
+
57
+ def proxy_is_working(driver, host: str):
58
+ ip = get_my_ip(driver)
59
+
60
+ if ip == host:
61
+ return True
62
+ else:
63
+ return False
requirements.txt CHANGED
@@ -1,3 +1,15 @@
1
  selenium >=4.0.0, < 5.0.0
2
  gradio>=3.40.1
3
  Pillow>=8.3.1,<9.0
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  selenium >=4.0.0, < 5.0.0
2
  gradio>=3.40.1
3
  Pillow>=8.3.1,<9.0
4
+ googletrans==4.0.0rc1
5
+ moviepy==1.0.3
6
+ SpeechRecognition
7
+ glob2
8
+ spacy
9
+ voicerss_tts
10
+ requests
11
+ fakeyou
12
+ openai==0.27.0
13
+ pixabay==0.0.4
14
+ ffmpeg-python
15
+ tiktok_uploader
screen.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from selenium import webdriver
3
+ from selenium.common.exceptions import WebDriverException
4
+ from PIL import Image
5
+ from io import BytesIO
6
+
7
+ def take_screenshot(url):
8
+ options = webdriver.ChromeOptions()
9
+ options.add_argument('--headless')
10
+ options.add_argument('--no-sandbox')
11
+ options.add_argument('--disable-dev-shm-usage')
12
+
13
+ try:
14
+ wd = webdriver.Chrome(options=options)
15
+ wd.set_window_size(1080, 720) # Adjust the window size here
16
+ wd.get(url)
17
+ wd.implicitly_wait(10)
18
+ screenshot = wd.get_screenshot_as_png()
19
+ except WebDriverException as e:
20
+ return Image.new('RGB', (1, 1))
21
+ finally:
22
+ if wd:
23
+ wd.quit()
24
+
25
+ return Image.open(BytesIO(screenshot))
26
+
27
+ iface = gr.Interface(
28
+ fn=take_screenshot,
29
+ inputs=gr.inputs.Textbox(label="Website URL", default="https://kargaranamir.github.io"),
30
+ outputs=gr.Image(type="pil", height=360, width=540), # Adjust the image size here
31
+ title="Website Screenshot",
32
+ description="Take a screenshot of a website.",
33
+ )
34
+
35
+ iface.launch()
srt.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import ffmpeg
2
+
3
+ input = ffmpeg.input('ninja300123.mp4')
4
+ output = ffmpeg.output(input, 'outputxd.mp4', vf="drawtext=text='Hola Mundo':x=10:y=H-th-10:fontsize=24:fontcolor=white")
5
+ output.run()
upload.py ADDED
@@ -0,0 +1,706 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ `tiktok_uploader` module for uploading videos to TikTok
3
+
4
+ Key Functions
5
+ -------------
6
+ upload_video : Uploads a single TikTok video
7
+ upload_videos : Uploads multiple TikTok videos
8
+ """
9
+ from os.path import abspath, exists
10
+ from typing import List
11
+ import time
12
+ import pytz
13
+ import datetime
14
+
15
+ from selenium.webdriver.common.by import By
16
+
17
+ from selenium.webdriver.common.action_chains import ActionChains
18
+ from selenium.webdriver.support.ui import WebDriverWait
19
+ from selenium.webdriver.support import expected_conditions as EC
20
+ from selenium.webdriver.common.keys import Keys
21
+
22
+ from tiktok_uploader.browsers import get_browser
23
+ from tiktok_uploader.auth import AuthBackend
24
+ from tiktok_uploader import logger
25
+ from tiktok_uploader.utils import bold, green
26
+ import toml
27
+ config = toml.load("./config.toml")
28
+ from proxy_auth_extension.proxy_auth_extension import proxy_is_working
29
+
30
+
31
+ def upload_video(filename=None, description='', schedule: datetime.datetime = None, username='',
32
+ password='', cookies='', sessionid=None, cookies_list=None, cookies_str=None, proxy=None, *args, **kwargs):
33
+ """
34
+ Uploads a single TikTok video.
35
+
36
+ Conder using `upload_videos` if using multiple videos
37
+
38
+ Parameters
39
+ ----------
40
+ filename : str
41
+ The path to the video to upload
42
+ description : str
43
+ The description to set for the video
44
+ schedule: datetime.datetime
45
+ The datetime to schedule the video, must be naive or aware with UTC timezone, if naive it will be aware with UTC timezone
46
+ cookies : str
47
+ The cookies to use for uploading
48
+ sessionid: str
49
+ The `sessionid` is the only required cookie for uploading,
50
+ but it is recommended to use all cookies to avoid detection
51
+ """
52
+ auth = AuthBackend(username=username, password=password, cookies=cookies,
53
+ cookies_list=cookies_list, cookies_str=cookies_str, sessionid=sessionid)
54
+
55
+ return upload_videos(
56
+ videos=[ { 'path': filename, 'description': description, 'schedule': schedule } ],
57
+ auth=auth,
58
+ proxy=proxy,
59
+ *args, **kwargs
60
+ )
61
+
62
+
63
+ def upload_videos(videos: list = None, auth: AuthBackend = None, proxy: dict = None, browser='chrome',
64
+ browser_agent=None, on_complete=None, headless=True, num_retires : int = 1, *args, **kwargs):
65
+ """
66
+ Uploads multiple videos to TikTok
67
+
68
+ Parameters
69
+ ----------
70
+ videos : list
71
+ A list of dictionaries containing the video's ('path') and description ('description')
72
+ proxy: dict
73
+ A dictionary containing the proxy user, pass, host and port
74
+ browser : str
75
+ The browser to use for uploading
76
+ browser_agent : selenium.webdriver
77
+ A selenium webdriver object to use for uploading
78
+ on_complete : function
79
+ A function to call when the upload is complete
80
+ headless : bool
81
+ Whether or not the browser should be run in headless mode
82
+ num_retries : int
83
+ The number of retries to attempt if the upload fails
84
+ options : SeleniumOptions
85
+ The options to pass into the browser -> custom privacy settings, etc.
86
+ *args :
87
+ Additional arguments to pass into the upload function
88
+ **kwargs :
89
+ Additional keyword arguments to pass into the upload function
90
+
91
+ Returns
92
+ -------
93
+ failed : list
94
+ A list of videos which failed to upload
95
+ """
96
+ videos = _convert_videos_dict(videos)
97
+
98
+ if videos and len(videos) > 1:
99
+ logger.debug("Uploading %d videos", len(videos))
100
+
101
+ if not browser_agent: # user-specified browser agent
102
+ logger.debug('Create a %s browser instance %s', browser,
103
+ 'in headless mode' if headless else '')
104
+ driver = get_browser(name=browser, headless=headless, proxy=proxy, *args, **kwargs)
105
+ else:
106
+ logger.debug('Using user-defined browser agent')
107
+ driver = browser_agent
108
+ if proxy:
109
+ if proxy_is_working(driver, proxy['host']):
110
+ logger.debug(green('Proxy is working'))
111
+ else:
112
+ logger.error('Proxy is not working')
113
+ driver.quit()
114
+ raise Exception('Proxy is not working')
115
+ driver = auth.authenticate_agent(driver)
116
+
117
+ failed = []
118
+ # uploads each video
119
+ for video in videos:
120
+ try:
121
+ path = abspath(video.get('path'))
122
+ description = video.get('description', '')
123
+ schedule = video.get('schedule', None)
124
+
125
+ logger.debug('Posting %s%s', bold(video.get('path')),
126
+ f'\n{" " * 15}with description: {bold(description)}' if description else '')
127
+
128
+ # Video must be of supported type
129
+ if not _check_valid_path(path):
130
+ print(f'{path} is invalid, skipping')
131
+ failed.append(video)
132
+ continue
133
+
134
+ # Video must have a valid datetime for tiktok's scheduler
135
+ if schedule:
136
+ timezone = pytz.UTC
137
+ if schedule.tzinfo is None:
138
+ schedule = schedule.astimezone(timezone)
139
+ elif int(schedule.utcoffset().total_seconds()) == 0: # Equivalent to UTC
140
+ schedule = timezone.localize(schedule)
141
+ else:
142
+ print(f'{schedule} is invalid, the schedule datetime must be naive or aware with UTC timezone, skipping')
143
+ failed.append(video)
144
+ continue
145
+
146
+ valid_tiktok_minute_multiple = 5
147
+ schedule = _get_valid_schedule_minute(schedule, valid_tiktok_minute_multiple)
148
+ if not _check_valid_schedule(schedule):
149
+ print(f'{schedule} is invalid, the schedule datetime must be as least 20 minutes in the future, and a maximum of 10 days, skipping')
150
+ failed.append(video)
151
+ continue
152
+
153
+ complete_upload_form(driver, path, description, schedule,
154
+ num_retires=num_retires, headless=headless,
155
+ *args, **kwargs)
156
+ except Exception as exception:
157
+ logger.error('Failed to upload %s', path)
158
+ logger.error(exception)
159
+ failed.append(video)
160
+
161
+ if on_complete is callable: # calls the user-specified on-complete function
162
+ on_complete(video)
163
+
164
+ if config['quit_on_end']:
165
+ driver.quit()
166
+
167
+ return failed
168
+
169
+
170
+ def complete_upload_form(driver, path: str, description: str, schedule: datetime.datetime, headless=True, *args, **kwargs) -> None:
171
+ """
172
+ Actually uploades each video
173
+
174
+ Parameters
175
+ ----------
176
+ driver : selenium.webdriver
177
+ The selenium webdriver to use for uploading
178
+ path : str
179
+ The path to the video to upload
180
+ """
181
+ _go_to_upload(driver)
182
+ _set_video(driver, path=path, **kwargs)
183
+ _set_interactivity(driver, **kwargs)
184
+ _set_description(driver, description)
185
+ if schedule:
186
+ _set_schedule_video(driver, schedule)
187
+ _post_video(driver)
188
+
189
+
190
+ def _go_to_upload(driver) -> None:
191
+ """
192
+ Navigates to the upload page, switches to the iframe and waits for it to load
193
+
194
+ Parameters
195
+ ----------
196
+ driver : selenium.webdriver
197
+ """
198
+ logger.debug(green('Navigating to upload page'))
199
+
200
+ driver.get(config['paths']['upload'])
201
+
202
+ # changes to the iframe
203
+ iframe_selector = EC.presence_of_element_located(
204
+ (By.XPATH, config['selectors']['upload']['iframe'])
205
+ )
206
+ iframe = WebDriverWait(driver, config['explicit_wait']).until(iframe_selector)
207
+ driver.switch_to.frame(iframe)
208
+
209
+ # waits for the iframe to load
210
+ root_selector = EC.presence_of_element_located((By.ID, 'root'))
211
+ WebDriverWait(driver, config['explicit_wait']).until(root_selector)
212
+
213
+
214
+ def _set_description(driver, description: str) -> None:
215
+ """
216
+ Sets the description of the video
217
+
218
+ Parameters
219
+ ----------
220
+ driver : selenium.webdriver
221
+ description : str
222
+ The description to set
223
+ """
224
+ if description is None:
225
+ # if no description is provided, filename
226
+ return
227
+
228
+ logger.debug(green('Setting description'))
229
+
230
+ saved_description = description # save the description in case it fails
231
+
232
+ desc = driver.find_element(By.XPATH, config['selectors']['upload']['description'])
233
+
234
+ # desc populates with filename before clearing
235
+ WebDriverWait(driver, config['explicit_wait']).until(lambda driver: desc.text != '')
236
+
237
+ _clear(desc)
238
+
239
+ try:
240
+ while description:
241
+ nearest_mention = description.find('@')
242
+ nearest_hash = description.find('#')
243
+
244
+ if nearest_mention == 0 or nearest_hash == 0:
245
+ desc.send_keys('@' if nearest_mention == 0 else '#')
246
+
247
+ # wait for the frames to load
248
+ time.sleep(config['implicit_wait'])
249
+
250
+ name = description[1:].split(' ')[0]
251
+ if nearest_mention == 0: # @ case
252
+ mention_xpath = config['selectors']['upload']['mention_box']
253
+ condition = EC.presence_of_element_located((By.XPATH, mention_xpath))
254
+ mention_box = WebDriverWait(driver, config['explicit_wait']).until(condition)
255
+ mention_box.send_keys(name)
256
+ else:
257
+ desc.send_keys(name)
258
+
259
+ time.sleep(config['implicit_wait'])
260
+
261
+ if nearest_mention == 0: # @ case
262
+ mention_xpath = config['selectors']['upload']['mentions'].format('@' + name)
263
+ condition = EC.presence_of_element_located((By.XPATH, mention_xpath))
264
+ else:
265
+ hashtag_xpath = config['selectors']['upload']['hashtags'].format(name)
266
+ condition = EC.presence_of_element_located((By.XPATH, hashtag_xpath))
267
+
268
+ elem = WebDriverWait(driver, config['explicit_wait']).until(condition)
269
+
270
+ ActionChains(driver).move_to_element(elem).click(elem).perform()
271
+
272
+ description = description[len(name) + 2:]
273
+ else:
274
+ min_index = _get_splice_index(nearest_mention, nearest_hash, description)
275
+
276
+ desc.send_keys(description[:min_index])
277
+ description = description[min_index:]
278
+ except Exception as exception:
279
+ print('Failed to set description: ', exception)
280
+ _clear(desc)
281
+ desc.send_keys(saved_description) # if fail, use saved description
282
+
283
+
284
+ def _clear(element) -> None:
285
+ """
286
+ Clears the text of the element (an issue with the TikTok website when automating)
287
+
288
+ Parameters
289
+ ----------
290
+ element
291
+ The text box to clear
292
+ """
293
+ element.send_keys(2 * len(element.text) * Keys.BACKSPACE)
294
+
295
+
296
+ def _set_video(driver, path: str = '', num_retries: int = 3, **kwargs) -> None:
297
+ """
298
+ Sets the video to upload
299
+
300
+ Parameters
301
+ ----------
302
+ driver : selenium.webdriver
303
+ path : str
304
+ The path to the video to upload
305
+ num_retries : number of retries (can occassionally fail)
306
+ """
307
+ # uploades the element
308
+ logger.debug(green('Uploading video file'))
309
+
310
+ for _ in range(num_retries):
311
+ try:
312
+ logger.debug(green('Uploading video file 1'))
313
+ upload_box = driver.find_element(
314
+ By.XPATH, config['selectors']['upload']['upload_video']
315
+ )
316
+ logger.debug(green('Uploading video file 2'))
317
+ upload_box.send_keys(path)
318
+ logger.debug(green('Uploading video file 3'))
319
+ # waits for the upload progress bar to disappear
320
+ upload_progress = EC.presence_of_element_located(
321
+ (By.XPATH, config['selectors']['upload']['upload_in_progress'])
322
+ )
323
+ logger.debug(green('Uploading video file 4'))
324
+ WebDriverWait(driver, config['implicit_wait']).until(upload_progress)
325
+ WebDriverWait(driver, config['explicit_wait']).until_not(upload_progress)
326
+ logger.debug(green('Uploading video file 5'))
327
+ # waits for the video to upload
328
+ upload_confirmation = EC.presence_of_element_located(
329
+ (By.XPATH, config['selectors']['upload']['upload_confirmation'])
330
+ )
331
+ logger.debug(green('Uploading video file 6'))
332
+ # An exception throw here means the video failed to upload an a retry is needed
333
+ WebDriverWait(driver, config['explicit_wait']).until(upload_confirmation)
334
+ logger.debug(green('Uploading video file 7'))
335
+ # wait until a non-draggable image is found
336
+ process_confirmation = EC.presence_of_element_located(
337
+ (By.XPATH, config['selectors']['upload']['process_confirmation'])
338
+ )
339
+ logger.debug(green('Uploading video file 8'))
340
+ WebDriverWait(driver, config['explicit_wait']).until(process_confirmation)
341
+ logger.debug(green('Uploading video file 9'))
342
+ return
343
+ except Exception as exception:
344
+ print(exception)
345
+
346
+ raise FailedToUpload()
347
+
348
+
349
+ def _set_interactivity(driver, comment=True, stitch=True, duet=True, *args, **kwargs) -> None:
350
+ """
351
+ Sets the interactivity settings of the video
352
+
353
+ Parameters
354
+ ----------
355
+ driver : selenium.webdriver
356
+ comment : bool
357
+ Whether or not to allow comments
358
+ stitch : bool
359
+ Whether or not to allow stitching
360
+ duet : bool
361
+ Whether or not to allow duets
362
+ """
363
+ try:
364
+ logger.debug(green('Setting interactivity settings'))
365
+
366
+ comment_box = driver.find_element(By.XPATH, config['selectors']['upload']['comment'])
367
+ stitch_box = driver.find_element(By.XPATH, config['selectors']['upload']['stitch'])
368
+ duet_box = driver.find_element(By.XPATH, config['selectors']['upload']['duet'])
369
+
370
+ # xor the current state with the desired state
371
+ if comment ^ comment_box.is_selected():
372
+ comment_box.click()
373
+
374
+ if stitch ^ stitch_box.is_selected():
375
+ stitch_box.click()
376
+
377
+ if duet ^ duet_box.is_selected():
378
+ duet_box.click()
379
+
380
+ except Exception as _:
381
+ logger.error('Failed to set interactivity settings')
382
+
383
+
384
+ def _set_schedule_video(driver, schedule: datetime.datetime) -> None:
385
+ """
386
+ Sets the schedule of the video
387
+
388
+ Parameters
389
+ ----------
390
+ driver : selenium.webdriver
391
+ schedule : datetime.datetime
392
+ The datetime to set
393
+ """
394
+
395
+ logger.debug(green('Setting schedule'))
396
+
397
+ driver_timezone = __get_driver_timezone(driver)
398
+ schedule = schedule.astimezone(driver_timezone)
399
+
400
+ month = schedule.month
401
+ day = schedule.day
402
+ hour = schedule.hour
403
+ minute = schedule.minute
404
+
405
+ try:
406
+ switch = driver.find_element(By.XPATH, config['selectors']['schedule']['switch'])
407
+ switch.click()
408
+ __date_picker(driver, month, day)
409
+ __time_picker(driver, hour, minute)
410
+ except Exception as e:
411
+ msg = f'Failed to set schedule: {e}'
412
+ print(msg)
413
+ logger.error(msg)
414
+ raise FailedToUpload()
415
+
416
+
417
+
418
+ def __date_picker(driver, month: int, day: int) -> None:
419
+ logger.debug(green('Picking date'))
420
+
421
+ condition = EC.presence_of_element_located(
422
+ (By.XPATH, config['selectors']['schedule']['date_picker'])
423
+ )
424
+ date_picker = WebDriverWait(driver, config['implicit_wait']).until(condition)
425
+ date_picker.click()
426
+
427
+ condition = EC.presence_of_element_located(
428
+ (By.XPATH, config['selectors']['schedule']['calendar'])
429
+ )
430
+ calendar = WebDriverWait(driver, config['implicit_wait']).until(condition)
431
+
432
+ calendar_month = driver.find_element(By.XPATH, config['selectors']['schedule']['calendar_month']).text
433
+ n_calendar_month = datetime.datetime.strptime(calendar_month, '%B').month
434
+ if n_calendar_month != month: # Max can be a month before or after
435
+ if n_calendar_month < month:
436
+ arrow = driver.find_elements(By.XPATH, config['selectors']['schedule']['calendar_arrows'])[-1]
437
+ else:
438
+ arrow = driver.find_elements(By.XPATH, config['selectors']['schedule']['calendar_arrows'])[0]
439
+ arrow.click()
440
+ valid_days = driver.find_elements(By.XPATH, config['selectors']['schedule']['calendar_valid_days'])
441
+
442
+ day_to_click = None
443
+ for day_option in valid_days:
444
+ if int(day_option.text) == day:
445
+ day_to_click = day_option
446
+ break
447
+ if day_to_click:
448
+ day_to_click.click()
449
+ else:
450
+ raise Exception('Day not found in calendar')
451
+
452
+ __verify_date_picked_is_correct(driver, month, day)
453
+
454
+
455
+ def __verify_date_picked_is_correct(driver, month: int, day: int):
456
+ date_selected = driver.find_element(By.XPATH, config['selectors']['schedule']['date_picker']).text
457
+ date_selected_month = int(date_selected.split('-')[1])
458
+ date_selected_day = int(date_selected.split('-')[2])
459
+
460
+ if date_selected_month == month and date_selected_day == day:
461
+ logger.debug(green('Date picked correctly'))
462
+ else:
463
+ msg = f'Something went wrong with the date picker, expected {month}-{day} but got {date_selected_month}-{date_selected_day}'
464
+ logger.error(msg)
465
+ raise Exception(msg)
466
+
467
+
468
+ def __time_picker(driver, hour: int, minute: int) -> None:
469
+ logger.debug(green('Picking time'))
470
+
471
+ condition = EC.presence_of_element_located(
472
+ (By.XPATH, config['selectors']['schedule']['time_picker'])
473
+ )
474
+ time_picker = WebDriverWait(driver, config['implicit_wait']).until(condition)
475
+ time_picker.click()
476
+
477
+ condition = EC.presence_of_element_located(
478
+ (By.XPATH, config['selectors']['schedule']['time_picker_container'])
479
+ )
480
+ time_picker_container = WebDriverWait(driver, config['implicit_wait']).until(condition)
481
+
482
+ # 00 = 0, 01 = 1, 02 = 2, 03 = 3, 04 = 4, 05 = 5, 06 = 6, 07 = 7, 08 = 8, 09 = 9, 10 = 10, 11 = 11, 12 = 12,
483
+ # 13 = 13, 14 = 14, 15 = 15, 16 = 16, 17 = 17, 18 = 18, 19 = 19, 20 = 20, 21 = 21, 22 = 22, 23 = 23
484
+ hour_options = driver.find_elements(By.XPATH, config['selectors']['schedule']['timepicker_hours'])
485
+ # 00 == 0, 05 == 1, 10 == 2, 15 == 3, 20 == 4, 25 == 5, 30 == 6, 35 == 7, 40 == 8, 45 == 9, 50 == 10, 55 == 11
486
+ minute_options = driver.find_elements(By.XPATH, config['selectors']['schedule']['timepicker_minutes'])
487
+
488
+ hour_to_click = hour_options[hour]
489
+ minute_option_correct_index = int(minute / 5)
490
+ minute_to_click = minute_options[minute_option_correct_index]
491
+
492
+ driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", hour_to_click)
493
+ hour_to_click.click()
494
+ driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", minute_to_click)
495
+ minute_to_click.click()
496
+
497
+ # click somewhere else to close the time picker
498
+ time_picker.click()
499
+
500
+ time.sleep(.5) # wait for the DOM change
501
+ __verify_time_picked_is_correct(driver, hour, minute)
502
+
503
+
504
+ def __verify_time_picked_is_correct(driver, hour: int, minute: int):
505
+ time_selected = driver.find_element(By.XPATH, config['selectors']['schedule']['time_picker_text']).text
506
+ time_selected_hour = int(time_selected.split(':')[0])
507
+ time_selected_minute = int(time_selected.split(':')[1])
508
+
509
+ if time_selected_hour == hour and time_selected_minute == minute:
510
+ logger.debug(green('Time picked correctly'))
511
+ else:
512
+ msg = f'Something went wrong with the time picker, ' \
513
+ f'expected {hour:02d}:{minute:02d} ' \
514
+ f'but got {time_selected_hour:02d}:{time_selected_minute:02d}'
515
+ logger.error(msg)
516
+ raise Exception(msg)
517
+
518
+
519
+ def _post_video(driver) -> None:
520
+ """
521
+ Posts the video by clicking the post button
522
+
523
+ Parameters
524
+ ----------
525
+ driver : selenium.webdriver
526
+ """
527
+ logger.debug(green('Clicking the post button'))
528
+
529
+ post = driver.find_element(By.XPATH, config['selectors']['upload']['post'])
530
+ post.click()
531
+
532
+ # waits for the video to upload
533
+ post_confirmation = EC.presence_of_element_located(
534
+ (By.XPATH, config['selectors']['upload']['post_confirmation'])
535
+ )
536
+ WebDriverWait(driver, config['explicit_wait']).until(post_confirmation)
537
+
538
+ logger.debug(green('Video posted successfully'))
539
+
540
+
541
+ # HELPERS
542
+
543
+ def _check_valid_path(path: str) -> bool:
544
+ """
545
+ Returns whether or not the filetype is supported by TikTok
546
+ """
547
+ return exists(path) and path.split('.')[-1] in config['supported_file_types']
548
+
549
+
550
+ def _get_valid_schedule_minute(schedule, valid_multiple) -> datetime.datetime:
551
+ """
552
+ Returns a datetime.datetime with valid minute for TikTok
553
+ """
554
+ if _is_valid_schedule_minute(schedule.minute, valid_multiple):
555
+ return schedule
556
+ else:
557
+ return _set_valid_schedule_minute(schedule, valid_multiple)
558
+
559
+
560
+ def _is_valid_schedule_minute(minute, valid_multiple) -> bool:
561
+ if minute % valid_multiple != 0:
562
+ return False
563
+ else:
564
+ return True
565
+
566
+
567
+ def _set_valid_schedule_minute(schedule, valid_multiple) -> datetime.datetime:
568
+ minute = schedule.minute
569
+
570
+ remainder = minute % valid_multiple
571
+ integers_to_valid_multiple = 5 - remainder
572
+ schedule += datetime.timedelta(minutes=integers_to_valid_multiple)
573
+
574
+ return schedule
575
+
576
+
577
+ def _check_valid_schedule(schedule: datetime.datetime) -> bool:
578
+ """
579
+ Returns if the schedule is supported by TikTok
580
+ """
581
+ valid_tiktok_minute_multiple = 5
582
+ margin_to_complete_upload_form = 5
583
+
584
+ datetime_utc_now = pytz.UTC.localize(datetime.datetime.utcnow())
585
+ min_datetime_tiktok_valid = datetime_utc_now + datetime.timedelta(minutes=15)
586
+ min_datetime_tiktok_valid += datetime.timedelta(minutes=margin_to_complete_upload_form)
587
+ max_datetime_tiktok_valid = datetime_utc_now + datetime.timedelta(days=10)
588
+ if schedule < min_datetime_tiktok_valid \
589
+ or schedule > max_datetime_tiktok_valid:
590
+ return False
591
+ elif not _is_valid_schedule_minute(schedule.minute, valid_tiktok_minute_multiple):
592
+ return False
593
+ else:
594
+ return True
595
+
596
+
597
+ def _get_splice_index(nearest_mention: int, nearest_hashtag: int, description: str) -> int:
598
+ """
599
+ Returns the index to splice the description at
600
+
601
+ Parameters
602
+ ----------
603
+ nearest_mention : int
604
+ The index of the nearest mention
605
+ nearest_hashtag : int
606
+ The index of the nearest hashtag
607
+
608
+ Returns
609
+ -------
610
+ int
611
+ The index to splice the description at
612
+ """
613
+ if nearest_mention == -1 and nearest_hashtag == -1:
614
+ return len(description)
615
+ elif nearest_hashtag == -1:
616
+ return nearest_mention
617
+ elif nearest_mention == -1:
618
+ return nearest_hashtag
619
+ else:
620
+ return min(nearest_mention, nearest_hashtag)
621
+
622
+ def _convert_videos_dict(videos_list_of_dictionaries) -> List:
623
+ """
624
+ Takes in a videos dictionary and converts it.
625
+
626
+ This allows the user to use the wrong stuff and thing to just work
627
+ """
628
+ if not videos_list_of_dictionaries:
629
+ raise RuntimeError("No videos to upload")
630
+
631
+ valid_path = config['valid_path_names']
632
+ valid_description = config['valid_descriptions']
633
+
634
+ correct_path = valid_path[0]
635
+ correct_description = valid_description[0]
636
+
637
+ def intersection(lst1, lst2):
638
+ """ return the intersection of two lists """
639
+ return list(set(lst1) & set(lst2))
640
+
641
+ return_list = []
642
+ for elem in videos_list_of_dictionaries:
643
+ # preprocesses the dictionary
644
+ elem = {k.strip().lower(): v for k, v in elem.items()}
645
+
646
+ keys = elem.keys()
647
+ path_intersection = intersection(valid_path, keys)
648
+ description_interesection = intersection(valid_description, keys)
649
+
650
+ if path_intersection:
651
+ # we have a path
652
+ path = elem[path_intersection.pop()]
653
+
654
+ if not _check_valid_path(path):
655
+ raise RuntimeError("Invalid path: " + path)
656
+
657
+ elem[correct_path] = path
658
+ else:
659
+ # iterates over the elem and find a key which is a path with a valid extension
660
+ for _, value in elem.items():
661
+ if _check_valid_path(value):
662
+ elem[correct_path] = value
663
+ break
664
+ else:
665
+ # no valid path found
666
+ raise RuntimeError("Path not found in dictionary: " + str(elem))
667
+
668
+ if description_interesection:
669
+ # we have a description
670
+ elem[correct_description] = elem[description_interesection.pop()]
671
+ else:
672
+ # iterates over the elem and finds a description which is not a valid path
673
+ for _, value in elem.items():
674
+ if not _check_valid_path(value):
675
+ elem[correct_description] = value
676
+ break
677
+ else:
678
+ elem[correct_description] = '' # null description is fine
679
+
680
+ return_list.append(elem)
681
+
682
+ return return_list
683
+
684
+ def __get_driver_timezone(driver) -> pytz.timezone:
685
+ """
686
+ Returns the timezone of the driver
687
+ """
688
+ timezone_str = driver.execute_script("return Intl.DateTimeFormat().resolvedOptions().timeZone")
689
+ return pytz.timezone(timezone_str)
690
+
691
+ class DescriptionTooLong(Exception):
692
+ """
693
+ A video description longer than the maximum allowed by TikTok's website (not app) uploader
694
+ """
695
+
696
+ def __init__(self, message=None):
697
+ super().__init__(message or self.__doc__)
698
+
699
+
700
+ class FailedToUpload(Exception):
701
+ """
702
+ A video failed to upload
703
+ """
704
+
705
+ def __init__(self, message=None):
706
+ super().__init__(message or self.__doc__)
utils.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydub import AudioSegment
2
+ #from pydub.utils import mediainfo
3
+ from pydub.utils import make_chunks
4
+ import math
5
+ #flac_audio = AudioSegment.from_file("sample.flac", "flac")
6
+ #flac_audio.export("audio.wav", format="wav")
7
+ def split_audio_wav(filename):
8
+ myaudio = AudioSegment.from_file(filename , "wav")
9
+ channel_count = myaudio.channels #Get channels
10
+ sample_width = myaudio.sample_width #Get sample width
11
+ duration_in_sec = len(myaudio) / 1000#Length of audio in sec
12
+ sample_rate = myaudio.frame_rate
13
+ print("sample_width=", sample_width)
14
+ print("channel_count=", channel_count)
15
+ print("duration_in_sec=", duration_in_sec)
16
+ print("frame_rate=", sample_rate)
17
+ bit_rate =16 #assumption , you can extract from mediainfo("test.wav") dynamically
18
+ wav_file_size = (sample_rate * bit_rate * channel_count * duration_in_sec) / 8
19
+ print("wav_file_size = ",wav_file_size)
20
+ file_split_size = 40000000 # 40mb OR 40, 000, 000 bytes
21
+ total_chunks = wav_file_size // file_split_size
22
+ #Get chunk size by following method #There are more than one ofcourse
23
+ #for duration_in_sec (X) --> wav_file_size (Y)
24
+ #So whats duration in sec (K) --> for file size of 40Mb
25
+ # K = X * 40Mb / Y
26
+ chunk_length_in_sec = math.ceil((duration_in_sec * 40000000 ) /wav_file_size) #in sec
27
+ chunk_length_ms = chunk_length_in_sec * 1000
28
+ chunks = make_chunks(myaudio, chunk_length_ms)
29
+ number_chunks=len(chunks)
30
+ chunks_list=[]
31
+ #Export all of the individual chunks as wav files
32
+ for i, chunk in enumerate(chunks):
33
+ chunk_name = "chunk{0}.wav".format(i)
34
+ print("exporting", chunk_name)
35
+ chunk.export(chunk_name, format="wav")
36
+ chunks_list.append(chunk_name)
37
+ return chunks_list