| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- 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
|