from flask import Blueprint, jsonify, request, current_app from application.kodi_thread import ( start_kodi_play, is_kodi_thread_running, play_image, play_rtsp, revoke_individual_state, start_all_kodi_apps, get_kodi_clients, get_video_list, set_volume, get_volume ) from application.kodi_free_time_thread import ( start_kodi_free_time_play, stop_kodi_free_time_play, get_free_time_play_status ) from application.wled_thread import start_exhibit_led_effect from utils.logger_config import logger from api.utils import login_required, allowed_file, get_server_ip, ALLOWED_EXTENSIONS import os import time import uuid kodi_bp = Blueprint('kodi', __name__) @kodi_bp.route('/api/kodi/status', methods=['GET']) @login_required def get_kodi_status(): try: running = is_kodi_thread_running() return jsonify({ "success": True, "data": { "is_running": running, "message": "Kodi播放线程运行中" if running else "Kodi播放线程已停止" } }) except Exception as e: return jsonify({ "success": False, "message": f"获取Kodi状态失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/clients', methods=['GET']) @login_required def get_kodi_clients_api(): """获取所有Kodi客户端列表""" try: clients = get_kodi_clients() return jsonify({ "success": True, "data": clients }) except Exception as e: return jsonify({ "success": False, "message": f"获取Kodi客户端列表失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/videos', methods=['GET']) def get_kodi_videos_api(): """获取所有视频列表""" try: videos = get_video_list() return jsonify({ "success": True, "data": videos }) except Exception as e: return jsonify({ "success": False, "message": f"获取视频列表失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/start', methods=['POST']) def start_kodi_play_api(): try: data = request.get_json() if not data or 'video_id' not in data: return jsonify({ "success": False, "message": "缺少视频ID参数" }), 400 video_id = data['video_id'] volume = data.get('volume', -1) if not isinstance(video_id, int) or video_id < 0: return jsonify({ "success": False, "message": "视频ID必须是大于等于0的整数" }), 400 # 验证 volume 参数 if volume != -1: if not isinstance(volume, int) or not (0 <= volume <= 100): return jsonify({ "success": False, "message": "音量必须是 0-100 之间的整数" }), 400 ok = start_kodi_play(video_id, volume) if ok: # 像播放视频一样,直接触发一次 LED 任务(内部已是线程异步) if video_id > 0: led_ok = start_exhibit_led_effect(video_id-1) return jsonify({ "success": True, "message": f"Kodi开始/切换播放 视频ID={video_id} 音量={'默认' if volume == -1 else volume}", "data": { "video_id": video_id, "volume": volume } }) return jsonify({ "success": False, "message": f"Kodi播放启动失败(视频ID={video_id})" }), 500 except Exception as e: return jsonify({ "success": False, "message": f"Kodi播放启动异常: {str(e)}" }), 500 # 指定某台kodi_client_index播放图片,这边要上传图片并且传递完整url给kodi播放 @kodi_bp.route('/api/kodi/play_image', methods=['POST']) @login_required def play_image_api(): """播放图片接口,支持文件上传或直接传递图片URL""" try: # 检查是否有文件上传 if 'file' in request.files: file = request.files['file'] if file.filename == '': return jsonify({ "success": False, "message": "未选择文件" }), 400 if file and allowed_file(file.filename): # 获取原始文件扩展名 original_filename = file.filename # 提取文件扩展名(不包含点号) if '.' in original_filename: file_ext = original_filename.rsplit('.', 1)[1].lower() else: # 如果没有扩展名,根据Content-Type推断或默认为jpg content_type = file.content_type or '' if 'png' in content_type: file_ext = 'png' elif 'jpeg' in content_type or 'jpg' in content_type: file_ext = 'jpg' elif 'gif' in content_type: file_ext = 'gif' else: file_ext = 'jpg' # 默认扩展名 # 确保扩展名在允许列表中 if file_ext not in ALLOWED_EXTENSIONS: file_ext = 'jpg' # 生成唯一文件名:使用时间戳和UUID,确保文件名唯一且安全 timestamp = int(time.time() * 1000) unique_id = str(uuid.uuid4())[:8] # 使用UUID的前8位作为唯一标识 filename = f"{timestamp}_{unique_id}.{file_ext}" # 使用 current_app.config filepath = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) file.save(filepath) # 生成可访问的URL(使用服务器的实际IP地址,而不是localhost) # 如果request.host包含localhost或127.0.0.1,使用实际IP host = request.host if 'localhost' in host or '127.0.0.1' in host: server_ip = get_server_ip() port = request.environ.get('SERVER_PORT', '5000') host = f"{server_ip}:{port}" image_url = f"http://{host}/uploads/{filename}" logger.info(f"图片已上传: {filepath}, URL: {image_url}, 原始文件名: {original_filename}") else: return jsonify({ "success": False, "message": f"不支持的文件类型,允许的类型: {', '.join(ALLOWED_EXTENSIONS)}" }), 400 elif request.is_json: # 检查是否有直接的图片URL data = request.get_json() if 'image_url' not in data: return jsonify({ "success": False, "message": "缺少参数:需要 'file'(文件上传)或 'image_url'(图片URL)" }), 400 image_url = data['image_url'] if not image_url or not isinstance(image_url, str): return jsonify({ "success": False, "message": "无效的图片URL" }), 400 else: return jsonify({ "success": False, "message": "缺少参数:需要 'file'(文件上传)或 'image_url'(图片URL)" }), 400 # 获取客户端索引 if 'kodi_client_index' in request.form: try: client_index = int(request.form['kodi_client_index']) except (ValueError, TypeError): return jsonify({ "success": False, "message": "kodi_client_index 必须是整数" }), 400 elif request.is_json and 'kodi_client_index' in request.get_json(): client_index = request.get_json()['kodi_client_index'] else: return jsonify({ "success": False, "message": "缺少参数: kodi_client_index" }), 400 if not isinstance(client_index, int) or client_index < 0: return jsonify({ "success": False, "message": "kodi_client_index 必须是大于等于0的整数" }), 400 # 调用播放函数 success = play_image(image_url, client_index) if success: return jsonify({ "success": True, "message": f"已在客户端 {client_index} 上启动图片播放", "data": { "image_url": image_url, "client_index": client_index } }) else: return jsonify({ "success": False, "message": f"在客户端 {client_index} 上启动图片播放失败" }), 500 except Exception as e: logger.error(f"播放图片异常: {str(e)}") return jsonify({ "success": False, "message": f"播放图片失败: {str(e)}" }), 500 # 指定某台kodi_client_index播放rtsp视频 @kodi_bp.route('/api/kodi/play_rtsp', methods=['POST']) @login_required def play_rtsp_api(): """播放RTSP视频流接口""" try: data = request.get_json() if not data: return jsonify({ "success": False, "message": "请求体不能为空" }), 400 # 检查必需的参数 if 'rtsp_url' not in data: return jsonify({ "success": False, "message": "缺少参数: rtsp_url" }), 400 if 'kodi_client_index' not in data: return jsonify({ "success": False, "message": "缺少参数: kodi_client_index" }), 400 rtsp_url = data['rtsp_url'] client_index = data['kodi_client_index'] volume = data.get('volume', 0) # 可选参数,默认为0 # 参数验证 if not isinstance(rtsp_url, str) or not rtsp_url.strip(): return jsonify({ "success": False, "message": "rtsp_url 必须是有效的字符串" }), 400 if not isinstance(client_index, int) or client_index < 0: return jsonify({ "success": False, "message": "kodi_client_index 必须是大于等于0的整数" }), 400 if not isinstance(volume, int) or volume < 0 or volume > 100: return jsonify({ "success": False, "message": "volume 必须是 0-100 之间的整数" }), 400 # 调用播放函数 success = play_rtsp(rtsp_url, client_index, volume) if success: return jsonify({ "success": True, "message": f"已在客户端 {client_index} 上启动RTSP播放", "data": { "rtsp_url": rtsp_url, "client_index": client_index, "volume": volume } }) else: return jsonify({ "success": False, "message": f"在客户端 {client_index} 上启动RTSP播放失败" }), 500 except Exception as e: logger.error(f"播放RTSP异常: {str(e)}") return jsonify({ "success": False, "message": f"播放RTSP失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/revoke_individual_state', methods=['POST']) @login_required def revoke_individual_state_api(): """撤销所有客户端的独立状态接口""" try: success = revoke_individual_state() if success: return jsonify({ "success": True, "message": "已撤销所有客户端的独立状态" }) else: return jsonify({ "success": False, "message": "撤销所有客户端的独立状态失败" }), 500 except Exception as e: logger.error(f"撤销独立状态异常: {str(e)}") return jsonify({ "success": False, "message": f"撤销独立状态失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/start_all_apps', methods=['POST']) @login_required def start_all_kodi_apps_api(): """启动所有kodi应用程序接口""" try: success = start_all_kodi_apps() if success: return jsonify({ "success": True, "message": "已启动所有kodi应用程序" }) else: return jsonify({ "success": False, "message": "启动所有kodi应用程序失败" }), 500 except Exception as e: logger.error(f"启动所有kodi应用程序异常: {str(e)}") return jsonify({ "success": False, "message": f"启动所有kodi应用程序失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/set_volume', methods=['POST']) @login_required def set_kodi_volume_api(): """设置Kodi播放音量接口""" try: data = request.get_json() if not data or 'volume' not in data: return jsonify({ "success": False, "message": "缺少 volume 参数" }), 400 volume = data['volume'] if not isinstance(volume, int) or not (0 <= volume <= 100): return jsonify({ "success": False, "message": "音量必须是 0-100 之间的整数" }), 400 success = set_volume(volume) if success: return jsonify({ "success": True, "message": f"已设置音量为 {volume}", "data": {"volume": volume} }) else: return jsonify({ "success": False, "message": "设置音量失败" }), 500 except Exception as e: logger.error(f"设置音量异常: {str(e)}") return jsonify({ "success": False, "message": f"设置音量失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/get_volume', methods=['GET']) @login_required def get_kodi_volume_api(): """获取Kodi全局音量接口""" try: volume = get_volume() if volume != -1: return jsonify({ "success": True, "data": {"volume": volume} }) else: return jsonify({ "success": False, "message": "获取音量失败" }), 500 except Exception as e: logger.error(f"获取音量异常: {str(e)}") return jsonify({ "success": False, "message": f"获取音量失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/free_time/control', methods=['POST']) @login_required def control_free_time_play_api(): """ 控制Kodi闲时播放功能 参数: - action: 'start' (启动线程) | 'stop' (停止线程) """ try: data = request.get_json() if not data: return jsonify({ "success": False, "message": "请求参数为空" }), 400 action = data.get('action') message = "" # 处理 action if action == 'stop': stop_kodi_free_time_play() message = "闲时播放线程已停止" elif action == 'start': start_kodi_free_time_play() message = "闲时播放线程已启动" else: return jsonify({ "success": False, "message": "无效的 action 参数,必须为 'start' 或 'stop'" }), 400 # 返回最新状态 status = get_free_time_play_status() return jsonify({ "success": True, "message": message, "data": status }) except Exception as e: logger.error(f"控制闲时播放异常: {str(e)}") return jsonify({ "success": False, "message": f"控制闲时播放失败: {str(e)}" }), 500 @kodi_bp.route('/api/kodi/free_time/status', methods=['GET']) @login_required def get_free_time_play_status_api(): """获取Kodi闲时播放状态""" try: status = get_free_time_play_status() return jsonify({ "success": True, "data": status }) except Exception as e: logger.error(f"获取闲时播放状态异常: {str(e)}") return jsonify({ "success": False, "message": f"获取闲时播放状态失败: {str(e)}" }), 500