TonieToolbox 0.6.0a3__tar.gz → 0.6.0a5__tar.gz
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-0.6.0a3/TonieToolbox.egg-info → tonietoolbox-0.6.0a5}/PKG-INFO +6 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/README.md +3 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/__init__.py +2 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/__main__.py +162 -27
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/artwork.py +47 -3
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/audio_conversion.py +2 -1
- tonietoolbox-0.6.0a5/TonieToolbox/constants.py +213 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/dependency_manager.py +675 -185
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/filename_generator.py +51 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/integration.py +7 -6
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/integration_macos.py +15 -5
- tonietoolbox-0.6.0a5/TonieToolbox/integration_ubuntu.py +2 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/integration_windows.py +12 -4
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/logger.py +1 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/media_tags.py +2 -3
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/ogg_page.py +2 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/opus_packet.py +2 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/recursive_processor.py +2 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tags.py +1 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/teddycloud.py +114 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tonie_analysis.py +2 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tonie_file.py +2 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tonies_json.py +2 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/version_handler.py +2 -5
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5/TonieToolbox.egg-info}/PKG-INFO +6 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox.egg-info/requires.txt +1 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/pyproject.toml +3 -2
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/setup.py +5 -2
- tonietoolbox-0.6.0a3/TonieToolbox/constants.py +0 -209
- tonietoolbox-0.6.0a3/TonieToolbox/integration_ubuntu.py +0 -1
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/LICENSE.md +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/MANIFEST.in +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tonie_header.proto +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox/tonie_header_pb2.py +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox.egg-info/SOURCES.txt +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox.egg-info/dependency_links.txt +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox.egg-info/entry_points.txt +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/TonieToolbox.egg-info/top_level.txt +0 -0
- {tonietoolbox-0.6.0a3 → tonietoolbox-0.6.0a5}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: TonieToolbox
|
3
|
-
Version: 0.6.
|
4
|
-
Summary:
|
3
|
+
Version: 0.6.0a5
|
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
|
7
7
|
Author-email: Quentendo64 <quentin@wohlfeil.at>
|
@@ -23,6 +23,7 @@ Requires-Dist: protobuf<=3.19.0
|
|
23
23
|
Requires-Dist: requests>=2.32.3
|
24
24
|
Requires-Dist: mutagen>=1.47.0
|
25
25
|
Requires-Dist: packaging>=25.0
|
26
|
+
Requires-Dist: tqdm>=4.67.1
|
26
27
|
Provides-Extra: test
|
27
28
|
Requires-Dist: pytest>=8.3.5; extra == "test"
|
28
29
|
Requires-Dist: pytest-cov>=6.1.1; extra == "test"
|
@@ -806,3 +807,6 @@ If you need help, have questions, or want to report a bug, please use the follow
|
|
806
807
|
- [GitHub Issues](https://github.com/Quentendo64/TonieToolbox/issues) for bug reports and feature requests
|
807
808
|
- [GitHub Discussions](https://github.com/Quentendo64/TonieToolbox/discussions) for general questions and community support
|
808
809
|
- [HOWTO Guide](HOWTO.md) for common usage instructions
|
810
|
+
|
811
|
+
## Attribution
|
812
|
+
[Parrot Icon created by Freepik - Flaticon](https://www.flaticon.com/free-animated-icons/parrot)
|
@@ -773,3 +773,6 @@ If you need help, have questions, or want to report a bug, please use the follow
|
|
773
773
|
- [GitHub Issues](https://github.com/Quentendo64/TonieToolbox/issues) for bug reports and feature requests
|
774
774
|
- [GitHub Discussions](https://github.com/Quentendo64/TonieToolbox/discussions) for general questions and community support
|
775
775
|
- [HOWTO Guide](HOWTO.md) for common usage instructions
|
776
|
+
|
777
|
+
## Attribution
|
778
|
+
[Parrot Icon created by Freepik - Flaticon](https://www.flaticon.com/free-animated-icons/parrot)
|
@@ -11,9 +11,9 @@ from . import __version__
|
|
11
11
|
from .audio_conversion import get_input_files, append_to_filename
|
12
12
|
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
|
-
from .dependency_manager import get_ffmpeg_binary, get_opus_binary
|
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: "{
|
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 -------------
|
@@ -135,7 +137,7 @@ def main():
|
|
135
137
|
args = parser.parse_args()
|
136
138
|
|
137
139
|
# ------------- Parser - Source Input -------------
|
138
|
-
if args.input_filename is None and not (args.get_tags or args.upload or args.install_integration or args.uninstall_integration or args.config_integration):
|
140
|
+
if args.input_filename is None and not (args.get_tags or args.upload or args.install_integration or args.uninstall_integration or args.config_integration or args.auto_download):
|
139
141
|
parser.error("the following arguments are required: SOURCE")
|
140
142
|
|
141
143
|
# ------------- Logging -------------
|
@@ -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(
|
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
|
|
@@ -174,19 +176,37 @@ def main():
|
|
174
176
|
|
175
177
|
if not is_latest and not update_confirmed and not (args.silent or args.quiet):
|
176
178
|
logger.info("Update available but user chose to continue without updating.")
|
179
|
+
|
180
|
+
# ------------- Autodownload & Dependency Checks -------------
|
181
|
+
if args.auto_download:
|
182
|
+
logger.debug("Auto-download requested for ffmpeg and opusenc")
|
183
|
+
ffmpeg_binary = get_ffmpeg_binary(auto_download=True)
|
184
|
+
opus_binary = get_opus_binary(auto_download=True)
|
185
|
+
if ffmpeg_binary and opus_binary:
|
186
|
+
logger.info("FFmpeg and opusenc downloaded successfully.")
|
187
|
+
if args.input_filename is None:
|
188
|
+
sys.exit(0)
|
189
|
+
else:
|
190
|
+
logger.error("Failed to download ffmpeg or opusenc. Please install them manually.")
|
191
|
+
sys.exit(1)
|
192
|
+
|
177
193
|
# ------------- Context Menu Integration -------------
|
178
194
|
if args.install_integration or args.uninstall_integration:
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
if
|
184
|
-
|
195
|
+
if ensure_dependency('ffmpeg') and ensure_dependency('opusenc'):
|
196
|
+
logger.debug("Context menu integration requested: install=%s, uninstall=%s",
|
197
|
+
args.install_integration, args.uninstall_integration)
|
198
|
+
success = handle_integration(args)
|
199
|
+
if success:
|
200
|
+
if args.install_integration:
|
201
|
+
logger.info("Context menu integration installed successfully")
|
202
|
+
else:
|
203
|
+
logger.info("Context menu integration uninstalled successfully")
|
185
204
|
else:
|
186
|
-
logger.
|
205
|
+
logger.error("Failed to handle context menu integration")
|
206
|
+
sys.exit(0)
|
187
207
|
else:
|
188
|
-
logger.error("
|
189
|
-
|
208
|
+
logger.error("FFmpeg and opusenc are required for context menu integration")
|
209
|
+
sys.exit(1)
|
190
210
|
if args.config_integration:
|
191
211
|
logger.debug("Opening configuration file for editing")
|
192
212
|
handle_config()
|
@@ -261,8 +281,33 @@ def main():
|
|
261
281
|
file_path, file_size, file_ext)
|
262
282
|
logger.info("Uploading %s to TeddyCloud %s", file_path, teddycloud_url)
|
263
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
|
+
|
264
309
|
response = client.upload_file(
|
265
|
-
destination_path=
|
310
|
+
destination_path=upload_path,
|
266
311
|
file_path=file_path,
|
267
312
|
special=args.special_folder,
|
268
313
|
)
|
@@ -465,25 +510,25 @@ def main():
|
|
465
510
|
|
466
511
|
guessed_name = None
|
467
512
|
if args.use_media_tags:
|
513
|
+
logger.debug("Using media tags for naming")
|
468
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")
|
469
516
|
folder_path = os.path.dirname(files[0])
|
470
517
|
logger.debug("Extracting album info from folder: %s", folder_path)
|
471
518
|
album_info = extract_album_info(folder_path)
|
472
519
|
if album_info:
|
473
|
-
template = args.name_template or "{
|
474
|
-
new_name = format_metadata_filename(album_info, template)
|
475
|
-
|
520
|
+
template = args.name_template or "{artist} - {album}"
|
521
|
+
new_name = format_metadata_filename(album_info, template)
|
476
522
|
if new_name:
|
477
523
|
logger.info("Using album metadata for output filename: %s", new_name)
|
478
524
|
guessed_name = new_name
|
479
525
|
else:
|
480
526
|
logger.debug("Could not format filename from album metadata")
|
481
527
|
elif len(files) == 1:
|
482
|
-
|
483
|
-
|
484
528
|
tags = get_file_tags(files[0])
|
485
529
|
if tags:
|
486
|
-
|
530
|
+
logger.debug("")
|
531
|
+
template = args.name_template or "{artist} - {title}"
|
487
532
|
new_name = format_metadata_filename(tags, template)
|
488
533
|
|
489
534
|
if new_name:
|
@@ -520,20 +565,71 @@ def main():
|
|
520
565
|
else:
|
521
566
|
logger.debug("Could not format filename from common metadata")
|
522
567
|
|
523
|
-
if args.output_filename:
|
568
|
+
if args.output_filename:
|
524
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
|
525
620
|
elif guessed_name:
|
621
|
+
logger.debug("Using guessed name for output: %s", guessed_name)
|
526
622
|
if args.output_to_source:
|
527
623
|
source_dir = os.path.dirname(files[0]) if files else '.'
|
528
624
|
out_filename = os.path.join(source_dir, guessed_name)
|
529
|
-
logger.debug("Using source location for output
|
625
|
+
logger.debug("Using source location for output: %s", out_filename)
|
530
626
|
else:
|
531
627
|
output_dir = './output'
|
532
628
|
if not os.path.exists(output_dir):
|
533
629
|
logger.debug("Creating default output directory: %s", output_dir)
|
534
630
|
os.makedirs(output_dir, exist_ok=True)
|
535
631
|
out_filename = os.path.join(output_dir, guessed_name)
|
536
|
-
logger.debug("Using default output location
|
632
|
+
logger.debug("Using default output location: %s", out_filename)
|
537
633
|
else:
|
538
634
|
guessed_name = guess_output_filename(args.input_filename, files)
|
539
635
|
if args.output_to_source:
|
@@ -563,6 +659,7 @@ def main():
|
|
563
659
|
|
564
660
|
if not out_filename.lower().endswith('.taf'):
|
565
661
|
out_filename += '.taf'
|
662
|
+
ensure_directory_exists(out_filename)
|
566
663
|
|
567
664
|
logger.info("Creating Tonie file: %s with %d input file(s)", out_filename, len(files))
|
568
665
|
create_tonie_file(out_filename, files, args.no_tonie_header, args.user_timestamp,
|
@@ -573,10 +670,48 @@ def main():
|
|
573
670
|
|
574
671
|
# ------------- Single File Upload -------------
|
575
672
|
artwork_url = None
|
576
|
-
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
|
+
|
577
712
|
response = client.upload_file(
|
578
713
|
file_path=out_filename,
|
579
|
-
destination_path=
|
714
|
+
destination_path=upload_path,
|
580
715
|
special=args.special_folder,
|
581
716
|
)
|
582
717
|
upload_success = response.get('success', False)
|
@@ -4,6 +4,7 @@ Artwork handling functionality for TonieToolbox.
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import os
|
7
|
+
import base64
|
7
8
|
import tempfile
|
8
9
|
import shutil
|
9
10
|
from typing import List, Optional, Tuple
|
@@ -12,6 +13,7 @@ from .logger import get_logger
|
|
12
13
|
from .teddycloud import TeddyCloudClient
|
13
14
|
from .media_tags import extract_artwork, find_cover_image
|
14
15
|
|
16
|
+
logger = get_logger(__name__)
|
15
17
|
|
16
18
|
def upload_artwork(
|
17
19
|
client: TeddyCloudClient,
|
@@ -29,8 +31,7 @@ def upload_artwork(
|
|
29
31
|
audio_files (list[str]): List of audio files to extract artwork from if needed
|
30
32
|
Returns:
|
31
33
|
tuple[bool, Optional[str]]: (success, artwork_url) where success is a boolean and artwork_url is the URL of the uploaded artwork
|
32
|
-
"""
|
33
|
-
logger = get_logger('artwork')
|
34
|
+
"""
|
34
35
|
logger.info("Looking for artwork for Tonie file: %s", taf_filename)
|
35
36
|
taf_basename = os.path.basename(taf_filename)
|
36
37
|
taf_name = os.path.splitext(taf_basename)[0]
|
@@ -107,4 +108,47 @@ def upload_artwork(
|
|
107
108
|
except Exception as e:
|
108
109
|
logger.debug("Failed to remove temporary artwork file: %s", e)
|
109
110
|
|
110
|
-
return upload_success, artwork_url
|
111
|
+
return upload_success, artwork_url
|
112
|
+
|
113
|
+
def ico_to_base64(ico_path):
|
114
|
+
"""
|
115
|
+
Convert an ICO file to a base64 string
|
116
|
+
|
117
|
+
Args:
|
118
|
+
ico_path: Path to the ICO file
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Base64 encoded string of the ICO file
|
122
|
+
"""
|
123
|
+
if not os.path.exists(ico_path):
|
124
|
+
raise FileNotFoundError(f"ICO file not found: {ico_path}")
|
125
|
+
|
126
|
+
with open(ico_path, "rb") as ico_file:
|
127
|
+
ico_bytes = ico_file.read()
|
128
|
+
|
129
|
+
base64_string = base64.b64encode(ico_bytes).decode('utf-8')
|
130
|
+
return base64_string
|
131
|
+
|
132
|
+
|
133
|
+
def base64_to_ico(base64_string, output_path):
|
134
|
+
"""
|
135
|
+
Convert a base64 string back to an ICO file
|
136
|
+
|
137
|
+
Args:
|
138
|
+
base64_string: Base64 encoded string of the ICO file
|
139
|
+
output_path: Path where to save the ICO file
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Path to the saved ICO file
|
143
|
+
"""
|
144
|
+
ico_bytes = base64.b64decode(base64_string)
|
145
|
+
|
146
|
+
# Create directory if it doesn't exist
|
147
|
+
output_dir = os.path.dirname(output_path)
|
148
|
+
if output_dir and not os.path.exists(output_dir):
|
149
|
+
os.makedirs(output_dir)
|
150
|
+
|
151
|
+
with open(output_path, "wb") as ico_file:
|
152
|
+
ico_file.write(ico_bytes)
|
153
|
+
|
154
|
+
return output_path
|
@@ -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(
|
14
|
+
logger = get_logger(__name__)
|
14
15
|
|
15
16
|
|
16
17
|
def get_opus_tempfile(
|