Download app.py from by3axap/PatternV3: direct link, hf CLI and curl.
- Browser
- Download file 8.21 kB
-
https://huggingface.co/spaces/by3axap/PatternV3/resolve/main/app.py
- Command line
-
hf download hf://spaces/by3axap/PatternV3/app.py
-
curl -L -o app.py https://huggingface.co/spaces/by3axap/PatternV3/resolve/main/app.py
8.21 kB
| import math | |
| import os | |
| import re | |
| import tempfile | |
| from flask import Flask, render_template, request, send_file, Response | |
| from PIL import Image, ImageOps, ImageDraw, ImageColor | |
| import numpy as np | |
| # Проверка наличия MoviePy | |
| try: | |
| from moviepy.editor import VideoFileClip | |
| MOVIEPY_AVAILABLE = True | |
| except ImportError: | |
| MOVIEPY_AVAILABLE = False | |
| app = Flask(__name__) | |
| # --- Вспомогательные функции --- | |
| def prepare_svg_content(file_storage): | |
| """Читает SVG и готовит его для вставки""" | |
| try: | |
| raw_data = file_storage.read().decode('utf-8') | |
| vb_match = re.search(r'viewBox=["\']([^"\']+)["\']', raw_data, re.I) | |
| vbox = vb_match.group(1) if vb_match else "0 0 100 100" | |
| inner = re.sub(r'<svg[^>]*>', '', raw_data, flags=re.I) | |
| inner = re.sub(r'</svg>', '', inner, flags=re.I) | |
| inner = re.sub(r'<\?xml[^>]*\?>|<!DOCTYPE[^>]*>', '', inner, flags=re.I) | |
| return inner, vbox | |
| except Exception: | |
| return '<rect width="100" height="100" />', "0 0 100 100" | |
| def calculate_grid_items(width, height, density, gap_x, gap_y, shift, base_scale, growth, growth_type, invert, pixel_data=None): | |
| """Математическое ядро расчета сетки""" | |
| step = width / density | |
| rows = math.ceil(height / step) | |
| cell_w_max = step * (1.0 - gap_x) | |
| cell_h_max = step * (1.0 - gap_y) | |
| items = [] | |
| start_c = -int(density * 0.5) | |
| end_c = int(density * 1.5) | |
| for r in range(rows): | |
| row_shift = (r * shift * step) | |
| for c in range(start_c, end_c): | |
| grid_x = c * step + row_shift | |
| grid_y = r * step | |
| if grid_x < -step or grid_x > width + step: | |
| continue | |
| # Логика яркости | |
| is_visible = True | |
| if pixel_data is not None: | |
| sx = max(0, min(int(grid_x + step / 2), width - 1)) | |
| sy = max(0, min(int(grid_y + step / 2), height - 1)) | |
| try: | |
| val = pixel_data.getpixel((sx, sy)) | |
| brightness = val / 255.0 | |
| limit = 0.5 | |
| is_visible = (brightness > limit) if invert else (brightness < limit) | |
| except Exception: | |
| pass | |
| if not is_visible: | |
| continue | |
| # Логика искажения (Growth) | |
| wave = 0 | |
| if growth_type == 'sine': | |
| wave = (math.sin(c * 0.3 + r * 0.3) + 1) / 2 | |
| elif growth_type == 'linear': | |
| wave = (c % density) / density | |
| elif growth_type == 'steps': | |
| wave = (r % 5) / 5.0 | |
| mod_w = 1.0 - (wave * growth) | |
| mod_w = max(0.01, min(1.0, mod_w)) | |
| content_w = cell_w_max * base_scale * mod_w | |
| content_h = cell_h_max * base_scale | |
| cx = grid_x + step / 2 | |
| cy = grid_y + step / 2 | |
| items.append({ | |
| 'cx': cx, | |
| 'cy': cy, | |
| 'w': content_w, | |
| 'h': content_h | |
| }) | |
| return items, step | |
| # --- Роуты --- | |
| def index(): | |
| return render_template('index.html') | |
| def download_svg(): | |
| try: | |
| # Извлечение параметров | |
| density = int(request.form.get('density', 24)) | |
| gap_x = float(request.form.get('gap_x', 0.2)) | |
| gap_y = float(request.form.get('gap_y', 0.0)) | |
| shift = float(request.form.get('shift_stagger', 0.34)) | |
| base_scale = float(request.form.get('base_scale', 1.0)) | |
| growth = float(request.form.get('growth', 0.9)) | |
| growth_type = request.form.get('growth_type', 'sine') | |
| invert = request.form.get('invert', 'false').lower() == 'true' | |
| bg_color = request.form.get('bg_color', '#ffffff') | |
| fill_color = request.form.get('fill_color', '#000000') | |
| canvas_w, canvas_h = 1400, 900 | |
| # SVG Symbol по умолчанию или из файла | |
| symbol_content = '<rect width="100" height="100" />' | |
| v_box = "0 0 100 100" | |
| svg_file = request.files.get('shape_svg') | |
| if svg_file and svg_file.filename != '': | |
| symbol_content, v_box = prepare_svg_content(svg_file) | |
| vb_parts = [float(x) for x in v_box.split()] | |
| vb_w, vb_h = vb_parts[2], vb_parts[3] | |
| # Raster Image для маски | |
| raster_file = request.files.get('raster_image') | |
| pixel_img = None | |
| if raster_file and raster_file.filename != '': | |
| img = Image.open(raster_file) | |
| img = ImageOps.fit(img, (canvas_w, canvas_h), method=Image.Resampling.LANCZOS) | |
| pixel_img = ImageOps.grayscale(img) | |
| items, _ = calculate_grid_items( | |
| canvas_w, canvas_h, density, gap_x, gap_y, shift, | |
| base_scale, growth, growth_type, invert, pixel_img | |
| ) | |
| body = [] | |
| for item in items: | |
| sx = item['w'] / vb_w | |
| sy = item['h'] / vb_h | |
| transform = f'translate({item["cx"]:.2f} {item["cy"]:.2f}) scale({sx:.4f} {sy:.4f}) translate({-vb_w/2:.2f} {-vb_h/2:.2f})' | |
| body.append(f'<g transform="{transform}" fill="{fill_color}">{symbol_content}</g>') | |
| 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>' | |
| return Response( | |
| svg_str, | |
| mimetype="image/svg+xml", | |
| headers={"Content-disposition": "attachment; filename=pattern.svg"} | |
| ) | |
| except Exception as e: | |
| return str(e), 500 | |
| def process_video(): | |
| if not MOVIEPY_AVAILABLE: | |
| return "MoviePy not installed on server", 500 | |
| temp_dir = tempfile.mkdtemp() | |
| try: | |
| density = int(request.form.get('density', 24)) | |
| gap_x = float(request.form.get('gap_x', 0.2)) | |
| gap_y = float(request.form.get('gap_y', 0.0)) | |
| shift = float(request.form.get('shift_stagger', 0.34)) | |
| base_scale = float(request.form.get('base_scale', 1.0)) | |
| growth = float(request.form.get('growth', 0.9)) | |
| growth_type = request.form.get('growth_type', 'sine') | |
| invert = request.form.get('invert', 'false').lower() == 'true' | |
| bg_color = request.form.get('bg_color', '#ffffff') | |
| fill_color = request.form.get('fill_color', '#000000') | |
| video_file = request.files.get('video_file') | |
| if not video_file: | |
| return "No video uploaded", 400 | |
| input_path = os.path.join(temp_dir, 'input.mp4') | |
| video_file.save(input_path) | |
| def process_frame(get_frame, t): | |
| frame = get_frame(t) | |
| img_pil = Image.fromarray(frame).convert("L") | |
| w, h = img_pil.size | |
| canvas_img = Image.new("RGB", (w, h), bg_color) | |
| draw = ImageDraw.Draw(canvas_img) | |
| items, _ = calculate_grid_items( | |
| w, h, density, gap_x, gap_y, shift, | |
| base_scale, growth, growth_type, invert, img_pil | |
| ) | |
| for item in items: | |
| x0 = item['cx'] - item['w'] / 2 | |
| y0 = item['cy'] - item['h'] / 2 | |
| draw.rectangle([x0, y0, x0 + item['w'], y0 + item['h']], fill=fill_color) | |
| return np.array(canvas_img) | |
| clip = VideoFileClip(input_path) | |
| new_clip = clip.fl(process_frame) | |
| output_path = os.path.join(temp_dir, 'output.mp4') | |
| new_clip.write_videofile(output_path, codec='libx264', audio_codec='aac', fps=clip.fps) | |
| return send_file(output_path, as_attachment=True, download_name="processed_video.mp4") | |
| except Exception as e: | |
| return f"Error processing video: {str(e)}", 500 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) |