TonieToolbox 0.5.1__py3-none-any.whl → 0.6.0a1__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.
@@ -0,0 +1,428 @@
1
+ # filepath: d:\Repository\TonieToolbox\TonieToolbox\integration_macos.py
2
+ import os
3
+ import sys
4
+ import json
5
+ import plistlib
6
+ import subprocess
7
+ from pathlib import Path
8
+ from .constants import SUPPORTED_EXTENSIONS
9
+ from .logger import get_logger
10
+
11
+ logger = get_logger('integration_macos')
12
+
13
+ class MacOSContextMenuIntegration:
14
+ """
15
+ Class to generate macOS Quick Actions for TonieToolbox integration.
16
+ Creates Quick Actions (Services) for supported audio files, .taf files, and folders.
17
+ """
18
+ def __init__(self):
19
+ # Find the installed command-line tool path
20
+ self.exe_path = os.path.join(sys.prefix, 'bin', 'tonietoolbox')
21
+ self.output_dir = os.path.join(os.path.expanduser('~'), '.tonietoolbox')
22
+ self.services_dir = os.path.join(os.path.expanduser('~'), 'Library', 'Services')
23
+ self.icon_path = os.path.join(self.output_dir, 'icon.png')
24
+ os.makedirs(self.output_dir, exist_ok=True)
25
+
26
+ # Error handling and success messages for shell scripts
27
+ self.error_handling = 'if [ $? -ne 0 ]; then\n echo "Error: Command failed with error code $?"\n read -p "Press any key to close this window..." key\n exit 1\nfi'
28
+ self.success_handling = 'echo "Command completed successfully"\nsleep 2'
29
+
30
+ # Load configuration
31
+ self.config = self._load_config()
32
+
33
+ # Ensure these attributes always exist
34
+ self.upload_url = ''
35
+ self.log_level = self.config.get('log_level', 'SILENT')
36
+ self.log_to_file = self.config.get('log_to_file', False)
37
+ self.basic_authentication_cmd = ''
38
+ self.client_cert_cmd = ''
39
+ self.upload_enabled = self._setup_upload()
40
+
41
+ logger.debug(f"Upload enabled: {self.upload_enabled}")
42
+ logger.debug(f"Upload URL: {self.upload_url}")
43
+ logger.debug(f"Authentication: {'Basic Authentication' if self.basic_authentication else ('None' if self.none_authentication else ('Client Cert' if self.client_cert_authentication else 'Unknown'))}")
44
+
45
+ self._setup_commands()
46
+
47
+ def _build_cmd(self, base_args, file_placeholder='$1', output_to_source=True, use_upload=False, use_artwork=False, use_json=False, use_compare=False, use_info=False, is_recursive=False, is_split=False, is_folder=False, keep_open=False, log_to_file=False):
48
+ """Dynamically build command strings for quick actions."""
49
+ exe = self.exe_path
50
+ cmd = '#!/bin/bash\n\n'
51
+
52
+ # Add a description of what's being executed
53
+ cmd += 'echo "Running TonieToolbox'
54
+ if use_info:
55
+ cmd += ' info'
56
+ elif is_split:
57
+ cmd += ' split'
58
+ elif use_compare:
59
+ cmd += ' compare'
60
+ elif is_recursive:
61
+ cmd += ' recursive folder convert'
62
+ elif is_folder:
63
+ cmd += ' folder convert'
64
+ elif use_upload and use_artwork and use_json:
65
+ cmd += ' convert, upload, artwork and JSON'
66
+ elif use_upload and use_artwork:
67
+ cmd += ' convert, upload and artwork'
68
+ elif use_upload:
69
+ cmd += ' convert and upload'
70
+ else:
71
+ cmd += ' convert'
72
+ cmd += ' command..."\n\n'
73
+
74
+ # Build the actual command
75
+ cmd_line = f'"{exe}" {base_args}'
76
+ if log_to_file:
77
+ cmd_line += ' --log-file'
78
+ if is_recursive:
79
+ cmd_line += ' --recursive'
80
+ if output_to_source:
81
+ cmd_line += ' --output-to-source'
82
+ if use_info:
83
+ cmd_line += ' --info'
84
+ if is_split:
85
+ cmd_line += ' --split'
86
+ if use_compare:
87
+ cmd_line += ' --compare "$1" "$2"'
88
+ else:
89
+ cmd_line += f' "{file_placeholder}"'
90
+ if use_upload:
91
+ cmd_line += f' --upload "{self.upload_url}"'
92
+ if self.basic_authentication_cmd:
93
+ cmd_line += f' {self.basic_authentication_cmd}'
94
+ elif self.client_cert_cmd:
95
+ cmd_line += f' {self.client_cert_cmd}'
96
+ if getattr(self, "ignore_ssl_verify", False):
97
+ cmd_line += ' --ignore-ssl-verify'
98
+ if use_artwork:
99
+ cmd_line += ' --include-artwork'
100
+ if use_json:
101
+ cmd_line += ' --create-custom-json'
102
+
103
+ # Add the command to the script
104
+ cmd += f'{cmd_line}\n\n'
105
+
106
+ # Add error and success handling
107
+ cmd += f'{self.error_handling}\n\n'
108
+ if use_info or use_compare or keep_open:
109
+ cmd += 'echo ""\nread -p "Press any key to close this window..." key\n'
110
+ else:
111
+ cmd += f'{self.success_handling}\n'
112
+
113
+ return cmd
114
+
115
+ def _get_log_level_arg(self):
116
+ """Return the correct log level argument for TonieToolbox CLI based on self.log_level."""
117
+ level = str(self.log_level).strip().upper()
118
+ if level == 'DEBUG':
119
+ return '--debug'
120
+ elif level == 'INFO':
121
+ return '--info'
122
+ return '--silent'
123
+
124
+ def _setup_commands(self):
125
+ """Set up all command strings for quick actions dynamically."""
126
+ log_level_arg = self._get_log_level_arg()
127
+
128
+ # Audio file commands
129
+ self.convert_cmd = self._build_cmd(f'{log_level_arg}', log_to_file=self.log_to_file)
130
+ self.upload_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, log_to_file=self.log_to_file)
131
+ self.upload_artwork_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
132
+ self.upload_artwork_json_cmd = self._build_cmd(f'{log_level_arg}', use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
133
+
134
+ # .taf file commands
135
+ self.show_info_cmd = self._build_cmd(log_level_arg, use_info=True, keep_open=True, log_to_file=self.log_to_file)
136
+ self.extract_opus_cmd = self._build_cmd(log_level_arg, is_split=True, log_to_file=self.log_to_file)
137
+ self.upload_taf_cmd = self._build_cmd(log_level_arg, use_upload=True, log_to_file=self.log_to_file)
138
+ self.upload_taf_artwork_cmd = self._build_cmd(log_level_arg, use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
139
+ self.upload_taf_artwork_json_cmd = self._build_cmd(log_level_arg, use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
140
+ self.compare_taf_cmd = self._build_cmd(log_level_arg, use_compare=True, keep_open=True, log_to_file=self.log_to_file)
141
+
142
+ # Folder commands
143
+ self.convert_folder_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, log_to_file=self.log_to_file)
144
+ self.upload_folder_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, log_to_file=self.log_to_file)
145
+ self.upload_folder_artwork_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
146
+ self.upload_folder_artwork_json_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, use_upload=True, use_artwork=True, use_json=True, log_to_file=self.log_to_file)
147
+
148
+ def _load_config(self):
149
+ """Load configuration settings from config.json"""
150
+ config_path = os.path.join(self.output_dir, 'config.json')
151
+ if not os.path.exists(config_path):
152
+ logger.debug(f"Configuration file not found: {config_path}")
153
+ return {}
154
+
155
+ try:
156
+ with open(config_path, 'r') as f:
157
+ config = json.loads(f.read())
158
+ return config
159
+ except (json.JSONDecodeError, IOError) as e:
160
+ logger.debug(f"Error loading config: {e}")
161
+ return {}
162
+
163
+ def _setup_upload(self):
164
+ """Set up upload functionality based on config.json settings"""
165
+ # Always initialize authentication flags
166
+ self.basic_authentication = False
167
+ self.client_cert_authentication = False
168
+ self.none_authentication = False
169
+
170
+ config = self.config
171
+ try:
172
+ upload_config = config.get('upload', {})
173
+ self.upload_urls = upload_config.get('url', [])
174
+ self.ignore_ssl_verify = upload_config.get('ignore_ssl_verify', False)
175
+ self.username = upload_config.get('username', '')
176
+ self.password = upload_config.get('password', '')
177
+ self.basic_authentication_cmd = ''
178
+ self.client_cert_cmd = ''
179
+
180
+ if self.username and self.password:
181
+ self.basic_authentication_cmd = f'--username {self.username} --password {self.password}'
182
+ self.basic_authentication = True
183
+
184
+ self.client_cert_path = upload_config.get('client_cert_path', '')
185
+ self.client_cert_key_path = upload_config.get('client_cert_key_path', '')
186
+ if self.client_cert_path and self.client_cert_key_path:
187
+ self.client_cert_cmd = f'--client-cert {self.client_cert_path} --client-cert-key {self.client_cert_key_path}'
188
+ self.client_cert_authentication = True
189
+
190
+ if self.client_cert_authentication and self.basic_authentication:
191
+ logger.warning("Both client certificate and basic authentication are set. Only one can be used.")
192
+ return False
193
+
194
+ self.upload_url = self.upload_urls[0] if self.upload_urls else ''
195
+ if not self.client_cert_authentication and not self.basic_authentication and self.upload_url:
196
+ self.none_authentication = True
197
+
198
+ return bool(self.upload_url)
199
+ except Exception as e:
200
+ logger.debug(f"Unexpected error while loading configuration: {e}")
201
+ return False
202
+
203
+ def _create_quick_action(self, name, command, file_types=None, directory_based=False):
204
+ """Create a macOS Quick Action (Service) with the given name and command."""
205
+ # Create Quick Action directory
206
+ action_dir = os.path.join(self.services_dir, f"{name}.workflow")
207
+ os.makedirs(action_dir, exist_ok=True)
208
+
209
+ # Create Contents directory
210
+ contents_dir = os.path.join(action_dir, "Contents")
211
+ os.makedirs(contents_dir, exist_ok=True)
212
+
213
+ # Create document.wflow file with plist content
214
+ document_path = os.path.join(contents_dir, "document.wflow")
215
+
216
+ # Create Info.plist
217
+ info_plist = {
218
+ "NSServices": [
219
+ {
220
+ "NSMenuItem": {
221
+ "default": name
222
+ },
223
+ "NSMessage": "runWorkflowAsService",
224
+ "NSRequiredContext": {
225
+ "NSApplicationIdentifier": "com.apple.finder"
226
+ },
227
+ "NSSendFileTypes": file_types if file_types else [],
228
+ "NSSendTypes": ["NSFilenamesPboardType"] if directory_based else []
229
+ }
230
+ ]
231
+ }
232
+
233
+ info_path = os.path.join(contents_dir, "Info.plist")
234
+ with open(info_path, "wb") as f:
235
+ plistlib.dump(info_plist, f)
236
+
237
+ # Create script file
238
+ script_dir = os.path.join(contents_dir, "MacOS")
239
+ os.makedirs(script_dir, exist_ok=True)
240
+ script_path = os.path.join(script_dir, "script")
241
+
242
+ with open(script_path, "w") as f:
243
+ f.write(command)
244
+
245
+ # Make the script executable
246
+ os.chmod(script_path, 0o755)
247
+
248
+ # Create document.wflow file with a basic workflow definition
249
+ workflow = {
250
+ "AMApplication": "Automator",
251
+ "AMCanShowSelectedItemsWhenRun": False,
252
+ "AMCanShowWhenRun": True,
253
+ "AMDockBadgeLabel": "",
254
+ "AMDockBadgeStyle": "badge",
255
+ "AMName": name,
256
+ "AMRootElement": {
257
+ "actions": [
258
+ {
259
+ "action": "run-shell-script",
260
+ "parameters": {
261
+ "shell": "/bin/bash",
262
+ "script": command,
263
+ "input": "as arguments"
264
+ }
265
+ }
266
+ ],
267
+ "class": "workflow",
268
+ "connections": {},
269
+ "id": "workflow-element",
270
+ "title": name
271
+ },
272
+ "AMWorkflowSchemeVersion": 2.0,
273
+ }
274
+
275
+ with open(document_path, "wb") as f:
276
+ plistlib.dump(workflow, f)
277
+
278
+ return action_dir
279
+
280
+ def _generate_audio_extension_actions(self):
281
+ """Generate Quick Actions for supported audio file extensions."""
282
+ extensions = [ext.lower().lstrip('.') for ext in SUPPORTED_EXTENSIONS]
283
+
284
+ # Create audio file actions
285
+ self._create_quick_action(
286
+ "TonieToolbox - Convert to TAF",
287
+ self.convert_cmd,
288
+ file_types=extensions
289
+ )
290
+
291
+ if self.upload_enabled:
292
+ self._create_quick_action(
293
+ "TonieToolbox - Convert and Upload",
294
+ self.upload_cmd,
295
+ file_types=extensions
296
+ )
297
+
298
+ self._create_quick_action(
299
+ "TonieToolbox - Convert, Upload with Artwork",
300
+ self.upload_artwork_cmd,
301
+ file_types=extensions
302
+ )
303
+
304
+ self._create_quick_action(
305
+ "TonieToolbox - Convert, Upload with Artwork and JSON",
306
+ self.upload_artwork_json_cmd,
307
+ file_types=extensions
308
+ )
309
+
310
+ def _generate_taf_file_actions(self):
311
+ """Generate Quick Actions for .taf files."""
312
+ self._create_quick_action(
313
+ "TonieToolbox - Show Info",
314
+ self.show_info_cmd,
315
+ file_types=["taf"]
316
+ )
317
+
318
+ self._create_quick_action(
319
+ "TonieToolbox - Extract Opus Tracks",
320
+ self.extract_opus_cmd,
321
+ file_types=["taf"]
322
+ )
323
+
324
+ if self.upload_enabled:
325
+ self._create_quick_action(
326
+ "TonieToolbox - Upload",
327
+ self.upload_taf_cmd,
328
+ file_types=["taf"]
329
+ )
330
+
331
+ self._create_quick_action(
332
+ "TonieToolbox - Upload with Artwork",
333
+ self.upload_taf_artwork_cmd,
334
+ file_types=["taf"]
335
+ )
336
+
337
+ self._create_quick_action(
338
+ "TonieToolbox - Upload with Artwork and JSON",
339
+ self.upload_taf_artwork_json_cmd,
340
+ file_types=["taf"]
341
+ )
342
+
343
+ self._create_quick_action(
344
+ "TonieToolbox - Compare with another TAF file",
345
+ self.compare_taf_cmd,
346
+ file_types=["taf"]
347
+ )
348
+
349
+ def _generate_folder_actions(self):
350
+ """Generate Quick Actions for folders."""
351
+ self._create_quick_action(
352
+ "TonieToolbox - Convert Folder to TAF (recursive)",
353
+ self.convert_folder_cmd,
354
+ directory_based=True
355
+ )
356
+
357
+ if self.upload_enabled:
358
+ self._create_quick_action(
359
+ "TonieToolbox - Convert Folder and Upload (recursive)",
360
+ self.upload_folder_cmd,
361
+ directory_based=True
362
+ )
363
+
364
+ self._create_quick_action(
365
+ "TonieToolbox - Convert Folder, Upload with Artwork (recursive)",
366
+ self.upload_folder_artwork_cmd,
367
+ directory_based=True
368
+ )
369
+
370
+ self._create_quick_action(
371
+ "TonieToolbox - Convert Folder, Upload with Artwork and JSON (recursive)",
372
+ self.upload_folder_artwork_json_cmd,
373
+ directory_based=True
374
+ )
375
+
376
+ def install_quick_actions(self):
377
+ """Install all Quick Actions."""
378
+ # Ensure Services directory exists
379
+ os.makedirs(self.services_dir, exist_ok=True)
380
+
381
+ # Check if the icon exists, copy default if needed
382
+ if not os.path.exists(self.icon_path):
383
+ # Include code to extract icon from resources
384
+ logger.debug(f"Icon not found at {self.icon_path}, using default")
385
+
386
+ # Generate Quick Actions for different file types
387
+ self._generate_audio_extension_actions()
388
+ self._generate_taf_file_actions()
389
+ self._generate_folder_actions()
390
+
391
+ # Refresh the Services menu by restarting the Finder
392
+ subprocess.run(["killall", "-HUP", "Finder"], check=False)
393
+
394
+ print("TonieToolbox Quick Actions installed successfully.")
395
+ print("You'll find them in the Services menu when right-clicking on audio files, TAF files, or folders.")
396
+
397
+ def uninstall_quick_actions(self):
398
+ """Uninstall all TonieToolbox Quick Actions."""
399
+ # Find and remove all TonieToolbox Quick Actions
400
+ for item in os.listdir(self.services_dir):
401
+ if item.startswith("TonieToolbox - ") and item.endswith(".workflow"):
402
+ action_path = os.path.join(self.services_dir, item)
403
+ try:
404
+ subprocess.run(["rm", "-rf", action_path], check=True)
405
+ print(f"Removed: {item}")
406
+ except subprocess.CalledProcessError:
407
+ print(f"Failed to remove: {item}")
408
+
409
+ # Refresh the Services menu
410
+ subprocess.run(["killall", "-HUP", "Finder"], check=False)
411
+
412
+ print("TonieToolbox Quick Actions uninstalled successfully.")
413
+
414
+ @classmethod
415
+ def install(cls):
416
+ """
417
+ Generate Quick Actions and install them.
418
+ """
419
+ instance = cls()
420
+ instance.install_quick_actions()
421
+
422
+ @classmethod
423
+ def uninstall(cls):
424
+ """
425
+ Uninstall all TonieToolbox Quick Actions.
426
+ """
427
+ instance = cls()
428
+ instance.uninstall_quick_actions()
@@ -0,0 +1 @@
1
+ # TODO: Add integration_ubuntu.py