# test_ffmpeg.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
from horizontal_video import HorizontalVideoGenerator
from vertical_video import VerticalVideoGenerator
from tiktok_processor import TikTokVideoGenerator, process_tiktok_videos, create_tts_with_censoring, get_tiktok_duration_settings


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 select_background_folder(backgrounds_dir):
  """Дозволяє користувачу вибрати папку з фоновими відео"""
  print("\n" + "="*60)
  print("🎬 ВИБІР ФОНОВИХ ВІДЕО")
  print("="*60)

  # Знаходимо всі папки в директорії backgrounds
  bg_folders = [f for f in backgrounds_dir.iterdir() if f.is_dir()]

  if not bg_folders:
    print("❌ Папки з фоновими відео не знайдено!")
    print(f"Створіть папки в: {backgrounds_dir}")
    print("Наприклад: minecraft, cakes, future, тощо")
    return None

  # Показуємо доступні папки
  for i, folder in enumerate(bg_folders, 1):
    # Підрахунок відео в папці
    video_files = list(folder.glob("*.mp4")) + list(folder.glob("*.mov")) + list(folder.glob("*.avi"))
    print(f"{i}. 📁 {folder.name} ({len(video_files)} відео)")

  print("="*60)

  while True:
    try:
      choice = input(f"Виберіть папку (1-{len(bg_folders)}): ").strip()
      choice_idx = int(choice) - 1

      if 0 <= choice_idx < len(bg_folders):
        selected_folder = bg_folders[choice_idx]

        # Перевіряємо чи є відео в папці
        video_files = list(selected_folder.glob("*.mp4")) + list(selected_folder.glob("*.mov")) + list(selected_folder.glob("*.avi"))
        if not video_files:
          print(f"❌ У папці {selected_folder.name} немає відео файлів!")
          continue

        print(f"✅ Вибрано папку: {selected_folder.name} ({len(video_files)} відео)")
        return selected_folder
      else:
        print(f"❌ Невірний вибір. Введіть число від 1 до {len(bg_folders)}")

    except ValueError:
      print(f"❌ Невірний вибір. Введіть число від 1 до {len(bg_folders)}")


def chunk_words(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 create_ass_subtitles(chunks, ass_path, font_path=None, font_size=48, vertical=False):
  """Створює ASS файл субтитрів з центрованим текстом та обводкою"""

  font_name = "Komika Axis"

  if vertical:
    font_size = 28
    margin_v = 250
    margin_lr = 100
  else:
    margin_v = 0
    margin_lr = 30

  ass_content = f"""[Script Info]
Title: Generated 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 = format_ass_time(chunk["start"])
    end_time = format_ass_time(chunk["end"])

    text = chunk["text"].replace("\\", "\\\\").replace("{", "\\{").replace("}", "\\}")

    if vertical:
      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(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 apply_subtitles_ass(video_path, chunks, story_id, vertical=False, temp_folder=None):
  """Додає субтитри використовуючи ASS файл"""
  if not chunks:
    return video_path

  print(f"🔤 Створення ASS субтитрів для {len(chunks)} фрагментів...")

  suffix = "_vertical" if vertical else ""
  ass_path = temp_folder / f"{story_id}_subtitles{suffix}.ass"
  create_ass_subtitles(chunks, ass_path, font_size=56, vertical=vertical)

  output_video = temp_folder / f"{story_id}_with_subtitles{suffix}.mp4"

  # Escape шлях для Windows
  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", "p4", "-b:v", "5M",
    "-c:a", "copy",
    str(output_video)
  ]

  try:
    print(f" 📝 Додавання субтитрів...")
    subprocess.run(cmd, check=True, capture_output=True, text=True)
    print(f" ✅ Субтитри успішно додано")
    return output_video
  except subprocess.CalledProcessError as e:
    print(f"❌ Помилка при додаванні субтитрів: {e}")
    return video_path


def process_youtube_story(story_path, horizontal_generator, vertical_generator, selected_template, coqui_tts, asr_model, temp_folder, final_dir):
  """Обробляє YouTube історію (горизонтальне + вертикальне відео)"""
  story_txt = story_path.read_text(encoding="utf-8").strip()
  preview_path = story_path.with_name(f"{story_path.stem}_preview.txt")
  description_path = story_path.with_name(f"{story_path.stem}_description.txt")

  if not preview_path.exists() or not description_path.exists():
    print(f"❌ Не знайдено preview або description для {story_path.name}")
    return

  preview_txt = preview_path.read_text(encoding="utf-8").strip()
  description_txt = description_path.read_text(encoding="utf-8").strip()

  if not story_txt or not preview_txt or not description_txt:
    print(f"❌ Порожні файли для {story_path.name}")
    return

  story_id = hashlib.md5(story_txt.encode()).hexdigest()
  video_title = safe_filename(preview_txt)
  output_dir = final_dir / video_title
  output_dir.mkdir(parents=True, exist_ok=True)

  print(f"🎨 Використовується template: {selected_template}")

  # === ГОРИЗОНТАЛЬНЕ ВІДЕО ===
  print(f"🎵 Генерація аудіо для горизонтального відео...")
  audio_preview = coqui_tts(preview_txt)
  audio_story = coqui_tts(story_txt)
  final_audio = audio_preview + AudioSegment.silent(duration=500) + audio_story
  audio_path = temp_folder / f"{story_id}_voice.wav"
  final_audio.export(audio_path, format="wav")

  # Створюємо горизонтальне відео
  combined_video, preview_img_path = horizontal_generator.create_horizontal_video(
    story_id, preview_txt, audio_preview, audio_story, selected_template
  )

  print(f"🔤 Розпізнавання мовлення...")
  # === Субтитри ===
  story_offset = audio_preview.duration_seconds + 0.5
  segments, _ = asr_model.transcribe(str(audio_path), word_timestamps=True)
  words = [{"word": w.word.strip(), "start": w.start, "end": w.end} for seg in segments for w in seg.words if w.start >= story_offset]
  chunks = chunk_words(words)

  if not chunks:
    print("⚠️ Субтитри не знайдено")
    final_video = combined_video
  else:
    final_video = apply_subtitles_ass(combined_video, chunks, story_id, temp_folder=temp_folder)

  # Фінальна збірка горизонтального відео
  final_path = output_dir / f"{video_title}.mp4"
  horizontal_generator.finalize_video(final_video, audio_path, final_path)

  # === ВЕРТИКАЛЬНЕ ВІДЕО (SHORTS) ===
  try:
    print(f"📱 Створення Shorts відео...")
    combined_shorts_video, shorts_audio_path = vertical_generator.create_shorts_video(
      story_txt, preview_txt, story_id, preview_img_path, coqui_tts
    )

    # Розпізнавання мовлення для Shorts
    print(f"🔤 Розпізнавання мовлення для Shorts...")
    segments_shorts, _ = asr_model.transcribe(str(shorts_audio_path), word_timestamps=True)

    # Створюємо субтитри тільки для story та ending частин
    audio_preview_shorts = coqui_tts(preview_txt)
    story_start_time = audio_preview_shorts.duration_seconds + 0.5

    story_words = []
    for seg in segments_shorts:
      for w in seg.words:
        if w.start >= story_start_time:
          story_words.append({"word": w.word.strip(), "start": w.start, "end": w.end})

    chunks_shorts = chunk_words(story_words)

    if chunks_shorts:
      final_shorts_video = apply_subtitles_ass(combined_shorts_video, chunks_shorts, f"{story_id}_shorts", vertical=True, temp_folder=temp_folder)
    else:
      print("⚠️ Субтитри не знайдено для Shorts")
      final_shorts_video = combined_shorts_video

    # Фінальна збірка Shorts відео
    shorts_path = output_dir / f"short_{video_title}.mp4"
    vertical_generator.finalize_shorts_video(final_shorts_video, shorts_audio_path, shorts_path)
    print(f"📱 Shorts відео створено: {shorts_path}")

  except Exception as e:
    print(f"❌ Помилка при створенні Shorts відео: {e}")

  # Зберігаємо файли
  Image.open(preview_img_path).convert("RGB").save(output_dir / "preview.jpg", "JPEG")
  (output_dir / "description.txt").write_text(description_txt, encoding="utf-8")
  for f in [story_path, preview_path, description_path]:
    f.write_text("", encoding="utf-8")

  print(f"\n✅ {story_path.name} → {final_path}")
  time.sleep(2)
  mouse.move(random.randint(0, 1920), random.randint(0, 1080), duration=1)
  time.sleep(2)

  # Надсилаємо превью в Telegram
  try:
    print(f"📤 Надсилання превью в Telegram...")
    preview_jpg_path = output_dir / "preview.jpg"
    asyncio.run(send_video_preview(preview_txt, str(preview_jpg_path)))
    print(f"✅ Превью успішно надіслано в Telegram")
  except Exception as e:
    print(f"❌ Помилка при надсиланні превью: {e}")


def select_video_type():
  """Дозволяє користувачу вибрати тип відео для генерації"""
  print("\n" + "="*60)
  print("🎬 ВИБІР ТИПУ ВІДЕО")
  print("="*60)
  print("1. 📺 YouTube відео (горизонтальне + shorts)")
  print("2. 📱 TikTok відео (розділені на частини)")
  print("3. 🔄 Обидва типи")
  print("="*60)

  while True:
    choice = input("Ваш вибір (1-3): ").strip()
    if choice in ["1", "2", "3"]:
      return choice
    print("❌ Невірний вибір. Введіть 1, 2 або 3")


def select_template_for_video(template_mode, default_template, preview_generator):
  """Вибирає template для поточного відео залежно від режиму"""
  if template_mode == "fixed":
    return default_template
  elif template_mode == "interactive":
    template_name, _ = preview_generator.select_template()
    return template_name
  else: # auto
    return default_template


def main():
  """Головна функція з вибором типу відео"""

  # Запитуємо про вимкнення ПК
  off = int(input("Введіть 1 щоб вимкнути ПК після завершення скрипта: "))

  # --- Шляхи (визначаємо рано для вибору фонових відео)
  ROOT = Path(r"K:/test_ffmpeg_drama")
  BACKGROUNDS_DIR = ROOT / "backgrounds"
  STORIES_DIR = ROOT / "stories"
  TIKTOK_STORIES_DIR = ROOT / "tiktok_stories"
  TEMP_FOLDER = ROOT / "temp"
  TEMP_TIKTOK_FOLDER = ROOT / "temp_tiktok"
  FINAL_DIR = ROOT / "final_videos"
  FINAL_TIKTOK_DIR = ROOT / "final_tiktok_videos"
  TEMPLATES_DIR = ROOT / "templates"
  FONT_PREVIEW = ROOT / "fonts/NotoSans-ExtraBold.ttf"
  SUB_FONT_PATH = ROOT / "fonts/KOMIKAX_.ttf"
  BEEP_SOUND_PATH = ROOT / "beep.wav"

  # --- Ініціалізація папок
  BACKGROUNDS_DIR.mkdir(parents=True, exist_ok=True)
  STORIES_DIR.mkdir(parents=True, exist_ok=True)
  TIKTOK_STORIES_DIR.mkdir(parents=True, exist_ok=True)
  FINAL_DIR.mkdir(parents=True, exist_ok=True)
  FINAL_TIKTOK_DIR.mkdir(parents=True, exist_ok=True)
  TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)

  # --- Вибір папки з фоновими відео
  SOURCE_FOLDER = select_background_folder(BACKGROUNDS_DIR)
  if SOURCE_FOLDER is None:
    print("❌ Неможливо продовжити без фонових відео")
    return

  # Вибір типу відео
  video_type = select_video_type()

  # Налаштування TikTok тривалості (якщо потрібно)
  tiktok_min_duration = 63
  tiktok_target_duration = 105

  if video_type in ["2", "3"]: # TikTok або обидва
    tiktok_min_duration, tiktok_target_duration = get_tiktok_duration_settings()

  # --- Параметри
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
  if DEVICE == "cuda":
    torch.cuda.empty_cache()
    torch.backends.cudnn.benchmark = True
    torch.set_float32_matmul_precision("high")
    torch.set_default_device(DEVICE)

  RESOLUTION = (1920, 1080)
  SHORTS_RESOLUTION = (1080, 1920)
  TIKTOK_RESOLUTION = (1080, 1920)
  FADE_DURATION = 1
  SHORTS_TARGET_DURATION = 59

  # Очищуємо temp папки залежно від типу відео
  if video_type in ["1", "3"]: # YouTube або обидва
    shutil.rmtree(TEMP_FOLDER, ignore_errors=True)
    TEMP_FOLDER.mkdir(parents=True, exist_ok=True)

  if video_type in ["2", "3"]: # TikTok або обидва
    shutil.rmtree(TEMP_TIKTOK_FOLDER, ignore_errors=True)
    TEMP_TIKTOK_FOLDER.mkdir(parents=True, exist_ok=True)

  # --- Ініціалізація TTS та ASR
  MODEL = "tts_models/en/vctk/vits"
  VOICE = "p312"
  tts = TTS(model_name=MODEL, progress_bar=False)
  ASR_MODEL = WhisperModel("small", device=DEVICE, compute_type="float16" if DEVICE == "cuda" else "int8")

  # --- Ініціалізація генераторів
  try:
    preview_generator = PreviewGenerator(
      templates_dir=TEMPLATES_DIR,
      font_path=FONT_PREVIEW,
      shorts_resolution=SHORTS_RESOLUTION
    )

    # Питаємо користувача який template використовувати
    print("\n🎨 Налаштування template для відео:")
    print("Ви можете:")
    print("1. Вибрати один template для всіх відео")
    print("2. Вибирати template для кожного відео окремо")
    print("3. Використовувати перший доступний template")

    template_choice = input("Ваш вибір (1-3): ").strip()

    if template_choice == "1":
      DEFAULT_TEMPLATE = preview_generator.select_template()[0]
      TEMPLATE_MODE = "fixed"
      print(f"✅ Вибрано template: {DEFAULT_TEMPLATE}")
    elif template_choice == "2":
      DEFAULT_TEMPLATE = None
      TEMPLATE_MODE = "interactive"
    else:
      DEFAULT_TEMPLATE = list(preview_generator.templates.keys())[0]
      TEMPLATE_MODE = "auto"
      print(f"✅ Буде використовуватись template: {DEFAULT_TEMPLATE}")

  except FileNotFoundError as e:
    print(f"❌ Помилка з template: {e}")
    # Створюємо приклад template
    example_template = TEMPLATES_DIR / "template_default_x40_y224_w1837_h751.png"
    if not example_template.exists():
      example_img = Image.new('RGBA', (1920, 1080), (255, 255, 255, 0))
      example_img.save(example_template)

    preview_generator = PreviewGenerator(
      templates_dir=TEMPLATES_DIR,
      font_path=FONT_PREVIEW,
      shorts_resolution=SHORTS_RESOLUTION
    )
    DEFAULT_TEMPLATE = "template_default_x40_y224_w1837_h751"
    TEMPLATE_MODE = "auto"

  # --- Створення TTS функції з цензурою
  coqui_tts = create_tts_with_censoring(tts, TEMP_FOLDER, BEEP_SOUND_PATH, VOICE)

  # --- Ініціалізація генераторів відео
  generators = {}

  if video_type in ["1", "3"]: # YouTube або обидва
    generators['horizontal'] = HorizontalVideoGenerator(
      source_folder=SOURCE_FOLDER,
      temp_folder=TEMP_FOLDER,
      preview_generator=preview_generator,
      resolution=RESOLUTION,
      fade_duration=FADE_DURATION
    )

    generators['vertical'] = VerticalVideoGenerator(
      source_folder=SOURCE_FOLDER,
      temp_folder=TEMP_FOLDER,
      preview_generator=preview_generator,
      shorts_resolution=SHORTS_RESOLUTION,
      target_duration=SHORTS_TARGET_DURATION
    )

  if video_type in ["2", "3"]: # TikTok або обидва
    # Для TikTok використовуємо окрему TTS функцію з правильною temp папкою
    tiktok_tts = create_tts_with_censoring(tts, TEMP_TIKTOK_FOLDER, BEEP_SOUND_PATH, VOICE)

    generators['tiktok'] = TikTokVideoGenerator(
      source_folder=SOURCE_FOLDER,
      temp_folder=TEMP_TIKTOK_FOLDER,
      preview_generator=preview_generator,
      tts_func=tiktok_tts,
      asr_model=ASR_MODEL,
      tiktok_resolution=TIKTOK_RESOLUTION,
      target_part_duration=tiktok_target_duration,
      min_part_duration=tiktok_min_duration
    )

  # --- Обробка відео
  if video_type == "1": # Тільки YouTube
    print(f"\n🎬 РЕЖИМ: Тільки YouTube відео")
    print(f"🎥 Використовуються фонові відео з: {SOURCE_FOLDER.name}")
    process_youtube_videos(STORIES_DIR, generators, DEFAULT_TEMPLATE, TEMPLATE_MODE, preview_generator, coqui_tts, ASR_MODEL, TEMP_FOLDER, FINAL_DIR)

  elif video_type == "2": # Тільки TikTok
    print(f"\n📱 РЕЖИМ: Тільки TikTok відео")
    print(f"🎥 Використовуються фонові відео з: {SOURCE_FOLDER.name}")
    print(f"⚙️ Налаштування: мінімум {tiktok_min_duration}с, ціль {tiktok_target_duration}с")
    process_tiktok_videos(TIKTOK_STORIES_DIR, generators['tiktok'], generators.get('horizontal'), DEFAULT_TEMPLATE, TEMPLATE_MODE, preview_generator, tiktok_tts, ASR_MODEL)

  elif video_type == "3": # Обидва типи
    print(f"\n🎬📱 РЕЖИМ: YouTube + TikTok відео")
    print(f"🎥 Використовуються фонові відео з: {SOURCE_FOLDER.name}")
    print(f"⚙️ TikTok налаштування: мінімум {tiktok_min_duration}с, ціль {tiktok_target_duration}с")

    # Спочатку YouTube
    youtube_processed = process_youtube_videos(STORIES_DIR, generators, DEFAULT_TEMPLATE, TEMPLATE_MODE, preview_generator, coqui_tts, ASR_MODEL, TEMP_FOLDER, FINAL_DIR)

    # Потім TikTok
    tiktok_processed = process_tiktok_videos(TIKTOK_STORIES_DIR, generators['tiktok'], generators.get('horizontal'), DEFAULT_TEMPLATE, TEMPLATE_MODE, preview_generator, tiktok_tts, ASR_MODEL)

    print(f"\n🎉 Загальний підсумок:")
    print(f"📺 YouTube відео оброблено: {youtube_processed}")
    print(f"📱 TikTok відео оброблено: {tiktok_processed}")

  print(f"\n🎬 Всі відео оброблено!")

  # Вимкнення ПК
  if off == 1:
    print("💤 Вимкнення комп'ютера через 3 хвилини...")
    time.sleep(180)
    os.system("shutdown /s /t 1")
  else:
    print("🎉 Генератор відео завершив роботу успішно!")


def process_youtube_videos(stories_dir, generators, default_template, template_mode, preview_generator, coqui_tts, asr_model, temp_folder, final_dir):
  """Обробляє YouTube відео"""
  story_files = list(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("❌ YouTube історій не знайдено")
    return 0

  print(f"📚 Знайдено {len(story_files)} YouTube історій для обробки")

  processed_count = 0
  for i, story in enumerate(story_files, 1):
    print(f"\n{'='*60}")
    print(f"📜 [{i}/{len(story_files)}] Обробка YouTube історії → {story.name}")
    print(f"{'='*60}")

    try:
      # Вибираємо template для цього відео
      selected_template = select_template_for_video(template_mode, default_template, preview_generator)
      if selected_template is None:
        print("❌ Template не вибрано, пропускаємо відео")
        continue

      process_youtube_story(
        story, generators['horizontal'], generators['vertical'],
        selected_template, coqui_tts, asr_model, temp_folder, final_dir
      )
      processed_count += 1

    except Exception as e:
      print(f"❌ Помилка з {story.name}: {e}")

  return processed_count


if __name__ == "__main__":
  try:
    main()
  except KeyboardInterrupt:
    print("\n⚠️ Скрипт перервано користувачем")
  except Exception as e:
    print(f"\n❌ Критична помилка: {e}")
    import traceback
    traceback.print_exc()
Made on
Tilda