from flask import Flask, jsonify, request import subprocess import threading import time import os from datetime import datetime import schedule app = Flask(__name__) # 配置参数 RTSP_URL = "rtsp://10.68.66.174/audio" RECORD_DURATION_MINUTES = 60 # 录制时长(分钟) REST_DURATION_MINUTES = 20 # 休息时长(分钟) START_HOUR = 8 # 开始时间(小时) END_HOUR = 20 # 结束时间(小时) OUTPUT_DIR = "./" # 输出目录 # 录制状态 recording_state = { "is_recording": False, "current_file": None, "start_time": None, "total_recorded": 0, "schedule_next": None } def get_output_path(): """生成带时间戳的文件名""" timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') return os.path.join(OUTPUT_DIR, f"rtsp_{timestamp}.wav") def is_within_schedule(): """检查当前是否在有效时间段内""" current_hour = datetime.now().hour return START_HOUR <= current_hour < END_HOUR def record_audio(): """执行音频录制""" if recording_state["is_recording"]: print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 正在录制中,跳过本次录制") return if not is_within_schedule(): print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 不在有效时间段内(8:00-20:00),跳过本次录制") return output_file = get_output_path() print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 开始录制: {output_file}") recording_state["is_recording"] = True recording_state["current_file"] = output_file recording_state["start_time"] = datetime.now() try: # 使用 ffmpeg 录制 cmd = [ 'ffmpeg', '-rtsp_transport', 'tcp', '-i', RTSP_URL, '-t', str(RECORD_DURATION_MINUTES * 60), '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', '-y', output_file ] process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True ) # 监控录制进度 start_time = time.time() while process.poll() is None: if os.path.exists(output_file): size_mb = os.path.getsize(output_file) / (1024 * 1024) elapsed = time.time() - start_time print(f"\r进度: {int(elapsed)}秒 / {RECORD_DURATION_MINUTES * 60}秒, 大小: {size_mb:.2f}MB", end='') time.sleep(1) print() # 换行 # 录制完成 if os.path.exists(output_file): size_mb = os.path.getsize(output_file) / (1024 * 1024) recording_state["total_recorded"] += 1 print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 录制完成: {output_file} ({size_mb:.2f}MB)") else: print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 录制失败") except Exception as e: print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 录制错误: {e}") finally: recording_state["is_recording"] = False recording_state["current_file"] = None recording_state["start_time"] = None # 录制完成后,在有效时间段内等待 REST_DURATION_MINUTES 分钟 if is_within_schedule(): print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 进入休息模式,等待 {REST_DURATION_MINUTES} 分钟...") time.sleep(REST_DURATION_MINUTES * 60) print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 休息结束") else: # 不在有效时间段,等待到下一个有效时间段 now = datetime.now() if now.hour >= END_HOUR: # 等待到明天早上 8 点 from datetime import timedelta next_day = now + timedelta(days=1) target = next_day.replace(hour=START_HOUR, minute=0, second=0, microsecond=0) else: # 等待到今天早上 8 点 target = now.replace(hour=START_HOUR, minute=0, second=0, microsecond=0) wait_seconds = (target - now).total_seconds() print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 不在有效时间段,等待 {wait_seconds/3600:.1f} 小时...") time.sleep(wait_seconds) def schedule_recording(): """定时录制任务""" thread = threading.Thread(target=record_audio, daemon=True) thread.start() def run_scheduler(): """运行定时器""" # 设置定时任务:每小时整点录制 schedule.every().hour.at(":00").do(schedule_recording) print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 定时录制服务已启动") print(f" - 录制时长: {RECORD_DURATION_MINUTES} 分钟") print(f" - 休息时长: {REST_DURATION_MINUTES} 分钟") print(f" - 有效时段: {START_HOUR}:00 - {END_HOUR}:00") print("-" * 50) while True: schedule.run_pending() time.sleep(1) # Flask 路由 @app.route('/') def index(): """主页""" return jsonify({ "service": "RTSP Audio Recorder", "status": "running" if recording_state["is_recording"] else "idle", "config": { "rtsp_url": RTSP_URL, "record_duration": f"{RECORD_DURATION_MINUTES} minutes", "rest_duration": f"{REST_DURATION_MINUTES} minutes", "schedule": f"{START_HOUR}:00 - {END_HOUR}:00" } }) @app.route('/status') def status(): """获取录制状态""" return jsonify({ "is_recording": recording_state["is_recording"], "current_file": recording_state["current_file"], "start_time": recording_state["start_time"].isoformat() if recording_state["start_time"] else None, "total_recorded": recording_state["total_recorded"], "current_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "is_in_schedule": is_within_schedule() }) @app.route('/start') def start_recording(): """手动开始录制""" if recording_state["is_recording"]: return jsonify({"status": "error", "message": "正在录制中"}) thread = threading.Thread(target=record_audio, daemon=True) thread.start() return jsonify({"status": "success", "message": "开始录制"}) @app.route('/stop') def stop_recording(): """手动停止录制(终止 ffmpeg 进程)""" if not recording_state["is_recording"]: return jsonify({"status": "error", "message": "当前没有在录制"}) # 查找并终止 ffmpeg 进程 try: subprocess.run(['taskkill', '/f', '/im', 'ffmpeg.exe'], capture_output=True) recording_state["is_recording"] = False return jsonify({"status": "success", "message": "已停止录制"}) except Exception as e: return jsonify({"status": "error", "message": str(e)}) @app.route('/files') def list_files(): """列出已录制的文件""" files = [] for f in os.listdir(OUTPUT_DIR): if f.endswith('.wav') and f.startswith('rtsp_'): path = os.path.join(OUTPUT_DIR, f) files.append({ "name": f, "size": os.path.getsize(path), "modified": datetime.fromtimestamp(os.path.getmtime(path)).isoformat() }) return jsonify({"files": files}) if __name__ == '__main__': # 确保输出目录存在 os.makedirs(OUTPUT_DIR, exist_ok=True) # 启动定时器线程 scheduler_thread = threading.Thread(target=run_scheduler, daemon=True) scheduler_thread.start() # 启动 Flask 服务 print(f"\n{'='*50}") print(f"RTSP 音频录制服务") print(f"{'='*50}") app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)