commit 2aff8cde8c332e9843265f5b934de0991198afd9 Author: Davide Grilli Date: Wed Oct 28 16:30:47 2026 +0100 Notebook di trascrizione MP3 con Whisper su Colab Co-Authored-By: Claude Sonnet 5.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7be61c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.mp3 +*.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..9463692 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Trascrizione MP3 con Whisper su Google Colab + +Notebook Jupyter che trascrive file audio (MP3, WAV, M4A, FLAC, OGG, OPUS, AAC) usando [faster-whisper](https://github.com/SYSTRAN/faster-whisper) e `large-v3` sulla GPU di Google Colab. Nessuna API a pagamento né token Hugging Face: il modello è pubblico e l'audio è elaborato nella macchina Colab. + +## Output + +Per ogni audio, raccolti in uno ZIP: + +| File | Contenuto | +|---|---| +| `.md` | Testo con timestamp, sezioni temporali e segnalazioni "da riascoltare" | +| `.txt` | Solo il testo riconosciuto | +| `.srt` | Sottotitoli | +| `.json` | Segmenti, timestamp e probabilità per parola | +| `esecuzione.log`, `riepilogo.json` | Log ed esito di ogni file | + +La trascrizione è automatica: non fa riassunti e non identifica gli interlocutori. + +## Uso + +1. Apri [Google Colab](https://colab.research.google.com), scegli **File → Carica notebook** e seleziona `trascrizione_mp3_whisper_colab.ipynb`. +2. **Runtime → Cambia tipo di runtime → GPU T4**. +3. Modifica le opzioni nella prima cella (vedi sotto). +4. **Runtime → Esegui tutto** e carica gli MP3 quando compare **Scegli file**. +5. Al termine lo ZIP si scarica automaticamente. + +Senza GPU il notebook si ferma con un messaggio, invece di avviare una trascrizione lenta su CPU. + +## Opzioni (cella 1) + +| Variabile | Default | Descrizione | +|---|---|---| +| `MODELLO` | `large-v3` | `large-v3` per la qualità, `turbo` per la velocità | +| `LINGUA` | `it` | Codice lingua, oppure `None` per il rilevamento automatico | +| `PRECISIONE` | `float16` | `int8_float16` usa meno memoria GPU | +| `CONTESTO` | `""` | Nomi o termini difficili, es. `"Kiev, Chisinau"`; riduce gli errori sui nomi propri | +| `ORIGINE` | `upload` | `upload` carica dal PC, `drive` legge da una cartella di Google Drive | +| `CARTELLA_DRIVE` | `/content/drive/MyDrive/Audio da trascrivere` | Cartella di input con `ORIGINE = "drive"` | +| `SALVA_SU_DRIVE` | `False` | Copia i risultati anche su Drive | +| `FILTRO_SILENZI` | `False` | Attiva il filtro VAD sul non-parlato | +| `MINUTI_PER_SEZIONE` | `5` | Durata delle sezioni nel Markdown | +| `SOGLIA_PAROLA_INCERTA` | `0.45` | Sotto questa probabilità la parola è segnalata | +| `SCARICA_ZIP` | `True` | Scarica lo ZIP a fine esecuzione | + +Per audio lunghi usa `ORIGINE = "drive"` e `SALVA_SU_DRIVE = True`: i file in `/content` sono temporanei. + +## Note + +- Il notebook usa `google.colab` (upload, Drive): va eseguito su Colab. Con l'estensione Colab di VS Code il pulsante di upload potrebbe non comparire; usa Drive. +- Il warning sull'HF Hub non autenticato è innocuo. +- Memoria GPU insufficiente: `PRECISIONE = "int8_float16"` o `MODELLO = "turbo"`. +- Riascolta i punti segnalati: i nomi propri possono essere sbagliati anche senza segnalazione. diff --git a/trascrizione_mp3_whisper_colab.ipynb b/trascrizione_mp3_whisper_colab.ipynb new file mode 100644 index 0000000..5bda2ae --- /dev/null +++ b/trascrizione_mp3_whisper_colab.ipynb @@ -0,0 +1,481 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell-000", + "metadata": {}, + "source": [ + "# Trascrizione MP3 su Google Colab — GPU Tesla T4\n", + "\n", + "Trascrive uno o più file senza riassumere. Produce **Markdown, TXT, SRT e JSON**, raccolti in uno ZIP.\n", + "\n", + "1. Seleziona **Runtime → Cambia tipo di runtime → GPU T4**, se disponibile.\n", + "2. Regola le opzioni nella prima cella (italiano e `large-v3` sono preimpostati). In `CONTESTO` puoi indicare nomi e termini difficili (es. luoghi, persone) per ridurre gli errori.\n", + "3. Scegli **Runtime → Esegui tutto**, quindi carica gli MP3 quando richiesto.\n", + "4. Al termine scarica lo ZIP. Puoi anche leggere i risultati nella cartella indicata.\n", + "\n", + "Il modello viene scaricato al primo avvio. L'audio viene elaborato nella macchina Colab di Google, non sul tuo PC. Non occorre una chiave API. La disponibilità della T4 dipende da Colab.\n", + "\n", + "**Limiti:** la trascrizione è automatica e può perdere parole o introdurre errori. Le segnalazioni di bassa affidabilità non dimostrano che un passaggio sia incomprensibile. Non viene eseguita l'identificazione delle voci: nessun nome o cambio di interlocutore viene inventato. Le sezioni Markdown sono intervalli temporali, non riassunti.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell-001", + "metadata": {}, + "outputs": [], + "source": [ + "# 1. Opzioni\n", + "MODELLO = \"large-v3\" # \"large-v3\" per qualità; \"turbo\" per maggiore velocità\n", + "LINGUA = \"it\" # \"it\" per italiano; None per rilevamento automatico\n", + "PRECISIONE = \"float16\" # adatta alla T4; \"int8_float16\" usa meno memoria\n", + "CONTESTO = \"\" # nomi/termini da riconoscere bene, es. \"Kiev, Chisinau, Bucarest\"\n", + "ORIGINE = \"upload\" # \"upload\" oppure \"drive\"\n", + "CARTELLA_DRIVE = \"/content/drive/MyDrive/Audio da trascrivere\"\n", + "SALVA_SU_DRIVE = False # True per conservare i risultati anche su Drive\n", + "CARTELLA_RISULTATI_DRIVE = \"/content/drive/MyDrive/Trascrizioni\"\n", + "FILTRO_SILENZI = False # False: nessun filtro VAD; True: filtra il non-parlato stimato\n", + "MINUTI_PER_SEZIONE = 5\n", + "SOGLIA_PAROLA_INCERTA = 0.45\n", + "SCARICA_ZIP = True\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-002", + "metadata": {}, + "source": [ + "## Installazione e verifica della GPU\n", + "La cella verifica la GPU realmente assegnata. Se non c'è una GPU, si ferma con istruzioni invece di avviare una lenta trascrizione sulla CPU.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell-003", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "GPU assegnata: Tesla T4, 15360 MiB\n", + "Installazione completata.\n" + ] + } + ], + "source": [ + "# 2. Dipendenze\n", + "import subprocess, sys\n", + "gpu = subprocess.run([\"nvidia-smi\", \"--query-gpu=name,memory.total\", \"--format=csv,noheader\"],\n", + " capture_output=True, text=True) if __import__(\"shutil\").which(\"nvidia-smi\") else None\n", + "if gpu is None or gpu.returncode != 0:\n", + " raise RuntimeError(\"GPU assente. Seleziona Runtime > Cambia tipo di runtime > GPU T4, poi riesegui tutto.\")\n", + "print(\"GPU assegnata:\", gpu.stdout.strip())\n", + "if \"T4\" not in gpu.stdout:\n", + " print(\"Colab ha assegnato una GPU diversa dalla T4; il notebook userà quella disponibile.\")\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\",\n", + " \"faster-whisper==1.2.1\", \"ctranslate2>=4.5,<5\", \"av>=11,<16\",\n", + " \"nvidia-cublas-cu12\", \"nvidia-cudnn-cu12>=9,<10\"])\n", + "print(\"Installazione completata.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-004", + "metadata": {}, + "source": [ + "## Caricamento degli audio\n", + "Con `upload` puoi selezionare più MP3. Per file grandi usa `drive`: autorizza il collegamento a Google Drive e indica la cartella nelle opzioni. Sono accettati anche WAV, M4A, FLAC, OGG, OPUS e AAC. I file di Drive sono letti senza modificarli.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-005", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " Upload widget is only available when the cell has been executed in the\n", + " current browser session. Please rerun this cell to enable.\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# 3. Sorgenti e cartelle\n", + "from pathlib import Path\n", + "from datetime import datetime, timezone\n", + "import json, os, shutil\n", + "from google.colab import files, drive\n", + "if ORIGINE not in {\"upload\", \"drive\"}:\n", + " raise ValueError(\"ORIGINE deve essere 'upload' oppure 'drive'.\")\n", + "if ORIGINE == \"drive\" or SALVA_SU_DRIVE:\n", + " drive.mount(\"/content/drive\")\n", + "RUN_ID = datetime.now(timezone.utc).strftime(\"%Y%m%d_%H%M%S_%f\")\n", + "RUN_DIR = Path(\"/content/trascrizioni\") / RUN_ID\n", + "RUN_DIR.mkdir(parents=True, exist_ok=True)\n", + "if ORIGINE == \"upload\":\n", + " INPUT_DIR = Path(\"/content/audio_caricati\") / RUN_ID\n", + " INPUT_DIR.mkdir(parents=True, exist_ok=True)\n", + " old_cwd = Path.cwd()\n", + " try:\n", + " os.chdir(INPUT_DIR)\n", + " uploaded = files.upload()\n", + " finally:\n", + " os.chdir(old_cwd)\n", + " del uploaded # libera la copia in memoria\n", + "else:\n", + " INPUT_DIR = Path(CARTELLA_DRIVE)\n", + "if not INPUT_DIR.is_dir():\n", + " raise FileNotFoundError(f\"Cartella non trovata: {INPUT_DIR}\")\n", + "extensions = {\".mp3\", \".wav\", \".m4a\", \".flac\", \".ogg\", \".opus\", \".aac\"}\n", + "AUDIO_FILES = sorted(p for p in INPUT_DIR.iterdir() if p.is_file() and p.suffix.lower() in extensions)\n", + "if not AUDIO_FILES:\n", + " raise ValueError(\"Nessun audio supportato nella cartella selezionata.\")\n", + "if MINUTI_PER_SEZIONE <= 0 or not 0 <= SOGLIA_PAROLA_INCERTA <= 1:\n", + " raise ValueError(\"Controlla MINUTI_PER_SEZIONE e SOGLIA_PAROLA_INCERTA.\")\n", + "for p in AUDIO_FILES:\n", + " print(f\"• {p.name} ({p.stat().st_size / 1024**2:.1f} MB)\")\n", + "print(\"Risultati:\", RUN_DIR)\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-006", + "metadata": {}, + "source": [ + "## Trascrizione sulla GPU\n", + "Il modello viene caricato una volta e riutilizzato per tutti gli audio. Ogni segmento viene salvato progressivamente: in caso di errore rimane un file `.partial.jsonl`, che **non** va confuso con una trascrizione completa.\n", + "\n", + "La procedura lavora in un processo separato, configurando prima le librerie CUDA e cuDNN. Non è necessario riavviare il runtime dopo l'installazione. I messaggi mostrano la posizione raggiunta nell'audio.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-007", + "metadata": {}, + "outputs": [], + "source": [ + "# 4. Prepara il programma di trascrizione\n", + "WORKER_SOURCE = 'import os, sys, json, traceback, re\\nfrom pathlib import Path\\nfrom dataclasses import asdict\\nfrom importlib.metadata import version\\nfrom faster_whisper import WhisperModel\\nimport ctranslate2\\n\\ndef timestamp(seconds, srt=False):\\n ms = max(0, round(seconds * 1000))\\n h, ms = divmod(ms, 3600000)\\n m, ms = divmod(ms, 60000)\\n s, ms = divmod(ms, 1000)\\n return f\"{h:02}:{m:02}:{s:02}\" + (f\",{ms:03}\" if srt else \"\")\\n\\ndef export_result(source, base, segments, info, cfg):\\n md = [f\"# Trascrizione — {source.name}\", \"\",\\n f\"**Lingua rilevata/impostata:** {info.language} \",\\n f\"**Durata:** {timestamp(info.duration)} \",\\n f\"**Modello:** {cfg[\\'model\\']} \",\\n \"**Interlocutori:** non identificati automaticamente.\", \"\",\\n \"> Trascrizione automatica, non verificata mediante ascolto. Nessun riassunto. \"\\n \"Le parole dubbie sono conservate e segnalate, non sostituite con ipotesi. \"\\n \"I timestamp e i segnali di affidabilità sono approssimativi.\", \"\"]\\n txt, srt = [], []\\n previous_section = None\\n for i, seg in enumerate(segments, 1):\\n text = seg[\\'text\\'].strip()\\n section = int(seg[\\'start\\'] // (cfg[\\'section_minutes\\'] * 60))\\n if section != previous_section:\\n start = section * cfg[\\'section_minutes\\'] * 60\\n end = min(start + cfg[\\'section_minutes\\'] * 60, info.duration)\\n md += [f\"## {timestamp(start)}–{timestamp(end)}\", \"\"]\\n previous_section = section\\n md += [f\"**[{timestamp(seg[\\'start\\'])}–{timestamp(seg[\\'end\\'])}]**\", \"\", text, \"\"]\\n uncertain = [w for w in (seg.get(\\'words\\') or []) if w[\\'probability\\'] < cfg[\\'threshold\\']]\\n if uncertain:\\n detail = \"; \".join(f\"{timestamp(w[\\'start\\'])}: «{w[\\'word\\'].strip()}»\" for w in uncertain)\\n md += [f\"*[Da riascoltare: letture automatiche incerte — {detail}]*\", \"\"]\\n if seg.get(\\'avg_logprob\\', 0) < -1 or seg.get(\\'compression_ratio\\', 0) > 2.4:\\n md += [\"*[Segmento da verificare: possibile riconoscimento errato o ripetitivo.]*\", \"\"]\\n txt.append(text)\\n srt.append(f\"{i}\\\\n{timestamp(seg[\\'start\\'], True)} --> {timestamp(seg[\\'end\\'], True)}\\\\n{text}\\\\n\")\\n if not segments:\\n md += [\"[Nessun parlato riconosciuto automaticamente: verificare l’audio.]\", \"\"]\\n for ext, content in [(\\'md\\', \\'\\\\n\\'.join(md)), (\\'txt\\', \\'\\\\n\\'.join(txt)), (\\'srt\\', \\'\\\\n\\'.join(srt))]:\\n base.with_suffix(\\'.\\' + ext).write_text(content, encoding=\\'utf-8\\')\\n metadata = {\\'source\\': str(source), \\'duration_seconds\\': info.duration,\\n \\'language\\': info.language, \\'language_probability\\': info.language_probability,\\n \\'settings\\': cfg, \\'segments\\': segments,\\n \\'versions\\': {p: version(p) for p in [\\'faster-whisper\\',\\'ctranslate2\\',\\'av\\']}}\\n base.with_suffix(\\'.json\\').write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding=\\'utf-8\\')\\n\\ndef main():\\n cfg = json.loads(Path(sys.argv[1]).read_text(encoding=\\'utf-8\\'))\\n if ctranslate2.get_cuda_device_count() < 1:\\n raise RuntimeError(\\'GPU CUDA non utilizzabile. Verifica il runtime Colab.\\')\\n print(\\'Caricamento modello \\' + cfg[\\'model\\'] + \\' sulla GPU...\\', flush=True)\\n model = WhisperModel(cfg[\\'model\\'], device=\\'cuda\\', compute_type=cfg[\\'precision\\'])\\n results=[]\\n for index, name in enumerate(cfg[\\'files\\'], 1):\\n source = Path(name)\\n # Prefix and suffix protect different inputs with the same stem.\\n safe = re.sub(r\\'[^\\\\w\\\\- ]\\', \\'_\\', source.stem)[:100] or \\'audio\\'\\n base = Path(cfg[\\'output\\']) / f\\'{index:03}_{safe}_trascrizione\\'\\n print(f\\'\\\\n[{index}/{len(cfg[\"files\"])}] {source.name}\\', flush=True)\\n partial = base.with_suffix(\\'.partial.jsonl\\')\\n try:\\n iterator, info = model.transcribe(str(source), language=cfg[\\'language\\'],\\n task=\\'transcribe\\', beam_size=5, word_timestamps=True,\\n vad_filter=cfg[\\'vad\\'], hotwords=cfg[\\'context\\'] or None, condition_on_previous_text=False)\\n segments=[]\\n with partial.open(\\'w\\', encoding=\\'utf-8\\') as f:\\n for segment in iterator:\\n record=asdict(segment)\\n segments.append(record)\\n f.write(json.dumps(record, ensure_ascii=False)+\\'\\\\n\\'); f.flush()\\n print(f\\' {timestamp(segment.end)} / {timestamp(info.duration)}\\', flush=True)\\n export_result(source, base, segments, info, cfg)\\n partial.unlink()\\n results.append({\\'file\\': source.name, \\'status\\': \\'ok\\', \\'segments\\': len(segments)})\\n print(\\' Esportazione completata.\\', flush=True)\\n except Exception as exc:\\n base.with_suffix(\\'.errore.txt\\').write_text(traceback.format_exc(), encoding=\\'utf-8\\')\\n results.append({\\'file\\': source.name, \\'status\\': \\'errore\\', \\'error\\': str(exc)})\\n print(\\' ERRORE: \\' + str(exc) + \\' — proseguo con il file successivo.\\', flush=True)\\n Path(cfg[\\'output\\'], \\'riepilogo.json\\').write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding=\\'utf-8\\')\\n print(\\'\\\\nCompletati:\\', sum(r[\\'status\\']==\\'ok\\' for r in results), \\'/\\', len(results), flush=True)\\n if not any(r[\\'status\\']==\\'ok\\' for r in results):\\n sys.exit(2)\\n\\nif __name__ == \\'__main__\\':\\n main()\\n'\n", + "WORKER = Path(\"/content/trascrivi_gpu.py\")\n", + "WORKER.write_text(WORKER_SOURCE, encoding=\"utf-8\")\n", + "CONFIG = {\n", + " \"model\": MODELLO, \"language\": LINGUA, \"precision\": PRECISIONE,\n", + " \"vad\": FILTRO_SILENZI, \"context\": CONTESTO.strip(), \"section_minutes\": MINUTI_PER_SEZIONE,\n", + " \"threshold\": SOGLIA_PAROLA_INCERTA,\n", + " \"files\": [str(p) for p in AUDIO_FILES], \"output\": str(RUN_DIR)\n", + "}\n", + "CONFIG_PATH = RUN_DIR / \"configurazione.json\"\n", + "CONFIG_PATH.write_text(json.dumps(CONFIG, ensure_ascii=False, indent=2), encoding=\"utf-8\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-008", + "metadata": {}, + "outputs": [], + "source": [ + "# 5. Esegui il riconoscimento e conserva il log\n", + "import site\n", + "library_dirs = []\n", + "for folder in site.getsitepackages():\n", + " for package in (\"cublas\", \"cudnn\"):\n", + " candidate = Path(folder) / \"nvidia\" / package / \"lib\"\n", + " if candidate.is_dir():\n", + " library_dirs.append(str(candidate))\n", + "if len(library_dirs) < 2:\n", + " raise RuntimeError(\"Librerie CUDA/cuDNN non trovate: riesegui la cella 2.\")\n", + "env = os.environ.copy()\n", + "env[\"LD_LIBRARY_PATH\"] = \":\".join(library_dirs + [env.get(\"LD_LIBRARY_PATH\", \"\")])\n", + "env[\"PYTHONUNBUFFERED\"] = \"1\"\n", + "with (RUN_DIR / \"esecuzione.log\").open(\"w\", encoding=\"utf-8\") as log:\n", + " process = subprocess.Popen([sys.executable, \"-u\", str(WORKER), str(CONFIG_PATH)],\n", + " env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,\n", + " text=True, bufsize=1)\n", + " try:\n", + " for line in process.stdout:\n", + " print(line, end=\"\")\n", + " log.write(line); log.flush()\n", + " returncode = process.wait()\n", + " except KeyboardInterrupt:\n", + " process.terminate()\n", + " process.wait()\n", + " print(\"Interrotto: i segmenti già elaborati restano nei file partial.jsonl.\")\n", + " raise\n", + "if returncode:\n", + " print(f\"Processo terminato con codice {returncode}. Controlla il log e gli eventuali errori nello ZIP.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-009", + "metadata": {}, + "source": [ + "## Scarica i risultati\n", + "Il Markdown include timestamp, sezioni temporali e letture da controllare. TXT e SRT mantengono il testo riconosciuto senza le annotazioni editoriali; il JSON conserva anche i timestamp e i punteggi delle parole.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell-010", + "metadata": {}, + "outputs": [], + "source": [ + "# 6. Archivio ZIP e copia facoltativa su Drive\n", + "archive = Path(shutil.make_archive(str(RUN_DIR) + \"_risultati\", \"zip\", root_dir=RUN_DIR))\n", + "if SALVA_SU_DRIVE:\n", + " destination = Path(CARTELLA_RISULTATI_DRIVE) / RUN_ID\n", + " destination.mkdir(parents=True, exist_ok=True)\n", + " shutil.copytree(RUN_DIR, destination, dirs_exist_ok=True)\n", + " shutil.copy2(archive, destination / archive.name)\n", + " print(\"Copia salvata su Drive:\", destination)\n", + "print(\"Archivio:\", archive)\n", + "print(\"Trascrizioni Markdown:\")\n", + "for p in sorted(RUN_DIR.glob(\"*.md\")):\n", + " print(\"•\", p.name)\n", + "if SCARICA_ZIP:\n", + " files.download(str(archive))\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell-011", + "metadata": {}, + "source": [ + "## Se qualcosa non funziona\n", + "- **GPU assente:** seleziona un runtime GPU e riesegui tutto. Se Colab non offre una T4, attendi o usa la GPU disponibile.\n", + "- **Memoria GPU insufficiente:** imposta `PRECISIONE = \"int8_float16\"`; se necessario scegli `MODELLO = \"turbo\"`, disconnetti/elimina il runtime e riparti.\n", + "- **Download del modello interrotto:** riprova la cella 5 quando la connessione è disponibile.\n", + "- **Errore CUDA/cuDNN:** riparti da un runtime nuovo e riesegui le celle nell'ordine. Il log è incluso nello ZIP.\n", + "- **Audio lungo:** usa Google Drive, attiva `SALVA_SU_DRIVE` e scarica i risultati prima che la sessione termini. I file di lavoro in `/content` sono temporanei; la copia su Drive viene effettuata nella cella 6.\n", + "- **Passaggi strani, sovrapposti o nomi sbagliati:** riascolta i punti indicati. Il modello non può certificare da solo l'incomprensibilità né garantire una trascrizione integrale fedele.\n", + "\n", + "### Riferimenti\n", + "- [faster-whisper: installazione, GPU e trascrizione](https://github.com/SYSTRAN/faster-whisper)\n", + "- [FAQ ufficiali di Google Colab](https://research.google.com/colaboratory/faq.html)\n", + "\n", + "Notebook verificato staticamente e funzioni di esportazione provate con dati sintetici; l'esecuzione end-to-end su una T4 Colab va verificata nel proprio runtime.\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "name": "Trascrizione_MP3_GPU_T4.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}