tictacsync 0.82a0__py3-none-any.whl → 0.95a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tictacsync/yaltc.py CHANGED
@@ -29,21 +29,15 @@ try:
29
29
  except:
30
30
  import device_scanner
31
31
 
32
- TEENSY_MAX_LAG = 128/44100 # sec, duration of a default length audio block
32
+ TEENSY_MAX_LAG = 1.01*128/44100 # sec, duration of a default length audio block
33
33
 
34
34
  # see extract_seems_TicTacCode() for duration criterion values
35
35
 
36
36
  CACHING = True
37
37
  DEL_TEMP = False
38
38
  DB_RMS_SILENCE_SOX = -58
39
- MAXDRIFT = 10e-3 # in sec, normally 10e-3 (10 ms)
39
+ MAXDRIFT = 15e-3 # in sec, for end of clip
40
40
 
41
- SAFE_SILENCE_WINDOW_WIDTH = 400 # ms, not the full 500 ms, to accommodate decay
42
- # used in _get_silent_zone_indices()
43
- WORDWIDTHFACTOR = 2
44
- # see _get_word_width_parameters()
45
-
46
- OVER_NOISE_SYNC_DETECT_LEVEL = 2
47
41
 
48
42
  ################## pasted from FSKfreqCalculator.py output:
49
43
  F1 = 630.00 # Hertz
@@ -52,16 +46,16 @@ SYMBOL_LENGTH = 14.286 # ms, from FSKfreqCalculator.py
52
46
  N_SYMBOLS = 35 # including sync pulse
53
47
  ##################
54
48
 
55
- MINIMUM_LENGTH = 4 # sec
49
+ MINIMUM_LENGTH = 8 # sec
56
50
  TRIAL_TIMES = [ # in seconds
57
- (0.5, -2),
58
- (0.5, -3.5),
59
- (0.5, -5),
51
+ (3.5, -2),
52
+ (3.5, -3.5),
53
+ (3.5, -5),
60
54
  (2, -2),
61
55
  (2, -3.5),
62
56
  (2, -5),
63
- (3.5, -2),
64
- (3.5, -3.5),
57
+ (0.5, -2),
58
+ (0.5, -3.5),
65
59
  ]
66
60
  SOUND_EXTRACT_LENGTH = (10*SYMBOL_LENGTH*1e-3 + 1) # second
67
61
  SYMBOL_LENGTH_TOLERANCE = 0.07 # relative
@@ -144,17 +138,16 @@ def to_precision(x,p):
144
138
 
145
139
  class Decoder:
146
140
  """
147
- Object encapsulating DSP processes to demodulate TicTacCode track from audio file;
148
- Decoders are instantiated by their respective Recording object. Produces
149
- plots on demand for diagnostic purposes.
141
+ Object encapsulating DSP processes to demodulate TicTacCode track from audio
142
+ file; Decoders are instantiated by their respective Recording object.
143
+ Produces plots on demand for diagnostic purposes.
150
144
 
151
145
  Attributes:
152
146
 
153
- sound_extract : numpy.ndarray of int16, shaped (N)
154
- duration of about SOUND_EXTRACT_LENGTH sec. sound data extract,
155
- could be anywhere in the audio file (start, end, etc...) Set by
156
- Recording object. This audio signal might or might not be the TicTacCode
157
- track.
147
+ sound_extract : 1d numpy.ndarray of int16
148
+ length determined by SOUND_EXTRACT_LENGTH (sec). Could be anywhere
149
+ in the audio file (start, end, etc...) Set by Recording object.
150
+ This audio signal might or might not be the TicTacCode track.
158
151
 
159
152
  sound_extract_position : int
160
153
  where the sound_extract is located in the file, samples
@@ -181,10 +174,6 @@ class Decoder:
181
174
  detected_pulse_position : int
182
175
  pulse position (samples) relative to the start of self.sound_extract
183
176
 
184
- cached_convolution_fit : dict
185
- if _fit_triangular_signal_to_convoluted_env() has already been called,
186
- will use cached values if sound_extract_position is the same.
187
-
188
177
  """
189
178
 
190
179
  def __init__(self, aRec, do_plots):
@@ -244,7 +233,7 @@ class Decoder:
244
233
 
245
234
  Uses the conditions below:
246
235
 
247
- Extract duration is 1.143 s.
236
+ Extract duration is 1.143 s. (ie one sec + 1 symbol duration)
248
237
  In self.word_props (list of morphology.regionprops):
249
238
  if one region, duration should be in [0.499 0.512] sec
250
239
  if two regions, total duration should be in [0.50 0.655]
@@ -255,13 +244,19 @@ class Decoder:
255
244
  props = self.words_props
256
245
  if len(props) not in [1,2]:
257
246
  failing_comment = 'len(props) not in [1,2]: %i'%len(props)
247
+ else:
248
+ logger.debug('len(props), %i, is in [1,2]'%len(props))
258
249
  if len(props) == 1:
250
+ logger.debug('one region')
259
251
  w = _width(props[0])/self.samplerate
260
252
  # self.effective_word_duration = w
261
253
  # logger.debug('effective_word_duration %f (one region)'%w)
262
254
  if not 0.499 < w < 0.512: # TODO: move as TOP OF FILE PARAMS
263
255
  failing_comment = '_width %f not in [0.499 0.512]'%w
256
+ else:
257
+ logger.debug('0.499 < width < 0.512, %f'%w)
264
258
  else: # 2 regions
259
+ logger.debug('two regions')
265
260
  widths = [_width(p)/self.samplerate for p in props] # in sec
266
261
  total_w = sum(widths)
267
262
  # extra_window_duration = SOUND_EXTRACT_LENGTH - 1
@@ -271,19 +266,26 @@ class Decoder:
271
266
  failing_comment = 'two regions duration %f not in [0.50 0.655]\n%s'%(total_w, widths)
272
267
  # fig, ax = plt.subplots()
273
268
  # p(ax, sound_extract_one_bit)
269
+ else:
270
+ logger.debug('0.5 < total_w < 0.656, %f'%total_w)
274
271
  logger.debug('failing_comment: %s'%(
275
272
  'none' if failing_comment=='' else failing_comment))
276
273
  return failing_comment == '' # no comment = extract seems TicTacCode
277
274
 
278
275
  def _plot_extract(self):
279
276
  fig, ax = plt.subplots()
280
- ax.plot(self.sound_extract, marker='o', markersize='1',
277
+ start = self.sound_extract_position
278
+ i_samples = np.arange(start, start + len(self.sound_extract))
279
+ yt = ax.get_yaxis_transform()
280
+ ax.hlines(0, 0, 1,
281
+ transform=yt, alpha=0.3,
282
+ linewidth=2, colors='black')
283
+ ax.plot(i_samples, self.sound_extract, marker='o', markersize='1',
281
284
  linewidth=1.5,alpha=0.3, color='blue' )
282
- ax.plot(self.sound_extract_one_bit*np.max(np.abs(self.sound_extract)),
285
+ ax.plot(i_samples, self.sound_extract_one_bit*np.max(np.abs(self.sound_extract)),
283
286
  marker='o', markersize='1',
284
287
  linewidth=1.5,alpha=0.3,color='red')
285
288
  xt = ax.get_xaxis_transform()
286
- yt = ax.get_yaxis_transform()
287
289
  ax.hlines(self.pulse_detection_level, 0, 1,
288
290
  transform=yt, alpha=0.3,
289
291
  linewidth=2, colors='green')
@@ -296,7 +298,8 @@ class Decoder:
296
298
  custom_lines,
297
299
  'detection level, signal, detected region'.split(','),
298
300
  loc='lower right')
299
- ax.set_title('Finding word and sync pulse')
301
+ ax.set_title('Finding word + sync pulse')
302
+ plt.xlabel("Position in file (samples)")
300
303
  plt.show()
301
304
 
302
305
  def get_time_in_sound_extract(self):
@@ -450,14 +453,7 @@ class Decoder:
450
453
  start = round(0.5*symbol_length) # half symbol
451
454
  end = start + symbol_length
452
455
  word_begining = whole_word[start:]
453
- # word_one_bit = np.abs(word_begining)>self.pulse_detection_level
454
- # N_ones = round(1.5*SYMBOL_LENGTH*1e-3*self.samplerate) # so it includes sync pulse
455
- # word_one_bit = closing(word_one_bit, np.ones(N_ones))
456
456
  gt_detection_level = np.argwhere(np.abs(word_begining)>self.pulse_detection_level)
457
- # print(gt_detection_level)
458
- # plt.plot(word_one_bit)
459
- # plt.plot(word_begining/abs(np.max(word_begining)))
460
- # plt.show()
461
457
  word_start = gt_detection_level[0][0]
462
458
  word_end = gt_detection_level[-1][0]
463
459
  self.effective_word_duration = (word_end - word_start)/self.samplerate
@@ -474,7 +470,10 @@ class Decoder:
474
470
  1e3*TEENSY_MAX_LAG))
475
471
  logger.debug('relative audio_block gap %.2f'%(relative_gap))
476
472
  if relative_gap > 1:
477
- print('bug with relative_gap')
473
+ print('Warning: gap between spike and word is too big for %s'%self.rec)
474
+ print('Audio update() gap between sync pulse and word start: ')
475
+ print('%.2f ms (max value %.2f)'%(1e3*gap/self.samplerate,
476
+ 1e3*TEENSY_MAX_LAG))
478
477
  symbol_width_samples_theor = self.samplerate*SYMBOL_LENGTH*1e-3
479
478
  symbol_width_samples_eff = self.effective_word_duration * \
480
479
  self.samplerate/(N_SYMBOLS - 1)
@@ -487,14 +486,23 @@ class Decoder:
487
486
  symbols_indices = symbol_positions.round().astype(int)
488
487
  if self.do_plots:
489
488
  fig, ax = plt.subplots()
490
- ax.plot(whole_word, marker='o', markersize='1',
489
+ ax.hlines(0, 0, 1,
490
+ transform=ax.get_yaxis_transform(), alpha=0.3,
491
+ linewidth=2, colors='black')
492
+ start = self.sound_extract_position
493
+ i_samples = np.arange(start, start + len(whole_word))
494
+ ax.plot(i_samples, whole_word, marker='o', markersize='1',
491
495
  linewidth=1.5,alpha=0.3, color='blue' )
492
496
  xt = ax.get_xaxis_transform()
493
497
  for x in symbols_indices:
494
- ax.vlines(x, 0, 1,
498
+ ax.vlines(x + start, 0, 1,
495
499
  transform=xt,
496
500
  linewidth=0.6, colors='green')
497
501
  ax.set_title('Slicing the 34 bits word:')
502
+ plt.xlabel("Position in file (samples)")
503
+ ax.vlines(start, 0, 1,
504
+ transform=xt,
505
+ linewidth=0.6, colors='red')
498
506
  plt.show()
499
507
  slice_width = round(SYMBOL_LENGTH*1e-3*self.samplerate)
500
508
  slices = [whole_word[i:i+slice_width] for i in symbols_indices]
@@ -517,8 +525,10 @@ class Recording:
517
525
  AVpath : pathlib.path
518
526
  path of video+sound+TicTacCode file, relative to working directory
519
527
 
520
- valid_sound : pathlib.path
521
- path of sound file stripped of silent and TicTacCode channels
528
+ audio_data : in16 numpy.array of shape [nchan] x [N samples]
529
+
530
+ # valid_sound : pathlib.path
531
+ # path of sound file stripped of silent and TicTacCode channels
522
532
 
523
533
  device : Device
524
534
  identifies the device used for the recording, set in __init__()
@@ -532,7 +542,7 @@ class Recording:
532
542
 
533
543
  TicTacCode_channel : int
534
544
  which channel is sync track. 0 is first channel,
535
- set in _read_sound_find_TicTacCode().
545
+ set in _find_TicTacCode().
536
546
 
537
547
  decoder : yaltc.decoder
538
548
  associated decoder object, if file is audiovideo
@@ -556,7 +566,7 @@ class Recording:
556
566
  implicitly True for each video recordings (but not set)
557
567
 
558
568
  device_relative_speed : float
559
-
569
+ Set by
560
570
  the ratio of the recording device clock speed relative to the
561
571
  video recorder clock device, in order to correct clock drift with
562
572
  pysox tempo transform. If value < 1.0 then the recording is
@@ -591,13 +601,15 @@ class Recording:
591
601
 
592
602
  def __init__(self, media, do_plots=False):
593
603
  """
604
+ Set AVfilename string and check if file exists, does not read any
605
+ media data right away but uses ffprobe to parses the file and sets
606
+ probe attribute.
607
+
608
+ Logs a warning and sets Recording.decoder to None if ffprobe cant
609
+ interpret the file or if file has no audio. If file contains audio,
610
+ initialise Recording.decoder(but doesnt try to decode anything yet).
611
+
594
612
  If multifile recording, AVfilename is sox merged audio file;
595
- Set AVfilename string and check if file exists, does not read
596
- any media data right away but uses ffprobe to parses the file and
597
- sets probe attribute.
598
- Logs a warning if ffprobe cant interpret the file or if file
599
- has no audio; if file contains audio, instantiates a Decoder object
600
- (but doesnt try to decode anything yet)
601
613
 
602
614
  Parameters
603
615
  ----------
@@ -635,7 +647,7 @@ class Recording:
635
647
  self.TicTacCode_channel = None
636
648
  self.is_reference = False
637
649
  self.device_relative_speed = 1.0
638
- self.valid_sound = None
650
+ # self.valid_sound = None
639
651
  self.final_synced_file = None
640
652
  self.synced_audio = None
641
653
  self.new_rec_name = media.path.name
@@ -671,11 +683,44 @@ class Recording:
671
683
  print('Recording init failed: %s'%recording_init_fail)
672
684
  self.probe = None
673
685
  self.decoder = None
686
+ return
674
687
  logger.debug('ffprobe found: %s'%self.probe)
675
688
  logger.debug('n audio chan: %i'%self.get_audio_channels_nbr())
689
+ self._read_audio_data()
690
+
691
+ def _read_audio_data(self):
692
+ # sets Recording.audio_data
693
+ dryrun = (ffmpeg
694
+ .input(str(self.AVpath))
695
+ .output('pipe:', format='s16le', acodec='pcm_s16le')
696
+ .get_args())
697
+ dryrun = ' '.join(dryrun)
698
+ logger.debug('using ffmpeg-python built args to pipe audio stream into numpy array:\nffmpeg %s'%dryrun)
699
+ try:
700
+ out, _ = (ffmpeg
701
+ # .input(str(path), ss=time_where, t=chunk_length)
702
+ .input(str(self.AVpath))
703
+ .output('pipe:', format='s16le', acodec='pcm_s16le')
704
+ .global_args("-loglevel", "quiet")
705
+ .global_args("-nostats")
706
+ .global_args("-hide_banner")
707
+ .run(capture_stdout=True))
708
+ data = np.frombuffer(out, np.int16)
709
+ except ffmpeg.Error as e:
710
+ print('error',e.stderr)
711
+ n_chan = self.get_audio_channels_nbr()
712
+ if n_chan == 1 and not self.is_video():
713
+ logger.error('file is sound mono')
714
+ if np.isclose(np.std(data), 0, rtol=1e-2):
715
+ logger.error("ffmpeg can't extract audio from %s"%self.AVpath)
716
+ # from 1D interleaved channels to [chan1, chan2, chanN]
717
+ self.audio_data = data.reshape(int(len(data)/n_chan),n_chan).T
718
+ logger.debug('Recording.audio_data: %s of shape %s'%(self.audio_data,
719
+ self.audio_data.shape))
676
720
 
677
721
  def __repr__(self):
678
- return 'Recording of %s'%_pathname(self.new_rec_name)
722
+ # return 'Recording of %s'%_pathname(self.new_rec_name)
723
+ return _pathname(self.new_rec_name)
679
724
 
680
725
  def _check_for_camera_error_correction(self):
681
726
  # look for a file number
@@ -712,9 +757,9 @@ class Recording:
712
757
  recording duration in seconds.
713
758
 
714
759
  """
715
- if self.valid_sound:
716
- val = sox.file_info.duration(_pathname(self.valid_sound))
717
- logger.debug('sox duration of valid_sound %f for %s'%(val,_pathname(self.valid_sound)))
760
+ if self.is_audio():
761
+ val = sox.file_info.duration(_pathname(self.AVpath))
762
+ logger.debug('sox duration of valid_sound %f for %s'%(val,_pathname(self.AVpath)))
718
763
  return val #########################################################
719
764
  else:
720
765
  if self.probe is None:
@@ -740,8 +785,8 @@ class Recording:
740
785
  recording duration in seconds.
741
786
 
742
787
  """
743
- val = sox.file_info.duration(_pathname(self.valid_sound))
744
- logger.debug('duration of valid_sound %f'%val)
788
+ val = sox.file_info.duration(_pathname(self.AVpath))
789
+ logger.debug('duration of AVpath %f'%val)
745
790
  return val
746
791
 
747
792
  def get_corrected_duration(self):
@@ -754,13 +799,14 @@ class Recording:
754
799
 
755
800
  def needs_dedrifting(self):
756
801
  rel_sp = self.device_relative_speed
757
- if rel_sp > 1:
758
- delta = (rel_sp - 1)*self.get_original_duration()
759
- else:
760
- delta = (1 - rel_sp)*self.get_original_duration()
802
+ # if rel_sp > 1:
803
+ # delta = (rel_sp - 1)*self.get_original_duration()
804
+ # else:
805
+ # delta = (1 - rel_sp)*self.get_original_duration()
806
+ delta = abs((1 - rel_sp)*self.get_original_duration())
761
807
  logger.debug('%s delta drift %.2f ms'%(str(self), delta*1e3))
762
808
  if delta > MAXDRIFT:
763
- print('[gold1]%s[/gold1] will get drift correction: delta of [gold1]%.3f[/gold1] ms is too big'%
809
+ print('\n[gold1]%s[/gold1] will get drift correction: delta of [gold1]%.3f[/gold1] ms is too big'%
764
810
  (self.AVpath, delta*1e3))
765
811
  return delta > MAXDRIFT, delta
766
812
 
@@ -783,8 +829,8 @@ class Recording:
783
829
 
784
830
  def _find_time_around(self, time):
785
831
  """
786
- Actually reads sound data and tries to decode it
787
- through decoder object, if successful return a time dict, eg:
832
+ Tries to decode FSK around time (in sec)
833
+ through decoder object; if successful return a time dict, eg:
788
834
  {'version': 0, 'seconds': 44, 'minutes': 57,
789
835
  'hours': 19, 'day': 1, 'month': 3, 'year offset': 1,
790
836
  'pulse at': 670451.2217 }
@@ -794,7 +840,7 @@ class Recording:
794
840
  there = self.get_duration() + time
795
841
  else:
796
842
  there = time
797
- self._read_sound_find_TicTacCode(there, SOUND_EXTRACT_LENGTH)
843
+ self._find_TicTacCode(there, SOUND_EXTRACT_LENGTH)
798
844
  if self.TicTacCode_channel is None:
799
845
  return None
800
846
  else:
@@ -908,9 +954,13 @@ class Recording:
908
954
  Try to decode a TicTacCode_channel at start AND finish;
909
955
  if successful, returns a datetime.datetime instance;
910
956
  if not returns None.
911
- If successful AND self is audio, sets self.valid_sound
912
957
  """
958
+ logger.debug('for %s, recording.start_time %s'%(self,
959
+ self.start_time))
960
+ if self.decoder is None:
961
+ return None # ffprobe failes or file too short, see __init__
913
962
  if self.start_time is not None:
963
+ logger.debug('Recording.start_time already found %s'%self.start_time)
914
964
  return self.start_time #############################################
915
965
  cached_times = {}
916
966
  def find_time(t_sec):
@@ -935,8 +985,8 @@ class Recording:
935
985
  len(TRIAL_TIMES)))
936
986
  # time_around_beginning = self._find_time_around(near_beg)
937
987
  time_around_beginning = find_time(near_beg)
938
- if self.TicTacCode_channel is None:
939
- return None ####################################################
988
+ # if self.TicTacCode_channel is None:
989
+ # return None ####################################################
940
990
  logger.debug('Trial #%i, end at %f'%(i+1, near_end))
941
991
  # time_around_end = self._find_time_around(near_end)
942
992
  time_around_end = find_time(near_end)
@@ -949,6 +999,7 @@ class Recording:
949
999
  time_around_end)
950
1000
  logger.debug('_two_times_are_coherent: %s'%coherence)
951
1001
  if coherence:
1002
+ logger.debug('Trial #%i successful'%(i+1))
952
1003
  break
953
1004
  if not coherence:
954
1005
  logger.warning('found times are incoherent')
@@ -972,49 +1023,10 @@ class Recording:
972
1023
  logger.debug('recording started at %s'%start_UTC)
973
1024
  self.start_time = start_UTC
974
1025
  self.sync_position = time_around_beginning['pulse at']
975
- if self.is_audio():
976
- # self.valid_sound = self._strip_TTC_and_Null() # why now? :-)
977
- self.valid_sound = self.AVpath
1026
+ # if self.is_audio():
1027
+ # self.valid_sound = self.AVpath
978
1028
  return start_UTC
979
1029
 
980
- def _sox_strip(self, audio_file, excluded_channels) -> tempfile.NamedTemporaryFile:
981
- # building dict according to pysox.remix format.
982
- # https://pysox.readthedocs.io/en/latest/api.html#sox.transform.Transformer.remix
983
- # eg: 4 channels with TicTacCode_channel at #2
984
- # returns {1: [1], 2: [3], 3: [4]}
985
- # ie the number of channels drops by one and chan 2 is missing
986
- # excluded_channels is a list of Zero Based indexing chan numbers
987
- n_channels = self.device.n_chan
988
- all_channels = range(1, n_channels + 1) # from 1 to n_channels included
989
- sox_excluded_channels = [n+1 for n in excluded_channels]
990
- logger.debug('for file %s'%self.AVpath.name)
991
- logger.debug('excluded chans %s (not ZBIDX)'%sox_excluded_channels)
992
- kept_chans = [[n] for n in all_channels if n not in sox_excluded_channels]
993
- # eg [[1], [3], [4]]
994
- sox_remix_dict = dict(zip(all_channels, kept_chans))
995
- # {1: [1], 2: [3], 3: [4]} -> from 4 to 3 chan and chan 2 is dropped
996
- output_fh = tempfile.NamedTemporaryFile(suffix='.wav', delete=DEL_TEMP)
997
- out_file = _pathname(output_fh)
998
- logger.debug('sox in and out files: %s %s'%(audio_file, out_file))
999
- # sox_transform.set_output_format(channels=1)
1000
- sox_transform = sox.Transformer()
1001
- sox_transform.remix(sox_remix_dict)
1002
- logger.debug('sox remix transform: %s'%sox_transform)
1003
- logger.debug('sox remix dict: %s'%sox_remix_dict)
1004
- status = sox_transform.build(audio_file, out_file, return_output=True )
1005
- logger.debug('sox.build exit code %s'%str(status))
1006
- p = Popen('ffprobe %s -hide_banner'%audio_file,
1007
- shell=True, stdout=PIPE, stderr=PIPE)
1008
- stdout, stderr = p.communicate()
1009
- logger.debug('remixed input_file ffprobe:\n%s'%(stdout +
1010
- stderr).decode('utf-8'))
1011
- p = Popen('ffprobe %s -hide_banner'%out_file,
1012
- shell=True, stdout=PIPE, stderr=PIPE)
1013
- stdout, stderr = p.communicate()
1014
- logger.debug('remixed out_file ffprobe:\n%s'%(stdout +
1015
- stderr).decode('utf-8'))
1016
- return output_fh
1017
-
1018
1030
  def _ffprobe_audio_stream(self):
1019
1031
  streams = self.probe['streams']
1020
1032
  audio_streams = [
@@ -1052,6 +1064,7 @@ class Recording:
1052
1064
  return int(ppm)
1053
1065
 
1054
1066
  def get_speed_ratio(self, videoclip):
1067
+ # ratio between real samplerates of audio and videoclip
1055
1068
  nominal = self.get_samplerate()
1056
1069
  true = self.true_samplerate
1057
1070
  ratio = true/nominal
@@ -1071,9 +1084,10 @@ class Recording:
1071
1084
  string = self._ffprobe_video_stream()['avg_frame_rate']
1072
1085
  return eval(string) # eg eval(24000/1001)
1073
1086
 
1074
- def get_timecode(self, with_offset=0):
1075
- # returns a HHMMSS:FR string
1087
+ def get_start_timecode_string(self, with_offset=0):
1088
+ # returns a HH:MM:SS:FR string
1076
1089
  start_datetime = self.get_start_time()
1090
+ # logger.debug('CLI_offset %s'%CLI_offset)
1077
1091
  logger.debug('start_datetime %s'%start_datetime)
1078
1092
  start_datetime += timedelta(seconds=with_offset)
1079
1093
  logger.debug('shifted start_datetime %s (offset %f)'%(start_datetime,
@@ -1130,12 +1144,11 @@ class Recording:
1130
1144
  def is_audio(self):
1131
1145
  return not self.is_video()
1132
1146
 
1133
- def _read_sound_find_TicTacCode(self, time_where, chunk_length):
1147
+ def _find_TicTacCode(self, time_where, chunk_length):
1134
1148
  """
1135
- If this is called for the first time for the recording, it loads audio
1136
- data reading from self.AVpath; Split data into channels if stereo; Send
1137
- this data to Decoder object with set_sound_extract_and_sr() to find
1138
- which channel contains a TicTacCode track and sets TicTacCode_channel
1149
+ Extracts a chunk from Recording.audio_data and sends it to
1150
+ Recording.decoder object with set_sound_extract_and_sr() to find which
1151
+ channel contains a TicTacCode track and sets TicTacCode_channel
1139
1152
  accordingly (index of channel). On exit, self.decoder.sound_extract
1140
1153
  contains TicTacCode data ready to be demodulated. If not,
1141
1154
  self.TicTacCode_channel is set to None.
@@ -1164,44 +1177,18 @@ class Recording:
1164
1177
  decoder = self.decoder
1165
1178
  if decoder:
1166
1179
  decoder.clear_decoder()
1167
- # decoder.cached_convolution_fit['is clean'] = False
1168
1180
  if not self.has_audio():
1169
1181
  self.TicTacCode_channel = None
1170
1182
  return #############################################################
1171
- logger.debug('will read around %.2f sec'%time_where)
1172
- dryrun = (ffmpeg
1173
- .input(str(path))
1174
- .output('pipe:', format='s16le', acodec='pcm_s16le')
1175
- .get_args())
1176
- dryrun = ' '.join(dryrun)
1177
- logger.debug('using ffmpeg-python built args to pipe wav file into numpy array:\nffmpeg %s'%dryrun)
1178
- try:
1179
- out, _ = (ffmpeg
1180
- # .input(str(path), ss=time_where, t=chunk_length)
1181
- .input(str(path))
1182
- .output('pipe:', format='s16le', acodec='pcm_s16le')
1183
- .global_args("-loglevel", "quiet")
1184
- .global_args("-nostats")
1185
- .global_args("-hide_banner")
1186
- .run(capture_stdout=True))
1187
- data = np.frombuffer(out, np.int16)
1188
- except ffmpeg.Error as e:
1189
- print('error',e.stderr)
1190
- sound_data_var = np.std(data)
1191
- logger.debug('extracting sound, ffmpeg output:%s with variance %f'%(data,
1192
- sound_data_var))
1193
- sound_extract_position = int(self.get_samplerate()*time_where) # from sec to samples
1194
- n_chan = self.get_audio_channels_nbr()
1195
- if n_chan == 1 and not self.is_video():
1196
- logger.warning('file is sound mono')
1197
- if np.isclose(sound_data_var, 0, rtol=1e-2):
1198
- logger.warning("ffmpeg can't extract audio from %s"%self.AVpath)
1199
- # from 1D interleaved channels to [chan1, chan2, chanN]
1200
- all_channels_data = data.reshape(int(len(data)/n_chan),n_chan).T
1183
+ sound_data_var = np.std(self.audio_data)
1184
+ sound_extract_position = int(self.get_samplerate()*time_where)
1185
+ logger.debug('extracting sound at %i with variance %f'%(
1186
+ sound_extract_position,
1187
+ sound_data_var))
1201
1188
  if self.TicTacCode_channel == None:
1202
1189
  logger.debug('first call, will loop through all %i channels'%len(
1203
- all_channels_data))
1204
- for i_chan, chan_dat in enumerate(all_channels_data):
1190
+ self.audio_data))
1191
+ for i_chan, chan_dat in enumerate(self.audio_data):
1205
1192
  logger.debug('testing chan %i'%i_chan)
1206
1193
  start_idx = round(time_where*self.get_samplerate())
1207
1194
  extract_length = round(chunk_length*self.get_samplerate())
@@ -1227,7 +1214,7 @@ class Recording:
1227
1214
  start_idx = round(time_where*self.get_samplerate())
1228
1215
  extract_length = round(chunk_length*self.get_samplerate())
1229
1216
  end_idx = start_idx + extract_length
1230
- chan_dat = all_channels_data[self.TicTacCode_channel]
1217
+ chan_dat = self.audio_data[self.TicTacCode_channel]
1231
1218
  extract_audio_data = chan_dat[start_idx:end_idx]
1232
1219
  decoder.set_sound_extract_and_sr(
1233
1220
  extract_audio_data,
@@ -1239,7 +1226,7 @@ class Recording:
1239
1226
  def seems_to_have_TicTacCode_at_beginning(self):
1240
1227
  if self.probe is None:
1241
1228
  return False #######################################################
1242
- self._read_sound_find_TicTacCode(TRIAL_TIMES[0][0],
1229
+ self._find_TicTacCode(TRIAL_TIMES[0][0],
1243
1230
  SOUND_EXTRACT_LENGTH)
1244
1231
  return self.TicTacCode_channel is not None
1245
1232
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.82a0
3
+ Version: 0.95a0
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -0,0 +1,15 @@
1
+ tictacsync/LTCcheck.py,sha256=IEfpB_ZajWuRTWtqji0H-B2g7GQvWmGVjfT0Icumv7o,15704
2
+ tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ tictacsync/device_scanner.py,sha256=kkwiO6qFNyBwqyZGqsf2q8WUqu4BBMFqFaUFFTeFfiY,29034
4
+ tictacsync/entry.py,sha256=fklCyTgqxJPWzKsn1ow3IxLPq8obv-N8Z72ieRzulCI,13352
5
+ tictacsync/multi2polywav.py,sha256=BsZxUjZo2Px6opKpFlgcvdZuUKDANEVTdapuWrX1jKw,7287
6
+ tictacsync/remergemix.py,sha256=FJTMipIS0O7mMl_tr8BhuYqWvanSydvjGkFCEd-jaDk,9829
7
+ tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
8
+ tictacsync/timeline.py,sha256=YhnNqYnbTTf2YVN5nLlQY62UA4z9fTij6x18tR_u3Nc,60424
9
+ tictacsync/yaltc.py,sha256=EzAF5VnMMeBp_o2AOs7wj0p31mElcb6D57YJVKUbOxM,53400
10
+ tictacsync-0.95a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
11
+ tictacsync-0.95a0.dist-info/METADATA,sha256=oHUqw0Q9bboCClpSRB8wNs5rQv_Ex5RnYefGLV2bpik,5502
12
+ tictacsync-0.95a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
13
+ tictacsync-0.95a0.dist-info/entry_points.txt,sha256=g3tdFFrVRcrKpuyKOCLUVBMgYfV65q9kpLZUOD_XCKg,139
14
+ tictacsync-0.95a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
15
+ tictacsync-0.95a0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- tictacsync/LTCcheck.py,sha256=IEfpB_ZajWuRTWtqji0H-B2g7GQvWmGVjfT0Icumv7o,15704
2
- tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- tictacsync/device_scanner.py,sha256=lvWps5XPvzubnTqisFWjdRUpxsM9YMRYMp-xQu7H6Os,27515
4
- tictacsync/entry.py,sha256=Uf-vSHVmGfWnyXq8Ee4bWLzzJMoo9G1iuRZKOeYk8aE,11386
5
- tictacsync/multi2polywav.py,sha256=k7VU-yjO1_0DbygWNytYvaExbiAs3_0-n0UmgGTa8wM,7282
6
- tictacsync/remergemix.py,sha256=FJTMipIS0O7mMl_tr8BhuYqWvanSydvjGkFCEd-jaDk,9829
7
- tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
8
- tictacsync/timeline.py,sha256=HvSy8_0auI2-jE1rZ9Bu9PcCIAHhNyUv9tpL75Po-uk,57877
9
- tictacsync/yaltc.py,sha256=zXbAvM00t1-VXRooH28AO3AcYgLSoo0p2Fq2LN4SALU,54457
10
- tictacsync-0.82a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
11
- tictacsync-0.82a0.dist-info/METADATA,sha256=z4G9ejQfvAURTp340mIdCbhoAkV-6pTJdjd8TN8UgDI,5502
12
- tictacsync-0.82a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
13
- tictacsync-0.82a0.dist-info/entry_points.txt,sha256=g3tdFFrVRcrKpuyKOCLUVBMgYfV65q9kpLZUOD_XCKg,139
14
- tictacsync-0.82a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
15
- tictacsync-0.82a0.dist-info/RECORD,,