TonieToolbox 0.5.1__py3-none-any.whl → 0.6.0a2__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 +91 -84
- TonieToolbox/artwork.py +12 -7
- TonieToolbox/audio_conversion.py +31 -28
- TonieToolbox/constants.py +110 -9
- TonieToolbox/filename_generator.py +6 -8
- TonieToolbox/integration.py +68 -0
- TonieToolbox/integration_macos.py +477 -0
- TonieToolbox/integration_ubuntu.py +1 -0
- TonieToolbox/integration_windows.py +437 -0
- TonieToolbox/logger.py +8 -10
- TonieToolbox/media_tags.py +17 -97
- TonieToolbox/ogg_page.py +39 -39
- TonieToolbox/opus_packet.py +13 -13
- TonieToolbox/recursive_processor.py +22 -22
- TonieToolbox/tags.py +3 -4
- TonieToolbox/teddycloud.py +50 -50
- TonieToolbox/tonie_analysis.py +24 -23
- TonieToolbox/tonie_file.py +71 -44
- TonieToolbox/tonies_json.py +69 -66
- TonieToolbox/version_handler.py +12 -15
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a2.dist-info}/METADATA +3 -3
- tonietoolbox-0.6.0a2.dist-info/RECORD +30 -0
- tonietoolbox-0.5.1.dist-info/RECORD +0 -26
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a2.dist-info}/WHEEL +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a2.dist-info}/entry_points.txt +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a2.dist-info}/licenses/LICENSE.md +0 -0
- {tonietoolbox-0.5.1.dist-info → tonietoolbox-0.6.0a2.dist-info}/top_level.txt +0 -0
TonieToolbox/tonie_file.py
CHANGED
@@ -18,14 +18,14 @@ from .logger import get_logger
|
|
18
18
|
logger = get_logger('tonie_file')
|
19
19
|
|
20
20
|
|
21
|
-
def toniefile_comment_add(buffer, length, comment_str):
|
21
|
+
def toniefile_comment_add(buffer: bytearray, length: int, comment_str: str) -> int:
|
22
22
|
"""
|
23
23
|
Add a comment string to an Opus comment packet buffer.
|
24
24
|
|
25
25
|
Args:
|
26
|
-
buffer: Bytearray buffer to add comment to
|
27
|
-
length: Current position in the buffer
|
28
|
-
comment_str: Comment string to add
|
26
|
+
buffer (bytearray): Bytearray buffer to add comment to
|
27
|
+
length (int): Current position in the buffer
|
28
|
+
comment_str (str): Comment string to add
|
29
29
|
|
30
30
|
Returns:
|
31
31
|
int: New position in the buffer after adding comment
|
@@ -44,7 +44,7 @@ def toniefile_comment_add(buffer, length, comment_str):
|
|
44
44
|
return length
|
45
45
|
|
46
46
|
|
47
|
-
def check_identification_header(page):
|
47
|
+
def check_identification_header(page) -> None:
|
48
48
|
"""
|
49
49
|
Check if a page contains a valid Opus identification header.
|
50
50
|
|
@@ -77,16 +77,16 @@ def check_identification_header(page):
|
|
77
77
|
logger.debug("Opus identification header is valid")
|
78
78
|
|
79
79
|
|
80
|
-
def prepare_opus_tags(page, custom_tags=False, bitrate=64, vbr=True, opus_binary=None):
|
80
|
+
def prepare_opus_tags(page, custom_tags: bool = False, bitrate: int = 64, vbr: bool = True, opus_binary: str = None) -> OggPage:
|
81
81
|
"""
|
82
82
|
Prepare standard Opus tags for a Tonie file.
|
83
83
|
|
84
84
|
Args:
|
85
85
|
page: OggPage to modify
|
86
|
-
custom_tags: Whether to use custom TonieToolbox tags instead of default ones
|
87
|
-
bitrate: Actual bitrate used for encoding
|
88
|
-
vbr: Whether variable bitrate was used
|
89
|
-
opus_binary: Path to opusenc binary for version detection
|
86
|
+
custom_tags (bool): Whether to use custom TonieToolbox tags instead of default ones
|
87
|
+
bitrate (int): Actual bitrate used for encoding
|
88
|
+
vbr (bool): Whether variable bitrate was used
|
89
|
+
opus_binary (str | None): Path to opusenc binary for version detection
|
90
90
|
|
91
91
|
Returns:
|
92
92
|
OggPage: Modified page with Tonie-compatible Opus tags
|
@@ -162,19 +162,28 @@ def prepare_opus_tags(page, custom_tags=False, bitrate=64, vbr=True, opus_binary
|
|
162
162
|
return page
|
163
163
|
|
164
164
|
|
165
|
-
def copy_first_and_second_page(
|
165
|
+
def copy_first_and_second_page(
|
166
|
+
in_file,
|
167
|
+
out_file,
|
168
|
+
timestamp: int,
|
169
|
+
sha,
|
170
|
+
use_custom_tags: bool = True,
|
171
|
+
bitrate: int = 64,
|
172
|
+
vbr: bool = True,
|
173
|
+
opus_binary: str = None
|
174
|
+
) -> None:
|
166
175
|
"""
|
167
176
|
Copy and modify the first two pages of an Opus file for a Tonie file.
|
168
177
|
|
169
178
|
Args:
|
170
179
|
in_file: Input file handle
|
171
180
|
out_file: Output file handle
|
172
|
-
timestamp: Timestamp to use for the Tonie file
|
181
|
+
timestamp (int): Timestamp to use for the Tonie file
|
173
182
|
sha: SHA1 hash object to update with written data
|
174
|
-
use_custom_tags: Whether to use custom TonieToolbox tags
|
175
|
-
bitrate: Actual bitrate used for encoding
|
176
|
-
vbr: Whether VBR was used
|
177
|
-
opus_binary: Path to opusenc binary
|
183
|
+
use_custom_tags (bool): Whether to use custom TonieToolbox tags
|
184
|
+
bitrate (int): Actual bitrate used for encoding
|
185
|
+
vbr (bool): Whether VBR was used
|
186
|
+
opus_binary (str | None): Path to opusenc binary
|
178
187
|
"""
|
179
188
|
logger.debug("Copying first and second pages with timestamp %d", timestamp)
|
180
189
|
found = OggPage.seek_to_page_header(in_file)
|
@@ -202,7 +211,7 @@ def copy_first_and_second_page(in_file, out_file, timestamp, sha, use_custom_tag
|
|
202
211
|
logger.debug("Second page written successfully")
|
203
212
|
|
204
213
|
|
205
|
-
def skip_first_two_pages(in_file):
|
214
|
+
def skip_first_two_pages(in_file) -> None:
|
206
215
|
"""
|
207
216
|
Skip the first two pages of an Opus file.
|
208
217
|
|
@@ -235,7 +244,7 @@ def skip_first_two_pages(in_file):
|
|
235
244
|
logger.debug("First two pages skipped successfully")
|
236
245
|
|
237
246
|
|
238
|
-
def read_all_remaining_pages(in_file):
|
247
|
+
def read_all_remaining_pages(in_file) -> list:
|
239
248
|
"""
|
240
249
|
Read all remaining OGG pages from an input file.
|
241
250
|
|
@@ -260,19 +269,26 @@ def read_all_remaining_pages(in_file):
|
|
260
269
|
return remaining_pages
|
261
270
|
|
262
271
|
|
263
|
-
def resize_pages(
|
264
|
-
|
272
|
+
def resize_pages(
|
273
|
+
old_pages: list,
|
274
|
+
max_page_size: int,
|
275
|
+
first_page_size: int,
|
276
|
+
template_page,
|
277
|
+
last_granule: int = 0,
|
278
|
+
start_no: int = 2,
|
279
|
+
set_last_page_flag: bool = False
|
280
|
+
) -> list:
|
265
281
|
"""
|
266
282
|
Resize OGG pages to fit Tonie requirements.
|
267
283
|
|
268
284
|
Args:
|
269
|
-
old_pages: List of original OggPage objects
|
270
|
-
max_page_size: Maximum size for pages
|
271
|
-
first_page_size: Size for the first page
|
285
|
+
old_pages (list): List of original OggPage objects
|
286
|
+
max_page_size (int): Maximum size for pages
|
287
|
+
first_page_size (int): Size for the first page
|
272
288
|
template_page: Template OggPage to use for creating new pages
|
273
|
-
last_granule: Last granule position
|
274
|
-
start_no: Starting page number
|
275
|
-
set_last_page_flag: Whether to set the last page flag
|
289
|
+
last_granule (int): Last granule position
|
290
|
+
start_no (int): Starting page number
|
291
|
+
set_last_page_flag (bool): Whether to set the last page flag
|
276
292
|
|
277
293
|
Returns:
|
278
294
|
list: List of resized OggPage objects
|
@@ -326,14 +342,14 @@ def resize_pages(old_pages, max_page_size, first_page_size, template_page, last_
|
|
326
342
|
return new_pages
|
327
343
|
|
328
344
|
|
329
|
-
def fix_tonie_header(out_file, chapters, timestamp, sha):
|
345
|
+
def fix_tonie_header(out_file, chapters: list, timestamp: int, sha) -> None:
|
330
346
|
"""
|
331
347
|
Fix the Tonie header in a file.
|
332
348
|
|
333
349
|
Args:
|
334
350
|
out_file: Output file handle
|
335
|
-
chapters: List of chapter page numbers
|
336
|
-
timestamp: Timestamp for the Tonie file
|
351
|
+
chapters (list): List of chapter page numbers
|
352
|
+
timestamp (int): Timestamp for the Tonie file
|
337
353
|
sha: SHA1 hash object with file content
|
338
354
|
"""
|
339
355
|
logger.info("Writing Tonie header with %d chapters and timestamp %d", len(chapters), timestamp)
|
@@ -362,25 +378,36 @@ def fix_tonie_header(out_file, chapters, timestamp, sha):
|
|
362
378
|
logger.debug("Tonie header written successfully (size: %d bytes)", len(header))
|
363
379
|
|
364
380
|
|
365
|
-
def create_tonie_file(
|
366
|
-
|
367
|
-
|
381
|
+
def create_tonie_file(
|
382
|
+
output_file: str,
|
383
|
+
input_files: list[str],
|
384
|
+
no_tonie_header: bool = False,
|
385
|
+
user_timestamp: str = None,
|
386
|
+
bitrate: int = 96,
|
387
|
+
vbr: bool = True,
|
388
|
+
ffmpeg_binary: str = None,
|
389
|
+
opus_binary: str = None,
|
390
|
+
keep_temp: bool = False,
|
391
|
+
auto_download: bool = False,
|
392
|
+
use_custom_tags: bool = True,
|
393
|
+
no_mono_conversion: bool = False
|
394
|
+
) -> None:
|
368
395
|
"""
|
369
396
|
Create a Tonie file from input files.
|
370
397
|
|
371
398
|
Args:
|
372
|
-
output_file: Output file path
|
373
|
-
input_files: List of input file paths
|
374
|
-
no_tonie_header: Whether to omit the Tonie header
|
375
|
-
user_timestamp: Custom timestamp to use
|
376
|
-
bitrate: Bitrate for encoding in kbps
|
377
|
-
vbr: Whether to use variable bitrate encoding (True) or constant (False)
|
378
|
-
ffmpeg_binary: Path to ffmpeg binary
|
379
|
-
opus_binary: Path to opusenc binary
|
380
|
-
keep_temp: Whether to keep temporary opus files for testing
|
381
|
-
auto_download: Whether to automatically download dependencies if not found
|
382
|
-
use_custom_tags: Whether to use dynamic comment tags generated with toniefile_comment_add
|
383
|
-
no_mono_conversion: Whether to skip mono conversion during audio processing
|
399
|
+
output_file (str): Output file path
|
400
|
+
input_files (list[str]): List of input file paths
|
401
|
+
no_tonie_header (bool): Whether to omit the Tonie header
|
402
|
+
user_timestamp (str | None): Custom timestamp to use
|
403
|
+
bitrate (int): Bitrate for encoding in kbps
|
404
|
+
vbr (bool): Whether to use variable bitrate encoding (True) or constant (False)
|
405
|
+
ffmpeg_binary (str | None): Path to ffmpeg binary
|
406
|
+
opus_binary (str | None): Path to opusenc binary
|
407
|
+
keep_temp (bool): Whether to keep temporary opus files for testing
|
408
|
+
auto_download (bool): Whether to automatically download dependencies if not found
|
409
|
+
use_custom_tags (bool): Whether to use dynamic comment tags generated with toniefile_comment_add
|
410
|
+
no_mono_conversion (bool): Whether to skip mono conversion during audio processing
|
384
411
|
"""
|
385
412
|
from .audio_conversion import get_opus_tempfile
|
386
413
|
|
TonieToolbox/tonies_json.py
CHANGED
@@ -29,7 +29,7 @@ class ToniesJsonHandlerv1:
|
|
29
29
|
Initialize the handler.
|
30
30
|
|
31
31
|
Args:
|
32
|
-
client: TeddyCloudClient instance to use for API communication
|
32
|
+
client (TeddyCloudClient | None): TeddyCloudClient instance to use for API communication
|
33
33
|
"""
|
34
34
|
self.client = client
|
35
35
|
self.custom_json = []
|
@@ -40,7 +40,7 @@ class ToniesJsonHandlerv1:
|
|
40
40
|
Load tonies.custom.json from the TeddyCloud server.
|
41
41
|
|
42
42
|
Returns:
|
43
|
-
True if successful, False otherwise
|
43
|
+
bool: True if successful, False otherwise
|
44
44
|
"""
|
45
45
|
if self.client is None:
|
46
46
|
logger.error("Cannot load from server: no client provided")
|
@@ -71,10 +71,10 @@ class ToniesJsonHandlerv1:
|
|
71
71
|
Load tonies.custom.json from a local file.
|
72
72
|
|
73
73
|
Args:
|
74
|
-
file_path: Path to the tonies.custom.json file
|
74
|
+
file_path (str): Path to the tonies.custom.json file
|
75
75
|
|
76
76
|
Returns:
|
77
|
-
True if successful, False otherwise
|
77
|
+
bool: True if successful, False otherwise
|
78
78
|
"""
|
79
79
|
try:
|
80
80
|
if os.path.exists(file_path):
|
@@ -109,10 +109,10 @@ class ToniesJsonHandlerv1:
|
|
109
109
|
Save tonies.custom.json to a local file.
|
110
110
|
|
111
111
|
Args:
|
112
|
-
file_path: Path where to save the tonies.custom.json file
|
112
|
+
file_path (str): Path where to save the tonies.custom.json file
|
113
113
|
|
114
114
|
Returns:
|
115
|
-
True if successful, False otherwise
|
115
|
+
bool: True if successful, False otherwise
|
116
116
|
"""
|
117
117
|
if not self.is_loaded:
|
118
118
|
logger.error("Cannot save tonies.custom.json: data not loaded")
|
@@ -131,10 +131,13 @@ class ToniesJsonHandlerv1:
|
|
131
131
|
logger.error("Error saving tonies.custom.json to file: %s", e)
|
132
132
|
return False
|
133
133
|
|
134
|
-
def renumber_series_entries(self, series: str):
|
134
|
+
def renumber_series_entries(self, series: str) -> None:
|
135
135
|
"""
|
136
136
|
Re-sort and re-number all entries for a series by year (chronological),
|
137
137
|
with entries without a year coming last.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
series (str): Series name to renumber
|
138
141
|
"""
|
139
142
|
# Collect all entries for the series
|
140
143
|
series_entries = [entry for entry in self.custom_json if entry.get('series') == series]
|
@@ -167,12 +170,12 @@ class ToniesJsonHandlerv1:
|
|
167
170
|
If an entry with the same series+episodes exists, the new hash will be added to it.
|
168
171
|
|
169
172
|
Args:
|
170
|
-
taf_file: Path to the TAF file
|
171
|
-
input_files: List of input audio files used to create the TAF
|
172
|
-
artwork_url: URL of the uploaded artwork (if any)
|
173
|
+
taf_file (str): Path to the TAF file
|
174
|
+
input_files (list[str]): List of input audio files used to create the TAF
|
175
|
+
artwork_url (str | None): URL of the uploaded artwork (if any)
|
173
176
|
|
174
177
|
Returns:
|
175
|
-
True if successful, False otherwise
|
178
|
+
bool: True if successful, False otherwise
|
176
179
|
"""
|
177
180
|
logger.trace("Entering add_entry_from_taf() with taf_file=%s, input_files=%s, artwork_url=%s",
|
178
181
|
taf_file, input_files, artwork_url)
|
@@ -329,12 +332,12 @@ class ToniesJsonHandlerv1:
|
|
329
332
|
2. For entries without years: assign the next available number after those with years
|
330
333
|
|
331
334
|
Args:
|
332
|
-
series: Series name
|
333
|
-
episodes: Episodes name
|
334
|
-
year: Release year from metadata, if available
|
335
|
+
series (str): Series name
|
336
|
+
episodes (str): Episodes name
|
337
|
+
year (int | None): Release year from metadata, if available
|
335
338
|
|
336
339
|
Returns:
|
337
|
-
Generated entry number as string
|
340
|
+
str: Generated entry number as string
|
338
341
|
"""
|
339
342
|
logger.trace("Entering _generate_entry_no() with series='%s', episodes='%s', year=%s",
|
340
343
|
series, episodes, year)
|
@@ -443,10 +446,10 @@ class ToniesJsonHandlerv1:
|
|
443
446
|
Extract a year (1900-2099) from text.
|
444
447
|
|
445
448
|
Args:
|
446
|
-
text: The text to extract the year from
|
449
|
+
text (str): The text to extract the year from
|
447
450
|
|
448
451
|
Returns:
|
449
|
-
The extracted year as int, or None if no valid year found
|
452
|
+
int | None: The extracted year as int, or None if no valid year found
|
450
453
|
"""
|
451
454
|
import re
|
452
455
|
year_pattern = re.compile(r'(19\d{2}|20\d{2})')
|
@@ -467,11 +470,11 @@ class ToniesJsonHandlerv1:
|
|
467
470
|
Format a number to match the existing entry number format (e.g., with leading zeros).
|
468
471
|
|
469
472
|
Args:
|
470
|
-
number: The number to format
|
471
|
-
existing_entries: List of existing entries with their numbers
|
473
|
+
number (int): The number to format
|
474
|
+
existing_entries (list[dict]): List of existing entries with their numbers
|
472
475
|
|
473
476
|
Returns:
|
474
|
-
Formatted number as string
|
477
|
+
str: Formatted number as string
|
475
478
|
"""
|
476
479
|
max_digits = 1
|
477
480
|
for entry in existing_entries:
|
@@ -492,7 +495,7 @@ class ToniesJsonHandlerv1:
|
|
492
495
|
Generate a unique model number for a new entry.
|
493
496
|
|
494
497
|
Returns:
|
495
|
-
Unique model number in the format "model-" followed by sequential number with zero padding
|
498
|
+
str: Unique model number in the format "model-" followed by sequential number with zero padding
|
496
499
|
"""
|
497
500
|
logger.trace("Entering _generate_model_number()")
|
498
501
|
highest_num = -1
|
@@ -525,10 +528,10 @@ class ToniesJsonHandlerv1:
|
|
525
528
|
Determine the category in v1 format.
|
526
529
|
|
527
530
|
Args:
|
528
|
-
metadata: Dictionary containing file metadata
|
531
|
+
metadata (dict): Dictionary containing file metadata
|
529
532
|
|
530
533
|
Returns:
|
531
|
-
Category string in v1 format
|
534
|
+
str: Category string in v1 format
|
532
535
|
"""
|
533
536
|
if 'genre' in metadata:
|
534
537
|
genre_value = metadata['genre'].lower().strip()
|
@@ -551,10 +554,10 @@ class ToniesJsonHandlerv1:
|
|
551
554
|
Find an entry in the custom JSON by TAF hash.
|
552
555
|
|
553
556
|
Args:
|
554
|
-
taf_hash: SHA1 hash of the TAF file to find
|
557
|
+
taf_hash (str): SHA1 hash of the TAF file to find
|
555
558
|
|
556
559
|
Returns:
|
557
|
-
Tuple of (entry, entry_index) if found, or (None, None) if not found
|
560
|
+
tuple[dict | None, int | None]: Tuple of (entry, entry_index) if found, or (None, None) if not found
|
558
561
|
"""
|
559
562
|
logger.trace("Searching for entry with hash %s", taf_hash)
|
560
563
|
|
@@ -575,11 +578,11 @@ class ToniesJsonHandlerv1:
|
|
575
578
|
Find an entry in the custom JSON by series and episodes.
|
576
579
|
|
577
580
|
Args:
|
578
|
-
series: Series name to find
|
579
|
-
episodes: Episodes name to find
|
581
|
+
series (str): Series name to find
|
582
|
+
episodes (str): Episodes name to find
|
580
583
|
|
581
584
|
Returns:
|
582
|
-
Tuple of (entry, entry_index) if found, or (None, None) if not found
|
585
|
+
tuple[dict | None, int | None]: Tuple of (entry, entry_index) if found, or (None, None) if not found
|
583
586
|
"""
|
584
587
|
logger.trace("Searching for entry with series='%s', episodes='%s'", series, episodes)
|
585
588
|
|
@@ -596,10 +599,10 @@ class ToniesJsonHandlerv1:
|
|
596
599
|
Extract metadata from audio files to use in the custom JSON entry.
|
597
600
|
|
598
601
|
Args:
|
599
|
-
input_files: List of paths to audio files
|
602
|
+
input_files (list[str]): List of paths to audio files
|
600
603
|
|
601
604
|
Returns:
|
602
|
-
Dictionary containing metadata extracted from files
|
605
|
+
dict: Dictionary containing metadata extracted from files
|
603
606
|
"""
|
604
607
|
metadata = {}
|
605
608
|
track_descriptions = []
|
@@ -638,10 +641,10 @@ class ToniesJsonHandlerv1:
|
|
638
641
|
Convert data from v2 format to v1 format.
|
639
642
|
|
640
643
|
Args:
|
641
|
-
v2_data: Data in v2 format
|
644
|
+
v2_data (list[dict]): Data in v2 format
|
642
645
|
|
643
646
|
Returns:
|
644
|
-
Converted data in v1 format
|
647
|
+
list[dict]: Converted data in v1 format
|
645
648
|
"""
|
646
649
|
v1_data = []
|
647
650
|
|
@@ -687,10 +690,10 @@ class ToniesJsonHandlerv1:
|
|
687
690
|
Convert category from v2 format to v1 format.
|
688
691
|
|
689
692
|
Args:
|
690
|
-
v2_category: Category in v2 format
|
693
|
+
v2_category (str): Category in v2 format
|
691
694
|
|
692
695
|
Returns:
|
693
|
-
Category in v1 format
|
696
|
+
str: Category in v1 format
|
694
697
|
"""
|
695
698
|
v2_to_v1_mapping = {
|
696
699
|
"music": "music",
|
@@ -710,7 +713,7 @@ class ToniesJsonHandlerv2:
|
|
710
713
|
Initialize the handler.
|
711
714
|
|
712
715
|
Args:
|
713
|
-
client: TeddyCloudClient instance to use for API communication
|
716
|
+
client (TeddyCloudClient | None): TeddyCloudClient instance to use for API communication
|
714
717
|
"""
|
715
718
|
self.client = client
|
716
719
|
self.custom_json = []
|
@@ -721,7 +724,7 @@ class ToniesJsonHandlerv2:
|
|
721
724
|
Load tonies.custom.json from the TeddyCloud server.
|
722
725
|
|
723
726
|
Returns:
|
724
|
-
True if successful, False otherwise
|
727
|
+
bool: True if successful, False otherwise
|
725
728
|
"""
|
726
729
|
if self.client is None:
|
727
730
|
logger.error("Cannot load from server: no client provided")
|
@@ -747,10 +750,10 @@ class ToniesJsonHandlerv2:
|
|
747
750
|
Load tonies.custom.json from a local file.
|
748
751
|
|
749
752
|
Args:
|
750
|
-
file_path: Path to the tonies.custom.json file
|
753
|
+
file_path (str): Path to the tonies.custom.json file
|
751
754
|
|
752
755
|
Returns:
|
753
|
-
True if successful, False otherwise
|
756
|
+
bool: True if successful, False otherwise
|
754
757
|
"""
|
755
758
|
try:
|
756
759
|
if os.path.exists(file_path):
|
@@ -780,10 +783,10 @@ class ToniesJsonHandlerv2:
|
|
780
783
|
Save tonies.custom.json to a local file.
|
781
784
|
|
782
785
|
Args:
|
783
|
-
file_path: Path where to save the tonies.custom.json file
|
786
|
+
file_path (str): Path where to save the tonies.custom.json file
|
784
787
|
|
785
788
|
Returns:
|
786
|
-
True if successful, False otherwise
|
789
|
+
bool: True if successful, False otherwise
|
787
790
|
"""
|
788
791
|
if not self.is_loaded:
|
789
792
|
logger.error("Cannot save tonies.custom.json: data not loaded")
|
@@ -809,12 +812,12 @@ class ToniesJsonHandlerv2:
|
|
809
812
|
If an entry with the same series+episode exists, the new hash will be added to it.
|
810
813
|
|
811
814
|
Args:
|
812
|
-
taf_file: Path to the TAF file
|
813
|
-
input_files: List of input audio files used to create the TAF
|
814
|
-
artwork_url: URL of the uploaded artwork (if any)
|
815
|
+
taf_file (str): Path to the TAF file
|
816
|
+
input_files (list[str]): List of input audio files used to create the TAF
|
817
|
+
artwork_url (str | None): URL of the uploaded artwork (if any)
|
815
818
|
|
816
819
|
Returns:
|
817
|
-
True if successful, False otherwise
|
820
|
+
bool: True if successful, False otherwise
|
818
821
|
"""
|
819
822
|
logger.trace("Entering add_entry_from_taf() with taf_file=%s, input_files=%s, artwork_url=%s",
|
820
823
|
taf_file, input_files, artwork_url)
|
@@ -915,7 +918,7 @@ class ToniesJsonHandlerv2:
|
|
915
918
|
Generate a unique article ID for a new entry.
|
916
919
|
|
917
920
|
Returns:
|
918
|
-
Unique article ID in the format "tt-42" followed by sequential number starting from 0
|
921
|
+
str: Unique article ID in the format "tt-42" followed by sequential number starting from 0
|
919
922
|
"""
|
920
923
|
logger.trace("Entering _generate_article_id()")
|
921
924
|
highest_num = -1
|
@@ -948,10 +951,10 @@ class ToniesJsonHandlerv2:
|
|
948
951
|
Extract metadata from audio files to use in the custom JSON entry.
|
949
952
|
|
950
953
|
Args:
|
951
|
-
input_files: List of paths to audio files
|
954
|
+
input_files (list[str]): List of paths to audio files
|
952
955
|
|
953
956
|
Returns:
|
954
|
-
Dictionary containing metadata extracted from files
|
957
|
+
dict: Dictionary containing metadata extracted from files
|
955
958
|
"""
|
956
959
|
metadata = {}
|
957
960
|
track_descriptions = []
|
@@ -1043,10 +1046,10 @@ class ToniesJsonHandlerv2:
|
|
1043
1046
|
Find an entry in the custom JSON by TAF hash.
|
1044
1047
|
|
1045
1048
|
Args:
|
1046
|
-
taf_hash: SHA1 hash of the TAF file to find
|
1049
|
+
taf_hash (str): SHA1 hash of the TAF file to find
|
1047
1050
|
|
1048
1051
|
Returns:
|
1049
|
-
Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
|
1052
|
+
tuple[dict | None, int | None, int | None]: Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
|
1050
1053
|
"""
|
1051
1054
|
logger.trace("Searching for entry with hash %s", taf_hash)
|
1052
1055
|
|
@@ -1071,11 +1074,11 @@ class ToniesJsonHandlerv2:
|
|
1071
1074
|
Find an entry in the custom JSON by series and episode.
|
1072
1075
|
|
1073
1076
|
Args:
|
1074
|
-
series: Series name to find
|
1075
|
-
episode: Episode name to find
|
1077
|
+
series (str): Series name to find
|
1078
|
+
episode (str): Episode name to find
|
1076
1079
|
|
1077
1080
|
Returns:
|
1078
|
-
Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
|
1081
|
+
tuple[dict | None, int | None, int | None]: Tuple of (entry, entry_index, data_index) if found, or (None, None, None) if not found
|
1079
1082
|
"""
|
1080
1083
|
logger.trace("Searching for entry with series='%s', episode='%s'", series, episode)
|
1081
1084
|
|
@@ -1096,10 +1099,10 @@ class ToniesJsonHandlerv2:
|
|
1096
1099
|
Calculate the total runtime in minutes from a list of audio files.
|
1097
1100
|
|
1098
1101
|
Args:
|
1099
|
-
input_files: List of paths to audio files
|
1102
|
+
input_files (list[str]): List of paths to audio files
|
1100
1103
|
|
1101
1104
|
Returns:
|
1102
|
-
Total runtime in minutes (rounded to the nearest minute)
|
1105
|
+
int: Total runtime in minutes (rounded to the nearest minute)
|
1103
1106
|
"""
|
1104
1107
|
logger.trace("Entering _calculate_runtime() with %d input files", len(input_files))
|
1105
1108
|
total_runtime_seconds = 0
|
@@ -1166,14 +1169,14 @@ def fetch_and_update_tonies_json_v1(client: TeddyCloudClient, taf_file: Optional
|
|
1166
1169
|
Fetch tonies.custom.json from server and merge with local file if it exists, then update with new entry in v1 format.
|
1167
1170
|
|
1168
1171
|
Args:
|
1169
|
-
client: TeddyCloudClient instance to use for API communication
|
1170
|
-
taf_file: Path to the TAF file to add
|
1171
|
-
input_files: List of input audio files used to create the TAF
|
1172
|
-
artwork_url: URL of the uploaded artwork (if any)
|
1173
|
-
output_dir: Directory where to save the tonies.custom.json file (defaults to './output')
|
1172
|
+
client (TeddyCloudClient): TeddyCloudClient instance to use for API communication
|
1173
|
+
taf_file (str | None): Path to the TAF file to add
|
1174
|
+
input_files (list[str] | None): List of input audio files used to create the TAF
|
1175
|
+
artwork_url (str | None): URL of the uploaded artwork (if any)
|
1176
|
+
output_dir (str | None): Directory where to save the tonies.custom.json file (defaults to './output')
|
1174
1177
|
|
1175
1178
|
Returns:
|
1176
|
-
True if successful, False otherwise
|
1179
|
+
bool: True if successful, False otherwise
|
1177
1180
|
"""
|
1178
1181
|
logger.trace("Entering fetch_and_update_tonies_json_v1 with client=%s, taf_file=%s, input_files=%s, artwork_url=%s, output_dir=%s",
|
1179
1182
|
client, taf_file, input_files, artwork_url, output_dir)
|
@@ -1275,14 +1278,14 @@ def fetch_and_update_tonies_json_v2(client: TeddyCloudClient, taf_file: Optional
|
|
1275
1278
|
Fetch tonies.custom.json from server and merge with local file if it exists, then update with new entry.
|
1276
1279
|
|
1277
1280
|
Args:
|
1278
|
-
client: TeddyCloudClient instance to use for API communication
|
1279
|
-
taf_file: Path to the TAF file to add
|
1280
|
-
input_files: List of input audio files used to create the TAF
|
1281
|
-
artwork_url: URL of the uploaded artwork (if any)
|
1282
|
-
output_dir: Directory where to save the tonies.custom.json file (defaults to './output')
|
1281
|
+
client (TeddyCloudClient): TeddyCloudClient instance to use for API communication
|
1282
|
+
taf_file (str | None): Path to the TAF file to add
|
1283
|
+
input_files (list[str] | None): List of input audio files used to create the TAF
|
1284
|
+
artwork_url (str | None): URL of the uploaded artwork (if any)
|
1285
|
+
output_dir (str | None): Directory where to save the tonies.custom.json file (defaults to './output')
|
1283
1286
|
|
1284
1287
|
Returns:
|
1285
|
-
True if successful, False otherwise
|
1288
|
+
bool: True if successful, False otherwise
|
1286
1289
|
"""
|
1287
1290
|
logger.trace("Entering fetch_and_update_tonies_json with client=%s, taf_file=%s, input_files=%s, artwork_url=%s, output_dir=%s",
|
1288
1291
|
client, taf_file, input_files, artwork_url, output_dir)
|
TonieToolbox/version_handler.py
CHANGED
@@ -18,15 +18,14 @@ CACHE_FILE = os.path.join(CACHE_DIR, "version_cache.json")
|
|
18
18
|
CACHE_EXPIRY = 86400 # 24 hours in seconds
|
19
19
|
|
20
20
|
|
21
|
-
def get_pypi_version(force_refresh=False):
|
21
|
+
def get_pypi_version(force_refresh: bool = False) -> tuple[str, str | None]:
|
22
22
|
"""
|
23
23
|
Get the latest version of TonieToolbox from PyPI.
|
24
24
|
|
25
25
|
Args:
|
26
|
-
force_refresh: If True, ignore the cache and fetch directly from PyPI
|
27
|
-
|
26
|
+
force_refresh (bool): If True, ignore the cache and fetch directly from PyPI
|
28
27
|
Returns:
|
29
|
-
tuple: (latest_version, None) on success, (current_version, error_message) on failure
|
28
|
+
tuple[str, str | None]: (latest_version, None) on success, (current_version, error_message) on failure
|
30
29
|
"""
|
31
30
|
logger = get_logger("version_handler")
|
32
31
|
logger.debug("Checking for latest version (force_refresh=%s)", force_refresh)
|
@@ -88,14 +87,13 @@ def get_pypi_version(force_refresh=False):
|
|
88
87
|
return __version__, f"Unexpected error checking for updates: {str(e)}"
|
89
88
|
|
90
89
|
|
91
|
-
def compare_versions(v1, v2):
|
90
|
+
def compare_versions(v1: str, v2: str) -> int:
|
92
91
|
"""
|
93
92
|
Compare two version strings according to PEP 440.
|
94
93
|
|
95
94
|
Args:
|
96
|
-
v1: First version string
|
97
|
-
v2: Second version string
|
98
|
-
|
95
|
+
v1 (str): First version string
|
96
|
+
v2 (str): Second version string
|
99
97
|
Returns:
|
100
98
|
int: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
|
101
99
|
"""
|
@@ -133,16 +131,15 @@ def compare_versions(v1, v2):
|
|
133
131
|
return 1
|
134
132
|
|
135
133
|
|
136
|
-
def check_for_updates(quiet=False, force_refresh=False):
|
134
|
+
def check_for_updates(quiet: bool = False, force_refresh: bool = False) -> tuple[bool, str, str, bool]:
|
137
135
|
"""
|
138
136
|
Check if the current version of TonieToolbox is the latest.
|
139
137
|
|
140
138
|
Args:
|
141
|
-
quiet: If True, will not log any information messages and skip user confirmation
|
142
|
-
force_refresh: If True, bypass cache and check PyPI directly
|
143
|
-
|
139
|
+
quiet (bool): If True, will not log any information messages and skip user confirmation
|
140
|
+
force_refresh (bool): If True, bypass cache and check PyPI directly
|
144
141
|
Returns:
|
145
|
-
tuple: (is_latest, latest_version, message, update_confirmed)
|
142
|
+
tuple[bool, str, str, bool]: (is_latest, latest_version, message, update_confirmed)
|
146
143
|
is_latest: boolean indicating if the current version is the latest
|
147
144
|
latest_version: string with the latest version
|
148
145
|
message: string message about the update status or error
|
@@ -200,7 +197,7 @@ def check_for_updates(quiet=False, force_refresh=False):
|
|
200
197
|
return is_latest, latest_version, message, update_confirmed
|
201
198
|
|
202
199
|
|
203
|
-
def install_update():
|
200
|
+
def install_update() -> bool:
|
204
201
|
"""
|
205
202
|
Try to install the update using pip, pip3, or pipx.
|
206
203
|
|
@@ -238,7 +235,7 @@ def install_update():
|
|
238
235
|
return False
|
239
236
|
|
240
237
|
|
241
|
-
def clear_version_cache():
|
238
|
+
def clear_version_cache() -> bool:
|
242
239
|
"""
|
243
240
|
Clear the version cache file to force a refresh on next check.
|
244
241
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: TonieToolbox
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0a2
|
4
4
|
Summary: Create files for the Tonie box and interact with TeddyCloud servers
|
5
5
|
Home-page: https://github.com/Quentendo64/TonieToolbox
|
6
6
|
Author: Quentendo64
|
@@ -33,7 +33,7 @@ Dynamic: requires-python
|
|
33
33
|
|
34
34
|
# TonieToolbox 🎵📦
|
35
35
|
|
36
|
-
[](https://github.com/Quentendo64/TonieToolbox/actions)
|
37
37
|
[](https://github.com/Quentendo64/TonieToolbox/actions)
|
38
38
|
|
39
39
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
@@ -225,7 +225,7 @@ docker run --rm -v "$(pwd)/input:/tonietoolbox/input" -v "$(pwd)/output:/tonieto
|
|
225
225
|
docker run --rm -v "%cd%\input:/tonietoolbox/input" -v "%cd%\output:/tonietoolbox/output" quentendo64/tonietoolbox input/my-audio-file.mp3
|
226
226
|
```
|
227
227
|
|
228
|
-
**Or using docker-compose
|
228
|
+
**Or using docker-compose:**
|
229
229
|
|
230
230
|
```shell
|
231
231
|
docker-compose run --rm tonietoolbox input/my-audio-file.mp3
|