tictacsync 0.97a0__py3-none-any.whl → 0.99a0__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,3 @@
1
- print('Loading modules...', end='')
2
1
 
3
2
  # I know, the following is ugly, but I need those try's to
4
3
  # run the command in my dev setting AND from
@@ -16,18 +15,18 @@ except:
16
15
  import timeline
17
16
  import multi2polywav
18
17
 
19
- import argparse, tempfile
18
+ import argparse, tempfile, configparser
20
19
  from loguru import logger
21
20
  from pathlib import Path
22
21
  # import os, sys
23
- import os, sys, sox
22
+ import os, sys, sox, platformdirs
24
23
  from rich.progress import track
25
24
  # from pprint import pprint
26
25
  from rich.console import Console
27
26
  # from rich.text import Text
28
27
  from rich.table import Table
29
28
  from rich import print
30
- from pprint import pprint
29
+ from pprint import pprint, pformat
31
30
  import numpy as np
32
31
 
33
32
  DEL_TEMP = False
@@ -41,37 +40,22 @@ ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".spl
41
40
 
42
41
  logger.level("DEBUG", color="<yellow>")
43
42
 
44
- def process_files_with_progress_bars(medias):
45
- recordings = []
46
- rec_with_TTC = []
47
- times = []
48
- for m in track(medias,
49
- description="1/4 Initializing Recordings:"):
50
- # file_alias = 'dummy'
51
- recordings.append(yaltc.Recording(m))
52
- for r in track(recordings,
53
- description="2/4 Looking for TicTacCode:"):
54
- if r.seems_to_have_TicTacCode_at_beginning():
55
- rec_with_TTC.append(r)
56
- for r in track(rec_with_TTC,
57
- description="3/4 Finding start times:"):
58
- times.append(r.get_start_time())
59
- return recordings, rec_with_TTC, times
60
-
61
- def process_files(medias):
62
- # maps Media objects -> Recording objects
63
- recordings = []
64
- rec_with_TTC = []
65
- times = []
66
- for m in medias:
67
- recordings.append(yaltc.Recording(m))
68
- for r in recordings:
69
- # print('%s duration %.2fs'%(r.AVpath.name, r.get_duration()))
70
- if r.seems_to_have_TicTacCode_at_beginning():
71
- rec_with_TTC.append(r)
72
- for r in rec_with_TTC:
73
- times.append(r.get_start_time())
74
- return recordings, rec_with_TTC, times
43
+ # def process_files_with_progress_bars(medias): # [todo, replace]
44
+ # recordings = []
45
+ # rec_with_TTC = []
46
+ # times = []
47
+ # for m in track(medias,
48
+ # description="1/4 Initializing Recordings:"):
49
+ # # file_alias = 'dummy'
50
+ # recordings.append(yaltc.Recording(m))
51
+ # for r in track(recordings,
52
+ # description="2/4 Looking for TicTacCode:"):
53
+ # if r.seems_to_have_TicTacCode_at_beginning():
54
+ # rec_with_TTC.append(r)
55
+ # for r in track(rec_with_TTC,
56
+ # description="3/4 Finding start times:"):
57
+ # times.append(r.get_start_time())
58
+ # return recordings, rec_with_TTC, times
75
59
 
76
60
  def process_single(file, args):
77
61
  # argument is a single file
@@ -108,7 +92,7 @@ def process_lag_adjustement(media_object):
108
92
  # returns nothing
109
93
  lags = media_object.device.tracks.lag_values
110
94
  logger.debug('will process %s lags'%[lags])
111
- channels = timeline._split_channels(media_object.path)
95
+ channels = timeline._sox_split_channels(media_object.path)
112
96
  # add bk to file on filesystem, but media_object.path is unchanged (?)
113
97
  backup_name = str(media_object.path) + 'bk'
114
98
  if Path(backup_name).exists():
@@ -138,12 +122,67 @@ def process_lag_adjustement(media_object):
138
122
  logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
139
123
  Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
140
124
 
125
+ def start_proj(folders):
126
+ # if existing values are found,
127
+ # confirm new values with user.
128
+ # returns (source_RAW, destination_synced)
129
+ # either old (and confirmed) or new ones
130
+ def _write_cfg():
131
+ conf_dir = platformdirs.user_config_dir('tictacsync', 'plutz',
132
+ ensure_exists=True)
133
+ logger.debug('will start project with folders %s'%folders)
134
+ conf_file = Path(conf_dir)/'mirrored.cfg'
135
+ logger.debug('writing config in %s'%conf_file)
136
+ conf_prs = configparser.ConfigParser()
137
+ conf_prs['MIRRORED'] = {'source_RAW': folders[0],
138
+ 'destination_synced': folders[1]}
139
+ with open(conf_file, 'w') as configfile:
140
+ conf_prs.write(configfile)
141
+ known_values = get_proj()
142
+ if known_values != ():
143
+ source_RAW, destination_synced = known_values
144
+ print('Warning: there is a current project')
145
+ print('with source (RAW) folder: %s\nand destination (synced) folder: %s'%
146
+ (source_RAW, destination_synced))
147
+ answer = input("\nDo you want to change values? [YES|NO]")
148
+ if answer.upper()[0] in ["Y", "YES"]:
149
+ _write_cfg()
150
+ return folders
151
+ elif answer.upper()[0] in ["N", "NO"]:
152
+ print('Ok, will keep old ones')
153
+ return source_RAW, destination_synced
154
+ else:
155
+ _write_cfg()
156
+ return folders
157
+
158
+ sys.exit(0)
159
+
160
+ def get_proj():
161
+ # check if user started a project before.
162
+ # stored in platformdirs.user_config_dir
163
+ # returns (source_RAW, destination_synced) if any
164
+ # () otherwise
165
+ conf_dir = platformdirs.user_config_dir('tictacsync', 'plutz')
166
+ conf_file = Path(conf_dir)/'mirrored.cfg'
167
+ if conf_file.exists():
168
+ logger.debug('reading config in %s'%conf_file)
169
+ conf_prs = configparser.ConfigParser()
170
+ conf_prs.read(conf_file)
171
+ source_RAW = conf_prs.get('MIRRORED', 'source_RAW')
172
+ destination_synced = conf_prs.get('MIRRORED', 'destination_synced')
173
+ logger.debug('read source_RAW: %s and destination_synced: %s'%
174
+ (source_RAW, destination_synced))
175
+ return (source_RAW, destination_synced)
176
+ else:
177
+ logger.debug('no config file found')
178
+ return ()
179
+
141
180
  def main():
142
181
  parser = argparse.ArgumentParser()
143
182
  parser.add_argument(
144
183
  "path",
145
184
  type=str,
146
- nargs=1,
185
+ nargs='*',
147
186
  help="directory_name or media_file"
148
187
  )
149
188
  # parser.add_argument("directory", nargs="?", help="path of media directory")
@@ -152,8 +191,15 @@ def main():
152
191
  action='store_true', #ie default False
153
192
  dest='verbose_output',
154
193
  help='Set verbose ouput')
155
- parser.add_argument('-o', nargs=1,
156
- help='Where to write the SyncedMedia folder [default to "path" ]')
194
+ parser.add_argument('--stop_mirroring',
195
+ action='store_true', #ie default False
196
+ dest='stop_mirroring',
197
+ help='Stop mirroring mode, will write synced files alongside originals.')
198
+ parser.add_argument('--start-project', '-s' ,
199
+ nargs=2,
200
+ dest='proj_folders',
201
+ default = [],
202
+ help='start mirrored tree output mode and specifies 2 folders: source (RAW) and destination (synced).')
157
203
  parser.add_argument('-t','--timelineoffset',
158
204
  nargs=1,
159
205
  default=['00:00:00:00'],
@@ -163,6 +209,14 @@ def main():
163
209
  action='store_true',
164
210
  dest='plotting',
165
211
  help='Produce plots')
212
+ parser.add_argument('-d',
213
+ action='store_true',
214
+ dest='dont_write_cam_folder',
215
+ help="Even if originals are inside CAM folders, don't put synced clips inside a CAM identified folder")
216
+ # parser.add_argument('-m',
217
+ # action='store_true',
218
+ # dest='multicam',
219
+ # help='Outputs multicam structure for NLE program (based on Davinci Resolve input processing)')
166
220
  parser.add_argument('--isos',
167
221
  action='store_true',
168
222
  dest='write_ISOs',
@@ -182,12 +236,39 @@ def main():
182
236
  quit()
183
237
  if args.verbose_output:
184
238
  logger.add(sys.stderr, level="DEBUG")
239
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "move_multicam_to_dir")
240
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_merge_audio_and_video")
241
+ #
185
242
  # logger.add(sys.stdout, filter="__main__")
186
243
  # logger.add(sys.stdout, filter="yaltc")
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")
244
+
245
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "start_proj")
246
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_dev_type_for_name")
247
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_media_and_build_devices_UID")
248
+
249
+
250
+ if args.proj_folders != []:
251
+ print('\bSorry, mirrored output mode not implemented yet: synced clips will')
252
+ print('be written alongside originals in a SyncedMedia folder. Bye.')
253
+ sys.exit(0)
254
+
255
+ if not len(args.proj_folders) in [0, 2]:
256
+ print('Error, -s option requires two folders')
257
+ sys.exit(0)
258
+ if len(args.proj_folders) == 2:
259
+ source_RAW, destination_synced = start_proj(args.proj_folders)
260
+ logger.debug('source_RAW: %s destination_synced: %s'%(source_RAW,
261
+ destination_synced))
262
+ proj_folders = get_proj()
263
+ if proj_folders != ():
264
+ # mirrored mode
265
+ source_RAW, destination_synced = proj_folders
266
+ logger.debug('Mirrored mode ON, with %s '%str(proj_folders))
267
+ else:
268
+ source_RAW, destination_synced = None, None
269
+ logger.debug('Mirrored mode OFF, ')
270
+ if source_RAW or destination_synced:
271
+ print('Mirrored not implemented yet, ignoring.')
191
272
  top_dir = args.path[0]
192
273
  if os.path.isfile(top_dir):
193
274
  file = top_dir
@@ -197,13 +278,13 @@ def main():
197
278
  sys.exit(1)
198
279
  # logger.debug('args.o %s'%args.o)
199
280
  # print('args.o %s'%args.o)
200
- if args.o:
201
- arg_out_dir = args.o[0]
202
- else:
203
- arg_out_dir = None
204
- if args.o and not os.path.isdir(arg_out_dir):
205
- print('%s is not a directory or doesnt exist.'%arg_out_dir)
206
- sys.exit(1)
281
+ # if args.anchor:
282
+ # anchor_dir = args.anchor[0]
283
+ # else:
284
+ # anchor_dir = None
285
+ # if args.anchor and not os.path.isdir(anchor_dir):
286
+ # print('%s is not a directory or doesnt exist.'%anchor_dir)
287
+ # sys.exit(1)
207
288
  multi2polywav.poly_all(top_dir)
208
289
  scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
209
290
  scanner.scan_media_and_build_devices_UID()
@@ -212,12 +293,12 @@ def main():
212
293
  if not all([lv == None for lv in m.device.tracks.lag_values]):
213
294
  logger.debug('%s has lag_values %s'%(
214
295
  m.path, m.device.tracks.lag_values))
296
+ # any lag for a channel is specified by user in tracks.txt
215
297
  process_lag_adjustement(m)
216
298
  audio_REC_only = all([m.device.dev_type == 'REC' for m
217
299
  in scanner.found_media_files])
218
-
219
300
  if audio_REC_only:
220
- if scanner.input_structure != 'folder_is_device':
301
+ if scanner.input_structure != 'ordered':
221
302
  print('For merging audio only, use a directory per device, quitting')
222
303
  sys.exit(1)
223
304
  print('\n\n\nOnly audio recordings are present')
@@ -242,12 +323,12 @@ def main():
242
323
  # ref_device = list(devices)[3 - 1]
243
324
  print('When only audio recordings are present, ISOs files will be cut and written.')
244
325
  if not args.terse:
245
- if scanner.input_structure == 'folder_is_device':
246
- print('\nDetected structured folders', end='')
247
- if scanner.top_dir_has_multicam:
248
- print(', multicam')
249
- else:
250
- print()
326
+ if scanner.input_structure == 'ordered':
327
+ print('\nDetected structured folders')
328
+ # if scanner.top_dir_has_multicam:
329
+ # print(', multicam')
330
+ # else:
331
+ # print()
251
332
  else:
252
333
  print('\nDetected loose structure')
253
334
  if scanner.CAM_numbers() > 1:
@@ -269,18 +350,19 @@ def main():
269
350
  # check if all audio recorders have same sampling freq
270
351
  freqs = [dev.sampling_freq for dev in all_devices if dev.dev_type == 'REC']
271
352
  same = np.isclose(np.std(freqs),0)
272
- logger.debug('sampling freqs from audio recoders %s, same:%s'%(freqs, same))
353
+ logger.debug('sampling freqs from audio recorders %s, same:%s'%(freqs, same))
273
354
  if not same:
274
355
  print('some audio recorders have different sampling frequencies:')
275
356
  print(freqs)
276
357
  print('resulting in undefined results: quitting...')
277
358
  quit()
278
359
  print()
279
- recordings, rec_with_TTC, times = \
280
- process_files(scanner.found_media_files)
360
+ # recordings, rec_with_TTC = process_files(scanner.found_media_files, args)
361
+ recordings = [yaltc.Recording(m, do_plots=args.plotting) for m
362
+ in scanner.found_media_files]
281
363
  recordings_with_time = [
282
364
  rec
283
- for rec in rec_with_TTC
365
+ for rec in recordings
284
366
  if rec.get_start_time()
285
367
  ]
286
368
  if audio_REC_only:
@@ -299,9 +381,8 @@ def main():
299
381
  table.add_column("Date\n", justify="center", style='gold1')
300
382
  rec_WO_time = [
301
383
  rec.AVpath.name
302
- for rec in rec_with_TTC
303
- if not rec.get_start_time()
304
- ]
384
+ for rec in recordings
385
+ if rec not in recordings_with_time]
305
386
  if rec_WO_time:
306
387
  print('No time found for: ',end='')
307
388
  [print(rec, end=' ') for rec in rec_WO_time]
@@ -325,7 +406,13 @@ def main():
325
406
  console.print(table)
326
407
  print()
327
408
  n_devices = scanner.get_devices_number()
328
- OUT_struct_for_mcam = scanner.top_dir_has_multicam
409
+ # OUT_struct_for_mcam = scanner.top_dir_has_multicam and \
410
+ # scanner.input_structure != 'loose'
411
+ # OUT_struct_for_mcam = args.multicam
412
+ # if OUT_struct_for_mcam and scanner.input_structure == 'loose':
413
+ # print("\nSorry, can't output multicam structure if input is not structured:")
414
+ # print("each camera must have its own folder with its clips stored inside, quitting.")
415
+ # sys.exit(0)
329
416
  if len(recordings_with_time) < 2:
330
417
  if not args.terse:
331
418
  print('\nNothing to sync, exiting.\n')
@@ -337,30 +424,45 @@ def main():
337
424
  print('\nNothing to sync, bye.\n')
338
425
  sys.exit(1)
339
426
  asked_ISOs = args.write_ISOs
340
- if asked_ISOs and scanner.input_structure != 'folder_is_device':
427
+ if asked_ISOs and scanner.input_structure != 'ordered':
341
428
  print('Warning, can\'t write ISOs without structured folders: [gold1]--isos[/gold1] option ignored.\n')
342
429
  asked_ISOs = False
343
- output_dir = args.o
430
+ # output_dir = args.o
344
431
  # if args.verbose_output or args.terse: # verbose, so no progress bars
345
432
  for merger in matcher.mergers:
346
- merger.build_audio_and_write_merged_media(top_dir, arg_out_dir,
347
- OUT_struct_for_mcam,
433
+ merger.build_audio_and_write_merged_media(top_dir,
434
+ args.dont_write_cam_folder,
348
435
  asked_ISOs,
349
436
  audio_REC_only)
350
437
  if not args.terse:
351
438
  print("\n")
352
- # find out where files were wrtitten
353
- a_merger = matcher.mergers[0]
354
- print('\nWrote output in folder [gold1]%s[/gold1]'%(
355
- a_merger.synced_clip_dir))
439
+ # find out where files were written
440
+ # a_merger = matcher.mergers[0]
356
441
  for merger in matcher.mergers:
357
442
  print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
358
443
  for audio in merger.get_matched_audio_recs():
359
444
  print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
360
445
  new_file = merger.videoclip.final_synced_file.parts
361
- print(' became [gold1]%s[/gold1]'%merger.videoclip.final_synced_file.name)
446
+ final_p = merger.videoclip.final_synced_file
447
+ nameAnd2Parents = Path('').joinpath(*final_p.parts[-2:])
448
+ print(' became [gold1]%s[/gold1]'%nameAnd2Parents)
362
449
  # matcher._build_otio_tracks_for_cam()
450
+ matcher.set_up_clusters() # multicam
363
451
  matcher.shrink_gaps_between_takes(args.timelineoffset)
452
+ logger.debug('matcher.multicam_clips_clusters %s'%
453
+ pformat(matcher.multicam_clips_clusters))
454
+ # clusters is list of {'end': t1, 'start': t2, 'vids': [r1,r3]}
455
+ # really_clusters is True if one of them has len() > 1
456
+ really_clusters = any([len(cl['vids']) > 1 for cl
457
+ in matcher.multicam_clips_clusters])
458
+ if really_clusters:
459
+ if scanner.input_structure == 'loose':
460
+ print('\nThere are synced multicam clips but without structured folders')
461
+ print('they were not grouped together under the same folder.')
462
+ else:
463
+ matcher.move_multicam_to_dir()
464
+ else:
465
+ logger.debug('not really a multicam cluster, nothing to move')
364
466
  sys.exit(0)
365
467
 
366
468
  if __name__ == '__main__':
tictacsync/remrgmx.py CHANGED
@@ -1,5 +1,11 @@
1
- import os, itertools
1
+ import os, itertools, argparse
2
2
  from pathlib import Path
3
+ from loguru import logger
4
+ import sys
5
+ from pprint import pformat
6
+
7
+ logger.level("DEBUG", color="<yellow>")
8
+
3
9
 
4
10
  video_extensions = \
5
11
  """webm mkv flv flv vob ogv ogg drc gif gifv mng avi mov
@@ -14,8 +20,11 @@ def is_video(f):
14
20
  name, ext = name_ext
15
21
  return ext.lower() in video_extensions
16
22
 
17
- def find_ISO_vids_pairs(top):
18
- # top is
23
+ def find_ISO_vids_pairs_in_dir(top):
24
+ # look for matching video name and ISO dir name
25
+ # eg: IMG04.mp4 and IMG04_ISO
26
+ # returns list of matches
27
+ # recursively search from 'top' argument
19
28
  vids = []
20
29
  ISOs = []
21
30
  for (root,dirs,files) in os.walk(top, topdown=True):
@@ -23,8 +32,9 @@ def find_ISO_vids_pairs(top):
23
32
  if d[-4:] == '_ISO':
24
33
  ISOs.append(Path(root)/d)
25
34
  for f in files:
26
- if is_video(f):
27
- vids.append(Path(root)/f)
35
+ if is_video(f): # add being in SyncedMedia or SyncedMulticamClips folder
36
+ vids.append(Path(root)/f)
37
+ logger.debug('vids %s ISOs %s'%(pformat(vids), pformat(ISOs)))
28
38
  matches = []
29
39
  for pair in list(itertools.product(vids, ISOs)):
30
40
  # print(pair)
@@ -33,6 +43,74 @@ def find_ISO_vids_pairs(top):
33
43
  if vidname == ISO.name[:-4]:
34
44
  matches.append(pair)
35
45
  # print(vidname, ISO.name[:-4])
46
+ logger.debug('matches: %s'%pformat(matches))
36
47
  return matches
37
48
 
38
- [print( vid, ISO) for vid, ISO in find_ISO_vids_pairs('.')]
49
+ # [print( vid, ISO) for vid, ISO in find_ISO_vids_pairs('.')]
50
+
51
+ def parse_and_check_arguments():
52
+ # parses directories from command arguments
53
+ # check for consistencies and warn user and exits,
54
+ # if returns, gives:
55
+ # proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only
56
+ parser = argparse.ArgumentParser()
57
+ parser.add_argument('-v',
58
+ nargs=*,
59
+ dest='video_dirs',
60
+ help='Where proxy clips and/or originals are stored')
61
+ parser.add_argument('-a',
62
+ nargs=1,
63
+ dest='audio_dir',
64
+ help='Contains newly changed mix files')
65
+ parser.add_argument('-b',
66
+ nargs=1,
67
+ dest='both_audio_vid',
68
+ help='Directory scanned for both audio and video')
69
+ parser.add_argument('--dry',
70
+ action='store_true',
71
+ dest='scan_only',
72
+ help="Just display changed audio, don't merge")
73
+ args = parser.parse_args()
74
+ logger.debug('args %s'%args)
75
+ # ok cases:
76
+ # -p -o -a + no -b
77
+ # -o -a + no -b
78
+ args_set = [args.proxies_dir != None,
79
+ args.originals_dir != None,
80
+ args.audio_dir != None,
81
+ args.both_audio_vid != None,
82
+ ]
83
+ p, o, a, b = args_set
84
+ # check that argument -b (both_audio_vid) is used alone
85
+ if b and any([o, a, p]):
86
+ print("\nDon't specify other argument than -b if both audio and video searched in the same directory.\n")
87
+ parser.print_help(sys.stderr)
88
+ sys.exit(0)
89
+ # check that if proxies (-p) are specified, orginals too (-o)
90
+ if p and not o:
91
+ print("\nIf proxies directory is specified, so should originals directory.\n")
92
+ parser.print_help(sys.stderr)
93
+ sys.exit(0)
94
+ # check that -o and -a are used together
95
+ if not b and not (o and a):
96
+ print("\nAt least originals and audio directories must be given (-o and -a) when audio and video are in different dir.\n")
97
+ parser.print_help(sys.stderr)
98
+ sys.exit(0)
99
+ # work in progress (aug 2025), so limit to -b:
100
+ if not b :
101
+ print("\nFor now, only -b argument is supported (a directory scanned for both audio and video) .\n")
102
+ parser.print_help(sys.stderr)
103
+ sys.exit(0)
104
+ arg_dict = vars(args)
105
+ # list of singletons, so flatten. Keep None and False as is
106
+ return [e[0] if isinstance(e, list) else e for e in arg_dict.values() ]
107
+
108
+
109
+
110
+ def main():
111
+ proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only = \
112
+ parse_and_check_arguments()
113
+ m = find_ISO_vids_pairs_in_dir(both_audio_vid)
114
+
115
+ if __name__ == '__main__':
116
+ main()