TonieToolbox 0.2.1__tar.gz → 0.2.3__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.2.1/TonieToolbox.egg-info → tonietoolbox-0.2.3}/PKG-INFO +60 -4
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/README.md +59 -3
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/__init__.py +1 -1
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/__main__.py +126 -5
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/dependency_manager.py +136 -1
- tonietoolbox-0.2.3/TonieToolbox/media_tags.py +471 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/recursive_processor.py +96 -11
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/tonie_analysis.py +66 -4
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/tonie_file.py +117 -21
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3/TonieToolbox.egg-info}/PKG-INFO +60 -4
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox.egg-info/SOURCES.txt +1 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/LICENSE.md +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/MANIFEST.in +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/audio_conversion.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/constants.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/filename_generator.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/logger.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/ogg_page.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/opus_packet.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/tonie_header.proto +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/tonie_header_pb2.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox/version_handler.py +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox.egg-info/dependency_links.txt +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox.egg-info/entry_points.txt +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox.egg-info/requires.txt +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/TonieToolbox.egg-info/top_level.txt +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/pyproject.toml +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/setup.cfg +0 -0
- {tonietoolbox-0.2.1 → tonietoolbox-0.2.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: TonieToolbox
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.3
|
4
4
|
Summary: Convert audio files to Tonie box compatible format
|
5
5
|
Home-page: https://github.com/Quentendo64/TonieToolbox
|
6
6
|
Author: Quentendo64
|
@@ -46,6 +46,7 @@ A Python tool for converting audio files to Tonie box compatible format (TAF - T
|
|
46
46
|
- [Basic Usage](#basic-usage)
|
47
47
|
- [Advanced Options](#advanced-options)
|
48
48
|
- [Common Usage Examples](#common-usage-examples)
|
49
|
+
- [Media Tags](#media-tags)
|
49
50
|
- [Technical Details](#technical-details)
|
50
51
|
- [TAF File Structure](#taf-tonie-audio-format-file-structure)
|
51
52
|
- [File Analysis](#file-analysis)
|
@@ -67,15 +68,17 @@ The tool provides several capabilities:
|
|
67
68
|
- Split Tonie files into individual opus tracks
|
68
69
|
- Compare two TAF files for debugging differences
|
69
70
|
- Support various input formats through FFmpeg conversion
|
71
|
+
- Extract and use audio media tags (ID3, Vorbis Comments, etc.) for better file naming
|
70
72
|
|
71
73
|
## Requirements
|
72
74
|
|
73
75
|
- Python 3.6 or higher
|
74
|
-
- FFmpeg (for converting non-opus audio files)
|
76
|
+
- FFmpeg (for converting non-opus audio files)
|
75
77
|
- opus-tools (specifically `opusenc` for encoding to opus format)
|
78
|
+
- mutagen (for reading audio file metadata, auto-installed when needed)
|
76
79
|
|
77
80
|
Make sure FFmpeg and opus-tools are installed on your system and accessible in your PATH.
|
78
|
-
If the requirements are not found in PATH. TonieToolbox will download the missing requirements.
|
81
|
+
If the requirements are not found in PATH. TonieToolbox will download the missing requirements with --auto-download.
|
79
82
|
|
80
83
|
## Installation
|
81
84
|
|
@@ -166,7 +169,8 @@ Output:
|
|
166
169
|
```
|
167
170
|
usage: TonieToolbox.py [-h] [-v] [-t TIMESTAMP] [-f FFMPEG] [-o OPUSENC]
|
168
171
|
[-b BITRATE] [-c] [-a TAG] [-n] [-i] [-s] [-r] [-O]
|
169
|
-
[-A] [-k] [-C FILE2] [-D] [-
|
172
|
+
[-A] [-k] [-C FILE2] [-D] [-m] [--name-template TEMPLATE]
|
173
|
+
[--show-tags] [-d] [-T] [-q] [-Q]
|
170
174
|
SOURCE [TARGET]
|
171
175
|
|
172
176
|
Create Tonie compatible file from Ogg opus file(s).
|
@@ -198,6 +202,12 @@ optional arguments:
|
|
198
202
|
-D, --detailed-compare
|
199
203
|
Show detailed OGG page differences when comparing files
|
200
204
|
|
205
|
+
Media Tag Options:
|
206
|
+
-m, --use-media-tags Use media tags from audio files for naming
|
207
|
+
--name-template TEMPLATE
|
208
|
+
Template for naming files using media tags. Example: "{album} - {artist}"
|
209
|
+
--show-tags Show available media tags from input files
|
210
|
+
|
201
211
|
Version Check Options:
|
202
212
|
-S, --skip-update-check
|
203
213
|
Skip checking for updates
|
@@ -269,6 +279,52 @@ Process a music collection with nested album folders and save TAF files alongsid
|
|
269
279
|
tonietoolbox --recursive --output-to-source "\Hörspiele\"
|
270
280
|
```
|
271
281
|
|
282
|
+
### Media Tags
|
283
|
+
|
284
|
+
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.
|
285
|
+
|
286
|
+
#### View available tags in audio files:
|
287
|
+
|
288
|
+
To see what tags are available in your audio files:
|
289
|
+
|
290
|
+
```
|
291
|
+
tonietoolbox --show-tags input.mp3
|
292
|
+
```
|
293
|
+
|
294
|
+
This will display all readable tags from the file, which can be useful for creating naming templates.
|
295
|
+
|
296
|
+
#### Use media tags for file naming:
|
297
|
+
|
298
|
+
To use the metadata from audio files when generating output filenames:
|
299
|
+
|
300
|
+
```
|
301
|
+
tonietoolbox input.mp3 --use-media-tags
|
302
|
+
```
|
303
|
+
|
304
|
+
For single files, this will use a default template of "{title} - {artist}" for the output filename.
|
305
|
+
|
306
|
+
#### Custom naming templates:
|
307
|
+
|
308
|
+
You can specify custom templates for generating filenames based on the audio metadata:
|
309
|
+
|
310
|
+
```
|
311
|
+
tonietoolbox input.mp3 --use-media-tags --name-template "{artist} - {album} - {title}"
|
312
|
+
```
|
313
|
+
|
314
|
+
#### Recursive processing with media tags:
|
315
|
+
|
316
|
+
When processing folders recursively, media tags can provide more consistent naming:
|
317
|
+
|
318
|
+
```
|
319
|
+
tonietoolbox --recursive --use-media-tags "Music/Collection/"
|
320
|
+
```
|
321
|
+
|
322
|
+
This will attempt to use the album information from the audio files for naming the output files:
|
323
|
+
|
324
|
+
```
|
325
|
+
tonietoolbox --recursive --use-media-tags --name-template "{date} - {album} ({artist})" "Music/Collection/"
|
326
|
+
```
|
327
|
+
|
272
328
|
## Technical Details
|
273
329
|
|
274
330
|
### TAF (Tonie Audio Format) File Structure
|
@@ -19,6 +19,7 @@ A Python tool for converting audio files to Tonie box compatible format (TAF - T
|
|
19
19
|
- [Basic Usage](#basic-usage)
|
20
20
|
- [Advanced Options](#advanced-options)
|
21
21
|
- [Common Usage Examples](#common-usage-examples)
|
22
|
+
- [Media Tags](#media-tags)
|
22
23
|
- [Technical Details](#technical-details)
|
23
24
|
- [TAF File Structure](#taf-tonie-audio-format-file-structure)
|
24
25
|
- [File Analysis](#file-analysis)
|
@@ -40,15 +41,17 @@ The tool provides several capabilities:
|
|
40
41
|
- Split Tonie files into individual opus tracks
|
41
42
|
- Compare two TAF files for debugging differences
|
42
43
|
- Support various input formats through FFmpeg conversion
|
44
|
+
- Extract and use audio media tags (ID3, Vorbis Comments, etc.) for better file naming
|
43
45
|
|
44
46
|
## Requirements
|
45
47
|
|
46
48
|
- Python 3.6 or higher
|
47
|
-
- FFmpeg (for converting non-opus audio files)
|
49
|
+
- FFmpeg (for converting non-opus audio files)
|
48
50
|
- opus-tools (specifically `opusenc` for encoding to opus format)
|
51
|
+
- mutagen (for reading audio file metadata, auto-installed when needed)
|
49
52
|
|
50
53
|
Make sure FFmpeg and opus-tools are installed on your system and accessible in your PATH.
|
51
|
-
If the requirements are not found in PATH. TonieToolbox will download the missing requirements.
|
54
|
+
If the requirements are not found in PATH. TonieToolbox will download the missing requirements with --auto-download.
|
52
55
|
|
53
56
|
## Installation
|
54
57
|
|
@@ -139,7 +142,8 @@ Output:
|
|
139
142
|
```
|
140
143
|
usage: TonieToolbox.py [-h] [-v] [-t TIMESTAMP] [-f FFMPEG] [-o OPUSENC]
|
141
144
|
[-b BITRATE] [-c] [-a TAG] [-n] [-i] [-s] [-r] [-O]
|
142
|
-
[-A] [-k] [-C FILE2] [-D] [-
|
145
|
+
[-A] [-k] [-C FILE2] [-D] [-m] [--name-template TEMPLATE]
|
146
|
+
[--show-tags] [-d] [-T] [-q] [-Q]
|
143
147
|
SOURCE [TARGET]
|
144
148
|
|
145
149
|
Create Tonie compatible file from Ogg opus file(s).
|
@@ -171,6 +175,12 @@ optional arguments:
|
|
171
175
|
-D, --detailed-compare
|
172
176
|
Show detailed OGG page differences when comparing files
|
173
177
|
|
178
|
+
Media Tag Options:
|
179
|
+
-m, --use-media-tags Use media tags from audio files for naming
|
180
|
+
--name-template TEMPLATE
|
181
|
+
Template for naming files using media tags. Example: "{album} - {artist}"
|
182
|
+
--show-tags Show available media tags from input files
|
183
|
+
|
174
184
|
Version Check Options:
|
175
185
|
-S, --skip-update-check
|
176
186
|
Skip checking for updates
|
@@ -242,6 +252,52 @@ Process a music collection with nested album folders and save TAF files alongsid
|
|
242
252
|
tonietoolbox --recursive --output-to-source "\Hörspiele\"
|
243
253
|
```
|
244
254
|
|
255
|
+
### Media Tags
|
256
|
+
|
257
|
+
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.
|
258
|
+
|
259
|
+
#### View available tags in audio files:
|
260
|
+
|
261
|
+
To see what tags are available in your audio files:
|
262
|
+
|
263
|
+
```
|
264
|
+
tonietoolbox --show-tags input.mp3
|
265
|
+
```
|
266
|
+
|
267
|
+
This will display all readable tags from the file, which can be useful for creating naming templates.
|
268
|
+
|
269
|
+
#### Use media tags for file naming:
|
270
|
+
|
271
|
+
To use the metadata from audio files when generating output filenames:
|
272
|
+
|
273
|
+
```
|
274
|
+
tonietoolbox input.mp3 --use-media-tags
|
275
|
+
```
|
276
|
+
|
277
|
+
For single files, this will use a default template of "{title} - {artist}" for the output filename.
|
278
|
+
|
279
|
+
#### Custom naming templates:
|
280
|
+
|
281
|
+
You can specify custom templates for generating filenames based on the audio metadata:
|
282
|
+
|
283
|
+
```
|
284
|
+
tonietoolbox input.mp3 --use-media-tags --name-template "{artist} - {album} - {title}"
|
285
|
+
```
|
286
|
+
|
287
|
+
#### Recursive processing with media tags:
|
288
|
+
|
289
|
+
When processing folders recursively, media tags can provide more consistent naming:
|
290
|
+
|
291
|
+
```
|
292
|
+
tonietoolbox --recursive --use-media-tags "Music/Collection/"
|
293
|
+
```
|
294
|
+
|
295
|
+
This will attempt to use the album information from the audio files for naming the output files:
|
296
|
+
|
297
|
+
```
|
298
|
+
tonietoolbox --recursive --use-media-tags --name-template "{date} - {album} ({artist})" "Music/Collection/"
|
299
|
+
```
|
300
|
+
|
245
301
|
## Technical Details
|
246
302
|
|
247
303
|
### TAF (Tonie Audio Format) File Structure
|
@@ -17,6 +17,7 @@ from .logger import setup_logging, get_logger
|
|
17
17
|
from .filename_generator import guess_output_filename
|
18
18
|
from .version_handler import check_for_updates, clear_version_cache
|
19
19
|
from .recursive_processor import process_recursive_folders
|
20
|
+
from .media_tags import is_available as is_media_tags_available, ensure_mutagen
|
20
21
|
|
21
22
|
def main():
|
22
23
|
"""Entry point for the TonieToolbox application."""
|
@@ -45,11 +46,22 @@ def main():
|
|
45
46
|
parser.add_argument('-A', '--auto-download', action='store_true', help='Automatically download FFmpeg and opusenc if needed')
|
46
47
|
parser.add_argument('-k', '--keep-temp', action='store_true',
|
47
48
|
help='Keep temporary opus files in a temp folder for testing')
|
49
|
+
parser.add_argument('-u', '--use-legacy-tags', action='store_true',
|
50
|
+
help='Use legacy hardcoded tags instead of dynamic TonieToolbox tags')
|
48
51
|
parser.add_argument('-C', '--compare', action='store', metavar='FILE2',
|
49
52
|
help='Compare input file with another .taf file for debugging')
|
50
53
|
parser.add_argument('-D', '--detailed-compare', action='store_true',
|
51
54
|
help='Show detailed OGG page differences when comparing files')
|
52
55
|
|
56
|
+
# Media tag options
|
57
|
+
media_tag_group = parser.add_argument_group('Media Tag Options')
|
58
|
+
media_tag_group.add_argument('-m', '--use-media-tags', action='store_true',
|
59
|
+
help='Use media tags from audio files for naming')
|
60
|
+
media_tag_group.add_argument('--name-template', metavar='TEMPLATE', action='store',
|
61
|
+
help='Template for naming files using media tags. Example: "{album} - {artist}"')
|
62
|
+
media_tag_group.add_argument('--show-tags', action='store_true',
|
63
|
+
help='Show available media tags from input files')
|
64
|
+
|
53
65
|
# Version check options
|
54
66
|
version_group = parser.add_argument_group('Version Check Options')
|
55
67
|
version_group.add_argument('-S', '--skip-update-check', action='store_true',
|
@@ -83,14 +95,12 @@ def main():
|
|
83
95
|
logger = get_logger('main')
|
84
96
|
logger.debug("Starting TonieToolbox v%s with log level: %s", __version__, logging.getLevelName(log_level))
|
85
97
|
|
86
|
-
# Handle version cache operations
|
87
98
|
if args.clear_version_cache:
|
88
99
|
if clear_version_cache():
|
89
100
|
logger.info("Version cache cleared successfully")
|
90
101
|
else:
|
91
102
|
logger.info("No version cache to clear or error clearing cache")
|
92
103
|
|
93
|
-
# Check for updates
|
94
104
|
if not args.skip_update_check:
|
95
105
|
logger.debug("Checking for updates (force_refresh=%s)", args.force_refresh_cache)
|
96
106
|
is_latest, latest_version, message, update_confirmed = check_for_updates(
|
@@ -117,10 +127,24 @@ def main():
|
|
117
127
|
sys.exit(1)
|
118
128
|
logger.debug("Using opusenc binary: %s", opus_binary)
|
119
129
|
|
130
|
+
# Check for media tags library and handle --show-tags option
|
131
|
+
if (args.use_media_tags or args.show_tags or args.name_template) and not is_media_tags_available():
|
132
|
+
if not ensure_mutagen(auto_install=args.auto_download):
|
133
|
+
logger.warning("Media tags functionality requires the mutagen library but it could not be installed.")
|
134
|
+
if args.use_media_tags or args.show_tags:
|
135
|
+
logger.error("Cannot proceed with --use-media-tags or --show-tags without mutagen library")
|
136
|
+
sys.exit(1)
|
137
|
+
else:
|
138
|
+
logger.info("Successfully enabled media tag support")
|
139
|
+
|
120
140
|
# Handle recursive processing
|
121
141
|
if args.recursive:
|
122
142
|
logger.info("Processing folders recursively: %s", args.input_filename)
|
123
|
-
process_tasks = process_recursive_folders(
|
143
|
+
process_tasks = process_recursive_folders(
|
144
|
+
args.input_filename,
|
145
|
+
use_media_tags=args.use_media_tags,
|
146
|
+
name_template=args.name_template
|
147
|
+
)
|
124
148
|
|
125
149
|
if not process_tasks:
|
126
150
|
logger.error("No folders with audio files found for recursive processing")
|
@@ -143,7 +167,7 @@ def main():
|
|
143
167
|
|
144
168
|
create_tonie_file(task_out_filename, audio_files, args.no_tonie_header, args.user_timestamp,
|
145
169
|
args.bitrate, not args.cbr, ffmpeg_binary, opus_binary, args.keep_temp,
|
146
|
-
args.auto_download)
|
170
|
+
args.auto_download, not args.use_legacy_tags)
|
147
171
|
logger.info("Successfully created Tonie file: %s", task_out_filename)
|
148
172
|
|
149
173
|
logger.info("Recursive processing completed. Created %d Tonie files.", len(process_tasks))
|
@@ -176,8 +200,104 @@ def main():
|
|
176
200
|
logger.error("No files found for pattern %s", args.input_filename)
|
177
201
|
sys.exit(1)
|
178
202
|
|
203
|
+
# Show tags for input files if requested
|
204
|
+
if args.show_tags:
|
205
|
+
from .media_tags import get_file_tags
|
206
|
+
logger.info("Showing media tags for input files:")
|
207
|
+
|
208
|
+
for file_index, file_path in enumerate(files):
|
209
|
+
tags = get_file_tags(file_path)
|
210
|
+
if tags:
|
211
|
+
print(f"\nFile {file_index + 1}: {os.path.basename(file_path)}")
|
212
|
+
print("-" * 40)
|
213
|
+
for tag_name, tag_value in sorted(tags.items()):
|
214
|
+
print(f"{tag_name}: {tag_value}")
|
215
|
+
else:
|
216
|
+
print(f"\nFile {file_index + 1}: {os.path.basename(file_path)} - No tags found")
|
217
|
+
|
218
|
+
sys.exit(0)
|
219
|
+
|
220
|
+
# Use media tags for file naming if requested
|
221
|
+
guessed_name = None
|
222
|
+
if args.use_media_tags:
|
223
|
+
# If this is a single folder, try to get consistent album info
|
224
|
+
if len(files) > 1 and os.path.dirname(files[0]) == os.path.dirname(files[-1]):
|
225
|
+
folder_path = os.path.dirname(files[0])
|
226
|
+
|
227
|
+
from .media_tags import extract_album_info, format_metadata_filename
|
228
|
+
logger.debug("Extracting album info from folder: %s", folder_path)
|
229
|
+
|
230
|
+
album_info = extract_album_info(folder_path)
|
231
|
+
if album_info:
|
232
|
+
# Use album info for naming the output file
|
233
|
+
template = args.name_template or "{album} - {artist}"
|
234
|
+
new_name = format_metadata_filename(album_info, template)
|
235
|
+
|
236
|
+
if new_name:
|
237
|
+
logger.info("Using album metadata for output filename: %s", new_name)
|
238
|
+
guessed_name = new_name
|
239
|
+
else:
|
240
|
+
logger.debug("Could not format filename from album metadata")
|
241
|
+
|
242
|
+
# For single files, use the file's metadata
|
243
|
+
elif len(files) == 1:
|
244
|
+
from .media_tags import get_file_tags, format_metadata_filename
|
245
|
+
|
246
|
+
tags = get_file_tags(files[0])
|
247
|
+
if tags:
|
248
|
+
template = args.name_template or "{title} - {artist}"
|
249
|
+
new_name = format_metadata_filename(tags, template)
|
250
|
+
|
251
|
+
if new_name:
|
252
|
+
logger.info("Using file metadata for output filename: %s", new_name)
|
253
|
+
guessed_name = new_name
|
254
|
+
else:
|
255
|
+
logger.debug("Could not format filename from file metadata")
|
256
|
+
|
257
|
+
# For multiple files from different folders, try to use common tags if they exist
|
258
|
+
elif len(files) > 1:
|
259
|
+
from .media_tags import get_file_tags, format_metadata_filename
|
260
|
+
|
261
|
+
# Try to find common tags among files
|
262
|
+
common_tags = {}
|
263
|
+
for file_path in files:
|
264
|
+
tags = get_file_tags(file_path)
|
265
|
+
if tags:
|
266
|
+
for key, value in tags.items():
|
267
|
+
if key in ['album', 'albumartist', 'artist']:
|
268
|
+
if key not in common_tags:
|
269
|
+
common_tags[key] = value
|
270
|
+
# Only keep values that are the same across files
|
271
|
+
elif common_tags[key] != value:
|
272
|
+
common_tags[key] = None
|
273
|
+
|
274
|
+
# Remove None values
|
275
|
+
common_tags = {k: v for k, v in common_tags.items() if v is not None}
|
276
|
+
|
277
|
+
if common_tags:
|
278
|
+
template = args.name_template or "Collection - {album}" if 'album' in common_tags else "Collection"
|
279
|
+
new_name = format_metadata_filename(common_tags, template)
|
280
|
+
|
281
|
+
if new_name:
|
282
|
+
logger.info("Using common metadata for output filename: %s", new_name)
|
283
|
+
guessed_name = new_name
|
284
|
+
else:
|
285
|
+
logger.debug("Could not format filename from common metadata")
|
286
|
+
|
179
287
|
if args.output_filename:
|
180
288
|
out_filename = args.output_filename
|
289
|
+
elif guessed_name:
|
290
|
+
if args.output_to_source:
|
291
|
+
source_dir = os.path.dirname(files[0]) if files else '.'
|
292
|
+
out_filename = os.path.join(source_dir, guessed_name)
|
293
|
+
logger.debug("Using source location for output with media tags: %s", out_filename)
|
294
|
+
else:
|
295
|
+
output_dir = './output'
|
296
|
+
if not os.path.exists(output_dir):
|
297
|
+
logger.debug("Creating default output directory: %s", output_dir)
|
298
|
+
os.makedirs(output_dir, exist_ok=True)
|
299
|
+
out_filename = os.path.join(output_dir, guessed_name)
|
300
|
+
logger.debug("Using default output location with media tags: %s", out_filename)
|
181
301
|
else:
|
182
302
|
guessed_name = guess_output_filename(args.input_filename, files)
|
183
303
|
if args.output_to_source:
|
@@ -207,7 +327,8 @@ def main():
|
|
207
327
|
|
208
328
|
logger.info("Creating Tonie file: %s with %d input file(s)", out_filename, len(files))
|
209
329
|
create_tonie_file(out_filename, files, args.no_tonie_header, args.user_timestamp,
|
210
|
-
args.bitrate, not args.cbr, ffmpeg_binary, opus_binary, args.keep_temp,
|
330
|
+
args.bitrate, not args.cbr, ffmpeg_binary, opus_binary, args.keep_temp,
|
331
|
+
args.auto_download, not args.use_legacy_tags)
|
211
332
|
logger.info("Successfully created Tonie file: %s", out_filename)
|
212
333
|
|
213
334
|
|
@@ -52,6 +52,10 @@ DEPENDENCIES = {
|
|
52
52
|
'darwin': {
|
53
53
|
'package': 'opus-tools'
|
54
54
|
}
|
55
|
+
},
|
56
|
+
'mutagen': {
|
57
|
+
'package': 'mutagen',
|
58
|
+
'python_package': True
|
55
59
|
}
|
56
60
|
}
|
57
61
|
|
@@ -365,6 +369,92 @@ def install_package(package_name):
|
|
365
369
|
logger.error("Failed to install %s: %s", package_name, e)
|
366
370
|
return False
|
367
371
|
|
372
|
+
def install_python_package(package_name):
|
373
|
+
"""
|
374
|
+
Attempt to install a Python package using pip.
|
375
|
+
|
376
|
+
Args:
|
377
|
+
package_name (str): Name of the package to install
|
378
|
+
|
379
|
+
Returns:
|
380
|
+
bool: True if installation was successful, False otherwise
|
381
|
+
"""
|
382
|
+
logger.info("Attempting to install Python package: %s", package_name)
|
383
|
+
try:
|
384
|
+
import subprocess
|
385
|
+
import sys
|
386
|
+
|
387
|
+
# Try to install the package using pip
|
388
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
|
389
|
+
logger.info("Successfully installed Python package: %s", package_name)
|
390
|
+
return True
|
391
|
+
except Exception as e:
|
392
|
+
logger.error("Failed to install Python package %s: %s", package_name, str(e))
|
393
|
+
return False
|
394
|
+
|
395
|
+
def check_python_package(package_name):
|
396
|
+
"""
|
397
|
+
Check if a Python package is installed.
|
398
|
+
|
399
|
+
Args:
|
400
|
+
package_name (str): Name of the package to check
|
401
|
+
|
402
|
+
Returns:
|
403
|
+
bool: True if the package is installed, False otherwise
|
404
|
+
"""
|
405
|
+
logger.debug("Checking if Python package is installed: %s", package_name)
|
406
|
+
try:
|
407
|
+
__import__(package_name)
|
408
|
+
logger.debug("Python package %s is installed", package_name)
|
409
|
+
return True
|
410
|
+
except ImportError:
|
411
|
+
logger.debug("Python package %s is not installed", package_name)
|
412
|
+
return False
|
413
|
+
|
414
|
+
def ensure_mutagen(auto_install=True):
|
415
|
+
"""
|
416
|
+
Ensure that the Mutagen library is available, installing it if necessary and allowed.
|
417
|
+
|
418
|
+
Args:
|
419
|
+
auto_install (bool): Whether to automatically install Mutagen if not found (defaults to True)
|
420
|
+
|
421
|
+
Returns:
|
422
|
+
bool: True if Mutagen is available, False otherwise
|
423
|
+
"""
|
424
|
+
logger.debug("Checking if Mutagen is available")
|
425
|
+
|
426
|
+
try:
|
427
|
+
import mutagen
|
428
|
+
logger.debug("Mutagen is already installed")
|
429
|
+
return True
|
430
|
+
except ImportError:
|
431
|
+
logger.debug("Mutagen is not installed")
|
432
|
+
|
433
|
+
if auto_install:
|
434
|
+
logger.info("Auto-install enabled, attempting to install Mutagen")
|
435
|
+
if install_python_package('mutagen'):
|
436
|
+
try:
|
437
|
+
import mutagen
|
438
|
+
logger.info("Successfully installed and imported Mutagen")
|
439
|
+
return True
|
440
|
+
except ImportError:
|
441
|
+
logger.error("Mutagen was installed but could not be imported")
|
442
|
+
else:
|
443
|
+
logger.error("Failed to install Mutagen")
|
444
|
+
else:
|
445
|
+
logger.warning("Mutagen is not installed and --auto-download is not used.")
|
446
|
+
|
447
|
+
return False
|
448
|
+
|
449
|
+
def is_mutagen_available():
|
450
|
+
"""
|
451
|
+
Check if the Mutagen library is available.
|
452
|
+
|
453
|
+
Returns:
|
454
|
+
bool: True if Mutagen is available, False otherwise
|
455
|
+
"""
|
456
|
+
return check_python_package('mutagen')
|
457
|
+
|
368
458
|
def ensure_dependency(dependency_name, auto_download=False):
|
369
459
|
"""
|
370
460
|
Ensure that a dependency is available, downloading it if necessary.
|
@@ -532,4 +622,49 @@ def get_opus_binary(auto_download=False):
|
|
532
622
|
Returns:
|
533
623
|
str: Path to the opusenc binary if available, None otherwise
|
534
624
|
"""
|
535
|
-
return ensure_dependency('opusenc', auto_download)
|
625
|
+
return ensure_dependency('opusenc', auto_download)
|
626
|
+
|
627
|
+
def get_opus_version(opus_binary=None):
|
628
|
+
"""
|
629
|
+
Get the version of opusenc.
|
630
|
+
|
631
|
+
Args:
|
632
|
+
opus_binary: Path to the opusenc binary
|
633
|
+
|
634
|
+
Returns:
|
635
|
+
str: The version string of opusenc, or a fallback string if the version cannot be determined
|
636
|
+
"""
|
637
|
+
import subprocess
|
638
|
+
import re
|
639
|
+
|
640
|
+
logger = get_logger('dependency_manager')
|
641
|
+
|
642
|
+
if opus_binary is None:
|
643
|
+
opus_binary = get_opus_binary()
|
644
|
+
|
645
|
+
if opus_binary is None:
|
646
|
+
logger.debug("opusenc binary not found, using fallback version string")
|
647
|
+
return "opusenc from opus-tools XXX" # Fallback
|
648
|
+
|
649
|
+
try:
|
650
|
+
# Run opusenc --version and capture output
|
651
|
+
result = subprocess.run([opus_binary, "--version"],
|
652
|
+
capture_output=True, text=True, check=False)
|
653
|
+
|
654
|
+
# Extract version information from output
|
655
|
+
version_output = result.stdout.strip() or result.stderr.strip()
|
656
|
+
|
657
|
+
if version_output:
|
658
|
+
# Try to extract just the version information using regex
|
659
|
+
match = re.search(r"(opusenc.*)", version_output)
|
660
|
+
if match:
|
661
|
+
return match.group(1)
|
662
|
+
else:
|
663
|
+
return version_output.splitlines()[0] # Use first line
|
664
|
+
else:
|
665
|
+
logger.debug("Could not determine opusenc version, using fallback")
|
666
|
+
return "opusenc from opus-tools XXX" # Fallback
|
667
|
+
|
668
|
+
except Exception as e:
|
669
|
+
logger.debug(f"Error getting opusenc version: {str(e)}")
|
670
|
+
return "opusenc from opus-tools XXX" # Fallback
|