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.

@@ -159,7 +159,7 @@ def get_device_ffprobe_UID(file):
159
159
  except ffmpeg.Error as e:
160
160
  print('ffmpeg.probe error')
161
161
  print(e.stderr, file)
162
- return None, None #-----------------------------------------------------
162
+ return None, None, None #-----------------------------------------------------
163
163
  # fall back to folder name
164
164
  logger.debug('ffprobe %s'%probe)
165
165
  streams = probe['streams']
@@ -271,7 +271,7 @@ class Scanner:
271
271
  with open(p, 'r') as fh:
272
272
  done = set(fh.read().split()) # sets of strings of abs path
273
273
  logger.debug(f'done clips: {pformat(done)}')
274
- files = Path(self.top_directory).rglob('*')
274
+ files = [p for p in Path(self.top_directory).rglob('*') if not p.name[0] == '.']
275
275
  clip_paths = []
276
276
  some_done = False
277
277
  for raw_path in files:
tictacsync/entry.py CHANGED
@@ -117,21 +117,10 @@ def main():
117
117
  nargs=1,
118
118
  help="directory_name or media_file"
119
119
  )
120
- # parser.add_argument("directory", nargs="?", help="path of media directory")
121
- # parser.add_argument('-v', action='store_true')
122
120
  parser.add_argument('-v',
123
121
  action='store_true', #ie default False
124
122
  dest='verbose_output',
125
123
  help='Set verbose ouput')
126
- # parser.add_argument('--stop_mirroring',
127
- # action='store_true', #ie default False
128
- # dest='stop_mirroring',
129
- # help='Stop mirroring mode, will write synced files alongside originals.')
130
- # parser.add_argument('--start-project', '-s' ,
131
- # nargs=2,
132
- # dest='proj_folders',
133
- # default = [],
134
- # help='start mirrored tree output mode and specifies 2 folders: source (RAW) and destination (synced).')
135
124
  parser.add_argument('-t','--timelineoffset',
136
125
  nargs=1,
137
126
  default=['00:00:00:00'],
@@ -145,10 +134,6 @@ def main():
145
134
  action='store_true',
146
135
  dest='dont_write_cam_folder',
147
136
  help="Even if originals are inside CAM folders, don't put synced clips inside a CAM identified folder")
148
- # parser.add_argument('-m',
149
- # action='store_true',
150
- # dest='multicam',
151
- # help='Outputs multicam structure for NLE program (based on Davinci Resolve input processing)')
152
137
  parser.add_argument('--isos',
153
138
  action='store_true',
154
139
  dest='write_ISOs',
@@ -308,15 +293,7 @@ def main():
308
293
  if asked_ISOs and scanner.input_structure != 'ordered':
309
294
  print('Warning, can\'t write ISOs without structured folders: [gold1]--isos[/gold1] option ignored.\n')
310
295
  asked_ISOs = False
311
- # output_dir = args.o
312
- # if args.verbose_output or args.terse: # verbose, so no progress bars
313
- print('Merging...')
314
- # for merger in matcher.mergers:
315
- # merger.build_audio_and_write_merged_media(top_dir,
316
- # args.dont_write_cam_folder,
317
- # asked_ISOs,
318
- # audio_REC_only)
319
- for merger in matcher.mergers:
296
+ for merger in track(matcher.mergers, description='Merging audio with video...'):
320
297
  if audio_REC_only:
321
298
  # rare
322
299
  merger._build_and_write_audio(top_dir)
tictacsync/mamsync.py CHANGED
@@ -45,7 +45,7 @@ amr ape au awb dss dvf flac gsm iklax ivs m4a m4b m4p mmf mp3 mpc msv nmf
45
45
  ogg oga mogg opus ra rm raw rf64 sln tta voc vox wav wma wv webm 8svx cda""".split()
46
46
 
47
47
  logger.remove()
48
- logger.level("DEBUG", color="<yellow>")
48
+ # logger.level("DEBUG", color="<yellow>")
49
49
  # logger.add(sys.stdout, level="DEBUG")
50
50
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "__init__" and r["module"] == "yaltc")
51
51
  # logger.add(sys.stdout, filter=lambda r: r["function"] == "_fit_length")
@@ -85,24 +85,6 @@ def copy_to_syncedroot(raw_root, synced_root):
85
85
  logger.debug('same content, next')
86
86
  continue # next raw_path in loop
87
87
 
88
- # logger.add(sys.stdout, filter=lambda r: r["function"] == "clean_synced")
89
- # def clean_synced(raw_root, synced_root):
90
- # # removes <>v<capitalLetter>.mov if any
91
- # # returns a list of deleted pathlib.Path
92
- # project = Path(raw_root).name
93
- # logger.debug(f'project {project}')
94
- # synced_proj = Path(synced_root)/project
95
- # if not synced_proj.exists():
96
- # synced_proj.mkdir()
97
- # deleted = []
98
- # for raw_path in Path(synced_proj).rglob('*'):
99
- # m = re.match(r'.*(v[A-Z])\..+', raw_path.name)
100
- # if m != None:
101
- # logger.debug(f'cleaning {raw_path}')
102
- # raw_path.unlink()
103
- # deleted.append(raw_path)
104
- # return deleted
105
-
106
88
  def copy_raw_root_tree_to_sndroot(raw_root, snd_root):
107
89
  # args are str
108
90
  # copy only tree structure, no files
@@ -29,8 +29,10 @@ def print_grby(grby):
29
29
  print(' ', e)
30
30
 
31
31
  def wav_recursive_scan(top_directory):
32
- files_lower_case = Path(top_directory).rglob('*.wav')
32
+ files_lower_case = Path(top_directory).rglob('*.wav')
33
+ files_lower_case = [p for p in files_lower_case if p.name[0] != '.']
33
34
  files_upper_case = Path(top_directory).rglob('*.WAV')
35
+ files_upper_case = [p for p in files_upper_case if p.name[0] != '.']
34
36
  files = set(list(files_lower_case) + list(files_upper_case))
35
37
  paths = [
36
38
  p
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 1.4.0b0
3
+ Version: 1.4.5b0
4
4
  Summary: commands for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -26,6 +26,7 @@ Requires-Dist: loguru >=0.6.0
26
26
  Requires-Dist: matplotlib >=3.7.1
27
27
  Requires-Dist: numpy >=1.24.3
28
28
  Requires-Dist: rich >=10.12.0
29
+ Requires-Dist: lmfit
29
30
  Requires-Dist: scikit-image
30
31
  Requires-Dist: scipy >=1.10.1
31
32
  Requires-Dist: platformdirs
@@ -0,0 +1,16 @@
1
+ tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tictacsync/device_scanner.py,sha256=lYCMWSCeI0KNQclrRMOzeIERh2ME2gvBxV-q9Y2uou0,26175
3
+ tictacsync/entry.py,sha256=JmOx7B6d4LnGLWGuuMFpKV9GxkWhmAG4fdKayI7qzUA,15024
4
+ tictacsync/mamconf.py,sha256=nfXTwabx-tJmBcpnDR4CRkFe9W4fudzfnbq_nHUg0qE,6424
5
+ tictacsync/mamdav.py,sha256=2we8tfIbJBtDMQdpZZVlCQ9hCQRMbKmV2aU3dDEUf2k,27457
6
+ tictacsync/mamreap.py,sha256=ej7Ap8nbVBCkfah2j5hrE7QBWuqL6Zm-OEsQpNK8mYg,21085
7
+ tictacsync/mamsync.py,sha256=orwP-TzKdRTiTCoiM7BsQgVK1KtAIs3SpKe9K8ZWM_Q,13872
8
+ tictacsync/multi2polywav.py,sha256=OX72eDtanaax-lGc6JJXwOz9MaveNcYlgBfBijzR8oA,7583
9
+ tictacsync/timeline.py,sha256=ykmB8EfnprQZoEHXRYzriASNWZ7bHfkmQ2-TR6gxZ6Y,75985
10
+ tictacsync/yaltc.py,sha256=xrgL7qokP1A7B_VF4W_BZcC7q9APSmYpmtWH8_t3VWc,68003
11
+ tictacsync-1.4.5b0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
12
+ tictacsync-1.4.5b0.dist-info/METADATA,sha256=isx8DzvfowkLJgEPTX6y7Q3xyqEvH2Rt2yRHPgHvMVM,5689
13
+ tictacsync-1.4.5b0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
14
+ tictacsync-1.4.5b0.dist-info/entry_points.txt,sha256=0R8K6T0iUJGj87LDZ0vNO8pToshbkxrXZqTRgcjBlMk,244
15
+ tictacsync-1.4.5b0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
16
+ tictacsync-1.4.5b0.dist-info/RECORD,,
@@ -1,7 +1,7 @@
1
1
  [console_scripts]
2
2
  mamconf = tictacsync.mamconf:main
3
- mamdav = mamdav:main
4
- mamreap = mamreap:main
3
+ mamdav = tictacsync.mamdav:called_from_cli
4
+ mamreap = tictacsync.mamreap:main
5
5
  mamsync = tictacsync.mamsync:main
6
6
  multi2polywav = tictacsync.multi2polywav:main
7
7
  tictacsync = tictacsync.entry:main
tictacsync/LTCcheck.py DELETED
@@ -1,394 +0,0 @@
1
- print('Loading modules')
2
- import subprocess, io
3
- import argparse, os, sys, ffmpeg
4
- from loguru import logger
5
- from pathlib import Path
6
- from scipy.io import wavfile
7
- import numpy as np
8
- import matplotlib.pyplot as plt
9
- from rich.progress import track, Progress
10
- from pprint import pprint
11
- from collections import deque
12
- import wave
13
- try:
14
- from . import yaltc
15
- except:
16
- import yaltc
17
- try:
18
- from . import device_scanner
19
- except:
20
- import device_scanner
21
-
22
- # LEVELMODE = 'over_noise_silence'
23
- LEVELMODE = 'mean_silence_AFSK'
24
-
25
- OFFSET_NXT_PULSE = 50 # samples
26
- LENGTH_EXTRACT = int(14e-3 * 96000) # samples max freq
27
-
28
- logger.level("DEBUG", color="<yellow>")
29
- logger.remove()
30
- # logger.add(sys.stdout, filter="tictacsync.LTCcheck")
31
- # logger.add(sys.stdout, filter="tictacsync.yaltc")
32
-
33
- def ppm(a,b):
34
- return 1e6*(max(a,b)/min(a,b)-1)
35
-
36
- class TCframe:
37
- def __init__(self, string, max_FF):
38
- # string is 'HH:MM:SS:FF' or ;|,|.
39
- # max_FF is int for max frame number (hence fps-1)
40
- string = string.replace('.',':').replace(';',':').replace(',',':')
41
- ints = [int(e) for e in string.split(':')]
42
- self.HH = ints[0]
43
- self.MM = ints[1]
44
- self.SS = ints[2]
45
- self.FF = ints[3]
46
- self.MAXFF = max_FF
47
-
48
- def __repr__(self):
49
- # return '%s-%s-%s-%s/%i'%(*self.ints(), self.MAXFF)
50
- return '%02i-%02i-%02i-%02i'%self.ints()
51
-
52
- def ints(self):
53
- return (self.HH,self.MM,self.SS,self.FF)
54
-
55
- def __eq__(self, other):
56
- a,b,c,d = self.ints()
57
- h,m,s,f = other.ints()
58
- return a==h and b==m and c==s and d==f
59
-
60
- def __sub__(self, tcf2):
61
- # H1, M1, S1, F1 = self.ints()
62
- # H2, M2, S2, F2 = tcf2.ints()
63
- f1 = np.array(self.ints())
64
- f2 = np.array(tcf2.ints())
65
- HR, MR, SR, FR = f1 - f2
66
- if FR < 0:
67
- FR += self.MAXFF + 1
68
- SR -= 1 # borrow
69
- if SR < 0:
70
- SR += 60
71
- MR -= 1 # borrow
72
- if MR < 0:
73
- MR += 60
74
- HR -= 1 # borrow
75
- if HR < 0:
76
- HR += 24 # underflow?
77
- # logger.debug('%s %s'%(self.ints(), tcf2.ints()))
78
- return TCframe('%02i:%02i:%02i:%02i'%(HR,MR,SR,FR), self.MAXFF)
79
-
80
- def read_whole_audio_data(path):
81
- dryrun = (ffmpeg
82
- .input(str(path))
83
- .output('pipe:', format='s16le', acodec='pcm_s16le')
84
- .get_args())
85
- dryrun = ' '.join(dryrun)
86
- logger.debug('using ffmpeg-python built args to pipe wav file into numpy array:\nffmpeg %s'%dryrun)
87
- try:
88
- out, _ = (ffmpeg
89
- .input(str(path))
90
- .output('pipe:', format='s16le', acodec='pcm_s16le')
91
- .global_args("-loglevel", "quiet")
92
- .global_args("-nostats")
93
- .global_args("-hide_banner")
94
- .run(capture_stdout=True))
95
- data = np.frombuffer(out, np.int16)
96
- except ffmpeg.Error as e:
97
- print('error',e.stderr)
98
- with wave.open(path, 'rb') as f:
99
- samplerate = f.getframerate()
100
- n_chan = f.getnchannels()
101
- all_channels_data = data.reshape(int(len(data)/n_chan),n_chan).T
102
- return all_channels_data
103
-
104
- def find_nearest_fps(value):
105
- array = np.asarray([24, 25, 30])
106
- idx = (np.abs(array - value)).argmin()
107
- return array[idx]
108
-
109
- def fps_rel_to_audio(frame_pos, samplerate):
110
- _, first_frame_pos = frame_pos[0]
111
- _, scnd_last_frame_pos = frame_pos[-2]
112
- frame_duration = (scnd_last_frame_pos - first_frame_pos)/len(frame_pos[:-2]) # in audio samples
113
- fps = float(samplerate) / frame_duration
114
- return fps
115
-
116
- # def HHMMSSFF_from_line(line):
117
- # line = line.replace('.',':')
118
- # line = line.replace(';',':')
119
- # ll = line.split()[1].split(':')
120
- # return [int(e) for e in ll]
121
-
122
- def check_continuity_and_DF(LTC_frames_and_pos):
123
- errors = []
124
- DF_flag = False
125
- oneframe = TCframe('00:00:00:01',None)
126
- threeframes = TCframe('00:00:00:03',None)
127
- last_two_TC = deque([], maxlen=2)
128
- last_two_TC.append(LTC_frames_and_pos[0][0])
129
- last_two_TC.append(LTC_frames_and_pos[1][0])
130
- for frame, pos in track(LTC_frames_and_pos[2:],
131
- description="Checking each frame increment"):
132
- last_two_TC.append(frame)
133
- past, now = last_two_TC
134
- diff = now - past
135
- if diff not in [oneframe, threeframes]:
136
- errors.append((frame, pos))
137
- continue
138
- if diff == oneframe:
139
- continue
140
- if diff == threeframes:
141
- # DF? check if it is 59:xx and minutes are not mult. of tens
142
- if past.SS != 59 or now.MM%10 == 0:
143
- errors.append((frame, pos))
144
- DF_flag = True
145
- return errors, DF_flag
146
-
147
- def ltcdump_and_check(file, channel):
148
- # returns list of anormal frames, a bool if TC is DF, fps and
149
- # a list of tuples (frame => str, sample position in file => int) as
150
- # determined by external util ltcdump https://github.com/x42/ltc-tools
151
- process_list = ["ltcdump","-c %i"%channel, file]
152
- logger.debug('process %s'%process_list)
153
- proc = subprocess.Popen(process_list, stdout=subprocess.PIPE)
154
- LTC_frames_and_pos = []
155
- iter_io = io.TextIOWrapper(proc.stdout, encoding="utf-8")
156
- next(iter_io) # ltcdump 1st line: User bits Timecode | Pos. (samples)
157
- print()
158
- try:
159
- next(iter_io) # ltcdump 2nd line: #DISCONTINUITY
160
- except StopIteration:
161
- print('ltcdump has no output, is channel #%i really LTC?'%channel)
162
- quit()
163
- old = 0
164
- for line in track(iter_io,
165
- description=' Parsing ltcdump output'): # next ones
166
- # print(line)
167
- if line == '#DISCONTINUITY\n':
168
- # print('#DISCONTINUITY!')
169
- continue
170
- user_bits, HHMMSSFF_str, _, start_sample, end_sample =\
171
- line.split()
172
- audio_position = int(end_sample)
173
- # print(audio_position - old, end=' ')
174
- # old = audio_position
175
- # audio_position = int(start_sample)
176
- tc = HHMMSSFF_str
177
- LTC_frames_and_pos.append((tc, audio_position))
178
- with wave.open(file, 'rb') as f:
179
- samplerate = f.getframerate()
180
- fps = fps_rel_to_audio(LTC_frames_and_pos, samplerate)
181
- rounded_fps = round(fps)
182
- LTC_frames_and_pos = [(TCframe(tc, rounded_fps-1), pos) for tc, pos in LTC_frames_and_pos]
183
- errors, DF_flag = check_continuity_and_DF(LTC_frames_and_pos)
184
- return errors, DF_flag, fps, LTC_frames_and_pos
185
-
186
- def find_pulses(TTC_data, recording):
187
- samplerate = recording.true_samplerate
188
- i_samplerate = round(samplerate)
189
- pulse_position = recording.sync_position
190
- logger.debug('first detected pulse %i'%pulse_position)
191
- # first_pulse_nbr_of_seconds = int(pulse_position/samplerate)
192
- # if first_pulse_nbr_of_seconds > 1:
193
- # pulse_position = pulse_position%i_samplerate # very first pulse in file
194
- # print('0 %i'%pulse_position)
195
- pulse_position = pulse_position%i_samplerate
196
- logger.debug('starting at %i'%pulse_position)
197
- second = 0
198
- duration = int(recording.get_duration())
199
- decoder = recording.decoder
200
- pulse_detection_level = decoder._get_pulse_detection_level()
201
- logger.debug(' detection level %f'%pulse_detection_level)
202
- pulses = []
203
- approx_next_pulse = pulse_position
204
- skipped_printed = False
205
- while second < duration - 1:
206
- second += 1
207
- approx_next_pulse -= OFFSET_NXT_PULSE
208
- start_of_extract = approx_next_pulse
209
- sound_extract = TTC_data[start_of_extract:start_of_extract + LENGTH_EXTRACT]
210
- abs_signal = abs(sound_extract)
211
- detected_point = \
212
- np.argmax(abs_signal > pulse_detection_level)
213
- old_pulse_position = pulse_position
214
- pulse_position = detected_point + start_of_extract
215
- diff = pulse_position - old_pulse_position
216
- logger.debug('pulse_position %f old_pulse_position %f diff %f'%(pulse_position,
217
- old_pulse_position, diff))
218
- if not np.isclose(diff, samplerate, rtol=1e-4):
219
- if not skipped_printed:
220
- print('\nSkipped: ', end='')
221
- skipped_printed = True
222
- print('%i, '%(pulse_position), end='')
223
- # if diff < samplerate:
224
- # else:
225
- # print('skipped: samples %i and %i are too far'%(pulse_position, old_pulse_position))
226
- else:
227
- pulses.append((second, pulse_position))
228
- approx_next_pulse = pulse_position + i_samplerate
229
- if skipped_printed:
230
- print('\n')
231
- return pulses
232
-
233
- def main():
234
- print('in main()')
235
- parser = argparse.ArgumentParser()
236
- parser.add_argument(
237
- "LTC_chan",
238
- type=int,
239
- # nargs=2,
240
- help="LTC channel number"
241
- )
242
- parser.add_argument(
243
- "file_argument",
244
- type=str,
245
- nargs=1,
246
- help="media file"
247
- )
248
- args = parser.parse_args()
249
- # print(args.channels)
250
- LTC_chan = args.LTC_chan
251
- file_argument = args.file_argument[0]
252
- logger.info('args.file_argument: %s'%file_argument)
253
- if os.path.isdir(file_argument):
254
- print('argument shoud be a media file, not a directory. Bye...')
255
- quit()
256
- # print(file_argument)
257
- if not os.path.exists(file_argument):
258
- print('%s does not exist, bye'%file_argument)
259
- quit()
260
- errors, DF_flag, fps_rel_to_audio, LTC_frames_and_pos = ltcdump_and_check(file_argument, LTC_chan)
261
- if errors:
262
- print('errors! %s'%errors)
263
- print('Some errors in those %i but detected FPS rel to audio is %0.3f%s'%(len(LTC_frames_and_pos),
264
- fps_rel_to_audio, 'DF' if DF_flag else 'NDF'))
265
- else:
266
- print('\nAll %i frames are sequential and detected FPS rel to audio is %0.3f%s\n'%(len(LTC_frames_and_pos),
267
- fps_rel_to_audio, 'DF' if DF_flag else 'NDF'))
268
- # print('trying to decode TTC...')
269
- with Progress(transient=True) as progress:
270
- task = progress.add_task("trying to decode TTC...")
271
- progress.start()
272
- m = device_scanner.media_at_path(Path(file_argument))
273
- logger.debug('media_at_path %s'%m)
274
- recording = yaltc.Recording(m, )
275
- logger.debug('Rec %s'%recording)
276
- time = recording.get_start_time(progress=progress, task=task)
277
- if time == None:
278
- print('Start time couldnt be determined')
279
- else:
280
- audio_samplerate_gps_corrected = recording.true_samplerate
281
- audio_error = audio_samplerate_gps_corrected/recording.get_samplerate()
282
- gps_corrected_framerate = fps_rel_to_audio*audio_error
283
- print('gps_corrected_framerate',gps_corrected_framerate,audio_error)
284
- frac_time = int(time.microsecond / 1e2)
285
- d = '%s.%s'%(time.strftime("%Y-%m-%d %H:%M:%S"),frac_time)
286
- base = os.path.basename(file_argument)
287
- print('%s UTC:%s pulse: %i on chan %i'%(base, d,
288
- recording.sync_position,
289
- recording.TicTacCode_channel))
290
- print('audio samplerate (gps)', audio_samplerate_gps_corrected)
291
- all_channels_data = read_whole_audio_data(file_argument)
292
- TTC_data = all_channels_data[recording.TicTacCode_channel]
293
- sec_and_pulses = find_pulses(TTC_data, recording)
294
- secs, pulses = list(zip(*sec_and_pulses))
295
- pulses = list(pulses)
296
- logger.debug('pulses %s'%pulses)
297
- samples_between_UTC_pulses = []
298
- for n1, n2 in zip(pulses[1:], pulses):
299
- delta = n1 - n2
300
- if np.isclose(delta, audio_samplerate_gps_corrected, rtol=1e-3):
301
- samples_between_UTC_pulses.append(delta - audio_samplerate_gps_corrected)
302
- samples_between_UTC_pulses = np.array(samples_between_UTC_pulses)
303
- pulse_length_std = samples_between_UTC_pulses.std()
304
- max_min_over_2 = abs(samples_between_UTC_pulses.max() - samples_between_UTC_pulses.min())/2
305
- # print(samples_between_UTC_pulses)
306
- # print('time is measured with a precision of %f audio samples'%(pulse_length_std))
307
- precision = 1e6*max_min_over_2/audio_samplerate_gps_corrected
308
- print('Time is measured with a precision of %0.1f audio samples (%0.1f μs)'%(max_min_over_2, precision))
309
- frame_duration = 1/fps_rel_to_audio
310
- rel_min_error = 100*1e-6*precision/frame_duration
311
- print('so LTC syncword jitter less than %0.1f %% wont be detected'%(rel_min_error))
312
- # fig, ax = plt.subplots()
313
- # n, bins, patches = ax.hist(samples_between_UTC_pulses)
314
- # plt.show()
315
- # x = range(len(pulses))
316
- a, b = np.polyfit(pulses, secs, 1)
317
- logger.debug('slope, b = %f %f'%(a,b))
318
- # sr_slope = 1/a
319
- # print(sr_slope/recording.true_samplerate)
320
- coherent_sr = np.isclose(a*audio_samplerate_gps_corrected, 1, rtol=1e-7)
321
- logger.debug('samplerates (slope VS rec) are close: %s ratio %f'%(coherent_sr,
322
- a*audio_samplerate_gps_corrected))
323
- if not coherent_sr:
324
- print('warning, wav samplerate are incoherent (Rec + Decode VS slope)')
325
- def make_sample2time(a, b):
326
- return lambda n : a*n + b
327
- sample2time = make_sample2time(a, b)
328
- logger.debug('sample2time fct: %s'%sample2time)
329
- LTC_samples = [N for _, N in LTC_frames_and_pos]
330
- LTC_times = [sample2time(N) for N in LTC_samples]
331
- slope_fps, _ = np.polyfit(LTC_times, range(len(LTC_times)), 1)
332
- print('slope_fps l329', ppm(slope_fps,24))
333
- print('diff slope, ppm',ppm(gps_corrected_framerate, slope_fps))
334
- LTC_frame_durations_samples = [a - b for a, b in zip(LTC_samples[1:], LTC_samples)]
335
- # print(LTC_frame_durations_samples)
336
- frame_duration = 1/fps_rel_to_audio
337
- errors_useconds = [1e6*(frame_duration -(a - b)) for a, b in zip(LTC_times[1:], LTC_times)]
338
- # print(errors_useconds)
339
- errors_useconds = np.array(errors_useconds)
340
- LTC_std = abs(errors_useconds).std()
341
- LTC_max_min = abs(errors_useconds.max() - errors_useconds.min())/2
342
- # print('Mean frame duration is %i audio samples'%)
343
- print('\nhere LTC frame duration varies by %f μs ('%LTC_max_min, end='')
344
- print('%0.3fFPS nominal frame duration is %0.0f μs)\n'%(fps_rel_to_audio, 1e6/fps_rel_to_audio))
345
- # print(errors_useconds[:200])
346
- # audio_sampling_period = 1/samplerate
347
- # print(audio_sampling_period)
348
- # errors_in_audiosamples = [int(e/audio_sampling_period) for e in errors_seconds]
349
- # print(delta_milliseconds)
350
- # plt.plot(LTC_times, marker='.', markersize='1',
351
- # linestyle='None', color='black')
352
- # plt.show()
353
- # print(LTC_times)
354
- # fig, ax = plt.subplots()
355
-
356
- # the histogram of the data
357
- # print(errors_in_audiosamples)
358
- fig, ax = plt.subplots()
359
- n, bins, patches = ax.hist(errors_useconds, bins=40)
360
- plt.show()
361
- quit()
362
-
363
- if __name__ == '__main__':
364
- main()
365
-
366
- # import matplotlib.pyplot as plt
367
- # import numpy as np
368
-
369
- # rng = np.random.default_rng(19680801)
370
-
371
- # # example data
372
- # mu = 106 # mean of distribution
373
- # sigma = 17 # standard deviation of distribution
374
- # x = rng.normal(loc=mu, scale=sigma, size=420)
375
-
376
- # num_bins = 42
377
-
378
- # fig, ax = plt.subplots()
379
-
380
- # # the histogram of the data
381
- # n, bins, patches = ax.hist(x, num_bins, density=True)
382
-
383
- # # add a 'best fit' line
384
- # y = ((1 / (np.sqrt(2 * np.pi) * sigma)) *
385
- # np.exp(-0.5 * (1 / sigma * (bins - mu))**2))
386
- # ax.plot(bins, y, '--')
387
- # ax.set_xlabel('Value')
388
- # ax.set_ylabel('Probability density')
389
- # ax.set_title('Histogram of normal distribution sample: '
390
- # fr'$\mu={mu:.0f}$, $\sigma={sigma:.0f}$')
391
-
392
- # # Tweak spacing to prevent clipping of ylabel
393
- # fig.tight_layout()
394
- # plt.show()