tictacsync 0.98a0__py3-none-any.whl → 1.4.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 +62 -268
- tictacsync/entry.py +57 -166
- tictacsync/load_fieldr_reaper.py +352 -0
- tictacsync/mamconf.py +157 -0
- tictacsync/mamdav.py +642 -0
- tictacsync/mamreap.py +481 -0
- tictacsync/mamsync.py +343 -0
- tictacsync/multi2polywav.py +4 -3
- tictacsync/new-sound-resolve.py +469 -0
- tictacsync/newmix.py +483 -0
- tictacsync/remrgmx.py +6 -10
- tictacsync/splitmix.py +87 -0
- tictacsync/timeline.py +154 -98
- tictacsync/yaltc.py +359 -31
- {tictacsync-0.98a0.dist-info → tictacsync-1.4.0b0.dist-info}/METADATA +5 -6
- tictacsync-1.4.0b0.dist-info/RECORD +24 -0
- tictacsync-1.4.0b0.dist-info/entry_points.txt +7 -0
- tictacsync-0.98a0.dist-info/RECORD +0 -16
- tictacsync-0.98a0.dist-info/entry_points.txt +0 -4
- {tictacsync-0.98a0.dist-info → tictacsync-1.4.0b0.dist-info}/LICENSE +0 -0
- {tictacsync-0.98a0.dist-info → tictacsync-1.4.0b0.dist-info}/WHEEL +0 -0
- {tictacsync-0.98a0.dist-info → tictacsync-1.4.0b0.dist-info}/top_level.txt +0 -0
tictacsync/timeline.py
CHANGED
|
@@ -9,7 +9,7 @@ from rich import print
|
|
|
9
9
|
from itertools import groupby
|
|
10
10
|
# import opentimelineio as otio
|
|
11
11
|
from datetime import timedelta
|
|
12
|
-
import shutil, os, sys, stat
|
|
12
|
+
import shutil, os, sys, stat, subprocess
|
|
13
13
|
from subprocess import Popen, PIPE
|
|
14
14
|
from pprint import pformat
|
|
15
15
|
|
|
@@ -27,6 +27,7 @@ DB_OSX_NORM = -6 #dB
|
|
|
27
27
|
OUT_DIR_DEFAULT = 'SyncedMedia'
|
|
28
28
|
MCCDIR = 'SyncedMulticamClips'
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
# utility to lock ISO audio files
|
|
31
32
|
def remove_write_permissions(path):
|
|
32
33
|
"""Remove write permissions from this path, while keeping all other permissions intact.
|
|
@@ -38,7 +39,6 @@ def remove_write_permissions(path):
|
|
|
38
39
|
NO_GROUP_WRITING = ~stat.S_IWGRP
|
|
39
40
|
NO_OTHER_WRITING = ~stat.S_IWOTH
|
|
40
41
|
NO_WRITING = NO_USER_WRITING & NO_GROUP_WRITING & NO_OTHER_WRITING
|
|
41
|
-
|
|
42
42
|
current_permissions = stat.S_IMODE(os.lstat(path).st_mode)
|
|
43
43
|
os.chmod(path, current_permissions & NO_WRITING)
|
|
44
44
|
|
|
@@ -55,6 +55,10 @@ def _pathname(tempfile_or_path) -> str:
|
|
|
55
55
|
else:
|
|
56
56
|
raise Exception('%s should be Path or tempfile...'%tempfile_or_path)
|
|
57
57
|
|
|
58
|
+
def ffprobe_duration(f):
|
|
59
|
+
pr = ffmpeg.probe(f)
|
|
60
|
+
return pr['format']['duration']
|
|
61
|
+
|
|
58
62
|
# utility for printing groupby results
|
|
59
63
|
def print_grby(grby):
|
|
60
64
|
for key, keylist in grby:
|
|
@@ -202,7 +206,7 @@ def _sox_multi2stereo(multichan_tmpfl, stereo_trxs) -> tempfile.NamedTemporaryFi
|
|
|
202
206
|
stereo_tempfile = tempfile.NamedTemporaryFile(suffix='.wav',
|
|
203
207
|
delete=DEL_TEMP)
|
|
204
208
|
tfm = sox.Transformer()
|
|
205
|
-
tfm.channels(1)
|
|
209
|
+
tfm.channels(1) # why ? https://pysox.readthedocs.io/en/latest/api.html?highlight=channels#sox.transform.Transformer.channels
|
|
206
210
|
status = tfm.build(_pathname(multichan_tmpfl),_pathname(stereo_tempfile))
|
|
207
211
|
logger.debug('n chan ouput: %s'%
|
|
208
212
|
sox.file_info.channels(_pathname(stereo_tempfile)))
|
|
@@ -401,6 +405,7 @@ class AudioStitcherVideoMerger:
|
|
|
401
405
|
Returns audio recordings that overlap self.videoclip.
|
|
402
406
|
Simply keys of self.soxed_audio dict
|
|
403
407
|
"""
|
|
408
|
+
logger.debug(f'soxed_audio {pformat(self.soxed_audio)}')
|
|
404
409
|
return list(self.soxed_audio.keys())
|
|
405
410
|
|
|
406
411
|
def _get_audio_devices(self):
|
|
@@ -411,15 +416,16 @@ class AudioStitcherVideoMerger:
|
|
|
411
416
|
return devices
|
|
412
417
|
|
|
413
418
|
def _get_secondary_audio_devices(self):
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return devices
|
|
419
|
+
# when only audio devices are synced.
|
|
420
|
+
# identical to _get_audio_devices()...
|
|
421
|
+
# name changed for clarity
|
|
422
|
+
return self._get_audio_devices()
|
|
419
423
|
|
|
420
424
|
def _get_all_recordings_for(self, device):
|
|
421
425
|
# return recordings for a particular device, sorted by time
|
|
422
|
-
recs =
|
|
426
|
+
recs = self.get_matched_audio_recs()
|
|
427
|
+
logger.debug(f'device: {device.name} matched audio recs: {recs}')
|
|
428
|
+
recs = [a for a in recs if a.device == device]
|
|
423
429
|
recs.sort(key=lambda r: r.start_time)
|
|
424
430
|
return recs
|
|
425
431
|
|
|
@@ -652,14 +658,16 @@ class AudioStitcherVideoMerger:
|
|
|
652
658
|
self.soxed_audio[audio_rec] = output_fh
|
|
653
659
|
return output_fh
|
|
654
660
|
|
|
655
|
-
def _write_ISOs(self, edited_audio_all_devices
|
|
661
|
+
def _write_ISOs(self, edited_audio_all_devices,
|
|
662
|
+
snd_root=None, synced_root=None, raw_root=None, audio_only=False):
|
|
656
663
|
"""
|
|
664
|
+
[TODO: this multiline doc is obsolete]
|
|
657
665
|
Writes isolated audio files that were synced to synced_clip_file,
|
|
658
666
|
each track will have its dedicated monofile, named sequentially or with
|
|
659
|
-
the name find in
|
|
667
|
+
the name find in TRACKSFILE if any, see Scanner._get_tracks_from_file()
|
|
660
668
|
|
|
661
669
|
edited_audio_all_devices:
|
|
662
|
-
a list of (name, mono_tempfile)
|
|
670
|
+
a list of (name, mono_tempfile, dev) -------------------------------------------> add argument for device for calling _get_all_recordings_for() for file for metada
|
|
663
671
|
|
|
664
672
|
Returns nothing, output is written to filesystem as below.
|
|
665
673
|
ISOs subfolders structure when user invokes the --isos flag:
|
|
@@ -671,7 +679,7 @@ class AudioStitcherVideoMerger:
|
|
|
671
679
|
canon24fps01.MOV ━━━━┓ name of clip is name of folder
|
|
672
680
|
canon24fps01_ISO/ <━━┛
|
|
673
681
|
chan_1.wav
|
|
674
|
-
chan_2.wav
|
|
682
|
+
chan_2.wav [UPDATE FOR MAM mode]
|
|
675
683
|
canon24fps02.MOV
|
|
676
684
|
canon24fps01_ISO/
|
|
677
685
|
chan_1.wav
|
|
@@ -707,20 +715,52 @@ class AudioStitcherVideoMerger:
|
|
|
707
715
|
sox.file_info.duration(_pathname(out_tf)))
|
|
708
716
|
logger.debug('video duration %.2f s'%
|
|
709
717
|
self.videoclip.get_duration())
|
|
718
|
+
logger.debug(f'video {self.videoclip}')
|
|
710
719
|
return out_tf
|
|
711
|
-
|
|
712
|
-
|
|
720
|
+
def _meta_wav_dest(p1, p2, p3):
|
|
721
|
+
"""
|
|
722
|
+
takes metadata from p1, sound from p2 and combine them to create p3.
|
|
723
|
+
arguments are pathlib.Path or string;
|
|
724
|
+
returns nothing.
|
|
725
|
+
"""
|
|
726
|
+
f1, f2, f3 = [_pathname(p) for p in [p1, p2, p3]]
|
|
727
|
+
process_list = ['ffmpeg', '-y', '-loglevel', 'quiet', '-nostats', '-hide_banner',
|
|
728
|
+
'-i', f1, '-i', f2, '-map', '1',
|
|
729
|
+
'-map_metadata', '0', '-c', 'copy', f3]
|
|
730
|
+
proc = subprocess.run(process_list)
|
|
731
|
+
logger.debug(f'synced_clip_file raw')
|
|
732
|
+
if snd_root == None:
|
|
733
|
+
# alongside mode
|
|
734
|
+
synced_clip_file = self.videoclip.final_synced_file
|
|
735
|
+
logger.debug('alongside mode')
|
|
736
|
+
synced_clip_dir = synced_clip_file.parent
|
|
737
|
+
else:
|
|
738
|
+
# MAM mode
|
|
739
|
+
synced_clip_file = self.videoclip.AVpath
|
|
740
|
+
logger.debug('MAM mode')
|
|
741
|
+
rel = synced_clip_file.parent.relative_to(raw_root)
|
|
742
|
+
synced_clip_dir = Path(snd_root)/Path(raw_root).name/rel
|
|
743
|
+
logger.debug(f'synced_clip_dir: {synced_clip_dir}')
|
|
713
744
|
# build ISOs subfolders structure, see comment string below
|
|
714
745
|
video_stem_WO_suffix = synced_clip_file.stem
|
|
715
|
-
ISOdir = synced_clip_dir/(video_stem_WO_suffix + '
|
|
746
|
+
# ISOdir = synced_clip_dir/(video_stem_WO_suffix + 'ISO')
|
|
747
|
+
ISOdir = synced_clip_dir/(video_stem_WO_suffix + '_SND')/'ISOfiles'
|
|
716
748
|
os.makedirs(ISOdir, exist_ok=True)
|
|
717
749
|
logger.debug('edited_audio_all_devices %s'%edited_audio_all_devices)
|
|
718
750
|
logger.debug('ISOdir %s'%ISOdir)
|
|
719
|
-
for name, mono_tmpfl in edited_audio_all_devices:
|
|
720
|
-
|
|
751
|
+
for name, mono_tmpfl, device in edited_audio_all_devices:
|
|
752
|
+
logger.debug(f'name:{name} mono_tmpfl:{mono_tmpfl} device:{pformat(device)}')
|
|
753
|
+
# destination = ISOdir/(f'{video_stem_WO_suffix}_{name}.wav')
|
|
754
|
+
destination = ISOdir/(f'{name}_{video_stem_WO_suffix}.wav')
|
|
721
755
|
mono_tmpfl_trimpad = _fit_length(mono_tmpfl)
|
|
722
|
-
|
|
723
|
-
|
|
756
|
+
# if audio_only, self.ref_audio does not have itself as matching audio
|
|
757
|
+
if audio_only and device == self.ref_audio.device:
|
|
758
|
+
first_rec = self.ref_audio
|
|
759
|
+
else:
|
|
760
|
+
first_rec = self._get_all_recordings_for(device)[0]
|
|
761
|
+
logger.debug(f'will use {first_rec} for metadata source to copy over {destination}')
|
|
762
|
+
_meta_wav_dest(first_rec.AVpath, mono_tmpfl_trimpad, destination)
|
|
763
|
+
# remove_write_permissions(destination)
|
|
724
764
|
logger.debug('destination:%s'%destination)
|
|
725
765
|
|
|
726
766
|
def _get_device_mix(self, device, multichan_tmpfl) -> tempfile.NamedTemporaryFile:
|
|
@@ -794,8 +834,8 @@ class AudioStitcherVideoMerger:
|
|
|
794
834
|
if device.ttc + 1 != device.tracks.ttc: # warn and quit
|
|
795
835
|
print('Error: TicTacCode channel detected is [gold1]%i[/gold1]'%
|
|
796
836
|
(device.ttc), end=' ')
|
|
797
|
-
print('and [gold1]%s[/gold1]
|
|
798
|
-
(
|
|
837
|
+
print('and file [gold1]%s[/gold1]\nfor the device [gold1]%s[/gold1] specifies channel [gold1]%i[/gold1],'%
|
|
838
|
+
(device.folder/Path(yaltc.TRACKSFILE),
|
|
799
839
|
device.name, device.tracks.ttc-1))
|
|
800
840
|
print('Please correct the discrepancy and rerun. Quitting.')
|
|
801
841
|
sys.exit(1)
|
|
@@ -866,19 +906,7 @@ class AudioStitcherVideoMerger:
|
|
|
866
906
|
stereo_files = mic_stereo_files + new_stereo_files
|
|
867
907
|
return _sox_mix_files(stereo_files)
|
|
868
908
|
|
|
869
|
-
def
|
|
870
|
-
dont_write_cam_folder, asked_ISOs, audio_REC_only):
|
|
871
|
-
# simply bifurcates depending if ref media is video (prob 99%)
|
|
872
|
-
# (then audio_REC_only == False)
|
|
873
|
-
# or if ref media is audio (no camera detected, 1% of cases)
|
|
874
|
-
# (with audio_REC_only == True)
|
|
875
|
-
if not audio_REC_only:
|
|
876
|
-
self._build_audio_and_write_video(top_dir,
|
|
877
|
-
dont_write_cam_folder, asked_ISOs)
|
|
878
|
-
else:
|
|
879
|
-
self._build_and_write_audio(top_dir, anchor_dir)
|
|
880
|
-
|
|
881
|
-
def _build_and_write_audio(self, top_dir, anchor_dir):
|
|
909
|
+
def _build_and_write_audio(self, top_dir, anchor_dir=None):
|
|
882
910
|
"""
|
|
883
911
|
This is called when only audio recorders were found (no cam).
|
|
884
912
|
|
|
@@ -910,8 +938,7 @@ class AudioStitcherVideoMerger:
|
|
|
910
938
|
self.synced_clip_dir = synced_clip_dir
|
|
911
939
|
os.makedirs(synced_clip_dir, exist_ok=True)
|
|
912
940
|
logger.debug('synced_clip_dir is: %s'%synced_clip_dir)
|
|
913
|
-
synced_clip_file = synced_clip_dir
|
|
914
|
-
Path(self.videoclip.new_rec_name).name
|
|
941
|
+
synced_clip_file = synced_clip_dir/self.videoclip.AVpath.name
|
|
915
942
|
logger.debug('editing files for synced_clip_file%s'%synced_clip_file)
|
|
916
943
|
self.ref_audio.final_synced_file = synced_clip_file # relative path
|
|
917
944
|
# Collecting edited audio by device, in (Device, tempfile) pairs:
|
|
@@ -971,14 +998,15 @@ class AudioStitcherVideoMerger:
|
|
|
971
998
|
logger.debug('track_name %s'%track_name)
|
|
972
999
|
if track_name[0] == '0': # muted, skip
|
|
973
1000
|
continue
|
|
974
|
-
names_audio_tempfiles.append((track_name, monotf))
|
|
975
|
-
logger.debug('names_audio_tempfiles %s'%names_audio_tempfiles)
|
|
976
|
-
self._write_ISOs(names_audio_tempfiles)
|
|
1001
|
+
names_audio_tempfiles.append((track_name, monotf, dev))
|
|
1002
|
+
logger.debug('names_audio_tempfiles %s'%pformat(names_audio_tempfiles))
|
|
1003
|
+
self._write_ISOs(names_audio_tempfiles, audio_only=True)
|
|
977
1004
|
logger.debug('merged_audio_files_by_device %s'%
|
|
978
1005
|
merged_audio_files_by_device)
|
|
979
1006
|
|
|
980
1007
|
def _build_audio_and_write_video(self, top_dir, dont_write_cam_folder,
|
|
981
|
-
asked_ISOs
|
|
1008
|
+
asked_ISOs, synced_root = None,
|
|
1009
|
+
snd_root = None, raw_root = None):
|
|
982
1010
|
"""
|
|
983
1011
|
top_dir: Path, directory where media were looked for
|
|
984
1012
|
|
|
@@ -1003,22 +1031,25 @@ class AudioStitcherVideoMerger:
|
|
|
1003
1031
|
(top_dir, dont_write_cam_folder, asked_ISOs))
|
|
1004
1032
|
logger.debug('device for rec %s: %s'%(self.videoclip,
|
|
1005
1033
|
self.videoclip.device))
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1034
|
+
if synced_root == None:
|
|
1035
|
+
# alongside, within SyncedMedia dirs
|
|
1036
|
+
synced_clip_dir = self.videoclip.AVpath.parent/OUT_DIR_DEFAULT
|
|
1037
|
+
logger.debug('"alongside mode" for clip: %s'%self.videoclip.AVpath)
|
|
1038
|
+
logger.debug(f'will save in {synced_clip_dir}')
|
|
1039
|
+
else:
|
|
1040
|
+
# MAM mode
|
|
1041
|
+
logger.debug('MAM mode')
|
|
1042
|
+
synced_clip_dir = Path(synced_root)/str(self.videoclip.AVpath.parent)[1:] # strip leading /
|
|
1043
|
+
logger.debug(f'self.videoclip.AVpath.parent: {self.videoclip.AVpath.parent}')
|
|
1044
|
+
logger.debug(f'raw_root {raw_root}')
|
|
1045
|
+
# rel = self.videoclip.AVpath.parent.relative_to(raw_root).parent # removes ROLL01?
|
|
1046
|
+
rel = self.videoclip.AVpath.parent.relative_to(raw_root)
|
|
1047
|
+
logger.debug(f'relative path {rel}')
|
|
1048
|
+
synced_clip_dir = Path(synced_root)/Path(raw_root).name/rel
|
|
1049
|
+
logger.debug(f'will save in {synced_clip_dir}')
|
|
1019
1050
|
self.synced_clip_dir = synced_clip_dir
|
|
1020
1051
|
os.makedirs(synced_clip_dir, exist_ok=True)
|
|
1021
|
-
logger.debug('synced_clip_dir is: %s'%synced_clip_dir)
|
|
1052
|
+
# logger.debug('synced_clip_dir is: %s'%synced_clip_dir)
|
|
1022
1053
|
synced_clip_file = synced_clip_dir/self.videoclip.AVpath.name
|
|
1023
1054
|
logger.debug('editing files for synced_clip_file %s'%synced_clip_file)
|
|
1024
1055
|
self.videoclip.final_synced_file = synced_clip_file # relative path
|
|
@@ -1104,6 +1135,7 @@ class AudioStitcherVideoMerger:
|
|
|
1104
1135
|
# generates track name for later if asked_ISOs
|
|
1105
1136
|
# idx is from 0 to nchan-1 for this device
|
|
1106
1137
|
if dev.tracks == None:
|
|
1138
|
+
logger.debug('dev.tracks == None')
|
|
1107
1139
|
# no tracks.txt was found so use ascending numbers for name
|
|
1108
1140
|
chan_name = 'chan%s'%str(idx+1).zfill(2)
|
|
1109
1141
|
else:
|
|
@@ -1131,9 +1163,10 @@ class AudioStitcherVideoMerger:
|
|
|
1131
1163
|
logger.debug('track_name %s'%track_name)
|
|
1132
1164
|
if track_name[0] == '0': # muted, skip
|
|
1133
1165
|
continue
|
|
1134
|
-
names_audio_tempfiles.append((track_name, monotf))
|
|
1166
|
+
names_audio_tempfiles.append((track_name, monotf, dev))
|
|
1135
1167
|
logger.debug('names_audio_tempfiles %s'%pformat(names_audio_tempfiles))
|
|
1136
|
-
self._write_ISOs(names_audio_tempfiles
|
|
1168
|
+
self._write_ISOs(names_audio_tempfiles,
|
|
1169
|
+
snd_root=snd_root, synced_root=synced_root, raw_root=raw_root)
|
|
1137
1170
|
logger.debug('merged_audio_files_by_device %s'%
|
|
1138
1171
|
merged_audio_files_by_device)
|
|
1139
1172
|
# This loop below for logging purpose only:
|
|
@@ -1177,9 +1210,12 @@ class AudioStitcherVideoMerger:
|
|
|
1177
1210
|
"""
|
|
1178
1211
|
synced_clip_file = self.videoclip.final_synced_file
|
|
1179
1212
|
video_path = self.videoclip.AVpath
|
|
1213
|
+
logger.debug(f'original clip {video_path}')
|
|
1214
|
+
logger.debug(f'clip duration {ffprobe_duration(video_path)} s')
|
|
1180
1215
|
timecode = self.videoclip.get_start_timecode_string()
|
|
1181
1216
|
# self.videoclip.synced_audio = audio_path
|
|
1182
1217
|
audio_path = self.videoclip.synced_audio
|
|
1218
|
+
logger.debug(f'audio duration {sox.file_info.duration(_pathname(audio_path))}')
|
|
1183
1219
|
vid_only_handle = self._keep_VIDEO_only(video_path)
|
|
1184
1220
|
a_n = _pathname(audio_path)
|
|
1185
1221
|
v_n = str(vid_only_handle.name)
|
|
@@ -1193,8 +1229,8 @@ class AudioStitcherVideoMerger:
|
|
|
1193
1229
|
ffmpeg_args = (
|
|
1194
1230
|
ffmpeg
|
|
1195
1231
|
.input(v_n)
|
|
1196
|
-
|
|
1197
|
-
.output(out_n, vcodec='copy',
|
|
1232
|
+
.output(out_n, shortest=None, vcodec='copy',
|
|
1233
|
+
# .output(out_n, vcodec='copy',
|
|
1198
1234
|
timecode=timecode)
|
|
1199
1235
|
.global_args('-i', a_n, "-hide_banner")
|
|
1200
1236
|
.overwrite_output()
|
|
@@ -1205,9 +1241,8 @@ class AudioStitcherVideoMerger:
|
|
|
1205
1241
|
_, out = (
|
|
1206
1242
|
ffmpeg
|
|
1207
1243
|
.input(v_n)
|
|
1208
|
-
.output(out_n, vcodec='copy',
|
|
1209
|
-
|
|
1210
|
-
# metadata='reel_name=foo', not all container support gen MD
|
|
1244
|
+
# .output(out_n, vcodec='copy',
|
|
1245
|
+
.output(out_n, shortest=None, vcodec='copy',
|
|
1211
1246
|
timecode=timecode,
|
|
1212
1247
|
)
|
|
1213
1248
|
.global_args('-i', a_n, "-hide_banner")
|
|
@@ -1215,8 +1250,8 @@ class AudioStitcherVideoMerger:
|
|
|
1215
1250
|
.run(capture_stderr=True)
|
|
1216
1251
|
)
|
|
1217
1252
|
logger.debug('ffmpeg output')
|
|
1218
|
-
for l in out.decode("utf-8").split('\n'):
|
|
1219
|
-
logger.debug(l)
|
|
1253
|
+
# for l in out.decode("utf-8").split('\n'):
|
|
1254
|
+
# logger.debug(l)
|
|
1220
1255
|
except ffmpeg.Error as e:
|
|
1221
1256
|
print('ffmpeg.run error merging: \n\t %s + %s = %s\n'%(
|
|
1222
1257
|
audio_path,
|
|
@@ -1226,6 +1261,9 @@ class AudioStitcherVideoMerger:
|
|
|
1226
1261
|
print(e)
|
|
1227
1262
|
print(e.stderr.decode('UTF-8'))
|
|
1228
1263
|
sys.exit(1)
|
|
1264
|
+
logger.debug(f'merged clip {out_n}')
|
|
1265
|
+
logger.debug(f'clip duration {ffprobe_duration(out_n)} s')
|
|
1266
|
+
|
|
1229
1267
|
|
|
1230
1268
|
class Matcher:
|
|
1231
1269
|
"""
|
|
@@ -1267,21 +1305,6 @@ class Matcher:
|
|
|
1267
1305
|
self.recordings = recordings_list
|
|
1268
1306
|
self.mergers = []
|
|
1269
1307
|
|
|
1270
|
-
# def _rename_all_recs(self):
|
|
1271
|
-
# """
|
|
1272
|
-
# Add _synced to filenames of synced video files. Change stored name only:
|
|
1273
|
-
# files have yet to be written to.
|
|
1274
|
-
# """
|
|
1275
|
-
# # match IO_structure:
|
|
1276
|
-
# # case 'foldercam':
|
|
1277
|
-
# for rec in self.recordings:
|
|
1278
|
-
# rec_extension = rec.AVpath.suffix
|
|
1279
|
-
# rel_path_new_name = '%s%s'%(rec.AVpath.stem, rec_extension)
|
|
1280
|
-
# rec.new_rec_name = Path(rel_path_new_name)
|
|
1281
|
-
# logger.debug('for %s new name: %s'%(
|
|
1282
|
-
# _pathname(rec.AVpath),
|
|
1283
|
-
# _pathname(rec.new_rec_name)))
|
|
1284
|
-
|
|
1285
1308
|
def scan_audio_for_each_videoclip(self):
|
|
1286
1309
|
"""
|
|
1287
1310
|
For each video (and for the Main Sound) in self.recordings, this finds
|
|
@@ -1293,10 +1316,11 @@ class Matcher:
|
|
|
1293
1316
|
V3 checked against ...
|
|
1294
1317
|
Main Sound checked against A1, A2, A3, A4
|
|
1295
1318
|
"""
|
|
1296
|
-
video_recordings = [r for r in self.recordings
|
|
1297
|
-
|
|
1319
|
+
video_recordings = [r for r in self.recordings
|
|
1320
|
+
if r.is_video() or r.is_audio_reference]
|
|
1321
|
+
# if r.is_audio_reference then audio, and will pass as video
|
|
1298
1322
|
audio_recs = [r for r in self.recordings if r.is_audio()
|
|
1299
|
-
and not r.
|
|
1323
|
+
and not r.is_audio_reference]
|
|
1300
1324
|
if not audio_recs:
|
|
1301
1325
|
print('\nNo audio recording found, syncing of videos only not implemented yet, exiting...\n')
|
|
1302
1326
|
sys.exit(1)
|
|
@@ -1452,7 +1476,7 @@ class Matcher:
|
|
|
1452
1476
|
vid.write_file_timecode(tc)
|
|
1453
1477
|
return
|
|
1454
1478
|
|
|
1455
|
-
def move_multicam_to_dir(self):
|
|
1479
|
+
def move_multicam_to_dir(self, raw_root=None, synced_root=None):
|
|
1456
1480
|
# creates a dedicated multicam directory and move clusters there
|
|
1457
1481
|
# e.g., for "top/day01/camA/roll02"
|
|
1458
1482
|
# ^ at that level
|
|
@@ -1461,6 +1485,7 @@ class Matcher:
|
|
|
1461
1485
|
#
|
|
1462
1486
|
# check for consistency: are all clips at the same level from topdir?
|
|
1463
1487
|
# Only for video, not audio (which doesnt fill up cards)
|
|
1488
|
+
logger.debug(f'synced_root: {synced_root}')
|
|
1464
1489
|
video_medias = [m for m in self.recordings if m.device.dev_type == 'CAM']
|
|
1465
1490
|
video_paths = [m.AVpath.parts for m in video_medias]
|
|
1466
1491
|
AV_path_lengths = [len(p) for p in video_paths]
|
|
@@ -1479,32 +1504,63 @@ class Matcher:
|
|
|
1479
1504
|
sys.exit(0)
|
|
1480
1505
|
# pick first
|
|
1481
1506
|
CAM_level, avp = CAM_levels[0], video_medias[0].AVpath
|
|
1482
|
-
logger.debug('CAM_levels: %s for
|
|
1507
|
+
logger.debug('CAM_levels: %s for %s\n'%(CAM_level, avp))
|
|
1483
1508
|
# MCCDIR = 'SyncedMulticamClips'
|
|
1484
|
-
parts_up_a_level = avp.parts[:CAM_level]
|
|
1485
|
-
|
|
1509
|
+
parts_up_a_level = [prt for prt in avp.parts[:CAM_level] if prt != '/']
|
|
1510
|
+
logger.debug(f'parts_up_a_level: {parts_up_a_level}')
|
|
1511
|
+
if synced_root == None:
|
|
1512
|
+
# alongside mode
|
|
1513
|
+
logger.debug('alongside mode')
|
|
1514
|
+
multicam_dir = Path('/').joinpath(*parts_up_a_level)/MCCDIR
|
|
1515
|
+
else:
|
|
1516
|
+
# MAM mode
|
|
1517
|
+
logger.debug('MAM mode')
|
|
1518
|
+
abs_path_up = Path('/').joinpath(*parts_up_a_level)/MCCDIR
|
|
1519
|
+
logger.debug(f'abs_path_up: {abs_path_up}')
|
|
1520
|
+
rel_up = abs_path_up.relative_to(raw_root)
|
|
1521
|
+
logger.debug(f'rel_up: {rel_up}')
|
|
1522
|
+
multicam_dir = Path(synced_root)/Path(raw_root).name/rel_up
|
|
1523
|
+
# multicam_dir = Path(synced_root).joinpath(*parts_up_a_level)/MCCDIR
|
|
1486
1524
|
logger.debug('multicam_dir: %s'%multicam_dir)
|
|
1487
1525
|
Path.mkdir(multicam_dir, exist_ok=True)
|
|
1488
1526
|
cam_clips = []
|
|
1489
1527
|
[cam_clips.append(cl['vids']) for cl in self.multicam_clips_clusters]
|
|
1490
1528
|
cam_clips = _flatten(cam_clips)
|
|
1491
|
-
logger.debug('cam_clips: %s'%cam_clips)
|
|
1529
|
+
logger.debug('cam_clips: %s'%pformat(cam_clips))
|
|
1492
1530
|
cam_names = set([r.device.name for r in cam_clips])
|
|
1493
1531
|
# create new dirs for each CAM
|
|
1494
1532
|
[Path.mkdir(multicam_dir/cam_name, exist_ok=True)
|
|
1495
1533
|
for cam_name in cam_names]
|
|
1496
1534
|
# move clips there
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1535
|
+
if synced_root == None:
|
|
1536
|
+
# alongside mode
|
|
1537
|
+
for r in cam_clips:
|
|
1538
|
+
cam = r.device.name
|
|
1539
|
+
clip_name = r.AVpath.name
|
|
1540
|
+
dest = r.final_synced_file.replace(multicam_dir/cam/clip_name)
|
|
1541
|
+
logger.debug('dest: %s'%dest)
|
|
1542
|
+
origin_folder = r.final_synced_file.parent
|
|
1543
|
+
folder_now_empty = len(list(origin_folder.glob('*'))) == 0
|
|
1544
|
+
if folder_now_empty:
|
|
1545
|
+
logger.debug('after moving %s, folder is now empty, removing it'%dest)
|
|
1546
|
+
origin_folder.rmdir()
|
|
1547
|
+
print('\nMoved %i multicam clips in %s'%(len(cam_clips), multicam_dir))
|
|
1548
|
+
else:
|
|
1549
|
+
# MAM mode
|
|
1550
|
+
for r in cam_clips:
|
|
1551
|
+
cam = r.device.name
|
|
1552
|
+
clip_name = r.AVpath.name
|
|
1553
|
+
logger.debug(f'r.final_synced_file: {r.final_synced_file}')
|
|
1554
|
+
dest = r.final_synced_file.replace(multicam_dir/cam/clip_name)
|
|
1555
|
+
# leave a symlink behind
|
|
1556
|
+
os.symlink(multicam_dir/cam/clip_name, r.final_synced_file)
|
|
1557
|
+
logger.debug('dest: %s'%dest)
|
|
1558
|
+
origin_folder = r.final_synced_file.parent
|
|
1559
|
+
# folder_now_empty = len(list(origin_folder.glob('*'))) == 0
|
|
1560
|
+
# if folder_now_empty:
|
|
1561
|
+
# logger.debug('after moving %s, folder is now empty, removing it'%dest)
|
|
1562
|
+
# origin_folder.rmdir()
|
|
1563
|
+
print('\nMoved %i multicam clips in %s'%(len(cam_clips), multicam_dir))
|
|
1508
1564
|
|
|
1509
1565
|
|
|
1510
1566
|
|