tictacsync 0.91a0__py3-none-any.whl → 0.96a0__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 +183 -133
- tictacsync/entry.py +152 -61
- tictacsync/multi2polywav.py +1 -1
- tictacsync/remergemix.py +4 -2
- tictacsync/remrgmx.py +28 -0
- tictacsync/timeline.py +285 -95
- tictacsync/yaltc.py +47 -32
- {tictacsync-0.91a0.dist-info → tictacsync-0.96a0.dist-info}/METADATA +1 -1
- tictacsync-0.96a0.dist-info/RECORD +16 -0
- tictacsync-0.91a0.dist-info/RECORD +0 -15
- {tictacsync-0.91a0.dist-info → tictacsync-0.96a0.dist-info}/LICENSE +0 -0
- {tictacsync-0.91a0.dist-info → tictacsync-0.96a0.dist-info}/WHEEL +0 -0
- {tictacsync-0.91a0.dist-info → tictacsync-0.96a0.dist-info}/entry_points.txt +0 -0
- {tictacsync-0.91a0.dist-info → tictacsync-0.96a0.dist-info}/top_level.txt +0 -0
tictacsync/entry.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
print('Loading modules...', end='')
|
|
2
2
|
|
|
3
3
|
# I know, the following is ugly, but I need those try's to
|
|
4
4
|
# run the command in my dev setting AND from
|
|
@@ -16,18 +16,21 @@ except:
|
|
|
16
16
|
import timeline
|
|
17
17
|
import multi2polywav
|
|
18
18
|
|
|
19
|
-
import argparse
|
|
19
|
+
import argparse, tempfile
|
|
20
20
|
from loguru import logger
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
# import os, sys
|
|
23
|
-
import os, sys
|
|
23
|
+
import os, sys, sox
|
|
24
24
|
from rich.progress import track
|
|
25
25
|
# from pprint import pprint
|
|
26
26
|
from rich.console import Console
|
|
27
27
|
# from rich.text import Text
|
|
28
28
|
from rich.table import Table
|
|
29
29
|
from rich import print
|
|
30
|
-
from pprint import pprint
|
|
30
|
+
from pprint import pprint
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
DEL_TEMP = False
|
|
31
34
|
|
|
32
35
|
av_file_extensions = \
|
|
33
36
|
"""MOV webm mkv flv flv vob ogv ogg drc gif gifv mng avi MTS M2TS TS mov qt
|
|
@@ -40,7 +43,7 @@ logger.level("DEBUG", color="<yellow>")
|
|
|
40
43
|
|
|
41
44
|
def process_files_with_progress_bars(medias):
|
|
42
45
|
recordings = []
|
|
43
|
-
|
|
46
|
+
rec_with_TTC = []
|
|
44
47
|
times = []
|
|
45
48
|
for m in track(medias,
|
|
46
49
|
description="1/4 Initializing Recordings:"):
|
|
@@ -49,25 +52,26 @@ def process_files_with_progress_bars(medias):
|
|
|
49
52
|
for r in track(recordings,
|
|
50
53
|
description="2/4 Looking for TicTacCode:"):
|
|
51
54
|
if r.seems_to_have_TicTacCode_at_beginning():
|
|
52
|
-
|
|
53
|
-
for r in track(
|
|
55
|
+
rec_with_TTC.append(r)
|
|
56
|
+
for r in track(rec_with_TTC,
|
|
54
57
|
description="3/4 Finding start times:"):
|
|
55
58
|
times.append(r.get_start_time())
|
|
56
|
-
return recordings,
|
|
59
|
+
return recordings, rec_with_TTC, times
|
|
57
60
|
|
|
58
61
|
def process_files(medias):
|
|
62
|
+
# maps Media objects -> Recording objects
|
|
59
63
|
recordings = []
|
|
60
|
-
|
|
64
|
+
rec_with_TTC = []
|
|
61
65
|
times = []
|
|
62
66
|
for m in medias:
|
|
63
67
|
recordings.append(yaltc.Recording(m))
|
|
64
68
|
for r in recordings:
|
|
65
69
|
# print('%s duration %.2fs'%(r.AVpath.name, r.get_duration()))
|
|
66
70
|
if r.seems_to_have_TicTacCode_at_beginning():
|
|
67
|
-
|
|
68
|
-
for r in
|
|
71
|
+
rec_with_TTC.append(r)
|
|
72
|
+
for r in rec_with_TTC:
|
|
69
73
|
times.append(r.get_start_time())
|
|
70
|
-
return recordings,
|
|
74
|
+
return recordings, rec_with_TTC, times
|
|
71
75
|
|
|
72
76
|
def process_single(file, args):
|
|
73
77
|
# argument is a single file
|
|
@@ -97,6 +101,43 @@ def process_single(file, args):
|
|
|
97
101
|
print('Start time couldnt be determined')
|
|
98
102
|
sys.exit(1)
|
|
99
103
|
|
|
104
|
+
def process_lag_adjustement(media_object):
|
|
105
|
+
# trim channels that are lagging (as stated in tracks.txt)
|
|
106
|
+
# replace the old file, and rename the old one with .wavbk
|
|
107
|
+
# if .wavbk exist, process was done already, so dont process
|
|
108
|
+
# returns nothing
|
|
109
|
+
lags = media_object.device.tracks.lag_values
|
|
110
|
+
logger.debug('will process %s lags'%[lags])
|
|
111
|
+
channels = timeline._split_channels(media_object.path)
|
|
112
|
+
# add bk to file on filesystem, but media_object.path is unchanged (?)
|
|
113
|
+
backup_name = str(media_object.path) + 'bk'
|
|
114
|
+
if Path(backup_name).exists():
|
|
115
|
+
logger.debug('%s exists, so return now.'%backup_name)
|
|
116
|
+
return
|
|
117
|
+
media_object.path.replace(backup_name)
|
|
118
|
+
logger.debug('channels %s'%channels)
|
|
119
|
+
def _trim(lag, chan_file):
|
|
120
|
+
# for lag
|
|
121
|
+
if lag == None:
|
|
122
|
+
return chan_file
|
|
123
|
+
else:
|
|
124
|
+
logger.debug('process %s for lag of %s'%(chan_file, lag))
|
|
125
|
+
sox_transform = sox.Transformer()
|
|
126
|
+
sox_transform.trim(float(lag)*1e-3)
|
|
127
|
+
output_fh = tempfile.NamedTemporaryFile(suffix='.wav', delete=DEL_TEMP)
|
|
128
|
+
out_file = timeline._pathname(output_fh)
|
|
129
|
+
input_file = timeline._pathname(chan_file)
|
|
130
|
+
logger.debug('sox in and out files: %s %s'%(input_file, out_file))
|
|
131
|
+
logger.debug('calling sox_transform.build()')
|
|
132
|
+
status = sox_transform.build(input_file, out_file, return_output=True )
|
|
133
|
+
logger.debug('sox.build exit code %s'%str(status))
|
|
134
|
+
return output_fh
|
|
135
|
+
new_channels = [_trim(*e) for e in zip(lags, channels)]
|
|
136
|
+
logger.debug('new_channels %s'%new_channels)
|
|
137
|
+
trimmed_multichanfile = timeline._sox_combine(new_channels)
|
|
138
|
+
logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
|
|
139
|
+
Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
|
|
140
|
+
|
|
100
141
|
def main():
|
|
101
142
|
parser = argparse.ArgumentParser()
|
|
102
143
|
parser.add_argument(
|
|
@@ -107,32 +148,46 @@ def main():
|
|
|
107
148
|
)
|
|
108
149
|
# parser.add_argument("directory", nargs="?", help="path of media directory")
|
|
109
150
|
# parser.add_argument('-v', action='store_true')
|
|
110
|
-
parser.add_argument('-v',
|
|
111
|
-
|
|
112
|
-
|
|
151
|
+
parser.add_argument('-v',
|
|
152
|
+
action='store_true', #ie default False
|
|
153
|
+
dest='verbose_output',
|
|
154
|
+
help='Set verbose ouput')
|
|
113
155
|
parser.add_argument('-o', nargs=1,
|
|
114
156
|
help='Where to write the SyncedMedia folder [default to "path" ]')
|
|
115
|
-
parser.add_argument('-
|
|
157
|
+
parser.add_argument('-t','--timelineoffset',
|
|
158
|
+
nargs=1,
|
|
159
|
+
default=['00:00:00:00'],
|
|
160
|
+
dest='timelineoffset',
|
|
161
|
+
help='When processing multicam, where to place clips on NLE timeline (HH:MM:SS:FF)')
|
|
162
|
+
parser.add_argument('-p',
|
|
163
|
+
action='store_true',
|
|
116
164
|
dest='plotting',
|
|
117
165
|
help='Produce plots')
|
|
118
|
-
parser.add_argument('--isos',
|
|
166
|
+
parser.add_argument('--isos',
|
|
167
|
+
action='store_true',
|
|
119
168
|
dest='write_ISOs',
|
|
120
169
|
help='Write ISO sound files')
|
|
121
|
-
parser.add_argument('--nosync',
|
|
170
|
+
parser.add_argument('--nosync',
|
|
171
|
+
action='store_true',
|
|
122
172
|
dest='nosync',
|
|
123
173
|
help='Just scan and decode')
|
|
124
|
-
parser.add_argument('--terse',
|
|
174
|
+
parser.add_argument('--terse',
|
|
175
|
+
action='store_true',
|
|
125
176
|
dest='terse',
|
|
126
177
|
help='Terse output')
|
|
127
178
|
args = parser.parse_args()
|
|
179
|
+
# print(args)
|
|
180
|
+
if len(args.timelineoffset) != 1:
|
|
181
|
+
print('--timelineoffset needs one value, got %s'%args.timelineoffset)
|
|
182
|
+
quit()
|
|
128
183
|
if args.verbose_output:
|
|
129
184
|
logger.add(sys.stderr, level="DEBUG")
|
|
130
185
|
# logger.add(sys.stdout, filter="__main__")
|
|
131
186
|
# logger.add(sys.stdout, filter="yaltc")
|
|
132
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
133
|
-
# logger.
|
|
134
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
135
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
187
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_audio_for_each_videoclip")
|
|
188
|
+
# logger.debug(sys.stdout, filter=lambda r: r["function"] == "main")
|
|
189
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_and_write_audio")
|
|
190
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_build_audio_and_write_video")
|
|
136
191
|
top_dir = args.path[0]
|
|
137
192
|
if os.path.isfile(top_dir):
|
|
138
193
|
file = top_dir
|
|
@@ -151,7 +206,41 @@ def main():
|
|
|
151
206
|
sys.exit(1)
|
|
152
207
|
multi2polywav.poly_all(top_dir)
|
|
153
208
|
scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
|
|
154
|
-
scanner.scan_media_and_build_devices_UID()
|
|
209
|
+
scanner.scan_media_and_build_devices_UID()
|
|
210
|
+
for m in scanner.found_media_files:
|
|
211
|
+
if m.device.tracks:
|
|
212
|
+
if not all([lv == None for lv in m.device.tracks.lag_values]):
|
|
213
|
+
logger.debug('%s has lag_values %s'%(
|
|
214
|
+
m.path, m.device.tracks.lag_values))
|
|
215
|
+
process_lag_adjustement(m)
|
|
216
|
+
audio_REC_only = all([m.device.dev_type == 'REC' for m
|
|
217
|
+
in scanner.found_media_files])
|
|
218
|
+
|
|
219
|
+
if audio_REC_only:
|
|
220
|
+
if scanner.input_structure != 'folder_is_device':
|
|
221
|
+
print('For merging audio only, use a directory per device, quitting')
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
print('\n\n\nOnly audio recordings are present')
|
|
224
|
+
print('Which device should be the reference?\n')
|
|
225
|
+
devices = scanner.get_devices()
|
|
226
|
+
maxch = len(devices)
|
|
227
|
+
for i, d in enumerate(devices):
|
|
228
|
+
print('\t%i - %s'%(i+1, d.name))
|
|
229
|
+
# while True:
|
|
230
|
+
# print('\nEnter your choice:', end='')
|
|
231
|
+
# choice = input()
|
|
232
|
+
# try:
|
|
233
|
+
# choice = int(choice)
|
|
234
|
+
# except:
|
|
235
|
+
# print('Please use numeric digits.')
|
|
236
|
+
# continue
|
|
237
|
+
# if choice not in list(range(1, maxch + 1)):
|
|
238
|
+
# print('Please enter a number in [1..%i]'%maxch)
|
|
239
|
+
# continue
|
|
240
|
+
# break
|
|
241
|
+
# ref_device = list(devices)[choice - 1]
|
|
242
|
+
choice = 3
|
|
243
|
+
ref_device = list(devices)[choice - 1]
|
|
155
244
|
if not args.terse:
|
|
156
245
|
if scanner.input_structure == 'folder_is_device':
|
|
157
246
|
print('\nDetected structured folders', end='')
|
|
@@ -162,32 +251,43 @@ def main():
|
|
|
162
251
|
else:
|
|
163
252
|
print('\nDetected loose structure')
|
|
164
253
|
if scanner.CAM_numbers() > 1:
|
|
165
|
-
print('\nNote: different CAMs are present, will sync audio')
|
|
166
|
-
print('
|
|
167
|
-
print('respective timecode for NLE timeline alignement')
|
|
168
|
-
print('you should regroup clips by CAM under their own DIR.')
|
|
254
|
+
print('\nNote: different CAMs are present, will sync audio for each of them but if you want to set their')
|
|
255
|
+
print('respective timecode for NLE timeline alignement you should regroup clips by CAM under their own DIR.')
|
|
169
256
|
print('\nFound [gold1]%i[/gold1] media files '%(
|
|
170
257
|
len(scanner.found_media_files)), end='')
|
|
171
258
|
print('from [gold1]%i[/gold1] devices:\n'%(
|
|
172
259
|
scanner.get_devices_number()))
|
|
173
|
-
|
|
174
|
-
|
|
260
|
+
all_devices = scanner.get_devices()
|
|
261
|
+
for dev in all_devices:
|
|
262
|
+
dt = 'Camera' if dev.dev_type == 'CAM' else 'Recorder'
|
|
263
|
+
print('%s [gold1]%s[/gold1] with files:'%(dt, dev.name), end = ' ')
|
|
175
264
|
medias = scanner.get_media_for_device(dev)
|
|
176
|
-
for m in medias[:-1]:
|
|
265
|
+
for m in medias[:-1]: # last printed out of loop
|
|
177
266
|
print('[gold1]%s[/gold1]'%m.path.name, end=', ')
|
|
178
267
|
print('[gold1]%s[/gold1]'%medias[-1].path.name)
|
|
268
|
+
a_media = medias[0]
|
|
269
|
+
# check if all audio recorders have same sampling freq
|
|
270
|
+
freqs = [dev.sampling_freq for dev in all_devices if dev.dev_type == 'REC']
|
|
271
|
+
same = np.isclose(np.std(freqs),0)
|
|
272
|
+
logger.debug('sampling freqs from audio recoders %s, same:%s'%(freqs, same))
|
|
273
|
+
if not same:
|
|
274
|
+
print('some audio recorders have different sampling frequencies:')
|
|
275
|
+
print(freqs)
|
|
276
|
+
print('resulting in undefined results: quitting...')
|
|
277
|
+
quit()
|
|
179
278
|
print()
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# else:
|
|
183
|
-
# rez = process_files_with_progress_bars(scanner.found_media_files)
|
|
184
|
-
rez = process_files(scanner.found_media_files)
|
|
185
|
-
recordings, rec_with_yaltc, times = rez
|
|
279
|
+
recordings, rec_with_TTC, times = \
|
|
280
|
+
process_files(scanner.found_media_files)
|
|
186
281
|
recordings_with_time = [
|
|
187
282
|
rec
|
|
188
|
-
for rec in
|
|
283
|
+
for rec in rec_with_TTC
|
|
189
284
|
if rec.get_start_time()
|
|
190
285
|
]
|
|
286
|
+
if audio_REC_only:
|
|
287
|
+
for rec in recordings:
|
|
288
|
+
# print(rec, rec.device == ref_device)
|
|
289
|
+
if rec.device == ref_device:
|
|
290
|
+
rec.is_reference = True
|
|
191
291
|
if not args.terse:
|
|
192
292
|
table = Table(title="tictacsync results")
|
|
193
293
|
table.add_column("Recording\n", justify="center", style='gold1')
|
|
@@ -199,7 +299,7 @@ def main():
|
|
|
199
299
|
table.add_column("Date\n", justify="center", style='gold1')
|
|
200
300
|
rec_WO_time = [
|
|
201
301
|
rec.AVpath.name
|
|
202
|
-
for rec in
|
|
302
|
+
for rec in rec_with_TTC
|
|
203
303
|
if not rec.get_start_time()
|
|
204
304
|
]
|
|
205
305
|
if rec_WO_time:
|
|
@@ -226,16 +326,13 @@ def main():
|
|
|
226
326
|
print()
|
|
227
327
|
n_devices = scanner.get_devices_number()
|
|
228
328
|
OUT_struct_for_mcam = scanner.top_dir_has_multicam
|
|
229
|
-
# if n_devices > 2:
|
|
230
|
-
# print('\nMerging for more than 2 devices is not implemented yet, quitting...')
|
|
231
|
-
# sys.exit(1)
|
|
232
329
|
if len(recordings_with_time) < 2:
|
|
233
330
|
if not args.terse:
|
|
234
331
|
print('\nNothing to sync, exiting.\n')
|
|
235
332
|
sys.exit(1)
|
|
236
333
|
matcher = timeline.Matcher(recordings_with_time)
|
|
237
334
|
matcher.scan_audio_for_each_videoclip()
|
|
238
|
-
if not matcher.
|
|
335
|
+
if not matcher.mergers:
|
|
239
336
|
if not args.terse:
|
|
240
337
|
print('\nNothing to sync, bye.\n')
|
|
241
338
|
sys.exit(1)
|
|
@@ -245,31 +342,25 @@ def main():
|
|
|
245
342
|
asked_ISOs = False
|
|
246
343
|
output_dir = args.o
|
|
247
344
|
# if args.verbose_output or args.terse: # verbose, so no progress bars
|
|
248
|
-
for
|
|
249
|
-
|
|
345
|
+
for merger in matcher.mergers:
|
|
346
|
+
merger.build_audio_and_write_merged_media(top_dir, arg_out_dir,
|
|
250
347
|
OUT_struct_for_mcam,
|
|
251
|
-
asked_ISOs,
|
|
252
|
-
|
|
253
|
-
# print()
|
|
254
|
-
# for stitcher in track(matcher.video_mergers,
|
|
255
|
-
# description="4/4 Merging sound to videos:"):
|
|
256
|
-
# stitcher.build_audio_and_write_video(top_dir, arg_out_dir,
|
|
257
|
-
# OUT_struct_for_mcam,
|
|
258
|
-
# asked_ISOs,)
|
|
348
|
+
asked_ISOs,
|
|
349
|
+
audio_REC_only)
|
|
259
350
|
if not args.terse:
|
|
260
351
|
print("\n")
|
|
261
352
|
# find out where files were wrtitten
|
|
262
|
-
|
|
353
|
+
a_merger = matcher.mergers[0]
|
|
263
354
|
print('\nWrote output in folder [gold1]%s[/gold1]'%(
|
|
264
|
-
|
|
265
|
-
for
|
|
266
|
-
print('[gold1]%s[/gold1]'%
|
|
267
|
-
for audio in
|
|
355
|
+
a_merger.synced_clip_dir))
|
|
356
|
+
for merger in matcher.mergers:
|
|
357
|
+
print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
|
|
358
|
+
for audio in merger.get_matched_audio_recs():
|
|
268
359
|
print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
|
|
269
|
-
new_file =
|
|
270
|
-
print(' became [gold1]%s[/gold1]'%
|
|
360
|
+
new_file = merger.videoclip.final_synced_file.parts
|
|
361
|
+
print(' became [gold1]%s[/gold1]'%merger.videoclip.final_synced_file.name)
|
|
271
362
|
# matcher._build_otio_tracks_for_cam()
|
|
272
|
-
matcher.shrink_gaps_between_takes()
|
|
363
|
+
matcher.shrink_gaps_between_takes(args.timelineoffset)
|
|
273
364
|
sys.exit(0)
|
|
274
365
|
|
|
275
366
|
if __name__ == '__main__':
|
tictacsync/multi2polywav.py
CHANGED
tictacsync/remergemix.py
CHANGED
|
@@ -67,7 +67,8 @@ def _join_audio2video(audio_path: Path, video: Path):
|
|
|
67
67
|
ffmpeg_args = (
|
|
68
68
|
ffmpeg
|
|
69
69
|
.input(v_n)
|
|
70
|
-
.output(out_n,
|
|
70
|
+
.output(out_n, vcodec='copy')
|
|
71
|
+
# .output(out_n, shortest=None, vcodec='copy')
|
|
71
72
|
.global_args('-i', a_n, "-hide_banner")
|
|
72
73
|
.overwrite_output()
|
|
73
74
|
.get_args()
|
|
@@ -77,7 +78,8 @@ def _join_audio2video(audio_path: Path, video: Path):
|
|
|
77
78
|
_, out = (
|
|
78
79
|
ffmpeg
|
|
79
80
|
.input(v_n)
|
|
80
|
-
.output(out_n, shortest=None, vcodec='copy')
|
|
81
|
+
# .output(out_n, shortest=None, vcodec='copy')
|
|
82
|
+
.output(out_n, vcodec='copy')
|
|
81
83
|
.global_args('-i', a_n, "-hide_banner")
|
|
82
84
|
.overwrite_output()
|
|
83
85
|
.run(capture_stderr=True)
|
tictacsync/remrgmx.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
def is_video(f):
|
|
2
|
+
# True if name as video extension
|
|
3
|
+
name_ext = f.split('.')
|
|
4
|
+
if len(name_ext) != 2:
|
|
5
|
+
return False
|
|
6
|
+
name, ext = name_ext
|
|
7
|
+
return ext.lower() in video_extensions
|
|
8
|
+
|
|
9
|
+
# def find_ISO_vids_pairs(top):
|
|
10
|
+
# # top is
|
|
11
|
+
vids = []
|
|
12
|
+
ISOs = []
|
|
13
|
+
for (root,dirs,files) in os.walk(Path('.'), topdown=True):
|
|
14
|
+
for d in dirs:
|
|
15
|
+
if d[-4:] == '_ISO':
|
|
16
|
+
ISOs.append(Path(root)/d)
|
|
17
|
+
for f in files:
|
|
18
|
+
if is_video(f):
|
|
19
|
+
vids.append(Path(root)/f)
|
|
20
|
+
for pair in list(itertools.product(vids, ISOs)):
|
|
21
|
+
# print(pair)
|
|
22
|
+
matches = []
|
|
23
|
+
vid, ISO = pair
|
|
24
|
+
vidname, ext = vid.name.split('.')
|
|
25
|
+
if vidname == ISO.name[:-4]:
|
|
26
|
+
matches.append(pair)
|
|
27
|
+
# print(vidname, ISO.name[:-4])
|
|
28
|
+
# return matches
|