# How Video Upload to YouTube Works Using MoviePy in MoneyPrinterV2

> Automate YouTube Shorts uploads with MoneyPrinterV2. Learn how MoviePy creates MP4s and Selenium handles authenticated uploads to YouTube Studio. Boost your content creation.

- Repository: [FujiwaraChoki/MoneyPrinterV2](https://github.com/FujiwaraChoki/MoneyPrinterV2)
- Tags: how-to-guide
- Published: 2026-03-20

---

**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`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/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`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py)) executes a sequential preparation pipeline:

1. **Topic generation** – `generate_topic()` creates a niche-specific subject
2. **Script writing** – `generate_script()` produces the narration text
3. **Metadata creation** – `generate_metadata()` crafts titles and descriptions
4. **Image prompts** – `generate_prompts()` creates AI image generation instructions
5. **Asset generation** – `generate_image()` downloads AI-generated images for each prompt
6. **Audio synthesis** – `generate_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`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py)) is the core MoviePy implementation. It performs the following composition steps:

**Image Clip Creation**

```python

# 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**

```python
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`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/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**

```python
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:
  ```python
  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:

```python
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

```python
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

```python

# 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

```python

# 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`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/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.