epub2speech 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OOMOL Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.3
2
+ Name: epub2speech
3
+ Version: 0.0.1
4
+ Summary: Convert EPUB e-books into high-quality audiobooks using Azure Text-to-Speech technology
5
+ License: MIT
6
+ Keywords: epub,audiobook,text-to-speech,tts,azure,m4b,ebook
7
+ Author: TaoZeyu
8
+ Author-email: i@taozeyu.com
9
+ Maintainer: TaoZeyu
10
+ Maintainer-email: i@taozeyu.com
11
+ Requires-Python: >=3.11,<3.14
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
21
+ Classifier: Topic :: Text Processing :: Markup
22
+ Classifier: Topic :: Utilities
23
+ Provides-Extra: dev
24
+ Requires-Dist: azure-cognitiveservices-speech (>=1.46.0,<2.0.0)
25
+ Requires-Dist: ebooklib (>=0.19,<0.20)
26
+ Requires-Dist: numpy (>=2.3.3,<3.0.0)
27
+ Requires-Dist: pytest (>=7.0.0) ; extra == "dev"
28
+ Requires-Dist: pytest-cov (>=4.0.0) ; extra == "dev"
29
+ Requires-Dist: resource-segmentation (==0.0.5)
30
+ Requires-Dist: soundfile (>=0.13.1,<0.14.0)
31
+ Requires-Dist: spacy (>=3.8.7,<4.0.0)
32
+ Project-URL: Bug Tracker, https://github.com/oomol-lab/epub2speech/issues
33
+ Project-URL: Documentation, https://github.com/oomol-lab/epub2speech/blob/main/README.md
34
+ Project-URL: Homepage, https://github.com/oomol-lab/epub2speech
35
+ Project-URL: Repository, https://github.com/oomol-lab/epub2speech
36
+ Description-Content-Type: text/markdown
37
+
38
+ <div align=center>
39
+ <h1>EPUB to Speech</h1>
40
+ <p>English | <a href="./README_zh-CN.md">δΈ­ζ–‡</a></p>
41
+ </div>
42
+
43
+ Convert EPUB e-books into high-quality audiobooks using Azure Text-to-Speech technology.
44
+
45
+ ## Features
46
+
47
+ - **πŸ“š EPUB Support**: Compatible with EPUB 2 and EPUB 3 formats
48
+ - **πŸŽ™οΈ High-Quality TTS**: Uses Azure Cognitive Services Speech for natural voice synthesis
49
+ - **🌍 Multi-Language Support**: Supports various languages and voices via Azure TTS
50
+ - **πŸ“± M4B Output**: Generates standard M4B audiobook format with chapter navigation
51
+ - **πŸ”§ CLI Interface**: Easy-to-use command-line tool with progress tracking
52
+
53
+ ## Basic Usage
54
+
55
+ Convert an EPUB file to audiobook:
56
+
57
+ ```bash
58
+ epub2speech input.epub output.m4b --voice zh-CN-XiaoxiaoNeural --azure-key YOUR_KEY --azure-region YOUR_REGION
59
+ ```
60
+
61
+ ## Installation
62
+
63
+ ### Prerequisites
64
+
65
+ - Python 3.11 or higher
66
+ - FFmpeg (for audio processing)
67
+ - Azure Speech Service credentials
68
+
69
+ ### Install Dependencies
70
+
71
+ ```bash
72
+ # Install Python dependencies
73
+ pip install poetry
74
+ poetry install
75
+
76
+ # Install FFmpeg
77
+ # macOS: brew install ffmpeg
78
+ # Ubuntu/Debian: sudo apt install ffmpeg
79
+ # Windows: Download from https://ffmpeg.org/download.html
80
+ ```
81
+
82
+ ### Azure Speech Service Setup
83
+
84
+ 1. Create an Azure account at https://azure.microsoft.com
85
+ 2. Create a Speech Service resource in Azure Portal
86
+ 3. Get your subscription key and region from the Azure dashboard
87
+
88
+ ## Quick Start
89
+
90
+ ### Environment Variables
91
+
92
+ Set your Azure credentials as environment variables:
93
+
94
+ ```bash
95
+ export AZURE_SPEECH_KEY="your-subscription-key"
96
+ export AZURE_SPEECH_REGION="your-region"
97
+
98
+ epub2speech input.epub output.m4b --voice zh-CN-XiaoxiaoNeural
99
+ ```
100
+
101
+ ### Advanced Options
102
+
103
+ ```bash
104
+ # Limit to first 5 chapters
105
+ epub2speech input.epub output.m4b --voice en-US-AriaNeural --max-chapters 5
106
+
107
+ # Use custom workspace directory
108
+ epub2speech input.epub output.m4b --voice zh-CN-YunxiNeural --workspace /tmp/my-workspace
109
+
110
+ # Quiet mode (no progress output)
111
+ epub2speech input.epub output.m4b --voice ja-JP-NanamiNeural --quiet
112
+ ```
113
+
114
+ ## Available Voices
115
+
116
+ For a complete list, see [Azure Neural Voices](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#neural-voices).
117
+
118
+ ## How It Works
119
+
120
+ 1. **EPUB Parsing**: Extracts text content and metadata from EPUB files
121
+ 2. **Chapter Detection**: Identifies chapters using EPUB navigation data
122
+ 3. **Text Processing**: Cleans and segments text for optimal speech synthesis
123
+ 4. **Audio Generation**: Converts text to speech using Azure TTS
124
+ 5. **M4B Creation**: Combines audio files with chapter metadata into M4B format
125
+
126
+ ## Development
127
+
128
+ ### Running Tests
129
+
130
+ ```bash
131
+ python test.py
132
+ ```
133
+
134
+ Run specific test modules:
135
+
136
+ ```bash
137
+ python test.py --test test_epub_picker
138
+ python test.py --test test_tts
139
+ ```
140
+
141
+ ## Contributing
142
+
143
+ Contributions are welcome! Please feel free to submit issues or pull requests.
144
+
145
+ ## License
146
+
147
+ This project is licensed under the MIT License - see the LICENSE file for details.
148
+
149
+ ## Acknowledgments
150
+
151
+ - [Azure Cognitive Services](https://azure.microsoft.com/services/cognitive-services/) for text-to-speech technology
152
+ - [ebooklib](https://github.com/aerkalov/ebooklib) for EPUB parsing
153
+ - [FFmpeg](https://ffmpeg.org/) for audio processing
154
+ - [spaCy](https://spacy.io/) for natural language processing
155
+
156
+ ## Support
157
+
158
+ For issues and questions:
159
+ 1. Check existing GitHub issues
160
+ 2. Create a new issue with detailed information
161
+ 3. Include EPUB file samples if relevant (ensure no copyright restrictions)β€οΌŒβ€œfile_path”:
@@ -0,0 +1,124 @@
1
+ <div align=center>
2
+ <h1>EPUB to Speech</h1>
3
+ <p>English | <a href="./README_zh-CN.md">δΈ­ζ–‡</a></p>
4
+ </div>
5
+
6
+ Convert EPUB e-books into high-quality audiobooks using Azure Text-to-Speech technology.
7
+
8
+ ## Features
9
+
10
+ - **πŸ“š EPUB Support**: Compatible with EPUB 2 and EPUB 3 formats
11
+ - **πŸŽ™οΈ High-Quality TTS**: Uses Azure Cognitive Services Speech for natural voice synthesis
12
+ - **🌍 Multi-Language Support**: Supports various languages and voices via Azure TTS
13
+ - **πŸ“± M4B Output**: Generates standard M4B audiobook format with chapter navigation
14
+ - **πŸ”§ CLI Interface**: Easy-to-use command-line tool with progress tracking
15
+
16
+ ## Basic Usage
17
+
18
+ Convert an EPUB file to audiobook:
19
+
20
+ ```bash
21
+ epub2speech input.epub output.m4b --voice zh-CN-XiaoxiaoNeural --azure-key YOUR_KEY --azure-region YOUR_REGION
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ ### Prerequisites
27
+
28
+ - Python 3.11 or higher
29
+ - FFmpeg (for audio processing)
30
+ - Azure Speech Service credentials
31
+
32
+ ### Install Dependencies
33
+
34
+ ```bash
35
+ # Install Python dependencies
36
+ pip install poetry
37
+ poetry install
38
+
39
+ # Install FFmpeg
40
+ # macOS: brew install ffmpeg
41
+ # Ubuntu/Debian: sudo apt install ffmpeg
42
+ # Windows: Download from https://ffmpeg.org/download.html
43
+ ```
44
+
45
+ ### Azure Speech Service Setup
46
+
47
+ 1. Create an Azure account at https://azure.microsoft.com
48
+ 2. Create a Speech Service resource in Azure Portal
49
+ 3. Get your subscription key and region from the Azure dashboard
50
+
51
+ ## Quick Start
52
+
53
+ ### Environment Variables
54
+
55
+ Set your Azure credentials as environment variables:
56
+
57
+ ```bash
58
+ export AZURE_SPEECH_KEY="your-subscription-key"
59
+ export AZURE_SPEECH_REGION="your-region"
60
+
61
+ epub2speech input.epub output.m4b --voice zh-CN-XiaoxiaoNeural
62
+ ```
63
+
64
+ ### Advanced Options
65
+
66
+ ```bash
67
+ # Limit to first 5 chapters
68
+ epub2speech input.epub output.m4b --voice en-US-AriaNeural --max-chapters 5
69
+
70
+ # Use custom workspace directory
71
+ epub2speech input.epub output.m4b --voice zh-CN-YunxiNeural --workspace /tmp/my-workspace
72
+
73
+ # Quiet mode (no progress output)
74
+ epub2speech input.epub output.m4b --voice ja-JP-NanamiNeural --quiet
75
+ ```
76
+
77
+ ## Available Voices
78
+
79
+ For a complete list, see [Azure Neural Voices](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support#neural-voices).
80
+
81
+ ## How It Works
82
+
83
+ 1. **EPUB Parsing**: Extracts text content and metadata from EPUB files
84
+ 2. **Chapter Detection**: Identifies chapters using EPUB navigation data
85
+ 3. **Text Processing**: Cleans and segments text for optimal speech synthesis
86
+ 4. **Audio Generation**: Converts text to speech using Azure TTS
87
+ 5. **M4B Creation**: Combines audio files with chapter metadata into M4B format
88
+
89
+ ## Development
90
+
91
+ ### Running Tests
92
+
93
+ ```bash
94
+ python test.py
95
+ ```
96
+
97
+ Run specific test modules:
98
+
99
+ ```bash
100
+ python test.py --test test_epub_picker
101
+ python test.py --test test_tts
102
+ ```
103
+
104
+ ## Contributing
105
+
106
+ Contributions are welcome! Please feel free to submit issues or pull requests.
107
+
108
+ ## License
109
+
110
+ This project is licensed under the MIT License - see the LICENSE file for details.
111
+
112
+ ## Acknowledgments
113
+
114
+ - [Azure Cognitive Services](https://azure.microsoft.com/services/cognitive-services/) for text-to-speech technology
115
+ - [ebooklib](https://github.com/aerkalov/ebooklib) for EPUB parsing
116
+ - [FFmpeg](https://ffmpeg.org/) for audio processing
117
+ - [spaCy](https://spacy.io/) for natural language processing
118
+
119
+ ## Support
120
+
121
+ For issues and questions:
122
+ 1. Check existing GitHub issues
123
+ 2. Create a new issue with detailed information
124
+ 3. Include EPUB file samples if relevant (ensure no copyright restrictions)β€οΌŒβ€œfile_path”:
@@ -0,0 +1,9 @@
1
+ from .convertor import convert_epub_to_m4b, ConversionProgress
2
+ from .tts import TextToSpeechProtocol, AzureTextToSpeech
3
+
4
+ __all__ = [
5
+ "convert_epub_to_m4b",
6
+ "ConversionProgress",
7
+ "TextToSpeechProtocol",
8
+ "AzureTextToSpeech",
9
+ ]
@@ -0,0 +1,179 @@
1
+ import uuid
2
+ import numpy as np
3
+ import soundfile as sf
4
+ from pathlib import Path
5
+ from typing import List, Optional, Callable, Generator
6
+ from spacy.lang.xx import MultiLanguage
7
+ from spacy.language import Language
8
+ from spacy.tokens import Span
9
+
10
+ from .tts import TextToSpeechProtocol
11
+ from resource_segmentation import split, Resource, Segment
12
+
13
+ SEGMENT_LEVEL = 1
14
+ SENTENCE_LEVEL = 2
15
+
16
+ class ChapterTTS:
17
+ def __init__(
18
+ self,
19
+ tts_protocol: TextToSpeechProtocol,
20
+ sample_rate: int = 24000,
21
+ max_segment_length: int = 500,
22
+ language_model: Optional[str] = None
23
+ ):
24
+ self.tts_protocol = tts_protocol
25
+ self.sample_rate = sample_rate
26
+ self.max_segment_length = max_segment_length
27
+ self._nlp = self._load_language_model(language_model)
28
+
29
+ def _load_language_model(self, language_model: Optional[str]) -> Language:
30
+ if language_model:
31
+ try:
32
+ import spacy
33
+ return spacy.load(language_model)
34
+ except OSError:
35
+ pass
36
+
37
+ nlp: Language = MultiLanguage()
38
+ nlp.add_pipe("sentencizer")
39
+ return nlp
40
+
41
+ def process_chapter(
42
+ self,
43
+ text: str,
44
+ output_path: Path,
45
+ workspace_path: Path,
46
+ voice: str,
47
+ progress_callback: Optional[Callable[[int, int], None]] = None
48
+ ) -> None:
49
+ # TODO: ι‡ζž„ temp ι€»θΎ‘οΌŒδΈιœ€θ¦ temp θΏ™δΈͺ概忡了
50
+ segments = list(self.split_text_into_segments(text))
51
+ if not segments:
52
+ return
53
+
54
+ audio_segments = []
55
+ temp_files_created = []
56
+ try:
57
+ for i, segment in enumerate(segments):
58
+ if progress_callback:
59
+ progress_callback(i + 1, len(segments))
60
+
61
+ session_id = str(uuid.uuid4())[:8]
62
+ temp_audio_path = workspace_path / f"{session_id}_segment_{i:04d}.wav"
63
+ temp_files_created.append(temp_audio_path)
64
+
65
+ self.tts_protocol.convert_text_to_audio(
66
+ text=segment,
67
+ output_path=temp_audio_path,
68
+ voice=voice
69
+ )
70
+ if not temp_audio_path.exists():
71
+ continue
72
+
73
+ audio_data: np.ndarray
74
+ sr: int
75
+ audio_data, sr = sf.read(temp_audio_path)
76
+ if sr != self.sample_rate:
77
+ pass
78
+ audio_segments.append(audio_data)
79
+
80
+ final_audio = np.concatenate(audio_segments)
81
+ sf.write(output_path, final_audio, self.sample_rate)
82
+
83
+ finally:
84
+ for temp_file in temp_files_created:
85
+ if temp_file.exists():
86
+ temp_file.unlink()
87
+
88
+ def split_text_into_segments(self, text: str) -> Generator[str, None, None]:
89
+ text = text.strip()
90
+ if not text:
91
+ return
92
+
93
+ all_resources = []
94
+ doc = self._nlp(text)
95
+ next_start_incision = 2
96
+
97
+ for sent in doc.sents:
98
+ segment_text = sent.text.strip()
99
+ if not segment_text:
100
+ continue
101
+
102
+ resources = list(self._build_segment_internal_structure(sent))
103
+ if not resources:
104
+ continue
105
+
106
+ resources[0].start_incision = next_start_incision
107
+ resources[-1].end_incision = 2
108
+ next_start_incision = 2
109
+
110
+ all_resources.extend(resources)
111
+
112
+ if not all_resources:
113
+ text_resource = Resource(
114
+ count=len(text),
115
+ start_incision=SENTENCE_LEVEL,
116
+ end_incision=SENTENCE_LEVEL,
117
+ payload=text
118
+ )
119
+ all_resources.append(text_resource)
120
+
121
+ yield from self._split_by_resource_segmentation(all_resources)
122
+
123
+ def _build_segment_internal_structure(self, sent: Span) -> Generator[Resource, None, None]:
124
+ current_fragment: list[str] = []
125
+ for token in sent:
126
+ if token.is_punct:
127
+ if current_fragment:
128
+ fragment_text = "".join(current_fragment)
129
+ fragment_resource = Resource(
130
+ count=len(fragment_text),
131
+ start_incision=SEGMENT_LEVEL,
132
+ end_incision=SEGMENT_LEVEL,
133
+ payload=fragment_text
134
+ )
135
+ yield fragment_resource
136
+ current_fragment = []
137
+
138
+ punct_resource = Resource(
139
+ count=len(token.text),
140
+ start_incision=SEGMENT_LEVEL,
141
+ end_incision=SEGMENT_LEVEL,
142
+ payload=token.text
143
+ )
144
+ yield punct_resource
145
+ else:
146
+ current_fragment.append(token.text_with_ws)
147
+
148
+ if current_fragment:
149
+ fragment_text = "".join(current_fragment)
150
+ fragment_resource = Resource(
151
+ count=len(fragment_text),
152
+ start_incision=SEGMENT_LEVEL,
153
+ end_incision=SEGMENT_LEVEL,
154
+ payload=fragment_text
155
+ )
156
+ yield fragment_resource
157
+
158
+ def _split_by_resource_segmentation(self, resources: List[Resource]) -> Generator[str, None, None]:
159
+ max_byte_length = self.max_segment_length * 3
160
+ groups = list(split(
161
+ iter(resources),
162
+ max_segment_count=max_byte_length,
163
+ border_incision=1,
164
+ gap_rate=0.0,
165
+ tail_rate=0.0
166
+ ))
167
+
168
+ for group in groups:
169
+ segment_chars = []
170
+ for item in group.body:
171
+ if isinstance(item, Segment):
172
+ for resource in item.resources:
173
+ segment_chars.append(resource.payload)
174
+ elif isinstance(item, Resource):
175
+ segment_chars.append(item.payload)
176
+
177
+ combined_text = "".join(segment_chars).strip()
178
+ if combined_text:
179
+ yield combined_text
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from .convertor import convert_epub_to_m4b, ConversionProgress
8
+
9
+
10
+ def progress_callback(progress: ConversionProgress) -> None:
11
+ print(f"Progress: {progress.progress:.1f}% - Chapter {progress.current_chapter}/{progress.total_chapters}: {progress.chapter_title}")
12
+
13
+
14
+ def main():
15
+ parser = argparse.ArgumentParser(
16
+ description="Convert EPUB files to audiobooks (M4B format)",
17
+ formatter_class=argparse.RawDescriptionHelpFormatter,
18
+ epilog="""
19
+ Examples:
20
+ %(prog)s input.epub output.m4b --voice zh-CN-XiaoxiaoNeural
21
+ %(prog)s input.epub output.m4b --voice zh-CN-XiaoxiaoNeural --max-chapters 5
22
+ %(prog)s input.epub output.m4b --voice zh-CN-XiaoxiaoNeural --workspace /tmp/workspace
23
+ """
24
+ )
25
+
26
+ parser.add_argument(
27
+ "epub_path",
28
+ type=str,
29
+ help="Input EPUB file path"
30
+ )
31
+
32
+ parser.add_argument(
33
+ "output_path",
34
+ type=str,
35
+ help="Output M4B file path"
36
+ )
37
+
38
+ parser.add_argument(
39
+ "--voice",
40
+ type=str,
41
+ default="zh-CN-XiaoxiaoNeural",
42
+ help="TTS voice name (default: zh-CN-XiaoxiaoNeural)"
43
+ )
44
+
45
+ parser.add_argument(
46
+ "--max-chapters",
47
+ type=int,
48
+ help="Maximum number of chapters to convert (optional)"
49
+ )
50
+
51
+ parser.add_argument(
52
+ "--workspace",
53
+ type=str,
54
+ help="Workspace directory path (default: system temp directory)"
55
+ )
56
+
57
+ parser.add_argument(
58
+ "--azure-key",
59
+ type=str,
60
+ default=os.environ.get("AZURE_SPEECH_KEY"),
61
+ help="Azure Speech Service Key (can also be set via AZURE_SPEECH_KEY environment variable)"
62
+ )
63
+
64
+ parser.add_argument(
65
+ "--azure-region",
66
+ type=str,
67
+ default=os.environ.get("AZURE_SPEECH_REGION"),
68
+ help="Azure Speech Service region (can also be set via AZURE_SPEECH_REGION environment variable)"
69
+ )
70
+
71
+ parser.add_argument(
72
+ "--quiet",
73
+ action="store_true",
74
+ help="Quiet mode, do not show progress information"
75
+ )
76
+
77
+ args = parser.parse_args()
78
+
79
+ epub_path = Path(args.epub_path)
80
+ if not epub_path.exists():
81
+ print(f"Error: EPUB file does not exist: {epub_path}", file=sys.stderr)
82
+ sys.exit(1)
83
+
84
+ if not epub_path.suffix.lower() == '.epub':
85
+ print(f"Error: Input file must be in EPUB format: {epub_path}", file=sys.stderr)
86
+ sys.exit(1)
87
+
88
+ if not args.azure_key or not args.azure_region:
89
+ print("Error: Azure Speech Service credentials must be provided", file=sys.stderr)
90
+ print("Please provide via --azure-key and --azure-region parameters, or set AZURE_SPEECH_KEY and AZURE_SPEECH_REGION environment variables", file=sys.stderr)
91
+ sys.exit(1)
92
+
93
+ if args.workspace:
94
+ workspace = Path(args.workspace)
95
+ workspace.mkdir(parents=True, exist_ok=True)
96
+ else:
97
+ import tempfile
98
+ workspace = Path(tempfile.mkdtemp(prefix="epub2speech_"))
99
+
100
+ output_path = Path(args.output_path)
101
+ output_path.parent.mkdir(parents=True, exist_ok=True)
102
+
103
+ try:
104
+ from .tts.azure_provider import AzureTextToSpeech
105
+ tts_provider = AzureTextToSpeech(
106
+ subscription_key=args.azure_key,
107
+ region=args.azure_region,
108
+ default_voice=args.voice
109
+ )
110
+
111
+ print(f"Starting conversion: {epub_path.name}")
112
+ print(f"Output file: {output_path}")
113
+ print(f"Workspace: {workspace}")
114
+ print(f"Using voice: {args.voice}")
115
+ if args.max_chapters:
116
+ print(f"Maximum chapters: {args.max_chapters}")
117
+ print()
118
+
119
+ result_path = convert_epub_to_m4b(
120
+ epub_path=epub_path,
121
+ workspace=workspace,
122
+ output_path=output_path,
123
+ tts_protocol=tts_provider,
124
+ voice=args.voice,
125
+ max_chapters=args.max_chapters,
126
+ progress_callback=None if args.quiet else progress_callback
127
+ )
128
+ if result_path:
129
+ print(f"\nConversion complete! Output file: {result_path}")
130
+ print(f"File size: {result_path.stat().st_size / (1024*1024):.1f} MB")
131
+ else:
132
+ print("\nConversion failed: no output file generated", file=sys.stderr)
133
+ sys.exit(1)
134
+
135
+ except KeyboardInterrupt:
136
+ print("\nConversion interrupted by user", file=sys.stderr)
137
+ sys.exit(1)
138
+ except Exception as e:
139
+ print(f"\nConversion failed: {e}", file=sys.stderr)
140
+ sys.exit(1)
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()