tictacsync 0.1a14__py3-none-any.whl → 1.4.4b0__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.

@@ -16,7 +16,10 @@ M4V SVI 3GP 3G2 MXF ROQ NSV FLV F4V F4P F4A F4B 3GP AA AAC AAX ACT AIFF ALAC
16
16
  AMR APE AU AWB DSS DVF FLAC GSM IKLAX IVS M4A M4B M4P MMF MP3 MPC MSV NMF
17
17
  OGG OGA MOGG OPUS RA RM RAW RF64 SLN TTA VOC VOX WAV WMA WV WEBM 8SVX CDA MOV AVI BWF""".split()
18
18
 
19
- import ffmpeg
19
+ audio_ext = 'aiff wav mp3'.split()
20
+
21
+ from dataclasses import dataclass
22
+ import ffmpeg, os, sys, shutil
20
23
  from os import listdir
21
24
  from os.path import isfile, join, isdir
22
25
  from collections import namedtuple
@@ -25,7 +28,7 @@ from pprint import pformat
25
28
  # from collections import defaultdict
26
29
  from loguru import logger
27
30
  # import pathlib, os.path
28
- import sox, tempfile
31
+ import sox, tempfile, platformdirs, filecmp
29
32
  # from functools import reduce
30
33
  from rich import print
31
34
  from itertools import groupby
@@ -33,9 +36,17 @@ from itertools import groupby
33
36
  # import distance
34
37
  try:
35
38
  from . import multi2polywav
39
+ from . import mamsync
40
+ from . import mamconf
41
+ from . import yaltc
36
42
  except:
37
43
  import multi2polywav
44
+ import mamsync
45
+ import mamconf
46
+ import yaltc
38
47
 
48
+ MCCDIR = 'SyncedMulticamClips'
49
+ SYNCEDFOLDER = 'SyncedMedia'
39
50
 
40
51
  # utility for accessing pathnames
41
52
  def _pathname(tempfile_or_path):
@@ -53,40 +64,89 @@ def print_grby(grby):
53
64
  print('\ngrouped by %s:'%key)
54
65
  for e in keylist:
55
66
  print(' ', e)
56
-
57
- Device = namedtuple("Device", ["UID", "folder", "name", "type"])
58
-
59
- def media_dict_from_path(p):
60
- # return dict of metadata for mediafile using ffprobe
61
- # probe = ffmpeg.probe(p)
62
- try:
63
- probe = ffmpeg.probe(p)
64
- logger.debug('probing %s'%p)
65
- except ffmpeg._run.Error:
66
- print('Error ffprobing %s\nquitting.'%p)
67
+ @dataclass
68
+ class Tracks:
69
+ # track numbers start at 1 for first track (as needed by sox,1 Based Index)
70
+ ttc: int # track number of TicTacCode signal
71
+ unused: list # of unused tracks
72
+ stereomics: list # of stereo mics track tuples (Lchan#, Rchan#)
73
+ mix: list # of mixed tracks, if a pair, order is L than R
74
+ others: list #of all other tags: (tag, track#) tuples
75
+ rawtrx: list # list of strings read from file
76
+ error_msg: str # 'None' if none
77
+ lag_values: list # list of lags in ms, entry is None if not specified.
78
+ # UTC_timestamp: str # to the nearest minute ISO 8601 date and time e.g.: "2007-04-05T14:30Z"
79
+
80
+
81
+ @dataclass
82
+ class Device:
83
+ UID: int
84
+ folder: Path # media's parent folder
85
+ name: str
86
+ dev_type: str # CAM or REC
87
+ n_chan: int
88
+ ttc: int # zero based index?
89
+ tracks: Tracks
90
+ sampling_freq: float # fps if cam
91
+ def __hash__(self):
92
+ return self.UID
93
+ def __eq__(self, other):
94
+ return self.UID == other
95
+
96
+ @dataclass
97
+ class Media:
98
+ """A custom data type that represents data for a media file.
99
+ """
100
+ path: Path
101
+ device: Device
102
+
103
+ def media_at_path(input_structure, p):
104
+ # return Media object for mediafile using ffprobe
105
+ dev_UID, dt, sf = get_device_ffprobe_UID(p)
106
+ dev_name = None
107
+ logger.debug('ffprobe dev_UID:%s dt:%s sf:%s'%(dev_UID, dt,sf))
108
+ if input_structure == 'ordered':
109
+ dev_name = p.parent.name
110
+ if dev_UID is None:
111
+ dev_UID = hash(dev_name)
112
+ if dt == 'CAM':
113
+ streams = ffmpeg.probe(p)['streams']
114
+ audio_streams = [
115
+ stream
116
+ for stream
117
+ in streams
118
+ if stream['codec_type']=='audio'
119
+ ]
120
+ if len(audio_streams) > 1:
121
+ print('\nfor [gold1]%s[/gold1], ffprobe gave multiple audio streams, quitting.'%p)
67
122
  quit()
68
-
69
- dev_UID, dev_type = get_device_ffprobe_UID(p)
70
- time_base = eval(probe['streams'][0]['time_base'])
71
- duration_in_secondes = float(probe['format']['duration'])
72
- sample_length = round(duration_in_secondes/time_base)
73
- return {
74
- 'path' : p,
75
- 'sample length' : sample_length,
76
- 'dev' : Device(dev_UID, p.parent, None, dev_type),
77
- }
123
+ # raise Exception('ffprobe gave multiple audio streams?')
124
+ if len(audio_streams) == 0:
125
+ print('ffprobe gave no audio stream for [gold1]%s[/gold1], quitting.'%p)
126
+ quit()
127
+ # raise Exception('ffprobe gave no audio stream for %s, quitting'%p)
128
+ audio_str = audio_streams[0]
129
+ n = audio_str['channels']
130
+ # pprint(ffmpeg.probe(p))
131
+ else:
132
+ n = sox.file_info.channels(_pathname(p)) # eg 2
133
+ logger.debug('for file %s dev_UID established %s'%(p.name, dev_UID))
134
+ device = Device(UID=dev_UID, folder=p.parent, name=dev_name, dev_type=dt,
135
+ n_chan=n, ttc=None, sampling_freq=sf, tracks=None)
136
+ logger.debug('for path: %s, device:%s'%(p,device))
137
+ return Media(p, device)
78
138
 
79
139
  def get_device_ffprobe_UID(file):
80
140
  """
81
141
  Tries to find an unique hash integer identifying the device that produced
82
142
  the file based on the string inside ffprobe metadata without any
83
- reference to date nor length nor time. Find out with ffprobe the type
143
+ reference to date, location, length or time. Find out with ffprobe the type
84
144
  of device: CAM or REC for videocamera or audio recorder.
85
145
 
86
146
  Device UIDs are used later in Montage._get_concatenated_audiofile_for()
87
147
  for grouping each audio or video clip along its own timeline track.
88
148
 
89
- Returns a tuple: (UID, CAM|REC)
149
+ Returns a tuple: (UID, CAM|REC, sampling_freq)
90
150
 
91
151
  If an ffmpeg.Error occurs, returns (None, None)
92
152
  if no UID is found, but device type is identified, returns (None, CAM|REC)
@@ -99,63 +159,66 @@ def get_device_ffprobe_UID(file):
99
159
  except ffmpeg.Error as e:
100
160
  print('ffmpeg.probe error')
101
161
  print(e.stderr, file)
102
- return None, None
162
+ return None, None #-----------------------------------------------------
103
163
  # fall back to folder name
164
+ logger.debug('ffprobe %s'%probe)
104
165
  streams = probe['streams']
166
+ video_streams = [st for st in streams if st['codec_type'] == 'video']
167
+ audio_streams = [st for st in streams if st['codec_type'] == 'audio']
168
+ if len(video_streams) > 1:
169
+ print('\nmore than one video stream for %s... quitting'%file)
170
+ quit()
171
+ if len(audio_streams) != 1:
172
+ print('\nnbr of audio stream for %s not 1 ... quitting'%file)
173
+ quit()
105
174
  codecs = [stream['codec_type'] for stream in streams]
106
- device_type = 'CAM' if 'video' in codecs else 'REC'
175
+ # cameras have two streams: video AND audio
176
+ device_type = 'CAM' if len(video_streams) == 1 else 'REC'
177
+ if device_type == 'CAM':
178
+ sampling_freq = eval(video_streams[0]['r_frame_rate'])
179
+ else:
180
+ sampling_freq = float(audio_streams[0]['sample_rate'])
107
181
  format_dict = probe['format'] # all files should have this
108
182
  if 'tags' in format_dict:
109
183
  probe_string = pformat(format_dict['tags'])
110
184
  probe_lines = [l for l in probe_string.split('\n')
111
185
  if '_time' not in l
112
186
  and 'time_' not in l
187
+ and 'location' not in l
113
188
  and 'date' not in l ]
114
189
  # this removes any metadata related to the file
115
190
  # but keeps metadata related to the device
191
+ logger.debug('probe_lines %s'%probe_lines)
116
192
  UID = hash(''.join(probe_lines))
117
193
  else:
118
194
  UID = None
195
+ if UID == 0: # empty probe_lines from Audacity ?!?
196
+ UID = None
119
197
  logger.debug('ffprobe_UID is: %s'%UID)
120
- return UID, device_type
121
-
198
+ return UID, device_type, sampling_freq
122
199
 
123
200
  class Scanner:
124
201
  """
125
202
  Class that encapsulates scanning of the directory given as CLI argument.
126
- Depending on the IO structure choosen (loose|folder_is_device), enforce some directory
127
- structure (or not). Build a list of media files found and a try to
128
- indentify uniquely the device used to record each media file.
203
+ Depending on the input_structure detected (loose|ordered), enforce
204
+ some directory structure (or not). Build a list of media files found and a
205
+ try to indentify uniquely the device used to record each media file.
129
206
 
130
207
  Attributes:
131
208
 
132
209
  input_structure: string
133
-
134
- Any of
210
+ Any of:
135
211
  'loose'
136
212
  all files audio + video are in top folder
137
- 'folder_is_device'
213
+ 'ordered'
138
214
  eg for multicam on Davinci Resolve
215
+ input_structure is set in scan_media_and_build_devices_UID()
139
216
 
140
217
  top_directory : string
218
+ String of path where to start searching for media files.
141
219
 
142
- String of path of where to start searching for media files.
143
-
144
- top_dir_has_multicam : bool
145
-
146
- If top dir is folder structures AND more than on cam
147
-
148
- devices_names : dict of str
149
-
150
- more evocative names for each device, keys are same as
151
- self.devices_UID_count
152
-
153
- found_media_files: list of dicts
154
- {
155
- 'path' : as is ,
156
- 'sample length' : as is
157
- 'dev' : Device namedtuple
158
- }
220
+ found_media_files: list of dataclass Media instances encapsulating
221
+ the pathlibPath and the device (of Device dataclass).
159
222
  """
160
223
 
161
224
  def __init__(
@@ -171,202 +234,332 @@ class Scanner:
171
234
  self.found_media_files = []
172
235
  self.stay_silent = stay_silent
173
236
 
174
-
175
237
  def get_devices_number(self):
176
238
  # how many devices have been found
177
- return len(set([m['dev'].UID for m in self.found_media_files]))
239
+ return len(set([m.device.UID for m in self.found_media_files]))
240
+
241
+ def get_devices(self):
242
+ return set([m.device for m in self.found_media_files])
243
+
244
+ def get_media_for_device(self, dev):
245
+ return [m for m in self.found_media_files if m.device == dev]
246
+
247
+ def CAM_numbers(self):
248
+ devices = [m.device for m in self.found_media_files]
249
+ CAMs = [d for d in devices if d.dev_type == 'CAM']
250
+ return len(set(CAMs))
178
251
 
179
- def scan_media_and_build_devices_UID(self, recursive=True):
252
+ def scan_media_and_build_devices_UID(self, synced_root = None):
180
253
  """
181
- Scans self.top_directory recursively for files with known audio-video
254
+ Scans Scanner.top_directory recursively for files with known audio-video
182
255
  extensions. For each file found, a device fingerprint is obtained from
183
256
  their ffprobe result to ID the device used.
184
257
 
185
-
186
258
  Also looked for are multifile recordings: files with the exact same
187
259
  length. When done, calls
188
260
 
189
261
  Returns nothing
190
262
 
191
- Populates Scanner.found_media_files, a list of dict as
192
- {
193
- 'path' : as is ,
194
- 'sample length' : as is
195
- 'dev' : Device namedtuple
196
- }
263
+ Populates Scanner.found_media_files, a list of Media objects
197
264
 
198
- Sets input_structure = 'loose'|'folder_is_device'
265
+ Sets Scanner.input_structure = 'loose'|'ordered'
199
266
 
200
267
  """
201
- visible = [f for f in listdir(self.top_directory) if f[0] != '.']
202
- logger.debug('visible: %s'%visible)
203
- are_dir = all([isdir(join(self.top_directory, f)) for f in visible])
204
- are_files = all([isfile(join(self.top_directory, f)) for f in visible])
205
- # onlyfiles = [f for f in listdir(self.top_directory) if isfile()]
206
-
207
- logger.debug('dir: %s'%[isdir(join(self.top_directory, f)) for f in visible])
208
- if are_dir:
209
- print('\nAssuming those are device folders: ',end='')
210
- [print(f, end=', ') for f in visible[:-1]]
211
- print('%s.\n'%visible[-1])
212
- self.input_structure = 'folder_is_device'
213
- else: # are_files
268
+ logger.debug(f'on entry synced_root: {synced_root}')
269
+ if synced_root != None: # mam mode
270
+ p = Path(platformdirs.user_data_dir('mamsync', 'plutz'))/mamconf.LOG_FILE
271
+ with open(p, 'r') as fh:
272
+ done = set(fh.read().split()) # sets of strings of abs path
273
+ logger.debug(f'done clips: {pformat(done)}')
274
+ files = Path(self.top_directory).rglob('*')
275
+ clip_paths = []
276
+ some_done = False
277
+ for raw_path in files:
278
+ if raw_path.suffix[1:] in av_file_extensions:
279
+ if SYNCEDFOLDER not in raw_path.parts: # SyncedMedia
280
+ if MCCDIR not in raw_path.parts: # SyncedMulticamClips
281
+ if '_ISO' not in [part[-4:] for part in raw_path.parts]: # exclude ISO wav files
282
+ if synced_root != None and str(raw_path) in done:
283
+ logger.debug(f'{raw_path} done')
284
+ some_done = True
285
+ continue
286
+ else:
287
+ clip_paths.append(raw_path)
288
+ if some_done:
289
+ print('Somme media files were already synced...')
290
+ logger.debug('found media files %s'%clip_paths)
291
+ if len(clip_paths) == 0:
292
+ print('No media found, bye.')
293
+ sys.exit(0)
294
+ # self.found_media_files = []
295
+ # self.input_structure = 'loose'
296
+ # return
297
+ parents = [p.parent for p in clip_paths]
298
+ logger.debug('found parents %s'%pformat(parents))
299
+ # True if all elements are identical
300
+ AV_files_have_same_parent = parents.count(parents[0]) == len(parents)
301
+ logger.debug('AV_files_have_same_parent %s'%AV_files_have_same_parent)
302
+ if AV_files_have_same_parent:
303
+ # all media (video + audio) are in a same folder, so this is loose
214
304
  self.input_structure = 'loose'
215
- if not are_dir and not are_files:
216
- print('\nInput structure mixed up, files AND folders at top level')
217
- print(' %s, quitting.\n'%self.top_directory)
218
- quit()
219
- # quit()
220
- files = Path(self.top_directory).rglob('*.*')
221
- paths = [
222
- p
223
- for p in files
224
- if p.suffix[1:] in av_file_extensions
225
- and 'SyncedMedia' not in p.parts
226
- ]
227
- for p in paths:
228
- new_media = media_dict_from_path(p) # dev UID set here
229
- self.found_media_files.append(new_media)
230
- logger.debug('Scanner.found_media_files = %s'%self.found_media_files)
231
- if self.input_structure == 'folder_is_device':
232
- self._enforce_folder_is_device()
233
- self._use_folder_as_device_name()
305
+ # for now (TO DO?) 'loose' == no multi-cam
306
+ # self.top_dir_has_multicam = False
234
307
  else:
235
- self.top_dir_has_multicam = False
308
+ # check later if inside each folder, media have same device
309
+ # for now, we'll guess structure is 'ordered'
310
+ self.input_structure = 'ordered'
311
+ for p in clip_paths:
312
+ new_media = media_at_path(self.input_structure, p) # dev UID set here
313
+ self.found_media_files.append(new_media)
314
+ # for non UIDed try building UID from filenam
315
+ def _try_name_from_files(medias):
316
+ # return common first strings in filename
317
+ def _all_identical(a_list):
318
+ return a_list.count(a_list[0]) == len(a_list)
319
+ names = [m.path.name for m in medias]
320
+ transposed_names = list(map(list, zip(*names)))
321
+ same = list(map(_all_identical, transposed_names))
322
+ try:
323
+ first_diff = same.index(False)
324
+ except:
325
+ return names[0].split('.')[0]
326
+ return names[0][:first_diff]
327
+ no_device_UID_medias = [m for m in self.found_media_files
328
+ if not m.device.UID]
329
+ logger.debug('those media have no device UID %s'%no_device_UID_medias)
330
+ if no_device_UID_medias:
331
+ # will guess a device name from media filenames
332
+ logger.debug('no_device_UID_medias %s'%no_device_UID_medias)
333
+ start_string = _try_name_from_files(no_device_UID_medias)
334
+ if len(start_string) < 2:
335
+ print('\nError, cant identify the device for those files:')
336
+ [print('%s, '%m.path.name, end='') for m in no_device_UID_medias]
337
+ print('\n')
338
+ sys.exit(1)
339
+ one_device = no_device_UID_medias[0].device
340
+ one_device.name = start_string
341
+ if not one_device.UID:
342
+ one_device.UID = hash(start_string)
343
+ print('\nWarning, guessing a device ID for those files:')
344
+ [print('[gold1]%s[/gold1], '%m.path.name, end='') for m
345
+ in no_device_UID_medias]
346
+ print('UID: [gold1]%s[/gold1]'%start_string)
347
+ for m in no_device_UID_medias:
348
+ m.device = one_device
349
+ logger.debug('new device added %s'%self.found_media_files)
350
+ logger.debug('Scanner.found_media_files = %s'%pformat(self.found_media_files))
351
+ if self.input_structure == 'ordered':
352
+ self._confirm_folders_have_same_device()
353
+ # [TODO] move this where Recordings have been TCed for any tracks timestamps
354
+ # <begin>
355
+ # devices = set([m.device for m in self.found_media_files])
356
+ # audio_devices = [d for d in devices if d.dev_type == 'REC']
357
+ # for recorder in audio_devices:
358
+ # # process tracks.txt for audio recorders
359
+ # recorder.tracks = self._get_tracks_from_file(recorder)
360
+ # # logging only:
361
+ # if recorder.tracks:
362
+ # if not all([lv == None for lv in recorder.tracks.lag_values]):
363
+ # logger.debug('%s has lag_values %s'%(
364
+ # recorder.name, recorder.tracks.lag_values))
365
+ # </end>
366
+ # check if device is in fact two parents up (and parent = ROLLxx):
367
+ # Group media by folder 2up and verify all media for each
368
+ # group have same device.
369
+ folder2up = lambda m: m.path.parent.parent
370
+ # logger.debug('folder2up: %s'%pformat([folder2up(m) for m
371
+ # in self.found_media_files]))
372
+ medias = sorted(self.found_media_files, key=folder2up)
373
+ # build lists for multiple reference of iterators
374
+ media_grouped_by_folder2up = [ (k, list(iterator)) for k, iterator
375
+ in groupby(medias, folder2up)]
376
+ logger.debug('media_grouped_by_folder2up: %s'%pformat(
377
+ media_grouped_by_folder2up))
378
+ folder_and_UIDs = [(f, [m.device.UID for m in medias])
379
+ for f, medias in media_grouped_by_folder2up]
380
+ logger.debug('devices: %s'%pformat(folder_and_UIDs))
381
+ def _multiple_and_same(a_list):
382
+ same = a_list.count(a_list[0]) == len(a_list)
383
+ return len(a_list) > 1 and same
384
+ folders_with_same_dev = [(f.name, UIDs[0]) for f, UIDs
385
+ in folder_and_UIDs
386
+ if _multiple_and_same(UIDs)]
387
+ logger.debug('folders_with_same_dev: %s'%pformat(folders_with_same_dev))
388
+ for name, UID in folders_with_same_dev:
389
+ for m in self.found_media_files:
390
+ if m.device.UID == UID:
391
+ m.device.name = name
392
+ no_name_devices = [m.device for m in self.found_media_files
393
+ if not m.device.name]
394
+ # possible if self.input_structure == 'loose'
395
+ def _try_name_from_metadata(media): # unused for now
396
+ # search model and make from fprobe
397
+ file = Path(media.path)
398
+ logger.debug('trying to find maker model for %s'%file)
399
+ try:
400
+ probe = ffmpeg.probe(file)
401
+ except ffmpeg.Error as e:
402
+ print('ffmpeg.probe error')
403
+ print(e.stderr, file)
404
+ return None, None #-----------------------------------------------------
405
+ # fall back to folder name
406
+ logger.debug('ffprobe %s'%pformat(probe))
407
+ # [TO BE COMPLETED]
408
+ # could reside in ['format','tags','com.apple.quicktime.model'],
409
+ # or ['format','tags','model'],
410
+ # or ['streams'][0]['tags']['vendor_id']) :-(
411
+ for anon_dev in no_name_devices:
412
+ medias = self.get_media_for_device(anon_dev)
413
+ guess_name = _try_name_from_files(medias)
414
+ # print('dev %s has no name, guessing %s'%(anon_dev, guess_name))
415
+ logger.debug('dev %s has no name, guessing %s'%(anon_dev, guess_name))
416
+ anon_dev.name = guess_name
236
417
  pprint_found_media_files = pformat(self.found_media_files)
237
418
  logger.debug('scanner.found_media_files = %s'%pprint_found_media_files)
419
+ logger.debug('all devices %s'%[m.device for m in self.found_media_files])
420
+ dev_is_REC = [m.device.dev_type == 'REC' for m in self.found_media_files]
421
+ if not any(dev_is_REC): # no audio recordings!
422
+ print('\rNo audio recording found, nothing to sync, bye.')
423
+ sys.exit(0)
238
424
 
239
- def _use_folder_as_device_name(self):
425
+ def _confirm_folders_have_same_device(self):
240
426
  """
241
- For each media in self.found_media_files replace existing Device.name by
242
- folder name.
243
-
244
- Returns nothing
245
- """
246
- for m in self.found_media_files:
247
- folder_name = m['path'].parent.name
248
- # known_folder_name = [media['dev'].UID for media
249
- # in self.found_media_files]
250
- # if folder_name not in known_folder_name:
251
- # print('l238', m['dev'])
252
- m['dev'] = m['dev']._replace(name=folder_name)
253
- # print('l240', m['dev'])
254
- # m['dev'].UID = folder_name
255
- # else:
256
- # print('already existing folder name: [gold1]%s[/gold1] please change it and rerun'%m['path'].parent)
257
- # quit()
258
- logger.debug(self.found_media_files)
259
-
260
- def _enforce_folder_is_device(self):
261
- """
262
-
263
- Checks for files in self.found_media_files for structure as following.
427
+ Since input_structure == 'ordered',
428
+ checks for files in self.found_media_files for structure as following.
264
429
 
265
430
  Warns user and quit program for:
266
431
  A- folders with mix of video and audio
267
432
  B- folders with mix of uniquely identified devices and unUIDied ones
268
- C- folders with mixed audio (or video) devices
433
+ C- folders with mixed audio an video files
269
434
 
270
435
  Warns user but proceeds for:
271
436
  D- folder with only unUIDied files (overlaps will be check later)
272
437
 
438
+ Changes self.input_structure to 'loose' if a folder contains files
439
+ from different devices.
440
+
273
441
  Proceeds silently if
274
442
  E- all files in the folder are from the same device
275
443
 
276
444
  Returns nothing
277
445
  """
278
- def _list_duplicates(seq):
279
- seen = set()
280
- seen_add = seen.add
281
- # adds all elements it doesn't know yet to seen and all other to seen_twice
282
- seen_twice = set( x for x in seq if x in seen or seen_add(x) )
283
- # turn the set into a list (as requested)
284
- return list( seen_twice )
285
- folder_key = lambda m: m['path'].parent
286
- medias = sorted(self.found_media_files, key=folder_key)
287
- # build lists for multiple reference of iterators
288
- media_grouped_by_folder = [ (k, list(iterator)) for k, iterator
289
- in groupby(medias, folder_key)]
290
- logger.debug('media_grouped_by_folder %s'%media_grouped_by_folder)
291
- complete_path_folders = [e[0] for e in media_grouped_by_folder]
292
- name_of_folders = [p.name for p in complete_path_folders]
293
- logger.debug('complete_path_folders with media files %s'%complete_path_folders)
294
- logger.debug('name_of_folders with media files %s'%name_of_folders)
295
- # unique_folder_names = set(name_of_folders)
296
- repeated_folders = _list_duplicates(name_of_folders)
297
- logger.debug('repeated_folders %s'%repeated_folders)
298
- if repeated_folders:
299
- print('There are conflicts for some repeated folder names:')
300
- for f in [str(p) for p in repeated_folders]:
301
- print(' [gold1]%s[/gold1]'%f)
302
- print('Here are the complete paths:')
303
- for f in [str(p) for p in complete_path_folders]:
304
- print(' [gold1]%s[/gold1]'%f)
305
- print('please rename and rerun. Quitting..')
306
- quit()
307
- # print(media_grouped_by_folder)
446
+ def _exit_on_folder_name_clash():
447
+ # Check media parent folders are unique
448
+ # returns media_grouped_by_folder
449
+ def _list_duplicates(seq):
450
+ seen = set()
451
+ seen_add = seen.add
452
+ # adds all elements it doesn't know yet to seen and all other to seen_twice
453
+ seen_twice = set( x for x in seq if x in seen or seen_add(x) )
454
+ # turn the set into a list (as requested)
455
+ return list( seen_twice )
456
+ folder_key = lambda m: m.path.parent
457
+ medias = sorted(self.found_media_files, key=folder_key)
458
+ # build lists for multiple reference of iterators
459
+ media_grouped_by_folder = [ (k, list(iterator)) for k, iterator
460
+ in groupby(medias, folder_key)]
461
+ logger.debug('media_grouped_by_folder %s'%pformat(
462
+ media_grouped_by_folder))
463
+ complete_path_folders = [e[0] for e in media_grouped_by_folder]
464
+ name_of_folders = [p.name for p in complete_path_folders]
465
+ logger.debug('complete_path_folders with media files %s'%
466
+ complete_path_folders)
467
+ logger.debug('name_of_folders with media files %s'%name_of_folders)
468
+ # unique_folder_names = set(name_of_folders) [TODO] is this useful ?
469
+ # repeated_folders = _list_duplicates(name_of_folders)
470
+ # logger.debug('repeated_folders %s'%repeated_folders)
471
+ # if repeated_folders:
472
+ # print('There are conflicts for some repeated folder names:')
473
+ # for f in [str(p) for p in repeated_folders]:
474
+ # print(' [gold1]%s[/gold1]'%f)
475
+ # print('Here are the complete paths:')
476
+ # for f in [str(p) for p in complete_path_folders]:
477
+ # print(' [gold1]%s[/gold1]'%f)
478
+ # print('please rename and rerun. Quitting..')
479
+ # sys.exit(1) ####################################################
480
+ return media_grouped_by_folder
481
+ media_grouped_by_folder = _exit_on_folder_name_clash()
308
482
  n_CAM_folder = 0
309
483
  for folder, list_of_medias_in_folder in media_grouped_by_folder:
310
- # list_of_medias_in_folder = list(media_files_same_folder_iterator)
311
484
  # check all medias are either video or audio recordings in folder
312
485
  # if not, warn user and quit.
313
- dev_types = set([m['dev'].type for m in list_of_medias_in_folder])
314
- logger.debug('dev_types %s'%dev_types)
486
+ dev_types = set([m.device.dev_type for m in list_of_medias_in_folder])
487
+ logger.debug('dev_types for folder%s: %s'%(folder,dev_types))
315
488
  if dev_types == {'CAM'}:
316
489
  n_CAM_folder += 1
317
490
  if len(dev_types) != 1:
318
491
  print('\nProblem while scanning for media files. In [gold1]%s[/gold1]:'%folder)
319
492
  print('There is a mix of video and audio files:')
320
- [print('[gold1]%s[/gold1]'%m['path'].name, end =', ')
493
+ [print('[gold1]%s[/gold1]'%m.path.name, end =', ')
321
494
  for m in list_of_medias_in_folder]
322
495
  print('\nplease move them in exclusive folders and rerun.\n')
323
- quit()
496
+ sys.exit(1) ######################################################
324
497
  unidentified = [m for m in list_of_medias_in_folder
325
- if m['dev'].UID == None]
498
+ if m.device.UID == None]
326
499
  UIDed = [m for m in list_of_medias_in_folder
327
- if m['dev'].UID != None]
500
+ if m.device.UID != None]
328
501
  logger.debug('devices in folder %s:'%folder)
329
- logger.debug(' media with unknown devices %s'%unidentified)
330
- logger.debug(' media with UIDed devices %s'%UIDed)
502
+ logger.debug(' media with unknown devices %s'%pformat(unidentified))
503
+ logger.debug(' media with UIDed devices %s'%pformat(UIDed))
331
504
  if len(unidentified) != 0 and len(UIDed) != 0:
332
505
  print('\nProblem while grouping files in [gold1]%s[/gold1]:'%folder)
333
506
  print('There is a mix of unidentifiable and identified devices.')
334
507
  print('Is this file:')
335
508
  for m in unidentified:
336
- print(' [gold1]%s[/gold1]'%m['path'].name)
509
+ print(' [gold1]%s[/gold1]'%m.path.name)
337
510
  answer = input("In the right folder?")
338
511
  if answer.upper() in ["Y", "YES"]:
339
512
  continue
340
513
  elif answer.upper() in ["N", "NO"]:
341
514
  # Do action you need
342
515
  print('please move the following files in a folder named appropriately:\n')
343
- quit()
516
+ sys.exit(1) ################################################
344
517
  # if, in a folder, there's a mix of different identified devices,
345
518
  # Warn user and quit.
346
- # devices = set([m['dev'].UID for m in list_of_medias_in_folder])
519
+ UIDs = [m.device.UID for m in UIDed]
520
+ all_same_device = UIDs.count(UIDs[0]) == len(UIDs)
521
+ logger.debug('UIDs in %s: %s. all_same_device %s'%(folder,
522
+ pformat(UIDs), all_same_device))
523
+ if not all_same_device:
524
+ self.input_structure = 'loose'
525
+ # self.top_dir_has_multicam = False
526
+ logger.debug('changed input_structure to loose')
527
+ # device name should be generated (it isn't the folder name...)
528
+ distinct_UIDS = set(UIDs)
529
+ n_UIDs = len(distinct_UIDS)
530
+ logger.debug('There are %i UIDs: %s'%(n_UIDs, distinct_UIDS))
531
+ # Buid CAM01, CAM02 or REC01, REC02.
532
+ # Get dev type from first media in list
533
+ devT = UIDed[0].device.dev_type # 'CAM' or 'REC'
534
+ generic_names = [devT + str(i).zfill(2) for i in range(n_UIDs)]
535
+ devUIDs_names = dict(zip(distinct_UIDS, generic_names))
536
+ logger.debug('devUIDs_names %s'%pformat(devUIDs_names))
537
+ # rename
538
+ for m in UIDed:
539
+ m.device.name = devUIDs_names[m.device.UID]
540
+ logger.debug('new name %s'%m.device.name)
347
541
  if len(dev_types) != 1:
348
542
  print('\nProblem while scanning for media files. In [gold1]%s[/gold1]:'%folder)
349
543
  print('There is a mix of files from different devices:')
350
- [print('[gold1]%s[/gold1]'%m['path'].name, end =', ')
544
+ [print('[gold1]%s[/gold1]'%m.path.name, end =', ')
351
545
  for m in list_of_medias_in_folder]
352
546
  print('\nplease move them in exclusive folders and rerun.\n')
353
- quit()
547
+ sys.exit(1) ####################################################
354
548
  if len(unidentified) == len(list_of_medias_in_folder):
355
549
  # all unidentified
356
550
  if len(unidentified) > 1:
357
551
  print('Assuming those files are from the same device:')
358
- [print('[gold1]%s[/gold1]'%m['path'].name, end =', ')
552
+ [print('[gold1]%s[/gold1]'%m.path.name, end =', ')
359
553
  for m in unidentified]
360
554
  print('\nIf not, there\'s a risk of error: put them in exclusive folders and rerun.')
361
555
  # if we are here, the check is done: either
362
556
  # all files in folder are from unidentified device or
363
557
  # all files in folder are from the same identified device
364
558
  logger.debug('n_CAM_folder %i'%n_CAM_folder)
365
- self.top_dir_has_multicam = n_CAM_folder > 1
366
- logger.debug('top_dir_has_multicam: %s'%self.top_dir_has_multicam)
367
559
  return
368
560
 
369
561
 
370
562
 
371
563
 
372
564
 
565
+