Background Music
Add background music to your generated videos to enhance engagement and production value.
Quick Start
Add MP3 files to Songs/ directory
Place your music files in the Songs/ folder: MoneyPrinter/
├── Songs/
│ ├── upbeat_track.mp3
│ ├── chill_ambient.mp3
│ └── energetic_beat.mp3
Enable background music
In the UI, toggle “Background Music” on. Or via API: {
"videoSubject" : "Tech News" ,
"useMusic" : true
}
Generate video
MoneyPrinter will:
Choose a random song from Songs/
Loop the song to match video duration
Mix at 10% volume with voice audio
Render the final video with music
How It Works
Song Selection
Random selection from available MP3 files:
def choose_random_song () -> Optional[ str ]:
try :
if not SONGS_DIR .exists():
return None
songs = [
song
for song in SONGS_DIR .iterdir()
if song.is_file() and song.suffix.lower() == ".mp3"
]
if not songs:
return None
song = random.choice(songs)
logger.info(colored( f "Chose song: { song } " , "green" ))
return str (song)
except Exception as e:
logger.error(
colored( f "Error occurred while choosing random song: { str (e) } " , "red" )
)
Audio Mixing
The pipeline mixes music with the voice audio:
if use_music:
song_path = choose_random_song()
if not song_path:
emit(
"[-] Could not find songs in Songs/. Continuing without background music." ,
"warning" ,
)
use_music = False
if use_music:
video_clip = VideoFileClip(rendered_video_path)
song_clip = AudioFileClip(song_path).with_fps( 44100 )
# Loop song to match video duration
original_duration = video_clip.duration
song_clip = song_clip.with_effects(
[afx.AudioLoop( duration = original_duration)]
)
# Reduce volume to 10%
song_clip = song_clip.with_volume_scaled( 0.1 ).with_fps( 44100 )
# Mix audio tracks
original_audio = video_clip.audio
mixed_audio = CompositeAudioClip(
[original_audio, song_clip]
).with_duration(original_duration)
Volume Levels
Voice audio : 100% (original TTS volume)
Background music : 10% (0.1x volume scaling)
This ensures the narration remains clear and prominent.
Music Requirements
Only MP3 files are supported. Other formats (WAV, M4A, FLAC) are ignored.
Recommendations
Attribute Recommendation Why Format MP3 Universal support, small file size Bitrate 128-192 kbps Good quality, manageable size Sample Rate 44.1 kHz Standard audio rate Duration Any Automatically looped to match video Genre Instrumental Doesn’t compete with narration Mood Matches content Enhances video tone
Copyright Considerations
Important : Only use royalty-free music or music you have rights to use.
Royalty-free music sources:
Upload Songs via API
Upload songs programmatically:
Response:
{
"status" : "success" ,
"message" : "Uploaded 3 song(s)."
}
The endpoint implementation:
@app.route ( "/api/upload-songs" , methods = [ "POST" ])
def upload_songs ():
try :
files = request.files.getlist( "songs" )
if not files:
return jsonify({ "status" : "error" , "message" : "No files uploaded." }), 400
clean_dir( str ( SONGS_DIR )) # Remove existing songs
saved = 0
for file_item in files:
if file_item.filename and file_item.filename.lower().endswith( ".mp3" ):
safe_name = os.path.basename(file_item.filename)
file_item.save( str ( SONGS_DIR / safe_name))
saved += 1
if saved == 0 :
return jsonify({ "status" : "error" , "message" : "No MP3 files found." }), 400
log( f "[+] Uploaded { saved } song(s) to { SONGS_DIR } " , "success" )
return jsonify({ "status" : "success" , "message" : f "Uploaded { saved } song(s)." })
except Exception as err:
log( f "[-] Error uploading songs: { str (err) } " , "error" )
return jsonify({ "status" : "error" , "message" : str (err)}), 500
upload-songs clears the Songs/ directory before uploading new files.
Audio Rendering
FFmpeg Remux (Preferred)
Fast audio replacement using FFmpeg:
subprocess.run(
[
"ffmpeg" ,
"-y" ,
"-i" , rendered_video_path,
"-i" , mixed_audio_path,
"-map" , "0:v:0" , # Video stream from first input
"-map" , "1:a:0" , # Audio stream from second input
"-c:v" , "copy" , # Copy video codec (no re-encode)
"-c:a" , "aac" , # AAC audio codec
"-b:a" , "192k" , # Audio bitrate
"-shortest" , # Match shortest stream
final_output_path,
],
check = True ,
capture_output = True ,
text = True ,
)
Benefits:
Fast : No video re-encoding
Quality : Preserves video quality
Small : Minimal CPU usage
MoviePy Fallback
If FFmpeg remux fails:
except Exception :
emit(
"[!] ffmpeg remux failed. Falling back to MoviePy render for music mix." ,
"warning" ,
)
video_clip = VideoFileClip(rendered_video_path)
song_clip = AudioFileClip(song_path).with_fps( 44100 )
# Mix audio
comp_audio = CompositeAudioClip([video_clip.audio, song_clip])
video_clip = video_clip.with_audio(comp_audio)
# Re-encode entire video
video_clip.write_videofile(
final_output_path,
threads = render_threads,
fps = 30 ,
codec = "libx264" ,
audio_codec = "aac" ,
preset = "medium" ,
)
MoviePy fallback is slower but more reliable for complex scenarios.
Customizing Music
Change Volume
Edit Backend/pipeline.py to adjust music volume:
# Default: 10%
song_clip = song_clip.with_volume_scaled( 0.1 )
# Louder: 20%
song_clip = song_clip.with_volume_scaled( 0.2 )
# Quieter: 5%
song_clip = song_clip.with_volume_scaled( 0.05 )
Fade In/Out
Add fade effects:
from moviepy import afx
song_clip = song_clip.with_effects([
afx.AudioFadeIn( 1.0 ), # 1-second fade in
afx.AudioFadeOut( 1.0 ), # 1-second fade out
])
Specific Song Selection
Modify song selection logic to use specific tracks:
# Instead of random selection:
song_path = str ( SONGS_DIR / "my_favorite_track.mp3" )
if not Path(song_path).exists():
emit( "Specific song not found. Using random." , "warning" )
song_path = choose_random_song()
Troubleshooting
No music in generated video
Check Songs/ directory :Ensure:
Directory exists
Contains at least one .mp3 file
Files are readable
Check logs :[-] Could not find songs in Songs/. Continuing without background music.
Music too loud / too quiet
Adjust volume in Backend/pipeline.py:# Current: 10%
song_clip = song_clip.with_volume_scaled( 0.1 )
# Change to 15%:
song_clip = song_clip.with_volume_scaled( 0.15 )
Error message :[!] ffmpeg remux failed. Falling back to MoviePy render for music mix.
Causes :
Incompatible codec
Corrupted audio file
FFmpeg not in PATH
Solution : MoviePy automatically handles the fallback. Video will take longer to render.
Cause : Music file shorter than video.Solution : The pipeline automatically loops music:song_clip = song_clip.with_effects(
[afx.AudioLoop( duration = video_duration)]
)
Ensure this line is present in Backend/pipeline.py.
Next Steps
Generating Videos Complete video generation guide
YouTube Upload Automate YouTube uploads
Pipeline Video generation pipeline details
Troubleshooting Common issues and solutions