tictacsync 1.2.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/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/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,19 +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=ZF4ufRQGUv-6zMFW4fzNZpcFYGr8uUPKRK6jM9_b-P4,36435
4
- tictacsync/entry.py,sha256=xwqziTH1M1m1-6ZUOVO_B7xht9zRZ22G_6ITBtRsEXE,15911
5
- tictacsync/mamconf.py,sha256=FCgwsAadFzrhEUHa7IFSA0nBJV1SyKK55iSTAFR5Nwc,6974
6
- tictacsync/mamsync.py,sha256=2XPhlw2P6IBUoNFUjBhpV4eKP3JqD501zy0iZFXXEjo,16696
7
- tictacsync/multi2polywav.py,sha256=78W5yzKBfWy4nmD837VKwmcHAUZm10zRNe8ARUBJWCI,7439
8
- tictacsync/newmix.py,sha256=-zDxr6_O-rjyo1QfgktvHgwqy_un07eFI4zKi8nygIQ,19188
9
- tictacsync/remergemix.py,sha256=bRyi1hyNcyM1rTkHh8DmSsIQjYpwPprxSyyVipnxz30,9909
10
- tictacsync/remrgmx.py,sha256=FxaAo5qqynpj6O56ekQGD31YP6X2g-kEdwVpHSCoh4Q,4265
11
- tictacsync/synciso.py,sha256=XmUcdUF9rl4VdCm7XW4PeYWYWM0vgAY9dC2hapoul9g,4821
12
- tictacsync/timeline.py,sha256=wcj3n5nAWavJlrZ5Ia-WFmdI2lflU8V1uL13yhrUI7s,75836
13
- tictacsync/yaltc.py,sha256=1XlOmdz-8ZUKF97A30MEkIA0F5oThqDgWPV8Ik3CHn4,53179
14
- tictacsync-1.2.0b0.dist-info/LICENSE,sha256=ZAOPXLh1zlQAnhHUd7oLslKM01YZ5UiAu3STYjwIxck,1068
15
- tictacsync-1.2.0b0.dist-info/METADATA,sha256=SzpJguMsyz9ZRg6uhP7Csw-oa2URSuCNKjj4kWVj66E,5694
16
- tictacsync-1.2.0b0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
17
- tictacsync-1.2.0b0.dist-info/entry_points.txt,sha256=EZrwgJ0nlXDdVUDcMEijMnha5lb1YCkkg4DY9iz97BE,199
18
- tictacsync-1.2.0b0.dist-info/top_level.txt,sha256=eaCWG-BsYTRR-gLTJbK4RfcaXajr0gjQ6wG97MkGRrg,11
19
- tictacsync-1.2.0b0.dist-info/RECORD,,