Files
Voice-Cloning-App/application/utils.py

234 lines
7.0 KiB
Python
Raw Normal View History

2021-03-10 15:56:40 +00:00
import logging
import os
2021-09-18 13:23:16 +01:00
import io
2021-03-10 15:56:40 +00:00
from datetime import datetime
2021-03-24 20:30:25 +00:00
import traceback
2021-04-04 18:52:36 +01:00
import shutil
2021-05-02 17:09:05 +01:00
import zipfile
import librosa
2021-09-18 13:23:16 +01:00
from flask import send_file
2021-08-08 21:43:24 +01:00
import resampy # noqa
2021-03-10 15:56:40 +00:00
from main import socketio
2021-04-01 19:12:38 +01:00
from dataset.audio_processing import convert_audio
2021-04-07 20:26:51 +01:00
from dataset.analysis import save_dataset_info
from dataset.clip_generator import CHARACTER_ENCODING
2021-03-10 15:56:40 +00:00
class SocketIOHandler(logging.Handler):
2021-05-05 21:13:58 +01:00
"""
Sends logger messages to the frontend using flask-socketio.
These are handled in application.js
"""
2021-03-10 15:56:40 +00:00
def emit(self, record):
text = record.getMessage()
if text.startswith("Progress"):
text = text.split("-")[1]
current, total = text.split("/")
socketio.emit("progress", {"number": current, "total": total}, namespace="/voice")
elif text.startswith("Status"):
socketio.emit("status", {"text": text.replace("Status -", "")}, namespace="/voice")
2021-09-17 17:06:15 +01:00
elif text.startswith("Alignment"):
text = text.split("- ")[1]
iteration, image = text.split(", ")
socketio.emit("alignment", {"iteration": iteration, "image": image}, namespace="/voice")
2021-03-10 15:56:40 +00:00
else:
socketio.emit("logs", {"text": text}, namespace="/voice")
# Data
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("voice")
logger.addHandler(SocketIOHandler())
thread = None
def background_task(func, **kwargs):
2021-05-05 21:13:58 +01:00
"""
Runs a background task.
2021-04-20 12:23:11 +01:00
If function errors out it will send an error log to the error logging server and page.
Sends 'done' message to frontend when complete.
Parameters
----------
func : function
Function to run in background
kwargs : kwargs
Kwargs to pass to function
"""
2021-03-10 15:56:40 +00:00
try:
socketio.sleep(5)
func(logging=logger, **kwargs)
except Exception as e:
2021-03-22 19:34:45 +00:00
error = {"type": e.__class__.__name__, "text": str(e), "stacktrace": traceback.format_exc()}
socketio.emit("error", error, namespace="/voice")
2021-05-02 17:09:05 +01:00
raise e
2021-03-10 15:56:40 +00:00
2021-05-02 17:09:05 +01:00
socketio.emit("done", {"text": None}, namespace="/voice")
2021-03-10 15:56:40 +00:00
def start_progress_thread(func, **kwargs):
2021-05-05 21:13:58 +01:00
"""
Starts a background task using socketio.
2021-04-20 12:23:11 +01:00
Parameters
----------
func : function
Function to run in background
kwargs : kwargs
Kwargs to pass to function
"""
2021-03-10 15:56:40 +00:00
global thread
print("Starting Thread")
thread = socketio.start_background_task(background_task, func=func, **kwargs)
2021-09-18 15:59:23 +01:00
def serve_file(path, filename, mimetype, as_attachment=True):
2021-09-18 13:23:16 +01:00
with open(path, "rb") as f:
2021-09-18 16:33:43 +01:00
return send_file(
io.BytesIO(f.read()), attachment_filename=filename, mimetype=mimetype, as_attachment=as_attachment
)
2021-09-18 13:23:16 +01:00
2021-03-10 15:56:40 +00:00
def get_next_url(urls, path):
2021-05-05 21:13:58 +01:00
"""
Returns the URL of the next step in the voice cloning process.
2021-04-20 12:23:11 +01:00
Parameters
----------
urls : dict
Frontend url paths and names
path : str
Current URL
Returns
-------
str
URL of next step or '' if not found
"""
2021-03-10 15:56:40 +00:00
urls = list(urls.keys())
next_url_index = urls.index(path) + 1
return urls[next_url_index] if next_url_index < len(urls) else ""
2021-04-01 19:12:38 +01:00
def get_suffix():
2021-05-05 21:13:58 +01:00
"""
Generates a filename suffix using the currrent datetime.
2021-04-20 12:23:11 +01:00
Returns
-------
str
String suffix
"""
2021-03-10 15:56:40 +00:00
return datetime.now().strftime("%d-%m-%Y_%H-%M-%S")
2021-04-04 18:52:36 +01:00
def delete_folder(path):
2021-05-05 21:13:58 +01:00
"""
Deletes a folder.
2021-04-20 12:23:11 +01:00
Parameters
----------
path : str
Path to folder
Raises
-------
AssertionError
If folder is not found
"""
2021-04-04 18:52:36 +01:00
assert os.path.isdir(path), f"{path} does not exist"
shutil.rmtree(path)
2021-05-02 17:09:05 +01:00
def import_dataset(dataset, dataset_directory, audio_folder, logging):
2021-05-05 21:13:58 +01:00
"""
Imports a dataset zip into the app.
Checks required files are present, saves the files,
converts the audio to the required format and generates the info file.
Deletes given zip regardless of success.
Parameters
----------
dataset : str
Path to dataset zip
dataset_directory : str
Destination path for the dataset
audio_folder : str
Destination path for the dataset audio
logging : logging
Logging object to write logs to
Raises
-------
AssertionError
If files are missing or invalid
"""
2021-05-02 17:09:05 +01:00
try:
with zipfile.ZipFile(dataset, mode="r") as z:
files_list = z.namelist()
2021-05-02 17:09:23 +01:00
assert (
"metadata.csv" in files_list
), "Dataset missing metadata.csv. Make sure this file is in the root of the zip file"
2021-05-02 17:09:05 +01:00
folders = [x.split("/")[0] for x in files_list if "/" in x]
2021-05-02 17:09:23 +01:00
assert (
"wavs" in folders
), "Dataset missing wavs folder. Make sure this folder is in the root of the zip file"
2021-05-02 17:09:05 +01:00
wavs = [x for x in files_list if x.startswith("wavs/") and x.endswith(".wav")]
assert wavs, "No wavs found in wavs folder"
2021-07-03 13:19:47 +01:00
metadata = z.read("metadata.csv").decode(CHARACTER_ENCODING, "ignore").replace("\r\n", "\n")
2021-05-02 17:44:18 +01:00
num_metadata_rows = len([row for row in metadata.split("\n") if row])
2021-05-02 17:09:23 +01:00
assert (
len(wavs) == num_metadata_rows
), f"Number of wavs and labels do not match. metadata: {num_metadata_rows}, wavs: {len(wavs)}"
2021-05-02 17:09:05 +01:00
logging.info("Creating directory")
os.makedirs(dataset_directory, exist_ok=False)
os.makedirs(audio_folder, exist_ok=False)
# Save metadata
logging.info("Saving files")
2021-07-03 13:19:47 +01:00
with open(os.path.join(dataset_directory, "metadata.csv"), "w", encoding=CHARACTER_ENCODING) as f:
2021-05-02 17:09:05 +01:00
f.write(metadata)
# Save wavs
total_wavs = len(wavs)
clip_lengths = []
filenames = {}
for i in range(total_wavs):
wav = wavs[i]
data = z.read(wav)
path = os.path.join(dataset_directory, "wavs", wav.split("/")[1])
with open(path, "wb") as f:
f.write(data)
new_path = convert_audio(path)
2021-06-10 22:15:05 +01:00
duration = librosa.get_duration(filename=new_path)
clip_lengths.append(duration)
2021-05-02 17:09:05 +01:00
filenames[path] = new_path
logging.info(f"Progress - {i+1}/{total_wavs}")
logging.info(f"Longest clip: {max(clip_lengths)}s, Shortest clip: {min(clip_lengths)}s")
2021-05-02 17:09:05 +01:00
# Get around "file in use" by using delay
logging.info("Deleting temp files")
for old_path, new_path in filenames.items():
os.remove(old_path)
os.rename(new_path, old_path)
# Create info file
logging.info("Creating info file")
save_dataset_info(
os.path.join(dataset_directory, "metadata.csv"),
os.path.join(dataset_directory, "wavs"),
os.path.join(dataset_directory, "info.json"),
2021-05-02 17:09:23 +01:00
clip_lengths=clip_lengths,
2021-05-02 17:09:05 +01:00
)
except Exception as e:
os.remove(dataset)
raise e
2021-05-02 17:09:23 +01:00
2021-05-02 17:09:05 +01:00
os.remove(dataset)