downloader job: handle softvc hifigan

This commit is contained in:
Brandon Thomas 2022-12-16 02:41:40 -05:00
parent 91ac868369
commit 91695cde00
15 changed files with 421 additions and 8 deletions

View File

@ -9,6 +9,7 @@
<w>fakeyou</w>
<w>hifigan</w>
<w>scrollback</w>
<w>softvc</w>
<w>tacotron</w>
<w>transcoded</w>
<w>transcodings</w>

View File

@ -22,6 +22,7 @@ CREATE TABLE generic_download_jobs (
-- The type of the object will vary based on the type of the upload,
-- and we may include heuristics that auto-detect types in the future
-- Example types: 'hifigan', 'hifigan_rocket_vc' (codename for softvc), 'tacotron2', ...
on_success_downloaded_entity_type VARCHAR(32) DEFAULT NULL,
-- ========== UPLOAD DETAILS ==========

View File

@ -10,7 +10,9 @@ CREATE TABLE vocoder_models (
token VARCHAR(32) NOT NULL,
-- The architecture of the vocoder.
-- Currently supported values: 'hifigan'
-- Currently supported values:
-- * 'hifigan' - hifigan for Tacotron2
-- * 'hifigan_rocket_vc' - hifigan for SoftVC ("rocket" is a codename)
vocoder_type VARCHAR(32) NOT NULL,
-- ========== MUTABLE METADATA ==========

View File

@ -38,7 +38,11 @@ impl BadgeGranter {
let mut maybe_badge = None;
if count >= 250 {
if count >= 1000 {
maybe_badge = Some(UserBadge::TtsModelUploader1000);
} else if count >= 500 {
maybe_badge = Some(UserBadge::TtsModelUploader500);
} else if count >= 250 {
maybe_badge = Some(UserBadge::TtsModelUploader250);
} else if count >= 200 {
maybe_badge = Some(UserBadge::TtsModelUploader200);
@ -133,7 +137,11 @@ impl BadgeGranter {
let mut maybe_badge = None;
if count >= 250 {
if count >= 1000 {
maybe_badge = Some(UserBadge::VocoderModelUploader1000);
} else if count >= 500 {
maybe_badge = Some(UserBadge::VocoderModelUploader500);
} else if count >= 250 {
maybe_badge = Some(UserBadge::VocoderModelUploader250);
} else if count >= 200 {
maybe_badge = Some(UserBadge::VocoderModelUploader200);
@ -173,6 +181,56 @@ impl BadgeGranter {
Ok(())
}
/// This needs to be called *after* successful upload.
pub async fn maybe_grant_softvc_vocoder_model_uploads_badge(&self, user_token: &str) -> AnyhowResult<()> {
let count = self.count_softvc_vocoder_models_uploaded(user_token).await?;
let mut maybe_badge = None;
if count >= 1000 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader1000);
} else if count >= 500 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader500);
} else if count >= 250 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader250);
} else if count >= 200 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader200);
} else if count >= 150 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader150);
} else if count >= 100 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader100);
} else if count >= 50 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader50);
} else if count >= 20 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader20);
} else if count >= 10 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader10);
} else if count >= 5 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader5);
} else if count >= 1 {
maybe_badge = Some(UserBadge::VocoderRocketVcModelUploader1);
}
let badge = match maybe_badge {
Some(badge) => badge,
None => return Ok(()),
};
if self.has_badge(user_token, badge).await? {
return Ok(())
}
let _record_id = self.insert(
badge,
user_token,
).await?;
self.firehose_publisher.publish_user_badge_granted(user_token, badge.to_db_value())
.await?;
Ok(())
}
// =======================================================================
pub async fn has_badge(&self, user_token: &str, user_badge: UserBadge) -> AnyhowResult<bool> {
@ -245,6 +303,30 @@ SELECT count(*) as count
FROM vocoder_models
WHERE
creator_user_token = ?
AND vocoder_type = 'hifigan'
LIMIT 1
"#,
user_token
)
.fetch_one(&self.mysql_pool)
.await;
self.handle_count_query(maybe_result)
}
async fn count_softvc_vocoder_models_uploaded(
&self,
user_token: &str,
) -> AnyhowResult<u64> {
// NB: This could get expensive!
let maybe_result = sqlx::query_as!(
CountRecord,
r#"
SELECT count(*) as count
FROM vocoder_models
WHERE
creator_user_token = ?
AND vocoder_type = 'hifigan_rocket_vc'
LIMIT 1
"#,
user_token

View File

@ -17,6 +17,8 @@ pub enum UserBadge {
TtsModelUploader150,
TtsModelUploader200,
TtsModelUploader250,
TtsModelUploader500,
TtsModelUploader1000,
// Granted for vocoder models
VocoderModelUploader1,
@ -28,6 +30,21 @@ pub enum UserBadge {
VocoderModelUploader150,
VocoderModelUploader200,
VocoderModelUploader250,
VocoderModelUploader500,
VocoderModelUploader1000,
// Granted for vocoder (softvc aka "rocket_vc") models
VocoderRocketVcModelUploader1,
VocoderRocketVcModelUploader5,
VocoderRocketVcModelUploader10,
VocoderRocketVcModelUploader20,
VocoderRocketVcModelUploader50,
VocoderRocketVcModelUploader100,
VocoderRocketVcModelUploader150,
VocoderRocketVcModelUploader200,
VocoderRocketVcModelUploader250,
VocoderRocketVcModelUploader500,
VocoderRocketVcModelUploader1000,
// Granted for uploading templates
W2lTemplateUploader1,
@ -57,6 +74,7 @@ pub enum UserBadge {
}
impl UserBadge {
/// This is the value that lives in the database as a VARCHAR(32)
pub fn to_db_value(&self) -> &'static str {
match self {
UserBadge::EarlyUser => "early_user",
@ -69,6 +87,9 @@ impl UserBadge {
UserBadge::TtsModelUploader150 => "tts_model_uploader_150",
UserBadge::TtsModelUploader200 => "tts_model_uploader_200",
UserBadge::TtsModelUploader250 => "tts_model_uploader_250",
UserBadge::TtsModelUploader500 => "tts_model_uploader_500",
UserBadge::TtsModelUploader1000 => "tts_model_uploader_1000",
UserBadge::VocoderModelUploader1 => "vocoder_model_uploader_1",
UserBadge::VocoderModelUploader5 => "vocoder_model_uploader_5",
UserBadge::VocoderModelUploader10 => "vocoder_model_uploader_10",
@ -78,6 +99,21 @@ impl UserBadge {
UserBadge::VocoderModelUploader150 => "vocoder_model_uploader_150",
UserBadge::VocoderModelUploader200 => "vocoder_model_uploader_200",
UserBadge::VocoderModelUploader250 => "vocoder_model_uploader_250",
UserBadge::VocoderModelUploader500 => "vocoder_model_uploader_500",
UserBadge::VocoderModelUploader1000 => "vocoder_model_uploader_1000",
UserBadge::VocoderRocketVcModelUploader1 => "vocoder_rocket_vc_uploader_1",
UserBadge::VocoderRocketVcModelUploader5 => "vocoder_rocket_vc_uploader_5",
UserBadge::VocoderRocketVcModelUploader10 => "vocoder_rocket_vc_uploader_10",
UserBadge::VocoderRocketVcModelUploader20 => "vocoder_rocket_vc_uploader_20",
UserBadge::VocoderRocketVcModelUploader50 => "vocoder_rocket_vc_uploader_50",
UserBadge::VocoderRocketVcModelUploader100 => "vocoder_rocket_vc_uploader_100",
UserBadge::VocoderRocketVcModelUploader150 => "vocoder_rocket_vc_uploader_150",
UserBadge::VocoderRocketVcModelUploader200 => "vocoder_rocket_vc_uploader_200",
UserBadge::VocoderRocketVcModelUploader250 => "vocoder_rocket_vc_uploader_250",
UserBadge::VocoderRocketVcModelUploader500 => "vocoder_rocket_vc_uploader_500",
UserBadge::VocoderRocketVcModelUploader1000 => "vocoder_rocket_vc_uploader_1000", // NB: 31 characters, table max is 32
UserBadge::W2lTemplateUploader1 => "w2l_template_uploader_1",
UserBadge::W2lTemplateUploader10 => "w2l_template_uploader_10",
UserBadge::W2lTemplateUploader50 => "w2l_template_uploader_50",
@ -88,6 +124,7 @@ impl UserBadge {
UserBadge::W2lTemplateUploader2000 => "w2l_template_uploader_2000",
UserBadge::W2lTemplateUploader5000 => "w2l_template_uploader_5000",
UserBadge::W2lTemplateUploader10000 => "w2l_template_uploader_10000",
//UserBadge::TtsInferenceUser100 => "tts_inference_100",
//UserBadge::TtsInferenceUser500 => "tts_inference_500",
//UserBadge::TtsInferenceUser1000 => "tts_inference_1000",
@ -113,7 +150,10 @@ impl UserBadge {
| UserBadge::TtsModelUploader100
| UserBadge::TtsModelUploader150
| UserBadge::TtsModelUploader200
| UserBadge::TtsModelUploader250 => UserBadgeType::TtsModelUploader,
| UserBadge::TtsModelUploader250
| UserBadge::TtsModelUploader500
| UserBadge::TtsModelUploader1000
=> UserBadgeType::TtsModelUploader,
UserBadge::VocoderModelUploader1
| UserBadge::VocoderModelUploader5
@ -123,7 +163,23 @@ impl UserBadge {
| UserBadge::VocoderModelUploader100
| UserBadge::VocoderModelUploader150
| UserBadge::VocoderModelUploader200
| UserBadge::VocoderModelUploader250 => UserBadgeType::VocoderModelUploader,
| UserBadge::VocoderModelUploader250
| UserBadge::VocoderModelUploader500
| UserBadge::VocoderModelUploader1000
=> UserBadgeType::VocoderModelUploader,
UserBadge::VocoderRocketVcModelUploader1
| UserBadge::VocoderRocketVcModelUploader5
| UserBadge::VocoderRocketVcModelUploader10
| UserBadge::VocoderRocketVcModelUploader20
| UserBadge::VocoderRocketVcModelUploader50
| UserBadge::VocoderRocketVcModelUploader100
| UserBadge::VocoderRocketVcModelUploader150
| UserBadge::VocoderRocketVcModelUploader200
| UserBadge::VocoderRocketVcModelUploader250
| UserBadge::VocoderRocketVcModelUploader500
| UserBadge::VocoderRocketVcModelUploader1000
=> UserBadgeType::VocoderRocketVcModelUploader,
UserBadge::W2lTemplateUploader1
| UserBadge::W2lTemplateUploader10

View File

@ -11,6 +11,9 @@ pub enum UserBadgeType {
/// Granted for uploading vocoder models
VocoderModelUploader,
/// Granted for uploading vocoder (softvc aka "rocket_vc") models
VocoderRocketVcModelUploader,
/// Granted for uploading W2L templates
W2lTemplateUploader,

View File

@ -1,8 +1,16 @@
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize, sqlx::Type)]
pub enum VocoderType {
/// NB: Note - this is hifigan for Tacotron2.
/// Some work will be needed to unify this with other hifigan types.
#[serde(rename = "hifigan")]
#[sqlx(rename = "hifigan")]
HifiGan,
/// NB: Note - this is hifigan for SoftVC (our internal codename is "rocketvc").
/// Some work will need to be done to unify this with other hifigan types.
#[serde(rename = "hifigan_rocket_vc")]
#[sqlx(rename = "hifigan_rocket_vc")]
HifiGanRocketVc,
}
/// NB: Legacy API for older code.
@ -10,12 +18,14 @@ impl VocoderType {
pub fn to_str(&self) -> &'static str {
match self {
Self::HifiGan=> "hifigan",
Self::HifiGanRocketVc => "hifigan_rocket_vc",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"hifigan" => Ok(Self::HifiGan),
"hifigan_rocket_vc" => Ok(Self::HifiGanRocketVc),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@ -29,5 +39,18 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(VocoderType::HifiGan, "hifigan");
assert_serialization(VocoderType::HifiGanRocketVc, "hifigan_rocket_vc");
}
#[test]
fn to_str() {
assert_eq!(VocoderType::HifiGan.to_str(), "hifigan");
assert_eq!(VocoderType::HifiGanRocketVc.to_str(), "hifigan_rocket_vc");
}
#[test]
fn from_str() {
assert_eq!(VocoderType::from_str("hifigan").unwrap(), VocoderType::HifiGan);
assert_eq!(VocoderType::from_str("hifigan_rocket_vc").unwrap(), VocoderType::HifiGanRocketVc);
}
}

View File

@ -66,6 +66,7 @@ impl BucketPathUnifier {
}
/// User-uploaded vocoders.
/// These can be HifiGan and HifiGanSoftVc
pub fn vocoder_path(&self, vocoder_file_hash: &str) -> PathBuf {
let hashed_path = Self::hashed_directory_path(vocoder_file_hash);
let model_filename = format!("{}.pt", &vocoder_file_hash);

View File

@ -1,4 +1,5 @@
use crate::job_types::hifigan::hifigan_model_check_command::HifiGanModelCheckCommand;
use crate::job_types::hifigan_softvc::hifigan_softvc_model_check_command::HifiGanSoftVcModelCheckCommand;
use crate::job_types::tacotron::tacotron_model_check_command::TacotronModelCheckCommand;
use database_queries::mediators::badge_granter::BadgeGranter;
use database_queries::mediators::firehose_publisher::FirehosePublisher;
@ -43,4 +44,5 @@ pub struct SidecarConfigs {
pub google_drive_downloader: GoogleDriveDownloadCommand,
pub tacotron_model_check_command: TacotronModelCheckCommand,
pub hifigan_model_check_command: HifiGanModelCheckCommand,
pub hifigan_softvc_model_check_command: HifiGanSoftVcModelCheckCommand,
}

View File

@ -4,6 +4,7 @@ use container_common::anyhow_result::AnyhowResult;
use container_common::filesystem::safe_delete_temp_directory::safe_delete_temp_directory;
use crate::JobState;
use crate::job_types::hifigan::process_hifigan_vocoder::process_hifigan_vocoder;
use crate::job_types::hifigan_softvc::process_hifigan_softvc_vocoder::process_hifigan_softvc_vocoder;
use crate::job_types::tacotron::process_tacotron_model::process_tacotron_model;
use database_queries::queries::generic_download::job::list_available_generic_download_jobs::AvailableDownloadJob;
use database_queries::queries::generic_download::job::mark_generic_download_job_done::mark_generic_download_job_done;
@ -69,6 +70,28 @@ pub async fn process_single_job(job_state: &JobState, job: &AvailableDownloadJob
entity_token = results.entity_token.clone();
entity_type = results.entity_type.clone();
}
GenericDownloadType::HifiGanRocketVc => {
let results = process_hifigan_softvc_vocoder(
job_state,
job,
&temp_dir,
&download_filename,
&mut redis_logger,
).await?;
entity_token = results.entity_token.clone();
entity_type = results.entity_type.clone();
}
GenericDownloadType::RocketVc => {
let results = process_softvc_model(
job_state,
job,
&temp_dir,
&download_filename,
&mut redis_logger,
).await?;
entity_token = results.entity_token.clone();
entity_type = results.entity_type.clone();
}
GenericDownloadType::Tacotron2 => {
let results = process_tacotron_model(
job_state,
@ -80,8 +103,6 @@ pub async fn process_single_job(job_state: &JobState, job: &AvailableDownloadJob
entity_token = results.entity_token.clone();
entity_type = results.entity_type.clone();
}
GenericDownloadType::HifiGanRocketVc => {}
GenericDownloadType::RocketVc => {}
}
// =====================================================

View File

@ -1,7 +1,7 @@
use crate::AnyhowResult;
use log::info;
use subprocess::{Popen, PopenConfig};
use std::path::Path;
use subprocess::{Popen, PopenConfig};
use subprocess_common::docker_options::DockerOptions;
/// This command is used to check hifigan for being a real model

View File

@ -0,0 +1,84 @@
use crate::AnyhowResult;
use log::info;
use std::path::Path;
use subprocess::{Popen, PopenConfig};
use subprocess_common::docker_options::DockerOptions;
/// This command is used to check hifigan for being a real model
#[derive(Clone)]
pub struct HifiGanSoftVcModelCheckCommand {
/// Where the HifiGan code lives
hifigan_softvc_root_code_directory: String,
/// eg. `source python/bin/activate`
maybe_virtual_env_activation_command: Option<String>,
hifigan_softvc_model_check_script_name: String,
/// If this is run under Docker (eg. in development), these are the options.
maybe_docker_options: Option<DockerOptions>,
}
impl HifiGanSoftVcModelCheckCommand {
pub fn new(
hifigan_root_code_directory: &str,
maybe_virtual_env_activation_command: Option<&str>,
hifigan_model_check_script_name: &str,
maybe_docker_options: Option<DockerOptions>,
) -> Self {
Self {
hifigan_softvc_root_code_directory: hifigan_root_code_directory.to_string(),
maybe_virtual_env_activation_command: maybe_virtual_env_activation_command.map(|s| s.to_string()),
hifigan_softvc_model_check_script_name: hifigan_model_check_script_name.to_string(),
maybe_docker_options,
}
}
pub fn execute<P: AsRef<Path>>(
&self,
checkpoint_path: P,
output_metadata_filename: P,
) -> AnyhowResult<()> {
let mut command = String::new();
command.push_str(&format!("cd {}", self.hifigan_softvc_root_code_directory));
if let Some(venv_command) = self.maybe_virtual_env_activation_command.as_deref() {
command.push_str(" && ");
command.push_str(venv_command);
command.push_str(" ");
}
command.push_str(" && ");
command.push_str("python ");
command.push_str(&self.hifigan_softvc_model_check_script_name);
command.push_str(" --checkpoint_path ");
command.push_str(&checkpoint_path.as_ref().display().to_string());
command.push_str(" --output_metadata_filename ");
command.push_str(&output_metadata_filename.as_ref().display().to_string());
if let Some(docker_options) = self.maybe_docker_options.as_ref() {
command = docker_options.to_command_string(&command);
}
info!("Command: {:?}", command);
let command_parts = [
"bash",
"-c",
&command
];
let mut p = Popen::create(&command_parts, PopenConfig {
..Default::default()
})?;
info!("Pid : {:?}", p.pid());
let exit_status = p.wait()?;
info!("Exit status: {:?}", exit_status);
Ok(())
}
}

View File

@ -0,0 +1,2 @@
pub mod hifigan_softvc_model_check_command;
pub mod process_hifigan_softvc_vocoder;

View File

@ -0,0 +1,134 @@
use anyhow::anyhow;
use container_common::anyhow_result::AnyhowResult;
use container_common::filesystem::check_file_exists::check_file_exists;
use container_common::filesystem::safe_delete_temp_directory::safe_delete_temp_directory;
use container_common::filesystem::safe_delete_temp_file::safe_delete_temp_file;
use container_common::hashing::hash_file_sha2::hash_file_sha2;
use crate::JobState;
use crate::job_steps::job_results::JobResults;
use database_queries::queries::generic_download::job::list_available_generic_download_jobs::AvailableDownloadJob;
use database_queries::queries::vocoder::insert_vocoder_model::{Args, insert_vocoder_model};
use jobs_common::redis_job_status_logger::RedisJobStatusLogger;
use log::{info, warn};
use reusable_types::db::enums::vocoder_type::VocoderType;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use tempdir::TempDir;
/// Returns the token of the entity.
pub async fn process_hifigan_softvc_vocoder<'a, 'b>(
job_state: &JobState,
job: &AvailableDownloadJob,
temp_dir: &TempDir,
download_filename: &str,
redis_logger: &'a mut RedisJobStatusLogger<'b>,
) -> AnyhowResult<JobResults> {
// ==================== RUN MODEL CHECK ==================== //
info!("Checking that model is valid...");
redis_logger.log_status("checking hifigan (softvc) model")?;
let file_path = PathBuf::from(download_filename.clone());
let output_metadata_fs_path = temp_dir.path().join("metadata.json");
let model_check_result = job_state.sidecar_configs.hifigan_model_check_command.execute(
&file_path,
&output_metadata_fs_path
);
if let Err(e) = model_check_result {
safe_delete_temp_file(&file_path);
safe_delete_temp_directory(&temp_dir);
return Err(anyhow!("model check error: {:?}", e));
}
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
info!("Checking that metadata output file exists...");
check_file_exists(&output_metadata_fs_path)?;
let file_metadata = match read_metadata_file(&output_metadata_fs_path) {
Ok(metadata) => metadata,
Err(e) => {
safe_delete_temp_file(&file_path);
safe_delete_temp_file(&output_metadata_fs_path);
safe_delete_temp_directory(&temp_dir);
return Err(e);
}
};
// ==================== UPLOAD MODEL FILE ==================== //
info!("Uploading HifiGan (softvc) vocoder to GCS...");
let private_bucket_hash = hash_file_sha2(&download_filename)?;
info!("File hash: {}", private_bucket_hash);
let model_bucket_path = job_state.bucket_path_unifier.vocoder_path(&private_bucket_hash);
info!("Destination bucket path: {:?}", &model_bucket_path);
redis_logger.log_status("uploading hifigan model")?;
if let Err(e) = job_state.bucket_client.upload_filename(&model_bucket_path, &file_path).await {
safe_delete_temp_file(&output_metadata_fs_path);
safe_delete_temp_file(&file_path);
safe_delete_temp_directory(&temp_dir);
return Err(e);
}
// ==================== DELETE DOWNLOADED FILE ==================== //
// NB: We should be using a tempdir, but to make absolutely certain we don't overflow the disk...
safe_delete_temp_file(&output_metadata_fs_path);
safe_delete_temp_file(&file_path);
safe_delete_temp_directory(&temp_dir);
// ==================== SAVE RECORDS ==================== //
info!("Saving model record...");
let (_id, model_token) = insert_vocoder_model(Args {
vocoder_type: VocoderType::HifiGanRocketVc, // NB: "rocket_vc" is an internal codename for softvc.
title: &job.title,
original_download_url: &job.download_url,
original_filename: &download_filename,
file_size_bytes: file_metadata.file_size_bytes,
creator_user_token: &job.creator_user_token,
creator_ip_address: &job.creator_ip_address,
creator_set_visibility: job.creator_set_visibility,
private_bucket_hash: &private_bucket_hash,
private_bucket_object_name: &model_bucket_path,
mysql_pool: &job_state.mysql_pool
}).await?;
job_state.badge_granter.maybe_grant_softvc_vocoder_model_uploads_badge(&job.creator_user_token)
.await
.map_err(|e| {
warn!("error maybe awarding badge: {:?}", e);
anyhow!("error maybe awarding badge")
})?;
Ok(JobResults {
entity_token: Some(model_token),
entity_type: Some("hifigan_rocket_vc".to_string()), // NB: This may be different from `GenericDownloadType` in the future!
})
}
#[derive(Deserialize)]
struct FileMetadata {
pub file_size_bytes: u64,
}
fn read_metadata_file(filename: &PathBuf) -> AnyhowResult<FileMetadata> {
let mut file = File::open(filename)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
Ok(serde_json::from_str(&buffer)?)
}

View File

@ -1,3 +1,4 @@
pub mod hifigan;
pub mod hifigan_softvc;
pub mod tacotron;
pub mod talknet;