mirror of
https://github.com/NohamR/OqeeRewind.git
synced 2026-01-11 16:48:17 +00:00
Add EPG program selection to stream workflow
Introduces EPG data fetching and program selection in main.py, allowing users to choose a program from the EPG guide if the start date is within the last 7 days. Adds get_epg_data_at and select_program_from_epg utilities to input.py, and updates stream_selection to include channel info in the returned selections.
This commit is contained in:
20
main.py
20
main.py
@@ -1,14 +1,34 @@
|
|||||||
"""Main module for Oqee channel selection and stream management."""
|
"""Main module for Oqee channel selection and stream management."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from utils.input import (
|
from utils.input import (
|
||||||
stream_selection,
|
stream_selection,
|
||||||
get_date_input,
|
get_date_input,
|
||||||
|
get_epg_data_at,
|
||||||
|
select_program_from_epg
|
||||||
)
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
selections = stream_selection()
|
selections = stream_selection()
|
||||||
|
freebox_id = selections.get("channel", {}).get("freebox_id")
|
||||||
|
channel_id = selections.get("channel", {}).get("id")
|
||||||
|
|
||||||
start_date, end_date = get_date_input()
|
start_date, end_date = get_date_input()
|
||||||
|
|
||||||
|
if start_date > datetime.now() - timedelta(days=7):
|
||||||
|
epg_data = get_epg_data_at(start_date)
|
||||||
|
|
||||||
|
programs = epg_data["entries"][str(channel_id)]
|
||||||
|
program_selection = select_program_from_epg(
|
||||||
|
programs,
|
||||||
|
start_date,
|
||||||
|
end_date
|
||||||
|
)
|
||||||
|
if program_selection:
|
||||||
|
start_date = program_selection['start_date']
|
||||||
|
end_date = program_selection['end_date']
|
||||||
|
title = program_selection['title']
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n\nProgramme interrompu par l'utilisateur. Au revoir !")
|
print("\n\nProgramme interrompu par l'utilisateur. Au revoir !")
|
||||||
|
|||||||
147
utils/input.py
147
utils/input.py
@@ -15,6 +15,7 @@ from utils.stream import (
|
|||||||
SERVICE_PLAN_API_URL = "https://api.oqee.net/api/v6/service_plan"
|
SERVICE_PLAN_API_URL = "https://api.oqee.net/api/v6/service_plan"
|
||||||
EPG_API_URL = "https://api.oqee.net/api/v1/epg/all/{unix}"
|
EPG_API_URL = "https://api.oqee.net/api/v1/epg/all/{unix}"
|
||||||
|
|
||||||
|
|
||||||
class DatetimeValidator(Validator):
|
class DatetimeValidator(Validator):
|
||||||
"""
|
"""
|
||||||
Validateur personnalisé pour les chaînes datetime au format "YYYY-MM-DD HH:MM:SS".
|
Validateur personnalisé pour les chaînes datetime au format "YYYY-MM-DD HH:MM:SS".
|
||||||
@@ -28,6 +29,7 @@ class DatetimeValidator(Validator):
|
|||||||
cursor_position=len(document.text),
|
cursor_position=len(document.text),
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
class DurationValidator(Validator):
|
class DurationValidator(Validator):
|
||||||
"""
|
"""
|
||||||
Validateur personnalisé pour les chaînes de durée au format "HH:MM:SS".
|
Validateur personnalisé pour les chaînes de durée au format "HH:MM:SS".
|
||||||
@@ -48,6 +50,8 @@ class DurationValidator(Validator):
|
|||||||
message="Format invalide. Utilisez HH:MM:SS avec des nombres valides.",
|
message="Format invalide. Utilisez HH:MM:SS avec des nombres valides.",
|
||||||
cursor_position=len(document.text),
|
cursor_position=len(document.text),
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def get_date_input():
|
def get_date_input():
|
||||||
"""Prompt user for start and end date/time or duration.
|
"""Prompt user for start and end date/time or duration.
|
||||||
|
|
||||||
@@ -113,7 +117,9 @@ def get_date_input():
|
|||||||
|
|
||||||
elif end_date_result.get("datetime"):
|
elif end_date_result.get("datetime"):
|
||||||
try:
|
try:
|
||||||
end_date = datetime.datetime.strptime(end_date_result["datetime"], "%Y-%m-%d %H:%M:%S")
|
end_date = datetime.datetime.strptime(
|
||||||
|
end_date_result["datetime"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
print(f"\nDate/heure de fin : {end_date}")
|
print(f"\nDate/heure de fin : {end_date}")
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
print("Impossible d'analyser la chaîne de date/heure fournie.")
|
print("Impossible d'analyser la chaîne de date/heure fournie.")
|
||||||
@@ -320,7 +326,146 @@ def stream_selection():
|
|||||||
break
|
break
|
||||||
|
|
||||||
if final_selections:
|
if final_selections:
|
||||||
|
final_selections['channel'] = selected_channel
|
||||||
return final_selections
|
return final_selections
|
||||||
|
|
||||||
print("\nAucun flux n'a été sélectionné.")
|
print("\nAucun flux n'a été sélectionné.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_epg_data_at(dt: datetime.datetime):
|
||||||
|
"""
|
||||||
|
Fetch EPG data from the Oqee API for the nearest aligned hour of a given datetime.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dt (datetime.datetime): datetime输入 (with hour, minute, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict | None: EPG data or None on error
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Round to nearest hour
|
||||||
|
if dt.minute >= 30:
|
||||||
|
dt_aligned = (dt + datetime.timedelta(hours=1)).replace(minute=0, second=0, microsecond=0)
|
||||||
|
else:
|
||||||
|
dt_aligned = dt.replace(minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
unix_time = int(dt_aligned.timestamp())
|
||||||
|
print(f"Fetching EPG for aligned time: {dt_aligned} (unix={unix_time})")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(EPG_API_URL.format(unix=unix_time), timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
return data.get("result")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Une erreur réseau est survenue : {e}")
|
||||||
|
return None
|
||||||
|
except ValueError:
|
||||||
|
print("Erreur lors de l'analyse de la réponse JSON.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def select_program_from_epg(programs, original_start_date, original_end_date):
|
||||||
|
"""
|
||||||
|
Prompt user to select a program from EPG data or keep original selection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
programs (list): List of program dictionaries from EPG data
|
||||||
|
original_start_date (datetime.datetime): User's original start date selection
|
||||||
|
original_end_date (datetime.datetime): User's original end date selection
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary containing:
|
||||||
|
- 'start_date': datetime object for start
|
||||||
|
- 'end_date': datetime object for end
|
||||||
|
- 'title': str or None (program title if selected)
|
||||||
|
- 'program': dict or None (full program data if selected)
|
||||||
|
"""
|
||||||
|
if not programs:
|
||||||
|
print("Aucun programme disponible dans le guide EPG.")
|
||||||
|
return {
|
||||||
|
'start_date': original_start_date,
|
||||||
|
'end_date': original_end_date,
|
||||||
|
'title': None,
|
||||||
|
'program': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create choices list with program information
|
||||||
|
program_choices = []
|
||||||
|
for program in programs:
|
||||||
|
# Extract the live data from the program
|
||||||
|
live_data = program.get("live", program)
|
||||||
|
title = live_data.get('title', 'Sans titre')
|
||||||
|
start_time = datetime.datetime.fromtimestamp(live_data.get('start', 0))
|
||||||
|
end_time = datetime.datetime.fromtimestamp(live_data.get('end', 0))
|
||||||
|
duration_min = (end_time - start_time).total_seconds() / 60
|
||||||
|
|
||||||
|
choice_name = (
|
||||||
|
f"{start_time.strftime('%H:%M')} - {end_time.strftime('%H:%M')} | "
|
||||||
|
f"{title} ({int(duration_min)} min)"
|
||||||
|
)
|
||||||
|
program_choices.append({
|
||||||
|
"name": choice_name,
|
||||||
|
"value": program # Store the full program object
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add option to keep original selection
|
||||||
|
program_choices.insert(0, {
|
||||||
|
"name": (
|
||||||
|
f"Garder la sélection manuelle originale "
|
||||||
|
f"({original_start_date.strftime('%Y-%m-%d %H:%M:%S')} - "
|
||||||
|
f"{original_end_date.strftime('%Y-%m-%d %H:%M:%S')})"
|
||||||
|
),
|
||||||
|
"value": None
|
||||||
|
})
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"message": "Sélectionnez un programme ou gardez votre sélection manuelle :",
|
||||||
|
"choices": program_choices,
|
||||||
|
"long_instruction": "Utilisez les flèches pour naviguer, Entrée pour sélectionner.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = prompt(questions)
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
|
||||||
|
selected_program = result[0]
|
||||||
|
|
||||||
|
# If user chose to keep original selection
|
||||||
|
if selected_program is None:
|
||||||
|
print("\n✅ Sélection manuelle conservée")
|
||||||
|
return {
|
||||||
|
'start_date': original_start_date,
|
||||||
|
'end_date': original_end_date,
|
||||||
|
'title': None,
|
||||||
|
'program': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract live data and convert program timestamps to datetime objects
|
||||||
|
live_data = selected_program.get('live', selected_program)
|
||||||
|
program_start = datetime.datetime.fromtimestamp(live_data.get('start', 0))
|
||||||
|
program_end = datetime.datetime.fromtimestamp(live_data.get('end', 0))
|
||||||
|
program_title = live_data.get('title', 'Sans titre')
|
||||||
|
|
||||||
|
print("\n✅ Programme sélectionné :")
|
||||||
|
print(f" - Titre : {program_title}")
|
||||||
|
print(f" - Début : {program_start.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
print(f" - Fin : {program_end.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'start_date': program_start,
|
||||||
|
'end_date': program_end,
|
||||||
|
'title': program_title,
|
||||||
|
'program': selected_program
|
||||||
|
}
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nOpération annulée par l'utilisateur.")
|
||||||
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user