diff --git a/AIMeiSheng/docker_demo/common.py b/AIMeiSheng/docker_demo/common.py index 25d701e..b9adb82 100644 --- a/AIMeiSheng/docker_demo/common.py +++ b/AIMeiSheng/docker_demo/common.py @@ -1,93 +1,100 @@ import os import time -import logging +# import logging import urllib, urllib.request +# 测试/正式环境 +gs_prod = True + gs_tmp_dir = "/tmp/ai_meisheng_tmp" gs_model_dir = "/tmp/ai_meisheng_models" gs_resource_cache_dir = "/tmp/ai_meisheng_resource_cache" gs_svc_model_path = os.path.join(gs_model_dir, "weights/xusong_v2_org_version_alldata_embed1_enzx_diff_fi_e15_s244110.pth") gs_embed_model_path = os.path.join(gs_model_dir, "RawNet3/models/weights/model.pt") gs_hubert_model_path = os.path.join(gs_model_dir, "hubert.pt") gs_rmvpe_model_path = os.path.join(gs_model_dir, "rmvpe.pt") # errcode gs_err_code_success = 0 gs_err_code_download_vocal = 100 gs_err_code_download_svc_url = 101 gs_err_code_svc_process = 102 gs_err_code_transcode = 103 gs_err_code_volume_adjust = 104 gs_err_code_upload = 105 gs_err_code_params = 106 gs_err_code_pending = 107 gs_err_code_too_many_connections = 429 gs_redis_conf = { "host": "av-credis.starmaker.co", "port": 6379, "pwd": "lKoWEhz%jxTO", } gs_server_redis_conf = { - "producer": "ai_meisheng_producer", # 输入的队列 - "ai_meisheng_key_prefix": "ai_meisheng_key_", # 存储结果情况 + "producer": "test_ai_meisheng_producer", # 输入的队列 + "ai_meisheng_key_prefix": "test_ai_meisheng_key_", # 存储结果情况 } +if gs_prod: + gs_server_redis_conf = { + "producer": "ai_meisheng_producer", # 输入的队列 + "ai_meisheng_key_prefix": "ai_meisheng_key_", # 存储结果情况 + } + def download2disk(url, dst_path): - st = time.time() urllib.request.urlretrieve(url, dst_path) - print(f"download {url} -> {dst_path} sp = {time.time() - st}") return os.path.exists(dst_path) def exec_cmd(cmd): # gs_logger.info(cmd) print(cmd) ret = os.system(cmd) if ret != 0: return False return True def exec_cmd_and_result(cmd): r = os.popen(cmd) text = r.read() r.close() return text def upload_file2cos(key, file_path, region='ap-singapore', bucket_name='av-audit-sync-sg-1256122840'): """ 将文件上传到cos :param key: 桶上的具体地址 :param file_path: 本地文件地址 :param region: 区域 :param bucket_name: 桶地址 :return: """ gs_coscmd = "coscmd" gs_coscmd_conf = "~/.cos.conf" cmd = "{} -c {} -r {} -b {} upload {} {}".format(gs_coscmd, gs_coscmd_conf, region, bucket_name, file_path, key) if exec_cmd(cmd): cmd = "{} -c {} -r {} -b {} info {}".format(gs_coscmd, gs_coscmd_conf, region, bucket_name, key) \ + "| grep Content-Length |awk \'{print $2}\'" res_str = exec_cmd_and_result(cmd) - logging.info("{},res={}".format(key, res_str)) + # logging.info("{},res={}".format(key, res_str)) size = float(res_str) if size > 0: return True return False return False def check_input(input_data): key_list = ["record_song_url", "target_url", "start", "end", "vocal_loudness", "female_recording_url", "male_recording_url"] for key in key_list: if key not in input_data.keys(): return False return True diff --git a/AIMeiSheng/docker_demo/cos_helper.py b/AIMeiSheng/docker_demo/cos_helper.py new file mode 100644 index 0000000..a746d49 --- /dev/null +++ b/AIMeiSheng/docker_demo/cos_helper.py @@ -0,0 +1,66 @@ +import time + +from qcloud_cos import CosConfig +from qcloud_cos import CosS3Client + +import os + +# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成 +gs_secret_id = 'AKIDoQmshFWXGitnQmrfCTYNwEExPaU6RVHm' +gs_secret_key = 'F9n9E2ZonWy93f04qMaYFfogHadPt62h' + + +def get_client(region): + config = CosConfig(Region=region, SecretId=gs_secret_id, SecretKey=gs_secret_key) + client = CosS3Client(config) + return client + + +def parse_url(url): + bucket_name = url.split(".")[0].split("//")[-1] + region = url.split(".")[2] + key = "/".join(url.split("/")[3:]) + return bucket_name, region, key + + +class CosHelper: + def __init__(self, time_esp=10 * 60): + self.time_esp = time_esp + self.client_map = {} # key: region, value: client + self.client_map_tm = {} # key: region, value: timestamp + + def get_client(self, region): + if region in self.client_map_tm.keys() and time.time() - self.client_map_tm[region] > self.time_esp: + del self.client_map[region] + del self.client_map_tm[region] + + if region not in self.client_map.keys(): + self.client_map[region] = get_client(region) + self.client_map_tm[region] = time.time() + return self.client_map[region] + + def upload_by_url(self, local_path, target_url): + try: + bucket_name, region, key = parse_url(target_url) + response = self.get_client(region).upload_file( + Bucket=bucket_name, + LocalFilePath=local_path, + Key=key, + PartSize=1, + MAXThread=10, + EnableMD5=False + ) + if "ETag" in response.keys(): + return True + return False + except Exception as ex: + print(f"ex:{ex}") + return False + + +if __name__ == '__main__': + cos_client = CosHelper() + l_path = "/data/rsync/jianli.yang/av_svc/AIMeiSheng/docker_demo/readme.txt1" + d_url = "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/tmp/readme.txt" + for i in range(0, 1): + cos_client.upload_by_url(l_path, d_url + f"_{i}.txt") diff --git a/AIMeiSheng/docker_demo/http_server.py b/AIMeiSheng/docker_demo/http_server.py index d4e7c85..f329506 100644 --- a/AIMeiSheng/docker_demo/http_server.py +++ b/AIMeiSheng/docker_demo/http_server.py @@ -1,83 +1,83 @@ # -*- coding: UTF-8 -*- """ SVC处理逻辑 1. 根据跟定的vocal_url 判别男女 2. 根据男女信息选择适合的男女url 3. 模型推理 """ import gc import os import sys sys.path.append(os.path.dirname(__file__)) sys.path.append(os.path.join(os.path.dirname(__file__), "../")) import json import time import socket import logging import hashlib from flask import Flask, jsonify, request, abort from redis_helper import RedisHelper from common import * # 全局设置 -hostname = socket.gethostname() -# log_file_name = f"/tmp/av_meisheng_http_{hostname}.log" logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S', level=logging.INFO) app = Flask(__name__) class HttpServer: def __init__(self, redis_conf, server_conf): + st = time.time() self.redis_helper = RedisHelper(redis_conf) self.server_conf = server_conf + logging.info(f"HttpServer init conf={redis_conf}, {server_conf} sp={time.time() - st}") def process(self, in_data): msg = { "status": gs_err_code_params, "schedule": 100, "gender": "unknown", "target_song_url": "", } if not check_input(in_data): return msg if self.redis_helper.llen(self.server_conf["producer"]) > 10: msg["status"] = gs_err_code_too_many_connections return msg distinct_id = hashlib.md5(in_data["record_song_url"].encode()).hexdigest() distinct_key = self.server_conf["ai_meisheng_key_prefix"] + distinct_id if not self.redis_helper.exists(distinct_key): msg["status"] = gs_err_code_pending self.redis_helper.set(distinct_key, json.dumps(msg)) self.redis_helper.lpush(self.server_conf["producer"], json.dumps(in_data)) self.redis_helper.expire(distinct_key, 15) msg = self.redis_helper.get(distinct_key) return json.loads(msg) gs_http_server = HttpServer(gs_redis_conf, gs_server_redis_conf) @app.route("/ai_meisheng", methods=["POST"]) def ai_meisheng(): data = request.json st = time.time() logging.info(f"ai_meisheng:in:{data}") msg = gs_http_server.process(data) json_msg = jsonify(msg) logging.info(f"ai_meisheng:out:{data}-{json_msg}, sp={time.time() - st}") return json_msg if __name__ == "__main__": app.run(host='0.0.0.0', port=5000, threaded=False) diff --git a/AIMeiSheng/docker_demo/multi_curl.py b/AIMeiSheng/docker_demo/multi_curl.py new file mode 100644 index 0000000..4585d32 --- /dev/null +++ b/AIMeiSheng/docker_demo/multi_curl.py @@ -0,0 +1,52 @@ +# 压测,测试多线程并发请求 +import time +import numpy as np +from multiprocessing import Pool +import requests + + +def calc_one(url, idx): + register_url = "http://127.0.0.1:5000/ai_meisheng" + # 添加请求头,需要就传 + header = { + "Content-Type": "application/json" + } + + # json类型的参数 + json = { + "record_song_url": url, + "target_url": f"https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/out_{idx}.m4a", + "start": 0, "end": 15000, "vocal_loudness": -14.57, + "female_recording_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/female.m4a", + "male_recording_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/male.m4a" + } + + # 发送post请求 + response = requests.post(url=register_url, json=json, headers=header) + data = response.json() + if (data["status"] == 0 and data["schedule"] != 100) or (data["status"] == 107): + time.sleep(1) + return calc_one(url, idx) + return data + + +def calc(): + thread_pool = Pool(1) + st = time.time() + res_list = [] + url = "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/yinse.m4a" + for i in range(0, 100): + res = thread_pool.apply_async(calc_one, args=(url, i)) + url = f"https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/out_{i}.m4a" + res_list.append(res) + + for i in range(0, len(res_list)): + print(f"res_list[{i}]:{res_list[i].get()}") + + thread_pool.close() + thread_pool.join() + print(f"cost:{time.time() - st}") + + +if __name__ == '__main__': + calc() \ No newline at end of file diff --git a/AIMeiSheng/docker_demo/offline_server.py b/AIMeiSheng/docker_demo/offline_server.py index 185ae73..163413b 100644 --- a/AIMeiSheng/docker_demo/offline_server.py +++ b/AIMeiSheng/docker_demo/offline_server.py @@ -1,157 +1,151 @@ # -*- coding: UTF-8 -*- """ 离线处理: 使用redis进行交互,从redis中获取数据资源,在将结果写入到redis """ import os import sys import time import json import socket import hashlib -import logging from redis_helper import RedisHelper +from cos_helper import CosHelper from common import * -from svc_online import GSWorkerAttr, SVCOnline, volume_adjustment - -hostname = socket.gethostname() -log_file_name = f"{os.path.dirname(os.path.abspath(__file__))}/av_meisheng_{hostname}.log" -logging.basicConfig(filename=log_file_name, format='%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S', - level=logging.INFO) +import logging +from svc_online import GSWorkerAttr, SVCOnline, volume_adjustment, svc_offline_logger sys.path.append(os.path.dirname(__file__)) sys.path.append(os.path.join(os.path.dirname(__file__), "../")) def download_data(worker_attr): - vocal_path = os.path.join(worker_attr.tmp_dir, worker_attr.distinct_id) - if os.path.exists(vocal_path): - os.remove(vocal_path) + if os.path.exists(worker_attr.vocal_path): + os.unlink(worker_attr.vocal_path) st = time.time() if not download2disk(worker_attr.vocal_url, worker_attr.vocal_path): return gs_err_code_download_vocal - logging.info(f"download vocal_url={worker_attr.vocal_url} sp = {time.time() - st}") + svc_offline_logger.info(f"download vocal_url={worker_attr.vocal_url} sp = {time.time() - st}") # download svc_source_url if not os.path.exists(worker_attr.female_svc_source_path): st = time.time() if not download2disk(worker_attr.female_svc_source_url, worker_attr.female_svc_source_path): return gs_err_code_download_svc_url - logging.info(f"download female_url={worker_attr.female_svc_source_url} sp = {time.time() - st}") + svc_offline_logger.info(f"download female_url={worker_attr.female_svc_source_url} sp = {time.time() - st}") # download svc_source_url if not os.path.exists(worker_attr.male_svc_source_path): st = time.time() if not download2disk(worker_attr.male_svc_source_url, worker_attr.male_svc_source_path): return gs_err_code_download_svc_url - logging.info(f"download male_url={worker_attr.male_svc_source_url} sp = {time.time() - st}") + svc_offline_logger.info(f"download male_url={worker_attr.male_svc_source_url} sp = {time.time() - st}") return gs_err_code_success def transcode(wav_path, dst_path): st = time.time() cmd = f"ffmpeg -i {wav_path} -ar 44100 -ac 2 -b:a 64k -y {dst_path} -loglevel fatal" exec_cmd(cmd) - logging.info(f"transcode cmd={cmd}, sp = {time.time() - st}") + svc_offline_logger.info(f"transcode cmd={cmd}, sp = {time.time() - st}") return os.path.exists(dst_path) class OfflineServer: def __init__(self, redis_conf, server_conf, update_redis=False): + st = time.time() self.redis_helper = RedisHelper(redis_conf) + self.cos_helper = CosHelper() self.svc_online = SVCOnline() self.server_conf = server_conf self.distinct_key = server_conf["ai_meisheng_key_prefix"] self.update_redis = update_redis + svc_offline_logger.info(f"config={redis_conf}---server_conf={self.server_conf}") + svc_offline_logger.info(f"offline init finish sp={time.time() - st}") def exists(self): return self.redis_helper.exists(self.distinct_key) def update_result(self, errcode, schedule, gender, target_song_url): msg = { "status": errcode, "schedule": schedule, "gender": gender, "target_song_url": target_song_url, } # 结果保存15min if self.update_redis: self.redis_helper.set(self.distinct_key, json.dumps(msg)) self.redis_helper.expire(self.distinct_key, 60 * 10) def process_one(self, worker_attr): self.distinct_key = self.server_conf["ai_meisheng_key_prefix"] + worker_attr.distinct_id - logging.info(f"{worker_attr.log_info_name()}, start download ...") + svc_offline_logger.info(f"{worker_attr.log_info_name()}, start download ...") err = download_data(worker_attr) if err != gs_err_code_success: self.update_result(err, 100, "unknown", worker_attr.target_url) return err, None, None self.update_result(err, 35, "unknown", worker_attr.target_url) - logging.info(f"{worker_attr.log_info_name()}, start process ...") + svc_offline_logger.info(f"{worker_attr.log_info_name()}, start process ...") gender = self.svc_online.process(worker_attr) if not os.path.exists(worker_attr.target_wav_path): self.update_result(gs_err_code_svc_process, 100, gender, worker_attr.target_url) return gs_err_code_svc_process, None, None self.update_result(err, 85, gender, worker_attr.target_url) # 音量拉伸到指定响度 - logging.info(f"{worker_attr.log_info_name()}, start volume_adjustment ...") + svc_offline_logger.info(f"{worker_attr.log_info_name()}, start volume_adjustment ...") volume_adjustment(worker_attr.target_wav_path, worker_attr.target_loudness, worker_attr.target_wav_ad_path) if not os.path.exists(worker_attr.target_wav_ad_path): self.update_result(gs_err_code_volume_adjust, 100, gender, worker_attr.target_url) return gs_err_code_volume_adjust, None, None self.update_result(err, 90, gender, worker_attr.target_url) # transcode - logging.info(f"{worker_attr.log_info_name()}, start transcode ...") + svc_offline_logger.info(f"{worker_attr.log_info_name()}, start transcode ...") if not transcode(worker_attr.target_wav_path, worker_attr.target_path): self.update_result(gs_err_code_transcode, 100, gender, worker_attr.target_url) return gs_err_code_transcode, None, None self.update_result(err, 95, gender, worker_attr.target_url) # upload - logging.info(f"{worker_attr.log_info_name()}, start upload_file2cos ...") + svc_offline_logger.info(f"{worker_attr.log_info_name()}, start upload_file2cos ...") st = time.time() - # 从target_url 分离出bucket_name,ap,和key - # "http://starmaker-sv-1256122840.cos.na-siliconvalley.myqcloud.com/production/ai_voice/7036874317774285/xxalkdjfladjflkasdf-target.mp4", - bucket_name = worker_attr.target_url.split(".")[0].split("//")[-1] - region = worker_attr.target_url.split(".")[2] - key = "/".join(worker_attr.target_url.split("/")[3:]) - - logging.info(f"{worker_attr.log_info_name()}, start upload_file2cos {bucket_name}, {region}, {key}") - if not upload_file2cos(key, worker_attr.target_path, region=region, bucket_name=bucket_name): + if not self.cos_helper.upload_by_url(worker_attr.target_path, worker_attr.target_url): self.update_result(gs_err_code_upload, 100, gender, worker_attr.target_url) return gs_err_code_upload, None, None self.update_result(gs_err_code_success, 100, gender, worker_attr.target_url) - logging.info(f"{worker_attr.log_info_name()} upload {worker_attr.target_url} sp = {time.time() - st}") + svc_offline_logger.info(f"{worker_attr.log_info_name()} upload {worker_attr.target_url} sp = {time.time() - st}") return gs_err_code_success, worker_attr.target_url, gender def process(self): while True: data = self.redis_helper.rpop(self.server_conf["producer"]) if data is None: time.sleep(1) continue data = json.loads(data) if not check_input(data): - logging.error(f"input data error={data}") + svc_offline_logger.error(f"input data error={data}") continue worker_attr = GSWorkerAttr(data) self.distinct_key = self.server_conf["ai_meisheng_key_prefix"] + worker_attr.distinct_id if not self.exists(): - logging.warning(f"input {data}, timeout abandon ....") + svc_offline_logger.warning(f"input {data}, timeout abandon ....") + worker_attr.rm_cache() continue st = time.time() errcode, target_path, gender = self.process_one(worker_attr) self.update_result(errcode, 100, gender, target_path) - logging.info(f"{worker_attr.log_info_name()} finish sp = {time.time() - st}") + svc_offline_logger.info(f"{worker_attr.log_info_name()} finish sp = {time.time() - st}") + worker_attr.rm_cache() if __name__ == '__main__': offline_server = OfflineServer(gs_redis_conf, gs_server_redis_conf, True) offline_server.process() diff --git a/AIMeiSheng/docker_demo/readme.txt b/AIMeiSheng/docker_demo/readme.txt index 066ca90..beaf3e6 100644 --- a/AIMeiSheng/docker_demo/readme.txt +++ b/AIMeiSheng/docker_demo/readme.txt @@ -1,23 +1,24 @@ 简介: ai美声功能,其核心是输入一段15-30s的人声作为音色信息,再给定输入音源,将音源转换为指定音色的声音的效果。例如,孙燕姿演唱的东风破 架构方案: http_server.py (1个) 作为服务端,接收外部传来的数据,塞入到redis中,由offline_server.py (多个服务) 进行承接 # 部署要求: 1. http_server.py 部署在sg-prod-songrefresh-gpu-7 上 2. offline_server.py 使用docker 部署在超级节点上,由运维进行控制 # http_server.py 环境要求: pip install redis pip install flask # offline_server.py 环境要求(docker) cd docker_demo目录下(例子如下): 1. docker build -f Dockerfile -t av_ai_meisheng . (通过docker images 获取av_ai_meisheng的image_id) 2. docker run --gpus all -it -v /data/rsync/jianli.yang/av_svc:/data/code image_id # 即可启动服务 # 测试代码: docker 环境下, offline_server.py 即可验证 # http测试命令: curl http://127.0.0.1:5004/ai_meisheng -H "Content-Type: application/json" -d '{ "record_song_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/yinse.m4a", "target_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/out.m4a","start": 0,"end": 15000,"vocal_loudness": -14.57,"female_recording_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/female.m4a", "male_recording_url": "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/male.m4a"}' {"gender":"male","schedule":100,"status":0,"target_song_url":"https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/vocal_test/out.m4a"} -# 资源消耗: 显存占用约2G,建议一台GPU机器部署2个offline_server.py +# 资源消耗: 显存占用约2G,但是最高能到9G, 所以,一台机器部署一个即可 +注意: 通过common.py 的prod可以控制是否是线上环境 \ No newline at end of file diff --git a/AIMeiSheng/docker_demo/svc_online.py b/AIMeiSheng/docker_demo/svc_online.py index ec3cdf0..fa16544 100644 --- a/AIMeiSheng/docker_demo/svc_online.py +++ b/AIMeiSheng/docker_demo/svc_online.py @@ -1,165 +1,180 @@ # -*- coding: UTF-8 -*- """ SVC的核心处理逻辑 """ import os +import time +import socket import shutil import hashlib -import time from AIMeiSheng.meisheng_svc_final import load_model, process_svc_online from AIMeiSheng.meisheng_env_preparex import meisheng_env_prepare from AIMeiSheng.voice_classification.online.voice_class_online_fang import VoiceClass, download_volume_balanced from AIMeiSheng.docker_demo.common import * +import logging + +hostname = socket.gethostname() +log_file_name = f"{os.path.dirname(os.path.abspath(__file__))}/av_meisheng_{hostname}.log" + +# 设置logger +svc_offline_logger = logging.getLogger("svc_offline") +file_handler = logging.FileHandler(log_file_name) +file_handler.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %I:%M:%S') +file_handler.setFormatter(formatter) +if gs_prod: + svc_offline_logger.addHandler(file_handler) + if os.path.exists(gs_tmp_dir): shutil.rmtree(gs_tmp_dir) + os.makedirs(gs_model_dir, exist_ok=True) os.makedirs(gs_resource_cache_dir, exist_ok=True) # 预设参数 gs_gender_models_url = "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/hub/voice_classification/models.zip" gs_volume_bin_url = "https://av-audit-sync-sg-1256122840.cos.ap-singapore.myqcloud.com/dataset/AIMeiSheng/ebur128_tool" class GSWorkerAttr: def __init__(self, input_data): # 取出输入资源 vocal_url = input_data["record_song_url"] target_url = input_data["target_url"] start = input_data["start"] # 单位是ms end = input_data["end"] # 单位是ms vocal_loudness = input_data["vocal_loudness"] female_recording_url = input_data["female_recording_url"] male_recording_url = input_data["male_recording_url"] self.distinct_id = hashlib.md5(vocal_url.encode()).hexdigest() self.tmp_dir = os.path.join(gs_tmp_dir, self.distinct_id) if os.path.exists(self.tmp_dir): shutil.rmtree(self.tmp_dir) os.makedirs(self.tmp_dir) self.vocal_url = vocal_url self.target_url = target_url ext = vocal_url.split(".")[-1] self.vocal_path = os.path.join(self.tmp_dir, self.distinct_id + f"_in.{ext}") self.target_wav_path = os.path.join(self.tmp_dir, self.distinct_id + "_out.wav") self.target_wav_ad_path = os.path.join(self.tmp_dir, self.distinct_id + "_out_ad.wav") self.target_path = os.path.join(self.tmp_dir, self.distinct_id + "_out.m4a") self.female_svc_source_url = female_recording_url self.male_svc_source_url = male_recording_url ext = female_recording_url.split(".")[-1] self.female_svc_source_path = os.path.join(gs_resource_cache_dir, hashlib.md5(female_recording_url.encode()).hexdigest() + "." + ext) ext = male_recording_url.split(".")[-1] self.male_svc_source_path = os.path.join(gs_resource_cache_dir, hashlib.md5(male_recording_url.encode()).hexdigest() + "." + ext) self.st_tm = start self.ed_tm = end self.target_loudness = vocal_loudness def log_info_name(self): return f"d_id={self.distinct_id}, vocal_url={self.vocal_url}" - def __del__(self): - pass - # if os.path.exists(self.tmp_dir): - # shutil.rmtree(self.tmp_dir) + def rm_cache(self): + if os.path.exists(self.tmp_dir): + shutil.rmtree(self.tmp_dir) def init_gender_model(): """ 下载模型 :return: """ dst_model_dir = os.path.join(gs_model_dir, "voice_classification") if not os.path.exists(dst_model_dir): dst_zip_path = os.path.join(gs_model_dir, "models.zip") if not download2disk(gs_gender_models_url, dst_zip_path): - logging.fatal(f"download gender_model err={gs_gender_models_url}") + svc_offline_logger.fatal(f"download gender_model err={gs_gender_models_url}") cmd = f"cd {gs_model_dir}; unzip {dst_zip_path}; mv models voice_classification; rm -f {dst_zip_path}" os.system(cmd) if not os.path.exists(dst_model_dir): - logging.fatal(f"unzip {dst_zip_path} err") + svc_offline_logger.fatal(f"unzip {dst_zip_path} err") music_voice_pure_model = os.path.join(dst_model_dir, "voice_005_rec_v5.pth") music_voice_no_pure_model = os.path.join(dst_model_dir, "voice_10_v5.pth") gender_pure_model = os.path.join(dst_model_dir, "gender_8k_ratev5_v6_adam.pth") gender_no_pure_model = os.path.join(dst_model_dir, "gender_8k_v6_adam.pth") vc = VoiceClass(music_voice_pure_model, music_voice_no_pure_model, gender_pure_model, gender_no_pure_model) return vc def init_svc_model(): meisheng_env_prepare(logging, gs_model_dir) embed_model, hubert_model = load_model() return embed_model, hubert_model def download_volume_adjustment(): """ 下载音量调整工具 :return: """ volume_bin_path = os.path.join(gs_model_dir, "ebur128_tool") if not os.path.exists(volume_bin_path): if not download2disk(gs_volume_bin_url, volume_bin_path): - logging.fatal(f"download volume_bin err={gs_volume_bin_url}") + svc_offline_logger.fatal(f"download volume_bin err={gs_volume_bin_url}") os.system(f"chmod +x {volume_bin_path}") def volume_adjustment(wav_path, target_loudness, out_path): """ 音量调整 :param wav_path: :param target_loudness: :param out_path: :return: """ volume_bin_path = os.path.join(gs_model_dir, "ebur128_tool") cmd = f"{volume_bin_path} {wav_path} {target_loudness} {out_path}" os.system(cmd) class SVCOnline: def __init__(self): st = time.time() self.gender_model = init_gender_model() self.embed_model, self.hubert_model = init_svc_model() download_volume_adjustment() download_volume_balanced() - logging.info(f"svc init finished, sp = {time.time() - st}") + svc_offline_logger.info(f"svc init finished, sp = {time.time() - st}") def gender_process(self, worker_attr): st = time.time() gender, female_rate, is_pure = self.gender_model.process(worker_attr.vocal_path) - logging.info( + svc_offline_logger.info( f"{worker_attr.vocal_url}, gender={gender}, female_rate={female_rate}, is_pure={is_pure}, " f"gender_process sp = {time.time() - st}") if gender == 0: gender = 'female' elif gender == 1: gender = 'male' elif female_rate > 0.5: gender = 'female' else: gender = 'male' - logging.info(f"{worker_attr.vocal_url}, modified gender={gender}") + svc_offline_logger.info(f"{worker_attr.vocal_url}, modified gender={gender}") return gender def process(self, worker_attr): gender = self.gender_process(worker_attr) song_path = worker_attr.female_svc_source_path if gender == "male": song_path = worker_attr.male_svc_source_path params = {'gender': gender, 'tst': worker_attr.st_tm, "tnd": worker_attr.ed_tm, 'delay': 0, 'song_path': None} st = time.time() similar = process_svc_online(song_path, worker_attr.vocal_path, worker_attr.target_wav_path, self.embed_model, self.hubert_model, params) - logging.info(f"{worker_attr.vocal_url}, similar={similar} process svc sp = {time.time() - st}") + svc_offline_logger.info(f"{worker_attr.vocal_url}, similar={similar} process svc sp = {time.time() - st}") return gender