# How to Extend Video Generation with Custom Visual Effects in MoneyPrinterV2

> Learn how to extend video generation with custom visual effects in MoneyPrinterV2. Modify the combine method to apply MoviePy FX like fadein, resize, and rotate to your video clips.

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

---

**To extend video generation with custom visual effects in MoneyPrinterV2, modify the `combine()` method in [`src/classes/YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py) to apply MoviePy FX—such as `fadein`, `resize`, or `rotate`—to each `ImageClip` before concatenation, and expose configuration parameters through [`src/constants.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/constants.py).**

MoneyPrinterV2 automates YouTube Shorts creation by stitching AI-generated images, Text-to-Speech audio, and subtitles into a final video. While the default pipeline only resizes and concatenates clips, the codebase is built on MoviePy and already imports the `moviepy.video.fx.all` toolbox, making it straightforward to inject custom visual effects at the clip or composite level.

## Locate the Video Assembly Pipeline

The core video assembly logic resides in **[`src/classes/YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py)** inside the `combine()` method (approximately lines 552–595). This method constructs the final video by iterating over generated images, converting them into `ImageClip` objects, and concatenating them.

Key variables available inside this method:

- **`self.images`** – List of absolute PNG paths generated earlier in the pipeline.
- **`self.tts_path`** – Path to the synthesized speech audio file.
- **`req_dur`** – Calculated duration per image (`max_duration / len(self.images)`).
- **`clips`** – Temporary list that accumulates `ImageClip` instances before final concatenation.

You will insert effect calls inside the `while tot_dur < max_duration:` loop, immediately after each clip is resized but before it is appended to the `clips` list.

## Create a Reusable Effect Helper

For maintainability, create a helper function to chain multiple effects. You can add this to **[`src/utils.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/utils.py)** or directly inside [`src/classes/YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py).

```python
from moviepy.video.fx.all import fadein, fadeout, resize, rotate

def apply_effects(clip, fade_in=0.5, fade_out=0.5, zoom=1.0, rotate_deg=0):
    """Apply a chain of MoviePy visual effects to an ImageClip."""
    if fade_in:
        clip = fadein(clip, fade_in)
    if fade_out:
        clip = fadeout(clip, fade_out)
    if zoom != 1.0:
        clip = resize(clip, zoom)
    if rotate_deg:
        clip = rotate(clip, rotate_deg)
    return clip

```

This abstraction keeps the main assembly loop clean and allows you to toggle individual effects via boolean or numeric parameters.

## Apply Effects to Individual Image Clips

Inside `YouTube.combine()`, import the Movie FX module at the top of the file and apply effects after the resize logic:

```python
from moviepy import video.fx as vfx
import src.constants as constants

# Inside the while tot_dur < max_duration: loop

for image_path in self.images:
    clip = ImageClip(image_path).set_fps(30)
    clip.duration = req_dur
    
    # Existing resize/crop logic...

    if round((clip.w / clip.h), 4) < 0.5625:
        clip = crop(clip, width=clip.w, height=round(clip.w / 0.5625),
                   x_center=clip.w / 2, y_center=clip.h / 2)
    else:
        clip = crop(clip, width=round(0.5625 * clip.h), height=clip.h,
                   x_center=clip.w / 2, y_center=clip.h / 2)
    clip = clip.resize((1080, 1920))
    
    # Apply custom visual effects

    clip = vfx.fadein(clip, getattr(constants, "DEFAULT_FADE_IN", 0.5))
    clip = vfx.fadeout(clip, getattr(constants, "DEFAULT_FADE_OUT", 0.5))
    clip = vfx.resize(clip, getattr(constants, "DEFAULT_ZOOM", 1.02))
    clip = vfx.rotate(clip, getattr(constants, "DEFAULT_ROTATE_DEG", 2))
    
    clips.append(clip)
    tot_dur += clip.duration

```

Using `getattr()` with defaults ensures the code remains functional even if you have not yet defined the constants.

## Add Global Post-Processing Effects

For effects that should apply to the entire video (such as color grading or contrast adjustments), process the final composite after concatenation:

```python
final_clip = concatenate_videoclips(clips).set_fps(30)

# Global color correction

final_clip = final_clip.fx(vfx.colorx, 1.2)  # Boost contrast by 20%

final_clip = final_clip.fx(vfx.lum_contrast, lum=0, contrast=30)

```

This approach is ideal for maintaining visual consistency across all stitched segments.

## Expose Configuration Options

Hard-coded effect parameters make experimentation difficult. Instead, centralize defaults in **[`src/constants.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/constants.py)**:

```python

# Visual effect configuration

DEFAULT_FADE_IN = 0.5        # seconds

DEFAULT_FADE_OUT = 0.5       # seconds  

DEFAULT_ZOOM = 1.02          # 2% zoom effect

DEFAULT_ROTATE_DEG = 2       # degrees of rotation

```

These values are then referenced dynamically in [`YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/YouTube.py) using `getattr(constants, "VAR_NAME", default_value)`, allowing you to tweak the visual style without modifying the source logic.

## Complete Implementation Example

Below is a condensed excerpt of `combine()` demonstrating the integration points:

```python
def combine(self) -> str:
    combined_image_path = os.path.join(ROOT_DIR, ".mp", f"{uuid4()}.mp4")
    threads = get_threads()
    tts_clip = AudioFileClip(self.tts_path)
    max_duration = tts_clip.duration
    req_dur = max_duration / len(self.images)
    
    clips = []
    tot_dur = 0
    
    while tot_dur < max_duration:
        for image_path in self.images:
            clip = ImageClip(image_path).set_fps(30)
            clip.duration = req_dur
            
            # Crop to 9:16 aspect ratio

            if round((clip.w / clip.h), 4) < 0.5625:
                clip = crop(clip, width=clip.w, height=round(clip.w / 0.5625),
                           x_center=clip.w / 2, y_center=clip.h / 2)
            else:
                clip = crop(clip, width=round(0.5625 * clip.h), height=clip.h,
                           x_center=clip.w / 2, y_center=clip.h / 2)
            clip = clip.resize((1080, 1920))
            
            # Custom effects

            from moviepy import video.fx as vfx
            clip = vfx.fadein(clip, getattr(constants, "DEFAULT_FADE_IN", 0.5))
            clip = vfx.fadeout(clip, getattr(constants, "DEFAULT_FADE_OUT", 0.5))
            clip = vfx.resize(clip, getattr(constants, "DEFAULT_ZOOM", 1.02))
            
            clips.append(clip)
            tot_dur += clip.duration
    
    final_clip = concatenate_videoclips(clips).set_fps(30)
    final_clip.write_videofile(combined_image_path, threads=threads)
    return combined_image_path

```

## Summary

- **Modify [`src/classes/YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py)** – Insert effect calls inside the `combine()` method after clip resizing.
- **Leverage MoviePy FX** – Use `fadein`, `fadeout`, `resize`, `rotate`, and `colorx` from `moviepy.video.fx.all`.
- **Centralize config** – Store default values in [`src/constants.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/constants.py) and access them via `getattr()` to avoid hard-coding.
- **Test incrementally** – Add one effect at a time, verify the output in `./.mp/`, then stack additional effects to avoid rendering errors.

## Frequently Asked Questions

### Where is the video generation logic located in MoneyPrinterV2?

The video generation logic is located in **[`src/classes/YouTube.py`](https://github.com/FujiwaraChoki/MoneyPrinterV2/blob/main/src/classes/YouTube.py)** inside the `combine()` method (lines approximately 552–595). This method handles loading images, creating `ImageClip` objects, resizing them to 9:16 aspect ratio, applying effects, concatenating them, and writing the final MP4 file.

### Can I apply effects to the entire video instead of individual images?

Yes. After calling `concatenate_videoclips(clips)` to create `final_clip`, you can apply global effects such as `vfx.colorx()` for contrast adjustment or `vfx.lum_contrast()` for brightness control. These effects process the entire composite timeline rather than individual image segments.

### How do I add advanced effects like glitch overlays or masks?

For advanced effects, create custom `VideoClip` objects using `moviepy.video.VideoClip.ColorClip` or numpy arrays, then overlay them using `CompositeVideoClip`. For example, generate a short noise clip with random pixel values and `set_opacity(0.15)`, then composite it over the main video at specific timestamps to create glitch effects.