feat: add pipeline
Some checks failed
continuous-integration/drone Build is failing

This commit is contained in:
Yandrik 2024-04-25 15:37:15 +02:00
parent 69da9f8d25
commit 918eca78cb
4 changed files with 150 additions and 37 deletions

33
.drone.yml Normal file
View File

@ -0,0 +1,33 @@
kind: pipeline
type: docker
name: default
steps:
- name: Unit Tests
image: python:3.12
commands:
- pip install poetry
- poetry config virtualenvs.create false
- poetry install
- poetry run pytest
- name: Python Code Lint
image: python:3.12
commands:
- pip install poetry
- poetry config virtualenvs.create false
- poetry install
- poetry run black .
- name: Static Type check
image: python:3.12
commands:
- pip install poetry
- poetry config virtualenvs.create false
- poetry install
- poetry run mypy .
- name: Deploy
image: python:3.12
commands:
- echo "TODO"

View File

@ -4,21 +4,33 @@ from pathlib import Path
from pydub import AudioSegment, silence
from openai import OpenAI
import time
from sys import exit
def get_api_key() -> str:
try:
with open('apikey.secret') as f:
with open("apikey.secret") as f:
api_key = f.read().strip()
if api_key == '':
raise ValueError('API key not found. Please provide your API key in the file \'apikey.secret\'.')
if api_key == "":
raise ValueError(
"API key not found. Please provide your API key in the file 'apikey.secret'."
)
return api_key
except FileNotFoundError:
raise ValueError('Couldn\'t read API key from file \'apikey.secret\'. Does it exist?')
raise ValueError(
"Couldn't read API key from file 'apikey.secret'. Does it exist? Alternatively, use the argument '--api-key' to provide your API key."
)
class AudioGenerator:
def __init__(self, parsed_data, output_file, default_silence=650, ai_provider="openai", api_key=None):
def __init__(
self,
parsed_data,
output_file,
default_silence=650,
ai_provider="openai",
api_key=None,
):
self.parsed_data = parsed_data
self.output_file = output_file
self.default_silence = default_silence
@ -32,18 +44,20 @@ class AudioGenerator:
case "openai":
self.client = OpenAI(api_key=api_key)
case "zuki":
self.client = OpenAI(base_url="https://zukijourney.xyzbot.net/v1", api_key=api_key)
self.client = OpenAI(
base_url="https://zukijourney.xyzbot.net/v1", api_key=api_key
)
case _:
raise ValueError(f"Unsupported AI provider: {ai_provider}")
def validate_voices(self):
"""Check if all voices in the parsed data are valid."""
valid_voices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer']
valid_voices = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
invalid_voices = set()
for item in self.parsed_data:
if item['type'] == 'voice' and item['voice'] not in valid_voices:
invalid_voices.add(item['voice'])
if item["type"] == "voice" and item["voice"] not in valid_voices:
invalid_voices.add(item["voice"])
if invalid_voices:
raise ValueError(f"Invalid voice(s) found: {', '.join(invalid_voices)}")
@ -56,22 +70,24 @@ class AudioGenerator:
section_errors = []
for item in self.parsed_data:
if item['type'] == 'section_start':
defined_sections.add(item['section_id'])
elif item['type'] == 'insert_section':
section_id = item['section_id']
if item["type"] == "section_start":
defined_sections.add(item["section_id"])
elif item["type"] == "insert_section":
section_id = item["section_id"]
if section_id not in defined_sections:
section_errors.append(f"Section {section_id} is used before being defined.")
used_sections.add(item['section_id'])
section_errors.append(
f"Section {section_id} is used before being defined."
)
used_sections.add(item["section_id"])
undefined_sections = used_sections - defined_sections
if undefined_sections or len(section_errors) > 0:
raise ValueError(f"Section Validation Errors:\n {'\n '.join(section_errors)}\n\nUndefined section(s) used: {', '.join(map(str, undefined_sections))}")
raise ValueError(
f"Section Validation Errors:\n {'\n '.join(section_errors)}\n\nUndefined section(s) used: {', '.join(map(str, undefined_sections))}"
)
print("All sections are properly defined.")
def text_to_speech(self, text, voice):
"""Generate speech using OpenAI's voice API with retry logic."""
print(f"Voice {voice} chosen")
@ -95,15 +111,19 @@ class AudioGenerator:
print(f"Failed to generate TTS: {e}")
attempts += 1
if attempts >= 3:
user_decision = input("Retry TTS generation? (yes/no): ").strip().lower()
if user_decision.lower() in ['y', 'yes']:
user_decision = (
input("Retry TTS generation? (yes/no): ").strip().lower()
)
if user_decision.lower() in ["y", "yes"]:
attempts = 0 # Reset attempts for another round of retries
else:
print("Exiting due to TTS generation failure.")
exit(1)
else:
print("Retrying...")
time.sleep(1) # Wait a bit before retrying to avoid hammering the API too quickly
time.sleep(
1
) # Wait a bit before retrying to avoid hammering the API too quickly
def generate_audio(self):
self.validate_voices()
@ -112,32 +132,36 @@ class AudioGenerator:
current_voice = None
for item in self.parsed_data:
if item['type'] == 'voice':
current_voice = item['voice']
elif item['type'] == 'text':
if item["type"] == "voice":
current_voice = item["voice"]
elif item["type"] == "text":
if not current_voice:
raise ValueError("First text segment before voice was selected!")
audio_segment = self.text_to_speech(item['text'], current_voice)
audio_segment = self.text_to_speech(item["text"], current_voice)
combined_audio += audio_segment
if self.default_silence > 0:
combined_audio += AudioSegment.silent(duration=self.default_silence)
if self.current_section is not None:
self.sections[self.current_section] += audio_segment
elif item['type'] == 'silence':
combined_audio += AudioSegment.silent(duration=item['duration'])
elif item["type"] == "silence":
combined_audio += AudioSegment.silent(duration=item["duration"])
if self.current_section is not None:
self.sections[self.current_section] += AudioSegment.silent(duration=item['duration'])
elif item['type'] == 'section_start':
self.current_section = item['section_id']
self.sections[self.current_section] += AudioSegment.silent(
duration=item["duration"]
)
elif item["type"] == "section_start":
self.current_section = item["section_id"]
self.sections[self.current_section] = AudioSegment.empty()
elif item['type'] == 'section_end':
elif item["type"] == "section_end":
self.current_section = None
elif item['type'] == 'insert_section':
section_id = item['section_id']
elif item["type"] == "insert_section":
section_id = item["section_id"]
if section_id in self.sections:
combined_audio += self.sections[section_id]
else:
raise ValueError(f"Section {section_id} not found!")
combined_audio.export(self.output_file, format="mp3")
# Example usage

59
poetry.lock generated
View File

@ -178,6 +178,63 @@ files = [
[package.dependencies]
altgraph = ">=0.17"
[[package]]
name = "mypy"
version = "1.10.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
{file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
{file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
{file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
{file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
{file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
{file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
{file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
{file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
{file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
{file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
{file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
{file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
{file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
{file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
{file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
{file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
{file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
{file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
{file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
{file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
{file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
{file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
{file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
{file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
{file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
]
[package.dependencies]
mypy-extensions = ">=1.0.0"
typing-extensions = ">=4.1.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "openai"
version = "1.23.6"
@ -500,4 +557,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.12,<3.13"
content-hash = "5edb9bebdbf3d2cbd05201e953830c0bd3cb05956885df55a192ad1026081cc3"
content-hash = "b7a69be6accf7803d29e50194355e464610cabb3e6114dc7c5e4f027b4a475d0"

View File

@ -13,11 +13,10 @@ argparse = "^1.4.0"
# pathlib = "^1.0.1"
pydub = "^0.25.1"
[tool.poetry.dev-dependencies]
pytest = "^8.1.1"
[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
pyinstaller = "^6.6.0"
mypy = "^1.10.0"
[build-system]
requires = ["poetry-core"]