TonieToolbox 0.4.1__py3-none-any.whl → 0.5.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.
@@ -8,50 +8,46 @@ which can be used to manage custom Tonies on TeddyCloud servers.
8
8
  import os
9
9
  import json
10
10
  import time
11
- import urllib.error
12
- import ssl
13
- import uuid
14
11
  import locale
15
12
  import re
16
- from typing import Dict, Any, List, Optional, Tuple
13
+ import hashlib
14
+ import mutagen
15
+ from typing import Dict, Any, List, Optional
17
16
 
18
17
  from .logger import get_logger
19
18
  from .media_tags import get_file_tags, extract_album_info
20
19
  from .constants import LANGUAGE_MAPPING, GENRE_MAPPING
21
- from .teddycloud import get_tonies_custom_json_from_server, put_tonies_custom_json_to_server
20
+ from .teddycloud import TeddyCloudClient
22
21
 
23
22
  logger = get_logger('tonies_json')
24
23
 
25
24
  class ToniesJsonHandler:
26
25
  """Handler for tonies.custom.json operations."""
27
26
 
28
- def __init__(self, teddycloud_url: Optional[str] = None, ignore_ssl_verify: bool = False):
27
+ def __init__(self, client: TeddyCloudClient = None):
29
28
  """
30
29
  Initialize the handler.
31
30
 
32
31
  Args:
33
- teddycloud_url: URL of the TeddyCloud instance (optional)
34
- ignore_ssl_verify: If True, SSL certificate verification will be disabled
35
- """
36
- self.teddycloud_url = teddycloud_url.rstrip('/') if teddycloud_url else None
37
- self.ignore_ssl_verify = ignore_ssl_verify
32
+ client: TeddyCloudClient instance to use for API communication
33
+ """
34
+ self.client = client
38
35
  self.custom_json = []
39
36
  self.is_loaded = False
40
-
37
+
41
38
  def load_from_server(self) -> bool:
42
39
  """
43
40
  Load tonies.custom.json from the TeddyCloud server.
44
41
 
45
42
  Returns:
46
43
  True if successful, False otherwise
47
- """
48
- if not self.teddycloud_url:
49
- logger.error("Cannot load from server: No TeddyCloud URL provided")
44
+ """
45
+ if self.client is None:
46
+ logger.error("Cannot load from server: no client provided")
50
47
  return False
51
48
 
52
49
  try:
53
- result = get_tonies_custom_json_from_server(self.teddycloud_url, self.ignore_ssl_verify)
54
-
50
+ result = self.client.get_tonies_custom_json()
55
51
  if result is not None:
56
52
  self.custom_json = result
57
53
  self.is_loaded = True
@@ -98,39 +94,6 @@ class ToniesJsonHandler:
98
94
  logger.error("Error loading tonies.custom.json from file: %s", e)
99
95
  return False
100
96
 
101
- def save_to_server(self) -> bool:
102
- """
103
- Save tonies.custom.json to the TeddyCloud server.
104
-
105
- Returns:
106
- True if successful, False otherwise
107
- """
108
- if not self.teddycloud_url:
109
- logger.error("Cannot save to server: No TeddyCloud URL provided")
110
- return False
111
-
112
- if not self.is_loaded:
113
- logger.error("Cannot save tonies.custom.json: data not loaded")
114
- return False
115
-
116
- try:
117
- result = put_tonies_custom_json_to_server(
118
- self.teddycloud_url,
119
- self.custom_json,
120
- self.ignore_ssl_verify
121
- )
122
-
123
- if result:
124
- logger.info("Successfully saved tonies.custom.json to server")
125
- return True
126
- else:
127
- logger.error("Failed to save tonies.custom.json to server")
128
- return False
129
-
130
- except Exception as e:
131
- logger.error("Error saving tonies.custom.json to server: %s", e)
132
- return False
133
-
134
97
  def save_to_file(self, file_path: str) -> bool:
135
98
  """
136
99
  Save tonies.custom.json to a local file.
@@ -146,9 +109,7 @@ class ToniesJsonHandler:
146
109
  return False
147
110
 
148
111
  try:
149
- # Ensure the directory exists
150
112
  os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
151
-
152
113
  logger.info("Saving tonies.custom.json to file: %s", file_path)
153
114
  with open(file_path, 'w', encoding='utf-8') as f:
154
115
  json.dump(self.custom_json, f, indent=2, ensure_ascii=False)
@@ -163,6 +124,8 @@ class ToniesJsonHandler:
163
124
  def add_entry_from_taf(self, taf_file: str, input_files: List[str], artwork_url: Optional[str] = None) -> bool:
164
125
  """
165
126
  Add an entry to the custom JSON from a TAF file.
127
+ If an entry with the same hash exists, it will be updated.
128
+ If an entry with the same series+episode exists, the new hash will be added to it.
166
129
 
167
130
  Args:
168
131
  taf_file: Path to the TAF file
@@ -182,17 +145,77 @@ class ToniesJsonHandler:
182
145
  try:
183
146
  logger.info("Adding entry for %s to tonies.custom.json", taf_file)
184
147
 
185
- logger.debug("Generating article ID")
186
- article_id = self._generate_article_id()
187
- logger.debug("Generated article ID: %s", article_id)
188
-
189
148
  logger.debug("Extracting metadata from input files")
190
149
  metadata = self._extract_metadata_from_files(input_files)
191
150
  logger.debug("Extracted metadata: %s", metadata)
151
+ with open(taf_file, 'rb') as f:
152
+ taf_hash = hashlib.sha1(f.read()).hexdigest()
153
+
154
+ taf_size = os.path.getsize(taf_file)
155
+ timestamp = int(time.time())
156
+ series = metadata.get('albumartist', metadata.get('artist', 'Unknown Artist'))
157
+ episode = metadata.get('album', os.path.splitext(os.path.basename(taf_file))[0])
158
+ track_desc = metadata.get('track_descriptions', [])
159
+ language = self._determine_language(metadata)
160
+ category = self._determine_category(metadata)
161
+ age = self._estimate_age(metadata)
162
+ new_id_entry = {
163
+ "audio-id": timestamp,
164
+ "hash": taf_hash,
165
+ "size": taf_size,
166
+ "tracks": len(track_desc),
167
+ "confidence": 1
168
+ }
169
+ existing_entry, entry_idx, data_idx = self.find_entry_by_hash(taf_hash)
170
+ if existing_entry:
171
+ logger.info("Found existing entry with the same hash, updating it")
172
+ data = existing_entry['data'][data_idx]
173
+ if artwork_url and artwork_url != data.get('image', ''):
174
+ logger.debug("Updating artwork URL")
175
+ data['image'] = artwork_url
176
+ if track_desc and track_desc != data.get('track-desc', []):
177
+ logger.debug("Updating track descriptions")
178
+ data['track-desc'] = track_desc
179
+
180
+ logger.info("Successfully updated existing entry for %s", taf_file)
181
+ return True
182
+ existing_entry, entry_idx, data_idx = self.find_entry_by_series_episode(series, episode)
183
+ if existing_entry:
184
+ logger.info("Found existing entry with the same series/episode, adding hash to it")
185
+ existing_data = existing_entry['data'][data_idx]
186
+ if 'ids' not in existing_data:
187
+ existing_data['ids'] = []
188
+
189
+ existing_data['ids'].append(new_id_entry)
190
+ if artwork_url and artwork_url != existing_data.get('image', ''):
191
+ logger.debug("Updating artwork URL")
192
+ existing_data['image'] = artwork_url
193
+
194
+ logger.info("Successfully added new hash to existing entry for %s", taf_file)
195
+ return True
196
+ logger.debug("No existing entry found, creating new entry")
197
+ logger.debug("Generating article ID")
198
+ article_id = self._generate_article_id()
199
+ logger.debug("Generated article ID: %s", article_id)
192
200
 
193
- logger.debug("Creating JSON entry")
194
- entry = self._create_json_entry(article_id, taf_file, metadata, input_files, artwork_url)
195
- logger.debug("Created entry: %s", entry)
201
+ entry = {
202
+ "article": article_id,
203
+ "data": [
204
+ {
205
+ "series": series,
206
+ "episode": episode,
207
+ "release": timestamp,
208
+ "language": language,
209
+ "category": category,
210
+ "runtime": self._calculate_runtime(input_files),
211
+ "age": age,
212
+ "origin": "custom",
213
+ "image": artwork_url if artwork_url else "",
214
+ "track-desc": track_desc,
215
+ "ids": [new_id_entry]
216
+ }
217
+ ]
218
+ }
196
219
 
197
220
  self.custom_json.append(entry)
198
221
  logger.debug("Added entry to custom_json (new length: %d)", len(self.custom_json))
@@ -214,8 +237,6 @@ class ToniesJsonHandler:
214
237
  Unique article ID in the format "tt-42" followed by sequential number starting from 0
215
238
  """
216
239
  logger.trace("Entering _generate_article_id()")
217
-
218
- # Find the highest sequential number for tt-42 IDs
219
240
  highest_num = -1
220
241
  pattern = re.compile(r'tt-42(\d+)')
221
242
 
@@ -234,11 +255,7 @@ class ToniesJsonHandler:
234
255
  pass
235
256
 
236
257
  logger.debug("Highest tt-42 ID number found: %d", highest_num)
237
-
238
- # Generate the next sequential number
239
258
  next_num = highest_num + 1
240
-
241
- # Format the ID with leading zeros to make it 10 digits
242
259
  result = f"tt-42{next_num:010d}"
243
260
  logger.debug("Generated new article ID: %s", result)
244
261
 
@@ -246,26 +263,25 @@ class ToniesJsonHandler:
246
263
  return result
247
264
 
248
265
  def _extract_metadata_from_files(self, input_files: List[str]) -> Dict[str, Any]:
249
- metadata = {}
250
-
251
- # If there are multiple files in the same folder, use album info
252
- if len(input_files) > 1 and os.path.dirname(input_files[0]) == os.path.dirname(input_files[-1]):
253
- folder_path = os.path.dirname(input_files[0])
254
- album_info = extract_album_info(folder_path)
255
- metadata.update(album_info)
266
+ """
267
+ Extract metadata from audio files to use in the custom JSON entry.
256
268
 
257
- # For all files, collect tags to use for track descriptions
269
+ Args:
270
+ input_files: List of paths to audio files
271
+
272
+ Returns:
273
+ Dictionary containing metadata extracted from files
274
+ """
275
+ metadata = {}
258
276
  track_descriptions = []
259
277
  for file_path in input_files:
260
278
  tags = get_file_tags(file_path)
261
279
  if 'title' in tags:
262
280
  track_descriptions.append(tags['title'])
263
281
  else:
264
- # Use filename as fallback
265
282
  filename = os.path.splitext(os.path.basename(file_path))[0]
266
283
  track_descriptions.append(filename)
267
284
 
268
- # Extract language and genre from the first file if not already present
269
285
  if 'language' not in metadata and 'language' in tags:
270
286
  metadata['language'] = tags['language']
271
287
 
@@ -277,51 +293,31 @@ class ToniesJsonHandler:
277
293
  return metadata
278
294
 
279
295
  def _determine_language(self, metadata: Dict[str, Any]) -> str:
280
- # Check for language tag in metadata
281
296
  if 'language' in metadata:
282
297
  lang_value = metadata['language'].lower().strip()
283
298
  if lang_value in LANGUAGE_MAPPING:
284
299
  return LANGUAGE_MAPPING[lang_value]
285
-
286
- # If not found, try to use system locale
287
300
  try:
288
301
  system_lang, _ = locale.getdefaultlocale()
289
302
  if system_lang:
290
303
  lang_code = system_lang.split('_')[0].lower()
291
304
  if lang_code in LANGUAGE_MAPPING:
292
305
  return LANGUAGE_MAPPING[lang_code]
293
- # Try to map system language code to tonie format
294
- if lang_code == 'de':
295
- return 'de-de'
296
- elif lang_code == 'en':
297
- return 'en-us'
298
- elif lang_code == 'fr':
299
- return 'fr-fr'
300
- elif lang_code == 'it':
301
- return 'it-it'
302
- elif lang_code == 'es':
303
- return 'es-es'
304
306
  except Exception:
305
307
  pass
306
-
307
- # Default to German as it's most common for Tonies
308
308
  return 'de-de'
309
309
 
310
310
  def _determine_category(self, metadata: Dict[str, Any]) -> str:
311
- # Check for genre tag in metadata
312
311
  if 'genre' in metadata:
313
312
  genre_value = metadata['genre'].lower().strip()
314
313
 
315
- # Check for direct mapping
316
314
  if genre_value in GENRE_MAPPING:
317
315
  return GENRE_MAPPING[genre_value]
318
316
 
319
- # Check for partial matching
320
317
  for genre_key, category in GENRE_MAPPING.items():
321
318
  if genre_key in genre_value:
322
319
  return category
323
-
324
- # Check for common keywords in the genre
320
+
325
321
  if any(keyword in genre_value for keyword in ['musik', 'song', 'music', 'lied']):
326
322
  return 'music'
327
323
  elif any(keyword in genre_value for keyword in ['hörspiel', 'hörspiele', 'audio play']):
@@ -334,8 +330,6 @@ class ToniesJsonHandler:
334
330
  return 'Wissen & Hörmagazine'
335
331
  elif any(keyword in genre_value for keyword in ['schlaf', 'sleep', 'meditation']):
336
332
  return 'Schlaflieder & Entspannung'
337
-
338
- # Default to standard category for most custom content
339
333
  return 'Hörspiele & Hörbücher'
340
334
 
341
335
  def _estimate_age(self, metadata: Dict[str, Any]) -> int:
@@ -363,67 +357,136 @@ class ToniesJsonHandler:
363
357
 
364
358
  return default_age
365
359
 
366
- def _create_json_entry(self, article_id: str, taf_file: str, metadata: Dict[str, Any],
367
- input_files: List[str], artwork_url: Optional[str] = None) -> Dict[str, Any]:
368
- # Calculate the size in bytes
369
- taf_size = os.path.getsize(taf_file)
360
+ def find_entry_by_hash(self, taf_hash: str) -> tuple[Optional[Dict[str, Any]], Optional[int], Optional[int]]:
361
+ """
362
+ Find an entry in the custom JSON by TAF hash.
363
+
364
+ Args:
365
+ taf_hash: SHA1 hash of the TAF file to find
366
+
367
+ Returns:
368
+ Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
369
+ """
370
+ logger.trace("Searching for entry with hash %s", taf_hash)
371
+
372
+ for entry_idx, entry in enumerate(self.custom_json):
373
+ if 'data' not in entry:
374
+ continue
375
+
376
+ for data_idx, data in enumerate(entry['data']):
377
+ if 'ids' not in data:
378
+ continue
379
+
380
+ for id_entry in data['ids']:
381
+ if id_entry.get('hash') == taf_hash:
382
+ logger.debug("Found existing entry with matching hash %s", taf_hash)
383
+ return entry, entry_idx, data_idx
370
384
 
371
- # Get current timestamp
372
- timestamp = int(time.time())
385
+ logger.debug("No entry found with hash %s", taf_hash)
386
+ return None, None, None
387
+
388
+ def find_entry_by_series_episode(self, series: str, episode: str) -> tuple[Optional[Dict[str, Any]], Optional[int], Optional[int]]:
389
+ """
390
+ Find an entry in the custom JSON by series and episode.
373
391
 
374
- # Create entry from metadata
375
- series = metadata.get('albumartist', metadata.get('artist', 'Unknown Artist'))
376
- episode = metadata.get('album', os.path.splitext(os.path.basename(taf_file))[0])
377
- track_desc = metadata.get('track_descriptions', [])
378
- language = self._determine_language(metadata)
379
- category = self._determine_category(metadata)
380
- age = self._estimate_age(metadata)
392
+ Args:
393
+ series: Series name to find
394
+ episode: Episode name to find
395
+
396
+ Returns:
397
+ Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
398
+ """
399
+ logger.trace("Searching for entry with series='%s', episode='%s'", series, episode)
381
400
 
382
- # Create a unique hash for the file
383
- import hashlib
384
- with open(taf_file, 'rb') as f:
385
- taf_hash = hashlib.sha1(f.read()).hexdigest()
401
+ for entry_idx, entry in enumerate(self.custom_json):
402
+ if 'data' not in entry:
403
+ continue
404
+
405
+ for data_idx, data in enumerate(entry['data']):
406
+ if data.get('series') == series and data.get('episode') == episode:
407
+ logger.debug("Found existing entry with matching series/episode: %s / %s", series, episode)
408
+ return entry, entry_idx, data_idx
386
409
 
387
- # Build the entry
388
- entry = {
389
- "article": article_id,
390
- "data": [
391
- {
392
- "series": series,
393
- "episode": episode,
394
- "release": timestamp,
395
- "language": language,
396
- "category": category,
397
- "runtime": 0, # Could calculate this with proper audio analysis
398
- "age": age,
399
- "origin": "custom",
400
- "image": artwork_url if artwork_url else "",
401
- "track-desc": track_desc,
402
- "ids": [
403
- {
404
- "audio-id": timestamp,
405
- "hash": taf_hash,
406
- "size": taf_size,
407
- "tracks": len(track_desc),
408
- "confidence": 1
409
- }
410
- ]
411
- }
412
- ]
413
- }
410
+ logger.debug("No entry found with series/episode: %s / %s", series, episode)
411
+ return None, None, None
412
+
413
+ def _calculate_runtime(self, input_files: List[str]) -> int:
414
+ """
415
+ Calculate the total runtime in minutes from a list of audio files.
416
+
417
+ Args:
418
+ input_files: List of paths to audio files
419
+
420
+ Returns:
421
+ Total runtime in minutes (rounded to the nearest minute)
422
+ """
423
+ logger.trace("Entering _calculate_runtime() with %d input files", len(input_files))
424
+ total_runtime_seconds = 0
425
+ processed_files = 0
414
426
 
415
- return entry
427
+ try:
428
+ logger.debug("Starting runtime calculation for %d audio files", len(input_files))
429
+
430
+ for i, file_path in enumerate(input_files):
431
+ logger.trace("Processing file %d/%d: %s", i+1, len(input_files), file_path)
432
+
433
+ if not os.path.exists(file_path):
434
+ logger.warning("File does not exist: %s", file_path)
435
+ continue
436
+
437
+ try:
438
+ logger.trace("Loading audio file with mutagen: %s", file_path)
439
+ audio = mutagen.File(file_path)
440
+
441
+ if audio is None:
442
+ logger.warning("Mutagen could not identify file format: %s", file_path)
443
+ continue
444
+
445
+ if not hasattr(audio, 'info'):
446
+ logger.warning("Audio file has no info attribute: %s", file_path)
447
+ continue
448
+
449
+ if not hasattr(audio.info, 'length'):
450
+ logger.warning("Audio info has no length attribute: %s", file_path)
451
+ continue
452
+
453
+ file_runtime_seconds = int(audio.info.length)
454
+ total_runtime_seconds += file_runtime_seconds
455
+ processed_files += 1
456
+
457
+ logger.debug("File %s: runtime=%d seconds, format=%s",
458
+ file_path, file_runtime_seconds, audio.__class__.__name__)
459
+ logger.trace("Current total runtime: %d seconds after %d/%d files",
460
+ total_runtime_seconds, i+1, len(input_files))
461
+
462
+ except Exception as e:
463
+ logger.warning("Error processing file %s: %s", file_path, e)
464
+ logger.trace("Exception details for %s: %s", file_path, str(e), exc_info=True)
416
465
 
466
+ # Convert seconds to minutes, rounding to nearest minute
467
+ total_runtime_minutes = round(total_runtime_seconds / 60)
468
+
469
+ logger.info("Calculated total runtime: %d seconds (%d minutes) from %d/%d files",
470
+ total_runtime_seconds, total_runtime_minutes, processed_files, len(input_files))
471
+
472
+ except ImportError as e:
473
+ logger.warning("Mutagen library not available, cannot calculate runtime: %s", str(e))
474
+ return 0
475
+ except Exception as e:
476
+ logger.error("Unexpected error during runtime calculation: %s", str(e))
477
+ logger.trace("Exception details: %s", str(e), exc_info=True)
478
+ return 0
417
479
 
418
- def fetch_and_update_tonies_json(teddycloud_url: Optional[str] = None, ignore_ssl_verify: bool = False,
419
- taf_file: Optional[str] = None, input_files: Optional[List[str]] = None,
480
+ logger.trace("Exiting _calculate_runtime() with total runtime=%d minutes", total_runtime_minutes)
481
+ return total_runtime_minutes
482
+
483
+ def fetch_and_update_tonies_json(client: TeddyCloudClient, taf_file: Optional[str] = None, input_files: Optional[List[str]] = None,
420
484
  artwork_url: Optional[str] = None, output_dir: Optional[str] = None) -> bool:
421
485
  """
422
486
  Fetch tonies.custom.json from server and merge with local file if it exists, then update with new entry.
423
487
 
424
488
  Args:
425
- teddycloud_url: URL of the TeddyCloud instance (optional)
426
- ignore_ssl_verify: If True, SSL certificate verification will be disabled
489
+ client: TeddyCloudClient instance to use for API communication
427
490
  taf_file: Path to the TAF file to add
428
491
  input_files: List of input audio files used to create the TAF
429
492
  artwork_url: URL of the uploaded artwork (if any)
@@ -432,71 +495,85 @@ def fetch_and_update_tonies_json(teddycloud_url: Optional[str] = None, ignore_ss
432
495
  Returns:
433
496
  True if successful, False otherwise
434
497
  """
435
- handler = ToniesJsonHandler(teddycloud_url, ignore_ssl_verify)
498
+ logger.trace("Entering fetch_and_update_tonies_json with client=%s, taf_file=%s, input_files=%s, artwork_url=%s, output_dir=%s",
499
+ client, taf_file, input_files, artwork_url, output_dir)
436
500
 
437
- # Determine where to load from and save to
501
+ handler = ToniesJsonHandler(client)
438
502
  if not output_dir:
439
503
  output_dir = './output'
440
-
441
- # Ensure output directory exists
504
+ logger.debug("No output directory specified, using default: %s", output_dir)
505
+
442
506
  os.makedirs(output_dir, exist_ok=True)
507
+ logger.debug("Ensuring output directory exists: %s", output_dir)
443
508
 
444
- # Create the full path for the JSON file
445
509
  json_file_path = os.path.join(output_dir, 'tonies.custom.json')
510
+ logger.debug("JSON file path: %s", json_file_path)
446
511
 
447
512
  loaded_from_server = False
448
-
449
- # Step 1: Try to get live version from the server first
450
- if teddycloud_url:
513
+ if client:
451
514
  logger.info("Attempting to load tonies.custom.json from server")
452
515
  loaded_from_server = handler.load_from_server()
516
+ logger.debug("Load from server result: %s", "success" if loaded_from_server else "failed")
517
+ else:
518
+ logger.debug("No client provided, skipping server load")
453
519
 
454
- # Step 2: If we have a local file, merge with the server content
455
520
  if os.path.exists(json_file_path):
456
521
  logger.info("Local tonies.custom.json file found, merging with server content")
522
+ logger.debug("Local file exists at %s, size: %d bytes", json_file_path, os.path.getsize(json_file_path))
457
523
 
458
- # Create a temporary handler to load local content
459
524
  local_handler = ToniesJsonHandler()
460
525
  if local_handler.load_from_file(json_file_path):
526
+ logger.debug("Successfully loaded local file with %d entries", len(local_handler.custom_json))
527
+
461
528
  if loaded_from_server:
462
- # Merge local content with server content
463
- # Use server-loaded content as base, then add any local entries not in server version
529
+ logger.debug("Merging local entries with server entries")
464
530
  server_article_ids = {entry.get('article') for entry in handler.custom_json}
531
+ logger.debug("Found %d unique article IDs from server", len(server_article_ids))
532
+
533
+ added_count = 0
465
534
  for local_entry in local_handler.custom_json:
466
535
  local_article_id = local_entry.get('article')
467
536
  if local_article_id not in server_article_ids:
468
- logger.info(f"Adding local-only entry {local_article_id} to merged content")
537
+ logger.trace("Adding local-only entry %s to merged content", local_article_id)
469
538
  handler.custom_json.append(local_entry)
539
+ added_count += 1
540
+
541
+ logger.debug("Added %d local-only entries to merged content", added_count)
470
542
  else:
471
- # Use local content as we couldn't load from server
543
+ logger.debug("Using only local entries (server load failed or no client)")
472
544
  handler.custom_json = local_handler.custom_json
473
545
  handler.is_loaded = True
474
546
  logger.info("Using local tonies.custom.json content")
475
547
  elif not loaded_from_server:
476
- # No server content and no local file, start with empty list
548
+ logger.debug("No local file found and server load failed, starting with empty list")
477
549
  handler.custom_json = []
478
550
  handler.is_loaded = True
479
551
  logger.info("No tonies.custom.json found, starting with empty list")
480
552
 
481
- # Add entry if needed
482
553
  if taf_file and input_files and handler.is_loaded:
554
+ logger.debug("Adding new entry for TAF file: %s", taf_file)
555
+ logger.debug("Using %d input files for metadata extraction", len(input_files))
556
+
483
557
  if not handler.add_entry_from_taf(taf_file, input_files, artwork_url):
484
558
  logger.error("Failed to add entry to tonies.custom.json")
559
+ logger.trace("Exiting fetch_and_update_tonies_json with success=False (failed to add entry)")
485
560
  return False
561
+
562
+ logger.debug("Successfully added new entry for %s", taf_file)
563
+ else:
564
+ if not taf_file:
565
+ logger.debug("No TAF file provided, skipping add entry step")
566
+ elif not input_files:
567
+ logger.debug("No input files provided, skipping add entry step")
568
+ elif not handler.is_loaded:
569
+ logger.debug("Handler not properly loaded, skipping add entry step")
486
570
 
487
- # Save to file
571
+ logger.debug("Saving updated tonies.custom.json to %s", json_file_path)
488
572
  if not handler.save_to_file(json_file_path):
489
573
  logger.error("Failed to save tonies.custom.json to file")
574
+ logger.trace("Exiting fetch_and_update_tonies_json with success=False (failed to save file)")
490
575
  return False
491
576
 
492
- # Try to save to server if URL is provided
493
- # For future use if the API enpoints are available
494
- #if teddycloud_url and handler.is_loaded:
495
- try:
496
- if not handler.save_to_server():
497
- logger.warning("Could not save tonies.custom.json to server")
498
- except Exception as e:
499
- logger.warning("Error when saving tonies.custom.json to server: %s", e)
500
- # Don't fail the operation if server upload fails
501
-
577
+ logger.debug("Successfully saved tonies.custom.json with %d entries", len(handler.custom_json))
578
+ logger.trace("Exiting fetch_and_update_tonies_json with success=True")
502
579
  return True