How Video Upload to YouTube Works Using MoviePy in MoneyPrinterV2

MoneyPrinterV2 automates YouTube Shorts creation and upload by using MoviePy to compile AI-generated images and TTS audio into an MP4, then employs Selenium to drive a Firefox browser for authenticated upload to YouTube Studio.

MoneyPrinterV2 is an open-source automation tool that generates monetizable YouTube Shorts from scratch. Understanding how video upload to YouTube works using MoviePy in MoneyPrinterV2 requires examining the two-phase pipeline implemented in the YouTube class, which handles everything from asset generation to browser automation.

Architecture Overview

The upload workflow is split between content generation (handled by MoviePy) and distribution (handled by Selenium WebDriver). This separation allows the system to create video assets offline before automating the browser-based upload process.

The orchestration happens entirely within src/classes/YouTube.py, which contains three critical methods:

  • generate_video() – coordinates the full creation pipeline
  • combine() – uses MoviePy to stitch assets together
  • upload_video() – automates YouTube Studio via Selenium

Phase 1: Video Generation with MoviePy

Asset Preparation

Before video composition begins, generate_video() (lines 60-80 in src/classes/YouTube.py) executes a sequential preparation pipeline:

  1. Topic generationgenerate_topic() creates a niche-specific subject
  2. Script writinggenerate_script() produces the narration text
  3. Metadata creationgenerate_metadata() crafts titles and descriptions
  4. Image promptsgenerate_prompts() creates AI image generation instructions
  5. Asset generationgenerate_image() downloads AI-generated images for each prompt
  6. Audio synthesisgenerate_script_to_speech() converts the script to a TTS .wav file

Only after all assets exist on disk does the process invoke self.combine() to create the final MP4.

The combine() Method

The combine() method (lines 52-46 in src/classes/YouTube.py) is the core MoviePy implementation. It performs the following composition steps:

Image Clip Creation


# Pseudocode representing the MoviePy implementation

clips = []
for img_path in self.image_paths:
    clip = ImageClip(img_path)
    clip = clip.resize(height=1920)  # Force vertical format

    clip = clip.crop(x_center=clip.w/2, y_center=clip.h/2, 
                     width=1080, height=1920)
    clips.append(clip)

Duration Synchronization

  • Calculates the total duration from the TTS audio file
  • Distributes each image clip equally across the audio duration
  • Concatenates clips into a single video sequence

Audio Mixing

  • Loads the TTS audio as the primary narration track
  • Optionally mixes in a random background song from the configured music directory
  • Sets the final audio codec to AAC with a 192 kbps bitrate

Final Render

final_clip.write_videofile(
    output_path,
    fps=24,
    audio_codec='aac',
    audio_bitrate='192k',
    preset='medium'
)

The method returns the absolute path to the generated MP4, which is stored in self.video_path for the subsequent upload phase.

Phase 2: Automated YouTube Upload with Selenium

Once the MP4 file exists, upload_video() (lines 703-750 in src/classes/YouTube.py) handles the browser automation. Unlike the MoviePy phase, this requires an active Firefox profile with YouTube authentication cookies.

Browser Initialization

The method initializes a Firefox WebDriver instance using the profile path provided during YouTube class instantiation (fp_profile_path). This profile must contain valid YouTube/Google authentication sessions to bypass login flows.

Upload Workflow

The Selenium automation follows a precise sequence of DOM interactions:

1. File Selection

driver.get("https://www.youtube.com/upload")
file_input = driver.find_element(By.TAG_NAME, "ytcp-uploads-file-picker")\
                .find_element(By.TAG_NAME, "input")
file_input.send_keys(self.video_path)

2. Metadata Entry

  • Waits for the upload dialog to initialize
  • Locates title and description text boxes using YOUTUBE_TEXTBOX_ID
  • Injects the metadata generated earlier:
    title_el.send_keys(self.metadata["title"])
    description_el.send_keys(self.metadata["description"])

3. Compliance Flags

  • Checks configuration for "made for kids" settings
  • Clicks the appropriate radio button using YOUTUBE_MADE_FOR_KIDS_NAME or YOUTUBE_NOT_MADE_FOR_KIDS_NAME

4. Navigation Steps

  • Clicks the "Next" button (ID: YOUTUBE_NEXT_BUTTON_ID) three times to progress through "Details," "Video elements," and "Checks" screens
  • On the "Visibility" screen, selects the "Unlisted" option using YOUTUBE_RADIO_BUTTON_XPATH (index 2)
  • Clicks the "Done" button (ID: YOUTUBE_DONE_BUTTON_ID) to finalize

5. URL Extraction After upload completion, the method navigates to the YouTube Studio shorts listing page, extracts the video ID from the first row, and constructs the public URL:

driver.get(f"https://studio.youtube.com/channel/{self.channel_id}/videos/short")
video_row = driver.find_elements(By.TAG_NAME, "ytcp-video-row")[0]
video_id = video_row.find_element(By.TAG_NAME, "a").get_attribute("href").split("/")[-2]
self.uploaded_video_url = build_url(video_id)

The method returns True on successful completion and caches the video record locally. On any exception, it quits the driver and returns False.

Implementation Examples

Generating a Short with MoviePy

from src.classes.YouTube import YouTube
from src.classes.Tts import TTS

# Initialize the channel instance

yt = YouTube(
    account_uuid="1234-abcd",
    account_nickname="TechChannel",
    fp_profile_path="/home/user/.mozilla/firefox/profile",
    niche="artificial intelligence",
    language="en",
)

# Generate the video asset

tts = TTS()
video_path = yt.generate_video(tts)
print(f"Video created at: {video_path}")

Uploading to YouTube Studio


# Ensure Firefox profile is authenticated before running

success = yt.upload_video()

if success:
    print(f"Upload complete: {yt.uploaded_video_url}")
else:
    print("Upload failed - check browser automation logs")

End-to-End Automation


# src/main.py workflow

if __name__ == "__main__":
    # Configuration loading omitted for brevity

    yt = YouTube(account_uuid, nickname, profile_path, niche, language)
    
    # Phase 1: MoviePy generation

    yt.generate_video(TTS())
    
    # Phase 2: Selenium upload

    if yt.upload_video():
        print("Pipeline completed successfully")

Summary

  • MoviePy handles composition: The combine() method in src/classes/YouTube.py stitches AI-generated images, TTS audio, and background music into a 1080×1920 vertical MP4 using ImageClip and write_videofile.

  • Selenium drives distribution: The upload_video() method automates Firefox to navigate YouTube Studio, inject metadata, handle compliance flags, and publish the video as Unlisted, extracting the final public URL from the studio interface.

  • Pipeline isolation: Generation and upload are decoupled—generate_video() produces the file asset while upload_video() consumes it, allowing independent testing of content creation versus browser automation.

  • Authentication dependency: The upload phase requires a pre-authenticated Firefox profile (fp_profile_path) as the implementation relies on active cookies rather than API credentials.

Frequently Asked Questions

How does MoneyPrinterV2 create the video file before uploading to YouTube?

MoneyPrinterV2 uses the MoviePy library within the combine() method to generate the MP4. It creates ImageClip objects for each AI-generated image, resizes them to 1080×1920 vertical format, synchronizes their total duration to match the TTS audio length, overlays optional subtitles, mixes in background music, and renders the final output using write_videofile() with AAC audio encoding at 192 kbps.

Why does the upload process use Selenium instead of the YouTube Data API?

The implementation uses Selenium WebDriver to automate the YouTube Studio web interface rather than the Data API because it allows the application to bypass API quota limits and complex OAuth consent screens typical of automated content farms. The upload_video() method drives a Firefox browser instance using a pre-authenticated profile, enabling direct interaction with YouTube's upload DOM elements, metadata fields, and visibility settings exactly as a human user would.

What prerequisites are required for the YouTube upload to succeed?

Successful upload requires three specific prerequisites: (1) a valid Firefox profile path (fp_profile_path) containing active YouTube/Google authentication cookies passed during YouTube class instantiation; (2) the generated MP4 file must exist at self.video_path (typically created by generate_video()); and (3) the Firefox WebDriver must be installed and accessible in the system PATH. Without the authenticated profile, the Selenium automation cannot access the YouTube Studio upload interface.

Can I upload videos without generating them through MoviePy first?

Yes, the upload_video() method operates independently of the MoviePy generation pipeline. While the standard workflow calls generate_video() to populate self.video_path, you can manually set yt.video_path = "/path/to/existing.mp4" before invoking upload_video(). This decoupled architecture allows users to upload externally created content while still leveraging MoneyPrinterV2's Selenium automation for metadata injection and YouTube Studio navigation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →