Files
viral_project/brainrotinator/main.py
T

427 lines
20 KiB
Python

from datetime import datetime, timedelta
import os
import time
import argparse
import random
import shutil
import json
from brainrotinator.video_editor import VideoEditor
from uploaders.uploader_selenium import upload_video_youtube, upload_video_Instagram
from uploaders.upload_tiktok import upload_video_Tiktok
from downloader.downloadVid import download_vid
from config import Config
class Color:
PURPLE = '\033[95m'
CYAN = '\033[96m'
DARKCYAN = '\033[36m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
END = '\033[0m'
"""
Function to upload the chunks to YouTube
Uploads videos from the done_split folder, uploads until reaches howManyUploads, then sleeps for 23 hours
time between uploads = howManyMinsBetweenUpload + random wait of 0-5 mins
@param output_directory - path to the output directory(folder with the clips of our videos)
@param howManyUploads - how many times to upload before sleeping for 24 hours
@param howManyHoursBetweenUpload - how many hours delay between each upload scheduled to be uploaded
@param howManyMinsBetweenUpload - how many minutes to wait between each upload
@param howManyHoursLongToSleep - how many hours to sleep after uploading howManyUploads times
@param tags - list of tags to add to the video
@param description_video - description of the video
"""
def uploadVideos(doneVideosDirectory : str,
cfg: Config,
) -> None:
howManyUploads = cfg.howManyUploads
howManyHoursBetweenSchedule = cfg.howManyHoursBetweenSchedule
howManyMinsBetweenUpload = cfg.howManyMinsBetweenUpload
howManyHoursLongToSleep = cfg.howManyHoursLongToSleep
tags = cfg.tags
description_video = cfg.description
print(Color.GREEN + "Done Videos directory: " + doneVideosDirectory + Color.END)
print(Color.BLUE + "Video Upload Order order: \n" + str(os.listdir(doneVideosDirectory)) + "\n\n" + Color.END)
i : int = 0
for videoFileName in os.listdir(doneVideosDirectory):
print(Color.BLUE+ "Video file: " + videoFileName + Color.END)
if videoFileName.endswith(".mp4"):
print(Color.GREEN + f"i = {i} \n" + videoFileName + Color.END)
#if we have uploaded howmanyUploads times and is not first upload since starting program, sleep for howManyHoursLongToSleep
if(i % howManyUploads == 0 and i != 0) :
timeToSleepInHours = (howManyHoursLongToSleep*60*60)
print(Color.GREEN + f"Sleeping for {howManyHoursLongToSleep} hrs since uploaded {howManyUploads} vids... \n calculated: {timeToSleepInHours/60/60} hrs " + Color.END)
nextUploadTime = datetime.now() + timedelta(seconds=timeToSleepInHours)
print(Color.BLUE+ f" => next upload at {nextUploadTime}" + Color.END)
#Sleep for howManyHoursLongToSleep hours
time.sleep(timeToSleepInHours)
i = 0 #reset the counter to 0 after sleeping for 24 hours
#if we are not on the first video, sleep for hour since we have uploaded a video
if(i != 0) :
print(Color.GREEN + "Time now: " + str(datetime.now()) + Color.END)
randomSleepSeconds = random.randint(0, 300)
sleepTimeSeconds = (howManyMinsBetweenUpload * 60) + randomSleepSeconds
now = datetime.now()
nextUploadTime = now + timedelta(seconds=sleepTimeSeconds)
print(Color.BLUE+ f" => next upload at {nextUploadTime}" + Color.END)
print(Color.GREEN + "Sleeping for:"+str(sleepTimeSeconds /60) + "minutes" + Color.END)
time.sleep(sleepTimeSeconds) #sleep for 1 hour before uploading the next video
print(Color.GREEN+ "\n ---------------------------------------------------------------------" + Color.END)
if cfg.uploadToYoutube:
print(Color.GREEN+ "Uploading video to YouTube..." + Color.END)
videoFilePath :str = os.path.join(doneVideosDirectory, videoFileName)
print(Color.GREEN+"Uploading video from: \n" + videoFilePath + Color.END)
videoFileNameWithoutMP4ForSummary :str = os.path.splitext(videoFileName)[0]
print(Color.GREEN + "Base name: " + videoFileNameWithoutMP4ForSummary + Color.END)
#if "_filtered" in videoFileNameWithoutMP4: remove the "_filtered" from the name
#ai summary file is named without the "_filtered" so we need to remove it from the name
if "_filtered" in videoFileNameWithoutMP4ForSummary:
videoFileNameWithoutMP4ForSummary = videoFileNameWithoutMP4ForSummary.replace("_filtered", "")
print(Color.GREEN + "Base name after removing '_filtered': " + videoFileNameWithoutMP4ForSummary + Color.END)
project_root :str = os.getcwd()
subtitles_path :str = os.path.join(project_root, "subtitles")
print(Color.YELLOW + "Subtitles path: " + subtitles_path + Color.END)
AISummaryPath: str = os.path.join(subtitles_path, f"{videoFileNameWithoutMP4ForSummary}_summary.txt")
print(Color.YELLOW + "Summary path: " + AISummaryPath + Color.END)
AiTitle :str = ""
try:
with open(AISummaryPath, "r") as f:
AiTitle = f.read()
except FileNotFoundError:
print(Color.RED + "Summary file not found: " + AISummaryPath + Color.END)
AiTitle = "default"
print(Color.YELLOW + "Ai Title: " + AiTitle + Color.END)
print(Color.BLUE+ f" => Scheduling video to be uploaded in {i * howManyHoursBetweenSchedule} hours" + Color.END)
# Calculate the scheduled time based on the number of iterations
now = datetime.now()
random_mins = random.randint(0, 10)
howManyHoursLaterToSchedule = now + timedelta(hours=(i * howManyHoursBetweenSchedule), minutes=random_mins) #hr+rand mins to avoid getting banned
# Format the time in ISO 8601 format
formatted_time = howManyHoursLaterToSchedule.strftime("%m/%d/%Y, %H:%M")
print("Current time:", now)
print(f"{i*howManyHoursBetweenSchedule} hours later:", howManyHoursLaterToSchedule)
print("ISO format:", formatted_time)
#initialize the json object
upload_json = {}
#if we are on the first video, upload immediately or if we are not scheduling the video
if (i == 0 or howManyHoursBetweenSchedule == 0):
print(Color.GREEN + "Uploading immediately..." + Color.END)
upload_json = {
"title": AiTitle,
"description": description_video,
"tags": tags,
}
#if we are not on the first video, schedule the video to be uploaded in future
else :
print(Color.GREEN + "Scheduling video to be uploaded in the future..." + Color.END)
upload_json = {
"title": AiTitle,
"description": description_video,
"tags": tags,
"schedule": formatted_time
}
json_file_path :str = os.path.join(doneVideosDirectory, "upload.json")
with open(json_file_path, "w") as json_file:
json.dump(upload_json, json_file)
print(Color.GREEN + "JSON file path: " + json_file_path + Color.END)
if cfg.uploadToYoutube:
upload_video_youtube(videoFilePath, json_file_path, headless=cfg.firefoxHeadless)
print(Color.GREEN+ "Uploaded video to youtube: " + videoFileName + Color.END)
print(Color.GREEN+ "\n ---------------------------------------------------------------------" + Color.END)
copyOfTags = tags.copy() #copy the tags so we dont modify the original list
for x in range(len(copyOfTags)):
copyOfTags[x] = "#" + copyOfTags[x].replace(" ", "")
AiTitle = AiTitle + " " + " ".join(copyOfTags)
print(Color.GREEN + "Title for TikTok+Instagram: " + AiTitle + Color.END)
upload_json = {
"title": AiTitle,
"description": AiTitle,
}
with open(json_file_path, "w") as json_file:
json.dump(upload_json, json_file)
if cfg.uploadToInstagram:
print(Color.GREEN+ "Uploading video to Instagram..." + Color.END)
upload_video_Instagram(videoFilePath, json_file_path, headless=cfg.firefoxHeadless)
print(Color.GREEN + "Uploaded to Instagram " + Color.END)
print(Color.GREEN+ "\n ---------------------------------------------------------------------" + Color.END)
cookies : str = os.path.join(os.getcwd(), "cookies-tiktok.txt")
if cfg.uploadToTiktok:
print(Color.GREEN + "Uploading to TikTok..." + Color.END)
upload_video_Tiktok(video_path=videoFilePath, description= AiTitle, cookies = cookies)
print(Color.GREEN + "Uploaded to TikTok " + Color.END)
#-----------------------------------------------------------------------------
move_files_to_uploaded(videoFileName, doneVideosDirectory, videoFilePath) #move the uploaded video to the uploaded folder
i += 1 #increment the counter (only if we just uploaded a video, if not then dont increment hour time)
# Move the uploaded video to the "uploaded" directory, so we don't upload it again if the script crashes
def move_files_to_uploaded(videoFileName: str, doneVideoDirectory: str, videoFilePath) -> None:
uploaded_directory = os.path.join(doneVideoDirectory, "uploaded")
print(Color.GREEN + "Uploaded directory: " + uploaded_directory + Color.END)
newVideoFilePath = os.path.join(uploaded_directory, videoFileName)
shutil.move(videoFilePath, newVideoFilePath)
print(Color.GREEN + f"Moved uploaded video to: {newVideoFilePath}" + Color.END)
"""
Function that goes through the to_split folder and splits each video into chunks
If no mp4 files in the folder, prompts user to add mp4 files to the folder, or exit editing
Only splits mp4 files
@param folder_path - the path to the folder with the video
@param output_directory - the path to the output folder
@param chunk_duration - the duration of each chunk in seconds
@param edited_directory - the path to the edited folder
@param gui - boolean to check if the program is running in GUI mode
@param splitOneVideo - boolean to check if the program is splitting one video
"""
def splitVideos(folder_path : str,
output_directory : str,
cfg: Config,
edited_directory: str,
gui: bool = False,
splitOneVideo: bool = False) -> None:
chunk_duration = cfg.chunkDuration
print(Color.GREEN+ "\n ---------------------------------------------------------------------" + Color.END)
if(splitOneVideo):
print(Color.GREEN + "Splitting one video..." + Color.END)
else:
print(Color.GREEN + "Splitting all videos in to_Split folder..." + Color.END)
#for each video in the folder, split the video into 60 second chunks
for video_file in os.listdir(folder_path):
print(Color.BLUE + "Video file: " + video_file + Color.END )
if video_file.endswith(".mp4"):
input_video_path = os.path.join(folder_path, video_file)
print("Input video path: " + input_video_path)
base_name = os.path.splitext(video_file)[0] # Get the base name without .mp4
print(Color.GREEN + "Base name: " + base_name + Color.END)
#initialize the class with (video_path, output_folder, chunk_duration, vname)
video_editor = VideoEditor(input_path=input_video_path,
output_folder=output_directory,
chunk_duration=chunk_duration,
name=base_name,
useWhisper=cfg.useWhisperForTranscription,
filterProfanityInSubtitles=cfg.filterProfanityInSubtitles,
voskModelDir=cfg.voskModelDir,
tinyLlamaDir=cfg.tinyLlamaDir)
if cfg.blurTopBottomOfClip:
print (Color.RED + "Split the video into chunks using blur" + Color.END)
video_editor.split_video_into_chunks_blur()
else :
print (Color.RED + "Split the video into chunks without blur" + Color.END)
video_editor.split_video_into_chunks()
print (Color.GREEN + "Done editing the video into chunks" + Color.END)
# Move the original video to the "edited" directory
new_file_path = os.path.join(edited_directory, video_file)
print("Input video path: " + input_video_path)
print("New file path: " + new_file_path)
while(True):
try:
shutil.move(input_video_path, new_file_path)
print(Color.GREEN + f"Moved original video to: {new_file_path}" + Color.END)
break
except Exception as e:
print(Color.RED + "Error moving file: " + str(e) + Color.END)
time.sleep(5)
#if we are only splitting one video, break out of the loop
if splitOneVideo:
return
#if reach here then no mp4 files in the folder (since would have broken out of the loop if there was a mp4 file)
#prompt user to give url to download new youtube video
if(gui):
print("GUI mode")
#return ui.promptUserToGiveVideoPath(folder_path, output_directory, chunk_duration, edited_directory)
return
else:
print("CLI mode")
return promptUserForURLCLI(folder_path, output_directory, cfg, edited_directory)
def promptUserForURLCLI(folder_path : str,
output_directory : str,
cfg: Config,
edited_directory: str) -> None:
print(Color.RED + "No mp4 files in the folder. Please add mp4 files to the folder." + Color.END)
#wait for user input
waiting : bool = True
while (waiting):
answer: str = input(Color.YELLOW + "Enter a url for a youtube video to download to edit \n or type 'exit' to stop editing \n or type 'yes' if you added a file to the 'to_split' folder \n" + Color.END)
if(answer == "exit"):
print(Color.GREEN + "exiting video editing..." + Color.END)
return
if (answer == "yes"):
waiting = False
print(Color.GREEN + "Continuing program..." + Color.END)
return splitVideos(folder_path, output_directory, cfg, edited_directory) #call the function again to split the video into chunks
#check if answer is url
else :
try:
print(Color.GREEN + "Downloading video..." + Color.END)
#print url and folder path we are dolownloading to
print(Color.BLUE + "URL: " + answer + Color.END)
print(Color.BLUE + "Save Path: " + folder_path + Color.END)
print(Color.GREEN + "\n--------------------------------------------" + Color.END)
#call the download_vid function to download the video
#downloads the highest quality audio and video
#saves it to the to_split folder
download_vid(answer, folder_path)
#call the function again to split the video, checks if there are mp4 files in the folder, should be since we just downloaded a video
return splitVideos(folder_path, output_directory, cfg, edited_directory=edited_directory)
except Exception as e:
print(Color.RED + "Error downloading video: " + str(e) + Color.END)
waiting = True
#default to none if no arguments provided
def main(editBool = None, uploadBool = None, gui: bool = False) -> None:
# Get the current working directory (where your Python script is located)
project_root = os.getcwd()
#read title.txt file to print title
with open(os.path.join('assets', 'title.txt'), 'r') as file:
title = file.read().strip()
print(Color.RED + title + Color.END)
time.sleep(2)
# Define relative paths from the project root
folder_path = os.path.join(project_root, "to_split")
output_directory = os.path.join(project_root, "done_split")
edited_directory = os.path.join(folder_path, "edited")
print(Color.GREEN + "Folder path: " + folder_path + Color.END)
print(Color.GREEN + "Output directory: " + output_directory + Color.END)
print(Color.GREEN + "Edited directory: " + edited_directory + Color.END)
# Load the config file
cfg = Config.load()
MinsBefore = cfg.sleepXMinsBeforeStartingUploader
print(Color.GREEN + "Config: " + str(cfg.model_dump()) + Color.END)
#if a flag is provided, run the program only for that flag
if(editBool or uploadBool):
# Split one of the videos into chunks, move into edited folder
if editBool:
splitVideos(folder_path, output_directory, cfg, edited_directory=edited_directory, gui=gui)
return
# Upload the chunks to YouTube within the done_split folder
# do this until there are no more videos to upload, then go back to editing
if uploadBool:
print(Color.BLUE + "Sleeping for " + str(MinsBefore) + " minutes" + Color.END)
time.sleep(MinsBefore * 60)
uploadVideos(output_directory, cfg)
return
#else run the program for both flags, edit one vid -> upload -> edit another vid -> upload -> etc
i : int = 0
while(True):
# Split one of the videos into chunks, move into edited folder
splitVideos(folder_path, output_directory, cfg, edited_directory=edited_directory, gui=gui, splitOneVideo=True)
#Upload the chunks to YouTube within the done_split folder
#if we are on the first video, sleep for MinsBefore minutes in order to delay the first upload
if(i == 0):
print(Color.BLUE + "Sleeping for " + str(MinsBefore) + " minutes" + Color.END)
#print when next upload will be
next_upload = datetime.now() + timedelta(seconds=MinsBefore * 60)
print(Color.BLUE+ f" => next upload at {next_upload}" + Color.END)
time.sleep(MinsBefore * 60)
uploadVideos(output_directory, cfg)
i += 1
# Run the main function if the name of the module is __main__
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Process videos for YouTube.')
parser.add_argument('--edit', '-e', action='store_true', help='Split videos into chunks. \n \
Edits all mp4 files within the to_split folder. \n \
Without uploading the videos after editing.')
parser.add_argument('--upload', '-u', action='store_true', help='Upload video chunks to YouTube. \n \
Uploads all mp4 files within the done_split folder.')
args = parser.parse_args() #parse the arguments, so we can use them in the program, shows up in help message for functions
if not args.edit and not args.upload:
print(Color.GREEN+ "\n ---------------------------------------------------------------------" + Color.END)
print(Color.YELLOW + "\n No arguments provided. \n \
Defualt functionality is to edit one video into chunks, then upload the chunks to YouTube, and continue edit-upload as a loop. \n \
Please use --edit to only edit videos from to_split folder, or --upload to only upload videos from done_split folder. \n" + Color.END)
main(editBool=args.edit, uploadBool=args.upload) #call the main function with the arguments provided