TonieToolbox 0.6.0a4__py3-none-any.whl → 0.6.0rc1__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.0rc1'
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:
@@ -23,24 +25,26 @@ def handle_integration(args):
23
25
  else:
24
26
  logger.error("Integration uninstallation failed.")
25
27
  return False
28
+ #elif platform.system() == 'Darwin':
29
+ # from .integration_macos import MacOSContextMenuIntegration as ContextMenuIntegration
30
+ # if args.install_integration:
31
+ # success = ContextMenuIntegration.install()
32
+ # if success:
33
+ # logger.info("Integration installed successfully.")
34
+ # return True
35
+ # else:
36
+ # logger.error("Integration installation failed.")
37
+ # return False
38
+ # elif args.uninstall_integration:
39
+ # success = ContextMenuIntegration.uninstall()
40
+ # if success:
41
+ # logger.info("Integration uninstalled successfully.")
42
+ # return True
43
+ # else:
44
+ # logger.error("Integration uninstallation failed.")
45
+ # return False
26
46
  elif platform.system() == 'Darwin':
27
- from .integration_macos import MacOSContextMenuIntegration as ContextMenuIntegration
28
- if args.install_integration:
29
- success = ContextMenuIntegration.install()
30
- if success:
31
- logger.info("Integration installed successfully.")
32
- return True
33
- else:
34
- logger.error("Integration installation failed.")
35
- return False
36
- elif args.uninstall_integration:
37
- success = ContextMenuIntegration.uninstall()
38
- if success:
39
- logger.info("Integration uninstalled successfully.")
40
- return True
41
- else:
42
- logger.error("Integration uninstallation failed.")
43
- return False
47
+ raise NotImplementedError("Context menu integration is not supported on MacOS YET. But Soon™")
44
48
  elif platform.system() == 'Linux':
45
49
  raise NotImplementedError("Context menu integration is not supported on Linux YET. But Soon™")
46
50
  else:
@@ -48,11 +52,6 @@ def handle_integration(args):
48
52
 
49
53
  def handle_config():
50
54
  """Opens the configuration file in the default text editor."""
51
- import os
52
- import platform
53
- import subprocess
54
-
55
-
56
55
  config_path = os.path.join(os.path.expanduser("~"), ".tonietoolbox", "config.json")
57
56
  if not os.path.exists(config_path):
58
57
  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
  """
@@ -119,7 +123,7 @@ class WindowsClassicContextMenuIntegration:
119
123
  self.upload_taf_cmd = self._build_cmd(log_level_arg, use_upload=True, log_to_file=self.log_to_file)
120
124
  self.upload_taf_artwork_cmd = self._build_cmd(log_level_arg, use_upload=True, use_artwork=True, log_to_file=self.log_to_file)
121
125
  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)
122
- self.compare_taf_cmd = self._build_cmd(log_level_arg, use_compare=True, keep_open=True, log_to_file=self.log_to_file)
126
+ #self.compare_taf_cmd = self._build_cmd(log_level_arg, use_compare=True, keep_open=True, log_to_file=self.log_to_file)
123
127
 
124
128
  # Folder commands
125
129
  self.convert_folder_cmd = self._build_cmd(f'{log_level_arg}', is_recursive=True, is_folder=True, log_to_file=self.log_to_file)
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.0rc1
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
@@ -49,6 +49,14 @@ A Toolkit for converting various audio formats into the Tonie-compatible TAF for
49
49
  → [HOWTO Guide for Beginners](HOWTO.md)
50
50
  → [Contributing Guidelines](CONTRIBUTING.md)
51
51
 
52
+ ## 🎯 New Features (v0.6.0)
53
+
54
+ The latest release of TonieToolbox includes exciting new capabilities:
55
+
56
+ - **Enhanced Media Tag Support**: Better handling of complex audio libraries with advanced metadata extraction and usage of tags in your upload path (--path) or as output directory with the new argument --output-to-template eg. "C:\Music\\{albumartist}\\{album}"
57
+ - **Windows Context Menu Integration**: Right-click to convert audio files directly from File Explorer. Use --config-integration to configure the upload functions. If not needed just use --install-integration
58
+
59
+
52
60
  ## Table of Contents
53
61
 
54
62
  - [Overview](#overview)
@@ -263,12 +271,17 @@ Output:
263
271
  ```shell
264
272
  usage: TonieToolbox.py [-h] [-v] [--upload URL] [--include-artwork] [--get-tags URL]
265
273
  [--ignore-ssl-verify] [--special-folder FOLDER] [--path PATH]
266
- [--show-progress] [--connection-timeout SECONDS]
267
- [--read-timeout SECONDS] [--max-retries RETRIES]
268
- [--retry-delay SECONDS] [--create-custom-json] [-t TIMESTAMP] [-f FFMPEG]
274
+ [--connection-timeout SECONDS] [--read-timeout SECONDS]
275
+ [--max-retries RETRIES] [--retry-delay SECONDS]
276
+ [--create-custom-json] [--version-2] [--username USERNAME]
277
+ [--password PASSWORD] [--client-cert CERT_FILE]
278
+ [--client-key KEY_FILE] [-t TIMESTAMP] [-f FFMPEG]
269
279
  [-o OPUSENC] [-b BITRATE] [-c] [-a TAG] [-n] [-i] [-s] [-r] [-O]
270
- [-A] [-k] [-u] [-C FILE2] [-D] [-m] [--name-template TEMPLATE]
271
- [--show-tags] [-S] [-F] [-X] [-d] [-T] [-q] [-Q]
280
+ [-fc] [--no-mono-conversion] [-A] [-k] [-u] [-C FILE2] [-D]
281
+ [--config-integration] [--install-integration]
282
+ [--uninstall-integration] [-m] [--name-template TEMPLATE]
283
+ [--output-to-template PATH_TEMPLATE] [--show-tags]
284
+ [-S] [-F] [-X] [-d] [-T] [-q] [-Q] [--log-file]
272
285
  SOURCE [TARGET]
273
286
 
274
287
  Create Tonie compatible file from Ogg opus file(s).
@@ -284,8 +297,7 @@ TeddyCloud Options:
284
297
  --ignore-ssl-verify Ignore SSL certificate verification (for self-signed certificates)
285
298
  --special-folder FOLDER
286
299
  Special folder to upload to (currently only "library" is supported)
287
- --path PATH Path where to write the file on TeddyCloud server
288
- --show-progress Show progress bar during file upload (default: enabled)
300
+ --path PATH Path where to write the file on TeddyCloud server (supports templates like "/{albumartist}/{album}")
289
301
  --connection-timeout SECONDS
290
302
  Connection timeout in seconds (default: 10)
291
303
  --read-timeout SECONDS
@@ -295,12 +307,19 @@ TeddyCloud Options:
295
307
  --retry-delay SECONDS
296
308
  Delay between retry attempts in seconds (default: 5)
297
309
  --create-custom-json Fetch and update custom Tonies JSON data
310
+ --version-2 Use version 2 of the Tonies JSON format (default: version 1)
311
+ --username USERNAME Username for basic authentication
312
+ --password PASSWORD Password for basic authentication
313
+ --client-cert CERT_FILE
314
+ Path to client certificate file for certificate-based authentication
315
+ --client-key KEY_FILE
316
+ Path to client private key file for certificate-based authentication
298
317
 
299
318
  optional arguments:
300
319
  -h, --help show this help message and exit
301
320
  -v, --version show program version and exit
302
321
  -t, --timestamp TIMESTAMP
303
- set custom timestamp / bitstream serial / reference .taf file
322
+ set custom timestamp / bitstream serial
304
323
  -f, --ffmpeg FFMPEG specify location of ffmpeg
305
324
  -o, --opusenc OPUSENC specify location of opusenc
306
325
  -b, --bitrate BITRATE set encoding bitrate in kbps (default: 96)
@@ -313,17 +332,27 @@ optional arguments:
313
332
  -r, --recursive Process folders recursively
314
333
  -O, --output-to-source
315
334
  Save output files in the source directory instead of output directory
335
+ -fc, --force-creation
336
+ Force creation of Tonie file even if it already exists
337
+ --no-mono-conversion Do not convert mono audio to stereo (default: convert mono to stereo)
316
338
  -A, --auto-download Automatically download FFmpeg and opusenc if needed
317
339
  -k, --keep-temp Keep temporary opus files in a temp folder for testing
318
- -u, --use-legacy-tags Use legacy hardcoded tags instead of dynamic TonieToolbox tags (DEPRECATED)
340
+ -u, --use-legacy-tags Use legacy hardcoded tags instead of dynamic TonieToolbox tags
319
341
  -C, --compare FILE2 Compare input file with another .taf file for debugging
320
342
  -D, --detailed-compare
321
343
  Show detailed OGG page differences when comparing files
322
- --no-mono-conversion Do not convert mono audio to stereo (default: convert mono to stereo)
344
+ --config-integration Configure context menu integration
345
+ --install-integration
346
+ Integrate with the system (e.g., create context menu entries)
347
+ --uninstall-integration
348
+ Uninstall context menu integration
349
+
323
350
  Media Tag Options:
324
351
  -m, --use-media-tags Use media tags from audio files for naming
325
352
  --name-template TEMPLATE
326
353
  Template for naming files using media tags. Example: "{album} - {artist}"
354
+ --output-to-template PATH_TEMPLATE
355
+ Template for output path using media tags. Example: "C:\Music\{albumartist}\{album}"
327
356
  --show-tags Show available media tags from input files
328
357
 
329
358
  Version Check Options:
@@ -544,6 +573,87 @@ tonietoolbox --split my_tonie.taf --debug
544
573
  tonietoolbox --recursive "Music/Collection/" --quiet
545
574
  ```
546
575
 
576
+ #### Force creation of TAF files
577
+
578
+ If a valid TAF file already exists, TonieToolbox will skip recreating it by default. To force creation even if the file exists:
579
+
580
+ ```shell
581
+ # Force creation of a TAF file even if it already exists
582
+ tonietoolbox input.mp3 --force-creation
583
+ ```
584
+
585
+ This is useful when you want to update the content or encoding settings of an existing TAF file.
586
+
587
+ #### Windows Context Menu Integration
588
+
589
+ TonieToolbox can integrate with Windows Explorer, allowing you to right-click on audio files or folders to convert them:
590
+
591
+ ```shell
592
+ # Install context menu integration (one-time setup)
593
+ tonietoolbox --install-integration
594
+
595
+ # Configure context menu options
596
+ tonietoolbox --config-integration
597
+
598
+ # Remove context menu integration
599
+ tonietoolbox --uninstall-integration
600
+ ```
601
+
602
+ After installation, you can right-click on any audio file or folder in Windows Explorer and select "Convert to Tonie Format".
603
+
604
+ When changing the configuration via `--config-integration`. Apply them to the integration by simply execute `tonietoolbox --install-integration` again.
605
+
606
+ #### Log File Generation
607
+
608
+ Save detailed logs to a timestamped file for troubleshooting complex operations:
609
+
610
+ ```shell
611
+ # Enable log file generation
612
+ tonietoolbox input.mp3 --log-file
613
+
614
+ # Combine with debug logging for maximum detail
615
+ tonietoolbox --recursive input_directory/ --log-file --debug
616
+ ```
617
+
618
+ Log files are saved in the `.tonietoolbox\logs` folder in your user directory.
619
+
620
+ #### Enhanced Media Tag Templates
621
+
622
+ Create custom directory structures based on media tags:
623
+
624
+ ```shell
625
+ # Create output based on a path template
626
+ tonietoolbox input.mp3 --use-media-tags --output-to-template "C:\Music\{albumartist}\{album}"
627
+
628
+ # Use with recursive processing
629
+ tonietoolbox --recursive "Music/Collection/" --use-media-tags --output-to-template "Organized/{genre}/{year} - {album}"
630
+ ```
631
+
632
+ This creates a directory structure based on the audio files' metadata and places the converted TAF files accordingly.
633
+
634
+ #### TeddyCloud Authentication Options
635
+
636
+ > **Note:** Authentication is based on the Features available when using [TeddyCloudStarter](https://github.com/Quentendo64/TeddyCloudStarter).
637
+
638
+ TonieToolbox supports multiple authentication methods for secure TeddyCloud connections:
639
+
640
+ ```shell
641
+ # Basic authentication with username and password
642
+ tonietoolbox input.mp3 --upload https://teddycloud.example.com --username admin --password secret
643
+
644
+ # Certificate-based authentication
645
+ tonietoolbox input.mp3 --upload https://teddycloud.example.com --client-cert certificate.crt --client-key private.key
646
+ ```
647
+
648
+ #### Custom JSON Format Versioning
649
+
650
+ Choose between different versions of the Tonies JSON format:
651
+
652
+ ```shell
653
+ # Use version 2 of the Tonies JSON format (enhanced metadata)
654
+ tonietoolbox input.mp3 --upload https://teddycloud.example.com --create-custom-json --version-2
655
+ ```
656
+
547
657
  ### Media Tags
548
658
 
549
659
  TonieToolbox can read metadata tags from audio files (such as ID3 tags in MP3 files, Vorbis comments in FLAC/OGG files, etc.) and use them to create more meaningful filenames or display information about your audio collection.
@@ -700,81 +810,7 @@ tonietoolbox "C:\Music\Classical\Bach" --use-media-tags --name-template "{compos
700
810
  The first command shows what tags are available, allowing you to create precise naming templates for classical music collections.
701
811
 
702
812
  ## Technical Details
703
-
704
- ### TAF (Tonie Audio Format) File Structure
705
-
706
- The Tonie Audio Format (TAF) consists of several parts:
707
-
708
- #### 1. Tonie Header (0x1000 bytes)
709
-
710
- Located at the beginning of the file, structured as:
711
-
712
- - A 4-byte big-endian integer specifying the header length
713
- - A Protocol Buffer encoded header (defined in `tonie_header.proto`)
714
- - Padding to fill the entire 4096 bytes (0x1000)
715
-
716
- The Protocol Buffer structure contains:
717
-
718
- ```protobuf
719
- message TonieHeader {
720
- bytes dataHash = 1; // SHA1 hash of the audio data
721
- uint32 dataLength = 2; // Length of the audio data in bytes
722
- uint32 timestamp = 3; // Unix timestamp (also used as bitstream serial number)
723
- repeated uint32 chapterPages = 4 [packed=true]; // Page numbers for chapter starts
724
- bytes padding = 5; // Padding to fill up the header
725
- }
726
- ```
727
-
728
- #### 2. Audio Data
729
-
730
- The audio data consists of:
731
-
732
- - Opus encoded audio in Ogg container format
733
- - Every page after the header has a fixed size of 4096 bytes (0x1000)
734
- - First page contains the Opus identification header
735
- - Second page contains the Opus comments/tags
736
- - Remaining pages contain the actual audio data
737
- - All pages use the same bitstream serial number (timestamp from header)
738
-
739
- #### 3. Special Requirements
740
-
741
- For optimal compatibility with Tonie boxes:
742
-
743
- - Audio should be stereo (2 channels)
744
- - Sample rate must be 48 kHz
745
- - Pages must be aligned to 4096 byte boundaries
746
- - Bitrate of 96 kbps VBR is recommended
747
-
748
- **Mono audio handling:**
749
-
750
- - By default, TonieToolbox will automatically convert mono audio files to stereo for compatibility.
751
- - To disable this behavior (and require your input to already be stereo), use the `--no-mono-conversion` flag.
752
-
753
- ### File Analysis
754
-
755
- When using the `--info` flag, TonieToolbox checks and displays detailed information about a .TAF (Tonie Audio File):
756
-
757
- - SHA1 hash validation
758
- - Timestamp/bitstream serial consistency
759
- - Opus data length verification
760
- - Opus header validation (version, channels, sample rate)
761
- - Page alignment and size validation
762
- - Total runtime
763
- - Track listing with durations
764
-
765
- ### File Comparison
766
-
767
- When using the `--compare` flag, TonieToolbox provides a detailed comparison of two .TAF files:
768
-
769
- - File size comparison
770
- - Header size verification
771
- - Timestamp comparison
772
- - Data length validation
773
- - SHA1 hash verification
774
- - Chapter page structure analysis
775
- - OGG page-by-page comparison (with `--detailed-compare` flag)
776
-
777
- This is particularly useful for debugging when creating TAF files with different tools or parameters.
813
+ [Moved to TECHNICAL.md](TECHNICAL.md)
778
814
 
779
815
  ## Related Projects
780
816
 
@@ -784,9 +820,6 @@ This project is inspired by and builds upon the work of other Tonie-related open
784
820
  - [teddycloud](https://github.com/toniebox-reverse-engineering/teddycloud) - Self-hosted alternative to the Tonie cloud / Boxine cloud for managing custom content
785
821
  - [TeddyCloudStarter](https://github.com/Quentendo64/TeddyCloudStarter) - A Wizard for Docker-based deployment of [teddycloud](https://github.com/toniebox-reverse-engineering/teddycloud)
786
822
 
787
- ## Contributing
788
-
789
- Contributions are welcome! Please feel free to submit a Pull Request.
790
823
 
791
824
  ## Legal Notice
792
825
 
@@ -0,0 +1,30 @@
1
+ TonieToolbox/__init__.py,sha256=HhwGRpXWdTEn172esWH5wvevmLmbNCNZhYw3mb7VH2Q,118
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=jZuv17i5qRBpDiy_jKDpDO0YxFBYGmJJgLzGpqfpPfs,3226
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=Ptzow2cuva0PkvaIlTEvcpk8_NzAbL7I80BQlfx5SZk,24012
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.0rc1.dist-info/licenses/LICENSE.md,sha256=rGoga9ZAgNco9fBapVFpWf6ri7HOBp1KRnt1uIruXMk,35190
26
+ tonietoolbox-0.6.0rc1.dist-info/METADATA,sha256=XDVnTt3hL1wFoxSAopsTj9wJweDg6tqRIStAKBb7Fmw,29087
27
+ tonietoolbox-0.6.0rc1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
28
+ tonietoolbox-0.6.0rc1.dist-info/entry_points.txt,sha256=oqpeyBxel7aScg35Xr4gZKnf486S5KW9okqeBwyJxxc,60
29
+ tonietoolbox-0.6.0rc1.dist-info/top_level.txt,sha256=Wkkm-2p7I3ENfS7ZbYtYUB2g-xwHrXVlERHfonsOPuE,13
30
+ tonietoolbox-0.6.0rc1.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,,