Files
Serien-Checker/serien_checker.py

891 lines
38 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import json
import requests
import re
import logging
from bs4 import BeautifulSoup
from datetime import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QPushButton, QLineEdit, QListWidget,
QLabel, QMessageBox, QSpinBox, QComboBox, QTableWidget,
QTableWidgetItem, QHeaderView, QDialog, QDialogButtonBox,
QTextEdit, QGroupBox, QToolBar, QSplitter, QFormLayout,
QListWidgetItem, QCheckBox)
from PyQt5.QtCore import Qt, QTimer
# Logging Konfiguration
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
class LogWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Debug Log")
self.setGeometry(100, 100, 800, 400)
layout = QVBoxLayout(self)
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
layout.addWidget(self.log_text)
close_button = QPushButton("Schließen")
close_button.clicked.connect(self.close)
layout.addWidget(close_button)
def append_log(self, message):
self.log_text.append(message)
class DebugHandler(logging.Handler):
def __init__(self, log_window):
super().__init__()
self.log_window = log_window
def emit(self, record):
msg = self.format(record)
self.log_window.append_log(msg)
class NewSeriesDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
def setup_ui(self):
self.setWindowTitle("Neue Serie hinzufügen")
self.setMinimumWidth(400)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout(self)
# Einstellungen
settings_group = QGroupBox("Serie hinzufügen")
settings_layout = QFormLayout()
# Serien-Eingabe
self.slug_input = QLineEdit()
self.slug_input.setPlaceholderText("Serien-URL oder Slug")
settings_layout.addRow("Serie:", self.slug_input)
# Staffel-Einstellungen
self.staffel_mode = QComboBox()
self.staffel_mode.addItems(["Neuste Staffel", "Alle Staffeln", "Bestimmte Staffel"])
self.staffel_mode.currentIndexChanged.connect(self.on_staffel_mode_changed)
settings_layout.addRow("Staffel-Modus:", self.staffel_mode)
self.staffel_spin = QSpinBox()
self.staffel_spin.setMinimum(1)
self.staffel_spin.setMaximum(100)
self.staffel_spin.setEnabled(False)
settings_layout.addRow("Staffel:", self.staffel_spin)
# Datumspräferenz
self.date_pref = QComboBox()
self.date_pref.addItems(["Bevorzuge Erstausstrahlung", "Bevorzuge TV", "Bevorzuge Streaming", "Bevorzuge deutsche Synchro"])
settings_layout.addRow("Datum Präferenz:", self.date_pref)
# Checkbox für Serien ohne klassische Staffeln
self.no_seasons_check = QCheckBox("Serie ohne klassische Staffeln")
self.no_seasons_check.setToolTip("Aktivieren für Serien mit fortlaufender Nummerierung oder Jahresformat")
settings_layout.addRow("", self.no_seasons_check)
settings_group.setLayout(settings_layout)
layout.addWidget(settings_group)
# Dialog Buttons
buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Qt.Horizontal, self)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def on_staffel_mode_changed(self, index):
"""Wird aufgerufen, wenn sich der Staffel-Modus ändert"""
self.staffel_spin.setEnabled(self.staffel_mode.currentText() == "Bestimmte Staffel")
def get_series_data(self):
"""Extrahiert die Seriendaten aus den Eingabefeldern"""
input_text = self.slug_input.text().strip()
if not input_text:
QMessageBox.warning(self, "Fehler", "Bitte geben Sie eine Serie ein.")
return None
# Extrahiere Slug aus URL wenn nötig
if "/" in input_text:
try:
# Prüfe ob es eine fernsehserien.de URL ist
if "fernsehserien.de" not in input_text:
QMessageBox.warning(self, "Fehler", "Die URL muss von fernsehserien.de sein.")
return None
# Extrahiere den Slug (Teil nach dem letzten /)
slug = input_text.rstrip("/").split("/")[-1]
if slug == "episodenguide":
# Wenn die URL auf /episodenguide endet, nimm den Teil davor
slug = input_text.rstrip("/").split("/")[-2]
except Exception as e:
QMessageBox.warning(self, "Fehler", f"Ungültige URL: {str(e)}")
return None
else:
slug = input_text
# Hole den Seriennamen von fernsehserien.de
try:
url = f"https://www.fernsehserien.de/{slug}/episodenguide"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
title_elem = soup.find('h1')
if title_elem:
series_name = title_elem.text.strip()
else:
QMessageBox.warning(self, "Fehler", "Serie nicht gefunden.")
return None
except Exception as e:
QMessageBox.warning(self, "Fehler", f"Fehler beim Abrufen der Serie: {str(e)}")
return None
return {
'name': series_name,
'slug': slug,
'staffel_setting': {
'mode': self.staffel_mode.currentText(),
'staffel': self.staffel_spin.value()
},
'date_preference': self.date_pref.currentText(),
'no_seasons': self.no_seasons_check.isChecked()
}
class SeriesEditDialog(QDialog):
def __init__(self, series_data, parent=None):
super().__init__(parent)
self.series_data = series_data.copy()
self.parent = parent
self.setup_ui()
def setup_ui(self):
self.setWindowTitle("Serien verwalten")
self.setMinimumWidth(500)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout(self)
# Serien Liste und Einstellungen nebeneinander
content_layout = QHBoxLayout()
# Linke Seite - Serien Liste und Info
left_layout = QVBoxLayout()
list_group = QGroupBox("Gespeicherte Serien")
list_layout = QVBoxLayout()
self.series_list = QListWidget()
self.series_list.itemClicked.connect(self.on_series_selected)
list_layout.addWidget(self.series_list)
# Buttons für die Liste
button_layout = QHBoxLayout()
add_button = QPushButton("Neue Serie")
add_button.clicked.connect(self.add_series)
delete_button = QPushButton("Löschen")
delete_button.clicked.connect(self.delete_series)
button_layout.addWidget(add_button)
button_layout.addWidget(delete_button)
list_layout.addLayout(button_layout)
list_group.setLayout(list_layout)
left_layout.addWidget(list_group)
# Info Label
info_label = QLabel('v1.1 | © Akamaru | <a href="https://git.ponywave.de/Akamaru/Serien-Checker">Source auf PonyGit</a>')
info_label.setOpenExternalLinks(True) # Erlaubt das Öffnen des Links
info_label.setTextFormat(Qt.RichText) # Aktiviert HTML-Formatierung
left_layout.addWidget(info_label)
content_layout.addLayout(left_layout)
# Rechte Seite - Einstellungen
self.settings_group = QGroupBox("Einstellungen")
settings_layout = QFormLayout()
# Serien-Eingabe
self.slug_input = QLineEdit()
self.slug_input.setPlaceholderText("Serien-URL oder Slug")
self.slug_input.setEnabled(False) # Deaktiviert, da nur zum Anzeigen
settings_layout.addRow("Serie:", self.slug_input)
# Staffel-Einstellungen
self.staffel_mode = QComboBox()
self.staffel_mode.addItems(["Neuste Staffel", "Alle Staffeln", "Bestimmte Staffel"])
self.staffel_mode.currentIndexChanged.connect(self.on_staffel_mode_changed)
settings_layout.addRow("Staffel-Modus:", self.staffel_mode)
self.staffel_spin = QSpinBox()
self.staffel_spin.setMinimum(1)
self.staffel_spin.setMaximum(100)
self.staffel_spin.setEnabled(False)
settings_layout.addRow("Staffel:", self.staffel_spin)
# Datumspräferenz
self.date_pref = QComboBox()
self.date_pref.addItems(["Bevorzuge Erstausstrahlung", "Bevorzuge TV", "Bevorzuge Streaming", "Bevorzuge deutsche Synchro"])
settings_layout.addRow("Datum Präferenz:", self.date_pref)
# Checkbox für Serien ohne klassische Staffeln
self.no_seasons_check = QCheckBox("Serie ohne klassische Staffeln")
self.no_seasons_check.setToolTip("Aktivieren für Serien mit fortlaufender Nummerierung oder Jahresformat")
settings_layout.addRow("", self.no_seasons_check)
# Speichern Button für Einstellungen
save_button = QPushButton("Einstellungen speichern")
save_button.clicked.connect(self.save_settings)
settings_layout.addRow("", save_button)
self.settings_group.setLayout(settings_layout)
content_layout.addWidget(self.settings_group)
# Füge das Content-Layout zum Hauptlayout hinzu
layout.addLayout(content_layout)
# Dialog Buttons
buttons = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
Qt.Horizontal, self)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
# Initialisiere die Liste und deaktiviere die Einstellungen
self.update_series_list()
self.settings_group.setEnabled(False)
def accept(self):
"""Schließt den Dialog"""
super().accept() # Schließe den Dialog einfach
def save_settings(self):
"""Speichert die Einstellungen für die aktuelle Serie"""
current_item = self.series_list.currentItem()
if not current_item:
QMessageBox.warning(self, "Fehler", "Bitte wählen Sie eine Serie zum Bearbeiten aus!")
return
slug = current_item.data(Qt.UserRole)
if slug not in self.series_data:
QMessageBox.warning(self, "Fehler", "Serie nicht gefunden!")
return
# Aktualisiere die Einstellungen
self.series_data[slug].update({
'staffel_setting': {
'mode': self.staffel_mode.currentText(),
'staffel': self.staffel_spin.value()
},
'date_preference': self.date_pref.currentText(),
'no_seasons': self.no_seasons_check.isChecked()
})
QMessageBox.information(self, "Erfolg", "Einstellungen wurden gespeichert!")
def add_series(self):
"""Öffnet den Dialog zum Hinzufügen einer neuen Serie"""
dialog = NewSeriesDialog(self)
if dialog.exec_() == QDialog.Accepted:
series_data = dialog.get_series_data()
if series_data:
slug = series_data['slug']
self.series_data[slug] = series_data
self.update_series_list()
# Wähle die neue Serie aus
for i in range(self.series_list.count()):
if self.series_list.item(i).data(Qt.UserRole) == slug:
self.series_list.setCurrentRow(i)
break
QMessageBox.information(self, "Erfolg", "Serie wurde hinzugefügt!")
def on_staffel_mode_changed(self, index):
"""Wird aufgerufen, wenn sich der Staffel-Modus ändert"""
self.staffel_spin.setEnabled(self.staffel_mode.currentText() == "Bestimmte Staffel")
def update_series_list(self):
"""Aktualisiert die Liste der Serien"""
self.series_list.clear()
# Sortiere nach Namen
sorted_series = sorted(self.series_data.items(), key=lambda x: x[1]['name'].lower())
for slug, data in sorted_series:
item = QListWidgetItem(data['name'])
item.setData(Qt.UserRole, slug) # Speichere den Slug als Zusatzdaten
self.series_list.addItem(item)
def on_series_selected(self, item):
"""Wird aufgerufen, wenn eine Serie ausgewählt wird"""
self.settings_group.setEnabled(bool(item))
if item:
slug = item.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten
data = self.series_data.get(slug, {})
self.slug_input.setText(data.get('slug', slug))
# Staffel-Einstellungen laden
staffel_setting = data.get('staffel_setting', {})
mode = staffel_setting.get('mode', "Neuste Staffel")
staffel = staffel_setting.get('staffel', 1)
index = self.staffel_mode.findText(mode)
if index >= 0:
self.staffel_mode.setCurrentIndex(index)
self.staffel_spin.setValue(staffel)
# Datumspräferenz laden
date_pref = data.get('date_preference', "Bevorzuge Erstausstrahlung")
index = self.date_pref.findText(date_pref)
if index >= 0:
self.date_pref.setCurrentIndex(index)
# Keine Staffeln Checkbox laden
self.no_seasons_check.setChecked(data.get('no_seasons', False))
def delete_series(self):
"""Löscht die ausgewählte Serie"""
current_item = self.series_list.currentItem()
if not current_item:
QMessageBox.warning(self, "Fehler", "Bitte wählen Sie eine Serie zum Löschen aus!")
return
slug = current_item.data(Qt.UserRole)
name = self.series_data[slug]['name']
reply = QMessageBox.question(
self,
"Serie löschen",
f"Möchten Sie die Serie '{name}' wirklich löschen?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
del self.series_data[slug]
self.update_series_list()
self.slug_input.clear()
QMessageBox.information(self, "Erfolg", f"Serie '{name}' wurde gelöscht!")
class SerienChecker(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Serien Checker")
self.setGeometry(100, 100, 1000, 800)
# Initialisiere series als leeres Dictionary
self.series = {}
# Log-Fenster erstellen
self.log_window = LogWindow(self)
# Zentral-Widget und Layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Toolbar
toolbar = QToolBar()
self.addToolBar(toolbar)
# Serien verwalten Button
manage_button = QPushButton("Serien verwalten")
manage_button.clicked.connect(self.manage_series)
toolbar.addWidget(manage_button)
# Debug Log Button
debug_button = QPushButton("Debug Log")
debug_button.clicked.connect(self.show_debug_log)
toolbar.addWidget(debug_button)
# Serien und Episoden Layout
content_layout = QHBoxLayout()
# Linke Seite - Serien Liste
list_group = QGroupBox("Gespeicherte Serien")
list_layout = QVBoxLayout()
self.series_list = QListWidget()
self.series_list.currentItemChanged.connect(self.on_series_selected)
list_layout.addWidget(self.series_list)
list_group.setLayout(list_layout)
content_layout.addWidget(list_group)
# Rechte Seite - Episoden
episodes_group = QGroupBox("Episoden")
episodes_layout = QVBoxLayout()
self.episodes_table = QTableWidget()
self.episodes_table.setColumnCount(4)
self.episodes_table.setHorizontalHeaderLabels(["Datum", "Staffel", "Folge", "Titel"])
header = self.episodes_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
header.setSectionResizeMode(3, QHeaderView.Stretch)
episodes_layout.addWidget(self.episodes_table)
# Aktualisieren Button
refresh_button = QPushButton("Aktualisieren")
refresh_button.clicked.connect(self.refresh_selected_series)
episodes_layout.addWidget(refresh_button)
episodes_group.setLayout(episodes_layout)
content_layout.addWidget(episodes_group)
# Füge das Content-Layout zum Hauptlayout hinzu
layout.addLayout(content_layout)
# Timer für automatische Aktualisierung (alle 30 Minuten)
self.timer = QTimer()
self.timer.timeout.connect(self.refresh_selected_series)
self.timer.start(30 * 60 * 1000)
# Lade die Konfiguration und aktualisiere die Liste
self.load_config()
self.update_series_list()
self.show()
def manage_series(self):
dialog = SeriesEditDialog(self.series, self)
if dialog.exec_() == QDialog.Accepted:
self.series = dialog.series_data
self.save_config()
self.update_series_list()
self.refresh_all()
def load_config(self):
"""Lädt die Konfiguration"""
try:
with open('series_config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
if isinstance(config, dict) and 'series' in config:
self.series = config['series']
else:
# Alte Konfiguration kompatibel machen
self.series = {}
for slug, data in config.items():
self.series[slug] = {
'name': data.get('name', slug),
'staffel_setting': {
'mode': data.get('settings', {}).get('mode', "Neuste Staffel"),
'staffel': data.get('settings', {}).get('staffel', 1)
},
'date_preference': "Bevorzuge Erstausstrahlung"
}
except FileNotFoundError:
logging.debug("Keine Konfigurationsdatei gefunden, verwende leeres Dictionary")
self.series = {}
def save_config(self):
"""Speichert die Konfiguration"""
with open('series_config.json', 'w', encoding='utf-8') as f:
json.dump({'series': self.series}, f, indent=4, ensure_ascii=False)
def update_series_list(self):
"""Aktualisiert die Liste der Serien"""
self.series_list.clear()
# Sortiere nach Namen
sorted_series = sorted(self.series.items(), key=lambda x: x[1]['name'].lower())
for slug, data in sorted_series:
item = QListWidgetItem(data['name'])
item.setData(Qt.UserRole, slug) # Speichere den Slug als Zusatzdaten
self.series_list.addItem(item)
def parse_date(self, date_str):
try:
return datetime.strptime(date_str.split()[0], "%d.%m.%Y")
except:
return None
def get_premiere_date(self, episode):
"""Extrahiert das erste deutsche Ausstrahlungsdatum (TV oder Streaming) basierend auf der Präferenz"""
logging.debug("Suche nach Premierendaten")
try:
tv_date = None
streaming_date = None
synchro_date = None
# Suche nach allen deutschen Premieren
for ea_angabe in episode.find_all('ea-angabe'):
titel_elem = ea_angabe.find('ea-angabe-titel')
if not titel_elem:
continue
titel = titel_elem.text.strip()
logging.debug(f"Gefundene Premiere: {titel}")
# Prüfe auf deutsche Daten
datum_elem = ea_angabe.find('ea-angabe-datum')
if datum_elem:
date_str = datum_elem.text.strip()
if '. ' in date_str:
date_str = date_str.split('. ', 1)[1]
parsed_date = self.parse_date(date_str)
if parsed_date:
if "TV-Premiere" in titel and "Deutsche" in titel:
tv_date = (parsed_date, date_str)
logging.debug(f"Deutsche TV-Premiere gefunden: {date_str}")
elif "Streaming-Premiere" in titel and "Deutsche" in titel:
streaming_date = (parsed_date, date_str)
logging.debug(f"Deutsche Streaming-Premiere gefunden: {date_str}")
elif "deutschen Synchronfassung" in titel:
synchro_date = (parsed_date, date_str)
logging.debug(f"Deutsche Synchro-Premiere gefunden: {date_str}")
# Hole die Datumspräferenz für diese Serie
current_series = self.series_list.currentItem()
if current_series:
slug = current_series.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten
pref = self.series[slug].get('date_preference', "Bevorzuge Erstausstrahlung")
else:
pref = "Bevorzuge Erstausstrahlung"
# Prüfe zuerst auf Synchro-Präferenz
if pref == "Bevorzuge deutsche Synchro":
if synchro_date:
result = synchro_date[1]
logging.debug(f"Synchro-Premiere gewählt: {result}")
return result
else:
logging.debug("Keine Synchro-Premiere gefunden, zeige TBA")
return "TBA"
# Andere Präferenzen
if pref == "Bevorzuge TV" and tv_date:
result = tv_date[1]
logging.debug(f"TV-Premiere gewählt: {result}")
elif pref == "Bevorzuge Streaming" and streaming_date:
result = streaming_date[1]
logging.debug(f"Streaming-Premiere gewählt: {result}")
else: # Bevorzuge Erstausstrahlung
dates = []
if tv_date:
dates.append(tv_date)
if streaming_date:
dates.append(streaming_date)
if synchro_date:
dates.append(synchro_date)
if dates:
result = min(dates, key=lambda x: x[0])[1]
logging.debug(f"Frühestes Datum gewählt: {result}")
else:
logging.warning("Keine deutschen Premierendaten gefunden!")
return "TBA"
return result
except Exception as e:
logging.error(f"Fehler beim Extrahieren des Premierendatums: {str(e)}")
return "TBA"
def on_date_preference_changed(self, series_slug):
"""Wird aufgerufen, wenn sich die Datumspräferenz einer Serie ändert"""
logging.debug(f"Datumspräferenz für Serie {series_slug} wurde geändert")
self.refresh_selected_series()
def get_episode_info(self, episode):
"""Extrahiert Folgeninformationen aus einem Episode-Element"""
try:
logging.debug("Versuche Episodeninformationen zu extrahieren")
# Suche nach Staffel und Folge in der Episodennummer (z.B. "7.01")
header = episode.find('h3', class_='episode-output-titel')
if header:
episode_link = header.find('a')
if episode_link:
# URL enthält die Episodennummer (z.B. "7x01")
href = episode_link.get('href', '')
match = re.search(r'(\d+)x(\d+)-', href)
if match:
staffel = int(match.group(1))
folge = int(match.group(2))
logging.debug(f"Gefundene Staffel/Folge aus URL: {staffel}/{folge}")
else:
# Alternative: Suche in der Episoden-Zeile
episode_info = episode.find('div', {'itemprop': 'episodeNumber'})
if episode_info and episode_info.text:
# Prüfe auf "Folge X" Format
folge_match = re.search(r'Folge (\d+)', episode_info.text)
if folge_match:
folge = int(folge_match.group(1))
# Bei Serien ohne Staffeln setzen wir Staffel auf 1
staffel = 1
logging.debug(f"Gefundene Folge aus 'Folge X' Format: {folge}")
else:
try:
folge = int(episode_info.text)
# Staffel aus übergeordnetem Element
staffel_info = episode.find_previous('h2', class_='header-2015')
if staffel_info:
staffel_match = re.search(r'Staffel (\d+)', staffel_info.text)
if staffel_match:
staffel = int(staffel_match.group(1))
logging.debug(f"Gefundene Staffel/Folge aus Text: {staffel}/{folge}")
except ValueError:
logging.debug(f"Konnte Folge nicht aus Text extrahieren: {episode_info.text}")
# Suche nach deutschem Titel
if episode_link:
# Prüfe zuerst, ob ein deutscher Titel existiert
title_spans = episode_link.find_all('span')
title = "Noch kein Titel"
for span in title_spans:
# Überspringe Folgen-Nummer
if span.text.isdigit():
continue
# Überspringe englischen Titel
if span.get('class') and 'episode-output-originaltitel' in span.get('class'):
continue
# Überspringe Platzhalter für fehlenden Titel
if span.get('title') == "Titel unbekannt":
continue
# Wenn wir hier sind und der span itemprop="name" hat, ist es der deutsche Titel
if span.get('itemprop') == 'name' and span.text.strip() not in ['', '-']:
title = span.text.strip()
logging.debug(f"Gefundener deutscher Titel: {title}")
break
logging.debug(f"Finaler Titel: {title}")
else:
title = "Noch kein Titel"
logging.debug("Kein Link gefunden, verwende Standardtitel")
if not all([staffel, folge]):
logging.warning(f"Unvollständige Daten: Staffel={staffel}, Folge={folge}")
return None, None, None
return staffel, folge, title
except Exception as e:
logging.error(f"Fehler beim Extrahieren der Episodeninformationen: {str(e)}")
return None, None, None
def get_staffel_url(self, slug, staffel_nr=None):
"""Generiert die URL für eine bestimmte Staffel"""
logging.debug(f"Generiere Staffel-URL für Slug: {slug}, Staffel: {staffel_nr}")
try:
# Hole die Übersichtsseite
url = f"https://www.fernsehserien.de/{slug}/episodenguide"
logging.debug(f"Hole Übersichtsseite: {url}")
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
staffel_links = []
headers = soup.find_all('h2', class_='header-2015')
for header in headers:
link = header.find('a', class_='fs-linkable')
if not link or not link.get('href'):
continue
href = link['href']
# Prüfe auf normale Staffel-Links
match = re.search(r'/staffel-(\d+)/(\d+)$', href)
if match:
s_nr = int(match.group(1))
serie_id = match.group(2)
staffel_links.append(('staffel', s_nr, serie_id))
logging.debug(f"Gefundene Staffel: {s_nr} mit ID: {serie_id}")
continue
# Prüfe auf "bisher X Folgen" oder Jahres-Format
match = re.search(r'episodenguide/(\d+)/(\d+)$', href)
if match:
section_id = match.group(1)
serie_id = match.group(2)
# Prüfe den Text für die Art des Eintrags
text = link.text.strip()
if "bisher" in text and "Folgen" in text:
logging.debug(f"Gefunden 'bisher X Folgen' mit ID: {serie_id}")
staffel_links.append(('folgen', section_id, serie_id))
elif text.isdigit(): # Jahresformat
logging.debug(f"Gefunden Jahr {text} mit ID: {serie_id}")
staffel_links.append(('jahr', section_id, serie_id))
if not staffel_links:
logging.warning("Keine Staffeln oder Episodengruppen gefunden!")
return None
if staffel_nr:
# Suche nach der gewünschten Staffel (nur für normale Staffeln)
for typ, nr, serie_id in staffel_links:
if typ == 'staffel' and nr == staffel_nr:
url = f"https://www.fernsehserien.de/{slug}/episodenguide/staffel-{staffel_nr}/{serie_id}"
logging.debug(f"Generierte URL für spezifische Staffel: {url}")
return url
logging.warning(f"Staffel {staffel_nr} nicht gefunden!")
return None
else:
# Nehme den neuesten Eintrag
if not staffel_links:
return None
# Sortiere nach Typ und dann nach Nummer/ID
def sort_key(x):
typ, nr, serie_id = x
type_priority = {'staffel': 3, 'jahr': 2, 'folgen': 1}
# Bei Staffeln nach Staffelnummer sortieren, sonst nach section_id
if typ == 'staffel':
return (type_priority.get(typ, 0), int(nr)) # nr ist hier die Staffelnummer
else:
return (type_priority.get(typ, 0), int(nr)) # nr ist hier die section_id
newest = max(staffel_links, key=sort_key)
typ, nr, serie_id = newest
if typ == 'staffel':
url = f"https://www.fernsehserien.de/{slug}/episodenguide/staffel-{nr}/{serie_id}"
logging.debug(f"Generierte URL für neueste Staffel ({nr}): {url}")
else:
url = f"https://www.fernsehserien.de/{slug}/episodenguide/{nr}/{serie_id}"
logging.debug(f"Generierte URL für Episodengruppe: {url}")
return url
except Exception as e:
logging.error(f"Fehler beim Generieren der Staffel-URL: {str(e)}")
return None
def on_series_selected(self, current, previous):
"""Wird aufgerufen, wenn eine Serie in der Liste ausgewählt wird"""
if current:
slug = current.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten
if slug in self.series:
self.refresh_selected_series()
else:
logging.warning(f"Serie {slug} nicht gefunden!")
else:
self.episodes_table.setRowCount(0)
def refresh_selected_series(self):
"""Aktualisiert die Episodenliste für die ausgewählte Serie"""
current_item = self.series_list.currentItem()
if not current_item:
logging.warning("Keine Serie ausgewählt!")
return
slug = current_item.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten
if slug not in self.series:
logging.warning(f"Serie {slug} nicht gefunden!")
return
selected_data = self.series[slug]
logging.debug(f"Aktualisiere Serie: {selected_data['name']}")
try:
# Bestimme die Staffel-URL basierend auf den Einstellungen
settings = selected_data.get('staffel_setting', {})
mode = settings.get('mode', "Neuste Staffel")
staffel_nr = settings.get('staffel') if mode == "Bestimmte Staffel" else None
url = self.get_staffel_url(slug, staffel_nr)
if not url:
self.episodes_table.setRowCount(1)
if mode == "Bestimmte Staffel":
error_msg = f"Staffel {staffel_nr} wurde nicht gefunden!"
else:
error_msg = "Keine Staffeln gefunden!"
self.episodes_table.setItem(0, 0, QTableWidgetItem(error_msg))
for i in range(1, 4):
self.episodes_table.setItem(0, i, QTableWidgetItem(""))
logging.error(error_msg)
return
episodes = []
page = 1
# Prüfe ob die Serie als "ohne klassische Staffeln" markiert ist
no_seasons = selected_data.get('no_seasons', False)
while True:
current_url = f"{url}/{page}" if page > 1 and no_seasons else url
logging.debug(f"Hole Episoden von: {current_url}")
try:
response = requests.get(current_url)
if response.status_code == 404:
logging.debug(f"Seite {page} nicht gefunden, beende Suche")
break
# Bei normalen Staffeln nur eine Seite laden
if not no_seasons:
logging.debug("Serie hat klassische Staffeln, lade nur aktuelle Seite")
response.raise_for_status() # Prüfe auf andere HTTP-Fehler
soup = BeautifulSoup(response.text, 'html.parser')
# Prüfe ob die Seite Episoden enthält
page_episodes = soup.find_all('section', {'itemprop': 'episode'})
if not page_episodes:
logging.debug(f"Keine Episoden auf Seite {page} gefunden, beende Suche")
break
for episode in page_episodes:
staffel, folge, titel = self.get_episode_info(episode)
if all([staffel, folge, titel]):
datum = self.get_premiere_date(episode)
episodes.append({
'date': datum,
'staffel': staffel,
'folge': folge,
'titel': titel
})
# Bei normalen Staffeln nach der ersten Seite aufhören
if not no_seasons:
break
page += 1
except requests.RequestException as e:
logging.error(f"Fehler beim Abrufen von Seite {page}: {str(e)}")
break
if not episodes:
self.episodes_table.setRowCount(1)
error_msg = "Keine Episoden gefunden!"
self.episodes_table.setItem(0, 0, QTableWidgetItem(error_msg))
for i in range(1, 4):
self.episodes_table.setItem(0, i, QTableWidgetItem(""))
logging.warning(error_msg)
return
# Sortiere nach Datum (wenn verfügbar) und Staffel/Folge
episodes.sort(key=lambda x: (
datetime.strptime(x['date'], '%d.%m.%Y') if x['date'] != 'TBA' else datetime.max,
x['staffel'],
x['folge']
))
# Zeige alle gefundenen Episoden an
# episodes = episodes[:20] # Alte Begrenzung entfernt
# Aktualisiere die Tabelle
self.episodes_table.setRowCount(len(episodes))
for row, episode in enumerate(episodes):
self.episodes_table.setItem(row, 0, QTableWidgetItem(episode['date']))
self.episodes_table.setItem(row, 1, QTableWidgetItem(str(episode['staffel'])))
self.episodes_table.setItem(row, 2, QTableWidgetItem(str(episode['folge'])))
self.episodes_table.setItem(row, 3, QTableWidgetItem(episode['titel']))
except Exception as e:
logging.error(f"Fehler beim Aktualisieren der Serie: {str(e)}")
self.episodes_table.setRowCount(1)
self.episodes_table.setItem(0, 0, QTableWidgetItem(f"Fehler: {str(e)}"))
for i in range(1, 4):
self.episodes_table.setItem(0, i, QTableWidgetItem(""))
def refresh_all(self):
self.update_series_list()
if self.series_list.selectedItems():
self.refresh_selected_series()
def show_debug_log(self):
self.log_window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = SerienChecker()
window.show()
sys.exit(app.exec_())