tictacsync 0.3a4__py3-none-any.whl → 0.5a0__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
@@ -71,11 +71,11 @@ BPF_LOW_FRQ, BPF_HIGH_FRQ = (0.5*F1, 2*F2)
71
71
  # utility for accessing pathnames
72
72
  def _pathname(tempfile_or_path):
73
73
  if isinstance(tempfile_or_path, type('')):
74
- return tempfile_or_path
74
+ return tempfile_or_path ################################################
75
75
  if isinstance(tempfile_or_path, Path):
76
- return str(tempfile_or_path)
76
+ return str(tempfile_or_path) ###########################################
77
77
  if isinstance(tempfile_or_path, tempfile._TemporaryFileWrapper):
78
- return tempfile_or_path.name
78
+ return tempfile_or_path.name ###########################################
79
79
  else:
80
80
  raise Exception('%s should be Path or tempfile... is %s'%(
81
81
  tempfile_or_path,
@@ -90,7 +90,7 @@ def to_precision(x,p):
90
90
  """
91
91
  x = float(x)
92
92
  if x == 0.:
93
- return "0." + "0"*(p-1)
93
+ return "0." + "0"*(p-1) ################################################
94
94
  out = []
95
95
  if x < 0:
96
96
  out.append("-")
@@ -243,7 +243,7 @@ class Decoder:
243
243
  # return self.detected_pulse_position
244
244
  if self.estimated_pulse_position:
245
245
  logger.debug('returning cached estimated value')
246
- return self.estimated_pulse_position
246
+ return self.estimated_pulse_position ###############################
247
247
  _, silence_center_x = self._fit_triangular_signal_to_convoluted_env()
248
248
  # symbol_width_samples = 1e-3*SYMBOL_LENGTH
249
249
  self.estimated_pulse_position = silence_center_x + int(0.5*(
@@ -394,8 +394,9 @@ class Decoder:
394
394
  v1 = self.cached_convolution_fit['chi_square']
395
395
  v2 = self.cached_convolution_fit['minimum position']
396
396
  v2_file = v2 + self.sound_extract_position
397
- logger.debug('cached chi_sq: %s minimum position in file: %s'%(v1, v2_file))
398
- return (v1, v2)
397
+ logger.debug('cached chi_sq: %s minimum position in file: %s'%
398
+ (v1, v2_file))
399
+ return (v1, v2) ####################################################
399
400
  # cached!
400
401
  x_shifted, convolution = self._get_square_convolution()
401
402
  # see numpy.convolve(..., mode='valid')
@@ -419,7 +420,7 @@ class Decoder:
419
420
  mp = pars['min_position']
420
421
  model = 2*A*arcsin(abs(sin((x - mp)*2*pi/p)))/pi
421
422
  if signal_data is None:
422
- return model
423
+ return model ###################################################
423
424
  return model - signal_data
424
425
  fit_trig = lmfit.minimize(
425
426
  trig_wave, trig_params,
@@ -430,7 +431,8 @@ class Decoder:
430
431
  min_position = int(fit_trig.params['min_position'].value) + shift
431
432
  logger.debug('chi_square %.1f minimum convolution position %i in file'%
432
433
  (chi_square, min_position + self.sound_extract_position))
433
- self.cached_convolution_fit['sound_extract_position'] = self.sound_extract_position
434
+ self.cached_convolution_fit['sound_extract_position'] = \
435
+ self.sound_extract_position
434
436
  self.cached_convolution_fit['chi_square'] = chi_square
435
437
  self.cached_convolution_fit['minimum position'] = min_position
436
438
 
@@ -465,8 +467,9 @@ class Decoder:
465
467
 
466
468
  """
467
469
  if self.silent_zone_indices:
468
- return self.silent_zone_indices
469
- _, silence_center_position = self._fit_triangular_signal_to_convoluted_env()
470
+ return self.silent_zone_indices ####################################
471
+ _, silence_center_position = \
472
+ self._fit_triangular_signal_to_convoluted_env()
470
473
  srate = self.samplerate
471
474
  half_window = int(SAFE_SILENCE_WINDOW_WIDTH * 1e-3 * srate/2)
472
475
  left_window_boundary = silence_center_position - half_window
@@ -711,7 +714,7 @@ class Decoder:
711
714
  boundaries_OK, left_boundary, right_boundary = \
712
715
  self._get_BFSK_word_boundaries()
713
716
  if left_boundary is None:
714
- return None, None, None
717
+ return None, None, None ############################################
715
718
  symbol_width_samples = \
716
719
  float(right_boundary - left_boundary)/N_SYMBOLS_SAMD21
717
720
  symbol_positions = symbol_width_samples * \
@@ -911,7 +914,7 @@ class Decoder:
911
914
 
912
915
  def get_time_in_sound_extract(self, plots):
913
916
  if self.sound_extract is None:
914
- return None
917
+ return None ########################################################
915
918
  if plots:
916
919
  self.make_silence_analysis_plot()
917
920
  pulse_position = self._detect_sync_pulse_position()
@@ -928,7 +931,7 @@ class Decoder:
928
931
  self._plot_slices(pulse_position, symbols_indices, word_lft,
929
932
  word_rght, title)
930
933
  if symbols_indices is None:
931
- return None
934
+ return None ########################################################
932
935
  sliced_data = self._slice_sound_extract(symbols_indices)
933
936
  frequencies = [self._get_main_frequency(data_slice)
934
937
  for data_slice
@@ -950,7 +953,7 @@ class Decoder:
950
953
  symbols_indices[i],
951
954
  symbols_indices[i+1]))
952
955
  if None in bits:
953
- return None
956
+ return None ########################################################
954
957
  bits_string = ''.join(bits)
955
958
  logger.debug('bits = %s'%bits_string)
956
959
  time_values = self._values_from_bits(bits_string)
@@ -1011,25 +1014,26 @@ class Recording:
1011
1014
  implicitly True for each video recordings (but not set)
1012
1015
 
1013
1016
  device_relative_speed : float
1017
+
1014
1018
  the ratio of the recording device clock speed relative to the
1015
- reference_rec clock device, in order to correct clock drift with
1016
- pysox tempo transform. If value < 1.0 then the recording is slower
1017
- than reference_rec. Updated by each AudioStitcherVideoMerger
1018
- instance so the value can change depending on the reference
1019
- recording (video or main sound). A mean is calculated for all
1019
+ video recorder clock device, in order to correct clock drift with
1020
+ pysox tempo transform. If value < 1.0 then the recording is
1021
+ slower than video recorder. Updated by each
1022
+ AudioStitcherVideoMerger instance so the value can change
1023
+ depending on the video recording . A mean is calculated for all
1020
1024
  recordings of the same device in
1021
1025
  AudioStitcherVideoMerger._get_concatenated_audiofile_for()
1022
1026
 
1023
1027
  time_position : float
1024
1028
  The time (in seconds) at which the recording starts relative to the
1025
- reference recording. Updated by each AudioStitcherVideoMerger
1026
- instance so the value can change depending on the reference
1029
+ video recording. Updated by each AudioStitcherVideoMerger
1030
+ instance so the value can change depending on the video
1027
1031
  recording (a video or main sound).
1028
1032
 
1029
1033
  final_synced_file : a pathlib.Path
1030
1034
  contains the path of the merged video file after the call to
1031
1035
  AudioStitcher.build_audio_and_write_video if the Recording is a
1032
- reference recording, relative to the working directory
1036
+ video recording, relative to the working directory
1033
1037
 
1034
1038
  synced_audio : pathlib.Path
1035
1039
  contains the path of audio only of self.final_synced_file. Absolute
@@ -1168,10 +1172,10 @@ class Recording:
1168
1172
  if self.valid_sound:
1169
1173
  val = sox.file_info.duration(_pathname(self.valid_sound))
1170
1174
  logger.debug('duration of valid_sound %f'%val)
1171
- return val
1175
+ return val #########################################################
1172
1176
  else:
1173
1177
  if self.probe is None:
1174
- return 0
1178
+ return 0 #######################################################
1175
1179
  try:
1176
1180
  probed_duration = float(self.probe['format']['duration'])
1177
1181
  except:
@@ -1292,7 +1296,7 @@ class Recording:
1292
1296
 
1293
1297
  """
1294
1298
  if t1 == None or t2 == None:
1295
- return False
1299
+ return False #######################################################
1296
1300
  logger.debug('t1 : %s t2: %s'%(t1, t2))
1297
1301
  datetime_1 = self._get_timedate_from_dict(t1)
1298
1302
  datetime_2 = self._get_timedate_from_dict(t2)
@@ -1334,19 +1338,19 @@ class Recording:
1334
1338
  to_precision(true_samplerate, 8))
1335
1339
  return true_samplerate
1336
1340
 
1337
- def set_time_position_to(self, reference_recording):
1341
+ def set_time_position_to(self, video_clip):
1338
1342
  """
1339
1343
  Sets self.time_position, the time (in seconds) at which the recording
1340
- starts relative to the reference recording. Updated by each AudioStitcherVideoMerger
1341
- instance so the value can change depending on the reference
1344
+ starts relative to the video recording. Updated by each AudioStitcherVideoMerger
1345
+ instance so the value can change depending on the video
1342
1346
  recording (a video or main sound).
1343
1347
 
1344
1348
  called by timeline.AudioStitcher._get_concatenated_audiofile_for()
1345
1349
 
1346
1350
  """
1347
- ref_start_time = reference_recording.get_start_time()
1351
+ video_start_time = video_clip.get_start_time()
1348
1352
  self.time_position = (self.get_start_time()
1349
- - ref_start_time).total_seconds()
1353
+ - video_start_time).total_seconds()
1350
1354
 
1351
1355
  def get_Dt_with(self, later_recording):
1352
1356
  """
@@ -1366,14 +1370,14 @@ class Recording:
1366
1370
  If successful AND self is audio, sets self.valid_sound
1367
1371
  """
1368
1372
  if self.start_time is not None:
1369
- return self.start_time
1373
+ return self.start_time #############################################
1370
1374
  cached_times = {}
1371
1375
  def find_time(t_sec, plots=False):
1372
1376
  time_k = int(t_sec)
1373
1377
  # if cached_times.has_key(time_k):
1374
1378
  if CACHING and time_k in cached_times:
1375
1379
  logger.debug('cache hit _find_time_around() for t=%s s'%time_k)
1376
- return cached_times[time_k]
1380
+ return cached_times[time_k] ####################################
1377
1381
  else:
1378
1382
  logger.debug('_find_time_around() for t=%s s not cached'%time_k)
1379
1383
  new_t = self._find_time_around(t_sec, plots)
@@ -1391,7 +1395,7 @@ class Recording:
1391
1395
  # time_around_beginning = self._find_time_around(near_beg)
1392
1396
  time_around_beginning = find_time(near_beg, plots)
1393
1397
  if self.TicTacCode_channel is None:
1394
- return None
1398
+ return None ####################################################
1395
1399
  logger.debug('Trial #%i, end at %f'%(i+1, near_end))
1396
1400
  # time_around_end = self._find_time_around(near_end)
1397
1401
  time_around_end = find_time(near_end, plots)
@@ -1407,11 +1411,11 @@ class Recording:
1407
1411
  break
1408
1412
  if not coherence:
1409
1413
  logger.warning('found times are incoherent')
1410
- return None
1414
+ return None ########################################################
1411
1415
  if None in [time_around_beginning, time_around_end]:
1412
1416
  logger.warning('didnt find any time in file')
1413
1417
  self.start_time = None
1414
- return None
1418
+ return None ########################################################
1415
1419
  true_sr = self._compute_true_samplerate(
1416
1420
  time_around_beginning,
1417
1421
  time_around_end)
@@ -1428,132 +1432,134 @@ class Recording:
1428
1432
  self.start_time = start_UTC
1429
1433
  self.sync_position = time_around_beginning['pulse at']
1430
1434
  if self.is_audio():
1431
- self.valid_sound = self._strip_TTC_and_Null()
1435
+ # self.valid_sound = self._strip_TTC_and_Null() # why now? :-)
1436
+ self.valid_sound = self.AVpath
1432
1437
  return start_UTC
1433
1438
 
1434
- def _strip_TTC_and_Null(self) -> tempfile.NamedTemporaryFile:
1435
- """
1436
- TTC is stripped from original audio and a tempfile.NamedTemporaryFile is
1437
- returned. If the original audio is stereo, this is simply the audio
1438
- without the TicTacCode channel, so this fct returns a mono wav
1439
- tempfile. But if the original audio is multitrack, two possibilities:
1440
-
1441
- A- there's a track.txt file declaring null channels (with '0' tags)
1442
- then those tracks are excluded too (a check is done those tracks
1443
- have low signal and warns the user otherwise)
1444
-
1445
- B- it's a multitrack recording but without track declaration
1446
- (no track.txt was found in the device folder): all the tracks are
1447
- returned into a multiwav tempfile (except TTC).
1448
-
1449
- Notes:
1450
- 'track.txt' is defined in device_scanner.TRACKSFN
1451
-
1452
- Beware of track indexing: sox starts indexing tracks at 1 but code
1453
- here uses zero based indexing.
1454
- """
1455
- tracks_file = self.device.folder/device_scanner.TRACKSFN
1456
- input_file = _pathname(self.AVpath)
1457
- # n_channels = sox.file_info.channels(input_file) # eg 2
1458
- n_channels = self.device.n_chan
1459
- sox_TicTacCode_channel = self.TicTacCode_channel + 1 # sox channels start at 1
1460
- if n_channels == 2:
1461
- logger.debug('stereo, so only excluding TTC (ZB idx) %i'%
1462
- self.TicTacCode_channel)
1463
- return self._sox_strip(input_file, [self.TicTacCode_channel])
1464
- # First a check is done if the ttc tracks concur: the track detected
1465
- # by the Decoder class, stored in Recording.TicTacCode_channel VS the
1466
- # track declared by the user, Tracks.ttc (see device_scanner.py). If
1467
- # not, warn the user and exit.
1468
- trax = self.device.tracks # a Tracks dataclass instance, if any
1469
- logger.debug('trax %s'%trax)
1470
- if trax == None: #TO DO: mix or mixL, mixR or do mixdown
1471
- return self._sox_strip(input_file, [self.TicTacCode_channel])
1472
- else:
1473
- logger.debug('ttc channel declared for the device: %i, ttc detected: %i, non zero base indexing'%
1474
- (trax.ttc, sox_TicTacCode_channel))
1475
- if trax.ttc != sox_TicTacCode_channel: # warn and quit
1476
- print('Error: TicTacCode channel detected is [gold1]%i[/gold1]'%
1477
- sox_TicTacCode_channel, end=' ')
1478
- print('and the file [gold1]%s[/gold1] specifies channel [gold1]%i[/gold1],'%
1479
- (tracks_file, trax.ttc))
1480
- print('Please correct the discrepancy and rerun. Quitting.')
1481
- sys.exit(1)
1482
- track_is_declared_audio = [ tag not in ['ttc','0','tc']
1483
- for tag in trax.rawtrx]
1484
- logger.debug('from tracks_file %s'%tracks_file)
1485
- logger.debug('track_is_declared_audio (not ttc or 0): %s'%
1486
- track_is_declared_audio)
1487
- # Now find out which files are silent, ie those with sox stats "RMS lev
1488
- # db" value inferior to DB_RMS_SILENCE_SOX (typ -50 -60 dbFS) from the
1489
- # sox "stat" command. Ex output belwow:
1490
- #
1491
- # sox input.wav -n channels stats -w 0.01
1492
- #
1493
- # Overall Ch1 Ch2 Ch3 Ch4
1494
- # DC offset -0.000047 -0.000047 -0.000047 -0.000016 -0.000031
1495
- # Min level -0.060913 -0.060913 -0.048523 -0.036438 -0.002777
1496
- # Max level 0.050201 0.050201 0.048767 0.039032 0.002838
1497
- # Pk lev dB -24.31 -24.31 -26.24 -28.17 -50.94
1498
- # RMS lev dB -40.33 -55.29 -53.70 -34.41 -59.75 <- this line
1499
- # RMS Pk dB -28.39 -28.39 -30.90 -31.20 -55.79
1500
- # RMS Tr dB -97.42 -79.66 -75.87 -97.42 -96.09
1501
- # Crest factor - 35.41 23.61 2.05 2.76
1502
- # Flat factor 6.93 0.00 0.00 0.97 10.63
1503
- # Pk count 10.2 2 2 17 20
1504
- # Bit-depth 12/16 12/16 12/16 12/16 8/16
1505
- # Num samples 11.2M
1506
- # Length s 232.780
1507
- # Scale max 1.000000
1508
- # Window s 0.010
1509
- args = ['sox', input_file, '-n', 'channels', 'stats', '-w', '0.01']
1510
- _, _, stat_output = sox.core.sox(args)
1511
- logger.debug('sox stat output: \n%s'%stat_output)
1512
- sox_RMS_lev_dB = stat_output.split('\n')[5].split()[4:]
1513
- logger.debug('Rec %s'%self)
1514
- logger.debug('Sox RMS %s, n_channels: %i'%(sox_RMS_lev_dB, n_channels))
1515
- # valid audio is non silent and not ttc
1516
- track_is_active = [float(db) > DB_RMS_SILENCE_SOX
1517
- if idx + 1 != sox_TicTacCode_channel else False
1518
- for idx, db in enumerate(sox_RMS_lev_dB)]
1519
- logger.debug('track_is_active %s'%track_is_active)
1520
- # Stored in self.device.tracks and as declared by the user, a track is
1521
- # either:
1522
- #
1523
- # - an active track (because a name was given to it)
1524
- # - the ttc track (as identified by the user)
1525
- # - a muted track (declared by a "0" in tracks.txt)
1526
- #
1527
- # the following checks active tracks are effectively non silent and
1528
- # muted tracks are effectively silent (warn the user if not but
1529
- # proceed, giving priority to status declared in the tracks.txt file.
1530
- # eg a non silent track will be discarded if the user tagged it with
1531
- # a "0")
1532
- declared_and_detected_are_same = all([a==b for a,b
1533
- in zip(track_is_declared_audio, track_is_active)])
1534
- logger.debug('declared_and_detected_are_same: %s'%
1535
- declared_and_detected_are_same)
1536
- if not declared_and_detected_are_same:
1537
- print('Warning, the file [gold1]%s[/gold1] specifies channel usage'%
1538
- (tracks_file))
1539
- print('and some muted tracks are not silent (or the inverse, see below),')
1540
- print('will proceed but if it\'s an error do necessary corrections and rerun.\n')
1541
- table = Table(title="Tracks status")
1542
- table.add_column("track #", justify="center", style='gold1')
1543
- table.add_column("RMS Level", justify="center", style='gold1')
1544
- table.add_column("Declared", justify="center", style='gold1')
1545
- for n in range(n_channels):
1546
- table.add_row(str(n+1), '%.0f dBFS'%float(sox_RMS_lev_dB[n]),
1547
- trax.rawtrx[n])
1548
- console = Console()
1549
- console.print(table)
1550
- if trax:
1551
- excluded_channels = [i for i in range(n_channels)
1552
- if not track_is_declared_audio[i]]
1553
- else:
1554
- excluded_channels = [self.TicTacCode_channel]
1555
- logger.debug('excluded_channels %s (ZB idx)'%excluded_channels)
1556
- return self._sox_strip(input_file, excluded_channels)
1439
+ # def _strip_TTC_and_Null(self) -> tempfile.NamedTemporaryFile:
1440
+ # """
1441
+ # TTC is stripped from original audio and a tempfile.NamedTemporaryFile is
1442
+ # returned. If the original audio is stereo, this is simply the audio
1443
+ # without the TicTacCode channel, so this fct returns a mono wav
1444
+ # tempfile. But if the original audio is multitrack, two possibilities:
1445
+
1446
+ # A- there's a track.txt file declaring null channels (with '0' tags)
1447
+ # then those tracks are excluded too (a check is done those tracks
1448
+ # have low signal and warns the user otherwise)
1449
+
1450
+ # B- it's a multitrack recording but without track declaration
1451
+ # (no track.txt was found in the device folder): all the tracks are
1452
+ # returned into a multiwav tempfile (except TTC).
1453
+
1454
+ # Notes:
1455
+ # 'track.txt' is defined in device_scanner.TRACKSFN
1456
+
1457
+ # Beware of track indexing: sox starts indexing tracks at 1 but code
1458
+ # here uses zero based indexing.
1459
+ # """
1460
+ # tracks_file = self.device.folder/device_scanner.TRACKSFN
1461
+ # input_file = _pathname(self.AVpath)
1462
+ # # n_channels = sox.file_info.channels(input_file) # eg 2
1463
+ # n_channels = self.device.n_chan
1464
+ # sox_TicTacCode_channel = self.TicTacCode_channel + 1 # sox start at 1
1465
+ # if n_channels == 2:
1466
+ # logger.debug('stereo, so only excluding TTC (ZB idx) %i'%
1467
+ # self.TicTacCode_channel)
1468
+ # return self._sox_strip(input_file, [self.TicTacCode_channel]) ######
1469
+ # #
1470
+ # # First a check is done if the ttc tracks concur: the track detected
1471
+ # # by the Decoder class, stored in Recording.TicTacCode_channel VS the
1472
+ # # track declared by the user, Tracks.ttc (see device_scanner.py). If
1473
+ # # not, warn the user and exit.
1474
+ # trax = self.device.tracks # a Tracks dataclass instance, if any
1475
+ # logger.debug('trax %s'%trax)
1476
+ # if trax == None:
1477
+ # return self._sox_strip(input_file, [self.TicTacCode_channel]) ######
1478
+ # else:
1479
+ # logger.debug('ttc channel declared for the device: %i, ttc detected: %i, non zero base indexing'%
1480
+ # (trax.ttc, sox_TicTacCode_channel))
1481
+ # if trax.ttc != sox_TicTacCode_channel: # warn and quit
1482
+ # print('Error: TicTacCode channel detected is [gold1]%i[/gold1]'%
1483
+ # sox_TicTacCode_channel, end=' ')
1484
+ # print('and the file [gold1]%s[/gold1] specifies channel [gold1]%i[/gold1],'%
1485
+ # (tracks_file, trax.ttc))
1486
+ # print('Please correct the discrepancy and rerun. Quitting.')
1487
+ # sys.exit(1)
1488
+ # track_is_declared_audio = [ tag not in ['ttc','0','tc']
1489
+ # for tag in trax.rawtrx]
1490
+ # logger.debug('from tracks_file %s'%tracks_file)
1491
+ # logger.debug('track_is_declared_audio (not ttc or 0): %s'%
1492
+ # track_is_declared_audio)
1493
+ # # Now find out which files are silent, ie those with sox stats "RMS lev
1494
+ # # db" value inferior to DB_RMS_SILENCE_SOX (typ -50 -60 dbFS) from the
1495
+ # # sox "stat" command. Ex output belwow:
1496
+ # #
1497
+ # # sox input.wav -n channels stats -w 0.01
1498
+ # #
1499
+ # # Overall Ch1 Ch2 Ch3 Ch4
1500
+ # # DC offset -0.000047 -0.000047 -0.000047 -0.000016 -0.000031
1501
+ # # Min level -0.060913 -0.060913 -0.048523 -0.036438 -0.002777
1502
+ # # Max level 0.050201 0.050201 0.048767 0.039032 0.002838
1503
+ # # Pk lev dB -24.31 -24.31 -26.24 -28.17 -50.94
1504
+ # # RMS lev dB -40.33 -55.29 -53.70 -34.41 -59.75 <- this line
1505
+ # # RMS Pk dB -28.39 -28.39 -30.90 -31.20 -55.79
1506
+ # # RMS Tr dB -97.42 -79.66 -75.87 -97.42 -96.09
1507
+ # # Crest factor - 35.41 23.61 2.05 2.76
1508
+ # # Flat factor 6.93 0.00 0.00 0.97 10.63
1509
+ # # Pk count 10.2 2 2 17 20
1510
+ # # Bit-depth 12/16 12/16 12/16 12/16 8/16
1511
+ # # Num samples 11.2M
1512
+ # # Length s 232.780
1513
+ # # Scale max 1.000000
1514
+ # # Window s 0.010
1515
+ # args = ['sox', input_file, '-n', 'channels', 'stats', '-w', '0.01']
1516
+ # _, _, stat_output = sox.core.sox(args)
1517
+ # logger.debug('sox stat output: \n%s'%stat_output)
1518
+ # sox_RMS_lev_dB = stat_output.split('\n')[5].split()[4:]
1519
+ # logger.debug('Rec %s'%self)
1520
+ # logger.debug('Sox RMS %s, n_channels: %i'%(sox_RMS_lev_dB, n_channels))
1521
+ # # valid audio is non silent and not ttc
1522
+ # track_is_active = [float(db) > DB_RMS_SILENCE_SOX
1523
+ # if idx + 1 != sox_TicTacCode_channel else False
1524
+ # for idx, db in enumerate(sox_RMS_lev_dB)]
1525
+ # logger.debug('track_is_active %s'%track_is_active)
1526
+ # # Stored in self.device.tracks and as declared by the user, a track is
1527
+ # # either:
1528
+ # #
1529
+ # # - an active track (because a name was given to it)
1530
+ # # - the ttc track (as identified by the user)
1531
+ # # - a muted track (declared by a "0" in tracks.txt)
1532
+ # #
1533
+ # # the following checks active tracks are effectively non silent and
1534
+ # # muted tracks are effectively silent (warn the user if not but
1535
+ # # proceed, giving priority to status declared in the tracks.txt file.
1536
+ # # eg a non silent track will be discarded if the user tagged it with
1537
+ # # a "0")
1538
+ # declared_and_detected_are_same = all([a==b for a,b
1539
+ # in zip(track_is_declared_audio, track_is_active)])
1540
+ # logger.debug('declared_and_detected_are_same: %s'%
1541
+ # declared_and_detected_are_same)
1542
+ # if not declared_and_detected_are_same:
1543
+ # print('Warning, the file [gold1]%s[/gold1] specifies channel usage'%
1544
+ # (tracks_file))
1545
+ # print('and some muted tracks are not silent (or the inverse, see below),')
1546
+ # print('will proceed but if it\'s an error do necessary corrections and rerun.\n')
1547
+ # table = Table(title="Tracks status")
1548
+ # table.add_column("track #", justify="center", style='gold1')
1549
+ # table.add_column("RMS Level", justify="center", style='gold1')
1550
+ # table.add_column("Declared", justify="center", style='gold1')
1551
+ # for n in range(n_channels):
1552
+ # table.add_row(str(n+1), '%.0f dBFS'%float(sox_RMS_lev_dB[n]),
1553
+ # trax.rawtrx[n])
1554
+ # console = Console()
1555
+ # console.print(table)
1556
+ # if trax:
1557
+ # excluded_channels = [i for i in range(n_channels)
1558
+ # if not track_is_declared_audio[i]]
1559
+ # else:
1560
+ # excluded_channels = [self.TicTacCode_channel]
1561
+ # logger.debug('excluded_channels %s (ZB idx)'%excluded_channels)
1562
+ # return self._sox_strip(input_file, excluded_channels)
1557
1563
 
1558
1564
 
1559
1565
 
@@ -1631,13 +1637,13 @@ class Recording:
1631
1637
  ppm = - (nominal/true - 1) * 1e6
1632
1638
  return int(ppm)
1633
1639
 
1634
- def get_speed_ratio(self, ref_recording):
1640
+ def get_speed_ratio(self, videoclip):
1635
1641
  nominal = self.get_samplerate()
1636
1642
  true = self.true_samplerate
1637
1643
  ratio = true/nominal
1638
- nominal_ref = ref_recording.get_samplerate()
1639
- true_ref = ref_recording.true_samplerate
1640
- ratio_ref = true_ref/nominal_ref
1644
+ nominal_vid = videoclip.get_samplerate()
1645
+ true_ref = videoclip.true_samplerate
1646
+ ratio_ref = true_ref/nominal_vid
1641
1647
  return ratio/ratio_ref
1642
1648
 
1643
1649
  def get_samplerate(self):
@@ -1688,20 +1694,20 @@ class Recording:
1688
1694
 
1689
1695
  def has_audio(self):
1690
1696
  if not self.probe:
1691
- return False
1697
+ return False #######################################################
1692
1698
  streams = self.probe['streams']
1693
1699
  codecs = [stream['codec_type'] for stream in streams]
1694
1700
  return 'audio' in codecs
1695
1701
 
1696
1702
  def get_audio_channels_nbr(self):
1697
1703
  if not self.has_audio():
1698
- return 0
1704
+ return 0 ###########################################################
1699
1705
  audio_str = self._ffprobe_audio_stream()
1700
1706
  return audio_str['channels']
1701
1707
 
1702
1708
  def is_video(self):
1703
1709
  if not self.probe:
1704
- return False
1710
+ return False #######################################################
1705
1711
  streams = self.probe['streams']
1706
1712
  codecs = [stream['codec_type'] for stream in streams]
1707
1713
  return 'video' in codecs
@@ -1742,7 +1748,7 @@ class Recording:
1742
1748
  # decoder.cached_convolution_fit['is clean'] = False
1743
1749
  if not self.has_audio():
1744
1750
  self.TicTacCode_channel = None
1745
- return
1751
+ return #############################################################
1746
1752
  logger.debug('will read around %.2f sec'%time_where)
1747
1753
  dryrun = (ffmpeg
1748
1754
  .input(str(path), ss=time_where, t=chunk_length)
@@ -1775,9 +1781,10 @@ class Recording:
1775
1781
  )
1776
1782
  if decoder.extract_seems_TicTacCode():
1777
1783
  self.TicTacCode_channel = i_chan
1784
+ self.device.ttc = i_chan
1778
1785
  logger.debug('find TicTacCode channel, chan #%i'%
1779
1786
  self.TicTacCode_channel)
1780
- return self
1787
+ return self ####################################################
1781
1788
  # end of loop: none found
1782
1789
  self.TicTacCode_channel = None
1783
1790
  logger.warning('found no TicTacCode channel')
@@ -1785,8 +1792,9 @@ class Recording:
1785
1792
 
1786
1793
  def seems_to_have_TicTacCode_at_beginning(self):
1787
1794
  if self.probe is None:
1788
- return False
1789
- self._read_sound_find_TicTacCode(TRIAL_TIMES[0][0], SOUND_EXTRACT_LENGTH)
1795
+ return False #######################################################
1796
+ self._read_sound_find_TicTacCode(TRIAL_TIMES[0][0],
1797
+ SOUND_EXTRACT_LENGTH)
1790
1798
  return self.TicTacCode_channel is not None
1791
1799
 
1792
1800
  def does_overlap_with_time(self, time):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.3a4
3
+ Version: 0.5a0
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -38,15 +38,21 @@ Unfinished sloppy code ahead, but should run without errors. Some functionalitie
38
38
 
39
39
  ## Description
40
40
 
41
- `tictacsync` is a python script to sync audio and video files shot
42
- with [dual system sound](https://www.learnlightandsound.com/blog/2017/2/23/how-to-record-sound-for-video-dual-systemsync-sound) using a specific hardware timecode generator
43
- called [Tic Tac Sync](https://tictacsync.org). The timecode is named YaLTC for *yet
44
- another longitudinal time code* and should be recorded on a scratch
45
- track on each device for the syncing to be performed, later in _postprod_ before editing.
46
-
41
+ `tictacsync` is a python script to sync, cut and join audio files against camera files shot using a specific hardware timecode generator
42
+ called [Tic Tac Sync](https://tictacsync.org). The timecode is named TicTacCode and should be recorded on a scratch
43
+ track on each device for `tictacsync` to work.
47
44
  ## Status
48
45
 
49
- `tictacsync` scans for audio video files and displays their starting time and then merges overlapping audio and video recordings. Multicam syncing with one stereo audio recorder has been tested (spring 2023, [see demo](https://youtu.be/pklTSTi7cqs)). Multi audio recorders coming soon...
46
+ Feature complete! `tictacsync` scans for audio video files and then merges overlapping audio and video recordings, It
47
+
48
+ * Decodes the TicTacCode audio track alongside your audio tracks
49
+ * Establishes UTC start time (and end time) within 100 μs!
50
+ * Syncs, cuts and joins any concurrent audio to camera files (using `FFmpeg`)
51
+ * Processes _multiple_ audio recorders
52
+ * Corrects device clock drift so _both_ ends coincide (thanks to `sox`)
53
+ * Sets video metadata TC of multicam files for NLE timeline alignement
54
+ * Writes _synced_ ISO files with dedicated file names declared in `tracks.txt`
55
+ * Produces nice plots.
50
56
 
51
57
 
52
58
  ## Installation
@@ -86,7 +92,7 @@ For a one line output (or to suppress the progress bars) use the `--terse` flag:
86
92
  > tictacsync --terse dailies/loose/MVI_0024.MP4
87
93
  dailies/loose/MVI_0024.MP4 UTC:2024-03-12 23:07:01.4281 pulse: 27450 in chan 0
88
94
 
89
- To also produce _synced_ ISO audio files, specify `--isos` . A directory named `ISOs` will contain _for each synced video_ a set of audio files of exact same length, padded or trimmed to coincide with the video track. After re-editing and re-mixing a `remergemix` command will resync the new sound track with the video [TODO].
95
+ To also produce _synced_ ISO audio files, specify `--isos` . A directory named `ISOs` will contain _for each synced video_ a set of ISO audio files of exact same length, padded or trimmed to coincide with the video track. After re-editing and re-mixing a `remergemix` command will resync the new audio with the video and _the new sound track will be updated on your NLE timeline_, at least in Kdenlive...
90
96
 
91
97
  > tictacsync --isos dailies/structured
92
98
 
@@ -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=lvWps5XPvzubnTqisFWjdRUpxsM9YMRYMp-xQu7H6Os,27515
4
+ tictacsync/entry.py,sha256=o6ktHALspGelgNuMKrdVm-mmw4EgmQowPF6S7VQf554,11391
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=9p66EosNuX-9npr_2EXl_puuiMiNk8pAm5zN_1gpTKk,57449
9
+ tictacsync/yaltc.py,sha256=n7ubnlcFDzCpvhZNIwMZMfijZq-ROEA-hS0dbcZgPtE,78490
10
+ tictacsync-0.5a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
11
+ tictacsync-0.5a0.dist-info/METADATA,sha256=i1JscHsCMOUpU23bqmIU_17fwxGtcXfo8ekUlj6p83Q,5509
12
+ tictacsync-0.5a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
13
+ tictacsync-0.5a0.dist-info/entry_points.txt,sha256=g3tdFFrVRcrKpuyKOCLUVBMgYfV65q9kpLZUOD_XCKg,139
14
+ tictacsync-0.5a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
15
+ tictacsync-0.5a0.dist-info/RECORD,,