├── .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 |
50 |
51 |
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 firsttag 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 thetag 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:") --------------------------------------------------------------------------------