Notebook di trascrizione MP3 con Whisper su Colab

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-28 16:30:47 +01:00
commit 2aff8cde8c
3 files changed
+535

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
*.mp3
*.zip
+52
View File
@@ -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.
+481
View File
@@ -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",
" <input type=\"file\" id=\"files-c3bb888e-e70c-4287-a754-e4e1a27b4503\" name=\"files[]\" multiple disabled\n",
" style=\"border:none\" />\n",
" <output id=\"result-c3bb888e-e70c-4287-a754-e4e1a27b4503\">\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",
" </output>\n",
" <script>// Copyright 2017 Google LLC\n",
"//\n",
"// Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"// you may not use this file except in compliance with the License.\n",
"// You may obtain a copy of the License at\n",
"//\n",
"// http://www.apache.org/licenses/LICENSE-2.0\n",
"//\n",
"// Unless required by applicable law or agreed to in writing, software\n",
"// distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"// See the License for the specific language governing permissions and\n",
"// limitations under the License.\n",
"\n",
"/**\n",
" * @fileoverview Helpers for google.colab Python module.\n",
" */\n",
"(function(scope) {\n",
"function span(text, styleAttributes = {}) {\n",
" const element = document.createElement('span');\n",
" element.textContent = text;\n",
" for (const key of Object.keys(styleAttributes)) {\n",
" element.style[key] = styleAttributes[key];\n",
" }\n",
" return element;\n",
"}\n",
"\n",
"// Max number of bytes which will be uploaded at a time.\n",
"const MAX_PAYLOAD_SIZE = 100 * 1024;\n",
"\n",
"function _uploadFiles(inputId, outputId) {\n",
" const steps = uploadFilesStep(inputId, outputId);\n",
" const outputElement = document.getElementById(outputId);\n",
" // Cache steps on the outputElement to make it available for the next call\n",
" // to uploadFilesContinue from Python.\n",
" outputElement.steps = steps;\n",
"\n",
" return _uploadFilesContinue(outputId);\n",
"}\n",
"\n",
"// This is roughly an async generator (not supported in the browser yet),\n",
"// where there are multiple asynchronous steps and the Python side is going\n",
"// to poll for completion of each step.\n",
"// This uses a Promise to block the python side on completion of each step,\n",
"// then passes the result of the previous step as the input to the next step.\n",
"function _uploadFilesContinue(outputId) {\n",
" const outputElement = document.getElementById(outputId);\n",
" const steps = outputElement.steps;\n",
"\n",
" const next = steps.next(outputElement.lastPromiseValue);\n",
" return Promise.resolve(next.value.promise).then((value) => {\n",
" // Cache the last promise value to make it available to the next\n",
" // step of the generator.\n",
" outputElement.lastPromiseValue = value;\n",
" return next.value.response;\n",
" });\n",
"}\n",
"\n",
"/**\n",
" * Generator function which is called between each async step of the upload\n",
" * process.\n",
" * @param {string} inputId Element ID of the input file picker element.\n",
" * @param {string} outputId Element ID of the output display.\n",
" * @return {!Iterable<!Object>} Iterable of next steps.\n",
" */\n",
"function* uploadFilesStep(inputId, outputId) {\n",
" const inputElement = document.getElementById(inputId);\n",
" inputElement.disabled = false;\n",
"\n",
" const outputElement = document.getElementById(outputId);\n",
" outputElement.innerHTML = '';\n",
"\n",
" const pickedPromise = new Promise((resolve) => {\n",
" inputElement.addEventListener('change', (e) => {\n",
" resolve(e.target.files);\n",
" });\n",
" });\n",
"\n",
" const cancel = document.createElement('button');\n",
" inputElement.parentElement.appendChild(cancel);\n",
" cancel.textContent = 'Cancel upload';\n",
" const cancelPromise = new Promise((resolve) => {\n",
" cancel.onclick = () => {\n",
" resolve(null);\n",
" };\n",
" });\n",
"\n",
" // Wait for the user to pick the files.\n",
" const files = yield {\n",
" promise: Promise.race([pickedPromise, cancelPromise]),\n",
" response: {\n",
" action: 'starting',\n",
" }\n",
" };\n",
"\n",
" cancel.remove();\n",
"\n",
" // Disable the input element since further picks are not allowed.\n",
" inputElement.disabled = true;\n",
"\n",
" if (!files) {\n",
" return {\n",
" response: {\n",
" action: 'complete',\n",
" }\n",
" };\n",
" }\n",
"\n",
" for (const file of files) {\n",
" const li = document.createElement('li');\n",
" li.append(span(file.name, {fontWeight: 'bold'}));\n",
" li.append(span(\n",
" `(${file.type || 'n/a'}) - ${file.size} bytes, ` +\n",
" `last modified: ${\n",
" file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() :\n",
" 'n/a'} - `));\n",
" const percent = span('0% done');\n",
" li.appendChild(percent);\n",
"\n",
" outputElement.appendChild(li);\n",
"\n",
" const fileDataPromise = new Promise((resolve) => {\n",
" const reader = new FileReader();\n",
" reader.onload = (e) => {\n",
" resolve(e.target.result);\n",
" };\n",
" reader.readAsArrayBuffer(file);\n",
" });\n",
" // Wait for the data to be ready.\n",
" let fileData = yield {\n",
" promise: fileDataPromise,\n",
" response: {\n",
" action: 'continue',\n",
" }\n",
" };\n",
"\n",
" // Use a chunked sending to avoid message size limits. See b/62115660.\n",
" let position = 0;\n",
" do {\n",
" const length = Math.min(fileData.byteLength - position, MAX_PAYLOAD_SIZE);\n",
" const chunk = new Uint8Array(fileData, position, length);\n",
" position += length;\n",
"\n",
" const base64 = btoa(String.fromCharCode.apply(null, chunk));\n",
" yield {\n",
" response: {\n",
" action: 'append',\n",
" file: file.name,\n",
" data: base64,\n",
" },\n",
" };\n",
"\n",
" let percentDone = fileData.byteLength === 0 ?\n",
" 100 :\n",
" Math.round((position / fileData.byteLength) * 100);\n",
" percent.textContent = `${percentDone}% done`;\n",
"\n",
" } while (position < fileData.byteLength);\n",
" }\n",
"\n",
" // All done.\n",
" yield {\n",
" response: {\n",
" action: 'complete',\n",
" }\n",
" };\n",
"}\n",
"\n",
"scope.google = scope.google || {};\n",
"scope.google.colab = scope.google.colab || {};\n",
"scope.google.colab._files = {\n",
" _uploadFiles,\n",
" _uploadFilesContinue,\n",
"};\n",
"})(self);\n",
"</script> "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"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.unlinkLine truncated
"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
}