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/mamsync.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
|
|
2
|
+
# I know, the following is ugly, but I need those try's to
|
|
3
|
+
# run the command in my dev setting AND from
|
|
4
|
+
# a deployment set-up... surely I'm setting
|
|
5
|
+
# things wrong [TODO]: find why and clean up this mess
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from . import yaltc
|
|
9
|
+
from . import device_scanner
|
|
10
|
+
from . import timeline
|
|
11
|
+
from . import multi2polywav
|
|
12
|
+
from . import mamconf
|
|
13
|
+
from . import entry
|
|
14
|
+
except:
|
|
15
|
+
import yaltc
|
|
16
|
+
import device_scanner
|
|
17
|
+
import timeline
|
|
18
|
+
import multi2polywav
|
|
19
|
+
import mamconf
|
|
20
|
+
import entry
|
|
21
|
+
|
|
22
|
+
import argparse, tempfile, configparser, re
|
|
23
|
+
from loguru import logger
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
# import os, sys
|
|
26
|
+
import os, sys, sox, platformdirs, shutil, filecmp
|
|
27
|
+
from rich.progress import track
|
|
28
|
+
# from pprint import pprint
|
|
29
|
+
from rich.console import Console
|
|
30
|
+
# from rich.text import Text
|
|
31
|
+
from rich.table import Table
|
|
32
|
+
from rich import print
|
|
33
|
+
from pprint import pprint, pformat
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
DEL_TEMP = False
|
|
37
|
+
# CONF_FILE = 'mamsync.cfg'
|
|
38
|
+
# LOG_FILE = 'mamdone.txt'
|
|
39
|
+
|
|
40
|
+
av_file_extensions = \
|
|
41
|
+
"""MOV webm mkv flv flv vob ogv ogg drc gif gifv mng avi MTS M2TS TS mov qt
|
|
42
|
+
wmv yuv rm rmvb viv asf amv mp4 m4p m4v mpg mp2 mpeg mpe mpv mpg mpeg m2v
|
|
43
|
+
m4v svi 3gp 3g2 mxf roq nsv flv f4v f4p f4a f4b 3gp aa aac aax act aiff alac
|
|
44
|
+
amr ape au awb dss dvf flac gsm iklax ivs m4a m4b m4p mmf mp3 mpc msv nmf
|
|
45
|
+
ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".split()
|
|
46
|
+
|
|
47
|
+
logger.remove()
|
|
48
|
+
logger.level("DEBUG", color="<yellow>")
|
|
49
|
+
# logger.add(sys.stdout, level="DEBUG")
|
|
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")
|
|
52
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_write_ISOs")
|
|
53
|
+
|
|
54
|
+
def copy_to_syncedroot(raw_root, synced_root):
|
|
55
|
+
# args are str
|
|
56
|
+
# copy dirs and non AV files
|
|
57
|
+
logger.debug(f'raw_root {raw_root}')
|
|
58
|
+
logger.debug(f'synced_root {synced_root}')
|
|
59
|
+
for raw_path in Path(raw_root).rglob('*'):
|
|
60
|
+
ext = raw_path.suffix[1:]
|
|
61
|
+
is_DS_Store = raw_path.name == '.DS_Store'# mac os
|
|
62
|
+
if ext not in av_file_extensions and not is_DS_Store:
|
|
63
|
+
logger.debug(f'raw_path: {raw_path}')
|
|
64
|
+
# dont copy WAVs either, they will be in ISOs
|
|
65
|
+
rel = raw_path.relative_to(raw_root)
|
|
66
|
+
logger.debug(f'relative path {rel}')
|
|
67
|
+
synced_path = Path(synced_root)/Path(raw_root).name/rel
|
|
68
|
+
logger.debug(f'synced_path: {synced_path}')
|
|
69
|
+
if raw_path.is_dir():
|
|
70
|
+
synced_path.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
continue
|
|
72
|
+
# if here, it's a file
|
|
73
|
+
if not synced_path.exists():
|
|
74
|
+
print(f'will mirror non AV file {synced_path}')
|
|
75
|
+
logger.debug(f'will mirror non AV file at {synced_path}')
|
|
76
|
+
shutil.copy2(raw_path, synced_path)
|
|
77
|
+
continue
|
|
78
|
+
# file exists, check if same
|
|
79
|
+
same = filecmp.cmp(raw_path, synced_path, shallow=False)
|
|
80
|
+
logger.debug(f'copy exists of:\n{raw_path}\n{synced_path}')
|
|
81
|
+
if not same:
|
|
82
|
+
print(f'file changed, copying again\n{raw_path}')
|
|
83
|
+
shutil.copy2(raw_path, synced_path)
|
|
84
|
+
else:
|
|
85
|
+
logger.debug('same content, next')
|
|
86
|
+
continue # next raw_path in loop
|
|
87
|
+
|
|
88
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "clean_synced")
|
|
89
|
+
# def clean_synced(raw_root, synced_root):
|
|
90
|
+
# # removes <>v<capitalLetter>.mov if any
|
|
91
|
+
# # returns a list of deleted pathlib.Path
|
|
92
|
+
# project = Path(raw_root).name
|
|
93
|
+
# logger.debug(f'project {project}')
|
|
94
|
+
# synced_proj = Path(synced_root)/project
|
|
95
|
+
# if not synced_proj.exists():
|
|
96
|
+
# synced_proj.mkdir()
|
|
97
|
+
# deleted = []
|
|
98
|
+
# for raw_path in Path(synced_proj).rglob('*'):
|
|
99
|
+
# m = re.match(r'.*(v[A-Z])\..+', raw_path.name)
|
|
100
|
+
# if m != None:
|
|
101
|
+
# logger.debug(f'cleaning {raw_path}')
|
|
102
|
+
# raw_path.unlink()
|
|
103
|
+
# deleted.append(raw_path)
|
|
104
|
+
# return deleted
|
|
105
|
+
|
|
106
|
+
def copy_raw_root_tree_to_sndroot(raw_root, snd_root):
|
|
107
|
+
# args are str
|
|
108
|
+
# copy only tree structure, no files
|
|
109
|
+
for raw_path in Path(raw_root).rglob('*'):
|
|
110
|
+
synced_path = Path(snd_root)/str(raw_path)[1:] # cant join abs. paths
|
|
111
|
+
if raw_path.is_dir():
|
|
112
|
+
synced_path.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
|
|
114
|
+
def new_parser():
|
|
115
|
+
parser = argparse.ArgumentParser()
|
|
116
|
+
parser.add_argument('--resync',
|
|
117
|
+
action='store_true',
|
|
118
|
+
dest='resync',
|
|
119
|
+
help='Resync previously done clips.')
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"sub_dir",
|
|
122
|
+
type=str,
|
|
123
|
+
nargs='?',
|
|
124
|
+
help="Sub directory to scan, should under RAWROOT."
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument('--terse',
|
|
127
|
+
action='store_true',
|
|
128
|
+
dest='terse',
|
|
129
|
+
help='Terse output')
|
|
130
|
+
# parser.add_argument('--isos', # default True in mamsync
|
|
131
|
+
# action='store_true',
|
|
132
|
+
# dest='write_ISOs',
|
|
133
|
+
# help='Cut ISO sound files')
|
|
134
|
+
parser.add_argument('-t','--timelineoffset',
|
|
135
|
+
nargs=1,
|
|
136
|
+
default=['00:00:00:00'],
|
|
137
|
+
dest='timelineoffset',
|
|
138
|
+
help='When processing multicam, where to place clips on NLE timeline (HH:MM:SS:FF)')
|
|
139
|
+
return parser
|
|
140
|
+
|
|
141
|
+
def clear_log():
|
|
142
|
+
# clear the file logging clips already synced
|
|
143
|
+
data_dir = platformdirs.user_data_dir('mamsync', 'plutz', ensure_exists=True)
|
|
144
|
+
log_file = Path(data_dir)/mamconf.LOG_FILE
|
|
145
|
+
print('Clearing log file of synced clips: "%s"'%log_file)
|
|
146
|
+
with open(log_file, 'w') as fh:
|
|
147
|
+
fh.write('done:\n')
|
|
148
|
+
|
|
149
|
+
def main():
|
|
150
|
+
parser = new_parser()
|
|
151
|
+
args = parser.parse_args()
|
|
152
|
+
logger.debug(f'arguments from argparse {args}')
|
|
153
|
+
roots_strings = mamconf.get_proj(False)
|
|
154
|
+
roots_pathlibPaths = [Path(s) for s in mamconf.get_proj(False)]
|
|
155
|
+
logger.debug(f'roots_strings from mamconf.get_proj {roots_strings}')
|
|
156
|
+
logger.debug(f'roots_pathlibPaths from mamconf.get_proj {roots_pathlibPaths}')
|
|
157
|
+
# check all have values, except for PROXIES, the last one
|
|
158
|
+
if any([r == '' for r in roots_strings][:-1]):
|
|
159
|
+
print("Can't sync if some folders are not set:")
|
|
160
|
+
mamconf.print_out_conf(*mamconf.get_proj())
|
|
161
|
+
print('Bye.')
|
|
162
|
+
sys.exit(0)
|
|
163
|
+
# because optional PROXIES folder '' yields a '.' path, exclude it
|
|
164
|
+
for r in [rp for rp in roots_pathlibPaths if rp != Path('.')]:
|
|
165
|
+
if not r.is_absolute():
|
|
166
|
+
print(f'\rError: folder {r} must be an absolute path. Bye')
|
|
167
|
+
sys.exit(0)
|
|
168
|
+
if not r.exists():
|
|
169
|
+
print(f'\rError: folder {r} does not exist. Bye')
|
|
170
|
+
sys.exit(0)
|
|
171
|
+
if not r.is_dir():
|
|
172
|
+
print(f'\rError: path {r} is not a folder. Bye')
|
|
173
|
+
sys.exit(0)
|
|
174
|
+
raw_root, synced_root, snd_root, _ = roots_pathlibPaths
|
|
175
|
+
if args.sub_dir != None:
|
|
176
|
+
top_dir = args.sub_dir
|
|
177
|
+
logger.debug(f'sub _dir: {args.sub_dir}')
|
|
178
|
+
if not Path(top_dir).exists():
|
|
179
|
+
print(f"\rError: folder {top_dir} doesn't exist, bye.")
|
|
180
|
+
sys.exit(0)
|
|
181
|
+
else:
|
|
182
|
+
top_dir = raw_root
|
|
183
|
+
if args.resync:
|
|
184
|
+
clear_log()
|
|
185
|
+
# deleted = clean_synced(raw_root, synced_root)
|
|
186
|
+
# logger.debug(f'deleted older clip versions: {deleted}')
|
|
187
|
+
# go, mamsync!
|
|
188
|
+
copy_to_syncedroot(raw_root, synced_root)
|
|
189
|
+
# copy_raw_root_tree_to_sndroot(raw_root, snd_root) # why?
|
|
190
|
+
multi2polywav.poly_all(top_dir)
|
|
191
|
+
scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
|
|
192
|
+
scanner.scan_media_and_build_devices_UID(synced_root=synced_root)
|
|
193
|
+
for m in scanner.found_media_files:
|
|
194
|
+
if m.device.tracks:
|
|
195
|
+
if not all([lv == None for lv in m.device.tracks.lag_values]):
|
|
196
|
+
logger.debug('%s has lag_values %s'%(
|
|
197
|
+
m.path, m.device.tracks.lag_values))
|
|
198
|
+
# any lag for a channel is specified by user in tracks.txt
|
|
199
|
+
entry.process_lag_adjustement(m)
|
|
200
|
+
audio_REC_only = all([m.device.dev_type == 'REC' for m
|
|
201
|
+
in scanner.found_media_files])
|
|
202
|
+
if not args.terse:
|
|
203
|
+
if scanner.input_structure == 'ordered':
|
|
204
|
+
print('\nDetected structured folders')
|
|
205
|
+
# if scanner.top_dir_has_multicam:
|
|
206
|
+
# print(', multicam')
|
|
207
|
+
# else:
|
|
208
|
+
# print()
|
|
209
|
+
else:
|
|
210
|
+
print('\nDetected loose structure')
|
|
211
|
+
if scanner.CAM_numbers() > 1:
|
|
212
|
+
print('\nNote: different CAMs are present, will sync audio for each of them but if you want to set their')
|
|
213
|
+
print('respective timecode for NLE timeline alignement you should regroup clips by CAM under their own DIR.')
|
|
214
|
+
print('\nFound [gold1]%i[/gold1] media files '%(
|
|
215
|
+
len(scanner.found_media_files)), end='')
|
|
216
|
+
print('from [gold1]%i[/gold1] devices:\n'%(
|
|
217
|
+
scanner.get_devices_number()))
|
|
218
|
+
all_devices = scanner.get_devices()
|
|
219
|
+
for dev in all_devices:
|
|
220
|
+
dt = 'Camera' if dev.dev_type == 'CAM' else 'Recorder'
|
|
221
|
+
print('%s [gold1]%s[/gold1] with files:'%(dt, dev.name), end = ' ')
|
|
222
|
+
medias = scanner.get_media_for_device(dev)
|
|
223
|
+
for m in medias[:-1]: # last printed out of loop
|
|
224
|
+
print('[gold1]%s[/gold1]'%m.path.name, end=', ')
|
|
225
|
+
print('[gold1]%s[/gold1]'%medias[-1].path.name)
|
|
226
|
+
a_media = medias[0]
|
|
227
|
+
# check if all audio recorders have same sampling freq
|
|
228
|
+
freqs = [dev.sampling_freq for dev in all_devices if dev.dev_type == 'REC']
|
|
229
|
+
same = np.isclose(np.std(freqs),0)
|
|
230
|
+
logger.debug('sampling freqs from audio recorders %s, same:%s'%(freqs, same))
|
|
231
|
+
if not same:
|
|
232
|
+
print('some audio recorders have different sampling frequencies:')
|
|
233
|
+
print(freqs)
|
|
234
|
+
print('resulting in undefined results: quitting...')
|
|
235
|
+
quit()
|
|
236
|
+
print()
|
|
237
|
+
recordings = [yaltc.Recording(m, do_plots=False) for m
|
|
238
|
+
in scanner.found_media_files]
|
|
239
|
+
recordings_with_time = [
|
|
240
|
+
rec
|
|
241
|
+
for rec in recordings
|
|
242
|
+
if rec.get_start_time()
|
|
243
|
+
]
|
|
244
|
+
[r.load_track_info() for r in recordings_with_time if r.is_audio()]
|
|
245
|
+
if not args.terse:
|
|
246
|
+
table = Table(title="tictacsync results")
|
|
247
|
+
table.add_column("Recording\n", justify="center", style='gold1')
|
|
248
|
+
table.add_column("TTC chan\n (1st=#0)", justify="center", style='gold1')
|
|
249
|
+
# table.add_column("Device\n", justify="center", style='gold1')
|
|
250
|
+
table.add_column("UTC times\nstart:end", justify="center", style='gold1')
|
|
251
|
+
table.add_column("Clock drift\n(ppm)", justify="right", style='gold1')
|
|
252
|
+
# table.add_column("SN ratio\n(dB)", justify="center", style='gold1')
|
|
253
|
+
table.add_column("Date\n", justify="center", style='gold1')
|
|
254
|
+
rec_WO_time = [
|
|
255
|
+
rec.AVpath.name
|
|
256
|
+
for rec in recordings
|
|
257
|
+
if rec not in recordings_with_time]
|
|
258
|
+
if rec_WO_time:
|
|
259
|
+
print('No time found for: ',end='')
|
|
260
|
+
[print(rec, end=' ') for rec in rec_WO_time]
|
|
261
|
+
print('\n')
|
|
262
|
+
for r in recordings_with_time:
|
|
263
|
+
date = r.get_start_time().strftime("%y-%m-%d")
|
|
264
|
+
start_HHMMSS = r.get_start_time().strftime("%Hh%Mm%Ss")
|
|
265
|
+
end_MMSS = r.get_end_time().strftime("%Mm%Ss")
|
|
266
|
+
times_range = start_HHMMSS + ':' + end_MMSS
|
|
267
|
+
table.add_row(
|
|
268
|
+
str(r.AVpath.name),
|
|
269
|
+
str(r.TicTacCode_channel),
|
|
270
|
+
# r.device,
|
|
271
|
+
times_range,
|
|
272
|
+
# '%.6f'%(r.true_samplerate/1e3),
|
|
273
|
+
'%2i'%(r.get_samplerate_drift()),
|
|
274
|
+
# '%.0f'%r.decoder.SN_ratio,
|
|
275
|
+
date
|
|
276
|
+
)
|
|
277
|
+
console = Console()
|
|
278
|
+
console.print(table)
|
|
279
|
+
print()
|
|
280
|
+
n_devices = scanner.get_devices_number()
|
|
281
|
+
if len(recordings_with_time) < 2:
|
|
282
|
+
if not args.terse:
|
|
283
|
+
print('\nNothing to sync, exiting.\n')
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
matcher = timeline.Matcher(recordings_with_time)
|
|
286
|
+
matcher.scan_audio_for_each_videoclip()
|
|
287
|
+
if not matcher.mergers:
|
|
288
|
+
if not args.terse:
|
|
289
|
+
print('\nNothing to sync, bye.\n')
|
|
290
|
+
sys.exit(1)
|
|
291
|
+
if scanner.input_structure != 'ordered':
|
|
292
|
+
print('Warning, can\'t run mamsync without structured folders: [gold1]--isos[/gold1] option ignored.\n')
|
|
293
|
+
asked_ISOs = True # par defaut
|
|
294
|
+
dont_write_cam_folder = False # write them
|
|
295
|
+
for merger in track(matcher.mergers, description="Merging..."):
|
|
296
|
+
merger._build_audio_and_write_video(top_dir,
|
|
297
|
+
dont_write_cam_folder,
|
|
298
|
+
asked_ISOs,
|
|
299
|
+
synced_root = synced_root,
|
|
300
|
+
snd_root = snd_root,
|
|
301
|
+
raw_root = raw_root)
|
|
302
|
+
if not args.terse:
|
|
303
|
+
print("\n")
|
|
304
|
+
# find out where files were written
|
|
305
|
+
# a_merger = matcher.mergers[0]
|
|
306
|
+
# log file
|
|
307
|
+
p = Path(platformdirs.user_data_dir('mamsync', 'plutz'))/mamconf.LOG_FILE
|
|
308
|
+
log_filehandle = open(p, 'a')
|
|
309
|
+
for merger in matcher.mergers:
|
|
310
|
+
print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
|
|
311
|
+
for audio in merger.get_matched_audio_recs():
|
|
312
|
+
print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
|
|
313
|
+
new_file = merger.videoclip.final_synced_file.parts
|
|
314
|
+
final_p = merger.videoclip.final_synced_file
|
|
315
|
+
nameAnd2Parents = Path('').joinpath(*final_p.parts[-2:])
|
|
316
|
+
print(' became [gold1]%s[/gold1]'%nameAnd2Parents)
|
|
317
|
+
# add full path to log file
|
|
318
|
+
log_filehandle.write(f'{merger.videoclip.AVpath}\n')
|
|
319
|
+
# matcher._build_otio_tracks_for_cam()
|
|
320
|
+
log_filehandle.close()
|
|
321
|
+
matcher.set_up_clusters() # multicam
|
|
322
|
+
matcher.shrink_gaps_between_takes(args.timelineoffset)
|
|
323
|
+
logger.debug('matcher.multicam_clips_clusters %s'%
|
|
324
|
+
pformat(matcher.multicam_clips_clusters))
|
|
325
|
+
# clusters is list of {'end': t1, 'start': t2, 'vids': [r1,r3]}
|
|
326
|
+
# really_clusters is True if one of them has len() > 1
|
|
327
|
+
really_clusters = any([len(cl['vids']) > 1 for cl
|
|
328
|
+
in matcher.multicam_clips_clusters])
|
|
329
|
+
if really_clusters:
|
|
330
|
+
if scanner.input_structure == 'loose':
|
|
331
|
+
print('\nThere are synced multicam clips but without structured folders')
|
|
332
|
+
print('they were not grouped together under the same folder.')
|
|
333
|
+
else:
|
|
334
|
+
matcher.move_multicam_to_dir(raw_root=raw_root, synced_root=synced_root)
|
|
335
|
+
else:
|
|
336
|
+
logger.debug('not really a multicam cluster, nothing to move')
|
|
337
|
+
sys.exit(0)
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
main()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
tictacsync/multi2polywav.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import argparse, wave, subprocess
|
|
1
|
+
import argparse, wave, subprocess, sys
|
|
2
2
|
from loguru import logger
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from itertools import groupby
|
|
5
5
|
import sox, tempfile, os, ffmpeg
|
|
6
|
+
from rich import print
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
"""
|
|
@@ -30,11 +31,11 @@ def print_grby(grby):
|
|
|
30
31
|
def wav_recursive_scan(top_directory):
|
|
31
32
|
files_lower_case = Path(top_directory).rglob('*.wav')
|
|
32
33
|
files_upper_case = Path(top_directory).rglob('*.WAV')
|
|
33
|
-
files = list(files_lower_case) + list(files_upper_case)
|
|
34
|
+
files = set(list(files_lower_case) + list(files_upper_case))
|
|
34
35
|
paths = [
|
|
35
36
|
p
|
|
36
37
|
for p in files
|
|
37
|
-
|
|
38
|
+
if 'SyncedMedia' not in p.parts
|
|
38
39
|
]
|
|
39
40
|
return paths
|
|
40
41
|
|
|
@@ -55,34 +56,35 @@ def nframes(path):
|
|
|
55
56
|
return duration_ts
|
|
56
57
|
return n_frames
|
|
57
58
|
|
|
58
|
-
|
|
59
59
|
def build_poly_name(multifiles):
|
|
60
60
|
"""
|
|
61
61
|
Returns string of polywav filename, constructed from similitudes between
|
|
62
62
|
multifile names. Ex:
|
|
63
|
-
|
|
63
|
+
4CH002I.wav and 4CH002M.wav returns 4CH002X.wav
|
|
64
64
|
"""
|
|
65
65
|
s1 = str(multifiles[0].stem)
|
|
66
66
|
s2 = str(multifiles[1].stem)
|
|
67
67
|
if len(s1) != len(s2):
|
|
68
|
-
print('
|
|
68
|
+
print('\nCan not build compound name with %s.wav and %s.wav'%(s1,s2))
|
|
69
69
|
print('names lengths differ.')
|
|
70
70
|
print('In folder "%s", quitting.'%multifiles[0].parent)
|
|
71
|
-
|
|
71
|
+
sys.exit(1)
|
|
72
72
|
pairs = list(zip(s1, s2))
|
|
73
73
|
not_same = [a for a, b in pairs if a != b ]
|
|
74
74
|
if len(not_same) > 2:
|
|
75
|
-
print('
|
|
75
|
+
print('\nCan not build compound name with %s.wav and %s.wav'%(s1,s2))
|
|
76
76
|
print('names differ by more than two characters.')
|
|
77
77
|
print('In folder "%s", quitting.'%multifiles[0].parent)
|
|
78
|
-
|
|
78
|
+
sys.exit(1)
|
|
79
79
|
compound = [a if a == b else 'X' for a, b in pairs ]
|
|
80
80
|
return ''.join(compound) + '.wav'
|
|
81
81
|
|
|
82
82
|
def jump_metadata(from_file, to_file):
|
|
83
83
|
tempfile_for_metadata = tempfile.NamedTemporaryFile(suffix='.wav', delete=True)
|
|
84
84
|
tempfile_for_metadata = tempfile_for_metadata.name
|
|
85
|
-
|
|
85
|
+
# ffmpeg -i 32ch-44100-bwf.wav -i onechan.wav -map 1 -map_metadata 0 -c copy outmeta.wav
|
|
86
|
+
process_list = ['ffmpeg', '-loglevel', 'quiet', '-nostats', '-hide_banner', '-i', from_file, '-i', to_file, '-map', '1',
|
|
87
|
+
'-map_metadata', '0', '-c', 'copy', tempfile_for_metadata]
|
|
86
88
|
# ss = shlex.split("ffmpeg -i %s -i %s -map_metadata 0 -c copy %s"%(from_file, to_file, tempfile_for_metadata))
|
|
87
89
|
# print(ss)
|
|
88
90
|
# logger.debug('process %s'%process_list)
|
|
@@ -95,8 +97,9 @@ def build_poly(multifiles):
|
|
|
95
97
|
# multifiles is list of Path
|
|
96
98
|
# change extensions to mfw (multifile wav)
|
|
97
99
|
dir_multi = multifiles[0].parent
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
multifiles.reverse()
|
|
101
|
+
poly_name_b = build_poly_name(multifiles) # base only
|
|
102
|
+
poly_name = str(dir_multi/Path(poly_name_b))
|
|
100
103
|
filenames = [str(p) for p in multifiles]
|
|
101
104
|
logger.debug('sox.build args: %s %s'%(
|
|
102
105
|
filenames,
|
|
@@ -110,10 +113,14 @@ def build_poly(multifiles):
|
|
|
110
113
|
logger.debug('sox.build status: %s'%status)
|
|
111
114
|
if status != True:
|
|
112
115
|
print('sox did not merge files')
|
|
113
|
-
|
|
116
|
+
sys.exit(1)
|
|
114
117
|
jump_metadata(filenames[0], poly_name)
|
|
118
|
+
print('\nMerging multifiles recordings:', end = ' ')
|
|
119
|
+
[print('[gold1]%s[/gold1]'%p.name, end=' + ') for p in multifiles[:-1]]
|
|
120
|
+
print('[gold1]%s[/gold1]'%multifiles[-1].name, end=' = ')
|
|
121
|
+
print('[gold1]%s[/gold1]'%poly_name_b, end=' ')
|
|
122
|
+
print('(backups will have .MFW as extension)')
|
|
115
123
|
[p.rename(str(p.parent/p.stem) + '.mfw') for p in multifiles]
|
|
116
|
-
# [os.remove(f) for f in filenames] # not anymore, non destructive..
|
|
117
124
|
|
|
118
125
|
def getch():
|
|
119
126
|
"""Read single character from standard input without echo."""
|