Spaces:
Sleeping
Sleeping
| """ | |
| π¨ Studio Artistico PRO - Streamlit Version | |
| Tab 1: Video Sketch Converter PRO (con opzione 9:16 Shorts) | |
| Tab 2: Matita & Carboncino (20 effetti foto) | |
| Tab 3: Video Canny Edge Detection | |
| Compatible with Python 3.13 | |
| """ | |
| import streamlit as st | |
| import cv2 | |
| import numpy as np | |
| import tempfile | |
| import time | |
| import os | |
| import io | |
| from PIL import Image | |
| from scipy import ndimage | |
| st.set_page_config( | |
| page_title="π¨ Studio Artistico PRO", | |
| page_icon="π¨", | |
| layout="wide" | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # VIDEO SKETCH CONVERTER | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class VideoSketchConverter: | |
| STILI = { | |
| "sketch": "βοΈ Sketch Classico - Linee pulite e definite", | |
| "pencil": "π Pencil Drawing - Effetto matita con texture", | |
| "ink": "ποΈ Ink Drawing - Linee nere marcate", | |
| "charcoal": "π¨ Charcoal - Effetto carboncino morbido", | |
| "comic": "π₯ Comic Style - Stile fumetto" | |
| } | |
| def applica_effetto_sketch(frame, style, intensity, detail, contrast): | |
| try: | |
| if detail % 2 == 0: | |
| detail += 1 | |
| gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) | |
| if style == "sketch": | |
| inverted = cv2.bitwise_not(gray) | |
| blurred = cv2.GaussianBlur(inverted, (detail, detail), 0) | |
| sketch = cv2.divide(gray, 255 - blurred, scale=256.0) | |
| sketch = cv2.convertScaleAbs(sketch, alpha=contrast, beta=0) | |
| _, sketch = cv2.threshold(sketch, int(intensity), 255, cv2.THRESH_BINARY) | |
| return sketch | |
| elif style == "pencil": | |
| sketch_gray, _ = cv2.pencilSketch(frame, sigma_s=60, sigma_r=0.07, shade_factor=0.05) | |
| sketch_gray = cv2.convertScaleAbs(sketch_gray, alpha=contrast, beta=0) | |
| _, sketch_gray = cv2.threshold(sketch_gray, int(intensity), 255, cv2.THRESH_BINARY) | |
| return sketch_gray | |
| elif style == "ink": | |
| bilateral = cv2.bilateralFilter(gray, 9, 75, 75) | |
| edges = cv2.Canny(bilateral, 50, 150) | |
| ink = cv2.bitwise_not(edges) | |
| kernel = np.ones((2, 2), np.uint8) | |
| ink = cv2.erode(ink, kernel, iterations=1) | |
| return ink | |
| elif style == "charcoal": | |
| inverted = cv2.bitwise_not(gray) | |
| blur_size = max(3, detail * 2 + 1) | |
| if blur_size % 2 == 0: | |
| blur_size += 1 | |
| blurred = cv2.GaussianBlur(inverted, (blur_size, blur_size), 0) | |
| charcoal = cv2.divide(gray, 255 - blurred, scale=256.0) | |
| charcoal = cv2.GaussianBlur(charcoal, (3, 3), 0) | |
| return charcoal | |
| elif style == "comic": | |
| bilateral = cv2.bilateralFilter(gray, 9, 75, 75) | |
| edges = cv2.adaptiveThreshold( | |
| bilateral, 255, cv2.ADAPTIVE_THRESH_MEAN_C, | |
| cv2.THRESH_BINARY, 9, 2) | |
| return edges | |
| return gray | |
| except Exception as e: | |
| st.error(f"Errore: {e}") | |
| return gray | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONVERSIONE 9:16 β zoom + crop centrale | |
| # Input: frame orizzontale 16:9 (es. 1920x1080) | |
| # Output: frame verticale 9:16 (es. 1080x1920) | |
| # Come: scala l'altezza a out_h, poi ritaglia il centro | |
| # β niente bande nere, immagine piΓΉ stretta e alta | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def converti_9_16(frame_bgr, out_w=1080, out_h=1920): | |
| src_h, src_w = frame_bgr.shape[:2] | |
| # Scala in modo che l'altezza riempia out_h esattamente | |
| scale = out_h / src_h | |
| new_w = int(src_w * scale) # per un 16:9 β molto piΓΉ largo di out_w | |
| resized = cv2.resize(frame_bgr, (new_w, out_h), interpolation=cv2.INTER_LANCZOS4) | |
| # Ritaglia al centro orizzontalmente | |
| if new_w >= out_w: | |
| x = (new_w - out_w) // 2 | |
| return resized[:, x:x + out_w] | |
| else: | |
| # Caso raro (video giΓ molto stretto): pad nero laterale | |
| pad = (out_w - new_w) // 2 | |
| return cv2.copyMakeBorder(resized, 0, 0, pad, out_w - new_w - pad, | |
| cv2.BORDER_CONSTANT, value=[0, 0, 0]) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MATITA & CARBONCINO β 20 effetti su foto | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _gray(img): | |
| return cv2.cvtColor(np.array(img.convert("RGB")), cv2.COLOR_RGB2GRAY) | |
| def _to_cv(img): | |
| return cv2.cvtColor(np.array(img.convert("RGB")), cv2.COLOR_RGB2BGR) | |
| def matita_classica(img, v=21): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| return Image.fromarray(cv2.divide(g, 255 - b, scale=256)) | |
| def matita_morbida(img, v=51): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| return Image.fromarray(cv2.GaussianBlur(cv2.divide(g, 255 - b, scale=256), (3,3), 0)) | |
| def matita_dura(img, v=7): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = cv2.divide(g, 255 - b, scale=256) | |
| _, s = cv2.threshold(s, 200, 255, cv2.THRESH_BINARY) | |
| return Image.fromarray(s) | |
| def linee_canny(img, v=150): | |
| g = _gray(img) | |
| return Image.fromarray(255 - cv2.Canny(cv2.GaussianBlur(g,(5,5),0), int(v)//3, int(v))) | |
| def linee_laplacian(img, v=3): | |
| g = _gray(img) | |
| return Image.fromarray(255 - np.uint8(np.clip(np.abs(cv2.Laplacian(g, cv2.CV_64F, ksize=3)), 0, 255))) | |
| def carboncino_leggero(img, v=21): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = cv2.equalizeHist(cv2.divide(g, 255 - b, scale=256)) | |
| return Image.fromarray(np.clip(s.astype(np.int32) - 30, 0, 255).astype(np.uint8)) | |
| def carboncino_intenso(img, v=15): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = np.clip(cv2.divide(g, 255 - b, scale=256).astype(np.int32) - 60, 0, 255).astype(np.uint8) | |
| return Image.fromarray(cv2.createCLAHE(3.0, (8,8)).apply(s)) | |
| def carboncino_sfumato(img, v=31): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = cv2.GaussianBlur(cv2.divide(g, 255 - b, scale=256), (7,7), 0) | |
| return Image.fromarray(np.clip(s.astype(np.int32) - 20, 0, 255).astype(np.uint8)) | |
| def effetto_grafite(img, v=25): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = cv2.divide(g, 255 - b, scale=256) | |
| return Image.fromarray(np.clip(s.astype(np.int16) + np.random.normal(0,5,s.shape).astype(np.int16), 0, 255).astype(np.uint8)) | |
| def cross_hatching(img, v=21): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b1 = cv2.GaussianBlur(255 - g, (v, v), 0); s1 = cv2.divide(g, 255 - b1, scale=256) | |
| rot = ndimage.rotate(g, 45, reshape=False, mode='nearest') | |
| b2 = cv2.GaussianBlur(255 - rot, (v, v), 0) | |
| s2 = ndimage.rotate(cv2.divide(rot, 255 - b2, scale=256), -45, reshape=False, mode='nearest').astype(np.uint8) | |
| return Image.fromarray((s1.astype(np.float32)/255 * s2.astype(np.float32)/255 * 255).astype(np.uint8)) | |
| def schizzo_rapido(img, v=8): | |
| g = _gray(img) | |
| return Image.fromarray(255 - np.clip(cv2.filter2D(g, -1, np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]])), 0, 255).astype(np.uint8)) | |
| def matita_artistica(img, v=21): | |
| v = max(3, int(v) | 1) | |
| g = cv2.cvtColor(cv2.bilateralFilter(_to_cv(img), 9, 75, 75), cv2.COLOR_BGR2GRAY) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| return Image.fromarray(cv2.divide(g, 255 - b, scale=256)) | |
| def matita_carta(img, v=21): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0); s = cv2.divide(g, 255 - b, scale=256) | |
| h, w = s.shape; noise = np.random.randint(200, 256, (h, w), dtype=np.uint8) | |
| return Image.fromarray((s.astype(np.float32)/255 * noise.astype(np.float32)/255 * 255).astype(np.uint8)) | |
| def matita_colorata(img, v=21): | |
| v = max(3, int(v) | 1); cv_img = _to_cv(img); sketches = [] | |
| for ch in cv2.split(cv_img): | |
| b = cv2.GaussianBlur(255 - ch, (v, v), 0); sketches.append(cv2.divide(ch, 255 - b, scale=256)) | |
| return Image.fromarray(cv2.cvtColor(cv2.merge(sketches), cv2.COLOR_BGR2RGB)) | |
| def effetto_inchiostro(img, v=127): | |
| g = _gray(img); blur = cv2.GaussianBlur(g, (5,5), 0) | |
| _, binary = cv2.threshold(blur, int(v), 255, cv2.THRESH_BINARY) | |
| return Image.fromarray(cv2.morphologyEx(binary, cv2.MORPH_CLOSE, np.ones((2,2), np.uint8))) | |
| def matita_xdog(img, v=10): | |
| g = _gray(img).astype(np.float32) / 255.0 | |
| diff = cv2.GaussianBlur(g,(0,0),0.4) - 0.99 * cv2.GaussianBlur(g,(0,0),80.0) | |
| xdog = np.where(diff > -0.1, 1.0, 1.0 + np.tanh(float(v) * diff)) | |
| return Image.fromarray((np.clip(xdog, 0, 1) * 255).astype(np.uint8)) | |
| def carboncino_vignetta(img, v=21): | |
| v = max(3, int(v) | 1); g = _gray(img) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = np.clip(cv2.divide(g, 255 - b, scale=256).astype(np.int32) - 40, 0, 255).astype(np.uint8) | |
| h, w = s.shape; Y, X = np.ogrid[:h, :w]; cx, cy = w/2, h/2 | |
| mask = 1 - np.clip(((X-cx)**2/cx**2 + (Y-cy)**2/cy**2), 0, 1) * 0.5 | |
| return Image.fromarray((s * mask).astype(np.uint8)) | |
| def schizzo_sobel(img, v=1): | |
| g = _gray(img) | |
| sx = cv2.Sobel(g, cv2.CV_64F, 1, 0, ksize=3); sy = cv2.Sobel(g, cv2.CV_64F, 0, 1, ksize=3) | |
| return Image.fromarray(255 - np.clip(np.sqrt(sx**2+sy**2), 0, 255).astype(np.uint8)) | |
| def matita_stilizzata(img, v=5): | |
| shade = max(1, min(20, int(v))) / 100 | |
| gray_sketch, _ = cv2.pencilSketch(_to_cv(img), sigma_s=60, sigma_r=0.07, shade_factor=shade) | |
| return Image.fromarray(gray_sketch) | |
| def carboncino_drammatico(img, v=15): | |
| v = max(3, int(v) | 1) | |
| g = cv2.createCLAHE(4.0, (8,8)).apply(_gray(img)) | |
| b = cv2.GaussianBlur(255 - g, (v, v), 0) | |
| s = np.clip(cv2.divide(g, 255 - b, scale=256).astype(np.int32) - 80, 0, 255).astype(np.uint8) | |
| _, s = cv2.threshold(s, 10, 255, cv2.THRESH_TOZERO) | |
| return Image.fromarray(s) | |
| EFFETTI = { | |
| "1. Matita Classica": (matita_classica, 3, 101, 21), | |
| "2. Matita Morbida": (matita_morbida, 3, 101, 51), | |
| "3. Matita Dura / Tecnica": (matita_dura, 3, 51, 7), | |
| "4. Linee Pulite (Canny)": (linee_canny, 10, 300,150), | |
| "5. Linee Fini (Laplacian)": (linee_laplacian, 1, 10, 3), | |
| "6. Carboncino Leggero": (carboncino_leggero, 3, 101, 21), | |
| "7. Carboncino Intenso": (carboncino_intenso, 3, 51, 15), | |
| "8. Carboncino Sfumato": (carboncino_sfumato, 3, 101, 31), | |
| "9. Effetto Grafite": (effetto_grafite, 3, 101, 25), | |
| "10. Cross-Hatching": (cross_hatching, 3, 51, 21), | |
| "11. Schizzo Rapido": (schizzo_rapido, 1, 20, 8), | |
| "12. Matita Artistica": (matita_artistica, 3, 101, 21), | |
| "13. Matita + Texture Carta": (matita_carta, 3, 101, 21), | |
| "14. Matita Colorata": (matita_colorata, 3, 101, 21), | |
| "15. Effetto Inchiostro": (effetto_inchiostro,50, 250,127), | |
| "16. Matita XDoG (Fumetto)": (matita_xdog, 1, 50, 10), | |
| "17. Carboncino + Vignetta": (carboncino_vignetta,3, 101, 21), | |
| "18. Schizzo Sobel": (schizzo_sobel, 1, 5, 1), | |
| "19. Matita Stilizzata (CV2)": (matita_stilizzata, 1, 20, 5), | |
| "20. Carboncino Drammatico": (carboncino_drammatico,3, 51, 15), | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # VIDEO CANNY β funzioni core | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def canny_applica_frame(frame, low, high, blur_k): | |
| gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) | |
| return cv2.Canny(cv2.GaussianBlur(gray, (blur_k, blur_k), 0), low, high) | |
| def canny_genera_anteprima(video_path, low, high, blur_k, posizione=0.3): | |
| cap = cv2.VideoCapture(video_path) | |
| total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| cap.set(cv2.CAP_PROP_POS_FRAMES, int(total * posizione)) | |
| ret, frame = cap.read() | |
| cap.release() | |
| if not ret: | |
| return None | |
| return Image.fromarray(canny_applica_frame(frame, low, high, blur_k)) | |
| def canny_processa_video(video_path, low, high, blur_k, converti_916, | |
| progress_bar, status_text): | |
| cap = cv2.VideoCapture(video_path) | |
| fps = cap.get(cv2.CAP_PROP_FPS) or 25.0 | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| if converti_916: | |
| out_w, out_h, suffix = 1080, 1920, "canny_916" | |
| else: | |
| out_w, out_h, suffix = orig_w, orig_h, "canny" | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_out: | |
| tmp_out_path = tmp_out.name | |
| out = cv2.VideoWriter(tmp_out_path, cv2.VideoWriter_fourcc(*"mp4v"), | |
| fps, (out_w, out_h), isColor=False) | |
| frame_count = 0 | |
| status_text.text("β³ Elaborazione in corsoβ¦") | |
| while True: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| if converti_916: | |
| frame = converti_9_16(frame, out_w, out_h) | |
| out.write(canny_applica_frame(frame, low, high, blur_k)) | |
| frame_count += 1 | |
| if total_frames > 0: | |
| progress_bar.progress(min(frame_count / total_frames, 1.0)) | |
| cap.release(); out.release() | |
| with open(tmp_out_path, "rb") as f: | |
| video_bytes = f.read() | |
| os.unlink(tmp_out_path) | |
| status_text.text("β Completato!") | |
| return video_bytes, f"{suffix}_output.mp4" | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN β tre tabs | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def main(): | |
| st.markdown("# π¨ Studio Artistico PRO") | |
| tab1, tab2, tab3 = st.tabs([ | |
| "π¬ Video Sketch Converter", | |
| "βοΈ Matita & Carboncino (Foto)", | |
| "β‘ Video Canny Edge" | |
| ]) | |
| # ββ TAB 1: Video Sketch Converter βββββββββββββββββββββββββββββββββββ | |
| with tab1: | |
| st.markdown("### Trasforma i tuoi video in opere d'arte") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.subheader("π¬ Input") | |
| uploaded_file = st.file_uploader("Carica Video", | |
| type=['mp4', 'avi', 'mov', 'mkv']) | |
| style = st.selectbox( | |
| "π¨ Stile Artistico", | |
| options=list(VideoSketchConverter.STILI.keys()), | |
| format_func=lambda x: VideoSketchConverter.STILI[x] | |
| ) | |
| # ββ FORMATO OUTPUT ββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown("#### π Formato output") | |
| formato = st.radio( | |
| "Scegli il formato", | |
| ["originale", "9:16"], | |
| format_func=lambda x: { | |
| "originale": "π² Originale (mantieni dimensioni)", | |
| "9:16": "π± 9:16 verticale β Shorts / Reels / TikTok", | |
| }[x], | |
| horizontal=False, | |
| key="fmt_sketch" | |
| ) | |
| if formato == "9:16": | |
| st.info("βοΈ Zoom + crop centrale: l'altezza riempie lo schermo, i lati vengono tagliati. Niente bande nere.") | |
| res_916 = st.radio("Risoluzione verticale", | |
| ["1080x1920", "720x1280"], | |
| horizontal=True, key="res_sketch_916") | |
| out_w_sk, out_h_sk = map(int, res_916.split("x")) | |
| else: | |
| out_w_sk, out_h_sk = None, None | |
| with st.expander("βοΈ Parametri Avanzati"): | |
| intensity = st.slider("IntensitΓ ", 50, 255, 150) | |
| detail = st.slider("Dettaglio", 1, 15, 5, step=2) | |
| contrast = st.slider("Contrasto", 1.0, 3.0, 1.5, step=0.1) | |
| process_btn = st.button("π CONVERTI VIDEO", type="primary", | |
| use_container_width=True, key="btn_video") | |
| with col2: | |
| st.subheader("π₯ Output") | |
| st.empty() | |
| if uploaded_file and process_btn: | |
| with st.spinner("Elaborazione in corso..."): | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_input: | |
| tmp_input.write(uploaded_file.read()) | |
| input_path = tmp_input.name | |
| cap = cv2.VideoCapture(input_path) | |
| orig_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| orig_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| width_out = out_w_sk if formato == "9:16" else orig_width | |
| height_out = out_h_sk if formato == "9:16" else orig_height | |
| output_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name | |
| out = cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*'mp4v'), | |
| fps, (width_out, height_out), False) | |
| progress_bar = st.progress(0) | |
| status_text = st.empty() | |
| frame_count = 0 | |
| start_time = time.time() | |
| while True: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| # Conversione 9:16 prima dello sketch | |
| if formato == "9:16": | |
| frame = converti_9_16(frame, width_out, height_out) | |
| sketch = VideoSketchConverter.applica_effetto_sketch( | |
| frame, style, intensity, detail, contrast) | |
| out.write(sketch) | |
| frame_count += 1 | |
| if frame_count % 10 == 0: | |
| progress = frame_count / total_frames | |
| progress_bar.progress(progress) | |
| elapsed = time.time() - start_time | |
| fps_proc = frame_count / elapsed if elapsed > 0 else 0 | |
| eta = (total_frames - frame_count) / fps_proc if fps_proc > 0 else 0 | |
| status_text.text( | |
| f"Frame {frame_count}/{total_frames} | " | |
| f"{fps_proc:.1f} fps | ETA: {int(eta)}s") | |
| cap.release() | |
| out.release() | |
| elapsed_total = time.time() - start_time | |
| progress_bar.progress(1.0) | |
| st.success(f"β Completato in {elapsed_total:.1f}s! ({width_out}Γ{height_out}px)") | |
| suffix_nome = "_shorts" if formato == "9:16" else "" | |
| with open(output_path, 'rb') as f: | |
| st.download_button( | |
| label="π₯ SCARICA VIDEO", | |
| data=f.read(), | |
| file_name=f"sketch_{style}{suffix_nome}_{uploaded_file.name}", | |
| mime="video/mp4", | |
| type="primary", | |
| use_container_width=True | |
| ) | |
| try: | |
| os.unlink(input_path) | |
| os.unlink(output_path) | |
| except Exception: | |
| pass | |
| # ββ TAB 2: Matita & Carboncino (foto) βββββββββββββββββββββββββββββββ | |
| with tab2: | |
| st.markdown("### Trasforma le tue foto in disegni a matita o carboncino") | |
| col_a, col_b = st.columns(2) | |
| with col_a: | |
| st.subheader("π· Input") | |
| foto = st.file_uploader("Carica una foto", | |
| type=['jpg', 'jpeg', 'png', 'bmp', 'webp'], | |
| key="foto_uploader") | |
| nome_eff = st.selectbox("π¨ Effetto", options=list(EFFETTI.keys())) | |
| fn, mn, mx, default = EFFETTI[nome_eff] | |
| intensita = st.slider("π§ IntensitΓ / Parametro", min_value=mn, max_value=mx, value=default) | |
| col_btn1, col_btn2 = st.columns(2) | |
| with col_btn1: | |
| btn_applica = st.button("β¨ Applica", type="primary", | |
| use_container_width=True, key="btn_foto") | |
| with col_btn2: | |
| btn_batch = st.button("π Tutti e 20", use_container_width=True, key="btn_batch") | |
| with col_b: | |
| st.subheader("π¨ Risultato") | |
| risultato_placeholder = st.empty() | |
| if foto and btn_applica: | |
| pil_img = Image.open(foto).convert("RGB") | |
| with st.spinner(f"Applicando {nome_eff}β¦"): | |
| risultato = fn(pil_img, v=intensita) | |
| risultato_placeholder.image(risultato, use_container_width=True) | |
| buf = io.BytesIO() | |
| risultato.save(buf, format="PNG") | |
| st.download_button("πΎ Scarica", data=buf.getvalue(), | |
| file_name=f"matita_{nome_eff[:20].strip()}.png", | |
| mime="image/png") | |
| if foto and btn_batch: | |
| pil_img = Image.open(foto).convert("RGB") | |
| st.markdown("#### πΌοΈ Galleria β Tutti e 20 gli effetti") | |
| cols = st.columns(4) | |
| with st.spinner("Elaborazione tutti gli effettiβ¦"): | |
| for i, (nome, (f, mn, mx, default)) in enumerate(EFFETTI.items()): | |
| try: | |
| res = f(pil_img, v=default) | |
| with cols[i % 4]: | |
| st.image(res, caption=nome, use_container_width=True) | |
| except Exception as e: | |
| st.warning(f"{nome}: {e}") | |
| # ββ TAB 3: Video Canny Edge Detection βββββββββββββββββββββββββββββββ | |
| with tab3: | |
| st.markdown("### β‘ Trasforma il tuo video in Canny Edge Detection") | |
| uploaded_canny = st.file_uploader( | |
| "π Carica il tuo video", | |
| type=["mp4", "avi", "mov", "mkv", "flv", "wmv", "webm"], | |
| key="canny_uploader" | |
| ) | |
| if not uploaded_canny: | |
| st.info("π Carica un video per iniziare.") | |
| else: | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp_c: | |
| tmp_c.write(uploaded_canny.read()) | |
| tmp_canny_path = tmp_c.name | |
| cap_info = cv2.VideoCapture(tmp_canny_path) | |
| total_f = int(cap_info.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| fps_i = cap_info.get(cv2.CAP_PROP_FPS) or 25.0 | |
| w_i = int(cap_info.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| h_i = int(cap_info.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| cap_info.release() | |
| ci1, ci2, ci3, ci4 = st.columns(4) | |
| ci1.metric("π Larghezza", f"{w_i}px") | |
| ci2.metric("π Altezza", f"{h_i}px") | |
| ci3.metric("ποΈ FPS", f"{fps_i:.1f}") | |
| ci4.metric("β±οΈ Durata", f"{total_f / fps_i:.1f}s") | |
| st.divider() | |
| st.markdown("#### βοΈ Parametri Canny") | |
| cp1, cp2, cp3 = st.columns(3) | |
| with cp1: c_low = st.slider("Soglia Bassa", 0, 200, 50, key="c_low") | |
| with cp2: c_high = st.slider("Soglia Alta", 0, 300, 150, key="c_high") | |
| with cp3: c_blur = st.slider("Sfocatura", 1, 15, 5, step=2, key="c_blur") | |
| c_916 = st.checkbox("π± Converti in 9:16 verticale (1080Γ1920)", value=False, key="c_916") | |
| blur_k = c_blur if c_blur % 2 == 1 else c_blur + 1 | |
| st.divider() | |
| st.markdown("#### ποΈ Anteprima") | |
| pos_col, _ = st.columns([2, 3]) | |
| with pos_col: | |
| c_pos = st.slider("Posizione nel video", 0, 100, 30, format="%d%%", key="c_pos") | |
| preview = canny_genera_anteprima(tmp_canny_path, c_low, c_high, blur_k, c_pos/100.0) | |
| if preview: | |
| st.image(preview, | |
| caption=f"Anteprima β Low={c_low}, High={c_high}, Blur={blur_k}", | |
| use_container_width=True) | |
| else: | |
| st.warning("β οΈ Impossibile generare l'anteprima.") | |
| st.divider() | |
| st.markdown("#### π Elabora il Video Completo") | |
| if st.button("βΆοΈ AVVIA ELABORAZIONE", type="primary", | |
| use_container_width=True, key="btn_canny"): | |
| c_progress = st.progress(0.0) | |
| c_status = st.empty() | |
| try: | |
| video_bytes, nome_file = canny_processa_video( | |
| tmp_canny_path, c_low, c_high, blur_k, c_916, c_progress, c_status) | |
| st.success(f"β Video elaborato! ({len(video_bytes)/1024/1024:.1f} MB)") | |
| st.download_button("β¬οΈ Scarica Video Canny", data=video_bytes, | |
| file_name=nome_file, mime="video/mp4", | |
| use_container_width=True, key="dl_canny") | |
| except Exception as e: | |
| st.error(f"β Errore: {e}") | |
| import traceback; st.code(traceback.format_exc()) | |
| try: | |
| os.unlink(tmp_canny_path) | |
| except Exception: | |
| pass | |
| if __name__ == "__main__": | |
| main() | |