tictacsync 0.98a0__py3-none-any.whl → 1.4.0b0__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.

Potentially problematic release.


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

tictacsync/yaltc.py CHANGED
@@ -15,6 +15,7 @@ logging.config.dictConfig({
15
15
  'disable_existing_loggers': True,
16
16
  }) # for sox "output file already exists and will be overwritten on build"
17
17
  from datetime import datetime, timezone, timedelta
18
+ from pprint import pformat
18
19
  from collections import deque
19
20
  from loguru import logger
20
21
  from skimage.morphology import closing, erosion, remove_small_objects
@@ -33,6 +34,10 @@ TEENSY_MAX_LAG = 1.01*128/44100 # sec, duration of a default length audio block
33
34
 
34
35
  # see extract_seems_TicTacCode() for duration criterion values
35
36
 
37
+ TRACKSFILE = 'tracks.txt'
38
+ SILENT_TRACK_TOKENS = '-0n'
39
+
40
+
36
41
  CACHING = True
37
42
  DEL_TEMP = False
38
43
  MAXDRIFT = 15e-3 # in sec, for end of clip
@@ -68,6 +73,7 @@ BPF_LOW_FRQ, BPF_HIGH_FRQ = (0.5*F1, 2*F2)
68
73
 
69
74
  # utility for accessing pathnames
70
75
  def _pathname(tempfile_or_path):
76
+ # always returns a str
71
77
  if isinstance(tempfile_or_path, type('')):
72
78
  return tempfile_or_path ################################################
73
79
  if isinstance(tempfile_or_path, Path):
@@ -135,6 +141,34 @@ def to_precision(x,p):
135
141
 
136
142
  return "".join(out)
137
143
 
144
+ def read_audio_data_from_file(file, n_channels):
145
+ """
146
+ reads file and returns a numpy.array of shape (N1 channels, N2 samples)
147
+ where N1 >= 2 (minimaly solo track + TC)
148
+ """
149
+ dryrun = (ffmpeg
150
+ .input(_pathname(file))
151
+ .output('pipe:', format='s16le', acodec='pcm_s16le')
152
+ .get_args())
153
+ dryrun = ' '.join(dryrun)
154
+ logger.debug('using ffmpeg-python built args to pipe audio stream into numpy array:\nffmpeg %s'%dryrun)
155
+ try:
156
+ out, _ = (ffmpeg
157
+ # .input(str(path), ss=time_where, t=chunk_length)
158
+ # .input(str(self.AVpath))
159
+ .input(_pathname(file))
160
+ .output('pipe:', format='s16le', acodec='pcm_s16le')
161
+ .global_args("-loglevel", "quiet")
162
+ .global_args("-nostats")
163
+ .global_args("-hide_banner")
164
+ .run(capture_stdout=True))
165
+ data = np.frombuffer(out, np.int16)
166
+ except ffmpeg.Error as e:
167
+ print('error',e.stderr)
168
+ # transform 1D interleaved channels to [chan1, chan2, chanN]
169
+ return data.reshape(int(len(data)/n_channels),n_channels).T
170
+
171
+
138
172
  class Decoder:
139
173
  """
140
174
  Object encapsulating DSP processes to demodulate TicTacCode track from audio
@@ -188,7 +222,6 @@ class Decoder:
188
222
  self.do_plots = do_plots
189
223
  self.clear_decoder()
190
224
 
191
-
192
225
  def clear_decoder(self):
193
226
  self.sound_data_extract = None
194
227
  self.pulse_detection_level = None
@@ -524,6 +557,7 @@ class Decoder:
524
557
  return int(round(freq_in_hertz))
525
558
 
526
559
 
560
+
527
561
  class Recording:
528
562
  """
529
563
  Wrapper for file objects, ffmpeg read operations and fprobe functions
@@ -561,7 +595,7 @@ class Recording:
561
595
  sync_position : int
562
596
  position of first detected syn pulse
563
597
 
564
- is_reference : bool (True for ref rec only)
598
+ is_audio_reference : bool (True for ref rec only)
565
599
  in multi recorders set-ups, user decides if a sound-only recording
566
600
  is the time reference for all other audio recordings. By
567
601
  default any video recording is the time reference for other audio,
@@ -647,7 +681,7 @@ class Recording:
647
681
  self.decoder = None
648
682
  self.probe = None
649
683
  self.TicTacCode_channel = None
650
- self.is_reference = False
684
+ self.is_audio_reference = False
651
685
  self.device_relative_speed = 1.0
652
686
  # self.valid_sound = None
653
687
  self.final_synced_file = None
@@ -688,35 +722,16 @@ class Recording:
688
722
  return
689
723
  logger.debug('ffprobe found: %s'%self.probe)
690
724
  logger.debug('n audio chan: %i'%self.get_audio_channels_nbr())
691
- self._read_audio_data()
692
-
693
- def _read_audio_data(self):
694
- # sets Recording.audio_data
695
- dryrun = (ffmpeg
696
- .input(str(self.AVpath))
697
- .output('pipe:', format='s16le', acodec='pcm_s16le')
698
- .get_args())
699
- dryrun = ' '.join(dryrun)
700
- logger.debug('using ffmpeg-python built args to pipe audio stream into numpy array:\nffmpeg %s'%dryrun)
701
- try:
702
- out, _ = (ffmpeg
703
- # .input(str(path), ss=time_where, t=chunk_length)
704
- .input(str(self.AVpath))
705
- .output('pipe:', format='s16le', acodec='pcm_s16le')
706
- .global_args("-loglevel", "quiet")
707
- .global_args("-nostats")
708
- .global_args("-hide_banner")
709
- .run(capture_stdout=True))
710
- data = np.frombuffer(out, np.int16)
711
- except ffmpeg.Error as e:
712
- print('error',e.stderr)
713
- n_chan = self.get_audio_channels_nbr()
714
- if n_chan == 1 and not self.is_video():
715
- logger.error('file is sound mono')
725
+ # self._read_audio_data()
726
+ N = self.get_audio_channels_nbr()
727
+ data = read_audio_data_from_file(self.AVpath, n_channels=N)
728
+ if len(data) == 1 and not self.is_video():
729
+ print(f'file sound is mono ({self.AVpath}), bye.')
730
+ sys.exit(0)
716
731
  if np.isclose(np.std(data), 0, rtol=1e-2):
717
- logger.error("ffmpeg can't extract audio from %s"%self.AVpath)
718
- # from 1D interleaved channels to [chan1, chan2, chanN]
719
- self.audio_data = data.reshape(int(len(data)/n_chan),n_chan).T
732
+ logger.error("ffmpeg can't extract audio from %s"%file)
733
+ sys.exit(0)
734
+ self.audio_data = data
720
735
  logger.debug('Recording.audio_data: %s of shape %s'%(self.audio_data,
721
736
  self.audio_data.shape))
722
737
 
@@ -1029,6 +1044,319 @@ class Recording:
1029
1044
  # self.valid_sound = self.AVpath
1030
1045
  return start_UTC
1031
1046
 
1047
+ def _find_timed_tracks_(self, tracks_file) -> device_scanner.Tracks:
1048
+ """
1049
+ Look for any ISO 8601 timestamp e.g.: 2007-04-05T14:30Z
1050
+ and choose the right chunk according to Recording.start_time
1051
+ """
1052
+ file=open(tracks_file,"r")
1053
+ whole_txt = file.read()
1054
+ tracks_lines = []
1055
+ for l in whole_txt.splitlines():
1056
+ after_sharp = l.split('#')[0]
1057
+ if len(after_sharp) > 0:
1058
+ tracks_lines.append(after_sharp)
1059
+ logger.debug('file %s filtered lines:\n%s'%(tracks_file,
1060
+ pformat(tracks_lines)))
1061
+ def _seems_timestamp(line):
1062
+ # will validate format later with datetime.fromisoformat()
1063
+ m = re.match(r'ts=(.*)', line)
1064
+ # m = re.match(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z', line)
1065
+ if m != None:
1066
+ return m.groups()[0]
1067
+ else:
1068
+ return None
1069
+ chunks = []
1070
+ new_chunk = []
1071
+ timestamps_str = []
1072
+ for line in tracks_lines:
1073
+ timestamp_candidate = _seems_timestamp(line)
1074
+ if timestamp_candidate != None:
1075
+ logger.debug(f'timestamp: {line}')
1076
+ timestamps_str.append(timestamp_candidate)
1077
+ chunks.append(new_chunk)
1078
+ new_chunk = []
1079
+ else:
1080
+ new_chunk.append(line)
1081
+ chunks.append(new_chunk)
1082
+ logger.debug(f'chunks {chunks}, timestamps_str {timestamps_str}')
1083
+ str_frmt = '%Y-%m-%dT%H:%MZ'
1084
+ # from strings to datetime instances
1085
+ timestamps = []
1086
+ for dtstr in timestamps_str:
1087
+ try:
1088
+ ts = datetime.fromisoformat(dtstr)
1089
+ except:
1090
+ print(f'Error: in file {tracks_file},\ntimestamp {dtstr} is ill formatted, bye.')
1091
+ sys.exit(0)
1092
+ timestamps.append(ts)
1093
+ # timestamps = [datetime.strptime(dtstr, str_frmt, tzinfo=timezone.utc)
1094
+ # for dtstr in timestamps]
1095
+ logger.debug(f'datetime timestamps {timestamps}')
1096
+ # input validations, check order:
1097
+ if sorted(timestamps) != timestamps:
1098
+ print(f'Error in {tracks_file}\nSome timestamps are not in ascending order:\n')
1099
+ multi_lines = "\n".join(tracks_lines)
1100
+ print(f'{multi_lines}, Bye.')
1101
+ sys.exit(0)
1102
+ time_ranges = [t2-t1 for t1,t2 in zip(timestamps, timestamps[1:])]
1103
+ logger.debug(f'time_ranges {time_ranges} ')
1104
+ # check times between timestamps are realistic
1105
+ if timedelta(0) in time_ranges:
1106
+ print(f'Error in {tracks_file}\nSome timestamps are repeating:\n')
1107
+ multi_lines = "\n".join(tracks_lines)
1108
+ print(f'{multi_lines}, Bye.')
1109
+ sys.exit(0)
1110
+ if any([ dt < timedelta(minutes=2) for dt in time_ranges]):
1111
+ print(f'Warning in {tracks_file}\nSome timestamps are spaced by less than 2 minutes:\n')
1112
+ print("\n".join(tracks_lines))
1113
+ print(f'If this is an error, correct and rerun. For now will continue...')
1114
+ if any([ dt > timedelta(days=1) for dt in time_ranges]):
1115
+ print(f'Warning in {tracks_file}\nSome timestamps are spaced by more than 24 hrs:\n')
1116
+ print("\n".join(tracks_lines))
1117
+ print(f'If this is an error, correct and rerun. For now will continue...')
1118
+ # add 'infinite in future' to time stamps for time matching
1119
+ future = datetime.max
1120
+ future = future.replace(tzinfo=timezone.utc)
1121
+ timestamps.append(future)
1122
+ # zip it with chunks
1123
+ timed_chunks = list(zip(chunks,timestamps))
1124
+ logger.debug(f'timed_chunks\n{pformat(timed_chunks)} ')
1125
+ logger.debug(f'will find match with {self.start_time}')
1126
+ # for tch in timed_chunks:
1127
+ # print(tch[1], self.start_time)
1128
+ # print(tch[1] > self.start_time)
1129
+ idx = 0
1130
+ # while timed_chunks[idx][1] < self.start_time:
1131
+ # logger.debug(f'does {timed_chunks[idx][1]} < {self.start_time} ?')
1132
+ # idx += 1
1133
+ max_idx = len(timed_chunks) - 1
1134
+ while True:
1135
+ if timed_chunks[idx][1] > self.start_time or idx == max_idx:
1136
+ break
1137
+ idx += 1
1138
+ chunk_idx = idx
1139
+ logger.debug(f'chunk_idx {chunk_idx}')
1140
+ right_chunk = chunks[chunk_idx]
1141
+ logger.debug(f'found right chunk {right_chunk}')
1142
+ tracks_instance = self._parse_trx_lines(right_chunk, tracks_file)
1143
+ return tracks_instance
1144
+
1145
+ def _parse_trx_lines(self, tracks_lines_with_spaces, tracks_file):
1146
+ """
1147
+ read track names for naming separated ISOs
1148
+ from tracks_file.
1149
+
1150
+ tokens looked for: mix; mix L; mix R; 0 and TC
1151
+
1152
+ repeating "mic*" pattern signals a stereo track
1153
+ and entries will correspondingly panned into
1154
+ a stero mix named mixL.wav and mixL.wav
1155
+
1156
+ mic L # spaces are ignored |
1157
+ mic R | stereo pair
1158
+ micB L
1159
+ micB R
1160
+
1161
+ Returns: a Tracks instance:
1162
+ # track numbers start at 1 for first track (as needed by sox)
1163
+ ttc: int # track number of TicTacCode signal
1164
+ unused: list # of unused tracks
1165
+ stereomics: list # of stereo mics track tuples (Lchan#, Rchan#)
1166
+ mix: list # of mixed tracks, if a pair, order is L than R
1167
+ others: list #of all other tags: (tag, track#) tuples
1168
+ rawtrx: list # list of strings read from file
1169
+ error_msg: str # 'None' if none
1170
+ e.g.: Tracks( ttc=2,
1171
+ unused=[],
1172
+ stereomics=[('mic', (4, 3)), ('mic2', (6, 5))],
1173
+ mix=[], others=[('clics', 1)],
1174
+ rawtrx=['clics', 'TC', 'micL', 'micR', 'mic2L;1000', 'mic2R;1000', 'mixL', 'mixR'],
1175
+ error_msg=None, lag_values=[None, None, None, None, '1000', '1000', None, None])
1176
+ """
1177
+ def _WOspace(chaine):
1178
+ ch = [c for c in chaine if c != ' ']
1179
+ return ''.join(ch)
1180
+ tracks_lines = [_WOspace(l) for l in tracks_lines_with_spaces if len(l) > 0 ]
1181
+ rawtrx = [l for l in tracks_lines_with_spaces if len(l) > 0 ]
1182
+ # add index with tuples, starting at 1
1183
+ logger.debug('tracks_lines whole: %s'%tracks_lines)
1184
+ def _detach_lag_value(line):
1185
+ # look for ";number" ending any line, returns a two-list
1186
+ splt = line.split(';')
1187
+ if len(splt) == 1:
1188
+ splt += [None]
1189
+ if len(splt) != 2:
1190
+ # error
1191
+ print('\nText error in %s, line %s has too many ";"'%(
1192
+ tracks_file, line))
1193
+ return splt
1194
+ tracks_lines, lag_values = zip(*[_detach_lag_value(l) for l
1195
+ in tracks_lines])
1196
+ lag_values = [e for e in lag_values] # from tuple to list
1197
+ # logger.debug('tracks_lines WO lag: %s'%tracks_lines)
1198
+ tracks_lines = [l.lower() for l in tracks_lines]
1199
+ logger.debug('tracks_lines lower case: %s'%tracks_lines)
1200
+ # print(lag_values)
1201
+ logger.debug('lag_values: %s'%lag_values)
1202
+ tagsWOl_r = [e[:-1] for e in tracks_lines] # skip last letter
1203
+ logger.debug('tags WO LR letter %s'%tagsWOl_r)
1204
+ # find idx of start of pairs
1205
+ # ['clics', 'TC', 'micL', 'micR', 'mic2L', 'mic2R', 'mixL', 'mixR']
1206
+ def _micOrmix(a,b):
1207
+ # test if same and mic mic or mix mix
1208
+ if len(a) == 0:
1209
+ return False
1210
+ return (a == b) and (a in 'micmix')
1211
+ pair_idx_start =[i for i, same in enumerate([_micOrmix(a,b) for a,b
1212
+ in zip(tagsWOl_r,tagsWOl_r[1:])]) if same]
1213
+ logger.debug('pair_idx_start %s'%pair_idx_start)
1214
+ def LR_OK(idx):
1215
+ # in tracks_lines, check if idx ends a LR pair
1216
+ # delays, if any, have been removed
1217
+ a = tracks_lines[idx][-1]
1218
+ b = tracks_lines[idx+1][-1]
1219
+ return a+b in ['lr', 'rl']
1220
+ LR_OKs = [LR_OK(p) for p in pair_idx_start]
1221
+ logger.debug('LR_OKs %s'%LR_OKs)
1222
+ if not all(LR_OKs):
1223
+ print('\nError in %s'%tracks_file)
1224
+ print('Some tracks are paired but not L and R: %s'%rawtrx)
1225
+ print('quitting...')
1226
+ quit()
1227
+ complete_pairs_idx = pair_idx_start + [i + 1 for i in pair_idx_start]
1228
+ singles = set(range(len(tracks_lines))).difference(complete_pairs_idx)
1229
+ logger.debug('complete_pairs_idx %s'%complete_pairs_idx)
1230
+ logger.debug('singles %s'%singles)
1231
+ singles_tag = [tracks_lines[i] for i in singles]
1232
+ logger.debug('singles_tag %s'%singles_tag)
1233
+ n_tc_token = sum([t == 'tc' for t in singles_tag])
1234
+ logger.debug('n tc tags %s'%n_tc_token)
1235
+ if n_tc_token == 0:
1236
+ print('\nError in %s'%tracks_file)
1237
+ print('with %s'%rawtrx)
1238
+ print('no TC track found, quitting...')
1239
+ quit()
1240
+ if n_tc_token > 1:
1241
+ print('\nError in %s'%tracks_file)
1242
+ print('with %s'%rawtrx)
1243
+ print('more than one TC track, quitting...')
1244
+ quit()
1245
+ output_tracks = device_scanner.Tracks(None,[],[],[],[],rawtrx,None,[])
1246
+ output_tracks.ttc = tracks_lines.index('tc') + 1 # 1st = 1
1247
+ logger.debug('ttc_chan %s'%output_tracks.ttc)
1248
+ zeroed = [i+1 for i, t in enumerate(tracks_lines) if t == '0']
1249
+ logger.debug('zeroed %s'%zeroed)
1250
+ output_tracks.unused = zeroed
1251
+ output_tracks.others = [(st, tracks_lines.index(st)+1) for st
1252
+ in singles_tag if st not
1253
+ in ['tc', 'monomix', '0']]
1254
+ logger.debug('output_tracks.others %s'%output_tracks.others)
1255
+ # check for monomix
1256
+ if 'monomix' in tracks_lines:
1257
+ output_tracks.mix = [tracks_lines.index('monomix')+1]
1258
+ else:
1259
+ output_tracks.mix = []
1260
+ # check for stereo mix
1261
+ def _findLR(i_first):
1262
+ # returns L R indexes (+1 for sox non zero based indexing)
1263
+ i_2nd = i_first + 1
1264
+ a = tracks_lines[i_first][-1] # l|r at end
1265
+ b = tracks_lines[i_2nd][-1] # l|r at end
1266
+ if a == 'l':
1267
+ if b == 'r':
1268
+ # sequence is mixL mixR
1269
+ return i_first+1, i_2nd+1
1270
+ else:
1271
+ print('\nError in %s'%tracks_file)
1272
+ print('with %s'%rawtrx)
1273
+ print('can not find stereo mix')
1274
+ quit()
1275
+ elif a == 'r':
1276
+ if b == 'l':
1277
+ # sequence is mixR mixL
1278
+ return i_2nd+1, i_first+1
1279
+ else:
1280
+ print('\nError in %s'%tracks_file)
1281
+ print('with %s'%rawtrx)
1282
+ print('can not find stereo mix')
1283
+ quit()
1284
+ logger.debug('for now, output_tracks.mix %s'%output_tracks.mix)
1285
+ mix_pair = [p for p in pair_idx_start if tracks_lines[p][1:] == 'mix']
1286
+ if len(mix_pair) == 1:
1287
+ # one stereo mix, remove it from other pairs
1288
+ i = mix_pair[0]
1289
+ LR_pair = _findLR(i)
1290
+ logger.debug('LR_pair %s'%str(LR_pair))
1291
+ pair_idx_start.remove(i)
1292
+ # consistency check
1293
+ if output_tracks.mix != []:
1294
+ # already found a mono mix above!
1295
+ print('\nError in %s'%tracks_file)
1296
+ print('with %s'%rawtrx)
1297
+ print('found a mono mix AND a stereo mix')
1298
+ quit()
1299
+ output_tracks.mix = LR_pair
1300
+ logger.debug('finally, output_tracks.mix %s'%str(output_tracks.mix))
1301
+ logger.debug('remaining pairs %s'%pair_idx_start)
1302
+ # those are stereo pairs
1303
+ stereo_pairs = []
1304
+ for first_in_pair in pair_idx_start:
1305
+ suffix = tracks_lines[first_in_pair][:-1]
1306
+ stereo_pairs.append((suffix, _findLR(first_in_pair)))
1307
+ logger.debug('stereo_pairs %s'%stereo_pairs)
1308
+ output_tracks.stereomics = stereo_pairs
1309
+ logger.debug('finished: %s'%output_tracks)
1310
+ return output_tracks
1311
+
1312
+ def load_track_info(self):
1313
+ """
1314
+ If audio rec, look for eventual track names in TRACKSFILE file, stored inside the
1315
+ recorder folder alongside the audio files. If there, store a Tracks
1316
+ object into Recording.device.tracks .
1317
+ """
1318
+ if self.is_video():
1319
+ return
1320
+ source_audio_folder = self.device.folder
1321
+ tracks_file = source_audio_folder/TRACKSFILE
1322
+ track_names = False
1323
+ # a_recording = [m for m in self.found_media_files
1324
+ # if m.device == device][0]
1325
+ # logger.debug('a_recording for device %s : %s'%(device, a_recording))
1326
+ nchan = self.get_audio_channels_nbr()
1327
+ # nchan = sox.file_info.channels(str(a_recording.path))
1328
+ if os.path.isfile(tracks_file):
1329
+ logger.debug('found file: %s'%(TRACKSFILE))
1330
+ tracks = self._find_timed_tracks_(tracks_file)
1331
+ if tracks.error_msg:
1332
+ print('\nError parsing [gold1]%s[/gold1] file: %s, quitting.\n'%
1333
+ (tracks_file, tracks.error_msg))
1334
+ sys.exit(1)
1335
+ logger.debug('parsed tracks %s'%tracks)
1336
+ ntracks = 2*len(tracks.stereomics)
1337
+ ntracks += len(tracks.mix)
1338
+ ntracks += len(tracks.unused)
1339
+ ntracks += len(tracks.others)
1340
+ ntracks += 1 # for ttc track
1341
+ logger.debug(' n chan: %i n tracks file: %i'%(nchan, ntracks))
1342
+ if ntracks != nchan:
1343
+ print('\nError parsing %s content'%tracks_file)
1344
+ print('incoherent number of tracks, %i vs %i quitting\n'%
1345
+ (nchan, ntracks))
1346
+ sys.exit(1)
1347
+ err_msg = tracks.error_msg
1348
+ if err_msg != None:
1349
+ print('\nError, quitting: in file %s, %s'%(tracks_file, err_msg))
1350
+ raise Exception
1351
+ else:
1352
+ self.device.tracks = tracks
1353
+ logger.debug('for rec %s'%self)
1354
+ logger.debug('tracks object: %s'%self.device.tracks)
1355
+ return
1356
+ else:
1357
+ logger.debug('no tracks.txt file found')
1358
+ return None
1359
+
1032
1360
  def _ffprobe_audio_stream(self):
1033
1361
  streams = self.probe['streams']
1034
1362
  audio_streams = [
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.98a0
4
- Summary: command for syncing audio video recordings
3
+ Version: 1.4.0b0
4
+ Summary: commands for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
7
7
  Author-email: lutzrayblog@mac.com
8
- Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Environment :: Console
10
10
  Classifier: Intended Audience :: End Users/Desktop
11
11
  Classifier: License :: OSI Approved :: MIT License
@@ -26,14 +26,13 @@ Requires-Dist: loguru >=0.6.0
26
26
  Requires-Dist: matplotlib >=3.7.1
27
27
  Requires-Dist: numpy >=1.24.3
28
28
  Requires-Dist: rich >=10.12.0
29
- Requires-Dist: lmfit
30
29
  Requires-Dist: scikit-image
31
30
  Requires-Dist: scipy >=1.10.1
32
31
  Requires-Dist: platformdirs
33
32
 
34
33
  # tictacsync
35
34
 
36
- ## Warning: this is at pre-alpha stage
35
+ ## Warning: this is at beta stage
37
36
 
38
37
  Unfinished sloppy code ahead, but should run without errors. Some functionalities are still missing. Don't run the code without parental supervision. Suggestions and enquiries are welcome via the [lists hosted on sourcehut](https://sr.ht/~proflutz/TicTacSync/lists).
39
38
 
@@ -68,7 +67,7 @@ Then pip install the syncing program:
68
67
  This should install python dependencies _and_ the `tictacsync` command.
69
68
  ## Usage
70
69
 
71
- Download multiple sample files [here](https://nuage.lutz.quebec/s/NpjzXH5R7DrQEWS/download/dailies.zip) (625 MB, sorry) unzip and run:
70
+ Download multiple sample files [here](https://nuage.lutz.quebec/s/4jw4xgqysLPS8EQ/download/dailies1_3.zip) (700+ MB, sorry) unzip and run:
72
71
 
73
72
  > tictacsync dailies/loose
74
73
  The program `tictacsync` will recursively scan the directory given as argument, find all audio that coincide with any video and merge them into a subfolder named `SyncedMedia`. When the argument is an unique media file (not a directory), no syncing will occur but the decoded starting time will be printed to stdout:
@@ -0,0 +1,24 @@
1
+ tictacsync/LTCcheck.py,sha256=IEfpB_ZajWuRTWtqji0H-B2g7GQvWmGVjfT0Icumv7o,15704
2
+ tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ tictacsync/device_scanner.py,sha256=YxA3_0O1ZPE1ZuD-OgD-dWhtTWE-LamDhqXXyLo3IMw,26132
4
+ tictacsync/entry.py,sha256=pcGuS4_o0o5dREpcccx1_X3w14PeHdQi5z1Ikzmhpwk,16198
5
+ tictacsync/load_fieldr_reaper.py,sha256=tat0tTZpOyshzVvlqyu1r0u3cpf6TNgK-2C6xJk3_Fw,14708
6
+ tictacsync/mamconf.py,sha256=nfXTwabx-tJmBcpnDR4CRkFe9W4fudzfnbq_nHUg0qE,6424
7
+ tictacsync/mamdav.py,sha256=2we8tfIbJBtDMQdpZZVlCQ9hCQRMbKmV2aU3dDEUf2k,27457
8
+ tictacsync/mamreap.py,sha256=ej7Ap8nbVBCkfah2j5hrE7QBWuqL6Zm-OEsQpNK8mYg,21085
9
+ tictacsync/mamsync.py,sha256=mpoHUAuJWiZ1JfVCECiiSLH_HNdXNV1Z_VlUlJBlPcM,14565
10
+ tictacsync/multi2polywav.py,sha256=qJJhjwIgP1BCTpi2e0wfR95XlgZ2-EIqmefVh-jUBPc,7438
11
+ tictacsync/new-sound-resolve.py,sha256=si7NC_VE_2rNV9jR_Nz_YxK1c92JwzWw5BIWdvLdvAQ,18994
12
+ tictacsync/newmix.py,sha256=-zDxr6_O-rjyo1QfgktvHgwqy_un07eFI4zKi8nygIQ,19188
13
+ tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
14
+ tictacsync/remrgmx.py,sha256=FxaAo5qqynpj6O56ekQGD31YP6X2g-kEdwVpHSCoh4Q,4265
15
+ tictacsync/splitmix.py,sha256=dpTQYXXCYoertGOXPnMVCrh6xYh390YqmvOHK9hDg90,3148
16
+ tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
17
+ tictacsync/timeline.py,sha256=ykmB8EfnprQZoEHXRYzriASNWZ7bHfkmQ2-TR6gxZ6Y,75985
18
+ tictacsync/yaltc.py,sha256=xrgL7qokP1A7B_VF4W_BZcC7q9APSmYpmtWH8_t3VWc,68003
19
+ tictacsync-1.4.0b0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
20
+ tictacsync-1.4.0b0.dist-info/METADATA,sha256=HMz1ALEb9soZkeMFu0XWdfSTPMhGq_0zVLDiNjZfu1E,5668
21
+ tictacsync-1.4.0b0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
22
+ tictacsync-1.4.0b0.dist-info/entry_points.txt,sha256=1ymCpiosJdolsqz4yPx8aDbBBePqVt4Zz2m228JkBZ4,211
23
+ tictacsync-1.4.0b0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
24
+ tictacsync-1.4.0b0.dist-info/RECORD,,
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ mamconf = tictacsync.mamconf:main
3
+ mamdav = mamdav:main
4
+ mamreap = mamreap:main
5
+ mamsync = tictacsync.mamsync:main
6
+ multi2polywav = tictacsync.multi2polywav:main
7
+ tictacsync = tictacsync.entry:main
@@ -1,16 +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=6XTO4N0ipJ3HNa1I0aSKkTNIgPk_BtCoDDjwCVhOjpI,35446
4
- tictacsync/entry.py,sha256=KOhB8ivgme3GPpWShad2adS1lvIU9v0yMFY0CELwAmM,20673
5
- tictacsync/multi2polywav.py,sha256=-nX5reZo6QNxFYdhsliHTs8bTfMjPzcONDT8vJbkZUA,7291
6
- tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
7
- tictacsync/remrgmx.py,sha256=nGuNg55BtXpKTpklwZqunsgVNi-1h-_22OFSnGk7K8k,4340
8
- tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
9
- tictacsync/timeline.py,sha256=2CkTzMDiazYlBq2F1fhM2w4r6CIgmpQk1L2ZvAYRcnA,72532
10
- tictacsync/yaltc.py,sha256=xbMucI19UJKrEvIzyfpOsi3piSWzqM1gKgooeT9DV8g,53167
11
- tictacsync-0.98a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
12
- tictacsync-0.98a0.dist-info/METADATA,sha256=PuGAcwkwrbbkh8wwudBmw4s5DU0kbgKsXJq27dYSXzY,5693
13
- tictacsync-0.98a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
14
- tictacsync-0.98a0.dist-info/entry_points.txt,sha256=g3tdFFrVRcrKpuyKOCLUVBMgYfV65q9kpLZUOD_XCKg,139
15
- tictacsync-0.98a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
16
- tictacsync-0.98a0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- multi2polywav = tictacsync.multi2polywav:main
3
- remergemix = tictacsync.remergemix:main
4
- tictacsync = tictacsync.entry:main