TonieToolbox 0.5.1__py3-none-any.whl → 0.6.0a2__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.
@@ -14,16 +14,16 @@ from .logger import get_logger
14
14
  logger = get_logger('recursive_processor')
15
15
 
16
16
 
17
- def find_audio_folders(root_path: str) -> List[Dict[str, any]]:
17
+ def find_audio_folders(root_path: str) -> list[dict[str, any]]:
18
18
  """
19
19
  Find and return all folders that contain audio files in a recursive manner,
20
20
  organized in a way that handles nested folder structures.
21
21
 
22
22
  Args:
23
- root_path: Root directory to start searching from
23
+ root_path (str): Root directory to start searching from
24
24
 
25
25
  Returns:
26
- List of dictionaries with folder information, including paths and relationships
26
+ list[dict[str, any]]: List of dictionaries with folder information, including paths and relationships
27
27
  """
28
28
  logger.info("Finding folders with audio files in: %s", root_path)
29
29
 
@@ -68,15 +68,15 @@ def find_audio_folders(root_path: str) -> List[Dict[str, any]]:
68
68
  return folder_list
69
69
 
70
70
 
71
- def determine_processing_folders(folders: List[Dict[str, any]]) -> List[Dict[str, any]]:
71
+ def determine_processing_folders(folders: list[dict[str, any]]) -> list[dict[str, any]]:
72
72
  """
73
73
  Determine which folders should be processed based on their position in the hierarchy.
74
74
 
75
75
  Args:
76
- folders: List of folder dictionaries with hierarchy information
76
+ folders (list[dict[str, any]]): List of folder dictionaries with hierarchy information
77
77
 
78
78
  Returns:
79
- List of folders that should be processed (filtered)
79
+ list[dict[str, any]]: List of folders that should be processed (filtered)
80
80
  """
81
81
  # We'll use a set to track which folders we've decided to process
82
82
  to_process = set()
@@ -120,15 +120,15 @@ def determine_processing_folders(folders: List[Dict[str, any]]) -> List[Dict[str
120
120
  return result
121
121
 
122
122
 
123
- def get_folder_audio_files(folder_path: str) -> List[str]:
123
+ def get_folder_audio_files(folder_path: str) -> list[str]:
124
124
  """
125
125
  Get all audio files in a specific folder.
126
126
 
127
127
  Args:
128
- folder_path: Path to folder
128
+ folder_path (str): Path to folder
129
129
 
130
130
  Returns:
131
- List of paths to audio files in natural sort order
131
+ list[str]: List of paths to audio files in natural sort order
132
132
  """
133
133
  audio_files = glob.glob(os.path.join(folder_path, "*"))
134
134
  filtered_files = filter_directories(audio_files)
@@ -140,15 +140,15 @@ def get_folder_audio_files(folder_path: str) -> List[str]:
140
140
  return sorted_files
141
141
 
142
142
 
143
- def natural_sort(file_list: List[str]) -> List[str]:
143
+ def natural_sort(file_list: list[str]) -> list[str]:
144
144
  """
145
145
  Sort a list of files in natural order (so that 2 comes before 10).
146
146
 
147
147
  Args:
148
- file_list: List of file paths
148
+ file_list (list[str]): List of file paths
149
149
 
150
150
  Returns:
151
- Naturally sorted list of file paths
151
+ list[str]: Naturally sorted list of file paths
152
152
  """
153
153
  def convert(text):
154
154
  return int(text) if text.isdigit() else text.lower()
@@ -159,16 +159,16 @@ def natural_sort(file_list: List[str]) -> List[str]:
159
159
  return sorted(file_list, key=alphanum_key)
160
160
 
161
161
 
162
- def extract_folder_meta(folder_path: str) -> Dict[str, str]:
162
+ def extract_folder_meta(folder_path: str) -> dict[str, str]:
163
163
  """
164
164
  Extract metadata from folder name.
165
165
  Common format might be: "YYYY - NNN - Title"
166
166
 
167
167
  Args:
168
- folder_path: Path to folder
168
+ folder_path (str): Path to folder
169
169
 
170
170
  Returns:
171
- Dictionary with extracted metadata (year, number, title)
171
+ dict[str, str]: Dictionary with extracted metadata (year, number, title)
172
172
  """
173
173
  folder_name = os.path.basename(folder_path)
174
174
  logger.debug("Extracting metadata from folder: %s", folder_name)
@@ -210,12 +210,12 @@ def get_folder_name_from_metadata(folder_path: str, use_media_tags: bool = False
210
210
  and optionally audio file metadata.
211
211
 
212
212
  Args:
213
- folder_path: Path to folder
214
- use_media_tags: Whether to use media tags from audio files if available
215
- template: Optional template for formatting output name using media tags
213
+ folder_path (str): Path to folder
214
+ use_media_tags (bool): Whether to use media tags from audio files if available
215
+ template (str | None): Optional template for formatting output name using media tags
216
216
 
217
217
  Returns:
218
- String with cleaned output name
218
+ str: String with cleaned output name
219
219
  """
220
220
  folder_meta = extract_folder_meta(folder_path)
221
221
  output_name = None
@@ -289,17 +289,17 @@ def get_folder_name_from_metadata(folder_path: str, use_media_tags: bool = False
289
289
  return output_name
290
290
 
291
291
 
292
- def process_recursive_folders(root_path, use_media_tags=False, name_template=None):
292
+ def process_recursive_folders(root_path: str, use_media_tags: bool = False, name_template: str = None) -> list[tuple[str, str, list[str]]]:
293
293
  """
294
294
  Process folders recursively for audio files to create Tonie files.
295
295
 
296
296
  Args:
297
297
  root_path (str): The root path to start processing from
298
298
  use_media_tags (bool): Whether to use media tags for naming
299
- name_template (str): Template for naming files using media tags
299
+ name_template (str | None): Template for naming files using media tags
300
300
 
301
301
  Returns:
302
- list: A list of tuples (output_name, folder_path, audio_files)
302
+ list[tuple[str, str, list[str]]]: A list of tuples (output_name, folder_path, audio_files)
303
303
  """
304
304
  logger = get_logger("recursive_processor")
305
305
  logger.info("Processing folders recursively: %s", root_path)
TonieToolbox/tags.py CHANGED
@@ -10,15 +10,14 @@ from typing import Optional, Union
10
10
 
11
11
  logger = get_logger('tags')
12
12
 
13
- def get_tags(client: TeddyCloudClient) -> bool:
13
+ def get_tags(client: 'TeddyCloudClient') -> bool:
14
14
  """
15
15
  Get and display tags from a TeddyCloud instance.
16
16
 
17
17
  Args:
18
- client: TeddyCloudClient instance to use for API communication
19
-
18
+ client (TeddyCloudClient): TeddyCloudClient instance to use for API communication
20
19
  Returns:
21
- True if tags were retrieved successfully, False otherwise
20
+ bool: True if tags were retrieved successfully, False otherwise
22
21
  """
23
22
  logger.info("Getting tags from TeddyCloud using provided client")
24
23
 
@@ -19,27 +19,33 @@ DEFAULT_RETRY_DELAY = 5 # seconds
19
19
  class TeddyCloudClient:
20
20
  """Client for interacting with TeddyCloud API."""
21
21
 
22
- def __init__(self, base_url: str, ignore_ssl_verify: bool = False,
23
- connection_timeout: int = DEFAULT_CONNECTION_TIMEOUT,
24
- read_timeout: int = DEFAULT_READ_TIMEOUT,
25
- max_retries: int = DEFAULT_MAX_RETRIES,
26
- retry_delay: int = DEFAULT_RETRY_DELAY,
27
- username: str = None, password: str = None,
28
- cert_file: str = None, key_file: str = None):
22
+ def __init__(
23
+ self,
24
+ base_url: str,
25
+ ignore_ssl_verify: bool = False,
26
+ connection_timeout: int = DEFAULT_CONNECTION_TIMEOUT,
27
+ read_timeout: int = DEFAULT_READ_TIMEOUT,
28
+ max_retries: int = DEFAULT_MAX_RETRIES,
29
+ retry_delay: int = DEFAULT_RETRY_DELAY,
30
+ username: str = None,
31
+ password: str = None,
32
+ cert_file: str = None,
33
+ key_file: str = None
34
+ ) -> None:
29
35
  """
30
36
  Initialize the TeddyCloud client.
31
37
 
32
38
  Args:
33
- base_url: Base URL of the TeddyCloud instance (e.g., https://teddycloud.example.com)
34
- ignore_ssl_verify: If True, SSL certificate verification will be disabled (useful for self-signed certificates)
35
- connection_timeout: Timeout for establishing a connection
36
- read_timeout: Timeout for reading data from the server
37
- max_retries: Maximum number of retries for failed requests
38
- retry_delay: Delay between retries
39
- username: Username for basic authentication (optional)
40
- password: Password for basic authentication (optional)
41
- cert_file: Path to client certificate file for certificate-based authentication (optional)
42
- key_file: Path to client private key file for certificate-based authentication (optional)
39
+ base_url (str): Base URL of the TeddyCloud instance (e.g., https://teddycloud.example.com)
40
+ ignore_ssl_verify (bool): If True, SSL certificate verification will be disabled (useful for self-signed certificates)
41
+ connection_timeout (int): Timeout for establishing a connection
42
+ read_timeout (int): Timeout for reading data from the server
43
+ max_retries (int): Maximum number of retries for failed requests
44
+ retry_delay (int): Delay between retries
45
+ username (str | None): Username for basic authentication (optional)
46
+ password (str | None): Password for basic authentication (optional)
47
+ cert_file (str | None): Path to client certificate file for certificate-based authentication (optional)
48
+ key_file (str | None): Path to client private key file for certificate-based authentication (optional)
43
49
  """
44
50
  self.base_url = base_url.rstrip('/')
45
51
  self.ignore_ssl_verify = ignore_ssl_verify
@@ -81,7 +87,7 @@ class TeddyCloudClient:
81
87
  except ssl.SSLError as e:
82
88
  raise ValueError(f"Failed to load client certificate: {e}")
83
89
 
84
- def _create_request_kwargs(self):
90
+ def _create_request_kwargs(self) -> dict:
85
91
  """
86
92
  Create common request keyword arguments for all API calls.
87
93
 
@@ -98,18 +104,16 @@ class TeddyCloudClient:
98
104
  kwargs['cert'] = self.cert
99
105
  return kwargs
100
106
 
101
- def _make_request(self, method, endpoint, **kwargs):
107
+ def _make_request(self, method: str, endpoint: str, **kwargs) -> 'requests.Response':
102
108
  """
103
109
  Make an HTTP request to the TeddyCloud API with retry logic.
104
110
 
105
111
  Args:
106
- method: HTTP method (GET, POST, etc.)
107
- endpoint: API endpoint (without base URL)
112
+ method (str): HTTP method (GET, POST, etc.)
113
+ endpoint (str): API endpoint (without base URL)
108
114
  **kwargs: Additional arguments to pass to requests
109
-
110
115
  Returns:
111
116
  requests.Response: Response object
112
-
113
117
  Raises:
114
118
  requests.exceptions.RequestException: If request fails after all retries
115
119
  """
@@ -171,7 +175,7 @@ class TeddyCloudClient:
171
175
 
172
176
  # ------------- GET API Methods -------------
173
177
 
174
- def get_tonies_custom_json(self):
178
+ def get_tonies_custom_json(self) -> dict:
175
179
  """
176
180
  Get custom Tonies JSON data from the TeddyCloud server.
177
181
 
@@ -181,7 +185,7 @@ class TeddyCloudClient:
181
185
  response = self._make_request('GET', '/api/toniesCustomJson')
182
186
  return response.json()
183
187
 
184
- def get_tonies_json(self):
188
+ def get_tonies_json(self) -> dict:
185
189
  """
186
190
  Get Tonies JSON data from the TeddyCloud server.
187
191
 
@@ -191,7 +195,7 @@ class TeddyCloudClient:
191
195
  response = self._make_request('GET', '/api/toniesJson')
192
196
  return response.json()
193
197
 
194
- def get_tag_index(self):
198
+ def get_tag_index(self) -> dict:
195
199
  """
196
200
  Get tag index data from the TeddyCloud server.
197
201
 
@@ -201,7 +205,7 @@ class TeddyCloudClient:
201
205
  response = self._make_request('GET', '/api/getTagIndex')
202
206
  return response.json()
203
207
 
204
- def get_file_index(self):
208
+ def get_file_index(self) -> dict:
205
209
  """
206
210
  Get file index data from the TeddyCloud server.
207
211
 
@@ -211,7 +215,7 @@ class TeddyCloudClient:
211
215
  response = self._make_request('GET', '/api/fileIndex')
212
216
  return response.json()
213
217
 
214
- def get_file_index_v2(self):
218
+ def get_file_index_v2(self) -> dict:
215
219
  """
216
220
  Get version 2 file index data from the TeddyCloud server.
217
221
 
@@ -221,7 +225,7 @@ class TeddyCloudClient:
221
225
  response = self._make_request('GET', '/api/fileIndexV2')
222
226
  return response.json()
223
227
 
224
- def get_tonieboxes_json(self):
228
+ def get_tonieboxes_json(self) -> dict:
225
229
  """
226
230
  Get Tonieboxes JSON data from the TeddyCloud server.
227
231
 
@@ -233,15 +237,14 @@ class TeddyCloudClient:
233
237
 
234
238
  # ------------- POST API Methods -------------
235
239
 
236
- def create_directory(self, path, overlay=None, special=None):
240
+ def create_directory(self, path: str, overlay: str = None, special: str = None) -> str:
237
241
  """
238
242
  Create a directory on the TeddyCloud server.
239
243
 
240
244
  Args:
241
- path: Directory path to create
242
- overlay: Settings overlay ID (optional)
243
- special: Special folder source, only 'library' supported yet (optional)
244
-
245
+ path (str): Directory path to create
246
+ overlay (str | None): Settings overlay ID (optional)
247
+ special (str | None): Special folder source, only 'library' supported yet (optional)
245
248
  Returns:
246
249
  str: Response message from server (usually "OK")
247
250
  """
@@ -254,15 +257,14 @@ class TeddyCloudClient:
254
257
  response = self._make_request('POST', '/api/dirCreate', params=params, data=path)
255
258
  return response.text
256
259
 
257
- def delete_directory(self, path, overlay=None, special=None):
260
+ def delete_directory(self, path: str, overlay: str = None, special: str = None) -> str:
258
261
  """
259
262
  Delete a directory from the TeddyCloud server.
260
263
 
261
264
  Args:
262
- path: Directory path to delete
263
- overlay: Settings overlay ID (optional)
264
- special: Special folder source, only 'library' supported yet (optional)
265
-
265
+ path (str): Directory path to delete
266
+ overlay (str | None): Settings overlay ID (optional)
267
+ special (str | None): Special folder source, only 'library' supported yet (optional)
266
268
  Returns:
267
269
  str: Response message from server (usually "OK")
268
270
  """
@@ -275,15 +277,14 @@ class TeddyCloudClient:
275
277
  response = self._make_request('POST', '/api/dirDelete', params=params, data=path)
276
278
  return response.text
277
279
 
278
- def delete_file(self, path, overlay=None, special=None):
280
+ def delete_file(self, path: str, overlay: str = None, special: str = None) -> str:
279
281
  """
280
282
  Delete a file from the TeddyCloud server.
281
283
 
282
284
  Args:
283
- path: File path to delete
284
- overlay: Settings overlay ID (optional)
285
- special: Special folder source, only 'library' supported yet (optional)
286
-
285
+ path (str): File path to delete
286
+ overlay (str | None): Settings overlay ID (optional)
287
+ special (str | None): Special folder source, only 'library' supported yet (optional)
287
288
  Returns:
288
289
  str: Response message from server (usually "OK")
289
290
  """
@@ -296,16 +297,15 @@ class TeddyCloudClient:
296
297
  response = self._make_request('POST', '/api/fileDelete', params=params, data=path)
297
298
  return response.text
298
299
 
299
- def upload_file(self, file_path, destination_path=None, overlay=None, special=None):
300
+ def upload_file(self, file_path: str, destination_path: str = None, overlay: str = None, special: str = None) -> dict:
300
301
  """
301
302
  Upload a file to the TeddyCloud server.
302
303
 
303
304
  Args:
304
- file_path: Local path to the file to upload
305
- destination_path: Server path where to write the file to (optional)
306
- overlay: Settings overlay ID (optional)
307
- special: Special folder source, only 'library' supported yet (optional)
308
-
305
+ file_path (str): Local path to the file to upload
306
+ destination_path (str | None): Server path where to write the file to (optional)
307
+ overlay (str | None): Settings overlay ID (optional)
308
+ special (str | None): Special folder source, only 'library' supported yet (optional)
309
309
  Returns:
310
310
  dict: JSON response from server
311
311
  """
@@ -11,12 +11,13 @@ from .ogg_page import OggPage
11
11
  from .logger import get_logger
12
12
 
13
13
  logger = get_logger('tonie_analysis')
14
- def format_time(ts):
14
+
15
+ def format_time(ts: float) -> str:
15
16
  """
16
17
  Format a timestamp as a human-readable date and time string.
17
18
 
18
19
  Args:
19
- ts: Timestamp to format
20
+ ts (float): Timestamp to format
20
21
 
21
22
  Returns:
22
23
  str: Formatted date and time string
@@ -24,12 +25,12 @@ def format_time(ts):
24
25
  return datetime.datetime.fromtimestamp(ts, datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
25
26
 
26
27
 
27
- def format_hex(data):
28
+ def format_hex(data: bytes) -> str:
28
29
  """
29
30
  Format binary data as a hex string.
30
31
 
31
32
  Args:
32
- data: Binary data to format
33
+ data (bytes): Binary data to format
33
34
 
34
35
  Returns:
35
36
  str: Formatted hex string
@@ -37,13 +38,13 @@ def format_hex(data):
37
38
  return "".join(format(x, "02X") for x in data)
38
39
 
39
40
 
40
- def granule_to_time_string(granule, sample_rate=1):
41
+ def granule_to_time_string(granule: int, sample_rate: int = 1) -> str:
41
42
  """
42
43
  Convert a granule position to a time string.
43
44
 
44
45
  Args:
45
- granule: Granule position
46
- sample_rate: Sample rate in Hz
46
+ granule (int): Granule position
47
+ sample_rate (int): Sample rate in Hz
47
48
 
48
49
  Returns:
49
50
  str: Formatted time string (HH:MM:SS.FF)
@@ -56,7 +57,7 @@ def granule_to_time_string(granule, sample_rate=1):
56
57
  return "{:02d}:{:02d}:{:02d}.{:02d}".format(hours, minutes, seconds, fraction)
57
58
 
58
59
 
59
- def get_header_info(in_file):
60
+ def get_header_info(in_file) -> tuple:
60
61
  """
61
62
  Get header information from a Tonie file.
62
63
 
@@ -164,15 +165,15 @@ def get_header_info(in_file):
164
165
  )
165
166
 
166
167
 
167
- def get_audio_info(in_file, sample_rate, tonie_header, header_size):
168
+ def get_audio_info(in_file, sample_rate: int, tonie_header, header_size: int) -> tuple:
168
169
  """
169
170
  Get audio information from a Tonie file.
170
171
 
171
172
  Args:
172
173
  in_file: Input file handle
173
- sample_rate: Sample rate in Hz
174
+ sample_rate (int): Sample rate in Hz
174
175
  tonie_header: Tonie header object
175
- header_size: Header size in bytes
176
+ header_size (int): Header size in bytes
176
177
 
177
178
  Returns:
178
179
  tuple: Page count, alignment OK flag, page size OK flag, total time, chapter times
@@ -228,12 +229,12 @@ def get_audio_info(in_file, sample_rate, tonie_header, header_size):
228
229
  return page_count, alignment_okay, page_size_okay, total_time, chapter_times
229
230
 
230
231
 
231
- def check_tonie_file(filename):
232
+ def check_tonie_file(filename: str) -> bool:
232
233
  """
233
234
  Check if a file is a valid Tonie file and display information about it.
234
235
 
235
236
  Args:
236
- filename: Path to the file to check
237
+ filename (str): Path to the file to check
237
238
 
238
239
  Returns:
239
240
  bool: True if the file is valid, False otherwise
@@ -315,13 +316,13 @@ def check_tonie_file(filename):
315
316
  return all_ok
316
317
 
317
318
 
318
- def split_to_opus_files(filename, output=None):
319
+ def split_to_opus_files(filename: str, output: str = None) -> None:
319
320
  """
320
321
  Split a Tonie file into individual Opus files.
321
322
 
322
323
  Args:
323
- filename: Path to the Tonie file
324
- output: Output directory path (optional)
324
+ filename (str): Path to the Tonie file
325
+ output (str | None): Output directory path (optional)
325
326
  """
326
327
  logger.info("Splitting Tonie file into individual Opus tracks: %s", filename)
327
328
 
@@ -412,14 +413,14 @@ def split_to_opus_files(filename, output=None):
412
413
  logger.info("Successfully split Tonie file into %d individual tracks", len(tonie_header.chapterPages))
413
414
 
414
415
 
415
- def compare_taf_files(file1, file2, detailed=False):
416
+ def compare_taf_files(file1: str, file2: str, detailed: bool = False) -> bool:
416
417
  """
417
418
  Compare two .taf files for debugging purposes.
418
419
 
419
420
  Args:
420
- file1: Path to the first .taf file
421
- file2: Path to the second .taf file
422
- detailed: Whether to show detailed comparison results
421
+ file1 (str): Path to the first .taf file
422
+ file2 (str): Path to the second .taf file
423
+ detailed (bool): Whether to show detailed comparison results
423
424
 
424
425
  Returns:
425
426
  bool: True if files are equivalent, False otherwise
@@ -572,7 +573,7 @@ def compare_taf_files(file1, file2, detailed=False):
572
573
  logger.info("Files comparison result: Equivalent")
573
574
  return True
574
575
 
575
- def get_header_info_cli(in_file):
576
+ def get_header_info_cli(in_file) -> tuple:
576
577
  """
577
578
  Get header information from a Tonie file.
578
579
 
@@ -687,12 +688,12 @@ def get_header_info_cli(in_file):
687
688
  return (0, tonie_header_pb2.TonieHeader(), 0, 0, None, False, 0, 0, 0, 0, {}, False)
688
689
 
689
690
 
690
- def check_tonie_file_cli(filename):
691
+ def check_tonie_file_cli(filename: str) -> bool:
691
692
  """
692
693
  Check if a file is a valid Tonie file
693
694
 
694
695
  Args:
695
- filename: Path to the file to check
696
+ filename (str): Path to the file to check
696
697
 
697
698
  Returns:
698
699
  bool: True if the file is valid, False otherwise