convertDataset / app.py
archivartaunik's picture
Rename app_updated.py to app.py
cd81cb4 verified
Raw
History Blame Contribute Delete
35.2 kB
from __future__ import annotations
import csv
import json
import os
import re
import tempfile
from collections import Counter
from pathlib import Path
from typing import Any
import gradio as gr
import pyarrow as pa
import pyarrow.parquet as pq
from huggingface_hub import (
CommitOperationAdd,
CommitOperationDelete,
HfApi,
hf_hub_download,
snapshot_download,
)
AUDIO_SUFFIXES = {
".mp3",
".wav",
".ogg",
".opus",
".flac",
".m4a",
".aac",
}
DEFAULT_SHARD_TARGET_MB = 230
HARD_SHARD_LIMIT_MB = 260
DEFAULT_ALLOW_PATTERNS = [
"**/*.mp3",
"**/*.MP3",
"**/*.wav",
"**/*.WAV",
"**/*.ogg",
"**/*.OGG",
"**/*.opus",
"**/*.OPUS",
"**/*.flac",
"**/*.FLAC",
"**/*.m4a",
"**/*.M4A",
"**/*.aac",
"**/*.AAC",
"**/metadata.csv",
"metadata.csv",
]
TITLE_KEYS = (
"Title",
"title",
"book_title",
"Book Title",
"album",
"Album",
"name",
"Name",
"pretty_name",
)
AUTHOR_KEYS = (
"Author",
"author",
"authors",
"Authors",
"writer",
"Writer",
"creator",
"Creator",
)
NARRATOR_KEYS = (
"Narrator",
"narrator",
"reader",
"Reader",
"voice",
"Voice",
"performer",
"Performer",
)
SOURCE_GROUP_KEYS = (
"Source Group",
"source_group",
"source-group",
"source",
"Source",
"collection",
"Collection",
"category",
"Category",
)
LANGUAGE_KEYS = (
"language",
"Language",
"lang",
"Lang",
"locale",
"Locale",
)
# -----------------------------------------------------------------------------
# General helpers
# -----------------------------------------------------------------------------
def normalize_repo_id(value: str) -> str:
value = (value or "").strip().rstrip("/")
if "huggingface.co/datasets/" in value:
value = value.split("huggingface.co/datasets/", 1)[1]
value = value.split("/tree/", 1)[0]
value = value.split("/blob/", 1)[0]
value = value.split("/resolve/", 1)[0]
value = value.split("?", 1)[0]
value = value.strip("/")
return value
def slugify(text: str, max_len: int = 80) -> str:
text = text.lower()
text = re.sub(r"[^\w\s.-]", "", text, flags=re.UNICODE)
text = re.sub(r"[\s_-]+", "_", text)
return text.strip("_.-")[:max_len] or "audio"
def humanize_repo_name(repo_id: str) -> str:
name = repo_id.split("/", 1)[-1]
name = re.sub(r"[-_]+", " ", name).strip()
return name[:1].upper() + name[1:] if name else repo_id
def get_optional_token(token_from_ui: str | None) -> str | None:
return (token_from_ui or "").strip() or os.environ.get("HF_TOKEN", "").strip() or None
def get_token(token_from_ui: str | None) -> str:
token = get_optional_token(token_from_ui)
if not token:
raise gr.Error(
"HF token не знойдзены. Дадай HF_TOKEN у Space Settings → Secrets "
"або ўвядзі token у поле HF token override."
)
return token
def suggest_output_repo(source_repo: str) -> str:
if not source_repo:
return ""
if source_repo.endswith("-input"):
return source_repo
if "/" not in source_repo:
return f"{source_repo}-input"
owner, name = source_repo.split("/", 1)
return f"{owner}/{name}-input"
def unique_keep_order(values: list[str]) -> list[str]:
seen = set()
result = []
for value in values:
value = value.strip()
if value and value not in seen:
seen.add(value)
result.append(value)
return result
# -----------------------------------------------------------------------------
# Metadata prefill helpers
# -----------------------------------------------------------------------------
def strip_frontmatter(readme_text: str) -> tuple[str, str]:
"""Return (frontmatter, markdown_body). YAML is parsed lightly by regex only."""
text = readme_text or ""
if not text.startswith("---"):
return "", text
match = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", text, flags=re.DOTALL)
if not match:
return "", text
return match.group(1), match.group(2)
def yaml_scalar_value(frontmatter: str, key: str) -> str:
"""
Very small YAML front-matter reader for common HF card fields.
Handles:
key: value
key:
- value
key: [a, b]
"""
if not frontmatter:
return ""
lines = frontmatter.splitlines()
for index, line in enumerate(lines):
scalar = re.match(rf"^\s*{re.escape(key)}\s*:\s*(.*?)\s*$", line)
if not scalar:
continue
value = scalar.group(1).strip().strip('"\'')
if value:
if value.startswith("[") and value.endswith("]"):
value = value[1:-1].split(",", 1)[0].strip().strip('"\'')
return value
# List value on next lines.
collected = []
for next_line in lines[index + 1 :]:
if re.match(r"^\S[^:]*:\s*", next_line):
break
list_item = re.match(r"^\s*-\s*(.*?)\s*$", next_line)
if list_item:
item = list_item.group(1).strip().strip('"\'')
if item:
collected.append(item)
if collected:
return collected[0]
return ""
def markdown_h1(readme_body: str) -> str:
for line in (readme_body or "").splitlines():
match = re.match(r"^#\s+(.+?)\s*$", line)
if match:
return match.group(1).strip()
return ""
def markdown_inline_field(readme_text: str, *labels: str) -> str:
"""
Finds simple card lines such as:
Author: Іван Мележ
- Author: Іван Мележ
**Author:** Іван Мележ
"""
for label in labels:
pattern = rf"(?im)^\s*(?:[-*]\s*)?(?:\*\*)?{re.escape(label)}(?:\*\*)?\s*:\s*(.+?)\s*$"
match = re.search(pattern, readme_text or "")
if match:
value = match.group(1).strip()
value = re.sub(r"^[`*_\s]+|[`*_\s]+$", "", value)
if value:
return value
return ""
def first_existing_value(row: dict[str, str], keys: tuple[str, ...]) -> str:
for key in keys:
value = row.get(key)
if value:
return str(value).strip()
return ""
def most_common_metadata_value(rows: list[dict[str, str]], keys: tuple[str, ...]) -> str:
values = []
for row in rows:
value = first_existing_value(row, keys)
if value:
values.append(value)
if not values:
return ""
counter = Counter(values)
return counter.most_common(1)[0][0]
def read_metadata_csv_rows(
repo_id: str,
repo_files: list[str],
token: str | None,
max_rows: int = 500,
) -> list[dict[str, str]]:
metadata_paths = [p for p in repo_files if Path(p).name.lower() == "metadata.csv"]
rows: list[dict[str, str]] = []
for metadata_path in metadata_paths[:5]:
try:
local_path = hf_hub_download(
repo_id=repo_id,
repo_type="dataset",
filename=metadata_path,
token=token,
)
with open(local_path, "r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
cleaned_row = {
str(k).strip(): str(v).strip()
for k, v in row.items()
if k is not None and v is not None and str(v).strip()
}
rows.append(cleaned_row)
if len(rows) >= max_rows:
return rows
except Exception:
continue
return rows
def read_readme_metadata(repo_id: str, token: str | None) -> dict[str, str]:
for filename in ("README.md", "readme.md", "Readme.md"):
try:
local_path = hf_hub_download(
repo_id=repo_id,
repo_type="dataset",
filename=filename,
token=token,
)
text = Path(local_path).read_text(encoding="utf-8", errors="ignore")
frontmatter, body = strip_frontmatter(text)
return {
"title": (
yaml_scalar_value(frontmatter, "pretty_name")
or markdown_inline_field(text, "Title", "title")
or markdown_h1(body)
),
"author": markdown_inline_field(text, "Author", "author", "Authors", "authors"),
"narrator": markdown_inline_field(text, "Narrator", "narrator", "Reader", "reader"),
"source_group": markdown_inline_field(
text,
"Source Group",
"source_group",
"Source",
"source",
"Collection",
"collection",
),
"language": (
yaml_scalar_value(frontmatter, "language")
or yaml_scalar_value(frontmatter, "languages")
or markdown_inline_field(text, "Language", "language", "Lang", "lang")
),
}
except Exception:
continue
return {}
def infer_allow_patterns_from_repo_files(repo_files: list[str]) -> str:
audio_exts = sorted({Path(p).suffix.lower() for p in repo_files if Path(p).suffix.lower() in AUDIO_SUFFIXES})
patterns: list[str] = []
# Preserve a useful explicit pattern for datasets split into part_*/ directories.
has_part_dirs = any(re.search(r"(^|/)part_[^/]+/[^/]+$", p) for p in repo_files)
if has_part_dirs:
for ext in audio_exts:
patterns.append(f"part_*/*{ext}")
for ext in audio_exts:
patterns.append(f"**/*{ext}")
upper_ext = ext.upper()
if upper_ext != ext:
patterns.append(f"**/*{upper_ext}")
metadata_paths = [p for p in repo_files if Path(p).name.lower() == "metadata.csv"]
if metadata_paths:
patterns.extend(metadata_paths)
patterns.append("**/metadata.csv")
patterns.append("metadata.csv")
if not patterns:
patterns = DEFAULT_ALLOW_PATTERNS.copy()
return "\n".join(unique_keep_order(patterns))
def prefill_from_dataset_metadata(source_repo: str, hf_token: str):
"""
Fill Gradio fields from a pasted Hugging Face dataset URL/repo_id.
Priority:
1. metadata.csv values, if present
2. README.md card/front matter
3. repo name fallback
"""
source_repo = normalize_repo_id(source_repo)
if not source_repo:
raise gr.Error(
"Устаў спасылку на Hugging Face dataset або repo id, напрыклад "
"`https://huggingface.co/datasets/owner/name`."
)
token = get_optional_token(hf_token)
api = HfApi(token=token)
try:
repo_files = api.list_repo_files(
repo_id=source_repo,
repo_type="dataset",
)
except Exception as exc:
raise gr.Error(
"Не атрымалася прачытаць файлы dataset repo. Калі датасэт прыватны, "
"дадай HF_TOKEN у Space Secrets або ў поле HF token override.\n\n"
f"Дэталі: {exc}"
)
readme_meta = read_readme_metadata(source_repo, token)
metadata_rows = read_metadata_csv_rows(source_repo, repo_files, token)
title_value = (
most_common_metadata_value(metadata_rows, TITLE_KEYS)
or readme_meta.get("title", "")
or humanize_repo_name(source_repo)
)
author_value = (
most_common_metadata_value(metadata_rows, AUTHOR_KEYS)
or readme_meta.get("author", "")
)
narrator_value = (
most_common_metadata_value(metadata_rows, NARRATOR_KEYS)
or readme_meta.get("narrator", "")
)
source_group_value = (
most_common_metadata_value(metadata_rows, SOURCE_GROUP_KEYS)
or readme_meta.get("source_group", "")
or "Аўдыёкнігі"
)
language_value = (
most_common_metadata_value(metadata_rows, LANGUAGE_KEYS)
or readme_meta.get("language", "")
or "be"
)
allow_patterns_text = infer_allow_patterns_from_repo_files(repo_files)
output_repo_value = suggest_output_repo(source_repo)
log_lines = [
"DONE: палі запоўнены з metadata dataset-а.",
f"Source repo: {source_repo}",
f"Output repo: {output_repo_value}",
f"Repo files detected: {len(repo_files)}",
f"metadata.csv rows sampled: {len(metadata_rows)}",
f"Title: {title_value}",
f"Author: {author_value or '-'}",
f"Narrator: {narrator_value or '-'}",
f"Source Group: {source_group_value or '-'}",
f"Language: {language_value or '-'}",
]
return (
source_repo,
output_repo_value,
title_value,
author_value,
narrator_value,
source_group_value,
language_value,
allow_patterns_text,
"\n".join(log_lines),
)
# -----------------------------------------------------------------------------
# Dataset conversion helpers
# -----------------------------------------------------------------------------
def discover_audio_files(local_dir: Path) -> list[Path]:
files = []
for path in local_dir.rglob("*"):
if path.is_file() and path.suffix.lower() in AUDIO_SUFFIXES:
files.append(path)
return sorted(files, key=lambda p: p.as_posix())
def load_metadata_maps(local_dir: Path) -> dict[str, dict[str, str]]:
"""
Reads metadata.csv files if they exist.
Supports:
file_name
filename
audio
path
Stores metadata by:
relative path
basename
"""
result: dict[str, dict[str, str]] = {}
for metadata_path in local_dir.rglob("metadata.csv"):
try:
with metadata_path.open("r", encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
raw_file = (
row.get("file_name")
or row.get("filename")
or row.get("audio")
or row.get("path")
or ""
).strip()
if not raw_file:
continue
raw_file = raw_file.replace("\\", "/")
basename = Path(raw_file).name
candidate = metadata_path.parent / raw_file
try:
relative = candidate.resolve().relative_to(local_dir.resolve()).as_posix()
except Exception:
relative = raw_file
cleaned_row = {
str(k).strip(): str(v).strip()
for k, v in row.items()
if k is not None and v is not None
}
result[relative] = cleaned_row
result[basename] = cleaned_row
except Exception:
continue
return result
def metadata_for_audio(
audio_path: Path,
local_dir: Path,
metadata_maps: dict[str, dict[str, str]],
) -> dict[str, str]:
rel = audio_path.relative_to(local_dir).as_posix()
name = audio_path.name
return metadata_maps.get(rel) or metadata_maps.get(name) or {}
def value_from_meta(
meta: dict[str, str],
*keys: str,
default: str = "",
) -> str:
for key in keys:
value = meta.get(key)
if value:
return value
return default
def build_schema() -> pa.Schema:
audio_type = pa.struct(
[
pa.field("bytes", pa.binary()),
pa.field("path", pa.string()),
]
)
hf_meta = {
"info": {
"features": {
"id": {"_type": "Value", "dtype": "string"},
"audio": {"_type": "Audio"},
"title": {"_type": "Value", "dtype": "string"},
"language": {"_type": "Value", "dtype": "string"},
"file_name": {"_type": "Value", "dtype": "string"},
"filename": {"_type": "Value", "dtype": "string"},
"Author": {"_type": "Value", "dtype": "string"},
"Title": {"_type": "Value", "dtype": "string"},
"Narrator": {"_type": "Value", "dtype": "string"},
"Source Group": {"_type": "Value", "dtype": "string"},
"original_file_name": {"_type": "Value", "dtype": "string"},
"original_extension": {"_type": "Value", "dtype": "string"},
"file_size_bytes": {"_type": "Value", "dtype": "int64"},
}
}
}
return pa.schema(
[
pa.field("id", pa.string()),
pa.field("audio", audio_type),
pa.field("title", pa.string()),
pa.field("language", pa.string()),
pa.field("file_name", pa.string()),
pa.field("filename", pa.string()),
pa.field("Author", pa.string()),
pa.field("Title", pa.string()),
pa.field("Narrator", pa.string()),
pa.field("Source Group", pa.string()),
pa.field("original_file_name", pa.string()),
pa.field("original_extension", pa.string()),
pa.field("file_size_bytes", pa.int64()),
],
metadata={
b"huggingface": json.dumps(hf_meta, ensure_ascii=False).encode("utf-8")
},
)
def rows_to_table(rows: list[dict[str, Any]]) -> pa.Table:
schema = build_schema()
audio_type = schema.field("audio").type
return pa.table(
{
"id": pa.array([r["id"] for r in rows], type=pa.string()),
"audio": pa.array(
[
{
"bytes": r["audio"]["bytes"],
"path": r["audio"]["path"],
}
for r in rows
],
type=audio_type,
),
"title": pa.array([r["title"] for r in rows], type=pa.string()),
"language": pa.array([r["language"] for r in rows], type=pa.string()),
"file_name": pa.array([r["file_name"] for r in rows], type=pa.string()),
"filename": pa.array([r["filename"] for r in rows], type=pa.string()),
"Author": pa.array([r["Author"] for r in rows], type=pa.string()),
"Title": pa.array([r["Title"] for r in rows], type=pa.string()),
"Narrator": pa.array([r["Narrator"] for r in rows], type=pa.string()),
"Source Group": pa.array([r["Source Group"] for r in rows], type=pa.string()),
"original_file_name": pa.array(
[r["original_file_name"] for r in rows],
type=pa.string(),
),
"original_extension": pa.array(
[r["original_extension"] for r in rows],
type=pa.string(),
),
"file_size_bytes": pa.array(
[r["file_size_bytes"] for r in rows],
type=pa.int64(),
),
},
schema=schema,
)
def write_parquet_shard(rows: list[dict[str, Any]], path: Path) -> None:
table = rows_to_table(rows)
pq.write_table(
table,
path,
row_group_size=1,
compression="snappy",
)
def make_dataset_card(
source_repo: str,
title: str,
language: str,
row_count: int,
shard_count: int,
) -> str:
return f"""---
configs:
- config_name: default
data_files:
- split: train
path: data/train/*.parquet
---
# {title}
This is a Hugging Face Parquet input dataset for an audio pipeline.
Source dataset: `{source_repo}`
## Format
- config: `default`
- split: `train`
- format: `parquet`
- id column: `id`
- audio column: `audio`
- rows: `{row_count}`
- shards: `{shard_count}`
- language: `{language}`
The `audio` column is embedded into Parquet as Hugging Face `Audio`:
```python
audio = {{
"path": "file.mp3",
"bytes": b"..."
}}
```
## Columns
- `id`
- `audio`
- `title`
- `language`
- `file_name`
- `filename`
- `Author`
- `Title`
- `Narrator`
- `Source Group`
- `original_file_name`
- `original_extension`
- `file_size_bytes`
"""
def build_row(
index: int,
audio_path: Path,
local_dir: Path,
metadata_maps: dict[str, dict[str, str]],
default_title: str,
default_author: str,
default_narrator: str,
default_source_group: str,
default_language: str,
) -> dict[str, Any]:
meta = metadata_for_audio(audio_path, local_dir, metadata_maps)
rel_path = audio_path.relative_to(local_dir).as_posix()
file_name = audio_path.name
stem = audio_path.stem
extension = audio_path.suffix.lower().lstrip(".")
file_bytes = audio_path.read_bytes()
row_id = value_from_meta(
meta,
"id",
"ID",
default=f"{index:05d}_{slugify(stem)}",
)
title = (
value_from_meta(meta, "title", "Title", default="")
or default_title
or stem
)
language = (
value_from_meta(meta, "language", "Language", "lang", default="")
or default_language
or "be"
)
author = (
value_from_meta(meta, "Author", "author", default="")
or default_author
)
narrator = (
value_from_meta(meta, "Narrator", "narrator", default="")
or default_narrator
)
source_group = (
value_from_meta(meta, "Source Group", "source_group", "source", default="")
or default_source_group
)
metadata_file_name = value_from_meta(
meta,
"file_name",
"filename",
default=file_name,
)
return {
"id": str(row_id),
"audio": {
"path": rel_path,
"bytes": file_bytes,
},
"title": str(title),
"language": str(language),
"file_name": str(metadata_file_name),
"filename": str(metadata_file_name),
"Author": str(author),
"Title": str(title),
"Narrator": str(narrator),
"Source Group": str(source_group),
"original_file_name": str(file_name),
"original_extension": str(extension),
"file_size_bytes": int(len(file_bytes)),
}
def push_to_hub(
output_repo: str,
parquet_paths: list[Path],
readme_path: Path,
token: str,
private: bool,
overwrite_train: bool,
) -> None:
api = HfApi(token=token)
api.create_repo(
repo_id=output_repo,
repo_type="dataset",
exist_ok=True,
private=private,
)
operations = []
if overwrite_train:
try:
existing_files = api.list_repo_files(
repo_id=output_repo,
repo_type="dataset",
)
for path in existing_files:
if path.startswith("data/train/") and path.endswith(".parquet"):
operations.append(
CommitOperationDelete(path_in_repo=path)
)
except Exception:
pass
shard_count = len(parquet_paths)
for i, path in enumerate(parquet_paths):
path_in_repo = f"data/train/train-{i:05d}-of-{shard_count:05d}.parquet"
operations.append(
CommitOperationAdd(
path_in_repo=path_in_repo,
path_or_fileobj=str(path),
)
)
operations.append(
CommitOperationAdd(
path_in_repo="README.md",
path_or_fileobj=str(readme_path),
)
)
api.create_commit(
repo_id=output_repo,
repo_type="dataset",
operations=operations,
commit_message=f"Add train parquet input dataset: {shard_count} shard(s)",
)
def convert_dataset(
source_repo: str,
output_repo: str,
title: str,
author: str,
narrator: str,
source_group: str,
language: str,
allow_patterns_text: str,
hf_token: str,
private: bool,
overwrite_train: bool,
shard_target_mb: int,
):
logs: list[str] = []
def add_log(message: str) -> str:
logs.append(message)
return "\n".join(logs)
try:
token = get_token(hf_token)
source_repo = normalize_repo_id(source_repo)
output_repo = normalize_repo_id(output_repo)
if not source_repo:
raise gr.Error(
"Укажы зыходны dataset repo, напрыклад "
"`archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich`."
)
if not output_repo:
raise gr.Error(
"Укажы output dataset repo, напрыклад "
"`archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich-input`."
)
shard_target_bytes = int(shard_target_mb) * 1024 * 1024
hard_shard_limit_bytes = HARD_SHARD_LIMIT_MB * 1024 * 1024
allow_patterns = [
p.strip()
for p in allow_patterns_text.replace(",", "\n").splitlines()
if p.strip()
]
if not allow_patterns:
allow_patterns = DEFAULT_ALLOW_PATTERNS.copy()
yield add_log(f"Source repo: {source_repo}")
yield add_log(f"Output repo: {output_repo}")
yield add_log("Downloading source dataset files...")
yield add_log(f"Allow patterns: {allow_patterns}")
local_dir = Path(
snapshot_download(
repo_id=source_repo,
repo_type="dataset",
allow_patterns=allow_patterns,
token=token,
)
)
yield add_log(f"Downloaded to local cache: {local_dir}")
metadata_maps = load_metadata_maps(local_dir)
yield add_log(f"metadata.csv rows detected: {len(metadata_maps)} lookup keys")
audio_files = discover_audio_files(local_dir)
if not audio_files:
raise gr.Error(
"Аўдыяфайлы не знойдзены. Правер allow patterns, напрыклад `part_*/*.mp3` або `**/*.mp3`."
)
yield add_log(f"Audio files detected: {len(audio_files)}")
too_large = [
p for p in audio_files
if p.stat().st_size > hard_shard_limit_bytes
]
if too_large:
examples = "\n".join(
f"- {p.relative_to(local_dir).as_posix()}: {p.stat().st_size / 1024 / 1024:.1f} MB"
for p in too_large[:10]
)
raise gr.Error(
"Ёсць асобныя аўдыяфайлы большыя за hard limit shard-а. "
"Іх трэба спачатку парэзаць на меншыя часткі.\n\n"
f"{examples}"
)
total_rows = 0
parquet_paths: list[Path] = []
with tempfile.TemporaryDirectory() as tmp:
tmp_dir = Path(tmp)
current_rows: list[dict[str, Any]] = []
current_bytes = 0
for index, audio_path in enumerate(audio_files, start=1):
file_size = audio_path.stat().st_size
if current_rows and current_bytes + file_size > shard_target_bytes:
shard_path = tmp_dir / f"shard-{len(parquet_paths):05d}.parquet"
write_parquet_shard(current_rows, shard_path)
shard_size = shard_path.stat().st_size
if shard_size > hard_shard_limit_bytes:
raise gr.Error(
f"Shard занадта вялікі: {shard_size / 1024 / 1024:.1f} MB. "
"Паменшы shard target MB або парэж аўдыя на меншыя файлы."
)
parquet_paths.append(shard_path)
yield add_log(
f"Wrote shard {len(parquet_paths)}: "
f"{shard_size / 1024 / 1024:.1f} MB, "
f"{len(current_rows)} rows"
)
current_rows = []
current_bytes = 0
row = build_row(
index=index,
audio_path=audio_path,
local_dir=local_dir,
metadata_maps=metadata_maps,
default_title=title.strip(),
default_author=author.strip(),
default_narrator=narrator.strip(),
default_source_group=source_group.strip(),
default_language=language.strip() or "be",
)
current_rows.append(row)
current_bytes += file_size
total_rows += 1
if current_rows:
shard_path = tmp_dir / f"shard-{len(parquet_paths):05d}.parquet"
write_parquet_shard(current_rows, shard_path)
shard_size = shard_path.stat().st_size
if shard_size > hard_shard_limit_bytes:
raise gr.Error(
f"Last shard занадта вялікі: {shard_size / 1024 / 1024:.1f} MB. "
"Паменшы shard target MB або парэж аўдыя на меншыя файлы."
)
parquet_paths.append(shard_path)
yield add_log(
f"Wrote shard {len(parquet_paths)}: "
f"{shard_size / 1024 / 1024:.1f} MB, "
f"{len(current_rows)} rows"
)
readme_path = tmp_dir / "README.md"
readme_path.write_text(
make_dataset_card(
source_repo=source_repo,
title=title.strip() or output_repo,
language=language.strip() or "be",
row_count=total_rows,
shard_count=len(parquet_paths),
),
encoding="utf-8",
)
yield add_log("Pushing Parquet dataset to Hub...")
push_to_hub(
output_repo=output_repo,
parquet_paths=parquet_paths,
readme_path=readme_path,
token=token,
private=private,
overwrite_train=overwrite_train,
)
yield add_log("")
yield add_log("DONE")
yield add_log(f"Rows: {total_rows}")
yield add_log(f"Shards: {len(parquet_paths)}")
yield add_log(f"Dataset: https://huggingface.co/datasets/{output_repo}")
except gr.Error:
raise
except Exception as exc:
raise gr.Error(str(exc))
# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------
with gr.Blocks(title="Audio Dataset to HF Parquet Input") as demo:
gr.Markdown(
"""
# Audio Dataset → Hugging Face Parquet Input
Гэты Space чытае аўдыя з зыходнага Hugging Face dataset repo і стварае новы dataset у Parquet-фармаце.
1. Устаў `Source dataset repo або URL`.
2. Націсні `Запоўніць з metadata dataset-а`.
3. Правер палі і націсні `Convert and push`.
Выхадны фармат:
```text
config: default
split: train
format: parquet
id column: id
audio column: audio
path: data/train/train-xxxxx-of-yyyyy.parquet
```
Калонка `audio` захоўваецца як Hugging Face `Audio` з убудаванымі bytes.
"""
)
with gr.Row():
source_repo = gr.Textbox(
label="Source dataset repo або URL",
value="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich",
placeholder="https://huggingface.co/datasets/owner/name або owner/name",
)
output_repo = gr.Textbox(
label="Output dataset repo",
value="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich-input",
placeholder="owner/name-input",
)
with gr.Row():
title = gr.Textbox(
label="Title",
value="Подых навальніцы",
)
author = gr.Textbox(
label="Author",
value="Іван Мележ",
)
narrator = gr.Textbox(
label="Narrator",
value="Валер Будзевіч",
)
with gr.Row():
source_group = gr.Textbox(
label="Source Group",
value="Аўдыёкнігі",
)
language = gr.Textbox(
label="Language",
value="be",
)
allow_patterns_text = gr.Textbox(
label="Allow patterns для зыходнага dataset",
value="part_*/*.mp3\n**/*.mp3\n**/metadata.csv\nmetadata.csv",
lines=5,
)
with gr.Row():
hf_token = gr.Textbox(
label="HF token override, optional",
type="password",
placeholder="Лепш дадаць HF_TOKEN у Space Settings → Secrets",
)
shard_target_mb = gr.Number(
label="Shard target MB",
value=DEFAULT_SHARD_TARGET_MB,
precision=0,
)
with gr.Row():
private = gr.Checkbox(
label="Create output dataset as private",
value=True,
)
overwrite_train = gr.Checkbox(
label="Delete old data/train/*.parquet before push",
value=True,
)
with gr.Row():
fill_from_metadata_button = gr.Button(
"Запоўніць з metadata dataset-а",
variant="secondary",
)
run_button = gr.Button(
"Convert and push",
variant="primary",
)
log_output = gr.Textbox(
label="Log",
lines=25,
max_lines=60,
)
fill_from_metadata_button.click(
fn=prefill_from_dataset_metadata,
inputs=[
source_repo,
hf_token,
],
outputs=[
source_repo,
output_repo,
title,
author,
narrator,
source_group,
language,
allow_patterns_text,
log_output,
],
)
run_button.click(
fn=convert_dataset,
inputs=[
source_repo,
output_repo,
title,
author,
narrator,
source_group,
language,
allow_patterns_text,
hf_token,
private,
overwrite_train,
shard_target_mb,
],
outputs=log_output,
)
if __name__ == "__main__":
demo.launch()