File size: 6,455 Bytes
287c9ca
 
 
 
 
 
 
 
 
 
b97795f
 
 
 
 
 
 
287c9ca
b97795f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287c9ca
 
b97795f
 
 
 
 
287c9ca
 
 
b97795f
287c9ca
b97795f
 
 
 
 
287c9ca
b97795f
287c9ca
b97795f
 
 
 
 
 
 
287c9ca
b97795f
 
 
 
287c9ca
b97795f
 
 
 
 
 
 
 
 
 
 
287c9ca
 
b97795f
 
 
 
 
 
287c9ca
 
b97795f
287c9ca
 
 
 
 
b97795f
 
 
287c9ca
b97795f
 
 
 
 
 
 
 
 
 
 
287c9ca
b97795f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# core/visual_engine.py
from PIL import Image, ImageDraw, ImageFont
from moviepy.editor import ImageClip, concatenate_videoclips
import os

class VisualEngine:
    def __init__(self, output_dir="generated_media"):
        self.output_dir = output_dir
        os.makedirs(self.output_dir, exist_ok=True)
        try:
            # Try to load Arial font. Ensure it's installed in your Docker image.
            # Common paths: "arial.ttf", "/usr/share/fonts/truetype/msttcorefonts/Arial.ttf"
            # If using a custom font, ensure you COPY it into the Docker image and provide the correct path.
            self.font_path = "arial.ttf" # This assumes Arial is in a standard font path or current dir
            self.font_size_pil = 24 # For Pillow text drawing
            self.font = ImageFont.truetype(self.font_path, self.font_size_pil)
            print(f"Successfully loaded font: {self.font_path} with size {self.font_size_pil}")
        except IOError:
            print(f"Warning: Could not load font '{self.font_path}'. Falling back to default font.")
            self.font = ImageFont.load_default()
            self.font_size_pil = 10 # Default font is smaller, adjust size for calculations


    def _get_text_dimensions(self, text_content, font_obj):
        """
        Gets the width and height of a single line of text with the given font.
        Returns (width, height).
        """
        if hasattr(font_obj, 'getbbox'): # For newer Pillow versions (>=8.0.0)
            # getbbox returns (left, top, right, bottom)
            bbox = font_obj.getbbox(text_content)
            width = bbox[2] - bbox[0]
            height = bbox[3] - bbox[1]
            return width, height
        elif hasattr(font_obj, 'getsize'): # For older Pillow versions
            return font_obj.getsize(text_content)
        else: # Fallback for very basic font objects (like default font)
            # Estimate: average char width * num_chars, fixed height
            # This is a rough estimate.
            avg_char_width = self.font_size_pil * 0.6
            height_estimate = self.font_size_pil * 1.2
            return int(len(text_content) * avg_char_width), int(height_estimate)


    def create_placeholder_image(self, text_description, filename, size=(1280, 720)):
        img = Image.new('RGB', size, color=(73, 109, 137))  # Blueish background
        draw = ImageDraw.Draw(img)

        padding = 40
        max_text_width = size[0] - (2 * padding)
        lines = []
        words = text_description.split()
        current_line = ""

        for word in words:
            test_line = current_line + word + " "
            line_width, _ = self._get_text_dimensions(test_line.strip(), self.font)

            if line_width <= max_text_width:
                current_line = test_line
            else:
                lines.append(current_line.strip())
                current_line = word + " "
        lines.append(current_line.strip()) # Add the last line

        # Calculate starting y position to center the text block
        # Use the height from the first line as an estimate for line height
        _, single_line_height = self._get_text_dimensions("Tg", self.font) # Get height of a typical line
        line_spacing_factor = 1.2 # Adjust for spacing between lines
        total_text_height = len(lines) * single_line_height * line_spacing_factor
        
        y_text = (size[1] - total_text_height) / 2
        if y_text < padding: # Ensure text doesn't start too high if too much text
            y_text = padding

        for line in lines:
            line_width, _ = self._get_text_dimensions(line, self.font)
            x_text = (size[0] - line_width) / 2

            # Corrected ImageDraw.text() call with explicit keyword arguments
            draw.text(
                xy=(x_text, y_text),
                text=line,
                fill=(255, 255, 0),  # Yellow text
                font=self.font
            )
            y_text += single_line_height * line_spacing_factor

        filepath = os.path.join(self.output_dir, filename)
        try:
            img.save(filepath)
            print(f"Placeholder image saved: {filepath}")
        except Exception as e:
            print(f"Error saving image {filepath}: {e}")
            return None
        return filepath


    def create_video_from_images(self, image_paths, output_filename="final_video.mp4", fps=1, duration_per_image=3):
        if not image_paths:
            print("No images provided to create video.")
            return None

        valid_image_paths = [p for p in image_paths if p and os.path.exists(p)]
        if not valid_image_paths:
            print("No valid image paths found to create video.")
            return None
        
        print(f"Creating video from images: {valid_image_paths}")

        try:
            clips = [ImageClip(m).set_duration(duration_per_image) for m in valid_image_paths]
            if not clips:
                print("Could not create ImageClips from the provided image paths.")
                return None
                
            video_clip = concatenate_videoclips(clips, method="compose")
            output_path = os.path.join(self.output_dir, output_filename)
            
            # Using recommended parameters for write_videofile, especially in headless environments
            video_clip.write_videofile(
                output_path,
                fps=fps,
                codec='libx264',          # A common and good codec
                audio_codec='aac',        # If you ever add audio
                temp_audiofile='temp-audio.m4a', # For temporary audio processing
                remove_temp=True,         # Clean up temporary audio file
                threads=os.cpu_count() or 2, # Use available CPUs or default to 2
                logger=None               # Suppress verbose ffmpeg output, use 'bar' for progress
            )
            print(f"Video successfully created: {output_path}")
            return output_path
        except Exception as e:
            print(f"Error creating video: {e}")
            # Print more details if it's an OSError, which can happen with ffmpeg issues
            if isinstance(e, OSError):
                print("OSError during video creation. This often indicates an issue with ffmpeg.")
                print("Ensure ffmpeg is correctly installed and accessible in the environment PATH.")
            return None