TonieToolbox 0.4.1__py3-none-any.whl → 0.5.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- TonieToolbox/__init__.py +1 -1
- TonieToolbox/__main__.py +227 -324
- TonieToolbox/artwork.py +105 -0
- TonieToolbox/recursive_processor.py +14 -8
- TonieToolbox/tags.py +74 -0
- TonieToolbox/teddycloud.py +250 -593
- TonieToolbox/tonie_analysis.py +173 -13
- TonieToolbox/tonies_json.py +251 -174
- TonieToolbox/version_handler.py +26 -22
- {tonietoolbox-0.4.1.dist-info → tonietoolbox-0.5.0a1.dist-info}/METADATA +7 -2
- tonietoolbox-0.5.0a1.dist-info/RECORD +26 -0
- {tonietoolbox-0.4.1.dist-info → tonietoolbox-0.5.0a1.dist-info}/WHEEL +1 -1
- tonietoolbox-0.4.1.dist-info/RECORD +0 -24
- {tonietoolbox-0.4.1.dist-info → tonietoolbox-0.5.0a1.dist-info}/entry_points.txt +0 -0
- {tonietoolbox-0.4.1.dist-info → tonietoolbox-0.5.0a1.dist-info}/licenses/LICENSE.md +0 -0
- {tonietoolbox-0.4.1.dist-info → tonietoolbox-0.5.0a1.dist-info}/top_level.txt +0 -0
TonieToolbox/artwork.py
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/python3
|
2
|
+
"""
|
3
|
+
Artwork handling functionality for TonieToolbox.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import tempfile
|
8
|
+
import shutil
|
9
|
+
from typing import List, Optional, Tuple
|
10
|
+
|
11
|
+
from .logger import get_logger
|
12
|
+
from .teddycloud import TeddyCloudClient
|
13
|
+
from .media_tags import extract_artwork, find_cover_image
|
14
|
+
|
15
|
+
|
16
|
+
def upload_artwork(client: TeddyCloudClient, taf_filename, source_path, audio_files) -> Tuple[bool, Optional[str]]:
|
17
|
+
"""
|
18
|
+
Find and upload artwork for a Tonie file.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
client: TeddyCloudClient instance to use for API communication
|
22
|
+
taf_filename: The filename of the Tonie file (.taf)
|
23
|
+
source_path: Source directory to look for artwork
|
24
|
+
audio_files: List of audio files to extract artwork from if needed
|
25
|
+
Returns:
|
26
|
+
tuple: (success, artwork_url) where success is a boolean and artwork_url is the URL of the uploaded artwork
|
27
|
+
"""
|
28
|
+
logger = get_logger('artwork')
|
29
|
+
logger.info("Looking for artwork for Tonie file: %s", taf_filename)
|
30
|
+
taf_basename = os.path.basename(taf_filename)
|
31
|
+
taf_name = os.path.splitext(taf_basename)[0]
|
32
|
+
artwork_path = None
|
33
|
+
temp_artwork = None
|
34
|
+
artwork_path = find_cover_image(source_path)
|
35
|
+
if not artwork_path and audio_files and len(audio_files) > 0:
|
36
|
+
logger.info("No cover image found, trying to extract from audio files")
|
37
|
+
temp_artwork = extract_artwork(audio_files[0])
|
38
|
+
if temp_artwork:
|
39
|
+
artwork_path = temp_artwork
|
40
|
+
logger.info("Extracted artwork from audio file: %s", temp_artwork)
|
41
|
+
|
42
|
+
if not artwork_path:
|
43
|
+
logger.warning("No artwork found for %s", source_path)
|
44
|
+
return False, None
|
45
|
+
|
46
|
+
logger.info("Found artwork: %s", artwork_path)
|
47
|
+
artwork_upload_path = "/custom_img"
|
48
|
+
artwork_ext = os.path.splitext(artwork_path)[1]
|
49
|
+
renamed_artwork_path = None
|
50
|
+
upload_success = False
|
51
|
+
artwork_url = None
|
52
|
+
|
53
|
+
try:
|
54
|
+
renamed_artwork_path = os.path.join(os.path.dirname(artwork_path),
|
55
|
+
f"{taf_name}{artwork_ext}")
|
56
|
+
|
57
|
+
if renamed_artwork_path != artwork_path:
|
58
|
+
shutil.copy(artwork_path, renamed_artwork_path)
|
59
|
+
logger.debug("Created renamed artwork copy: %s", renamed_artwork_path)
|
60
|
+
|
61
|
+
logger.info("Uploading artwork to path: %s as %s%s",
|
62
|
+
artwork_upload_path, taf_name, artwork_ext)
|
63
|
+
try:
|
64
|
+
response = client.upload_file(
|
65
|
+
file_path=renamed_artwork_path,
|
66
|
+
destination_path=artwork_upload_path,
|
67
|
+
special="library"
|
68
|
+
)
|
69
|
+
upload_success = response.get('success', False)
|
70
|
+
|
71
|
+
if not upload_success:
|
72
|
+
logger.error("Failed to upload %s to TeddyCloud", renamed_artwork_path)
|
73
|
+
else:
|
74
|
+
logger.info("Successfully uploaded %s to TeddyCloud", renamed_artwork_path)
|
75
|
+
logger.debug("Upload response: %s", response)
|
76
|
+
except Exception as e:
|
77
|
+
logger.error("Error uploading artwork: %s", e)
|
78
|
+
upload_success = False
|
79
|
+
|
80
|
+
if upload_success:
|
81
|
+
if not artwork_upload_path.endswith('/'):
|
82
|
+
artwork_upload_path += '/'
|
83
|
+
artwork_url = f"{artwork_upload_path}{taf_name}{artwork_ext}"
|
84
|
+
logger.debug("Artwork URL: %s", artwork_url)
|
85
|
+
|
86
|
+
except Exception as e:
|
87
|
+
logger.error("Error during artwork handling: %s", e)
|
88
|
+
upload_success = False
|
89
|
+
|
90
|
+
finally:
|
91
|
+
if renamed_artwork_path != artwork_path and renamed_artwork_path and os.path.exists(renamed_artwork_path):
|
92
|
+
try:
|
93
|
+
os.unlink(renamed_artwork_path)
|
94
|
+
logger.debug("Removed temporary renamed artwork file: %s", renamed_artwork_path)
|
95
|
+
except Exception as e:
|
96
|
+
logger.debug("Failed to remove temporary renamed artwork file: %s", e)
|
97
|
+
if temp_artwork and os.path.exists(temp_artwork):
|
98
|
+
try:
|
99
|
+
if temp_artwork.startswith(tempfile.gettempdir()):
|
100
|
+
os.unlink(temp_artwork)
|
101
|
+
logger.debug("Removed temporary extracted artwork file: %s", temp_artwork)
|
102
|
+
except Exception as e:
|
103
|
+
logger.debug("Failed to remove temporary artwork file: %s", e)
|
104
|
+
|
105
|
+
return upload_success, artwork_url
|
@@ -290,20 +290,26 @@ def get_folder_name_from_metadata(folder_path: str, use_media_tags: bool = False
|
|
290
290
|
return output_name
|
291
291
|
|
292
292
|
|
293
|
-
def process_recursive_folders(root_path
|
294
|
-
name_template: str = None) -> List[Tuple[str, str, List[str]]]:
|
293
|
+
def process_recursive_folders(root_path, use_media_tags=False, name_template=None):
|
295
294
|
"""
|
296
|
-
Process folders recursively
|
295
|
+
Process folders recursively for audio files to create Tonie files.
|
297
296
|
|
298
297
|
Args:
|
299
|
-
root_path:
|
300
|
-
use_media_tags: Whether to use media tags
|
301
|
-
name_template:
|
302
|
-
|
298
|
+
root_path (str): The root path to start processing from
|
299
|
+
use_media_tags (bool): Whether to use media tags for naming
|
300
|
+
name_template (str): Template for naming files using media tags
|
301
|
+
|
303
302
|
Returns:
|
304
|
-
|
303
|
+
list: A list of tuples (output_name, folder_path, audio_files)
|
305
304
|
"""
|
305
|
+
logger = get_logger("recursive_processor")
|
306
306
|
logger.info("Processing folders recursively: %s", root_path)
|
307
|
+
# Make sure the path exists
|
308
|
+
if not os.path.exists(root_path):
|
309
|
+
logger.error("Path does not exist: %s", root_path)
|
310
|
+
return []
|
311
|
+
|
312
|
+
logger.info("Finding folders with audio files in: %s", root_path)
|
307
313
|
|
308
314
|
# Get folder info with hierarchy details
|
309
315
|
all_folders = find_audio_folders(root_path)
|
TonieToolbox/tags.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/python3
|
2
|
+
"""
|
3
|
+
TonieToolbox - Tags handling functionality.
|
4
|
+
This module provides functionality to retrieve and display tags from a TeddyCloud instance.
|
5
|
+
"""
|
6
|
+
from .logger import get_logger
|
7
|
+
from .teddycloud import TeddyCloudClient
|
8
|
+
import json
|
9
|
+
from typing import Optional, Union
|
10
|
+
|
11
|
+
logger = get_logger('tags')
|
12
|
+
|
13
|
+
def get_tags(client: TeddyCloudClient) -> bool:
|
14
|
+
"""
|
15
|
+
Get and display tags from a TeddyCloud instance.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
client: TeddyCloudClient instance to use for API communication
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
True if tags were retrieved successfully, False otherwise
|
22
|
+
"""
|
23
|
+
logger.info("Getting tags from TeddyCloud using provided client")
|
24
|
+
|
25
|
+
response = client.get_tag_index()
|
26
|
+
|
27
|
+
if not response:
|
28
|
+
logger.error("Failed to retrieve tags from TeddyCloud")
|
29
|
+
return False
|
30
|
+
if isinstance(response, dict) and 'tags' in response:
|
31
|
+
tags = response['tags']
|
32
|
+
logger.info("Successfully retrieved %d tags from TeddyCloud", len(tags))
|
33
|
+
|
34
|
+
print("\nAvailable Tags from TeddyCloud:")
|
35
|
+
print("-" * 60)
|
36
|
+
|
37
|
+
sorted_tags = sorted(tags, key=lambda x: (x.get('type', ''), x.get('uid', '')))
|
38
|
+
|
39
|
+
for tag in sorted_tags:
|
40
|
+
uid = tag.get('uid', 'Unknown UID')
|
41
|
+
tag_type = tag.get('type', 'Unknown')
|
42
|
+
valid = "✓" if tag.get('valid', False) else "✗"
|
43
|
+
series = tag.get('tonieInfo', {}).get('series', '')
|
44
|
+
episode = tag.get('tonieInfo', {}).get('episode', '')
|
45
|
+
source = tag.get('source', '')
|
46
|
+
print(f"UID: {uid} ({tag_type}) - Valid: {valid}")
|
47
|
+
if series:
|
48
|
+
print(f"Series: {series}")
|
49
|
+
if episode:
|
50
|
+
print(f"Episode: {episode}")
|
51
|
+
if source:
|
52
|
+
print(f"Source: {source}")
|
53
|
+
tracks = tag.get('tonieInfo', {}).get('tracks', [])
|
54
|
+
if tracks:
|
55
|
+
print("Tracks:")
|
56
|
+
for i, track in enumerate(tracks, 1):
|
57
|
+
print(f" {i}. {track}")
|
58
|
+
track_seconds = tag.get('trackSeconds', [])
|
59
|
+
if track_seconds and len(track_seconds) > 1:
|
60
|
+
total_seconds = track_seconds[-1]
|
61
|
+
minutes = total_seconds // 60
|
62
|
+
seconds = total_seconds % 60
|
63
|
+
print(f"Duration: {minutes}:{seconds:02d} ({len(track_seconds)-1} tracks)")
|
64
|
+
|
65
|
+
print("-" * 60)
|
66
|
+
else:
|
67
|
+
logger.info("Successfully retrieved tag data from TeddyCloud")
|
68
|
+
print("\nTag data from TeddyCloud:")
|
69
|
+
print("-" * 60)
|
70
|
+
print(json.dumps(response, indent=2))
|
71
|
+
|
72
|
+
print("-" * 60)
|
73
|
+
|
74
|
+
return True
|