tictacsync 0.99a0__py3-none-any.whl → 1.0.1a0__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.

@@ -3,7 +3,7 @@
3
3
  # while inotifywait --recursive -e close_write . ; do python entry.py tests/multi2/; done
4
4
  # above for linux
5
5
 
6
- TRACKSFN = 'tracks.txt'
6
+ TRACKSFILE = 'tracks.txt'
7
7
  SILENT_TRACK_TOKENS = '-0n'
8
8
 
9
9
  av_file_extensions = \
@@ -413,19 +413,19 @@ class Scanner:
413
413
 
414
414
  def _get_tracks_from_file(self, device) -> Tracks:
415
415
  """
416
- Look for eventual track names in TRACKSFN file, stored inside the
416
+ Look for eventual track names in TRACKSFILE file, stored inside the
417
417
  recorder folder alongside the audio files. If there, returns a Tracks
418
418
  object, if not returns None.
419
419
  """
420
420
  source_audio_folder = device.folder
421
- tracks_file = source_audio_folder/TRACKSFN
421
+ tracks_file = source_audio_folder/TRACKSFILE
422
422
  track_names = False
423
423
  a_recording = [m for m in self.found_media_files
424
424
  if m.device == device][0]
425
425
  logger.debug('a_recording for device %s : %s'%(device, a_recording))
426
426
  nchan = sox.file_info.channels(str(a_recording.path))
427
427
  if os.path.isfile(tracks_file):
428
- logger.debug('found file: %s'%(TRACKSFN))
428
+ logger.debug('found file: %s'%(TRACKSFILE))
429
429
  tracks = self._parse_track_values(tracks_file)
430
430
  if tracks.error_msg:
431
431
  print('\nError parsing [gold1]%s[/gold1] file: %s, quitting.\n'%
tictacsync/newmix.py ADDED
@@ -0,0 +1,323 @@
1
+ import os, itertools, argparse, ffmpeg, tempfile
2
+ from pathlib import Path
3
+ from loguru import logger
4
+ import shutil, sys, re, sox
5
+ from pprint import pformat
6
+ from rich import print
7
+
8
+ OUT_DIR_DEFAULT = 'SyncedMedia'
9
+ MCCDIR = 'SyncedMulticamClips'
10
+ SEC_DELAY_CHANGED_SND = 10 #sec, SND_DIR changed if diff time is bigger
11
+ DEL_TEMP = True
12
+
13
+ logger.level("DEBUG", color="<yellow>")
14
+ logger.remove()
15
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "_change_audio4video")
16
+ # logger.add(sys.stdout, filter=lambda r: r["function"] == "find_SND_vids_pairs_in_dir")
17
+
18
+ video_extensions = \
19
+ """webm mkv flv flv vob ogv ogg drc gif gifv mng avi mov
20
+ qt wmv yuv rm rmvb viv asf mp4 m4p m4v mpg mp2 mpeg mpe
21
+ mpv mpg mpeg m2v m4v svi 3gp 3g2 mxf roq nsv""".split() # from wikipedia
22
+
23
+ def _pathname(tempfile_or_path) -> str:
24
+ # utility for obtaining a str from different filesystem objects
25
+ if isinstance(tempfile_or_path, str):
26
+ return tempfile_or_path
27
+ if isinstance(tempfile_or_path, Path):
28
+ return str(tempfile_or_path)
29
+ if isinstance(tempfile_or_path, tempfile._TemporaryFileWrapper):
30
+ return tempfile_or_path.name
31
+ else:
32
+ raise Exception('%s should be Path or tempfile...'%tempfile_or_path)
33
+
34
+ def is_synced_video(f):
35
+ # True if name as video extension
36
+ # and is under SyncedMedia or SyncedMulticamClips folders
37
+ # f is a Path
38
+ ext = f.suffix[1:] # removing leading '.'
39
+ ok_ext = ext.lower() in video_extensions
40
+ f_parts = f.parts
41
+ ok_folders = OUT_DIR_DEFAULT in f_parts or MCCDIR in f_parts
42
+ # logger.debug('ok_ext: %s ok_folders: %s'%(ok_ext, ok_folders))
43
+ return ok_ext and ok_folders
44
+
45
+ def find_SND_vids_pairs_in_dir(top):
46
+ # look for matching video name and SND dir name
47
+ # eg: IMG04.mp4 and IMG04_SND
48
+ # maybe IMG04v2.mp4 if audio changed before (than it will be IMG04v3.mp4)
49
+ # returns list of matches
50
+ # recursively search from 'top' argument
51
+ vids = []
52
+ SNDs = []
53
+ for (root,dirs,files) in os.walk(top):
54
+ for d in dirs:
55
+ if d[-4:] == '_SND':
56
+ SNDs.append(Path(root)/d)
57
+ for f in files:
58
+ if is_synced_video(Path(root)/f): # add being in SyncedMedia or SyncedMulticamClips folder
59
+ vids.append(Path(root)/f)
60
+ logger.debug('vids %s SNDs %s'%(pformat(vids), pformat(SNDs)))
61
+ matches = []
62
+ def _names_match(vidname, SND_name):
63
+ # vidname is a str and has no extension
64
+ # vidname could have vNN suffix as in DSC_8064v31 so matches DSC_8064
65
+ if vidname == SND_name: # no suffix presents
66
+ return True
67
+ m = re.match(SND_name + r'v(\d+)', vidname)
68
+ if m != None:
69
+ logger.debug('its a natch and N= %s'%m.groups()[0])
70
+ return m != None
71
+ for pair in list(itertools.product(SNDs, vids)):
72
+ # print(pair)
73
+ SND, vid = pair # Paths
74
+ vidname, ext = vid.name.split('.') # string
75
+ if _names_match(vidname, SND.name[:-4]):
76
+ logger.debug('SND %s matches video %s'%(
77
+ Path('').joinpath(*SND.parts[-2:]),
78
+ Path('').joinpath(*vid.parts[-3:])))
79
+ matches.append(pair) # list of Paths
80
+ logger.debug('matches: %s'%pformat(matches))
81
+ return matches
82
+
83
+ def parse_and_check_arguments():
84
+ # parses directories from command arguments
85
+ # check for consistencies and warn user and exits,
86
+ # if returns, gives:
87
+ # proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only
88
+ parser = argparse.ArgumentParser()
89
+ # parser.add_argument('-v',
90
+ # nargs=1,
91
+ # dest='video_dirs',
92
+ # help='Where proxy clips and/or originals are stored')
93
+ # parser.add_argument('-a',
94
+ # nargs=1,
95
+ # dest='audio_dir',
96
+ # help='Contains newly changed mix files')
97
+ parser.add_argument('-b',
98
+ nargs=1,
99
+ dest='both_audio_vid',
100
+ help='Directory scanned for both audio and video, when tictacsync was used in "alongside mode"')
101
+ parser.add_argument('--dry',
102
+ action='store_true',
103
+ dest='scan_only',
104
+ help="Just display changed audio, don't merge")
105
+ args = parser.parse_args()
106
+ logger.debug('args %s'%args)
107
+ # ok cases:
108
+ # -p -o -a + no -b
109
+ # -o -a + no -b
110
+ # args_set = [args.originals_dir != None,
111
+ # args.audio_dir != None,
112
+ # args.both_audio_vid != None,
113
+ # ]
114
+ # p, o, a, b = args_set
115
+ # check that argument -b (both_audio_vid) is used alone
116
+ # if b and any([o, a, p]):
117
+ # print("\nDon't specify other argument than -b if both audio and video searched in the same directory.\n")
118
+ # parser.print_help(sys.stderr)
119
+ # sys.exit(0)
120
+ # check that if proxies (-p) are specified, orginals too (-o)
121
+ # if p and not o:
122
+ # print("\nIf proxies directory is specified, so should originals directory.\n")
123
+ # parser.print_help(sys.stderr)
124
+ # sys.exit(0)
125
+ # check that -o and -a are used together
126
+ # if not b and not (o and a):
127
+ # print("\nAt least originals and audio directories must be given (-o and -a) when audio and video are in different dir.\n")
128
+ # parser.print_help(sys.stderr)
129
+ # sys.exit(0)
130
+ # # work in progress (aug 2025), so limit to -b:
131
+ # if not b :
132
+ # print("\nFor now, only -b argument is supported (a directory scanned for both audio and video) .\n")
133
+ # parser.print_help(sys.stderr)
134
+ # sys.exit(0)
135
+ # list of singletons, so flatten. Keep None and False as is
136
+ if args.scan_only:
137
+ print('Sorry, --dry option not implemented yet, bye.')
138
+ sys.exit(0)
139
+ return args
140
+
141
+ def get_recent_mix(SND_dir, vid):
142
+ # check if there are mixl, mixr or mix files in SND_dir
143
+ # and return the paths if they are more recent than vid.
144
+ # returns empty tuple otherwise
145
+ # arguments SND_dir, vid and returned values are of Path type
146
+ wav_files = list(SND_dir.iterdir())
147
+ logger.debug(f'wav_files {wav_files} in {SND_dir}')
148
+ def is_mix(p):
149
+ re_result = re.match(r'mix([lrLR])*', p.name)
150
+ logger.debug(f'for {p.name} re_result {re_result}')
151
+ return re_result is not None
152
+ mix_files = [p for p in wav_files if is_mix(p)]
153
+ if len(mix_files) == 0:
154
+ return ()
155
+ # consistency check, should be 1 or 2 files
156
+ if not len(mix_files) in (1,2):
157
+ print(f'\nError: too many mix files in [bold]{SND_dir}[/bold], bye.')
158
+ sys.exit(0)
159
+ # one file? it must be mix.wav
160
+ if len(mix_files) == 1:
161
+ fn = mix_files[0].name
162
+ if fn.upper() != 'MIX.WAV':
163
+ print(f'\nError in [bold]{SND_dir}[/bold], the only file should be mix.wav, not [bold]{fn}[/bold][/bold]; bye.')
164
+ sys.exit(0)
165
+ # two files? verify they are mixL and mixR and mono each
166
+ if len(mix_files) == 2:
167
+ first3uppercase = [p.name[:4].upper() for p in mix_files]
168
+ first3uppercase.sort()
169
+ first3uppercase = ''.join(first3uppercase)
170
+ if first3uppercase != 'MIXLMIXR':
171
+ print(f'\nError: mix names mismatch in [bold]{SND_dir}[/bold];')
172
+ print(f'names are [bold]{[p.name for p in mix_files]}[/bold], check they are simply mixL.wav and mixR.wav; bye.')
173
+ sys.exit(0)
174
+ def _nch(p):
175
+ return sox.file_info.channels(str(p))
176
+ are_mono = [_nch(p) == 1 for p in mix_files]
177
+ logger.debug('are_mono: %s'%are_mono)
178
+ if not all(are_mono):
179
+ print(f'\nError in [bold]{SND_dir}[/bold], some files are not mono, bye.')
180
+ sys.exit(0)
181
+ logger.debug(f'mix_files: {mix_files}')
182
+ # check dates, if two files, take first
183
+ mix_modification_time = mix_files[0].stat().st_mtime
184
+ vid_mod_time = vid.stat().st_mtime
185
+ # difference of modification time in secs
186
+ mix_more_recent_by = mix_modification_time - vid_mod_time
187
+ logger.debug('mix_more_recent_by: %s'%mix_more_recent_by)
188
+ if mix_more_recent_by > SEC_DELAY_CHANGED_SND:
189
+ if len(mix_files) == 1:
190
+ two_folders_up = mix_files[0]
191
+ # two_folders_up = Path('').joinpath(*mix_files[0].parts[-3:])
192
+ print(f'\nFound new mix: [bold]{two_folders_up}[/bold]')
193
+ return mix_files
194
+ else:
195
+ return ()
196
+
197
+ def _keep_VIDEO_only(video_path):
198
+ # return file handle to a temp video file formed from the video_path
199
+ # stripped of its sound
200
+ in1 = ffmpeg.input(_pathname(video_path))
201
+ video_extension = video_path.suffix
202
+ silenced_opts = ["-loglevel", "quiet", "-nostats", "-hide_banner"]
203
+ file_handle = tempfile.NamedTemporaryFile(suffix=video_extension,
204
+ delete=DEL_TEMP)
205
+ out1 = in1.output(file_handle.name, map='0:v', vcodec='copy')
206
+ ffmpeg.run([out1.global_args(*silenced_opts)], overwrite_output=True)
207
+ return file_handle
208
+
209
+ def _change_audio4video(audio_path: Path, video: Path):
210
+ """
211
+ Replace audio in video (argument) by the audio contained in
212
+ audio_path (argument) returns nothing
213
+ If name has version number, bump it (DSC_8064v13.MOV -> DSC_8064v14.MOV)
214
+
215
+ """
216
+ vidname, video_ext = video.name.split('.')
217
+ vid_only_handle = _keep_VIDEO_only(video)
218
+ a_n = _pathname(audio_path)
219
+ v_n = _pathname(vid_only_handle)
220
+ # check v suffix
221
+ m = re.match(f'(.*)v(\\d+)', vidname)
222
+ if m == None:
223
+ # no suffix, add one
224
+ out_path = video.parent / f'{vidname}v2.{video_ext}'
225
+ out_n = _pathname(out_path)
226
+ else:
227
+ base, number_str = m.groups()
228
+ logger.debug(f'base {base}, number_str {number_str}')
229
+ up_tick = 1 + int(number_str)
230
+ out_path = video.parent / f'{base}v{up_tick}.{video_ext}'
231
+ out_n = _pathname(out_path)
232
+ print(f'Video [bold]{video}[/bold] \nhas new sound and is now [bold]{out_path}[/bold]')
233
+ video.unlink()
234
+ # building args for debug purpose only:
235
+ ffmpeg_args = (
236
+ ffmpeg
237
+ .input(v_n)
238
+ .output(out_n, vcodec='copy')
239
+ # .output(out_n, shortest=None, vcodec='copy')
240
+ .global_args('-i', a_n, "-hide_banner")
241
+ .overwrite_output()
242
+ .get_args()
243
+ )
244
+ logger.debug('ffmpeg args: %s'%' '.join(ffmpeg_args))
245
+ try: # for real now
246
+ _, out = (
247
+ ffmpeg
248
+ .input(v_n)
249
+ # .output(out_n, shortest=None, vcodec='copy')
250
+ .output(out_n, vcodec='copy')
251
+ .global_args('-i', a_n, "-hide_banner")
252
+ .overwrite_output()
253
+ .run(capture_stderr=True)
254
+ )
255
+ logger.debug('ffmpeg output')
256
+ for l in out.decode("utf-8").split('\n'):
257
+ logger.debug(l)
258
+ except ffmpeg.Error as e:
259
+ print('ffmpeg.run error merging: \n\t %s + %s = %s\n'%(
260
+ audio_path,
261
+ video_path,
262
+ synced_clip_file
263
+ ))
264
+ print(e)
265
+ print(e.stderr.decode('UTF-8'))
266
+ sys.exit(1)
267
+
268
+ def _sox_combine(paths) -> Path:
269
+ """
270
+ Combines (stacks) files referred by the list of Path into a new temporary
271
+ files passed on return each files are stacked in a different channel, so
272
+ len(paths) == n_channels
273
+ """
274
+ if len(paths) == 1: # one device only, nothing to stack
275
+ logger.debug('one device only, nothing to stack')
276
+ return paths[0] ########################################################
277
+ out_file_handle = tempfile.NamedTemporaryFile(suffix='.wav',
278
+ delete=DEL_TEMP)
279
+ filenames = [_pathname(p) for p in paths]
280
+ out_file_name = _pathname(out_file_handle)
281
+ logger.debug('combining files: %s into %s'%(
282
+ filenames,
283
+ out_file_name))
284
+ cbn = sox.Combiner()
285
+ cbn.set_input_format(file_type=['wav']*len(paths))
286
+ status = cbn.build(
287
+ filenames,
288
+ out_file_name,
289
+ combine_type='merge')
290
+ logger.debug('sox.build status: %s'%status)
291
+ if status != True:
292
+ print('Error, sox did not merge files in _sox_combine()')
293
+ sys.exit(1)
294
+ merged_duration = sox.file_info.duration(
295
+ _pathname(out_file_handle))
296
+ nchan = sox.file_info.channels(
297
+ _pathname(out_file_handle))
298
+ logger.debug('merged file duration %f s with %i channels '%
299
+ (merged_duration, nchan))
300
+ return out_file_handle
301
+
302
+
303
+ def main():
304
+ # proxies_dir, originals_dir, audio_dir, both_audio_vid, scan_only = \
305
+ # parse_and_check_arguments()
306
+ args = parse_and_check_arguments()
307
+ matching_pairs = find_SND_vids_pairs_in_dir(args.both_audio_vid[0])
308
+ for SND_dir, vid in matching_pairs:
309
+ new_mix_files = get_recent_mix(SND_dir, vid)
310
+ # logger.debug('new_mix_files: %s'%str(new_mix_files))
311
+ if new_mix_files != ():
312
+ logger.debug(f'new mixes {new_mix_files} in {SND_dir} for {vid.name}')
313
+ if len(new_mix_files) == 2:
314
+ new_audio_wav = _sox_combine(new_mix_files)
315
+ logger.debug('stereo_wav: %s'%new_audio_wav)
316
+ else: # len == 1, mono wav file
317
+ new_audio_wav = new_mix_files[0]
318
+ _change_audio4video(new_audio_wav, vid)
319
+ # print('\nVideo %s has new audio'%vid)
320
+
321
+
322
+ if __name__ == '__main__':
323
+ main()
tictacsync/timeline.py CHANGED
@@ -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.
@@ -201,7 +202,7 @@ def _sox_multi2stereo(multichan_tmpfl, stereo_trxs) -> tempfile.NamedTemporaryFi
201
202
  stereo_tempfile = tempfile.NamedTemporaryFile(suffix='.wav',
202
203
  delete=DEL_TEMP)
203
204
  tfm = sox.Transformer()
204
- tfm.channels(1)
205
+ tfm.channels(1) # why ? https://pysox.readthedocs.io/en/latest/api.html?highlight=channels#sox.transform.Transformer.channels
205
206
  status = tfm.build(_pathname(multichan_tmpfl),_pathname(stereo_tempfile))
206
207
  logger.debug('n chan ouput: %s'%
207
208
  sox.file_info.channels(_pathname(stereo_tempfile)))
@@ -655,7 +656,7 @@ class AudioStitcherVideoMerger:
655
656
  """
656
657
  Writes isolated audio files that were synced to synced_clip_file,
657
658
  each track will have its dedicated monofile, named sequentially or with
658
- the name find in TRACKSFN if any, see Scanner._get_tracks_from_file()
659
+ the name find in TRACKSFILE if any, see Scanner._get_tracks_from_file()
659
660
 
660
661
  edited_audio_all_devices:
661
662
  a list of (name, mono_tempfile)
@@ -711,7 +712,8 @@ class AudioStitcherVideoMerger:
711
712
  synced_clip_dir = synced_clip_file.parent
712
713
  # build ISOs subfolders structure, see comment string below
713
714
  video_stem_WO_suffix = synced_clip_file.stem
714
- ISOdir = synced_clip_dir/(video_stem_WO_suffix + '_ISO')
715
+ # ISOdir = synced_clip_dir/(video_stem_WO_suffix + 'ISO')
716
+ ISOdir = synced_clip_dir/(video_stem_WO_suffix + '_SND')/'ISOfiles'
715
717
  os.makedirs(ISOdir, exist_ok=True)
716
718
  logger.debug('edited_audio_all_devices %s'%edited_audio_all_devices)
717
719
  logger.debug('ISOdir %s'%ISOdir)
@@ -794,7 +796,7 @@ class AudioStitcherVideoMerger:
794
796
  print('Error: TicTacCode channel detected is [gold1]%i[/gold1]'%
795
797
  (device.ttc), end=' ')
796
798
  print('and [gold1]%s[/gold1] for the device [gold1]%s[/gold1] specifies channel [gold1]%i[/gold1],'%
797
- (device_scanner.TRACKSFN,
799
+ (device_scanner.TRACKSFILE,
798
800
  device.name, device.tracks.ttc-1))
799
801
  print('Please correct the discrepancy and rerun. Quitting.')
800
802
  sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tictacsync
3
- Version: 0.99a0
3
+ Version: 1.0.1a0
4
4
  Summary: command for syncing audio video recordings
5
5
  Home-page: https://tictacsync.org/
6
6
  Author: Raymond Lutz
@@ -0,0 +1,17 @@
1
+ tictacsync/LTCcheck.py,sha256=IEfpB_ZajWuRTWtqji0H-B2g7GQvWmGVjfT0Icumv7o,15704
2
+ tictacsync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ tictacsync/device_scanner.py,sha256=lNoOyPuAsPzjGs5a6iKJQ-BAVXKKINk8fvTEgXgFeK0,35593
4
+ tictacsync/entry.py,sha256=KOhB8ivgme3GPpWShad2adS1lvIU9v0yMFY0CELwAmM,20673
5
+ tictacsync/multi2polywav.py,sha256=-nX5reZo6QNxFYdhsliHTs8bTfMjPzcONDT8vJbkZUA,7291
6
+ tictacsync/newmix.py,sha256=HB5lThhSr1oVtUyIordaB-lwAxm4bbuijRr-MBdZWhA,13161
7
+ tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
8
+ tictacsync/remrgmx.py,sha256=FxaAo5qqynpj6O56ekQGD31YP6X2g-kEdwVpHSCoh4Q,4265
9
+ tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
10
+ tictacsync/timeline.py,sha256=aMu1ntVrpFtH1YfyIgp80k7DT2RFo5B0E-BlMWE8wAs,72723
11
+ tictacsync/yaltc.py,sha256=xbMucI19UJKrEvIzyfpOsi3piSWzqM1gKgooeT9DV8g,53167
12
+ tictacsync-1.0.1a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
13
+ tictacsync-1.0.1a0.dist-info/METADATA,sha256=3LdhjwU_XGS8-rBcRvROlH54wnUyrG6q06cMJSr5XhE,5697
14
+ tictacsync-1.0.1a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
+ tictacsync-1.0.1a0.dist-info/entry_points.txt,sha256=bMsk7T_7fwCtAOUbFyvECvHOzCJb2fmWjkUKQTkwbsc,131
16
+ tictacsync-1.0.1a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
17
+ tictacsync-1.0.1a0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  [console_scripts]
2
2
  multi2polywav = tictacsync.multi2polywav:main
3
- remergemix = tictacsync.remergemix:main
3
+ newmix = tictacsync.newmix:main
4
4
  tictacsync = tictacsync.entry:main
@@ -1,16 +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=A41PmzI6jr4zCKRm3BL5aBQkdJImpN3DH4QIicCZolA,35585
4
- tictacsync/entry.py,sha256=KOhB8ivgme3GPpWShad2adS1lvIU9v0yMFY0CELwAmM,20673
5
- tictacsync/multi2polywav.py,sha256=-nX5reZo6QNxFYdhsliHTs8bTfMjPzcONDT8vJbkZUA,7291
6
- tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
7
- tictacsync/remrgmx.py,sha256=FxaAo5qqynpj6O56ekQGD31YP6X2g-kEdwVpHSCoh4Q,4265
8
- tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
9
- tictacsync/timeline.py,sha256=DW_24cddLIxmcAEywy_sU46lBc7bJLhpVSVi7TlWV4E,72531
10
- tictacsync/yaltc.py,sha256=xbMucI19UJKrEvIzyfpOsi3piSWzqM1gKgooeT9DV8g,53167
11
- tictacsync-0.99a0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
12
- tictacsync-0.99a0.dist-info/METADATA,sha256=XRzYPb479yzUd7SgUf5YagcDHeQqREeTUTOZnZyXWCE,5696
13
- tictacsync-0.99a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
14
- tictacsync-0.99a0.dist-info/entry_points.txt,sha256=g3tdFFrVRcrKpuyKOCLUVBMgYfV65q9kpLZUOD_XCKg,139
15
- tictacsync-0.99a0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
16
- tictacsync-0.99a0.dist-info/RECORD,,