mirror of
https://github.com/NohamR/OqeeRewind.git
synced 2026-01-10 08:08:20 +00:00
Refactor stream and channel selection logic
This commit is contained in:
204
main.py
204
main.py
@@ -6,209 +6,15 @@ from InquirerPy import prompt
|
|||||||
from InquirerPy.validator import EmptyInputValidator
|
from InquirerPy.validator import EmptyInputValidator
|
||||||
from InquirerPy.base.control import Choice
|
from InquirerPy.base.control import Choice
|
||||||
|
|
||||||
from utils.stream import (
|
from utils.input import (
|
||||||
get_manifest,
|
stream_selection,
|
||||||
parse_mpd_manifest,
|
get_date_input,
|
||||||
organize_by_content_type
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SERVICE_PLAN_API_URL = "https://api.oqee.net/api/v6/service_plan"
|
|
||||||
|
|
||||||
def select_oqee_channel():
|
|
||||||
"""Select an Oqee channel from the API.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Selected channel details or None if cancelled/error.
|
|
||||||
"""
|
|
||||||
api_url = SERVICE_PLAN_API_URL
|
|
||||||
try:
|
|
||||||
print("Chargement de la liste des chaînes depuis l'API Oqee...")
|
|
||||||
response = requests.get(api_url, timeout=10)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
if not data.get("success") or "channels" not in data.get("result", {}):
|
|
||||||
print("Erreur: Le format de la réponse de l'API est inattendu.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
channels_data = data["result"]["channels"]
|
|
||||||
choices = [
|
|
||||||
{"name": f"{channel_info.get('name', 'Nom inconnu')}", "value": channel_id}
|
|
||||||
for channel_id, channel_info in channels_data.items()
|
|
||||||
]
|
|
||||||
choices.sort(key=lambda x: x['name'])
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
questions = [
|
|
||||||
{
|
|
||||||
"type": "fuzzy",
|
|
||||||
"message": "Veuillez choisir une chaîne (tapez pour filtrer) :",
|
|
||||||
"choices": choices,
|
|
||||||
"multiselect": False,
|
|
||||||
"validate": EmptyInputValidator(),
|
|
||||||
"invalid_message": "Vous devez sélectionner une chaîne.",
|
|
||||||
"long_instruction": "Utilisez les flèches pour naviguer, Entrée pour sélectionner.",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = prompt(questions)
|
|
||||||
selected_channel_id = result[0]
|
|
||||||
selected_channel_details = channels_data.get(selected_channel_id)
|
|
||||||
if selected_channel_details:
|
|
||||||
print("\n✅ Vous avez sélectionné :")
|
|
||||||
print(f" - Nom : {selected_channel_details.get('name')}")
|
|
||||||
print(f" - ID : {selected_channel_details.get('id')}")
|
|
||||||
print(f" - ID Freebox : {selected_channel_details.get('freebox_id')}")
|
|
||||||
else:
|
|
||||||
print("Impossible de retrouver les détails de la chaîne sélectionnée.")
|
|
||||||
return selected_channel_details
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nOpération annulée par l'utilisateur.")
|
|
||||||
return None
|
|
||||||
except (ValueError, KeyError, IndexError) as e:
|
|
||||||
print(f"Une erreur inattenante est survenue : {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def prompt_for_stream_selection(stream_info, already_selected_types):
|
|
||||||
"""Guide l'utilisateur pour sélectionner un flux, en désactivant les types déjà choisis."""
|
|
||||||
try:
|
|
||||||
content_type_choices = [
|
|
||||||
Choice(value, name=value, enabled=value not in already_selected_types)
|
|
||||||
for value in stream_info.keys()
|
|
||||||
]
|
|
||||||
|
|
||||||
questions = [
|
|
||||||
{
|
|
||||||
"type": "list",
|
|
||||||
"message": "Quel type de flux souhaitez-vous sélectionner ?",
|
|
||||||
"choices": content_type_choices
|
|
||||||
}
|
|
||||||
]
|
|
||||||
result = prompt(questions)
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
selected_type = result[0]
|
|
||||||
|
|
||||||
selected_content_data = stream_info[selected_type]
|
|
||||||
|
|
||||||
questions = [
|
|
||||||
{
|
|
||||||
"type": "list",
|
|
||||||
"message": f"Choisissez une qualité pour '{selected_type}':",
|
|
||||||
"choices": list(selected_content_data.keys())
|
|
||||||
}
|
|
||||||
]
|
|
||||||
result = prompt(questions)
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
quality_group_key = result[0]
|
|
||||||
|
|
||||||
available_streams = selected_content_data[quality_group_key]
|
|
||||||
|
|
||||||
final_selection = None
|
|
||||||
if len(available_streams) == 1:
|
|
||||||
final_selection = available_streams[0]
|
|
||||||
print("Un seul flux disponible pour cette qualité, sélection automatique.")
|
|
||||||
else:
|
|
||||||
stream_choices = [
|
|
||||||
{
|
|
||||||
"name": (
|
|
||||||
f"Bitrate: {s.get('bitrate_kbps')} kbps | "
|
|
||||||
f"Codec: {s.get('codec', 'N/A')} | ID: {s.get('track_id')}"
|
|
||||||
),
|
|
||||||
"value": s
|
|
||||||
}
|
|
||||||
for s in available_streams
|
|
||||||
]
|
|
||||||
questions = [
|
|
||||||
{
|
|
||||||
"type": "list",
|
|
||||||
"message": "Plusieurs flux sont disponibles, choisissez-en un :",
|
|
||||||
"choices": stream_choices
|
|
||||||
}
|
|
||||||
]
|
|
||||||
result = prompt(questions)
|
|
||||||
if not result:
|
|
||||||
return None
|
|
||||||
final_selection = result[0]
|
|
||||||
|
|
||||||
final_selection['content_type'] = selected_type
|
|
||||||
return final_selection
|
|
||||||
|
|
||||||
except (KeyboardInterrupt, TypeError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
selected_channel = select_oqee_channel()
|
selections = stream_selection()
|
||||||
|
start_date, end_date = get_date_input()
|
||||||
if selected_channel:
|
|
||||||
print("\n✅ Chaîne sélectionnée :")
|
|
||||||
print(f" - Nom : {selected_channel.get('name')}")
|
|
||||||
print(f" - ID : {selected_channel.get('id')}")
|
|
||||||
|
|
||||||
dash_id = selected_channel.get('streams', {}).get('dash')
|
|
||||||
if dash_id:
|
|
||||||
mpd_content = get_manifest(dash_id)
|
|
||||||
manifest_info = parse_mpd_manifest(mpd_content)
|
|
||||||
organized_info = organize_by_content_type(manifest_info)
|
|
||||||
|
|
||||||
final_selections = {}
|
|
||||||
|
|
||||||
while True:
|
|
||||||
selection = prompt_for_stream_selection(
|
|
||||||
organized_info, final_selections.keys()
|
|
||||||
)
|
|
||||||
|
|
||||||
if selection:
|
|
||||||
content_type = selection.pop('content_type')
|
|
||||||
final_selections[content_type] = selection
|
|
||||||
|
|
||||||
print("\n--- Récapitulatif de votre sélection ---")
|
|
||||||
for stream_type, details in final_selections.items():
|
|
||||||
bitrate = details.get('bitrate_kbps')
|
|
||||||
track_id = details.get('track_id')
|
|
||||||
print(
|
|
||||||
f" - {stream_type.capitalize()}: "
|
|
||||||
f"Bitrate {bitrate} kbps (ID: {track_id})"
|
|
||||||
)
|
|
||||||
print("----------------------------------------")
|
|
||||||
|
|
||||||
continue_prompt = [
|
|
||||||
{
|
|
||||||
"type": "list",
|
|
||||||
"message": "Que souhaitez-vous faire ?",
|
|
||||||
"choices": [
|
|
||||||
"Sélectionner un autre flux",
|
|
||||||
"Terminer et continuer"
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
action_result = prompt(continue_prompt)
|
|
||||||
|
|
||||||
if (
|
|
||||||
not action_result or
|
|
||||||
action_result[0] == "Terminer et continuer"
|
|
||||||
):
|
|
||||||
break
|
|
||||||
|
|
||||||
if final_selections:
|
|
||||||
print("\n✅ Sélection finale terminée. Voici les flux choisis :")
|
|
||||||
pprint(final_selections)
|
|
||||||
else:
|
|
||||||
print("\nAucun flux n'a été sélectionné.")
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Aucun flux DASH trouvé pour cette chaîne.")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n\nProgramme interrompu par l'utilisateur. Au revoir !")
|
print("\n\nProgramme interrompu par l'utilisateur. Au revoir !")
|
||||||
|
|||||||
208
utils/input.py
208
utils/input.py
@@ -1,6 +1,17 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import requests
|
||||||
from InquirerPy import prompt
|
from InquirerPy import prompt
|
||||||
from prompt_toolkit.validation import Validator, ValidationError
|
from prompt_toolkit.validation import Validator, ValidationError
|
||||||
|
from InquirerPy.validator import EmptyInputValidator
|
||||||
|
from InquirerPy.base.control import Choice
|
||||||
|
|
||||||
|
from .stream import (
|
||||||
|
get_manifest,
|
||||||
|
parse_mpd_manifest,
|
||||||
|
organize_by_content_type
|
||||||
|
)
|
||||||
|
|
||||||
|
SERVICE_PLAN_API_URL = "https://api.oqee.net/api/v6/service_plan"
|
||||||
|
|
||||||
class DatetimeValidator(Validator):
|
class DatetimeValidator(Validator):
|
||||||
"""
|
"""
|
||||||
@@ -97,4 +108,199 @@ def get_date_input():
|
|||||||
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.")
|
||||||
return start_date, end_date
|
return start_date, end_date
|
||||||
|
|
||||||
|
|
||||||
|
def select_oqee_channel():
|
||||||
|
"""Select an Oqee channel from the API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Selected channel details or None if cancelled/error.
|
||||||
|
"""
|
||||||
|
api_url = SERVICE_PLAN_API_URL
|
||||||
|
try:
|
||||||
|
print("Chargement de la liste des chaînes depuis l'API Oqee...")
|
||||||
|
response = requests.get(api_url, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success") or "channels" not in data.get("result", {}):
|
||||||
|
print("Erreur: Le format de la réponse de l'API est inattendu.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
channels_data = data["result"]["channels"]
|
||||||
|
choices = [
|
||||||
|
{"name": f"{channel_info.get('name', 'Nom inconnu')}", "value": channel_id}
|
||||||
|
for channel_id, channel_info in channels_data.items()
|
||||||
|
]
|
||||||
|
choices.sort(key=lambda x: x['name'])
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "fuzzy",
|
||||||
|
"message": "Veuillez choisir une chaîne (tapez pour filtrer) :",
|
||||||
|
"choices": choices,
|
||||||
|
"multiselect": False,
|
||||||
|
"validate": EmptyInputValidator(),
|
||||||
|
"invalid_message": "Vous devez sélectionner une chaîne.",
|
||||||
|
"long_instruction": "Utilisez les flèches pour naviguer, Entrée pour sélectionner.",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = prompt(questions)
|
||||||
|
selected_channel_id = result[0]
|
||||||
|
selected_channel_details = channels_data.get(selected_channel_id)
|
||||||
|
if selected_channel_details:
|
||||||
|
print("\n✅ Vous avez sélectionné :")
|
||||||
|
print(f" - Nom : {selected_channel_details.get('name')}")
|
||||||
|
print(f" - ID : {selected_channel_details.get('id')}")
|
||||||
|
print(f" - ID Freebox : {selected_channel_details.get('freebox_id')}")
|
||||||
|
else:
|
||||||
|
print("Impossible de retrouver les détails de la chaîne sélectionnée.")
|
||||||
|
return selected_channel_details
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nOpération annulée par l'utilisateur.")
|
||||||
|
return None
|
||||||
|
except (ValueError, KeyError, IndexError) as e:
|
||||||
|
print(f"Une erreur inattenante est survenue : {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_for_stream_selection(stream_info, already_selected_types):
|
||||||
|
"""Guide l'utilisateur pour sélectionner un flux, en désactivant les types déjà choisis."""
|
||||||
|
try:
|
||||||
|
content_type_choices = [
|
||||||
|
Choice(value, name=value, enabled=value not in already_selected_types)
|
||||||
|
for value in stream_info.keys()
|
||||||
|
]
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"message": "Quel type de flux souhaitez-vous sélectionner ?",
|
||||||
|
"choices": content_type_choices
|
||||||
|
}
|
||||||
|
]
|
||||||
|
result = prompt(questions)
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
selected_type = result[0]
|
||||||
|
|
||||||
|
selected_content_data = stream_info[selected_type]
|
||||||
|
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"message": f"Choisissez une qualité pour '{selected_type}':",
|
||||||
|
"choices": list(selected_content_data.keys())
|
||||||
|
}
|
||||||
|
]
|
||||||
|
result = prompt(questions)
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
quality_group_key = result[0]
|
||||||
|
|
||||||
|
available_streams = selected_content_data[quality_group_key]
|
||||||
|
|
||||||
|
final_selection = None
|
||||||
|
if len(available_streams) == 1:
|
||||||
|
final_selection = available_streams[0]
|
||||||
|
print("Un seul flux disponible pour cette qualité, sélection automatique.")
|
||||||
|
else:
|
||||||
|
stream_choices = [
|
||||||
|
{
|
||||||
|
"name": (
|
||||||
|
f"Bitrate: {s.get('bitrate_kbps')} kbps | "
|
||||||
|
f"Codec: {s.get('codec', 'N/A')} | ID: {s.get('track_id')}"
|
||||||
|
),
|
||||||
|
"value": s
|
||||||
|
}
|
||||||
|
for s in available_streams
|
||||||
|
]
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"message": "Plusieurs flux sont disponibles, choisissez-en un :",
|
||||||
|
"choices": stream_choices
|
||||||
|
}
|
||||||
|
]
|
||||||
|
result = prompt(questions)
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
final_selection = result[0]
|
||||||
|
|
||||||
|
final_selection['content_type'] = selected_type
|
||||||
|
return final_selection
|
||||||
|
|
||||||
|
except (KeyboardInterrupt, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def stream_selection():
|
||||||
|
selected_channel = select_oqee_channel()
|
||||||
|
|
||||||
|
if selected_channel:
|
||||||
|
print("\n✅ Chaîne sélectionnée :")
|
||||||
|
print(f" - Nom : {selected_channel.get('name')}")
|
||||||
|
print(f" - ID : {selected_channel.get('id')}")
|
||||||
|
|
||||||
|
dash_id = selected_channel.get('streams', {}).get('dash')
|
||||||
|
if dash_id:
|
||||||
|
mpd_content = get_manifest(dash_id)
|
||||||
|
manifest_info = parse_mpd_manifest(mpd_content)
|
||||||
|
organized_info = organize_by_content_type(manifest_info)
|
||||||
|
|
||||||
|
final_selections = {}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
selection = prompt_for_stream_selection(
|
||||||
|
organized_info, final_selections.keys()
|
||||||
|
)
|
||||||
|
|
||||||
|
if selection:
|
||||||
|
content_type = selection.pop('content_type')
|
||||||
|
final_selections[content_type] = selection
|
||||||
|
|
||||||
|
print("\n--- Récapitulatif de votre sélection ---")
|
||||||
|
for stream_type, details in final_selections.items():
|
||||||
|
bitrate = details.get('bitrate_kbps')
|
||||||
|
track_id = details.get('track_id')
|
||||||
|
print(
|
||||||
|
f" - {stream_type.capitalize()}: "
|
||||||
|
f"Bitrate {bitrate} kbps (ID: {track_id})"
|
||||||
|
)
|
||||||
|
print("----------------------------------------")
|
||||||
|
|
||||||
|
continue_prompt = [
|
||||||
|
{
|
||||||
|
"type": "list",
|
||||||
|
"message": "Que souhaitez-vous faire ?",
|
||||||
|
"choices": [
|
||||||
|
"Sélectionner un autre flux",
|
||||||
|
"Terminer et continuer"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
action_result = prompt(continue_prompt)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not action_result or
|
||||||
|
action_result[0] == "Terminer et continuer"
|
||||||
|
):
|
||||||
|
break
|
||||||
|
|
||||||
|
if final_selections:
|
||||||
|
return final_selections
|
||||||
|
else:
|
||||||
|
print("\nAucun flux n'a été sélectionné.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Aucun flux DASH trouvé pour cette chaîne.")
|
||||||
Reference in New Issue
Block a user