tester1hf commited on
Commit
400f26d
·
verified ·
1 Parent(s): a422de3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -50
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import gradio as gr
2
  from g4f.client import Client
3
- import re
4
  import json
5
  import torch
6
  import soundfile as sf
@@ -8,6 +7,14 @@ from openvoice_cli.__main__ import tune_one
8
  import os
9
  import uuid
10
  import zipfile
 
 
 
 
 
 
 
 
11
 
12
  # Initialize G4F client
13
  client = Client()
@@ -19,12 +26,13 @@ speaker = 'baya' # Russian voice
19
  sample_rate = 48000 # Sample rate for Russian model
20
  device = torch.device('cpu')
21
 
22
- # Load Silero TTS model
23
  model, _ = torch.hub.load(repo_or_dir='snakers4/silero-models',
24
  model='silero_tts',
25
  language=language,
26
  speaker=model_id)
27
  model.to(device)
 
28
 
29
  def get_task(prompt):
30
  return f'''Ты генерируешь скрипт продолжения прямой линии с Владимиром Владимировичем Путиным. Вопросы телезрителей передаёт Киселёв - он максимально подобострастен, поддерживает Путина и Россию. Пиши в духе пропаганды пророссийской. Каждая реплика - не менее 150-200 символов. Английские названия при необходимости всегда транскрибируй кириллицей. Числительные также записывай кириллицей. Пиши в стиле кремлёвской пропаганды. Пиши в стереотипном стиле. Киселёв задаёт вопросы, обрашается к Путину как "Владимир Владимирович" или "Господин Президент". Путин отвечает. Всё максимально аутентично. Фразы Киселёва: {{"Киселёв":"фраза"}} Фразы Путина: {{"Путин":"фраза"}} Ответ дай в формате JSON без дополнительных символов: [{{"Киселёв":"фраза"}}, {{"Путин":"фраза"}} . . . ].
@@ -32,9 +40,7 @@ def get_task(prompt):
32
 
33
  def validate_response(response):
34
  try:
35
- # Attempt to parse the response as JSON
36
  data = json.loads(response)
37
- # Check if the response is a list and follows the required pattern
38
  if isinstance(data, list) and all(isinstance(item, dict) and len(item) == 1 for item in data):
39
  return True
40
  except json.JSONDecodeError:
@@ -44,75 +50,116 @@ def validate_response(response):
44
  def generate_text(prompt):
45
  max_retries = 4
46
  for attempt in range(max_retries):
 
47
  response = client.chat.completions.create(
48
  model="llama-3.3-70b",
49
  messages=[{"role": "user", "content": get_task(prompt)}],
50
  web_search=False
51
  )
52
  response_text = response.choices[0].message.content
 
 
53
  if validate_response(response_text):
54
  return response_text
55
- # If all retries fail, return a placeholder
 
 
56
  return '[{"Киселёв":"К сожалению, не удалось расслышать вопрос. Пожалуйста, попробуйте еще раз."}, {"Путин":"Мы работаем над улучшением системы. Спасибо за понимание."}]'
57
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def generate_audio(text, speaker_name):
59
- # Generate speech using Silero TTS
60
- audio = model.apply_tts(ssml_text=f"<speak>{text}</speak>",
61
- speaker=speaker,
62
- sample_rate=sample_rate,
63
- put_accent=True,
64
- put_yo=True)
65
 
66
- # Save to a temporary file
67
- temp_filename = f"temp_{speaker_name}.wav"
68
- sf.write(temp_filename, audio, sample_rate)
69
- return temp_filename
70
-
71
- def generate_cover(base_audio, ref_audio):
72
- # Create unique output filename
73
- output_filename = f"output_{uuid.uuid4().hex[:6]}.wav"
74
 
75
- # Process the audio with OpenVoice
76
- tune_one(
77
- input_file=base_audio,
78
- ref_file=ref_audio,
79
- output_file=output_filename,
80
- device='cpu'
81
- )
 
 
 
82
 
83
- return output_filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  def process_prompt(prompt):
86
- # Generate the script
 
 
 
87
  script = generate_text(prompt)
88
  script_data = json.loads(script)
89
 
90
- # Prepare a list to store the generated audio files
91
- audio_files = []
 
 
92
 
93
- for i, item in enumerate(script_data):
94
- for speaker, text in item.items():
95
- # Generate the base audio using Silero TTS
96
- base_audio_file = generate_audio(text, speaker)
97
-
98
- # Determine the reference audio file based on the speaker
99
- ref_audio_file = "kisel.mp3" if speaker == "Киселёв" else "putin.mp3"
100
-
101
- # Generate the covered audio using OpenVoice
102
- covered_audio_file = generate_cover(base_audio_file, ref_audio_file)
103
-
104
- # Rename the file to include the speaker and sequence number
105
- final_filename = f"t{i+1}-{speaker}.wav"
106
- os.rename(covered_audio_file, final_filename)
107
-
108
- # Add the final file to the list
109
- audio_files.append(final_filename)
110
 
111
- # Create a zip file containing all the audio files
112
  zip_filename = "output_audio_files.zip"
113
  with zipfile.ZipFile(zip_filename, 'w') as zipf:
114
- for audio_file in audio_files:
115
- zipf.write(audio_file)
 
 
 
 
116
 
117
  return zip_filename
118
 
 
1
  import gradio as gr
2
  from g4f.client import Client
 
3
  import json
4
  import torch
5
  import soundfile as sf
 
7
  import os
8
  import uuid
9
  import zipfile
10
+ import logging
11
+ import numpy as np
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ import threading
14
+
15
+ # Configure logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
 
19
  # Initialize G4F client
20
  client = Client()
 
26
  sample_rate = 48000 # Sample rate for Russian model
27
  device = torch.device('cpu')
28
 
29
+ # Load Silero TTS model with thread safety
30
  model, _ = torch.hub.load(repo_or_dir='snakers4/silero-models',
31
  model='silero_tts',
32
  language=language,
33
  speaker=model_id)
34
  model.to(device)
35
+ tts_lock = threading.Lock() # Lock for TTS model thread safety
36
 
37
  def get_task(prompt):
38
  return f'''Ты генерируешь скрипт продолжения прямой линии с Владимиром Владимировичем Путиным. Вопросы телезрителей передаёт Киселёв - он максимально подобострастен, поддерживает Путина и Россию. Пиши в духе пропаганды пророссийской. Каждая реплика - не менее 150-200 символов. Английские названия при необходимости всегда транскрибируй кириллицей. Числительные также записывай кириллицей. Пиши в стиле кремлёвской пропаганды. Пиши в стереотипном стиле. Киселёв задаёт вопросы, обрашается к Путину как "Владимир Владимирович" или "Господин Президент". Путин отвечает. Всё максимально аутентично. Фразы Киселёва: {{"Киселёв":"фраза"}} Фразы Путина: {{"Путин":"фраза"}} Ответ дай в формате JSON без дополнительных символов: [{{"Киселёв":"фраза"}}, {{"Путин":"фраза"}} . . . ].
 
40
 
41
  def validate_response(response):
42
  try:
 
43
  data = json.loads(response)
 
44
  if isinstance(data, list) and all(isinstance(item, dict) and len(item) == 1 for item in data):
45
  return True
46
  except json.JSONDecodeError:
 
50
  def generate_text(prompt):
51
  max_retries = 4
52
  for attempt in range(max_retries):
53
+ logger.info(f"Generating response for prompt: {prompt} (attempt {attempt+1})")
54
  response = client.chat.completions.create(
55
  model="llama-3.3-70b",
56
  messages=[{"role": "user", "content": get_task(prompt)}],
57
  web_search=False
58
  )
59
  response_text = response.choices[0].message.content
60
+ logger.info(f"Generated response: {response_text}")
61
+
62
  if validate_response(response_text):
63
  return response_text
64
+ logger.warning("Invalid response format, retrying...")
65
+
66
+ logger.error("Failed to generate valid response after 4 attempts")
67
  return '[{"Киселёв":"К сожалению, не удалось расслышать вопрос. Пожалуйста, попробуйте еще раз."}, {"Путин":"Мы работаем над улучшением системы. Спасибо за понимание."}]'
68
 
69
+ def split_text(text, max_length=800):
70
+ """Split text into chunks of maximum length, trying to preserve word boundaries"""
71
+ chunks = []
72
+ while len(text) > max_length:
73
+ split_at = text.rfind(' ', 0, max_length)
74
+ if split_at == -1:
75
+ split_at = max_length
76
+ chunks.append(text[:split_at])
77
+ text = text[split_at:].lstrip()
78
+ chunks.append(text)
79
+ return chunks
80
+
81
  def generate_audio(text, speaker_name):
82
+ """Generate audio with thread-safe splitting and synthesis"""
83
+ logger.info(f"Generating audio for {speaker_name} ({len(text)} chars)")
 
 
 
 
84
 
85
+ chunks = split_text(text)
86
+ audio_arrays = []
 
 
 
 
 
 
87
 
88
+ for chunk in chunks:
89
+ with tts_lock: # Ensure thread-safe TTS operations
90
+ audio = model.apply_tts(
91
+ ssml_text=f"<speak>{chunk}</speak>",
92
+ speaker=speaker,
93
+ sample_rate=sample_rate,
94
+ put_accent=True,
95
+ put_yo=True
96
+ )
97
+ audio_arrays.append(audio)
98
 
99
+ full_audio = np.concatenate(audio_arrays)
100
+ temp_filename = f"temp_{uuid.uuid4().hex}.wav"
101
+ sf.write(temp_filename, full_audio, sample_rate)
102
+ return temp_filename
103
+
104
+ def process_line(args):
105
+ """Process single dialogue line with parallel execution support"""
106
+ idx, speaker, text = args
107
+ try:
108
+ logger.info(f"Processing line {idx+1} for {speaker}")
109
+
110
+ # Generate base audio
111
+ base_audio = generate_audio(text, speaker)
112
+
113
+ # Generate voice cover
114
+ ref_audio = "kisel.mp3" if speaker == "Киселёв" else "putin.mp3"
115
+ covered_audio = tune_one(
116
+ input_file=base_audio,
117
+ ref_file=ref_audio,
118
+ output_file=f"output_{uuid.uuid4().hex[:6]}.wav",
119
+ device='cpu'
120
+ )
121
+
122
+ # Cleanup and rename
123
+ final_filename = f"t{idx+1}-{speaker}.wav"
124
+ os.rename(covered_audio, final_filename)
125
+ os.remove(base_audio) # Clean up temporary file
126
+
127
+ return final_filename
128
+ except Exception as e:
129
+ logger.error(f"Error processing line {idx+1}: {str(e)}")
130
+ return None
131
 
132
  def process_prompt(prompt):
133
+ """Main processing pipeline with parallel execution"""
134
+ logger.info(f"Starting processing for prompt: {prompt}")
135
+
136
+ # Generate script
137
  script = generate_text(prompt)
138
  script_data = json.loads(script)
139
 
140
+ # Prepare tasks for parallel processing
141
+ tasks = [(idx, speaker, text)
142
+ for idx, item in enumerate(script_data)
143
+ for speaker, text in item.items()]
144
 
145
+ # Process lines in parallel
146
+ audio_files = []
147
+ with ThreadPoolExecutor(max_workers=4) as executor: # Optimal for CPU-bound tasks
148
+ futures = [executor.submit(process_line, task) for task in tasks]
149
+ for future in futures:
150
+ result = future.result()
151
+ if result:
152
+ audio_files.append(result)
 
 
 
 
 
 
 
 
 
153
 
154
+ # Package results
155
  zip_filename = "output_audio_files.zip"
156
  with zipfile.ZipFile(zip_filename, 'w') as zipf:
157
+ for file in audio_files:
158
+ zipf.write(file)
159
+
160
+ # Cleanup working files
161
+ for file in audio_files:
162
+ os.remove(file)
163
 
164
  return zip_filename
165