wiktortokumpel

wesion z kopią

Nov 14th, 2025 (edited)
24
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import os
  2. import time
  3. import socket
  4. import threading
  5. import subprocess
  6. from datetime import datetime
  7. import re
  8.  
  9. # =============== KONFIGURACJA ===============
  10.  
  11. RTSP_URL = "rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif"
  12.  
  13. BASE_DIR = r"D:\wideo"
  14. RAW_DIR = os.path.join(BASE_DIR, "raw")
  15. CLIPS_DIR = os.path.join(BASE_DIR, "clips")
  16.  
  17. RAW_PATH = os.path.join(RAW_DIR, "cam1_raw.mp4") # tu ffmpeg nagrywa ciągły strumień
  18. CAMERA_NAME = "KAMERA1"
  19.  
  20. PRE_SECONDS = 10 # ile sekund PRZED triggerem
  21. POST_SECONDS = 15 # ile sekund PO triggerze
  22. RETENTION_DAYS = 1 # na testy, potem np. 30
  23.  
  24. TCP_HOST = "0.0.0.0"
  25. TCP_PORT = 5000
  26.  
  27. FFMPEG_EXE = "ffmpeg" # jeśli nie w PATH, podaj pełną ścieżkę, np. r"C:\ffmpeg\bin\ffmpeg.exe"
  28.  
  29. # globalnie zapamiętany czas startu RAW-a
  30. raw_start_time = None
  31. raw_lock = threading.Lock()
  32.  
  33.  
  34. # =============== POMOCNICZE ===============
  35.  
  36. def ensure_dirs():
  37. os.makedirs(RAW_DIR, exist_ok=True)
  38. os.makedirs(CLIPS_DIR, exist_ok=True)
  39.  
  40.  
  41. def sanitize_part_code(code: str) -> str:
  42. code = code.strip()
  43. if not code:
  44. code = "NO_CODE"
  45. return re.sub(r"[^0-9A-Za-z_-]", "_", code)
  46.  
  47.  
  48. def cleanup_old_clips():
  49. while True:
  50. now = time.time()
  51. cutoff = now - RETENTION_DAYS * 24 * 3600
  52. try:
  53. for fname in os.listdir(CLIPS_DIR):
  54. fpath = os.path.join(CLIPS_DIR, fname)
  55. if os.path.isfile(fpath) and os.path.getmtime(fpath) < cutoff:
  56. print(f"[CLEANUP] Usuwam stary klip: {fname}")
  57. os.remove(fpath)
  58. except Exception as e:
  59. print("[CLEANUP] Błąd cleanup:", e)
  60. time.sleep(24 * 3600)
  61.  
  62.  
  63. # =============== START FFMPEG (RAW) ===============
  64.  
  65. def start_ffmpeg_raw():
  66. """
  67. Uruchamia ffmpeg, który ciągle nagrywa RTSP do RAW_PATH.
  68. """
  69. global raw_start_time
  70.  
  71. # jeśli plik już istnieje – spróbuj go usunąć, żeby zacząć od nowa
  72. if os.path.exists(RAW_PATH):
  73. try:
  74. os.remove(RAW_PATH)
  75. except Exception:
  76. pass
  77.  
  78. cmd = [
  79. FFMPEG_EXE,
  80. "-rtsp_transport", "tcp",
  81. "-i", RTSP_URL,
  82. "-c", "copy",
  83. "-an",
  84. "-reset_timestamps", "1",
  85. RAW_PATH,
  86. ]
  87.  
  88. print("[FFMPEG] Start nagrywania RAW...")
  89. # odpalamy ffmpeg jako proces w tle
  90. proc = subprocess.Popen(
  91. cmd,
  92. stdout=subprocess.DEVNULL,
  93. stderr=subprocess.DEVNULL
  94. )
  95.  
  96. with raw_lock:
  97. raw_start_time = time.time()
  98.  
  99. print(f"[FFMPEG] RAW zapisuje się do: {RAW_PATH}")
  100. print(f"[FFMPEG] Czas startu RAW: {datetime.fromtimestamp(raw_start_time)}")
  101.  
  102. # proces ffmpeg będzie sobie chodził w tle; nie czekamy na niego tutaj
  103. return proc
  104.  
  105.  
  106. # =============== WYCIĘCIE KLIPU ===============
  107.  
  108. def cut_clip(part_code: str, trigger_time: float):
  109. """
  110. Wywołuje ffmpeg, żeby wyciąć klip:
  111. [trigger_time - PRE, trigger_time + POST]
  112. z pliku RAW.
  113. """
  114. with raw_lock:
  115. rst = raw_start_time
  116.  
  117. if rst is None:
  118. print("[CLIP] RAW jeszcze nie wystartował – pomijam.")
  119. return
  120.  
  121. if not os.path.exists(RAW_PATH):
  122. print(f"[CLIP] RAW nie istnieje: {RAW_PATH}")
  123. return
  124.  
  125. window_start = trigger_time - PRE_SECONDS
  126. offset = window_start - rst
  127. if offset < 0:
  128. offset = 0.0 # jeśli trigger był bardzo szybko po starcie
  129.  
  130. duration = PRE_SECONDS + POST_SECONDS
  131.  
  132. ts_name = datetime.fromtimestamp(trigger_time).strftime("%Y_%m_%d_%H_%M_%S")
  133. safe_code = sanitize_part_code(part_code)
  134. out_name = f"{ts_name}_{safe_code}_{CAMERA_NAME}.mp4"
  135. out_path = os.path.join(CLIPS_DIR, out_name)
  136.  
  137. print(f"[CLIP] Wycinam klip: {out_name}")
  138. print(f"[CLIP] offset={offset:.2f}s, duration={duration:.2f}s")
  139.  
  140. cmd = [
  141. FFMPEG_EXE,
  142. "-y",
  143. "-ss", f"{offset:.3f}",
  144. "-i", RAW_PATH,
  145. "-t", f"{duration:.3f}",
  146. "-c", "copy",
  147. out_path,
  148. ]
  149.  
  150. try:
  151. subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  152. print(f"[CLIP] Klip zapisany: {out_path}")
  153. except Exception as e:
  154. print(f"[CLIP] Błąd wycinania: {e}")
  155.  
  156.  
  157. # =============== SERVER TCP ===============
  158.  
  159. def tcp_server():
  160. srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  161. srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  162. srv.bind((TCP_HOST, TCP_PORT))
  163. srv.listen(5)
  164. print(f"[TCP] Nasłuch na {TCP_HOST}:{TCP_PORT}")
  165.  
  166. while True:
  167. conn, addr = srv.accept()
  168. with conn:
  169. try:
  170. data = conn.recv(1024)
  171. if not data:
  172. continue
  173. code = data.decode(errors="ignore").strip()
  174. print(f"[TCP] Kod części: {code!r} od {addr}")
  175. if not code:
  176. continue
  177.  
  178. trigger_time = time.time()
  179. # wycinanie w osobnym wątku, żeby nie blokować TCP
  180. th = threading.Thread(target=cut_clip, args=(code, trigger_time), daemon=True)
  181. th.start()
  182.  
  183. except Exception as e:
  184. print("[TCP] Błąd:", e)
  185.  
  186.  
  187. # =============== MAIN ===============
  188.  
  189. def main():
  190. ensure_dirs()
  191.  
  192. # startujemy ffmpeg
  193. ffmpeg_proc = start_ffmpeg_raw()
  194.  
  195. # uruchamiamy sprzątanie starych klipów
  196. threading.Thread(target=cleanup_old_clips, daemon=True).start()
  197.  
  198. print("=== TRIGGER + FFMPEG RECORDER URUCHOMIONY ===")
  199. print("RAW:", RAW_PATH)
  200. print("CLIPS:", CLIPS_DIR)
  201. print(f"Pre={PRE_SECONDS}s, Post={POST_SECONDS}s, Retencja={RETENTION_DAYS} dni\n")
  202.  
  203. try:
  204. tcp_server()
  205. finally:
  206. # przy zamknięciu – spróbuj zabić ffmpeg
  207. try:
  208. ffmpeg_proc.terminate()
  209. except Exception:
  210. pass
  211.  
  212.  
  213. if __name__ == "__main__":
  214. main()
  215.  
Advertisement
Add Comment
Please, Sign In to add comment