tictacsync 0.95a0__tar.gz → 0.96a0__tar.gz

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.

Potentially problematic release.


This version of tictacsync might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.95a0
3
+ Version: 0.96a0
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -32,7 +32,7 @@ setup(
32
32
  'multi2polywav = tictacsync.multi2polywav:main',
33
33
  ]
34
34
  },
35
- version = '0.95a',
35
+ version = '0.96a',
36
36
  description = "command for syncing audio video recordings",
37
37
  long_description_content_type='text/markdown',
38
38
  long_description = long_descr,
@@ -58,7 +58,6 @@ def print_grby(grby):
58
58
  for e in keylist:
59
59
  print(' ', e)
60
60
 
61
-
62
61
  @dataclass
63
62
  class Tracks:
64
63
  # track numbers start at 1 for first track (as needed by sox)
@@ -78,8 +77,9 @@ class Device:
78
77
  name: str
79
78
  dev_type: str # CAM or REC
80
79
  n_chan: int
81
- ttc: int
80
+ ttc: int # zero based index?
82
81
  tracks: Tracks
82
+ sampling_freq: float # fps if cam
83
83
  def __hash__(self):
84
84
  return self.UID
85
85
  def __eq__(self, other):
@@ -94,9 +94,9 @@ class Media:
94
94
 
95
95
  def media_at_path(input_structure, p):
96
96
  # return Media object for mediafile using ffprobe
97
- dev_UID, dt = get_device_ffprobe_UID(p)
97
+ dev_UID, dt, sf = get_device_ffprobe_UID(p)
98
98
  dev_name = None
99
- logger.debug('ffprobe dev_UID:%s dt:%s'%(dev_UID, dt))
99
+ logger.debug('ffprobe dev_UID:%s dt:%s sf:%s'%(dev_UID, dt,sf))
100
100
  if input_structure == 'folder_is_device':
101
101
  dev_name = p.parent.name
102
102
  if dev_UID is None:
@@ -124,7 +124,7 @@ def media_at_path(input_structure, p):
124
124
  n = sox.file_info.channels(_pathname(p)) # eg 2
125
125
  logger.debug('for file %s dev_UID established %s'%(p.name, dev_UID))
126
126
  device = Device(UID=dev_UID, folder=p.parent, name=dev_name, dev_type=dt,
127
- n_chan=n, ttc=None, tracks=None)
127
+ n_chan=n, ttc=None, sampling_freq=sf, tracks=None)
128
128
  logger.debug('for path: %s, device:%s'%(p,device))
129
129
  return Media(p, device)
130
130
 
@@ -138,7 +138,7 @@ def get_device_ffprobe_UID(file):
138
138
  Device UIDs are used later in Montage._get_concatenated_audiofile_for()
139
139
  for grouping each audio or video clip along its own timeline track.
140
140
 
141
- Returns a tuple: (UID, CAM|REC)
141
+ Returns a tuple: (UID, CAM|REC, sampling_freq)
142
142
 
143
143
  If an ffmpeg.Error occurs, returns (None, None)
144
144
  if no UID is found, but device type is identified, returns (None, CAM|REC)
@@ -155,8 +155,21 @@ def get_device_ffprobe_UID(file):
155
155
  # fall back to folder name
156
156
  logger.debug('ffprobe %s'%probe)
157
157
  streams = probe['streams']
158
+ video_streams = [st for st in streams if st['codec_type'] == 'video']
159
+ audio_streams = [st for st in streams if st['codec_type'] == 'audio']
160
+ if len(video_streams) > 1:
161
+ print('more than one video stream for %s... quitting'%file)
162
+ quit()
163
+ if len(audio_streams) != 1:
164
+ print('nbr of audio stream for %s not 1 ... quitting'%file)
165
+ quit()
158
166
  codecs = [stream['codec_type'] for stream in streams]
159
- device_type = 'CAM' if 'video' in codecs else 'REC'
167
+ # cameras have two streams: video AND audio
168
+ device_type = 'CAM' if len(video_streams) == 1 else 'REC'
169
+ if device_type == 'CAM':
170
+ sampling_freq = eval(video_streams[0]['r_frame_rate'])
171
+ else:
172
+ sampling_freq = float(audio_streams[0]['sample_rate'])
160
173
  format_dict = probe['format'] # all files should have this
161
174
  if 'tags' in format_dict:
162
175
  probe_string = pformat(format_dict['tags'])
@@ -174,7 +187,7 @@ def get_device_ffprobe_UID(file):
174
187
  if UID == 0: # empty probe_lines from Audacity ?!?
175
188
  UID = None
176
189
  logger.debug('ffprobe_UID is: %s'%UID)
177
- return UID, device_type
190
+ return UID, device_type, sampling_freq
178
191
 
179
192
  class Scanner:
180
193
  """
@@ -486,18 +499,16 @@ class Scanner:
486
499
  read track names for naming separated ISOs
487
500
  from tracks_file.
488
501
 
489
- tokens looked for: mix mixL mixR 0 ttc
502
+ tokens looked for: mix; L mix; R mix; 0 and TC
490
503
 
491
- repeting prefixes signals a stereo track
504
+ repeating "mic*" pattern signals a stereo track
492
505
  and entries will correspondingly panned into
493
- a stero mix named mixL.wav and mixR.wav
494
-
495
- xyz L # spaces are ignored |
496
- zyz R | stereo pair
497
- abc L
498
- abc R
506
+ a stero mix named Lmix.wav and Rmix.wav
499
507
 
500
- mixL
508
+ L mic # spaces are ignored |
509
+ R mic | stereo pair
510
+ L micB
511
+ R micB
501
512
 
502
513
  Returns: a Tracks instance:
503
514
  # track numbers start at 1 for first track (as needed by sox)
@@ -508,6 +519,12 @@ class Scanner:
508
519
  others: list #of all other tags: (tag, track#) tuples
509
520
  rawtrx: list # list of strings read from file
510
521
  error_msg: str # 'None' if none
522
+ e.g.: Tracks( ttc=2,
523
+ unused=[],
524
+ stereomics=[('mic', (4, 3)), ('mic2', (6, 5))],
525
+ mix=[], others=[('clics', 1)],
526
+ rawtrx=['clics', 'TC', 'L mic', 'R mic', 'L mic2;1000', 'R mic2;1000', 'Lmix', 'Rmix'],
527
+ error_msg=None, lag_values=[None, None, None, None, '1000', '1000', None, None])
511
528
  """
512
529
  def _WOspace(chaine):
513
530
  ch = [c for c in chaine if c != ' ']
@@ -517,15 +534,15 @@ class Scanner:
517
534
  return ''.join(ch)
518
535
  def _seemsStereoMic(tag):
519
536
  # is tag likely a stereo pair tag?
520
- # should start with 'mic' and end with 'L' or 'R'
521
- return tag[:3]=='mic' and tag[-1] in 'LR'
537
+ # should ends with 'mic' and starts with 'l' or 'r'
538
+ return tag[1:4]=='mic' and tag[0] in 'lr'
522
539
  file=open(tracks_file,"r")
523
540
  whole_txt = file.read()
524
541
  logger.debug('all_lines:\n%s'%whole_txt)
525
- tracks_lines = [l.split('#')[0] for l in whole_txt.splitlines()
542
+ tracks_lines_wspaces = [l.split('#')[0] for l in whole_txt.splitlines()
526
543
  if len(l) > 0 ]
527
- tracks_lines = [_WOspace(l) for l in tracks_lines if len(l) > 0 ]
528
- rawtrx = tracks_lines
544
+ tracks_lines = [_WOspace(l) for l in tracks_lines_wspaces if len(l) > 0 ]
545
+ rawtrx = [l for l in tracks_lines_wspaces if len(l) > 0 ]
529
546
  # add index with tuples, starting at 1
530
547
  logger.debug('tracks_lines whole: %s'%tracks_lines)
531
548
  def _detach_lag_value(line):
@@ -540,119 +557,120 @@ class Scanner:
540
557
  return splt
541
558
  tracks_lines, lag_values = zip(*[_detach_lag_value(l) for l
542
559
  in tracks_lines])
543
- logger.debug('tracks_lines WO lag: %s'%[tracks_lines])
544
- logger.debug('lag_values: %s'%[lag_values])
545
- tracks_lines = [(t,ix+1) for ix,t in enumerate(tracks_lines)]
546
- # first check for stereo mic pairs (could be more than one pair):
547
- spairs = [e for e in tracks_lines if _seemsStereoMic(e[0])]
548
- # spairs is stereo pairs candidates
549
- msg = 'Confusing stereo pair tags: %s'%' '.join([e[0]
550
- for e in spairs])
551
- error_output_stereo = Tracks(None,[],[],[],[],[],msg,[])
552
- if len(spairs)%2 == 1: # not pairs?, quit.
553
- return error_output_stereo
554
- logger.debug('_seemsStereoM: %s'%spairs)
560
+ lag_values = [e for e in lag_values] # from tuple to list
561
+ # logger.debug('tracks_lines WO lag: %s'%tracks_lines)
562
+ tracks_lines = [l.lower() for l in tracks_lines]
563
+ logger.debug('tracks_lines lower case: %s'%tracks_lines)
564
+ # print(lag_values)
565
+ logger.debug('lag_values: %s'%lag_values)
566
+ tagsWOl_r = [e[1:] for e in tracks_lines] # skip first letter
567
+ logger.debug('tags WO 1st letter %s'%tagsWOl_r)
568
+ # find idx of start of pairs
569
+ # ['clics', 'TC', 'Lmic', 'Rmic', 'Lmic2', 'Lmic2', 'Lmix', 'Rmix']
570
+ # ^ ^ ^
571
+ def _micOrmix(a,b):
572
+ # test if same and mic mic or mix mix
573
+ if len(a) == 0:
574
+ return False
575
+ return (a == b) and (a in 'micmix')
576
+ pair_idx_start =[i for i, same in enumerate([_micOrmix(a,b) for a,b
577
+ in zip(tagsWOl_r,tagsWOl_r[1:])]) if same]
578
+ logger.debug('pair_idx_start %s'%pair_idx_start)
579
+ def LR_OK(idx):
580
+ # in tracks_lines, check if idx start a LR pair
581
+ a = tracks_lines[idx][0]
582
+ b = tracks_lines[idx+1][0]
583
+ return a+b in ['lr', 'rl']
584
+ LR_OKs = [LR_OK(p) for p in pair_idx_start]
585
+ logger.debug('LR_OKs %s'%LR_OKs)
586
+ if not all(LR_OKs):
587
+ print('Error in %s'%tracks_file)
588
+ print('Some tracks are paired but not L and R: %s'%rawtrx)
589
+ print('quitting...')
590
+ quit()
591
+ complete_pairs_idx = pair_idx_start + [i + 1 for i in pair_idx_start]
592
+ singles = set(range(len(tracks_lines))).difference(complete_pairs_idx)
593
+ logger.debug('complete_pairs_idx %s'%complete_pairs_idx)
594
+ logger.debug('singles %s'%singles)
595
+ singles_tag = [tracks_lines[i] for i in singles]
596
+ logger.debug('singles_tag %s'%singles_tag)
597
+ n_tc_token = sum([t == 'tc' for t in singles_tag])
598
+ logger.debug('n tc tags %s'%n_tc_token)
599
+ if n_tc_token == 0:
600
+ print('Error in %s'%tracks_file)
601
+ print('with %s'%rawtrx)
602
+ print('no TC track found, quitting...')
603
+ quit()
604
+ if n_tc_token > 1:
605
+ print('Error in %s'%tracks_file)
606
+ print('with %s'%rawtrx)
607
+ print('more than one TC track, quitting...')
608
+ quit()
555
609
  output_tracks = Tracks(None,[],[],[],[],rawtrx,None,[])
556
- output_tracks.lag_values = lag_values
557
- # def _LR(p):
558
- # # p = (('mic1l', 1), ('mic1r', 2))
559
- # # check if L then R
560
- # p1, p2 = p
561
- # return p1[0][-1] == 'l' and p2[0][-1] == 'r'
562
- if spairs:
563
- even_idxes = range(0,len(spairs),2)
564
- paired = [(spairs[i], spairs[i+1]) for i in even_idxes]
565
- # eg [(('mic1L', 1), ('mic1R', 2)), (('mic2L', 3), ('mic2R', 4))]
566
- def _mic_same(p):
567
- # p = (('mic1l', 1), ('mic1r', 2))
568
- # check if mic1 == mic1
569
- p1, p2 = p
570
- return _WO_LR(p1[0]) == _WO_LR(p2[0])
571
- mic_prefix_OK = all([_mic_same(p) for p in paired])
572
- logger.debug('mic_prefix_OK: %s'%mic_prefix_OK)
573
- if not mic_prefix_OK:
574
- return error_output_stereo
575
- # mic_LR_OK = all([_LR(p) for p in paired])
576
- # logger.debug('mic_LR_OK %s'%mic_LR_OK)
577
- # if not mic_LR_OK:
578
- # return error_output_stereo
579
- def _stereo_mic_pref_chan(p):
580
- # p = (('mic1R', 1), ('mic1L', 2))
581
- # returns ('mic1', (1,2))
582
- first, second = p
583
- mic_prefix = _WO_LR(first[0])
584
- # check if first token last char
585
- if p[0][0][-1] == 'L':
586
- logger.debug('sequence %s is L+R'%[p])
587
- return (mic_prefix, (first[1], second[1]) )
588
- else:
589
- logger.debug('sequence %s is R+L'%[p])
590
- return (mic_prefix, (second[1], first[1]) )
591
- grouped_stereo_mic_channels = [_stereo_mic_pref_chan(p) for p
592
- in paired]
593
- logger.debug('grouped_stereo_mic_channels: %s'%
594
- grouped_stereo_mic_channels)
595
- output_tracks.stereomics = grouped_stereo_mic_channels
596
- [tracks_lines.remove(e) for e in spairs]
597
- logger.debug('stereo mic pairs done, continue with %s'%tracks_lines)
598
- # second, check for stereo mix down (one mixL mixR pair)
599
- def _seemsStereoMix(tag):
600
- # is tag likely a stereo pair tag?
601
- # should start with 'mic' and end with 'l' or 'r'
602
- return tag[:3]=='mix' and tag[-1] in 'lr'
603
- stereo_mix_tags = [e for e in tracks_lines if _seemsStereoMix(e[0])]
604
- logger.debug('stereo_mix_tags: %s'%stereo_mix_tags)
605
- str_msg = 'Confusing mix pair tags: %s L should appear before R'%' '.join([e[0]
606
- for e in stereo_mix_tags])
607
- # error_output = Tracks(None,[],[],[],[],msg)
608
- def _error_Track(msg):
609
- return Tracks(None,[],[],[],[],[],msg)
610
- if stereo_mix_tags:
611
- if len(stereo_mix_tags) != 2:
612
- return _error_Track(str_msg)
613
- mix_LR_OK = _LR(stereo_mix_tags)
614
- logger.debug('mix_LR_OK %s'%mix_LR_OK)
615
- if not mix_LR_OK:
616
- return _error_Track(str_msg)
617
- stereo_mix_channels = [t[1] for t in stereo_mix_tags]
618
- output_tracks.mix = stereo_mix_channels
619
- logger.debug('output_tracks.mix %s'%stereo_mix_channels)
620
- [tracks_lines.remove(e) for e in stereo_mix_tags]
621
- logger.debug('stereo mix done, will continue with %s'%tracks_lines)
622
- # third, check for a mono mix
623
- mono_mix_tags = [e for e in tracks_lines if e[0] == 'mix']
624
- if not output_tracks.mix and mono_mix_tags:
625
- logger.debug('mono_mix_tags: %s'%mono_mix_tags)
626
- if len(mono_mix_tags) != 1:
627
- return _error_Track('more than one "mix" token')
628
- output_tracks.mix = [mono_mix_tags[0][1]]
629
- [tracks_lines.remove(e) for e in mono_mix_tags]
630
- logger.debug('mono mix done, will continue with %s'%tracks_lines)
631
- # fourth, look for 'ttc'
632
- ttc_chan = [idx for tag, idx in tracks_lines if tag == 'ttc']
633
- if ttc_chan:
634
- if len(ttc_chan) > 1:
635
- return _error_Track('more than one "ttc" token')
636
- output_tracks.ttc = ttc_chan[0]
637
- tracks_lines.remove(('ttc', ttc_chan[0]))
638
- else:
639
- return _error_Track('no "ttc" token')
640
- # fifth, check for '0'
641
- logger.debug('ttc done, will continue with %s'%tracks_lines)
642
- zeroed = [idx for tag, idx in tracks_lines if tag == '0']
610
+ output_tracks.ttc = tracks_lines.index('tc') + 1 # 1st = 1
611
+ logger.debug('ttc_chan %s'%output_tracks.ttc)
612
+ zeroed = [i+1 for i, t in enumerate(tracks_lines) if t == '0']
643
613
  logger.debug('zeroed %s'%zeroed)
644
- if zeroed:
645
- output_tracks.unused = zeroed
646
- [tracks_lines.remove(('0',i)) for i in zeroed]
614
+ output_tracks.unused = zeroed
615
+ output_tracks.others = [(st, tracks_lines.index(st)+1) for st
616
+ in singles_tag if st not
617
+ in ['tc', 'monomix', '0']]
618
+ logger.debug('output_tracks.others %s'%output_tracks.others)
619
+ # check for monomix
620
+ if 'monomix' in tracks_lines:
621
+ output_tracks.mix = [tracks_lines.index('monomix')+1]
647
622
  else:
648
- output_tracks.unused = []
649
- # sixth, check for 'others'
650
- logger.debug('0s done, will continue with %s'%tracks_lines)
651
- if tracks_lines:
652
- output_tracks.others = tracks_lines
653
- else:
654
- output_tracks.others = []
655
- logger.debug('Tracks %s'%output_tracks)
623
+ output_tracks.mix = []
624
+ # check for stereo mix
625
+ def _findLR(i_first):
626
+ # returns L R indexes (+1 for sox non zero based indexing)
627
+ i_2nd = i_first + 1
628
+ a = tracks_lines[i_first][0]
629
+ b = tracks_lines[i_2nd][0]
630
+ if a == 'l':
631
+ if b == 'r':
632
+ # sequence is Lmix Rmix
633
+ return i_first+1, i_2nd+1
634
+ else:
635
+ print('Error in %s'%tracks_file)
636
+ print('with %s'%rawtrx)
637
+ print('can not find stereo mix')
638
+ quit()
639
+ elif a == 'r':
640
+ if b == 'l':
641
+ # sequence is Rmix Lmix
642
+ return i_2nd+1, i_first+1
643
+ else:
644
+ print('Error in %s'%tracks_file)
645
+ print('with %s'%rawtrx)
646
+ print('can not find stereo mix')
647
+ quit()
648
+ logger.debug('for now, output_tracks.mix %s'%output_tracks.mix)
649
+ mix_pair = [p for p in pair_idx_start if tracks_lines[p][1:] == 'mix']
650
+ if len(mix_pair) == 1:
651
+ # one stereo mix, remove it from other pairs
652
+ i = mix_pair[0]
653
+ LR_pair = _findLR(i)
654
+ logger.debug('LR_pair %s'%str(LR_pair))
655
+ pair_idx_start.remove(i)
656
+ # consistency check
657
+ if output_tracks.mix != []:
658
+ # already found a mono mix above!
659
+ print('Error in %s'%tracks_file)
660
+ print('with %s'%rawtrx)
661
+ print('found a mono mix AND a stereo mix')
662
+ quit()
663
+ output_tracks.mix = LR_pair
664
+ logger.debug('finally, output_tracks.mix %s'%str(output_tracks.mix))
665
+ logger.debug('remaining pairs %s'%pair_idx_start)
666
+ # those are stereo pairs
667
+ stereo_pairs = []
668
+ for first_in_pair in pair_idx_start:
669
+ suffix = tracks_lines[first_in_pair][1:]
670
+ stereo_pairs.append((suffix, _findLR(first_in_pair)))
671
+ logger.debug('stereo_pairs %s'%stereo_pairs)
672
+ output_tracks.stereomics = stereo_pairs
673
+ logger.debug('finished: %s'%output_tracks)
656
674
  return output_tracks
657
675
 
658
676
 
@@ -27,7 +27,8 @@ from rich.console import Console
27
27
  # from rich.text import Text
28
28
  from rich.table import Table
29
29
  from rich import print
30
- from pprint import pprint
30
+ from pprint import pprint
31
+ import numpy as np
31
32
 
32
33
  DEL_TEMP = False
33
34
 
@@ -58,6 +59,7 @@ def process_files_with_progress_bars(medias):
58
59
  return recordings, rec_with_TTC, times
59
60
 
60
61
  def process_files(medias):
62
+ # maps Media objects -> Recording objects
61
63
  recordings = []
62
64
  rec_with_TTC = []
63
65
  times = []
@@ -115,6 +117,7 @@ def process_lag_adjustement(media_object):
115
117
  media_object.path.replace(backup_name)
116
118
  logger.debug('channels %s'%channels)
117
119
  def _trim(lag, chan_file):
120
+ # for lag
118
121
  if lag == None:
119
122
  return chan_file
120
123
  else:
@@ -181,10 +184,10 @@ def main():
181
184
  logger.add(sys.stderr, level="DEBUG")
182
185
  # logger.add(sys.stdout, filter="__main__")
183
186
  # logger.add(sys.stdout, filter="yaltc")
184
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "process_lag_adjustement")
185
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "_dedrift_rec")
186
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_media_and_build_devices_UID")
187
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "_parse_track_values")
187
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_audio_for_each_videoclip")
188
+ # logger.debug(sys.stdout, filter=lambda r: r["function"] == "main")
189
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_and_write_audio")
190
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_audio_and_write_video")
188
191
  top_dir = args.path[0]
189
192
  if os.path.isfile(top_dir):
190
193
  file = top_dir
@@ -209,7 +212,35 @@ def main():
209
212
  if not all([lv == None for lv in m.device.tracks.lag_values]):
210
213
  logger.debug('%s has lag_values %s'%(
211
214
  m.path, m.device.tracks.lag_values))
212
- process_lag_adjustement(m)
215
+ process_lag_adjustement(m)
216
+ audio_REC_only = all([m.device.dev_type == 'REC' for m
217
+ in scanner.found_media_files])
218
+
219
+ if audio_REC_only:
220
+ if scanner.input_structure != 'folder_is_device':
221
+ print('For merging audio only, use a directory per device, quitting')
222
+ sys.exit(1)
223
+ print('\n\n\nOnly audio recordings are present')
224
+ print('Which device should be the reference?\n')
225
+ devices = scanner.get_devices()
226
+ maxch = len(devices)
227
+ for i, d in enumerate(devices):
228
+ print('\t%i - %s'%(i+1, d.name))
229
+ # while True:
230
+ # print('\nEnter your choice:', end='')
231
+ # choice = input()
232
+ # try:
233
+ # choice = int(choice)
234
+ # except:
235
+ # print('Please use numeric digits.')
236
+ # continue
237
+ # if choice not in list(range(1, maxch + 1)):
238
+ # print('Please enter a number in [1..%i]'%maxch)
239
+ # continue
240
+ # break
241
+ # ref_device = list(devices)[choice - 1]
242
+ choice = 3
243
+ ref_device = list(devices)[choice - 1]
213
244
  if not args.terse:
214
245
  if scanner.input_structure == 'folder_is_device':
215
246
  print('\nDetected structured folders', end='')
@@ -226,20 +257,37 @@ def main():
226
257
  len(scanner.found_media_files)), end='')
227
258
  print('from [gold1]%i[/gold1] devices:\n'%(
228
259
  scanner.get_devices_number()))
229
- for dev in scanner.get_devices():
230
- print(' [gold1]%s[/gold1] with files:'%dev.name, end = ' ')
260
+ all_devices = scanner.get_devices()
261
+ for dev in all_devices:
262
+ dt = 'Camera' if dev.dev_type == 'CAM' else 'Recorder'
263
+ print('%s [gold1]%s[/gold1] with files:'%(dt, dev.name), end = ' ')
231
264
  medias = scanner.get_media_for_device(dev)
232
- for m in medias[:-1]:
265
+ for m in medias[:-1]: # last printed out of loop
233
266
  print('[gold1]%s[/gold1]'%m.path.name, end=', ')
234
267
  print('[gold1]%s[/gold1]'%medias[-1].path.name)
268
+ a_media = medias[0]
269
+ # check if all audio recorders have same sampling freq
270
+ freqs = [dev.sampling_freq for dev in all_devices if dev.dev_type == 'REC']
271
+ same = np.isclose(np.std(freqs),0)
272
+ logger.debug('sampling freqs from audio recoders %s, same:%s'%(freqs, same))
273
+ if not same:
274
+ print('some audio recorders have different sampling frequencies:')
275
+ print(freqs)
276
+ print('resulting in undefined results: quitting...')
277
+ quit()
235
278
  print()
236
- rez = process_files(scanner.found_media_files)
237
- recordings, rec_with_TTC, times = rez
279
+ recordings, rec_with_TTC, times = \
280
+ process_files(scanner.found_media_files)
238
281
  recordings_with_time = [
239
282
  rec
240
283
  for rec in rec_with_TTC
241
284
  if rec.get_start_time()
242
285
  ]
286
+ if audio_REC_only:
287
+ for rec in recordings:
288
+ # print(rec, rec.device == ref_device)
289
+ if rec.device == ref_device:
290
+ rec.is_reference = True
243
291
  if not args.terse:
244
292
  table = Table(title="tictacsync results")
245
293
  table.add_column("Recording\n", justify="center", style='gold1')
@@ -284,7 +332,7 @@ def main():
284
332
  sys.exit(1)
285
333
  matcher = timeline.Matcher(recordings_with_time)
286
334
  matcher.scan_audio_for_each_videoclip()
287
- if not matcher.video_mergers:
335
+ if not matcher.mergers:
288
336
  if not args.terse:
289
337
  print('\nNothing to sync, bye.\n')
290
338
  sys.exit(1)
@@ -294,17 +342,18 @@ def main():
294
342
  asked_ISOs = False
295
343
  output_dir = args.o
296
344
  # if args.verbose_output or args.terse: # verbose, so no progress bars
297
- for merger in matcher.video_mergers:
298
- merger.build_audio_and_write_video(top_dir, arg_out_dir,
345
+ for merger in matcher.mergers:
346
+ merger.build_audio_and_write_merged_media(top_dir, arg_out_dir,
299
347
  OUT_struct_for_mcam,
300
- asked_ISOs,)
348
+ asked_ISOs,
349
+ audio_REC_only)
301
350
  if not args.terse:
302
351
  print("\n")
303
352
  # find out where files were wrtitten
304
- a_merger = matcher.video_mergers[0]
353
+ a_merger = matcher.mergers[0]
305
354
  print('\nWrote output in folder [gold1]%s[/gold1]'%(
306
355
  a_merger.synced_clip_dir))
307
- for merger in matcher.video_mergers:
356
+ for merger in matcher.mergers:
308
357
  print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
309
358
  for audio in merger.get_matched_audio_recs():
310
359
  print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
@@ -67,7 +67,8 @@ def _join_audio2video(audio_path: Path, video: Path):
67
67
  ffmpeg_args = (
68
68
  ffmpeg
69
69
  .input(v_n)
70
- .output(out_n, shortest=None, vcodec='copy')
70
+ .output(out_n, vcodec='copy')
71
+ # .output(out_n, shortest=None, vcodec='copy')
71
72
  .global_args('-i', a_n, "-hide_banner")
72
73
  .overwrite_output()
73
74
  .get_args()
@@ -77,7 +78,8 @@ def _join_audio2video(audio_path: Path, video: Path):
77
78
  _, out = (
78
79
  ffmpeg
79
80
  .input(v_n)
80
- .output(out_n, shortest=None, vcodec='copy')
81
+ # .output(out_n, shortest=None, vcodec='copy')
82
+ .output(out_n, vcodec='copy')
81
83
  .global_args('-i', a_n, "-hide_banner")
82
84
  .overwrite_output()
83
85
  .run(capture_stderr=True)
@@ -0,0 +1,28 @@
1
+ def is_video(f):
2
+ # True if name as video extension
3
+ name_ext = f.split('.')
4
+ if len(name_ext) != 2:
5
+ return False
6
+ name, ext = name_ext
7
+ return ext.lower() in video_extensions
8
+
9
+ # def find_ISO_vids_pairs(top):
10
+ # # top is
11
+ vids = []
12
+ ISOs = []
13
+ for (root,dirs,files) in os.walk(Path('.'), topdown=True):
14
+ for d in dirs:
15
+ if d[-4:] == '_ISO':
16
+ ISOs.append(Path(root)/d)
17
+ for f in files:
18
+ if is_video(f):
19
+ vids.append(Path(root)/f)
20
+ for pair in list(itertools.product(vids, ISOs)):
21
+ # print(pair)
22
+ matches = []
23
+ vid, ISO = pair
24
+ vidname, ext = vid.name.split('.')
25
+ if vidname == ISO.name[:-4]:
26
+ matches.append(pair)
27
+ # print(vidname, ISO.name[:-4])
28
+ # return matches
@@ -332,6 +332,10 @@ class AudioStitcherVideoMerger:
332
332
  videoclip : a Recording instance
333
333
  The video to which audio files are synced
334
334
 
335
+ ref_audio : a Recording instance
336
+ If no video is present, this is the reference audio to which others
337
+ audio files are synced
338
+
335
339
  soxed_audio : dict as {Recording : path}
336
340
  keys are elements of matched_audio_recordings and the value are
337
341
  the Pathlib path of the eventual edited audio(trimmed or padded).
@@ -383,6 +387,13 @@ class AudioStitcherVideoMerger:
383
387
  logger.debug('devices %s'%devices)
384
388
  return devices
385
389
 
390
+ def _get_secondary_audio_devices(self):
391
+ devices = set([r.device for r in self.get_matched_audio_recs()])
392
+ logger.debug('get_matched_audio_recs: %s'%
393
+ pprint.pformat(self.get_matched_audio_recs()))
394
+ logger.debug('devices %s'%devices)
395
+ return devices
396
+
386
397
  def _get_all_recordings_for(self, device):
387
398
  # return recordings for a particular device, sorted by time
388
399
  recs = [a for a in self.get_matched_audio_recs() if a.device == device]
@@ -635,11 +646,11 @@ class AudioStitcherVideoMerger:
635
646
  leftCAM/
636
647
 
637
648
  canon24fps01.MOV ━━━━┓ name of clip is name of folder
638
- canon24fps01.ISO/ <━━┛
649
+ canon24fps01_ISO/ <━━┛
639
650
  chan_1.wav
640
651
  chan_2.wav
641
652
  canon24fps02.MOV
642
- canon24fps01.ISO/
653
+ canon24fps01_ISO/
643
654
  chan_1.wav
644
655
  chan_2.wav
645
656
 
@@ -680,7 +691,7 @@ class AudioStitcherVideoMerger:
680
691
  video_stem_WO_suffix = synced_clip_file.stem
681
692
  # video_stem_WO_suffix = synced_clip_file.stem.split('.')[0]
682
693
  # OUT_DIR_DEFAULT, D2 = ISOsDIR.split('/')
683
- ISOdir = synced_clip_dir/(video_stem_WO_suffix + '.ISO')
694
+ ISOdir = synced_clip_dir/(video_stem_WO_suffix + '_ISO')
684
695
  os.makedirs(ISOdir, exist_ok=True)
685
696
  logger.debug('edited_audio_all_devices %s'%edited_audio_all_devices)
686
697
  logger.debug('ISOdir %s'%ISOdir)
@@ -713,16 +724,16 @@ class AudioStitcherVideoMerger:
713
724
  In details:
714
725
 
715
726
  If no device tracks.txt file declared a mix track (or if tracks.txt is
716
- absent), a mix is done programmatically. Two possibilities:
727
+ absent) a mix is done programmatically. Two possibilities:
717
728
 
718
729
  #1- no stereo pairs were declared: a global mono mix is returned.
719
730
  #2- one or more stereo pair mics were used and declared (micL, micR):
720
731
  a global stereo mix is returned with mono tracks panned 50-50
721
732
 
722
- If device has an associated Tracks description AND it declares a(mono or
733
+ If device has an associated Tracks description AND it declares a (mono or
723
734
  stereo) mix track, this fct returns a tempfile containing the
724
- corresponding tracks, simpley extarcting them from multichan_tmpfl
725
- (thos covers cases #3 and #4)
735
+ corresponding tracks, simply extracting them from multichan_tmpfl
736
+ (those covers cases #3 and #4)
726
737
 
727
738
  Args:
728
739
  device : device_scanner.Device dataclass
@@ -793,12 +804,13 @@ class AudioStitcherVideoMerger:
793
804
  logger.debug('%s has mix %s'%(device.name, device.tracks.mix))
794
805
  logger.debug('device %s'%device)
795
806
  # just checking coherency
796
- if 'ttc' in device.tracks.rawtrx:
797
- trx_TTC_chan = device.tracks.rawtrx.index('ttc')
798
- elif 'tc' in device.tracks.rawtrx:
807
+ if 'tc' in device.tracks.rawtrx:
799
808
  trx_TTC_chan = device.tracks.rawtrx.index('tc')
809
+ elif 'TC' in device.tracks.rawtrx:
810
+ trx_TTC_chan = device.tracks.rawtrx.index('TC')
800
811
  else:
801
812
  print('Error: no tc or ttc tag in track.txt')
813
+ print(device.tracks.rawtrx)
802
814
  sys.exit(1)
803
815
  logger.debug('TTC chan %i, dev ttc %i'%(trx_TTC_chan, device.ttc))
804
816
  if trx_TTC_chan != device.ttc:
@@ -839,7 +851,120 @@ class AudioStitcherVideoMerger:
839
851
  stereo_files = mic_stereo_files + new_stereo_files
840
852
  return _sox_mix_files(stereo_files)
841
853
 
842
- def build_audio_and_write_video(self, top_dir, output_dir,
854
+ def build_audio_and_write_merged_media(self, top_dir, output_dir,
855
+ write_multicam_structure,
856
+ asked_ISOs, audio_REC_only):
857
+ # simply bifurcates depending if ref media is video (prob 99%)
858
+ # (then audio_REC_only == False)
859
+ # or if ref media is audio (no camera detected)
860
+ # (with audio_REC_only == True)
861
+ if not audio_REC_only:
862
+ self._build_audio_and_write_video(top_dir, output_dir,
863
+ write_multicam_structure, asked_ISOs)
864
+ else:
865
+ self._build_and_write_audio(top_dir, output_dir)
866
+
867
+ def _build_and_write_audio(self, top_dir, output_dir):
868
+ """
869
+ This is called when only audio recorders were found (no cam).
870
+
871
+ top_dir: Path, directory where media were looked for
872
+
873
+ output_dir: str for optional folder specified as CLI argument, if
874
+ value is None, fall back to OUT_DIR_DEFAULT
875
+
876
+ For each audio devices found overlapping self.ref_audio: pad, trim
877
+ or stretch audio files by calling _get_concatenated_audiofile_for(), and
878
+ put them in merged_audio_files_by_device. More than one audio recorder
879
+ can be used for a shot: that's why merged_audio_files_by_device is a
880
+ list.
881
+
882
+ Returns nothing
883
+
884
+ Sets AudioStitcherVideoMerger.final_synced_file on completion to list
885
+ containing all the synced and patched audio files.
886
+ """
887
+ self.ref_audio = self.videoclip # ref audio was stored in videoclip
888
+ logger.debug('Will merge audio against %s from %s'%(self.ref_audio,
889
+ self.ref_audio.device.name))
890
+ # eg, suppose the user called tictacsync with 'mondayPM' as top folder
891
+ # to scan for dailies (and 'somefolder' for output):
892
+ if output_dir == None:
893
+ synced_clip_dir = Path(top_dir)/OUT_DIR_DEFAULT # = mondayPM/SyncedMedia
894
+ else:
895
+ synced_clip_dir = Path(output_dir)/Path(top_dir).name # = somefolder/mondayPM
896
+ self.synced_clip_dir = synced_clip_dir
897
+ os.makedirs(synced_clip_dir, exist_ok=True)
898
+ logger.debug('synced_clip_dir is: %s'%synced_clip_dir)
899
+ synced_clip_file = synced_clip_dir/\
900
+ Path(self.videoclip.new_rec_name).name
901
+ logger.debug('editing files for %s'%synced_clip_file)
902
+ self.ref_audio.final_synced_file = synced_clip_file # relative path
903
+ # Collecting edited audio by device, in (Device, tempfile) pairs:
904
+ # for a given self.ref_audio, each other audio device will have a sequence
905
+ # of matched, synced and joined audio files present in a single
906
+ # edited audio file, returned by _get_concatenated_audiofile_for
907
+ merged_audio_files_by_device = [
908
+ (d, self._get_concatenated_audiofile_for(d))
909
+ for d in self._get_secondary_audio_devices()]
910
+ # at this point, audio editing has been done in tempfiles
911
+ logger.debug('%i elements in merged_audio_files_by_device'%len(
912
+ merged_audio_files_by_device))
913
+ for d, f, in merged_audio_files_by_device:
914
+ logger.debug('device: %s'%d.name)
915
+ logger.debug('file %s of %i channels'%(f.name,
916
+ sox.file_info.channels(f.name)))
917
+ logger.debug('')
918
+ if not merged_audio_files_by_device:
919
+ # no audio file overlaps for this clip
920
+ return #############################################################
921
+ logger.debug('will output ISO files since no cam')
922
+ devices_and_monofiles = [(device, _split_channels(multi_chan_audio))
923
+ for device, multi_chan_audio
924
+ in merged_audio_files_by_device]
925
+ # add device and file from self.ref_audio
926
+ new_tuple = (self.ref_audio.device,
927
+ _split_channels(self.ref_audio.AVpath))
928
+ devices_and_monofiles.append(new_tuple)
929
+ logger.debug('devices_and_monofiles: %s'%
930
+ pprint.pformat(devices_and_monofiles))
931
+ def _trnm(dev, idx): # used in the loop just below
932
+ # generates track name for later if asked_ISOs
933
+ # idx is from 0 to nchan-1 for this device
934
+ if dev.tracks == None:
935
+ chan_name = 'chan%s'%str(idx+1).zfill(2)
936
+ else:
937
+ # sanitize
938
+ symbols = set(r"""`~!@#$%^&*()_-+={[}}|\:;"'<,>.?/""")
939
+ chan_name = dev.tracks.rawtrx[idx]
940
+ logger.debug('raw chan_name %s'%chan_name)
941
+ chan_name = chan_name.split(';')[0] # if ex: "lav bob;25"
942
+ logger.debug('chan_name WO ; lag: %s'%chan_name)
943
+ chan_name =''.join([e if e not in symbols else ''
944
+ for e in chan_name])
945
+ logger.debug('chan_name WO special chars: %s'%chan_name)
946
+ chan_name = chan_name.replace(' ', '_')
947
+ logger.debug('chan_name WO spaces: %s'%chan_name)
948
+ chan_name += '_' + dev.name # TODO: make this an option?
949
+ logger.debug('track_name %s'%chan_name)
950
+ return chan_name #####################################################
951
+ # replace device, idx pair with track name (+ device name if many)
952
+ # loop over devices than loop over tracks
953
+ names_audio_tempfiles = []
954
+ for dev, mono_tmpfiles_list in devices_and_monofiles:
955
+ for idx, monotf in enumerate(mono_tmpfiles_list):
956
+ track_name = _trnm(dev, idx)
957
+ logger.debug('track_name %s'%track_name)
958
+ if track_name[0] == '0': # muted, skip
959
+ continue
960
+ names_audio_tempfiles.append((track_name, monotf))
961
+ logger.debug('names_audio_tempfiles %s'%names_audio_tempfiles)
962
+ self._write_ISOs(names_audio_tempfiles)
963
+ logger.debug('merged_audio_files_by_device %s'%
964
+ merged_audio_files_by_device)
965
+
966
+
967
+ def _build_audio_and_write_video(self, top_dir, output_dir,
843
968
  write_multicam_structure,
844
969
  asked_ISOs):
845
970
  """
@@ -862,12 +987,12 @@ class AudioStitcherVideoMerger:
862
987
 
863
988
  Sets AudioStitcherVideoMerger.final_synced_file on completion
864
989
  """
865
- logger.debug(' fct args: top_dir: %s; output_dir: %s; write_multicam_structure: %s; asked_ISOs: %s;'%
990
+ logger.debug(' fct args: top_dir: %s; output_dir: %s; write_multicam_structure: %s; asked_ISOs: %s'%
866
991
  (top_dir, output_dir, write_multicam_structure, asked_ISOs))
867
992
  logger.debug('device for rec %s: %s'%(self.videoclip,
868
993
  self.videoclip.device))
869
- # suppose the user called tictacsync with 'mondayPM' as top folder to
870
- # scan for dailies (and 'somefolder' for output):
994
+ # eg, suppose the user called tictacsync with 'mondayPM' as top folder
995
+ # to scan for dailies (and 'somefolder' for output):
871
996
  if output_dir == None:
872
997
  synced_clip_dir = Path(top_dir)/OUT_DIR_DEFAULT # = mondayPM/SyncedMedia
873
998
  else:
@@ -882,13 +1007,18 @@ class AudioStitcherVideoMerger:
882
1007
  Path(self.videoclip.new_rec_name).name
883
1008
  logger.debug('editing files for %s'%synced_clip_file)
884
1009
  self.videoclip.final_synced_file = synced_clip_file # relative path
885
- # Collecting edited audio by device, in (Device, tempfile) pairs:
1010
+ # Collecting edited audio by device, in (Device, tempfiles) pairs:
886
1011
  # for a given self.videoclip, each audio device will have a sequence
887
1012
  # of matched, synced and joined audio files present in a single
888
1013
  # edited audio file, returned by _get_concatenated_audiofile_for
889
1014
  merged_audio_files_by_device = [
890
1015
  (d, self._get_concatenated_audiofile_for(d))
891
1016
  for d in self._get_audio_devices()]
1017
+ # at this point, audio editing has been done in multichan wav tempfiles
1018
+ logger.debug('merged_audio_files_by_device %s'%merged_audio_files_by_device)
1019
+ for d, f, in merged_audio_files_by_device:
1020
+ logger.debug('%s'%d)
1021
+ logger.debug('file %s'%f.name)
892
1022
  if len(merged_audio_files_by_device) == 0:
893
1023
  # no audio file overlaps for this clip
894
1024
  return #############################################################
@@ -958,21 +1088,30 @@ class AudioStitcherVideoMerger:
958
1088
  # generates track name for later if asked_ISOs
959
1089
  # idx is from 0 to nchan-1 for this device
960
1090
  if dev.tracks == None:
961
- tag = 'chan%s'%str(idx+1).zfill(2)
1091
+ chan_name = 'chan%s'%str(idx+1).zfill(2)
962
1092
  else:
963
- # audio_tags = [tag for tag in dev.tracks.rawtrx
964
- # if tag not in ['ttc','0','tc']]
965
- tag = dev.tracks.rawtrx[idx]
1093
+ # sanitize
1094
+ symbols = set(r"""`~!@#$%^&*()_-+={[}}|\:;"'<,>.?/""")
1095
+ chan_name = dev.tracks.rawtrx[idx]
1096
+ logger.debug('raw chan_name %s'%chan_name)
1097
+ chan_name = chan_name.split(';')[0] # if ex: "lav bob;25"
1098
+ logger.debug('chan_name WO ; lag: %s'%chan_name)
1099
+ chan_name =''.join([e if e not in symbols else ''
1100
+ for e in chan_name])
1101
+ logger.debug('chan_name WO special chars: %s'%chan_name)
1102
+ chan_name = chan_name.replace(' ', '_')
1103
+ logger.debug('chan_name WO spaces: %s'%chan_name)
966
1104
  if multiple_recorders:
967
- tag += '_' + dev.name
968
- logger.debug('tag %s'%tag)
969
- return tag #####################################################
1105
+ chan_name += '_' + dev.name # TODO: make this an option?
1106
+ logger.debug('track_name %s'%chan_name)
1107
+ return chan_name #####################################################
970
1108
  # replace device, idx pair with track name (+ device name if many)
971
1109
  # loop over devices than loop over tracks
972
1110
  names_audio_tempfiles = []
973
1111
  for dev, mono_tmpfiles_list in devices_and_monofiles:
974
1112
  for idx, monotf in enumerate(mono_tmpfiles_list):
975
1113
  track_name = _trnm(dev, idx)
1114
+ logger.debug('track_name %s'%track_name)
976
1115
  if track_name[0] == '0': # muted, skip
977
1116
  continue
978
1117
  names_audio_tempfiles.append((track_name, monotf))
@@ -1009,7 +1148,8 @@ class AudioStitcherVideoMerger:
1009
1148
  def _merge_audio_and_video(self):
1010
1149
  """
1011
1150
  Calls ffmpeg to join video in self.videoclip.AVpath to
1012
- audio in self.videoclip.synced_audio
1151
+ audio in self.videoclip.synced_audio. Audio in original video
1152
+ is dropped.
1013
1153
 
1014
1154
  On entry, videoclip.final_synced_file is a Path to an non existing
1015
1155
  file (contrarily to videoclip.synced_audio).
@@ -1036,7 +1176,8 @@ class AudioStitcherVideoMerger:
1036
1176
  ffmpeg_args = (
1037
1177
  ffmpeg
1038
1178
  .input(v_n)
1039
- .output(out_n, shortest=None, vcodec='copy',
1179
+ # .output(out_n, shortest=None, vcodec='copy',
1180
+ .output(out_n, vcodec='copy',
1040
1181
  timecode=timecode)
1041
1182
  .global_args('-i', a_n, "-hide_banner")
1042
1183
  .overwrite_output()
@@ -1047,7 +1188,8 @@ class AudioStitcherVideoMerger:
1047
1188
  _, out = (
1048
1189
  ffmpeg
1049
1190
  .input(v_n)
1050
- .output(out_n, shortest=None, vcodec='copy',
1191
+ .output(out_n, vcodec='copy',
1192
+ # .output(out_n, shortest=None, vcodec='copy',
1051
1193
  # metadata='reel_name=foo', not all container support gen MD
1052
1194
  timecode=timecode,
1053
1195
  )
@@ -1085,7 +1227,7 @@ class Matcher:
1085
1227
  recordings : list of Recording instances
1086
1228
  all the scanned recordings with valid TicTacCode, set in __init__()
1087
1229
 
1088
- video_mergers : list
1230
+ mergers : list
1089
1231
  of AudioStitcherVideoMerger Class instances, built by
1090
1232
  scan_audio_for_each_videoclip(); each video has a corresponding
1091
1233
  AudioStitcherVideoMerger object. An audio_stitch doesn't extend
@@ -1102,7 +1244,7 @@ class Matcher:
1102
1244
 
1103
1245
  """
1104
1246
  self.recordings = recordings_list
1105
- self.video_mergers = []
1247
+ self.mergers = []
1106
1248
 
1107
1249
  def _rename_all_recs(self):
1108
1250
  """
@@ -1150,12 +1292,12 @@ class Matcher:
1150
1292
  logger.debug('recording %s overlaps,'%(audio))
1151
1293
  # print(' recording [gold1]%s[/gold1] overlaps,'%(audio))
1152
1294
  if len(audio_stitch.get_matched_audio_recs()) > 0:
1153
- self.video_mergers.append(audio_stitch)
1295
+ self.mergers.append(audio_stitch)
1154
1296
  else:
1155
1297
  logger.debug('\n nothing\n')
1156
1298
  print('No overlap found for %s'%videoclip.AVpath.name)
1157
1299
  del audio_stitch
1158
- logger.debug('%i video_mergers created'%len(self.video_mergers))
1300
+ logger.debug('%i mergers created'%len(self.mergers))
1159
1301
 
1160
1302
  def _does_overlap(self, videoclip, audio_rec):
1161
1303
  A1, A2 = audio_rec.get_start_time(), audio_rec.get_end_time()
@@ -1194,7 +1336,7 @@ Moves clusters at the timelineoffset
1194
1336
  Returns nothing, changes are done in the video files metadata
1195
1337
  (each referenced by Recording.final_synced_file)
1196
1338
  """
1197
- vids = [m.videoclip for m in self.video_mergers]
1339
+ vids = [m.videoclip for m in self.mergers]
1198
1340
  logger.debug('vids %s'%vids)
1199
1341
  if len(vids) == 1:
1200
1342
  logger.debug('just one take, no gap to shrink')
@@ -35,7 +35,6 @@ TEENSY_MAX_LAG = 1.01*128/44100 # sec, duration of a default length audio block
35
35
 
36
36
  CACHING = True
37
37
  DEL_TEMP = False
38
- DB_RMS_SILENCE_SOX = -58
39
38
  MAXDRIFT = 15e-3 # in sec, for end of clip
40
39
 
41
40
 
@@ -566,25 +565,24 @@ class Recording:
566
565
  implicitly True for each video recordings (but not set)
567
566
 
568
567
  device_relative_speed : float
569
- Set by
570
568
  the ratio of the recording device clock speed relative to the
571
569
  video recorder clock device, in order to correct clock drift with
572
570
  pysox tempo transform. If value < 1.0 then the recording is
573
571
  slower than video recorder. Updated by each
574
- AudioStitcherVideoMerger instance so the value can change
572
+ MediaMerger instance so the value can change
575
573
  depending on the video recording . A mean is calculated for all
576
574
  recordings of the same device in
577
- AudioStitcherVideoMerger._get_concatenated_audiofile_for()
575
+ MediaMerger._get_concatenated_audiofile_for()
578
576
 
579
577
  time_position : float
580
578
  The time (in seconds) at which the recording starts relative to the
581
- video recording. Updated by each AudioStitcherVideoMerger
579
+ video recording. Updated by each MediaMerger
582
580
  instance so the value can change depending on the video
583
581
  recording (a video or main sound).
584
582
 
585
583
  final_synced_file : a pathlib.Path
586
584
  contains the path of the merged video file after the call to
587
- AudioStitcher.build_audio_and_write_video if the Recording is a
585
+ AudioStitcher.build_audio_and_write_merged_media if the Recording is a
588
586
  video recording, relative to the working directory
589
587
 
590
588
  synced_audio : pathlib.Path
@@ -792,8 +790,8 @@ Set by
792
790
  def get_corrected_duration(self):
793
791
  """
794
792
  uses device_relative_speed to compute corrected duration. Updated by
795
- each AudioStitcherVideoMerger object in
796
- AudioStitcherVideoMerger._get_concatenated_audiofile_for()
793
+ each MediaMerger object in
794
+ MediaMerger._get_concatenated_audiofile_for()
797
795
  """
798
796
  return self.get_duration()/self.device_relative_speed
799
797
 
@@ -928,7 +926,7 @@ Set by
928
926
  def set_time_position_to(self, video_clip):
929
927
  """
930
928
  Sets self.time_position, the time (in seconds) at which the recording
931
- starts relative to the video recording. Updated by each AudioStitcherVideoMerger
929
+ starts relative to the video recording. Updated by each MediaMerger
932
930
  instance so the value can change depending on the video
933
931
  recording (a video or main sound).
934
932
 
@@ -1074,13 +1072,12 @@ Set by
1074
1072
  return ratio/ratio_ref
1075
1073
 
1076
1074
  def get_samplerate(self):
1077
- # return int samplerate (nominal)
1075
+ # returns int samplerate (nominal)
1078
1076
  string = self._ffprobe_audio_stream()['sample_rate']
1079
1077
  logger.debug('ffprobe samplerate: %s'%string)
1080
- return eval(string) # eg eval(24000/1001)
1078
+ return eval(string)
1081
1079
 
1082
1080
  def get_framerate(self):
1083
- # return int samplerate (nominal)
1084
1081
  string = self._ffprobe_video_stream()['avg_frame_rate']
1085
1082
  return eval(string) # eg eval(24000/1001)
1086
1083
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.95a0
3
+ Version: 0.96a0
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -6,6 +6,7 @@ tictacsync/device_scanner.py
6
6
  tictacsync/entry.py
7
7
  tictacsync/multi2polywav.py
8
8
  tictacsync/remergemix.py
9
+ tictacsync/remrgmx.py
9
10
  tictacsync/timeline.py
10
11
  tictacsync/yaltc.py
11
12
  tictacsync.egg-info/PKG-INFO
File without changes
File without changes
File without changes