tictacsync 0.1a13__tar.gz → 0.1a15__tar.gz
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-0.1a13/tictacsync.egg-info → tictacsync-0.1a15}/PKG-INFO +2 -2
- {tictacsync-0.1a13 → tictacsync-0.1a15}/setup.py +2 -2
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/device_scanner.py +55 -19
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/entry.py +25 -20
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/timeline.py +55 -41
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/yaltc.py +48 -44
- {tictacsync-0.1a13 → tictacsync-0.1a15/tictacsync.egg-info}/PKG-INFO +2 -2
- {tictacsync-0.1a13 → tictacsync-0.1a15}/LICENSE +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/README.md +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/setup.cfg +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/LTCcheck.py +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/__init__.py +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync/multi2polywav.py +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/SOURCES.txt +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/dependency_links.txt +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/entry_points.txt +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/not-zip-safe +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/requires.txt +0 -0
- {tictacsync-0.1a13 → tictacsync-0.1a15}/tictacsync.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tictacsync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1a15
|
|
4
4
|
Summary: command for syncing audio video recordings
|
|
5
|
-
Home-page: https://
|
|
5
|
+
Home-page: https://tictacsync.org/
|
|
6
6
|
Author: Raymond Lutz
|
|
7
7
|
Author-email: lutzrayblog@mac.com
|
|
8
8
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
@@ -31,7 +31,7 @@ setup(
|
|
|
31
31
|
'multi2polywav = tictacsync.multi2polywav:main',
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
|
-
version = '0.
|
|
34
|
+
version = '0.1a15',
|
|
35
35
|
description = "command for syncing audio video recordings",
|
|
36
36
|
long_description_content_type='text/markdown',
|
|
37
37
|
long_description = long_descr,
|
|
@@ -39,7 +39,7 @@ setup(
|
|
|
39
39
|
zip_safe=False,
|
|
40
40
|
author = "Raymond Lutz",
|
|
41
41
|
author_email = "lutzrayblog@mac.com",
|
|
42
|
-
url ='https://
|
|
42
|
+
url ='https://tictacsync.org/',
|
|
43
43
|
classifiers=[
|
|
44
44
|
'Development Status :: 2 - Pre-Alpha',
|
|
45
45
|
'Environment :: Console',
|
|
@@ -17,6 +17,8 @@ AMR APE AU AWB DSS DVF FLAC GSM IKLAX IVS M4A M4B M4P MMF MP3 MPC MSV NMF
|
|
|
17
17
|
OGG OGA MOGG OPUS RA RM RAW RF64 SLN TTA VOC VOX WAV WMA WV WEBM 8SVX CDA MOV AVI BWF""".split()
|
|
18
18
|
|
|
19
19
|
import ffmpeg
|
|
20
|
+
from os import listdir
|
|
21
|
+
from os.path import isfile, join, isdir
|
|
20
22
|
from collections import namedtuple
|
|
21
23
|
from pathlib import Path
|
|
22
24
|
from pprint import pformat
|
|
@@ -121,24 +123,28 @@ def get_device_ffprobe_UID(file):
|
|
|
121
123
|
class Scanner:
|
|
122
124
|
"""
|
|
123
125
|
Class that encapsulates scanning of the directory given as CLI argument.
|
|
124
|
-
Depending on the IO structure choosen (loose|
|
|
126
|
+
Depending on the IO structure choosen (loose|folder_is_device), enforce some directory
|
|
125
127
|
structure (or not). Build a list of media files found and a try to
|
|
126
128
|
indentify uniquely the device used to record each media file.
|
|
127
129
|
|
|
128
130
|
Attributes:
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
input_structure: string
|
|
131
133
|
|
|
132
134
|
Any of
|
|
133
|
-
'loose'
|
|
134
|
-
all files audio + video in
|
|
135
|
-
'
|
|
135
|
+
'loose'
|
|
136
|
+
all files audio + video are in top folder
|
|
137
|
+
'folder_is_device'
|
|
136
138
|
eg for multicam on Davinci Resolve
|
|
137
139
|
|
|
138
140
|
top_directory : string
|
|
139
141
|
|
|
140
142
|
String of path of where to start searching for media files.
|
|
141
143
|
|
|
144
|
+
top_dir_has_multicam : bool
|
|
145
|
+
|
|
146
|
+
If top dir is folder structures AND more than on cam
|
|
147
|
+
|
|
142
148
|
devices_names : dict of str
|
|
143
149
|
|
|
144
150
|
more evocative names for each device, keys are same as
|
|
@@ -148,14 +154,13 @@ class Scanner:
|
|
|
148
154
|
{
|
|
149
155
|
'path' : as is ,
|
|
150
156
|
'sample length' : as is
|
|
151
|
-
'dev' : Device
|
|
157
|
+
'dev' : Device namedtuple
|
|
152
158
|
}
|
|
153
159
|
"""
|
|
154
160
|
|
|
155
161
|
def __init__(
|
|
156
162
|
self,
|
|
157
163
|
top_directory,
|
|
158
|
-
IOstruct,
|
|
159
164
|
stay_silent=False,
|
|
160
165
|
):
|
|
161
166
|
"""
|
|
@@ -164,9 +169,9 @@ class Scanner:
|
|
|
164
169
|
"""
|
|
165
170
|
self.top_directory = top_directory
|
|
166
171
|
self.found_media_files = []
|
|
167
|
-
self.IOstruct = IOstruct
|
|
168
172
|
self.stay_silent = stay_silent
|
|
169
173
|
|
|
174
|
+
|
|
170
175
|
def get_devices_number(self):
|
|
171
176
|
# how many devices have been found
|
|
172
177
|
return len(set([m['dev'].UID for m in self.found_media_files]))
|
|
@@ -185,12 +190,33 @@ class Scanner:
|
|
|
185
190
|
|
|
186
191
|
Populates Scanner.found_media_files, a list of dict as
|
|
187
192
|
{
|
|
188
|
-
'path' :
|
|
189
|
-
'sample length' :
|
|
190
|
-
'dev
|
|
193
|
+
'path' : as is ,
|
|
194
|
+
'sample length' : as is
|
|
195
|
+
'dev' : Device namedtuple
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
Sets input_structure = 'loose'|'folder_is_device'
|
|
199
|
+
|
|
193
200
|
"""
|
|
201
|
+
visible = [f for f in listdir(self.top_directory) if f[0] != '.']
|
|
202
|
+
logger.debug('visible: %s'%visible)
|
|
203
|
+
are_dir = all([isdir(join(self.top_directory, f)) for f in visible])
|
|
204
|
+
are_files = all([isfile(join(self.top_directory, f)) for f in visible])
|
|
205
|
+
# onlyfiles = [f for f in listdir(self.top_directory) if isfile()]
|
|
206
|
+
|
|
207
|
+
logger.debug('dir: %s'%[isdir(join(self.top_directory, f)) for f in visible])
|
|
208
|
+
if are_dir:
|
|
209
|
+
print('\nAssuming those are device folders: ',end='')
|
|
210
|
+
[print(f, end=', ') for f in visible[:-1]]
|
|
211
|
+
print('%s.\n'%visible[-1])
|
|
212
|
+
self.input_structure = 'folder_is_device'
|
|
213
|
+
else: # are_files
|
|
214
|
+
self.input_structure = 'loose'
|
|
215
|
+
if not are_dir and not are_files:
|
|
216
|
+
print('\nInput structure mixed up, files AND folders at top level')
|
|
217
|
+
print(' %s, quitting.\n'%self.top_directory)
|
|
218
|
+
quit()
|
|
219
|
+
# quit()
|
|
194
220
|
files = Path(self.top_directory).rglob('*.*')
|
|
195
221
|
paths = [
|
|
196
222
|
p
|
|
@@ -202,15 +228,17 @@ class Scanner:
|
|
|
202
228
|
new_media = media_dict_from_path(p) # dev UID set here
|
|
203
229
|
self.found_media_files.append(new_media)
|
|
204
230
|
logger.debug('Scanner.found_media_files = %s'%self.found_media_files)
|
|
205
|
-
if self.
|
|
206
|
-
self.
|
|
207
|
-
self.
|
|
231
|
+
if self.input_structure == 'folder_is_device':
|
|
232
|
+
self._enforce_folder_is_device()
|
|
233
|
+
self._use_folder_as_device_name()
|
|
234
|
+
else:
|
|
235
|
+
self.top_dir_has_multicam = False
|
|
208
236
|
pprint_found_media_files = pformat(self.found_media_files)
|
|
209
237
|
logger.debug('scanner.found_media_files = %s'%pprint_found_media_files)
|
|
210
238
|
|
|
211
|
-
def
|
|
239
|
+
def _use_folder_as_device_name(self):
|
|
212
240
|
"""
|
|
213
|
-
For each media in self.found_media_files replace existing
|
|
241
|
+
For each media in self.found_media_files replace existing Device.name by
|
|
214
242
|
folder name.
|
|
215
243
|
|
|
216
244
|
Returns nothing
|
|
@@ -229,7 +257,7 @@ class Scanner:
|
|
|
229
257
|
# quit()
|
|
230
258
|
logger.debug(self.found_media_files)
|
|
231
259
|
|
|
232
|
-
def
|
|
260
|
+
def _enforce_folder_is_device(self):
|
|
233
261
|
"""
|
|
234
262
|
|
|
235
263
|
Checks for files in self.found_media_files for structure as following.
|
|
@@ -259,6 +287,7 @@ class Scanner:
|
|
|
259
287
|
# build lists for multiple reference of iterators
|
|
260
288
|
media_grouped_by_folder = [ (k, list(iterator)) for k, iterator
|
|
261
289
|
in groupby(medias, folder_key)]
|
|
290
|
+
logger.debug('media_grouped_by_folder %s'%media_grouped_by_folder)
|
|
262
291
|
complete_path_folders = [e[0] for e in media_grouped_by_folder]
|
|
263
292
|
name_of_folders = [p.name for p in complete_path_folders]
|
|
264
293
|
logger.debug('complete_path_folders with media files %s'%complete_path_folders)
|
|
@@ -276,11 +305,15 @@ class Scanner:
|
|
|
276
305
|
print('please rename and rerun. Quitting..')
|
|
277
306
|
quit()
|
|
278
307
|
# print(media_grouped_by_folder)
|
|
308
|
+
n_CAM_folder = 0
|
|
279
309
|
for folder, list_of_medias_in_folder in media_grouped_by_folder:
|
|
280
310
|
# list_of_medias_in_folder = list(media_files_same_folder_iterator)
|
|
281
311
|
# check all medias are either video or audio recordings in folder
|
|
282
312
|
# if not, warn user and quit.
|
|
283
313
|
dev_types = set([m['dev'].type for m in list_of_medias_in_folder])
|
|
314
|
+
logger.debug('dev_types %s'%dev_types)
|
|
315
|
+
if dev_types == {'CAM'}:
|
|
316
|
+
n_CAM_folder += 1
|
|
284
317
|
if len(dev_types) != 1:
|
|
285
318
|
print('\nProblem while scanning for media files. In [gold1]%s[/gold1]:'%folder)
|
|
286
319
|
print('There is a mix of video and audio files:')
|
|
@@ -295,7 +328,7 @@ class Scanner:
|
|
|
295
328
|
logger.debug('devices in folder %s:'%folder)
|
|
296
329
|
logger.debug(' media with unknown devices %s'%unidentified)
|
|
297
330
|
logger.debug(' media with UIDed devices %s'%UIDed)
|
|
298
|
-
if
|
|
331
|
+
if len(unidentified) != 0 and len(UIDed) != 0:
|
|
299
332
|
print('\nProblem while grouping files in [gold1]%s[/gold1]:'%folder)
|
|
300
333
|
print('There is a mix of unidentifiable and identified devices.')
|
|
301
334
|
print('Is this file:')
|
|
@@ -328,7 +361,10 @@ class Scanner:
|
|
|
328
361
|
# if we are here, the check is done: either
|
|
329
362
|
# all files in folder are from unidentified device or
|
|
330
363
|
# all files in folder are from the same identified device
|
|
331
|
-
|
|
364
|
+
logger.debug('n_CAM_folder %i'%n_CAM_folder)
|
|
365
|
+
self.top_dir_has_multicam = n_CAM_folder > 1
|
|
366
|
+
logger.debug('top_dir_has_multicam: %s'%self.top_dir_has_multicam)
|
|
367
|
+
return
|
|
332
368
|
|
|
333
369
|
|
|
334
370
|
|
|
@@ -118,25 +118,28 @@ def main():
|
|
|
118
118
|
parser.add_argument('-p', action='store_true', default=False,
|
|
119
119
|
dest='plotting',
|
|
120
120
|
help='Make plots')
|
|
121
|
+
parser.add_argument('--isos', action='store_true', default=False,
|
|
122
|
+
dest='write_ISOs',
|
|
123
|
+
help='Write ISO sound files')
|
|
121
124
|
parser.add_argument('--nosync', action='store_true',
|
|
122
125
|
dest='nosync',
|
|
123
126
|
help='just scan and decode')
|
|
124
127
|
parser.add_argument('--terse', action='store_true',
|
|
125
128
|
dest='terse',
|
|
126
129
|
help='terse output')
|
|
127
|
-
parser.add_argument('--io', dest='output_structure', type=str, default='loose',
|
|
128
|
-
|
|
130
|
+
# parser.add_argument('--io', dest='output_structure', type=str, default='loose',
|
|
131
|
+
# help='should be one of: loose [DEFAULT], foldercam')
|
|
129
132
|
args = parser.parse_args()
|
|
130
|
-
if not args.output_structure in 'loose foldercam reconf'.split():
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
# if not args.output_structure in 'loose foldercam reconf'.split():
|
|
134
|
+
# print('Unknown --io option value "%s", should be one of: loose [DEFAULT] or foldercam'%args.output_structure)
|
|
135
|
+
# quit()
|
|
133
136
|
if args.verbose_output:
|
|
134
137
|
logger.add(sys.stderr, level="DEBUG")
|
|
135
138
|
# logger.add(sys.stdout, filter="__main__")
|
|
136
139
|
# logger.add(sys.stdout, filter="device_scanner")
|
|
137
140
|
# logger.add(sys.stdout, filter="yaltc")
|
|
138
141
|
# logger.add(sys.stdout, filter="timeline")
|
|
139
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "
|
|
142
|
+
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_enforce_folder_is_device")
|
|
140
143
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "build_audio_and_write_video")
|
|
141
144
|
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_BFSK_word_boundaries")
|
|
142
145
|
top_dir = args.directory[0]
|
|
@@ -147,8 +150,7 @@ def main():
|
|
|
147
150
|
print('%s is not a directory or doesnt exist.'%top_dir)
|
|
148
151
|
quit()
|
|
149
152
|
multi2polywav.poly_all(top_dir)
|
|
150
|
-
scanner = device_scanner.Scanner(top_dir, args.
|
|
151
|
-
stay_silent=args.terse)
|
|
153
|
+
scanner = device_scanner.Scanner(top_dir, stay_silent=args.terse)
|
|
152
154
|
# (Path(top_dir)/Path('tictacsynced')).mkdir(exist_ok=True)
|
|
153
155
|
# scanner.cluster_media_files_by_name()
|
|
154
156
|
scanner.scan_media_and_build_devices_UID(recursive=False)
|
|
@@ -208,6 +210,7 @@ def main():
|
|
|
208
210
|
console.print(table)
|
|
209
211
|
print('\n')
|
|
210
212
|
n_devices = scanner.get_devices_number()
|
|
213
|
+
OUT_struct_for_mcam = scanner.top_dir_has_multicam
|
|
211
214
|
# if n_devices > 2:
|
|
212
215
|
# print('\nMerging for more than 2 devices is not implemented yet, quitting...')
|
|
213
216
|
# quit()
|
|
@@ -215,7 +218,7 @@ def main():
|
|
|
215
218
|
if not args.terse:
|
|
216
219
|
print('\nNothing to sync, exiting.\n')
|
|
217
220
|
quit()
|
|
218
|
-
matcher = timeline.Matcher(recordings_with_time
|
|
221
|
+
matcher = timeline.Matcher(recordings_with_time)
|
|
219
222
|
matcher.scan_audio_for_each_ref_rec()
|
|
220
223
|
if not matcher.video_mergers:
|
|
221
224
|
if not args.terse:
|
|
@@ -223,23 +226,25 @@ def main():
|
|
|
223
226
|
quit()
|
|
224
227
|
if args.verbose_output or args.terse: # verbose, so no progress bars
|
|
225
228
|
for stitcher in matcher.video_mergers:
|
|
226
|
-
stitcher.build_audio_and_write_video(top_dir,
|
|
229
|
+
stitcher.build_audio_and_write_video(top_dir, OUT_struct_for_mcam,
|
|
230
|
+
args.write_ISOs)
|
|
227
231
|
else:
|
|
228
232
|
for stitcher in track(matcher.video_mergers,
|
|
229
233
|
description="4/4 Merging sound to videos:"):
|
|
230
|
-
stitcher.build_audio_and_write_video(top_dir,
|
|
234
|
+
stitcher.build_audio_and_write_video(top_dir, OUT_struct_for_mcam,
|
|
235
|
+
args.write_ISOs)
|
|
231
236
|
if not args.terse:
|
|
232
237
|
print("\n")
|
|
233
|
-
if args.output_structure == 'foldercam':
|
|
234
|
-
|
|
238
|
+
# if args.output_structure == 'foldercam':
|
|
239
|
+
print('output written in [gold1]%s[/gold1] folder in [gold1]%s[/gold1]\n'%(
|
|
235
240
|
'SyncedMedia',top_dir))
|
|
236
|
-
if args.output_structure == 'loose':
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
241
|
+
# if args.output_structure == 'loose':
|
|
242
|
+
for stitcher in matcher.video_mergers:
|
|
243
|
+
print('[gold1]%s[/gold1]'%stitcher.ref_recording.AVpath.name, end='')
|
|
244
|
+
for audio in stitcher.get_matched_audio_recs():
|
|
245
|
+
print(' + [gold1]%s[/gold1]'%audio.AVpath.name, end='')
|
|
246
|
+
new_file = stitcher.ref_recording.final_synced_file.parts
|
|
247
|
+
print(' became [gold1]%s[/gold1]'%stitcher.ref_recording.final_synced_file.name)
|
|
243
248
|
# matcher._build_otio_tracks_for_cam()
|
|
244
249
|
matcher.shrink_gaps_between_takes()
|
|
245
250
|
|
|
@@ -464,7 +464,7 @@ class AudioStitcherVideoMerger:
|
|
|
464
464
|
return {'error msg': 'more than one "mix" token'}
|
|
465
465
|
output_dict['mix'] = [mono_mix_tags[0][1]]
|
|
466
466
|
else:
|
|
467
|
-
output_dict['mix'] =
|
|
467
|
+
output_dict['mix'] = []
|
|
468
468
|
[tracks_lines.remove(e) for e in mono_mix_tags]
|
|
469
469
|
logger.debug('mono mix done, will continue with %s'%tracks_lines)
|
|
470
470
|
# fourth, look for 'ttc'
|
|
@@ -484,18 +484,22 @@ class AudioStitcherVideoMerger:
|
|
|
484
484
|
output_dict['0'] = zeroed
|
|
485
485
|
[tracks_lines.remove(('0',i)) for i in zeroed]
|
|
486
486
|
else:
|
|
487
|
-
output_dict['0'] =
|
|
487
|
+
output_dict['0'] = []
|
|
488
488
|
# sixth, check for 'others'
|
|
489
489
|
logger.debug('0s done, will continue with %s'%tracks_lines)
|
|
490
490
|
if tracks_lines:
|
|
491
491
|
output_dict['others'] = tracks_lines
|
|
492
492
|
else:
|
|
493
|
-
output_dict['others'] =
|
|
493
|
+
output_dict['others'] = []
|
|
494
494
|
return output_dict
|
|
495
495
|
|
|
496
|
-
def build_audio_and_write_video(self, top_dir,
|
|
496
|
+
def build_audio_and_write_video(self, top_dir,
|
|
497
|
+
OUT_struct_for_mcam,
|
|
498
|
+
write_ISOs):
|
|
497
499
|
"""
|
|
498
|
-
|
|
500
|
+
OUT_struct_for_mcam: bool flag
|
|
501
|
+
|
|
502
|
+
write_ISOs: bool flag
|
|
499
503
|
|
|
500
504
|
For each audio devices found overlapping self.ref_recording: pad, trim
|
|
501
505
|
or stretch audio files calling _get_concatenated_audiofile_for(), and
|
|
@@ -509,15 +513,14 @@ class AudioStitcherVideoMerger:
|
|
|
509
513
|
"""
|
|
510
514
|
logger.debug('device for rec %s: %s'%(self.ref_recording,
|
|
511
515
|
self.ref_recording.device))
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
synced_clip_dir = Path(top_dir)
|
|
516
|
+
D1, D2 = CAMDIR.split('/')
|
|
517
|
+
if OUT_struct_for_mcam:
|
|
518
|
+
device_name = self.ref_recording.device.name
|
|
519
|
+
synced_clip_dir = Path(top_dir)/D1/D2/device_name
|
|
520
|
+
logger.debug('created %s'%synced_clip_dir)
|
|
521
|
+
else:
|
|
522
|
+
synced_clip_dir = Path(top_dir)/D1
|
|
523
|
+
os.makedirs(synced_clip_dir, exist_ok=True)
|
|
521
524
|
synced_clip_file = synced_clip_dir/Path(self.ref_recording.new_rec_name).name
|
|
522
525
|
logger.debug('editing files for %s'%synced_clip_file)
|
|
523
526
|
# looping over devices:
|
|
@@ -540,17 +543,34 @@ class AudioStitcherVideoMerger:
|
|
|
540
543
|
# look for track names in TRACKSFN file:
|
|
541
544
|
tracks_file = source_audio_folder/TRACKSFN
|
|
542
545
|
track_names = False
|
|
546
|
+
nchan = sox.file_info.channels(str(joined_audio))
|
|
543
547
|
if os.path.isfile(tracks_file):
|
|
544
548
|
logger.debug('found file: %s'%(TRACKSFN))
|
|
545
549
|
track_dict = self._parse_track_values(tracks_file)
|
|
550
|
+
if track_dict['error msg']:
|
|
551
|
+
print('\nError parsing %s file: %s, quitting.\n'%(tracks_file,
|
|
552
|
+
track_dict['error msg']))
|
|
553
|
+
quit()
|
|
554
|
+
|
|
546
555
|
logger.debug('parsed track_dict %s'%track_dict)
|
|
556
|
+
n_IDied_chan = 2*len(track_dict['stereomics'])
|
|
557
|
+
n_IDied_chan += len(track_dict['mix'])
|
|
558
|
+
n_IDied_chan += len(track_dict['0'])
|
|
559
|
+
n_IDied_chan += len(track_dict['others'])
|
|
560
|
+
n_IDied_chan += 1 # for ttc track
|
|
561
|
+
logger.debug(' n chan: %i n tracks file: %i'%(nchan, n_IDied_chan))
|
|
562
|
+
if n_IDied_chan != nchan:
|
|
563
|
+
print('\nError parsing %s content'%tracks_file)
|
|
564
|
+
print('incoherent number of tracks, %i vs %i quitting\n'%
|
|
565
|
+
(nchan, n_IDied_chan))
|
|
566
|
+
# quit()
|
|
547
567
|
err_msg = track_dict['error msg']
|
|
548
568
|
if err_msg != None:
|
|
549
569
|
print('Error, quitting: in file %s, %s'%(tracks_file, err_msg))
|
|
550
570
|
raise Exception
|
|
551
571
|
else:
|
|
552
572
|
logger.debug('no tracks.txt file found')
|
|
553
|
-
if
|
|
573
|
+
if write_ISOs:
|
|
554
574
|
# build ISOs subfolders structure, see comment string below
|
|
555
575
|
video_stem_WO_suffix = synced_clip_file.stem.split('.')[0]
|
|
556
576
|
D1, D2 = ISOsDIR.split('/')
|
|
@@ -561,11 +581,6 @@ class AudioStitcherVideoMerger:
|
|
|
561
581
|
logger.debug('will split audio to %s'%(ISOdir))
|
|
562
582
|
shutil.copy(joined_audio, ISO_multi_chan)
|
|
563
583
|
filepath = str(ISO_multi_chan)
|
|
564
|
-
nchan = sox.file_info.channels(str(ISO_multi_chan))
|
|
565
|
-
if track_names and nchan != len(track_names):
|
|
566
|
-
print('error parsing %s content'%tracks_file)
|
|
567
|
-
print('incoherent number of tracks, quitting\n')
|
|
568
|
-
quit()
|
|
569
584
|
for n in range(nchan):
|
|
570
585
|
if track_names:
|
|
571
586
|
iso_destination = ISOdir / (track_names[n]+'.wav')
|
|
@@ -599,7 +614,6 @@ class AudioStitcherVideoMerger:
|
|
|
599
614
|
canon24fps02.MOV
|
|
600
615
|
"""
|
|
601
616
|
|
|
602
|
-
|
|
603
617
|
def _keep_VIDEO_only(self, video_path):
|
|
604
618
|
# return file handle to a temp video file formed from the video_path
|
|
605
619
|
# stripped of its sound
|
|
@@ -701,7 +715,7 @@ class Matcher:
|
|
|
701
715
|
|
|
702
716
|
"""
|
|
703
717
|
|
|
704
|
-
def __init__(self, recordings_list
|
|
718
|
+
def __init__(self, recordings_list):
|
|
705
719
|
"""
|
|
706
720
|
At this point in the program, all recordings in recordings_list should
|
|
707
721
|
have a valid Recording.start_time attribute and one of its channels
|
|
@@ -711,31 +725,31 @@ class Matcher:
|
|
|
711
725
|
"""
|
|
712
726
|
self.recordings = recordings_list
|
|
713
727
|
self.video_mergers = []
|
|
714
|
-
self._rename_all_recs(IO_structure)
|
|
728
|
+
# self._rename_all_recs(IO_structure)
|
|
715
729
|
|
|
716
730
|
|
|
717
|
-
def _rename_all_recs(self
|
|
731
|
+
def _rename_all_recs(self):
|
|
718
732
|
"""
|
|
719
733
|
Add _synced to filenames of synced video files. Change stored name only:
|
|
720
734
|
files have yet to be written to.
|
|
721
735
|
"""
|
|
722
|
-
match IO_structure:
|
|
723
|
-
case 'foldercam':
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
case 'loose':
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
736
|
+
# match IO_structure:
|
|
737
|
+
# case 'foldercam':
|
|
738
|
+
for rec in self.recordings:
|
|
739
|
+
rec_extension = rec.AVpath.suffix
|
|
740
|
+
rel_path_new_name = '%s%s'%(rec.AVpath.stem, rec_extension)
|
|
741
|
+
rec.new_rec_name = Path(rel_path_new_name)
|
|
742
|
+
logger.debug('for %s new name: %s'%(
|
|
743
|
+
_pathname(rec.AVpath),
|
|
744
|
+
_pathname(rec.new_rec_name)))
|
|
745
|
+
# case 'loose':
|
|
746
|
+
# for rec in self.recordings:
|
|
747
|
+
# rec_extension = rec.AVpath.suffix
|
|
748
|
+
# rel_path_new_name = '%s%s%s'%('ttsd_',rec.AVpath.stem, rec_extension)
|
|
749
|
+
# rec.new_rec_name = Path(rel_path_new_name)
|
|
750
|
+
# logger.debug('for %s new name: %s'%(
|
|
751
|
+
# _pathname(rec.AVpath),
|
|
752
|
+
# _pathname(rec.new_rec_name)))
|
|
739
753
|
|
|
740
754
|
def scan_audio_for_each_ref_rec(self):
|
|
741
755
|
"""
|
|
@@ -68,42 +68,42 @@ N_SYMBOLS_SAMD21 = 35 # including sync pulse
|
|
|
68
68
|
|
|
69
69
|
BPF_LOW_FRQ, BPF_HIGH_FRQ = (0.5*F1, 2*F2)
|
|
70
70
|
|
|
71
|
-
try:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
except ffmpeg.Error as e:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
names_str, layouts = [l.split('\n') for l in
|
|
83
|
-
|
|
84
|
-
[names_str.pop(0) for i in range(2)]
|
|
85
|
-
[names_str.pop(-1) for i in range(3)]
|
|
86
|
-
layouts.pop(0)
|
|
87
|
-
layouts.pop(-1)
|
|
88
|
-
channel_name_regex = re.compile(r'(\w+)\s+(.+)')
|
|
89
|
-
layouts_regex = re.compile(r'(\S+)\s+(.+)')
|
|
90
|
-
FFMPEG_CHAN_NAMES = [
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]
|
|
94
|
-
FFMPEG_LAYO_NAMES = dict([
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
])
|
|
98
|
-
|
|
99
|
-
"""
|
|
100
|
-
FFMPEG_CHAN_NAMES are the names used by ffmpeg for channels, eg: FL, FR, FC for
|
|
101
|
-
front left, front right and front center.
|
|
102
|
-
FFMPEG_LAYO_NAMES are the order of channels for each layout, eg: stereo is FL+FR
|
|
103
|
-
and 5.1 is FL+FR+FC+LFE+BL+BR. Layout names are the dict keys.
|
|
104
|
-
Run ffmpeg -layouts for details.
|
|
105
|
-
|
|
106
|
-
"""
|
|
71
|
+
# try:
|
|
72
|
+
# layouts, _ = (
|
|
73
|
+
# ffmpeg.input('').output('')
|
|
74
|
+
# .global_args("-loglevel", "quiet","-nostats","-hide_banner","-layouts")
|
|
75
|
+
# .run(capture_stdout=True)
|
|
76
|
+
# )
|
|
77
|
+
# except ffmpeg.Error as e:
|
|
78
|
+
# print('error',e.stderr)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# names_str, layouts = [l.split('\n') for l in
|
|
83
|
+
# layouts.decode("utf-8").split('DECOMPOSITION')]
|
|
84
|
+
# [names_str.pop(0) for i in range(2)]
|
|
85
|
+
# [names_str.pop(-1) for i in range(3)]
|
|
86
|
+
# layouts.pop(0)
|
|
87
|
+
# layouts.pop(-1)
|
|
88
|
+
# channel_name_regex = re.compile(r'(\w+)\s+(.+)')
|
|
89
|
+
# layouts_regex = re.compile(r'(\S+)\s+(.+)')
|
|
90
|
+
# FFMPEG_CHAN_NAMES = [
|
|
91
|
+
# channel_name_regex.match(name).group(1)
|
|
92
|
+
# for name in names_str
|
|
93
|
+
# ]
|
|
94
|
+
# FFMPEG_LAYO_NAMES = dict([
|
|
95
|
+
# layouts_regex.match(lay_out).groups()
|
|
96
|
+
# for lay_out in layouts
|
|
97
|
+
# ])
|
|
98
|
+
|
|
99
|
+
# """
|
|
100
|
+
# FFMPEG_CHAN_NAMES are the names used by ffmpeg for channels, eg: FL, FR, FC for
|
|
101
|
+
# front left, front right and front center.
|
|
102
|
+
# FFMPEG_LAYO_NAMES are the order of channels for each layout, eg: stereo is FL+FR
|
|
103
|
+
# and 5.1 is FL+FR+FC+LFE+BL+BR. Layout names are the dict keys.
|
|
104
|
+
# Run ffmpeg -layouts for details.
|
|
105
|
+
|
|
106
|
+
# """
|
|
107
107
|
|
|
108
108
|
# utility for accessing pathnames
|
|
109
109
|
def _pathname(tempfile_or_path):
|
|
@@ -620,7 +620,6 @@ class Decoder:
|
|
|
620
620
|
format="png")
|
|
621
621
|
plt.close()
|
|
622
622
|
|
|
623
|
-
|
|
624
623
|
def _detect_sync_pulse_position(self):
|
|
625
624
|
"""
|
|
626
625
|
Determines noise level during silence period and use it to detect the
|
|
@@ -1022,10 +1021,13 @@ class Recording:
|
|
|
1022
1021
|
probe : dict
|
|
1023
1022
|
returned value of ffmpeg.probe(self.AVpath)
|
|
1024
1023
|
|
|
1025
|
-
YaLTC_channel :
|
|
1026
|
-
which channel is sync track
|
|
1027
|
-
|
|
1028
|
-
|
|
1024
|
+
YaLTC_channel : int
|
|
1025
|
+
which channel is sync track. 0 is first channel,
|
|
1026
|
+
set in _read_sound_find_YaLTC().
|
|
1027
|
+
|
|
1028
|
+
silent_channels : list of ints
|
|
1029
|
+
list of channel which are silent. 1 is first channel,
|
|
1030
|
+
set in _strip_yaltc()
|
|
1029
1031
|
|
|
1030
1032
|
decoder : yaltc.decoder
|
|
1031
1033
|
associated decoder object, if file is audiovideo
|
|
@@ -1461,6 +1463,8 @@ class Recording:
|
|
|
1461
1463
|
Returns a tempfile.NamedTemporaryFile of sound file stripped
|
|
1462
1464
|
of YaLTC channel and of any silent channel. Criteria for
|
|
1463
1465
|
silence is sox stats "RMS lev db" < DB_RMS_SILENCE_SOX
|
|
1466
|
+
|
|
1467
|
+
Sets self.silent_channels
|
|
1464
1468
|
"""
|
|
1465
1469
|
input_file = _pathname(self.AVpath)
|
|
1466
1470
|
# building dict according to pysox.remix format.
|
|
@@ -1472,12 +1476,12 @@ class Recording:
|
|
|
1472
1476
|
_, _, stat_output = sox.core.sox(args)
|
|
1473
1477
|
sox_RMS_lev_dB = stat_output.split('\n')[5].split()[4:]
|
|
1474
1478
|
logger.debug('Sox RMS %s, n_channels %i'%(sox_RMS_lev_dB, n_channels))
|
|
1475
|
-
|
|
1479
|
+
self.silent_channels = [i + 1 for i,db in enumerate(sox_RMS_lev_dB)
|
|
1476
1480
|
if float(db) < DB_RMS_SILENCE_SOX]
|
|
1477
|
-
logger.debug('silent chans (1st = #1) %s'%
|
|
1481
|
+
logger.debug('silent chans (1st = #1) %s'%self.silent_channels)
|
|
1478
1482
|
chan_list_out = range(1, n_channels) # eg [1,2,3]
|
|
1479
1483
|
all_channels = range(1, n_channels + 1) # eg [1,2,3,4]
|
|
1480
|
-
excluded_chans =
|
|
1484
|
+
excluded_chans = self.silent_channels + [YaLTC_channel]
|
|
1481
1485
|
logger.debug('excluded chans %s'%excluded_chans)
|
|
1482
1486
|
kept_chans = [[n] for n in all_channels if n not in excluded_chans]
|
|
1483
1487
|
# eg [[1], [3], [4]]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tictacsync
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1a15
|
|
4
4
|
Summary: command for syncing audio video recordings
|
|
5
|
-
Home-page: https://
|
|
5
|
+
Home-page: https://tictacsync.org/
|
|
6
6
|
Author: Raymond Lutz
|
|
7
7
|
Author-email: lutzrayblog@mac.com
|
|
8
8
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|