diff --git a/nava/functions.py b/nava/functions.py index 026350d..73a7623 100644 --- a/nava/functions.py +++ b/nava/functions.py @@ -5,10 +5,12 @@ import os import shlex from functools import wraps +import importlib.util from .thread import NavaThread from .params import OVERVIEW, Engine from .params import SOUND_FILE_PLAY_ERROR, SOUND_FILE_EXIST_ERROR, ENGINE_TYPE_ERROR from .params import SOUND_FILE_PATH_TYPE_ERROR, SOUND_ID_EXIST_ERROR, LOOP_ASYNC_ERROR +from .params import PythonEnvironment, SHELL_TYPE_ZMQ, SHELL_TYPE_TERMINAL, VSCODE_ENV_VARS from .errors import NavaBaseError from . import params @@ -129,6 +131,21 @@ def __play_winsound_flags(sound_path, flags): winsound.PlaySound(sound_path, flags) +def __play_google_colab(sound_path): + """ + Play sound in Google Colab Notebook. + + :param sound_path: sound path + :type sound_path: str + :param loop: sound loop flag + :type loop: bool + :return: None + """ + from IPython.display import Audio, display + audio = Audio(sound_path, autoplay=True) + display(audio) + + @quote def __play_alsa(sound_path, async_mode=False, loop=False): """ @@ -265,6 +282,11 @@ def __play_auto(sound_path, async_mode=False, loop=False): :type loop: bool :return: None or sound id """ + env = detect_environment() + if env == PythonEnvironment.COLAB: + return __play_google_colab(sound_path) + # we will add other notebook environment handlers in the future + sys_platform = sys.platform if sys_platform == "win32": return __play_winsound(sound_path, async_mode, loop) @@ -325,3 +347,44 @@ def play_cli(sound_path, loop=False): print("Error: {0}".format(e)) finally: stop_all() + + +def detect_environment(): + """ + Detect the current Python execution environment. + + Supported environments: + - Google Colab + - Local Jupyter Notebook/Lab + - VS Code Notebook + - IPython Terminal + - Plain Python script + + :return: PythonEnvironment Enum value indicating the environment. + """ + ip = None + try: + from IPython import get_ipython + ip = get_ipython() + except ImportError: + return PythonEnvironment.PLAIN_PYTHON + if ip is None: + return PythonEnvironment.PLAIN_PYTHON + + shell_name = ip.__class__.__name__.lower() + + # Explicit Google Colab check (most reliable) + if importlib.util.find_spec("google.colab") is not None: + return PythonEnvironment.COLAB + + # VS Code check via known env vars + if any(var in os.environ for var in VSCODE_ENV_VARS): + return PythonEnvironment.VSCODE + + if shell_name == SHELL_TYPE_ZMQ: + return PythonEnvironment.LOCAL_JUPYTER + + if shell_name == SHELL_TYPE_TERMINAL: + return PythonEnvironment.IPYTHON_TERMINAL + + return PythonEnvironment.UNKNOWN diff --git a/nava/params.py b/nava/params.py index b161dd7..7b3cbeb 100644 --- a/nava/params.py +++ b/nava/params.py @@ -24,6 +24,29 @@ class Engine(Enum): AFPLAY = "afplay" +class PythonEnvironment(Enum): + """Python environment class.""" + + COLAB = "Google Colab" + LOCAL_JUPYTER = "Local Jupyter Notebook or JupyterLab" + VSCODE = "VS Code Notebook" + IPYTHON_TERMINAL = "IPython Terminal" + PLAIN_PYTHON = "Plain Python (.py script)" + UNKNOWN = "Unknown Environment" + + +# Environment variables typically set by VS Code +VSCODE_ENV_VARS = [ + "VSCODE_PID", # this is often set when running in VS Code + "VSCODE_CWD", # this is often set when running in VS Code + "VSCODE_IPC_HOOK_CLI", + "TERM_PROGRAM", # often set to "vscode" +] + +# Shell type identifiers +SHELL_TYPE_ZMQ = "zmqinteractiveshell" # Jupyter Notebook/Lab +SHELL_TYPE_TERMINAL = "terminalinteractiveshell" # IPython Terminal + SOUND_FILE_PLAY_ERROR = "Sound can not play due to some issues." SOUND_FILE_EXIST_ERROR = "Given sound file doesn't exist." SOUND_FILE_PATH_TYPE_ERROR = "Sound file's path should be a string."