feat: implement vertical crop, local LLM deepseek-v4-flash, multi-threading, word-level pink box highlight subtitles, and Nextcloud scan sync

This commit is contained in:
Renato
2026-07-02 16:12:23 +02:00
parent 50bdaedebb
commit c7c3c4b74a
154 changed files with 36256 additions and 3 deletions
@@ -0,0 +1,54 @@
class Constant:
"""A class for storing constants for YoutubeUploader class"""
YOUTUBE_URL = 'https://www.youtube.com'
YOUTUBE_STUDIO_URL = 'https://studio.youtube.com'
YOUTUBE_UPLOAD_URL = 'https://www.youtube.com/upload'
USER_WAITING_TIME = 1
VIDEO_TITLE = 'title'
VIDEO_DESCRIPTION = 'description'
VIDEO_EDIT = 'edit'
VIDEO_TAGS = 'tags'
TEXTBOX_ID = 'textbox'
TEXT_INPUT = 'text-input'
RADIO_LABEL = 'radioLabel'
UPLOADING_STATUS_CONTAINER = '/html/body/ytcp-uploads-dialog/tp-yt-paper-dialog/div/ytcp-animatable[2]/div/div[1]/ytcp-video-upload-progress[@uploading=""]'
NOT_MADE_FOR_KIDS_LABEL = 'VIDEO_MADE_FOR_KIDS_NOT_MFK'
Click_Add = '//*[@id="mount_0_0_7N"]/div/div/div[2]/div/div/div[1]/div[1]/div[2]/div/div/div/div/div[2]/div[7]/div/span/div/a/div/div[1]/div/div/svg'
UPLOAD_DIALOG = '//ytcp-uploads-dialog'
ADVANCED_BUTTON_ID = 'toggle-button'
TAGS_CONTAINER_ID = 'tags-container'
TAGS_INPUT = 'text-input'
NEXT_BUTTON = 'next-button'
PUBLIC_BUTTON = 'PUBLIC'
VIDEO_URL_CONTAINER = "//span[@class='video-url-fadeable style-scope ytcp-video-info']"
VIDEO_URL_ELEMENT = "//a[@class='style-scope ytcp-video-info']"
HREF = 'href'
ERROR_CONTAINER = '//*[@id="error-message"]'
VIDEO_NOT_FOUND_ERROR = 'Could not find video_id'
DONE_BUTTON = 'done-button'
INPUT_FILE_VIDEO = "//input[@type='file']"
INPUT_FILE_THUMBNAIL = "//input[@id='file-loader']"
# Playlist
VIDEO_PLAYLIST = 'playlist_title'
PL_DROPDOWN_CLASS = 'ytcp-video-metadata-playlists'
PL_SEARCH_INPUT_ID = 'search-input'
PL_ITEMS_CONTAINER_ID = 'items'
PL_ITEM_CONTAINER = '//span[text()="{}"]'
PL_NEW_BUTTON_CLASS = 'new-playlist-button'
PL_CREATE_PLAYLIST_CONTAINER_ID = 'create-playlist-form'
PL_CREATE_BUTTON_CLASS = 'create-playlist-button'
PL_DONE_BUTTON_CLASS = 'done-button'
#Schedule
VIDEO_SCHEDULE = 'schedule'
SCHEDULE_CONTAINER_ID = 'second-container-expand-button'
SCHEDULE_DATE_ID = 'datepicker-trigger'
SCHEDULE_DATE_TEXTBOX = '/html/body/ytcp-date-picker/tp-yt-paper-dialog/div/form/tp-yt-paper-input/tp-yt-paper-input-container/div[2]/div/iron-input/input'
SCHEDULE_TIME = '//*[@id="input-1"]/input'
@@ -0,0 +1,221 @@
"""This module implements uploading videos on YouTube via Selenium using metadata JSON file
to extract its title, description etc."""
from typing import DefaultDict, Optional, Tuple
from selenium_firefox.firefox import Firefox
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from collections import defaultdict
from datetime import datetime
import json
import time
from .Constant import *
from pathlib import Path
import logging
import platform
logging.basicConfig()
import random
#used to generate a random wait time to perform the actions on the browser
def random_time():
rand : int = random.randint(1, 3)
print("Random time: ", rand)
return rand
def load_metadata(metadata_json_path: Optional[str] = None) -> DefaultDict[str, str]:
if metadata_json_path is None:
return defaultdict(str)
with open(metadata_json_path, encoding='utf-8') as metadata_json_file:
return defaultdict(str, json.load(metadata_json_file))
class InstagramUploader:
"""A class for uploading videos on Instagram via Selenium using metadata JSON file
to extract its title, description etc"""
def __init__(self, video_path: str, metadata_json_path: Optional[str] = None,
thumbnail_path: Optional[str] = None,
profile_path: Optional[str] = str(Path.cwd()) + "/profile",
headless : bool = True) -> None:
self.video_path = video_path
self.thumbnail_path = thumbnail_path
self.metadata_dict = load_metadata(metadata_json_path)
self.browser = Firefox(profile_path=profile_path, pickle_cookies=True, full_screen=False, headless=headless)
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.DEBUG)
self.__validate_inputs()
print("headless for instagram =" +str(headless))
self.is_mac = False
if not any(os_name in platform.platform() for os_name in ["Windows", "Linux"]):
self.is_mac = True
self.logger.debug("Use profile path: {}".format(self.browser.source_profile_path))
def __validate_inputs(self):
if not self.metadata_dict[Constant.VIDEO_TITLE]:
self.logger.warning(
"The video title was not found in a metadata file")
self.metadata_dict[Constant.VIDEO_TITLE] = Path(
self.video_path).stem
self.logger.warning("The video title was set to {}".format(
Path(self.video_path).stem))
if not self.metadata_dict[Constant.VIDEO_DESCRIPTION]:
self.logger.warning(
"The video description was not found in a metadata file")
def upload(self):
try:
self.login()
return self.__upload()
except Exception as e:
print(e)
self.__quit()
raise
def login(self):
self.browser.get("https://www.instagram.com/")
time.sleep(1)
if self.browser.has_cookies_for_current_website():
self.browser.load_cookies()
self.logger.debug("Loaded cookies from {}".format(self.browser.cookies_folder_path))
time.sleep(1)
self.browser.refresh()
else:
self.logger.info('Please sign in and then press enter')
input()
self.browser.get("https://www.instagram.com/")
time.sleep(3)
self.browser.save_cookies()
self.logger.debug("Saved cookies to {}".format(self.browser.cookies_folder_path))
def __clear_field(self, field):
field.click()
time.sleep(3)
if self.is_mac:
field.send_keys(Keys.COMMAND + 'a')
else:
field.send_keys(Keys.CONTROL + 'a')
time.sleep(2)
field.send_keys(Keys.BACKSPACE)
def __write_in_field(self, field, string, select_all=False):
if select_all:
self.__clear_field(field)
else:
field.click()
time.sleep(random_time())
field.send_keys(string)
#find element, if not found, wait 3 seconds and try again
#by - the type of element to find
#value - the value of the element to find
#name - the name of the element to find
#return - the element found
def find_element(self, by, value: str, name: str):
element = None
startTime = time.time()
while element is None:
element = self.browser.find(by, value)
time.sleep(3)
print(f"{name} not found")
#if 1 minute has passed, break the loop
if time.time() - startTime > 60:
break
print(f"{name} found")
return element
def __upload(self) -> bool:
self.browser.get("https://www.instagram.com/?next=%2F")
uploading_status_container = InstagramUploader.find_element(self,By.CSS_SELECTOR, 'button._a9--:nth-child(2)', "uploading status container")
uploading_status_container.click()
print("clicked no updates")
time.sleep(2)
upload_button = None
upload_button = InstagramUploader.find_element(self,By.XPATH, '/html/body/div[1]/div/div/div[2]/div/div/div[1]/div[1]/div[2]/div/div/div/div/div[2]/div[7]/div/span/div/a', "upload button")
upload_button.click()
print("clicked create")
time.sleep(3)
self.browser.find(By.XPATH, '/html/body/div[1]/div/div/div[2]/div/div/div[1]/div[1]/div[2]/div/div/div/div/div[2]/div[7]/div/span/div/div/div/div[1]/a[1]').click()
print("clicked post")
time.sleep(3)
absolute_video_path = str(Path.cwd() / self.video_path)
videoUpload = InstagramUploader.find_element(self,By.CSS_SELECTOR,'._ac69', "video upload")
videoUpload.send_keys(absolute_video_path)
print("gave video path: ", absolute_video_path)
time.sleep(3)
next = InstagramUploader.find_element(self,By.CSS_SELECTOR, '._acap', "next")
next.click()
print("clicked next")
time.sleep(3)
size = InstagramUploader.find_element(self,By.CSS_SELECTOR, 'div.xnz67gz > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div:nth-child(1) > button:nth-child(1)', "size")
size.click()
print("clicked size")
time.sleep(3)
phone_resolution = InstagramUploader.find_element(self,By.CSS_SELECTOR, 'div.x1i10hfl:nth-child(5)', "phone resolution")
phone_resolution.click()
print("clicked phone resolution")
clickOff = InstagramUploader.find_element(self,By.CSS_SELECTOR, 'div.xnz67gz > div:nth-child(1) > div:nth-child(3)', "click off")
clickOff.click()
time.sleep(3)
next = InstagramUploader.find_element(self,By.CSS_SELECTOR, '.x1f6kntn', "next")
next.click()
print("clicked next")
time.sleep(3)
next = InstagramUploader.find_element(self,By.CSS_SELECTOR, '.xyamay9 > div:nth-child(1)', "next")
next.click()
print("clicked next")
time.sleep(3)
description_field = self.browser.find(By.XPATH, '/html/body/div[6]/div[1]/div/div[3]/div/div/div/div/div/div/div/div[2]/div[2]/div/div/div/div[1]/div[2]/div/div[1]/div[1]')
video_description :str = self.metadata_dict[Constant.VIDEO_DESCRIPTION]
video_description = video_description.replace("\n", Keys.ENTER)
if video_description:
description_field = self.browser.find(By.CSS_SELECTOR, '.x1hnll1o')
print("Video Description: ", video_description)
description_field.click()
time.sleep(2)
[description_field.send_keys(c) for c in video_description] #send_keys(video_description)
print("Video description added")
time.sleep(2)
share = InstagramUploader.find_element(self,By.CSS_SELECTOR, '.x1f6kntn', "share")
share.click()
print("clicked share")
#uploading_status_container = self.browser.find(By.XPATH, Constant.UPLOADING_STATUS_CONTAINER)
uploading_status_container_done = InstagramUploader.find_element(self,By.CSS_SELECTOR, 'div.x5yr21d:nth-child(1) > div:nth-child(1) > div:nth-child(2)', "uploading status container done")
self.__quit()
return True
def __get_video_id(self) -> Optional[str]:
video_id = None
try:
video_url_container = self.browser.find(
By.XPATH, Constant.VIDEO_URL_CONTAINER)
video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container)
video_id = video_url_element.get_attribute(
Constant.HREF).split('/')[-1]
except:
self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR)
pass
return video_id
def __quit(self):
self.browser.driver.quit()
+23
View File
@@ -0,0 +1,23 @@
import os
from .youtube_uploader_selenium import YouTubeUploader
from .Instagram_Uploader.instagramUploader import InstagramUploader
from termcolor import colored
def login_youtube(metadata:str) -> None:
print(colored("Logging in to Youtube", "blue"))
uploader = YouTubeUploader("dummy", metadata, headless= False)
uploader.login()
def login_instagram(metadata:str) -> None:
print(colored("Logging in to Instagram", "magenta"))
uploader = InstagramUploader("dummy",metadata, headless= False)
uploader.login()
if __name__ == "__main__":
print(colored("Logging in to Youtube and Instagram to save cookies for future use", "green"))
print(colored("Firefox browser required", "yellow"))
outputDirectory :str = os.path.join(os.getcwd(), "done_split")
metadata_path :str = os.path.join(outputDirectory, "upload.json")
login_youtube(metadata_path)
login_instagram(metadata_path)
print(colored("Cookies saved successfully. Now you can upload videos without logging in again", "cyan"))
+16
View File
@@ -0,0 +1,16 @@
from tiktok_uploader.upload import upload_video, upload_videos
from tiktok_uploader.auth import AuthBackend
#https://github.com/wkaisertexas/tiktok-uploader?tab=readme-ov-file
def upload_video_Tiktok(video_path :str, description:str, cookies:str) -> None:
username = 'your username here'
password = 'password here'
upload_video(filename=video_path, description=description, cookies= cookies, username=username, password=password)
@@ -0,0 +1,31 @@
from .youtube_uploader_selenium import YouTubeUploader
#from https://github.com/linouk23/youtube_uploader_selenium
from .Instagram_Uploader.instagramUploader import InstagramUploader
def upload_video_youtube(video_path: str, metadata_path: str, headless: bool, i : int = 0) -> None:
uploader = YouTubeUploader(video_path, metadata_path, headless=headless)
try:
uploader.upload()
except Exception as e:
print(f"Error uploading video: {e}")
print("Retrying upload...")
if i < 5:
print(f"Retry number {i}")
i += 1
upload_video_youtube(video_path, metadata_path, headless, i) # retry the upload recursively
def upload_video_Instagram(video_path: str, metadata_path: str, headless: bool, i: int = 0) -> None:
uploader = InstagramUploader(video_path, metadata_path, headless=headless)
try:
uploader.upload()
except Exception as e:
print(f"Error uploading video: {e}")
print("Retrying upload...")
if(i < 5):
print(f"Retry number {i}")
i += 1
upload_video_Instagram(video_path, metadata_path,headless, i) # retry the upload recursively
@@ -0,0 +1,50 @@
class Constant:
"""A class for storing constants for YoutubeUploader class"""
YOUTUBE_URL = 'https://www.youtube.com'
YOUTUBE_STUDIO_URL = 'https://studio.youtube.com'
YOUTUBE_UPLOAD_URL = 'https://www.youtube.com/upload'
USER_WAITING_TIME = 1
VIDEO_TITLE = 'title'
VIDEO_DESCRIPTION = 'description'
VIDEO_EDIT = 'edit'
VIDEO_TAGS = 'tags'
TEXTBOX_ID = 'textbox'
TEXT_INPUT = 'text-input'
RADIO_LABEL = 'radioLabel'
UPLOADING_STATUS_CONTAINER = '/html/body/ytcp-uploads-dialog/tp-yt-paper-dialog/div/ytcp-animatable[2]/div/div[1]/ytcp-video-upload-progress[@uploading=""]'
NOT_MADE_FOR_KIDS_LABEL = 'VIDEO_MADE_FOR_KIDS_NOT_MFK'
UPLOAD_DIALOG = '//ytcp-uploads-dialog'
ADVANCED_BUTTON_ID = 'toggle-button'
TAGS_CONTAINER_ID = 'tags-container'
TAGS_INPUT = 'text-input'
NEXT_BUTTON = 'next-button'
PUBLIC_BUTTON = 'PUBLIC'
VIDEO_URL_CONTAINER = "//span[@class='video-url-fadeable style-scope ytcp-video-info']"
VIDEO_URL_ELEMENT = "//a[@class='style-scope ytcp-video-info']"
HREF = 'href'
ERROR_CONTAINER = '//*[@id="error-message"]'
VIDEO_NOT_FOUND_ERROR = 'Could not find video_id'
DONE_BUTTON = 'done-button'
INPUT_FILE_VIDEO = "//input[@type='file']"
INPUT_FILE_THUMBNAIL = "//input[@id='file-loader']"
# Playlist
VIDEO_PLAYLIST = 'playlist_title'
PL_DROPDOWN_CLASS = 'ytcp-video-metadata-playlists'
PL_SEARCH_INPUT_ID = 'search-input'
PL_ITEMS_CONTAINER_ID = 'items'
PL_ITEM_CONTAINER = '//span[text()="{}"]'
PL_NEW_BUTTON_CLASS = 'new-playlist-button'
PL_CREATE_PLAYLIST_CONTAINER_ID = 'create-playlist-form'
PL_CREATE_BUTTON_CLASS = 'create-playlist-button'
PL_DONE_BUTTON_CLASS = 'done-button'
#Schedule
VIDEO_SCHEDULE = 'schedule'
SCHEDULE_CONTAINER_ID = 'second-container-expand-button'
SCHEDULE_DATE_ID = 'datepicker-trigger'
SCHEDULE_DATE_TEXTBOX = '/html/body/ytcp-date-picker/tp-yt-paper-dialog/div/form/tp-yt-paper-input/tp-yt-paper-input-container/div[2]/div/iron-input/input'
SCHEDULE_TIME = '//*[@id="input-1"]/input'
@@ -0,0 +1,296 @@
"""This module implements uploading videos on YouTube via Selenium using metadata JSON file
to extract its title, description etc."""
from typing import DefaultDict, Optional, Tuple
from selenium_firefox.firefox import Firefox
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from collections import defaultdict
from datetime import datetime
import json
import time
from .Constant import *
from pathlib import Path
import logging
import platform
logging.basicConfig()
import random
#used to generate a random wait time to perform the actions on the browser
def random_time():
rand : int = random.randint(1, 3)
print("Random time: ", rand)
return rand
def load_metadata(metadata_json_path: Optional[str] = None) -> DefaultDict[str, str]:
if metadata_json_path is None:
return defaultdict(str)
with open(metadata_json_path, encoding='utf-8') as metadata_json_file:
return defaultdict(str, json.load(metadata_json_file))
class YouTubeUploader:
"""A class for uploading videos on YouTube via Selenium using metadata JSON file
to extract its title, description etc"""
def __init__(self, video_path: str, metadata_json_path: Optional[str] = None,
thumbnail_path: Optional[str] = None,
profile_path: Optional[str] = str(Path.cwd()) + "/profile",
headless : bool = True) -> None:
self.video_path = video_path
self.thumbnail_path = thumbnail_path
self.metadata_dict = load_metadata(metadata_json_path)
self.browser = Firefox(profile_path=profile_path, pickle_cookies=True, full_screen=False, headless=headless)
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.DEBUG)
self.__validate_inputs()
print("headless for youtube =" + str(headless))
self.is_mac = False
if not any(os_name in platform.platform() for os_name in ["Windows", "Linux"]):
self.is_mac = True
self.logger.debug("Use profile path: {}".format(self.browser.source_profile_path))
def __validate_inputs(self):
if not self.metadata_dict[Constant.VIDEO_TITLE]:
self.logger.warning(
"The video title was not found in a metadata file")
self.metadata_dict[Constant.VIDEO_TITLE] = Path(
self.video_path).stem
self.logger.warning("The video title was set to {}".format(
Path(self.video_path).stem))
if not self.metadata_dict[Constant.VIDEO_DESCRIPTION]:
self.logger.warning(
"The video description was not found in a metadata file")
def upload(self):
try:
self.login()
return self.__upload()
except Exception as e:
print(e)
self.__quit()
raise
def login(self):
self.browser.get(Constant.YOUTUBE_URL)
time.sleep(random_time())
if self.browser.has_cookies_for_current_website():
self.browser.load_cookies()
self.logger.debug("Loaded cookies from {}".format(self.browser.cookies_folder_path))
time.sleep(random_time())
self.browser.refresh()
else:
self.logger.info('Please sign in and then press enter')
input()
self.browser.get(Constant.YOUTUBE_URL)
time.sleep(random_time())
self.browser.save_cookies()
self.logger.debug("Saved cookies to {}".format(self.browser.cookies_folder_path))
def __clear_field(self, field):
field.click()
time.sleep(random_time())
if self.is_mac:
field.send_keys(Keys.COMMAND + 'a')
else:
field.send_keys(Keys.CONTROL + 'a')
time.sleep(random_time())
field.send_keys(Keys.BACKSPACE)
def __write_in_field(self, field, string, select_all=False):
if select_all:
self.__clear_field(field)
else:
field.click()
time.sleep(random_time())
field.send_keys(string)
def __upload(self) -> Tuple[bool, Optional[str]]:
self.browser.get(Constant.YOUTUBE_URL)
time.sleep(random_time())
self.browser.get(Constant.YOUTUBE_UPLOAD_URL)
time.sleep(random_time())
absolute_video_path = str(Path.cwd() / self.video_path)
self.browser.find(By.XPATH, Constant.INPUT_FILE_VIDEO).send_keys(
absolute_video_path)
self.logger.debug('Attached video {}'.format(self.video_path))
# Find status container
uploading_status_container = None
while uploading_status_container is None:
time.sleep(0.5) #bug where slept too long, missed finding the element got soft locked
uploading_status_container = self.browser.find(By.XPATH, Constant.UPLOADING_STATUS_CONTAINER)
if self.thumbnail_path is not None:
absolute_thumbnail_path = str(Path.cwd() / self.thumbnail_path)
self.browser.find(By.XPATH, Constant.INPUT_FILE_THUMBNAIL).send_keys(
absolute_thumbnail_path)
change_display = "document.getElementById('file-loader').style = 'display: block! important'"
self.browser.driver.execute_script(change_display)
self.logger.debug(
'Attached thumbnail {}'.format(self.thumbnail_path))
title_field, description_field = self.browser.find_all(By.ID, Constant.TEXTBOX_ID, timeout=15)
self.__write_in_field(
title_field, self.metadata_dict[Constant.VIDEO_TITLE], select_all=True)
self.logger.debug('The video title was set to \"{}\"'.format(
self.metadata_dict[Constant.VIDEO_TITLE]))
video_description = self.metadata_dict[Constant.VIDEO_DESCRIPTION]
video_description = video_description.replace("\n", Keys.ENTER)
if video_description:
self.__write_in_field(description_field, video_description, select_all=True)
self.logger.debug('Description filled.')
kids_section = self.browser.find(By.NAME, Constant.NOT_MADE_FOR_KIDS_LABEL)
kids_section.location_once_scrolled_into_view
time.sleep(random_time())
self.browser.find(By.ID, Constant.RADIO_LABEL, kids_section).click()
self.logger.debug('Selected \"{}\"'.format(Constant.NOT_MADE_FOR_KIDS_LABEL))
# Playlist
playlist = self.metadata_dict[Constant.VIDEO_PLAYLIST]
if playlist:
self.browser.find(By.CLASS_NAME, Constant.PL_DROPDOWN_CLASS).click()
time.sleep(random_time())
search_field = self.browser.find(By.ID, Constant.PL_SEARCH_INPUT_ID)
self.__write_in_field(search_field, playlist)
time.sleep(random_time() * 2)
playlist_items_container = self.browser.find(By.ID, Constant.PL_ITEMS_CONTAINER_ID)
# Try to find playlist
self.logger.debug('Playlist xpath: "{}".'.format(Constant.PL_ITEM_CONTAINER.format(playlist)))
playlist_item = self.browser.find(By.XPATH, Constant.PL_ITEM_CONTAINER.format(playlist), playlist_items_container)
if playlist_item:
self.logger.debug('Playlist found.')
playlist_item.click()
time.sleep(random_time())
else:
self.logger.debug('Playlist not found. Creating')
self.__clear_field(search_field)
time.sleep(random_time())
new_playlist_button = self.browser.find(By.CLASS_NAME, Constant.PL_NEW_BUTTON_CLASS)
new_playlist_button.click()
create_playlist_container = self.browser.find(By.ID, Constant.PL_CREATE_PLAYLIST_CONTAINER_ID)
playlist_title_textbox = self.browser.find(By.XPATH, "//textarea", create_playlist_container)
self.__write_in_field(playlist_title_textbox, playlist)
time.sleep(random_time())
create_playlist_button = self.browser.find(By.CLASS_NAME, Constant.PL_CREATE_BUTTON_CLASS)
create_playlist_button.click()
time.sleep(random_time())
done_button = self.browser.find(By.CLASS_NAME, Constant.PL_DONE_BUTTON_CLASS)
done_button.click()
# Advanced options
self.browser.find(By.ID, Constant.ADVANCED_BUTTON_ID).click()
self.logger.debug('Clicked MORE OPTIONS')
time.sleep(random_time())
#click not ai (added myself by inspecting element and right clicking copy by xpath)
not_ai = self.browser.find(By.XPATH, "/html/body/ytcp-uploads-dialog/tp-yt-paper-dialog/div/ytcp-animatable[1]/ytcp-ve/ytcp-video-metadata-editor/div/ytcp-video-metadata-editor-advanced/div[2]/ytkp-altered-content-select/div[2]/tp-yt-paper-radio-group/tp-yt-paper-radio-button[2]/div[1]/div[1]")
not_ai.click()
time.sleep(random_time())
# Tags
tags = self.metadata_dict[Constant.VIDEO_TAGS]
if tags:
tags_container = self.browser.find(By.ID, Constant.TAGS_CONTAINER_ID)
tags_field = self.browser.find(By.ID, Constant.TAGS_INPUT, tags_container)
self.__write_in_field(tags_field, ','.join(tags))
self.logger.debug('The tags were set to \"{}\"'.format(tags))
self.browser.find(By.ID, Constant.NEXT_BUTTON).click()
self.logger.debug('Clicked {} one'.format(Constant.NEXT_BUTTON))
self.browser.find(By.ID, Constant.NEXT_BUTTON).click()
self.logger.debug('Clicked {} two'.format(Constant.NEXT_BUTTON))
self.browser.find(By.ID, Constant.NEXT_BUTTON).click()
self.logger.debug('Clicked {} three'.format(Constant.NEXT_BUTTON))
schedule = self.metadata_dict[Constant.VIDEO_SCHEDULE]
#Schedule
if schedule:
upload_time_object = datetime.strptime(schedule, "%m/%d/%Y, %H:%M")
self.browser.find(By.ID, Constant.SCHEDULE_CONTAINER_ID).click() #click the schedule dropdown
time.sleep(1) #make sure there is time for the date picker to load
schedule2 = self.browser.find(By.ID, Constant.SCHEDULE_DATE_ID) #find the date picker
time.sleep(1) #wait for schedule2 to load
schedule2.click() #click to open calendar
time.sleep(1) #wait for the date picker to load
self.browser.find(By.XPATH, Constant.SCHEDULE_DATE_TEXTBOX).clear() #clear the date text box
self.browser.find(By.XPATH, Constant.SCHEDULE_DATE_TEXTBOX).send_keys(
datetime.strftime(upload_time_object, "%b %e, %Y"))
self.browser.find(By.XPATH, Constant.SCHEDULE_DATE_TEXTBOX).send_keys(Keys.ENTER)
time.sleep(1) #wait for the date picker to load
self.browser.find(By.XPATH, Constant.SCHEDULE_TIME).click()
self.browser.find(By.XPATH, Constant.SCHEDULE_TIME).clear()
time.sleep(1) #wait for the date picker to load
self.browser.find(By.XPATH, Constant.SCHEDULE_TIME).send_keys(
datetime.strftime(upload_time_object, "%H:%M"))
self.browser.find(By.XPATH, Constant.SCHEDULE_TIME).send_keys(Keys.ENTER)
self.logger.debug(f"Scheduled the video for {schedule}")
else:
public_main_button = self.browser.find(By.NAME, Constant.PUBLIC_BUTTON)
self.browser.find(By.ID, Constant.RADIO_LABEL, public_main_button).click()
self.logger.debug('Made the video {}'.format(Constant.PUBLIC_BUTTON))
video_id = self.__get_video_id()
# Check status container and upload progress
uploading_status_container = self.browser.find(By.XPATH, Constant.UPLOADING_STATUS_CONTAINER)
while uploading_status_container is not None:
uploading_progress = uploading_status_container.get_attribute('value')
self.logger.debug('Upload video progress: {}%'.format(uploading_progress))
time.sleep(random_time() * 5)
uploading_status_container = self.browser.find(By.XPATH, Constant.UPLOADING_STATUS_CONTAINER)
self.logger.debug('Upload container gone.')
done_button = self.browser.find(By.ID, Constant.DONE_BUTTON)
# Catch such error as
# "File is a duplicate of a video you have already uploaded"
if done_button.get_attribute('aria-disabled') == 'true':
error_message = self.browser.find(By.XPATH, Constant.ERROR_CONTAINER).text
self.logger.error(error_message)
return False, None
done_button.click()
self.logger.debug(
"Published the video with video_id = {}".format(video_id))
time.sleep(random_time())
self.browser.get(Constant.YOUTUBE_URL)
self.__quit()
return True, video_id
def __get_video_id(self) -> Optional[str]:
video_id = None
try:
video_url_container = self.browser.find(
By.XPATH, Constant.VIDEO_URL_CONTAINER)
video_url_element = self.browser.find(By.XPATH, Constant.VIDEO_URL_ELEMENT, element=video_url_container)
video_id = video_url_element.get_attribute(
Constant.HREF).split('/')[-1]
except:
self.logger.warning(Constant.VIDEO_NOT_FOUND_ERROR)
pass
return video_id
def __quit(self):
self.browser.driver.quit()