Guest User

Untitled

a guest
Nov 20th, 2025
1,315
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 39.25 KB | None | 0 0
  1. import os
  2. import shutil
  3. import tkinter as tk
  4. from pathlib import Path
  5. from PIL import Image, ImageTk, ImageSequence
  6. import requests
  7. from bs4 import BeautifulSoup
  8. from datetime import datetime
  9. from fake_useragent import UserAgent
  10. import threading
  11. import queue
  12. import time
  13. import json
  14. from tkinter import simpledialog, messagebox
  15.  
  16. class CombinedPhotoApp:
  17. def __init__(self):
  18. # Инициализируем настройки ПЕРВЫМИ
  19. self.settings = {
  20. 'interesting_folder': "interesting_media",
  21. 'not_interesting_folder': "not_interesting_media",
  22. 'interesting_button_text': "Интересно",
  23. 'not_interesting_button_text': "Не интересно",
  24. 'skip_button_text': "Пропустить"
  25. }
  26.  
  27. self.load_settings()
  28.  
  29. # Теперь инициализируем остальные переменные
  30. self.setup_gui()
  31. self.download_queue = queue.Queue()
  32. self.current_media_path = None
  33. self.download_thread = None
  34. self.stop_download = False
  35. self.is_playing_gif = False
  36. self.gif_frames = []
  37. self.current_gif_frame = 0
  38.  
  39. # Видео переменные
  40. self.video_capture = None
  41. self.is_playing_video = False
  42. self.current_video_path = None
  43. self.video_duration = 0
  44. self.video_position = 0
  45. self.is_seeking = False
  46.  
  47. # Создаем папки для сортировки
  48. Path(self.settings['interesting_folder']).mkdir(exist_ok=True)
  49. Path(self.settings['not_interesting_folder']).mkdir(exist_ok=True)
  50. Path("temp_downloads").mkdir(exist_ok=True)
  51.  
  52. def load_settings(self):
  53. """Загружаем настройки из файла"""
  54. try:
  55. if os.path.exists("settings.json"):
  56. with open("settings.json", 'r', encoding='utf-8') as f:
  57. saved_settings = json.load(f)
  58. self.settings.update(saved_settings)
  59. print("Настройки загружены из settings.json")
  60. except Exception as e:
  61. print(f"Ошибка загрузки настроек: {e}")
  62.  
  63. def save_settings(self):
  64. """Сохраняем настройки в файл"""
  65. try:
  66. with open("settings.json", 'w', encoding='utf-8') as f:
  67. json.dump(self.settings, f, ensure_ascii=False, indent=2)
  68. print("Настройки сохранены в settings.json")
  69. except Exception as e:
  70. print(f"Ошибка сохранения настроек: {e}")
  71.  
  72. def setup_gui(self):
  73. """Создаем графический интерфейс"""
  74. self.root = tk.Tk()
  75. self.root.title("Парсинг и Сортировка Медиа")
  76. self.root.geometry("1000x900")
  77. self.root.configure(bg='#f0f0f0')
  78.  
  79. # Верхняя панель - настройки парсинга
  80. settings_frame = tk.LabelFrame(self.root, text="Настройки парсинга", font=('Arial', 12, 'bold'), bg='#f0f0f0')
  81. settings_frame.pack(pady=10, padx=20, fill='x')
  82.  
  83. # Поле для имени
  84. tk.Label(settings_frame, text="Name:", bg='#f0f0f0').grid(row=0, column=0, padx=5, pady=5, sticky='w')
  85. self.name_entry = tk.Entry(settings_frame, width=20)
  86. self.name_entry.grid(row=0, column=1, padx=5, pady=5)
  87.  
  88. # Поле для offset
  89. tk.Label(settings_frame, text="Offset:", bg='#f0f0f0').grid(row=0, column=2, padx=5, pady=5, sticky='w')
  90. self.offset_entry = tk.Entry(settings_frame, width=10)
  91. self.offset_entry.insert(0, "3")
  92. self.offset_entry.grid(row=0, column=3, padx=5, pady=5)
  93.  
  94. # Кнопки управления парсингом
  95. self.start_btn = tk.Button(settings_frame, text="Начать парсинг", command=self.start_parsing,
  96. bg='#4CAF50', fg='white', font=('Arial', 10, 'bold'))
  97. self.start_btn.grid(row=0, column=4, padx=10, pady=5)
  98.  
  99. self.stop_btn = tk.Button(settings_frame, text="Остановить", command=self.stop_parsing,
  100. bg='#f44336', fg='white', font=('Arial', 10), state='disabled')
  101. self.stop_btn.grid(row=0, column=5, padx=5, pady=5)
  102.  
  103. # Кнопка для досортировки
  104. self.resume_btn = tk.Button(settings_frame, text="Досортировать из temp", command=self.resume_sorting,
  105. bg='#FF9800', fg='white', font=('Arial', 10))
  106. self.resume_btn.grid(row=0, column=6, padx=5, pady=5)
  107.  
  108. # Кнопка настроек
  109. self.settings_btn = tk.Button(settings_frame, text="⚙ Настройки", command=self.open_settings,
  110. bg='#2196F3', fg='white', font=('Arial', 10))
  111. self.settings_btn.grid(row=0, column=7, padx=5, pady=5)
  112.  
  113. # Прогресс парсинга
  114. self.parse_progress = tk.Label(settings_frame, text="Готов к работе", bg='#f0f0f0')
  115. self.parse_progress.grid(row=1, column=0, columnspan=8, pady=5)
  116.  
  117. # Область для медиа
  118. media_frame = tk.LabelFrame(self.root, text="Просмотр медиа", font=('Arial', 12, 'bold'), bg='#f0f0f0')
  119. media_frame.pack(pady=10, padx=20, fill='both', expand=True)
  120.  
  121. self.media_label = tk.Label(media_frame, text="Медиа будет показано здесь после начала загрузки",
  122. bg='black', fg='white', relief='sunken', bd=2)
  123. self.media_label.pack(pady=20, padx=20, fill='both', expand=True)
  124.  
  125. # Ползунок для видео
  126. self.seek_frame = tk.Frame(media_frame, bg='#f0f0f0')
  127.  
  128. self.time_label = tk.Label(self.seek_frame, text="00:00 / 00:00", bg='#f0f0f0', font=('Arial', 9))
  129. self.time_label.pack(side='left', padx=5)
  130.  
  131. self.seek_slider = tk.Scale(self.seek_frame, from_=0, to=100, orient='horizontal',
  132. showvalue=False, length=400, sliderlength=15,
  133. command=self.on_seek)
  134. self.seek_slider.pack(side='left', fill='x', expand=True, padx=5)
  135.  
  136. # Элементы управления видео
  137. self.video_controls = tk.Frame(media_frame, bg='#333')
  138. self.play_btn = tk.Button(self.video_controls, text="▶", command=self.play_video,
  139. bg='#4CAF50', fg='white', font=('Arial', 10))
  140. self.pause_btn = tk.Button(self.video_controls, text="⏸", command=self.pause_video,
  141. bg='#FF9800', fg='white', font=('Arial', 10))
  142. self.stop_btn_video = tk.Button(self.video_controls, text="⏹", command=self.stop_video,
  143. bg='#f44336', fg='white', font=('Arial', 10))
  144.  
  145. # Информация о файле
  146. self.info_label = tk.Label(self.root, text="", font=('Arial', 10), bg='#f0f0f0')
  147. self.info_label.pack(pady=5)
  148.  
  149. # Кнопки сортировки
  150. self.sort_frame = tk.Frame(self.root, bg='#f0f0f0')
  151. self.sort_frame.pack(pady=10)
  152.  
  153. self.update_sort_buttons()
  154.  
  155. # Статистика
  156. stats_frame = tk.LabelFrame(self.root, text="Статистика", font=('Arial', 12, 'bold'), bg='#f0f0f0')
  157. stats_frame.pack(pady=10, padx=20, fill='x')
  158.  
  159. self.stats_label = tk.Label(stats_frame, text="Ожидание начала...", font=('Arial', 10), bg='#f0f0f0')
  160. self.stats_label.pack(pady=5)
  161.  
  162. # Горячие клавиши
  163. hotkey_label = tk.Label(self.root,
  164. text="Горячие клавиши: 1 - Интересно, 2 - Не интересно, 3 - Пропустить, Space - Пауза/Плей, Esc - Выход",
  165. font=('Arial', 9), bg='#f0f0f0', fg='#666')
  166. hotkey_label.pack(pady=5)
  167.  
  168. # Привязываем горячие клавиши
  169. self.root.bind('1', lambda e: self.mark_interesting() if hasattr(self, 'interesting_btn') and self.interesting_btn['state'] == 'normal' else None)
  170. self.root.bind('2', lambda e: self.mark_not_interesting() if hasattr(self, 'not_interesting_btn') and self.not_interesting_btn['state'] == 'normal' else None)
  171. self.root.bind('3', lambda e: self.skip_media() if hasattr(self, 'skip_btn') and self.skip_btn['state'] == 'normal' else None)
  172. self.root.bind('<space>', lambda e: self.toggle_play_pause())
  173. self.root.bind('<Escape>', lambda e: self.root.quit())
  174.  
  175. self.root.focus_set()
  176.  
  177. def update_sort_buttons(self):
  178. """Обновляем кнопки сортировки согласно настройкам"""
  179. # Очищаем frame
  180. for widget in self.sort_frame.winfo_children():
  181. widget.destroy()
  182.  
  183. # Создаем кнопки с текущими настройками
  184. self.interesting_btn = tk.Button(
  185. self.sort_frame,
  186. text=self.settings['interesting_button_text'],
  187. command=self.mark_interesting,
  188. bg='#51cf66',
  189. fg='white',
  190. font=('Arial', 12, 'bold'),
  191. width=15,
  192. height=2,
  193. state='disabled'
  194. )
  195. self.interesting_btn.grid(row=0, column=0, padx=10)
  196.  
  197. self.not_interesting_btn = tk.Button(
  198. self.sort_frame,
  199. text=self.settings['not_interesting_button_text'],
  200. command=self.mark_not_interesting,
  201. bg='#ff6b6b',
  202. fg='white',
  203. font=('Arial', 12, 'bold'),
  204. width=15,
  205. height=2,
  206. state='disabled'
  207. )
  208. self.not_interesting_btn.grid(row=0, column=1, padx=10)
  209.  
  210. self.skip_btn = tk.Button(
  211. self.sort_frame,
  212. text=self.settings['skip_button_text'],
  213. command=self.skip_media,
  214. bg='#ffd43b',
  215. font=('Arial', 12),
  216. width=15,
  217. height=2,
  218. state='disabled'
  219. )
  220. self.skip_btn.grid(row=0, column=2, padx=10)
  221.  
  222. def open_settings(self):
  223. """Открываем окно настроек"""
  224. settings_window = tk.Toplevel(self.root)
  225. settings_window.title("Настройки")
  226. settings_window.geometry("400x350")
  227. settings_window.configure(bg='#f0f0f0')
  228. settings_window.transient(self.root)
  229. settings_window.grab_set()
  230.  
  231. # Создаем фрейм для полей ввода
  232. input_frame = tk.Frame(settings_window, bg='#f0f0f0')
  233. input_frame.pack(pady=20, padx=20, fill='both', expand=True)
  234.  
  235. # Папка для интересного контента
  236. tk.Label(input_frame, text="Папка для интересного:", bg='#f0f0f0', anchor='w').grid(row=0, column=0, sticky='w', pady=5)
  237. interesting_folder_entry = tk.Entry(input_frame, width=25)
  238. interesting_folder_entry.insert(0, self.settings['interesting_folder'])
  239. interesting_folder_entry.grid(row=0, column=1, padx=10, pady=5, sticky='ew')
  240.  
  241. # Папка для не интересного контента
  242. tk.Label(input_frame, text="Папка для не интересного:", bg='#f0f0f0', anchor='w').grid(row=1, column=0, sticky='w', pady=5)
  243. not_interesting_folder_entry = tk.Entry(input_frame, width=25)
  244. not_interesting_folder_entry.insert(0, self.settings['not_interesting_folder'])
  245. not_interesting_folder_entry.grid(row=1, column=1, padx=10, pady=5, sticky='ew')
  246.  
  247. # Текст кнопки для интересного
  248. tk.Label(input_frame, text="Кнопка интересно:", bg='#f0f0f0', anchor='w').grid(row=2, column=0, sticky='w', pady=5)
  249. interesting_button_entry = tk.Entry(input_frame, width=25)
  250. interesting_button_entry.insert(0, self.settings['interesting_button_text'])
  251. interesting_button_entry.grid(row=2, column=1, padx=10, pady=5, sticky='ew')
  252.  
  253. # Текст кнопки для не интересного
  254. tk.Label(input_frame, text="Кнопка не интересно:", bg='#f0f0f0', anchor='w').grid(row=3, column=0, sticky='w', pady=5)
  255. not_interesting_button_entry = tk.Entry(input_frame, width=25)
  256. not_interesting_button_entry.insert(0, self.settings['not_interesting_button_text'])
  257. not_interesting_button_entry.grid(row=3, column=1, padx=10, pady=5, sticky='ew')
  258.  
  259. # Текст кнопки пропуска
  260. tk.Label(input_frame, text="Кнопка пропуска:", bg='#f0f0f0', anchor='w').grid(row=4, column=0, sticky='w', pady=5)
  261. skip_button_entry = tk.Entry(input_frame, width=25)
  262. skip_button_entry.insert(0, self.settings['skip_button_text'])
  263. skip_button_entry.grid(row=4, column=1, padx=10, pady=5, sticky='ew')
  264.  
  265. # Настраиваем вес колонок для растягивания
  266. input_frame.columnconfigure(1, weight=1)
  267.  
  268. def save_settings():
  269. new_settings = {
  270. 'interesting_folder': interesting_folder_entry.get().strip(),
  271. 'not_interesting_folder': not_interesting_folder_entry.get().strip(),
  272. 'interesting_button_text': interesting_button_entry.get().strip(),
  273. 'not_interesting_button_text': not_interesting_button_entry.get().strip(),
  274. 'skip_button_text': skip_button_entry.get().strip()
  275. }
  276.  
  277. # Проверяем, что названия папок не пустые
  278. if not new_settings['interesting_folder'] or not new_settings['not_interesting_folder']:
  279. messagebox.showerror("Ошибка", "Названия папок не могут быть пустыми!")
  280. return
  281.  
  282. # Проверяем, что названия кнопок не пустые
  283. if not new_settings['interesting_button_text'] or not new_settings['not_interesting_button_text'] or not new_settings['skip_button_text']:
  284. messagebox.showerror("Ошибка", "Текст кнопок не может быть пустым!")
  285. return
  286.  
  287. # Обновляем настройки
  288. self.settings.update(new_settings)
  289.  
  290. # Создаем новые папки
  291. Path(self.settings['interesting_folder']).mkdir(exist_ok=True)
  292. Path(self.settings['not_interesting_folder']).mkdir(exist_ok=True)
  293.  
  294. # Сохраняем настройки
  295. self.save_settings()
  296.  
  297. # Обновляем интерфейс
  298. self.update_sort_buttons()
  299. self.update_stats()
  300.  
  301. settings_window.destroy()
  302. messagebox.showinfo("Успех", "Настройки сохранены!")
  303.  
  304. # Фрейм для кнопок
  305. button_frame = tk.Frame(settings_window, bg='#f0f0f0')
  306. button_frame.pack(pady=10)
  307.  
  308. tk.Button(button_frame, text="Сохранить", command=save_settings,
  309. bg='#4CAF50', fg='white', font=('Arial', 10, 'bold')).pack(side='left', padx=10)
  310.  
  311. tk.Button(button_frame, text="Отмена", command=settings_window.destroy,
  312. bg='#f44336', fg='white', font=('Arial', 10)).pack(side='left', padx=10)
  313.  
  314. def start_parsing(self):
  315. """Запускаем парсинг в отдельном потоке"""
  316. name = self.name_entry.get().strip()
  317. offset = self.offset_entry.get().strip()
  318.  
  319. if not name:
  320. self.parse_progress.config(text="Ошибка: введите Name!")
  321. return
  322.  
  323. if not offset.isdigit():
  324. self.parse_progress.config(text="Ошибка: Offset должен быть числом!")
  325. return
  326.  
  327. # Блокируем кнопки настройки
  328. self.name_entry.config(state='disabled')
  329. self.offset_entry.config(state='disabled')
  330. self.start_btn.config(state='disabled')
  331. self.stop_btn.config(state='normal')
  332. self.settings_btn.config(state='disabled')
  333. self.resume_btn.config(state='disabled')
  334.  
  335. # Активируем кнопки сортировки
  336. self.interesting_btn.config(state='normal')
  337. self.not_interesting_btn.config(state='normal')
  338. self.skip_btn.config(state='normal')
  339.  
  340. self.stop_download = False
  341. self.download_thread = threading.Thread(target=self.parse_media, args=(name, int(offset)))
  342. self.download_thread.daemon = True
  343. self.download_thread.start()
  344.  
  345. # Запускаем обработку очереди
  346. self.process_download_queue()
  347.  
  348. def stop_parsing(self):
  349. """Останавливаем парсинг"""
  350. self.stop_download = True
  351. self.stop_media_playback()
  352. self.parse_progress.config(text="Парсинг остановлен. Можно досортировать файлы из temp_downloads")
  353. self.stop_btn.config(state='disabled')
  354. self.name_entry.config(state='normal')
  355. self.offset_entry.config(state='normal')
  356. self.start_btn.config(state='normal')
  357. self.settings_btn.config(state='normal')
  358. self.resume_btn.config(state='normal')
  359.  
  360. def resume_sorting(self):
  361. """Продолжаем сортировку из папки temp_downloads"""
  362. temp_files = self.get_temp_files()
  363. if not temp_files:
  364. messagebox.showinfo("Информация", "В папке temp_downloads нет файлов для сортировки")
  365. return
  366.  
  367. # Добавляем файлы в очередь
  368. for file_path in temp_files:
  369. self.download_queue.put(file_path)
  370.  
  371. # Активируем кнопки сортировки
  372. self.interesting_btn.config(state='normal')
  373. self.not_interesting_btn.config(state='normal')
  374. self.skip_btn.config(state='normal')
  375.  
  376. self.parse_progress.config(text=f"Досортировка: {len(temp_files)} файлов в очереди")
  377.  
  378. # Запускаем обработку очереди
  379. self.process_download_queue()
  380.  
  381. def get_temp_files(self):
  382. """Получаем список файлов из папки temp_downloads"""
  383. temp_files = []
  384. temp_dir = "temp_downloads"
  385. if os.path.exists(temp_dir):
  386. for file in os.listdir(temp_dir):
  387. file_path = os.path.join(temp_dir, file)
  388. if os.path.isfile(file_path):
  389. temp_files.append(file_path)
  390. return temp_files
  391.  
  392. def parse_media(self, name, offset):
  393. """Парсим медиа в отдельном потоке"""
  394. User_Agent = f'{UserAgent().random}'
  395.  
  396. try:
  397. for month in range(1, 13):
  398. if self.stop_download:
  399. break
  400.  
  401. for day in range(31, 0, -1):
  402. if self.stop_download:
  403. break
  404.  
  405. for offset_num in range(1, offset + 1):
  406. if self.stop_download:
  407. break
  408.  
  409. offset_str = "" if offset_num == 1 else f"-{offset_num}"
  410.  
  411. # Обновляем прогресс в GUI потоке
  412. progress_text = f"Парсинг: {day:02}.{month:02}{offset_str}"
  413. self.root.after(0, lambda: self.parse_progress.config(text=progress_text))
  414.  
  415. # Парсим URL
  416. HEADERS = {'User-Agent': User_Agent}
  417. url = f"https://telegra.ph/{name}-{month:02}-{day:02}{offset_str}"
  418.  
  419. try:
  420. response = requests.get(url, headers=HEADERS, timeout=10)
  421. soup = BeautifulSoup(response.content, 'html.parser')
  422.  
  423. # Ищем все медиа-элементы (используем современный find_all вместо устаревшего findAll)
  424. img_items = soup.find_all('img')
  425. video_items = soup.find_all('video')
  426. source_items = soup.find_all('source')
  427.  
  428. media_urls = []
  429.  
  430. # Обрабатываем изображения и гифки
  431. for item in img_items:
  432. src = item.get('src')
  433. if src:
  434. if not src.startswith('http'):
  435. media_urls.append(f"https://telegra.ph{src}")
  436. else:
  437. media_urls.append(src)
  438.  
  439. # Обрабатываем видео
  440. for item in video_items:
  441. src = item.get('src')
  442. if src:
  443. if not src.startswith('http'):
  444. media_urls.append(f"https://telegra.ph{src}")
  445. else:
  446. media_urls.append(src)
  447.  
  448. # Обрабатываем source теги (для видео)
  449. for item in source_items:
  450. src = item.get('src')
  451. if src:
  452. if not src.startswith('http'):
  453. media_urls.append(f"https://telegra.ph{src}")
  454. else:
  455. media_urls.append(src)
  456.  
  457. # Скачиваем медиа
  458. for i, media_url in enumerate(media_urls):
  459. if self.stop_download:
  460. break
  461.  
  462. try:
  463. response = requests.get(media_url, headers=HEADERS, timeout=15)
  464. if response.status_code == 200:
  465. # Определяем расширение файла
  466. content_type = response.headers.get('content-type', '')
  467. if 'video' in content_type:
  468. extension = '.mp4'
  469. elif 'gif' in content_type:
  470. extension = '.gif'
  471. else:
  472. extension = '.jpg'
  473.  
  474. # Сохраняем во временную папку
  475. temp_filename = f"temp_{month:02}_{day:02}_{offset_num}_{i}{extension}"
  476. temp_path = os.path.join("temp_downloads", temp_filename)
  477.  
  478. with open(temp_path, "wb") as file:
  479. file.write(response.content)
  480.  
  481. # Добавляем в очередь для отображения
  482. self.download_queue.put(temp_path)
  483.  
  484. except Exception as e:
  485. print(f"Ошибка загрузки: {e}")
  486. continue
  487.  
  488. except Exception as e:
  489. print(f"Ошибка парсинга: {e}")
  490. continue
  491.  
  492. # Завершение парсинга
  493. self.root.after(0, self.parsing_completed)
  494.  
  495. except Exception as e:
  496. self.root.after(0, lambda: self.parse_progress.config(text=f"Ошибка: {e}"))
  497.  
  498. def process_download_queue(self):
  499. """Обрабатываем очередь загруженных медиа"""
  500. try:
  501. if not self.current_media_path and not self.download_queue.empty():
  502. next_media_path = self.download_queue.get_nowait()
  503. self.show_media(next_media_path)
  504.  
  505. if not self.stop_download or not self.download_queue.empty():
  506. self.root.after(100, self.process_download_queue)
  507.  
  508. except queue.Empty:
  509. if not self.stop_download:
  510. self.root.after(100, self.process_download_queue)
  511. else:
  512. # Если очередь пуста и парсинг остановлен, показываем сообщение
  513. if not self.current_media_path:
  514. self.parse_progress.config(text="Все файлы обработаны!")
  515.  
  516. def show_media(self, media_path):
  517. """Показываем медиа в интерфейсе"""
  518. self.stop_media_playback()
  519. self.current_media_path = media_path
  520. extension = Path(media_path).suffix.lower()
  521.  
  522. try:
  523. if extension in ['.gif']:
  524. self.show_gif(media_path)
  525. elif extension in ['.mp4', '.avi', '.mov', '.webm']:
  526. self.show_video(media_path)
  527. else:
  528. self.show_image(media_path)
  529.  
  530. # Обновляем информацию о файле
  531. filename = os.path.basename(media_path)
  532. file_size = os.path.getsize(media_path) / 1024
  533. file_info = f"Файл: {filename} | Размер: {file_size:.1f} KB | Тип: {extension}"
  534. self.info_label.configure(text=file_info)
  535.  
  536. self.update_stats()
  537.  
  538. except Exception as e:
  539. print(f"Ошибка загрузки медиа: {e}")
  540. self.current_media_path = None
  541. # Продолжаем обработку очереди
  542. self.root.after(100, self.process_download_queue)
  543.  
  544. def show_image(self, image_path):
  545. """Показываем изображение"""
  546. try:
  547. image = Image.open(image_path)
  548. self.display_pil_image(image)
  549. self.hide_video_controls()
  550. except Exception as e:
  551. print(f"Ошибка загрузки изображения: {e}")
  552.  
  553. def show_gif(self, gif_path):
  554. """Показываем GIF с анимацией"""
  555. try:
  556. self.gif_frames = []
  557. gif = Image.open(gif_path)
  558.  
  559. # Извлекаем все кадры GIF
  560. for frame in ImageSequence.Iterator(gif):
  561. photo = self.resize_frame(frame)
  562. self.gif_frames.append(photo)
  563.  
  564. if len(self.gif_frames) > 1:
  565. # Анимированный GIF
  566. self.is_playing_gif = True
  567. self.current_gif_frame = 0
  568. self.animate_gif()
  569. self.hide_video_controls()
  570. else:
  571. # Статический GIF
  572. self.display_pil_image(gif)
  573. self.hide_video_controls()
  574. except Exception as e:
  575. print(f"Ошибка загрузки GIF: {e}")
  576.  
  577. def show_video(self, video_path):
  578. """Показываем видео"""
  579. try:
  580. self.current_video_path = video_path
  581. self.video_capture = cv2.VideoCapture(video_path)
  582.  
  583. # Получаем информацию о видео
  584. fps = self.video_capture.get(cv2.CAP_PROP_FPS)
  585. frame_count = self.video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
  586. self.video_duration = frame_count / fps if fps > 0 else 0
  587.  
  588. # Настраиваем ползунок
  589. self.seek_slider.config(from_=0, to=100)
  590. self.seek_slider.set(0)
  591. self.update_time_label()
  592.  
  593. self.is_playing_video = True
  594. self.show_video_controls()
  595. self.play_video()
  596. except Exception as e:
  597. print(f"Ошибка загрузки видео: {e}")
  598.  
  599. def play_video(self):
  600. """Воспроизводим видео"""
  601. if self.video_capture and self.is_playing_video:
  602. ret, frame = self.video_capture.read()
  603. if ret:
  604. # Обновляем позицию
  605. current_frame = self.video_capture.get(cv2.CAP_PROP_POS_FRAMES)
  606. total_frames = self.video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
  607.  
  608. if total_frames > 0:
  609. self.video_position = current_frame / total_frames
  610. # Обновляем ползунок
  611. if not self.is_seeking:
  612. position_percent = self.video_position * 100
  613. self.seek_slider.set(position_percent)
  614. self.update_time_label()
  615.  
  616. # Конвертируем BGR в RGB
  617. frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
  618. pil_image = Image.fromarray(frame_rgb)
  619. self.display_pil_image(pil_image)
  620.  
  621. # Обновляем состояние кнопок
  622. self.play_btn.config(state='disabled')
  623. self.pause_btn.config(state='normal')
  624.  
  625. self.root.after(30, self.play_video) # ~33 FPS
  626. else:
  627. # Конец видео
  628. self.stop_video()
  629.  
  630. def pause_video(self):
  631. """Пауза видео"""
  632. self.is_playing_video = False
  633. self.play_btn.config(state='normal')
  634. self.pause_btn.config(state='disabled')
  635.  
  636. def stop_video(self):
  637. """Останавливаем видео"""
  638. self.is_playing_video = False
  639. if self.video_capture:
  640. self.video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
  641. self.play_btn.config(state='normal')
  642. self.pause_btn.config(state='disabled')
  643. self.seek_slider.set(0)
  644. self.update_time_label()
  645.  
  646. def toggle_play_pause(self):
  647. """Переключаем воспроизведение/паузу"""
  648. if self.current_media_path and Path(self.current_media_path).suffix.lower() in ['.mp4', '.avi', '.mov', '.webm']:
  649. if self.is_playing_video:
  650. self.pause_video()
  651. else:
  652. self.is_playing_video = True
  653. self.play_video()
  654.  
  655. def on_seek(self, value):
  656. """Обработка перемотки видео"""
  657. if self.video_capture:
  658. self.is_seeking = True
  659. position = float(value) / 100.0
  660. total_frames = self.video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
  661. target_frame = position * total_frames
  662. self.video_capture.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
  663. self.video_position = position
  664. self.update_time_label()
  665. self.is_seeking = False
  666.  
  667. def update_time_label(self):
  668. """Обновляем метку времени"""
  669. if self.video_duration > 0:
  670. current_time = self.video_position * self.video_duration
  671. total_time = self.video_duration
  672.  
  673. current_str = self.format_time(current_time)
  674. total_str = self.format_time(total_time)
  675. self.time_label.config(text=f"{current_str} / {total_str}")
  676.  
  677. def format_time(self, seconds):
  678. """Форматируем время в мм:сс"""
  679. minutes = int(seconds // 60)
  680. seconds = int(seconds % 60)
  681. return f"{minutes:02d}:{seconds:02d}"
  682.  
  683. def show_video_controls(self):
  684. """Показываем элементы управления видео"""
  685. # Показываем ползунок
  686. self.seek_frame.pack(fill='x', padx=10, pady=5)
  687.  
  688. # Показываем кнопки управления
  689. self.play_btn.pack(side='left', padx=5)
  690. self.pause_btn.pack(side='left', padx=5)
  691. self.stop_btn_video.pack(side='left', padx=5)
  692. self.video_controls.pack(pady=5)
  693.  
  694. # Устанавливаем начальное состояние кнопок
  695. self.play_btn.config(state='disabled')
  696. self.pause_btn.config(state='normal')
  697.  
  698. def hide_video_controls(self):
  699. """Скрываем элементы управления видео"""
  700. self.seek_frame.pack_forget()
  701. self.video_controls.pack_forget()
  702.  
  703. def stop_media_playback(self):
  704. """Останавливаем все воспроизведение медиа"""
  705. self.is_playing_gif = False
  706. self.is_playing_video = False
  707. self.gif_frames = []
  708. self.is_seeking = False
  709.  
  710. if self.video_capture:
  711. self.video_capture.release()
  712. self.video_capture = None
  713.  
  714. self.hide_video_controls()
  715.  
  716. def resize_frame(self, image):
  717. """Масштабируем кадр под размер окна"""
  718. window_width = 600
  719. window_height = 400
  720.  
  721. image_ratio = image.width / image.height
  722. window_ratio = window_width / window_height
  723.  
  724. if image_ratio > window_ratio:
  725. new_width = window_width
  726. new_height = int(window_width / image_ratio)
  727. else:
  728. new_height = window_height
  729. new_width = int(window_height * image_ratio)
  730.  
  731. image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
  732. return ImageTk.PhotoImage(image)
  733.  
  734. def display_pil_image(self, image):
  735. """Отображаем PIL изображение"""
  736. photo = self.resize_frame(image)
  737. self.media_label.configure(image=photo)
  738. self.media_label.image = photo
  739.  
  740. def update_stats(self):
  741. """Обновляем статистику"""
  742. try:
  743. interesting_count = len(os.listdir(self.settings['interesting_folder']))
  744. not_interesting_count = len(os.listdir(self.settings['not_interesting_folder']))
  745. stats_text = f"Интересно: {interesting_count} | Не интересно: {not_interesting_count}"
  746. self.stats_label.configure(text=stats_text)
  747. except:
  748. self.stats_label.configure(text="Ошибка загрузки статистики")
  749.  
  750. def mark_interesting(self):
  751. """Помечаем как интересное медиа"""
  752. if self.current_media_path:
  753. self.move_media(self.settings['interesting_folder'])
  754. self.process_download_queue()
  755.  
  756. def mark_not_interesting(self):
  757. """Помечаем как не интересное медиа"""
  758. if self.current_media_path:
  759. self.move_media(self.settings['not_interesting_folder'])
  760. self.process_download_queue()
  761.  
  762. def skip_media(self):
  763. """Пропускаем текущее медиа"""
  764. if self.current_media_path:
  765. self.stop_media_playback()
  766. try:
  767. os.remove(self.current_media_path)
  768. except:
  769. pass
  770. self.current_media_path = None
  771. self.process_download_queue()
  772.  
  773. def move_media(self, destination_folder):
  774. """Перемещаем медиа в указанную папку"""
  775. if self.current_media_path:
  776. self.stop_media_playback()
  777. filename = os.path.basename(self.current_media_path)
  778.  
  779. # Создаем уникальное имя
  780. counter = 1
  781. new_filename = filename
  782. base_name = Path(filename).stem
  783. extension = Path(filename).suffix
  784.  
  785. while os.path.exists(os.path.join(destination_folder, new_filename)):
  786. new_filename = f"{base_name}_{counter}{extension}"
  787. counter += 1
  788.  
  789. # Перемещаем файл
  790. try:
  791. shutil.move(self.current_media_path, os.path.join(destination_folder, new_filename))
  792. print(f"Файл перемещен в {destination_folder}")
  793. except Exception as e:
  794. print(f"Ошибка перемещения: {e}")
  795.  
  796. self.current_media_path = None
  797.  
  798. def parsing_completed(self):
  799. """Завершение парсинга"""
  800. self.parse_progress.config(text="Парсинг завершен! Можно досортировать файлы из temp_downloads")
  801. self.stop_btn.config(state='disabled')
  802. self.name_entry.config(state='normal')
  803. self.offset_entry.config(state='normal')
  804. self.start_btn.config(state='normal')
  805. self.settings_btn.config(state='normal')
  806. self.resume_btn.config(state='normal')
  807.  
  808. if not self.current_media_path and self.download_queue.empty():
  809. self.media_label.configure(text="Все медиа обработаны!", image='')
  810.  
  811. def run(self):
  812. """Запускаем приложение"""
  813. self.root.mainloop()
  814.  
  815. if __name__ == "__main__":
  816. # Проверяем наличие OpenCV для поддержки видео
  817. try:
  818. import cv2
  819. print("OpenCV найден, видео поддерживается")
  820. except ImportError:
  821. print("Предупреждение: OpenCV не установлен. Видео не будет поддерживаться.")
  822. print("Установите: pip install opencv-python")
  823.  
  824. app = CombinedPhotoApp()
  825. app.run()
Advertisement
Add Comment
Please, Sign In to add comment