Привет! Хочу поделиться своим скриптом для уникализации фото и видео. Софт применяет различные эффекты (шум, цветокоррекция, контраст и др.) и проверяет уникальность через перцептивные хэши (dhash, phash, ahash). Функции - Уникализация фото (.jpg, .png, .jpeg) и видео (.mp4, .avi, .mov, .mkv). - Эффекты: шум, размытие, поворот, цветокоррекция, контраст, насыщенность и др. - Проверка уникальности с заданным порогом. - Параллельная обработка файлов для скорости. Код import os import random import typer from tqdm import tqdm from typing import Dict, List from pathlib import Path from PIL import Image, ImageFilter, ImageOps, ImageEnhance import numpy as np from moviepy.editor import VideoFileClip import csv from datetime import datetime import cv2 import imagehash from colorama import init, Fore, Style from concurrent.futures import ThreadPoolExecutor # Инициализация colorama init() app = typer.Typer() PHOTO_EXT = [".jpg", ".jpeg", ".png"] VIDEO_EXT = [".mp4", ".avi", ".mov", ".mkv"] HASH_ALGORITHMS = { "dhash": imagehash.dhash, "phash": imagehash.phash, "ahash": imagehash.average_hash } def is_photo(file: str) -> bool: return Path(file).suffix.lower() in PHOTO_EXT def is_video(file: str) -> bool: return Path(file).suffix.lower() in VIDEO_EXT def perceptual_diff(img1: Image.Image, img2: Image.Image, algo="dhash") -> int: hash_func = HASH_ALGORITHMS.get(algo, imagehash.dhash) return abs(hash_func(img1) - hash_func(img2)) def extract_frame(video_path: str, timestamp: float = 0.5) -> Image.Image: clip = VideoFileClip(video_path) t = min(timestamp, clip.duration - 0.1) frame = clip.get_frame(t) clip.reader.close() if clip.audio: clip.audio.reader.close_proc() return Image.fromarray(frame) def add_noise(img_np): noise = np.random.randint(0, 15, img_np.shape, dtype='uint8') # Уменьшен до 15 return cv2.add(img_np, noise) def color_correction(img): r, g, b = img.split() channel = random.choice(['r', 'g', 'b']) if channel == 'r': r = r.point(lambda x: x * 1.1) elif channel == 'g': g = g.point(lambda x: x * 1.1) else: b = b.point(lambda x: x * 1.1) return Image.merge("RGB", (r, g, b)) def adjust_gamma(img, gamma=1.0): invGamma = 1.0 / gamma table = np.array([((i / 255.0) ** invGamma) * 255 for i in range(256)]).astype("uint8") img_np = np.array(img) img_np = cv2.LUT(img_np, table) return Image.fromarray(img_np) def adjust_contrast(img, contrast_factor): enhancer = ImageEnhance.Contrast(img) return enhancer.enhance(contrast_factor / 100.0) def adjust_saturation(img, saturation_factor): enhancer = ImageEnhance.Color(img) return enhancer.enhance(saturation_factor / 100.0) def adjust_darkness(img, darkness_factor): img_np = np.array(img) img_np = np.clip(img_np * (darkness_factor / 100.0), 0, 255).astype("uint8") return Image.fromarray(img_np) def adjust_warmth(img, warmth_factor): img_np = np.array(img) warmth_adjust = warmth_factor / 100.0 img_np[..., 0] = np.clip(img_np[..., 0] * (1 + warmth_adjust * 0.5), 0, 255) img_np[..., 2] = np.clip(img_np[..., 2] * (1 - warmth_adjust * 0.3), 0, 255) return Image.fromarray(img_np) def adjust_peak_brightness(img, peak_factor): enhancer = ImageEnhance.Brightness(img) return enhancer.enhance(peak_factor / 100.0) def adjust_brightness(img, brightness_factor): enhancer = ImageEnhance.Brightness(img) return enhancer.enhance(brightness_factor / 100.0) def adjust_sharpness(img, sharpness_factor): enhancer = ImageEnhance.Sharpness(img) return enhancer.enhance(sharpness_factor / 100.0) def add_background(img, video_mode=False): if video_mode: h, w, _ = np.array(img).shape else: w, h = img.size bg_color1 = tuple(random.randint(0, 255) for _ in range(3)) bg_color2 = tuple(random.randint(0, 255) for _ in range(3)) bg = np.zeros((h, w, 3), dtype=np.uint8) for i in range(h): r = int(bg_color1[0] + (bg_color2[0] - bg_color1[0]) * i / h) g = int(bg_color1[1] + (bg_color2[1] - bg_color1[1]) * i / h) b = int(bg_color1[2] + (bg_color2[2] - bg_color1[2]) * i / h) bg[i, :] = [r, g, b] if video_mode: return bg else: bg_img = Image.fromarray(bg).convert("RGBA") img = img.convert("RGBA") img = Image.blend(bg_img, img, alpha=0.7) return img.convert("RGB") def add_invisible_lines(img, video_mode=False): img_np = np.array(img) h, w = img_np.shape[:2] for _ in range(random.randint(5, 10)): # Уменьшен до 10 x1, y1 = random.randint(0, w), random.randint(0, h) x2, y2 = random.randint(0, w), random.randint(0, h) color = tuple(random.randint(0, 50) for _ in range(3)) cv2.line(img_np, (x1, y1), (x2, y2), color, 1) return img_np if video_mode else Image.fromarray(img_np) def candy_effect(img, video_mode=False): enhancer = ImageEnhance.Color(img) img = enhancer.enhance(random.uniform(1.2, 2.0)) # Уменьшен диапазон enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(random.uniform(1.1, 1.5)) # Уменьшен диапазон return np.array(img) if video_mode else img def apply_image_unique(path_in: str, path_out: str, methods: Dict[str, bool], min_diff: int = 10, hash_algo: str = "dhash") -> List[str]: original_img = Image.open(path_in).convert("RGB") available = [m for m, v in methods.items() if v] applied = [] for _ in range(3): # Уменьшено до 3 попыток img = original_img.copy() applied.clear() for method in random.sample(available, k=min(2, len(available))): # Уменьшено до 2 методов if method == "noise": img = Image.fromarray(add_noise(np.array(img))) elif method == "blur": img = img.filter(ImageFilter.BLUR) elif method == "rotate": img = img.rotate(random.choice([90, 180, 270])) elif method == "flip": img = ImageOps.mirror(img) elif method == "sharpen": img = img.filter(ImageFilter.SHARPEN) elif method == "resize": img = img.resize((img.width - 10, img.height - 10)) elif method == "color_correction": img = color_correction(img) elif method == "gamma": img = adjust_gamma(img, random.uniform(0.9, 1.1)) # Уменьшен диапазон elif method == "brightness": img = adjust_brightness(img, random.uniform(80, 120)) # Уменьшен диапазон elif method == "background": img = add_background(img) elif method == "invisible_lines": img = add_invisible_lines(img) elif method == "candy_effect": img = candy_effect(img) elif method == "contrast": img = adjust_contrast(img, random.uniform(80, 120)) # Уменьшен диапазон elif method == "saturation": img = adjust_saturation(img, random.uniform(50, 100)) # Уменьшен диапазон elif method == "darkness": img = adjust_darkness(img, random.uniform(50, 70)) # Уменьшен диапазон elif method == "warmth": img = adjust_warmth(img, random.uniform(45, 65)) # Уменьшен диапазон elif method == "peak_brightness": img = adjust_peak_brightness(img, random.uniform(70, 130)) # Уменьшен диапазон elif method == "sharpness": img = adjust_sharpness(img, random.uniform(80, 120)) # Уменьшен диапазон applied.append(method) diff = perceptual_diff(original_img, img, hash_algo) if diff >= min_diff: img.save(path_out, format="JPEG", quality=90) applied.append(f"{hash_algo}_diff={diff}") return applied img.save(path_out, format="JPEG", quality=90) applied.append(f"{hash_algo}_diff=FAIL") return applied def apply_video_unique(path_in: str, path_out: str, methods: Dict[str, bool], min_diff: int = 10, hash_algo: str = "dhash") -> List[str]: original_frame = extract_frame(path_in, 0.5) # Один кадр вместо трёх available = [m for m, v in methods.items() if v] applied = [] temp_output = path_out.replace(".mp4", "_temp.mp4") for _ in range(2): # Уменьшено до 2 попыток clip = VideoFileClip(path_in) applied.clear() for method in random.sample(available, k=min(2, len(available))): # Уменьшено до 2 методов if method == "fps": clip = clip.set_fps(max(1, clip.fps - random.uniform(0.1, 0.5))) # Меньшее изменение elif method == "resize": clip = clip.resize(height=int(clip.h * 0.99)) # Меньшее изменение elif method == "brightness": clip = clip.fl_image(lambda frame: np.clip(frame * 0.98, 0, 255).astype("uint8")) # Меньшее изменение elif method == "crop": clip = clip.subclip(0.3, clip.duration) # Меньшее обрезание elif method == "color_correction": clip = clip.fl_image(lambda f: np.array(color_correction(Image.fromarray(f)))) elif method == "gamma": g = random.uniform(0.9, 1.1) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_gamma(Image.fromarray(f), gamma=g))) elif method == "background": clip = clip.fl_image(lambda f: add_background(Image.fromarray(f), video_mode=True)) elif method == "invisible_lines": clip = clip.fl_image(lambda f: add_invisible_lines(Image.fromarray(f), video_mode=True)) elif method == "candy_effect": clip = clip.fl_image(lambda f: candy_effect(Image.fromarray(f), video_mode=True)) elif method == "contrast": v = random.uniform(80, 120) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_contrast(Image.fromarray(f), v))) elif method == "saturation": v = random.uniform(50, 100) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_saturation(Image.fromarray(f), v))) elif method == "darkness": v = random.uniform(50, 70) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_darkness(Image.fromarray(f), v))) elif method == "warmth": v = random.uniform(45, 65) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_warmth(Image.fromarray(f), v))) elif method == "peak_brightness": v = random.uniform(70, 130) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_peak_brightness(Image.fromarray(f), v))) elif method == "sharpness": v = random.uniform(80, 120) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_sharpness(Image.fromarray(f), v))) applied.append(method) clip.write_videofile(temp_output, codec='libx264', audio_codec='aac', verbose=False, logger=None) clip.reader.close() if clip.audio: clip.audio.reader.close_proc() new_frame = extract_frame(temp_output, 0.5) diff = perceptual_diff(original_frame, new_frame, hash_algo) if diff >= min_diff: os.replace(temp_output, path_out) applied.append(f"{hash_algo}_diff={round(diff, 1)}") os.remove(temp_output) if os.path.exists(temp_output) else None return applied else: os.remove(temp_output) if os.path.exists(temp_output) else None clip.write_videofile(path_out, codec='libx264', audio_codec='aac', verbose=False, logger=None) applied.append(f"{hash_algo}_diff=FAIL") return applied def process_file(file: str, input_folder: Path, output_folder: Path, photo_settings: Dict[str, bool], video_settings: Dict[str, bool], min_diff: int, hash_algo: str) -> List[str]: input_path = input_folder / file try: if is_photo(file): out_file = output_folder / f"{Path(file).stem}_uniq.jpg" applied = apply_image_unique(str(input_path), str(out_file), photo_settings, min_diff, hash_algo) return ["Фото", file, out_file.name, ", ".join(applied), "OK"] elif is_video(file): out_file = output_folder / f"{Path(file).stem}_uniq.mp4" applied = apply_video_unique(str(input_path), str(out_file), video_settings, min_diff, hash_algo) return ["Видео", file, out_file.name, ", ".join(applied), "OK"] except Exception as e: return ["?", file, "-", "-", f"Ошибка: {e}"] @app.command() def run(): typer.echo(f"{Fore.GREEN}==== УНИКАЛИЗАТОР ФОТО И ВИДЕО ===={Style.RESET_ALL}") input_folder = Path(typer.prompt("Введите путь к ВХОДНОЙ папке")) output_folder = Path(typer.prompt("Введите путь к ВЫХОДНОЙ папке")) output_folder.mkdir(parents=True, exist_ok=True) # Тип обрабатываемого медиа typer.echo("\nЧто обрабатывать?") typer.echo("1 — Только фото") typer.echo("2 — Только видео") typer.echo("3 — Всё сразу") media_choice = typer.prompt("Ваш выбор (1/2/3): ") media_mode = {"1": "photo", "2": "video", "3": "all"}.get(media_choice, "all") # Выбор алгоритма хэша typer.echo("\nВыберите алгоритм хэша (для проверки уникальности):") typer.echo("1 — dhash (рекомендуется)") typer.echo("2 — phash (точнее, но медленнее)") typer.echo("3 — ahash (простой)") hash_choice = typer.prompt("Ваш выбор (1/2/3): ", default="1") hash_algo = {"1": "dhash", "2": "phash", "3": "ahash"}.get(hash_choice, "dhash") min_diff = int(typer.prompt("Минимальная уникальность по хэшу (рекомендуется 10–15): ", default="12")) # Настройки фильтров (активируем все по умолчанию) all_methods = { "noise": True, "blur": True, "rotate": True, "flip": True, "sharpen": True, "resize": True, "color_correction": True, "gamma": True, "brightness": True, "background": True, "invisible_lines": True, "candy_effect": True, "contrast": True, "saturation": True, "darkness": True, "warmth": True, "peak_brightness": True, "sharpness": True, "fps": True, "crop": True } photo_settings = {k: v for k, v in all_methods.items() if k not in ["fps", "crop"]} video_settings = {k: v for k, v in all_methods.items()} files = os.listdir(input_folder) media_files = [f for f in files if ( (is_photo(f) and media_mode in ["photo", "all"]) or (is_video(f) and media_mode in ["video", "all"]) )] report = [] with ThreadPoolExecutor(max_workers=min(4, os.cpu_count() or 1)) as executor: # Параллельная обработка results = list(tqdm(executor.map( lambda f: process_file(f, input_folder, output_folder, photo_settings, video_settings, min_diff, hash_algo), media_files ), total=len(media_files), desc="Обработка", ncols=80)) report.extend(results) # Сохранение отчета save_report = typer.confirm("Сохранить отчёт в CSV-файл?") if save_report: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") report_file = output_folder / f"uniquify_report_{timestamp}.csv" with open(report_file, "w", newline='', encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Тип", "Оригинал", "Результат", "Методы", "Статус"]) writer.writerows(report) typer.echo(f"\n{Fore.GREEN}Готово! Отчёт сохранён: {report_file}{Style.RESET_ALL}") else: typer.echo(f"\n{Fore.GREEN}Готово! Отчёт не сохранён.{Style.RESET_ALL}") typer.echo(f"{Fore.CYAN}by lolz.live/soft 0O0{Style.RESET_ALL}") if __name__ == "__main__": app() Python import os import random import typer from tqdm import tqdm from typing import Dict, List from pathlib import Path from PIL import Image, ImageFilter, ImageOps, ImageEnhance import numpy as np from moviepy.editor import VideoFileClip import csv from datetime import datetime import cv2 import imagehash from colorama import init, Fore, Style from concurrent.futures import ThreadPoolExecutor # Инициализация colorama init() app = typer.Typer() PHOTO_EXT = [".jpg", ".jpeg", ".png"] VIDEO_EXT = [".mp4", ".avi", ".mov", ".mkv"] HASH_ALGORITHMS = { "dhash": imagehash.dhash, "phash": imagehash.phash, "ahash": imagehash.average_hash } def is_photo(file: str) -> bool: return Path(file).suffix.lower() in PHOTO_EXT def is_video(file: str) -> bool: return Path(file).suffix.lower() in VIDEO_EXT def perceptual_diff(img1: Image.Image, img2: Image.Image, algo="dhash") -> int: hash_func = HASH_ALGORITHMS.get(algo, imagehash.dhash) return abs(hash_func(img1) - hash_func(img2)) def extract_frame(video_path: str, timestamp: float = 0.5) -> Image.Image: clip = VideoFileClip(video_path) t = min(timestamp, clip.duration - 0.1) frame = clip.get_frame(t) clip.reader.close() if clip.audio: clip.audio.reader.close_proc() return Image.fromarray(frame) def add_noise(img_np): noise = np.random.randint(0, 15, img_np.shape, dtype='uint8') # Уменьшен до 15 return cv2.add(img_np, noise) def color_correction(img): r, g, b = img.split() channel = random.choice(['r', 'g', 'b']) if channel == 'r': r = r.point(lambda x: x * 1.1) elif channel == 'g': g = g.point(lambda x: x * 1.1) else: b = b.point(lambda x: x * 1.1) return Image.merge("RGB", (r, g, b)) def adjust_gamma(img, gamma=1.0): invGamma = 1.0 / gamma table = np.array([((i / 255.0) ** invGamma) * 255 for i in range(256)]).astype("uint8") img_np = np.array(img) img_np = cv2.LUT(img_np, table) return Image.fromarray(img_np) def adjust_contrast(img, contrast_factor): enhancer = ImageEnhance.Contrast(img) return enhancer.enhance(contrast_factor / 100.0) def adjust_saturation(img, saturation_factor): enhancer = ImageEnhance.Color(img) return enhancer.enhance(saturation_factor / 100.0) def adjust_darkness(img, darkness_factor): img_np = np.array(img) img_np = np.clip(img_np * (darkness_factor / 100.0), 0, 255).astype("uint8") return Image.fromarray(img_np) def adjust_warmth(img, warmth_factor): img_np = np.array(img) warmth_adjust = warmth_factor / 100.0 img_np[..., 0] = np.clip(img_np[..., 0] * (1 + warmth_adjust * 0.5), 0, 255) img_np[..., 2] = np.clip(img_np[..., 2] * (1 - warmth_adjust * 0.3), 0, 255) return Image.fromarray(img_np) def adjust_peak_brightness(img, peak_factor): enhancer = ImageEnhance.Brightness(img) return enhancer.enhance(peak_factor / 100.0) def adjust_brightness(img, brightness_factor): enhancer = ImageEnhance.Brightness(img) return enhancer.enhance(brightness_factor / 100.0) def adjust_sharpness(img, sharpness_factor): enhancer = ImageEnhance.Sharpness(img) return enhancer.enhance(sharpness_factor / 100.0) def add_background(img, video_mode=False): if video_mode: h, w, _ = np.array(img).shape else: w, h = img.size bg_color1 = tuple(random.randint(0, 255) for _ in range(3)) bg_color2 = tuple(random.randint(0, 255) for _ in range(3)) bg = np.zeros((h, w, 3), dtype=np.uint8) for i in range(h): r = int(bg_color1[0] + (bg_color2[0] - bg_color1[0]) * i / h) g = int(bg_color1[1] + (bg_color2[1] - bg_color1[1]) * i / h) b = int(bg_color1[2] + (bg_color2[2] - bg_color1[2]) * i / h) bg[i, :] = [r, g, b] if video_mode: return bg else: bg_img = Image.fromarray(bg).convert("RGBA") img = img.convert("RGBA") img = Image.blend(bg_img, img, alpha=0.7) return img.convert("RGB") def add_invisible_lines(img, video_mode=False): img_np = np.array(img) h, w = img_np.shape[:2] for _ in range(random.randint(5, 10)): # Уменьшен до 10 x1, y1 = random.randint(0, w), random.randint(0, h) x2, y2 = random.randint(0, w), random.randint(0, h) color = tuple(random.randint(0, 50) for _ in range(3)) cv2.line(img_np, (x1, y1), (x2, y2), color, 1) return img_np if video_mode else Image.fromarray(img_np) def candy_effect(img, video_mode=False): enhancer = ImageEnhance.Color(img) img = enhancer.enhance(random.uniform(1.2, 2.0)) # Уменьшен диапазон enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(random.uniform(1.1, 1.5)) # Уменьшен диапазон return np.array(img) if video_mode else img def apply_image_unique(path_in: str, path_out: str, methods: Dict[str, bool], min_diff: int = 10, hash_algo: str = "dhash") -> List[str]: original_img = Image.open(path_in).convert("RGB") available = [m for m, v in methods.items() if v] applied = [] for _ in range(3): # Уменьшено до 3 попыток img = original_img.copy() applied.clear() for method in random.sample(available, k=min(2, len(available))): # Уменьшено до 2 методов if method == "noise": img = Image.fromarray(add_noise(np.array(img))) elif method == "blur": img = img.filter(ImageFilter.BLUR) elif method == "rotate": img = img.rotate(random.choice([90, 180, 270])) elif method == "flip": img = ImageOps.mirror(img) elif method == "sharpen": img = img.filter(ImageFilter.SHARPEN) elif method == "resize": img = img.resize((img.width - 10, img.height - 10)) elif method == "color_correction": img = color_correction(img) elif method == "gamma": img = adjust_gamma(img, random.uniform(0.9, 1.1)) # Уменьшен диапазон elif method == "brightness": img = adjust_brightness(img, random.uniform(80, 120)) # Уменьшен диапазон elif method == "background": img = add_background(img) elif method == "invisible_lines": img = add_invisible_lines(img) elif method == "candy_effect": img = candy_effect(img) elif method == "contrast": img = adjust_contrast(img, random.uniform(80, 120)) # Уменьшен диапазон elif method == "saturation": img = adjust_saturation(img, random.uniform(50, 100)) # Уменьшен диапазон elif method == "darkness": img = adjust_darkness(img, random.uniform(50, 70)) # Уменьшен диапазон elif method == "warmth": img = adjust_warmth(img, random.uniform(45, 65)) # Уменьшен диапазон elif method == "peak_brightness": img = adjust_peak_brightness(img, random.uniform(70, 130)) # Уменьшен диапазон elif method == "sharpness": img = adjust_sharpness(img, random.uniform(80, 120)) # Уменьшен диапазон applied.append(method) diff = perceptual_diff(original_img, img, hash_algo) if diff >= min_diff: img.save(path_out, format="JPEG", quality=90) applied.append(f"{hash_algo}_diff={diff}") return applied img.save(path_out, format="JPEG", quality=90) applied.append(f"{hash_algo}_diff=FAIL") return applied def apply_video_unique(path_in: str, path_out: str, methods: Dict[str, bool], min_diff: int = 10, hash_algo: str = "dhash") -> List[str]: original_frame = extract_frame(path_in, 0.5) # Один кадр вместо трёх available = [m for m, v in methods.items() if v] applied = [] temp_output = path_out.replace(".mp4", "_temp.mp4") for _ in range(2): # Уменьшено до 2 попыток clip = VideoFileClip(path_in) applied.clear() for method in random.sample(available, k=min(2, len(available))): # Уменьшено до 2 методов if method == "fps": clip = clip.set_fps(max(1, clip.fps - random.uniform(0.1, 0.5))) # Меньшее изменение elif method == "resize": clip = clip.resize(height=int(clip.h * 0.99)) # Меньшее изменение elif method == "brightness": clip = clip.fl_image(lambda frame: np.clip(frame * 0.98, 0, 255).astype("uint8")) # Меньшее изменение elif method == "crop": clip = clip.subclip(0.3, clip.duration) # Меньшее обрезание elif method == "color_correction": clip = clip.fl_image(lambda f: np.array(color_correction(Image.fromarray(f)))) elif method == "gamma": g = random.uniform(0.9, 1.1) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_gamma(Image.fromarray(f), gamma=g))) elif method == "background": clip = clip.fl_image(lambda f: add_background(Image.fromarray(f), video_mode=True)) elif method == "invisible_lines": clip = clip.fl_image(lambda f: add_invisible_lines(Image.fromarray(f), video_mode=True)) elif method == "candy_effect": clip = clip.fl_image(lambda f: candy_effect(Image.fromarray(f), video_mode=True)) elif method == "contrast": v = random.uniform(80, 120) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_contrast(Image.fromarray(f), v))) elif method == "saturation": v = random.uniform(50, 100) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_saturation(Image.fromarray(f), v))) elif method == "darkness": v = random.uniform(50, 70) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_darkness(Image.fromarray(f), v))) elif method == "warmth": v = random.uniform(45, 65) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_warmth(Image.fromarray(f), v))) elif method == "peak_brightness": v = random.uniform(70, 130) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_peak_brightness(Image.fromarray(f), v))) elif method == "sharpness": v = random.uniform(80, 120) # Уменьшен диапазон clip = clip.fl_image(lambda f: np.array(adjust_sharpness(Image.fromarray(f), v))) applied.append(method) clip.write_videofile(temp_output, codec='libx264', audio_codec='aac', verbose=False, logger=None) clip.reader.close() if clip.audio: clip.audio.reader.close_proc() new_frame = extract_frame(temp_output, 0.5) diff = perceptual_diff(original_frame, new_frame, hash_algo) if diff >= min_diff: os.replace(temp_output, path_out) applied.append(f"{hash_algo}_diff={round(diff, 1)}") os.remove(temp_output) if os.path.exists(temp_output) else None return applied else: os.remove(temp_output) if os.path.exists(temp_output) else None clip.write_videofile(path_out, codec='libx264', audio_codec='aac', verbose=False, logger=None) applied.append(f"{hash_algo}_diff=FAIL") return applied def process_file(file: str, input_folder: Path, output_folder: Path, photo_settings: Dict[str, bool], video_settings: Dict[str, bool], min_diff: int, hash_algo: str) -> List[str]: input_path = input_folder / file try: if is_photo(file): out_file = output_folder / f"{Path(file).stem}_uniq.jpg" applied = apply_image_unique(str(input_path), str(out_file), photo_settings, min_diff, hash_algo) return ["Фото", file, out_file.name, ", ".join(applied), "OK"] elif is_video(file): out_file = output_folder / f"{Path(file).stem}_uniq.mp4" applied = apply_video_unique(str(input_path), str(out_file), video_settings, min_diff, hash_algo) return ["Видео", file, out_file.name, ", ".join(applied), "OK"] except Exception as e: return ["?", file, "-", "-", f"Ошибка: {e}"] @app.command() def run(): typer.echo(f"{Fore.GREEN}==== УНИКАЛИЗАТОР ФОТО И ВИДЕО ===={Style.RESET_ALL}") input_folder = Path(typer.prompt("Введите путь к ВХОДНОЙ папке")) output_folder = Path(typer.prompt("Введите путь к ВЫХОДНОЙ папке")) output_folder.mkdir(parents=True, exist_ok=True) # Тип обрабатываемого медиа typer.echo("\nЧто обрабатывать?") typer.echo("1 — Только фото") typer.echo("2 — Только видео") typer.echo("3 — Всё сразу") media_choice = typer.prompt("Ваш выбор (1/2/3): ") media_mode = {"1": "photo", "2": "video", "3": "all"}.get(media_choice, "all") # Выбор алгоритма хэша typer.echo("\nВыберите алгоритм хэша (для проверки уникальности):") typer.echo("1 — dhash (рекомендуется)") typer.echo("2 — phash (точнее, но медленнее)") typer.echo("3 — ahash (простой)") hash_choice = typer.prompt("Ваш выбор (1/2/3): ", default="1") hash_algo = {"1": "dhash", "2": "phash", "3": "ahash"}.get(hash_choice, "dhash") min_diff = int(typer.prompt("Минимальная уникальность по хэшу (рекомендуется 10–15): ", default="12")) # Настройки фильтров (активируем все по умолчанию) all_methods = { "noise": True, "blur": True, "rotate": True, "flip": True, "sharpen": True, "resize": True, "color_correction": True, "gamma": True, "brightness": True, "background": True, "invisible_lines": True, "candy_effect": True, "contrast": True, "saturation": True, "darkness": True, "warmth": True, "peak_brightness": True, "sharpness": True, "fps": True, "crop": True } photo_settings = {k: v for k, v in all_methods.items() if k not in ["fps", "crop"]} video_settings = {k: v for k, v in all_methods.items()} files = os.listdir(input_folder) media_files = [f for f in files if ( (is_photo(f) and media_mode in ["photo", "all"]) or (is_video(f) and media_mode in ["video", "all"]) )] report = [] with ThreadPoolExecutor(max_workers=min(4, os.cpu_count() or 1)) as executor: # Параллельная обработка results = list(tqdm(executor.map( lambda f: process_file(f, input_folder, output_folder, photo_settings, video_settings, min_diff, hash_algo), media_files ), total=len(media_files), desc="Обработка", ncols=80)) report.extend(results) # Сохранение отчета save_report = typer.confirm("Сохранить отчёт в CSV-файл?") if save_report: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") report_file = output_folder / f"uniquify_report_{timestamp}.csv" with open(report_file, "w", newline='', encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Тип", "Оригинал", "Результат", "Методы", "Статус"]) writer.writerows(report) typer.echo(f"\n{Fore.GREEN}Готово! Отчёт сохранён: {report_file}{Style.RESET_ALL}") else: typer.echo(f"\n{Fore.GREEN}Готово! Отчёт не сохранён.{Style.RESET_ALL}") typer.echo(f"{Fore.CYAN}by lolz.live/soft 0O0{Style.RESET_ALL}") if __name__ == "__main__": app() Requirements pip install Pillow moviepy opencv-python imagehash typer tqdm colorama numpy