# tiktok_processor.py
import os, re, shutil, subprocess, hashlib, random, time
from pathlib import Path
from datetime import datetime
from moviepy.editor import VideoFileClip
from TTS.api import TTS
from pydub import AudioSegment
from PIL import Image, ImageDraw, ImageFont
from faster_whisper import WhisperModel
import torch
import asyncio
import mouse
import json

# Імпорти модулів
from telegram_bot import send_video_preview
from preview_generator import PreviewGenerator

class TikTokVideoGenerator:
    def __init__(self, source_folder, temp_folder, preview_generator, tts_func, asr_model, 
                 tiktok_resolution=(1080, 1920), target_part_duration=105, min_part_duration=63,
                 quality_settings=None, fps_settings=None):
        """
        Ініціалізація генератора TikTok відео з підтримкою налаштувань якості та FPS
        """
        self.source_folder = Path(source_folder)
        self.temp_folder = Path(temp_folder)
        self.preview_generator = preview_generator
        self.tts_func = tts_func
        self.asr_model = asr_model
        self.tiktok_resolution = tiktok_resolution
        self.target_part_duration = target_part_duration
        self.min_part_duration = min_part_duration
        self.fade_duration = 0.3
        
        # Налаштування якості та FPS
        if quality_settings is None:
            quality_settings = {
                "video_bitrate": "12M",
                "audio_bitrate": "192k",
                "preset": "p4"
            }
        if fps_settings is None:
            fps_settings = {"fps": 30}
            
        self.quality_settings = quality_settings
        self.fps_settings = fps_settings
        
        print(f"???? TikTok генератор ініціалізовано:")
        print(f"   • Мінімальна тривалість частини: {self.min_part_duration} секунд")
        print(f"   • Цільова тривалість частини: {self.target_part_duration} секунд")
        print(f"   ???? Якість: {quality_settings['video_bitrate']} @ {fps_settings['fps']} FPS")

    def trim_silence_from_audio(self, audio_segment, silence_threshold_db=-40, chunk_size_ms=100):
        """Видаляє мовчання з початку та кінця аудіо"""
        if len(audio_segment) == 0:
            return audio_segment
        
        # Видаляємо мовчання з початку
        start_trim = 0
        for i in range(0, len(audio_segment), chunk_size_ms):
            chunk = audio_segment[i:i + chunk_size_ms]
            if chunk.dBFS > silence_threshold_db:
                start_trim = max(0, i - 200)
                break
        
        # Видаляємо мовчання з кінця
        end_trim = len(audio_segment)
        for i in range(len(audio_segment) - chunk_size_ms, 0, -chunk_size_ms):
            if i < 0:
                break
            chunk = audio_segment[i:i + chunk_size_ms]
            if chunk.dBFS > silence_threshold_db:
                end_trim = min(len(audio_segment), i + chunk_size_ms + 200)
                break
        
        if start_trim < end_trim:
            trimmed = audio_segment[start_trim:end_trim]
            removed_start = start_trim / 1000.0
            removed_end = (len(audio_segment) - end_trim) / 1000.0
            if removed_start > 0.2 or removed_end > 0.2:
                print(f"✂️ Видалено мовчання: початок {removed_start:.1f}с, кінець {removed_end:.1f}с")
            return trimmed
        else:
            return audio_segment

    def apply_video_speedup(self, input_video_path, output_video_path, speed_factor):
        """Прискорює TikTok відео зі збереженням якості"""
        if speed_factor <= 1.0:
            shutil.copy2(input_video_path, output_video_path)
            return Path(output_video_path)
        
        print(f"⚡ Прискорення TikTok відео в {speed_factor:.2f}x...")
        temp_video = self.temp_folder / f"speedup_tiktok_{random.randint(1000, 9999)}.mp4"
        
        try:
            cmd = [
                "ffmpeg", "-y",
                "-i", str(input_video_path),
                "-filter_complex",
                f"[0:v]setpts={1/speed_factor}*PTS[v];[0:a]atempo={speed_factor}[a]",
                "-map", "[v]",
                "-map", "[a]",
                "-c:v", "h264_nvenc",
                "-preset", self.quality_settings['preset'],
                "-b:v", self.quality_settings['video_bitrate'],
                "-r", str(self.fps_settings['fps']),
                "-c:a", "aac",
                "-b:a", self.quality_settings['audio_bitrate'],
                "-movflags", "+faststart",
                str(temp_video)
            ]
            
            result = subprocess.run(cmd, check=True, capture_output=True, text=True)
            shutil.move(str(temp_video), str(output_video_path))
            print(f"✅ TikTok відео прискорено успішно")
            return Path(output_video_path)
            
        except subprocess.CalledProcessError as e:
            print(f"❌ Помилка при прискоренні TikTok відео: {e}")
            print(f"FFmpeg stderr: {e.stderr}")
            shutil.copy2(input_video_path, output_video_path)
            return Path(output_video_path)
        finally:
            if temp_video.exists():
                temp_video.unlink()

    def split_story_into_parts(self, story_text, preview_text):
        """Розділяє історію на частини оптимальної тривалості"""
        print(f"???? Розділення історії на TikTok частини...")
        
        sentence_pattern = r'(?<=[.!?])\s+'
        sentences = re.split(sentence_pattern, story_text.strip())
        
        clean_sentences = []
        for sentence in sentences:
            sentence = sentence.strip()
            if sentence and len(sentence) > 5:
                if not sentence[-1] in '.!?':
                    sentence += '.'
                clean_sentences.append(sentence)
        
        if not clean_sentences:
            paragraphs = story_text.split('\n\n')
            clean_sentences = [p.strip() for p in paragraphs if p.strip()]
        
        if not clean_sentences:
            words = story_text.split()
            clean_sentences = [' '.join(words[i:i+15]) + '.' for i in range(0, len(words), 15)]
        
        print(f"???? Знайдено {len(clean_sentences)} речень для розділення")
        
        preview_audio = self.tts_func(preview_text)
        preview_duration = preview_audio.duration_seconds + 0.5
        
        parts = []
        current_sentences = []
        current_duration = 0
        part_number = 1
        
        for i, sentence in enumerate(clean_sentences):
            words_count = len(sentence.split())
            chars_count = len(sentence)
            sentence_duration = max(words_count / 2.5, chars_count / 12.0)
            
            current_sentences.append(sentence)
            current_duration += sentence_duration
            
            base_duration = preview_duration if part_number == 1 else 0
            total_duration = current_duration + base_duration
            
            is_last_sentence = (i == len(clean_sentences) - 1)
            exceeds_target = total_duration >= self.target_part_duration
            meets_minimum = total_duration >= self.min_part_duration
            
            if (exceeds_target and meets_minimum) or is_last_sentence:
                part_text = ' '.join(current_sentences)
                parts.append({
                    'number': part_number,
                    'text': part_text,
                    'preview_text': preview_text if part_number == 1 else None,
                    'estimated_duration': total_duration,
                    'has_preview': part_number == 1,
                    'sentence_count': len(current_sentences)
                })
                
                duration_status = "✅" if total_duration >= self.min_part_duration else "⚠️"
                print(f"  {duration_status} Частина {part_number}: {len(current_sentences)} речень, {total_duration:.1f}с")
                
                part_number += 1
                current_sentences = []
                current_duration = 0
        
        print(f"✅ Історія розділена на {len(parts)} частин")
        return parts

    def merge_short_parts(self, parts):
        """Об'єднує тільки критично короткі частини (менше 30 секунд)"""
        print(f"???? Об'єднання критично коротких частин (менше 30с)...")
        
        merged_parts = []
        i = 0
        critical_min_duration = 30
        
        while i < len(parts):
            current_part = parts[i].copy()
            original_number = current_part['number']
            
            if current_part['estimated_duration'] < critical_min_duration and i + 1 < len(parts):
                next_part = parts[i + 1]
                current_part['text'] += ' ' + next_part['text']
                
                words_count = len(current_part['text'].split())
                chars_count = len(current_part['text'])
                new_duration = max(words_count / 2.5, chars_count / 12.0)
                
                if current_part['has_preview']:
                    preview_audio = self.tts_func(current_part['preview_text'])
                    preview_duration = preview_audio.duration_seconds + 0.5
                    new_duration += preview_duration
                
                current_part['estimated_duration'] = new_duration
                current_part['sentence_count'] = current_part.get('sentence_count', 1) + next_part.get('sentence_count', 1)
                
                if not current_part['has_preview'] and next_part['has_preview']:
                    current_part['preview_text'] = next_part['preview_text']
                    current_part['has_preview'] = True
                
                i += 1
                print(f"  ???? Об'єднано критично короткі частини {original_number} і {next_part['number']} → {new_duration:.1f}с")
            
            current_part['number'] = len(merged_parts) + 1
            merged_parts.append(current_part)
            i += 1
        
        print(f"✅ Після об'єднання: {len(merged_parts)} частин")
        return merged_parts

    def create_background_segments_tiktok(self, story_id, part_number, duration):
        """Створює фонові відео сегменти для TikTok частини з налаштованими параметрами"""
        bg_videos = list(self.source_folder.glob("*.mp4"))
        if not bg_videos:
            raise ValueError("Не знайдено фонових відео!")
        
        random.shuffle(bg_videos)
        bg_iter = iter(bg_videos)
        clips = []
        duration_collected = 0
        
        while duration_collected < duration:
            try:
                bg_video = next(bg_iter)
            except StopIteration:
                bg_videos_reset = list(self.source_folder.glob("*.mp4"))
                random.shuffle(bg_videos_reset)
                bg_iter = iter(bg_videos_reset)
                bg_video = next(bg_iter)
            
            probe_cmd = [
                "ffprobe", "-v", "quiet",
                "-print_format", "json",
                "-show_format", "-show_streams",
                str(bg_video)
            ]
            
            try:
                probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True)
                probe_data = json.loads(probe_result.stdout)
                video_duration = float(probe_data['format']['duration'])
            except:
                clip = VideoFileClip(str(bg_video))
                video_duration = clip.duration
                clip.close()
            
            remaining_duration = duration - duration_collected
            use_duration = min(video_duration, remaining_duration)
            
            if use_duration <= 0:
                break
            
            clip_output = self.temp_folder / f"{story_id}_part{part_number}_bg_{len(clips):02d}.mp4"
            
            # Використовуємо налаштування якості та FPS
            base_filters = f"scale='max({self.tiktok_resolution[0]},iw*{self.tiktok_resolution[1]}/ih)':'max({self.tiktok_resolution[1]},ih*{self.tiktok_resolution[0]}/iw)',crop={self.tiktok_resolution[0]}:{self.tiktok_resolution[1]},fps={self.fps_settings['fps']}"
            fade_filters = self.apply_safe_fade_filters(use_duration, base_filters)
            
            subprocess.run([
                "ffmpeg", "-y",
                "-i", str(bg_video),
                "-ss", "0",
                "-t", str(use_duration),
                "-vf", fade_filters,
                "-an",
                "-c:v", "h264_nvenc",
                "-preset", self.quality_settings['preset'],
                "-b:v", self.quality_settings['video_bitrate'],
                "-r", str(self.fps_settings['fps']),
                str(clip_output)
            ], check=True, capture_output=True)
            
            clips.append(clip_output)
            duration_collected += use_duration
        
        return clips

    def apply_safe_fade_filters(self, use_dur, base_filters):
        """Застосовує fade фільтри безпечно"""
        fade_in_duration = min(self.fade_duration, use_dur * 0.3)
        fade_out_duration = min(self.fade_duration, use_dur * 0.3)
        
        if fade_in_duration + fade_out_duration > use_dur:
            fade_in_duration = use_dur * 0.4
            fade_out_duration = use_dur * 0.4
        
        fade_out_start = max(0, use_dur - fade_out_duration)
        
        fade_filters = []
        if fade_in_duration > 0.05:
            fade_filters.append(f"fade=t=in:st=0:d={fade_in_duration}")
        if fade_out_duration > 0.05 and fade_out_start > fade_in_duration:
            fade_filters.append(f"fade=t=out:st={fade_out_start}:d={fade_out_duration}")
        
        all_filters = [base_filters] + fade_filters
        return ",".join(all_filters)

    def create_preview_with_overlay_tiktok(self, story_id, part_number, preview_clips, horizontal_preview_path, preview_duration):
        """Створює preview відео з накладеним вертикальним preview зображенням для TikTok"""
        if len(preview_clips) > 1:
            preview_concat_list = self.temp_folder / f"{story_id}_part{part_number}_preview_concat.txt"
            with open(preview_concat_list, "w", encoding="utf-8") as f:
                for clip in preview_clips:
                    f.write(f"file '{clip.as_posix()}'\n")
            
            preview_combined = self.temp_folder / f"{story_id}_part{part_number}_preview_combined.mp4"
            subprocess.run([
                "ffmpeg", "-y",
                "-f", "concat",
                "-safe", "0",
                "-i", str(preview_concat_list),
                "-c:v", "h264_nvenc",
                "-b:v", self.quality_settings['video_bitrate'],
                "-preset", self.quality_settings['preset'],
                "-r", str(self.fps_settings['fps']),
                str(preview_combined)
            ], check=True, capture_output=True)
        else:
            preview_combined = preview_clips[0]
        
        preview_img_path = self.temp_folder / f"{story_id}_part{part_number}_preview_vertical.png"
        self.preview_generator.draw_vertical_preview(horizontal_preview_path, preview_img_path)
        
        preview_video = self.temp_folder / f"{story_id}_part{part_number}_preview_video.mp4"
        subprocess.run([
            "ffmpeg", "-y",
            "-i", str(preview_combined),
            "-i", str(preview_img_path),
            "-filter_complex",
            "[1:v]format=rgba[overlay];[0:v][overlay]overlay=(W-w)/2:(H-h)/2:format=auto",
            "-t", str(preview_duration),
            "-c:v", "h264_nvenc",
            "-preset", self.quality_settings['preset'],
            "-b:v", self.quality_settings['video_bitrate'],
            "-r", str(self.fps_settings['fps']),
            str(preview_video)
        ], check=True, capture_output=True)
        
        return preview_video

    def combine_tiktok_segments(self, story_id, part_number, preview_video, story_clips):
        """Об'єднує всі сегменти TikTok відео"""
        if preview_video:
            used_clips = [preview_video] + story_clips
        else:
            used_clips = story_clips
        
        concat_list = self.temp_folder / f"{story_id}_part{part_number}_concat.txt"
        with open(concat_list, "w", encoding="utf-8") as f:
            for clip in used_clips:
                f.write(f"file '{clip.as_posix()}'\n")
        
        combined_path = self.temp_folder / f"{story_id}_part{part_number}_combined.mp4"
        subprocess.run([
            "ffmpeg", "-y",
            "-f", "concat",
            "-safe", "0",
            "-i", str(concat_list),
            "-c:v", "h264_nvenc",
            "-b:v", self.quality_settings['video_bitrate'],
            "-preset", self.quality_settings['preset'],
            "-r", str(self.fps_settings['fps']),
            str(combined_path)
        ], check=True, capture_output=True)
        
        return combined_path

    def create_tiktok_part(self, story_part, story_id, horizontal_preview_path, selected_template, speed_settings=None):
        """Створює одну частину TikTok відео з правильною обробкою прискорення"""
        part_number = story_part['number']
        story_text = story_part['text']
        has_preview = story_part['has_preview']
        
        print(f"???? Створення TikTok частини {part_number}...")
        if speed_settings and speed_settings.get('enabled'):
            print(f"⚡ З прискоренням: {speed_settings}")
        print(f"???? Текст частини ({len(story_text)} символів): {story_text[:100]}...")
        
        if has_preview:
            preview_text = story_part['preview_text']
            audio_preview = self.tts_func(preview_text)
            audio_story = self.tts_func(story_text)
            
            audio_preview = self.trim_silence_from_audio(audio_preview)
            audio_story = self.trim_silence_from_audio(audio_story)
            
            final_audio = audio_preview + AudioSegment.silent(duration=500) + audio_story
            preview_duration = audio_preview.duration_seconds
        else:
            audio_story = self.tts_func(story_text)
            audio_story = self.trim_silence_from_audio(audio_story)
            final_audio = audio_story
            preview_duration = 0
        
        story_duration = audio_story.duration_seconds
        natural_duration = final_audio.duration_seconds
        
        print(f"⏱️ Натуральна тривалість аудіо: {natural_duration:.1f}с")
        
        is_accelerated = False
        speed_factor = 1.0
        
        if speed_settings and speed_settings.get('enabled'):
            if speed_settings.get('target_duration'):
                target_duration = speed_settings['target_duration']
                speed_factor = natural_duration / target_duration
                speed_factor = min(speed_factor, 4.0)
                print(f"???? Цільова тривалість: {target_duration}с, потрібне прискорення: {speed_factor:.2f}x")
            elif speed_settings.get('speed'):
                speed_factor = speed_settings['speed']
                print(f"⚡ Фіксоване прискорення: {speed_factor:.2f}x")
            
            if speed_factor > 1.0:
                is_accelerated = True
                print(f"✅ Буде застосовано прискорення {speed_factor:.2f}x")
        
        audio_path = self.temp_folder / f"{story_id}_part{part_number}_voice.wav"
        final_audio.export(audio_path, format="wav")
        
        total_duration = natural_duration
        print(f"⏱️ Тривалість частини {part_number}: {total_duration:.1f}с")
        
        preview_video = None
        if has_preview:
            preview_clips = self.create_background_segments_tiktok(story_id, part_number, preview_duration)
            preview_video = self.create_preview_with_overlay_tiktok(
                story_id, part_number, preview_clips, horizontal_preview_path, preview_duration
            )
        
        story_video_duration = story_duration
        story_clips = self.create_background_segments_tiktok(story_id, f"{part_number}_story", story_video_duration)
        
        combined_video = self.combine_tiktok_segments(story_id, part_number, preview_video, story_clips)
        
        return combined_video, audio_path, total_duration, is_accelerated, speed_factor

    def apply_subtitles_ass_tiktok(self, video_path, chunks, story_id, part_number, font_name="Komika Axis"):
        """Додає субтитри для TikTok відео"""
        if not chunks:
            return video_path
        
        print(f"???? Створення ASS субтитрів для частини {part_number}...")
        
        ass_path = self.temp_folder / f"{story_id}_part{part_number}_subtitles.ass"
        self.create_ass_subtitles_tiktok(chunks, ass_path, font_name)
        
        output_video = self.temp_folder / f"{story_id}_part{part_number}_with_subtitles.mp4"
        
        ass_path_escaped = str(ass_path).replace("\\", "/").replace(":", "\\:")
        
        cmd = [
            "ffmpeg", "-y",
            "-i", str(video_path),
            "-vf", f"ass='{ass_path_escaped}'",
            "-c:v", "h264_nvenc",
            "-preset", self.quality_settings['preset'],
            "-b:v", self.quality_settings['video_bitrate'],
            "-r", str(self.fps_settings['fps']),
            "-c:a", "copy",
            str(output_video)
        ]
        
        try:
            subprocess.run(cmd, check=True, capture_output=True, text=True)
            print(f"  ✅ Субтитри для частини {part_number} успішно додано")
            return output_video
        except subprocess.CalledProcessError as e:
            print(f"❌ Помилка при додаванні субтитрів для частини {part_number}: {e}")
            return video_path

    def create_ass_subtitles_tiktok(self, chunks, ass_path, font_name="Komika Axis"):
        """Створює ASS файл субтитрів для TikTok"""
        font_size = 28
        margin_v = 250
        margin_lr = 100
        
        ass_content = f"""[Script Info]
Title: TikTok Subtitles
ScriptType: v4.00+

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,{font_name},{font_size},&H00FFFFFF,&H000000FF,&H00000000,&H80000000,1,0,0,0,100,100,0,0,1,3,0,5,{margin_lr},{margin_lr},{margin_v},1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
        
        for chunk in chunks:
            start_time = self.format_ass_time(chunk["start"])
            end_time = self.format_ass_time(chunk["end"])
            text = chunk["text"].replace("\\", "\\\\").replace("{", "\\{").replace("}", "\\}")
            
            words = text.split()
            lines = []
            current_line = ""
            max_chars = 20
            
            for word in words:
                if len(word) > max_chars:
                    if current_line:
                        lines.append(current_line.strip())
                        current_line = ""
                    while len(word) > max_chars:
                        lines.append(word[:max_chars-1] + "-")
                        word = word[max_chars-1:]
                    if word:
                        current_line = word
                elif len(current_line + " " + word) <= max_chars:
                    current_line += (" " + word if current_line else word)
                else:
                    if current_line:
                        lines.append(current_line.strip())
                    current_line = word
            
            if current_line:
                lines.append(current_line.strip())
            
            text = "\\N".join(lines)
            ass_content += f"Dialogue: 0,{start_time},{end_time},Default,,0,0,0,,{text}\n"
        
        with open(ass_path, 'w', encoding='utf-8-sig') as f:
            f.write(ass_content)

    def format_ass_time(self, seconds):
        """Конвертує секунди в формат часу ASS (H:MM:SS.CC)"""
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = seconds % 60
        return f"{hours}:{minutes:02d}:{secs:05.2f}"

    def chunk_words(self, words, n=2):
        """Групує слова для субтитрів"""
        chunks = []
        i = 0
        while i < len(words):
            group = words[i:i+n]
            text = " ".join([w["word"] for w in group])
            start = group[0]["start"]
            end = group[-1]["end"]
            chunks.append({"text": text, "start": start, "end": end})
            i += n
        return chunks

    def finalize_tiktok_video(self, combined_video, audio_path, output_path, apply_speedup=False, speed_factor=1.0):
        """Фінальна збірка TikTok відео з правильною обробкою прискорення"""
        temp_output = self.temp_folder / f"temp_final_{random.randint(1000, 9999)}.mp4"
        
        print(f"???? Фінальна збірка TikTok відео...")
        
        audio_segment = AudioSegment.from_wav(audio_path)
        audio_duration = audio_segment.duration_seconds
        
        print(f"⏱️ Тривалість аудіо: {audio_duration:.1f}с")
        
        try:
            subprocess.run([
                "ffmpeg", "-y",
                "-i", str(combined_video),
                "-i", str(audio_path),
                "-t", str(audio_duration),
                "-c:v", "h264_nvenc",
                "-preset", self.quality_settings['preset'],
                "-b:v", self.quality_settings['video_bitrate'],
                "-r", str(self.fps_settings['fps']),
                "-c:a", "aac",
                "-b:a", self.quality_settings['audio_bitrate'],
                "-shortest",
                "-movflags", "+faststart",
                str(temp_output)
            ], check=True, capture_output=True)
            
            if apply_speedup and speed_factor > 1.0:
                print(f"⚡ Застосування прискорення {speed_factor:.2f}x...")
                self.apply_video_speedup(temp_output, output_path, speed_factor)
                temp_output.unlink()
            else:
                shutil.move(str(temp_output), str(output_path))
            
            try:
                final_clip = VideoFileClip(str(output_path))
                final_duration = final_clip.duration
                final_clip.close()
                expected_duration = audio_duration / speed_factor if apply_speedup else audio_duration
                print(f"✅ Фінальна тривалість відео: {final_duration:.1f}с (очікувалось: {expected_duration:.1f}с)")
                if abs(final_duration - expected_duration) > 1.0:
                    print(f"⚠️ УВАГА: Різниця в тривалості більше 1 секунди!")
            except Exception as e:
                print(f"⚠️ Не вдалося перевірити тривалість відео: {e}")
                
        except subprocess.CalledProcessError as e:
            print(f"❌ Помилка при фінальній збірці: {e}")
            raise
        finally:
            if temp_output.exists():
                temp_output.unlink()


# Допоміжні функції
def safe_filename(name):
    """Створює безпечну назву файлу"""
    name = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", name)
    name = re.sub(r'\s+','_', name).strip("_")
    name = name.rstrip(". ")
    return name[:50]

def detect_censored_words(text):
    """Знаходить слова з зірочками для цензури"""
    pattern = r'\b\w*\*+\w*\b'
    return re.findall(pattern, text)

def split_text_with_censoring(text):
    """Розділяє текст на частини, виділяючи цензуровані слова"""
    parts = []
    pattern = r'(\b\w*\*+\w*\b)'
    split_parts = re.split(pattern, text)
    
    for part in split_parts:
        if part.strip():
            if re.match(r'\b\w*\*+\w*\b', part):
                parts.append({"text": part, "is_censored": True})
            else:
                parts.append({"text": part, "is_censored": False})
    
    return parts

def generate_beep_sound(beep_sound_path):
    """Генерує звук запікування якщо файл не існує"""
    if not beep_sound_path.exists():
        print("???? Генерація звуку запікування...")
        beep = AudioSegment.sine(1000, duration=800)
        beep = beep - 10
        beep.export(beep_sound_path, format="wav")
        print("✅ Звук запікування створено")

def create_tts_with_censoring(tts, temp_folder, beep_sound_path, voice):
    """Створює функцію TTS з підтримкою цензури"""
    def trim_silence_from_audio_standalone(audio_segment, silence_threshold_db=-40, chunk_size_ms=100):
        if len(audio_segment) == 0:
            return audio_segment
        
        start_trim = 0
        for i in range(0, len(audio_segment), chunk_size_ms):
            chunk = audio_segment[i:i + chunk_size_ms]
            if chunk.dBFS > silence_threshold_db:
                start_trim = max(0, i - 200)
                break
        
        end_trim = len(audio_segment)
        for i in range(len(audio_segment) - chunk_size_ms, 0, -chunk_size_ms):
            if i < 0:
                break
            chunk = audio_segment[i:i + chunk_size_ms]
            if chunk.dBFS > silence_threshold_db:
                end_trim = min(len(audio_segment), i + chunk_size_ms + 200)
                break
        
        if start_trim < end_trim:
            trimmed = audio_segment[start_trim:end_trim]
            removed_start = start_trim / 1000.0
            removed_end = (len(audio_segment) - end_trim) / 1000.0
            if removed_start > 0.2 or removed_end > 0.2:
                print(f"✂️ Видалено мовчання: початок {removed_start:.1f}с, кінець {removed_end:.1f}с")
            return trimmed
        else:
            return audio_segment

    def coqui_tts_simple(text: str) -> AudioSegment:
        text_hash = hashlib.md5(text.encode()).hexdigest()
        raw_path = temp_folder / f"raw_{text_hash}.wav"
        clean_path = temp_folder / f"audio_{text_hash}.wav"
        
        if clean_path.exists():
            audio = AudioSegment.from_wav(clean_path)
            return trim_silence_from_audio_standalone(audio)
        
        try:
            tts.tts_to_file(text=text, speaker=voice, file_path=raw_path)
            
            subprocess.run([
                "ffmpeg", "-y",
                "-i", str(raw_path),
                "-af", "afftdn,volume=3dB,silenceremove=1:0:-50dB:1:0.5:-50dB",
                str(clean_path)
            ], check=True, capture_output=True)
            
            audio = AudioSegment.from_wav(clean_path)
            return trim_silence_from_audio_standalone(audio)
        except Exception as e:
            print(f"❌ Помилка TTS: {e}")
            return AudioSegment.silent(duration=1000)

    def coqui_tts_with_censoring(text: str) -> AudioSegment:
        text_hash = hashlib.md5(text.encode()).hexdigest()
        final_audio_path = temp_folder / f"final_audio_{text_hash}.wav"
        
        if final_audio_path.exists():
            audio = AudioSegment.from_wav(final_audio_path)
            return trim_silence_from_audio_standalone(audio)
        
        censored_words = detect_censored_words(text)
        if not censored_words:
            return coqui_tts_simple(text)
        
        print(f"???? Знайдено цензуровані слова: {censored_words}")
        generate_beep_sound(beep_sound_path)
        beep_audio = AudioSegment.from_wav(beep_sound_path)
        
        text_parts = split_text_with_censoring(text)
        final_audio = AudioSegment.empty()
        
        for part in text_parts:
            if part["is_censored"]:
                print(f"  ???? Цензура: {part['text']} → *БІП*")
                final_audio += beep_audio
            else:
                text_content = part["text"].strip()
                if text_content:
                    part_audio = coqui_tts_simple(text_content)
                    final_audio += part_audio
        
        if len(final_audio) == 0:
            print("⚠️ Порожнє аудіо після цензури, використовуємо оригінальний TTS")
            return coqui_tts_simple(text)
        
        final_audio = trim_silence_from_audio_standalone(final_audio)
        final_audio.export(final_audio_path, format="wav")
        return final_audio
    
    return coqui_tts_with_censoring

def get_interactive_speed_settings_tiktok(part_number, original_duration):
    """Запитує у користувача налаштування прискорення для конкретної TikTok частини"""
    print(f"\n???? Налаштування для TikTok частини {part_number}")
    print(f"⏱️ Поточна тривалість: {original_duration:.1f} секунд")
    
    choice = input("Прискорювати цю частину? (y/n): ").lower()
    
    if choice != 'y':
        return {'enabled': False, 'speed': 1.0, 'target_duration': None}
    
    print("Виберіть спосіб:")
    print("1. Фіксоване прискорення (наприклад, 1.5x)")
    print("2. До цільової тривалості")
    
    while True:
        method = input("Ваш вибір (1-2): ").strip()
        if method in ["1", "2"]:
            break
        print("❌ Введіть 1 або 2")
    
    if method == "1":
        while True:
            try:
                speed = float(input("Коефіцієнт прискорення (1.0-4.0): "))
                if 1.0 <= speed <= 4.0:
                    break
                print("❌ Коефіцієнт має бути від 1.0 до 4.0")
            except ValueError:
                print("❌ Введіть дійсне число!")
        
        final_duration = original_duration / speed
        print(f"✅ Швидкість {speed}x, фінальна тривалість: {final_duration:.1f}с")
        return {'enabled': True, 'speed': speed, 'target_duration': None}
    else:
        while True:
            try:
                target = float(input("Цільова тривалість (15-300с): "))
                if 15 <= target <= 300:
                    break
                print("❌ Тривалість має бути від 15 до 300 секунд")
            except ValueError:
                print("❌ Введіть дійсне число!")
        
        required_speed = original_duration / target
        if required_speed > 4.0:
            print(f"⚠️ Потрібне прискорення {required_speed:.1f}x перевищує максимум 4.0x")
            required_speed = 4.0
            final_duration = original_duration / required_speed
            print(f"Буде використано максимальне прискорення 4.0x, тривалість: {final_duration:.1f}с")
        else:
            print(f"✅ Прискорення {required_speed:.1f}x до {target}с")
        
        return {'enabled': True, 'speed': required_speed, 'target_duration': target}

def get_tiktok_duration_settings():
    """Дозволяє користувачу налаштувати мінімальну довжину TikTok відео"""
    print("\n" + "="*60)
    print("???? НАЛАШТУВАННЯ ТРИВАЛОСТІ TIKTOK ВІДЕО")
    print("="*60)
    print("Налаштуйте мінімальну тривалість частин TikTok відео:")
    print("• Рекомендовано: 30-60 секунд")
    print("• Мінімум: 15 секунд")
    print("• Максимум: 300 секунд (5 хвилин)")
    print("="*60)
    
    while True:
        try:
            min_duration_input = input("Введіть мінімальну тривалість частини (секунди, за замовчуванням 30): ").strip()
            if not min_duration_input:
                min_duration = 30
                break
            
            min_duration = int(min_duration_input)
            if min_duration < 15:
                print("❌ Мінімальна тривалість не може бути менше 15 секунд!")
                continue
            elif min_duration > 300:
                print("❌ Мінімальна тривалість не може бути більше 300 секунд!")
                continue
            else:
                break
        except ValueError:
            print("❌ Введіть число!")
    
    target_duration = min(min_duration + 40, min_duration * 1.5)
    target_duration = max(target_duration, min_duration + 10)
    
    print(f"✅ Налаштування TikTok відео:")
    print(f"   • Мінімальна тривалість частини: {min_duration} секунд")
    print(f"   • Цільова тривалість частини: {target_duration:.0f} секунд")
    
    return min_duration, int(target_duration)

def process_tiktok_videos(tiktok_stories_dir, tiktok_generator, horizontal_generator, 
                          default_template, template_mode, preview_generator, 
                          tiktok_tts, asr_model, speed_config):
    """Обробляє TikTok відео з підтримкою прискорення"""
    story_files = list(tiktok_stories_dir.glob("*.txt"))
    story_files = [f for f in story_files if not f.name.endswith("_preview.txt") and not f.name.endswith("_description.txt")]
    
    if not story_files:
        print(f"❌ TikTok історій не знайдено в {tiktok_stories_dir}")
        return 0
    
    print(f"???? Знайдено {len(story_files)} TikTok історій для обробки")
    
    processed_count = 0
    
    for i, story in enumerate(story_files, 1):
        print(f"\n{'='*60}")
        print(f"???? [{i}/{len(story_files)}] Обробка TikTok історії → {story.name}")
        print(f"{'='*60}")
        
        try:
            from test_ffmpeg import select_template_for_video, process_tiktok_story
            
            selected_template = select_template_for_video(template_mode, default_template, preview_generator)
            if selected_template is None:
                print("❌ Template не вибрано, пропускаємо відео")
                continue
            
            parts_created = process_tiktok_story(
                story,
                tiktok_generator,
                horizontal_generator,
                selected_template,
                tiktok_tts,
                asr_model,
                speed_config
            )
            
            if parts_created:
                processed_count += 1
                
                time.sleep(2)
                mouse.move(random.randint(0, 1920), random.randint(0, 1080), duration=1)
                time.sleep(2)
        
        except Exception as e:
            print(f"❌ Критична помилка з {story.name}: {e}")
            import traceback
            traceback.print_exc()
    
    print(f"\n✅ TikTok обробка завершена!")
    print(f"???? Оброблено історій: {processed_count}")
    
    return processed_count
Made on
Tilda