├── .gitignore ├── README.md ├── backgroundmusic ├── Alan Walker Style - Inner Peace (New Song 2020).mp3 ├── Bad Style Time Back - Copy.mp3 ├── Bad Style Time Back.mp3 ├── Blade Runner 2049.mp3 ├── Blue Blood.mp3 ├── La Lecon Particuliere(1968) - Theme De La Lecon Particuliere.mp3 ├── Late Night Melancholy.mp3 ├── Max Raabe - We Will Rock You.mp3 ├── Paris.mp3 ├── Reality.mp3 ├── Relaxing music Alec Koff - run to earth The Miracle Music.mp3 ├── SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net) - Copy.mp3 ├── SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net).mp3 ├── SLANDER - Love Is Gone (Lyrics) Im sorry dont leave me I want you here with me.mp3 ├── Stay With Me (Piano Version).mp3 ├── Tears - No Copyright Music Sad Emotional Background Music for Vlog Free Instrumental Music.mp3 ├── Windy Hill (BGM).mp3 ├── Xiao Ling (TikTok Roblox Song).mp3 ├── backup (1).mp3 ├── backup (2).mp3 ├── backup (3).mp3 ├── backup (4).mp3 ├── backup (5).mp3 ├── backup (6).mp3 └── emotionalpiano.mp3 ├── black_background.mp4 ├── client_secrets.json ├── clips └── .placeholder ├── combine_srt_script.py ├── config.json ├── get_movie_summary.py ├── images └── icon.ico ├── main.py ├── make_mp3_same_volume.py ├── movies └── .placeholder ├── output └── .placeholder ├── output_audio └── .placeholder ├── rename_files.py ├── requirements.txt ├── scripts ├── scrape_script.py └── scrape_subtitles.py ├── tiktok_resize.py ├── timestamp_assignments.py ├── upload_action.py ├── upload_tiktok.py └── youtube_upload.py /.gitignore: -------------------------------------------------------------------------------- 1 | client_secrets.json 2 | __pycache__\ 3 | clips/* 4 | output/* 5 | output_audio/* 6 | temp_movies/ 7 | tiktok_output/* 8 | Good Summaries\ 9 | movies_retired\ 10 | download_playlist.py 11 | scripts/srt_files/* 12 | scripts/parsed_scripts/* 13 | scripts/audio_extractions/* 14 | cookies.txt 15 | /client_secrets.json 16 | movies_retired/ 17 | movies/* 18 | tiktok_uploader\ 19 | tiktok_uploader/ 20 | tiktok_uploader/* 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI-Movie-Shorts 2 | Turn full movies or .MP4 Files into AI-generated movie trailers and summaries using OpenAI. 3 | 4 | Example Video: [Citizen Kane (1941)](https://www.youtube.com/watch?v=ej8c0NwKW00&t=5s) 5 | 6 |

Basic Installation Steps

7 | 8 | ```python 9 | #This repository only works on Windows PCs 10 | #Clone the Git repository: 11 | git clone https://github.com/keithhb33/AI-Movie-Shorts.git 12 | 13 | #Navigate to the cloned repo: 14 | cd AI-Movie-Shorts 15 | 16 | #Install modules and dependencies within the cloned "AI-Movie-Shorts" directory: 17 | python3 -m pip install -r requirements.txt 18 | 19 | #Remove .placeholder files (Windows PowerShell Terminal Command): 20 | Get-ChildItem -Recurse -Filter ".placeholder" | Remove-Item 21 | 22 | ``` 23 | 24 |

OpenAI API Setup & Configuration

25 | Due to OpenAI's API usage rate costs, users must configure their own GPT 4o API keys. 26 |
27 | Key acquisition guide here. 28 |
29 |
30 | Once acquired, edit the config.json file: 31 |
32 | 33 | ```python 34 | # Replace with OpenAI and ElevenLabs API keys ('legacy' user OpenAI API keys are utilized here). 35 | # IMPORTANT: OpenAI TIER 2 API ACCOUNT IS REQUIRED DUE TO CHARACTER LIMITS ON LEVEL 1. 36 | config.json 37 | ``` 38 | 39 |

Usage

40 | After configuration: 41 | 42 | ```python 43 | #Run the terminal command in the "AI-Movie-Shorts" directory: 44 | python3 main.py 45 | ``` 46 | 47 | An application GUI should appear. 48 | 49 |

50 | GUI Image 51 |

52 | 53 | Place .MP4 (movie) files into the "movies" directory. 54 | Ensure all filenames are the titles of their respective movies (Ex: "American Psycho.mp4") 55 | 56 | Click "Start Generation" 57 | 58 | If the algorithm cannot find a particular movie script, it will ask for you to place it in scripts/srt_files/{movie_title}_summary.txt. 59 | Find the movie script online and paste it into this file. Then rerun "Start Generation." 60 | 61 | For non-public movies, the algorithm may have trouble finding an SRT file for the movie. If this error occurs, find an SRT file 62 | for your movie/video and place it in scripts/srt_files/{movie_title}.srt 63 | 64 | After all narrations are complete, the GUI will indicate such. Processed movie shorts can be viewed in the "output" directory. 65 | Here is an example movie short: [Watch Short](https://youtu.be/TBBme4gQ9G8) 66 | 67 | 68 |

(Optional) YouTube API Setup & Configuration

69 | 70 | Users must configure the YouTube API to access their particular YouTube channel. This configuration is optional if users only wish to generate MP4 files. 71 | 72 | Follow the steps here only under the "Requirements" heading. Configure your YouTube API here. Once the YouTube Data API web application has been configured, paste the correct "client_id" and "client_secret" into 73 | a client_secrets.json file. 74 | 75 | After clicking "Upload All to Youtube" on the GUI, users should be asked to sign in to the Google account associated with their YouTube channel, and all of the outputted .MP4 files in the "output" directory will upload to YouTube. 76 | 77 | Unfortunately, as of 2020 July, only audited and approved user-created YouTube APIs can be used to upload public videos to the platform. Using non-audited APIs to upload videos to YouTube results in the videos being locked as private. The audit application can be found here. The process usually only takes a few days. 78 | -------------------------------------------------------------------------------- /backgroundmusic/Alan Walker Style - Inner Peace (New Song 2020).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Alan Walker Style - Inner Peace (New Song 2020).mp3 -------------------------------------------------------------------------------- /backgroundmusic/Bad Style Time Back - Copy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Bad Style Time Back - Copy.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Bad Style Time Back.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Bad Style Time Back.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Blade Runner 2049.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Blade Runner 2049.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Blue Blood.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Blue Blood.mp3 -------------------------------------------------------------------------------- /backgroundmusic/La Lecon Particuliere(1968) - Theme De La Lecon Particuliere.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/La Lecon Particuliere(1968) - Theme De La Lecon Particuliere.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Late Night Melancholy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Late Night Melancholy.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Max Raabe - We Will Rock You.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Max Raabe - We Will Rock You.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Paris.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Paris.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Reality.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Reality.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Relaxing music Alec Koff - run to earth The Miracle Music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Relaxing music Alec Koff - run to earth The Miracle Music.mp3 -------------------------------------------------------------------------------- /backgroundmusic/SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net) - Copy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net) - Copy.mp3 -------------------------------------------------------------------------------- /backgroundmusic/SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/SLANDER - I'm sorry don't leave me I want you here with me (Lyrics) Love Is Gone (mp3cut.net).mp3 -------------------------------------------------------------------------------- /backgroundmusic/SLANDER - Love Is Gone (Lyrics) Im sorry dont leave me I want you here with me.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/SLANDER - Love Is Gone (Lyrics) Im sorry dont leave me I want you here with me.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Stay With Me (Piano Version).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Stay With Me (Piano Version).mp3 -------------------------------------------------------------------------------- /backgroundmusic/Tears - No Copyright Music Sad Emotional Background Music for Vlog Free Instrumental Music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Tears - No Copyright Music Sad Emotional Background Music for Vlog Free Instrumental Music.mp3 -------------------------------------------------------------------------------- /backgroundmusic/Windy Hill (BGM).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Windy Hill (BGM).mp3 -------------------------------------------------------------------------------- /backgroundmusic/Xiao Ling (TikTok Roblox Song).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/Xiao Ling (TikTok Roblox Song).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (1).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (1).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (2).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (2).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (3).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (3).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (4).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (4).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (5).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (5).mp3 -------------------------------------------------------------------------------- /backgroundmusic/backup (6).mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/backup (6).mp3 -------------------------------------------------------------------------------- /backgroundmusic/emotionalpiano.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/backgroundmusic/emotionalpiano.mp3 -------------------------------------------------------------------------------- /black_background.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/black_background.mp4 -------------------------------------------------------------------------------- /client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "YOUTUBE DATA API CLIENT ID", 4 | "client_secret": "YOUTUBE API CLIENT SECRET", 5 | "redirect_uris": [], 6 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 7 | "token_uri": "https://accounts.google.com/o/oauth2/token" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /clips/.placeholder: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /combine_srt_script.py: -------------------------------------------------------------------------------- 1 | import re 2 | import argparse 3 | 4 | def normalize_text(text): 5 | # Remove punctuation, newlines, and extra spaces; convert to lowercase 6 | text = re.sub(r'[^\w\s]', '', text) 7 | text = re.sub(r'\s+', ' ', text) 8 | return text.strip().lower() 9 | 10 | def is_all_caps(text): 11 | # Check if the text is in all caps 12 | return text.isupper() 13 | 14 | def read_and_normalize_file(file_path, exclude_single_words=True): 15 | 16 | with open(file_path, 'r', encoding='utf-8') as file: 17 | lines = file.readlines() 18 | normalized_lines = [ 19 | (line, normalize_text(line)) 20 | for line in lines 21 | if not is_all_caps(line.strip()) 22 | ] 23 | for i in range(len(normalized_lines)): 24 | # Extract the current tuple 25 | current_tuple = normalized_lines[i] 26 | # Create a new tuple with the modified second value 27 | new_tuple = ( 28 | current_tuple[0], 29 | current_tuple[1].replace('–', '-').replace('“', '"').replace("’", "").replace('”', "") 30 | ) 31 | # Replace the old tuple with the new one 32 | normalized_lines[i] = new_tuple 33 | if exclude_single_words: 34 | normalized_lines = [ 35 | (line, norm_line) 36 | for line, norm_line in normalized_lines 37 | if len(norm_line.split()) > 1 38 | ] 39 | return normalized_lines 40 | 41 | def similar_enough(a, b, allowed_differences=1): 42 | words_a = a.split() 43 | words_b = b.split() 44 | if len(words_a) > 4 and len(words_b) > 4: 45 | common_words = set(words_a).intersection(set(words_b)) 46 | total_words = max(len(words_a), len(words_b)) 47 | differences = total_words - len(common_words) 48 | return differences <= allowed_differences 49 | return a == b 50 | 51 | def find_matching_lines(file1_lines, file2_lines): 52 | matching_lines = [] 53 | used_time_ranges = [] 54 | 55 | for line, normalized_line in file1_lines: 56 | matched = False 57 | for idx, (time_range, norm_line2) in enumerate(file2_lines): 58 | if similar_enough(normalized_line, norm_line2) and (time_range not in used_time_ranges): 59 | matching_lines.append((time_range, line.strip())) 60 | used_time_ranges.append(time_range) 61 | matched = True 62 | break 63 | if not matched: 64 | matching_lines.append((None, line.strip())) 65 | return matching_lines 66 | 67 | def read_and_normalize_srt(file_path): 68 | try: 69 | with open(file_path, 'r', encoding='utf-8') as file: 70 | content = file.read() 71 | except UnicodeDecodeError: 72 | with open(file_path, 'r', encoding='latin1') as file: 73 | content = file.read() 74 | # Split by subtitle blocks 75 | blocks = re.split(r'\n\s*\d+\s*\n', content) 76 | srt_lines = [] 77 | for block in blocks: 78 | if '-->' in block: 79 | parts = block.split('\n') 80 | time_range = parts[0].strip() 81 | lines = parts[1:] 82 | for line in lines: 83 | if line.strip(): 84 | normalized_line = normalize_text(line) 85 | srt_lines.append((time_range, normalized_line)) 86 | return srt_lines 87 | 88 | def main(): 89 | parser = argparse.ArgumentParser(description='Process a movie script and SRT file.') 90 | parser.add_argument('movie_title', type=str, help='The title of the movie') 91 | 92 | args = parser.parse_args() 93 | movie_title = args.movie_title 94 | 95 | file1_path = f'scripts/srt_files/{movie_title}_summary.txt' 96 | file2_path = f'scripts/srt_files/{movie_title}_modified.srt' 97 | output_path = f'scripts/srt_files/{movie_title}_combined.txt' 98 | 99 | # Read and normalize the files 100 | file1_lines = read_and_normalize_file(file1_path) 101 | file2_lines = read_and_normalize_srt(file2_path) 102 | 103 | # Find matching lines 104 | matching_lines = find_matching_lines(file1_lines, file2_lines) 105 | print("MATCHING LINES: " + str(matching_lines)) 106 | 107 | # Write matching lines with time ranges to the output file 108 | with open(output_path, 'w', encoding='utf-8') as output_file: 109 | output_file.write("0\n") # Write a 0 at the beginning of the file 110 | last_time_end = None 111 | for time_range, line in matching_lines: 112 | if time_range and ' --> ' in time_range: 113 | start_time, end_time = time_range.split(' --> ') 114 | if last_time_end: 115 | output_file.write(f"{last_time_end}\n") 116 | output_file.write(f"{start_time}\n{line}\n") 117 | last_time_end = end_time 118 | else: 119 | output_file.write(f"{line}\n") 120 | if last_time_end: 121 | output_file.write(f"{last_time_end}\n") 122 | 123 | if __name__ == '__main__': 124 | main() 125 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "open_api_key": "OPENAPIKEY", 3 | "elevenlabs_api_key": "ELEVENLABSAPIKEY" 4 | } 5 | -------------------------------------------------------------------------------- /get_movie_summary.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | from bs4 import BeautifulSoup 4 | 5 | 6 | def get_movie_plot_summary(movie_title): 7 | # Format the movie title to match Wikipedia URL format 8 | formatted_title = movie_title.replace(' ', '_').replace("'", "%27") 9 | url = f'https://imsdb.com/scripts/{formatted_title}_(film).html' 10 | 11 | # Fetch the Wikipedia page 12 | response = requests.get(url) 13 | if response.status_code != 200: 14 | print(f"Failed to retrieve Wikipedia page for {movie_title}... trying a different url") 15 | response = requests.get(url.replace("_(film)", "")) 16 | if response.status_code != 200: 17 | response = requests.get(url.replace("_", "%20")) 18 | if response.status_code != 200: 19 | response = requests.get(url + "%20Script") 20 | 21 | # Parse the page content 22 | soup = BeautifulSoup(response.content, 'html.parser') 23 | 24 | # Find the first
 tag
25 |     pre_tag = soup.find('pre')
26 | 
27 |     if not pre_tag:
28 |         print(f"Script section not found for {movie_title}")
29 |         return
30 | 
31 |     # Extract the text and remove HTML tags
32 |     script_text = pre_tag.get_text()
33 | 
34 |     file_path = f'scripts/srt_files/{movie_title}_summary.txt'
35 |     os.makedirs(os.path.dirname(file_path), exist_ok=True)
36 | 
37 |     # Save the script text to a file
38 |     with open(file_path, 'w', encoding='utf-8') as file:
39 |         file.write(script_text)
40 | 
41 |     print(f"Script for {movie_title} saved to {file_path}")
42 | get_movie_plot_summary("Eyes Wide Shut")


--------------------------------------------------------------------------------
/images/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keithhb33/AI-Movie-Shorts/2e4b52a7206b85724b5029a7918f49d61190dbd3/images/icon.ico


--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
  1 | import os
  2 | import math
  3 | import tkinter as tk
  4 | import sys
  5 | import random
  6 | import re
  7 | from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip
  8 | from moviepy.audio.AudioClip import concatenate_audioclips
  9 | import moviepy.editor as mpe
 10 | from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
 11 | import ffmpeg
 12 | from moviepy.audio.io.AudioFileClip import AudioFileClip
 13 | import random
 14 | from subliminal import download_best_subtitles, region, save_subtitles
 15 | from subliminal.cli import cache_file
 16 | from subliminal.video import Video
 17 | from babelfish import Language
 18 | import pyttsx3
 19 | from mutagen.mp3 import MP3
 20 | from bs4 import BeautifulSoup
 21 | import requests
 22 | from mutagen.mp4 import MP4
 23 | from moviepy.editor import *
 24 | from os import path
 25 | import speech_recognition as sr
 26 | from pydub import *
 27 | import re
 28 | import sys
 29 | import shutil
 30 | import ast
 31 | from make_mp3_same_volume import *
 32 | from moviepy.video.fx.all import speedx
 33 | from mutagen.mp3 import MP3
 34 | from youtube_upload import *
 35 | import subprocess
 36 | import shlex
 37 | import openai
 38 | import threading
 39 | import elevenlabs
 40 | import moviepy.video.fx.all as vfx
 41 | from timestamp_assignments import *
 42 | from PyBetterFileIO import *
 43 | import json
 44 | 
 45 | MIN_NUM_CLIPS = 20
 46 | MAX_NUM_CLIPS = 30
 47 | MIN_TOTAL_DURATION = 2.5 * 60
 48 | MAX_TOTAL_DURATION = 4.5 * 60
 49 | 
 50 | def load_config(file_path):
 51 |     with open(file_path, 'r') as file:
 52 |         config = json.load(file)
 53 |     return config
 54 | 
 55 | config = load_config('config.json')
 56 | open_api_key = config.get('open_api_key')
 57 | elevenlabs_api_key = config.get('elevenlabs_api_key')
 58 | 
 59 | class Gui:
 60 |     def __init__(self, root):
 61 |         self.root = root
 62 |         self.processing_label = None
 63 |         self.uploading_label = None
 64 |         self.upload_button = None
 65 |         self.start_button = None
 66 |         self.process_thread = None
 67 |         root.title("Movie Summary Bot")
 68 |         root.geometry("500x700")
 69 |         root.iconbitmap("images/icon.ico")
 70 |         self.progress_label = None
 71 |         if open_api_key == "OPEN_AI_API_KEY HERE" or open_api_key == "":
 72 |             self.upload_button.config(state=tk.DISABLED)
 73 |             self.start_button.config(state=tk.DISABLED)
 74 |         
 75 |         self.uploaded_movies = 0
 76 |         
 77 | 
 78 |         exit_button = tk.Button(root, text="Exit Program", command=self.end_program, font=("Helvetica", 10), height=2, width=10, bg='red', fg='white')
 79 |         exit_button.pack(anchor='nw', padx=10, pady=10)
 80 | 
 81 |         self.stop_button = tk.Button(root, text="Stop Processing", command=self.restart_program, font=("Helvetica", 10), height=2, width=15, bg="orange")
 82 |         self.stop_button.pack(anchor='ne', pady=12)
 83 |         self.stop_button.pack_forget()
 84 |         
 85 |         self.stop_uploading_button = tk.Button(root, text="Stop Uploading", command=self.restart_program, font=("Helvetica", 10), height=2, width=15, bg="orange")
 86 |         self.stop_uploading_button.pack(anchor='ne', pady=12)
 87 |         self.stop_uploading_button.pack_forget()
 88 |         
 89 |         title = tk.Label(root, text="Movie Summary Bot", font=("Helvetica", 24, "bold"))
 90 |         title.pack(pady=20)
 91 | 
 92 |         self.processing_label = tk.Label(root, text="Processing...", font=("Helvetica", 14))
 93 |         self.processing_label.pack(pady=10)
 94 |         self.processing_label.pack_forget()
 95 |         
 96 |         self.uploading_label = tk.Label(root, text="Uploading...", font=("Helvetica", 14))
 97 |         self.uploading_label.pack(pady=10)
 98 |         self.uploading_label.pack_forget()
 99 | 
100 |         movies_dir = "movies"
101 |         output_dir = "output"
102 | 
103 |         movie_button = tk.Button(root, text="Open Movie Directory", command=lambda: self.open_directory(movies_dir), font=("Helvetica", 14), height=2, width=20, bg='aqua')
104 |         movie_button.pack(pady=10)
105 | 
106 |         self.start_button = tk.Button(root, text="Start Generation", command=lambda: self.start_process(movies_dir, output_dir), fg="white", font=("Helvetica", 14), height=2, width=20, bg="grey")
107 |         self.start_button.pack(pady=10)
108 | 
109 |         output_button = tk.Button(root, text="Open Output Directory", command=lambda: self.open_directory(output_dir), font=("Helvetica", 14), height=2, width=20, bg='aqua')
110 |         output_button.pack(pady=10)
111 | 
112 |         self.upload_button = tk.Button(root, text="Upload all to YouTube", command=self.upload_to_youtube, font=("Helvetica", 14), height=2, width=20, bg='red', fg='white')
113 |         self.upload_button.pack(pady=10)
114 |         
115 |         
116 |         self.progress_status = tk.StringVar()
117 |         movie_count = Gui.get_number_of_movies(movies_dir, output_dir, len(os.listdir(movies_dir)))
118 |         self.progress_status.set(f"0/{movie_count} Generated")
119 |         
120 |         self.progress_label = tk.Label(root, textvariable=self.progress_status, font=("Helvetica", 14))
121 |         self.progress_label.pack()
122 |         
123 |         self.refresh()
124 |         
125 |     @staticmethod
126 |     def get_movie_plot_summary(movie_title):
127 |         formatted_title = movie_title.replace(' ', '_').replace("'", "%27")
128 |         url = f'https://imsdb.com/scripts/{formatted_title}_(film)'
129 | 
130 |         response = requests.get(url)
131 |         if response.status_code != 200:
132 |             print(f"Failed to retrieve Wikipedia page for {movie_title}... trying a different url")
133 |             response = requests.get(url.replace("_(film)", ""))
134 | 
135 |         # Parse the page content
136 |         soup = BeautifulSoup(response.content, 'html.parser')
137 | 
138 |         # Find the first 
 tag
139 |         pre_tag = soup.find('pre')
140 | 
141 |         if not pre_tag:
142 |             print(f"Script section not found for {movie_title}")
143 |             return
144 | 
145 |         script_text = pre_tag.get_text()
146 | 
147 |         file_path = f'scripts/srt_files/{movie_title}_summary.txt'
148 |         os.makedirs(os.path.dirname(file_path), exist_ok=True)
149 | 
150 |         with open(file_path, 'w', encoding='utf-8') as file:
151 |             file.write(script_text)
152 | 
153 |         print(f"Script for {movie_title} saved to {file_path}")
154 |         
155 | 
156 |         
157 |     def end_program(self):
158 |         if open_api_key == "OPEN_AI_API_KEY HERE":
159 |             print("Enter a valid openai_api key")
160 |         os._exit(0)
161 |         
162 |     @staticmethod
163 |     def get_number_of_movies(movies_dir, output_dir, num_of_movies):
164 |         if len(os.listdir(output_dir)) > 0:
165 |             for i in range(len(output_dir)):
166 |                 for movie in os.listdir(output_dir):
167 |                     try:
168 |                         if movie in os.listdir(movies_dir)[i]:
169 |                             num_of_movies += -1
170 |                     except Exception as e:
171 |                         continue
172 |         return num_of_movies
173 | 
174 |     @staticmethod
175 |     def chatGPT_response(message, number_of_words, movie_title):
176 |         try:                
177 |             openai.api_key = open_api_key
178 |                 
179 |             response = openai.ChatCompletion.create(
180 |             model="gpt-4o",
181 |             messages=[
182 |                     {"role": "system", "content": "You are a movie expert."},
183 |                     {"role": "user", "content": message},
184 |                 ]
185 |             )
186 |                 
187 |             answer = response['choices'][0]['message']['content']
188 |                 
189 |             if "here's a summary of" in answer:
190 |                 answer = answer.replace("here's a summary of", "")
191 |                 
192 |             return "Here we go (spoilers ahead)" + (response['choices'][0]['message']['content'])
193 |         except Exception as e:
194 |             time.sleep(1)
195 |             return Gui.chatGPT_response(message, number_of_words, movie_title)
196 |     
197 |     @staticmethod
198 |     def is_within_word_limit(response, number_of_words, tolerance=50):
199 |         words = re.findall(r'\w+', response.strip())
200 |             
201 |         response_word_count = len(words)
202 |         lower_limit = number_of_words - tolerance
203 |         upper_limit = number_of_words + tolerance
204 |             
205 |         return lower_limit <= response_word_count <= upper_limit
206 |     
207 |     @staticmethod
208 |     def convert_timestamp_to_seconds(timestamp):
209 |         hours, minutes, seconds = map(float, re.split('[:,]', timestamp)[:3])
210 |         total_seconds = int(hours * 3600 + minutes * 60 + seconds)
211 |         return total_seconds
212 | 
213 |     def convert_srt_timestamps(input_file, output_file):
214 |         try:
215 |             with open(input_file, 'r', encoding="utf-8-sig") as infile, open(output_file, 'w') as outfile:
216 |                 for line in infile:
217 |                     timestamp_match = re.match(r'(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})', line)
218 |                     if timestamp_match:
219 |                         start_time, end_time = timestamp_match.groups()
220 |                         start_seconds = Gui.convert_timestamp_to_seconds(start_time)
221 |                         end_seconds = Gui.convert_timestamp_to_seconds(end_time)
222 |                         outfile.write(f'{start_seconds} --> {end_seconds}\n')
223 |                     else:
224 |                         outfile.write(line)
225 |         except Exception as e:
226 |             try:
227 |                 with open(input_file, 'r', encoding="utf-8") as infile, open(output_file, 'w') as outfile:
228 |                     for line in infile:
229 |                         timestamp_match = re.match(r'(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})', line)
230 |                         if timestamp_match:
231 |                             start_time, end_time = timestamp_match.groups()
232 |                             start_seconds = Gui.convert_timestamp_to_seconds(start_time)
233 |                             end_seconds = Gui.convert_timestamp_to_seconds(end_time)
234 |                             outfile.write(f'{start_seconds} --> {end_seconds}\n')
235 |                         else:
236 |                             outfile.write(line)
237 |             except:
238 |                 with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
239 |                     for line in infile:
240 |                         timestamp_match = re.match(r'(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})', line)
241 |                         if timestamp_match:
242 |                             start_time, end_time = timestamp_match.groups()
243 |                             start_seconds = Gui.convert_timestamp_to_seconds(start_time)
244 |                             end_seconds = Gui.convert_timestamp_to_seconds(end_time)
245 |                             outfile.write(f'{start_seconds} --> {end_seconds}\n')
246 |                         else:
247 |                             outfile.write(line)
248 | 
249 |     
250 |     @staticmethod
251 |     def restart_program():
252 |         os.execl(sys.executable, '"{}"'.format(sys.executable), *sys.argv)
253 |         
254 |     def refresh(self):
255 |         self.root.update()
256 |         self.root.after(1000,self.refresh)
257 |         
258 |     def start_process(self, movies_dir, output_dir):
259 |         
260 |         num_of_movies = len(os.listdir(movies_dir))
261 |         
262 |         if len(os.listdir(output_dir)) > 0:
263 |             for i in range(len(output_dir)):
264 |                 for movie in os.listdir(output_dir):
265 |                     try:
266 |                         if movie in os.listdir(movies_dir)[i]:
267 |                             num_of_movies += -1
268 |                     except Exception as e:
269 |                         continue
270 |         
271 |         if num_of_movies == 0:
272 |             return ""
273 | 
274 |         self.start_button.config(state=tk.DISABLED)
275 |         self.upload_button.config(state=tk.DISABLED)
276 |         self.stop_button.pack()
277 |         # Show the processing label
278 |         self.processing_label.pack()
279 | 
280 |         self.refresh()
281 |         self.process_thread = threading.Thread(target=self.process_movies, args=(num_of_movies, movies_dir, output_dir))
282 |         self.process_thread.start()
283 |         
284 |     @staticmethod
285 |     def select_random_song():
286 |         songs_dir = "backgroundmusic"
287 |         songs = os.listdir(songs_dir)
288 |         song_path = None
289 |         song_name = None
290 | 
291 |         while not song_path:
292 |             song_name = random.choice(songs)
293 |             songs.remove(song_name)
294 |             song_path = os.path.join(songs_dir, song_name)
295 | 
296 |             audio_clip = AudioFileClip(song_path)
297 |             if audio_clip.duration <= 60:
298 |                 song_path = None
299 | 
300 |         song = AudioFileClip(song_path).subclip(40)
301 |         return song
302 |     
303 |     @staticmethod
304 |     def tiktok_version(video_path, output_path):
305 |         clip = VideoFileClip(video_path)
306 |         
307 |         original_width, original_height = clip.size
308 |         crop_width = int(original_width * 0.6)  # Keep 60% of the width
309 |         crop_x1 = int(original_width * 0.2)  # Crop 20% from the left
310 |         crop_x2 = crop_x1 + crop_width  # Set the right boundary of the crop
311 | 
312 |         cropped_clip = clip.crop(x1=crop_x1, x2=crop_x2)
313 |         
314 |         new_height = original_height
315 |         new_width = int(new_height * (9 / 16))
316 |         
317 |         target_height = int(new_height * 0.6 * 1.2)  # Stretch by 20%
318 |         resized_clip = cropped_clip.resize(height=target_height)
319 |         
320 |         background = VideoFileClip("black_background.mp4", has_mask=True).set_duration(clip.duration).resize((new_width, new_height))
321 |         
322 |         final_clip = CompositeVideoClip([background, resized_clip.set_position(('center', 'center'))])
323 |         
324 |         final_clip.write_videofile(output_path, codec='libx264')
325 | 
326 |     def process_movies(self, num_of_movies, movies_dir, output_dir):
327 | 
328 |         movie_array = [f for f in os.listdir(movies_dir) if os.path.isfile(os.path.join(movies_dir, f))]
329 |         print(movie_array)
330 |         for i in range(len(output_dir)):
331 |             for movie in os.listdir(output_dir):
332 |                 try:
333 |                     if movie in movie_array[i]:
334 |                         num_of_movies += -1
335 |                         movie_array.remove(os.path.join(movies_dir, movie))
336 | 
337 |                 except Exception as e:
338 |                     continue
339 |                 
340 |         for movie in os.listdir(movies_dir):
341 |             if not movie.endswith(".mp4"):
342 |                 movie_array.remove(os.path.join(movies_dir, movie))
343 |         
344 |         processed_movies = 0
345 |         
346 |         if num_of_movies == 0:
347 |             return
348 | 
349 |         i = 0
350 |         while i < num_of_movies:
351 |             if len(os.listdir(output_dir)) > 0:
352 |                 for j in range(len(output_dir)):
353 |                     for movie in os.listdir(output_dir):
354 |                         try:
355 |                             if movie in os.listdir(movies_dir)[j]:
356 |                                 num_of_movies += -1
357 |                         except Exception as e:
358 |                             continue
359 |             
360 |             movie_title = str(movie_array[i])[:-4]
361 |             video = VideoFileClip(os.path.join(movies_dir, str(movie_array[i])))
362 |             duration_in_seconds = video.duration
363 |             
364 |             openai.api_key = open_api_key
365 |             
366 |             input_dir_srt = f"scripts/srt_files/{movie_title}.srt"
367 |             output_dir_srt = f"scripts/srt_files/{movie_title}_modified.srt"
368 | 
369 |             subtitles_path = 'scripts/scrape_subtitles.py'
370 |             script_path = 'scripts/scrape_script.py'
371 |             
372 |             output_dir_script = f'scripts/srt_files/{movie_title}_summary.txt'
373 |             try:
374 |                 srt = subprocess.run(['python', subtitles_path, movie_title])
375 |                 
376 |                 if not os.path.isfile(input_dir_srt):
377 |                     input(f"SRT file for {movie_title} not available. Please manually place it in {input_dir_srt}. Hit enter to continue.")
378 | 
379 |                 if not os.path.isfile(output_dir_srt):
380 |                     Gui.convert_srt_timestamps(input_dir_srt, output_dir_srt)
381 |                     os.remove(input_dir_srt)
382 |                 
383 |                 Gui.get_movie_plot_summary(movie_title)
384 |                 script_movie = subprocess.run(['python', script_path, output_dir_script, movie_title])
385 |                 if not os.path.isfile(output_dir_script):
386 |                     input(f"Getting {movie_title} script failed. Manually place the script at '{output_dir_script}'\nHit 'Enter' after completed.")
387 |                     
388 |                 try:
389 |                     movie_scene_by_scene = subprocess.run(['python', "combine_srt_script.py", movie_title])
390 |                 except Exception as e:
391 |                     raise Exception("Problem analyzing SRT and Script files.")
392 | 
393 |             except Exception as e:
394 |                 print(e)
395 |                 continue
396 |             
397 |             script_dir = os.path.dirname(subtitles_path)
398 | 
399 |             try:
400 |                 File(output_dir_srt).replace("", "")
401 |             except Exception as e:
402 |                 print("FAILED")
403 |             
404 |             with open(output_dir_srt, 'r') as output_file_replace:
405 |                 data = output_file_replace.read()
406 |                 data = data.replace("", "").replace("", "")
407 |                 with open(output_dir_srt, 'w') as output_file_replace:
408 |                     output_file_replace.write(data)
409 |             with open(output_dir_srt, "r") as srt_file:
410 |                 data = srt_file.read()
411 |             print(data)
412 |             print("DURATION OF MOVIE: " + str(duration_in_seconds))
413 |             #Gui.get_movie_plot_summary(movie_title)
414 |             with open(f"scripts/srt_files/{movie_title}_combined.txt", 'r') as file:
415 |                 combined_script = file.read()
416 |             
417 |             
418 |             script = f'''Read and understand this script of the movie {movie_title}, which includes timestamps (indicating number of seconds into the movie): "{combined_script}"
419 |             From this, choose {num_clips} time ranges that are most essential to the plot and development of the movie's story. Each chosen range should be in between 10 seconds and 30 seconds. Choose ranges from the script provided or combinations of such.
420 |             Only output the time ranges, formatted in a Python dictionary in the format of: {{"120-145": "PLOT SUMMARY OF WHAT OCCURS DURING THAT TIME RANGE", "280-300": ...}}
421 |             Don't overlap time ranges.
422 |             Each value in this dictionary should be a commentary describing what is happening in the scene. This should be a description of each event within the time duration. Consult the SRT script. Use full sentences like you're a commentator speaking to an audience going scene-by-scene for each dict value.
423 |             For the first dict value start it with: "Here we go, let's go over the movie {movie_title}." Write at least 3 sentences for each value.
424 |             Make sure the whole movie's plot arc is covered, up until the final scene. Output the time ranges in numerical order. Ignore the very first time range, which starts at 0.
425 |             '''
426 |             
427 |         
428 |             response = Gui.get_SRT_response(script)
429 |             if response == "error":
430 |                 break
431 |             
432 |             response = response.replace("```", "").replace("python", "").replace("    ", "").replace(r"\n\n", "")
433 |             response = ast.literal_eval(response)
434 |             if not isinstance(response, dict):
435 |                 try:
436 |                     Gui.delete_clips("clips")
437 |                     continue
438 |                 except Exception as e:
439 |                     continue
440 |                 
441 |             for key, value in response.items():
442 |                 print(str(key) + ": " + str(value))
443 |             
444 | 
445 |             try:
446 |                 clips, audio_outputs = Gui.split_video_importance("movies/" + str(movie_array[i]), "clips", response)
447 |             except Exception as e:
448 |                 clips, audio_outputs = Gui.split_video_importance("movies/" + str(movie_array[i]), "clips", response)     
449 |         
450 |             
451 |             adjusted_clips = []
452 |             for j in range(len(audio_outputs)):
453 |                 clip = clips[j]
454 |                 audio = audio_outputs[j]
455 | 
456 |                 slowdown_factor = audio.duration / clip.duration
457 |                 
458 |                 if abs(clip.duration - audio.duration) > 4:
459 |                     clip = clip.fl_time(lambda t: t / slowdown_factor, apply_to=['mask', 'audio'])
460 |                     clip = clip.set_duration(audio.duration)
461 | 
462 |                 clip = clip.set_audio(audio.volumex(4.0))
463 |                 adjusted_clips.append(clip)
464 | 
465 |             final_clip = concatenate_videoclips(adjusted_clips)
466 |             final_clip_duration = final_clip.duration
467 |             final_output_path = os.path.join("output", f"{movie_title}.mp4")
468 | 
469 |             audio_clips = []
470 |             total_duration_covered = 0
471 | 
472 |             while total_duration_covered < final_clip_duration:
473 |                 song = Gui.select_random_song()
474 |                 remaining_duration = final_clip_duration - total_duration_covered
475 | 
476 |                 if song.duration <= remaining_duration + 5:
477 |                     audio_clips.append(song)
478 |                     total_duration_covered += song.duration
479 |                 else:
480 |                     song = song.subclip(0, remaining_duration)
481 |                     audio_clips.append(song)
482 |                     total_duration_covered += remaining_duration
483 | 
484 |             background_music = concatenate_audioclips(audio_clips)
485 | 
486 |             if background_music.duration < final_clip.duration:
487 |                 difference = final_clip.duration - background_music.duration
488 |                 song_to_append = Gui.select_random_song().subclip(0, difference)
489 |                 background_music = concatenate_audioclips([background_music, song_to_append])
490 | 
491 |             background_music = background_music.volumex(0.1)
492 | 
493 |             combined_audio = CompositeAudioClip([final_clip.audio, background_music])
494 |             final_clip = final_clip.set_audio(combined_audio)
495 | 
496 |             final_clip.write_videofile(final_output_path, codec='libx264')
497 |             
498 |             processed_movies += 1
499 | 
500 |             self.progress_status.set(f"{processed_movies}/{num_of_movies} Generated")
501 |             self.refresh()
502 |             Gui.tiktok_version(final_output_path, final_output_path[:-4].replace("output", "tiktok_output") + "_vertical.mp4")
503 | 
504 |             i += 1
505 |             time.sleep(5)
506 |         
507 |         self.processing_label.config(text="Process Complete")
508 |         self.stop_uploading_button.destroy()
509 |         self.stop_button.destroy()
510 |         if open_api_key != "OPEN_AI_API_KEY HERE" and open_api_key != "":
511 |             self.start_button.config(state=tk.NORMAL)
512 |             self.start_button.destroy()
513 |             self.upload_button.config(state=tk.NORMAL)
514 |         else:
515 |             print("Please enter an OpenAPI API Key")
516 | 
517 |         for vid in os.listdir(output_dir):
518 |             if "_vertical" in vid and vid.endswith(".mp4"):
519 |                 File(str(vid)).move_to(os.path.join("tiktok_output", vid))
520 |                 
521 |         for movie in os.listdir(movies_dir):
522 |             if movie.endswith(".mp4"):
523 |                 File(str(movie)).move_to(movies_dir + "_retired")
524 |         try:
525 |             Gui.rename_files(output_dir, "")
526 |             Gui.rename_again(output_dir, "")
527 |             Gui.fix_titles(output_dir)
528 |         except Exception as e:
529 |             pass
530 |         
531 | 
532 |             
533 |     @staticmethod
534 |     def parse_narration_script(response):
535 |         narration_script = ""
536 | 
537 |         for value in response.values():
538 |             narration_script += str(value) + "\n"
539 |     
540 |     @staticmethod
541 |     def fix_titles(output):
542 |         outputted = os.listdir(output)
543 |         for finished in outputted:
544 |             new_finished = finished
545 |             if "-" in finished:
546 |                 new_finished = new_finished.replace("-", " ")
547 |             os.rename(f"{output}/{finished}", f"{output}/{new_finished}")
548 |                 
549 |     
550 |     
551 |     @staticmethod
552 |     def rename_again(directory, channel_name):
553 |         for file in os.listdir(directory):
554 |             if str(channel_name) not in file:
555 |                 os.rename(directory + "/" + str(file), directory + "/" + str(file) + "" + channel_name)
556 |     
557 |     @staticmethod
558 |     def delete_clips(output_dir):
559 |         for f in os.listdir(output_dir):
560 |             os.remove(os.path.join(output_dir, f))
561 |     
562 |     @staticmethod
563 |     def rename_files(directory, channel_name):
564 |         if len(os.listdir(directory)) > 0:
565 |             for filename in os.listdir(directory):
566 |                 if filename.endswith(".mp4") and channel_name not in filename:
567 |                     base = os.path.splitext(filename)[0]
568 |                     new_filename = f"{base}.mp4"
569 |                     if os.path.exists(os.path.join(directory, new_filename)):
570 |                         os.replace(os.path.join(directory, filename), os.path.join(directory, new_filename))
571 |                     else:
572 |                         os.rename(os.path.join(directory, filename), os.path.join(directory, new_filename))
573 |     
574 |     
575 |     @staticmethod
576 |     def remove_processed_movies():
577 |         source_directory = "movies"
578 |         target_directory = "retiredmovies"
579 | 
580 |         if not os.path.exists(target_directory):
581 |             os.makedirs(target_directory)
582 |                 
583 |         if not os.path.exists(source_directory):
584 |             os.makedirs(source_directory)
585 | 
586 |         # Loop through all the files in the source directory
587 |         for filename in os.listdir(source_directory):
588 |             if filename.endswith(".mp4"):
589 |                 source = os.path.join(source_directory, filename)
590 |                 destination = os.path.join(target_directory, filename)
591 |                 shutil.move(source, destination)
592 |     
593 |     @staticmethod
594 |     def split_video_importance(input_file, output_dir, response_list):
595 |         try:
596 |             # Load the video
597 |             video = VideoFileClip(input_file)
598 | 
599 |             if video.duration < 59 * 4:
600 |                 clips = [video]
601 |                 return clips
602 | 
603 |             # Make sure the output directory exists
604 |             os.makedirs(output_dir, exist_ok=True)
605 |             audio_output_dir = os.path.join(output_dir, "audio")
606 |             os.makedirs(audio_output_dir, exist_ok=True)
607 | 
608 |             clips = []
609 |             audio_clips = []
610 |             for i, time_range in enumerate(response_list.keys()):
611 |                 start_str, end_str = time_range.split('-')
612 |                 start = int(start_str)
613 |                 end = int(end_str)
614 | 
615 |                 if end > video.duration:
616 |                     end = video.duration
617 | 
618 |                 clip = video.subclip(start, end)
619 | 
620 |                 output_file = os.path.join(output_dir, f"clip_{i+1}.mp4")
621 |                 clip.write_videofile(output_file, codec='libx264')
622 | 
623 |                 clips.append(clip)
624 | 
625 |                 narration_script = response_list[time_range]
626 |                 elevenlabs.set_api_key(elevenlabs_api_key)
627 |                 audio = elevenlabs.generate(
628 |                     text=narration_script,
629 |                     voice="Liam",
630 |                     model="eleven_multilingual_v2"
631 |                 )
632 |                 audio_file_path = os.path.join(audio_output_dir, f"audio_{i+1}.mp3")
633 |                 elevenlabs.save(audio, audio_file_path)
634 | 
635 |                 audio_clip = AudioFileClip(audio_file_path)
636 | 
637 |                 audio_clips.append(audio_clip)
638 | 
639 |             return clips, audio_clips
640 | 
641 |         except Exception as e:
642 |             print(f"An error occurred: {e}")
643 |             return []
644 |     
645 |     @staticmethod
646 |     def split_video_randomly(input_file, output_dir):
647 |         try:
648 |             # Load the video
649 |             video = VideoFileClip(input_file)
650 | 
651 |             if video.duration < 59 * 4:
652 |                 clips = [video]
653 |                 return clips
654 | 
655 |             clip_duration = total_duration / num_clips
656 | 
657 |             os.makedirs(output_dir, exist_ok=True)
658 |             time_multiple = (video.duration - clip_duration) / (num_clips - 1)
659 | 
660 |             start_times = [int(i * time_multiple) for i in range(num_clips)]
661 |             
662 |             for time in start_times:
663 |                 if time > 40 and time + 10 < total_duration:
664 |                     start_times.remove(time)
665 |                     start_times.append(time+7)
666 |             start_times = sorted(start_times)
667 | 
668 |             clips = []
669 |             for i, start in enumerate(start_times):
670 |                 end = start + clip_duration
671 |                     
672 |                 clip = video.subclip(start, end)
673 |                 output_file = os.path.join(output_dir, f"clip_{i+1}.mp4")
674 |                 clip.write_videofile(output_file, codec='libx264')
675 |                 clips.append(clip)
676 | 
677 |             return clips
678 |         except Exception as e:
679 |             Gui.split_video_randomly(input_file, output_dir)
680 |     
681 |     
682 |     def upload_individual(self, movietitle):
683 |             # Set the command
684 |         command = f'python3 youtube_upload.py --file="output/{movietitle}.mp4" --title="{movietitle}" --description="Like, comment, and subscribe for more top tier movie content." --category="22" --privacyStatus="public"'
685 |         args = shlex.split(command)
686 |     
687 |         # Run the command
688 |         subprocess.run(args)
689 |         
690 |         self.uploading_label.config(text="Upload Attempt Complete")
691 | 
692 | 
693 |     
694 |     def upload_thread(self):
695 |         movies = os.listdir("output")
696 |         if len(movies) > 0:
697 |             for i in range(len(movies)):
698 |                 temp = movies[i]
699 |                 temp = temp[0:len(temp) - 4]
700 |                 Gui.upload_individual(self, str(temp))
701 |             if len(movies) == 1:
702 |                 self.uploading_label.config(text = "Upload Complete")
703 |             else:
704 |                 self.uploading_label.config(text = "Uploads Complete")
705 | 
706 |             
707 |         if open_api_key != "OPEN_AI_API_KEY HERE":
708 |             self.upload_button.config(state=tk.NORMAL)
709 |         self.uploading_label.config(text="Uploading...")
710 |         
711 |     def upload_to_youtube(self):
712 |         # Make uploading label visible
713 |         self.uploading_label.pack()
714 |         self.stop_uploading_button.pack(pady=12)
715 |         self.upload_button.config(state=tk.DISABLED)
716 |         self.start_button.config(state=tk.DISABLED)
717 | 
718 |         threading.Thread(target=self.upload_thread).start()
719 | 
720 |     def open_directory(self, path):
721 |         path = os.path.realpath(path)
722 |         os.startfile(path)
723 | 
724 |     @staticmethod            
725 |     def get_SRT_response(script, models="gpt-4o"):
726 |         try:                
727 |             openai.api_key = open_api_key
728 |                     
729 |             response = openai.ChatCompletion.create(
730 |             model=models,
731 |             messages=[
732 |                     {"role": "system", "content": "You are a movie expert."},
733 |                     {"role": "user", "content": script},
734 |                 ]
735 |             )
736 |                     
737 |             answer = response['choices'][0]['message']['content']
738 |             
739 |             return answer
740 |         except Exception as e:
741 |             time.sleep(37)
742 |             print("GPT Failed... retrying... " + str(e))
743 |             if "too large" in str(e):
744 |                 return "error", "error"
745 |             else:
746 |                 return Gui.get_SRT_response(script)
747 |         
748 | def start(self, movies_dir="movies", output_dir="output"):
749 |     self.refresh()
750 |     threading.Thread(target=self.start_process, args=(movies_dir, output_dir)).start()
751 |     self.progress_label.pack()
752 | 
753 | def delete_files(starting_directory, file_name):
754 |     for root, dirs, files in os.walk(starting_directory):
755 |         for file in files:
756 |             if file == file_name:
757 |                 file_path = os.path.join(root, file)
758 |                 os.remove(file_path)
759 |                 print(f"Deleted file: {file_path}")
760 |                 
761 | def create_folders(folders):
762 |     for folder in folders:
763 |         if not os.path.isdir(folder):
764 |             os.makedirs(folder)
765 | 
766 |             
767 | 
768 | if __name__ == "__main__":
769 |     
770 |     total_duration = random.randint(MIN_TOTAL_DURATION, MAX_TOTAL_DURATION)
771 |     num_clips = random.randint(MIN_NUM_CLIPS, MAX_NUM_CLIPS)
772 | 
773 |     
774 |     folders = ["clips", "movies", "output", "backgroundmusic", "scripts", "output_audio", os.path.join("scripts", "audio_extractions"), os.path.join("scripts", "parsed_scripts")]
775 |     create_folders(folders)
776 |     
777 |     for file in os.listdir("movies"):
778 |         if "-" in file:
779 |             temp = file.replace("-", "")
780 |             os.rename(f"movies/{file}", f"movies/{temp}")
781 | 
782 |     clips_dir = os.listdir("clips")
783 |     for clip in clips_dir:
784 |         if os.path.isfile(clip):
785 |             os.remove("clips/" + clip)
786 |             
787 |     audio_clips_dir_temp = os.listdir("clips/audio")
788 |     for audio in audio_clips_dir_temp:
789 |         if os.path.isfile(audio):
790 |             os.remove("clips/audio/" + audio)
791 |         
792 |     out_aud = os.listdir("output_audio")
793 |     for audio in out_aud:
794 |         os.remove("output_audio/" + audio)
795 |         
796 |     main_directory = ""
797 |     file_name = ".placeholder"
798 |     delete_files(main_directory, file_name)
799 |     
800 |         
801 |     root = tk.Tk()
802 |     GUI = Gui(root)
803 |     root.mainloop()


--------------------------------------------------------------------------------
/make_mp3_same_volume.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
 3 | from ffmpeg import *
 4 | import ffmpeg
 5 | from pydub import AudioSegment
 6 | 
 7 | def adjust_volume(directory):
 8 |     max_dbfs = -float('inf')
 9 |     files_dbfs = {}
10 | 
11 |     # Iterate over all .mp3 files in the specified directory
12 |     for filename in os.listdir(directory):
13 |         if filename.endswith('.mp3'):
14 |             # Load audio file
15 |             audio = AudioSegment.from_mp3(os.path.join(directory, filename))
16 | 
17 |             # Calculate loudness of the file
18 |             dbfs = audio.dBFS
19 | 
20 |             # Store the dBFS value and audio segment for later processing
21 |             files_dbfs[filename] = (dbfs, audio)
22 | 
23 |             # Update max_dbfs if this file is louder
24 |             if dbfs > max_dbfs:
25 |                 max_dbfs = dbfs
26 | 
27 |     # Adjust volume of all files to the maximum dBFS value
28 |     for filename, (dbfs, audio) in files_dbfs.items():
29 |         if dbfs < max_dbfs:
30 |             # Calculate the difference in dBFS from the loudest file
31 |             dbfs_diff = max_dbfs - dbfs
32 |             
33 |             if dbfs_diff == 0:
34 |                 continue
35 | 
36 |             # Increase the volume
37 |             louder_audio = audio.apply_gain(dbfs_diff)
38 | 
39 |             # Export the result
40 |             louder_audio.export(os.path.join(directory, filename), format="mp3")
41 | 
42 | 
43 | 


--------------------------------------------------------------------------------
/movies/.placeholder:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/output/.placeholder:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/output_audio/.placeholder:
--------------------------------------------------------------------------------
1 | 
2 | 


--------------------------------------------------------------------------------
/rename_files.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import sys
 3 | import re
 4 | 
 5 | def rename_again(directory, channel_name):
 6 |     for file in os.listdir(directory):
 7 |         if str(channel_name) not in file:
 8 |             os.rename(directory + "/" + str(file), directory + "/" + str(file) + " — " + channel_name)        
 9 | 
10 | 
11 | def rename_files(directory, channel_name):
12 |     if len(os.listdir(directory)) > 0:
13 |         for filename in os.listdir(directory):
14 |             if filename.endswith(".mp4") and channel_name not in filename:
15 |                 base = os.path.splitext(filename)[0]
16 |                 new_filename = f"{base} — {channel_name}.mp4"
17 |                 if os.path.exists(os.path.join(directory, new_filename)):
18 |                     os.replace(os.path.join(directory, filename), os.path.join(directory, new_filename))
19 |                 else:
20 |                     os.rename(os.path.join(directory, filename), os.path.join(directory, new_filename))
21 | 
22 | 
23 | def add_space():
24 |     strings = os.listdir("output")
25 |     # For each string in the list
26 |     for i in range(len(strings)):
27 |         filename, file_extension = os.path.splitext(strings[i])
28 |         title, _, channel = filename.partition(" — ")
29 |         # If the string has an uppercase letter immediately following a lowercase letter or a digit
30 |         if re.search(r'([a-z]([A-Z0-9])|[A-Z]{2,})', title):
31 |             # Add a space between the lowercase and uppercase letter or digit
32 |             new_title = re.sub(r'([a-z])([A-Z0-9])', r'\1 \2', title)
33 |             # Add a space between consecutive uppercase letters
34 |             new_title = re.sub(r'([A-Z])([A-Z])', r'\1 \2', new_title)
35 |             new_filename = new_title + " — " + channel
36 |             # Append the file extension back onto the string
37 |             new_filename_with_ext = new_filename + file_extension
38 |             os.rename(f"output/{strings[i]}", f"output/{new_filename_with_ext}")
39 | 
40 | 
41 | 
42 | 
43 | 
44 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
 1 | moviepy
 2 | ffmpeg-python
 3 | subliminal
 4 | babelfish
 5 | pyttsx3
 6 | mutagen
 7 | beautifulsoup4
 8 | requests
 9 | speechrecognition
10 | pydub
11 | youtube-upload
12 | openai
13 | elevenlabs
14 | PyBetterFileIO
15 | pydub
16 | moviepy
17 | speechrecognition
18 | httplib2


--------------------------------------------------------------------------------
/scripts/scrape_script.py:
--------------------------------------------------------------------------------
 1 | import requests
 2 | from bs4 import BeautifulSoup
 3 | import os
 4 | import argparse
 5 | 
 6 | def get_movie_script(title):
 7 |     # Format the title to create the URL
 8 |     formatted_title = title.replace(' ', '-')
 9 |     url = f"https://imsdb.com/scripts/{formatted_title}.html"
10 | 
11 |     # Send a GET request to the URL
12 |     response = requests.get(url)
13 | 
14 |     if response.status_code != 200:
15 |         print(f"Failed to retrieve the script. Status code: {response.status_code}")
16 |         formatted_title = formatted_title.replace("(", "").replace(")", "")
17 |         url = f"https://imsdb.com/scripts/{formatted_title}.html"
18 |         response = requests.get(url)
19 |         if response.status_code != 200:
20 |             return None
21 | 
22 |     # Parse the HTML content
23 |     soup = BeautifulSoup(response.content, 'html.parser')
24 |     
25 |     # Find the script content
26 |     script_content = soup.find('pre')
27 |     
28 |     if script_content:
29 |         # Extract the text from the 
 tag
30 |         script_text = script_content.get_text(separator='\n')
31 |         return script_text
32 |     else:
33 |         print("Script content not found.")
34 |         return None
35 | 
36 | def save_script(script, path):
37 |     # Create the directory if it doesn't exist
38 |     os.makedirs(os.path.dirname(path), exist_ok=True)
39 |     
40 |     # Save the script to the specified location
41 |     with open(path, "w", encoding="utf-8") as file:
42 |         file.write(script)
43 |     
44 |     print(f"Script saved to {path}")
45 | 
46 | if __name__ == "__main__":
47 |     parser = argparse.ArgumentParser(description="Scrape movie script from IMSDb")
48 |     parser.add_argument("script_path", help="Path to save the script")
49 |     parser.add_argument("movie_title", help="Title of the movie")
50 |      
51 |     args = parser.parse_args()
52 |     
53 |     script = get_movie_script(args.movie_title)
54 |     
55 |     if script:
56 |         save_script(script.replace("\t", "").replace("  ", ""), args.script_path)
57 |     else:
58 |         print("No script found or there was an error retrieving the script.")
59 | 


--------------------------------------------------------------------------------
/scripts/scrape_subtitles.py:
--------------------------------------------------------------------------------
 1 | import requests
 2 | from bs4 import BeautifulSoup
 3 | import zipfile
 4 | import io
 5 | import sys
 6 | import os
 7 | 
 8 | def parse_movie_title(movie_title):
 9 |     parsed_movie_title = movie_title.replace("'", '').replace(' ', '-').replace(' ', '-').replace('(', '').replace(')', '').lower()
10 |     if parsed_movie_title.endswith("ii"):
11 |         parsed_movie_title += "-2"
12 |     if parsed_movie_title.endswith("iii"):
13 |         parsed_movie_title += "-3"
14 |     if parsed_movie_title.endswith("iv"):
15 |         parsed_movie_title += "-4"
16 |     return parsed_movie_title
17 | 
18 | def download_subtitle(parsed_movie_title, movie_title):
19 |     url = f"https://subf2m.co/subtitles/{parsed_movie_title}/english"
20 |     print(f"Accessing URL: {url}")
21 |     
22 |     response = requests.get(url)
23 |     if response.status_code != 200:
24 |         print(f"Failed to access URL: {url}, Status code: {response.status_code}")
25 |         end_index = parsed_movie_title.rfind('-')
26 |         if "ii" in parsed_movie_title[end_index:]:
27 |             ending = url[end_index+1:]  # Extract the part after the last '-'
28 |             parsed_movie_title = parsed_movie_title.replace(ending, "ii-2")
29 |             url = f"https://subf2m.co/subtitles/{parsed_movie_title}/english"
30 |             response = requests.get(url)
31 |             if response.status_code != 200:
32 |                 return
33 |         else:
34 |             return
35 | 
36 |     soup = BeautifulSoup(response.text, 'html.parser')
37 |     first_subtitle_link = None
38 | 
39 |     for link in soup.find_all('a', href=True):
40 |         if link['href'].startswith(f"/subtitles/{parsed_movie_title}/english") and link['href'] != f"/subtitles/{parsed_movie_title}/english" and link['href'] != f"/subtitles/{parsed_movie_title}/english-german":
41 |             first_subtitle_link = f"https://subf2m.co{link['href']}"
42 |             break
43 | 
44 |     if not first_subtitle_link:
45 |         print(f"No subtitle link found for {parsed_movie_title}")
46 |         return
47 | 
48 |     print(f"Accessing subtitle page: {first_subtitle_link}")
49 |     subtitle_page_response = requests.get(first_subtitle_link)
50 |     if subtitle_page_response.status_code != 200:
51 |         print(f"Failed to access subtitle page: {first_subtitle_link}, Status code: {subtitle_page_response.status_code}")
52 |         return
53 | 
54 |     subtitle_soup = BeautifulSoup(subtitle_page_response.text, 'html.parser')
55 |     download_link = None
56 | 
57 |     for link in subtitle_soup.find_all('a', href=True):
58 |         if link['href'].endswith("download"):
59 |             download_link = f"https://subf2m.co{link['href']}"
60 |             break
61 | 
62 |     if not download_link:
63 |         print("No download link found")
64 |         return
65 | 
66 |     print(f"Downloading from: {download_link}")
67 |     zip_response = requests.get(download_link)
68 |     if zip_response.status_code != 200:
69 |         print(f"Failed to download zip file from: {download_link}, Status code: {zip_response.status_code}")
70 |         return
71 | 
72 |     with zipfile.ZipFile(io.BytesIO(zip_response.content)) as z:
73 |         srt_files = [f for f in z.namelist() if f.endswith('.srt') or f.endswith('.SRT')]
74 |         if len(srt_files) != 1:
75 |             print("Error: Multiple or no SRT files found in the zip")
76 |             return
77 | 
78 |         srt_filename = srt_files[0]
79 |         with z.open(srt_filename) as srt_file:
80 |             with open(f"scripts/srt_files/{movie_title}.srt", "wb") as out_file:
81 |                 out_file.write(srt_file.read())
82 |         print(f"Subtitle downloaded and saved as {movie_title}.srt")
83 | 
84 | if len(sys.argv) > 1:
85 |     movie_title = sys.argv[1]
86 | else:
87 |     print("Pass a movie title as an argument")
88 |     os._exit(0)
89 | 
90 | parsed_movie_title = parse_movie_title(movie_title)
91 | download_subtitle(parsed_movie_title, movie_title)
92 | 


--------------------------------------------------------------------------------
/tiktok_resize.py:
--------------------------------------------------------------------------------
 1 | from moviepy.editor import *
 2 | import os
 3 | 
 4 | def tiktok_version(video_path, output_path):
 5 |     # Load the video
 6 |     clip = VideoFileClip(video_path)
 7 |         
 8 |     # Calculate the crop dimensions (20% off the left and right)
 9 |     original_width, original_height = clip.size
10 |     crop_width = int(original_width * 0.6)  # Keep 60% of the width
11 |     crop_x1 = int(original_width * 0.2)  # Crop 20% from the left
12 |     crop_x2 = crop_x1 + crop_width  # Set the right boundary of the crop
13 | 
14 |     # Crop the video
15 |     cropped_clip = clip.crop(x1=crop_x1, x2=crop_x2)
16 |         
17 |     # Calculate the new dimensions for the vertical video
18 |     new_height = original_height
19 |     new_width = int(new_height * (9 / 16))
20 |         
21 |     # Resize the cropped video to fit 60% of the vertical real estate, stretched by 20%
22 |     target_height = int(new_height * 0.6 * 1.2)  # Stretch by 20%
23 |     resized_clip = cropped_clip.resize(height=target_height)
24 |         
25 |     # Create a black background with the same size as the original using a black background video
26 |     background = VideoFileClip("black_background.mp4", has_mask=True).set_duration(clip.duration).resize((new_width, new_height))
27 |         
28 |     # Position the resized video in the center of the background
29 |     final_clip = CompositeVideoClip([background, resized_clip.set_position(('center', 'center'))])
30 |         
31 |     # Save the edited video
32 |     final_clip.write_videofile(output_path, codec='libx264')
33 |     
34 | tiktok_version("output/The Truman Show.mp4", "tiktok_output/The Truman Show_vertical.mp4")


--------------------------------------------------------------------------------
/timestamp_assignments.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | from pydub import AudioSegment
 3 | from moviepy.editor import VideoFileClip
 4 | import speech_recognition as sr
 5 | 
 6 | class Timestamps:
 7 | 
 8 |     @staticmethod
 9 |     def extract_audio(movie_path, audio_output_path):
10 |         clip = VideoFileClip(movie_path)
11 |         clip.audio.write_audiofile(audio_output_path)
12 |     
13 |     @staticmethod
14 |     def convert_audio_to_wav(mp3_path, wav_path):
15 |         audio = AudioSegment.from_file(mp3_path, format="mp3")
16 |         audio.export(wav_path, format="wav")
17 |         os.remove(mp3_path)
18 | 
19 |     @staticmethod
20 |     def transcribe_audio(audio_path):
21 |         print("Starting audio transcription")
22 |         recognizer = sr.Recognizer()
23 |         audio_file = sr.AudioFile(audio_path)
24 |         transcript = []
25 |         timestamps = []
26 | 
27 |         with audio_file as source:
28 |             duration = source.DURATION
29 | 
30 |         interval = 30  # Segment duration in seconds
31 |         overlap = 5    # Overlapping duration in seconds
32 |         current_offset = 0
33 | 
34 |         while current_offset < duration:
35 |             with audio_file as source:
36 |                 audio = recognizer.record(source, offset=current_offset, duration=interval)
37 |                 try:
38 |                     text = recognizer.recognize_google(audio)
39 |                     #if text.strip() != "" and text.strip() != "\n":
40 |                     transcript.append(text)
41 |                     timestamps.append(current_offset)
42 |                     print(text)
43 |                     print(f'{current_offset}/{duration}')
44 |                 except sr.UnknownValueError:
45 |                     continue
46 |             current_offset += (interval - overlap)
47 | 
48 |         print("Finished audio transcription")
49 |         return transcript, timestamps
50 | 
51 |     @staticmethod
52 |     def assign_timestamps_to_transcript(transcript, timestamps):
53 |         output_lines = []
54 |         transcript_index = 0
55 | 
56 |         for i, line in enumerate(transcript):
57 |             if (i + 1) % 5 == 0 and transcript_index < len(transcript):
58 |                 timestamp = timestamps[transcript_index]
59 |                 output_lines.append(f"{line} [{timestamp // 60}:{timestamp % 60}]\n")
60 |                 transcript_index += 1
61 |             else:
62 |                 output_lines.append(f"{line}\n")
63 | 
64 |         print("Finished assigning timestamps")
65 |         return output_lines
66 | 
67 |     @staticmethod
68 |     def save_output_script(output_path, output_lines):
69 |         with open(output_path, 'w') as file:
70 |             file.writelines(output_lines)
71 |             
72 |     @staticmethod
73 |     def main(movie_path, audio_output_path, wav_output_path, output_path):
74 |         # Extract audio from movie
75 |         Timestamps.extract_audio(movie_path, audio_output_path)
76 | 
77 |         # Convert MP3 to WAV
78 |         Timestamps.convert_audio_to_wav(audio_output_path, wav_output_path)
79 | 
80 |         # Transcribe audio to text
81 |         transcript, timestamps = Timestamps.transcribe_audio(wav_output_path)
82 | 
83 |         # Assign timestamps to every 5th line of the transcript
84 |         output_lines = Timestamps.assign_timestamps_to_transcript(transcript, timestamps)
85 | 
86 |         # Save the output script
87 |         Timestamps.save_output_script(output_path, output_lines)
88 |         os.remove(wav_output_path)
89 |         print("Transcription Process Complete.")
90 | 
91 | if __name__ == "__main__":
92 |     movie_title = "American Psycho"
93 |     movie_path = f'movies/{movie_title}.mp4'
94 |     audio_output_path = f'scripts/audio_extractions/{movie_title}.mp3'
95 |     wav_output_path = f'scripts/audio_extractions/{movie_title}.wav'
96 |     output_path = f'scripts/parsed_scripts/{movie_title} Script.txt'
97 |     Timestamps.main(movie_path, audio_output_path, wav_output_path, output_path)
98 | 


--------------------------------------------------------------------------------
/upload_action.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import subprocess
 3 | import shlex
 4 | 
 5 | def upload_to_youtube(movietitle):
 6 |     # Set the command
 7 |     command = f'python3 youtube_upload.py --file="output/{movietitle}.mp4" --title="{movietitle}" --description="Like, comment, and subscribe for more top tier movie content." --category="22" --privacyStatus="public"'
 8 |     
 9 |     # Use shlex to split the command string into a list
10 |     args = shlex.split(command)
11 |     
12 |     # Run the command
13 |     subprocess.run(args)
14 |     
15 | 
16 | # Use the function
17 | movies = os.listdir("output")
18 | for movie in movies:
19 |     temp = movie
20 |     temp = temp[0:len(temp)-4]
21 |     upload_to_youtube(str(temp))
22 | 


--------------------------------------------------------------------------------
/upload_tiktok.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | from tiktok_uploader.src.tiktok_uploader.upload import upload_video, upload_videos
 3 | from tiktok_uploader.src.tiktok_uploader.auth import AuthBackend
 4 | from selenium.webdriver.chrome.options import Options
 5 | import time
 6 | 
 7 | directory = "tiktok_output"
 8 | 
 9 | options = Options()
10 | options.add_argument('start-maximized')
11 | 
12 | auth = AuthBackend(cookies="cookies.txt")
13 | 
14 | videos_upload = []
15 | 
16 | while True:
17 | 
18 |     if len(os.listdir(directory)) in [0]:
19 |         print("Waiting for TikTok videos...")
20 |         time.sleep(5)
21 |         continue
22 |     
23 |     success = False
24 |     for video in os.listdir("tiktok_output"):
25 |         if video.endswith(".mp4"):
26 |             if video.endswith("_vertical.mp4"):
27 |                 vid_replace = video.replace("_vertical", "")
28 |                 while not success:
29 |                     try:
30 |                         os.rename("tiktok_output/" + video, "tiktok_output/" + vid_replace)
31 |                         success = True
32 |                     except Exception as e:
33 |                         print("Waiting for Movie Summary Bot to finish.")
34 |                         time.sleep(10)
35 |     
36 |     videos = os.listdir(directory)
37 | 
38 |     for video in videos:
39 |         title = video[0:len(video)-4]
40 |         description = f"{title}. Like, comment, and follow for more top tier movie content. #fyp #film #movies #tiktok #asian #jynxzi #summaries #movieshort #xiaoling"
41 |     
42 |         videos_upload.append({'video': f"{directory}/{title}.mp4", 'description': description})
43 |         
44 |     failed_videos = upload_videos(videos=videos_upload, auth=auth, headless=False)
45 |     
46 |     failed_vids = [video['video'] for video in failed_videos]
47 | 
48 |     for upload in videos_upload:
49 |         
50 |         print("upload['video']", upload['video'])
51 |         if upload['video'] not in failed_vids:
52 |             if os.listdir(directory) != videos:
53 |                 
54 |                 set_a = set(os.listdir(directory))
55 |                 set_b = set(videos)
56 |                 
57 |                 not_in_b = set_a.difference(set_b)
58 |                 odd_one_out = list(not_in_b)
59 |                 if upload['video'] != odd_one_out:
60 |                     os.remove(upload['video'])
61 |             else:
62 |                 os.remove(upload['video'])
63 | 
64 |     videos_upload.clear()
65 | 


--------------------------------------------------------------------------------
/youtube_upload.py:
--------------------------------------------------------------------------------
  1 | import httplib2
  2 | import os
  3 | import random
  4 | import sys
  5 | import time
  6 | 
  7 | from googleapiclient.discovery import build
  8 | from googleapiclient.errors import HttpError
  9 | from googleapiclient.http import MediaFileUpload
 10 | from oauth2client.client import flow_from_clientsecrets
 11 | from oauth2client.file import Storage
 12 | from oauth2client.tools import argparser, run_flow
 13 | 
 14 | # Explicitly tell the underlying HTTP transport library not to retry, since
 15 | # we are handling retry logic ourselves.
 16 | httplib2.RETRIES = 1
 17 | 
 18 | # Maximum number of times to retry before giving up.
 19 | MAX_RETRIES = 10
 20 | 
 21 | # Always retry when these exceptions are raised.
 22 | RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError)
 23 | 
 24 | # Always retry when an apiclient.errors.HttpError with one of these status
 25 | # codes is raised.
 26 | RETRIABLE_STATUS_CODES = [500, 502, 503, 504]
 27 | 
 28 | # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
 29 | # the OAuth 2.0 information for this application, including its client_id and
 30 | # client_secret. You can acquire an OAuth 2.0 client ID and client secret from
 31 | # the Google API Console at
 32 | # https://console.cloud.google.com/.
 33 | # Please ensure that you have enabled the YouTube Data API for your project.
 34 | # For more information about using OAuth2 to access the YouTube Data API, see:
 35 | #   https://developers.google.com/youtube/v3/guides/authentication
 36 | # For more information about the client_secrets.json file format, see:
 37 | #   https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
 38 | CLIENT_SECRETS_FILE = "client_secrets.json"
 39 | 
 40 | # This OAuth 2.0 access scope allows an application to upload files to the
 41 | # authenticated user's YouTube channel, but doesn't allow other types of access.
 42 | YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload"
 43 | YOUTUBE_API_SERVICE_NAME = "youtube"
 44 | YOUTUBE_API_VERSION = "v3"
 45 | 
 46 | # This variable defines a message to display if the CLIENT_SECRETS_FILE is
 47 | # missing.
 48 | MISSING_CLIENT_SECRETS_MESSAGE = """
 49 | WARNING: Please configure OAuth 2.0
 50 | 
 51 | To make this sample run you will need to populate the client_secrets.json file
 52 | found at:
 53 | 
 54 |    %s
 55 | 
 56 | with information from the API Console
 57 | https://console.cloud.google.com/
 58 | 
 59 | For more information about the client_secrets.json file format, please visit:
 60 | https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
 61 | """ % os.path.abspath(os.path.join(os.path.dirname(__file__),
 62 |                                    CLIENT_SECRETS_FILE))
 63 | 
 64 | VALID_PRIVACY_STATUSES = ("public", "private", "unlisted")
 65 | 
 66 | 
 67 | def get_authenticated_service(args):
 68 |   flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
 69 |     scope=YOUTUBE_UPLOAD_SCOPE,
 70 |     message=MISSING_CLIENT_SECRETS_MESSAGE)
 71 | 
 72 |   storage = Storage("%s-oauth2.json" % sys.argv[0])
 73 |   credentials = storage.get()
 74 | 
 75 |   if credentials is None or credentials.invalid:
 76 |     credentials = run_flow(flow, storage, args)
 77 | 
 78 |   return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
 79 |     http=credentials.authorize(httplib2.Http()))
 80 | 
 81 | def initialize_upload(youtube, options):
 82 |   tags = None
 83 |   if options.keywords:
 84 |     tags = options.keywords.split(",")
 85 | 
 86 |   body=dict(
 87 |     snippet=dict(
 88 |       title=options.title,
 89 |       description=options.description,
 90 |       tags=tags,
 91 |       categoryId=options.category
 92 |     ),
 93 |     status=dict(
 94 |       privacyStatus=options.privacyStatus
 95 |     )
 96 |   )
 97 | 
 98 |   # Call the API's videos.insert method to create and upload the video.
 99 |   insert_request = youtube.videos().insert(
100 |     part=",".join(body.keys()),
101 |     body=body,
102 |     # The chunksize parameter specifies the size of each chunk of data, in
103 |     # bytes, that will be uploaded at a time. Set a higher value for
104 |     # reliable connections as fewer chunks lead to faster uploads. Set a lower
105 |     # value for better recovery on less reliable connections.
106 |     #
107 |     # Setting "chunksize" equal to -1 in the code below means that the entire
108 |     # file will be uploaded in a single HTTP request. (If the upload fails,
109 |     # it will still be retried where it left off.) This is usually a best
110 |     # practice, but if you're using Python older than 2.6 or if you're
111 |     # running on App Engine, you should set the chunksize to something like
112 |     # 1024 * 1024 (1 megabyte).
113 |     media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True)
114 |   )
115 | 
116 |   resumable_upload(insert_request)
117 | 
118 | # This method implements an exponential backoff strategy to resume a
119 | # failed upload.
120 | def resumable_upload(insert_request):
121 |   response = None
122 |   error = None
123 |   retry = 0
124 |   while response is None:
125 |     try:
126 |       print("Uploading file...")
127 |       status, response = insert_request.next_chunk()
128 |       if response is not None:
129 |         if 'id' in response:
130 |           print("Video was successfully uploaded.")
131 |         else:
132 |           exit("The upload failed with an unexpected response")
133 |     except HttpError as e:
134 |       if e.resp.status in RETRIABLE_STATUS_CODES:
135 |         error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status,
136 |                                                              e.content)
137 |       else:
138 |         raise
139 |     except RETRIABLE_EXCEPTIONS as e:
140 |       error = "A retriable error occurred"
141 | 
142 |     if error is not None:
143 |       print(error)
144 |       retry += 1
145 |       if retry > MAX_RETRIES:
146 |         exit("No longer attempting to retry.")
147 | 
148 |       max_sleep = 2 ** retry
149 |       sleep_seconds = random.random() * max_sleep
150 |       print("Sleeping _ seconds and then retrying...")
151 |       time.sleep(sleep_seconds)
152 | 
153 | if __name__ == '__main__':
154 |   argparser.add_argument("--file", required=True, help="Video file to upload")
155 |   argparser.add_argument("--title", help="Video title", default="Test Title")
156 |   argparser.add_argument("--description", help="Video description",
157 |     default="Test Description")
158 |   argparser.add_argument("--category", default="22",
159 |     help="Numeric video category. " +
160 |       "See https://developers.google.com/youtube/v3/docs/videoCategories/list")
161 |   argparser.add_argument("--keywords", help="Video keywords, comma separated",
162 |     default="")
163 |   argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES,
164 |     default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.")
165 |   args = argparser.parse_args()
166 | 
167 |   if not os.path.exists(args.file):
168 |     exit("Please specify a valid file using the --file= parameter.")
169 | 
170 |   youtube = get_authenticated_service(args)
171 |   try:
172 |     initialize_upload(youtube, args)
173 |   except HttpError as e:
174 |     print("An HTTP error occurred:")


--------------------------------------------------------------------------------