by3axap commited on
Commit
7394880
·
verified ·
1 Parent(s): 8e60fc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -75
app.py CHANGED
@@ -1,14 +1,30 @@
1
  import math
2
  import re
3
- from flask import Flask, render_template, request
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  app = Flask(__name__)
6
 
7
- # --- КОНФИГУРАЦИЯ ХОЛСТА (16:9) ---
8
- CANVAS_W = 1600
9
- CANVAS_H = 900
10
 
11
- def prepare_svg_symbol(file_storage):
 
12
  try:
13
  raw_data = file_storage.read().decode('utf-8')
14
  vb_match = re.search(r'viewBox=["\']([^"\']+)["\']', raw_data, re.I)
@@ -18,97 +34,211 @@ def prepare_svg_symbol(file_storage):
18
  inner = re.sub(r'</svg>', '', inner, flags=re.I)
19
  inner = re.sub(r'<\?xml[^>]*\?>|<!DOCTYPE[^>]*>', '', inner, flags=re.I)
20
 
21
- if 'fill=' not in inner:
22
- inner = f'<g fill="black">{inner}</g>'
23
-
 
24
  return inner, vbox
25
  except:
26
- return '<rect width="100" height="100" fill="black"/>', "0 0 100 100"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  @app.route('/')
29
  def index():
30
  return render_template('index.html')
31
-
32
- @app.route('/generate', methods=['POST'])
33
- def generate():
34
  try:
35
- density = int(request.form.get('density', 10))
 
36
  gap_x = float(request.form.get('gap_x', 0))
37
  gap_y = float(request.form.get('gap_y', 0))
38
- shift_stagger = float(request.form.get('shift_stagger', 0))
39
- shift_wave = float(request.form.get('shift_wave', 0))
40
  base_scale = float(request.form.get('base_scale', 1.0))
41
- rotation = float(request.form.get('rotation', 0))
42
  growth = float(request.form.get('growth', 0))
43
  growth_type = request.form.get('growth_type', 'sine')
 
 
 
 
 
 
 
 
44
 
 
45
  svg_file = request.files.get('shape_svg')
46
- symbol_content, v_box = prepare_svg_symbol(svg_file) if (svg_file and svg_file.filename != '') else ('<rect width="100" height="100" fill="black"/>', "0 0 100 100")
47
-
48
  vb_parts = [float(x) for x in v_box.split()]
49
  vb_w, vb_h = vb_parts[2], vb_parts[3]
50
 
51
- # Расчет сетки на весь холст без отступов
52
- step_x = CANVAS_W / density
53
- # Чтобы сетка была равномерной (квадратные ячейки по пропорциям),
54
- # мы рассчитываем step_y исходя из step_x, но заполняем всю высоту.
55
- # Однако, если вы хотите именно сетку рядов:
56
- step_y = CANVAS_H / density
57
- # Примечание: если density = 10, будет 10 колонок и 10 рядов.
58
- # Так как холст прямоугольный, ячейки будут прямоугольными.
59
- # Если нужны квадратные ячейки, нужно менять логику density для Y.
60
- # Оставим прямоугольные ячейки, растянутые по экрану.
61
-
62
- cell_w_max = step_x * (1.0 - gap_x)
63
- cell_h_max = step_y * (1.0 - gap_y)
64
-
65
- offset_x_base = (step_x - cell_w_max) / 2
66
- offset_y_base = (step_y - cell_h_max) / 2
67
 
 
 
68
  body = []
69
- for r in range(density):
70
- row_wave = math.sin(r * 0.5) * shift_wave * step_x
71
- row_shift = (r * shift_stagger * step_x) + row_wave
 
 
 
72
 
73
- for c in range(density):
74
- grid_x_rel = (c * step_x + row_shift) % CANVAS_W
75
- grid_y_rel = r * step_y
76
-
77
- abs_x = grid_x_rel + offset_x_base
78
- abs_y = grid_y_rel + offset_y_base
79
-
80
- wave_norm = 0
81
- if growth_type == 'sine':
82
- wave_norm = (math.sin(c * 0.3 + r * 0.3) + 1) / 2
83
- elif growth_type == 'linear':
84
- wave_norm = c / (density - 1) if density > 1 else 0
85
- elif growth_type == 'steps':
86
- num_steps = 5
87
- wave_norm = math.floor(c / (density / num_steps)) / (num_steps - 1) if density > 1 else 0
88
-
89
- mod_w = max(0, 1.0 - (wave_norm * growth))
90
- content_w = cell_w_max * base_scale * mod_w
91
- content_h = cell_h_max * base_scale
92
-
93
- scale_x = content_w / vb_w
94
- scale_y = content_h / vb_h
95
-
96
- # Центрирование -> Вращение -> Масштаб -> Возврат origin
97
- transform = f'translate({cell_w_max/2:.2f} {cell_h_max/2:.2f}) rotate({rotation}) scale({scale_x:.4f} {scale_y:.4f}) translate({-vb_w/2:.2f} {-vb_h/2:.2f})'
98
-
99
- # Clipping через вложенный SVG
100
- cell = f'''
101
- <svg x="{abs_x:.2f}" y="{abs_y:.2f}" width="{cell_w_max:.2f}" height="{cell_h_max:.2f}" viewBox="0 0 {cell_w_max:.2f} {cell_h_max:.2f}">
102
- <g transform="{transform}">
103
- {symbol_content}
104
- </g>
105
- </svg>
106
- '''
107
- body.append(cell)
108
-
109
- return f'<svg xmlns="http://www.w3.org/2000/svg" width="{CANVAS_W}" height="{CANVAS_H}" viewBox="0 0 {CANVAS_W} {CANVAS_H}">{ "".join(body) }</svg>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  except Exception as e:
111
- return f'<text y="50" fill="red">Error: {str(e)}</text>', 200
112
 
113
  if __name__ == '__main__':
114
  app.run(host='0.0.0.0', port=7860)
 
1
  import math
2
  import re
3
+ import tempfile
4
+
5
+
6
+ from flask import Flask, render_template, request, send_file, Response
7
+ from PIL import Image, ImageOps, ImageDraw, ImageColor
8
+ import numpy as np
9
+
10
+ # Проверка наличия MoviePy
11
+
12
+ try:
13
+ from moviepy.editor import VideoFileClip
14
+ MOVIEPY_AVAILABLE = True
15
+ except ImportError:
16
+ MOVIEPY_AVAILABLE = False
17
+
18
+
19
+
20
+
21
 
22
  app = Flask(__name__)
23
 
24
+ # --- Вспомогательные функции ---
 
 
25
 
26
+ def prepare_svg_content(file_storage):
27
+ """Читает SVG и готовит его для вставки"""
28
  try:
29
  raw_data = file_storage.read().decode('utf-8')
30
  vb_match = re.search(r'viewBox=["\']([^"\']+)["\']', raw_data, re.I)
 
34
  inner = re.sub(r'</svg>', '', inner, flags=re.I)
35
  inner = re.sub(r'<\?xml[^>]*\?>|<!DOCTYPE[^>]*>', '', inner, flags=re.I)
36
 
37
+ # Удаляем явные заливки, чтобы управлять цветом через группу
38
+ # (Опционально, но помогает для монохромных иконок)
39
+ # inner = re.sub(r'fill="[^"]*"', '', inner)
40
+
41
  return inner, vbox
42
  except:
43
+ return '<rect width="100" height="100" />', "0 0 100 100"
44
+
45
+ def calculate_grid_items(width, height, density, gap_x, gap_y, shift, base_scale, growth, growth_type, invert, pixel_data=None):
46
+ """Математическое ядро расчета сетки"""
47
+ step = width / density
48
+ rows = math.ceil(height / step)
49
+
50
+ cell_w_max = step * (1.0 - gap_x)
51
+ cell_h_max = step * (1.0 - gap_y)
52
+
53
+ items = []
54
+
55
+
56
+
57
+
58
+
59
+
60
+ for r in range(rows):
61
+ row_shift = (r * shift * step)
62
+ # Оптимизация цикла по колонкам
63
+ start_c = -int(density * 0.5)
64
+ end_c = int(density * 1.5)
65
+
66
+ for c in range(start_c, end_c):
67
+ grid_x = c * step + row_shift
68
+ grid_y = r * step
69
+
70
+ if grid_x < -step or grid_x > width:
71
+
72
+ continue
73
+
74
+ # Логика яркости
75
+
76
+
77
+
78
+ is_visible = True
79
+
80
+ if pixel_data is not None:
81
+ # pixel_data должен быть PIL Image (Grayscale)
82
+ sx = max(0, min(int(grid_x + step/2), width - 1))
83
+ sy = max(0, min(int(grid_y + step/2), height - 1))
84
+ try:
85
+ val = pixel_data.getpixel((sx, sy))
86
+ brightness = val / 255.0
87
+ limit = 0.5
88
+ is_visible = brightness > limit if invert else brightness < limit
89
+ except:
90
+ pass
91
+
92
+
93
+ if not is_visible:
94
+ continue
95
+
96
+ # Логика искажения (Growth)
97
+ wave = 0
98
+ if growth_type == 'sine':
99
+ wave = (math.sin(c * 0.3 + r * 0.3) + 1) / 2
100
+ elif growth_type == 'linear':
101
+ wave = (c % density) / density
102
+ elif growth_type == 'steps':
103
+ wave = (r % 5) / 5.0
104
+
105
+ mod_w = 1.0 - (wave * growth)
106
+ mod_w = max(0.01, min(1.0, mod_w))
107
+
108
+
109
+
110
+
111
+
112
+
113
+ content_w = cell_w_max * base_scale * mod_w
114
+ content_h = cell_h_max * base_scale
115
+
116
+ cx = grid_x + step / 2
117
+ cy = grid_y + step / 2
118
+
119
+ items.append({
120
+ 'cx': cx, 'cy': cy,
121
+ 'w': content_w, 'h': content_h
122
+
123
+
124
+ })
125
+
126
+ return items, step
127
+
128
+ # --- Роуты ---
129
 
130
  @app.route('/')
131
  def index():
132
  return render_template('index.html')
133
+ @app.route('/download_svg', methods=['POST'])
134
+ def download_svg():
 
135
  try:
136
+ # Параметры
137
+ density = int(request.form.get('density', 40))
138
  gap_x = float(request.form.get('gap_x', 0))
139
  gap_y = float(request.form.get('gap_y', 0))
 
 
140
  base_scale = float(request.form.get('base_scale', 1.0))
 
141
  growth = float(request.form.get('growth', 0))
142
  growth_type = request.form.get('growth_type', 'sine')
143
+
144
+ invert = request.form.get('invert') == 'true'
145
+
146
+ # Цвета
147
+ bg_color = request.form.get('bg_color', '#ffffff')
148
+ fill_color = request.form.get('fill_color', '#000000')
149
+
150
+ canvas_w, canvas_h = 1400, 900
151
 
152
+ # SVG Symbol
153
  svg_file = request.files.get('shape_svg')
154
+ if svg_file and svg_file.filename != '':
155
+ symbol_content, v_box = prepare_svg_content(svg_file)
156
  vb_parts = [float(x) for x in v_box.split()]
157
  vb_w, vb_h = vb_parts[2], vb_parts[3]
158
 
159
+ # Raster Image для маски
160
+ raster_file = request.files.get('raster_image')
161
+ pixel_img = None
162
+ if raster_file and raster_file.filename != '':
163
+ img = Image.open(raster_file)
164
+ img = ImageOps.fit(img, (canvas_w, canvas_h), method=Image.Resampling.LANCZOS)
165
+ img = ImageOps.grayscale(img)
166
+ pixel_img = img
 
 
 
 
 
 
 
 
167
 
168
+ items, _ = calculate_grid_items(canvas_w, canvas_h, density, gap_x, gap_y, shift, base_scale, growth, growth_type, invert, pixel_img)
169
+
170
  body = []
171
+ for item in items:
172
+ sx = item['w'] / vb_w
173
+ sy = item['h'] / vb_h
174
+ transform = f'translate({item["cx"]:.2f} {item["cy"]:.2f}) scale({sx:.4f} {sy:.4f}) translate({-vb_w/2:.2f} {-vb_h/2:.2f})'
175
+ # Применяем цвет заливки к группе
176
+ body.append(f'<g transform="{transform}" fill="{fill_color}">{symbol_content}</g>')
177
 
178
+ svg_str = f'<svg xmlns="http://www.w3.org/2000/svg" width="{canvas_w}" height="{canvas_h}" viewBox="0 0 {canvas_w} {canvas_h}" style="background:{bg_color};">{"".join(body)}</svg>'
179
+
180
+ return Response(svg_str, mimetype="image/svg+xml", headers={"Content-disposition": "attachment; filename=pattern.svg"})
181
+
182
+ except Exception as e:
183
+ return str(e), 500
184
+
185
+ @app.route('/process_video', methods=['POST'])
186
+ def process_video():
187
+ if not MOVIEPY_AVAILABLE:
188
+ return "MoviePy not installed on server", 500
189
+
190
+ temp_dir = tempfile.mkdtemp()
191
+ try:
192
+ density = int(request.form.get('density', 40))
193
+ base_scale = float(request.form.get('base_scale', 1.0))
194
+ growth = float(request.form.get('growth', 0))
195
+ growth_type = request.form.get('growth_type', 'sine')
196
+
197
+ invert = request.form.get('invert') == 'true'
198
+
199
+ # Цвета
200
+ bg_color = request.form.get('bg_color', '#ffffff')
201
+ fill_color = request.form.get('fill_color', '#000000')
202
+
203
+ video_file = request.files.get('video_file')
204
+ if not video_file:
205
+ return "No video uploaded", 400
206
+
207
+ input_path = os.path.join(temp_dir, 'input.mp4')
208
+ video_file.save(input_path)
209
+
210
+ def process_frame(get_frame, t):
211
+ frame = get_frame(t)
212
+ img_pil = Image.fromarray(frame).convert("L")
213
+ w, h = img_pil.size
214
+
215
+ # Рендеринг кадра с цветом фона
216
+ canvas = Image.new("RGB", (w, h), bg_color)
217
+ draw = ImageDraw.Draw(canvas)
218
+
219
+ # Используем ту же логику расчета
220
+ items, _ = calculate_grid_items(w, h, density, gap_x, gap_y, shift, base_scale, growth, growth_type, invert, img_pil)
221
+
222
+ for item in items:
223
+ x0 = item['cx'] - item['w']/2
224
+ y0 = item['cy'] - item['h']/2
225
+ # Рисуем прямоугольники с цветом паттерна
226
+ draw.rectangle([x0, y0, x0 + item['w'], y0 + item['h']], fill=fill_color)
227
+
228
+ return np.array(canvas)
229
+
230
+ clip = VideoFileClip(input_path)
231
+ # clip = clip.set_fps(24)
232
+
233
+ new_clip = clip.fl(process_frame)
234
+
235
+ output_path = os.path.join(temp_dir, 'output.mp4')
236
+ new_clip.write_videofile(output_path, codec='libx264', audio_codec='aac', fps=clip.fps)
237
+
238
+ return send_file(output_path, as_attachment=True, download_name="processed_video.mp4")
239
+
240
  except Exception as e:
241
+ return f"Error processing video: {str(e)}", 500
242
 
243
  if __name__ == '__main__':
244
  app.run(host='0.0.0.0', port=7860)