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.
- tictacsync/device_scanner.py +362 -169
- tictacsync/entry.py +240 -135
- tictacsync/mamconf.py +157 -0
- tictacsync/mamdav.py +642 -0
- tictacsync/mamreap.py +481 -0
- tictacsync/mamsync.py +343 -0
- tictacsync/multi2polywav.py +21 -14
- tictacsync/timeline.py +1126 -442
- tictacsync/yaltc.py +895 -1067
- tictacsync-1.4.4b0.dist-info/METADATA +118 -0
- tictacsync-1.4.4b0.dist-info/RECORD +16 -0
- tictacsync-1.4.4b0.dist-info/entry_points.txt +7 -0
- tictacsync/LTCcheck.py +0 -394
- tictacsync-0.1a14.dist-info/METADATA +0 -96
- tictacsync-0.1a14.dist-info/RECORD +0 -13
- tictacsync-0.1a14.dist-info/entry_points.txt +0 -4
- {tictacsync-0.1a14.dist-info → tictacsync-1.4.4b0.dist-info}/LICENSE +0 -0
- {tictacsync-0.1a14.dist-info → tictacsync-1.4.4b0.dist-info}/WHEEL +0 -0
- {tictacsync-0.1a14.dist-info → tictacsync-1.4.4b0.dist-info}/top_level.txt +0 -0
tictacsync/entry.py
CHANGED
|
@@ -1,42 +1,35 @@
|
|
|
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
|
|
5
4
|
# a deployment set-up... surely I'm setting
|
|
6
|
-
# things wrong [TODO] find why and clean up this mess
|
|
5
|
+
# things wrong [TODO]: find why and clean up this mess
|
|
6
|
+
|
|
7
7
|
try:
|
|
8
8
|
from . import yaltc
|
|
9
|
-
except:
|
|
10
|
-
import yaltc
|
|
11
|
-
try:
|
|
12
9
|
from . import device_scanner
|
|
13
|
-
except:
|
|
14
|
-
import device_scanner
|
|
15
|
-
try:
|
|
16
10
|
from . import timeline
|
|
17
|
-
except:
|
|
18
|
-
import timeline
|
|
19
|
-
try:
|
|
20
11
|
from . import multi2polywav
|
|
21
12
|
except:
|
|
13
|
+
import yaltc
|
|
14
|
+
import device_scanner
|
|
15
|
+
import timeline
|
|
22
16
|
import multi2polywav
|
|
23
17
|
|
|
24
|
-
import argparse
|
|
18
|
+
import argparse, tempfile, configparser
|
|
25
19
|
from loguru import logger
|
|
26
20
|
from pathlib import Path
|
|
27
21
|
# import os, sys
|
|
28
|
-
import os, sys
|
|
22
|
+
import os, sys, sox, platformdirs
|
|
29
23
|
from rich.progress import track
|
|
30
24
|
# from pprint import pprint
|
|
31
25
|
from rich.console import Console
|
|
32
26
|
# from rich.text import Text
|
|
33
27
|
from rich.table import Table
|
|
34
28
|
from rich import print
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# print(' done')
|
|
29
|
+
from pprint import pprint, pformat
|
|
30
|
+
import numpy as np
|
|
39
31
|
|
|
32
|
+
DEL_TEMP = False
|
|
40
33
|
|
|
41
34
|
av_file_extensions = \
|
|
42
35
|
"""MOV webm mkv flv flv vob ogv ogg drc gif gifv mng avi MTS M2TS TS mov qt
|
|
@@ -47,146 +40,237 @@ ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".spl
|
|
|
47
40
|
|
|
48
41
|
logger.level("DEBUG", color="<yellow>")
|
|
49
42
|
|
|
50
|
-
def process_files_with_progress_bars(medias):
|
|
51
|
-
recordings = []
|
|
52
|
-
rec_with_yaltc = []
|
|
53
|
-
times = []
|
|
54
|
-
for m in track(medias,
|
|
55
|
-
description="1/4 Initializing Recording objects:"):
|
|
56
|
-
# file_alias = 'dummy'
|
|
57
|
-
recordings.append(yaltc.Recording(m))
|
|
58
|
-
for r in track(recordings,
|
|
59
|
-
description="2/4 Checking if files have YaLTC:"):
|
|
60
|
-
if r.seems_to_have_YaLTC_at_beginning():
|
|
61
|
-
rec_with_yaltc.append(r)
|
|
62
|
-
for r in track(rec_with_yaltc,
|
|
63
|
-
description="3/4 Finding start times:"):
|
|
64
|
-
times.append(r.get_start_time())
|
|
65
|
-
return recordings, rec_with_yaltc, times
|
|
66
|
-
|
|
67
|
-
def process_files(medias):
|
|
68
|
-
recordings = []
|
|
69
|
-
rec_with_yaltc = []
|
|
70
|
-
times = []
|
|
71
|
-
for m in medias:
|
|
72
|
-
recordings.append(yaltc.Recording(m))
|
|
73
|
-
for r in recordings:
|
|
74
|
-
# print('%s duration %.2fs'%(r.AVpath.name, r.get_duration()))
|
|
75
|
-
if r.seems_to_have_YaLTC_at_beginning():
|
|
76
|
-
rec_with_yaltc.append(r)
|
|
77
|
-
for r in rec_with_yaltc:
|
|
78
|
-
times.append(r.get_start_time())
|
|
79
|
-
return recordings, rec_with_yaltc, times
|
|
80
|
-
|
|
81
43
|
def process_single(file, args):
|
|
82
44
|
# argument is a single file
|
|
83
|
-
m = device_scanner.
|
|
84
|
-
|
|
85
|
-
|
|
45
|
+
m = device_scanner.media_at_path(None, Path(file))
|
|
46
|
+
if args.plotting:
|
|
47
|
+
print('\nPlots can be zoomed and panned...')
|
|
48
|
+
print('Close window for next one.')
|
|
49
|
+
a_rec = yaltc.Recording(m, do_plots=args.plotting)
|
|
50
|
+
time = a_rec.get_start_time()
|
|
51
|
+
# time = a_rec.get_start_time(plots=args.plotting)
|
|
86
52
|
if time != None:
|
|
87
53
|
frac_time = int(time.microsecond / 1e2)
|
|
88
54
|
d = '%s.%s'%(time.strftime("%Y-%m-%d %H:%M:%S"),frac_time)
|
|
89
55
|
if args.terse:
|
|
90
56
|
print('%s UTC:%s pulse: %i in chan %i'%(file, d, a_rec.sync_position,
|
|
91
|
-
a_rec.
|
|
57
|
+
a_rec.TicTacCode_channel))
|
|
92
58
|
else:
|
|
93
59
|
print('\nRecording started at [gold1]%s[/gold1] UTC'%d)
|
|
94
60
|
print('true sample rate: [gold1]%.3f Hz[/gold1]'%a_rec.true_samplerate)
|
|
95
61
|
print('first sync at [gold1]%i[/gold1] samples in channel %i'%(a_rec.sync_position,
|
|
96
|
-
a_rec.
|
|
62
|
+
a_rec.TicTacCode_channel))
|
|
97
63
|
print('N.B.: all results are precise to the displayed digits!\n')
|
|
98
64
|
else:
|
|
99
65
|
if args.terse:
|
|
100
66
|
print('%s UTC: None'%(file))
|
|
101
67
|
else:
|
|
102
68
|
print('Start time couldnt be determined')
|
|
103
|
-
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
def process_lag_adjustement(media_object):
|
|
72
|
+
# trim channels that are lagging (as stated in tracks.txt)
|
|
73
|
+
# replace the old file, and rename the old one with .wavbk
|
|
74
|
+
# if .wavbk exist, process was done already, so dont process
|
|
75
|
+
# returns nothing
|
|
76
|
+
lags = media_object.device.tracks.lag_values
|
|
77
|
+
logger.debug('will process %s lags'%[lags])
|
|
78
|
+
channels = timeline._sox_split_channels(media_object.path)
|
|
79
|
+
# add bk to file on filesystem, but media_object.path is unchanged (?)
|
|
80
|
+
backup_name = str(media_object.path) + 'bk'
|
|
81
|
+
if Path(backup_name).exists():
|
|
82
|
+
logger.debug('%s exists, so return now.'%backup_name)
|
|
83
|
+
return
|
|
84
|
+
media_object.path.replace(backup_name)
|
|
85
|
+
logger.debug('channels %s'%channels)
|
|
86
|
+
def _trim(lag, chan_file):
|
|
87
|
+
# counter intuitive I know. if a file lags, there's too
|
|
88
|
+
# much samples at the start:
|
|
89
|
+
# ..................|.........
|
|
90
|
+
# .......................|....
|
|
91
|
+
# ^ play head ->
|
|
92
|
+
if lag == None:
|
|
93
|
+
return chan_file
|
|
94
|
+
else:
|
|
95
|
+
logger.debug('process %s for lag of %s'%(chan_file, lag))
|
|
96
|
+
sox_transform = sox.Transformer()
|
|
97
|
+
sox_transform.trim(float(lag)*1e-3)
|
|
98
|
+
output_fh = tempfile.NamedTemporaryFile(suffix='.wav', delete=DEL_TEMP)
|
|
99
|
+
out_file = timeline._pathname(output_fh)
|
|
100
|
+
input_file = timeline._pathname(chan_file)
|
|
101
|
+
logger.debug('sox in and out files: %s %s'%(input_file, out_file))
|
|
102
|
+
logger.debug('calling sox_transform.build()')
|
|
103
|
+
status = sox_transform.build(input_file, out_file, return_output=True )
|
|
104
|
+
logger.debug('sox.build exit code %s'%str(status))
|
|
105
|
+
return output_fh
|
|
106
|
+
new_channels = [_trim(*e) for e in zip(lags, channels)]
|
|
107
|
+
logger.debug('new_channels %s'%new_channels)
|
|
108
|
+
trimmed_multichanfile = timeline._sox_combine(new_channels)
|
|
109
|
+
logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
|
|
110
|
+
Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
|
|
104
111
|
|
|
105
112
|
def main():
|
|
106
113
|
parser = argparse.ArgumentParser()
|
|
107
114
|
parser.add_argument(
|
|
108
|
-
"
|
|
115
|
+
"path",
|
|
109
116
|
type=str,
|
|
110
117
|
nargs=1,
|
|
111
|
-
help="
|
|
118
|
+
help="directory_name or media_file"
|
|
112
119
|
)
|
|
113
120
|
# parser.add_argument("directory", nargs="?", help="path of media directory")
|
|
114
121
|
# parser.add_argument('-v', action='store_true')
|
|
115
|
-
parser.add_argument('-v',
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
parser.add_argument('-v',
|
|
123
|
+
action='store_true', #ie default False
|
|
124
|
+
dest='verbose_output',
|
|
125
|
+
help='Set verbose ouput')
|
|
126
|
+
# parser.add_argument('--stop_mirroring',
|
|
127
|
+
# action='store_true', #ie default False
|
|
128
|
+
# dest='stop_mirroring',
|
|
129
|
+
# help='Stop mirroring mode, will write synced files alongside originals.')
|
|
130
|
+
# parser.add_argument('--start-project', '-s' ,
|
|
131
|
+
# nargs=2,
|
|
132
|
+
# dest='proj_folders',
|
|
133
|
+
# default = [],
|
|
134
|
+
# help='start mirrored tree output mode and specifies 2 folders: source (RAW) and destination (synced).')
|
|
135
|
+
parser.add_argument('-t','--timelineoffset',
|
|
136
|
+
nargs=1,
|
|
137
|
+
default=['00:00:00:00'],
|
|
138
|
+
dest='timelineoffset',
|
|
139
|
+
help='When processing multicam, where to place clips on NLE timeline (HH:MM:SS:FF)')
|
|
140
|
+
parser.add_argument('-p',
|
|
141
|
+
action='store_true',
|
|
119
142
|
dest='plotting',
|
|
120
|
-
help='
|
|
121
|
-
parser.add_argument('
|
|
143
|
+
help='Produce plots')
|
|
144
|
+
parser.add_argument('-d',
|
|
145
|
+
action='store_true',
|
|
146
|
+
dest='dont_write_cam_folder',
|
|
147
|
+
help="Even if originals are inside CAM folders, don't put synced clips inside a CAM identified folder")
|
|
148
|
+
# parser.add_argument('-m',
|
|
149
|
+
# action='store_true',
|
|
150
|
+
# dest='multicam',
|
|
151
|
+
# help='Outputs multicam structure for NLE program (based on Davinci Resolve input processing)')
|
|
152
|
+
parser.add_argument('--isos',
|
|
153
|
+
action='store_true',
|
|
122
154
|
dest='write_ISOs',
|
|
123
|
-
help='
|
|
124
|
-
parser.add_argument('--nosync',
|
|
155
|
+
help='Cut ISO sound files')
|
|
156
|
+
parser.add_argument('--nosync',
|
|
157
|
+
action='store_true',
|
|
125
158
|
dest='nosync',
|
|
126
|
-
help='
|
|
127
|
-
parser.add_argument('--terse',
|
|
159
|
+
help='Just scan and decode')
|
|
160
|
+
parser.add_argument('--terse',
|
|
161
|
+
action='store_true',
|
|
128
162
|
dest='terse',
|
|
129
|
-
help='
|
|
130
|
-
# parser.add_argument('--io', dest='output_structure', type=str, default='loose',
|
|
131
|
-
# help='should be one of: loose [DEFAULT], foldercam')
|
|
163
|
+
help='Terse output')
|
|
132
164
|
args = parser.parse_args()
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
|
|
165
|
+
# print(args)
|
|
166
|
+
if len(args.timelineoffset) != 1:
|
|
167
|
+
print('--timelineoffset needs one value, got %s'%args.timelineoffset)
|
|
168
|
+
quit()
|
|
136
169
|
if args.verbose_output:
|
|
137
170
|
logger.add(sys.stderr, level="DEBUG")
|
|
138
|
-
|
|
139
|
-
# logger.add(sys.stdout, filter="
|
|
140
|
-
# logger.add(sys.stdout, filter="
|
|
141
|
-
# logger.add(sys.stdout, filter="
|
|
142
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
143
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
144
|
-
|
|
145
|
-
top_dir = args.
|
|
171
|
+
|
|
172
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "move_multicam_to_dir")
|
|
173
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_audio_and_write_video")
|
|
174
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "main")
|
|
175
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_from_tracks_txt")
|
|
176
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_media_and_build_devices_UID")
|
|
177
|
+
|
|
178
|
+
top_dir = args.path[0]
|
|
146
179
|
if os.path.isfile(top_dir):
|
|
147
180
|
file = top_dir
|
|
148
181
|
process_single(file, args)
|
|
149
182
|
if not os.path.isdir(top_dir):
|
|
150
183
|
print('%s is not a directory or doesnt exist.'%top_dir)
|
|
151
|
-
|
|
184
|
+
sys.exit(1)
|
|
152
185
|
multi2polywav.poly_all(top_dir)
|
|
153
186
|
scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
187
|
+
scanner.scan_media_and_build_devices_UID()
|
|
188
|
+
for m in scanner.found_media_files:
|
|
189
|
+
if m.device.tracks:
|
|
190
|
+
if not all([lv == None for lv in m.device.tracks.lag_values]):
|
|
191
|
+
logger.debug('%s has lag_values %s'%(
|
|
192
|
+
m.path, m.device.tracks.lag_values))
|
|
193
|
+
# any lag for a channel is specified by user in tracks.txt
|
|
194
|
+
process_lag_adjustement(m)
|
|
195
|
+
audio_REC_only = all([m.device.dev_type == 'REC' for m
|
|
196
|
+
in scanner.found_media_files])
|
|
197
|
+
if audio_REC_only:
|
|
198
|
+
if scanner.input_structure != 'ordered':
|
|
199
|
+
print('For merging audio only, use a directory per device, quitting')
|
|
200
|
+
sys.exit(1)
|
|
201
|
+
print('\n\n\nOnly audio recordings are present')
|
|
202
|
+
print('Which device should be the reference?\n')
|
|
203
|
+
devices = scanner.get_devices()
|
|
204
|
+
maxch = len(devices)
|
|
205
|
+
for i, d in enumerate(devices):
|
|
206
|
+
print('\t%i - %s'%(i+1, d.name))
|
|
207
|
+
ref_device = list(devices)[3 - 1]
|
|
208
|
+
print('When only audio recordings are present, ISOs files will be cut and written.')
|
|
157
209
|
if not args.terse:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
scanner.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
print(
|
|
210
|
+
if scanner.input_structure == 'ordered':
|
|
211
|
+
print('\nDetected structured folders')
|
|
212
|
+
# if scanner.top_dir_has_multicam:
|
|
213
|
+
# print(', multicam')
|
|
214
|
+
# else:
|
|
215
|
+
# print()
|
|
216
|
+
else:
|
|
217
|
+
print('\nDetected loose structure')
|
|
218
|
+
if scanner.CAM_numbers() > 1:
|
|
219
|
+
print('\nNote: different CAMs are present, will sync audio for each of them but if you want to set their')
|
|
220
|
+
print('respective timecode for NLE timeline alignement you should regroup clips by CAM under their own DIR.')
|
|
221
|
+
print('\nFound [gold1]%i[/gold1] media files '%(
|
|
222
|
+
len(scanner.found_media_files)), end='')
|
|
223
|
+
print('from [gold1]%i[/gold1] devices:\n'%(
|
|
224
|
+
scanner.get_devices_number()))
|
|
225
|
+
all_devices = scanner.get_devices()
|
|
226
|
+
for dev in all_devices:
|
|
227
|
+
dt = 'Camera' if dev.dev_type == 'CAM' else 'Recorder'
|
|
228
|
+
print('%s [gold1]%s[/gold1] with files:'%(dt, dev.name), end = ' ')
|
|
229
|
+
medias = scanner.get_media_for_device(dev)
|
|
230
|
+
for m in medias[:-1]: # last printed out of loop
|
|
231
|
+
print('[gold1]%s[/gold1]'%m.path.name, end=', ')
|
|
232
|
+
print('[gold1]%s[/gold1]'%medias[-1].path.name)
|
|
233
|
+
a_media = medias[0]
|
|
234
|
+
# check if all audio recorders have same sampling freq
|
|
235
|
+
freqs = [dev.sampling_freq for dev in all_devices if dev.dev_type == 'REC']
|
|
236
|
+
same = np.isclose(np.std(freqs),0)
|
|
237
|
+
logger.debug('sampling freqs from audio recorders %s, same:%s'%(freqs, same))
|
|
238
|
+
if not same:
|
|
239
|
+
print('some audio recorders have different sampling frequencies:')
|
|
240
|
+
print(freqs)
|
|
241
|
+
print('resulting in undefined results: quitting...')
|
|
242
|
+
quit()
|
|
164
243
|
print()
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
else:
|
|
168
|
-
rez = process_files_with_progress_bars(scanner.found_media_files)
|
|
169
|
-
recordings, rec_with_yaltc, times = rez
|
|
244
|
+
recordings = [yaltc.Recording(m, do_plots=args.plotting) for m
|
|
245
|
+
in scanner.found_media_files]
|
|
170
246
|
recordings_with_time = [
|
|
171
247
|
rec
|
|
172
|
-
for rec in
|
|
248
|
+
for rec in recordings
|
|
173
249
|
if rec.get_start_time()
|
|
174
250
|
]
|
|
251
|
+
[r.load_track_info() for r in recordings_with_time if r.is_audio()]
|
|
252
|
+
for r in recordings:
|
|
253
|
+
# print(f'{r} device: #{id(r.device):x}')
|
|
254
|
+
logger.debug(f'{r} \nDevice instance id: #{id(r.device):x}')
|
|
255
|
+
logger.debug(f'device content: {r.device}')
|
|
256
|
+
if audio_REC_only:
|
|
257
|
+
for rec in recordings:
|
|
258
|
+
# print(rec, rec.device == ref_device)
|
|
259
|
+
if rec.device == ref_device:
|
|
260
|
+
rec.is_audio_reference = True
|
|
175
261
|
if not args.terse:
|
|
176
|
-
print('\n\n')
|
|
177
262
|
table = Table(title="tictacsync results")
|
|
178
263
|
table.add_column("Recording\n", justify="center", style='gold1')
|
|
179
|
-
table.add_column("
|
|
264
|
+
table.add_column("TTC chan\n (1st=#0)", justify="center", style='gold1')
|
|
180
265
|
# table.add_column("Device\n", justify="center", style='gold1')
|
|
181
266
|
table.add_column("UTC times\nstart:end", justify="center", style='gold1')
|
|
182
267
|
table.add_column("Clock drift\n(ppm)", justify="right", style='gold1')
|
|
183
|
-
table.add_column("SN ratio\n(dB)", justify="center", style='gold1')
|
|
268
|
+
# table.add_column("SN ratio\n(dB)", justify="center", style='gold1')
|
|
184
269
|
table.add_column("Date\n", justify="center", style='gold1')
|
|
185
270
|
rec_WO_time = [
|
|
186
271
|
rec.AVpath.name
|
|
187
|
-
for rec in
|
|
188
|
-
if not
|
|
189
|
-
]
|
|
272
|
+
for rec in recordings
|
|
273
|
+
if rec not in recordings_with_time]
|
|
190
274
|
if rec_WO_time:
|
|
191
275
|
print('No time found for: ',end='')
|
|
192
276
|
[print(rec, end=' ') for rec in rec_WO_time]
|
|
@@ -198,61 +282,82 @@ def main():
|
|
|
198
282
|
times_range = start_HHMMSS + ':' + end_MMSS
|
|
199
283
|
table.add_row(
|
|
200
284
|
str(r.AVpath.name),
|
|
201
|
-
str(r.
|
|
285
|
+
str(r.TicTacCode_channel),
|
|
202
286
|
# r.device,
|
|
203
287
|
times_range,
|
|
204
288
|
# '%.6f'%(r.true_samplerate/1e3),
|
|
205
289
|
'%2i'%(r.get_samplerate_drift()),
|
|
206
|
-
'%.0f'%r.decoder.SN_ratio,
|
|
290
|
+
# '%.0f'%r.decoder.SN_ratio,
|
|
207
291
|
date
|
|
208
292
|
)
|
|
209
293
|
console = Console()
|
|
210
294
|
console.print(table)
|
|
211
|
-
print(
|
|
295
|
+
print()
|
|
212
296
|
n_devices = scanner.get_devices_number()
|
|
213
|
-
OUT_struct_for_mcam = scanner.top_dir_has_multicam
|
|
214
|
-
# if n_devices > 2:
|
|
215
|
-
# print('\nMerging for more than 2 devices is not implemented yet, quitting...')
|
|
216
|
-
# quit()
|
|
217
297
|
if len(recordings_with_time) < 2:
|
|
218
298
|
if not args.terse:
|
|
219
299
|
print('\nNothing to sync, exiting.\n')
|
|
220
|
-
|
|
300
|
+
sys.exit(1)
|
|
221
301
|
matcher = timeline.Matcher(recordings_with_time)
|
|
222
|
-
matcher.
|
|
223
|
-
if not matcher.
|
|
302
|
+
matcher.scan_audio_for_each_videoclip()
|
|
303
|
+
if not matcher.mergers:
|
|
224
304
|
if not args.terse:
|
|
225
305
|
print('\nNothing to sync, bye.\n')
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
asked_ISOs = args.write_ISOs
|
|
308
|
+
if asked_ISOs and scanner.input_structure != 'ordered':
|
|
309
|
+
print('Warning, can\'t write ISOs without structured folders: [gold1]--isos[/gold1] option ignored.\n')
|
|
310
|
+
asked_ISOs = False
|
|
311
|
+
# output_dir = args.o
|
|
312
|
+
# if args.verbose_output or args.terse: # verbose, so no progress bars
|
|
313
|
+
print('Merging...')
|
|
314
|
+
# for merger in matcher.mergers:
|
|
315
|
+
# merger.build_audio_and_write_merged_media(top_dir,
|
|
316
|
+
# args.dont_write_cam_folder,
|
|
317
|
+
# asked_ISOs,
|
|
318
|
+
# audio_REC_only)
|
|
319
|
+
for merger in matcher.mergers:
|
|
320
|
+
if audio_REC_only:
|
|
321
|
+
# rare
|
|
322
|
+
merger._build_and_write_audio(top_dir)
|
|
323
|
+
else:
|
|
324
|
+
# almost always syncing audio to video clips
|
|
325
|
+
merger._build_audio_and_write_video(top_dir,
|
|
326
|
+
args.dont_write_cam_folder, asked_ISOs)
|
|
236
327
|
if not args.terse:
|
|
237
328
|
print("\n")
|
|
238
|
-
#
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
print('[gold1]%s[/gold1]'%stitcher.ref_recording.AVpath.name, end='')
|
|
244
|
-
for audio in stitcher.get_matched_audio_recs():
|
|
329
|
+
# find out where files were written
|
|
330
|
+
# a_merger = matcher.mergers[0]
|
|
331
|
+
for merger in matcher.mergers:
|
|
332
|
+
print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
|
|
333
|
+
for audio in merger.get_matched_audio_recs():
|
|
245
334
|
print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
|
|
246
|
-
new_file =
|
|
247
|
-
|
|
335
|
+
new_file = merger.videoclip.final_synced_file.parts
|
|
336
|
+
final_p = merger.videoclip.final_synced_file
|
|
337
|
+
nameAnd2Parents = Path('').joinpath(*final_p.parts[-2:])
|
|
338
|
+
print(' became [gold1]%s[/gold1]'%nameAnd2Parents)
|
|
248
339
|
# matcher._build_otio_tracks_for_cam()
|
|
249
|
-
|
|
340
|
+
if not audio_REC_only:
|
|
341
|
+
matcher.set_up_clusters() # multicam
|
|
342
|
+
matcher.shrink_gaps_between_takes(args.timelineoffset)
|
|
343
|
+
logger.debug('matcher.multicam_clips_clusters %s'%
|
|
344
|
+
pformat(matcher.multicam_clips_clusters))
|
|
345
|
+
# clusters is list of {'end': t1, 'start': t2, 'vids': [r1,r3]}
|
|
346
|
+
# really_clusters is True if one of them has len() > 1
|
|
347
|
+
really_clusters = any([len(cl['vids']) > 1 for cl
|
|
348
|
+
in matcher.multicam_clips_clusters])
|
|
349
|
+
if really_clusters:
|
|
350
|
+
if scanner.input_structure == 'loose':
|
|
351
|
+
print('\nThere are synced multicam clips but without structured folders')
|
|
352
|
+
print('they were not grouped together under the same folder.')
|
|
353
|
+
else:
|
|
354
|
+
matcher.move_multicam_to_dir()
|
|
355
|
+
else:
|
|
356
|
+
logger.debug('not really a multicam cluster, nothing to move')
|
|
357
|
+
sys.exit(0)
|
|
250
358
|
|
|
251
359
|
if __name__ == '__main__':
|
|
252
360
|
main()
|
|
253
361
|
|
|
254
362
|
|
|
255
363
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
tictacsync/mamconf.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import argparse, platformdirs, configparser, sys
|
|
2
|
+
from loguru import logger
|
|
3
|
+
from pprint import pformat
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich import print
|
|
6
|
+
|
|
7
|
+
# [TODO] add in the doc:
|
|
8
|
+
# RAWROOT (sources with TC): "/Users/foobar/movies/MyBigMovie/"
|
|
9
|
+
# SYNCEDROOT (where RAWROOT will be mirrored, but with synced clips): "/Users/foobar/synced"
|
|
10
|
+
# SNDROOT (destination of ISOs sound files): "/Users/foobar/MovieSounds"
|
|
11
|
+
# then
|
|
12
|
+
# "/Users/foobar/synced/MyBigMovie" and "/Users/foobar/MovieSounds/MyBigMovie" will be created
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
CONF_FILE = 'mamsync.cfg'
|
|
16
|
+
LOG_FILE = 'mamdone.txt'
|
|
17
|
+
|
|
18
|
+
logger.remove()
|
|
19
|
+
# logger.add(sys.stdout, level="DEBUG")
|
|
20
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "write_conf")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_out_conf(raw_root, synced_root, snd_root, proxies=''):
|
|
24
|
+
print(f'RAWROOT (sources with TC): "{raw_root}"')
|
|
25
|
+
print(f'SYNCEDROOT (where RAWROOT will be mirrored, but with synced clips): "{synced_root}"')
|
|
26
|
+
print(f'SNDROOT (destination of ISOs sound files): "{snd_root}"')
|
|
27
|
+
if proxies != '':
|
|
28
|
+
print(f'PROXIES (NLE proxy clips folder): "{proxies}"')
|
|
29
|
+
|
|
30
|
+
def write_conf(conf_key, conf_val):
|
|
31
|
+
# args are pahtlib.Paths.
|
|
32
|
+
# RAWROOT: files with TC (and ROLL folders), as is from cameras
|
|
33
|
+
# SYNCEDROOT: synced and no more TC (ROLL flattened)
|
|
34
|
+
# Writes configuration on filesystem for later retrieval
|
|
35
|
+
# Clears log of already synced clips.
|
|
36
|
+
conf_dir = platformdirs.user_config_dir('mamsync', 'plutz', ensure_exists=True)
|
|
37
|
+
current_values = dict(zip(['RAWROOT', 'SYNCEDROOT', 'SNDROOT', 'PROXIES'],
|
|
38
|
+
get_proj()))
|
|
39
|
+
logger.debug(f'old values {current_values}')
|
|
40
|
+
current_values[conf_key] = conf_val
|
|
41
|
+
logger.debug(f'updated values {current_values}')
|
|
42
|
+
conf_file = Path(conf_dir)/CONF_FILE
|
|
43
|
+
logger.debug('writing config in %s'%conf_file)
|
|
44
|
+
# print(f'\nWriting folders paths in configuration file "{conf_file}"')
|
|
45
|
+
# print_out_conf(raw_root, synced_root, snd_root)
|
|
46
|
+
conf_prs = configparser.ConfigParser()
|
|
47
|
+
conf_prs['SECTION1'] = current_values
|
|
48
|
+
with open(conf_file, 'w') as configfile_handle:
|
|
49
|
+
conf_prs.write(configfile_handle)
|
|
50
|
+
with open(conf_file, 'r') as configfile_handle:
|
|
51
|
+
logger.debug(f'config file content: \n{configfile_handle.read()}')
|
|
52
|
+
|
|
53
|
+
def get_proj(print_conf_stdout=False):
|
|
54
|
+
# check if user started a project before.
|
|
55
|
+
# stored in platformdirs.user_config_dir
|
|
56
|
+
# returns a tuple of strings (RAWROOT, SYNCEDROOTS, SNDROOT, PROXIES)
|
|
57
|
+
# if any, or a tuple of 4 empty strings '' otherwise.
|
|
58
|
+
# print location of conf file if print_conf_stdout
|
|
59
|
+
# Note: only raw_root contains the name project
|
|
60
|
+
conf_dir = platformdirs.user_config_dir('mamsync', 'plutz')
|
|
61
|
+
conf_file = Path(conf_dir)/CONF_FILE
|
|
62
|
+
logger.debug('try reading config in %s'%conf_file)
|
|
63
|
+
if print_conf_stdout:
|
|
64
|
+
print(f'Will read configuration from file {conf_file}')
|
|
65
|
+
if conf_file.exists():
|
|
66
|
+
conf_prs = configparser.ConfigParser()
|
|
67
|
+
conf_prs.read(conf_file)
|
|
68
|
+
try:
|
|
69
|
+
RAWROOT = conf_prs.get('SECTION1', 'RAWROOT')
|
|
70
|
+
except configparser.NoOptionError:
|
|
71
|
+
RAWROOT = ''
|
|
72
|
+
try:
|
|
73
|
+
SYNCEDROOT = conf_prs.get('SECTION1', 'SYNCEDROOT')
|
|
74
|
+
except configparser.NoOptionError:
|
|
75
|
+
SYNCEDROOT = ''
|
|
76
|
+
try:
|
|
77
|
+
PROXIES = conf_prs.get('SECTION1', 'PROXIES')
|
|
78
|
+
except configparser.NoOptionError:
|
|
79
|
+
PROXIES = ''
|
|
80
|
+
try:
|
|
81
|
+
SNDROOT = conf_prs.get('SECTION1', 'SNDROOT')
|
|
82
|
+
except configparser.NoOptionError:
|
|
83
|
+
SNDROOT = ''
|
|
84
|
+
logger.debug('read from conf: RAWROOT= %s SYNCEDROOT= %s SNDROOT=%s PROXIES=%s'%
|
|
85
|
+
(RAWROOT, SYNCEDROOT, SNDROOT, PROXIES))
|
|
86
|
+
return RAWROOT, SYNCEDROOT, SNDROOT, PROXIES
|
|
87
|
+
else:
|
|
88
|
+
logger.debug(f'no config file found at {conf_file}')
|
|
89
|
+
print('No configuration found.')
|
|
90
|
+
return '', '', '', ''
|
|
91
|
+
|
|
92
|
+
def new_parser():
|
|
93
|
+
parser = argparse.ArgumentParser()
|
|
94
|
+
parser.add_argument('--rr',
|
|
95
|
+
nargs = 1,
|
|
96
|
+
dest='rawroot',
|
|
97
|
+
help='Sets new value for raw root folder (i.e.: clips with TC)')
|
|
98
|
+
parser.add_argument('--sr',
|
|
99
|
+
nargs = 1,
|
|
100
|
+
dest='syncedroot',
|
|
101
|
+
help="""Sets where the synced files will be written, to be used by the NLE. Will contain a mirror copy of RAWROOT """)
|
|
102
|
+
parser.add_argument('--pr',
|
|
103
|
+
nargs = 1,
|
|
104
|
+
dest='proxies',
|
|
105
|
+
help='Sets where the proxy files are stored by the NLE')
|
|
106
|
+
parser.add_argument('--sf',
|
|
107
|
+
nargs = 1,
|
|
108
|
+
dest='sndfolder',
|
|
109
|
+
help='Sets value for sound folder (will contain a mirror copy of RAWROOT, but with ISO files only)')
|
|
110
|
+
parser.add_argument('--clearconf',
|
|
111
|
+
action='store_true',
|
|
112
|
+
dest='clearconf',
|
|
113
|
+
help='Clear configured values.')
|
|
114
|
+
parser.add_argument('--showconf',
|
|
115
|
+
action='store_true',
|
|
116
|
+
dest='showconf',
|
|
117
|
+
help='Show current configured values.')
|
|
118
|
+
return parser
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
parser = new_parser()
|
|
122
|
+
args = parser.parse_args()
|
|
123
|
+
logger.debug(f'arguments from argparse {args}')
|
|
124
|
+
if args.rawroot:
|
|
125
|
+
val = args.rawroot[0]
|
|
126
|
+
write_conf('RAWROOT', val)
|
|
127
|
+
print(f'Set source folder of unsynced clips (rawroot) to:\n{val}')
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
if args.syncedroot:
|
|
130
|
+
val = args.syncedroot[0]
|
|
131
|
+
write_conf('SYNCEDROOT', args.syncedroot[0])
|
|
132
|
+
print(f'Set destination folder of synced clips (syncedroot) to:\n{val}')
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
if args.proxies:
|
|
135
|
+
val = args.proxies[0]
|
|
136
|
+
write_conf('PROXIES', args.proxies[0])
|
|
137
|
+
print(f'Set proxies folder to:\n{val}')
|
|
138
|
+
sys.exit(0)
|
|
139
|
+
if args.sndfolder:
|
|
140
|
+
val = args.sndfolder[0]
|
|
141
|
+
write_conf('SNDROOT', args.sndfolder[0])
|
|
142
|
+
print(f'Set destination folder of ISOs sound files (sndfolder) to:\n{val}')
|
|
143
|
+
sys.exit(0)
|
|
144
|
+
if args.clearconf:
|
|
145
|
+
write_conf('RAWROOT', '')
|
|
146
|
+
write_conf('SYNCEDROOT', '')
|
|
147
|
+
write_conf('SNDROOT', '')
|
|
148
|
+
write_conf('PROXIES', '')
|
|
149
|
+
print_out_conf('','','','')
|
|
150
|
+
sys.exit(0)
|
|
151
|
+
if args.showconf:
|
|
152
|
+
get_proj()
|
|
153
|
+
print_out_conf(*get_proj(True))
|
|
154
|
+
sys.exit(0)
|
|
155
|
+
|
|
156
|
+
if __name__ == '__main__':
|
|
157
|
+
main()
|