archivartaunik commited on
Commit
da23c2e
·
verified ·
1 Parent(s): 7f1d02d

Upload app(1).py

Browse files
Files changed (1) hide show
  1. app(1).py +801 -0
app(1).py ADDED
@@ -0,0 +1,801 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ import json
5
+ import os
6
+ import re
7
+ import tempfile
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import gradio as gr
12
+ import pyarrow as pa
13
+ import pyarrow.parquet as pq
14
+ from huggingface_hub import (
15
+ CommitOperationAdd,
16
+ CommitOperationDelete,
17
+ HfApi,
18
+ snapshot_download,
19
+ )
20
+
21
+
22
+ AUDIO_SUFFIXES = {
23
+ ".mp3",
24
+ ".wav",
25
+ ".ogg",
26
+ ".opus",
27
+ ".flac",
28
+ ".m4a",
29
+ ".aac",
30
+ }
31
+
32
+ DEFAULT_SHARD_TARGET_MB = 230
33
+ HARD_SHARD_LIMIT_MB = 260
34
+
35
+
36
+ def normalize_repo_id(value: str) -> str:
37
+ value = value.strip().rstrip("/")
38
+
39
+ if "huggingface.co/datasets/" in value:
40
+ value = value.split("huggingface.co/datasets/", 1)[1]
41
+
42
+ value = value.split("/tree/", 1)[0]
43
+ value = value.split("/blob/", 1)[0]
44
+ value = value.split("?", 1)[0]
45
+ value = value.strip("/")
46
+
47
+ return value
48
+
49
+
50
+ def slugify(text: str, max_len: int = 80) -> str:
51
+ text = text.lower()
52
+ text = re.sub(r"[^\w\s.-]", "", text, flags=re.UNICODE)
53
+ text = re.sub(r"[\s_-]+", "_", text)
54
+ return text.strip("_.-")[:max_len] or "audio"
55
+
56
+
57
+ def get_token(token_from_ui: str | None) -> str:
58
+ token = (token_from_ui or "").strip() or os.environ.get("HF_TOKEN", "").strip()
59
+
60
+ if not token:
61
+ raise gr.Error(
62
+ "HF token не знойдзены. Дадай HF_TOKEN у Space Settings → Secrets "
63
+ "або ўвядзі token у поле HF token override."
64
+ )
65
+
66
+ return token
67
+
68
+
69
+ def discover_audio_files(local_dir: Path) -> list[Path]:
70
+ files = []
71
+
72
+ for path in local_dir.rglob("*"):
73
+ if path.is_file() and path.suffix.lower() in AUDIO_SUFFIXES:
74
+ files.append(path)
75
+
76
+ return sorted(files, key=lambda p: p.as_posix())
77
+
78
+
79
+ def load_metadata_maps(local_dir: Path) -> dict[str, dict[str, str]]:
80
+ """
81
+ Reads metadata.csv files if they exist.
82
+
83
+ Supports:
84
+ file_name
85
+ filename
86
+ audio
87
+ path
88
+
89
+ Stores metadata by:
90
+ relative path
91
+ basename
92
+ """
93
+ result: dict[str, dict[str, str]] = {}
94
+
95
+ for metadata_path in local_dir.rglob("metadata.csv"):
96
+ try:
97
+ with metadata_path.open("r", encoding="utf-8-sig", newline="") as f:
98
+ reader = csv.DictReader(f)
99
+
100
+ for row in reader:
101
+ raw_file = (
102
+ row.get("file_name")
103
+ or row.get("filename")
104
+ or row.get("audio")
105
+ or row.get("path")
106
+ or ""
107
+ ).strip()
108
+
109
+ if not raw_file:
110
+ continue
111
+
112
+ raw_file = raw_file.replace("\\", "/")
113
+ basename = Path(raw_file).name
114
+
115
+ candidate = metadata_path.parent / raw_file
116
+
117
+ try:
118
+ relative = candidate.resolve().relative_to(local_dir.resolve()).as_posix()
119
+ except Exception:
120
+ relative = raw_file
121
+
122
+ cleaned_row = {
123
+ str(k).strip(): str(v).strip()
124
+ for k, v in row.items()
125
+ if k is not None and v is not None
126
+ }
127
+
128
+ result[relative] = cleaned_row
129
+ result[basename] = cleaned_row
130
+
131
+ except Exception:
132
+ continue
133
+
134
+ return result
135
+
136
+
137
+ def metadata_for_audio(
138
+ audio_path: Path,
139
+ local_dir: Path,
140
+ metadata_maps: dict[str, dict[str, str]],
141
+ ) -> dict[str, str]:
142
+ rel = audio_path.relative_to(local_dir).as_posix()
143
+ name = audio_path.name
144
+
145
+ return metadata_maps.get(rel) or metadata_maps.get(name) or {}
146
+
147
+
148
+ def value_from_meta(
149
+ meta: dict[str, str],
150
+ *keys: str,
151
+ default: str = "",
152
+ ) -> str:
153
+ for key in keys:
154
+ value = meta.get(key)
155
+ if value:
156
+ return value
157
+ return default
158
+
159
+
160
+ def build_schema() -> pa.Schema:
161
+ audio_type = pa.struct(
162
+ [
163
+ pa.field("bytes", pa.binary()),
164
+ pa.field("path", pa.string()),
165
+ ]
166
+ )
167
+
168
+ hf_meta = {
169
+ "info": {
170
+ "features": {
171
+ "id": {"_type": "Value", "dtype": "string"},
172
+ "audio": {"_type": "Audio"},
173
+ "title": {"_type": "Value", "dtype": "string"},
174
+ "language": {"_type": "Value", "dtype": "string"},
175
+ "file_name": {"_type": "Value", "dtype": "string"},
176
+ "filename": {"_type": "Value", "dtype": "string"},
177
+ "Author": {"_type": "Value", "dtype": "string"},
178
+ "Title": {"_type": "Value", "dtype": "string"},
179
+ "Narrator": {"_type": "Value", "dtype": "string"},
180
+ "Source Group": {"_type": "Value", "dtype": "string"},
181
+ "original_file_name": {"_type": "Value", "dtype": "string"},
182
+ "original_extension": {"_type": "Value", "dtype": "string"},
183
+ "file_size_bytes": {"_type": "Value", "dtype": "int64"},
184
+ }
185
+ }
186
+ }
187
+
188
+ return pa.schema(
189
+ [
190
+ pa.field("id", pa.string()),
191
+ pa.field("audio", audio_type),
192
+ pa.field("title", pa.string()),
193
+ pa.field("language", pa.string()),
194
+ pa.field("file_name", pa.string()),
195
+ pa.field("filename", pa.string()),
196
+ pa.field("Author", pa.string()),
197
+ pa.field("Title", pa.string()),
198
+ pa.field("Narrator", pa.string()),
199
+ pa.field("Source Group", pa.string()),
200
+ pa.field("original_file_name", pa.string()),
201
+ pa.field("original_extension", pa.string()),
202
+ pa.field("file_size_bytes", pa.int64()),
203
+ ],
204
+ metadata={
205
+ b"huggingface": json.dumps(hf_meta, ensure_ascii=False).encode("utf-8")
206
+ },
207
+ )
208
+
209
+
210
+ def rows_to_table(rows: list[dict[str, Any]]) -> pa.Table:
211
+ schema = build_schema()
212
+ audio_type = schema.field("audio").type
213
+
214
+ return pa.table(
215
+ {
216
+ "id": pa.array([r["id"] for r in rows], type=pa.string()),
217
+ "audio": pa.array(
218
+ [
219
+ {
220
+ "bytes": r["audio"]["bytes"],
221
+ "path": r["audio"]["path"],
222
+ }
223
+ for r in rows
224
+ ],
225
+ type=audio_type,
226
+ ),
227
+ "title": pa.array([r["title"] for r in rows], type=pa.string()),
228
+ "language": pa.array([r["language"] for r in rows], type=pa.string()),
229
+ "file_name": pa.array([r["file_name"] for r in rows], type=pa.string()),
230
+ "filename": pa.array([r["filename"] for r in rows], type=pa.string()),
231
+ "Author": pa.array([r["Author"] for r in rows], type=pa.string()),
232
+ "Title": pa.array([r["Title"] for r in rows], type=pa.string()),
233
+ "Narrator": pa.array([r["Narrator"] for r in rows], type=pa.string()),
234
+ "Source Group": pa.array([r["Source Group"] for r in rows], type=pa.string()),
235
+ "original_file_name": pa.array(
236
+ [r["original_file_name"] for r in rows],
237
+ type=pa.string(),
238
+ ),
239
+ "original_extension": pa.array(
240
+ [r["original_extension"] for r in rows],
241
+ type=pa.string(),
242
+ ),
243
+ "file_size_bytes": pa.array(
244
+ [r["file_size_bytes"] for r in rows],
245
+ type=pa.int64(),
246
+ ),
247
+ },
248
+ schema=schema,
249
+ )
250
+
251
+
252
+ def write_parquet_shard(rows: list[dict[str, Any]], path: Path) -> None:
253
+ table = rows_to_table(rows)
254
+
255
+ pq.write_table(
256
+ table,
257
+ path,
258
+ row_group_size=1,
259
+ compression="snappy",
260
+ )
261
+
262
+
263
+ def make_dataset_card(
264
+ source_repo: str,
265
+ title: str,
266
+ language: str,
267
+ row_count: int,
268
+ shard_count: int,
269
+ ) -> str:
270
+ return f"""---
271
+ configs:
272
+ - config_name: default
273
+ data_files:
274
+ - split: train
275
+ path: data/train/*.parquet
276
+ ---
277
+
278
+ # {title}
279
+
280
+ This is a Hugging Face Parquet input dataset for an audio pipeline.
281
+
282
+ Source dataset: `{source_repo}`
283
+
284
+ ## Format
285
+
286
+ - config: `default`
287
+ - split: `train`
288
+ - format: `parquet`
289
+ - id column: `id`
290
+ - audio column: `audio`
291
+ - rows: `{row_count}`
292
+ - shards: `{shard_count}`
293
+ - language: `{language}`
294
+
295
+ The `audio` column is embedded into Parquet as Hugging Face `Audio`:
296
+
297
+ ```python
298
+ audio = {{
299
+ "path": "file.mp3",
300
+ "bytes": b"..."
301
+ }}
302
+ ```
303
+
304
+ ## Columns
305
+
306
+ - `id`
307
+ - `audio`
308
+ - `title`
309
+ - `language`
310
+ - `file_name`
311
+ - `filename`
312
+ - `Author`
313
+ - `Title`
314
+ - `Narrator`
315
+ - `Source Group`
316
+ - `original_file_name`
317
+ - `original_extension`
318
+ - `file_size_bytes`
319
+ """
320
+
321
+
322
+ def build_row(
323
+ index: int,
324
+ audio_path: Path,
325
+ local_dir: Path,
326
+ metadata_maps: dict[str, dict[str, str]],
327
+ default_title: str,
328
+ default_author: str,
329
+ default_narrator: str,
330
+ default_source_group: str,
331
+ default_language: str,
332
+ ) -> dict[str, Any]:
333
+ meta = metadata_for_audio(audio_path, local_dir, metadata_maps)
334
+
335
+ rel_path = audio_path.relative_to(local_dir).as_posix()
336
+ file_name = audio_path.name
337
+ stem = audio_path.stem
338
+ extension = audio_path.suffix.lower().lstrip(".")
339
+ file_bytes = audio_path.read_bytes()
340
+
341
+ row_id = value_from_meta(
342
+ meta,
343
+ "id",
344
+ "ID",
345
+ default=f"{index:05d}_{slugify(stem)}",
346
+ )
347
+
348
+ title = (
349
+ value_from_meta(meta, "title", "Title", default="")
350
+ or default_title
351
+ or stem
352
+ )
353
+
354
+ language = (
355
+ value_from_meta(meta, "language", "Language", "lang", default="")
356
+ or default_language
357
+ or "be"
358
+ )
359
+
360
+ author = (
361
+ value_from_meta(meta, "Author", "author", default="")
362
+ or default_author
363
+ )
364
+
365
+ narrator = (
366
+ value_from_meta(meta, "Narrator", "narrator", default="")
367
+ or default_narrator
368
+ )
369
+
370
+ source_group = (
371
+ value_from_meta(meta, "Source Group", "source_group", "source", default="")
372
+ or default_source_group
373
+ )
374
+
375
+ metadata_file_name = value_from_meta(
376
+ meta,
377
+ "file_name",
378
+ "filename",
379
+ default=file_name,
380
+ )
381
+
382
+ return {
383
+ "id": str(row_id),
384
+ "audio": {
385
+ "path": rel_path,
386
+ "bytes": file_bytes,
387
+ },
388
+ "title": str(title),
389
+ "language": str(language),
390
+ "file_name": str(metadata_file_name),
391
+ "filename": str(metadata_file_name),
392
+ "Author": str(author),
393
+ "Title": str(title),
394
+ "Narrator": str(narrator),
395
+ "Source Group": str(source_group),
396
+ "original_file_name": str(file_name),
397
+ "original_extension": str(extension),
398
+ "file_size_bytes": int(len(file_bytes)),
399
+ }
400
+
401
+
402
+ def push_to_hub(
403
+ output_repo: str,
404
+ parquet_paths: list[Path],
405
+ readme_path: Path,
406
+ token: str,
407
+ private: bool,
408
+ overwrite_train: bool,
409
+ ) -> None:
410
+ api = HfApi(token=token)
411
+
412
+ api.create_repo(
413
+ repo_id=output_repo,
414
+ repo_type="dataset",
415
+ exist_ok=True,
416
+ private=private,
417
+ )
418
+
419
+ operations = []
420
+
421
+ if overwrite_train:
422
+ try:
423
+ existing_files = api.list_repo_files(
424
+ repo_id=output_repo,
425
+ repo_type="dataset",
426
+ )
427
+
428
+ for path in existing_files:
429
+ if path.startswith("data/train/") and path.endswith(".parquet"):
430
+ operations.append(
431
+ CommitOperationDelete(path_in_repo=path)
432
+ )
433
+ except Exception:
434
+ pass
435
+
436
+ shard_count = len(parquet_paths)
437
+
438
+ for i, path in enumerate(parquet_paths):
439
+ path_in_repo = f"data/train/train-{i:05d}-of-{shard_count:05d}.parquet"
440
+
441
+ operations.append(
442
+ CommitOperationAdd(
443
+ path_in_repo=path_in_repo,
444
+ path_or_fileobj=str(path),
445
+ )
446
+ )
447
+
448
+ operations.append(
449
+ CommitOperationAdd(
450
+ path_in_repo="README.md",
451
+ path_or_fileobj=str(readme_path),
452
+ )
453
+ )
454
+
455
+ api.create_commit(
456
+ repo_id=output_repo,
457
+ repo_type="dataset",
458
+ operations=operations,
459
+ commit_message=f"Add train parquet input dataset: {shard_count} shard(s)",
460
+ )
461
+
462
+
463
+ def convert_dataset(
464
+ source_repo: str,
465
+ output_repo: str,
466
+ title: str,
467
+ author: str,
468
+ narrator: str,
469
+ source_group: str,
470
+ language: str,
471
+ allow_patterns_text: str,
472
+ hf_token: str,
473
+ private: bool,
474
+ overwrite_train: bool,
475
+ shard_target_mb: int,
476
+ ):
477
+ logs: list[str] = []
478
+
479
+ def add_log(message: str) -> str:
480
+ logs.append(message)
481
+ return "\n".join(logs)
482
+
483
+ try:
484
+ token = get_token(hf_token)
485
+
486
+ source_repo = normalize_repo_id(source_repo)
487
+ output_repo = normalize_repo_id(output_repo)
488
+
489
+ if not source_repo:
490
+ raise gr.Error(
491
+ "Укажы зыходны dataset repo, напрыклад "
492
+ "`archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich`."
493
+ )
494
+
495
+ if not output_repo:
496
+ raise gr.Error(
497
+ "Укажы output dataset repo, напрыклад "
498
+ "`archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich-input`."
499
+ )
500
+
501
+ shard_target_bytes = int(shard_target_mb) * 1024 * 1024
502
+ hard_shard_limit_bytes = HARD_SHARD_LIMIT_MB * 1024 * 1024
503
+
504
+ allow_patterns = [
505
+ p.strip()
506
+ for p in allow_patterns_text.replace(",", "\n").splitlines()
507
+ if p.strip()
508
+ ]
509
+
510
+ if not allow_patterns:
511
+ allow_patterns = [
512
+ "**/*.mp3",
513
+ "**/*.MP3",
514
+ "**/*.wav",
515
+ "**/*.WAV",
516
+ "**/*.ogg",
517
+ "**/*.OGG",
518
+ "**/*.opus",
519
+ "**/*.flac",
520
+ "**/*.m4a",
521
+ "**/*.aac",
522
+ "**/metadata.csv",
523
+ "metadata.csv",
524
+ ]
525
+
526
+ yield add_log(f"Source repo: {source_repo}")
527
+ yield add_log(f"Output repo: {output_repo}")
528
+ yield add_log("Downloading source dataset files...")
529
+ yield add_log(f"Allow patterns: {allow_patterns}")
530
+
531
+ local_dir = Path(
532
+ snapshot_download(
533
+ repo_id=source_repo,
534
+ repo_type="dataset",
535
+ allow_patterns=allow_patterns,
536
+ token=token,
537
+ )
538
+ )
539
+
540
+ yield add_log(f"Downloaded to local cache: {local_dir}")
541
+
542
+ metadata_maps = load_metadata_maps(local_dir)
543
+ yield add_log(f"metadata.csv rows detected: {len(metadata_maps)} lookup keys")
544
+
545
+ audio_files = discover_audio_files(local_dir)
546
+
547
+ if not audio_files:
548
+ raise gr.Error(
549
+ "Аўдыяфайлы не знойдзены. Правер allow patterns, напрыклад `part_*/*.mp3` або `**/*.mp3`."
550
+ )
551
+
552
+ yield add_log(f"Audio files detected: {len(audio_files)}")
553
+
554
+ too_large = [
555
+ p for p in audio_files
556
+ if p.stat().st_size > hard_shard_limit_bytes
557
+ ]
558
+
559
+ if too_large:
560
+ examples = "\n".join(
561
+ f"- {p.relative_to(local_dir).as_posix()}: {p.stat().st_size / 1024 / 1024:.1f} MB"
562
+ for p in too_large[:10]
563
+ )
564
+
565
+ raise gr.Error(
566
+ "Ёсць асобныя аўдыяфайлы большыя за hard limit shard-а. "
567
+ "Іх трэба спачатку парэзаць на меншыя часткі.\n\n"
568
+ f"{examples}"
569
+ )
570
+
571
+ total_rows = 0
572
+ parquet_paths: list[Path] = []
573
+
574
+ with tempfile.TemporaryDirectory() as tmp:
575
+ tmp_dir = Path(tmp)
576
+
577
+ current_rows: list[dict[str, Any]] = []
578
+ current_bytes = 0
579
+
580
+ for index, audio_path in enumerate(audio_files, start=1):
581
+ file_size = audio_path.stat().st_size
582
+
583
+ if current_rows and current_bytes + file_size > shard_target_bytes:
584
+ shard_path = tmp_dir / f"shard-{len(parquet_paths):05d}.parquet"
585
+ write_parquet_shard(current_rows, shard_path)
586
+
587
+ shard_size = shard_path.stat().st_size
588
+
589
+ if shard_size > hard_shard_limit_bytes:
590
+ raise gr.Error(
591
+ f"Shard занадта вялікі: {shard_size / 1024 / 1024:.1f} MB. "
592
+ "Паменшы shard target MB або парэж аўдыя на меншыя файлы."
593
+ )
594
+
595
+ parquet_paths.append(shard_path)
596
+
597
+ yield add_log(
598
+ f"Wrote shard {len(parquet_paths)}: "
599
+ f"{shard_size / 1024 / 1024:.1f} MB, "
600
+ f"{len(current_rows)} rows"
601
+ )
602
+
603
+ current_rows = []
604
+ current_bytes = 0
605
+
606
+ row = build_row(
607
+ index=index,
608
+ audio_path=audio_path,
609
+ local_dir=local_dir,
610
+ metadata_maps=metadata_maps,
611
+ default_title=title.strip(),
612
+ default_author=author.strip(),
613
+ default_narrator=narrator.strip(),
614
+ default_source_group=source_group.strip(),
615
+ default_language=language.strip() or "be",
616
+ )
617
+
618
+ current_rows.append(row)
619
+ current_bytes += file_size
620
+ total_rows += 1
621
+
622
+ if current_rows:
623
+ shard_path = tmp_dir / f"shard-{len(parquet_paths):05d}.parquet"
624
+ write_parquet_shard(current_rows, shard_path)
625
+
626
+ shard_size = shard_path.stat().st_size
627
+
628
+ if shard_size > hard_shard_limit_bytes:
629
+ raise gr.Error(
630
+ f"Last shard занадта вялікі: {shard_size / 1024 / 1024:.1f} MB. "
631
+ "Паменшы shard target MB або парэж аўдыя на меншыя файлы."
632
+ )
633
+
634
+ parquet_paths.append(shard_path)
635
+
636
+ yield add_log(
637
+ f"Wrote shard {len(parquet_paths)}: "
638
+ f"{shard_size / 1024 / 1024:.1f} MB, "
639
+ f"{len(current_rows)} rows"
640
+ )
641
+
642
+ readme_path = tmp_dir / "README.md"
643
+ readme_path.write_text(
644
+ make_dataset_card(
645
+ source_repo=source_repo,
646
+ title=title.strip() or output_repo,
647
+ language=language.strip() or "be",
648
+ row_count=total_rows,
649
+ shard_count=len(parquet_paths),
650
+ ),
651
+ encoding="utf-8",
652
+ )
653
+
654
+ yield add_log("Pushing Parquet dataset to Hub...")
655
+
656
+ push_to_hub(
657
+ output_repo=output_repo,
658
+ parquet_paths=parquet_paths,
659
+ readme_path=readme_path,
660
+ token=token,
661
+ private=private,
662
+ overwrite_train=overwrite_train,
663
+ )
664
+
665
+ yield add_log("")
666
+ yield add_log("DONE")
667
+ yield add_log(f"Rows: {total_rows}")
668
+ yield add_log(f"Shards: {len(parquet_paths)}")
669
+ yield add_log(f"Dataset: https://huggingface.co/datasets/{output_repo}")
670
+
671
+ except gr.Error:
672
+ raise
673
+ except Exception as exc:
674
+ raise gr.Error(str(exc))
675
+
676
+
677
+ with gr.Blocks(title="Audio Dataset to HF Parquet Input") as demo:
678
+ gr.Markdown(
679
+ """
680
+ # Audio Dataset → Hugging Face Parquet Input
681
+
682
+ Гэты Space чытае аўдыя з зыходнага Hugging Face dataset repo і стварае новы dataset у Parquet-фармаце.
683
+
684
+ Выхадны фармат:
685
+
686
+ ```text
687
+ config: default
688
+ split: train
689
+ format: parquet
690
+ id column: id
691
+ audio column: audio
692
+ path: data/train/train-xxxxx-of-yyyyy.parquet
693
+ ```
694
+
695
+ Калонка `audio` захоўваецца як Hugging Face `Audio` з убудаванымі bytes.
696
+ """
697
+ )
698
+
699
+ with gr.Row():
700
+ source_repo = gr.Textbox(
701
+ label="Source dataset repo або URL",
702
+ value="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich",
703
+ placeholder="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich",
704
+ )
705
+
706
+ output_repo = gr.Textbox(
707
+ label="Output dataset repo",
708
+ value="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich-input",
709
+ placeholder="archivartaunik/ivan-melezh-podykh-navalnitsy-valer-budzevich-input",
710
+ )
711
+
712
+ with gr.Row():
713
+ title = gr.Textbox(
714
+ label="Title",
715
+ value="Подых навальніцы",
716
+ )
717
+
718
+ author = gr.Textbox(
719
+ label="Author",
720
+ value="Іван Мележ",
721
+ )
722
+
723
+ narrator = gr.Textbox(
724
+ label="Narrator",
725
+ value="Валер Будзевіч",
726
+ )
727
+
728
+ with gr.Row():
729
+ source_group = gr.Textbox(
730
+ label="Source Group",
731
+ value="Аўдыёкнігі",
732
+ )
733
+
734
+ language = gr.Textbox(
735
+ label="Language",
736
+ value="be",
737
+ )
738
+
739
+ allow_patterns_text = gr.Textbox(
740
+ label="Allow patterns для зыходнага dataset",
741
+ value="part_*/*.mp3\n**/*.mp3\n**/metadata.csv\nmetadata.csv",
742
+ lines=5,
743
+ )
744
+
745
+ with gr.Row():
746
+ hf_token = gr.Textbox(
747
+ label="HF token override, optional",
748
+ type="password",
749
+ placeholder="Лепш дадаць HF_TOKEN у Space Settings → Secrets",
750
+ )
751
+
752
+ shard_target_mb = gr.Number(
753
+ label="Shard target MB",
754
+ value=DEFAULT_SHARD_TARGET_MB,
755
+ precision=0,
756
+ )
757
+
758
+ with gr.Row():
759
+ private = gr.Checkbox(
760
+ label="Create output dataset as private",
761
+ value=True,
762
+ )
763
+
764
+ overwrite_train = gr.Checkbox(
765
+ label="Delete old data/train/*.parquet before push",
766
+ value=True,
767
+ )
768
+
769
+ run_button = gr.Button(
770
+ "Convert and push",
771
+ variant="primary",
772
+ )
773
+
774
+ log_output = gr.Textbox(
775
+ label="Log",
776
+ lines=25,
777
+ max_lines=60,
778
+ )
779
+
780
+ run_button.click(
781
+ fn=convert_dataset,
782
+ inputs=[
783
+ source_repo,
784
+ output_repo,
785
+ title,
786
+ author,
787
+ narrator,
788
+ source_group,
789
+ language,
790
+ allow_patterns_text,
791
+ hf_token,
792
+ private,
793
+ overwrite_train,
794
+ shard_target_mb,
795
+ ],
796
+ outputs=log_output,
797
+ )
798
+
799
+
800
+ if __name__ == "__main__":
801
+ demo.launch()