How to Extend Video Generation with Custom Visual Effects in MoneyPrinterV2

To extend video generation with custom visual effects in MoneyPrinterV2, modify the combine() method in 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.

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 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 or directly inside src/classes/YouTube.py.

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:

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:

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:


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

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 – 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 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 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.

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 →