Files
SageTube/ytsage_gui_video_info.py
T
Muhamed 985ba9d864 Revert "4v.5.0"
This reverts commit 82de19c95a.
2025-04-29 15:03:52 +03:00

351 lines
13 KiB
Python

import sys
import os
import webbrowser
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLineEdit, QPushButton, QTableWidget,
QTableWidgetItem, QProgressBar, QLabel, QFileDialog,
QHeaderView, QStyle, QStyleFactory, QComboBox, QTextEdit, QDialog, QPlainTextEdit, QCheckBox, QButtonGroup)
from PySide6.QtCore import Qt, Signal, QObject, QThread
from PySide6.QtGui import QIcon, QPalette, QColor, QPixmap
import requests
from io import BytesIO
from PIL import Image
from datetime import datetime
import json
from pathlib import Path
from packaging import version
import subprocess
import re
import yt_dlp
class VideoInfoMixin:
def setup_video_info_section(self):
# Create a horizontal layout for thumbnail and video info
media_info_layout = QHBoxLayout()
media_info_layout.setSpacing(15)
# Left side container for thumbnail
thumbnail_container = QWidget()
thumbnail_container.setFixedWidth(320)
thumbnail_layout = QVBoxLayout(thumbnail_container)
thumbnail_layout.setContentsMargins(0, 0, 0, 0)
# Thumbnail on the left
self.thumbnail_label = QLabel()
self.thumbnail_label.setFixedSize(320, 180)
self.thumbnail_label.setStyleSheet("border: 2px solid #3d3d3d; border-radius: 4px;")
self.thumbnail_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
thumbnail_layout.addWidget(self.thumbnail_label)
thumbnail_layout.addStretch()
media_info_layout.addWidget(thumbnail_container)
# Video information on the right
video_info_layout = QVBoxLayout()
video_info_layout.setSpacing(2) # Reduce spacing between elements
video_info_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# Title and info labels
self.title_label = QLabel()
self.title_label.setWordWrap(True)
self.title_label.setStyleSheet("font-size: 14px; font-weight: bold;")
# Add basic info labels
self.channel_label = QLabel()
self.views_label = QLabel()
self.date_label = QLabel()
self.duration_label = QLabel()
# Style the info labels
for label in [self.channel_label, self.views_label, self.date_label, self.duration_label]:
label.setStyleSheet("""
QLabel {
color: #cccccc;
font-size: 12px;
padding: 1px 0;
}
""")
# Add labels to video info layout
video_info_layout.addWidget(self.title_label)
video_info_layout.addWidget(self.channel_label)
video_info_layout.addWidget(self.views_label)
video_info_layout.addWidget(self.date_label)
video_info_layout.addWidget(self.duration_label)
# Add spacing before subtitle section
video_info_layout.addSpacing(10)
# Create a horizontal layout for subtitle controls
subtitle_layout = QHBoxLayout()
subtitle_layout.setSpacing(5) # Reduce spacing between elements
# Create subtitle button
self.subtitle_check = QPushButton("Download Subtitles")
self.subtitle_check.setFixedHeight(30)
self.subtitle_check.setFixedWidth(150) # Set fixed width
self.subtitle_check.setCheckable(True)
self.subtitle_check.clicked.connect(self.toggle_subtitle_controls)
self.subtitle_check.setStyleSheet("""
QPushButton {
background-color: #363636;
border: 2px solid #3d3d3d;
border-radius: 4px;
padding: 5px;
min-height: 30px;
}
QPushButton:checked {
background-color: #ff0000;
border-color: #cc0000;
}
""")
subtitle_layout.addWidget(self.subtitle_check)
# Create subtitle combo box
self.subtitle_combo = QComboBox()
self.subtitle_combo.setFixedHeight(30)
self.subtitle_combo.setFixedWidth(200)
self.subtitle_combo.setVisible(False)
self.subtitle_combo.setStyleSheet("""
QComboBox {
background-color: #363636;
border: 2px solid #3d3d3d;
border-radius: 4px;
padding: 5px;
min-height: 30px;
}
""")
subtitle_layout.addWidget(self.subtitle_combo)
# Create merge subtitles checkbox
self.merge_subs_checkbox = QCheckBox("Merge Subtitles")
self.merge_subs_checkbox.setFixedHeight(30)
self.merge_subs_checkbox.setVisible(False)
self.merge_subs_checkbox.setStyleSheet("""
QCheckBox {
color: #ffffff;
padding: 5px;
margin-left: 10px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
border-radius: 9px;
}
QCheckBox::indicator:unchecked {
border: 2px solid #666666;
background: #2b2b2b;
border-radius: 9px;
}
QCheckBox::indicator:checked {
border: 2px solid #ff0000;
background: #ff0000;
border-radius: 9px;
}
""")
subtitle_layout.addWidget(self.merge_subs_checkbox)
# Add stretch to push everything to the left
subtitle_layout.addStretch()
# Create a second row for the filter input
filter_layout = QHBoxLayout()
filter_layout.setSpacing(5)
# Create subtitle filter input
self.subtitle_filter_input = QLineEdit()
self.subtitle_filter_input.setFixedHeight(30)
self.subtitle_filter_input.setFixedWidth(200)
self.subtitle_filter_input.setPlaceholderText("Filter languages (e.g., en, es)")
self.subtitle_filter_input.textChanged.connect(self.filter_subtitles)
self.subtitle_filter_input.setVisible(False)
self.subtitle_filter_input.setStyleSheet("""
QLineEdit {
background-color: #363636;
border: 2px solid #3d3d3d;
border-radius: 4px;
padding: 5px;
min-height: 30px;
color: white;
}
QLineEdit:focus {
border-color: #ff0000;
}
""")
filter_layout.addWidget(self.subtitle_filter_input)
filter_layout.addStretch()
# Add both layouts to video info
video_info_layout.addLayout(subtitle_layout)
video_info_layout.addLayout(filter_layout)
# Add stretch at the bottom
video_info_layout.addStretch()
# Add video info layout to main layout
media_info_layout.addLayout(video_info_layout, stretch=1)
return media_info_layout
def setup_playlist_info_section(self):
self.playlist_info_label = QLabel()
self.playlist_info_label.setVisible(False)
self.playlist_info_label.setStyleSheet("""
QLabel {
font-size: 12px;
color: #ff9900;
padding: 5px 8px;
margin: 0;
background-color: #2b2b2b;
border: 1px solid #3d3d3d;
border-radius: 4px;
min-height: 30px;
max-height: 30px;
}
""")
self.playlist_info_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
return self.playlist_info_label
def update_video_info(self, info):
# Format view count with commas
views = int(info.get('view_count', 0))
formatted_views = f"{views:,}"
# Format upload date
upload_date = info.get('upload_date', '')
if upload_date:
date_obj = datetime.strptime(upload_date, '%Y%m%d')
formatted_date = date_obj.strftime('%B %d, %Y')
else:
formatted_date = 'Unknown date'
# Format duration
duration = info.get('duration', 0)
minutes = duration // 60
seconds = duration % 60
duration_str = f"{minutes}:{seconds:02d}"
# Update labels
self.title_label.setText(info.get('title', 'Unknown title'))
self.channel_label.setText(f"Channel: {info.get('uploader', 'Unknown channel')}")
self.views_label.setText(f"Views: {formatted_views}")
self.date_label.setText(f"Upload date: {formatted_date}")
self.duration_label.setText(f"Duration: {duration_str}")
def toggle_subtitle_controls(self):
is_checked = self.subtitle_check.isChecked()
self.subtitle_combo.setVisible(is_checked)
self.subtitle_filter_input.setVisible(is_checked)
self.merge_subs_checkbox.setVisible(is_checked)
def update_subtitle_list(self):
self.subtitle_combo.clear()
if not (self.available_subtitles or self.available_automatic_subtitles):
self.subtitle_combo.addItem("No subtitles available")
return
# Add subtitle options
self.subtitle_combo.addItem("Select subtitle language")
# Filter and add subtitles
filter_text = self.subtitle_filter_input.text().lower()
# Add manual subtitles
for lang_code, subtitle_info in self.available_subtitles.items():
if not filter_text or filter_text in lang_code.lower():
self.subtitle_combo.addItem(f"{lang_code} - Manual")
# Add auto-generated subtitles
for lang_code, subtitle_info in self.available_automatic_subtitles.items():
if not filter_text or filter_text in lang_code.lower():
self.subtitle_combo.addItem(f"{lang_code} - Auto-generated")
def filter_subtitles(self):
self.subtitle_filter = self.subtitle_filter_input.text()
self.update_subtitle_list()
def download_thumbnail(self, url):
try:
# Store both thumbnail URL and video URL
self.thumbnail_url = url
self.video_url = self.url_input.text() # Get actual video URL
# Download thumbnail but don't save yet
response = requests.get(url)
self.thumbnail_image = Image.open(BytesIO(response.content))
# Display thumbnail
image = self.thumbnail_image.resize((320, 180), Image.Resampling.LANCZOS)
img_byte_arr = BytesIO()
image.save(img_byte_arr, format='PNG')
pixmap = QPixmap()
pixmap.loadFromData(img_byte_arr.getvalue())
self.thumbnail_label.setPixmap(pixmap)
except Exception as e:
print(f"Error loading thumbnail: {str(e)}")
def toggle_save_thumbnail(self):
self.save_thumbnail = self.save_thumbnail_checkbox.isChecked()
print(f"Save thumbnail toggled: {self.save_thumbnail}") # Debug print
def download_thumbnail_file(self, video_url, path):
if not self.save_thumbnail:
return False
try:
from yt_dlp import YoutubeDL
import requests # Use requests instead of urlopen
print(f"Attempting to save thumbnail for URL: {video_url}")
ydl_opts = {
'quiet': True,
'skip_download': True,
'force_generic_extractor': False,
'no_warnings': True,
'extract_flat': False
}
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(video_url, download=False)
thumbnails = info.get('thumbnails', [])
if not thumbnails:
raise ValueError("No thumbnails available")
thumbnail_url = max(
thumbnails,
key=lambda t: (t.get('height', 0) or 0) * (t.get('width', 0) or 0)
).get('url')
if not thumbnail_url:
raise ValueError("Failed to extract thumbnail URL")
# Download using requests
response = requests.get(thumbnail_url)
response.raise_for_status()
# Save the thumbnail
thumb_dir = os.path.join(path, 'Thumbnails')
os.makedirs(thumb_dir, exist_ok=True)
filename = f"{self.sanitize_filename(info['title'])}.jpg"
thumbnail_path = os.path.join(thumb_dir, filename)
with open(thumbnail_path, 'wb') as f:
f.write(response.content)
print(f"Thumbnail saved to: {thumbnail_path}")
self.signals.update_status.emit(f"✅ Thumbnail saved: {filename}")
return True
except Exception as e:
error_msg = f"❌ Thumbnail error: {str(e)}"
print(f"Thumbnail Save Error: {str(e)}")
self.signals.update_status.emit(error_msg)
return False
def sanitize_filename(self, name):
"""Clean filename for filesystem safety"""
return re.sub(r'[\\/*?:"<>|]', "", name).strip()[:75]