Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import numpy as np
|
3 |
+
import re
|
4 |
+
from PIL import Image, ImageDraw, ImageFilter, ImageEnhance, ImageFont
|
5 |
+
from moviepy.video.VideoClip import VideoClip
|
6 |
+
|
7 |
+
|
8 |
+
# Функция для добавления слабого эффекта сепии
|
9 |
+
def apply_sepia(image, intensity=0.3):
|
10 |
+
sepia_image = Image.new("RGB", image.size)
|
11 |
+
pixels = image.load()
|
12 |
+
sepia_pixels = sepia_image.load()
|
13 |
+
|
14 |
+
for y in range(image.height):
|
15 |
+
for x in range(image.width):
|
16 |
+
r, g, b = pixels[x, y]
|
17 |
+
tr = int(0.393 * r + 0.769 * g + 0.189 * b)
|
18 |
+
tg = int(0.349 * r + 0.686 * g + 0.168 * b)
|
19 |
+
tb = int(0.272 * r + 0.534 * g + 0.131 * b)
|
20 |
+
sepia_pixels[x, y] = (
|
21 |
+
int(r * (1 - intensity) + tr * intensity),
|
22 |
+
int(g * (1 - intensity) + tg * intensity),
|
23 |
+
int(b * (1 - intensity) + tb * intensity),
|
24 |
+
)
|
25 |
+
return sepia_image
|
26 |
+
|
27 |
+
|
28 |
+
# Функция для добавления эффекта старой плёнки
|
29 |
+
def add_film_effect(image, frame_number, fps):
|
30 |
+
image = apply_sepia(image, intensity=0.3) # Слабый эффект сепии
|
31 |
+
draw = ImageDraw.Draw(image)
|
32 |
+
width, height = image.size
|
33 |
+
|
34 |
+
# Эффект зернистости
|
35 |
+
noise = np.random.randint(0, 64, (height, width, 3), dtype='uint8')
|
36 |
+
noise_image = Image.fromarray(noise, 'RGB')
|
37 |
+
image = Image.blend(image, noise_image, alpha=0.1)
|
38 |
+
|
39 |
+
# Эффект случайных царапин
|
40 |
+
if frame_number % (fps // 2) == 0: # Царапины реже
|
41 |
+
for _ in range(np.random.randint(1, 3)):
|
42 |
+
x1 = np.random.randint(0, width)
|
43 |
+
y1 = np.random.randint(0, height)
|
44 |
+
x2 = x1 + np.random.randint(-10, 10)
|
45 |
+
y2 = y1 + np.random.randint(-height // 4, height // 4)
|
46 |
+
draw.line([(x1, y1), (x2, y2)], fill="white", width=1)
|
47 |
+
|
48 |
+
# Эффект вертикальных полос
|
49 |
+
if frame_number % (fps // 3) == 0: # Полосы реже
|
50 |
+
for _ in range(np.random.randint(1, 2)):
|
51 |
+
stripe_x = np.random.randint(0, width)
|
52 |
+
stripe_width = np.random.randint(1, 3)
|
53 |
+
draw.rectangle([(stripe_x, 0), (stripe_x + stripe_width, height)], fill=(255, 255, 255, 50))
|
54 |
+
|
55 |
+
# Эффект мерцания (изменение яркости)
|
56 |
+
if frame_number % (fps // 4) == 0: # Мерцание реже
|
57 |
+
brightness_factor = 0.95 + 0.05 * np.random.random()
|
58 |
+
enhancer = ImageEnhance.Brightness(image)
|
59 |
+
image = enhancer.enhance(brightness_factor)
|
60 |
+
|
61 |
+
# Усиление контраста
|
62 |
+
contrast_enhancer = ImageEnhance.Contrast(image)
|
63 |
+
image = contrast_enhancer.enhance(1.2)
|
64 |
+
|
65 |
+
# Размытость для усиления эффекта
|
66 |
+
image = image.filter(ImageFilter.GaussianBlur(radius=0.3))
|
67 |
+
return np.array(image)
|
68 |
+
|
69 |
+
|
70 |
+
# Основная функция создания анимации
|
71 |
+
def create_film_effect(image_path, output_path, duration=1, fps=24):
|
72 |
+
base_image = Image.open(image_path).convert("RGB")
|
73 |
+
width, height = base_image.size
|
74 |
+
|
75 |
+
def make_frame(t):
|
76 |
+
frame_number = int(t * fps)
|
77 |
+
|
78 |
+
# Уменьшенная тряска кадра
|
79 |
+
offset_x = np.random.randint(-1, 2) # Сокращённое смещение
|
80 |
+
offset_y = np.random.randint(-1, 2)
|
81 |
+
jittered_image = Image.new("RGB", (width, height), "black")
|
82 |
+
jittered_image.paste(base_image, (offset_x, offset_y))
|
83 |
+
return add_film_effect(jittered_image, frame_number, fps)
|
84 |
+
|
85 |
+
# Создание видео из кадров
|
86 |
+
animation = VideoClip(make_frame, duration=duration)
|
87 |
+
animation.write_videofile(output_path, fps=fps)
|
88 |
+
|
89 |
+
|
90 |
+
def create_image_with_text(phrase, bottom_text1="НЕЙРОННЫЕ", bottom_text2="ХРОНИКИ"):
|
91 |
+
words = phrase.split()
|
92 |
+
word1 = words[0].upper()
|
93 |
+
word2 = words[1]
|
94 |
+
word2 = word2[0].upper() + word2[1:-1].lower() + word2[-1].upper()
|
95 |
+
|
96 |
+
img_size = 512
|
97 |
+
image = Image.new('RGB', (img_size, img_size), 'black')
|
98 |
+
draw = ImageDraw.Draw(image)
|
99 |
+
|
100 |
+
def get_impact_font(text, max_width, max_height, initial_size=100):
|
101 |
+
size = initial_size
|
102 |
+
font = ImageFont.truetype("impact.ttf", size)
|
103 |
+
while size > 1:
|
104 |
+
font = ImageFont.truetype("impact.ttf", size)
|
105 |
+
bbox = draw.textbbox((0, 0), text, font=font)
|
106 |
+
if bbox[2] - bbox[0] <= max_width and bbox[3] - bbox[1] <= max_height:
|
107 |
+
return font, bbox
|
108 |
+
size -= 1
|
109 |
+
return ImageFont.truetype("impact.ttf", 1), draw.textbbox((0, 0), text,
|
110 |
+
font=ImageFont.truetype("impact.ttf", 1))
|
111 |
+
|
112 |
+
def get_arial_font(text, max_width, max_height, initial_size=100):
|
113 |
+
size = initial_size
|
114 |
+
font = ImageFont.truetype("arialbd.ttf", size)
|
115 |
+
while size > 1:
|
116 |
+
font = ImageFont.truetype("arialbd.ttf", size)
|
117 |
+
bbox = draw.textbbox((0, 0), text, font=font)
|
118 |
+
if bbox[2] - bbox[0] <= max_width and bbox[3] - bbox[1] <= max_height:
|
119 |
+
return font, bbox
|
120 |
+
size -= 1
|
121 |
+
return ImageFont.truetype("arialbd.ttf", 1), draw.textbbox((0, 0), text,
|
122 |
+
font=ImageFont.truetype("arialbd.ttf", 1))
|
123 |
+
|
124 |
+
initial_font1, initial_bbox = get_impact_font(word1, img_size // 2, img_size // 6)
|
125 |
+
text1_width = initial_bbox[2] - initial_bbox[0]
|
126 |
+
text1_height = initial_bbox[3] - initial_bbox[1]
|
127 |
+
|
128 |
+
padding = 40
|
129 |
+
square_size = max(text1_width + padding * 2, text1_height + padding * 2)
|
130 |
+
square_position = ((img_size - square_size) // 2, (img_size - square_size) // 2)
|
131 |
+
|
132 |
+
draw.rectangle(
|
133 |
+
[square_position, (square_position[0] + square_size, square_position[1] + square_size)],
|
134 |
+
fill='red'
|
135 |
+
)
|
136 |
+
|
137 |
+
text1_position = (
|
138 |
+
(img_size - text1_width) // 2,
|
139 |
+
square_position[1] + padding // 2
|
140 |
+
)
|
141 |
+
draw.text(text1_position, word1, font=initial_font1, fill='black')
|
142 |
+
|
143 |
+
bottom_font, bottom_bbox = get_arial_font(bottom_text1, square_size - padding, square_size // 8, initial_size=33)
|
144 |
+
bottom_text1_width = bottom_bbox[2] - bottom_bbox[0]
|
145 |
+
bottom_text1_height = bottom_bbox[3] - bottom_bbox[1]
|
146 |
+
|
147 |
+
bottom_font2, bottom_bbox2 = get_arial_font(bottom_text2, square_size - padding, square_size // 8, initial_size=33)
|
148 |
+
bottom_text2_width = bottom_bbox2[2] - bottom_bbox2[0]
|
149 |
+
bottom_text2_height = bottom_bbox2[3] - bottom_bbox2[1]
|
150 |
+
|
151 |
+
spacing = 1 # Расстояние между строками Современные хроники
|
152 |
+
|
153 |
+
bottom_text1_position = (
|
154 |
+
(img_size - bottom_text1_width) // 2,
|
155 |
+
square_position[1] + square_size - padding - bottom_text1_height * 2 - spacing
|
156 |
+
)
|
157 |
+
bottom_text2_position = (
|
158 |
+
(img_size - bottom_text2_width) // 2,
|
159 |
+
bottom_text1_position[1] + bottom_text1_height + spacing
|
160 |
+
)
|
161 |
+
|
162 |
+
draw.text(bottom_text1_position, bottom_text1, font=bottom_font, fill='white')
|
163 |
+
draw.text(bottom_text2_position, bottom_text2, font=bottom_font2, fill='white')
|
164 |
+
|
165 |
+
font2, _ = get_impact_font(word2, square_size * 1.5, square_size // 2, initial_size=150)
|
166 |
+
|
167 |
+
kerning_offset = -3
|
168 |
+
total_width = 0
|
169 |
+
letter_widths = []
|
170 |
+
for letter in word2:
|
171 |
+
bbox = draw.textbbox((0, 0), letter, font=font2)
|
172 |
+
letter_width = bbox[2] - bbox[0]
|
173 |
+
letter_widths.append(letter_width)
|
174 |
+
total_width += letter_width
|
175 |
+
|
176 |
+
total_width += kerning_offset * (len(word2) - 1)
|
177 |
+
start_x = (img_size - total_width) // 2
|
178 |
+
text_height = draw.textbbox((0, 0), word2, font=font2)[3] - draw.textbbox((0, 0), word2, font=font2)[1]
|
179 |
+
y_position = (img_size - text_height) // 2 - 20 # Поднимаем второе слово вверх
|
180 |
+
|
181 |
+
current_x = start_x
|
182 |
+
for i, letter in enumerate(word2):
|
183 |
+
draw.text((current_x, y_position), letter, font=font2, fill='white')
|
184 |
+
current_x += letter_widths[i] + kerning_offset
|
185 |
+
|
186 |
+
return image
|
187 |
+
|
188 |
+
|
189 |
+
def greet(text, duration):
|
190 |
+
text = text.replace("-", " ").replace("gate", "нейро")
|
191 |
+
print(text)
|
192 |
+
if len(text.split()) == 1:
|
193 |
+
text = f"Нейро {text}"
|
194 |
+
|
195 |
+
# create image
|
196 |
+
print("create image")
|
197 |
+
clear_text = re.sub(r'[^a-zA-Zа-яА-ЯёЁ ]', '', text)
|
198 |
+
image = create_image_with_text(clear_text)
|
199 |
+
image.save("output5.png")
|
200 |
+
|
201 |
+
# create video
|
202 |
+
print("create video")
|
203 |
+
create_film_effect("output5.png", "output_film_effect.mp4", duration=duration)
|
204 |
+
|
205 |
+
return ["output_film_effect.mp4", "output5.png"]
|
206 |
+
|
207 |
+
|
208 |
+
demo = gr.Interface(fn=greet, inputs=[
|
209 |
+
gr.Text(label="Text", value="Операция лесополоса"),
|
210 |
+
gr.Slider(value=1)], outputs=[gr.Video(), gr.Image()])
|
211 |
+
|
212 |
+
demo.queue(api_open=True).launch(show_error=True, show_api=True)
|