Spaces:
Running
Running
Navid Arabi
commited on
Commit
·
1ef4e10
1
Parent(s):
f0b4f1b
base app
Browse files- .gitignore +7 -1
- __pycache__/config.cpython-311.pyc +0 -0
- __pycache__/seed_db.cpython-311.pyc +0 -0
- app.py +27 -6
- assets/styles.css +4 -0
- components/__init__.py +0 -0
- components/dashboard_page.py +30 -0
- components/header.py +23 -0
- components/login_page.py +37 -0
- config.py +23 -9
- models.py → data/models.py +39 -52
- database.py +0 -152
- requirements.txt +2 -1
- seed_db.py +0 -52
- db_test.py → test/db_test.py +7 -5
- test/test.py +96 -0
- utils/__init__.py +0 -0
- utils/auth.py +42 -0
- utils/database.py +53 -0
- utils/logger.py +39 -0
.gitignore
CHANGED
@@ -1,2 +1,8 @@
|
|
1 |
venv/
|
2 |
-
.env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
venv/
|
2 |
+
.env
|
3 |
+
__pycache__/
|
4 |
+
*.py[cod]
|
5 |
+
*$py.class
|
6 |
+
.session
|
7 |
+
.sessions.json
|
8 |
+
sessions.pkl
|
__pycache__/config.cpython-311.pyc
DELETED
Binary file (842 Bytes)
|
|
__pycache__/seed_db.cpython-311.pyc
DELETED
Binary file (2.38 kB)
|
|
app.py
CHANGED
@@ -1,11 +1,32 @@
|
|
|
|
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
-
|
4 |
|
|
|
5 |
|
6 |
-
with gr.Blocks() as demo:
|
7 |
-
btn = gr.Button("Run!")
|
8 |
-
output = gr.Textbox(label="Output")
|
9 |
-
btn.click(fn=seed, outputs=output)
|
10 |
|
11 |
-
|
|
|
|
1 |
+
# app.py
|
2 |
+
|
3 |
import gradio as gr
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
from components.login_page import LoginPage
|
7 |
+
from components.dashboard_page import DashboardPage
|
8 |
+
from config import conf
|
9 |
+
|
10 |
+
CSS_FILE = Path(__file__).parent / "assets" / "styles.css"
|
11 |
+
custom_css = CSS_FILE.read_text(encoding="utf-8")
|
12 |
+
|
13 |
+
|
14 |
+
def build_app() -> gr.Blocks:
|
15 |
+
with gr.Blocks(title=conf.APP_TITLE, css=custom_css) as demo:
|
16 |
+
session = gr.State({})
|
17 |
+
|
18 |
+
gr.Markdown(f"### {conf.APP_TITLE}")
|
19 |
+
|
20 |
+
login_page = LoginPage()
|
21 |
+
dashboard_page = DashboardPage()
|
22 |
+
|
23 |
+
login_page.register_callbacks(dashboard_page, session)
|
24 |
+
dashboard_page.register_callbacks(login_page, session)
|
25 |
|
26 |
+
demo.queue(default_concurrency_limit=50)
|
27 |
|
28 |
+
return demo
|
29 |
|
|
|
|
|
|
|
|
|
30 |
|
31 |
+
if __name__ == "__main__":
|
32 |
+
build_app().launch()
|
assets/styles.css
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.header-row {
|
2 |
+
align-items: center;
|
3 |
+
padding: 12px 12px;
|
4 |
+
}
|
components/__init__.py
ADDED
File without changes
|
components/dashboard_page.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# components/dashboard_page.py
|
2 |
+
import gradio as gr
|
3 |
+
from utils.auth import AuthService
|
4 |
+
from components.header import Header
|
5 |
+
|
6 |
+
|
7 |
+
class DashboardPage:
|
8 |
+
"""UI elements + event wiring for the post-login dashboard."""
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
with gr.Column(visible=False) as self.container:
|
12 |
+
# ───── header (welcome + logout) ─────
|
13 |
+
self.header = Header()
|
14 |
+
|
15 |
+
# ─────────── main body ────────────
|
16 |
+
self.echo_box = gr.Textbox(label="Echo – whatever you type will be returned")
|
17 |
+
self.echo_btn = gr.Button("Echo")
|
18 |
+
self.echo_output = gr.Markdown()
|
19 |
+
|
20 |
+
# ─────────────────── event wiring ────────────────────
|
21 |
+
def register_callbacks(self, login_page, session_state):
|
22 |
+
# Echo button: prepend current username
|
23 |
+
self.echo_btn.click(
|
24 |
+
fn=AuthService.echo,
|
25 |
+
inputs=[self.echo_box, session_state],
|
26 |
+
outputs=self.echo_output,
|
27 |
+
)
|
28 |
+
|
29 |
+
# Header handles its own logout button
|
30 |
+
self.header.register_callbacks(login_page, self, session_state)
|
components/header.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# components/header.py
|
2 |
+
import gradio as gr
|
3 |
+
from utils.auth import AuthService
|
4 |
+
|
5 |
+
|
6 |
+
class Header:
|
7 |
+
def __init__(self):
|
8 |
+
with gr.Row(variant="panel", elem_classes="header-row") as self.container:
|
9 |
+
self.welcome = gr.Markdown()
|
10 |
+
|
11 |
+
self.logout_btn = gr.Button("Log out", scale=0, min_width=90)
|
12 |
+
|
13 |
+
# ---------------- wiring ----------------
|
14 |
+
def register_callbacks(self, login_page, dashboard_page, session_state):
|
15 |
+
self.logout_btn.click(
|
16 |
+
fn=AuthService.logout,
|
17 |
+
inputs=session_state,
|
18 |
+
outputs=[
|
19 |
+
login_page.container,
|
20 |
+
dashboard_page.container,
|
21 |
+
login_page.message,
|
22 |
+
],
|
23 |
+
)
|
components/login_page.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# components/login_page.py
|
2 |
+
import gradio as gr
|
3 |
+
from utils.auth import AuthService
|
4 |
+
|
5 |
+
|
6 |
+
class LoginPage:
|
7 |
+
"""UI elements + event wiring for the login screen (centered & narrow)."""
|
8 |
+
|
9 |
+
def __init__(self):
|
10 |
+
with gr.Row(visible=True) as self.container:
|
11 |
+
gr.Column(scale=1) # left spacer
|
12 |
+
|
13 |
+
with gr.Column(scale=0) as self.form_col:
|
14 |
+
# components with min_width control overall width
|
15 |
+
self.username = gr.Text(label="Username", min_width=300)
|
16 |
+
self.password = gr.Text(label="Password", type="password", min_width=300)
|
17 |
+
self.login_btn = gr.Button("Log in", min_width=300)
|
18 |
+
|
19 |
+
# ↓ removed min_width (Markdown doesn't accept it)
|
20 |
+
self.message = gr.Markdown()
|
21 |
+
|
22 |
+
gr.Column(scale=1) # right spacer
|
23 |
+
|
24 |
+
# event wiring unchanged …
|
25 |
+
def register_callbacks(self, dashboard_page, session_state):
|
26 |
+
header = dashboard_page.header
|
27 |
+
|
28 |
+
self.login_btn.click(
|
29 |
+
fn=AuthService.login,
|
30 |
+
inputs=[self.username, self.password, session_state],
|
31 |
+
outputs=[self.message, self.container, dashboard_page.container],
|
32 |
+
concurrency_limit=10,
|
33 |
+
).then(
|
34 |
+
lambda s: f"### User: {s.get('user', '')}",
|
35 |
+
inputs=session_state,
|
36 |
+
outputs=header.welcome,
|
37 |
+
)
|
config.py
CHANGED
@@ -1,13 +1,27 @@
|
|
|
|
|
|
1 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
|
|
|
|
|
|
3 |
|
4 |
-
db_config = {
|
5 |
-
'user': os.environ.get('DB_USER'),
|
6 |
-
'password': os.environ.get('DB_PASSWORD'),
|
7 |
-
'host': os.environ.get('DB_HOST'),
|
8 |
-
'port': os.environ.get('DB_PORT'),
|
9 |
-
'database': os.environ.get('DB_NAME')
|
10 |
-
}
|
11 |
|
12 |
-
|
13 |
-
hf_tts_ds_repo = os.environ.get('HF_TTS_DS_REPO')
|
|
|
1 |
+
# config.py
|
2 |
+
|
3 |
import os
|
4 |
+
from pydantic_settings import BaseSettings
|
5 |
+
|
6 |
+
|
7 |
+
class Config(BaseSettings):
|
8 |
+
DB_USER: str = os.getenv("DB_USER")
|
9 |
+
DB_PASSWORD: str = os.getenv("DB_PASSWORD")
|
10 |
+
DB_HOST: str = os.getenv("DB_HOST", "localhost")
|
11 |
+
DB_PORT: str = os.getenv("DB_PORT", "3306")
|
12 |
+
DB_NAME: str = os.getenv("DB_NAME", "defaultdb")
|
13 |
+
HF_TOKEN: str = os.environ.get("HF_TOKEN")
|
14 |
+
HF_TTS_DS_REPO: str = os.environ.get("HF_TTS_DS_REPO")
|
15 |
+
|
16 |
+
APP_TITLE: str = "Gooya TTS Annotation Tools"
|
17 |
+
|
18 |
+
class Config:
|
19 |
+
env_file = ".env"
|
20 |
+
case_sensitive = True
|
21 |
|
22 |
+
@property
|
23 |
+
def db_url(self) -> str:
|
24 |
+
return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
conf = Config()
|
|
models.py → data/models.py
RENAMED
@@ -1,89 +1,76 @@
|
|
1 |
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey
|
2 |
from sqlalchemy.orm import relationship, declarative_base
|
3 |
-
from pydantic import BaseModel, ConfigDict, StringConstraints
|
4 |
-
from typing import Annotated
|
5 |
-
from typing import List
|
6 |
-
from datetime import datetime
|
7 |
|
8 |
Base = declarative_base()
|
9 |
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
class TTSDataOut(BaseModel):
|
13 |
-
id: int
|
14 |
-
filename: Annotated[str, StringConstraints(max_length=255)]
|
15 |
-
sentence: str
|
16 |
-
|
17 |
class Annotator(Base):
|
18 |
-
__tablename__ =
|
19 |
|
20 |
id = Column(Integer, primary_key=True)
|
21 |
name = Column(String, nullable=False, unique=True)
|
22 |
password = Column(String, nullable=False)
|
23 |
last_login = Column(DateTime)
|
24 |
is_active = Column(Boolean, default=True)
|
25 |
-
annotations = relationship(
|
26 |
-
validators = relationship(
|
27 |
-
annotation_intervals = relationship(
|
28 |
|
29 |
|
30 |
class Validator(Base):
|
31 |
-
__tablename__ =
|
32 |
|
33 |
id = Column(Integer, primary_key=True)
|
34 |
-
annotator_id = Column(Integer, ForeignKey(
|
35 |
-
validator_id = Column(Integer, ForeignKey(
|
36 |
|
37 |
|
38 |
class AnnotationInterval(Base):
|
39 |
-
__tablename__ =
|
40 |
|
41 |
id = Column(Integer, primary_key=True)
|
42 |
-
annotator_id = Column(Integer, ForeignKey(
|
43 |
start_index = Column(Integer, nullable=True)
|
44 |
end_index = Column(Integer, nullable=True)
|
45 |
|
46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
class AudioTrim(Base):
|
48 |
-
__tablename__ =
|
49 |
|
50 |
id = Column(Integer, primary_key=True)
|
51 |
start = Column(Float, nullable=False)
|
52 |
end = Column(Float, nullable=False)
|
53 |
-
annotation_id = Column(Integer, ForeignKey(
|
54 |
|
55 |
-
|
56 |
-
|
|
|
57 |
|
58 |
id = Column(Integer, primary_key=True)
|
59 |
-
|
|
|
60 |
validated = Column(Boolean, nullable=False)
|
61 |
-
|
62 |
-
|
63 |
-
audio_trims = relationship('AudioTrim', cascade='all, delete', backref='annotation')
|
64 |
-
json_data_id = Column(Integer, ForeignKey('json_data.id'))
|
65 |
-
annotator_id = Column(Integer, ForeignKey('annotators.id'))
|
66 |
-
|
67 |
-
|
68 |
-
class AudioTrimModel(BaseModel):
|
69 |
-
start: float
|
70 |
-
end: float
|
71 |
-
model_config = ConfigDict(from_attributes=True)
|
72 |
-
|
73 |
-
class AnnotationModel(BaseModel):
|
74 |
-
annotator: str
|
75 |
-
annotated_subtitle: str
|
76 |
-
is_first_phase_accepted: bool
|
77 |
-
create_at: datetime
|
78 |
-
update_at: datetime
|
79 |
-
audio_trims: List[AudioTrimModel]
|
80 |
-
model_config = ConfigDict(from_attributes=True)
|
81 |
-
|
82 |
-
class JsonDataModel(BaseModel):
|
83 |
-
id: int
|
84 |
-
voice_name: str
|
85 |
-
original_subtitle: str
|
86 |
-
ignore_it: bool
|
87 |
-
is_approved_in_second_phase: bool
|
88 |
-
annotations: List[AnnotationModel]
|
89 |
-
model_config = ConfigDict(from_attributes=True)
|
|
|
1 |
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey
|
2 |
from sqlalchemy.orm import relationship, declarative_base
|
|
|
|
|
|
|
|
|
3 |
|
4 |
Base = declarative_base()
|
5 |
|
6 |
|
7 |
+
class TTSData(Base):
|
8 |
+
__tablename__ = "tts_data"
|
9 |
+
|
10 |
+
id = Column(Integer, primary_key=True)
|
11 |
+
filename = Column(String, nullable=False, unique=True)
|
12 |
+
sentence = Column(String, nullable=False)
|
13 |
+
|
14 |
|
|
|
|
|
|
|
|
|
|
|
15 |
class Annotator(Base):
|
16 |
+
__tablename__ = "annotators"
|
17 |
|
18 |
id = Column(Integer, primary_key=True)
|
19 |
name = Column(String, nullable=False, unique=True)
|
20 |
password = Column(String, nullable=False)
|
21 |
last_login = Column(DateTime)
|
22 |
is_active = Column(Boolean, default=True)
|
23 |
+
annotations = relationship("Annotation", backref="annotator")
|
24 |
+
validators = relationship("Validator", backref="annotator")
|
25 |
+
annotation_intervals = relationship("AnnotationInterval", backref="annotator")
|
26 |
|
27 |
|
28 |
class Validator(Base):
|
29 |
+
__tablename__ = "validators"
|
30 |
|
31 |
id = Column(Integer, primary_key=True)
|
32 |
+
annotator_id = Column(Integer, ForeignKey("annotators.id"))
|
33 |
+
validator_id = Column(Integer, ForeignKey("annotators.id"))
|
34 |
|
35 |
|
36 |
class AnnotationInterval(Base):
|
37 |
+
__tablename__ = "annotation_intervals"
|
38 |
|
39 |
id = Column(Integer, primary_key=True)
|
40 |
+
annotator_id = Column(Integer, ForeignKey("annotators.id"))
|
41 |
start_index = Column(Integer, nullable=True)
|
42 |
end_index = Column(Integer, nullable=True)
|
43 |
|
44 |
|
45 |
+
class Annotation(Base):
|
46 |
+
__tablename__ = "annotations"
|
47 |
+
|
48 |
+
id = Column(Integer, primary_key=True)
|
49 |
+
annotated_sentence = Column(String, nullable=False)
|
50 |
+
validated = Column(Boolean, nullable=False)
|
51 |
+
annotated_at = Column(DateTime, nullable=False)
|
52 |
+
annotator_id = Column(Integer, ForeignKey("annotators.id"))
|
53 |
+
audio_trims = relationship("AudioTrim", cascade="all, delete", backref="annotation")
|
54 |
+
validations = relationship(
|
55 |
+
"Validation", cascade="all, delete", backref="annotation"
|
56 |
+
)
|
57 |
+
|
58 |
+
|
59 |
class AudioTrim(Base):
|
60 |
+
__tablename__ = "audio_trims"
|
61 |
|
62 |
id = Column(Integer, primary_key=True)
|
63 |
start = Column(Float, nullable=False)
|
64 |
end = Column(Float, nullable=False)
|
65 |
+
annotation_id = Column(Integer, ForeignKey("annotations.id"))
|
66 |
|
67 |
+
|
68 |
+
class Validation(Base):
|
69 |
+
__tablename__ = "validations"
|
70 |
|
71 |
id = Column(Integer, primary_key=True)
|
72 |
+
annotation_id = Column(Integer, ForeignKey("annotations.id"))
|
73 |
+
validator_id = Column(Integer, ForeignKey("validators.id"))
|
74 |
validated = Column(Boolean, nullable=False)
|
75 |
+
description = Column(String, nullable=True)
|
76 |
+
validated_at = Column(DateTime, nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
database.py
DELETED
@@ -1,152 +0,0 @@
|
|
1 |
-
from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, DateTime, ForeignKey
|
2 |
-
from sqlalchemy.orm import sessionmaker, relationship, declarative_base
|
3 |
-
from pydantic import BaseModel, ConfigDict
|
4 |
-
from typing import List
|
5 |
-
from datetime import datetime
|
6 |
-
|
7 |
-
# ایجاد مدلهای SQLAlchemy
|
8 |
-
Base = declarative_base()
|
9 |
-
|
10 |
-
class Annotator(Base):
|
11 |
-
__tablename__ = 'annotators'
|
12 |
-
|
13 |
-
id = Column(Integer, primary_key=True)
|
14 |
-
name = Column(String, nullable=False, unique=True)
|
15 |
-
password = Column(String, nullable=False)
|
16 |
-
last_login = Column(DateTime)
|
17 |
-
is_active = Column(Boolean, default=True)
|
18 |
-
annotations = relationship('Annotation', backref='annotator')
|
19 |
-
|
20 |
-
class AudioTrim(Base):
|
21 |
-
__tablename__ = 'audio_trims'
|
22 |
-
|
23 |
-
id = Column(Integer, primary_key=True)
|
24 |
-
start = Column(Float, nullable=False)
|
25 |
-
end = Column(Float, nullable=False)
|
26 |
-
annotation_id = Column(Integer, ForeignKey('annotations.id'))
|
27 |
-
|
28 |
-
class Annotation(Base):
|
29 |
-
__tablename__ = 'annotations'
|
30 |
-
|
31 |
-
id = Column(Integer, primary_key=True)
|
32 |
-
annotated_subtitle = Column(String, nullable=False)
|
33 |
-
is_first_phase_accepted = Column(Boolean, nullable=False)
|
34 |
-
create_at = Column(DateTime, nullable=False)
|
35 |
-
update_at = Column(DateTime, nullable=False)
|
36 |
-
audio_trims = relationship('AudioTrim', cascade='all, delete', backref='annotation')
|
37 |
-
json_data_id = Column(Integer, ForeignKey('json_data.id'))
|
38 |
-
annotator_id = Column(Integer, ForeignKey('annotators.id'))
|
39 |
-
|
40 |
-
class JsonData(Base):
|
41 |
-
__tablename__ = 'json_data'
|
42 |
-
|
43 |
-
id = Column(Integer, primary_key=True)
|
44 |
-
voice_name = Column(String, nullable=False)
|
45 |
-
original_subtitle = Column(String, nullable=False)
|
46 |
-
ignore_it = Column(Boolean, nullable=False)
|
47 |
-
is_approved_in_second_phase = Column(Boolean, nullable=False)
|
48 |
-
annotations = relationship('Annotation', cascade='all, delete', backref='json_data')
|
49 |
-
|
50 |
-
# ایجاد مدلهای Pydantic
|
51 |
-
class AudioTrimModel(BaseModel):
|
52 |
-
start: float
|
53 |
-
end: float
|
54 |
-
model_config = ConfigDict(from_attributes=True)
|
55 |
-
|
56 |
-
class AnnotationModel(BaseModel):
|
57 |
-
annotator: str
|
58 |
-
annotated_subtitle: str
|
59 |
-
is_first_phase_accepted: bool
|
60 |
-
create_at: datetime
|
61 |
-
update_at: datetime
|
62 |
-
audio_trims: List[AudioTrimModel]
|
63 |
-
model_config = ConfigDict(from_attributes=True)
|
64 |
-
|
65 |
-
class JsonDataModel(BaseModel):
|
66 |
-
id: int
|
67 |
-
voice_name: str
|
68 |
-
original_subtitle: str
|
69 |
-
ignore_it: bool
|
70 |
-
is_approved_in_second_phase: bool
|
71 |
-
annotations: List[AnnotationModel]
|
72 |
-
model_config = ConfigDict(from_attributes=True)
|
73 |
-
|
74 |
-
# ایجاد جلسه
|
75 |
-
DB_URL = "mysql+pymysql://user:password@localhost/testdb" # اطلاعات کاربری و دیتابیس را ویرایش کنید
|
76 |
-
engine = create_engine(DB_URL)
|
77 |
-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
78 |
-
|
79 |
-
def initialize_database():
|
80 |
-
Base.metadata.create_all(bind=engine)
|
81 |
-
|
82 |
-
# تابع ذخیره دادهها در دیتابیس
|
83 |
-
def save_json_data(json_data_dict):
|
84 |
-
db = SessionLocal()
|
85 |
-
try:
|
86 |
-
# استفاده از Pydantic برای اعتبارسنجی دادهها
|
87 |
-
json_data = JsonDataModel(**json_data_dict)
|
88 |
-
|
89 |
-
for annotation in json_data.annotations:
|
90 |
-
# تامین موجودیت annotator
|
91 |
-
db_annotator = db.query(Annotator).filter_by(name=annotation.annotator).first()
|
92 |
-
if not db_annotator:
|
93 |
-
db_annotator = Annotator(name=annotation.annotator, password="default_password")
|
94 |
-
db.add(db_annotator)
|
95 |
-
db.commit() # اضافه کنید تا id اختصاص داده شود
|
96 |
-
|
97 |
-
# تبدیل مدل Pydantic به مدل SQLAlchemy
|
98 |
-
db_annotation = Annotation(
|
99 |
-
annotated_subtitle=annotation.annotated_subtitle,
|
100 |
-
is_first_phase_accepted=annotation.is_first_phase_accepted,
|
101 |
-
create_at=annotation.create_at,
|
102 |
-
update_at=annotation.update_at,
|
103 |
-
audio_trims=[
|
104 |
-
AudioTrim(start=trim.start, end=trim.end) for trim in annotation.audio_trims
|
105 |
-
],
|
106 |
-
annotator=db_annotator
|
107 |
-
)
|
108 |
-
|
109 |
-
db_json_data = JsonData(
|
110 |
-
id=json_data.id,
|
111 |
-
voice_name=json_data.voice_name,
|
112 |
-
original_subtitle=json_data.original_subtitle,
|
113 |
-
ignore_it=json_data.ignore_it,
|
114 |
-
is_approved_in_second_phase=json_data.is_approved_in_second_phase,
|
115 |
-
annotations=[db_annotation]
|
116 |
-
)
|
117 |
-
|
118 |
-
db.add(db_json_data)
|
119 |
-
db.commit()
|
120 |
-
|
121 |
-
except Exception as e:
|
122 |
-
db.rollback()
|
123 |
-
print(e)
|
124 |
-
finally:
|
125 |
-
db.close()
|
126 |
-
|
127 |
-
# فراخوانی تابع ایجاد جدولها و افزودن داده
|
128 |
-
initialize_database()
|
129 |
-
|
130 |
-
# دادهی نمونه برای افزودن
|
131 |
-
json_data = {
|
132 |
-
"id": 16562,
|
133 |
-
"voice_name": "voice1.wav",
|
134 |
-
"original_subtitle": "خلاصه با دلی ناراحت این کار رو کرد کاری رو که به نظرم میاد کار عقلانی بوده و رفتن رسیدن همون جایی که خاطره خداحافظی ۶ سال پیش رو واسه شکلتون زنده کرد اون بار به عنوان invalid اون بار به عنوان ادمی که از پس کار برنیومده",
|
135 |
-
"ignore_it": False,
|
136 |
-
"annotations": [
|
137 |
-
{
|
138 |
-
"annotator": "amin76",
|
139 |
-
"annotated_subtitle": "خلاصه با دلی ناراحت این کار رو کرد کاری رو که به نظرم میاد کار عقلانی بوده و رفتن رسیدن همون جایی که خاطره خداحافظی شش سال پیش رو واسه شکلتون زنده کرد اون بار به عنوان اينوليد اون بار به عنوان آدمی که از پس کار برنیومده",
|
140 |
-
"audio_trims": [
|
141 |
-
{"start": 14.5, "end": 15.0},
|
142 |
-
{"start": 14.3, "end": 15.0}
|
143 |
-
],
|
144 |
-
"is_first_phase_accepted": False,
|
145 |
-
"create_at": "2025-05-20T16:05:53.138811",
|
146 |
-
"update_at": "2025-05-20T16:06:28.525393"
|
147 |
-
}
|
148 |
-
],
|
149 |
-
"is_approved_in_second_phase": False
|
150 |
-
}
|
151 |
-
|
152 |
-
save_json_data(json_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -3,4 +3,5 @@ sqlalchemy
|
|
3 |
pydantic
|
4 |
mysql-connector-python
|
5 |
soundfile
|
6 |
-
librosa
|
|
|
|
3 |
pydantic
|
4 |
mysql-connector-python
|
5 |
soundfile
|
6 |
+
librosa
|
7 |
+
pydantic-settings
|
seed_db.py
DELETED
@@ -1,52 +0,0 @@
|
|
1 |
-
import mysql.connector
|
2 |
-
from datasets import load_dataset
|
3 |
-
from huggingface_hub import login
|
4 |
-
import config
|
5 |
-
|
6 |
-
|
7 |
-
def seed():
|
8 |
-
login(token=config.hf_token)
|
9 |
-
dataset = load_dataset(config.hf_tts_ds_repo, split="train", trust_remote_code=True)
|
10 |
-
|
11 |
-
print(dataset.column_names)
|
12 |
-
print(dataset[0])
|
13 |
-
|
14 |
-
conn = mysql.connector.connect(**config.db_config)
|
15 |
-
cursor = conn.cursor()
|
16 |
-
|
17 |
-
cursor.execute(
|
18 |
-
"""
|
19 |
-
CREATE TABLE IF NOT EXISTS tts_data (
|
20 |
-
id INT AUTO_INCREMENT PRIMARY KEY,
|
21 |
-
filename VARCHAR(200) UNIQUE,
|
22 |
-
sentence TEXT
|
23 |
-
)
|
24 |
-
"""
|
25 |
-
)
|
26 |
-
|
27 |
-
batch_size = 1000
|
28 |
-
batch = []
|
29 |
-
|
30 |
-
for i, item in enumerate(dataset):
|
31 |
-
filename = item["audio"]["path"]
|
32 |
-
sentence = item["sentence"]
|
33 |
-
batch.append((filename, sentence))
|
34 |
-
|
35 |
-
if len(batch) == batch_size:
|
36 |
-
cursor.executemany(
|
37 |
-
"INSERT INTO tts_data (filename, sentence) VALUES (%s, %s)", batch
|
38 |
-
)
|
39 |
-
conn.commit()
|
40 |
-
print(f"✅ {i + 1} records saved!")
|
41 |
-
batch = []
|
42 |
-
|
43 |
-
if batch:
|
44 |
-
cursor.executemany(
|
45 |
-
"INSERT INTO tts_data (filename, sentence) VALUES (%s, %s)", batch
|
46 |
-
)
|
47 |
-
conn.commit()
|
48 |
-
print(f"✅ last {len(batch)} records saved.")
|
49 |
-
|
50 |
-
cursor.close()
|
51 |
-
conn.close()
|
52 |
-
return "done!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db_test.py → test/db_test.py
RENAMED
@@ -2,16 +2,18 @@ import mysql.connector
|
|
2 |
from mysql.connector import Error
|
3 |
import config
|
4 |
|
5 |
-
|
6 |
try:
|
7 |
conn = mysql.connector.connect(**config.db_config)
|
8 |
if conn.is_connected():
|
9 |
print("✅OK!")
|
10 |
|
11 |
cursor = conn.cursor()
|
12 |
-
cursor.execute("
|
13 |
-
|
14 |
-
|
|
|
|
|
|
|
15 |
|
16 |
except Error as e:
|
17 |
print("❌ error:", e)
|
@@ -21,4 +23,4 @@ finally:
|
|
21 |
cursor.close()
|
22 |
if 'conn' in locals() and conn.is_connected():
|
23 |
conn.close()
|
24 |
-
print("⛔️Closed.")
|
|
|
2 |
from mysql.connector import Error
|
3 |
import config
|
4 |
|
|
|
5 |
try:
|
6 |
conn = mysql.connector.connect(**config.db_config)
|
7 |
if conn.is_connected():
|
8 |
print("✅OK!")
|
9 |
|
10 |
cursor = conn.cursor()
|
11 |
+
cursor.execute("SELECT id, filename, sentence FROM tts_data ORDER BY id DESC LIMIT 5")
|
12 |
+
records = cursor.fetchall()
|
13 |
+
|
14 |
+
print("🟢Last 5 records:")
|
15 |
+
for record in records:
|
16 |
+
print(record)
|
17 |
|
18 |
except Error as e:
|
19 |
print("❌ error:", e)
|
|
|
23 |
cursor.close()
|
24 |
if 'conn' in locals() and conn.is_connected():
|
25 |
conn.close()
|
26 |
+
print("⛔️Closed.")
|
test/test.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
|
3 |
+
USERS = {"u1": "123", "u2": "234"}
|
4 |
+
|
5 |
+
|
6 |
+
def local_login(username: str, password: str, session: dict):
|
7 |
+
"""Authenticate against the in-memory USERS dict."""
|
8 |
+
if USERS.get(username) != password:
|
9 |
+
return (
|
10 |
+
"❌ Wrong username or password!",
|
11 |
+
gr.update(), # keep login form unchanged
|
12 |
+
gr.update(visible=False), # hide dashboard
|
13 |
+
)
|
14 |
+
|
15 |
+
# Successful login
|
16 |
+
session["user"] = username
|
17 |
+
return (
|
18 |
+
f"✅ Hello *{username}*",
|
19 |
+
gr.update(visible=False), # hide login form
|
20 |
+
gr.update(visible=True), # show dashboard
|
21 |
+
)
|
22 |
+
|
23 |
+
|
24 |
+
# --- 1. Update the logout function -----------------------------------------
|
25 |
+
def local_logout(session: dict):
|
26 |
+
"""Clear the session, show the login form, hide the dashboard,
|
27 |
+
and clear any previous login message."""
|
28 |
+
session.clear()
|
29 |
+
return (
|
30 |
+
gr.update(visible=True), # show login_form
|
31 |
+
gr.update(visible=False), # hide dashboard
|
32 |
+
gr.update(value=""), # clear login_msg
|
33 |
+
)
|
34 |
+
|
35 |
+
|
36 |
+
def echo_with_user(text: str, session: dict):
|
37 |
+
"""Return 'username: text'."""
|
38 |
+
user = session.get("user", "(none)")
|
39 |
+
return f"{user}: {text}"
|
40 |
+
|
41 |
+
|
42 |
+
def oauth_welcome(profile: gr.OAuthProfile, session: dict):
|
43 |
+
"""Executed after a successful OAuth login."""
|
44 |
+
if profile is None:
|
45 |
+
return gr.update(visible=False)
|
46 |
+
|
47 |
+
session["user"] = profile.username
|
48 |
+
return gr.update(value=f"✅ Welcome {profile.username}", visible=True)
|
49 |
+
|
50 |
+
|
51 |
+
with gr.Blocks(title="Multi-user Login Demo") as demo:
|
52 |
+
# Session state is kept per browser tab
|
53 |
+
session = gr.State({})
|
54 |
+
|
55 |
+
# -------------------- Login form --------------------
|
56 |
+
with gr.Column(visible=True) as login_form:
|
57 |
+
username = gr.Text(label="Username")
|
58 |
+
password = gr.Text(label="Password", type="password")
|
59 |
+
login_btn = gr.Button("Log in")
|
60 |
+
login_msg = gr.Markdown()
|
61 |
+
|
62 |
+
# -------------------- Dashboard --------------------
|
63 |
+
with gr.Column(visible=False) as dashboard:
|
64 |
+
welcome = gr.Markdown()
|
65 |
+
echo_box = gr.Textbox(label="Echo – whatever you type will be returned")
|
66 |
+
echo_btn = gr.Button("Echo") # renamed button
|
67 |
+
echo_output = gr.Markdown() # where result is shown
|
68 |
+
logout_btn = gr.Button("Log out")
|
69 |
+
|
70 |
+
# -------------- Callbacks (local DB version) --------
|
71 |
+
login_btn.click(
|
72 |
+
fn=local_login,
|
73 |
+
inputs=[username, password, session],
|
74 |
+
outputs=[login_msg, login_form, dashboard],
|
75 |
+
concurrency_limit=10,
|
76 |
+
).then(
|
77 |
+
lambda s: f"👋 Active user: {s.get('user', '')}",
|
78 |
+
inputs=session,
|
79 |
+
outputs=welcome,
|
80 |
+
)
|
81 |
+
|
82 |
+
logout_btn.click(
|
83 |
+
fn=local_logout,
|
84 |
+
inputs=session,
|
85 |
+
outputs=[login_form, dashboard, login_msg], # ← added login_msg
|
86 |
+
)
|
87 |
+
|
88 |
+
echo_btn.click(
|
89 |
+
fn=echo_with_user,
|
90 |
+
inputs=[echo_box, session],
|
91 |
+
outputs=echo_output,
|
92 |
+
)
|
93 |
+
|
94 |
+
|
95 |
+
demo.queue(default_concurrency_limit=50)
|
96 |
+
demo.launch()
|
utils/__init__.py
ADDED
File without changes
|
utils/auth.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils/auth.py
|
2 |
+
import gradio as gr
|
3 |
+
|
4 |
+
|
5 |
+
class AuthService:
|
6 |
+
"""
|
7 |
+
Authentication / business-logic layer.
|
8 |
+
Replace with real DB + hashed passwords in production.
|
9 |
+
"""
|
10 |
+
_USERS = {"u1": "123", "u2": "234"}
|
11 |
+
|
12 |
+
# ─────────────────── core actions ────────────────────
|
13 |
+
@staticmethod
|
14 |
+
def login(username: str, password: str, session: dict):
|
15 |
+
if AuthService._USERS.get(username) != password:
|
16 |
+
return (
|
17 |
+
"❌ Wrong username or password!",
|
18 |
+
gr.update(), # keep login form visible
|
19 |
+
gr.update(visible=False), # hide dashboard
|
20 |
+
)
|
21 |
+
|
22 |
+
# success
|
23 |
+
session["user"] = username
|
24 |
+
return (
|
25 |
+
f"✅ Hello *{username}*",
|
26 |
+
gr.update(visible=False), # hide login form
|
27 |
+
gr.update(visible=True), # show dashboard
|
28 |
+
)
|
29 |
+
|
30 |
+
@staticmethod
|
31 |
+
def logout(session: dict):
|
32 |
+
session.clear()
|
33 |
+
return (
|
34 |
+
gr.update(visible=True), # show login page
|
35 |
+
gr.update(visible=False), # hide dashboard
|
36 |
+
gr.update(value="") # clear old messages
|
37 |
+
)
|
38 |
+
|
39 |
+
@staticmethod
|
40 |
+
def echo(text: str, session: dict):
|
41 |
+
user = session.get("user", "(none)")
|
42 |
+
return f"{user}: {text}"
|
utils/database.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import create_engine
|
2 |
+
from sqlalchemy.orm import sessionmaker
|
3 |
+
from contextlib import contextmanager
|
4 |
+
from config import conf
|
5 |
+
from utils.logger import Logger
|
6 |
+
|
7 |
+
log = Logger.get_logger()
|
8 |
+
|
9 |
+
|
10 |
+
def get_db_engine():
|
11 |
+
"""Create DB engine with error handling for HF Spaces"""
|
12 |
+
try:
|
13 |
+
engine = create_engine(
|
14 |
+
conf.db_url,
|
15 |
+
pool_pre_ping=True,
|
16 |
+
pool_size=5,
|
17 |
+
max_overflow=10,
|
18 |
+
connect_args={"connect_timeout": 10},
|
19 |
+
)
|
20 |
+
log.info("Database engine created successfully")
|
21 |
+
return engine
|
22 |
+
except Exception as e:
|
23 |
+
log.error(f"Failed to create database engine: {e}")
|
24 |
+
|
25 |
+
|
26 |
+
engine = get_db_engine()
|
27 |
+
SessionLocal = sessionmaker(bind=engine)
|
28 |
+
|
29 |
+
|
30 |
+
@contextmanager
|
31 |
+
def get_db():
|
32 |
+
"""Session manager for HF Spaces"""
|
33 |
+
db = SessionLocal()
|
34 |
+
try:
|
35 |
+
yield db
|
36 |
+
db.commit()
|
37 |
+
except Exception as e:
|
38 |
+
db.rollback()
|
39 |
+
log.error(f"Database error: {e}")
|
40 |
+
raise
|
41 |
+
finally:
|
42 |
+
db.close()
|
43 |
+
|
44 |
+
|
45 |
+
def initialize_database():
|
46 |
+
"""Initialize tables with HF Spaces compatibility"""
|
47 |
+
try:
|
48 |
+
from data.models import Base
|
49 |
+
|
50 |
+
Base.metadata.create_all(bind=engine)
|
51 |
+
log.info("Tables created successfully")
|
52 |
+
except Exception as e:
|
53 |
+
log.error(f"Table creation failed: {e}")
|
utils/logger.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import sys
|
3 |
+
from typing import Optional
|
4 |
+
|
5 |
+
|
6 |
+
class Logger:
|
7 |
+
_initialized = False
|
8 |
+
|
9 |
+
@classmethod
|
10 |
+
def initialize(cls, level: str = "INFO", name: Optional[str] = None):
|
11 |
+
if cls._initialized:
|
12 |
+
return
|
13 |
+
|
14 |
+
logger = logging.getLogger(name or __name__)
|
15 |
+
logger.setLevel(level)
|
16 |
+
|
17 |
+
# Create console handler
|
18 |
+
handler = logging.StreamHandler(sys.stdout)
|
19 |
+
handler.setLevel(level)
|
20 |
+
|
21 |
+
# Simple formatter
|
22 |
+
formatter = logging.Formatter(
|
23 |
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
24 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
25 |
+
)
|
26 |
+
handler.setFormatter(formatter)
|
27 |
+
|
28 |
+
# Add handler and prevent duplication
|
29 |
+
if not logger.handlers:
|
30 |
+
logger.addHandler(handler)
|
31 |
+
|
32 |
+
cls._initialized = True
|
33 |
+
return logger
|
34 |
+
|
35 |
+
@classmethod
|
36 |
+
def get_logger(cls, name: Optional[str] = None) -> logging.Logger:
|
37 |
+
if not cls._initialized:
|
38 |
+
cls.initialize()
|
39 |
+
return logging.getLogger(name or __name__)
|