diff --git a/nava/functions.py b/nava/functions.py index a380729..78319eb 100644 --- a/nava/functions.py +++ b/nava/functions.py @@ -7,11 +7,13 @@ import shlex from hashlib import sha256 from functools import wraps +import importlib.util from typing import Callable, List, Dict, Any, Optional 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 @@ -193,6 +195,17 @@ def __play_winsound_flags(sound_path: str, flags: int) -> None: winsound.PlaySound(sound_path, flags) +def __play_google_colab(sound_path: str) -> None: + """ + Play sound in Google Colab Notebook. + + :param sound_path: sound path + """ + from IPython.display import Audio, display + audio = Audio(sound_path, autoplay=True) + display(audio) + + @quote def __play_alsa(sound_path: str, async_mode: bool = False, loop: bool = False) -> Optional[int]: """ @@ -309,6 +322,11 @@ def __play_auto(sound_path: str, async_mode: bool = False, loop: bool = False) - :param async_mode: async mode flag :param loop: sound loop flag """ + 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) @@ -363,3 +381,42 @@ def play_cli(sound_path: str, loop: bool = False) -> None: print("Error: {0}".format(e)) finally: stop_all() + + +def detect_environment() -> PythonEnvironment: + """ + Detect the current Python execution environment. + + Supported environments: + - Google Colab + - Local Jupyter Notebook/Lab + - VS Code Notebook + - IPython Terminal + - Plain Python script + """ + 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 112c532..9479559 100644 --- a/nava/params.py +++ b/nava/params.py @@ -25,6 +25,29 @@ class Engine(Enum): WINMM = "winmm" +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."