TonieToolbox 0.6.0a4__py3-none-any.whl → 0.6.0a5__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.
TonieToolbox/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  TonieToolbox - Convert audio files to Tonie box compatible format
3
4
  """
4
5
 
5
- __version__ = '0.6.0a4'
6
+ __version__ = '0.6.0a5'
TonieToolbox/__main__.py CHANGED
@@ -13,7 +13,7 @@ from .tonie_file import create_tonie_file
13
13
  from .tonie_analysis import check_tonie_file, check_tonie_file_cli, split_to_opus_files, compare_taf_files
14
14
  from .dependency_manager import get_ffmpeg_binary, get_opus_binary, ensure_dependency
15
15
  from .logger import TRACE, setup_logging, get_logger
16
- from .filename_generator import guess_output_filename
16
+ from .filename_generator import guess_output_filename, apply_template_to_path,ensure_directory_exists
17
17
  from .version_handler import check_for_updates, clear_version_cache
18
18
  from .recursive_processor import process_recursive_folders
19
19
  from .media_tags import is_available as is_media_tags_available, ensure_mutagen, extract_album_info, format_metadata_filename, get_file_tags
@@ -41,7 +41,7 @@ def main():
41
41
  teddycloud_group.add_argument('--special-folder', action='store', metavar='FOLDER',
42
42
  help='Special folder to upload to (currently only "library" is supported)', default='library')
43
43
  teddycloud_group.add_argument('--path', action='store', metavar='PATH',
44
- help='Path where to write the file on TeddyCloud server')
44
+ help='Path where to write the file on TeddyCloud server (supports templates like "/{albumartist}/{album}")')
45
45
  teddycloud_group.add_argument('--connection-timeout', type=int, metavar='SECONDS', default=10,
46
46
  help='Connection timeout in seconds (default: 10)')
47
47
  teddycloud_group.add_argument('--read-timeout', type=int, metavar='SECONDS', default=300,
@@ -112,7 +112,9 @@ def main():
112
112
  media_tag_group.add_argument('-m', '--use-media-tags', action='store_true',
113
113
  help='Use media tags from audio files for naming')
114
114
  media_tag_group.add_argument('--name-template', metavar='TEMPLATE', action='store',
115
- help='Template for naming files using media tags. Example: "{album} - {artist}"')
115
+ help='Template for naming files using media tags. Example: "{albumartist} - {album}"')
116
+ media_tag_group.add_argument('--output-to-template', metavar='PATH_TEMPLATE', action='store',
117
+ help='Template for output path using media tags. Example: "C:\\Music\\{albumartist}\\{album}"')
116
118
  media_tag_group.add_argument('--show-tags', action='store_true',
117
119
  help='Show available media tags from input files')
118
120
  # ------------- Parser - Version handling -------------
@@ -150,7 +152,7 @@ def main():
150
152
  else:
151
153
  log_level = logging.INFO
152
154
  setup_logging(log_level, log_to_file=args.log_file)
153
- logger = get_logger('main')
155
+ logger = get_logger(__name__)
154
156
  logger.debug("Starting TonieToolbox v%s with log level: %s", __version__, logging.getLevelName(log_level))
155
157
  logger.debug("Command-line arguments: %s", vars(args))
156
158
 
@@ -279,8 +281,33 @@ def main():
279
281
  file_path, file_size, file_ext)
280
282
  logger.info("Uploading %s to TeddyCloud %s", file_path, teddycloud_url)
281
283
  logger.trace("Starting upload process for %s", file_path)
284
+
285
+ upload_path = args.path
286
+ if upload_path and '{' in upload_path and args.use_media_tags:
287
+ metadata = get_file_tags(file_path)
288
+ if metadata:
289
+ formatted_path = apply_template_to_path(upload_path, metadata)
290
+ if formatted_path:
291
+ logger.info("Using dynamic upload path from template: %s", formatted_path)
292
+ upload_path = formatted_path
293
+ else:
294
+ logger.warning("Could not apply all tags to path template '%s'. Using as-is.", upload_path)
295
+
296
+ # Create directories recursively if path is provided
297
+ if upload_path:
298
+ logger.debug("Creating directory structure on server: %s", upload_path)
299
+ try:
300
+ client.create_directories_recursive(
301
+ path=upload_path,
302
+ special=args.special_folder
303
+ )
304
+ logger.debug("Successfully created directory structure on server")
305
+ except Exception as e:
306
+ logger.warning("Failed to create directory structure on server: %s", str(e))
307
+ logger.debug("Continuing with upload anyway, in case the directory already exists")
308
+
282
309
  response = client.upload_file(
283
- destination_path=args.path,
310
+ destination_path=upload_path,
284
311
  file_path=file_path,
285
312
  special=args.special_folder,
286
313
  )
@@ -483,25 +510,25 @@ def main():
483
510
 
484
511
  guessed_name = None
485
512
  if args.use_media_tags:
513
+ logger.debug("Using media tags for naming")
486
514
  if len(files) > 1 and os.path.dirname(files[0]) == os.path.dirname(files[-1]):
515
+ logger.debug("Multiple files in the same folder, trying to extract album info")
487
516
  folder_path = os.path.dirname(files[0])
488
517
  logger.debug("Extracting album info from folder: %s", folder_path)
489
518
  album_info = extract_album_info(folder_path)
490
519
  if album_info:
491
- template = args.name_template or "{album} - {artist}"
492
- new_name = format_metadata_filename(album_info, template)
493
-
520
+ template = args.name_template or "{artist} - {album}"
521
+ new_name = format_metadata_filename(album_info, template)
494
522
  if new_name:
495
523
  logger.info("Using album metadata for output filename: %s", new_name)
496
524
  guessed_name = new_name
497
525
  else:
498
526
  logger.debug("Could not format filename from album metadata")
499
527
  elif len(files) == 1:
500
-
501
-
502
528
  tags = get_file_tags(files[0])
503
529
  if tags:
504
- template = args.name_template or "{title} - {artist}"
530
+ logger.debug("")
531
+ template = args.name_template or "{artist} - {title}"
505
532
  new_name = format_metadata_filename(tags, template)
506
533
 
507
534
  if new_name:
@@ -538,20 +565,71 @@ def main():
538
565
  else:
539
566
  logger.debug("Could not format filename from common metadata")
540
567
 
541
- if args.output_filename:
568
+ if args.output_filename:
542
569
  out_filename = args.output_filename
570
+ logger.debug("Output filename specified: %s", out_filename)
571
+ elif args.output_to_template and args.use_media_tags:
572
+ # Get metadata from files
573
+ if len(files) > 1 and os.path.dirname(files[0]) == os.path.dirname(files[-1]):
574
+ metadata = extract_album_info(os.path.dirname(files[0]))
575
+ elif len(files) == 1:
576
+ metadata = get_file_tags(files[0])
577
+ else:
578
+ # Try to get common tags for multiple files
579
+ metadata = {}
580
+ for file_path in files:
581
+ tags = get_file_tags(file_path)
582
+ if tags:
583
+ for key, value in tags.items():
584
+ if key not in metadata:
585
+ metadata[key] = value
586
+ elif metadata[key] != value:
587
+ metadata[key] = None
588
+ metadata = {k: v for k, v in metadata.items() if v is not None}
589
+
590
+ if metadata:
591
+ formatted_path = apply_template_to_path(args.output_to_template, metadata)
592
+ logger.debug("Formatted path from template: %s", formatted_path)
593
+ if formatted_path:
594
+ ensure_directory_exists(formatted_path)
595
+ if guessed_name:
596
+ logger.debug("Using guessed name for output: %s", guessed_name)
597
+ out_filename = os.path.join(formatted_path, guessed_name)
598
+ else:
599
+ logger.debug("Using template path for output: %s", formatted_path)
600
+ out_filename = formatted_path
601
+ logger.info("Using template path for output: %s", out_filename)
602
+ else:
603
+ logger.warning("Could not apply template to path. Using default output location.")
604
+ # Fall back to default output handling
605
+ if guessed_name:
606
+ logger.debug("Using guessed name for output: %s", guessed_name)
607
+ if args.output_to_source:
608
+ source_dir = os.path.dirname(files[0]) if files else '.'
609
+ out_filename = os.path.join(source_dir, guessed_name)
610
+ logger.debug("Using source location for output: %s", out_filename)
611
+ else:
612
+ output_dir = './output'
613
+ if not os.path.exists(output_dir):
614
+ os.makedirs(output_dir, exist_ok=True)
615
+ out_filename = os.path.join(output_dir, guessed_name)
616
+ logger.debug("Using default output location: %s", out_filename)
617
+ else:
618
+ logger.warning("No metadata available to apply to template path. Using default output location.")
619
+ # Fall back to default output handling
543
620
  elif guessed_name:
621
+ logger.debug("Using guessed name for output: %s", guessed_name)
544
622
  if args.output_to_source:
545
623
  source_dir = os.path.dirname(files[0]) if files else '.'
546
624
  out_filename = os.path.join(source_dir, guessed_name)
547
- logger.debug("Using source location for output with media tags: %s", out_filename)
625
+ logger.debug("Using source location for output: %s", out_filename)
548
626
  else:
549
627
  output_dir = './output'
550
628
  if not os.path.exists(output_dir):
551
629
  logger.debug("Creating default output directory: %s", output_dir)
552
630
  os.makedirs(output_dir, exist_ok=True)
553
631
  out_filename = os.path.join(output_dir, guessed_name)
554
- logger.debug("Using default output location with media tags: %s", out_filename)
632
+ logger.debug("Using default output location: %s", out_filename)
555
633
  else:
556
634
  guessed_name = guess_output_filename(args.input_filename, files)
557
635
  if args.output_to_source:
@@ -581,6 +659,7 @@ def main():
581
659
 
582
660
  if not out_filename.lower().endswith('.taf'):
583
661
  out_filename += '.taf'
662
+ ensure_directory_exists(out_filename)
584
663
 
585
664
  logger.info("Creating Tonie file: %s with %d input file(s)", out_filename, len(files))
586
665
  create_tonie_file(out_filename, files, args.no_tonie_header, args.user_timestamp,
@@ -591,10 +670,48 @@ def main():
591
670
 
592
671
  # ------------- Single File Upload -------------
593
672
  artwork_url = None
594
- if args.upload:
673
+ if args.upload:
674
+ upload_path = args.path
675
+ if upload_path and '{' in upload_path and args.use_media_tags:
676
+ metadata = {}
677
+ if len(files) > 1 and os.path.dirname(files[0]) == os.path.dirname(files[-1]):
678
+ metadata = extract_album_info(os.path.dirname(files[0]))
679
+ elif len(files) == 1:
680
+ metadata = get_file_tags(files[0])
681
+ else:
682
+ for file_path in files:
683
+ tags = get_file_tags(file_path)
684
+ if tags:
685
+ for key, value in tags.items():
686
+ if key not in metadata:
687
+ metadata[key] = value
688
+ elif metadata[key] != value:
689
+ metadata[key] = None
690
+ metadata = {k: v for k, v in metadata.items() if v is not None}
691
+ if metadata:
692
+ formatted_path = apply_template_to_path(upload_path, metadata)
693
+ if formatted_path:
694
+ logger.info("Using dynamic upload path from template: %s", formatted_path)
695
+ upload_path = formatted_path
696
+ else:
697
+ logger.warning("Could not apply all tags to path template '%s'. Using as-is.", upload_path)
698
+
699
+ # Create directories recursively if path is provided
700
+ if upload_path:
701
+ logger.debug("Creating directory structure on server: %s", upload_path)
702
+ try:
703
+ client.create_directories_recursive(
704
+ path=upload_path,
705
+ special=args.special_folder
706
+ )
707
+ logger.debug("Successfully created directory structure on server")
708
+ except Exception as e:
709
+ logger.warning("Failed to create directory structure on server: %s", str(e))
710
+ logger.debug("Continuing with upload anyway, in case the directory already exists")
711
+
595
712
  response = client.upload_file(
596
713
  file_path=out_filename,
597
- destination_path=args.path,
714
+ destination_path=upload_path,
598
715
  special=args.special_folder,
599
716
  )
600
717
  upload_success = response.get('success', False)
TonieToolbox/artwork.py CHANGED
@@ -13,6 +13,7 @@ from .logger import get_logger
13
13
  from .teddycloud import TeddyCloudClient
14
14
  from .media_tags import extract_artwork, find_cover_image
15
15
 
16
+ logger = get_logger(__name__)
16
17
 
17
18
  def upload_artwork(
18
19
  client: TeddyCloudClient,
@@ -30,8 +31,7 @@ def upload_artwork(
30
31
  audio_files (list[str]): List of audio files to extract artwork from if needed
31
32
  Returns:
32
33
  tuple[bool, Optional[str]]: (success, artwork_url) where success is a boolean and artwork_url is the URL of the uploaded artwork
33
- """
34
- logger = get_logger('artwork')
34
+ """
35
35
  logger.info("Looking for artwork for Tonie file: %s", taf_filename)
36
36
  taf_basename = os.path.basename(taf_filename)
37
37
  taf_name = os.path.splitext(taf_basename)[0]
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Audio conversion functionality for the TonieToolbox package
3
4
  """
@@ -10,7 +11,7 @@ from .dependency_manager import get_ffmpeg_binary, get_opus_binary
10
11
  from .constants import SUPPORTED_EXTENSIONS
11
12
  from .logger import get_logger
12
13
 
13
- logger = get_logger('audio_conversion')
14
+ logger = get_logger(__name__)
14
15
 
15
16
 
16
17
  def get_opus_tempfile(
TonieToolbox/constants.py CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Constants used throughout the TonieToolbox package
3
4
  """
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Dependency management for the TonieToolbox package.
3
4
 
@@ -22,7 +23,7 @@ import concurrent.futures
22
23
  from tqdm.auto import tqdm
23
24
 
24
25
  from .logger import get_logger
25
- logger = get_logger('dependency_manager')
26
+ logger = get_logger(__name__)
26
27
 
27
28
  CACHE_DIR = os.path.join(os.path.expanduser("~"), ".tonietoolbox")
28
29
  LIBS_DIR = os.path.join(CACHE_DIR, "libs")
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Module for generating intelligent output filenames for TonieToolbox.
3
4
  """
@@ -8,7 +9,7 @@ from pathlib import Path
8
9
  from typing import List, Optional
9
10
  from .logger import get_logger
10
11
 
11
- logger = get_logger('filename_generator')
12
+ logger = get_logger(__name__)
12
13
 
13
14
  def sanitize_filename(filename: str) -> str:
14
15
  """
@@ -89,4 +90,52 @@ def guess_output_filename(input_filename: str, input_files: list[str] = None) ->
89
90
  except ValueError:
90
91
  # Files might be on different drives
91
92
  logger.debug("Could not determine common path, using generic name")
92
- return "tonie_collection"
93
+ return "tonie_collection"
94
+
95
+ def apply_template_to_path(template, metadata):
96
+ """
97
+ Apply metadata to a path template and ensure the path is valid.
98
+
99
+ Args:
100
+ template: String template with {tag} placeholders
101
+ metadata: Dictionary of tag values
102
+
103
+ Returns:
104
+ Formatted path with placeholders replaced by actual values
105
+ """
106
+ if not template or not metadata:
107
+ return None
108
+
109
+ try:
110
+ # Replace any tags in the path with their values
111
+ formatted_path = template
112
+ for tag, value in metadata.items():
113
+ if value:
114
+ # Sanitize value for use in path
115
+ safe_value = re.sub(r'[<>:"|?*]', '_', str(value))
116
+ # Replace forward slashes with appropriate character, but NOT hyphens
117
+ safe_value = safe_value.replace('/', ' - ')
118
+ # Remove leading/trailing whitespace and dots
119
+ safe_value = safe_value.strip('. \t')
120
+ if not safe_value:
121
+ safe_value = "unknown"
122
+
123
+ placeholder = '{' + tag + '}'
124
+ formatted_path = formatted_path.replace(placeholder, safe_value)
125
+
126
+ # Check if there are any remaining placeholders
127
+ if re.search(r'{[^}]+}', formatted_path):
128
+ return None # Some placeholders couldn't be filled
129
+
130
+ # Normalize path separators for the OS
131
+ formatted_path = os.path.normpath(formatted_path)
132
+ return formatted_path
133
+ except Exception as e:
134
+ logger.error(f"Error applying template to path: {e}")
135
+ return None
136
+
137
+ def ensure_directory_exists(file_path):
138
+ """Create the directory structure for a given file path if it doesn't exist."""
139
+ directory = os.path.dirname(file_path)
140
+ if directory:
141
+ os.makedirs(directory, exist_ok=True)
@@ -1,10 +1,12 @@
1
+ #!/usr/bin/python3
1
2
  import platform
3
+ import os
4
+ import subprocess
2
5
  from .logger import get_logger
3
6
 
4
7
  logger = get_logger(__name__)
5
8
 
6
9
  def handle_integration(args):
7
- import platform
8
10
  if platform.system() == 'Windows':
9
11
  from .integration_windows import WindowsClassicContextMenuIntegration as ContextMenuIntegration
10
12
  if args.install_integration:
@@ -48,11 +50,6 @@ def handle_integration(args):
48
50
 
49
51
  def handle_config():
50
52
  """Opens the configuration file in the default text editor."""
51
- import os
52
- import platform
53
- import subprocess
54
-
55
-
56
53
  config_path = os.path.join(os.path.expanduser("~"), ".tonietoolbox", "config.json")
57
54
  if not os.path.exists(config_path):
58
55
  logger.info(f"Configuration file not found at {config_path}.")
@@ -1,3 +1,8 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ Integration for MacOS Quick Actions (Services) for TonieToolbox.
4
+ This module provides functionality to create and manage Quick Actions.
5
+ """
1
6
  import os
2
7
  import sys
3
8
  import json
@@ -8,7 +13,7 @@ from .constants import SUPPORTED_EXTENSIONS, CONFIG_TEMPLATE,UTI_MAPPINGS,ICON_B
8
13
  from .artwork import base64_to_ico
9
14
  from .logger import get_logger
10
15
 
11
- logger = get_logger('integration_macos')
16
+ logger = get_logger(__name__)
12
17
 
13
18
  class MacOSContextMenuIntegration:
14
19
  """
@@ -1 +1,2 @@
1
+ #!/usr/bin/python3
1
2
  # TODO: Add integration_ubuntu.py
@@ -1,4 +1,8 @@
1
- # filepath: d:\Repository\TonieToolbox\TonieToolbox\integration_windows.py
1
+ #!/usr/bin/python3
2
+ """
3
+ Integration for Windows "classic" context menu.
4
+ This module generates Windows registry entries to add a 'TonieToolbox' cascade menu.
5
+ """
2
6
  import os
3
7
  import sys
4
8
  import json
@@ -6,7 +10,7 @@ from .constants import SUPPORTED_EXTENSIONS, CONFIG_TEMPLATE, ICON_BASE64
6
10
  from .artwork import base64_to_ico
7
11
  from .logger import get_logger
8
12
 
9
- logger = get_logger('integration_windows')
13
+ logger = get_logger(__name__)
10
14
 
11
15
  class WindowsClassicContextMenuIntegration:
12
16
  """
TonieToolbox/logger.py CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Logging configuration for the TonieToolbox package.
3
4
  """
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Media tag processing functionality for the TonieToolbox package
3
4
 
@@ -14,6 +15,7 @@ from mutagen.flac import Picture
14
15
  from .logger import get_logger
15
16
  from .dependency_manager import is_mutagen_available, ensure_mutagen
16
17
  from .constants import ARTWORK_NAMES, ARTWORK_EXTENSIONS, TAG_VALUE_REPLACEMENTS, TAG_MAPPINGS
18
+ logger = get_logger(__name__)
17
19
 
18
20
  MUTAGEN_AVAILABLE = False
19
21
  mutagen = None
@@ -56,9 +58,6 @@ def _import_mutagen():
56
58
  if is_mutagen_available():
57
59
  _import_mutagen()
58
60
 
59
- logger = get_logger('media_tags')
60
-
61
-
62
61
  def normalize_tag_value(value: str) -> str:
63
62
  """
64
63
  Normalize tag values by replacing special characters or known patterns
TonieToolbox/ogg_page.py CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Classes and functions for handling OGG container pages
3
4
  """
@@ -14,8 +15,7 @@ from .constants import (
14
15
  )
15
16
  from .logger import get_logger
16
17
 
17
- # Setup logging
18
- logger = get_logger('ogg_page')
18
+ logger = get_logger(__name__)
19
19
 
20
20
 
21
21
  def create_crc_table() -> list[int]:
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Classes and functions for handling Opus audio packets
3
4
  """
@@ -6,8 +7,7 @@ import struct
6
7
  from .constants import SAMPLE_RATE_KHZ
7
8
  from .logger import get_logger
8
9
 
9
- # Setup logging
10
- logger = get_logger('opus_packet')
10
+ logger = get_logger(__name__)
11
11
 
12
12
 
13
13
  class OpusPacket:
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Recursive folder processing functionality for the TonieToolbox package
3
4
  """
@@ -11,7 +12,7 @@ import re
11
12
  from .audio_conversion import filter_directories
12
13
  from .logger import get_logger
13
14
 
14
- logger = get_logger('recursive_processor')
15
+ logger = get_logger(__name__)
15
16
 
16
17
 
17
18
  def find_audio_folders(root_path: str) -> list[dict[str, any]]:
TonieToolbox/tags.py CHANGED
@@ -8,7 +8,7 @@ from .teddycloud import TeddyCloudClient
8
8
  import json
9
9
  from typing import Optional, Union
10
10
 
11
- logger = get_logger('tags')
11
+ logger = get_logger(__name__)
12
12
 
13
13
  def get_tags(client: 'TeddyCloudClient') -> bool:
14
14
  """
@@ -9,8 +9,9 @@ import base64
9
9
  import ssl
10
10
  import socket
11
11
  import requests
12
+ import json
12
13
  from .logger import get_logger
13
- logger = get_logger('teddycloud')
14
+ logger = get_logger(__name__)
14
15
  DEFAULT_CONNECTION_TIMEOUT = 10
15
16
  DEFAULT_READ_TIMEOUT = 15 # seconds
16
17
  DEFAULT_MAX_RETRIES = 3
@@ -334,3 +335,115 @@ class TeddyCloudClient:
334
335
  }
335
336
 
336
337
  # ------------- Custom API Methods -------------
338
+
339
+ def _get_paths_cache_file(self) -> str:
340
+ """
341
+ Get the path to the paths cache file.
342
+
343
+ Returns:
344
+ str: Path to the paths cache file
345
+ """
346
+ cache_dir = os.path.join(os.path.expanduser("~"), ".tonietoolbox")
347
+ os.makedirs(cache_dir, exist_ok=True)
348
+ return os.path.join(cache_dir, "paths.json")
349
+
350
+ def _load_paths_cache(self) -> set:
351
+ """
352
+ Load the paths cache from the cache file.
353
+
354
+ Returns:
355
+ set: Set of existing directory paths
356
+ """
357
+ cache_file = self._get_paths_cache_file()
358
+ try:
359
+ if os.path.exists(cache_file):
360
+ with open(cache_file, 'r', encoding='utf-8') as f:
361
+ paths_data = json.load(f)
362
+ # Convert to set for faster lookups
363
+ return set(paths_data.get('paths', []))
364
+ return set()
365
+ except Exception as e:
366
+ logger.warning(f"Failed to load paths cache: {e}")
367
+ return set()
368
+
369
+ def _save_paths_cache(self, paths: set) -> None:
370
+ """
371
+ Save the paths cache to the cache file.
372
+
373
+ Args:
374
+ paths (set): Set of directory paths to save
375
+ """
376
+ cache_file = self._get_paths_cache_file()
377
+ try:
378
+ paths_data = {'paths': list(paths)}
379
+ with open(cache_file, 'w', encoding='utf-8') as f:
380
+ json.dump(paths_data, f, indent=2)
381
+ logger.debug(f"Saved {len(paths)} paths to cache file")
382
+ except Exception as e:
383
+ logger.warning(f"Failed to save paths cache: {e}")
384
+
385
+ def create_directories_recursive(self, path: str, overlay: str = None, special: str = "library") -> str:
386
+ """
387
+ Create directories recursively on the TeddyCloud server.
388
+
389
+ This function handles both cases:
390
+ - Directories that already exist (prevents 500 errors)
391
+ - Parent directories that don't exist yet (creates them first)
392
+
393
+ This optimized version uses a local paths cache instead of querying the file index,
394
+ since the file index might not represent the correct folders.
395
+
396
+ Args:
397
+ path (str): Directory path to create (can contain multiple levels)
398
+ overlay (str | None): Settings overlay ID (optional)
399
+ special (str | None): Special folder source, only 'library' supported yet (optional)
400
+
401
+ Returns:
402
+ str: Response message from server
403
+ """
404
+ path = path.replace('\\', '/').strip('/')
405
+ if not path:
406
+ return "Path is empty"
407
+ existing_dirs = self._load_paths_cache()
408
+ logger.debug(f"Loaded {len(existing_dirs)} existing paths from cache")
409
+ path_components = path.split('/')
410
+ current_path = ""
411
+ result = "OK"
412
+ paths_updated = False
413
+ for component in path_components:
414
+ if current_path:
415
+ current_path += f"/{component}"
416
+ else:
417
+ current_path = component
418
+ if current_path in existing_dirs:
419
+ logger.debug(f"Directory '{current_path}' exists in paths cache, skipping creation")
420
+ continue
421
+
422
+ try:
423
+ result = self.create_directory(current_path, overlay, special)
424
+ logger.debug(f"Created directory: {current_path}")
425
+ # Add the newly created directory to our cache
426
+ existing_dirs.add(current_path)
427
+ paths_updated = True
428
+ except requests.exceptions.HTTPError as e:
429
+ # If it's a 500 error, likely the directory already exists
430
+ if e.response.status_code == 500:
431
+ if "already exists" in e.response.text.lower():
432
+ logger.debug(f"Directory '{current_path}' already exists, continuing")
433
+ # Add to our cache for future operations
434
+ existing_dirs.add(current_path)
435
+ paths_updated = True
436
+ else:
437
+ # Log the actual error message but continue anyway
438
+ # This allows us to continue even if the error is something else
439
+ logger.warning(f"Warning while creating '{current_path}': {str(e)}")
440
+ else:
441
+ # Re-raise for other HTTP errors
442
+ logger.error(f"Failed to create directory '{current_path}': {str(e)}")
443
+ raise
444
+
445
+ # Save updated paths cache if any changes were made
446
+ if paths_updated:
447
+ self._save_paths_cache(existing_dirs)
448
+
449
+ return result
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Functions for analyzing Tonie files
3
4
  """
@@ -10,7 +11,7 @@ from . import tonie_header_pb2
10
11
  from .ogg_page import OggPage
11
12
  from .logger import get_logger
12
13
 
13
- logger = get_logger('tonie_analysis')
14
+ logger = get_logger(__name__)
14
15
 
15
16
  def format_time(ts: float) -> str:
16
17
  """
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Tonie file operations module
3
4
  """
@@ -15,7 +16,7 @@ from .ogg_page import OggPage
15
16
  from .constants import OPUS_TAGS, SAMPLE_RATE_KHZ, TIMESTAMP_DEDUCT
16
17
  from .logger import get_logger
17
18
 
18
- logger = get_logger('tonie_file')
19
+ logger = get_logger(__name__)
19
20
 
20
21
 
21
22
  def toniefile_comment_add(buffer: bytearray, length: int, comment_str: str) -> int:
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  TonieToolbox module for handling the tonies.custom.json operations.
3
4
 
@@ -19,7 +20,7 @@ from .media_tags import get_file_tags, extract_album_info
19
20
  from .constants import LANGUAGE_MAPPING, GENRE_MAPPING
20
21
  from .teddycloud import TeddyCloudClient
21
22
 
22
- logger = get_logger('tonies_json')
23
+ logger = get_logger(__name__)
23
24
 
24
25
  class ToniesJsonHandlerv1:
25
26
  """Handler for tonies.custom.json operations using v1 format."""
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/python3
1
2
  """
2
3
  Version handler to check if the latest version of TonieToolbox is being used.
3
4
  """
@@ -17,6 +18,7 @@ CACHE_DIR = os.path.join(os.path.expanduser("~"), ".tonietoolbox")
17
18
  CACHE_FILE = os.path.join(CACHE_DIR, "version_cache.json")
18
19
  CACHE_EXPIRY = 86400 # 24 hours in seconds
19
20
 
21
+ logger = get_logger(__name__)
20
22
 
21
23
  def get_pypi_version(force_refresh: bool = False) -> tuple[str, str | None]:
22
24
  """
@@ -27,7 +29,6 @@ def get_pypi_version(force_refresh: bool = False) -> tuple[str, str | None]:
27
29
  Returns:
28
30
  tuple[str, str | None]: (latest_version, None) on success, (current_version, error_message) on failure
29
31
  """
30
- logger = get_logger("version_handler")
31
32
  logger.debug("Checking for latest version (force_refresh=%s)", force_refresh)
32
33
  logger.debug("Current version: %s", __version__)
33
34
 
@@ -97,7 +98,6 @@ def compare_versions(v1: str, v2: str) -> int:
97
98
  Returns:
98
99
  int: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
99
100
  """
100
- logger = get_logger("version_handler")
101
101
  logger.debug("Comparing versions: '%s' vs '%s'", v1, v2)
102
102
 
103
103
  try:
@@ -145,7 +145,6 @@ def check_for_updates(quiet: bool = False, force_refresh: bool = False) -> tuple
145
145
  message: string message about the update status or error
146
146
  update_confirmed: boolean indicating if the user confirmed the update
147
147
  """
148
- logger = get_logger("version_handler")
149
148
  current_version = __version__
150
149
  update_confirmed = False
151
150
 
@@ -204,7 +203,6 @@ def install_update() -> bool:
204
203
  Returns:
205
204
  bool: True if the update was successfully installed, False otherwise
206
205
  """
207
- logger = get_logger("version_handler")
208
206
  import subprocess
209
207
  import sys
210
208
 
@@ -242,7 +240,6 @@ def clear_version_cache() -> bool:
242
240
  Returns:
243
241
  bool: True if cache was cleared, False otherwise
244
242
  """
245
- logger = get_logger("version_handler")
246
243
 
247
244
  try:
248
245
  if os.path.exists(CACHE_FILE):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TonieToolbox
3
- Version: 0.6.0a4
3
+ Version: 0.6.0a5
4
4
  Summary: Convert audio files to Toniebox compatible format (.TAF) and interact with TeddyCloud.
5
5
  Home-page: https://github.com/Quentendo64/TonieToolbox
6
6
  Author: Quentendo64
@@ -0,0 +1,30 @@
1
+ TonieToolbox/__init__.py,sha256=0gNvQWCJmnGv7_OCrSTA7qyNe3zUQ546m7TL9_SFtwY,117
2
+ TonieToolbox/__main__.py,sha256=KSDasW-QaTzStXSKtemMKxoFV8IWvCmq1Oig5yo8BQs,41573
3
+ TonieToolbox/artwork.py,sha256=BhAjLWqzui8zUOU9z9P4H3zbCCTOB94aT0MPlqEvZoY,5605
4
+ TonieToolbox/audio_conversion.py,sha256=xXXsRGPS_yQ_mqtFJiqupLbeXN8__Q2Hg05C94EPC_8,17199
5
+ TonieToolbox/constants.py,sha256=nTKDBy55PTv6Tazda3rsZZcckcm3AZOyWXa1LDuTuEk,33989
6
+ TonieToolbox/dependency_manager.py,sha256=sQv0vNE7Gp_RqdR9GOwF8jJgu8GTw0Krppg5MjG5qJU,49432
7
+ TonieToolbox/filename_generator.py,sha256=NJrRxv0sm_9G5ZDp1QG39mktgJnz5yGC8HUs7NhLYFg,5293
8
+ TonieToolbox/integration.py,sha256=qWRZfowNBIQJPPnGUJ52Xw0SqyfScuzoONPVtZI8yjA,3063
9
+ TonieToolbox/integration_macos.py,sha256=YxK7gIqiy1QTp7EM1M1OPC-mgb3gaIoI6Xfu3eUAyX0,27311
10
+ TonieToolbox/integration_ubuntu.py,sha256=_Da2s7CmuP7kts0W-yyt4odZA8jr14d8iisVF_ipLDA,52
11
+ TonieToolbox/integration_windows.py,sha256=WBclD1bW5X8cJ-x3j3-YPnSkpWOQmzxQXjj2-gemPN0,24011
12
+ TonieToolbox/logger.py,sha256=ppAnOJAKNwwbt5q3NdF2WHgHemZjP0QWRaUx8UrkQK8,3139
13
+ TonieToolbox/media_tags.py,sha256=pOLcqnY4sUudI-gBre0A7le3nA7ZDkhUNkozUdRVHiE,20843
14
+ TonieToolbox/ogg_page.py,sha256=CpL3mHoRsfHBBrT2r4x5p3LBPgHfYQxb_WQsCZq6SI0,22341
15
+ TonieToolbox/opus_packet.py,sha256=skKzr5sVpL7L77JisqqGL4Kz86acOGDzwbFRqRL2oSM,8017
16
+ TonieToolbox/recursive_processor.py,sha256=IcTmQY6OF01KUfqjYE7F0CFl1SeWcSLzkB_Du6R8deI,13910
17
+ TonieToolbox/tags.py,sha256=usNcZxMEAMP7vEfeNOtg70YBJpHiWuHb_XKCyEafsYc,2702
18
+ TonieToolbox/teddycloud.py,sha256=1fpRzhJrOiUXTBhX9JNL9rlO5obIl32WMoYz5RWz0zA,18486
19
+ TonieToolbox/tonie_analysis.py,sha256=KkaZ6vt4sntMTfutyQApI8gvCirMjTW8aeIBSbhtllA,30798
20
+ TonieToolbox/tonie_file.py,sha256=z7yRmK0ujZ6SjF78-xJImGmQ2bS-Q7b-6d2ryYJnmwA,21769
21
+ TonieToolbox/tonie_header.proto,sha256=WaWfwO4VrwGtscK2ujfDRKtpeBpaVPoZhI8iMmR-C0U,202
22
+ TonieToolbox/tonie_header_pb2.py,sha256=s5bp4ULTEekgq6T61z9fDkRavyPM-3eREs20f_Pxxe8,3665
23
+ TonieToolbox/tonies_json.py,sha256=Icu5ClFbRK0gFuefJkHQNRjeOW79tQV-GZ3hoIATo3M,60768
24
+ TonieToolbox/version_handler.py,sha256=Q-i5_0r5lX3dlwDLI2lMAx4X10UZGN2rvMcntmyuiT4,9862
25
+ tonietoolbox-0.6.0a5.dist-info/licenses/LICENSE.md,sha256=rGoga9ZAgNco9fBapVFpWf6ri7HOBp1KRnt1uIruXMk,35190
26
+ tonietoolbox-0.6.0a5.dist-info/METADATA,sha256=Hzna0YOJdh0olBqOV921ZCfa62g8b_iL555_WeFsYdo,27009
27
+ tonietoolbox-0.6.0a5.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
28
+ tonietoolbox-0.6.0a5.dist-info/entry_points.txt,sha256=oqpeyBxel7aScg35Xr4gZKnf486S5KW9okqeBwyJxxc,60
29
+ tonietoolbox-0.6.0a5.dist-info/top_level.txt,sha256=Wkkm-2p7I3ENfS7ZbYtYUB2g-xwHrXVlERHfonsOPuE,13
30
+ tonietoolbox-0.6.0a5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,30 +0,0 @@
1
- TonieToolbox/__init__.py,sha256=P-i-pmjFHddy0r76UYtLh4dFA2jppC0wc_BqhDAzfqw,98
2
- TonieToolbox/__main__.py,sha256=F_FHZC7rLejBdEQ4plOg6dcyV4lgFKZDvJtQHpCthKs,34934
3
- TonieToolbox/artwork.py,sha256=VRDa9Ydeq7AsXhRKYyEqpfFNH64AA1Xek7yo60bn8OI,5606
4
- TonieToolbox/audio_conversion.py,sha256=0GpC6mSRYikIjf_A1w26LAnYtCP2gpHLEKozOISapnM,17190
5
- TonieToolbox/constants.py,sha256=DabAgzKeG8trpV9I6tie1FA6mt3BhTRf_gF5UwtMpE4,33970
6
- TonieToolbox/dependency_manager.py,sha256=7HCyUhVLZ6l0W1HPdYr3AgLc935S43qdTLpf1PipPkQ,49425
7
- TonieToolbox/filename_generator.py,sha256=ATCG4w8uN1vyAqvmdhOtpJLlb9QFKCnYIdBViYqpHjw,3464
8
- TonieToolbox/integration.py,sha256=TXCkXxmeMGtw8G8M-jUVuKa7i_hI0JvVoVlIvYFvEnc,3097
9
- TonieToolbox/integration_macos.py,sha256=HWHBcMU5BRe50ak2nqB9kIFJS83sJ8t2_B_0FMhat2U,27159
10
- TonieToolbox/integration_ubuntu.py,sha256=MU6W0xRCdoHBxrIiOIHePqYTF5Wvn4JxBnDQUPf6fgg,33
11
- TonieToolbox/integration_windows.py,sha256=7F_8Dh7fWHuvCfcigM5TB-bF5CupzM6iQ-YqOvvNVFQ,23939
12
- TonieToolbox/logger.py,sha256=Q_cXbCWfzNmt5q6fvVzeM8IugkD24CSZAVjuf16n6b4,3120
13
- TonieToolbox/media_tags.py,sha256=oDlLe0AyvmIdQlqPzH74AUCqwbZZ-49AQKAJdrW26XE,20830
14
- TonieToolbox/ogg_page.py,sha256=IHdP0er0TYjyLfON8zes11FdQtRab3QNxeK6sxnAX08,22340
15
- TonieToolbox/opus_packet.py,sha256=yz5jvViGZ-nGZjEaQ1gCKd-j1jPW402szYirbEl4izA,8019
16
- TonieToolbox/recursive_processor.py,sha256=6JD4b5sGnCe45GkJqWua9u9ZHv-RC77BRV58qQ8pA3Q,13904
17
- TonieToolbox/tags.py,sha256=7BowNWmbJDymvJ0hPVAAXwJRAfPklLokQQuV8FVldSI,2700
18
- TonieToolbox/teddycloud.py,sha256=5PSV5Qp0VRfDG78kGPbKQ7bXxxB1sfmGNHYhvIumVF8,13794
19
- TonieToolbox/tonie_analysis.py,sha256=KYDfWi9EmA5EvVyq_h_gcxrZ5Eaa5etYsF152GRgyqI,30787
20
- TonieToolbox/tonie_file.py,sha256=YntpgpmtWTe64WWqJmWWo2sXyMUnZ57pp3sKGoY-Yik,21754
21
- TonieToolbox/tonie_header.proto,sha256=WaWfwO4VrwGtscK2ujfDRKtpeBpaVPoZhI8iMmR-C0U,202
22
- TonieToolbox/tonie_header_pb2.py,sha256=s5bp4ULTEekgq6T61z9fDkRavyPM-3eREs20f_Pxxe8,3665
23
- TonieToolbox/tonies_json.py,sha256=YGS2wtaDudxxSy7QuRLWaE5n4bf_AyoSvVLH1Vdh8SE,60754
24
- TonieToolbox/version_handler.py,sha256=MLpJ9mSEHkcSoyePnACpfINHTSB1q1_4iEgcT1tGqNU,10028
25
- tonietoolbox-0.6.0a4.dist-info/licenses/LICENSE.md,sha256=rGoga9ZAgNco9fBapVFpWf6ri7HOBp1KRnt1uIruXMk,35190
26
- tonietoolbox-0.6.0a4.dist-info/METADATA,sha256=1ygRy4_HXQsn3C_TcLydyvkdpTppmYECp4OH7eQ2NTE,27009
27
- tonietoolbox-0.6.0a4.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
28
- tonietoolbox-0.6.0a4.dist-info/entry_points.txt,sha256=oqpeyBxel7aScg35Xr4gZKnf486S5KW9okqeBwyJxxc,60
29
- tonietoolbox-0.6.0a4.dist-info/top_level.txt,sha256=Wkkm-2p7I3ENfS7ZbYtYUB2g-xwHrXVlERHfonsOPuE,13
30
- tonietoolbox-0.6.0a4.dist-info/RECORD,,