00_clip_video.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # This code clips the raw videos based on claps detected by the user during the execution and stores them in a new file
  2. import subprocess
  3. import glob
  4. import sounddevice as sd
  5. import argparse
  6. import os
  7. from scipy.signal import butter, filtfilt
  8. from scipy.io import wavfile
  9. from pylab import *
  10. def butter_lowpass(cutoff_low, cutoff_high, fs, order=5):
  11. nyq = 0.5 * fs
  12. cutoff_low = cutoff_low / nyq
  13. cutoff_high = cutoff_high / nyq
  14. b, a = butter(order, [cutoff_low, cutoff_high], btype='bandpass',
  15. analog=False)
  16. return b, a
  17. def butter_lowpass_filter(data, cutoff_low, cutoff_high, fs, order=5):
  18. b, a = butter_lowpass(cutoff_low, cutoff_high, fs, order=order)
  19. y = filtfilt(b, a, data)
  20. return y
  21. def find_start_frame_by_clap(path_to_audio_file, startt=0, stopt=500,
  22. where=None):
  23. """
  24. Detect clap in audio file. Sound is played and detection must be confirmed
  25. by pressing [d] or discarded by pressing [e]. [c] to restart, [a] to
  26. listen again,
  27. :param
  28. path_to_audio_file: path to .wav file
  29. startt: default 0, time in seconds to start search
  30. stopt: default 100, time in seconds to end search. By default only within
  31. the first minute and 30 seconds the clap is searched.
  32. where: valuesallowed are 'start' or 'end' if provided first half or last
  33. half resp. is set to zero
  34. :return:
  35. second_of_clap: Second of .wav file in which clap takes place
  36. """
  37. audioFreq, audio = wavfile.read(path_to_audio_file)
  38. audio.setflags(write=1)
  39. # set part of sequence to zero if searched for start / end clap
  40. if where != None:
  41. if where == 'end':
  42. audio[0:int(len(audio) / 2)] = 0
  43. elif where == 'start':
  44. audio[int(len(audio) / 2):len(audio)] = 0
  45. else:
  46. print('where must be \'start\' oder \'end\'')
  47. cutoff_low = 1000 # desired cutoff frequency of the filter, Hz
  48. cutoff_high = 1400
  49. fs = 3000
  50. order = 5
  51. foundStartClap = False
  52. k = 0
  53. while not foundStartClap:
  54. k += 10
  55. s = int(audioFreq * startt)
  56. e = int(audioFreq * (stopt + k))
  57. # Filter .wav file within given range of time and find argmax amplitude
  58. y = butter_lowpass_filter(audio[s:e, 0], cutoff_low,
  59. cutoff_high, fs, order)
  60. maxamp = np.argmax(y)
  61. # -/+ audioFreq is necessary to actually hear something (1 second)
  62. # when audio is played.
  63. l = int(maxamp - 2 * audioFreq)
  64. u = int(maxamp + 2 * audioFreq)
  65. print(l / audioFreq, u / audioFreq)
  66. sd.play(audio[l:u], audioFreq, blocking=True)
  67. print('Clap playing second: ', np.ceil(maxamp / audioFreq))
  68. # Capture keyboard events
  69. while True:
  70. print("Press [d] if clap was detected; [c] if sound was no clap; "
  71. "[a] to hear again; [e] to pass; press [n] if clap not "
  72. "found at all, second of clap will be set to one")
  73. x = input()
  74. if x == 'd':
  75. second_of_clap = maxamp / audioFreq
  76. print('Clap accepted; takes place at second ', second_of_clap)
  77. foundStartClap = True
  78. break
  79. elif x == 'c':
  80. print('Restart clap search')
  81. audio[l:u] = 0
  82. break
  83. elif x == 'a':
  84. sd.play(audio[l:u], audioFreq, blocking=True)
  85. elif x == 'e':
  86. return 0
  87. elif x == 'n':
  88. second_of_clap = 1
  89. foundStartClap = True
  90. print('Clap not found, video will start at ', second_of_clap)
  91. break
  92. else:
  93. print('Key must be [d],[c], [a] or [e]')
  94. return second_of_clap
  95. def clip_video_from_clap(video_path, output_path, start_time, end_time):
  96. command_clip = (
  97. f"ffmpeg -i {video_path} -ss {start_time} -to {end_time} -c copy {output_path}"
  98. )
  99. subprocess.call(command_clip, shell=True)
  100. if __name__ == "__main__":
  101. """
  102. This script processes video all in once. The default directory for videos
  103. being processed is data/Videos. Frames are automatically extracted after
  104. clap detection finished. The following steps are performed:
  105. 1) Create folder based on video name
  106. 2) Extract audio file
  107. 3) Clap detection (starting and ending clap) using low pass filter in
  108. video for synchronization of the three cameras.
  109. Make sure sound is turned on. Clap detection results are written to
  110. offset.csv. Headphones necessary to confirm detection.
  111. 4) Extract video frames using ffmpeg
  112. Args:
  113. inputpath: Path to folder with videos.
  114. Returns:
  115. Folder containing audio file and video frames. Writes number of
  116. frame where clap takes place and second of clap to offset.csv.
  117. """
  118. parser = argparse.ArgumentParser(description='Processes all Videos in '
  119. '--inputpath at once')
  120. parser.add_argument('--inputpath',
  121. default='/home/valapil/Project/ForkCausal_Adithya/raw_video/pair3',
  122. help='Path to folder with videos to process')
  123. parser.add_argument('--outputpath',
  124. default='/home/valapil/Project/ForkCausal_Adithya/clipped_vid',
  125. help='Path to folder with pair-folders')
  126. parser.add_argument('--ExtractFrames', action='store_false',
  127. help='If true frames are extracted from video')
  128. args = parser.parse_args()
  129. print(args, args.ExtractFrames)
  130. # Comment in if side view perspective is processed as well.
  131. # videofiles = glob.glob(args.inputpath + '/*.MTS')
  132. # videofiles.extend(glob.glob(args.inputpath + '/*.MOV'))
  133. # videofiles = sorted(videofiles)
  134. videofiles = sorted(glob.glob(args.inputpath + '/*.MTS'))
  135. print('Videos to process: ', args.inputpath, videofiles)
  136. f = open(os.path.join(args.outputpath, 'offset.csv'), 'a')
  137. # Extract clap and audio
  138. for video_dir in videofiles:
  139. accept_Video = True
  140. print('Processing video: ', video_dir)
  141. video = os.path.basename(video_dir)
  142. video_file = os.path.basename(video).split('.')[0]
  143. video_file = video_file.replace('r', 'o')
  144. print('PROCESS ', video_dir)
  145. # Create directory for pair
  146. pair = 'pair_' + str(video_file[0:3])
  147. create_folder_pair = os.path.join(args.outputpath, pair)
  148. command_folder = "mkdir -p " + create_folder_pair
  149. subprocess.call(command_folder, shell=True)
  150. # Create directory for images in outputpath
  151. create_folder_img = os.path.join(args.outputpath, pair,
  152. video_file, 'images')
  153. command_folder_img = "mkdir -p " + create_folder_img
  154. subprocess.call(command_folder_img, shell=True)
  155. # Create directory for audio file in outputpath
  156. create_folder_audio = os.path.join(args.outputpath, pair,
  157. video_file, 'audio')
  158. command_folder_audio = "mkdir -p " + create_folder_audio
  159. subprocess.call(command_folder_audio, shell=True)
  160. # Extract audio from video
  161. aud = os.path.join(create_folder_audio,
  162. video_file + "_audio.wav")
  163. command_audio = "ffmpeg -i " + video_dir + \
  164. " -ab 160k -ac 2 -ar 44100 -vn " + aud
  165. subprocess.call(command_audio, shell=True)
  166. # Detect strating and ending claps in video
  167. for i in ['start', 'end']:
  168. second_of_clap = find_start_frame_by_clap(path_to_audio_file
  169. =aud, where=i)
  170. # Video camera from media department format
  171. if os.path.basename(video).split('.')[1] == 'MTS':
  172. frame_rate = 25
  173. frame_of_clap = int(second_of_clap * frame_rate)
  174. # ipad video format
  175. elif os.path.basename(video).split('.')[1] == 'MOV':
  176. # original frame rate is of .MOV is 29.984664, but side
  177. # view should be synchronized with frontal perspective
  178. frame_rate = 25
  179. frame_of_clap = int(second_of_clap * frame_rate)
  180. print(video_file, i, second_of_clap, frame_of_clap)
  181. if i == 'start':
  182. start_clip = second_of_clap
  183. elif i == 'end':
  184. if second_of_clap == start_clip:
  185. accept_Video = False
  186. else:
  187. end_clip = second_of_clap
  188. if accept_Video == True:
  189. clip_output_path = os.path.join(create_folder_pair, video_file, f"{video}_clipped.mp4")
  190. clip_video_from_clap(video_path=video_dir, output_path=clip_output_path, start_time=start_clip, end_time= end_clip)
  191. # f.write(f'{video_file} {i} {second_of_clap} {frame_of_clap}\n')
  192. f.close()
  193. # # Extract video frames to pair/condition/images
  194. # if args.ExtractFrames:
  195. # print('Start extracting frames ... ')
  196. # for video_dir in videofiles:
  197. # video = os.path.basename(video_dir)
  198. # video_file = os.path.basename(video).split('.')[0]
  199. # # you can exclude videos (e.g. sideview)
  200. # # processedVideos = ['I am already processed']
  201. # # if np.isin(video_file, processedVideos, invert=True):
  202. # pair = 'pair_' + str(video_file[0:3])
  203. # create_folder_pair = os.path.join(args.outputpath, pair)
  204. # create_folder = os.path.join(create_folder_pair, video_file,
  205. # 'images')
  206. # vid = create_folder + '/' + video_file + '_%05d.png'
  207. #
  208. # # Call from shell
  209. # command_frames = "ffmpeg -i " + video_dir + " -r 25 " + vid
  210. # subprocess.call(command_frames, shell=True)