tictacsync 1.2.0b0__py3-none-any.whl → 1.3.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -0,0 +1,352 @@
1
+ import json, pathlib, itertools, os, re, ffmpeg
2
+ import argparse, platformdirs, configparser, sys
3
+ from loguru import logger
4
+ from pprint import pformat
5
+ from dataclasses import dataclass
6
+ from rich import print
7
+
8
+ try:
9
+ from . import mamconf
10
+ except:
11
+ import mamconf
12
+
13
+ dev = 'Cockos Incorporated'
14
+ app ='REAPER'
15
+
16
+ REAPER_SCRIPT_LOCATION = pathlib.Path(platformdirs.user_data_dir(app, dev)) / 'Scripts' / 'Atomic'
17
+
18
+ REAPER_LUA_CODE = """reaper.Main_OnCommand(40577, 0) -- lock left/right move
19
+ reaper.Main_OnCommand(40569, 0) -- lock enabled
20
+ local function placeWavsBeginingAtTrack(clip, start_idx)
21
+ for i, file in ipairs(clip.files) do
22
+ local track_idx = start_idx + i - 1
23
+ local track = reaper.GetTrack(nil,track_idx-1)
24
+ reaper.SetOnlyTrackSelected(track)
25
+ local left_trim = clip.in_time - clip.start_time
26
+ local where = clip.timeline_pos - left_trim
27
+ reaper.SetEditCurPos(where, false, false)
28
+ reaper.InsertMedia(file, 0 )
29
+ local item_cnt = reaper.CountTrackMediaItems( track )
30
+ local item = reaper.GetTrackMediaItem( track, item_cnt-1 )
31
+ local take = reaper.GetTake(item, 0)
32
+ -- reaper.GetSetMediaItemTakeInfo_String(take, "P_NAME", clip.name, true)
33
+ local pos = reaper.GetMediaItemInfo_Value(item, "D_POSITION")
34
+ reaper.BR_SetItemEdges(item, clip.timeline_pos, clip.timeline_pos + clip.cut_duration)
35
+ reaper.SetMediaItemInfo_Value(item, "C_LOCK", 2)
36
+ end
37
+ end
38
+
39
+ --cut here--
40
+
41
+ sample of the clips nested table (this will be discarded)
42
+ each clip has an EDL info table plus a sequence of ISO files:
43
+
44
+ clips =
45
+ {
46
+ {
47
+ name="canon24fps01.MOV", start_time=7.25, in_time=21.125, cut_duration=6.875, timeline_pos=3600,
48
+ files=
49
+ {
50
+ "/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/leftCAM/card01/canon24fps01_SND/ISOfiles/Alice_canon24fps01.wav",
51
+ "/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/leftCAM/card01/canon24fps01_SND/ISOfiles/Bob_canon24fps01.wav"
52
+ }
53
+ },
54
+ {name="DSC_8063.MOV", start_time=0.0, in_time=5.0, cut_duration=20.25, timeline_pos=3606.875,
55
+ files={"/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/rightCAM/ROLL01/DSC_8063_SND/ISOfiles/Alice_DSC_8063.wav",
56
+ "/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/rightCAM/ROLL01/DSC_8063_SND/ISOfiles/Bob_DSC_8063.wav"}},
57
+ {name="canon24fps02.MOV", start_time=35.166666666666664, in_time=35.166666666666664, cut_duration=20.541666666666668, timeline_pos=3627.125, files={"/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/leftCAM/card01/canon24fps02_SND/ISOfiles/Alice_canon24fps02.wav",
58
+ "/Users/lutzray/Downloads/SoundForMyMovie/MyBigMovie/day01/leftCAM/card01/canon24fps02_SND/ISOfiles/Bob_canon24fps02.wav"}}
59
+ }
60
+
61
+ --cut here--
62
+ -- make room fro the tracks to come
63
+ amplitude_top = 0
64
+ amplitude_bottom = 0
65
+ for i_clip, cl in pairs(clips) do
66
+ if i_clip%2 ~= 1 then
67
+ amplitude_top = math.max(amplitude_top, #cl.files)
68
+ else
69
+ amplitude_bottom = math.max(amplitude_bottom, #cl.files)
70
+ end
71
+ end
72
+ for i = 1 , amplitude_top + amplitude_bottom + 1 do
73
+ reaper.InsertTrackAtIndex( -1, false ) -- at end
74
+ end
75
+ track_count = reaper.CountTracks(0)
76
+ -- ISOs will be up and down the base_track index
77
+ base_track = track_count - amplitude_bottom
78
+ for iclip, clip in ipairs(clips) do
79
+ start_track_number = base_track
80
+ -- alternating even/odd, odd=below base_track
81
+ if iclip%2 == 0 then -- above base_track, start higher
82
+ start_track_number = base_track - #clip.files
83
+ end
84
+ placeWavsBeginingAtTrack(clip, start_track_number)
85
+ if #clips > 1 then -- interclips editing
86
+ reaper.AddProjectMarker(0, false, clip.timeline_pos, 0, '', -1)
87
+ end
88
+ end
89
+ reaper.SetEditCurPos(3600, false, false)
90
+ reaper.Main_OnCommand(40151, 0)
91
+ if #clips > 1 then -- interclips editing
92
+ -- last marker at the end
93
+ last_clip = clips[#clips]
94
+ reaper.AddProjectMarker(0, false, last_clip.timeline_pos + last_clip.cut_duration, 0, '', -1)
95
+ end
96
+
97
+ """
98
+
99
+ logger.level("DEBUG", color="<yellow>")
100
+ logger.add(sys.stdout, level="DEBUG")
101
+ logger.remove()
102
+
103
+ def parse_and_check_arguments():
104
+ # parses directories from command arguments
105
+ # check for consistencies and warn user and exits,
106
+ # returns parser.parse_args()
107
+ descr = "Parse the submitted OTIO timeline and build a Reaper Script to load the corresponding ISO files from SNDROOT (see mamconf --show)"
108
+ parser = argparse.ArgumentParser(description=descr)
109
+ parser.add_argument(
110
+ "a_file_argument",
111
+ type=str,
112
+ nargs=1,
113
+ help="path of timeline saved under OTIO format"
114
+ )
115
+ parser.add_argument('--interval',
116
+ dest='interval',
117
+ nargs=2,
118
+ help="One or two timecodes, space seperated, delimiting the zone to process (if not specified the whole timeline is processed)")
119
+ args = parser.parse_args()
120
+ logger.debug('args %s'%args)
121
+ return args
122
+
123
+ @dataclass
124
+ class Clip:
125
+ # all time in seconds
126
+ start_time: float # the start time of the clip in
127
+ in_time: float # time of 'in' point, relative to clip start_time
128
+ cut_duration: float
129
+ whole_duration: float # unedited clip duration
130
+ name: str #
131
+ path: str # path of clip
132
+ timeline_pos: float # when on the timeline the clip starts
133
+ ISOdir: None # folder of ISO files for clip
134
+
135
+ def clip_info_from_json(jsoncl):
136
+ """
137
+ parse data from an OTIO json Clip
138
+ https://opentimelineio.readthedocs.io/en/latest/tutorials/otio-serialized-schema.html#clip-2
139
+ returns a list composed of (all times are in seconds):
140
+ st, start time (from clip metadata TC)
141
+ In, the "in time"
142
+ cd, the cut duration
143
+ wl, the whole length of the unedited clip
144
+ the clip file path (string)
145
+ name (string)
146
+ NB: the position on the global timeline is not stored but latter computed from summing cut times
147
+ """
148
+ def _float_time(json_rationaltime):
149
+ return json_rationaltime['value']/json_rationaltime['rate']
150
+ av_range = jsoncl['media_references']['DEFAULT_MEDIA']['available_range']
151
+ src_rg = jsoncl['source_range']
152
+ st = av_range['start_time']
153
+ In = src_rg['start_time']
154
+ cd = src_rg['duration']
155
+ wl = av_range['duration']
156
+ path = jsoncl['media_references']['DEFAULT_MEDIA']['target_url']
157
+ name = jsoncl['media_references']['DEFAULT_MEDIA']['name']
158
+ return Clip(*[_float_time(t) for t in [st, In, cd, wl,]] + \
159
+ [name, path, 0, None])
160
+
161
+ def get_SND_dirs(snd_root):
162
+ # returns all directories found under snd_root
163
+ def _searchDirectory(cwd,searchResults):
164
+ dirs = os.listdir(cwd)
165
+ for dir in dirs:
166
+ fullpath = os.path.join(cwd,dir)
167
+ if os.path.isdir(fullpath):
168
+ searchResults.append(fullpath)
169
+ _searchDirectory(fullpath,searchResults)
170
+ searchResults = []
171
+ _searchDirectory(snd_root,searchResults)
172
+ return searchResults
173
+
174
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "find_and_set_ISO_dir")
175
+ def find_and_set_ISO_dir(clip, SND_dirs):
176
+ """
177
+ SND_dirs contains all the *_SND directories found in snd_root.
178
+ This fct finds out which one corresponds to the clip
179
+ and sets the found path to clip.ISOdir.
180
+ Returns nothing.
181
+ """
182
+ clip_stem = pathlib.Path(clip.path).stem
183
+ logger.debug(f'clip_stem {clip_stem}')
184
+ m = re.match('(.*)v([AB]*)', clip_stem)
185
+ logger.debug(f'{clip_stem} match (.*)v([AB]*) { m.groups() if m != None else None}')
186
+ if m != None:
187
+ clip_stem = m.groups()[0]
188
+ # /MyBigMovie/day01/leftCAM/card01/canon24fps01_SND -> canon24fps01_SND
189
+ names_only = [p.name for p in SND_dirs]
190
+ logger.debug(f'names-only {pformat(names_only)}')
191
+ clip_stem_SND = f'{clip_stem}_SND'
192
+ if clip_stem_SND in names_only:
193
+ where = names_only.index(clip_stem_SND)
194
+ else:
195
+ print(f'Error: OTIO file contains clip not in SYNCEDROOT: {clip_stem} (check with mamconf --show)')
196
+ sys.exit(0)
197
+ complete_path = SND_dirs[where]
198
+ logger.debug(f'found {complete_path}')
199
+ clip.ISOdir = str(complete_path)
200
+
201
+ def gen_lua_table(clips):
202
+ # returns a string defining a lua nested table
203
+ # top level: a sequence of clips
204
+ # a clip has keys: name, start_time, in_time, cut_duration, timeline_pos, files
205
+ # clip.files is a sequence of ISO wav files
206
+ def _list_ISO(dir):
207
+ iso_dir = pathlib.Path(dir)/'ISOfiles'
208
+ ISOs = [f for f in iso_dir.iterdir() if f.suffix.lower() == '.wav']
209
+ # ISOs = [f for f in ISOs if f.name[:2] != 'tc'] # no timecode
210
+ logger.debug(f'ISOs {ISOs}')
211
+ sequence = '{'
212
+ for file in ISOs:
213
+ sequence += f'"{file}",\n'
214
+ sequence += '}'
215
+ return sequence
216
+ lua_clips = '{'
217
+ for cl in clips:
218
+ ISOs = _list_ISO(cl.ISOdir)
219
+ # logger.debug(f'sequence {ISOs}')
220
+ clip_table = f'{{name="{cl.name}", start_time={cl.start_time}, in_time={cl.in_time}, cut_duration={cl.cut_duration}, timeline_pos={cl.timeline_pos}, files={ISOs}}}'
221
+ lua_clips += f'{clip_table},\n'
222
+ logger.debug(f'clip_table {clip_table}')
223
+ lua_clips += '}'
224
+ return lua_clips
225
+
226
+ def read_OTIO_file(f):
227
+ """
228
+ returns framerate and a list of Clip instances parsed from
229
+ the OTIO file passed as (string) argument f;
230
+ warns and exists if more than one video track.
231
+ """
232
+ with open(f) as fh:
233
+ oti = json.load(fh)
234
+ video_tracks = [tr for tr in oti['tracks']['children'] if tr['kind'] == 'Video']
235
+ if len(video_tracks) > 1:
236
+ print(f"Can only process timeline with one video track, this one has {len(video_tracks)}. Bye.")
237
+ sys.exit(0)
238
+ video_track = video_tracks[0]
239
+ clips = [clip_info_from_json(jscl) for jscl in video_track['children']]
240
+ logger.debug(f'clips: {pformat(clips)}')
241
+ # compute each clip global timeline position
242
+ clip_starts = [0] + list(itertools.accumulate([cl.cut_duration for cl in clips]))[:-1]
243
+ # Reaper can't handle negative item position (for the trimmed part)
244
+ # so starts at 1:00:00
245
+ clip_starts = [t + 3600 for t in clip_starts]
246
+ logger.debug(f'clip_starts: {clip_starts}')
247
+ for time, clip in zip(clip_starts, clips):
248
+ clip.timeline_pos = time
249
+ return int(oti['global_start_time']['rate']), clips
250
+
251
+ def reaper_save_action(wav_destination):
252
+ return f"""reaper.GetSetProjectInfo_String(0, "RENDER_FILE","{wav_destination.parent}",true)
253
+ reaper.GetSetProjectInfo_String(0, "RENDER_PATTERN","{wav_destination.name}",true)
254
+ reaper.SNM_SetIntConfigVar("projintmix", 4)
255
+ reaper.Main_OnCommand(40015, 0)
256
+ """
257
+
258
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "complete_clip_path")
259
+ def complete_clip_path(clip_stem, synced_proj):
260
+ match = []
261
+ for (root,dirs,files) in os.walk(synced_proj):
262
+ for f in files:
263
+ p = pathlib.Path(root)/f
264
+ if p.is_symlink() or p.suffix == '.reapeaks':
265
+ continue
266
+ # logger.debug(f'{f}')
267
+ if clip_stem in f.split('.')[0]: # match XYZvA.mov
268
+ match.append(p)
269
+ logger.debug(f'matches {match}')
270
+ if len(match) > 1:
271
+ print(f'Warning, some filenames collide {pformat(match)}, Bye.')
272
+ sys.exit(0)
273
+ if len(match) == 0:
274
+ print(f"Error, didn't find any clip containing *{clip_stem}*. Bye.")
275
+ sys.exit(0)
276
+ return match[0]
277
+
278
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "main")
279
+ def main():
280
+ def _where(a,x):
281
+ # find in which clip time x (in seconds) does fall.
282
+ n = 0
283
+ while n<len(a):
284
+ if a[n].timeline_pos > x:
285
+ break
286
+ else:
287
+ n += 1
288
+ return n-1
289
+ raw_root, synced_root, snd_root, proxies = mamconf.get_proj(False)
290
+ proj_name = pathlib.Path(raw_root).stem
291
+ synced_proj = pathlib.Path(synced_root)/proj_name
292
+ logger.debug(f'proj_name {proj_name}')
293
+ logger.debug(f'will search {snd_root} for ISOs')
294
+ all_SNDROOT_dirs = [pathlib.Path(f) for f in get_SND_dirs(snd_root)]
295
+ # keep only XYZ_SND dirs
296
+ SND_dirs = [p for p in all_SNDROOT_dirs if p.name[-4:] == '_SND']
297
+ logger.debug(f'SND_dirs {pformat(SND_dirs)}')
298
+ args = parse_and_check_arguments()
299
+ file_arg = pathlib.Path(args.a_file_argument[0])
300
+ # check if its intraclip or interclip sound edit
301
+ # if otio file then interclip
302
+ if file_arg.suffix == '.otio':
303
+ logger.debug('interclip sound edit, filling up clips')
304
+ _, clips = read_OTIO_file(file_arg)
305
+ [find_and_set_ISO_dir(clip, SND_dirs) for clip in clips]
306
+ else:
307
+ logger.debug('intraclip sound edit, clips will have one clip')
308
+ # traverse synced_root to find clip path
309
+ clip_path = complete_clip_path(file_arg.stem, synced_proj)
310
+ probe = ffmpeg.probe(clip_path)
311
+ duration = float(probe['format']['duration'])
312
+ clips = [Clip(
313
+ start_time=0,
314
+ in_time=0,
315
+ cut_duration=duration,
316
+ whole_duration=duration,
317
+ name=file_arg.stem,
318
+ path=clip_path,
319
+ timeline_pos=3600,
320
+ ISOdir='')]
321
+ [find_and_set_ISO_dir(clip, SND_dirs) for clip in clips]
322
+ print(f'For video clip \n{clip_path}\nfound audio in\n{clips[0].ISOdir}')
323
+ logger.debug(f'clips with found ISOdir: {pformat(clips)}')
324
+ lua_clips = gen_lua_table(clips)
325
+ logger.debug(f'lua_clips {lua_clips}')
326
+ # title = "Load cut26_MyBigMovie" or "Load clip026_MyBigMovie"
327
+ arg_name = pathlib.Path(args.a_file_argument[0]).stem
328
+ title = f'Load {arg_name}_{pathlib.Path(raw_root).stem}'
329
+ # script_path = pathlib.Path(REAPER_SCRIPT_LOCATION)/f'{title}.lua'
330
+ script_path = pathlib.Path(REAPER_SCRIPT_LOCATION)/f'Load Clip Audio.lua'
331
+ # script += f'os.remove("{script_path}")\n' # doesnt work
332
+ Lua_script_pre, _ , Lua_script_post = REAPER_LUA_CODE.split('--cut here--')
333
+ script = Lua_script_pre + 'clips=' + lua_clips + Lua_script_post
334
+ with open(script_path, 'w') as fh:
335
+ fh.write(script)
336
+ print(f'Wrote script {script_path}')
337
+ if file_arg.suffix != 'otio':
338
+ # build "Set rendering for" action
339
+ destination = pathlib.Path(clips[0].ISOdir)/'mix.wav'
340
+ logger.debug(f'will build set rendering for {arg_name} with dest: {destination}')
341
+ render_action = reaper_save_action(destination)
342
+ logger.debug(f'clip\n{render_action}')
343
+ # script_path = pathlib.Path(REAPER_SCRIPT_LOCATION)/f'Set rendering for {arg_name}.lua'
344
+ script_path = pathlib.Path(REAPER_SCRIPT_LOCATION)/f'Render Clip Audio.lua'
345
+ with open(script_path, 'w') as fh:
346
+ fh.write(render_action)
347
+ print(f'Wrote script {script_path}')
348
+
349
+
350
+ if __name__ == '__main__':
351
+ main()
352
+
tictacsync/mamconf.py CHANGED
@@ -3,6 +3,13 @@ from loguru import logger
3
3
  from pprint import pprint, pformat
4
4
  from pathlib import Path
5
5
 
6
+ # [TODO] add in the doc:
7
+ # RAWROOT (sources with TC): "/Users/foobar/movies/MyBigMovie/"
8
+ # SYNCEDROOT (where RAWROOT will be mirrored, but with synced clips): "/Users/foobar/synced"
9
+ # SNDROOT (destination of ISOs sound files): "/Users/foobar/MovieSounds"
10
+ # then
11
+ # "/Users/foobar/synced/MyBigMovie" and "/Users/foobar/MovieSounds/MyBigMovie" will be created
12
+
6
13
 
7
14
  CONF_FILE = 'mamsync.cfg'
8
15
  LOG_FILE = 'mamdone.txt'
@@ -13,8 +20,8 @@ logger.remove()
13
20
 
14
21
 
15
22
  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}"')
23
+ print(f'RAWROOT (sources with TC): "{raw_root}"')
24
+ print(f'SYNCEDROOT (where RAWROOT will be mirrored, but with synced clips): "{synced_root}"')
18
25
  print(f'SNDROOT (destination of ISOs sound files): "{snd_root}"')
19
26
  if proxies != '':
20
27
  print(f'PROXIES (NLE proxy clips folder): "{proxies}"')
@@ -52,7 +59,7 @@ def get_proj(print_conf_stdout=False):
52
59
  conf_file = Path(conf_dir)/CONF_FILE
53
60
  logger.debug('try reading config in %s'%conf_file)
54
61
  if print_conf_stdout:
55
- print(f'\nTrying to read configuration from file {conf_file}')
62
+ print(f'Will read configuration from file {conf_file}')
56
63
  if conf_file.exists():
57
64
  conf_prs = configparser.ConfigParser()
58
65
  conf_prs.read(conf_file)
@@ -82,22 +89,22 @@ def get_proj(print_conf_stdout=False):
82
89
 
83
90
  def new_parser():
84
91
  parser = argparse.ArgumentParser()
85
- parser.add_argument('--rawroot',
92
+ parser.add_argument('--rr',
86
93
  nargs = 1,
87
94
  dest='rawroot',
88
95
  help='Sets new value for raw root folder (i.e.: clips with TC)')
89
- parser.add_argument('--syncedroot',
96
+ parser.add_argument('--sr',
90
97
  nargs = 1,
91
98
  dest='syncedroot',
92
99
  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',
100
+ parser.add_argument('--pr',
94
101
  nargs = 1,
95
102
  dest='proxies',
96
103
  help='Sets where the proxy files are stored by the NLE')
97
- parser.add_argument('--sndfolder',
104
+ parser.add_argument('--sf',
98
105
  nargs = 1,
99
106
  dest='sndfolder',
100
- help='Sets new value for sound folder (where ISOs sound files will be stored)')
107
+ help='Sets value for sound folder (will contain a mirror copy of RAWROOT, but with ISO files only)')
101
108
  parser.add_argument('--clearconf',
102
109
  action='store_true',
103
110
  dest='clearconf',
@@ -143,33 +150,6 @@ def main():
143
150
  get_proj()
144
151
  print_out_conf(*get_proj(True))
145
152
  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
153
 
174
154
  if __name__ == '__main__':
175
155
  main()
tictacsync/mamsync.py CHANGED
@@ -10,12 +10,14 @@ try:
10
10
  from . import timeline
11
11
  from . import multi2polywav
12
12
  from . import mamconf
13
+ from . import entry
13
14
  except:
14
15
  import yaltc
15
16
  import device_scanner
16
17
  import timeline
17
18
  import multi2polywav
18
19
  import mamconf
20
+ import entry
19
21
 
20
22
  import argparse, tempfile, configparser
21
23
  from loguru import logger
@@ -42,76 +44,13 @@ m4v svi 3gp 3g2 mxf roq nsv flv f4v f4p f4a f4b 3gp aa aac aax act aiff alac
42
44
  amr ape au awb dss dvf flac gsm iklax ivs m4a m4b m4p mmf mp3 mpc msv nmf
43
45
  ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".split()
44
46
 
47
+ logger.level("DEBUG", color="<yellow>")
48
+ logger.add(sys.stdout, level="DEBUG")
45
49
  logger.remove()
46
- # logger.add(sys.stdout, level="DEBUG")
47
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "main")
50
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "__init__" and r["module"] == "yaltc")
51
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_fit_length")
48
52
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "_write_ISOs")
49
53
 
50
- def process_single(file, args):
51
- # argument is a single file
52
- m = device_scanner.media_at_path(None, Path(file))
53
- if args.plotting:
54
- print('\nPlots can be zoomed and panned...')
55
- print('Close window for next one.')
56
- a_rec = yaltc.Recording(m, do_plots=args.plotting)
57
- time = a_rec.get_start_time()
58
- # time = a_rec.get_start_time(plots=args.plotting)
59
- if time != None:
60
- frac_time = int(time.microsecond / 1e2)
61
- d = '%s.%s'%(time.strftime("%Y-%m-%d %H:%M:%S"),frac_time)
62
- if args.terse:
63
- print('%s UTC:%s pulse: %i in chan %i'%(file, d, a_rec.sync_position,
64
- a_rec.TicTacCode_channel))
65
- else:
66
- print('\nRecording started at [gold1]%s[/gold1] UTC'%d)
67
- print('true sample rate: [gold1]%.3f Hz[/gold1]'%a_rec.true_samplerate)
68
- print('first sync at [gold1]%i[/gold1] samples in channel %i'%(a_rec.sync_position,
69
- a_rec.TicTacCode_channel))
70
- print('N.B.: all results are precise to the displayed digits!\n')
71
- else:
72
- if args.terse:
73
- print('%s UTC: None'%(file))
74
- else:
75
- print('Start time couldnt be determined')
76
- sys.exit(1)
77
-
78
- def process_lag_adjustement(media_object):
79
- # trim channels that are lagging (as stated in tracks.txt)
80
- # replace the old file, and rename the old one with .wavbk
81
- # if .wavbk exist, process was done already, so dont process
82
- # returns nothing
83
- lags = media_object.device.tracks.lag_values
84
- logger.debug('will process %s lags'%[lags])
85
- channels = timeline._sox_split_channels(media_object.path)
86
- # add bk to file on filesystem, but media_object.path is unchanged (?)
87
- backup_name = str(media_object.path) + 'bk'
88
- if Path(backup_name).exists():
89
- logger.debug('%s exists, so return now.'%backup_name)
90
- return
91
- media_object.path.replace(backup_name)
92
- logger.debug('channels %s'%channels)
93
- def _trim(lag, chan_file):
94
- # for lag
95
- if lag == None:
96
- return chan_file
97
- else:
98
- logger.debug('process %s for lag of %s'%(chan_file, lag))
99
- sox_transform = sox.Transformer()
100
- sox_transform.trim(float(lag)*1e-3)
101
- output_fh = tempfile.NamedTemporaryFile(suffix='.wav', delete=DEL_TEMP)
102
- out_file = timeline._pathname(output_fh)
103
- input_file = timeline._pathname(chan_file)
104
- logger.debug('sox in and out files: %s %s'%(input_file, out_file))
105
- logger.debug('calling sox_transform.build()')
106
- status = sox_transform.build(input_file, out_file, return_output=True )
107
- logger.debug('sox.build exit code %s'%str(status))
108
- return output_fh
109
- new_channels = [_trim(*e) for e in zip(lags, channels)]
110
- logger.debug('new_channels %s'%new_channels)
111
- trimmed_multichanfile = timeline._sox_combine(new_channels)
112
- logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
113
- Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
114
-
115
54
  def copy_to_syncedroot(raw_root, synced_root):
116
55
  # args are str
117
56
  # copy dirs and non AV files
@@ -155,8 +94,6 @@ def copy_raw_root_tree_to_sndroot(raw_root, snd_root):
155
94
  if raw_path.is_dir():
156
95
  synced_path.mkdir(parents=True, exist_ok=True)
157
96
 
158
-
159
-
160
97
  def new_parser():
161
98
  parser = argparse.ArgumentParser()
162
99
  parser.add_argument('--resync',
@@ -240,7 +177,7 @@ def main():
240
177
  logger.debug('%s has lag_values %s'%(
241
178
  m.path, m.device.tracks.lag_values))
242
179
  # any lag for a channel is specified by user in tracks.txt
243
- process_lag_adjustement(m)
180
+ entry.process_lag_adjustement(m)
244
181
  audio_REC_only = all([m.device.dev_type == 'REC' for m
245
182
  in scanner.found_media_files])
246
183
  if not args.terse:
@@ -285,6 +222,7 @@ def main():
285
222
  for rec in recordings
286
223
  if rec.get_start_time()
287
224
  ]
225
+ [r.load_track_info() for r in recordings_with_time if r.is_audio()]
288
226
  if not args.terse:
289
227
  table = Table(title="tictacsync results")
290
228
  table.add_column("Recording\n", justify="center", style='gold1')
@@ -56,7 +56,6 @@ def nframes(path):
56
56
  return duration_ts
57
57
  return n_frames
58
58
 
59
-
60
59
  def build_poly_name(multifiles):
61
60
  """
62
61
  Returns string of polywav filename, constructed from similitudes between