index_recordings.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. import hashlib
  3. import re
  4. import sqlite3
  5. import subprocess
  6. from pathlib import Path
  7. ROOT = Path("data/recordings")
  8. DB = Path("data/recording_index.sqlite3")
  9. PATTERN = re.compile(
  10. r"^\[(?P<name>.*?)\]_(?P<ext>\d+)-(?P<number>.*?)_"
  11. r"(?P<date>\d{14})\((?P<id>\d+)\)\.wav$"
  12. )
  13. con = sqlite3.connect(DB)
  14. con.executescript("""
  15. CREATE TABLE IF NOT EXISTS recordings (
  16. id INTEGER PRIMARY KEY AUTOINCREMENT,
  17. path TEXT UNIQUE NOT NULL,
  18. filename TEXT NOT NULL,
  19. extension TEXT,
  20. caller_name TEXT,
  21. phone_number TEXT,
  22. recorded_at TEXT,
  23. recording_id TEXT,
  24. size INTEGER,
  25. sha256 TEXT,
  26. duration REAL,
  27. status TEXT DEFAULT 'NEW',
  28. transcript_path TEXT,
  29. analysis_path TEXT,
  30. taxonomy_version TEXT,
  31. created_at TEXT DEFAULT CURRENT_TIMESTAMP
  32. );
  33. CREATE INDEX IF NOT EXISTS idx_recording_id
  34. ON recordings(recording_id);
  35. CREATE INDEX IF NOT EXISTS idx_status
  36. ON recordings(status);
  37. CREATE INDEX IF NOT EXISTS idx_recorded_at
  38. ON recordings(recorded_at);
  39. """)
  40. def duration(path):
  41. try:
  42. result = subprocess.run(
  43. [
  44. "ffprobe",
  45. "-v", "error",
  46. "-show_entries",
  47. "format=duration",
  48. "-of", "default=noprint_wrappers=1:nokey=1",
  49. str(path),
  50. ],
  51. capture_output=True,
  52. text=True,
  53. timeout=15,
  54. )
  55. return float(result.stdout.strip())
  56. except Exception:
  57. return None
  58. def sha256(path):
  59. h = hashlib.sha256()
  60. with path.open("rb") as f:
  61. while chunk := f.read(1024 * 1024):
  62. h.update(chunk)
  63. return h.hexdigest()
  64. files = sorted(
  65. ROOT.rglob("*.wav")
  66. )
  67. print(f"Recordings gefunden: {len(files)}")
  68. new = 0
  69. updated = 0
  70. short = 0
  71. unknown = 0
  72. for index, path in enumerate(files, 1):
  73. filename = path.name
  74. match = PATTERN.match(filename)
  75. if not match:
  76. unknown += 1
  77. continue
  78. data = match.groupdict()
  79. recorded_at = (
  80. f"{data['date'][:4]}-"
  81. f"{data['date'][4:6]}-"
  82. f"{data['date'][6:8]} "
  83. f"{data['date'][8:10]}:"
  84. f"{data['date'][10:12]}:"
  85. f"{data['date'][12:14]}"
  86. )
  87. rel = str(path)
  88. existing = con.execute(
  89. "SELECT id, sha256 FROM recordings WHERE path = ?",
  90. (rel,),
  91. ).fetchone()
  92. size = path.stat().st_size
  93. if existing and existing[1]:
  94. con.execute("""
  95. UPDATE recordings
  96. SET size = ?
  97. WHERE id = ?
  98. """, (size, existing[0]))
  99. updated += 1
  100. continue
  101. print(
  102. f"[{index}/{len(files)}] "
  103. f"{filename}"
  104. )
  105. d = duration(path)
  106. digest = sha256(path)
  107. status = "NEW"
  108. if d is not None and d < 2:
  109. status = "TOO_SHORT"
  110. short += 1
  111. con.execute("""
  112. INSERT OR REPLACE INTO recordings (
  113. path,
  114. filename,
  115. extension,
  116. caller_name,
  117. phone_number,
  118. recorded_at,
  119. recording_id,
  120. size,
  121. sha256,
  122. duration,
  123. status
  124. )
  125. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  126. """, (
  127. rel,
  128. filename,
  129. data["ext"],
  130. data["name"],
  131. data["number"],
  132. recorded_at,
  133. data["id"],
  134. size,
  135. digest,
  136. d,
  137. status,
  138. ))
  139. new += 1
  140. if index % 50 == 0:
  141. con.commit()
  142. con.commit()
  143. total = con.execute(
  144. "SELECT COUNT(*) FROM recordings"
  145. ).fetchone()[0]
  146. new_count = con.execute(
  147. "SELECT COUNT(*) FROM recordings WHERE status='NEW'"
  148. ).fetchone()[0]
  149. short_count = con.execute(
  150. "SELECT COUNT(*) FROM recordings WHERE status='TOO_SHORT'"
  151. ).fetchone()[0]
  152. print()
  153. print("=" * 70)
  154. print("RECORDING-INDEX FERTIG")
  155. print("=" * 70)
  156. print(f"Dateien: {total}")
  157. print(f"Neu indexiert: {new}")
  158. print(f"Bereits bekannt: {updated}")
  159. print(f"Unbekanntes Format:{unknown}")
  160. print(f"< 2 Sekunden: {short_count}")
  161. print(f"Analyse-Kandidaten:{new_count}")
  162. print(f"DB: {DB}")
  163. print()
  164. print("Dauerverteilung:")
  165. for row in con.execute("""
  166. SELECT
  167. CASE
  168. WHEN duration < 2 THEN '<2s'
  169. WHEN duration < 10 THEN '2-10s'
  170. WHEN duration < 30 THEN '10-30s'
  171. WHEN duration < 60 THEN '30-60s'
  172. WHEN duration < 180 THEN '1-3min'
  173. WHEN duration < 600 THEN '3-10min'
  174. ELSE '>10min'
  175. END bucket,
  176. COUNT(*)
  177. FROM recordings
  178. GROUP BY bucket
  179. ORDER BY MIN(duration)
  180. """):
  181. print(f"{row[0]:10} {row[1]}")
  182. con.close()