From ffd7f2518ea1dec4e7c273ba8ba9d999a186724a Mon Sep 17 00:00:00 2001 From: cyteon Date: Sat, 25 May 2024 14:48:58 +0200 Subject: [PATCH] capcut --- .gitignore | 1 + main.py | 4 +- requirements.txt | 3 +- utils/.config.template.toml | 5 + utils/capcut.py | 191 ++++++++++++++++++++++++++++++++++ video_creation/final_video.py | 9 ++ 6 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 utils/capcut.py diff --git a/.gitignore b/.gitignore index da12cfe..bb3f9e8 100644 --- a/.gitignore +++ b/.gitignore @@ -240,6 +240,7 @@ out .DS_Store .setup-done-before results/* +capcut_results/* clipped/* reddit-bot-351418-5560ebc49cac.json /.idea diff --git a/main.py b/main.py index 2c6e89b..ab32f54 100755 --- a/main.py +++ b/main.py @@ -70,7 +70,7 @@ def run_many(times) -> None: f'on the {x}{("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")[x % 10]} iteration of {times}' ) # correct 1st 2nd 3rd 4th 5th.... main() - Popen("cls" if name == "nt" else "clear", shell=True).wait() + #Popen("cls" if name == "nt" else "clear", shell=True).wait() if settings.config["settings"]["mememode"]: make_meme_video() @@ -103,7 +103,7 @@ if __name__ == "__main__": f'on the {index}{("st" if index % 10 == 1 else ("nd" if index % 10 == 2 else ("rd" if index % 10 == 3 else "th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}' ) main(post_id) - Popen("cls" if name == "nt" else "clear", shell=True).wait() + #Popen("cls" if name == "nt" else "clear", shell=True).wait() elif config["settings"]["times_to_run"]: run_many(config["settings"]["times_to_run"]) else: diff --git a/requirements.txt b/requirements.txt index 84fae18..4efc842 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,5 @@ torch==2.2.2 transformers==4.39.3 ffmpeg-python==0.2.0 elevenlabs==0.2.17 -yt-dlp==2023.7.6 \ No newline at end of file +yt-dlp==2023.7.6 +bs4 \ No newline at end of file diff --git a/utils/.config.template.toml b/utils/.config.template.toml index e2fc135..5f1297d 100644 --- a/utils/.config.template.toml +++ b/utils/.config.template.toml @@ -5,6 +5,11 @@ username = { optional = false, nmin = 3, nmax = 20, explanation = "The username password = { optional = false, nmin = 8, explanation = "The password of your reddit account", example = "fFAGRNJru1FTz70BzhT3Zg", oob_error = "Password too short" } 2fa = { optional = true, type = "bool", options = [true, false, ], default = false, explanation = "Whether you have Reddit 2FA enabled, Valid options are True and False", example = true } +[capcut] +email = { optional = false, nmin = 3, nmax = 50, explanation = "The email of your CapCut account", example = ""} +password = { optional = false, nmin = 8, explanation = "The password of your CapCut account", example = ""} +cloud_id = { optional = false, nmin = 8, type="int", explanation = "The cloud id of your CapCut account", example = ""} +preset_number = { optional = true, default=18, type="int", explanation = "The preset number of your CapCut account", example = 1} [reddit.thread] random = { optional = true, options = [true, false, ], default = false, type = "bool", explanation = "If set to no, it will ask you a thread link to extract the thread, if yes it will randomize it. Default: 'False'", example = "True" } diff --git a/utils/capcut.py b/utils/capcut.py new file mode 100644 index 0000000..2679ff4 --- /dev/null +++ b/utils/capcut.py @@ -0,0 +1,191 @@ +import json +import time +import os + +from playwright.sync_api import sync_playwright +from playwright.async_api import async_playwright +import asyncio + +from bs4 import BeautifulSoup + +from utils import settings + +# Utils +def check_similarity(video_title, text): + video_title_words = set(video_title.lower().split()) + text_words = set(text.lower().split()) + + common_words = text_words.intersection(video_title_words) + + return len(common_words) / len(text_words) >= 0.6 + +# Runthrough +def generate_captions(file_path, title): + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=False) + context = browser.new_context(viewport={"width": 1920, "height": 1080}) + page = context.new_page() + page.goto("https://www.capcut.com/") + + email = settings.config["capcut"]["email"] + password = settings.config["capcut"]["password"] + video_title = title.lower() + video_file_name = title.replace(" ", "_") + + page.click("//span[contains(text(),'Decline all')]") + + page.click("//span[contains(text(),'OK')]") + + page.click("//span[contains(text(),'Sign in')]") + + page.fill("//input[@class='lv-input lv-input-size-default lv_sign_in_panel_wide-input']", email) + + page.click("//span[normalize-space()='Continue']") + + # time.sleep(5) + + page.fill("//input[@type='password']", password) + + page.click("//span[contains(text(),'Sign in')]") + + try: + page.click("//div[@class='skip--kncMC']") + + except: + pass + + page.goto(f"https://www.capcut.com/my-cloud/{str(settings.config['capcut']['cloud_id'])}?start_tab=video&enter_from=page_header&from_page=work_space&tab=all") + + try: + page.click("//span[contains(text(),'Decline all')]") + + except: + pass + + page.click("//div[@class='guide-modal-close-icon']") + + page.hover("//div[@data-selectable-item-id]") + + page.click("//*[@width='16']") + + page.click("//div[contains(text(),'Move to Trash')]") + + page.click("//span[contains(text(),'Confirm')]") + + # time.sleep(2) + + # page.click("//span[contains(text(),'Trash')]") + + # page.hover("//div[@data-selectable-item-id]") + + # page.screenshot(path="video_creation/Error3.png") + + page.goto("https://www.capcut.com/editor?enter_from=create_new¤t_page=landing_page&from_page=work_space&start_tab=video&__action_from=my_draft&position=my_draft&scenario=youtube_ads&scale=16%3A9") + + page.click("//div[@class='guide-close-icon-f8J9FZ']//*[name()='svg']") + + page.click("//div[@class='guide-placeholder-before-OsTdXF']") + + page.click("//div[@class='guide-close-icon-f8J9FZ']//*[name()='svg']") + + page.set_input_files("(//input[@type='file'])[1]", file_path) + + time.sleep(20) + + page.click("//div[@id='siderMenuCaption']//div[@class='menu-inner-box']//*[name()='svg']") + + page.click("//div[normalize-space()='Auto captions']") + + video_ready = False + while not video_ready: + page.click("//footer[@class='active-panel']//span[contains(text(),'Generate')]") + try: + # check if class="lv-message lv-message-error" is visible + if page.locator("//div[@class='lv-message lv-message-error']").is_visible(): + video_ready = False + else: + video_ready = True + except: + pass + + time.sleep(10) + + print("Changing settings") + + page.click("//div[@id='workbench-tool-bar-toolbarTextPreset']") + + time.sleep(1) + + page.click("//div[@id='lv-tabs-1-tab-1']") + + time.sleep(1) + + page.click(f"(//img[@class='image-DUnWNW'])[{str(settings.config['capcut']['preset_number'])}]") + + time.sleep(2) + + page.click("//div[@id='workbench-tool-bar-toolbarTextBasic']//div[@class='tool-bar-icon']//*[name()='svg']") + + time.sleep(2) + + page.fill("//input[@value='-255']", "0") + + time.sleep(1) + + page.fill("//input[@value='100' and @aria-valuemax='500']", "40") + + time.sleep(2) + + print("Cleaning up captions") + + for _ in range(10): + element = page.query_selector("//div[@class='subtitle-list-content']/section[1]") + html_code = page.evaluate("element => element.innerHTML", element) + soup = BeautifulSoup(html_code, 'html.parser') + textarea = soup.find('textarea', {'class': 'lv-textarea'}) + text = textarea.text.lower() + + if check_similarity(video_title.lower(), text.lower()): + page.click("//section[@class='subtitle-list-item']") + page.click("//button[@class='lv-btn lv-btn-text lv-btn-size-default lv-btn-shape-square']//*[name()='svg']") + else: + break + + print("Exporting video") + + page.click("//div[contains(@data-id,'titlebarExport')]//div[contains(@style,'position: relative;')]") + + page.click("//div[contains(@class,'content_7ddfe')]") + + page.fill("//input[@id='form-video_name_input']", video_file_name ) + + page.click("//span[contains(text(),'720p')]") + + page.click("//span[contains(text(),'1080p')]") + + page.click("//span[contains(text(),'Recommended quality')]") + + page.click("//li[contains(text(),'High quality')]") + + page.click("//span[contains(text(),'30fps')]") + + page.click("//li[contains(text(),'60fps')]") + + time.sleep(2) + + page.click("//button[@id='export-confirm-button']") + + time.sleep(35) + + with page.expect_download() as download_info: + page.locator("//a[@class='shadowAnchor_5bc06']").click() + dl = download_info.value + print(dl.path()) + working_dir_path = os.getcwd() + + os.makedirs(os.path.join(working_dir_path, "capcut_results", "videos"), exist_ok=True) + + final_path = os.path.join(working_dir_path, "capcut_results", "videos", video_file_name + ".mp4") + print(final_path) + dl.save_as(final_path) + browser.close() \ No newline at end of file diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 7029df6..cc24edb 100644 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -16,6 +16,7 @@ from rich.console import Console from rich.progress import track from utils import settings +from utils import capcut from utils.cleanup import cleanup from utils.console import print_step, print_substep from utils.thumbnail import create_thumbnail @@ -492,8 +493,16 @@ def make_final_video( old_percentage = pbar.n pbar.update(100 - old_percentage) pbar.close() + + + if settings.config["settings"]["storymodemethod"] == 0: + print_step("Adding CapCut captions 📝") + file_path = os.getcwd() + f"/results/{subreddit}/{filename}.mp4" + capcut.generate_captions(file_path, filename) + save_data(subreddit, filename + ".mp4", title, idx, background_config["video"][2]) print_step("Removing temporary files 🗑") cleanups = cleanup(reddit_id) print_substep(f"Removed {cleanups} temporary files 🗑") + print_step("Done! 🎉 The video is in the results folder 📁")