diff --git a/core/ajax/ttscast.ajax.php b/core/ajax/ttscast.ajax.php index 7e010631..0c7a1992 100644 --- a/core/ajax/ttscast.ajax.php +++ b/core/ajax/ttscast.ajax.php @@ -109,23 +109,25 @@ } log::add('ttscast', 'debug', "[UPLOAD][CustomSound] filename: {$_FILES['fileCustomSound']['name']}"); $extension = strtolower(strrchr($_FILES['fileCustomSound']['name'], '.')); - if (!in_array($extension, array('.mp3'))) { - throw new Exception('[UPLOAD][CustomSound] Extension de fichier non valide (autorisé .mp3) : ' . $extension); + if (!in_array($extension, array('.mp3', '.wav', '.ogg', '.opus', '.flac'))) { + throw new Exception('[UPLOAD][CustomSound] Extension de fichier non valide (autorisé .mp3, .wav, .ogg, .opus, .flac) : ' . $extension); } + $safeFilename = basename($_FILES['fileCustomSound']['name']); + # TODO limiter taille upload mp3 dans les customSounds ? /* if (filesize($_FILES['fileCustomSound']['tmp_name']) > 10000) { throw new Exception(__('[UPLOAD][CustomSound] Le fichier est trop gros (max. 10Ko)', __FILE__)); } */ - $filepath = __DIR__ . "/../../data/media/custom/{$_FILES['fileCustomSound']['name']}"; + $filepath = __DIR__ . "/../../data/media/custom/{$safeFilename}"; log::add('ttscast', 'debug', "[UPLOAD][CustomSound] filepath: {$filepath}"); file_put_contents($filepath, file_get_contents($_FILES['fileCustomSound']['tmp_name'])); if (!file_exists($filepath)) { throw new Exception(__('[UPLOAD][CustomSound] Impossible de sauvegarder le fichier', __FILE__)); } - log::add('ttscast', 'info', "[UPLOAD][CustomSound] Upload OK :: {$_FILES['fileCustomSound']['name']}"); - ajax::success("{$_FILES['fileCustomSound']['name']}"); + log::add('ttscast', 'info', "[UPLOAD][CustomSound] Upload OK :: {$safeFilename}"); + ajax::success("{$safeFilename}"); } if (init('action') == 'uploadCustomRadios') { diff --git a/core/class/ttscast.class.php b/core/class/ttscast.class.php index bde5c1f0..e72ee0e7 100644 --- a/core/class/ttscast.class.php +++ b/core/class/ttscast.class.php @@ -814,7 +814,7 @@ public function getSoundList() $filesReturn = ''; try { $filesArray = array(); - foreach (glob(dirname(__FILE__) . '/../../data/media/*.mp3') as $fileName) { + foreach (glob(dirname(__FILE__) . '/../../data/media/*.{mp3,wav,ogg,opus,flac}', GLOB_BRACE) as $fileName) { $filesArray[pathinfo($fileName, PATHINFO_BASENAME)] = ucwords(str_replace(["_", "-"], " ", pathinfo($fileName, PATHINFO_FILENAME))); } natsort($filesArray); @@ -833,7 +833,7 @@ public function getCustomSoundList() $filesReturn = ''; try { $filesArray = array(); - foreach (glob(dirname(__FILE__) . '/../../data/media/custom/*.mp3') as $fileName) { + foreach (glob(dirname(__FILE__) . '/../../data/media/custom/*.{mp3,wav,ogg,opus,flac}', GLOB_BRACE) as $fileName) { $filesArray[pathinfo($fileName, PATHINFO_BASENAME)] = ucwords(str_replace(["_", "-"], " ", pathinfo($fileName, PATHINFO_FILENAME))); } natsort($filesArray); diff --git a/core/php/ttscast.audio.proxy.php b/core/php/ttscast.audio.proxy.php index 48f2aecd..0814bfb2 100644 --- a/core/php/ttscast.audio.proxy.php +++ b/core/php/ttscast.audio.proxy.php @@ -24,7 +24,6 @@ die(); } - // Validation stricte : MD5 hex (32 car.) + extension audio autorisée $mimeTypes = [ 'mp3' => 'audio/mp3', 'wav' => 'audio/wav', @@ -33,15 +32,34 @@ 'flac' => 'audio/flac', ]; + $type = isset($_GET['type']) ? $_GET['type'] : ''; $file = isset($_GET['file']) ? $_GET['file'] : ''; - if (!preg_match('/^([a-f0-9]{32})\.(mp3|wav|ogg|opus|flac)$/', $file, $matches)) { + + if ($type === 'tts') { + // Validation stricte : MD5 hex (32 car.) + extension audio autorisée + if (!preg_match('/^([a-f0-9]{32})\.(mp3|wav|ogg|opus|flac)$/', $file, $matches)) { + http_response_code(400); + die(); + } + $mime = $mimeTypes[$matches[2]]; + $filePath = dirname(dirname(__DIR__)) . '/data/cache/' . $file; + + } elseif ($type === 'sounds' || $type === 'customsounds') { + // Validation : nom de fichier sûr (pas de séparateur de répertoire, extension autorisée) + $safeFile = basename($file); + if (!preg_match('/^([a-zA-Z0-9._-]+)\.(mp3|wav|ogg|opus|flac)$/', $safeFile, $matches)) { + http_response_code(400); + die(); + } + $mime = $mimeTypes[$matches[2]]; + $subDir = ($type === 'customsounds') ? 'custom/' : ''; + $filePath = dirname(dirname(__DIR__)) . '/data/media/' . $subDir . $safeFile; + + } else { http_response_code(400); die(); } - $mime = $mimeTypes[$matches[2]]; - $filePath = dirname(dirname(__DIR__)) . '/data/cache/' . $file; - if (!file_exists($filePath) || !is_file($filePath)) { http_response_code(404); die(); diff --git a/plugin_info/configuration.php b/plugin_info/configuration.php index 64b30642..65113a19 100644 --- a/plugin_info/configuration.php +++ b/plugin_info/configuration.php @@ -1069,11 +1069,11 @@
- {{Ajouter un Custom Sound (.mp3)}} + {{Ajouter un Custom Sound}}
diff --git a/plugin_info/info.json b/plugin_info/info.json index e4794574..3e5eac94 100644 --- a/plugin_info/info.json +++ b/plugin_info/info.json @@ -1,7 +1,7 @@ { "id": "ttscast", "name": "TTS Cast", - "pluginVersion": "1.9.0", + "pluginVersion": "1.9.1", "description": { "fr_FR": "Plugin pour gérer ses équipements Google, type Google Home, Nest Mini, Nest Hub (Max), Chromecast. Il permet de générer des notifications TTS (Text To Speech) et de les diffuser sur les équipements Google. Il permet également de diffuser des sons (mp3), des vidéos YouTube, une page Web, ou encore une radio en streaming sur ces mêmes équipements.", "en_US": "Plugin to manage Google equipment, such as Google Home, Nest Mini, Nest Hub (Max), Chromecast. It allows you to generate TTS (Text To Speech) notifications and broadcast them to Google devices. It also allows you to broadcast sounds (mp3), YouTube videos, a web page, or even streaming radio on the same equipment.", diff --git a/resources/ttscastd/ttscastd.py b/resources/ttscastd/ttscastd.py index 7ca64d4b..4e179263 100644 --- a/resources/ttscastd/ttscastd.py +++ b/resources/ttscastd/ttscastd.py @@ -31,7 +31,7 @@ import wave import io -from urllib.parse import urlencode, urlparse +from urllib.parse import urlencode, urlparse, quote from uuid import UUID # Import pour Jeedom @@ -2540,9 +2540,9 @@ def controllerSounds(cast, _googleUUID, _value, _options, _controller): cast.set_volume(volume=_volume / 100) if (_controller == 'customsounds'): - soundURL = f'{myConfig.ttsWebSrvMedia}custom/{_value}' + soundURL = f'{myConfig.ttsWebSrvMediaProxy}?type=customsounds&file={quote(_value, safe="")}' else: - soundURL = f'{myConfig.ttsWebSrvMedia}{_value}' + soundURL = f'{myConfig.ttsWebSrvMediaProxy}?type=sounds&file={quote(_value, safe="")}' logging.debug(f'[DAEMON][controllerActions] {soundType} :: FilePath :: {soundURL}') soundThumb = f'{myConfig.ttsWebSrvImages}tts.png' @@ -2550,11 +2550,18 @@ def controllerSounds(cast, _googleUUID, _value, _options, _controller): soundTitle = f"TTSCast {soundType}" soundArtist = _value + _soundMimeTypes = { + 'mp3': 'audio/mp3', 'wav': 'audio/wav', + 'ogg': 'audio/ogg', 'opus': 'audio/ogg; codecs=opus', 'flac': 'audio/flac' + } + _ext = os.path.splitext(_value)[1].lstrip('.').lower() + soundMimeType = _soundMimeTypes.get(_ext, 'audio/mp3') + app_name = "default_media_receiver" # app_name = "bubbleupnp" app_data = { "media_id": soundURL, - "media_type": "audio/mp3", + "media_type": soundMimeType, "stream_type": "BUFFERED", "title": soundTitle, "thumb": soundThumb, @@ -3557,8 +3564,8 @@ def shutdown(): if args.ttsweb: # Normalize base URL once for all paths (supports Jeedom in subdirectories) ttsweb_base_url = args.ttsweb.rstrip('/') - myConfig.ttsWebSrvCache = f'{ttsweb_base_url}/plugins/ttscast/core/php/ttscast.audio.proxy.php?file=' - myConfig.ttsWebSrvMedia = f'{ttsweb_base_url}/plugins/ttscast/data/media/' + myConfig.ttsWebSrvCache = f'{ttsweb_base_url}/plugins/ttscast/core/php/ttscast.audio.proxy.php?type=tts&file=' + myConfig.ttsWebSrvMediaProxy = f'{ttsweb_base_url}/plugins/ttscast/core/php/ttscast.audio.proxy.php' myConfig.ttsWebSrvImages = f'{ttsweb_base_url}/plugins/ttscast/data/images/' myConfig.ttsWebSrvJeeTTS = f'{ttsweb_base_url}/core/api/' @@ -3619,7 +3626,7 @@ def shutdown(): logging.info('[DAEMON][MAIN] Cmd Wait Timeout: %s', str(myConfig.cmdWaitTimeout)) logging.info('[DAEMON][MAIN] CallBack: %s', myConfig.callBack) logging.info('[DAEMON][MAIN] Jeedom WebSrvCache: %s', myConfig.ttsWebSrvCache) -logging.info('[DAEMON][MAIN] Jeedom WebSrvMedia: %s', myConfig.ttsWebSrvMedia) +logging.info('[DAEMON][MAIN] Jeedom WebSrvMediaProxy: %s', myConfig.ttsWebSrvMediaProxy) logging.info('[DAEMON][MAIN] Jeedom WebSrvImages: %s', myConfig.ttsWebSrvImages) logging.info('[DAEMON][MAIN] Jeedom WebSrvJeeTTS: %s', myConfig.ttsWebSrvJeeTTS) diff --git a/resources/ttscastd/utils.py b/resources/ttscastd/utils.py index e0d5ea7e..d83d0121 100644 --- a/resources/ttscastd/utils.py +++ b/resources/ttscastd/utils.py @@ -58,7 +58,7 @@ class Config: # soundsCustomPath = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'data/media/custom')) ttsWebSrvCache = '' - ttsWebSrvMedia = '' + ttsWebSrvMediaProxy = '' ttsWebSrvImages = '' ttsWebSrvJeeTTS = ''