Created
May 23, 2025 19:34
-
-
Save JupyterJones/992edb3dfbf94c5259a7fe7722e5da08 to your computer and use it in GitHub Desktop.
convert a text file to a scroling mp4 that matches length of mp3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PIL import Image, ImageDraw, ImageFont | |
import subprocess | |
import textwrap | |
from icecream import ic | |
#-------------------- | |
def get_mp3_duration(mp3_path): | |
cmd = [ | |
"ffprobe", "-v", "error", | |
"-show_entries", "format=duration", | |
"-of", "default=noprint_wrappers=1:nokey=1", | |
mp3_path | |
] | |
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
try: | |
duration = float(result.stdout.strip()) | |
ic(f"MP3 duration: {duration:.2f} seconds") | |
return duration | |
except ValueError: | |
ic("Could not extract duration from mp3") | |
return 5.0 | |
#-------------------- | |
def text_to_long_image(text_file_path, output_image_path, font_path=None, font_size=24, max_line_width=1000, line_spacing=8): | |
ic("Generating long image from text") | |
with open(text_file_path, 'r', encoding='utf-8') as f: | |
text = f.read() | |
ic(f"Text length: {len(text)} characters") | |
if not font_path: | |
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" | |
font = ImageFont.truetype(font_path, font_size) | |
lines = [] | |
for paragraph in text.split('\n'): | |
if paragraph.strip() == '': | |
lines.append('') | |
continue | |
wrapped = textwrap.wrap(paragraph, width=100) | |
for line in wrapped: | |
while font.getlength(line) > max_line_width: | |
cut = len(line) | |
low, high = 0, cut | |
while low < high: | |
mid = (low + high) // 2 | |
if font.getlength(line[:mid]) <= max_line_width: | |
low = mid + 1 | |
else: | |
high = mid | |
cut = low - 1 if low > 0 else 1 | |
lines.append(line[:cut]) | |
line = line[cut:] | |
lines.append(line) | |
line_height = font.getbbox("A")[3] + line_spacing | |
img_height = line_height * len(lines) + 20 | |
img_width = max_line_width + 40 | |
ic(f"Image size: {img_width}x{img_height}") | |
img = Image.new("RGB", (img_width, img_height), color="white") | |
draw = ImageDraw.Draw(img) | |
y = 10 | |
for line in lines: | |
draw.text((20, y), line, fill="black", font=font) | |
y += line_height | |
img.save(output_image_path) | |
ic(f"Saved long image: {output_image_path}") | |
#-------------------- | |
def scroll_image_to_video(image_path, output_video_path, mp3_path, width=512, height=768, scroll_speed_factor=1.0): | |
img = Image.open(image_path) | |
img_width, img_height = img.size | |
ic(f"Image dimensions: {img_width}x{img_height}") | |
scroll_distance = img_height - height | |
ic(f"Scroll distance: {scroll_distance} pixels") | |
duration = get_mp3_duration(mp3_path) | |
if scroll_distance <= 0: | |
ic("No scrolling needed") | |
scroll_filter = f"scale={width}:{height}" | |
else: | |
scroll_speed = (scroll_distance / duration) * scroll_speed_factor | |
ic(f"Scroll speed: {scroll_speed:.2f} px/sec") | |
scroll_filter = ( | |
f"scale={width}:-1," | |
f"crop={width}:{height}:0:y='min(t*{scroll_speed},{scroll_distance})'" | |
) | |
cmd = [ | |
"ffmpeg", | |
"-y", | |
"-loop", "1", | |
"-i", image_path, | |
"-i", mp3_path, | |
"-vf", scroll_filter, | |
"-t", f"{duration}", | |
"-c:v", "libx264", | |
"-pix_fmt", "yuv420p", | |
"-c:a", "aac", | |
"-b:a", "192k", | |
"-shortest", | |
"-movflags", "+faststart", | |
output_video_path | |
] | |
ic("Running ffmpeg command:") | |
ic(' '.join(cmd)) | |
subprocess.run(cmd, check=True) | |
ic(f"Video created: {output_video_path}") | |
#-------------------- | |
if __name__ == "__main__": | |
text_file = "short.txt" | |
long_image = "long_text.png" | |
output_video = "scroll_synced.mp4" | |
mp3_audio = "short.mp3" | |
# Generate the long image from the text file | |
text_to_long_image(text_file, long_image) | |
# Create the scrolling video synchronized with the MP3 file | |
#scroll_image_to_video(long_image, output_video, mp3_audio) | |
# scroll_speed_factor adjust scrolling speed + 1 is faster example: +1.3 | -float is slower example: -.5 | |
scroll_image_to_video(long_image, output_video, mp3_audio, scroll_speed_factor=.5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment