Spaces:
Running
Running
| import gradio as gr | |
| import numpy as np | |
| import re | |
| from PIL import Image, ImageDraw, ImageFilter, ImageEnhance, ImageFont | |
| from moviepy.video.VideoClip import VideoClip | |
| # Функция для добавления слабого эффекта сепии | |
| def apply_sepia(image, intensity=0.3): | |
| sepia_image = Image.new("RGB", image.size) | |
| pixels = image.load() | |
| sepia_pixels = sepia_image.load() | |
| for y in range(image.height): | |
| for x in range(image.width): | |
| r, g, b = pixels[x, y] | |
| tr = int(0.393 * r + 0.769 * g + 0.189 * b) | |
| tg = int(0.349 * r + 0.686 * g + 0.168 * b) | |
| tb = int(0.272 * r + 0.534 * g + 0.131 * b) | |
| sepia_pixels[x, y] = ( | |
| int(r * (1 - intensity) + tr * intensity), | |
| int(g * (1 - intensity) + tg * intensity), | |
| int(b * (1 - intensity) + tb * intensity), | |
| ) | |
| return sepia_image | |
| # Функция для добавления эффекта старой плёнки | |
| def add_film_effect(image, frame_number, fps): | |
| image = apply_sepia(image, intensity=0.3) # Слабый эффект сепии | |
| draw = ImageDraw.Draw(image) | |
| width, height = image.size | |
| # Эффект зернистости | |
| noise = np.random.randint(0, 64, (height, width, 3), dtype='uint8') | |
| noise_image = Image.fromarray(noise, 'RGB') | |
| image = Image.blend(image, noise_image, alpha=0.1) | |
| # Эффект случайных царапин | |
| if frame_number % (fps // 2) == 0: # Царапины реже | |
| for _ in range(np.random.randint(1, 3)): | |
| x1 = np.random.randint(0, width) | |
| y1 = np.random.randint(0, height) | |
| x2 = x1 + np.random.randint(-10, 10) | |
| y2 = y1 + np.random.randint(-height // 4, height // 4) | |
| draw.line([(x1, y1), (x2, y2)], fill="white", width=1) | |
| # Эффект вертикальных полос | |
| if frame_number % (fps // 3) == 0: # Полосы реже | |
| for _ in range(np.random.randint(1, 2)): | |
| stripe_x = np.random.randint(0, width) | |
| stripe_width = np.random.randint(1, 3) | |
| draw.rectangle([(stripe_x, 0), (stripe_x + stripe_width, height)], fill=(255, 255, 255, 50)) | |
| # Эффект мерцания (изменение яркости) | |
| if frame_number % (fps // 4) == 0: # Мерцание реже | |
| brightness_factor = 0.95 + 0.05 * np.random.random() | |
| enhancer = ImageEnhance.Brightness(image) | |
| image = enhancer.enhance(brightness_factor) | |
| # Усиление контраста | |
| contrast_enhancer = ImageEnhance.Contrast(image) | |
| image = contrast_enhancer.enhance(1.2) | |
| # Размытость для усиления эффекта | |
| image = image.filter(ImageFilter.GaussianBlur(radius=0.3)) | |
| return np.array(image) | |
| # Основная функция создания анимации | |
| def create_film_effect(image_path, output_path, duration=1, fps=24): | |
| base_image = Image.open(image_path).convert("RGB") | |
| width, height = base_image.size | |
| def make_frame(t): | |
| frame_number = int(t * fps) | |
| # Уменьшенная тряска кадра | |
| offset_x = np.random.randint(-1, 2) # Сокращённое смещение | |
| offset_y = np.random.randint(-1, 2) | |
| jittered_image = Image.new("RGB", (width, height), "black") | |
| jittered_image.paste(base_image, (offset_x, offset_y)) | |
| return add_film_effect(jittered_image, frame_number, fps) | |
| # Создание видео из кадров | |
| animation = VideoClip(make_frame, duration=duration) | |
| animation.write_videofile(output_path, fps=fps) | |
| def create_image_with_text(phrase, bottom_text1="НЕЙРОННЫЕ", bottom_text2="ХРОНИКИ"): | |
| words = phrase.split() | |
| word1 = words[0].upper() | |
| word2 = words[1] | |
| word2 = word2[0].upper() + word2[1:-1].lower() + word2[-1].upper() | |
| img_size = 512 | |
| image = Image.new('RGB', (img_size, img_size), 'black') | |
| draw = ImageDraw.Draw(image) | |
| def get_impact_font(text, max_width, max_height, initial_size=100): | |
| size = initial_size | |
| font = ImageFont.truetype("impact.ttf", size) | |
| while size > 1: | |
| font = ImageFont.truetype("impact.ttf", size) | |
| bbox = draw.textbbox((0, 0), text, font=font) | |
| if bbox[2] - bbox[0] <= max_width and bbox[3] - bbox[1] <= max_height: | |
| return font, bbox | |
| size -= 1 | |
| return ImageFont.truetype("impact.ttf", 1), draw.textbbox((0, 0), text, | |
| font=ImageFont.truetype("impact.ttf", 1)) | |
| def get_arial_font(text, max_width, max_height, initial_size=100): | |
| size = initial_size | |
| font = ImageFont.truetype("arialbd.ttf", size) | |
| while size > 1: | |
| font = ImageFont.truetype("arialbd.ttf", size) | |
| bbox = draw.textbbox((0, 0), text, font=font) | |
| if bbox[2] - bbox[0] <= max_width and bbox[3] - bbox[1] <= max_height: | |
| return font, bbox | |
| size -= 1 | |
| return ImageFont.truetype("arialbd.ttf", 1), draw.textbbox((0, 0), text, | |
| font=ImageFont.truetype("arialbd.ttf", 1)) | |
| initial_font1, initial_bbox = get_impact_font(word1, img_size // 2, img_size // 6) | |
| text1_width = initial_bbox[2] - initial_bbox[0] | |
| text1_height = initial_bbox[3] - initial_bbox[1] | |
| padding = 40 | |
| square_size = max(text1_width + padding * 2, text1_height + padding * 2) | |
| square_position = ((img_size - square_size) // 2, (img_size - square_size) // 2) | |
| draw.rectangle( | |
| [square_position, (square_position[0] + square_size, square_position[1] + square_size)], | |
| fill='red' | |
| ) | |
| text1_position = ( | |
| (img_size - text1_width) // 2, | |
| square_position[1] + padding // 2 | |
| ) | |
| draw.text(text1_position, word1, font=initial_font1, fill='black') | |
| bottom_font, bottom_bbox = get_arial_font(bottom_text1, square_size - padding, square_size // 8, initial_size=33) | |
| bottom_text1_width = bottom_bbox[2] - bottom_bbox[0] | |
| bottom_text1_height = bottom_bbox[3] - bottom_bbox[1] | |
| bottom_font2, bottom_bbox2 = get_arial_font(bottom_text2, square_size - padding, square_size // 8, initial_size=33) | |
| bottom_text2_width = bottom_bbox2[2] - bottom_bbox2[0] | |
| bottom_text2_height = bottom_bbox2[3] - bottom_bbox2[1] | |
| spacing = 1 # Расстояние между строками Современные хроники | |
| bottom_text1_position = ( | |
| (img_size - bottom_text1_width) // 2, | |
| square_position[1] + square_size - padding - bottom_text1_height * 2 - spacing | |
| ) | |
| bottom_text2_position = ( | |
| (img_size - bottom_text2_width) // 2, | |
| bottom_text1_position[1] + bottom_text1_height + spacing | |
| ) | |
| draw.text(bottom_text1_position, bottom_text1, font=bottom_font, fill='white') | |
| draw.text(bottom_text2_position, bottom_text2, font=bottom_font2, fill='white') | |
| font2, _ = get_impact_font(word2, square_size * 1.5, square_size // 2, initial_size=150) | |
| kerning_offset = -3 | |
| total_width = 0 | |
| letter_widths = [] | |
| for letter in word2: | |
| bbox = draw.textbbox((0, 0), letter, font=font2) | |
| letter_width = bbox[2] - bbox[0] | |
| letter_widths.append(letter_width) | |
| total_width += letter_width | |
| total_width += kerning_offset * (len(word2) - 1) | |
| start_x = (img_size - total_width) // 2 | |
| text_height = draw.textbbox((0, 0), word2, font=font2)[3] - draw.textbbox((0, 0), word2, font=font2)[1] | |
| y_position = (img_size - text_height) // 2 - 20 # Поднимаем второе слово вверх | |
| current_x = start_x | |
| for i, letter in enumerate(word2): | |
| draw.text((current_x, y_position), letter, font=font2, fill='white') | |
| current_x += letter_widths[i] + kerning_offset | |
| return image | |
| def greet(text, duration): | |
| text = text.replace("-", " ").replace("gate", "нейро") | |
| print(text) | |
| if len(text.split()) == 1: | |
| text = f"Нейро {text}" | |
| # create image | |
| print("create image") | |
| clear_text = re.sub(r'[^a-zA-Zа-яА-ЯёЁ ]', '', text) | |
| image = create_image_with_text(clear_text) | |
| image.save("output5.png") | |
| # create video | |
| print("create video") | |
| create_film_effect("output5.png", "output_film_effect.mp4", duration=duration) | |
| return ["output_film_effect.mp4", "output5.png"] | |
| demo = gr.Interface(fn=greet, inputs=[ | |
| gr.Text(label="Text", value="Операция лесополоса"), | |
| gr.Slider(value=1)], outputs=[gr.Video(), gr.Image()]) | |
| demo.queue(api_open=True).launch(show_error=True) | |