├── LICENSE ├── README.md └── srt_gpt_translator_claude.ipynb /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alex Fazio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Twitter Follow](https://img.shields.io/twitter/follow/alxfazio?style=social)](https://twitter.com/alxfazio) [![Open Main Version In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/alexfazio/srt-GPT-translator/blob/main/srt_gpt_translator_claude.ipynb) 2 | 3 | # `.srt` GPT Translator (Google Colab) 4 | 5 | This tool is designed to help users translate srt file into a different language using the Antrhopic (Claude 3) API . Does not support bilingual subtitles output. 6 | 7 | ## Description 8 | Translate `.srt` files from any language to any language using Anthropic (Claude). This Google Colab notebook provides a seamless way to translate `.srt` subtitle files between any supported languages using state-of-the-art language models from Anthropic (Claude). With just a few simple steps, you can quickly and accurately translate your subtitles, making your content accessible to a wider audience. 9 | 10 | Feel free to contribute to the project by submitting pull requests or reporting issues on the GitHub repository. Your feedback and contributions are greatly appreciated! 11 | 12 | ## Features 13 | - Translate `.srt` files between any supported languages using Anthropic (Claude) or OpenAI APIs. 14 | - Easy-to-use Google Colab notebook interface. 15 | 16 | ## Instructions 17 | To translate an `.srt` file: 18 | 1. Open the Google Colab notebook. 19 | 2. Insert your API key in the designated cell. 20 | 3. Upload your `.srt` file to the notebook. 21 | 4. Run the notebook cells to execute the translation process. 22 | 5. Download the translated `.srt` file(s) from the notebook. 23 | 24 | ## Upcoming Enhancements 25 | - Implement an API selector to choose between OpenAI and Anthropic APIs. 26 | - (Optional and Low Priority) Implement a string cleaner for improved output (Reference: https://x.com/mattshumer_/status/1777541303288340613). 27 | - Make the API key either an environment variable or provide a custom form space for the end user to input their key. 28 | - Implement context length selector, a toggle or slider that decides how many context words are given to the text. This is the number of segments used to flank the target segments and pass to the LLM for richer context reference during translation. The higher the context segments, the slower the process and more expensive in terms of API call usage, but more precise. 29 | - Support for bilingual subtitle output. 30 | 31 | ## API Documentation 32 | For more information on using the Anthropic API, refer to the official documentation: https://docs.anthropic.com/claude/page/polyglot-superpowers 33 | 34 | ## License 35 | This tool is released under the MIT License. 36 | 37 | ## Disclaimer 38 | The SRT Translator tool is provided for educational and informational purposes only. The accuracy, reliability, and completeness of the translations generated by the Anthropic Claude API model used in this tool cannot be guaranteed. Users of the SRT Translator tool are solely responsible for verifying the accuracy and usefulness of the translations obtained and should not rely solely on them without further verification. The use of the SRT Translator tool is at the user's own risk, and the tool's developers and contributors shall not be liable for any damages or losses arising from its use. By using the SRT Translator tool, you agree to these terms and conditions. 39 | 40 | If you have any concerns or suggestions about the use of this project, please contact us through the issues section. 41 | -------------------------------------------------------------------------------- /srt_gpt_translator_claude.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "private_outputs": true, 8 | "cell_execution_strategy": "setup", 9 | "authorship_tag": "ABX9TyMd5V8Cq2KibU7F9Q2QeQXR", 10 | "include_colab_link": true 11 | }, 12 | "kernelspec": { 13 | "name": "python3", 14 | "display_name": "Python 3" 15 | }, 16 | "language_info": { 17 | "name": "python" 18 | } 19 | }, 20 | "cells": [ 21 | { 22 | "cell_type": "markdown", 23 | "metadata": { 24 | "id": "view-in-github", 25 | "colab_type": "text" 26 | }, 27 | "source": [ 28 | "\"Open" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "source": [ 34 | "# srt-GPT-Translator (Claude 3)\n", 35 | "\n", 36 | "By Alex Fazio (https://www.linkedin.com/in/alxfazio)\n", 37 | "\n", 38 | "Github repo: https://github.com/alexfazio/srt-GPT-Translator\n", 39 | "\n", 40 | "## Description\n", 41 | "\n", 42 | "Translate `.srt` files from any language to any language using Anthropic (Claude) or OpenAI APIs.\n", 43 | "\n", 44 | "This notebook provides a seamless way to translate `.srt` subtitle files between any supported languages using state-of-the-art language models from Anthropic (Claude) or OpenAI. With just a few simple steps, you can quickly and accurately translate your subtitles, making your content accessible to a wider audience.\n", 45 | "\n", 46 | "Feel free to contribute to the project by submitting pull requests or reporting issues on the GitHub repository. Your feedback and contributions are greatly appreciated!\n", 47 | "\n", 48 | "## Instructions\n", 49 | "\n", 50 | "To translate an `.srt` file:\n", 51 | "\n", 52 | "1. Upload the `.srt` file you want to translate to your Colab folders.\n", 53 | "2. In the notebook, insert your Claude API key in the designated cell.\n", 54 | "3. Select the desired version of Claude 3 from the `GENERATION_MODEL` dropdown.\n", 55 | "4. Specify the pathname of the uploaded `.srt` file in the `SRT_PATHNAME` variable.\n", 56 | "5. Choose the target language for translation by selecting or typing the desired language in the `OUTPUT_LANGUAGE` variable.\n", 57 | "6. Run all the cells in the notebook by selecting `Runtime > Run all` from the menu or executing each cell individually.\n", 58 | "7. The translated `.srt` file will be generated and available for download.\n", 59 | "\n", 60 | "## Upcoming Enhancements\n", 61 | "\n", 62 | "- Implement an API selector to choose between OpenAI and Anthropic APIs\n", 63 | "- (Optional and Low Priority) Implement a string cleaner for improved output (Reference: https://x.com/mattshumer_/status/1777541303288340613)\n", 64 | "- Make the API key either an environment variable or provide a custom form space for the end user to input their key\n", 65 | "- Implement context length selector, a toggle or slider that decides how many context words are given to the text. This is the number of segments used to flank the target segments and pass to the LLM for richer context reference during translation. The higher the context segments the slower the process, and more expensive in terms of API calls usage, but more precise\n", 66 | "- Support for bilingual subtitles output.\n", 67 | "\n", 68 | "## API Documentation\n", 69 | "\n", 70 | "For more information on using the Anthropic API, refer to the official documentation:\n", 71 | "https://docs.anthropic.com/claude/page/polyglot-superpowers" 72 | ], 73 | "metadata": { 74 | "id": "jpJRCSAO8S3Y" 75 | } 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "source": [], 80 | "metadata": { 81 | "id": "vX8Ru2dYPnyA" 82 | } 83 | }, 84 | { 85 | "cell_type": "code", 86 | "source": [ 87 | "# @title Adjust settings here\n", 88 | "\n", 89 | "ANTHROPIC_API_KEY = '' #@param {type: 'string'}\n", 90 | "GENERATION_MODEL = \"claude-3-haiku-20240307\" # @param [\"claude-3-haiku-20240307\", \"claude-3-sonnet-20240229\", \"claude-3-opus-20240229\"] {type:\"string\"}\n", 91 | "SRT_PATHNAME = 'altman-sub.srt' #@param {type: 'string'}\n", 92 | "OUTPUT_LANGUAGE = \"Italiano\" # @param [\"Italiano\", \"English\", \"中文 (Zhōngwén)\", \"हिन्दी (Hindī)\", \"Español\", \"Français\"] {type:\"string\"}\n", 93 | "\n" 94 | ], 95 | "metadata": { 96 | "id": "8GbyIl96N4ey", 97 | "cellView": "form" 98 | }, 99 | "execution_count": null, 100 | "outputs": [] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "source": [ 105 | "# @title Install Required Libraries and Import Necessary Modules\n", 106 | "!pip install anthropic\n", 107 | "from tqdm import tqdm" 108 | ], 109 | "metadata": { 110 | "id": "T6aP_yPLLyDf" 111 | }, 112 | "execution_count": null, 113 | "outputs": [] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "source": [ 118 | "# @title Convert `.srt` File Into Segments\n", 119 | "\n", 120 | "def srt_to_segments(SRT_PATHNAME):\n", 121 | " all_text = [] # Store all text lines for the 'text' key\n", 122 | " segments = []\n", 123 | " segment_id = 0 # Initialize segment ID\n", 124 | " with open(SRT_PATHNAME, 'r') as file:\n", 125 | " srt_content = file.read().strip()\n", 126 | "\n", 127 | " entries = srt_content.split('\\n\\n')\n", 128 | "\n", 129 | " for entry in entries:\n", 130 | " lines = entry.split('\\n')\n", 131 | " if len(lines) >= 3:\n", 132 | " time_range, text_lines = lines[1], lines[2:]\n", 133 | " start_str, end_str = time_range.split(' --> ')\n", 134 | " start = srt_time_to_seconds(start_str)\n", 135 | " end = srt_time_to_seconds(end_str)\n", 136 | " text = ' '.join(text_lines).replace('\\\\N', '\\n')\n", 137 | " all_text.append(text)\n", 138 | "\n", 139 | " # Use None for placeholders for the additional info\n", 140 | " segments.append({\n", 141 | " 'id': segment_id,\n", 142 | " 'seek': None, # Placeholder for 'seek' value now None\n", 143 | " 'start': start,\n", 144 | " 'end': end,\n", 145 | " 'text': text,\n", 146 | " # Updating placeholders to None\n", 147 | " 'tokens': None, # Placeholder for tokens now None\n", 148 | " 'temperature': None, # Placeholder for temperature now None\n", 149 | " 'avg_logprob': None, # Placeholder for avg_logprob now None\n", 150 | " 'compression_ratio': None, # Placeholder for compression ratio now None\n", 151 | " 'no_speech_prob': None, # Placeholder for no_speech_prob now None\n", 152 | " })\n", 153 | " segment_id += 1\n", 154 | "\n", 155 | " # Compile the final dictionary with all text and segments\n", 156 | " final_output = {\n", 157 | " 'text': ' '.join(all_text),\n", 158 | " 'segments': segments\n", 159 | " }\n", 160 | "\n", 161 | " return final_output\n", 162 | "\n", 163 | "def srt_time_to_seconds(time_str):\n", 164 | " parts = time_str.split(':')\n", 165 | " seconds, milliseconds = parts[2].split(',')\n", 166 | " total_seconds = 3600 * int(parts[0]) + 60 * int(parts[1]) + int(seconds) + float(milliseconds) / 1000\n", 167 | " return total_seconds" 168 | ], 169 | "metadata": { 170 | "id": "XpydmfGysK9Z" 171 | }, 172 | "execution_count": null, 173 | "outputs": [] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "source": [ 178 | "# @title Extract segments of subtitles and the surrounding context for the purpose of translation\n", 179 | "\n", 180 | "def context(subtitles_dict):\n", 181 | " \"\"\"\n", 182 | " Generator function to yield the context and target segment for each subtitle segment in the dictionary,\n", 183 | " specifically treating the first and last segments according to the specified requirements.\n", 184 | "\n", 185 | " Args:\n", 186 | " subtitles_dict (dict): A dictionary containing subtitle segments with their text, start, and end times.\n", 187 | "\n", 188 | " Yields:\n", 189 | " tuple: A tuple containing the context text, the target segment text, and the target segment id.\n", 190 | " \"\"\"\n", 191 | "\n", 192 | " segments = subtitles_dict['segments'] # Shortcut for easier access\n", 193 | " num_segments = len(segments)\n", 194 | "\n", 195 | " for i in range(num_segments):\n", 196 | " # First segment translation case\n", 197 | " if i == 0:\n", 198 | " context_text = segments[i]['text']\n", 199 | " target_text = segments[i]['text']\n", 200 | " target_id = segments[i]['id']\n", 201 | "\n", 202 | " # Last segment translation case\n", 203 | " elif i == num_segments - 1:\n", 204 | " context_texts = [segments[i-1]['text'], segments[i]['text']]\n", 205 | " context_text = \" \".join(context_texts)\n", 206 | " target_text = segments[i]['text']\n", 207 | " target_id = segments[i]['id']\n", 208 | "\n", 209 | " # Middle segments translation case\n", 210 | " else:\n", 211 | " context_texts = [segments[i-1]['text'], segments[i]['text'], segments[i+1]['text']]\n", 212 | " context_text = \" \".join(context_texts)\n", 213 | " target_text = segments[i]['text']\n", 214 | " target_id = segments[i]['id']\n", 215 | "\n", 216 | " yield context_text, target_text, target_id" 217 | ], 218 | "metadata": { 219 | "id": "wWXnyB-FYQRh" 220 | }, 221 | "execution_count": null, 222 | "outputs": [] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "source": [ 227 | "# @title API Call for translation\n", 228 | "\n", 229 | "import anthropic\n", 230 | "\n", 231 | "def translate_segment(context_texts, target_segment_text):\n", 232 | " \"\"\"\n", 233 | " Translates a subtitle segment to target language using the Claude-3 model from Anthropic.\n", 234 | "\n", 235 | " Args:\n", 236 | " context_texts (str): The surrounding text for context.\n", 237 | " target_segment_text (str): The subtitle segment to be translated.\n", 238 | "\n", 239 | " Returns:\n", 240 | " str: The translated subtitle segment in target language.\n", 241 | " \"\"\"\n", 242 | "\n", 243 | " client = anthropic.Anthropic(\n", 244 | " api_key=ANTHROPIC_API_KEY,\n", 245 | " )\n", 246 | " system_prompt = f\"Translate the provided subtitle segment, '{target_segment_text}', into '{OUTPUT_LANGUAGE}'. Use the surrounding context, '{context_texts}', to inform your translation and ensure it fits cohesively within the larger conversation. Craft your translation to capture the meaning, tone, and any idiomatic expressions present in the original text. Do not translate the entire context, only the specified segment.\\n\\nWhen translating, follow these guidelines:\\n- Only output the translated text, without any labels or quotes.\\n- If the subtitle segment does not contain a complete sentence, do not end the translation with a period or other punctuation.\\n- However, if the segment contains one or more complete sentences, end each sentence with a period, even if the final sentence is a fragment.\"\n", 247 | "\n", 248 | " try:\n", 249 | " message = client.messages.create(\n", 250 | " model=GENERATION_MODEL,\n", 251 | " max_tokens=2000,\n", 252 | " temperature=0.2,\n", 253 | " system=system_prompt,\n", 254 | " messages=[\n", 255 | " {\n", 256 | " \"role\": \"user\",\n", 257 | " \"content\": [\n", 258 | " {\n", 259 | " \"type\": \"text\",\n", 260 | " \"text\": f\"'{target_segment_text}' --> '{OUTPUT_LANGUAGE}'\"\n", 261 | " }\n", 262 | " ]\n", 263 | " }\n", 264 | " ]\n", 265 | " )\n", 266 | " # Extract the translated text from the API response\n", 267 | " translated_text = message.content[0].text.strip()\n", 268 | " return translated_text\n", 269 | " except Exception as e:\n", 270 | " print(f\"An error occurred: {str(e)}\")\n", 271 | " return None" 272 | ], 273 | "metadata": { 274 | "id": "tAxuI2PhzFcg" 275 | }, 276 | "execution_count": null, 277 | "outputs": [] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "source": [ 282 | "# @title Main\n", 283 | "\n", 284 | "from tqdm import tqdm # Ensure tqdm is imported\n", 285 | "\n", 286 | "# RESETS DICTIONARIES\n", 287 | "dict = srt_to_segments(SRT_PATHNAME)\n", 288 | "trans_dict = dict\n", 289 | "\n", 290 | "# Initialize the context generator with the source dictionary to maintain its integrity\n", 291 | "context_generator = context(dict) # Use 'dict' for generating context_texts\n", 292 | "\n", 293 | "# Wrap the generator with tqdm to display the progress bar\n", 294 | "# Ensure that you pass the total number of items if known, to improve the accuracy of the progress estimation\n", 295 | "total_segments = len(dict['segments']) # Assuming dict structure contains a 'segments' list\n", 296 | "for context_texts, t_seg_text, t_target_id in tqdm(context_generator, total=total_segments):\n", 297 | " translated_segment = translate_segment(context_texts, t_seg_text)\n", 298 | " segment = next((seg for seg in trans_dict['segments'] if seg['id'] == t_target_id), None)\n", 299 | " if segment is not None:\n", 300 | " segment['text'] = translated_segment\n", 301 | " else:\n", 302 | " print(f\"Segment with ID {t_target_id} not found in trans_dict.\")" 303 | ], 304 | "metadata": { 305 | "id": "W-OCQuCH-gHw" 306 | }, 307 | "execution_count": null, 308 | "outputs": [] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "source": [ 313 | "# @title Parse result to output srt file\n", 314 | "\n", 315 | "def write_translated_srt(translated_dict, output_file_path):\n", 316 | " \"\"\"\n", 317 | " Writes the translated subtitles back into an SRT file format.\n", 318 | "\n", 319 | " Args:\n", 320 | " translated_dict (dict): Dictionary containing the translated subtitles and their timings.\n", 321 | " output_file_path (str): The path where the translated SRT file should be saved.\n", 322 | " \"\"\"\n", 323 | " with open(output_file_path, 'w') as file:\n", 324 | " for segment in translated_dict['segments']:\n", 325 | " # SRT segment number\n", 326 | " file.write(f\"{segment['id'] + 1}\\n\")\n", 327 | " # SRT timing format\n", 328 | " start_srt = seconds_to_srt_time(segment['start'])\n", 329 | " end_srt = seconds_to_srt_time(segment['end'])\n", 330 | " file.write(f\"{start_srt} --> {end_srt}\\n\")\n", 331 | " # Translated text\n", 332 | " file.write(f\"{segment['text']}\\n\\n\")\n", 333 | "\n", 334 | "def seconds_to_srt_time(seconds):\n", 335 | " \"\"\"\n", 336 | " Converts seconds to SRT time format (HH:MM:SS,MMM).\n", 337 | "\n", 338 | " Args:\n", 339 | " seconds (float): Time in seconds.\n", 340 | "\n", 341 | " Returns:\n", 342 | " str: Time in SRT format.\n", 343 | " \"\"\"\n", 344 | " hours, remainder = divmod(int(seconds), 3600)\n", 345 | " minutes, seconds = divmod(remainder, 60)\n", 346 | " milliseconds = int((seconds - int(seconds)) * 1000)\n", 347 | " return f\"{hours:02}:{minutes:02}:{int(seconds):02},{milliseconds:03}\"\n", 348 | "\n", 349 | "# Example usage:\n", 350 | "output_srt_path = 'translated_subtitles.srt' # Specify the output file name\n", 351 | "write_translated_srt(trans_dict, output_srt_path)" 352 | ], 353 | "metadata": { 354 | "id": "UD3-Kko5sYFE" 355 | }, 356 | "execution_count": null, 357 | "outputs": [] 358 | } 359 | ] 360 | } --------------------------------------------------------------------------------