Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions .github/pages.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
# Deploy pages workflows

Updates to the `mkdocs.yml` file or any file in the `docs` directory trigger one of two GitHub Actions workflows.
These workflows keep the deployed version of the documentation site up to date with the document files in the `main` branch.
When you update the `mkdocs.yml` file or any file in the `docs` folder, you trigger one of two GitHub Actions workflows. These workflows keep the deployed version of the documentation site up to date with the documents in the `main` branch.

Both workflows triggers workflow called `deploy-pages.yml`. This workflow runs the `mkdocs gh-deploy` command.
To make sure that the command only pushes the most recent version of the docs, there is a concurrency lock on this workflow.
The lock cancels running actions and launches a new one when it's triggered.
Both workflows triggers a workflow called `deploy-pages.yml`. This workflow runs the `mkdocs gh-deploy` command. To make sure that the command only pushes the most recent documentation, the workflow has a concurrency lock. The lock cancels any running actions and launches a new one when it's triggered.

## auto-deploy-pages

This workflow runs when a pull request has met the following criteria:

- It has been merged into the `main` branch.
- It includes changes to the `mkdocs.yml` file or any file in the `docs` directory.
* It has been merged into the `main` branch.
* It includes changes to the `mkdocs.yml` file or any file in the `docs` directory.

## man-deploy-pages

This workflow runs when there a push meets the following criteria:
This workflow runs when a direct push meets the following criteria:

- It is to the `main` branch.
- It includes changes to the `mkdocs.yml` file or any file in the `docs` directory.
* It is to the `main` branch.
* It includes changes to the `mkdocs.yml` file or any file in the `docs` directory.

You can also trigger this flow manually using the
[GitHub Actions extension](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions) in VS Code.
To trigger this flow manually, use the [GitHub Actions extension][1] in VS Code.

[1]: https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions
2 changes: 1 addition & 1 deletion .github/workflows/auto-deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: deploy-pages-from-pr

on:
pull_request:
branches: main
branches: [main]
types: ["closed"]
paths: ["docs/**", "mkdocs.yml"]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/man-deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: deploy-pages-from-push

on:
push:
branches: main
branches: [main]
paths: ["docs/**", "mkdocs.yml", ".github/workflows/**"]
workflow_dispatch:

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ cython_debug/
*.pdf
*.pptx
*.ppt
!**requirements.txt

#ffmpeg metadata
ffmetada
ffmetada
16 changes: 11 additions & 5 deletions backend_audio/ffmetadata_generator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/python3
"""
Generate metadata for audio files.
"""
__doc__ = """
Goal: generate metadata to inject in m4b format file
"""
from tinytag import TinyTag
from typing import Optional

def __get_ffmetadata1(**kwargs) -> str:
isok = (kwargs['title'] is not None) or (kwargs['author'] is not None)
Expand All @@ -25,18 +29,20 @@ def __get_track_times(input_audio_paths:list) -> list:
starttimes.append(str(int(time)))
return starttimes

def generate_ffmetadata(input_audio_paths:list,
chapter_titles:list=None,
def generate_ffmetadata(input_audio_paths:list[str],
chapter_titles:Optional[list[str]]=None,
author:str=None,
title:str=None) -> str:
"""Generate metadata in ffmpeg format.

Arguments:
input_audio_paths: List[str] - path of audiable files
chapter_titles: List[str] - name of chapters defined on each files
input_audio_paths: Path of audio files
chapter_titles: Name of chapters in each file
author: The original document's author
title: The original document's title

Returns:
metadata: str
metadata: The generated audio file metadata
"""
starttimes=__get_track_times(input_audio_paths)
if chapter_titles is None:
Expand Down
103 changes: 59 additions & 44 deletions backend_audio/m4b.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module aim to generate audio and the file result in M4B
"""
from typing import List, Callable, Dict, Any
Generate audio and save it to a M4B or MP3 file.
"""
from typing import Callable, Any
import logging
import sys
import time
Expand All @@ -25,48 +26,51 @@
logger = logging.getLogger(__name__)

def get_back_end_tts() -> str:
"""Get the TTS engine for the system's operating system.
"""Get the TTS API for the system's operating system.

Returns:
The string name of the engine used for the caller's operating system.
back_end_tts: The name of the API that the caller's operating system uses.
"""
os_engine_map = {
"win32": "EDGE_TTS",
"cygwin": "EDGE_TTS",
"darwin": "GTTS"
}
return os_engine_map.get(sys.platform, "PYTTS")
back_end_tts = os_engine_map.get(sys.platform, "PYTTS")
return back_end_tts

async def get_voices_edge_tts(lang:Optional[str]=None) -> list[dict[str, Any]]:
"""Get the female voice in target language from EDGE-TTS.

async def get_voices_edge_tts(lang:str=LANGUAGE_DICT["it"]) -> List[Dict[str, Any]]:
"""get FEMALE voices in target language from EDGE-TTS.

Arguments:
lang: The desired language abbreviation.
lang: The desired language abbreviation. See [Internet Engineering Task Force (IETF) language codes](https://en.wikipedia.org/wiki/IETF_language_tag) for accepted abbreviations.

Returns:
ret: A list of matching voice mappings based on lang.
"""
if lang is None:
lang = LANGUAGE_DICT['it']
try:
vs = await edge_tts.VoicesManager.create()
ret = vs.find(Gender="Female", Language=lang)
except Exception: #TODO add a best exception handling #pylint: disable=W0511,W0718
ret = []
return ret

async def generate_audio_edge_tts(text_in:str,
out_mp3_path:str, *,
lang:str="it", # pylint: disable=W0613
voice:str) -> bool:
"""Generate audio with EDGE-TTS starting from text_in string
and save it in out_mp3_path path.

"""Generate audio with EDGE-TTS and save it to `out_mp3_path`.

Arguments:
text_in: The text used to generate the TTS.
out_mp3_path: The path to save the result MP3 file.
lang: The desired language abbreviation.
voice: The TTS engine voice ID.
out_mp3_path: The path to save the MP3 file to.
lang: The desired language abbreviation. See [Internet Engineering Task Force (IETF) language codes](https://en.wikipedia.org/wiki/IETF_language_tag) for accepted abbreviations.
voice: The TTS API voice ID.

Returns:
True if the function succesfully saves the MP3 file.
result: True if the function succesfully saves the MP3 file.
"""
com = edge_tts.Communicate(text_in, voice)
await com.save(out_mp3_path)
Expand Down Expand Up @@ -105,15 +109,15 @@ def __save_tts_audio_gtts(text_to_speech_str:str, mp3_path:str, lang:str) -> boo
return True

def generate_audio_gtts(text_in:str, out_mp3_path:str, *, lang:str="it") -> bool:
"""Generate audio using GTTS apis.
"""Generate audio using GTTS APIs and save it to `out_mp3_path`.

Arguments:
text_in: The text used to generate the TTS.
out_mp3_path: The path to save the result MP3 file.
lang: The desired language abbreviation.
out_mp3_path: The path to save the MP3 file to.
lang: The desired language abbreviation. See [Internet Engineering Task Force (IETF) language codes](https://en.wikipedia.org/wiki/IETF_language_tag) for accepted abbreviations.

Returns:
True if the function succesfully saves the MP3 file.
result: True if the function succesfully saves the MP3 file.
"""
chunks = __split_text_into_chunks(text_in)
if len(chunks)>1:
Expand All @@ -123,15 +127,15 @@ def generate_audio_gtts(text_in:str, out_mp3_path:str, *, lang:str="it") -> bool
return True

def generate_audio_pytts(text_in:str, out_mp3_path:str, *, lang:str="it") -> bool:
"""Generate audio using PYTTS apis.
"""Generate audio using PYTTS APIs and save it to `out_mp3_path`.

Arguments:
text_in: The text used to generate the TTS.
out_mp3_path: The path to save the result MP3 file.
lang: The desired language abbreviation.
out_mp3_path: The path to save the MP3 file to.
lang: The desired language abbreviation. See [Internet Engineering Task Force (IETF) language codes](https://en.wikipedia.org/wiki/IETF_language_tag) for accepted abbreviations.

Returns:
True if the function succesfully saves the MP3 file.
result: True if the function succesfully saves the MP3 file.
"""
if engine_ptts.getProperty("voice") != lang:
engine_ptts.setProperty("voice", LANGUAGE_DICT_PYTTS[lang])
Expand All @@ -152,15 +156,15 @@ def __sub_audio(audio_generator:Callable[[str, str], bool],
out = ffmpeg.output(dummy_concat, output_path_mp3, f='mp3')
out.run()

def generate_m4b(output_path: str, chapter_paths: List[str],
def generate_m4b(output_path: str, chapter_paths: list[str],
ffmetadata: str, pause_duration:int=0) -> None:
"""Generate the final audiobook starting from MP3s and METADATAs.
"""Generate the final audiobook from MP3s and metadata.

Arguments:
output_path: The path to save the final audiobook.
output_path: The path to save the final audiobook to.
chapter_paths: The paths where each chapter was saved.
ffmetadata: The ffmetadata file content.
pause_duration: the time pass between chapters, by default no pause
ffmetadata: The `ffmetadata` file content.
pause_duration: The duration of pauses between chapters. By default, there is no pause between chapters.
"""
silence = None
if pause_duration > 0:
Expand All @@ -183,11 +187,14 @@ def generate_m4b(output_path: str, chapter_paths: List[str],
raise e

def init(backend:str) -> None:
"""Init back end code per text-to-speech
SUPPORTED: EDGE_TTS, PYTTS, GTTS.

"""Init back end code for one of the following text-to-speech APIs:

* EDGE_TTS
* PYTTS
* GTTS

Arguments:
backend: The string name of the TTS engine.
backend: The name of the TTS API.
"""
global engine_ptts #pylint: disable=W0603
global voice_edge #pylint: disable=W0603
Expand All @@ -205,14 +212,15 @@ def init(backend:str) -> None:

def generate_audio(text_in:str, out_mp3_path:str, *,
lang:str="it", backend:str="PYTTS") -> bool:
"""Generating audio using tts apis.
"""Generating audio with a TTS API.

Arguments:
text_in: The text used to generate the TTS.
out_mp3_path: The path to save the result MP3 file.
lang: The desired language abbreviation.
backend: The string name of the TTS engine.
out_mp3_path: The path to save the MP3 file to.
lang: The desired language abbreviation. See [Internet Engineering Task Force (IETF) language codes](https://en.wikipedia.org/wiki/IETF_language_tag) for accepted abbreviations.
backend: The name of the TTS API.
Returns:
True if the function succesfully saves the MP3 file.
result: True if the function succesfully saves the MP3 file.
"""
ret_val = True
text_in = text_in.strip()
Expand All @@ -229,12 +237,19 @@ def generate_audio(text_in:str, out_mp3_path:str, *,
return ret_val

def close_edge_tts() -> None:
"""Need to close the async io process."""
"""Close the async IO process."""
global loop #pylint: disable=W0603,W0602
if loop:
loop.close()

def add_cover_to_audiobook(audio_path: str, cover_path: str, output_path: str) -> None:
"""Add a cover image to an audiobook.

Arguments:
audio_path: The path to the MP3 file.
cover_path: The path to the cover image file.
output_path: The path to save the result to.
"""
import subprocess
command = [
'ffmpeg',
Expand All @@ -251,4 +266,4 @@ def add_cover_to_audiobook(audio_path: str, cover_path: str, output_path: str) -
subprocess.run(command, check=True)

if __name__ == "__main__":
add_cover_to_audiobook("../Don Giovanni.m4b", "../cover.jpg", "out.m4b")
add_cover_to_audiobook("../Don Giovanni.m4b", "../cover.jpg", "out.m4b")
4 changes: 4 additions & 0 deletions docs/about/license.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: License
description: Copyright information for the Write2Audiobook project.
---

<!-- vale off -->

Copyright information for the Write2Audiobook project.

## Included projects
Expand Down Expand Up @@ -33,3 +35,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

<!-- vale on -->
Loading