Понятные имена для аудиофайлов

This commit is contained in:
2026-02-08 20:41:06 +03:00
parent ef6a5800a4
commit 3ad6276084
5 changed files with 13 additions and 11 deletions

View File

@@ -41,13 +41,13 @@ docker compose up -d --build
### Результат работы бота
Бот сохраняет аудиофайлы с озвученными репликами в файлы `data/<scenario_id>/<replica_number>_<dataset_speaker_id>.wav`, где `<replica_number>` - это номер реплики в сценарии (0-indexed), `<dataset_speaker_id>` - это идентификатор диктора во всём датасете, по-сути, соответствует уникальному пользователю бота. Один диктор может озвучить несколько дорожек из разных совещаний. При этом он не может озвучивать две разные дорожки в рамках одного совещания, таким образом гарантируется, что разным `speaker_id` соответствуют разные дикторы в рамках одного совещания.
Бот сохраняет аудиофайлы с озвученными репликами в файлы `data/<scenario_id>/r<replica_idx>_s<scenario_speaker_id>_u<user_id>.wav`, где `<replica_idx>` номер реплики в сценарии (0-indexed, с ведущими нулями), `<scenario_speaker_id>` идентификатор диктора в рамках сценария (с ведущими нулями), `<user_id>` — уникальный идентификатор пользователя бота (с ведущими нулями). Пример: `r003_s01_u002.wav`. Один диктор может озвучить несколько дорожек из разных совещаний. При этом он не может озвучивать две разные дорожки в рамках одного совещания, таким образом гарантируется, что разным `speaker_id` соответствуют разные дикторы в рамках одного совещания.
В `data` попадают только полностью озвученные дорожки. Под дорожкой подразумевается набор всех реплик одного диктора в рамках сценария. Дорожки, озвученные частично, хранятся в `data_partial/<scenario_id>/<replica_number>_<dataset_speaker_id>.wav`. Они автоматически переносятся в `data` после завершения озвучивания.
В `data` попадают только полностью озвученные дорожки. Под дорожкой подразумевается набор всех реплик одного диктора в рамках сценария. Дорожки, озвученные частично, хранятся в `data_partial/<scenario_id>/r<replica_idx>_s<scenario_speaker_id>_u<user_id>.wav`. Они автоматически переносятся в `data` после завершения озвучивания.
### База данных
Служебные данные, такие как состояния пользовательских сессий, соответствие `dataset_speaker_id` и `telegram_user_id` и т. п., сохраняются в базе данных SQLite. После перезагрузки бота его состояние полностью восстанавливается из базы данных.
Служебные данные, такие как состояния пользовательских сессий, соответствие `user_id` и `telegram_user_id` и т. п., сохраняются в базе данных SQLite. После перезагрузки бота его состояние полностью восстанавливается из базы данных.
## Интерфейс бота

View File

@@ -11,6 +11,7 @@ async def save_voice_message(
file_id: str,
user_id: int,
scenario_id: str,
speaker_id: int,
replica_index: int,
duration: int,
) -> None:
@@ -21,7 +22,7 @@ async def save_voice_message(
# Скачиваем файл
file = await bot.get_file(file_id)
filename = get_audio_filename(replica_index, user_id)
filename = get_audio_filename(replica_index, speaker_id, user_id)
filepath = scenario_dir / filename
await file.download_to_drive(filepath)

View File

@@ -30,7 +30,7 @@ class UserState(Enum):
class User:
"""Пользователь бота (диктор в датасете)."""
id: int # dataset_speaker_id
id: int
telegram_id: int
created_at: datetime
gender: str | None # "male" или "female"
@@ -61,7 +61,7 @@ class Recording:
"""Запись озвучки реплики."""
id: int
user_id: int # dataset_speaker_id
user_id: int
scenario_id: str
replica_index: int
duration: float # длительность в секундах
@@ -190,7 +190,7 @@ def get_or_create_user(telegram_id: int) -> User:
)
row = cursor.fetchone()
conn.commit()
logger.info(f"Создан новый пользователь: dataset_speaker_id={row['id']}")
logger.info(f"Создан новый пользователь: user_id={row['id']}")
return User(
id=row["id"],
telegram_id=row["telegram_id"],

View File

@@ -878,6 +878,7 @@ async def handle_voice_message(
voice.file_id,
user.id,
session.scenario_id,
session.speaker_id,
real_replica_index,
voice.duration,
)

View File

@@ -222,9 +222,9 @@ def get_data_dir(scenario_id: str) -> Path:
return DATA_DIR / scenario_id
def get_audio_filename(replica_index: int, user_id: int) -> str:
def get_audio_filename(replica_index: int, speaker_id: int, user_id: int) -> str:
"""Формирует имя файла для аудиозаписи."""
return f"{replica_index}_{user_id}.wav"
return f"r{replica_index:03d}_s{speaker_id:02d}_u{user_id:03d}.wav"
def is_scenario_complete(scenario_id: str) -> bool:
@@ -302,7 +302,7 @@ def move_track_to_data(user_id: int, scenario_id: str, speaker_id: int) -> None:
moved_count = 0
for replica in track_replicas:
filename = get_audio_filename(replica.replica_index, user_id)
filename = get_audio_filename(replica.replica_index, speaker_id, user_id)
src = partial_dir / filename
dst = data_dir / filename
@@ -323,7 +323,7 @@ def delete_partial_track(user_id: int, scenario_id: str, speaker_id: int) -> Non
deleted_count = 0
for replica in track_replicas:
filename = get_audio_filename(replica.replica_index, user_id)
filename = get_audio_filename(replica.replica_index, speaker_id, user_id)
filepath = partial_dir / filename
if filepath.exists():
filepath.unlink()