222 lines
7.8 KiB
Python
222 lines
7.8 KiB
Python
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)
|