tictacsync 1.4.0b0__py3-none-any.whl → 1.4.5b0__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 +2 -2
- tictacsync/entry.py +1 -24
- tictacsync/mamsync.py +1 -19
- tictacsync/multi2polywav.py +3 -1
- {tictacsync-1.4.0b0.dist-info → tictacsync-1.4.5b0.dist-info}/METADATA +2 -1
- tictacsync-1.4.5b0.dist-info/RECORD +16 -0
- {tictacsync-1.4.0b0.dist-info → tictacsync-1.4.5b0.dist-info}/entry_points.txt +2 -2
- tictacsync/LTCcheck.py +0 -394
- tictacsync/load_fieldr_reaper.py +0 -352
- tictacsync/new-sound-resolve.py +0 -469
- tictacsync/newmix.py +0 -483
- tictacsync/remergemix.py +0 -259
- tictacsync/remrgmx.py +0 -116
- tictacsync/splitmix.py +0 -87
- tictacsync/synciso.py +0 -143
- tictacsync-1.4.0b0.dist-info/RECORD +0 -24
- {tictacsync-1.4.0b0.dist-info → tictacsync-1.4.5b0.dist-info}/LICENSE +0 -0
- {tictacsync-1.4.0b0.dist-info → tictacsync-1.4.5b0.dist-info}/WHEEL +0 -0
- {tictacsync-1.4.0b0.dist-info → tictacsync-1.4.5b0.dist-info}/top_level.txt +0 -0
tictacsync/remergemix.py
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import argparse, wave, subprocess
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import sox, tempfile, os, ffmpeg
|
|
5
|
-
from rich import print
|
|
6
|
-
import shutil, sys, re
|
|
7
|
-
from pprint import pformat
|
|
8
|
-
from itertools import groupby
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
from . import timeline
|
|
12
|
-
except:
|
|
13
|
-
import timeline
|
|
14
|
-
|
|
15
|
-
DEL_TEMP = False
|
|
16
|
-
|
|
17
|
-
logger.level("DEBUG", color="<yellow>")
|
|
18
|
-
logger.remove()
|
|
19
|
-
|
|
20
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_get_ISO_dirs")
|
|
21
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "_join_audio2video")
|
|
22
|
-
# logger.add(sys.stdout, filter=lambda r: r["function"] == "main")
|
|
23
|
-
# logger.add(sys.stdout, filter="__main__")
|
|
24
|
-
|
|
25
|
-
OUT_DIR = 'SyncedMedia'
|
|
26
|
-
SEC_DELAY_CHANGED_ISO = 10 #sec, ISO_DIR changed if diff time is bigger
|
|
27
|
-
|
|
28
|
-
video_extensions = \
|
|
29
|
-
"""webm mkv flv flv vob ogv ogg drc gif gifv mng avi mov
|
|
30
|
-
qt wmv yuv rm rmvb viv asf mp4 m4p m4v mpg mp2 mpeg mpe
|
|
31
|
-
mpv mpg mpeg m2v m4v svi 3gp 3g2 mxf roq nsv""".split() # from wikipedia
|
|
32
|
-
|
|
33
|
-
def _pathname(tempfile_or_path) -> str:
|
|
34
|
-
# utility for obtaining a str from different filesystem objects
|
|
35
|
-
if isinstance(tempfile_or_path, str):
|
|
36
|
-
return tempfile_or_path
|
|
37
|
-
if isinstance(tempfile_or_path, Path):
|
|
38
|
-
return str(tempfile_or_path)
|
|
39
|
-
if isinstance(tempfile_or_path, tempfile._TemporaryFileWrapper):
|
|
40
|
-
return tempfile_or_path.name
|
|
41
|
-
else:
|
|
42
|
-
raise Exception('%s should be Path or tempfile...'%tempfile_or_path)
|
|
43
|
-
|
|
44
|
-
def _keep_VIDEO_only(video_path):
|
|
45
|
-
# return file handle to a temp video file formed from the video_path
|
|
46
|
-
# stripped of its sound
|
|
47
|
-
in1 = ffmpeg.input(_pathname(video_path))
|
|
48
|
-
video_extension = video_path.suffix
|
|
49
|
-
silenced_opts = ["-loglevel", "quiet", "-nostats", "-hide_banner"]
|
|
50
|
-
file_handle = tempfile.NamedTemporaryFile(suffix=video_extension,
|
|
51
|
-
delete=DEL_TEMP)
|
|
52
|
-
out1 = in1.output(file_handle.name, map='0:v', vcodec='copy')
|
|
53
|
-
ffmpeg.run([out1.global_args(*silenced_opts)], overwrite_output=True)
|
|
54
|
-
return file_handle
|
|
55
|
-
|
|
56
|
-
def _join_audio2video(audio_path: Path, video: Path):
|
|
57
|
-
"""
|
|
58
|
-
Replace audio in video (argument) by the audio contained in
|
|
59
|
-
audio_path (argument) returns nothing
|
|
60
|
-
"""
|
|
61
|
-
video_ext = video.name.split('.')[1]
|
|
62
|
-
vid_only_handle = _keep_VIDEO_only(video)
|
|
63
|
-
a_n = _pathname(audio_path)
|
|
64
|
-
v_n = _pathname(vid_only_handle)
|
|
65
|
-
out_n = _pathname(video)
|
|
66
|
-
# building args for debug purpose only:
|
|
67
|
-
ffmpeg_args = (
|
|
68
|
-
ffmpeg
|
|
69
|
-
.input(v_n)
|
|
70
|
-
.output(out_n, vcodec='copy')
|
|
71
|
-
# .output(out_n, shortest=None, vcodec='copy')
|
|
72
|
-
.global_args('-i', a_n, "-hide_banner")
|
|
73
|
-
.overwrite_output()
|
|
74
|
-
.get_args()
|
|
75
|
-
)
|
|
76
|
-
logger.debug('ffmpeg args: %s'%' '.join(ffmpeg_args))
|
|
77
|
-
try: # for real now
|
|
78
|
-
_, out = (
|
|
79
|
-
ffmpeg
|
|
80
|
-
.input(v_n)
|
|
81
|
-
# .output(out_n, shortest=None, vcodec='copy')
|
|
82
|
-
.output(out_n, vcodec='copy')
|
|
83
|
-
.global_args('-i', a_n, "-hide_banner")
|
|
84
|
-
.overwrite_output()
|
|
85
|
-
.run(capture_stderr=True)
|
|
86
|
-
)
|
|
87
|
-
logger.debug('ffmpeg output')
|
|
88
|
-
for l in out.decode("utf-8").split('\n'):
|
|
89
|
-
logger.debug(l)
|
|
90
|
-
except ffmpeg.Error as e:
|
|
91
|
-
print('ffmpeg.run error merging: \n\t %s + %s = %s\n'%(
|
|
92
|
-
audio_path,
|
|
93
|
-
video_path,
|
|
94
|
-
synced_clip_file
|
|
95
|
-
))
|
|
96
|
-
print(e)
|
|
97
|
-
print(e.stderr.decode('UTF-8'))
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
|
|
100
|
-
def _changed(dir) -> bool:
|
|
101
|
-
"""
|
|
102
|
-
Returns True if any content of dir (arg) is more recent than dir itself by a
|
|
103
|
-
delay of SEC_DELAY_CHANGED_ISO. Uses modification times.
|
|
104
|
-
"""
|
|
105
|
-
logger.debug(f'checking {dir.name} for change')
|
|
106
|
-
ISO_modification_time = biggest_mtime_in_dir(dir)
|
|
107
|
-
clip = clip_from_iso(dir)
|
|
108
|
-
clip_mod_time = clip.stat().st_mtime
|
|
109
|
-
# difference of modification time in secs
|
|
110
|
-
ISO_more_recent_by = ISO_modification_time - clip_mod_time
|
|
111
|
-
logger.debug('ISO_more_recent_by: %s'%(ISO_more_recent_by))
|
|
112
|
-
iso_edited = ISO_more_recent_by > SEC_DELAY_CHANGED_ISO
|
|
113
|
-
logger.debug(f'_changed: {iso_edited}')
|
|
114
|
-
return iso_edited
|
|
115
|
-
|
|
116
|
-
def sox_mix(ISOdir):
|
|
117
|
-
"""
|
|
118
|
-
Mixes all wav files present in ISOdir (so excludes ttc file and nullified
|
|
119
|
-
ones).
|
|
120
|
-
Returns a mono or stereo tempfile
|
|
121
|
-
"""
|
|
122
|
-
logger.debug(f'mixing {ISOdir}')
|
|
123
|
-
def is_stereo_mic(p):
|
|
124
|
-
re_result = re.match(r'mic([lrLR])*', p.name)
|
|
125
|
-
# logger.debug(f're_result {re_result}')
|
|
126
|
-
return re_result is not None
|
|
127
|
-
stereo_mics = [p for p in ISOdir.iterdir() if is_stereo_mic(p)]
|
|
128
|
-
monofiles = [p for p in ISOdir.iterdir() if p not in stereo_mics]
|
|
129
|
-
# removing ttc files
|
|
130
|
-
def notTTC(p):
|
|
131
|
-
return p.name[:3] != 'ttc'
|
|
132
|
-
monofiles = [p for p in monofiles if notTTC(p)]
|
|
133
|
-
if stereo_mics == []: # mono
|
|
134
|
-
return timeline._sox_mix_files(monofiles) #-----------------------------
|
|
135
|
-
logger.debug(f'stereo_mics: {stereo_mics}')
|
|
136
|
-
def mic(p):
|
|
137
|
-
return re.search(r'(mic\d*)([lrLR])*', p.name).groups()
|
|
138
|
-
mics = [mic(p) for p in stereo_mics]
|
|
139
|
-
p_and_mic = list(zip(stereo_mics, mics))
|
|
140
|
-
logger.debug(f'p_and_mic: {p_and_mic}')
|
|
141
|
-
same_mic_key = lambda pair: pair[1][0]
|
|
142
|
-
p_and_mic = sorted(p_and_mic, key=same_mic_key)
|
|
143
|
-
grouped_by_mic = [ (k, list(iterator)) for k, iterator
|
|
144
|
-
in groupby(p_and_mic, same_mic_key)]
|
|
145
|
-
logger.debug(f'grouped_by_mic: {grouped_by_mic}')
|
|
146
|
-
def order_left_right(groupby_element):
|
|
147
|
-
# returns left and right path for a mic
|
|
148
|
-
name, paths = groupby_element
|
|
149
|
-
def chan(pair):
|
|
150
|
-
# (PosixPath('mic1r_ZOOM.wav'), ('mic1', 'r')) -> 'r'
|
|
151
|
-
return pair[1][1]
|
|
152
|
-
path_n_mic = sorted(paths, key=lambda pair: pair[1][1])
|
|
153
|
-
return [p[0] for p in path_n_mic] # just the path, not ('mic1', 'r')
|
|
154
|
-
left_right_paths = [order_left_right(e) for e in grouped_by_mic]
|
|
155
|
-
# logger.debug(f'left_right_paths: {left_right_paths}')
|
|
156
|
-
stereo_files = [timeline._sox_combine(pair) for pair in left_right_paths]
|
|
157
|
-
monoNstereo = monofiles + stereo_files
|
|
158
|
-
return timeline._sox_mix_files(monoNstereo)
|
|
159
|
-
|
|
160
|
-
def get_mix_file(iso_dir):
|
|
161
|
-
"""
|
|
162
|
-
If iso_dir (arg) contains a mono mix sound file or a stereo mix, returns its
|
|
163
|
-
path. If not, this creates the mix and returns it.
|
|
164
|
-
"""
|
|
165
|
-
wav_files = list(iso_dir.iterdir())
|
|
166
|
-
logger.debug(f'wav_files {wav_files}')
|
|
167
|
-
def is_mix(p):
|
|
168
|
-
re_result = re.match(r'mix([lrLR])*', p.name)
|
|
169
|
-
# logger.debug(f're_result {re_result}')
|
|
170
|
-
return re_result is not None
|
|
171
|
-
location_mix = [p for p in wav_files if is_mix(p)]
|
|
172
|
-
if location_mix == []:
|
|
173
|
-
logger.debug('no mix track, do the mix')
|
|
174
|
-
return sox_mix(iso_dir)
|
|
175
|
-
else:
|
|
176
|
-
return location_mix
|
|
177
|
-
|
|
178
|
-
def biggest_mtime_in_dir(folder: Path) -> float:
|
|
179
|
-
# return the most recent mod time of the files in a folder
|
|
180
|
-
dir_content = list(folder.iterdir())
|
|
181
|
-
stats = [p.stat() for p in dir_content]
|
|
182
|
-
mtimes = [stat.st_mtime for stat in stats]
|
|
183
|
-
return max(mtimes)
|
|
184
|
-
|
|
185
|
-
def clip_from_iso(ISO_dir: Path) -> Path:
|
|
186
|
-
# find the sibling video file of ISO_dir. eg : MVI_01.ISO -> MVI_01.MP4
|
|
187
|
-
folder = ISO_dir.parent
|
|
188
|
-
siblings = list(folder.glob('%s.*'%ISO_dir.stem))
|
|
189
|
-
candidates =[p for p in siblings if p.name.split('.')[1] != 'ISO']
|
|
190
|
-
# should be unique
|
|
191
|
-
if len(candidates) != 1:
|
|
192
|
-
print(f'Error finding video corresponding to {ISO_dir}, quitting')
|
|
193
|
-
sys.exit(1)
|
|
194
|
-
return candidates[0]
|
|
195
|
-
|
|
196
|
-
def _get_ISO_dirs(top_dir):
|
|
197
|
-
"""
|
|
198
|
-
Check if top_dir contains (or somewhere beneath) videos with their
|
|
199
|
-
accompanying ISO folder (like the pair MVI_023.MP4 + MVI_023.ISO). If not
|
|
200
|
-
warns and exits.
|
|
201
|
-
|
|
202
|
-
Returns list of paths pointing to ISO dirs.
|
|
203
|
-
"""
|
|
204
|
-
p = Path(top_dir)
|
|
205
|
-
ISO_dirs = list(Path(top_dir).rglob('*.ISO'))
|
|
206
|
-
logger.debug('all files: %s'%pformat(ISO_dirs))
|
|
207
|
-
# validation: .ISO should be dir
|
|
208
|
-
all_are_dir = all([p.is_dir() for p in ISO_dirs])
|
|
209
|
-
logger.debug('.ISO are all dir %s'%all_are_dir)
|
|
210
|
-
if not all_are_dir:
|
|
211
|
-
print('Error: some .ISO are not folders??? Quitting. %s'%ISO_dirs)
|
|
212
|
-
sys.exit(1)
|
|
213
|
-
# for each folder check a video file exists with the same stem
|
|
214
|
-
# but with a video format extension (listed in video_extensions)
|
|
215
|
-
for ISO in ISO_dirs:
|
|
216
|
-
logger.debug(f'checking {ISO}')
|
|
217
|
-
voisins = list(ISO.parent.glob(f'{ISO.stem}.*'))
|
|
218
|
-
voisins_suffixes = [p.suffix for p in voisins]
|
|
219
|
-
# remove ISO
|
|
220
|
-
voisins_suffixes.remove('.ISO')
|
|
221
|
-
# validations: should remain one element and should be video
|
|
222
|
-
suffix = voisins_suffixes[0].lower()[1:] # remove dot
|
|
223
|
-
logger.debug(f'remaining ext: {suffix}')
|
|
224
|
-
if len(voisins_suffixes) != 1 or suffix not in video_extensions:
|
|
225
|
-
print(f'Error with {voisins}, no video unique sibling?')
|
|
226
|
-
sys.exit(1) #-------------------------------------------------------
|
|
227
|
-
logger.debug(f'All ok, returning {ISO_dirs}')
|
|
228
|
-
return ISO_dirs
|
|
229
|
-
|
|
230
|
-
def main():
|
|
231
|
-
parser = argparse.ArgumentParser()
|
|
232
|
-
parser.add_argument(
|
|
233
|
-
"directory",
|
|
234
|
-
type=str,
|
|
235
|
-
nargs=1,
|
|
236
|
-
help="path of media directory containing Synced videos and their .ISO folder",
|
|
237
|
-
default='.'
|
|
238
|
-
)
|
|
239
|
-
args = parser.parse_args()
|
|
240
|
-
logger.debug('args %s'%args)
|
|
241
|
-
ISO_dirs = _get_ISO_dirs(args.directory[0])
|
|
242
|
-
logger.debug(f'Will check any change in {pformat(ISO_dirs)}')
|
|
243
|
-
changed_ISOs = [isod for isod in ISO_dirs if _changed(isod)]
|
|
244
|
-
logger.debug(f'changed_ISOs: {changed_ISOs}')
|
|
245
|
-
if changed_ISOs != []:
|
|
246
|
-
print('Will remix audio for:')
|
|
247
|
-
for p in changed_ISOs:
|
|
248
|
-
print(p.name)
|
|
249
|
-
newaudio_and_videos = [(get_mix_file(iso), clip_from_iso(iso)) for iso
|
|
250
|
-
in changed_ISOs]
|
|
251
|
-
for audio, video_clip in newaudio_and_videos:
|
|
252
|
-
print(f'Will remerge {video_clip.name}')
|
|
253
|
-
_join_audio2video(audio, video_clip)
|
|
254
|
-
if newaudio_and_videos == []:
|
|
255
|
-
print('Nothing has changed, bye.')
|
|
256
|
-
sys.exit(0)
|
|
257
|
-
|
|
258
|
-
if __name__ == '__main__':
|
|
259
|
-
main()
|
tictacsync/remrgmx.py
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import os, itertools, argparse
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from loguru import logger
|
|
4
|
-
import sys
|
|
5
|
-
from pprint import pformat
|
|
6
|
-
|
|
7
|
-
logger.level("DEBUG", color="<yellow>")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
video_extensions = \
|
|
11
|
-
"""webm mkv flv flv vob ogv ogg drc gif gifv mng avi mov
|
|
12
|
-
qt wmv yuv rm rmvb viv asf mp4 m4p m4v mpg mp2 mpeg mpe
|
|
13
|
-
mpv mpg mpeg m2v m4v svi 3gp 3g2 mxf roq nsv""".split() # from wikipedia
|
|
14
|
-
|
|
15
|
-
def is_video(f):
|
|
16
|
-
# True if name as video extension
|
|
17
|
-
name_ext = f.split('.')
|
|
18
|
-
if len(name_ext) != 2:
|
|
19
|
-
return False
|
|
20
|
-
name, ext = name_ext
|
|
21
|
-
return ext.lower() in video_extensions
|
|
22
|
-
|
|
23
|
-
def find_ISO_vids_pairs_in_dir(top):
|
|
24
|
-
# look for matching video name and ISO dir name
|
|
25
|
-
# eg: IMG04.mp4 and IMG04_ISO
|
|
26
|
-
# returns list of matches
|
|
27
|
-
# recursively search from 'top' argument
|
|
28
|
-
vids = []
|
|
29
|
-
ISOs = []
|
|
30
|
-
for (root,dirs,files) in os.walk(top, topdown=True):
|
|
31
|
-
for d in dirs:
|
|
32
|
-
if d[-4:] == '_ISO':
|
|
33
|
-
ISOs.append(Path(root)/d)
|
|
34
|
-
for f in files:
|
|
35
|
-
if is_video(f): # add being in SyncedMedia or SyncedMulticamClips folder
|
|
36
|
-
vids.append(Path(root)/f)
|
|
37
|
-
logger.debug('vids %s ISOs %s'%(pformat(vids), pformat(ISOs)))
|
|
38
|
-
matches = []
|
|
39
|
-
for pair in list(itertools.product(vids, ISOs)):
|
|
40
|
-
# print(pair)
|
|
41
|
-
vid, ISO = pair
|
|
42
|
-
vidname, ext = vid.name.split('.')
|
|
43
|
-
if vidname == ISO.name[:-4]:
|
|
44
|
-
matches.append(pair)
|
|
45
|
-
# print(vidname, ISO.name[:-4])
|
|
46
|
-
logger.debug('matches: %s'%pformat(matches))
|
|
47
|
-
return matches
|
|
48
|
-
|
|
49
|
-
# [print( vid, ISO) for vid, ISO in find_ISO_vids_pairs('.')]
|
|
50
|
-
|
|
51
|
-
def parse_and_check_arguments():
|
|
52
|
-
# parses directories from command arguments
|
|
53
|
-
# check for consistencies and warn user and exits,
|
|
54
|
-
# if returns, gives:
|
|
55
|
-
# proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only
|
|
56
|
-
parser = argparse.ArgumentParser()
|
|
57
|
-
parser.add_argument('-v',
|
|
58
|
-
nargs=*,
|
|
59
|
-
dest='video_dirs',
|
|
60
|
-
help='Where proxy clips and/or originals are stored')
|
|
61
|
-
parser.add_argument('-a',
|
|
62
|
-
nargs=1,
|
|
63
|
-
dest='audio_dir',
|
|
64
|
-
help='Contains newly changed mix files')
|
|
65
|
-
parser.add_argument('-b',
|
|
66
|
-
nargs=1,
|
|
67
|
-
dest='both_audio_vid',
|
|
68
|
-
help='Directory scanned for both audio and video')
|
|
69
|
-
parser.add_argument('--dry',
|
|
70
|
-
action='store_true',
|
|
71
|
-
dest='scan_only',
|
|
72
|
-
help="Just display changed audio, don't merge")
|
|
73
|
-
args = parser.parse_args()
|
|
74
|
-
logger.debug('args %s'%args)
|
|
75
|
-
# ok cases:
|
|
76
|
-
# -p -o -a + no -b
|
|
77
|
-
# -o -a + no -b
|
|
78
|
-
args_set = [args.proxies_dir != None,
|
|
79
|
-
args.originals_dir != None,
|
|
80
|
-
args.audio_dir != None,
|
|
81
|
-
args.both_audio_vid != None,
|
|
82
|
-
]
|
|
83
|
-
p, o, a, b = args_set
|
|
84
|
-
# check that argument -b (both_audio_vid) is used alone
|
|
85
|
-
if b and any([o, a, p]):
|
|
86
|
-
print("\nDon't specify other argument than -b if both audio and video searched in the same directory.\n")
|
|
87
|
-
parser.print_help(sys.stderr)
|
|
88
|
-
sys.exit(0)
|
|
89
|
-
# check that if proxies (-p) are specified, orginals too (-o)
|
|
90
|
-
if p and not o:
|
|
91
|
-
print("\nIf proxies directory is specified, so should originals directory.\n")
|
|
92
|
-
parser.print_help(sys.stderr)
|
|
93
|
-
sys.exit(0)
|
|
94
|
-
# check that -o and -a are used together
|
|
95
|
-
if not b and not (o and a):
|
|
96
|
-
print("\nAt least originals and audio directories must be given (-o and -a) when audio and video are in different dir.\n")
|
|
97
|
-
parser.print_help(sys.stderr)
|
|
98
|
-
sys.exit(0)
|
|
99
|
-
# work in progress (aug 2025), so limit to -b:
|
|
100
|
-
if not b :
|
|
101
|
-
print("\nFor now, only -b argument is supported (a directory scanned for both audio and video) .\n")
|
|
102
|
-
parser.print_help(sys.stderr)
|
|
103
|
-
sys.exit(0)
|
|
104
|
-
arg_dict = vars(args)
|
|
105
|
-
# list of singletons, so flatten. Keep None and False as is
|
|
106
|
-
return [e[0] if isinstance(e, list) else e for e in arg_dict.values() ]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def main():
|
|
111
|
-
proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only = \
|
|
112
|
-
parse_and_check_arguments()
|
|
113
|
-
m = find_ISO_vids_pairs_in_dir(both_audio_vid)
|
|
114
|
-
|
|
115
|
-
if __name__ == '__main__':
|
|
116
|
-
main()
|
tictacsync/splitmix.py
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import argparse, sys, sox
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from scipy.io.wavfile import write as wrt_wav
|
|
4
|
-
|
|
5
|
-
try:
|
|
6
|
-
from . import load_fieldr_reaper
|
|
7
|
-
from . import yaltc
|
|
8
|
-
except:
|
|
9
|
-
import load_fieldr_reaper
|
|
10
|
-
import yaltc
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
logger.level("DEBUG", color="<yellow>")
|
|
16
|
-
logger.add(sys.stdout, level="DEBUG")
|
|
17
|
-
# logger.remove()
|
|
18
|
-
|
|
19
|
-
def conf_and_parse_arguments():
|
|
20
|
-
# parses directories from command arguments
|
|
21
|
-
# check for consistencies and warn user and exits,
|
|
22
|
-
# returns parser.parse_args()
|
|
23
|
-
descr = "Parse the submitted OTIO timeline and split the specified mix wav file according to OTIO clips"
|
|
24
|
-
parser = argparse.ArgumentParser(description=descr)
|
|
25
|
-
parser.add_argument(
|
|
26
|
-
"otio_file",
|
|
27
|
-
type=str,
|
|
28
|
-
nargs=1,
|
|
29
|
-
help="path of timeline saved under OTIO format"
|
|
30
|
-
)
|
|
31
|
-
parser.add_argument('mix',
|
|
32
|
-
type=str,
|
|
33
|
-
nargs=1,
|
|
34
|
-
help="mix wav file to be splitted")
|
|
35
|
-
args = parser.parse_args()
|
|
36
|
-
logger.debug('args %s'%args)
|
|
37
|
-
return args
|
|
38
|
-
|
|
39
|
-
# def write_wav(file, audio, samplerate):
|
|
40
|
-
# # Put the channels together with shape (2, 44100).
|
|
41
|
-
# # audio = np.array([left_channel, right_channel]).T
|
|
42
|
-
|
|
43
|
-
# audio = (audio * (2 ** 15 - 1)).astype("<h")
|
|
44
|
-
|
|
45
|
-
# with wave.open(file, "w") as f:
|
|
46
|
-
# # 2 Channels.
|
|
47
|
-
# f.setnchannels(2)
|
|
48
|
-
# # 2 bytes per sample.
|
|
49
|
-
# f.setsampwidth(2)
|
|
50
|
-
# f.setframerate(samplerate)
|
|
51
|
-
# f.writeframes(audio.tobytes())
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def main():
|
|
55
|
-
# [TODO] split but duplicate audio to fill in the trimed parts of each clip
|
|
56
|
-
# so use whole clip duration rather than cut_duration for audio length
|
|
57
|
-
args = conf_and_parse_arguments()
|
|
58
|
-
fps, clips =load_fieldr_reaper.read_OTIO_file(args.otio_file[0])
|
|
59
|
-
logger.debug(f'otio has {fps} fps')
|
|
60
|
-
wav_file = args.mix[0]
|
|
61
|
-
N_channels = sox.file_info.channels(wav_file)
|
|
62
|
-
logger.debug(f'{wav_file} has {N_channels} channels')
|
|
63
|
-
tracks = yaltc.read_audio_data_from_file(wav_file, N_channels)
|
|
64
|
-
audio_data = tracks.T # interleave channels for cutting later
|
|
65
|
-
logger.debug(f'audio data shape {audio_data.shape}')
|
|
66
|
-
logger.debug(f'data: {tracks}')
|
|
67
|
-
logger.debug(f'tracks shape {tracks.shape}')
|
|
68
|
-
# start_frames, the "in" frame number (absolute, ie first "in" is 0)
|
|
69
|
-
start_frames = [int(round(cl.timeline_pos*fps)) - 3600*fps for cl in clips]
|
|
70
|
-
logger.debug(f'start_frames {start_frames}')
|
|
71
|
-
durations = [int(round(cl.cut_duration*fps)) for cl in clips]
|
|
72
|
-
logger.debug(f'durations {durations}')
|
|
73
|
-
# sampling frequency, samples per second
|
|
74
|
-
sps = sox.file_info.sample_rate(wav_file)
|
|
75
|
-
# number of audio samples per frames,
|
|
76
|
-
spf = sps/fps
|
|
77
|
-
logger.debug(f'there are {spf} audio samples for each frame')
|
|
78
|
-
audio_slices = [audio_data[int(spf*s):int(spf*(s+d))] for s,d in zip(start_frames, durations)]
|
|
79
|
-
logger.debug(f'audio_slices lengths {[len(s) for s in audio_slices]}')
|
|
80
|
-
for a in audio_slices:
|
|
81
|
-
logger.debug(f'audio_slices {a}')
|
|
82
|
-
|
|
83
|
-
[wrt_wav(f'{clips[i].name.split(".")[0]}.wav', int(sps), a) for i, a in enumerate(audio_slices)]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if __name__ == '__main__':
|
|
87
|
-
main()
|
tictacsync/synciso.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import argparse, wave, subprocess
|
|
2
|
-
from loguru import logger
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import sox, tempfile, os, ffmpeg
|
|
5
|
-
from rich import print
|
|
6
|
-
import shutil
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
logger.remove()
|
|
10
|
-
OUT_DIR = 'SyncedMedia'
|
|
11
|
-
|
|
12
|
-
def _pathname(tempfile_or_path) -> str:
|
|
13
|
-
if isinstance(tempfile_or_path, str):
|
|
14
|
-
return tempfile_or_path
|
|
15
|
-
if isinstance(tempfile_or_path, Path):
|
|
16
|
-
return str(tempfile_or_path)
|
|
17
|
-
if isinstance(tempfile_or_path, tempfile._TemporaryFileWrapper):
|
|
18
|
-
return tempfile_or_path.name
|
|
19
|
-
else:
|
|
20
|
-
raise Exception('%s should be Path or tempfile...'%tempfile_or_path)
|
|
21
|
-
|
|
22
|
-
def _join(left_right: list, video: Path, out: Path):
|
|
23
|
-
video_ext = video.name.split('.')[1]
|
|
24
|
-
audio_left = (
|
|
25
|
-
ffmpeg
|
|
26
|
-
.input(_pathname(left_right[0])))
|
|
27
|
-
audio_right = (
|
|
28
|
-
ffmpeg
|
|
29
|
-
.input(_pathname(left_right[1])))
|
|
30
|
-
input_video = ffmpeg.input(_pathname(video))
|
|
31
|
-
out_file = tempfile.NamedTemporaryFile(suffix='.%s'%video_ext)
|
|
32
|
-
try:
|
|
33
|
-
(ffmpeg
|
|
34
|
-
.filter((audio_left, audio_right), 'join', inputs=2,
|
|
35
|
-
channel_layout='stereo')
|
|
36
|
-
.output(input_video.video, _pathname(out_file),
|
|
37
|
-
shortest=None, vcodec='copy', loglevel="quiet")
|
|
38
|
-
.overwrite_output()
|
|
39
|
-
.run())
|
|
40
|
-
except ffmpeg.Error as e:
|
|
41
|
-
print(e)
|
|
42
|
-
print(e.stderr.decode('UTF-8'))
|
|
43
|
-
quit()
|
|
44
|
-
shutil.copyfile(_pathname(out_file), _pathname(video))
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def sync_cam(dir):
|
|
48
|
-
# dir is a CAM dir, contents are clips and ISO folders
|
|
49
|
-
ISOs = list(dir.glob('*.ISO'))
|
|
50
|
-
for iso in ISOs:
|
|
51
|
-
# iso is a folder
|
|
52
|
-
statResult = iso.stat()
|
|
53
|
-
mtime = statResult.st_mtime
|
|
54
|
-
# print('%s: %s'%(iso.name, biggest_mtime_in_dir(iso)))
|
|
55
|
-
iso_mod_time = biggest_mtime_in_dir(iso)
|
|
56
|
-
clip = clip_from_iso(iso)
|
|
57
|
-
clip_mod_time = clip.stat().st_mtime
|
|
58
|
-
iso_edited = iso_mod_time > clip_mod_time
|
|
59
|
-
# print('clip %s should be resync: %s'%(clip.name, iso_edited))
|
|
60
|
-
# print(clip)
|
|
61
|
-
if iso_edited:
|
|
62
|
-
print('Resyncing [gold1]%s[/gold1]'%clip.name)
|
|
63
|
-
LR_channels = list(valid_audio_files(iso))
|
|
64
|
-
_join(LR_channels, clip, 'test.MOV')
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def valid_audio_files(isofolder: Path) -> list:
|
|
68
|
-
"""
|
|
69
|
-
Returns two valid audio files to be synced with video
|
|
70
|
-
case A - only two files differing by L and R
|
|
71
|
-
case B - more than two files, two are mixL mixR
|
|
72
|
-
case C - one file only -> quit()
|
|
73
|
-
case D - more than two files, no mixL mixR -> quit()
|
|
74
|
-
"""
|
|
75
|
-
files = list(isofolder.iterdir())
|
|
76
|
-
if len(files) == 1: # case C
|
|
77
|
-
print('Error with folder %s: no mixL.wav, mixR.wav'%isofolder)
|
|
78
|
-
print('or micL.wav, micR.wav... Quitting.')
|
|
79
|
-
quit()
|
|
80
|
-
def _is_case_A(files):
|
|
81
|
-
if len(files) != 2:
|
|
82
|
-
return False
|
|
83
|
-
stems = [p.stem.upper() for p in files]
|
|
84
|
-
lasts = [st[-1] for st in stems]
|
|
85
|
-
LR_pair = ''.join(lasts) in ['LR', 'RL']
|
|
86
|
-
prefix = [st[:-1] for st in stems]
|
|
87
|
-
same = prefix[0] == prefix[1]
|
|
88
|
-
return same and LR_pair
|
|
89
|
-
if _is_case_A(files):
|
|
90
|
-
return files
|
|
91
|
-
def _is_case_B(files):
|
|
92
|
-
if len(files) <= 2:
|
|
93
|
-
return False
|
|
94
|
-
stems = [p.stem.upper() for p in files]
|
|
95
|
-
return 'MIXL' in stems and 'MIXR' in stems
|
|
96
|
-
if _is_case_B(files):
|
|
97
|
-
return isofolder.glob('mix?.*')
|
|
98
|
-
print('Error with folder %s: no mixL.wav, mixR.wav'%isofolder)
|
|
99
|
-
print('or micL.wav, micR.wav... Quitting.')
|
|
100
|
-
quit()
|
|
101
|
-
|
|
102
|
-
def biggest_mtime_in_dir(folder: Path) -> float:
|
|
103
|
-
# return the most recent mod time in a folder
|
|
104
|
-
dir_content = list(folder.iterdir())
|
|
105
|
-
stats = [p.stat() for p in dir_content]
|
|
106
|
-
mtimes = [stat.st_mtime for stat in stats]
|
|
107
|
-
return max(mtimes)
|
|
108
|
-
|
|
109
|
-
def clip_from_iso(p: Path) -> Path:
|
|
110
|
-
folder = p.parent
|
|
111
|
-
pair = list(folder.glob('%s.*'%p.stem))
|
|
112
|
-
return [p for p in pair if p.name.split('.')[1] != 'ISO'][0]
|
|
113
|
-
|
|
114
|
-
def synciso(top_dir):
|
|
115
|
-
p = Path(top_dir)/OUT_DIR
|
|
116
|
-
dir_content = list(p.iterdir())
|
|
117
|
-
logger.debug('dir_content %s'%dir_content)
|
|
118
|
-
all_are_dir = all([p.is_dir() for p in dir_content])
|
|
119
|
-
logger.debug('all_are_dir %s'%all_are_dir)
|
|
120
|
-
if not all_are_dir:
|
|
121
|
-
print('Error: resync possible only on structured folders,')
|
|
122
|
-
print('Rerun tictacsync with one directory for each device.')
|
|
123
|
-
quit()
|
|
124
|
-
[sync_cam(f) for f in dir_content]
|
|
125
|
-
|
|
126
|
-
def main():
|
|
127
|
-
parser = argparse.ArgumentParser()
|
|
128
|
-
parser.add_argument(
|
|
129
|
-
"directory",
|
|
130
|
-
type=str,
|
|
131
|
-
nargs='+',
|
|
132
|
-
help="path of media directory containing SyncedMedia/",
|
|
133
|
-
default='.'
|
|
134
|
-
)
|
|
135
|
-
args = parser.parse_args()
|
|
136
|
-
# logger.info('arguments: %s'%args)
|
|
137
|
-
logger.debug('args %s'%args)
|
|
138
|
-
synciso(args.directory)
|
|
139
|
-
# for e in keylist:
|
|
140
|
-
# print(' ', e)
|
|
141
|
-
|
|
142
|
-
if __name__ == '__main__':
|
|
143
|
-
main()
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
tictacsync/LTCcheck.py,sha256=IEfpB_ZajWuRTWtqji0H-B2g7GQvWmGVjfT0Icumv7o,15704
|
|
2
|
-
tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
tictacsync/device_scanner.py,sha256=YxA3_0O1ZPE1ZuD-OgD-dWhtTWE-LamDhqXXyLo3IMw,26132
|
|
4
|
-
tictacsync/entry.py,sha256=pcGuS4_o0o5dREpcccx1_X3w14PeHdQi5z1Ikzmhpwk,16198
|
|
5
|
-
tictacsync/load_fieldr_reaper.py,sha256=tat0tTZpOyshzVvlqyu1r0u3cpf6TNgK-2C6xJk3_Fw,14708
|
|
6
|
-
tictacsync/mamconf.py,sha256=nfXTwabx-tJmBcpnDR4CRkFe9W4fudzfnbq_nHUg0qE,6424
|
|
7
|
-
tictacsync/mamdav.py,sha256=2we8tfIbJBtDMQdpZZVlCQ9hCQRMbKmV2aU3dDEUf2k,27457
|
|
8
|
-
tictacsync/mamreap.py,sha256=ej7Ap8nbVBCkfah2j5hrE7QBWuqL6Zm-OEsQpNK8mYg,21085
|
|
9
|
-
tictacsync/mamsync.py,sha256=mpoHUAuJWiZ1JfVCECiiSLH_HNdXNV1Z_VlUlJBlPcM,14565
|
|
10
|
-
tictacsync/multi2polywav.py,sha256=qJJhjwIgP1BCTpi2e0wfR95XlgZ2-EIqmefVh-jUBPc,7438
|
|
11
|
-
tictacsync/new-sound-resolve.py,sha256=si7NC_VE_2rNV9jR_Nz_YxK1c92JwzWw5BIWdvLdvAQ,18994
|
|
12
|
-
tictacsync/newmix.py,sha256=-zDxr6_O-rjyo1QfgktvHgwqy_un07eFI4zKi8nygIQ,19188
|
|
13
|
-
tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
|
|
14
|
-
tictacsync/remrgmx.py,sha256=FxaAo5qqynpj6O56ekQGD31YP6X2g-kEdwVpHSCoh4Q,4265
|
|
15
|
-
tictacsync/splitmix.py,sha256=dpTQYXXCYoertGOXPnMVCrh6xYh390YqmvOHK9hDg90,3148
|
|
16
|
-
tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
|
|
17
|
-
tictacsync/timeline.py,sha256=ykmB8EfnprQZoEHXRYzriASNWZ7bHfkmQ2-TR6gxZ6Y,75985
|
|
18
|
-
tictacsync/yaltc.py,sha256=xrgL7qokP1A7B_VF4W_BZcC7q9APSmYpmtWH8_t3VWc,68003
|
|
19
|
-
tictacsync-1.4.0b0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
|
|
20
|
-
tictacsync-1.4.0b0.dist-info/METADATA,sha256=HMz1ALEb9soZkeMFu0XWdfSTPMhGq_0zVLDiNjZfu1E,5668
|
|
21
|
-
tictacsync-1.4.0b0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
22
|
-
tictacsync-1.4.0b0.dist-info/entry_points.txt,sha256=1ymCpiosJdolsqz4yPx8aDbBBePqVt4Zz2m228JkBZ4,211
|
|
23
|
-
tictacsync-1.4.0b0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
|
|
24
|
-
tictacsync-1.4.0b0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|