tictacsync 1.0.2a0__py3-none-any.whl → 1.2.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.
- tictacsync/device_scanner.py +5 -24
- tictacsync/entry.py +35 -38
- tictacsync/mamconf.py +175 -0
- tictacsync/mamsync.py +99 -172
- tictacsync/multi2polywav.py +3 -1
- tictacsync/newmix.py +217 -57
- tictacsync/timeline.py +137 -84
- tictacsync/yaltc.py +2 -2
- {tictacsync-1.0.2a0.dist-info → tictacsync-1.2.0b0.dist-info}/METADATA +4 -4
- tictacsync-1.2.0b0.dist-info/RECORD +19 -0
- {tictacsync-1.0.2a0.dist-info → tictacsync-1.2.0b0.dist-info}/entry_points.txt +2 -0
- tictacsync-1.0.2a0.dist-info/RECORD +0 -18
- {tictacsync-1.0.2a0.dist-info → tictacsync-1.2.0b0.dist-info}/LICENSE +0 -0
- {tictacsync-1.0.2a0.dist-info → tictacsync-1.2.0b0.dist-info}/WHEEL +0 -0
- {tictacsync-1.0.2a0.dist-info → tictacsync-1.2.0b0.dist-info}/top_level.txt +0 -0
tictacsync/mamsync.py
CHANGED
|
@@ -9,17 +9,19 @@ try:
|
|
|
9
9
|
from . import device_scanner
|
|
10
10
|
from . import timeline
|
|
11
11
|
from . import multi2polywav
|
|
12
|
+
from . import mamconf
|
|
12
13
|
except:
|
|
13
14
|
import yaltc
|
|
14
15
|
import device_scanner
|
|
15
16
|
import timeline
|
|
16
17
|
import multi2polywav
|
|
18
|
+
import mamconf
|
|
17
19
|
|
|
18
20
|
import argparse, tempfile, configparser
|
|
19
21
|
from loguru import logger
|
|
20
22
|
from pathlib import Path
|
|
21
23
|
# import os, sys
|
|
22
|
-
import os, sys, sox, platformdirs
|
|
24
|
+
import os, sys, sox, platformdirs, shutil, filecmp
|
|
23
25
|
from rich.progress import track
|
|
24
26
|
# from pprint import pprint
|
|
25
27
|
from rich.console import Console
|
|
@@ -30,8 +32,8 @@ from pprint import pprint, pformat
|
|
|
30
32
|
import numpy as np
|
|
31
33
|
|
|
32
34
|
DEL_TEMP = False
|
|
33
|
-
CONF_FILE = 'mamsync.cfg'
|
|
34
|
-
LOG_FILE = 'mamdone.txt'
|
|
35
|
+
# CONF_FILE = 'mamsync.cfg'
|
|
36
|
+
# LOG_FILE = 'mamdone.txt'
|
|
35
37
|
|
|
36
38
|
av_file_extensions = \
|
|
37
39
|
"""MOV webm mkv flv flv vob ogv ogg drc gif gifv mng avi MTS M2TS TS mov qt
|
|
@@ -40,12 +42,10 @@ m4v svi 3gp 3g2 mxf roq nsv flv f4v f4p f4a f4b 3gp aa aac aax act aiff alac
|
|
|
40
42
|
amr ape au awb dss dvf flac gsm iklax ivs m4a m4b m4p mmf mp3 mpc msv nmf
|
|
41
43
|
ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".split()
|
|
42
44
|
|
|
45
|
+
logger.remove()
|
|
43
46
|
# logger.add(sys.stdout, level="DEBUG")
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "scan_media_and_build_devices_UID")
|
|
47
47
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "main")
|
|
48
|
-
|
|
48
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_write_ISOs")
|
|
49
49
|
|
|
50
50
|
def process_single(file, args):
|
|
51
51
|
# argument is a single file
|
|
@@ -112,102 +112,53 @@ def process_lag_adjustement(media_object):
|
|
|
112
112
|
logger.debug('trimmed_multichanfile %s'%timeline._pathname(trimmed_multichanfile))
|
|
113
113
|
Path(timeline._pathname(trimmed_multichanfile)).replace(media_object.path)
|
|
114
114
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
115
|
+
def copy_to_syncedroot(raw_root, synced_root):
|
|
116
|
+
# args are str
|
|
117
|
+
# copy dirs and non AV files
|
|
118
|
+
logger.debug(f'raw_root {raw_root}')
|
|
119
|
+
logger.debug(f'synced_root {synced_root}')
|
|
120
|
+
for raw_path in Path(raw_root).rglob('*'):
|
|
121
|
+
ext = raw_path.suffix[1:]
|
|
122
|
+
is_DS_Store = raw_path.name == '.DS_Store'# mac os
|
|
123
|
+
if ext not in av_file_extensions and not is_DS_Store:
|
|
124
|
+
logger.debug(f'raw_path: {raw_path}')
|
|
125
|
+
# dont copy WAVs either, they will be in ISOs
|
|
126
|
+
# synced_path = Path(synced_root)/str(raw_path)[1:] # cant join abs. paths
|
|
127
|
+
rel = raw_path.relative_to(raw_root)
|
|
128
|
+
logger.debug(f'relative path {rel}')
|
|
129
|
+
synced_path = Path(synced_root)/Path(raw_root).name/rel
|
|
130
|
+
logger.debug(f'synced_path: {synced_path}')
|
|
131
|
+
if raw_path.is_dir():
|
|
132
|
+
synced_path.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
continue
|
|
134
|
+
# if here, it's a file
|
|
135
|
+
if not synced_path.exists():
|
|
136
|
+
print(f'will mirror non AV file {synced_path}')
|
|
137
|
+
logger.debug(f'will mirror non AV file at {synced_path}')
|
|
138
|
+
shutil.copy2(raw_path, synced_path)
|
|
139
|
+
continue
|
|
140
|
+
# file exists, check if same
|
|
141
|
+
same = filecmp.cmp(raw_path, synced_path, shallow=False)
|
|
142
|
+
logger.debug(f'copy exists of:\n{raw_path}\n{synced_path}')
|
|
143
|
+
if not same:
|
|
144
|
+
print(f'file changed, copying again\n{raw_path}')
|
|
145
|
+
shutil.copy2(raw_path, synced_path)
|
|
146
|
+
else:
|
|
147
|
+
logger.debug('same content, next')
|
|
148
|
+
continue # next raw_path in loop
|
|
149
149
|
|
|
150
|
+
def copy_raw_root_tree_to_sndroot(raw_root, snd_root):
|
|
151
|
+
# args are str
|
|
152
|
+
# copy only tree structure, no files
|
|
153
|
+
for raw_path in Path(raw_root).rglob('*'):
|
|
154
|
+
synced_path = Path(snd_root)/str(raw_path)[1:] # cant join abs. paths
|
|
155
|
+
if raw_path.is_dir():
|
|
156
|
+
synced_path.mkdir(parents=True, exist_ok=True)
|
|
150
157
|
|
|
151
158
|
|
|
152
|
-
# if known_values != ():
|
|
153
|
-
# RAWROOT, SYNCEDROOTS = known_values
|
|
154
|
-
# print('Warning: there is a current project')
|
|
155
|
-
# print('with source (RAW) folder: %s\nand destination (synced) folder: %s'%
|
|
156
|
-
# (RAWROOT, SYNCEDROOTS))
|
|
157
|
-
# answer = input("\nDo you want to change values? [YES|NO]")
|
|
158
|
-
# if answer.upper()[0] in ["Y", "YES"]:
|
|
159
|
-
# _write_cfg()
|
|
160
|
-
# return folders
|
|
161
|
-
# elif answer.upper()[0] in ["N", "NO"]:
|
|
162
|
-
# print('Ok, will keep old ones')
|
|
163
|
-
# return RAWROOT, SYNCEDROOTS
|
|
164
|
-
# else:
|
|
165
|
-
# _write_cfg()
|
|
166
|
-
# return folders
|
|
167
|
-
# sys.exit(0)
|
|
168
159
|
|
|
169
|
-
def
|
|
170
|
-
# check if user started a project before.
|
|
171
|
-
# stored in platformdirs.user_config_dir
|
|
172
|
-
# returns (RAWROOT, SYNCEDROOTS) if any, () otherwise.
|
|
173
|
-
# print location of conf file if print_conf_stdout
|
|
174
|
-
conf_dir = platformdirs.user_config_dir('mamsync', 'plutz')
|
|
175
|
-
conf_file = Path(conf_dir)/CONF_FILE
|
|
176
|
-
logger.debug('try reading config in %s'%conf_file)
|
|
177
|
-
if print_conf_stdout:
|
|
178
|
-
print(f'\nReading configuration from file {conf_file}')
|
|
179
|
-
if conf_file.exists():
|
|
180
|
-
conf_prs = configparser.ConfigParser()
|
|
181
|
-
conf_prs.read(conf_file)
|
|
182
|
-
RAWROOT = conf_prs.get('SECTION1', 'RAWROOT')
|
|
183
|
-
SYNCEDROOT = conf_prs.get('SECTION1', 'SYNCEDROOT')
|
|
184
|
-
SNDROOT = conf_prs.get('SECTION1', 'SNDROOT')
|
|
185
|
-
logger.debug('read from conf: RAWROOT= %s SYNCEDROOT= %s SNDROOT=%s'%
|
|
186
|
-
(RAWROOT, SYNCEDROOT, SNDROOT))
|
|
187
|
-
return RAWROOT, SYNCEDROOT, SNDROOT
|
|
188
|
-
else:
|
|
189
|
-
logger.debug(f'no config file found at {conf_file}')
|
|
190
|
-
return ()
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def main():
|
|
160
|
+
def new_parser():
|
|
194
161
|
parser = argparse.ArgumentParser()
|
|
195
|
-
parser.add_argument('--rawfolder',
|
|
196
|
-
nargs = 1,
|
|
197
|
-
dest='rawfolder',
|
|
198
|
-
help='Sets new value for raw root folder (i.e.: clips with TC)')
|
|
199
|
-
parser.add_argument('--syncedfolder',
|
|
200
|
-
nargs = 1,
|
|
201
|
-
dest='syncedfolder',
|
|
202
|
-
help='Sets new value for synced root folder (i.e.: synced clips without TC)')
|
|
203
|
-
parser.add_argument('--sndfolder',
|
|
204
|
-
nargs = 1,
|
|
205
|
-
dest='sndfolder',
|
|
206
|
-
help='Sets new value for sound folder (where ISOs will be stored)')
|
|
207
|
-
parser.add_argument('--showconf',
|
|
208
|
-
action='store_true',
|
|
209
|
-
dest='showconf',
|
|
210
|
-
help='Show current configured values.')
|
|
211
162
|
parser.add_argument('--resync',
|
|
212
163
|
action='store_true',
|
|
213
164
|
dest='resync',
|
|
@@ -222,47 +173,51 @@ def main():
|
|
|
222
173
|
action='store_true',
|
|
223
174
|
dest='terse',
|
|
224
175
|
help='Terse output')
|
|
225
|
-
parser.add_argument('--isos',
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
176
|
+
# parser.add_argument('--isos', # default True in mamsync
|
|
177
|
+
# action='store_true',
|
|
178
|
+
# dest='write_ISOs',
|
|
179
|
+
# help='Cut ISO sound files')
|
|
229
180
|
parser.add_argument('-t','--timelineoffset',
|
|
230
181
|
nargs=1,
|
|
231
182
|
default=['00:00:00:00'],
|
|
232
183
|
dest='timelineoffset',
|
|
233
184
|
help='When processing multicam, where to place clips on NLE timeline (HH:MM:SS:FF)')
|
|
185
|
+
return parser
|
|
186
|
+
|
|
187
|
+
def clear_log():
|
|
188
|
+
# clear the file logging clips already synced
|
|
189
|
+
data_dir = platformdirs.user_data_dir('mamsync', 'plutz', ensure_exists=True)
|
|
190
|
+
log_file = Path(data_dir)/mamconf.LOG_FILE
|
|
191
|
+
print('Clearing log file of synced clips: "%s"'%log_file)
|
|
192
|
+
with open(log_file, 'w') as fh:
|
|
193
|
+
fh.write('done:\n')
|
|
194
|
+
|
|
195
|
+
def main():
|
|
196
|
+
parser = new_parser()
|
|
234
197
|
args = parser.parse_args()
|
|
235
198
|
logger.debug(f'arguments from argparse {args}')
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
199
|
+
roots_strings = mamconf.get_proj(False)
|
|
200
|
+
roots_pathlibPaths = [Path(s) for s in mamconf.get_proj(False)]
|
|
201
|
+
logger.debug(f'roots_strings from mamconf.get_proj {roots_strings}')
|
|
202
|
+
logger.debug(f'roots_pathlibPaths from mamconf.get_proj {roots_pathlibPaths}')
|
|
203
|
+
# check all have values, except for PROXIES, the last one
|
|
204
|
+
if any([r == '' for r in roots_strings][:-1]):
|
|
205
|
+
print("Can't sync if some folders are not set:")
|
|
206
|
+
mamconf.print_out_conf(*mamconf.get_proj())
|
|
207
|
+
print('Bye.')
|
|
208
|
+
sys.exit(0)
|
|
209
|
+
# because optional PROXIES folder '' yields a '.' path, exclude it
|
|
210
|
+
for r in [rp for rp in roots_pathlibPaths if rp != Path('.')]:
|
|
211
|
+
if not r.is_absolute():
|
|
212
|
+
print(f'\rError: folder {r} must be an absolute path. Bye')
|
|
242
213
|
sys.exit(0)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
args.syncedfolder[0], args.sndfolder[0]]]
|
|
246
|
-
for r in roots:
|
|
247
|
-
if not r.is_absolute():
|
|
248
|
-
print(f'\rError: folder {r} must be an absolute path. Bye')
|
|
249
|
-
sys.exit(0)
|
|
250
|
-
if not r.exists():
|
|
251
|
-
print(f'\rError: folder {r} does not exist. Bye')
|
|
252
|
-
sys.exit(0)
|
|
253
|
-
if not r.is_dir():
|
|
254
|
-
print(f'\rError: path {r} is not a folder. Bye')
|
|
255
|
-
sys.exit(0)
|
|
256
|
-
# if still here, everything in 3 folders is ok
|
|
257
|
-
write_conf(*roots)
|
|
214
|
+
if not r.exists():
|
|
215
|
+
print(f'\rError: folder {r} does not exist. Bye')
|
|
258
216
|
sys.exit(0)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
# if still here, no rawfolder, --syncedfolder --sndfolder or --showconf
|
|
264
|
-
# so go for a scan and sync, maybe with a sub_dir
|
|
265
|
-
raw_root, synced_root, snd_root = get_proj(False)
|
|
217
|
+
if not r.is_dir():
|
|
218
|
+
print(f'\rError: path {r} is not a folder. Bye')
|
|
219
|
+
sys.exit(0)
|
|
220
|
+
raw_root, synced_root, snd_root, _ = roots_pathlibPaths
|
|
266
221
|
if args.sub_dir != None:
|
|
267
222
|
top_dir = args.sub_dir
|
|
268
223
|
logger.debug(f'sub _dir: {args.sub_dir}')
|
|
@@ -273,6 +228,9 @@ def main():
|
|
|
273
228
|
top_dir = raw_root
|
|
274
229
|
if args.resync:
|
|
275
230
|
clear_log()
|
|
231
|
+
# go, mamsync!
|
|
232
|
+
copy_to_syncedroot(raw_root, synced_root)
|
|
233
|
+
# copy_raw_root_tree_to_sndroot(raw_root, snd_root) # why?
|
|
276
234
|
multi2polywav.poly_all(top_dir)
|
|
277
235
|
scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
|
|
278
236
|
scanner.scan_media_and_build_devices_UID(synced_root=synced_root)
|
|
@@ -285,31 +243,6 @@ def main():
|
|
|
285
243
|
process_lag_adjustement(m)
|
|
286
244
|
audio_REC_only = all([m.device.dev_type == 'REC' for m
|
|
287
245
|
in scanner.found_media_files])
|
|
288
|
-
# if audio_REC_only:
|
|
289
|
-
# if scanner.input_structure != 'ordered':
|
|
290
|
-
# print('For merging audio only, use a directory per device, quitting')
|
|
291
|
-
# sys.exit(1)
|
|
292
|
-
# print('\n\n\nOnly audio recordings are present')
|
|
293
|
-
# print('Which device should be the reference?\n')
|
|
294
|
-
# devices = scanner.get_devices()
|
|
295
|
-
# maxch = len(devices)
|
|
296
|
-
# for i, d in enumerate(devices):
|
|
297
|
-
# print('\t%i - %s'%(i+1, d.name))
|
|
298
|
-
# while True:
|
|
299
|
-
# print('\nEnter your choice:', end='')
|
|
300
|
-
# choice = input()
|
|
301
|
-
# try:
|
|
302
|
-
# choice = int(choice)
|
|
303
|
-
# except:
|
|
304
|
-
# print('Please use numeric digits.')
|
|
305
|
-
# continue
|
|
306
|
-
# if choice not in list(range(1, maxch + 1)):
|
|
307
|
-
# print('Please enter a number in [1..%i]'%maxch)
|
|
308
|
-
# continue
|
|
309
|
-
# break
|
|
310
|
-
# ref_device = list(devices)[choice - 1]
|
|
311
|
-
# # ref_device = list(devices)[3 - 1]
|
|
312
|
-
# print('When only audio recordings are present, ISOs files will be cut and written.')
|
|
313
246
|
if not args.terse:
|
|
314
247
|
if scanner.input_structure == 'ordered':
|
|
315
248
|
print('\nDetected structured folders')
|
|
@@ -345,7 +278,6 @@ def main():
|
|
|
345
278
|
print('resulting in undefined results: quitting...')
|
|
346
279
|
quit()
|
|
347
280
|
print()
|
|
348
|
-
# recordings, rec_with_TTC = process_files(scanner.found_media_files, args)
|
|
349
281
|
recordings = [yaltc.Recording(m, do_plots=False) for m
|
|
350
282
|
in scanner.found_media_files]
|
|
351
283
|
recordings_with_time = [
|
|
@@ -353,11 +285,6 @@ def main():
|
|
|
353
285
|
for rec in recordings
|
|
354
286
|
if rec.get_start_time()
|
|
355
287
|
]
|
|
356
|
-
# if audio_REC_only:
|
|
357
|
-
# for rec in recordings:
|
|
358
|
-
# # print(rec, rec.device == ref_device)
|
|
359
|
-
# if rec.device == ref_device:
|
|
360
|
-
# rec.is_reference = True
|
|
361
288
|
if not args.terse:
|
|
362
289
|
table = Table(title="tictacsync results")
|
|
363
290
|
table.add_column("Recording\n", justify="center", style='gold1')
|
|
@@ -404,24 +331,24 @@ def main():
|
|
|
404
331
|
if not args.terse:
|
|
405
332
|
print('\nNothing to sync, bye.\n')
|
|
406
333
|
sys.exit(1)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
print('Warning, can\'t write ISOs without structured folders: [gold1]--isos[/gold1] option ignored.\n')
|
|
410
|
-
asked_ISOs = False
|
|
411
|
-
# output_dir = args.o
|
|
412
|
-
# if args.verbose_output or args.terse: # verbose, so no progress bars
|
|
334
|
+
if scanner.input_structure != 'ordered':
|
|
335
|
+
print('Warning, can\'t run mamsync without structured folders: [gold1]--isos[/gold1] option ignored.\n')
|
|
413
336
|
print('Merging...')
|
|
337
|
+
asked_ISOs = True # par defaut
|
|
338
|
+
dont_write_cam_folder = False # write them
|
|
414
339
|
for merger in matcher.mergers:
|
|
415
|
-
merger.
|
|
416
|
-
|
|
340
|
+
merger._build_audio_and_write_video(top_dir,
|
|
341
|
+
dont_write_cam_folder,
|
|
417
342
|
asked_ISOs,
|
|
418
|
-
|
|
343
|
+
synced_root = synced_root,
|
|
344
|
+
snd_root = snd_root,
|
|
345
|
+
raw_root = raw_root)
|
|
419
346
|
if not args.terse:
|
|
420
347
|
print("\n")
|
|
421
348
|
# find out where files were written
|
|
422
349
|
# a_merger = matcher.mergers[0]
|
|
423
350
|
# log file
|
|
424
|
-
p = Path(platformdirs.user_data_dir('mamsync', 'plutz'))/LOG_FILE
|
|
351
|
+
p = Path(platformdirs.user_data_dir('mamsync', 'plutz'))/mamconf.LOG_FILE
|
|
425
352
|
log_filehandle = open(p, 'a')
|
|
426
353
|
for merger in matcher.mergers:
|
|
427
354
|
print('[gold1]%s[/gold1]'%merger.videoclip.AVpath.name, end='')
|
|
@@ -448,7 +375,7 @@ def main():
|
|
|
448
375
|
print('\nThere are synced multicam clips but without structured folders')
|
|
449
376
|
print('they were not grouped together under the same folder.')
|
|
450
377
|
else:
|
|
451
|
-
matcher.move_multicam_to_dir()
|
|
378
|
+
matcher.move_multicam_to_dir(raw_root=raw_root, synced_root=synced_root)
|
|
452
379
|
else:
|
|
453
380
|
logger.debug('not really a multicam cluster, nothing to move')
|
|
454
381
|
sys.exit(0)
|
tictacsync/multi2polywav.py
CHANGED
|
@@ -83,7 +83,9 @@ def build_poly_name(multifiles):
|
|
|
83
83
|
def jump_metadata(from_file, to_file):
|
|
84
84
|
tempfile_for_metadata = tempfile.NamedTemporaryFile(suffix='.wav', delete=True)
|
|
85
85
|
tempfile_for_metadata = tempfile_for_metadata.name
|
|
86
|
-
|
|
86
|
+
# ffmpeg -i 32ch-44100-bwf.wav -i onechan.wav -map 1 -map_metadata 0 -c copy outmeta.wav
|
|
87
|
+
process_list = ['ffmpeg', '-loglevel', 'quiet', '-nostats', '-hide_banner', '-i', from_file, '-i', to_file, '-map', '1',
|
|
88
|
+
'-map_metadata', '0', '-c', 'copy', tempfile_for_metadata]
|
|
87
89
|
# ss = shlex.split("ffmpeg -i %s -i %s -map_metadata 0 -c copy %s"%(from_file, to_file, tempfile_for_metadata))
|
|
88
90
|
# print(ss)
|
|
89
91
|
# logger.debug('process %s'%process_list)
|