tictacsync 1.0.1a0__tar.gz → 1.1.0a0__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.

Files changed (22) hide show
  1. {tictacsync-1.0.1a0/tictacsync.egg-info → tictacsync-1.1.0a0}/PKG-INFO +3 -3
  2. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/README.md +1 -1
  3. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/setup.py +4 -2
  4. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/device_scanner.py +41 -29
  5. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/entry.py +46 -159
  6. tictacsync-1.1.0a0/tictacsync/mamconf.py +175 -0
  7. tictacsync-1.1.0a0/tictacsync/mamsync.py +387 -0
  8. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/multi2polywav.py +4 -2
  9. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/newmix.py +217 -57
  10. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/timeline.py +137 -84
  11. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/yaltc.py +2 -2
  12. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0/tictacsync.egg-info}/PKG-INFO +3 -3
  13. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/SOURCES.txt +2 -0
  14. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/entry_points.txt +2 -0
  15. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/LICENSE +0 -0
  16. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/setup.cfg +0 -0
  17. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/__init__.py +0 -0
  18. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync/remergemix.py +0 -0
  19. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/dependency_links.txt +0 -0
  20. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/not-zip-safe +0 -0
  21. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/requires.txt +0 -0
  22. {tictacsync-1.0.1a0 → tictacsync-1.1.0a0}/tictacsync.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 1.0.1a0
4
- Summary: command for syncing audio video recordings
3
+ Version: 1.1.0a0
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
@@ -58,7 +58,7 @@ Then pip install the syncing program:
58
58
  This should install python dependencies _and_ the `tictacsync` command.
59
59
  ## Usage
60
60
 
61
- Download multiple sample files [here](https://nuage.lutz.quebec/s/P3gbZR4GgGy8xQp/download/dailies1_1.zip) (625 MB, sorry) unzip and run:
61
+ Download multiple sample files [here](https://nuage.lutz.quebec/s/4jw4xgqysLPS8EQ/download/dailies1_3.zip) (700+ MB, sorry) unzip and run:
62
62
 
63
63
  > tictacsync dailies/loose
64
64
  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:
@@ -35,7 +35,7 @@ Then pip install the syncing program:
35
35
  This should install python dependencies _and_ the `tictacsync` command.
36
36
  ## Usage
37
37
 
38
- Download multiple sample files [here](https://nuage.lutz.quebec/s/P3gbZR4GgGy8xQp/download/dailies1_1.zip) (625 MB, sorry) unzip and run:
38
+ Download multiple sample files [here](https://nuage.lutz.quebec/s/4jw4xgqysLPS8EQ/download/dailies1_3.zip) (700+ MB, sorry) unzip and run:
39
39
 
40
40
  > tictacsync dailies/loose
41
41
  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:
@@ -30,11 +30,13 @@ setup(
30
30
  "console_scripts": [
31
31
  'tictacsync = tictacsync.entry:main',
32
32
  'newmix = tictacsync.newmix:main',
33
+ 'mamsync = tictacsync.mamsync:main',
34
+ 'mamconf = tictacsync.mamconf:main',
33
35
  'multi2polywav = tictacsync.multi2polywav:main',
34
36
  ]
35
37
  },
36
- version = '1.0.1-alpha',
37
- description = "command for syncing audio video recordings",
38
+ version = '1.1.0-alpha',
39
+ description = "commands for syncing audio video recordings",
38
40
  long_description_content_type='text/markdown',
39
41
  long_description = long_descr,
40
42
  include_package_data=True,
@@ -18,8 +18,10 @@ M4V SVI 3GP 3G2 MXF ROQ NSV FLV F4V F4P F4A F4B 3GP AA AAC AAX ACT AIFF ALAC
18
18
  AMR APE AU AWB DSS DVF FLAC GSM IKLAX IVS M4A M4B M4P MMF MP3 MPC MSV NMF
19
19
  OGG OGA MOGG OPUS RA RM RAW RF64 SLN TTA VOC VOX WAV WMA WV WEBM 8SVX CDA MOV AVI BWF""".split()
20
20
 
21
+ audio_ext = 'aiff wav mp3'.split()
22
+
21
23
  from dataclasses import dataclass
22
- import ffmpeg, os, sys
24
+ import ffmpeg, os, sys, shutil
23
25
  from os import listdir
24
26
  from os.path import isfile, join, isdir
25
27
  from collections import namedtuple
@@ -28,7 +30,7 @@ from pprint import pformat
28
30
  # from collections import defaultdict
29
31
  from loguru import logger
30
32
  # import pathlib, os.path
31
- import sox, tempfile
33
+ import sox, tempfile, platformdirs, filecmp
32
34
  # from functools import reduce
33
35
  from rich import print
34
36
  from itertools import groupby
@@ -36,8 +38,12 @@ from itertools import groupby
36
38
  # import distance
37
39
  try:
38
40
  from . import multi2polywav
41
+ from . import mamsync
42
+ from . import mamconf
39
43
  except:
40
44
  import multi2polywav
45
+ import mamsync
46
+ import mamconf
41
47
 
42
48
  MCCDIR = 'SyncedMulticamClips'
43
49
  SYNCEDFOLDER = 'SyncedMedia'
@@ -244,16 +250,7 @@ class Scanner:
244
250
  CAMs = [d for d in devices if d.dev_type == 'CAM']
245
251
  return len(set(CAMs))
246
252
 
247
- def get_device_in_path(self, a_media):
248
- """
249
- Return a device name taken from folders in path of a_media if and only
250
- if all media files below it are from the same device. Goes up max two
251
- levels from media; e.g., in /FOLDER2/FOLDER1/DSC00234.MOV will test up
252
- to FOLDER2 as candidate.
253
- """
254
-
255
-
256
- def scan_media_and_build_devices_UID(self, recursive=True):
253
+ def scan_media_and_build_devices_UID(self, synced_root = None):
257
254
  """
258
255
  Scans Scanner.top_directory recursively for files with known audio-video
259
256
  extensions. For each file found, a device fingerprint is obtained from
@@ -269,20 +266,36 @@ class Scanner:
269
266
  Sets Scanner.input_structure = 'loose'|'ordered'
270
267
 
271
268
  """
272
- files = Path(self.top_directory).rglob('*.*')
273
- def _last4letters(part):
274
- return
275
-
276
- paths = [
277
- p
278
- for p in files
279
- if p.suffix[1:] in av_file_extensions
280
- and SYNCEDFOLDER not in p.parts # SyncedMedia
281
- and MCCDIR not in p.parts # SyncedMulticamClips
282
- and '_ISO' not in [part[-4:] for part in p.parts] # exclude ISO wav files
283
- ]
284
- logger.debug('found media files %s'%paths)
285
- parents = [p.parent for p in paths]
269
+ logger.debug(f'on entry synced_root: {synced_root}')
270
+ if synced_root != None: # mam mode
271
+ p = Path(platformdirs.user_data_dir('mamsync', 'plutz'))/mamconf.LOG_FILE
272
+ with open(p, 'r') as fh:
273
+ done = set(fh.read().split()) # sets of strings of abs path
274
+ logger.debug(f'done clips: {pformat(done)}')
275
+ files = Path(self.top_directory).rglob('*')
276
+ clip_paths = []
277
+ some_done = False
278
+ for raw_path in files:
279
+ if raw_path.suffix[1:] in av_file_extensions:
280
+ if SYNCEDFOLDER not in raw_path.parts: # SyncedMedia
281
+ if MCCDIR not in raw_path.parts: # SyncedMulticamClips
282
+ if '_ISO' not in [part[-4:] for part in raw_path.parts]: # exclude ISO wav files
283
+ if synced_root != None and str(raw_path) in done:
284
+ logger.debug(f'{raw_path} done')
285
+ some_done = True
286
+ continue
287
+ else:
288
+ clip_paths.append(raw_path)
289
+ if some_done:
290
+ print('Somme media files were already synced...')
291
+ logger.debug('found media files %s'%clip_paths)
292
+ if len(clip_paths) == 0:
293
+ print('No media found, bye.')
294
+ sys.exit(0)
295
+ # self.found_media_files = []
296
+ # self.input_structure = 'loose'
297
+ # return
298
+ parents = [p.parent for p in clip_paths]
286
299
  logger.debug('found parents %s'%pformat(parents))
287
300
  # True if all elements are identical
288
301
  AV_files_have_same_parent = parents.count(parents[0]) == len(parents)
@@ -296,7 +309,7 @@ class Scanner:
296
309
  # check later if inside each folder, media have same device
297
310
  # for now, we'll guess structure is 'ordered'
298
311
  self.input_structure = 'ordered'
299
- for p in paths:
312
+ for p in clip_paths:
300
313
  new_media = media_at_path(self.input_structure, p) # dev UID set here
301
314
  self.found_media_files.append(new_media)
302
315
  # for non UIDed try building UID from filenam
@@ -379,7 +392,7 @@ class Scanner:
379
392
  no_name_devices = [m.device for m in self.found_media_files
380
393
  if not m.device.name]
381
394
  # possible if self.input_structure == 'loose'
382
- def _try_name_from_metadata(media):
395
+ def _try_name_from_metadata(media): # unused for now
383
396
  # search model and make from fprobe
384
397
  file = Path(media.path)
385
398
  logger.debug('trying to find maker model for %s'%file)
@@ -395,7 +408,6 @@ class Scanner:
395
408
  # could reside in ['format','tags','com.apple.quicktime.model'],
396
409
  # or ['format','tags','model'],
397
410
  # or ['streams'][0]['tags']['vendor_id']) :-(
398
-
399
411
  for anon_dev in no_name_devices:
400
412
  medias = self.get_media_for_device(anon_dev)
401
413
  guess_name = _try_name_from_files(medias)
@@ -2,7 +2,7 @@
2
2
  # I know, the following is ugly, but I need those try's to
3
3
  # run the command in my dev setting AND from
4
4
  # a deployment set-up... surely I'm setting
5
- # things wrong [TODO] find why and clean up this mess
5
+ # things wrong [TODO]: find why and clean up this mess
6
6
 
7
7
  try:
8
8
  from . import yaltc
@@ -40,23 +40,6 @@ ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".spl
40
40
 
41
41
  logger.level("DEBUG", color="<yellow>")
42
42
 
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
59
-
60
43
  def process_single(file, args):
61
44
  # argument is a single file
62
45
  m = device_scanner.media_at_path(None, Path(file))
@@ -122,61 +105,6 @@ def process_lag_adjustement(media_object):
122
105
  logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
123
106
  Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
124
107
 
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
-
180
108
  def main():
181
109
  parser = argparse.ArgumentParser()
182
110
  parser.add_argument(
@@ -191,15 +119,15 @@ def main():
191
119
  action='store_true', #ie default False
192
120
  dest='verbose_output',
193
121
  help='Set verbose ouput')
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).')
122
+ # parser.add_argument('--stop_mirroring',
123
+ # action='store_true', #ie default False
124
+ # dest='stop_mirroring',
125
+ # help='Stop mirroring mode, will write synced files alongside originals.')
126
+ # parser.add_argument('--start-project', '-s' ,
127
+ # nargs=2,
128
+ # dest='proj_folders',
129
+ # default = [],
130
+ # help='start mirrored tree output mode and specifies 2 folders: source (RAW) and destination (synced).')
203
131
  parser.add_argument('-t','--timelineoffset',
204
132
  nargs=1,
205
133
  default=['00:00:00:00'],
@@ -236,39 +164,17 @@ def main():
236
164
  quit()
237
165
  if args.verbose_output:
238
166
  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")
167
+
168
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_write_ISOs")
169
+ logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_and_write_audio")
241
170
  #
242
171
  # logger.add(sys.stdout, filter="__main__")
243
172
  # logger.add(sys.stdout, filter="yaltc")
244
173
 
245
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "start_proj")
174
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "move_multicam_to_dir")
246
175
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "_dev_type_for_name")
247
176
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_media_and_build_devices_UID")
248
177
 
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.')
272
178
  top_dir = args.path[0]
273
179
  if os.path.isfile(top_dir):
274
180
  file = top_dir
@@ -276,15 +182,6 @@ def main():
276
182
  if not os.path.isdir(top_dir):
277
183
  print('%s is not a directory or doesnt exist.'%top_dir)
278
184
  sys.exit(1)
279
- # logger.debug('args.o %s'%args.o)
280
- # print('args.o %s'%args.o)
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)
288
185
  multi2polywav.poly_all(top_dir)
289
186
  scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
290
187
  scanner.scan_media_and_build_devices_UID()
@@ -307,20 +204,7 @@ def main():
307
204
  maxch = len(devices)
308
205
  for i, d in enumerate(devices):
309
206
  print('\t%i - %s'%(i+1, d.name))
310
- while True:
311
- print('\nEnter your choice:', end='')
312
- choice = input()
313
- try:
314
- choice = int(choice)
315
- except:
316
- print('Please use numeric digits.')
317
- continue
318
- if choice not in list(range(1, maxch + 1)):
319
- print('Please enter a number in [1..%i]'%maxch)
320
- continue
321
- break
322
- ref_device = list(devices)[choice - 1]
323
- # ref_device = list(devices)[3 - 1]
207
+ ref_device = list(devices)[3 - 1]
324
208
  print('When only audio recordings are present, ISOs files will be cut and written.')
325
209
  if not args.terse:
326
210
  if scanner.input_structure == 'ordered':
@@ -369,7 +253,7 @@ def main():
369
253
  for rec in recordings:
370
254
  # print(rec, rec.device == ref_device)
371
255
  if rec.device == ref_device:
372
- rec.is_reference = True
256
+ rec.is_audio_reference = True
373
257
  if not args.terse:
374
258
  table = Table(title="tictacsync results")
375
259
  table.add_column("Recording\n", justify="center", style='gold1')
@@ -406,13 +290,6 @@ def main():
406
290
  console.print(table)
407
291
  print()
408
292
  n_devices = scanner.get_devices_number()
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)
416
293
  if len(recordings_with_time) < 2:
417
294
  if not args.terse:
418
295
  print('\nNothing to sync, exiting.\n')
@@ -429,11 +306,20 @@ def main():
429
306
  asked_ISOs = False
430
307
  # output_dir = args.o
431
308
  # if args.verbose_output or args.terse: # verbose, so no progress bars
309
+ print('Merging...')
310
+ # for merger in matcher.mergers:
311
+ # merger.build_audio_and_write_merged_media(top_dir,
312
+ # args.dont_write_cam_folder,
313
+ # asked_ISOs,
314
+ # audio_REC_only)
432
315
  for merger in matcher.mergers:
433
- merger.build_audio_and_write_merged_media(top_dir,
434
- args.dont_write_cam_folder,
435
- asked_ISOs,
436
- audio_REC_only)
316
+ if audio_REC_only:
317
+ # rare
318
+ merger._build_and_write_audio(top_dir)
319
+ else:
320
+ # almost always syncing audio to video clips
321
+ merger._build_audio_and_write_video(top_dir,
322
+ args.dont_write_cam_folder, asked_ISOs)
437
323
  if not args.terse:
438
324
  print("\n")
439
325
  # find out where files were written
@@ -447,23 +333,24 @@ def main():
447
333
  nameAnd2Parents = Path('').joinpath(*final_p.parts[-2:])
448
334
  print(' became [gold1]%s[/gold1]'%nameAnd2Parents)
449
335
  # matcher._build_otio_tracks_for_cam()
450
- matcher.set_up_clusters() # multicam
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.')
336
+ if not audio_REC_only:
337
+ matcher.set_up_clusters() # multicam
338
+ matcher.shrink_gaps_between_takes(args.timelineoffset)
339
+ logger.debug('matcher.multicam_clips_clusters %s'%
340
+ pformat(matcher.multicam_clips_clusters))
341
+ # clusters is list of {'end': t1, 'start': t2, 'vids': [r1,r3]}
342
+ # really_clusters is True if one of them has len() > 1
343
+ really_clusters = any([len(cl['vids']) > 1 for cl
344
+ in matcher.multicam_clips_clusters])
345
+ if really_clusters:
346
+ if scanner.input_structure == 'loose':
347
+ print('\nThere are synced multicam clips but without structured folders')
348
+ print('they were not grouped together under the same folder.')
349
+ else:
350
+ matcher.move_multicam_to_dir()
462
351
  else:
463
- matcher.move_multicam_to_dir()
464
- else:
465
- logger.debug('not really a multicam cluster, nothing to move')
466
- sys.exit(0)
352
+ logger.debug('not really a multicam cluster, nothing to move')
353
+ sys.exit(0)
467
354
 
468
355
  if __name__ == '__main__':
469
356
  main()
@@ -0,0 +1,175 @@
1
+ import argparse, platformdirs, configparser, sys
2
+ from loguru import logger
3
+ from pprint import pprint, pformat
4
+ from pathlib import Path
5
+
6
+
7
+ CONF_FILE = 'mamsync.cfg'
8
+ LOG_FILE = 'mamdone.txt'
9
+
10
+ logger.remove()
11
+ # logger.add(sys.stdout, level="DEBUG")
12
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "write_conf")
13
+
14
+
15
+ def print_out_conf(raw_root, synced_root, snd_root, proxies=''):
16
+ print(f'RAWROOT (source with TC): "{raw_root}"')
17
+ print(f'SYNCEDROOT (destination of synced folder): "{synced_root}"')
18
+ print(f'SNDROOT (destination of ISOs sound files): "{snd_root}"')
19
+ if proxies != '':
20
+ print(f'PROXIES (NLE proxy clips folder): "{proxies}"')
21
+
22
+ def write_conf(conf_key, conf_val):
23
+ # args are pahtlib.Paths.
24
+ # RAWROOT: files with TC (and ROLL folders), as is from cameras
25
+ # SYNCEDROOT: synced and no more TC (ROLL flattened)
26
+ # Writes configuration on filesystem for later retrieval
27
+ # Clears log of already synced clips.
28
+ conf_dir = platformdirs.user_config_dir('mamsync', 'plutz', ensure_exists=True)
29
+ current_values = dict(zip(['RAWROOT', 'SYNCEDROOT', 'SNDROOT', 'PROXIES'],
30
+ get_proj()))
31
+ logger.debug(f'old values {current_values}')
32
+ current_values[conf_key] = conf_val
33
+ logger.debug(f'updated values {current_values}')
34
+ conf_file = Path(conf_dir)/CONF_FILE
35
+ logger.debug('writing config in %s'%conf_file)
36
+ # print(f'\nWriting folders paths in configuration file "{conf_file}"')
37
+ # print_out_conf(raw_root, synced_root, snd_root)
38
+ conf_prs = configparser.ConfigParser()
39
+ conf_prs['SECTION1'] = current_values
40
+ with open(conf_file, 'w') as configfile_handle:
41
+ conf_prs.write(configfile_handle)
42
+ with open(conf_file, 'r') as configfile_handle:
43
+ logger.debug(f'config file content: \n{configfile_handle.read()}')
44
+
45
+ def get_proj(print_conf_stdout=False):
46
+ # check if user started a project before.
47
+ # stored in platformdirs.user_config_dir
48
+ # returns a tuple of strings (RAWROOT, SYNCEDROOTS, SNDROOT, PROXIES)
49
+ # if any, or a tuple of 4 empty strings '' otherwise.
50
+ # print location of conf file if print_conf_stdout
51
+ conf_dir = platformdirs.user_config_dir('mamsync', 'plutz')
52
+ conf_file = Path(conf_dir)/CONF_FILE
53
+ logger.debug('try reading config in %s'%conf_file)
54
+ if print_conf_stdout:
55
+ print(f'\nTrying to read configuration from file {conf_file}')
56
+ if conf_file.exists():
57
+ conf_prs = configparser.ConfigParser()
58
+ conf_prs.read(conf_file)
59
+ try:
60
+ RAWROOT = conf_prs.get('SECTION1', 'RAWROOT')
61
+ except configparser.NoOptionError:
62
+ RAWROOT = ''
63
+ try:
64
+ SYNCEDROOT = conf_prs.get('SECTION1', 'SYNCEDROOT')
65
+ except configparser.NoOptionError:
66
+ SYNCEDROOT = ''
67
+ try:
68
+ PROXIES = conf_prs.get('SECTION1', 'PROXIES')
69
+ except configparser.NoOptionError:
70
+ PROXIES = ''
71
+ try:
72
+ SNDROOT = conf_prs.get('SECTION1', 'SNDROOT')
73
+ except configparser.NoOptionError:
74
+ SNDROOT = ''
75
+ logger.debug('read from conf: RAWROOT= %s SYNCEDROOT= %s SNDROOT=%s PROXIES=%s'%
76
+ (RAWROOT, SYNCEDROOT, SNDROOT, PROXIES))
77
+ return RAWROOT, SYNCEDROOT, SNDROOT, PROXIES
78
+ else:
79
+ logger.debug(f'no config file found at {conf_file}')
80
+ print('No configuration found.')
81
+ return '', '', '', ''
82
+
83
+ def new_parser():
84
+ parser = argparse.ArgumentParser()
85
+ parser.add_argument('--rawroot',
86
+ nargs = 1,
87
+ dest='rawroot',
88
+ help='Sets new value for raw root folder (i.e.: clips with TC)')
89
+ parser.add_argument('--syncedroot',
90
+ nargs = 1,
91
+ dest='syncedroot',
92
+ help="""Sets where the synced files will be written, to be used by the NLE. Will contain a mirror copy of RAWROOT """)
93
+ parser.add_argument('--proxies',
94
+ nargs = 1,
95
+ dest='proxies',
96
+ help='Sets where the proxy files are stored by the NLE')
97
+ parser.add_argument('--sndfolder',
98
+ nargs = 1,
99
+ dest='sndfolder',
100
+ help='Sets new value for sound folder (where ISOs sound files will be stored)')
101
+ parser.add_argument('--clearconf',
102
+ action='store_true',
103
+ dest='clearconf',
104
+ help='Clear configured values.')
105
+ parser.add_argument('--showconf',
106
+ action='store_true',
107
+ dest='showconf',
108
+ help='Show current configured values.')
109
+ return parser
110
+
111
+ def main():
112
+ parser = new_parser()
113
+ args = parser.parse_args()
114
+ logger.debug(f'arguments from argparse {args}')
115
+ if args.rawroot:
116
+ val = args.rawroot[0]
117
+ write_conf('RAWROOT', val)
118
+ print(f'Set source folder of unsynced clips (rawroot) to:\n{val}')
119
+ sys.exit(0)
120
+ if args.syncedroot:
121
+ val = args.syncedroot[0]
122
+ write_conf('SYNCEDROOT', args.syncedroot[0])
123
+ print(f'Set destination folder of synced clips (syncedroot) to:\n{val}')
124
+ sys.exit(0)
125
+ if args.proxies:
126
+ val = args.proxies[0]
127
+ write_conf('PROXIES', args.proxies[0])
128
+ print(f'Set proxies folder to:\n{val}')
129
+ sys.exit(0)
130
+ if args.sndfolder:
131
+ val = args.sndfolder[0]
132
+ write_conf('SNDROOT', args.sndfolder[0])
133
+ print(f'Set destination folder of ISOs sound files (sndfolder) to:\n{val}')
134
+ sys.exit(0)
135
+ if args.clearconf:
136
+ write_conf('RAWROOT', '')
137
+ write_conf('SYNCEDROOT', '')
138
+ write_conf('SNDROOT', '')
139
+ write_conf('PROXIES', '')
140
+ print_out_conf('','','','')
141
+ sys.exit(0)
142
+ if args.showconf:
143
+ get_proj()
144
+ print_out_conf(*get_proj(True))
145
+ sys.exit(0)
146
+ # roots = get_proj(False)
147
+ # if any([r == '' for r in roots]):
148
+ # print("Can't sync if some folders are not set:")
149
+ # print_out_conf(*get_proj())
150
+ # print('Bye.')
151
+ # sys.exit(0)
152
+ # for r in roots:
153
+ # if not r.is_absolute():
154
+ # print(f'\rError: folder {r} must be an absolute path. Bye')
155
+ # sys.exit(0)
156
+ # if not r.exists():
157
+ # print(f'\rError: folder {r} does not exist. Bye')
158
+ # sys.exit(0)
159
+ # if not r.is_dir():
160
+ # print(f'\rError: path {r} is not a folder. Bye')
161
+ # sys.exit(0)
162
+ # raw_root, synced_root, snd_root = roots
163
+ # if args.sub_dir != None:
164
+ # top_dir = args.sub_dir
165
+ # logger.debug(f'sub _dir: {args.sub_dir}')
166
+ # if not Path(top_dir).exists():
167
+ # print(f"\rError: folder {top_dir} doesn't exist, bye.")
168
+ # sys.exit(0)
169
+ # else:
170
+ # top_dir = raw_root
171
+ # if args.resync:
172
+ # clear_log()
173
+
174
+ if __name__ == '__main__':
175
+ main()