tictacsync 0.91a0__py3-none-any.whl → 0.96a0__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/entry.py CHANGED
@@ -1,4 +1,4 @@
1
- # print('Loading modules...', end='')
1
+ print('Loading modules...', end='')
2
2
 
3
3
  # I know, the following is ugly, but I need those try's to
4
4
  # run the command in my dev setting AND from
@@ -16,18 +16,21 @@ except:
16
16
  import timeline
17
17
  import multi2polywav
18
18
 
19
- import argparse
19
+ import argparse, tempfile
20
20
  from loguru import logger
21
21
  from pathlib import Path
22
22
  # import os, sys
23
- import os, sys
23
+ import os, sys, sox
24
24
  from rich.progress import track
25
25
  # from pprint import pprint
26
26
  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
32
+
33
+ DEL_TEMP = False
31
34
 
32
35
  av_file_extensions = \
33
36
  """MOV webm mkv flv flv vob ogv ogg drc gif gifv mng avi MTS M2TS TS mov qt
@@ -40,7 +43,7 @@ logger.level("DEBUG", color="<yellow>")
40
43
 
41
44
  def process_files_with_progress_bars(medias):
42
45
  recordings = []
43
- rec_with_yaltc = []
46
+ rec_with_TTC = []
44
47
  times = []
45
48
  for m in track(medias,
46
49
  description="1/4 Initializing Recordings:"):
@@ -49,25 +52,26 @@ def process_files_with_progress_bars(medias):
49
52
  for r in track(recordings,
50
53
  description="2/4 Looking for TicTacCode:"):
51
54
  if r.seems_to_have_TicTacCode_at_beginning():
52
- rec_with_yaltc.append(r)
53
- for r in track(rec_with_yaltc,
55
+ rec_with_TTC.append(r)
56
+ for r in track(rec_with_TTC,
54
57
  description="3/4 Finding start times:"):
55
58
  times.append(r.get_start_time())
56
- return recordings, rec_with_yaltc, times
59
+ return recordings, rec_with_TTC, times
57
60
 
58
61
  def process_files(medias):
62
+ # maps Media objects -> Recording objects
59
63
  recordings = []
60
- rec_with_yaltc = []
64
+ rec_with_TTC = []
61
65
  times = []
62
66
  for m in medias:
63
67
  recordings.append(yaltc.Recording(m))
64
68
  for r in recordings:
65
69
  # print('%s duration %.2fs'%(r.AVpath.name, r.get_duration()))
66
70
  if r.seems_to_have_TicTacCode_at_beginning():
67
- rec_with_yaltc.append(r)
68
- for r in rec_with_yaltc:
71
+ rec_with_TTC.append(r)
72
+ for r in rec_with_TTC:
69
73
  times.append(r.get_start_time())
70
- return recordings, rec_with_yaltc, times
74
+ return recordings, rec_with_TTC, times
71
75
 
72
76
  def process_single(file, args):
73
77
  # argument is a single file
@@ -97,6 +101,43 @@ def process_single(file, args):
97
101
  print('Start time couldnt be determined')
98
102
  sys.exit(1)
99
103
 
104
+ def process_lag_adjustement(media_object):
105
+ # trim channels that are lagging (as stated in tracks.txt)
106
+ # replace the old file, and rename the old one with .wavbk
107
+ # if .wavbk exist, process was done already, so dont process
108
+ # returns nothing
109
+ lags = media_object.device.tracks.lag_values
110
+ logger.debug('will process %s lags'%[lags])
111
+ channels = timeline._split_channels(media_object.path)
112
+ # add bk to file on filesystem, but media_object.path is unchanged (?)
113
+ backup_name = str(media_object.path) + 'bk'
114
+ if Path(backup_name).exists():
115
+ logger.debug('%s exists, so return now.'%backup_name)
116
+ return
117
+ media_object.path.replace(backup_name)
118
+ logger.debug('channels %s'%channels)
119
+ def _trim(lag, chan_file):
120
+ # for lag
121
+ if lag == None:
122
+ return chan_file
123
+ else:
124
+ logger.debug('process %s for lag of %s'%(chan_file, lag))
125
+ sox_transform = sox.Transformer()
126
+ sox_transform.trim(float(lag)*1e-3)
127
+ output_fh = tempfile.NamedTemporaryFile(suffix='.wav', delete=DEL_TEMP)
128
+ out_file = timeline._pathname(output_fh)
129
+ input_file = timeline._pathname(chan_file)
130
+ logger.debug('sox in and out files: %s %s'%(input_file, out_file))
131
+ logger.debug('calling sox_transform.build()')
132
+ status = sox_transform.build(input_file, out_file, return_output=True )
133
+ logger.debug('sox.build exit code %s'%str(status))
134
+ return output_fh
135
+ new_channels = [_trim(*e) for e in zip(lags, channels)]
136
+ logger.debug('new_channels %s'%new_channels)
137
+ trimmed_multichanfile = timeline._sox_combine(new_channels)
138
+ logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
139
+ Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
140
+
100
141
  def main():
101
142
  parser = argparse.ArgumentParser()
102
143
  parser.add_argument(
@@ -107,32 +148,46 @@ def main():
107
148
  )
108
149
  # parser.add_argument("directory", nargs="?", help="path of media directory")
109
150
  # parser.add_argument('-v', action='store_true')
110
- parser.add_argument('-v', action='store_true', default=False,
111
- dest='verbose_output',
112
- help='Set verbose ouput')
151
+ parser.add_argument('-v',
152
+ action='store_true', #ie default False
153
+ dest='verbose_output',
154
+ help='Set verbose ouput')
113
155
  parser.add_argument('-o', nargs=1,
114
156
  help='Where to write the SyncedMedia folder [default to "path" ]')
115
- parser.add_argument('-p', action='store_true', default=False,
157
+ parser.add_argument('-t','--timelineoffset',
158
+ nargs=1,
159
+ default=['00:00:00:00'],
160
+ dest='timelineoffset',
161
+ help='When processing multicam, where to place clips on NLE timeline (HH:MM:SS:FF)')
162
+ parser.add_argument('-p',
163
+ action='store_true',
116
164
  dest='plotting',
117
165
  help='Produce plots')
118
- parser.add_argument('--isos', action='store_true', default=False,
166
+ parser.add_argument('--isos',
167
+ action='store_true',
119
168
  dest='write_ISOs',
120
169
  help='Write ISO sound files')
121
- parser.add_argument('--nosync', action='store_true',
170
+ parser.add_argument('--nosync',
171
+ action='store_true',
122
172
  dest='nosync',
123
173
  help='Just scan and decode')
124
- parser.add_argument('--terse', action='store_true',
174
+ parser.add_argument('--terse',
175
+ action='store_true',
125
176
  dest='terse',
126
177
  help='Terse output')
127
178
  args = parser.parse_args()
179
+ # print(args)
180
+ if len(args.timelineoffset) != 1:
181
+ print('--timelineoffset needs one value, got %s'%args.timelineoffset)
182
+ quit()
128
183
  if args.verbose_output:
129
184
  logger.add(sys.stderr, level="DEBUG")
130
185
  # logger.add(sys.stdout, filter="__main__")
131
186
  # logger.add(sys.stdout, filter="yaltc")
132
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "get_start_time")
133
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "_detect_sync_pulse_position")
134
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_device_mix")
135
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "_sox_mix_files")
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")
136
191
  top_dir = args.path[0]
137
192
  if os.path.isfile(top_dir):
138
193
  file = top_dir
@@ -151,7 +206,41 @@ def main():
151
206
  sys.exit(1)
152
207
  multi2polywav.poly_all(top_dir)
153
208
  scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
154
- scanner.scan_media_and_build_devices_UID()
209
+ scanner.scan_media_and_build_devices_UID()
210
+ for m in scanner.found_media_files:
211
+ if m.device.tracks:
212
+ if not all([lv == None for lv in m.device.tracks.lag_values]):
213
+ logger.debug('%s has lag_values %s'%(
214
+ m.path, m.device.tracks.lag_values))
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]
155
244
  if not args.terse:
156
245
  if scanner.input_structure == 'folder_is_device':
157
246
  print('\nDetected structured folders', end='')
@@ -162,32 +251,43 @@ def main():
162
251
  else:
163
252
  print('\nDetected loose structure')
164
253
  if scanner.CAM_numbers() > 1:
165
- print('\nNote: different CAMs are present, will sync audio')
166
- print('for each of them but if you want to set their')
167
- print('respective timecode for NLE timeline alignement')
168
- print('you should regroup clips by CAM under their own DIR.')
254
+ print('\nNote: different CAMs are present, will sync audio for each of them but if you want to set their')
255
+ print('respective timecode for NLE timeline alignement you should regroup clips by CAM under their own DIR.')
169
256
  print('\nFound [gold1]%i[/gold1] media files '%(
170
257
  len(scanner.found_media_files)), end='')
171
258
  print('from [gold1]%i[/gold1] devices:\n'%(
172
259
  scanner.get_devices_number()))
173
- for dev in scanner.get_devices():
174
- 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 = ' ')
175
264
  medias = scanner.get_media_for_device(dev)
176
- for m in medias[:-1]:
265
+ for m in medias[:-1]: # last printed out of loop
177
266
  print('[gold1]%s[/gold1]'%m.path.name, end=', ')
178
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()
179
278
  print()
180
- # if args.verbose_output or args.terse: # verbose or terse, so no progress bars
181
- # rez = process_files(scanner.found_media_files)
182
- # else:
183
- # rez = process_files_with_progress_bars(scanner.found_media_files)
184
- rez = process_files(scanner.found_media_files)
185
- recordings, rec_with_yaltc, times = rez
279
+ recordings, rec_with_TTC, times = \
280
+ process_files(scanner.found_media_files)
186
281
  recordings_with_time = [
187
282
  rec
188
- for rec in rec_with_yaltc
283
+ for rec in rec_with_TTC
189
284
  if rec.get_start_time()
190
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
191
291
  if not args.terse:
192
292
  table = Table(title="tictacsync results")
193
293
  table.add_column("Recording\n", justify="center", style='gold1')
@@ -199,7 +299,7 @@ def main():
199
299
  table.add_column("Date\n", justify="center", style='gold1')
200
300
  rec_WO_time = [
201
301
  rec.AVpath.name
202
- for rec in rec_with_yaltc
302
+ for rec in rec_with_TTC
203
303
  if not rec.get_start_time()
204
304
  ]
205
305
  if rec_WO_time:
@@ -226,16 +326,13 @@ def main():
226
326
  print()
227
327
  n_devices = scanner.get_devices_number()
228
328
  OUT_struct_for_mcam = scanner.top_dir_has_multicam
229
- # if n_devices > 2:
230
- # print('\nMerging for more than 2 devices is not implemented yet, quitting...')
231
- # sys.exit(1)
232
329
  if len(recordings_with_time) < 2:
233
330
  if not args.terse:
234
331
  print('\nNothing to sync, exiting.\n')
235
332
  sys.exit(1)
236
333
  matcher = timeline.Matcher(recordings_with_time)
237
334
  matcher.scan_audio_for_each_videoclip()
238
- if not matcher.video_mergers:
335
+ if not matcher.mergers:
239
336
  if not args.terse:
240
337
  print('\nNothing to sync, bye.\n')
241
338
  sys.exit(1)
@@ -245,31 +342,25 @@ def main():
245
342
  asked_ISOs = False
246
343
  output_dir = args.o
247
344
  # if args.verbose_output or args.terse: # verbose, so no progress bars
248
- for stitcher in matcher.video_mergers:
249
- stitcher.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,
250
347
  OUT_struct_for_mcam,
251
- asked_ISOs,)
252
- # else:
253
- # print()
254
- # for stitcher in track(matcher.video_mergers,
255
- # description="4/4 Merging sound to videos:"):
256
- # stitcher.build_audio_and_write_video(top_dir, arg_out_dir,
257
- # OUT_struct_for_mcam,
258
- # asked_ISOs,)
348
+ asked_ISOs,
349
+ audio_REC_only)
259
350
  if not args.terse:
260
351
  print("\n")
261
352
  # find out where files were wrtitten
262
- a_stitcher = matcher.video_mergers[0]
353
+ a_merger = matcher.mergers[0]
263
354
  print('\nWrote output in folder [gold1]%s[/gold1]'%(
264
- a_stitcher.synced_clip_dir))
265
- for stitcher in matcher.video_mergers:
266
- print('[gold1]%s[/gold1]'%stitcher.videoclip.AVpath.name, end='')
267
- for audio in stitcher.get_matched_audio_recs():
355
+ a_merger.synced_clip_dir))
356
+ for merger in matcher.mergers:
357
+ print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
358
+ for audio in merger.get_matched_audio_recs():
268
359
  print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
269
- new_file = stitcher.videoclip.final_synced_file.parts
270
- print(' became [gold1]%s[/gold1]'%stitcher.videoclip.final_synced_file.name)
360
+ new_file = merger.videoclip.final_synced_file.parts
361
+ print(' became [gold1]%s[/gold1]'%merger.videoclip.final_synced_file.name)
271
362
  # matcher._build_otio_tracks_for_cam()
272
- matcher.shrink_gaps_between_takes()
363
+ matcher.shrink_gaps_between_takes(args.timelineoffset)
273
364
  sys.exit(0)
274
365
 
275
366
  if __name__ == '__main__':
@@ -1,4 +1,4 @@
1
- import argparse, wave, subprocess
1
+ import argparse, wave, subprocess, sys
2
2
  from loguru import logger
3
3
  from pathlib import Path
4
4
  from itertools import groupby
tictacsync/remergemix.py CHANGED
@@ -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)
tictacsync/remrgmx.py ADDED
@@ -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