pax_global_header00006660000000000000000000000064146260765570014534gustar00rootroot0000000000000052 comment=8c7552053b89b198fa4249a178d05f70e0649456 webvtt-py-0.5.1/000077500000000000000000000000001462607655700135005ustar00rootroot00000000000000webvtt-py-0.5.1/.github/000077500000000000000000000000001462607655700150405ustar00rootroot00000000000000webvtt-py-0.5.1/.github/workflows/000077500000000000000000000000001462607655700170755ustar00rootroot00000000000000webvtt-py-0.5.1/.github/workflows/ci.yml000066400000000000000000000013331462607655700202130ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install tox run: pip install tox - name: Run tox run: tox - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} file: coverage.xmlwebvtt-py-0.5.1/.gitignore000066400000000000000000000002221462607655700154640ustar00rootroot00000000000000# Compiled files __pycache__/ *.pyc # Distribution *.egg-info .eggs/ build/ dist/ # Testing .tox/ # PyCharm .idea # Documentation docs/_build/webvtt-py-0.5.1/.readthedocs.yaml000066400000000000000000000003171462607655700167300ustar00rootroot00000000000000version: "2" build: os: "ubuntu-22.04" tools: python: "3.12" sphinx: configuration: docs/source/conf.py python: install: - requirements: docs/requirements.txt - method: pip path: .webvtt-py-0.5.1/CHANGELOG.rst000066400000000000000000000101341462607655700155200ustar00rootroot00000000000000History ======= 0.5.1 (30-05-2024) ------------------ * Added voice span support (#55) * Extended from_buffer support to allow BytesIO and also other format conversions (#32) * Fixed save SRT to not include cue tags, thanks to `@lilaboc `_ (#56) * Fixed saved caption to include a line break after the last caption as per standard (#49) 0.5.0 (15-05-2024) ------------------ * Added styles support * Added comments support * Added from_string * Added iterate over a slice of captions * Refactor of the library * Parser is no longer strict and ignores malformed blocks * Improved BOM support allowing to keep the BOM or remove it * Deprecated read_buffer in favor of from_buffer * Removed support for old versions of Python: 3.4, 3.5 and 3.6 0.4.6 (18-11-2020) ------------------ * Add capability to get WebVTT formatted content without an output file, thanks to `@DawoudSheraz `_ (#34) * Add Python 3.9 support 0.4.5 (09-04-2020) ------------------ * Fix issue reading buffer 0.4.4 (27-03-2020) ------------------ * Allow parsing empty SBV captions, thanks to `@ishunyu `_ (#26) * Fix invalid time cues, thanks to `@sontek `_ (#19) * Enable pytest as test runner, thanks to `@sontek `_ (#20) * Packaging improvements * Added Python 3.8 support * Improve parsing empty lines 0.4.3 (22-11-2019) ------------------ * Parsing improvements, thanks to `@sontek `_ (#18) * Add support for reading content from a file-like object, thanks to `@omerholz `_ (#23) * Documentation fixes thanks to `@sontek `_ (#22) and `@netcmcc `_ (#24) 0.4.2 (08-06-2018) ------------------ * Renamed and reorganized few of the modules * Parsing methods are now class methods: read, from_srt and from_sbv * Improved usability with the addition of shortcuts to avoid instantiating the classes so we can do: import webvtt webvtt.read('captions.vtt') # this will return a WebVTT instance 0.4.1 (24-12-2017) ------------------ * Support for saving cue identifiers 0.4.0 (18-09-2017) ------------------ The main goal of this release is a refactor of the WebVTT parser to be able to parse easier and give support to new features of the format. New features: * Support for cue identifiers * Support for parsing WebVTT captions with comments * Support for parsing WebVTT captions with Style blocks * Support for BOM in caption files * Added method to write the captions to an opened file * Convert WebVTT to SRT format * Ignore empty captions in SRT format Other: * Refactored WebVTT parser 0.3.3 (23-08-2017) ------------------ The text for the caption is now returned clean (tags removed). The cue text could contain tags like: * timestamp tags: *<00:19.000>* * class tags: *text* * and others... **Important**: It currently removes any tag present in the cue text. For example would be removed. Also a new attribute is available on captions to retrieve the text without cleaning tags: **raw_text** 0.3.2 (11-08-2017) ------------------ The goal of this release if to allow the WebVTT parser to be able to read caption files that contain metadata headers that extend to more than one line. 0.3.1 (08-08-2017) ------------------ * Made hours in WebVTT parser optional as per specs. * Added support to parse WebVTT files that contain metadata headers. 0.3.0 (02-06-2016) ------------------ New features: * Added support for YouTube SBV captions. * Added easy iteration to WebVTT class. * New CLI command for segmenting captions for HLS. Other: * Improved parsers to reuse functionality. * Added an exception for invalid timestamps in captions. * Added an exception when saving without a filename. 0.2.0 (23-05-2016) ------------------ * Refactor of the main module and parsers. 0.1.0 (20-05-2016) ------------------ This module is released with the following initial features: * Read/Edit/Write WebVTT captions. * Read SRT captions and convert to WebVTT. * Segment WebVTT files for captioning HLS video. webvtt-py-0.5.1/LICENSE000066400000000000000000000020601462607655700145030ustar00rootroot00000000000000MIT License Copyright (c) 2016 Alejandro Mendez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.webvtt-py-0.5.1/MANIFEST.in000066400000000000000000000001441462607655700152350ustar00rootroot00000000000000include LICENSE include README.rst graft tests global-exclude __pycache__ global-exclude *.py[co] webvtt-py-0.5.1/Makefile000066400000000000000000000003251462607655700151400ustar00rootroot00000000000000build: rm -rf dist mkdir dist python setup.py sdist bdist_wheel twine check dist/* release_test: build twine upload -r testpypi dist/* release: build twine upload dist/* .PHONY: build release_test releasewebvtt-py-0.5.1/README.rst000066400000000000000000000045471462607655700152010ustar00rootroot00000000000000webvtt-py ========= |pypi| |pyversions| |license| |coverage| |build-status| |docs-status| |downloads| ``webvtt-py`` is a Python module for reading/writing WebVTT_ caption files. It also features caption segmentation useful when captioning `HLS videos`_. Documentation is available at http://webvtt-py.readthedocs.io. .. _`WebVTT`: http://dev.w3.org/html5/webvtt/ .. _`HLS videos`: https://tools.ietf.org/html/draft-pantos-http-live-streaming-19 Installation ------------ :: $ pip install webvtt-py Usage ----- .. code-block:: python import webvtt for caption in webvtt.read('captions.vtt'): print(caption.start) print(caption.end) print(caption.text) Segmenting for HLS ------------------ .. code-block:: python import webvtt webvtt.segment('captions.vtt', 'output/path') Converting captions from other formats -------------------------------------- Supported formats: * SubRip (.srt) * YouTube SBV (.sbv) .. code-block:: python import webvtt webvtt = webvtt.from_srt('captions.srt') webvtt.save() # one liner if we just need to convert without editing webvtt.from_sbv('captions.sbv').save() CLI --- Caption segmentation is also available from the command line: :: $ webvtt segment captions.vtt --output output/path License ------- Licensed under the MIT License. .. |pypi| image:: https://img.shields.io/pypi/v/webvtt-py.svg :target: https://pypi.python.org/pypi/webvtt-py .. |pyversions| image:: https://img.shields.io/pypi/pyversions/webvtt-py.svg :alt: Supported Python versions :target: https://pypi.python.org/pypi/webvtt-py .. |license| image:: https://img.shields.io/pypi/l/webvtt-py.svg :alt: MIT License :target: https://opensource.org/licenses/MIT .. |coverage| image:: https://codecov.io/gh/glut23/webvtt-py/graph/badge.svg?branch=master :target: https://codecov.io/gh/glut23/webvtt-py .. |build-status| image:: https://github.com/glut23/webvtt-py/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/glut23/webvtt-py/actions/workflows/ci.yml .. |docs-status| image:: https://readthedocs.org/projects/webvtt-py/badge/?version=latest :target: http://webvtt-py.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |downloads| image:: https://static.pepy.tech/badge/webvtt-py :target: https://pepy.tech/project/webvtt-py :alt: Downloadswebvtt-py-0.5.1/docs/000077500000000000000000000000001462607655700144305ustar00rootroot00000000000000webvtt-py-0.5.1/docs/Makefile000066400000000000000000000011751462607655700160740ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)webvtt-py-0.5.1/docs/make.bat000066400000000000000000000014351462607655700160400ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popdwebvtt-py-0.5.1/docs/requirements.txt000066400000000000000000000000451462607655700177130ustar00rootroot00000000000000sphinx==7.3.7 sphinx-rtd-theme==2.0.0webvtt-py-0.5.1/docs/source/000077500000000000000000000000001462607655700157305ustar00rootroot00000000000000webvtt-py-0.5.1/docs/source/conf.py000066400000000000000000000017361462607655700172360ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. import sys from pathlib import Path from datetime import date # Add parent dir to path parent_directory = Path(__file__).parent.parent.parent.resolve() sys.path.append(str(parent_directory)) import webvtt # noqa # -- Project information project = 'webvtt-py' copyright = f'2016-{date.today().year}, {webvtt.__author__}' author = webvtt.__author__ release = webvtt.__version__ version = webvtt.__version__ # -- General configuration extensions = [ 'sphinx.ext.duration', 'sphinx.ext.doctest', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', ] intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None), 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), } intersphinx_disabled_domains = ['std'] templates_path = ['_templates'] # -- Options for HTML output html_theme = 'sphinx_rtd_theme' # -- Options for EPUB output epub_show_urls = 'footnote'webvtt-py-0.5.1/docs/source/history.rst000066400000000000000000000000401462607655700201550ustar00rootroot00000000000000.. include:: ../../CHANGELOG.rstwebvtt-py-0.5.1/docs/source/index.rst000066400000000000000000000002661462607655700175750ustar00rootroot00000000000000webvtt-py ========= Contents: .. toctree:: :maxdepth: 2 quickstart usage package history Indices and tables ================== * :ref:`genindex` * :ref:`modindex`webvtt-py-0.5.1/docs/source/package.rst000066400000000000000000000012041462607655700200520ustar00rootroot00000000000000webvtt-py package reference =========================== webvtt.webvtt ------------- .. automodule:: webvtt.webvtt :members: webvtt.segmenter ---------------- .. automodule:: webvtt.segmenter :members: webvtt.cli ---------- .. automodule:: webvtt.cli :members: webvtt.generic -------------- .. automodule:: webvtt.structures :members: webvtt.parsers -------------- .. automodule:: webvtt.parsers :members: :show-inheritance: webvtt.writers -------------- .. automodule:: webvtt.writers :members: :show-inheritance: webvtt.exceptions ----------------- .. automodule:: webvtt.exceptions :members: webvtt-py-0.5.1/docs/source/quickstart.rst000066400000000000000000000006371462607655700206620ustar00rootroot00000000000000Quickstart ========== Installation ------------ You can install ``webvtt-py`` with pip: :: $ pip install webvtt-py To install with easy_install: :: $ easy_install webvtt-py Requirements ------------ This module requires Python 3.7+. Source code ----------- This project is hosted on `GitHub`_. .. _`GitHub`: https://github.com/glut23/webvtt-py License ------- Licensed under the MIT License. webvtt-py-0.5.1/docs/source/usage.rst000066400000000000000000000173241462607655700175750ustar00rootroot00000000000000Usage ===== Reading WebVTT caption files ---------------------------- .. code-block:: python import webvtt # we can iterate over the captions for caption in webvtt.read('captions.vtt'): print(caption.start) # start timestamp in text format print(caption.end) # end timestamp in text format print(caption.text) # caption text print(caption.voice) # voice span if present # you can also iterate over the lines of a particular caption for line in vtt[0].lines: print(line) # caption text is returned clean without class tags # we can access the raw text of a caption with raw_text >>> vtt[0].text 'This is a caption text' >>> vtt[0].raw_text 'This is a caption text' # caption identifiers >>> vtt[0].identifier 'chapter 1' Reading WebVTT caption files from a file-like object ---------------------------------------------------- .. code-block:: python import webvtt import requests from io import StringIO payload = requests.get('http://subtitles.com/1234.vtt').text() buffer = StringIO(payload) for caption in webvtt.from_buffer(buffer): print(caption.start) print(caption.end) print(caption.text) Reading WebVTT caption files from a BytesIO object -------------------------------------------------- .. code-block:: python import webvtt from io import BytesIO with open('captions.vtt', 'rb') as f: buffer = BytesIO(f.read()) for caption in webvtt.from_buffer(buffer): print(caption.start) print(caption.end) print(caption.text) Reading caption files in other formats from a BytesIO object ------------------------------------------------------------ .. code-block:: python import webvtt from io import BytesIO with open('captions.srt', 'rb') as f: buffer = BytesIO(f.read()) # formats supported: vtt, srt, sbv for caption in webvtt.from_buffer(buffer, format='srt'): print(caption.start) print(caption.end) print(caption.text) Reading WebVTT captions from a string ------------------------------------- .. code-block:: python import webvtt import textwrap vtt = webvtt.from_string(textwrap.dedent(""" WEBVTT 00:00:00.500 --> 00:00:07.000 Caption #1 00:00:07.000 --> 00:00:11.890 Caption #2 line 1 Caption #2 line 2 00:00:11.890 --> 00:00:16.320 Caption #3 """).strip() ) for caption in vtt: print(caption.start) print(caption.end) print(caption.text) Iterate a slice of captions --------------------------- .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') for caption in vtt.iter_slice(start='00:00:11.000', end='00:00:27.000' ) print(caption.start) print(caption.end) print(caption.text) Creating captions ----------------- .. code-block:: python from webvtt import WebVTT, Caption vtt = WebVTT() # creating a caption with a list of lines caption = Caption( '00:00:00.500', '00:00:07.000', ['Caption line 1', 'Caption line 2'] ) # an identifier can be assigned caption.identifier = 'chapter 1' # adding a caption vtt.captions.append(caption) # creating another caption with a text caption = Caption( '00:00:07.000', '00:00:11.890', 'Caption line 1\nCaption line 2' ) vtt.captions.append(caption) Manipulating captions --------------------- .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') # update start timestamp vtt[0].start = '00:00:01.250' # update end timestamp vtt[0].end = '00:00:03.890' # update caption text vtt[0].text = 'New caption text' # delete a caption del vtt.captions[2] Saving captions --------------- .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') # save to the same file vtt.save() # save to a different file vtt.save('new_captions.vtt') # you can save to a file path vtt.save('other/folder/new_captions') # if there is a filename present in the object we can target a folder vtt.save('other/folder) # write to an opened file with open('other_captions.vtt', 'w') as f: vtt.write(f) Retrieving WebVTT formatted captions ------------------------------------ WebVTT content can be retrieved without an output file: .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') # print the content in WebVTT format print(vtt.content) Converting captions ------------------- You can read captions from the following formats: * SubRip (.srt) * YouTube SBV (.sbv) .. code-block:: python import webvtt # read captions from SRT format vtt = webvtt.from_srt('captions.srt') # save the captions in WebVTT format vtt.save() # the conversion can be done chaining the method calls webvtt.from_srt('captions.srt').save() # the same for SBV format vtt = webvtt.from_sbv('captions.sbv') Convert WebVTT captions to other formats: * SubRip (.srt) .. code-block:: python import webvtt # save in SRT format vtt = webvtt.read('captions.vtt') vtt.save_as_srt() # write to an opened file in SRT format with open('captions.srt', 'w') as f: vtt.write(f, format='srt') WebVTT files with Byte Order Mark (BOM) --------------------------------------- When the WebVTT file has BOM, saving it will keep the BOM: .. code-block:: python import webvtt vtt = webvtt.read('captions_with_bom.vtt') # saved file keeps the BOM vtt.save() Add a BOM to a file without it: .. code-block:: python import webvtt vtt = webvtt.read('captions_without_bom.vtt', add_bom=True ) # saved file has BOM vtt.save() Remove the BOM from a file: .. code-block:: python import webvtt vtt = webvtt.read('captions_with_bom.vtt') # saved file does not have BOM vtt.save(add_bom=False) Save file with a different encoding: .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') vtt.save(encoding='utf-32-le') # save in different encoding with BOM vtt.save(encoding='utf-32-le', add_bom=True ) WebVTT Styles ------------- .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') for style in vtt.styles: print(style.text) # retrieve list of lines print(style.lines) Adding styles: .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') vtt.styles.append( webvtt.Style('::cue(b) {\n color: peachpuff;\n}') ) # list of lines is supported vtt.styles.append( webvtt.Style(['::cue(b) {', ' color: peachpuff;', '}' ]) ) Updating styles: .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') vtt.styles[0].lines[1] = ' color: papayawhip;' WebVTT Comments --------------- Comments can be added or retrieved from different items: .. code-block:: python import webvtt vtt = webvtt.read('captions.vtt') # comments from the top of the file print(vtt.header_comments) # comments from the bottom of the file print(vtt.footer_comments) # comments in a style print(vtt.styles[0].comments) # comments in a caption print(vtt.captions[0].comments) # comments are just a list of strings vtt.captions[5].comments.append('caption for review') webvtt-py-0.5.1/setup.cfg000066400000000000000000000011251462607655700153200ustar00rootroot00000000000000[metadata] description_file = README.rst [flake8] doctests = true radon-max-cc=10 [tox:tox] envlist = codestyle py coverage isolated_build = True [coverage:run] source = webvtt branch = true [coverage:report] fail_under = 100 [testenv] deps = coverage description = run the tests and provide coverage metrics commands = coverage run -m unittest discover [testenv:codestyle] deps = flake8 flake8-docstrings radon mypy commands = flake8 webvtt setup.py mypy webvtt [testenv:coverage] deps = coverage commands = coverage xml coverage report webvtt-py-0.5.1/setup.py000066400000000000000000000030501462607655700152100ustar00rootroot00000000000000"""webvtt-py setuptools configuration.""" import re import pathlib from setuptools import setup, find_packages version, author, author_email = ( re.search( rf'__{name}__ = \'(.*?)\'', pathlib.Path('webvtt/__init__.py').read_text() ).group(1) for name in ('version', 'author', 'author_email') ) setup( name='webvtt-py', version=version, description='WebVTT reader, writer and segmenter', long_description=pathlib.Path('README.rst').read_text(), long_description_content_type='text/x-rst', author=author, author_email=author_email, url='https://github.com/glut23/webvtt-py', packages=find_packages('.', exclude=['tests']), include_package_data=True, entry_points={ 'console_scripts': [ 'webvtt=webvtt.cli:main' ] }, license='MIT', python_requires='>=3.7', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', ], keywords='webvtt captions', ) webvtt-py-0.5.1/tests/000077500000000000000000000000001462607655700146425ustar00rootroot00000000000000webvtt-py-0.5.1/tests/__init__.py000066400000000000000000000000001462607655700167410ustar00rootroot00000000000000webvtt-py-0.5.1/tests/samples/000077500000000000000000000000001462607655700163065ustar00rootroot00000000000000webvtt-py-0.5.1/tests/samples/captions_with_bom.vtt000066400000000000000000000003051462607655700225530ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4webvtt-py-0.5.1/tests/samples/comments.vtt000066400000000000000000000006131462607655700206720ustar00rootroot00000000000000WEBVTT - Translation of that film I like NOTE This translation was done by Kyle so that some friends can watch it with their parents. 1 00:02:15.000 --> 00:02:20.000 - Ta en kopp varmt te. - Det är inte varmt. 2 00:02:20.000 --> 00:02:25.000 - Har en kopp te. - Det smakar som te. NOTE This last line may not translate well. 3 00:02:25.000 --> 00:02:30.000 - Ta en kopp NOTE end of filewebvtt-py-0.5.1/tests/samples/cue_tags.vtt000066400000000000000000000003521462607655700206370ustar00rootroot00000000000000WEBVTT 00:16.500 --> 00:18.500 When the moon <00:17.500>hits your eye 00:00:18.500 --> 00:00:20.500 Like a <00:19.000>big-a <00:19.500>pizza <00:20.000>pie 00:00:20.500 --> 00:00:21.500 That's <00:00:21.000>amorewebvtt-py-0.5.1/tests/samples/empty.vtt000066400000000000000000000000001462607655700201710ustar00rootroot00000000000000webvtt-py-0.5.1/tests/samples/invalid.vtt000066400000000000000000000005071462607655700204750ustar00rootroot0000000000000000:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7webvtt-py-0.5.1/tests/samples/invalid_format.sbv000066400000000000000000000000111462607655700220100ustar00rootroot00000000000000some textwebvtt-py-0.5.1/tests/samples/invalid_format1.srt000066400000000000000000000000011462607655700221060ustar00rootroot000000000000001webvtt-py-0.5.1/tests/samples/invalid_format2.srt000066400000000000000000000000111462607655700221100ustar00rootroot00000000000000some textwebvtt-py-0.5.1/tests/samples/invalid_format3.srt000066400000000000000000000000131462607655700221130ustar00rootroot000000000000001 some textwebvtt-py-0.5.1/tests/samples/invalid_format4.srt000066400000000000000000000000471462607655700221230ustar00rootroot00000000000000some text 00:00:00,500 --> 00:00:07,000webvtt-py-0.5.1/tests/samples/invalid_timeframe.sbv000066400000000000000000000003131462607655700224760ustar00rootroot000000000000000:00:00.378,0:00:11.378 Caption text #1 0:00:11.378,0:00:12.305 Caption text #2 0:00:13.f89,0:00:15.489 Caption text #3 0:00:15.489,0:00:16.234 Caption text #4 0:00:17.484,0:00:16.543 Caption text #5webvtt-py-0.5.1/tests/samples/invalid_timeframe.srt000066400000000000000000000003631462607655700225210ustar00rootroot000000000000001 00:00:00,500 --> 00:00:07,000 Caption text #1 2 00:00:07,000 --> 00:00:11.890 Caption text #2 3 00:00:11,890 --> 00:00:16,320 Caption text #3 4 00:00:16,320 --> 00:00:21,580 Caption text #4 5 00:00:21,580 --> 00:00:23,880 Caption text #5webvtt-py-0.5.1/tests/samples/invalid_timeframe.vtt000066400000000000000000000005171462607655700225270ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:1d.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7webvtt-py-0.5.1/tests/samples/invalid_timeframe_in_cue_text.vtt000066400000000000000000000002611462607655700251110ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:10.890 00:00:07.000 --> 00:00:10.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3webvtt-py-0.5.1/tests/samples/metadata_headers.vtt000066400000000000000000000002001462607655700223100ustar00rootroot00000000000000WEBVTT Kind: captions Language: en 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2webvtt-py-0.5.1/tests/samples/metadata_headers_multiline.vtt000066400000000000000000000002761462607655700244070ustar00rootroot00000000000000WEBVTT Kind: captions Language: en Style: ::cue(c.colorCCCCCC) { color: rgb(204, 204, 204); } ## 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2webvtt-py-0.5.1/tests/samples/missing_caption_text.sbv000066400000000000000000000002731462607655700232560ustar00rootroot000000000000000:00:00.378,0:00:11.378 Caption text #1 0:00:11.378,0:00:12.305 0:00:13.489,0:00:15.489 Caption text #3 0:00:15.489,0:00:16.234 Caption text #4 0:00:17.484,0:00:16.543 Caption text #5webvtt-py-0.5.1/tests/samples/missing_caption_text.srt000066400000000000000000000003441462607655700232730ustar00rootroot000000000000001 00:00:00,500 --> 00:00:07,000 Caption text #1 2 00:00:07,000 --> 00:00:11,890 3 00:00:11,890 --> 00:00:16,320 Caption text #3 4 00:00:16,320 --> 00:00:21,580 Caption text #4 5 00:00:21,580 --> 00:00:23,880 Caption text #5webvtt-py-0.5.1/tests/samples/missing_caption_text.vtt000066400000000000000000000003411462607655700232750ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5webvtt-py-0.5.1/tests/samples/missing_timeframe.sbv000066400000000000000000000002631462607655700225250ustar00rootroot000000000000000:00:00.378,0:00:11.378 Caption text #1 Caption text #2 0:00:13.489,0:00:15.489 Caption text #3 0:00:15.489,0:00:16.234 Caption text #4 0:00:17.484,0:00:16.543 Caption text #5webvtt-py-0.5.1/tests/samples/missing_timeframe.srt000066400000000000000000000003251462607655700225420ustar00rootroot000000000000001 00:00:00,500 --> 00:00:07,000 Caption text #1 2 Caption text #2 3 00:00:11,890 --> 00:00:16,320 Caption text #3 4 00:00:16,320 --> 00:00:21,580 Caption text #4 5 00:00:21,580 --> 00:00:23,880 Caption text #5webvtt-py-0.5.1/tests/samples/missing_timeframe.vtt000066400000000000000000000004611462607655700225500ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7webvtt-py-0.5.1/tests/samples/netflix_chicas_del_cable.vtt000077500000000000000000003526561462607655700240260ustar00rootroot00000000000000WEBVTT NOTE Netflix NOTE Profile: webvtt-lssdh-ios8 NOTE Date: 2017/04/16 15:44:03 NOTE SegmentIndex NOTE Segment=597.080 20887@419 153 NOTE Segment=592.000 23822@21306 173 NOTE Segment=572.880 25784@45128 183 NOTE Segment=575.160 18267@70912 131 NOTE Segment=591.040 26386@89179 191 NOTE Segment=220.440 4676@115565 34 NOTE /SegmentIndex 1 00:00:07.960 --> 00:00:09.480 position:50.00%,middle align:middle size:80.00% line:84.67% [Alba] En 1928, 2 00:00:09.640 --> 00:00:13.080 position:50.00%,middle align:middle size:80.00% line:79.33% las mujeres éramos algo así como adornos 3 00:00:13.240 --> 00:00:16.440 position:50.00%,middle align:middle size:80.00% line:79.33% que se llevaban a las fiestas para presumir de ellos. 4 00:00:19.720 --> 00:00:22.160 position:50.00%,middle align:middle size:80.00% line:84.67% Objetos sin poder de opinión ni decisión. 5 00:00:22.640 --> 00:00:24.760 position:50.00%,middle align:middle size:80.00% line:84.67% Es cierto que la vida no era fácil 6 00:00:24.920 --> 00:00:27.680 position:50.00%,middle align:middle size:80.00% line:79.33% para nadie, pero mucho menos si eras mujer. 7 00:00:29.320 --> 00:00:32.200 position:50.00%,middle align:middle size:80.00% line:84.67% Si eras mujer, en 1928 8 00:00:32.360 --> 00:00:35.160 position:50.00%,middle align:middle size:80.00% line:79.33% ser libre era algo que parecía inalcanzable. 9 00:00:35.320 --> 00:00:36.880 position:50.00%,middle align:middle size:80.00% line:84.67% Porque para la sociedad 10 00:00:37.040 --> 00:00:41.040 position:50.00%,middle align:middle size:80.00% line:79.33% las mujeres solo éramos amas de casa, madres, esposas... 11 00:00:42.000 --> 00:00:45.080 position:50.00%,middle align:middle size:80.00% line:79.33% No teníamos derecho a tener sueños ni ambiciones. 12 00:00:45.240 --> 00:00:49.800 position:50.00%,middle align:middle size:80.00% line:79.33% Para buscar un futuro muchas tenían que marcharse lejos. 13 00:00:49.960 --> 00:00:51.240 position:50.00%,middle align:middle size:80.00% line:84.67% En el bar de enfrente. 14 00:00:52.360 --> 00:00:53.400 position:50.00%,middle align:middle size:80.00% line:84.67% Tú no vas a ir 15 00:00:53.560 --> 00:00:54.600 position:50.00%,middle align:middle size:80.00% line:84.67% a ningún sitio. 16 00:00:54.760 --> 00:00:56.480 position:50.00%,middle align:middle size:80.00% line:84.67% Y otras tenían que enfrentarse 17 00:00:56.640 --> 00:00:59.480 position:50.00%,middle align:middle size:80.00% line:79.33% a las normas de una sociedad machista y retrógrada. 18 00:00:59.640 --> 00:01:02.000 position:50.00%,middle align:middle size:80.00% line:79.33% - Déjala. - Tú no te metas. ¡No te metas! 19 00:01:02.160 --> 00:01:06.600 position:50.00%,middle align:middle size:80.00% line:79.33% Al final, todas, ricas, pobres, queríamos lo mismo: 20 00:01:06.760 --> 00:01:09.160 position:50.00%,middle align:middle size:80.00% line:84.67% ser libres. Y si para eso 21 00:01:09.320 --> 00:01:11.960 position:50.00%,middle align:middle size:80.00% line:79.33% había que quebrantar la ley, estábamos dispuestas 22 00:01:12.120 --> 00:01:15.560 position:50.00%,middle align:middle size:80.00% line:79.33% a hacerlo sin importarnos las consecuencias. 23 00:01:21.960 --> 00:01:23.520 position:50.00%,middle align:middle size:80.00% line:84.67% Solo las que luchan por ellos 24 00:01:23.680 --> 00:01:25.680 position:50.00%,middle align:middle size:80.00% line:84.67% consiguen sus sueños. 25 00:01:36.560 --> 00:01:37.920 position:50.00%,middle align:middle size:80.00% line:84.67% Eso creíamos. 26 00:01:38.080 --> 00:01:39.840 position:50.00%,middle align:middle size:80.00% line:84.67% Lo que no sabíamos era 27 00:01:40.000 --> 00:01:42.440 position:50.00%,middle align:middle size:80.00% line:84.67% que el destino nos tenía preparadas 28 00:01:42.600 --> 00:01:44.040 position:50.00%,middle align:middle size:80.00% line:84.67% muchas sorpresas. 29 00:01:46.520 --> 00:01:47.600 position:50.00%,middle align:middle size:80.00% line:84.67% Vámonos. 30 00:01:47.760 --> 00:01:48.800 position:50.00%,middle align:middle size:80.00% line:84.67% Rápido. 31 00:01:50.040 --> 00:01:51.800 position:50.00%,middle align:middle size:80.00% line:84.67% Argentina nos espera. 32 00:01:51.960 --> 00:01:54.120 position:50.00%,middle align:middle size:80.00% line:84.67% [ríen emocionadas] 33 00:01:54.280 --> 00:01:55.400 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Corre! 34 00:01:56.400 --> 00:01:59.800 position:50.00%,middle align:middle size:80.00% line:79.33% - Nunca has montado en barco. - Así que pensabais escaparos... 35 00:02:00.560 --> 00:02:03.560 position:50.00%,middle align:middle size:80.00% line:79.33% ¡Ni se te ocurra moverte! Esto es entre Gimena y yo. 36 00:02:03.720 --> 00:02:06.640 position:50.00%,middle align:middle size:80.00% line:79.33% - Mi amiga no quiere volver a verte. - Que me lo diga ella. 37 00:02:06.800 --> 00:02:08.800 position:50.00%,middle align:middle size:80.00% line:84.67% Como grites, te juro que te mato. 38 00:02:08.960 --> 00:02:10.560 position:50.00%,middle align:middle size:80.00% line:84.67% Tranquilízate, por favor. 39 00:02:10.720 --> 00:02:13.840 position:50.00%,middle align:middle size:80.00% line:79.33% [Pedro] Lo haré cuando le digas a tu amiga que te vienes conmigo. 40 00:02:14.000 --> 00:02:15.880 position:50.00%,middle align:middle size:80.00% line:84.67% No se va a ir contigo. 41 00:02:16.040 --> 00:02:19.920 position:50.00%,middle align:middle size:80.00% line:79.33% Va a hacer lo que yo diga. Si no, primero la mato a ella y luego a ti. 42 00:02:20.080 --> 00:02:23.040 position:50.00%,middle align:middle size:80.00% line:79.33% Está bien, haré lo que quieras, pero baja el arma. 43 00:02:23.200 --> 00:02:24.480 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Sí? 44 00:02:29.520 --> 00:02:31.040 position:50.00%,middle align:middle size:80.00% line:84.67% [grita con rabia] 45 00:02:31.200 --> 00:02:32.360 position:50.00%,middle align:middle size:80.00% line:84.67% [dos disparos] 46 00:02:32.520 --> 00:02:33.720 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Ah! 47 00:02:37.880 --> 00:02:39.920 position:50.00%,middle align:middle size:80.00% line:84.67% [respiración jadeante] 48 00:02:41.920 --> 00:02:45.560 position:50.00%,middle align:middle size:80.00% line:84.67% [respira agitada] 49 00:02:51.040 --> 00:02:52.240 position:50.00%,middle align:middle size:80.00% line:84.67% Gimena... 50 00:02:52.400 --> 00:02:53.840 position:50.00%,middle align:middle size:80.00% line:84.67% Gimena, no, por favor. 51 00:02:54.560 --> 00:02:58.240 position:50.00%,middle align:middle size:80.00% line:84.67% Mírame. Tú no tenías que estar aquí. 52 00:02:58.400 --> 00:03:00.360 position:50.00%,middle align:middle size:80.00% line:79.33% - No debería haberte traído. - Chist. 53 00:03:00.520 --> 00:03:02.760 position:50.00%,middle align:middle size:80.00% line:79.33% - Lo siento, lo siento. - Era nuestro sueño. 54 00:03:02.920 --> 00:03:04.680 position:50.00%,middle align:middle size:80.00% line:84.67% Es, es nuestro sueño. 55 00:03:05.520 --> 00:03:07.000 position:50.00%,middle align:middle size:80.00% line:84.67% Lo sigue siendo. 56 00:03:07.160 --> 00:03:08.920 position:50.00%,middle align:middle size:80.00% line:79.33% - Mujeres libres... - Sí, sí. 57 00:03:11.640 --> 00:03:13.640 position:50.00%,middle align:middle size:80.00% line:84.67% [llora] 58 00:03:22.520 --> 00:03:24.520 position:50.00%,middle align:middle size:80.00% line:84.67% [truenos] 59 00:03:41.760 --> 00:03:45.000 position:50.00%,middle align:middle size:80.00% line:79.33% [Beltrán] Así que estabas en la calle con dos cadáveres, 60 00:03:45.160 --> 00:03:47.160 position:50.00%,middle align:middle size:80.00% line:84.67% pero no tienes nada que ver. 61 00:03:50.760 --> 00:03:52.640 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Y por qué debería creerte? 62 00:03:52.800 --> 00:03:55.480 position:50.00%,middle align:middle size:80.00% line:79.33% - Porque digo la verdad. - Alba Romero, 63 00:03:55.640 --> 00:03:59.280 position:50.00%,middle align:middle size:80.00% line:79.33% 25 años, antecedentes de hurto y estafa. 64 00:03:59.440 --> 00:04:01.480 position:50.00%,middle align:middle size:80.00% line:84.67% Y ahora además asesinato... 65 00:04:02.280 --> 00:04:05.080 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Sabes lo que les pasa a los acusados de asesinato? 66 00:04:05.640 --> 00:04:07.040 position:50.00%,middle align:middle size:80.00% line:84.67% El garrote vil. 67 00:04:07.200 --> 00:04:08.440 position:50.00%,middle align:middle size:80.00% line:84.67% [solloza] 68 00:04:08.600 --> 00:04:11.200 position:50.00%,middle align:middle size:80.00% line:79.33% A no ser que colaboren con la justicia... 69 00:04:19.000 --> 00:04:20.279 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Cómo? 70 00:04:20.440 --> 00:04:23.280 position:50.00%,middle align:middle size:80.00% line:79.33% Pues haciendo lo que mejor sabes hacer. 71 00:04:24.840 --> 00:04:25.920 position:50.00%,middle align:middle size:80.00% line:84.67% Robar. 72 00:04:27.280 --> 00:04:28.760 position:50.00%,middle align:middle size:80.00% line:84.67% Pero ahora para mí. 73 00:04:29.400 --> 00:04:30.680 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Qué me dices? 74 00:04:34.520 --> 00:04:36.520 position:50.00%,middle align:middle size:80.00% line:84.67% [tema musical] 75 00:05:34.600 --> 00:05:38.320 position:50.00%,middle align:middle size:80.00% line:79.33% [Alba] Y allí estaba: el nuevo rascacielos de la única compañía 76 00:05:38.480 --> 00:05:40.120 position:50.00%,middle align:middle size:80.00% line:84.67% de telefonía del país. 77 00:05:41.080 --> 00:05:44.080 position:50.00%,middle align:middle size:80.00% line:79.33% 13 plantas, más de 30 millones de metros de cable, 78 00:05:44.240 --> 00:05:45.680 position:50.00%,middle align:middle size:80.00% line:84.67% miles de trabajadores: 79 00:05:45.840 --> 00:05:50.840 position:50.00%,middle align:middle size:80.00% line:10.00% ejecutivos, telefonistas, 800 puestos de trabajo libres 80 00:05:51.000 --> 00:05:52.400 position:50.00%,middle align:middle size:80.00% line:10.00% y una caja fuerte 81 00:05:52.560 --> 00:05:54.920 position:50.00%,middle align:middle size:80.00% line:10.00% llena de dinero que robar. 82 00:05:55.080 --> 00:05:56.720 position:50.00%,middle align:middle size:80.00% line:10.00% [Sara] Por favor, señoritas... 83 00:05:57.560 --> 00:05:59.840 position:50.00%,middle align:middle size:80.00% line:10.00% Bienvenidas a la nueva sede central 84 00:06:00.000 --> 00:06:01.640 position:50.00%,middle align:middle size:80.00% line:10.00% de la Compañía de Telefonía. 85 00:06:01.800 --> 00:06:05.400 position:50.00%,middle align:middle size:80.00% line:10.00% Disculpen tanto ir y venir, aún estamos ultimando la mudanza. 86 00:06:05.560 --> 00:06:08.520 position:50.00%,middle align:middle size:80.00% line:10.00% Desde hoy, muchos de los servicios de la compañía 87 00:06:08.680 --> 00:06:11.800 position:50.00%,middle align:middle size:80.00% line:10.00% y las conexiones telefónicas se han centralizado aquí, 88 00:06:11.960 --> 00:06:14.240 position:50.00%,middle align:middle size:80.00% line:84.67% así que les pediría que presten atención. 89 00:06:14.400 --> 00:06:16.960 position:50.00%,middle align:middle size:80.00% line:79.33% Las candidatas para los puestos a telefonistas, 90 00:06:17.120 --> 00:06:18.400 position:50.00%,middle align:middle size:80.00% line:84.67% a la izquierda, en fila, 91 00:06:18.560 --> 00:06:20.760 position:50.00%,middle align:middle size:80.00% line:84.67% y vayan diciéndome sus nombres. 92 00:06:20.920 --> 00:06:24.120 position:50.00%,middle align:middle size:80.00% line:79.33% Bienvenida a la Compañía de Telefonía, ¿puedo ayudarla? 93 00:06:24.280 --> 00:06:27.400 position:50.00%,middle align:middle size:80.00% line:79.33% - Buenas, vengo para las pruebas... - De telefonista supongo. 94 00:06:27.560 --> 00:06:29.200 position:50.00%,middle align:middle size:80.00% line:79.33% - Sí. - Si me dice su nombre, la busco. 95 00:06:29.360 --> 00:06:30.360 position:50.00%,middle align:middle size:80.00% line:79.33% - Lidia. - Lidia... 96 00:06:30.520 --> 00:06:32.200 position:50.00%,middle align:middle size:80.00% line:79.33% - Lidia Aguilar. - Aguilar. 97 00:06:32.360 --> 00:06:35.800 position:50.00%,middle align:middle size:80.00% line:79.33% Pues lo lamento, no se encuentra entre las preseleccionadas. 98 00:06:35.960 --> 00:06:39.240 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Preseleccionadas? Pensaba que con presentarnos hoy... 99 00:06:39.400 --> 00:06:41.920 position:50.00%,middle align:middle size:80.00% line:79.33% No es la única, ha habido un aluvión de solicitudes 100 00:06:42.080 --> 00:06:44.120 position:50.00%,middle align:middle size:80.00% line:84.67% y ha habido que hacer una preselección. 101 00:06:44.280 --> 00:06:45.880 position:50.00%,middle align:middle size:80.00% line:84.67% Lo siento muchísimo, Lidia. 102 00:06:46.040 --> 00:06:47.760 position:50.00%,middle align:middle size:80.00% line:79.33% - Gracias por venir. - Vaya... 103 00:06:47.920 --> 00:06:51.480 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Su nombre es? - Lidia Aguilar. 104 00:06:54.720 --> 00:06:56.760 position:50.00%,middle align:middle size:80.00% line:79.33% Las pruebas darán comienzo a las 12 en punto. 105 00:06:56.920 --> 00:06:59.840 position:50.00%,middle align:middle size:80.00% line:79.33% Quien no esté dentro de la sala no podrá acceder a las pruebas. 106 00:07:00.000 --> 00:07:02.120 position:50.00%,middle align:middle size:80.00% line:84.67% Hay 390 solicitudes para 12 plazas. 107 00:07:02.280 --> 00:07:04.640 position:50.00%,middle align:middle size:80.00% line:84.67% 12 afortunadas que, les puedo asegurar, 108 00:07:04.800 --> 00:07:07.240 position:50.00%,middle align:middle size:80.00% line:84.67% conseguirán el mejor trabajo de sus vidas, 109 00:07:07.400 --> 00:07:08.760 position:50.00%,middle align:middle size:80.00% line:84.67% así que no se despisten. 110 00:07:08.920 --> 00:07:10.440 position:50.00%,middle align:middle size:80.00% line:84.67% Acompáñenme, por favor. 111 00:07:11.400 --> 00:07:12.640 position:50.00%,middle align:middle size:80.00% line:84.67% Están ustedes 112 00:07:12.800 --> 00:07:14.920 position:50.00%,middle align:middle size:80.00% line:84.67% en el edificio más moderno de Madrid. 113 00:07:15.080 --> 00:07:17.920 position:50.00%,middle align:middle size:80.00% line:79.33% 13 plantas dedicadas a la compañía de teléfonos. 114 00:07:18.080 --> 00:07:20.960 position:50.00%,middle align:middle size:80.00% line:79.33% En la planta uno podrán encontrar el Servicio de Atención al Público 115 00:07:21.120 --> 00:07:22.800 position:50.00%,middle align:middle size:80.00% line:84.67% y el Departamento Comercial. 116 00:07:22.960 --> 00:07:26.920 position:50.00%,middle align:middle size:80.00% line:79.33% En las plantas cuatro y cinco, los servicios administrativos. 117 00:07:27.080 --> 00:07:28.520 position:50.00%,middle align:middle size:80.00% line:84.67% En los pisos seis y siete, 118 00:07:28.680 --> 00:07:31.120 position:50.00%,middle align:middle size:80.00% line:79.33% las centralitas, donde las elegidas tendrán 119 00:07:31.280 --> 00:07:34.240 position:50.00%,middle align:middle size:80.00% line:79.33% el honor de trabajar. A continuación tenemos dos plantas 120 00:07:34.400 --> 00:07:35.920 position:50.00%,middle align:middle size:80.00% line:84.67% para las salas de máquinas, 121 00:07:36.000 --> 00:07:38.040 position:50.00%,middle align:middle size:80.00% line:84.67% Mantenimiento y despachos de Ingeniería. 122 00:07:38.200 --> 00:07:41.960 position:50.00%,middle align:middle size:80.00% line:79.33% Más arriba estarían las plantas destinadas a eventos y reuniones. 123 00:07:42.120 --> 00:07:44.360 position:50.00%,middle align:middle size:80.00% line:79.33% Y, por último, en la planta 13 del edificio, 124 00:07:44.520 --> 00:07:46.120 position:50.00%,middle align:middle size:80.00% line:84.67% los despachos de Dirección. 125 00:08:00.040 --> 00:08:02.520 position:50.00%,middle align:middle size:80.00% line:79.33% - Gracias por todo. - Gracias, señor Cifuentes. 126 00:08:03.720 --> 00:08:04.920 position:50.00%,middle align:middle size:80.00% line:84.67% Eh, disculpe. 127 00:08:33.440 --> 00:08:34.840 position:50.00%,middle align:middle size:80.00% line:84.67% Señorita... 128 00:08:35.000 --> 00:08:37.080 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Puedo saber lo que está haciendo aquí? 129 00:08:37.679 --> 00:08:40.200 position:50.00%,middle align:middle size:80.00% line:79.33% [cándida] Vengo a las pruebas de telefonista 130 00:08:40.360 --> 00:08:41.960 position:50.00%,middle align:middle size:80.00% line:84.67% y me habían dicho que era aquí, 131 00:08:42.120 --> 00:08:44.360 position:50.00%,middle align:middle size:80.00% line:79.33% pero es que no veo a nadie por ningún sitio. 132 00:08:45.000 --> 00:08:47.480 position:50.00%,middle align:middle size:80.00% line:79.33% [risita] Sí. Son aquí si lleva traje y chaqueta, es un hombre 133 00:08:47.560 --> 00:08:49.240 position:50.00%,middle align:middle size:80.00% line:84.67% y pretende ser ejecutivo de cuentas, 134 00:08:49.400 --> 00:08:51.120 position:50.00%,middle align:middle size:80.00% line:84.67% cosa que, por lo que veo, no es el caso. 135 00:08:51.280 --> 00:08:52.920 position:50.00%,middle align:middle size:80.00% line:84.67% Las pruebas son en la sexta planta 136 00:08:53.080 --> 00:08:55.840 position:50.00%,middle align:middle size:80.00% line:79.33% y le quedan dos minutos para que le cierren las puertas, 137 00:08:56.000 --> 00:08:58.720 position:50.00%,middle align:middle size:80.00% line:79.33% y en esta empresa lo más importante es la puntualidad. 138 00:08:58.880 --> 00:08:59.920 position:50.00%,middle align:middle size:80.00% line:84.67% Muchas gracias. 139 00:09:02.360 --> 00:09:04.400 position:50.00%,middle align:middle size:80.00% line:84.67% Muy bien. ¿Ricardo Rodríguez? 140 00:09:05.680 --> 00:09:07.240 position:50.00%,middle align:middle size:80.00% line:84.67% Eh, bonita... 141 00:09:07.400 --> 00:09:08.920 position:50.00%,middle align:middle size:80.00% line:84.67% Por la escalera mejor... 142 00:09:11.240 --> 00:09:12.240 position:50.00%,middle align:middle size:80.00% line:84.67% [risita] 143 00:09:24.320 --> 00:09:25.960 position:50.00%,middle align:middle size:80.00% line:84.67% [ambas gritan] 144 00:09:28.040 --> 00:09:30.400 position:50.00%,middle align:middle size:80.00% line:84.67% Ay, que nos han cerrado, estamos fuera. 145 00:09:30.560 --> 00:09:32.360 position:50.00%,middle align:middle size:80.00% line:84.67% Las pruebas empezaban a las 12. 146 00:09:32.520 --> 00:09:33.800 position:50.00%,middle align:middle size:80.00% line:84.67% Es que soy lo peor. 147 00:09:33.960 --> 00:09:36.920 position:50.00%,middle align:middle size:80.00% line:79.33% - Me perdí tres veces en el metro. - [Ángeles] No, no, no, no puede... 148 00:09:38.360 --> 00:09:40.200 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Sí? - Disculpe. 149 00:09:40.800 --> 00:09:43.120 position:50.00%,middle align:middle size:80.00% line:79.33% - Venimos a las pruebas. - Llegan tarde. 150 00:09:43.280 --> 00:09:46.680 position:50.00%,middle align:middle size:80.00% line:79.33% Lo sé. Sé que lo primero en esta empresa es la puntualidad. 151 00:09:46.840 --> 00:09:49.160 position:50.00%,middle align:middle size:80.00% line:79.33% Entonces, ¿para qué ha llamado a la puerta? 152 00:09:50.040 --> 00:09:52.320 position:50.00%,middle align:middle size:80.00% line:84.67% Para saber si lo segundo 153 00:09:52.480 --> 00:09:53.600 position:50.00%,middle align:middle size:80.00% line:84.67% es la comprensión. 154 00:09:57.080 --> 00:09:58.440 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Mírela! 155 00:09:58.880 --> 00:10:00.880 position:50.00%,middle align:middle size:80.00% line:84.67% Directamente desde la estación 156 00:10:01.040 --> 00:10:04.200 position:50.00%,middle align:middle size:80.00% line:79.33% con la misma maleta, 600 km para poder estar aquí ahora. 157 00:10:04.360 --> 00:10:05.800 position:50.00%,middle align:middle size:80.00% line:79.33% - 550. - Sí. 158 00:10:06.880 --> 00:10:10.320 position:50.00%,middle align:middle size:80.00% line:79.33% Y por solo un minuto va a volverse a casa con las manos vacías. 159 00:10:11.880 --> 00:10:12.840 position:50.00%,middle align:middle size:80.00% line:84.67% La culpa ha sido mía. 160 00:10:13.760 --> 00:10:15.160 position:50.00%,middle align:middle size:80.00% line:84.67% Señorita Millán... 161 00:10:16.960 --> 00:10:18.480 position:50.00%,middle align:middle size:80.00% line:84.67% [tartamudea] Lo siento mucho. 162 00:10:19.440 --> 00:10:21.160 position:50.00%,middle align:middle size:80.00% line:84.67% Yo me he ofrecido a acompañarlas 163 00:10:21.320 --> 00:10:23.360 position:50.00%,middle align:middle size:80.00% line:84.67% de paso que le traía la documentación 164 00:10:23.520 --> 00:10:25.960 position:50.00%,middle align:middle size:80.00% line:79.33% que me pidió usted y me he confundido de piso. 165 00:10:26.120 --> 00:10:27.320 position:50.00%,middle align:middle size:80.00% line:84.67% Se equivocó de piso... 166 00:10:27.480 --> 00:10:28.480 position:50.00%,middle align:middle size:80.00% line:84.67% Sí. 167 00:10:28.880 --> 00:10:31.640 position:50.00%,middle align:middle size:80.00% line:79.33% Su primera equivocación desde que trabaja para mí. 168 00:10:31.800 --> 00:10:34.280 position:50.00%,middle align:middle size:80.00% line:79.33% Nuestros trabajadores son sumamente comprensivos. 169 00:10:34.440 --> 00:10:37.000 position:50.00%,middle align:middle size:80.00% line:79.33% Aunque, para su desgracia, no depende de Ángeles 170 00:10:37.160 --> 00:10:39.360 position:50.00%,middle align:middle size:80.00% line:79.33% que crucen o no esta puerta, depende de mí. 171 00:10:49.040 --> 00:10:50.640 position:50.00%,middle align:middle size:80.00% line:79.33% - Gracias. - Sí, muchas gracias. 172 00:10:50.800 --> 00:10:53.040 position:50.00%,middle align:middle size:80.00% line:79.33% Si me presento en el pueblo sin haber hecho... 173 00:10:53.200 --> 00:10:54.600 position:50.00%,middle align:middle size:80.00% line:79.33% - Ángeles, ¿verdad? - Ajá. 174 00:10:54.760 --> 00:10:57.880 position:50.00%,middle align:middle size:80.00% line:79.33% Con ese nombre, solamente podías ser nuestro ángel de la guarda. 175 00:10:58.040 --> 00:10:59.640 position:50.00%,middle align:middle size:80.00% line:84.67% Yo no diría tanto... 176 00:11:01.680 --> 00:11:03.080 position:50.00%,middle align:middle size:80.00% line:84.67% Perdón. Aquí. 177 00:11:04.680 --> 00:11:05.800 position:50.00%,middle align:middle size:80.00% line:84.67% Disculpe. 178 00:11:07.000 --> 00:11:10.120 position:50.00%,middle align:middle size:80.00% line:79.33% No habría alguna posibilidad de pasar las pruebas 179 00:11:10.280 --> 00:11:12.040 position:50.00%,middle align:middle size:80.00% line:79.33% - sin hacerlas, ¿verdad? - No. 180 00:11:12.200 --> 00:11:13.240 position:50.00%,middle align:middle size:80.00% line:84.67% Buenos días. 181 00:11:13.800 --> 00:11:15.280 position:50.00%,middle align:middle size:80.00% line:84.67% Señoritas... 182 00:11:19.240 --> 00:11:22.000 position:50.00%,middle align:middle size:80.00% line:79.33% Veo que hoy va a ser un gran día para esta compañía. 183 00:11:22.480 --> 00:11:24.360 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Vienen todas a las pruebas? 184 00:11:25.320 --> 00:11:27.600 position:50.00%,middle align:middle size:80.00% line:84.67% Me parece que va a ser muy difícil elegir. 185 00:11:28.320 --> 00:11:30.240 position:50.00%,middle align:middle size:80.00% line:84.67% Ojalá pudiéramos comprar más centralitas 186 00:11:30.400 --> 00:11:31.720 position:50.00%,middle align:middle size:80.00% line:84.67% para darles trabajo a todas. 187 00:11:31.800 --> 00:11:33.240 position:50.00%,middle align:middle size:80.00% line:79.33% [Ángeles ríe] [lapicero cae] 188 00:11:39.040 --> 00:11:40.200 position:50.00%,middle align:middle size:80.00% line:84.67% Gracias. 189 00:11:40.360 --> 00:11:43.880 position:50.00%,middle align:middle size:80.00% line:79.33% No quiero ponerlas más nerviosas de lo que ya están, así que... 190 00:11:44.440 --> 00:11:47.360 position:50.00%,middle align:middle size:80.00% line:79.33% les deseo mucha suerte a cada una de ustedes. 191 00:11:48.280 --> 00:11:49.440 position:50.00%,middle align:middle size:80.00% line:84.67% [inaudible] 192 00:12:36.760 --> 00:12:39.440 position:50.00%,middle align:middle size:80.00% line:84.67% Remedios Fuentes, admitida. Lucía Billar, 193 00:12:39.600 --> 00:12:41.800 position:50.00%,middle align:middle size:80.00% line:84.67% no admitida. Marta Rodríguez, admitida. 194 00:12:41.960 --> 00:12:45.080 position:50.00%,middle align:middle size:80.00% line:79.33% Laura Sarmiento, admitida. Verónica Sánchez, admitida. 195 00:12:45.240 --> 00:12:48.960 position:50.00%,middle align:middle size:80.00% line:79.33% Ángeles Montes, no admitida. Carlota Rodríguez de Senillosa, 196 00:12:49.120 --> 00:12:51.760 position:50.00%,middle align:middle size:80.00% line:84.67% admitida. María José Pando, admitida. 197 00:12:51.920 --> 00:12:54.440 position:50.00%,middle align:middle size:80.00% line:84.67% María Inmaculada Suárez, admitida. 198 00:12:55.840 --> 00:12:58.360 position:50.00%,middle align:middle size:80.00% line:84.67% Enhorabuena a las elegidas. 199 00:12:59.680 --> 00:13:00.880 position:50.00%,middle align:middle size:80.00% line:84.67% Disculpe. 200 00:13:01.280 --> 00:13:02.320 position:50.00%,middle align:middle size:80.00% line:84.67% Perdón. 201 00:13:02.480 --> 00:13:05.840 position:50.00%,middle align:middle size:80.00% line:79.33% - No ha dicho mi nombre. - Eso es que no ha pasado las pruebas. 202 00:13:06.000 --> 00:13:07.520 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Cómo? Me gustaría ver el listado. 203 00:13:07.680 --> 00:13:11.160 position:50.00%,middle align:middle size:80.00% line:79.33% Los resultados no son públicos, pero le aseguro que sí son justos. 204 00:13:11.320 --> 00:13:13.600 position:50.00%,middle align:middle size:80.00% line:79.33% - Tengo derecho a saber... - No tiene derecho a nada. 205 00:13:13.760 --> 00:13:15.760 position:50.00%,middle align:middle size:80.00% line:84.67% No ha pasado las pruebas, punto y final. 206 00:13:15.920 --> 00:13:16.920 position:50.00%,middle align:middle size:80.00% line:84.67% [Carlos] ¿Va todo bien? 207 00:13:31.320 --> 00:13:33.840 position:50.00%,middle align:middle size:80.00% line:79.33% Señorita Aguilar, ¿en qué academia ha estudiado? 208 00:13:34.000 --> 00:13:35.040 position:50.00%,middle align:middle size:80.00% line:84.67% Soy autodidacta. 209 00:13:35.200 --> 00:13:36.360 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Autodidacta? - Sí. 210 00:13:36.520 --> 00:13:39.200 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Usted nunca ha estudiado nada por su cuenta? 211 00:13:39.360 --> 00:13:42.000 position:50.00%,middle align:middle size:80.00% line:84.67% Yo, si le soy sincero, nunca he estudiado. 212 00:13:42.800 --> 00:13:44.960 position:50.00%,middle align:middle size:80.00% line:84.67% Pero no deje que se entere mi padre, 213 00:13:45.120 --> 00:13:46.440 position:50.00%,middle align:middle size:80.00% line:84.67% no me gustaría que pensase 214 00:13:46.600 --> 00:13:49.240 position:50.00%,middle align:middle size:80.00% line:79.33% que desperdició su dinero pagándome un internado. 215 00:13:52.560 --> 00:13:53.640 position:50.00%,middle align:middle size:80.00% line:84.67% Me imagino que no sabe 216 00:13:53.800 --> 00:13:56.160 position:50.00%,middle align:middle size:80.00% line:79.33% por qué, habiendo unas pruebas más que decentes, 217 00:13:56.320 --> 00:13:57.680 position:50.00%,middle align:middle size:80.00% line:84.67% no está entre las elegidas. 218 00:13:57.840 --> 00:13:58.960 position:50.00%,middle align:middle size:80.00% line:84.67% Sus brazos... 219 00:14:00.600 --> 00:14:02.800 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Cómo? - No son lo suficientemente largos. 220 00:14:02.960 --> 00:14:05.000 position:50.00%,middle align:middle size:80.00% line:84.67% Muchos abonados, paneles muy grandes... 221 00:14:05.160 --> 00:14:08.640 position:50.00%,middle align:middle size:80.00% line:79.33% A veces una telefonista tiene que hacerse cargo de hasta dos paneles. 222 00:14:08.800 --> 00:14:10.200 position:50.00%,middle align:middle size:80.00% line:84.67% Con su permiso... 223 00:14:11.960 --> 00:14:15.000 position:50.00%,middle align:middle size:80.00% line:79.33% Le faltan dos centímetros para estar en el mínimo exigido. 224 00:14:15.760 --> 00:14:17.880 position:50.00%,middle align:middle size:80.00% line:84.67% Perdone el atrevimiento, don Carlos, 225 00:14:18.040 --> 00:14:20.560 position:50.00%,middle align:middle size:80.00% line:79.33% pero este requisito me parece una tontería. 226 00:14:20.720 --> 00:14:21.880 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Perdón? 227 00:14:22.040 --> 00:14:25.680 position:50.00%,middle align:middle size:80.00% line:79.33% Trinidad Seijo, telefonista de la centralita de mi pueblo, 228 00:14:25.840 --> 00:14:28.920 position:50.00%,middle align:middle size:80.00% line:79.33% controlaba toda la comarca, más de 300 abonados, 229 00:14:29.080 --> 00:14:32.080 position:50.00%,middle align:middle size:80.00% line:79.33% todo lo hacía con una mano... porque era manca. 230 00:14:33.560 --> 00:14:36.080 position:50.00%,middle align:middle size:80.00% line:79.33% Así que no creo que sea una cuestión de centímetros 231 00:14:36.240 --> 00:14:37.240 position:50.00%,middle align:middle size:80.00% line:84.67% sino de habilidad. 232 00:14:40.040 --> 00:14:42.160 position:50.00%,middle align:middle size:80.00% line:84.67% Dígame por qué quiere ser telefonista. 233 00:14:42.320 --> 00:14:45.400 position:50.00%,middle align:middle size:80.00% line:79.33% Quiere que le diga que es el mejor trabajo del mundo, ¿verdad? 234 00:14:45.560 --> 00:14:46.960 position:50.00%,middle align:middle size:80.00% line:84.67% Pero es que no puedo hacer eso 235 00:14:47.120 --> 00:14:49.560 position:50.00%,middle align:middle size:80.00% line:79.33% porque entonces usted y yo sabríamos que miento. 236 00:14:50.720 --> 00:14:53.520 position:50.00%,middle align:middle size:80.00% line:79.33% El suyo es mucho mejor. Pero, por desgracia, 237 00:14:53.680 --> 00:14:55.320 position:50.00%,middle align:middle size:80.00% line:84.67% me han dicho que no está vacante. 238 00:14:55.480 --> 00:14:58.120 position:50.00%,middle align:middle size:80.00% line:79.33% Con carácter, don de la palabra y sentido del humor. 239 00:14:58.280 --> 00:15:00.880 position:50.00%,middle align:middle size:80.00% line:79.33% Estoy segura de que no es la primera que conoce así. 240 00:15:01.040 --> 00:15:02.520 position:50.00%,middle align:middle size:80.00% line:84.67% He conocido unas pocas, sí. 241 00:15:03.440 --> 00:15:05.200 position:50.00%,middle align:middle size:80.00% line:84.67% Aunque no sé si alguna como usted. 242 00:15:05.840 --> 00:15:07.240 position:50.00%,middle align:middle size:80.00% line:84.67% No, como yo no. 243 00:15:11.000 --> 00:15:13.400 position:50.00%,middle align:middle size:80.00% line:84.67% Carolina, acompañe a la señorita abajo... 244 00:15:14.440 --> 00:15:17.120 position:50.00%,middle align:middle size:80.00% line:79.33% y confírmele a Sara que está entre las seleccionadas. 245 00:15:17.280 --> 00:15:19.000 position:50.00%,middle align:middle size:80.00% line:79.33% - Bienvenida, señorita Aguilar. - Gracias. 246 00:15:19.160 --> 00:15:21.560 position:50.00%,middle align:middle size:80.00% line:79.33% La próxima vez que quiera impresionar a un directivo 247 00:15:21.720 --> 00:15:23.600 position:50.00%,middle align:middle size:80.00% line:79.33% no le diga que sueña con quitarle el puesto. 248 00:15:23.760 --> 00:15:26.480 position:50.00%,middle align:middle size:80.00% line:79.33% Y usted la próxima vez que quiera impresionar a una mujer 249 00:15:26.640 --> 00:15:29.200 position:50.00%,middle align:middle size:80.00% line:79.33% no le haga sentir que solo le interesan sus medidas. 250 00:15:32.920 --> 00:15:35.800 position:50.00%,middle align:middle size:80.00% line:79.33% Al final te ha servido esa carita de no haber roto un plato. 251 00:15:36.400 --> 00:15:37.720 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Perdona? 252 00:15:37.880 --> 00:15:41.120 position:50.00%,middle align:middle size:80.00% line:79.33% - No sé de qué me estás hablando... - La actitud de mosquita muerta. 253 00:15:41.280 --> 00:15:44.040 position:50.00%,middle align:middle size:80.00% line:79.33% Te servirá con Sara y con don Carlos, pero conmigo no. 254 00:15:44.200 --> 00:15:45.600 position:50.00%,middle align:middle size:80.00% line:84.67% Te voy a estar vigilando. 255 00:15:49.680 --> 00:15:52.320 position:50.00%,middle align:middle size:80.00% line:84.67% Bonita, por la escalera mejor... 256 00:15:58.560 --> 00:15:59.720 position:50.00%,middle align:middle size:80.00% line:84.67% [ríe suavemente] 257 00:16:03.880 --> 00:16:05.960 position:50.00%,middle align:middle size:80.00% line:84.67% [señal de teléfono y voces] 258 00:16:06.120 --> 00:16:09.080 position:50.00%,middle align:middle size:80.00% line:79.33% [Ángeles] Buenas tardes, operadora tres, ¿en qué puedo ayudarle? 259 00:16:10.880 --> 00:16:12.320 position:50.00%,middle align:middle size:80.00% line:84.67% Un momento, por favor. 260 00:16:12.480 --> 00:16:14.600 position:50.00%,middle align:middle size:80.00% line:84.67% Buenos días, operadora número 20. 261 00:16:20.000 --> 00:16:23.960 position:50.00%,middle align:middle size:80.00% line:79.33% Aquí Central. Tiene una llamada del abonado 235. 262 00:16:24.160 --> 00:16:25.520 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Desea aceptarla? 263 00:16:26.280 --> 00:16:27.520 position:50.00%,middle align:middle size:80.00% line:84.67% Gracias. 264 00:16:28.000 --> 00:16:30.200 position:50.00%,middle align:middle size:80.00% line:84.67% [Sara] Gracias, Ángeles, es suficiente. 265 00:16:30.360 --> 00:16:33.240 position:50.00%,middle align:middle size:80.00% line:79.33% Ángeles es la que mejor conoce esta empresa y su trabajo. 266 00:16:33.400 --> 00:16:35.440 position:50.00%,middle align:middle size:80.00% line:84.67% [Alba] La telefonista que mejor conocía 267 00:16:35.600 --> 00:16:38.120 position:50.00%,middle align:middle size:80.00% line:79.33% esa empresa y su trabajo. Sin duda, si alguien 268 00:16:38.280 --> 00:16:40.200 position:50.00%,middle align:middle size:80.00% line:84.67% podía contarme todo lo que necesitaba 269 00:16:40.360 --> 00:16:43.600 position:50.00%,middle align:middle size:80.00% line:79.33% para robar aquella caja fuerte y cumplir mi sueño era Ángeles. 270 00:16:43.760 --> 00:16:45.280 position:50.00%,middle align:middle size:80.00% line:84.67% ...mañana, tarde y noche. 271 00:16:45.440 --> 00:16:48.280 position:50.00%,middle align:middle size:80.00% line:79.33% Solo tenía que acercarme a ella y conseguir que confiase 272 00:16:48.440 --> 00:16:51.000 position:50.00%,middle align:middle size:80.00% line:79.33% lo suficiente en mí como para contarme los secretos 273 00:16:51.160 --> 00:16:54.920 position:50.00%,middle align:middle size:80.00% line:79.33% - de la Compañía de Telefonía. - Cuando suena el gong, es el momento 274 00:16:55.080 --> 00:16:57.360 position:50.00%,middle align:middle size:80.00% line:79.33% del cambio de puesto, ¿está todo claro? Bien. 275 00:16:57.520 --> 00:16:58.800 position:50.00%,middle align:middle size:80.00% line:84.67% Ahora, si me disculpan, 276 00:16:58.960 --> 00:17:01.560 position:50.00%,middle align:middle size:80.00% line:79.33% el director de la compañía quiere presentarse. 277 00:17:02.440 --> 00:17:05.680 position:50.00%,middle align:middle size:80.00% line:79.33% Don Francisco, le presento a las nuevas incorporaciones: 278 00:17:05.839 --> 00:17:06.880 position:50.00%,middle align:middle size:80.00% line:84.67% Marta Rodríguez... 279 00:17:07.040 --> 00:17:10.400 position:50.00%,middle align:middle size:80.00% line:79.33% El dire... Pero ¿el director no era don Carlos Cifuentes? 280 00:17:10.560 --> 00:17:13.480 position:50.00%,middle align:middle size:80.00% line:79.33% - Es el hijo del dueño. - ¿Y quién es el director? 281 00:17:14.480 --> 00:17:15.960 position:50.00%,middle align:middle size:80.00% line:84.67% [Sara] Laura Sarmiento. 282 00:17:17.319 --> 00:17:18.720 position:50.00%,middle align:middle size:80.00% line:84.67% Petra Silva. 283 00:17:19.480 --> 00:17:20.720 position:50.00%,middle align:middle size:80.00% line:84.67% Lourdes Trapero. 284 00:17:22.640 --> 00:17:24.400 position:50.00%,middle align:middle size:80.00% line:84.67% Ella es la señorita Raquel Levante. 285 00:17:24.960 --> 00:17:28.760 position:50.00%,middle align:middle size:80.00% line:79.33% Remedios Fuentes. Carlota Rodríguez de Senillosa. 286 00:17:29.720 --> 00:17:31.200 position:50.00%,middle align:middle size:80.00% line:84.67% Lidia Aguilar. 287 00:17:32.080 --> 00:17:33.240 position:50.00%,middle align:middle size:80.00% line:84.67% Alba. 288 00:17:34.920 --> 00:17:37.600 position:50.00%,middle align:middle size:80.00% line:79.33% Lo lamento, pero se ha equivocado de persona, no... 289 00:17:38.160 --> 00:17:39.960 position:50.00%,middle align:middle size:80.00% line:84.67% Me llamo Lidia Aguilar. 290 00:17:40.120 --> 00:17:41.160 position:50.00%,middle align:middle size:80.00% line:84.67% Encantada. 291 00:17:43.360 --> 00:17:45.000 position:50.00%,middle align:middle size:80.00% line:84.67% Lo siento, señorita Aguilar. 292 00:17:45.520 --> 00:17:47.400 position:50.00%,middle align:middle size:80.00% line:84.67% Me ha recordado a una vieja amiga. 293 00:17:48.520 --> 00:17:49.960 position:50.00%,middle align:middle size:80.00% line:84.67% No se apure. 294 00:17:53.320 --> 00:17:54.600 position:50.00%,middle align:middle size:80.00% line:84.67% [Sara] María José Pando. 295 00:17:57.560 --> 00:17:59.960 position:50.00%,middle align:middle size:80.00% line:79.33% [joven Francisco] Corre, Alba, vamos, venga. 296 00:18:00.120 --> 00:18:01.240 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Corre, corre, corre! 297 00:18:01.400 --> 00:18:03.320 position:50.00%,middle align:middle size:80.00% line:10.00% Pasa, pásame la maleta. 298 00:18:03.480 --> 00:18:06.160 position:50.00%,middle align:middle size:80.00% line:10.00% [joven Alba] Aún no me creo que nos vayamos a Madrid. 299 00:18:06.320 --> 00:18:07.800 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Te estás arrepintiendo? 300 00:18:07.960 --> 00:18:09.000 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Tú estás loco? 301 00:18:09.440 --> 00:18:12.400 position:50.00%,middle align:middle size:80.00% line:79.33% Estaba pensando en todas las cosas que haré cuando llegue, 302 00:18:12.560 --> 00:18:13.960 position:50.00%,middle align:middle size:80.00% line:84.67% como encontrar un trabajo. 303 00:18:14.120 --> 00:18:17.880 position:50.00%,middle align:middle size:80.00% line:79.33% Después ahorraré mucho, muchísimo y me compraré un vestido precioso. 304 00:18:18.040 --> 00:18:20.440 position:50.00%,middle align:middle size:80.00% line:79.33% E iré a la verbena y me tomaré un white lady. 305 00:18:20.600 --> 00:18:21.760 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Un white lady? 306 00:18:21.920 --> 00:18:24.360 position:50.00%,middle align:middle size:80.00% line:79.33% Claro. Es lo que toman las chicas de ciudad. 307 00:18:25.880 --> 00:18:27.640 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Qué vas a hacer tú? - ¿Yo? 308 00:18:28.280 --> 00:18:31.320 position:50.00%,middle align:middle size:80.00% line:79.33% Voy a besar este lunar tan bonito que tienes en el cuello. 309 00:18:44.200 --> 00:18:46.560 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Eh! ¡Mi maleta! ¡Oiga! 310 00:18:47.080 --> 00:18:48.240 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Oiga! 311 00:18:54.960 --> 00:18:58.480 position:50.00%,middle align:middle size:80.00% line:79.33% - Eh, eh, ladrón, que es mi maleta. - Oiga, no, es mi maleta. Oiga. 312 00:19:00.120 --> 00:19:02.320 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Suélteme! ¡Francisco! 313 00:19:02.480 --> 00:19:04.240 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Francisco! 314 00:19:12.280 --> 00:19:13.920 position:50.00%,middle align:middle size:80.00% line:84.67% [teléfono] 315 00:19:16.040 --> 00:19:18.240 position:50.00%,middle align:middle size:80.00% line:84.67% [teléfono] 316 00:19:20.360 --> 00:19:23.480 position:50.00%,middle align:middle size:80.00% line:79.33% - Te dije que no me llamaras aquí. - [Alba] Es una emergencia, Beltrán. 317 00:19:23.640 --> 00:19:24.880 position:50.00%,middle align:middle size:80.00% line:84.67% Ha surgido un problema. 318 00:19:25.040 --> 00:19:27.320 position:50.00%,middle align:middle size:80.00% line:79.33% Y no puedo dar el golpe que tenía previsto, 319 00:19:27.480 --> 00:19:29.920 position:50.00%,middle align:middle size:80.00% line:79.33% necesito buscar otro sitio... y más tiempo. 320 00:19:30.080 --> 00:19:34.400 position:50.00%,middle align:middle size:80.00% line:79.33% No me cuentes tu vida. ¿Sabes cuál es la condena por asesinato? 321 00:19:34.560 --> 00:19:35.640 position:50.00%,middle align:middle size:80.00% line:84.67% La muerte. 322 00:19:35.800 --> 00:19:37.960 position:50.00%,middle align:middle size:80.00% line:84.67% Tienes un día para librarte de ella. 323 00:19:38.120 --> 00:19:39.960 position:50.00%,middle align:middle size:80.00% line:84.67% Mañana a la una en el parque. 324 00:19:40.120 --> 00:19:41.600 position:50.00%,middle align:middle size:80.00% line:84.67% [Beltrán cuelga] 325 00:19:42.320 --> 00:19:45.560 position:50.00%,middle align:middle size:80.00% line:79.33% Carlota... ¿Entonces sabes cómo llegar aquí? 326 00:19:46.240 --> 00:19:48.920 position:50.00%,middle align:middle size:80.00% line:84.67% Pensión Dolores, calle Blancaró. 327 00:19:49.080 --> 00:19:52.440 position:50.00%,middle align:middle size:80.00% line:79.33% Sí, mujer, esto no tiene pérdida. Mira, tienes que seguir todo recto 328 00:19:52.600 --> 00:19:54.600 position:50.00%,middle align:middle size:80.00% line:84.67% y luego giras la tercera a la izquierda. 329 00:19:54.760 --> 00:19:57.640 position:50.00%,middle align:middle size:80.00% line:79.33% - [hombre] Es la segunda a la izquierda. - ¿Y quién dice eso? 330 00:19:57.800 --> 00:19:59.200 position:50.00%,middle align:middle size:80.00% line:84.67% Miguel Pascual. 331 00:20:00.040 --> 00:20:01.720 position:50.00%,middle align:middle size:80.00% line:84.67% Ingeniero de comunicaciones. 332 00:20:01.880 --> 00:20:05.080 position:50.00%,middle align:middle size:80.00% line:79.33% Madrileño de cuna, un humilde conocedor de la villa. 333 00:20:05.320 --> 00:20:07.520 position:50.00%,middle align:middle size:80.00% line:79.33% [ríe] ¿Has oído, Marga? Un hombre polifacético. 334 00:20:07.680 --> 00:20:09.760 position:50.00%,middle align:middle size:80.00% line:84.67% Míralo, porque de esos quedan muy pocos. 335 00:20:09.920 --> 00:20:11.080 position:50.00%,middle align:middle size:80.00% line:84.67% Le digo lo mismo... 336 00:20:11.240 --> 00:20:12.320 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Ah! 337 00:20:13.200 --> 00:20:14.440 position:50.00%,middle align:middle size:80.00% line:84.67% Él es Miguel. 338 00:20:14.600 --> 00:20:16.720 position:50.00%,middle align:middle size:80.00% line:84.67% [azorada] Ah, pero que es tu novio... 339 00:20:16.880 --> 00:20:20.040 position:50.00%,middle align:middle size:80.00% line:79.33% No repitas esa palabra delante de ella, es alérgica. 340 00:20:20.200 --> 00:20:23.400 position:50.00%,middle align:middle size:80.00% line:79.33% Oye, luego voy a tomarme algo al bar de enfrente, ¿te apuntas? 341 00:20:23.560 --> 00:20:24.920 position:50.00%,middle align:middle size:80.00% line:84.67% Cierro unas cosas y te busco. 342 00:20:26.480 --> 00:20:30.360 position:50.00%,middle align:middle size:80.00% line:79.33% Lidia, ¿te vienes al bar de enfrente a tomar algo con nosotras? 343 00:20:30.520 --> 00:20:32.280 position:50.00%,middle align:middle size:80.00% line:84.67% No, tengo cosas que hacer. 344 00:20:32.440 --> 00:20:33.480 position:50.00%,middle align:middle size:80.00% line:84.67% [Marga] ¿Yo puedo ir? 345 00:20:33.640 --> 00:20:36.760 position:50.00%,middle align:middle size:80.00% line:79.33% Aunque sea a agradecerle a Ángeles todo lo que ha hecho por nosotras. 346 00:20:36.920 --> 00:20:38.880 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Ángeles va? - Sí. 347 00:20:39.040 --> 00:20:41.840 position:50.00%,middle align:middle size:80.00% line:79.33% Va a dejar al niño con la vecina porque no quería demorarse mucho. 348 00:20:42.000 --> 00:20:43.720 position:50.00%,middle align:middle size:80.00% line:79.33% - Bueno, ¿qué, te vienes? - [Ángeles] Hola. 349 00:20:45.320 --> 00:20:47.880 position:50.00%,middle align:middle size:80.00% line:79.33% La familia Cifuentes ha hecho una inversión muy grande 350 00:20:48.040 --> 00:20:49.600 position:50.00%,middle align:middle size:80.00% line:84.67% con la construcción del edificio. 351 00:20:49.760 --> 00:20:52.080 position:50.00%,middle align:middle size:80.00% line:79.33% Ahora es lógico que quieran aumentar la seguridad. 352 00:20:52.240 --> 00:20:54.440 position:50.00%,middle align:middle size:80.00% line:79.33% Y solo se puede acceder durante nuestro turno... 353 00:20:54.600 --> 00:20:56.200 position:50.00%,middle align:middle size:80.00% line:84.67% Claro, tienen que ser precavidos. 354 00:20:56.360 --> 00:20:59.840 position:50.00%,middle align:middle size:80.00% line:79.33% Todo pasa por la central: recaudación de la empresa, el sueldo 355 00:21:00.000 --> 00:21:01.720 position:50.00%,middle align:middle size:80.00% line:84.67% de los empleados... Básicamente todo. 356 00:21:01.880 --> 00:21:03.760 position:50.00%,middle align:middle size:80.00% line:84.67% [Marga] Abuela, que me va a dejar sorda. 357 00:21:03.920 --> 00:21:06.400 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Ves? Si es que vales más que las pesetas. 358 00:21:06.560 --> 00:21:08.320 position:50.00%,middle align:middle size:80.00% line:84.67% Verás cuando se lo diga a la Rosario. 359 00:21:08.480 --> 00:21:11.040 position:50.00%,middle align:middle size:80.00% line:79.33% No, abuela, antes de que le diga nada a nadie... 360 00:21:11.840 --> 00:21:15.040 position:50.00%,middle align:middle size:80.00% line:79.33% Que, a ver, abuela, que yo lo he estado pensando y... 361 00:21:15.840 --> 00:21:17.360 position:50.00%,middle align:middle size:80.00% line:84.67% Y lo mejor es que renuncie. 362 00:21:17.520 --> 00:21:21.160 position:50.00%,middle align:middle size:80.00% line:79.33% - Que me vuelva al pueblo con usted. - ¿Cómo? Pero ¿estás loca? 363 00:21:21.720 --> 00:21:23.320 position:50.00%,middle align:middle size:80.00% line:84.67% No somos una familia de posibles 364 00:21:23.480 --> 00:21:25.760 position:50.00%,middle align:middle size:80.00% line:79.33% ni tienes un marido que te resuelva la vida. 365 00:21:25.920 --> 00:21:28.520 position:50.00%,middle align:middle size:80.00% line:79.33% No voy a dejar que te quedes el resto de tu vida en el pueblo 366 00:21:28.680 --> 00:21:29.960 position:50.00%,middle align:middle size:80.00% line:84.67% cuidando de una vieja, no. 367 00:21:30.120 --> 00:21:31.720 position:50.00%,middle align:middle size:80.00% line:84.67% Ya te has sacrificado suficiente 368 00:21:31.880 --> 00:21:34.360 position:50.00%,middle align:middle size:80.00% line:79.33% por tu madre, que en paz descanse, todos estos años. 369 00:21:34.520 --> 00:21:35.880 position:50.00%,middle align:middle size:80.00% line:84.67% Ya, abuela, pero... 370 00:21:36.280 --> 00:21:39.680 position:50.00%,middle align:middle size:80.00% line:79.33% Pero es que yo he sido muy feliz con usted y con madre. 371 00:21:39.840 --> 00:21:43.440 position:50.00%,middle align:middle size:80.00% line:79.33% Y ella contigo, hija, pero ahora tienes que ocuparte de ti misma. 372 00:21:43.600 --> 00:21:46.560 position:50.00%,middle align:middle size:80.00% line:79.33% Y por una vez en tu vida pensar solo en ti, no en los demás. 373 00:21:46.720 --> 00:21:49.560 position:50.00%,middle align:middle size:80.00% line:79.33% Ya, abuela, ya lo sé, y es lo que hago, pero... 374 00:21:51.240 --> 00:21:53.520 position:50.00%,middle align:middle size:80.00% line:79.33% Pero es que la capital me queda muy grande. 375 00:21:53.680 --> 00:21:55.360 position:50.00%,middle align:middle size:80.00% line:84.67% El miedo se pasa, Marga. 376 00:21:56.920 --> 00:21:59.440 position:50.00%,middle align:middle size:80.00% line:84.67% Y la vida también, si no se aprovecha. 377 00:21:59.600 --> 00:22:00.760 position:50.00%,middle align:middle size:80.00% line:84.67% No lo olvides. 378 00:22:00.920 --> 00:22:01.920 position:50.00%,middle align:middle size:80.00% line:84.67% Gracias. 379 00:22:02.760 --> 00:22:06.000 position:50.00%,middle align:middle size:80.00% line:79.33% Abuela, que... Que la tengo que dejar, ¿vale? 380 00:22:06.440 --> 00:22:08.840 position:50.00%,middle align:middle size:80.00% line:84.67% Cuídese mucho. [beso] La quiero. 381 00:22:14.880 --> 00:22:16.000 position:50.00%,middle align:middle size:80.00% line:84.67% Es mucho dinero... 382 00:22:16.160 --> 00:22:18.880 position:50.00%,middle align:middle size:80.00% line:79.33% [Carlota] Bueno, vamos a dejar de hablar de trabajo y vamos a brindar. 383 00:22:19.040 --> 00:22:20.120 position:50.00%,middle align:middle size:80.00% line:84.67% Los vermús. 384 00:22:20.280 --> 00:22:22.120 position:50.00%,middle align:middle size:80.00% line:84.67% Pero el vermú lleva alcohol. 385 00:22:23.080 --> 00:22:26.120 position:50.00%,middle align:middle size:80.00% line:79.33% Sí, sí, sí, pero puedes tomar otra cosa, si prefieres. 386 00:22:27.680 --> 00:22:29.400 position:50.00%,middle align:middle size:80.00% line:84.67% No, no, esto está bien. 387 00:22:30.120 --> 00:22:32.520 position:50.00%,middle align:middle size:80.00% line:79.33% Bueno, a lo mejor para mí es muy fuerte. 388 00:22:32.680 --> 00:22:35.320 position:50.00%,middle align:middle size:80.00% line:79.33% Yo hace que no me tomo uno... Ni me acuerdo. 389 00:22:35.480 --> 00:22:37.600 position:50.00%,middle align:middle size:80.00% line:79.33% No entraba a un bar desde que me quedé embarazada. 390 00:22:37.760 --> 00:22:39.200 position:50.00%,middle align:middle size:80.00% line:84.67% Así te preparas para el champán 391 00:22:39.360 --> 00:22:41.160 position:50.00%,middle align:middle size:80.00% line:79.33% - de la fiesta de esta noche. - [Marga] ¿Qué fiesta? 392 00:22:41.320 --> 00:22:43.280 position:50.00%,middle align:middle size:80.00% line:79.33% [Carlota] ¿Cómo que qué fiesta? La de inauguración. 393 00:22:43.440 --> 00:22:44.400 position:50.00%,middle align:middle size:80.00% line:84.67% [Ángeles] Va todo el mundo. 394 00:22:44.560 --> 00:22:46.880 position:50.00%,middle align:middle size:80.00% line:79.33% Yo no puedo porque me tengo que quedar con mi hija, 395 00:22:47.040 --> 00:22:49.880 position:50.00%,middle align:middle size:80.00% line:79.33% pero van telefonistas, oficinistas, van hasta los jefes. 396 00:22:50.040 --> 00:22:52.560 position:50.00%,middle align:middle size:80.00% line:79.33% Y los Cifuentes se van a mezclar con la plebe, claro. 397 00:22:52.720 --> 00:22:53.800 position:50.00%,middle align:middle size:80.00% line:84.67% Ya les toca, ¿no? 398 00:22:53.880 --> 00:22:57.480 position:50.00%,middle align:middle size:80.00% line:79.33% Yo no creo que pueda ir. Es que a mí no me gustan mucho las fiestas. 399 00:22:58.040 --> 00:23:00.400 position:50.00%,middle align:middle size:80.00% line:79.33% Marga, para decir que no te gustan las fiestas 400 00:23:00.560 --> 00:23:03.080 position:50.00%,middle align:middle size:80.00% line:79.33% tienes que haber ido al menos a alguna en tu vida, 401 00:23:03.240 --> 00:23:04.280 position:50.00%,middle align:middle size:80.00% line:84.67% y tú tienes pinta 402 00:23:04.440 --> 00:23:06.160 position:50.00%,middle align:middle size:80.00% line:84.67% de no haber ido a ninguna. 403 00:23:06.320 --> 00:23:07.800 position:50.00%,middle align:middle size:80.00% line:79.33% - Venga, vamos a brindar. - [Ángeles] Sí. 404 00:23:07.960 --> 00:23:09.120 position:50.00%,middle align:middle size:80.00% line:84.67% Vamos. Vamos, Lidia. 405 00:23:09.280 --> 00:23:11.720 position:50.00%,middle align:middle size:80.00% line:79.33% - [Ángeles] Ay, sí, vamos. - [Carlota] Vamos a brindar por Marga 406 00:23:11.880 --> 00:23:13.520 position:50.00%,middle align:middle size:80.00% line:84.67% y su primer vermú, por Ángeles, 407 00:23:13.680 --> 00:23:15.280 position:50.00%,middle align:middle size:80.00% line:79.33% - que gracias a tu ayuda... - No, no, no. 408 00:23:15.440 --> 00:23:17.080 position:50.00%,middle align:middle size:80.00% line:84.67% ...hemos tenido esta oportunidad. 409 00:23:17.240 --> 00:23:18.960 position:50.00%,middle align:middle size:80.00% line:84.67% Y por las nuevas chicas del cable. 410 00:23:19.120 --> 00:23:20.080 position:50.00%,middle align:middle size:80.00% line:79.33% - [Ángeles] ¡Sí! - Chinchín. 411 00:23:22.800 --> 00:23:26.120 position:50.00%,middle align:middle size:80.00% line:79.33% Ah, y por que a partir de ahora vamos a ser mujeres independientes. 412 00:23:26.640 --> 00:23:27.840 position:50.00%,middle align:middle size:80.00% line:84.67% [hombre] Carlota... 413 00:23:31.720 --> 00:23:32.880 position:50.00%,middle align:middle size:80.00% line:84.67% Coge tus cosas 414 00:23:33.040 --> 00:23:35.200 position:50.00%,middle align:middle size:80.00% line:79.33% - y ven conmigo ahora mismo. - No. 415 00:23:35.720 --> 00:23:37.320 position:50.00%,middle align:middle size:80.00% line:84.67% No tengo que ir a ningún sitio. 416 00:23:37.480 --> 00:23:39.840 position:50.00%,middle align:middle size:80.00% line:79.33% Tengo trabajo y soy una mujer independiente. 417 00:23:42.640 --> 00:23:45.480 position:50.00%,middle align:middle size:80.00% line:79.33% Que me suelte, que no soy ninguno de sus soldados. Suélteme. 418 00:23:45.880 --> 00:23:47.600 position:50.00%,middle align:middle size:80.00% line:79.33% - [Miguel] Don Emilio. - Y tú no te metas. 419 00:23:47.760 --> 00:23:49.400 position:50.00%,middle align:middle size:80.00% line:84.67% No eres nadie en esta familia. 420 00:23:49.600 --> 00:23:53.000 position:50.00%,middle align:middle size:80.00% line:79.33% - Me va a tener que meter a la fuerza. - ¿Pensaste que no iba a enterarme? 421 00:23:53.160 --> 00:23:55.880 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Se puede saber cuándo te has convertido en una mentirosa? 422 00:23:56.040 --> 00:23:58.360 position:50.00%,middle align:middle size:80.00% line:79.33% [Carlota] Desde el momento que le da igual lo que yo quiera. 423 00:23:58.520 --> 00:24:01.200 position:50.00%,middle align:middle size:80.00% line:79.33% No quiero un marido, quiero un trabajo y ser independiente. 424 00:24:01.360 --> 00:24:03.680 position:50.00%,middle align:middle size:80.00% line:84.67% Una señorita de tu categoría no trabaja. 425 00:24:03.840 --> 00:24:06.120 position:50.00%,middle align:middle size:80.00% line:84.67% Deja de decir tonterías y sube al coche. 426 00:24:07.400 --> 00:24:08.760 position:50.00%,middle align:middle size:80.00% line:84.67% ¡He dicho que entres! 427 00:24:13.200 --> 00:24:14.800 position:50.00%,middle align:middle size:80.00% line:84.67% A sus órdenes, mi coronel. 428 00:24:21.160 --> 00:24:24.560 position:50.00%,middle align:middle size:80.00% line:79.33% - Así que ese es el... - Padre de Carlota, sí. 429 00:24:27.440 --> 00:24:31.040 position:50.00%,middle align:middle size:80.00% line:79.33% Bueno, yo mejor me voy a casa, que tengo a mi marido esperándome. 430 00:24:31.200 --> 00:24:33.280 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Sabes cómo llegar al hostal? - Sí. 431 00:24:33.440 --> 00:24:36.640 position:50.00%,middle align:middle size:80.00% line:79.33% Carlota me contó cómo llegar desde aquí hasta la calle Blancaró. 432 00:24:36.800 --> 00:24:39.080 position:50.00%,middle align:middle size:80.00% line:79.33% - Muy bien. Bueno, hasta mañana. - Hasta mañana. 433 00:24:39.240 --> 00:24:41.600 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Ángeles! Espera, que te acompaño. 434 00:24:46.880 --> 00:24:49.600 position:50.00%,middle align:middle size:80.00% line:79.33% Que corra el champán. Y lo que necesiten los señores. 435 00:24:49.760 --> 00:24:52.520 position:50.00%,middle align:middle size:80.00% line:79.33% Supongo que estás aquí porque lo has conseguido, ¿no? 436 00:24:52.680 --> 00:24:55.800 position:50.00%,middle align:middle size:80.00% line:79.33% Tengo el trabajo y sé dónde está la caja... 437 00:24:55.960 --> 00:24:58.560 position:50.00%,middle align:middle size:80.00% line:79.33% Pero... me ha surgido un problema, he visto a Francisco. 438 00:24:58.720 --> 00:25:00.360 position:50.00%,middle align:middle size:80.00% line:79.33% - [Victoria] ¿Qué Francisco? - Mi Francisco. 439 00:25:00.520 --> 00:25:02.400 position:50.00%,middle align:middle size:80.00% line:79.33% - [Victoria] ¿Qué? - Es el director de la compañía. 440 00:25:02.560 --> 00:25:04.120 position:50.00%,middle align:middle size:80.00% line:79.33% - [Victoria] Pero ¿él te ha visto? - Sí. 441 00:25:04.280 --> 00:25:06.840 position:50.00%,middle align:middle size:80.00% line:79.33% [Victoria] Tienes que salir de ahí inmediatamente. 442 00:25:07.000 --> 00:25:09.920 position:50.00%,middle align:middle size:80.00% line:79.33% [Alba] No puedo. Si no pago lo que debo, me condenarán. 443 00:25:12.200 --> 00:25:16.440 position:50.00%,middle align:middle size:80.00% line:79.33% [Victoria] Tú ya sabes que si yo tuviera esa cantidad. Tú ya me conoces. 444 00:25:17.360 --> 00:25:19.200 position:50.00%,middle align:middle size:80.00% line:84.67% Victoria, voy a hacerlo esta noche. 445 00:25:19.760 --> 00:25:22.200 position:50.00%,middle align:middle size:80.00% line:79.33% Una de las chicas me ha dicho que hay seguridad, 446 00:25:22.360 --> 00:25:23.920 position:50.00%,middle align:middle size:80.00% line:84.67% pero podré, podré hacerlo. 447 00:25:24.360 --> 00:25:27.280 position:50.00%,middle align:middle size:80.00% line:79.33% Tengo que ir a la fiesta, coger las llaves, llevarme el dinero 448 00:25:27.440 --> 00:25:29.680 position:50.00%,middle align:middle size:80.00% line:79.33% y mañana por la mañana estaré muy lejos de aquí. 449 00:25:30.200 --> 00:25:31.760 position:50.00%,middle align:middle size:80.00% line:84.67% Y de Francisco también... 450 00:25:32.920 --> 00:25:34.680 position:50.00%,middle align:middle size:80.00% line:84.67% Alba, yo creo que... 451 00:25:34.880 --> 00:25:37.040 position:50.00%,middle align:middle size:80.00% line:79.33% Lo único que tengo que hacer es ser rápida. 452 00:25:38.000 --> 00:25:40.120 position:50.00%,middle align:middle size:80.00% line:84.67% Y si me lo encuentro, lo esquivo. 453 00:25:40.720 --> 00:25:43.960 position:50.00%,middle align:middle size:80.00% line:79.33% Alba, las prisas nunca han sido buenas consejeras. 454 00:25:44.320 --> 00:25:46.040 position:50.00%,middle align:middle size:80.00% line:84.67% Es una locura. 455 00:25:46.960 --> 00:25:49.000 position:50.00%,middle align:middle size:80.00% line:84.67% Victoria, es eso o el garrote. 456 00:25:51.160 --> 00:25:52.240 position:50.00%,middle align:middle size:80.00% line:84.67% Ah. 457 00:25:54.640 --> 00:25:55.800 position:50.00%,middle align:middle size:80.00% line:84.67% La 27. 458 00:25:56.280 --> 00:25:57.320 position:50.00%,middle align:middle size:80.00% line:84.67% Ven aquí. 459 00:25:59.240 --> 00:26:03.680 position:50.00%,middle align:middle size:80.00% line:79.33% Alba, cariño, ya perdí a Gimena, no quisiera perderte a ti también. 460 00:26:24.600 --> 00:26:26.840 position:50.00%,middle align:middle size:80.00% line:84.67% La cena se sirve a las ocho y media 461 00:26:27.000 --> 00:26:28.520 position:50.00%,middle align:middle size:80.00% line:84.67% y la puerta se cierra a las once. 462 00:26:28.680 --> 00:26:31.200 position:50.00%,middle align:middle size:80.00% line:79.33% Sé que no tengo que preocuparme por tu formalidad. 463 00:26:31.360 --> 00:26:33.720 position:50.00%,middle align:middle size:80.00% line:79.33% Tu abuela me ha asegurado que eres responsable. 464 00:26:33.880 --> 00:26:37.000 position:50.00%,middle align:middle size:80.00% line:79.33% Esta es una pensión decente, y ahora tu casa. 465 00:26:37.160 --> 00:26:38.160 position:50.00%,middle align:middle size:80.00% line:79.33% - Ya. - Ven. 466 00:26:42.240 --> 00:26:46.560 position:50.00%,middle align:middle size:80.00% line:79.33% Mira, sobre la cama tienes un juego de sábanas y uno de toallas, 467 00:26:46.720 --> 00:26:49.760 position:50.00%,middle align:middle size:80.00% line:79.33% para que las uses hasta que compres las tuyas. 468 00:26:49.920 --> 00:26:50.920 position:50.00%,middle align:middle size:80.00% line:84.67% Buenas noches. 469 00:26:51.080 --> 00:26:53.000 position:50.00%,middle align:middle size:80.00% line:84.67% Doña Lola, verá, es que... 470 00:26:53.480 --> 00:26:55.480 position:50.00%,middle align:middle size:80.00% line:79.33% esta noche es la inauguración de la empresa 471 00:26:55.640 --> 00:26:57.640 position:50.00%,middle align:middle size:80.00% line:84.67% y han invitado a las nuevas telefonistas. 472 00:26:57.840 --> 00:27:00.000 position:50.00%,middle align:middle size:80.00% line:79.33% Le prometo que llego antes de que cierre la puerta. 473 00:27:00.160 --> 00:27:01.480 position:50.00%,middle align:middle size:80.00% line:84.67% Ah, bien. Ten cuidado. 474 00:27:01.640 --> 00:27:03.440 position:50.00%,middle align:middle size:80.00% line:84.67% Siempre hay un joven desalmado 475 00:27:03.600 --> 00:27:06.880 position:50.00%,middle align:middle size:80.00% line:79.33% esperando para aprovecharse de una jovencita inocente. 476 00:27:07.040 --> 00:27:09.480 position:50.00%,middle align:middle size:80.00% line:84.67% Ya sabes, mente abierta, piernas cerradas. 477 00:27:09.640 --> 00:27:11.360 position:50.00%,middle align:middle size:80.00% line:79.33% - Buenas noches. - Buenas noches. 478 00:27:21.440 --> 00:27:23.280 position:50.00%,middle align:middle size:80.00% line:84.67% [riendo] ¿Qué? ¿Qué? ¿Qué? ¿Qué? 479 00:27:23.800 --> 00:27:26.320 position:50.00%,middle align:middle size:80.00% line:79.33% Estás hablando con el nuevo jefe técnico de la central 480 00:27:26.480 --> 00:27:29.120 position:50.00%,middle align:middle size:80.00% line:79.33% de la compañía de telefonía más importante de este país. 481 00:27:30.200 --> 00:27:31.920 position:50.00%,middle align:middle size:80.00% line:84.67% Pero ¿cuándo ha sido? ¿Cuándo ha sido? 482 00:27:32.080 --> 00:27:33.760 position:50.00%,middle align:middle size:80.00% line:79.33% Esta tarde me lo ha dicho don Francisco. 483 00:27:33.920 --> 00:27:35.520 position:50.00%,middle align:middle size:80.00% line:84.67% Digo: "Le doy un abrazo al estirado". 484 00:27:35.680 --> 00:27:37.080 position:50.00%,middle align:middle size:80.00% line:79.33% - ¡Mario! - ¿Qué, mi amor? 485 00:27:37.240 --> 00:27:39.600 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Qué? No he dicho nada que todo el mundo no piense. 486 00:27:39.760 --> 00:27:41.440 position:50.00%,middle align:middle size:80.00% line:79.33% - Ay, qué bien. - Vamos a pagar la casa. 487 00:27:41.600 --> 00:27:44.120 position:50.00%,middle align:middle size:80.00% line:79.33% Vas a dejar de trabajar. Se acabaron los problemas. 488 00:27:44.280 --> 00:27:45.560 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Entiendes esto, amor? 489 00:27:45.720 --> 00:27:47.720 position:50.00%,middle align:middle size:80.00% line:84.67% Te quiero. Vamos a celebrar. 490 00:27:49.560 --> 00:27:50.920 position:50.00%,middle align:middle size:80.00% line:84.67% ¡Feliz, feliz! 491 00:27:51.600 --> 00:27:52.800 position:50.00%,middle align:middle size:80.00% line:84.67% Sí... 492 00:27:52.960 --> 00:27:55.800 position:50.00%,middle align:middle size:80.00% line:84.67% Mario, y... ¿Quieres que deje de trabajar? 493 00:27:55.960 --> 00:27:57.280 position:50.00%,middle align:middle size:80.00% line:84.67% Lo que habíamos hablado. 494 00:27:58.200 --> 00:27:59.280 position:50.00%,middle align:middle size:80.00% line:84.67% Ya, ya. 495 00:27:59.440 --> 00:28:01.520 position:50.00%,middle align:middle size:80.00% line:84.67% Sí, sí, sí, sé que lo hablamos, pero... 496 00:28:02.680 --> 00:28:05.960 position:50.00%,middle align:middle size:80.00% line:84.67% Eh. Pero es que eso era antes y... 497 00:28:07.240 --> 00:28:09.560 position:50.00%,middle align:middle size:80.00% line:84.67% Mario, a mí me gusta mucho mi trabajo. 498 00:28:11.000 --> 00:28:14.520 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Te gusta tu trabajo más que estar en casa con tu marido y con tu hija? 499 00:28:14.680 --> 00:28:18.720 position:50.00%,middle align:middle size:80.00% line:79.33% No, Mario, mi hija y tú sois mi prioridad, eso siempre. 500 00:28:18.880 --> 00:28:21.240 position:50.00%,middle align:middle size:80.00% line:79.33% Si somos tu prioridad, ¿qué haces por ahí tomando vermú 501 00:28:21.400 --> 00:28:23.120 position:50.00%,middle align:middle size:80.00% line:84.67% mientras nosotros estamos en casa? 502 00:28:23.280 --> 00:28:25.680 position:50.00%,middle align:middle size:80.00% line:79.33% Mario, por Dios, es la primera vez en tres años. 503 00:28:32.320 --> 00:28:33.360 position:50.00%,middle align:middle size:80.00% line:84.67% Mario... 504 00:28:39.520 --> 00:28:40.680 position:50.00%,middle align:middle size:80.00% line:84.67% [portazo] 505 00:28:41.160 --> 00:28:42.360 position:50.00%,middle align:middle size:80.00% line:84.67% [suspira] 506 00:28:50.320 --> 00:28:51.960 position:50.00%,middle align:middle size:80.00% line:84.67% ¿No vas a bajar a cenar? 507 00:28:53.760 --> 00:28:55.880 position:50.00%,middle align:middle size:80.00% line:84.67% Esto lo hacemos por tu bien, hija. 508 00:28:56.520 --> 00:28:58.800 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Y no ha pensado, madre, que a lo mejor se equivoca 509 00:28:58.960 --> 00:29:00.200 position:50.00%,middle align:middle size:80.00% line:84.67% con lo que es mejor para mí? 510 00:29:21.960 --> 00:29:24.480 position:50.00%,middle align:middle size:80.00% line:84.67% [música festiva y ambiente animado] 511 00:29:47.240 --> 00:29:49.640 position:50.00%,middle align:middle size:80.00% line:79.33% - [Marga] Madre mía, cuánta gente. - 800 trabajadores... 512 00:29:49.800 --> 00:29:52.080 position:50.00%,middle align:middle size:80.00% line:79.33% Con que haya venido solo una cuarta parte... 513 00:29:52.240 --> 00:29:54.920 position:50.00%,middle align:middle size:80.00% line:79.33% Si es que yo sabía que esto no podía perdérmelo... 514 00:29:55.080 --> 00:29:56.520 position:50.00%,middle align:middle size:80.00% line:84.67% [Marga] Aún no me creo que hayas venido. 515 00:29:56.680 --> 00:29:59.800 position:50.00%,middle align:middle size:80.00% line:79.33% Esta tarde, cuando tu padre entró en el bar, casi entro yo en el coche 516 00:29:59.960 --> 00:30:01.200 position:50.00%,middle align:middle size:80.00% line:84.67% del miedo que me dio. 517 00:30:01.360 --> 00:30:04.640 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Tú sabes cuál es el peor enemigo de las mujeres, Marga? La sumisión. 518 00:30:04.800 --> 00:30:08.320 position:50.00%,middle align:middle size:80.00% line:79.33% Bueno, a mí siempre me han enseñado a obedecer. 519 00:30:08.480 --> 00:30:11.560 position:50.00%,middle align:middle size:80.00% line:79.33% Ya, y a mí. "Una mujer siempre tiene que ser correcta y obediente". 520 00:30:11.720 --> 00:30:14.640 position:50.00%,middle align:middle size:80.00% line:79.33% Pero ¿sabes qué? Que un día pensé: "Pero si tú no eres una mujer 521 00:30:14.800 --> 00:30:16.320 position:50.00%,middle align:middle size:80.00% line:84.67% para saber lo que yo necesito". 522 00:30:16.480 --> 00:30:19.720 position:50.00%,middle align:middle size:80.00% line:79.33% Puede cerrar todas las puertas que quiera porque "no hay barrera, 523 00:30:19.880 --> 00:30:23.240 position:50.00%,middle align:middle size:80.00% line:79.33% cerradura ni cerrojo que puedas imponer a la libertad de mi mente". 524 00:30:23.400 --> 00:30:27.040 position:50.00%,middle align:middle size:80.00% line:79.33% - Lo dijo Virginia Woolf. - ¿Esa es amiga tuya? 525 00:30:28.080 --> 00:30:29.840 position:50.00%,middle align:middle size:80.00% line:84.67% [aplausos] 526 00:30:31.880 --> 00:30:33.960 position:50.00%,middle align:middle size:80.00% line:84.67% [Carmen] Ni que fuéramos de la realeza. 527 00:30:34.120 --> 00:30:36.560 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Desde cuándo te molesta ser el centro de atención? 528 00:30:36.720 --> 00:30:38.400 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Quién ha dicho que me moleste? 529 00:30:38.680 --> 00:30:41.400 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Crees que alguna vez algo te parecerá bien, querida? 530 00:30:41.560 --> 00:30:44.880 position:50.00%,middle align:middle size:80.00% line:79.33% Sí, me pareció bien casarme contigo. Al principio. 531 00:30:45.040 --> 00:30:46.720 position:50.00%,middle align:middle size:80.00% line:84.67% Voy a saludar, ahora les veo. 532 00:30:51.480 --> 00:30:53.200 position:50.00%,middle align:middle size:80.00% line:84.67% [música de la fiesta] 533 00:31:03.520 --> 00:31:04.720 position:50.00%,middle align:middle size:80.00% line:84.67% [risas] 534 00:31:25.400 --> 00:31:26.440 position:50.00%,middle align:middle size:80.00% line:84.67% Buenas noches. 535 00:31:27.040 --> 00:31:28.080 position:50.00%,middle align:middle size:80.00% line:84.67% Disculpe. 536 00:31:28.240 --> 00:31:29.960 position:50.00%,middle align:middle size:80.00% line:84.67% Señorita... ¿Cómo era? 537 00:31:32.360 --> 00:31:34.000 position:50.00%,middle align:middle size:80.00% line:79.33% - Aguilar. [ríe] - Aguilar, eso es. 538 00:31:34.960 --> 00:31:37.440 position:50.00%,middle align:middle size:80.00% line:79.33% - [Carlos] Qué agradable sorpresa... - [Alba] Sí, ya he visto 539 00:31:37.600 --> 00:31:39.440 position:50.00%,middle align:middle size:80.00% line:84.67% que lo estaba pasando realmente mal. 540 00:31:39.600 --> 00:31:41.040 position:50.00%,middle align:middle size:80.00% line:84.67% Ah, ni siquiera he tenido tiempo. 541 00:31:41.200 --> 00:31:42.800 position:50.00%,middle align:middle size:80.00% line:84.67% Hay que ser amable con los empleados. 542 00:31:42.960 --> 00:31:44.920 position:50.00%,middle align:middle size:80.00% line:79.33% Eso es lo que hace conmigo, ser amable... 543 00:31:45.080 --> 00:31:47.960 position:50.00%,middle align:middle size:80.00% line:79.33% Hay cosas que se hacen por obligación y otras por placer. 544 00:31:48.120 --> 00:31:49.160 position:50.00%,middle align:middle size:80.00% line:84.67% Claramente 545 00:31:49.320 --> 00:31:50.760 position:50.00%,middle align:middle size:80.00% line:84.67% esta es una de las segundas. 546 00:31:53.040 --> 00:31:55.680 position:50.00%,middle align:middle size:80.00% line:79.33% Veo que esta noche está resultando divertida. 547 00:31:55.840 --> 00:31:58.480 position:50.00%,middle align:middle size:80.00% line:79.33% Señorita Aguilar, le presento a Francisco Gómez. 548 00:31:58.640 --> 00:32:00.600 position:50.00%,middle align:middle size:80.00% line:79.33% - Nos conocimos antes. - Sí. 549 00:32:01.680 --> 00:32:03.200 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Quiere tomar algo? - No, gracias. 550 00:32:03.360 --> 00:32:05.640 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Un white lady? La bebida de las chicas de ciudad. 551 00:32:05.800 --> 00:32:07.640 position:50.00%,middle align:middle size:80.00% line:84.67% Esta noche se merece un buen brindis. 552 00:32:07.800 --> 00:32:10.680 position:50.00%,middle align:middle size:80.00% line:79.33% Y se lo agradezco, pero no me gusta el white lady. 553 00:32:10.840 --> 00:32:12.200 position:50.00%,middle align:middle size:80.00% line:84.67% Eso se llama sinceridad. 554 00:32:12.360 --> 00:32:14.840 position:50.00%,middle align:middle size:80.00% line:79.33% Es una cualidad muy poco extendida, por cierto. 555 00:32:15.000 --> 00:32:16.600 position:50.00%,middle align:middle size:80.00% line:84.67% Resulta que la señorita Aguilar 556 00:32:16.760 --> 00:32:19.720 position:50.00%,middle align:middle size:80.00% line:79.33% tiene de sobra para ti y para mí, además de otras muchas virtudes. 557 00:32:19.880 --> 00:32:21.120 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Cuáles? 558 00:32:21.280 --> 00:32:22.960 position:50.00%,middle align:middle size:80.00% line:84.67% [para la música] 559 00:32:23.120 --> 00:32:26.080 position:50.00%,middle align:middle size:80.00% line:79.33% Me parece que vamos a tener que esperar para conocerlas. 560 00:32:26.240 --> 00:32:27.480 position:50.00%,middle align:middle size:80.00% line:84.67% Cariño... 561 00:32:30.000 --> 00:32:33.400 position:50.00%,middle align:middle size:80.00% line:79.33% Mi padre me ha pedido que os avise. Os espera para subir al escenario. 562 00:32:37.040 --> 00:32:39.680 position:50.00%,middle align:middle size:80.00% line:79.33% [Carlos] Ni se mueva, señorita Aguilar, aún me debe un baile. 563 00:32:43.040 --> 00:32:45.440 position:50.00%,middle align:middle size:80.00% line:79.33% [Ricardo] Quiero agradecer a todos los presentes 564 00:32:45.600 --> 00:32:48.800 position:50.00%,middle align:middle size:80.00% line:79.33% que hayáis venido esta noche a compartir con nosotros 565 00:32:48.960 --> 00:32:52.560 position:50.00%,middle align:middle size:80.00% line:79.33% la inauguración de la mayor central de telefonía de Europa. 566 00:32:52.720 --> 00:32:56.160 position:50.00%,middle align:middle size:80.00% line:79.33% Todos sabéis que no ha sido un camino fácil ni corto 567 00:32:56.320 --> 00:32:59.560 position:50.00%,middle align:middle size:80.00% line:79.33% y que estas canas están directamente relacionadas con este camino. 568 00:32:59.720 --> 00:33:01.800 position:50.00%,middle align:middle size:80.00% line:84.67% Pero ya estamos aquí. 569 00:33:01.960 --> 00:33:03.320 position:50.00%,middle align:middle size:80.00% line:84.67% Y no lo estaríamos si no fuese 570 00:33:03.480 --> 00:33:06.440 position:50.00%,middle align:middle size:80.00% line:79.33% por la persona que ha impulsado personalmente este proyecto. 571 00:33:07.360 --> 00:33:08.480 position:50.00%,middle align:middle size:80.00% line:84.67% Es leal, 572 00:33:08.640 --> 00:33:10.680 position:50.00%,middle align:middle size:80.00% line:84.67% trabajador, honesto 573 00:33:11.040 --> 00:33:13.360 position:50.00%,middle align:middle size:80.00% line:84.67% y tengo la tranquilidad de saber 574 00:33:13.520 --> 00:33:16.840 position:50.00%,middle align:middle size:80.00% line:79.33% que dejo el futuro de la compañía en manos de mi yerno, 575 00:33:17.000 --> 00:33:19.040 position:50.00%,middle align:middle size:80.00% line:84.67% al que ya considero como mi propio hijo. 576 00:33:19.200 --> 00:33:20.600 position:50.00%,middle align:middle size:80.00% line:84.67% Francisco Gómez. Francisco. 577 00:33:28.320 --> 00:33:29.320 position:50.00%,middle align:middle size:80.00% line:84.67% [Francisco] Gracias. 578 00:33:29.480 --> 00:33:30.560 position:50.00%,middle align:middle size:80.00% line:84.67% Gracias. 579 00:33:30.720 --> 00:33:32.280 position:50.00%,middle align:middle size:80.00% line:84.67% Hace 10 años, ni siquiera 580 00:33:32.440 --> 00:33:34.120 position:50.00%,middle align:middle size:80.00% line:84.67% hubiera imaginado estar hoy aquí. 581 00:33:37.200 --> 00:33:38.760 position:50.00%,middle align:middle size:80.00% line:84.67% Me conformaba con un buen trabajo 582 00:33:38.920 --> 00:33:41.440 position:50.00%,middle align:middle size:80.00% line:79.33% y una habitación que compartir con el amor de mi vida. 583 00:33:44.680 --> 00:33:47.280 position:50.00%,middle align:middle size:80.00% line:79.33% Hoy tengo casi todo aquello con lo que había soñado. 584 00:33:48.320 --> 00:33:50.360 position:50.00%,middle align:middle size:80.00% line:84.67% Casi, porque siempre se puede 585 00:33:50.520 --> 00:33:51.800 position:50.00%,middle align:middle size:80.00% line:84.67% desear algo más. 586 00:33:52.560 --> 00:33:55.120 position:50.00%,middle align:middle size:80.00% line:79.33% Hoy es el principio de una nueva era para la compañía. 587 00:33:55.280 --> 00:33:57.240 position:50.00%,middle align:middle size:80.00% line:84.67% Una era que va a traspasar océanos. 588 00:33:57.400 --> 00:34:00.640 position:50.00%,middle align:middle size:80.00% line:79.33% Me complace anunciar que esta compañía va a realizar mañana 589 00:34:00.800 --> 00:34:03.000 position:50.00%,middle align:middle size:80.00% line:84.67% la primera llamada intercontinental. 590 00:34:03.160 --> 00:34:05.440 position:50.00%,middle align:middle size:80.00% line:79.33% El presidente Coolidge y su majestad Alfonso XIII 591 00:34:05.600 --> 00:34:07.840 position:50.00%,middle align:middle size:80.00% line:79.33% establecerán la primera conexión de la historia 592 00:34:08.000 --> 00:34:11.120 position:50.00%,middle align:middle size:80.00% line:79.33% entre España y los Estados Unidos de América. 593 00:34:19.080 --> 00:34:23.320 position:50.00%,middle align:middle size:80.00% line:79.33% [Alba] Había intentado convencerme durante años de que Francisco 594 00:34:23.480 --> 00:34:25.239 position:50.00%,middle align:middle size:80.00% line:84.67% ya no significaba nada para mí. 595 00:34:25.400 --> 00:34:28.800 position:50.00%,middle align:middle size:80.00% line:79.33% Si quería ser libre, no debía dejarme llevar por el corazón. 596 00:34:34.159 --> 00:34:35.360 position:50.00%,middle align:middle size:80.00% line:84.67% Esa noche 597 00:34:35.520 --> 00:34:38.760 position:50.00%,middle align:middle size:80.00% line:79.33% robaría el dinero de la compañía y huiría lejos 598 00:34:38.920 --> 00:34:41.639 position:50.00%,middle align:middle size:80.00% line:79.33% de cualquier fantasma del pasado, bueno o malo, 599 00:34:42.080 --> 00:34:44.280 position:50.00%,middle align:middle size:80.00% line:84.67% para ser... simplemente yo. 600 00:34:44.440 --> 00:34:46.679 position:50.00%,middle align:middle size:80.00% line:84.67% Disculpe, señorita, su identificación. 601 00:34:53.400 --> 00:34:54.800 position:50.00%,middle align:middle size:80.00% line:84.67% Aquí pone "turno de día". 602 00:34:54.960 --> 00:34:56.679 position:50.00%,middle align:middle size:80.00% line:84.67% No se permite el paso a ningún empleado 603 00:34:56.840 --> 00:34:58.400 position:50.00%,middle align:middle size:80.00% line:79.33% - que no sea del turno de noche. - Ya, ya. 604 00:34:58.560 --> 00:35:01.000 position:50.00%,middle align:middle size:80.00% line:79.33% Pero será solo un momento. Me he dejado el bolso. 605 00:35:01.160 --> 00:35:03.320 position:50.00%,middle align:middle size:80.00% line:79.33% Lo siento, pero tendrá que recogerlo mañana. 606 00:35:04.640 --> 00:35:06.720 position:50.00%,middle align:middle size:80.00% line:84.67% Es que me he dejado las llaves dentro. 607 00:35:09.200 --> 00:35:11.040 position:50.00%,middle align:middle size:80.00% line:84.67% Y no tengo a nadie a quien llamar. 608 00:35:12.000 --> 00:35:13.400 position:50.00%,middle align:middle size:80.00% line:84.67% Hágame el favor. 609 00:35:14.160 --> 00:35:17.040 position:50.00%,middle align:middle size:80.00% line:79.33% O, si no, me voy a quedar toda la noche en la calle. Será... 610 00:35:17.200 --> 00:35:18.480 position:50.00%,middle align:middle size:80.00% line:84.67% Será solo un momento. 611 00:36:06.960 --> 00:36:09.640 position:50.00%,middle align:middle size:80.00% line:79.33% Debemos ser los únicos a los que no han invitado a la fiesta. 612 00:36:09.800 --> 00:36:12.000 position:50.00%,middle align:middle size:80.00% line:79.33% Julián dice que han ido hasta los mozos de carga. 613 00:36:12.160 --> 00:36:13.720 position:50.00%,middle align:middle size:80.00% line:84.67% [puerta] 614 00:36:19.080 --> 00:36:20.080 position:50.00%,middle align:middle size:80.00% line:84.67% [música] 615 00:36:20.240 --> 00:36:21.600 position:50.00%,middle align:middle size:80.00% line:84.67% Esta me encanta. ¿La conoces? 616 00:36:21.760 --> 00:36:23.600 position:50.00%,middle align:middle size:80.00% line:79.33% - No. - Bueno, pues si tienes 617 00:36:23.760 --> 00:36:26.480 position:50.00%,middle align:middle size:80.00% line:79.33% un mal día, te pones esta canción y todos tus males se te pasan. 618 00:36:26.640 --> 00:36:29.080 position:50.00%,middle align:middle size:80.00% line:79.33% - Mira, escucha. - Creo que las hay mejores que esta, 619 00:36:29.240 --> 00:36:31.960 position:50.00%,middle align:middle size:80.00% line:79.33% pero si esta consigue que te olvides de tus problemas... 620 00:36:32.120 --> 00:36:34.480 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Cómo has salido del cuartel? - Por la ventana. 621 00:36:34.640 --> 00:36:36.240 position:50.00%,middle align:middle size:80.00% line:84.67% Qué dura es la vida del soldado. 622 00:36:36.400 --> 00:36:38.400 position:50.00%,middle align:middle size:80.00% line:84.67% Menos mal que las noches son 623 00:36:38.560 --> 00:36:39.680 position:50.00%,middle align:middle size:80.00% line:84.67% mucho más alegres. 624 00:36:39.840 --> 00:36:41.160 position:50.00%,middle align:middle size:80.00% line:84.67% Venga, vamos a bailar. 625 00:37:06.000 --> 00:37:07.480 position:50.00%,middle align:middle size:80.00% line:84.67% [jadeos] 626 00:37:41.920 --> 00:37:43.960 position:50.00%,middle align:middle size:80.00% line:79.33% - ¡Ah! - ¡Eh! ¡Oiga! 627 00:37:44.520 --> 00:37:45.600 position:50.00%,middle align:middle size:80.00% line:84.67% Deténgase. 628 00:37:47.000 --> 00:37:48.320 position:50.00%,middle align:middle size:80.00% line:84.67% [gruñe] 629 00:37:58.520 --> 00:37:59.840 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Está usted bien? 630 00:38:03.680 --> 00:38:06.560 position:50.00%,middle align:middle size:80.00% line:79.33% No he podido atraparle, pero al menos he conseguido su bolso. 631 00:38:06.720 --> 00:38:07.840 position:50.00%,middle align:middle size:80.00% line:84.67% Creo que está todo. 632 00:38:33.080 --> 00:38:34.240 position:50.00%,middle align:middle size:80.00% line:84.67% [Carlota] ¡Ah! 633 00:38:40.960 --> 00:38:42.640 position:50.00%,middle align:middle size:80.00% line:79.33% [risita] A ver qué excusa me invento mañana 634 00:38:42.800 --> 00:38:45.080 position:50.00%,middle align:middle size:80.00% line:79.33% para salir de aquí y llegar a tiempo al turno. 635 00:38:45.240 --> 00:38:48.000 position:50.00%,middle align:middle size:80.00% line:79.33% Sabes que no puedes estar así toda la vida... 636 00:38:48.160 --> 00:38:50.280 position:50.00%,middle align:middle size:80.00% line:79.33% Ya, pero es que no me queda otra alternativa. 637 00:38:50.440 --> 00:38:51.720 position:50.00%,middle align:middle size:80.00% line:79.33% - Sí que la tienes. - Ah, ¿sí? 638 00:38:51.880 --> 00:38:52.840 position:50.00%,middle align:middle size:80.00% line:84.67% Cásate conmigo. 639 00:38:53.000 --> 00:38:54.200 position:50.00%,middle align:middle size:80.00% line:84.67% [riendo] Estás loco. 640 00:38:54.360 --> 00:38:56.960 position:50.00%,middle align:middle size:80.00% line:79.33% Sí, yo tampoco pensé nunca que te fuese a decir esto, 641 00:38:57.120 --> 00:38:58.160 position:50.00%,middle align:middle size:80.00% line:84.67% pero es la solución. 642 00:38:58.320 --> 00:39:00.400 position:50.00%,middle align:middle size:80.00% line:84.67% Ah, que es la solución, ¿eh? Sí. 643 00:39:02.640 --> 00:39:05.440 position:50.00%,middle align:middle size:80.00% line:79.33% - No dependerías de tu padre. - No, no dependería de mi padre. 644 00:39:05.600 --> 00:39:07.000 position:50.00%,middle align:middle size:80.00% line:84.67% Dependería de ti, mi marido, 645 00:39:07.160 --> 00:39:09.400 position:50.00%,middle align:middle size:80.00% line:79.33% que es el perfecto sinónimo de independencia. 646 00:39:09.560 --> 00:39:12.040 position:50.00%,middle align:middle size:80.00% line:79.33% Y te crees que yo te voy a decir lo que tienes que hacer... 647 00:39:12.840 --> 00:39:14.320 position:50.00%,middle align:middle size:80.00% line:84.67% Estás borracho, me voy a casa. 648 00:39:14.480 --> 00:39:16.960 position:50.00%,middle align:middle size:80.00% line:84.67% Sí, estoy borracho, pero tengo razón. 649 00:39:17.280 --> 00:39:18.920 position:50.00%,middle align:middle size:80.00% line:84.67% Piénsatelo. 650 00:39:26.480 --> 00:39:28.440 position:50.00%,middle align:middle size:80.00% line:84.67% [motor a lo lejos] 651 00:39:29.840 --> 00:39:31.040 position:50.00%,middle align:middle size:80.00% line:84.67% [hombre] ¡Vamos, Miguel! 652 00:39:32.680 --> 00:39:34.080 position:50.00%,middle align:middle size:80.00% line:84.67% [Miguel] Buenas noches. 653 00:39:34.320 --> 00:39:35.800 position:50.00%,middle align:middle size:80.00% line:84.67% Eh, esperadme, chicos. 654 00:39:50.920 --> 00:39:52.640 position:50.00%,middle align:middle size:80.00% line:84.67% [pequeños golpes] 655 00:40:00.320 --> 00:40:01.520 position:50.00%,middle align:middle size:80.00% line:84.67% [suspira] 656 00:40:07.560 --> 00:40:08.760 position:50.00%,middle align:middle size:80.00% line:84.67% Perdona. No hagas ruido. 657 00:40:08.920 --> 00:40:11.640 position:50.00%,middle align:middle size:80.00% line:79.33% Gracias, Marga. Es que no sabía dónde estaba Miguel. 658 00:40:11.800 --> 00:40:14.480 position:50.00%,middle align:middle size:80.00% line:79.33% No te preocupes, hay dos camas, y las dos son para ti si quieres. 659 00:40:14.640 --> 00:40:16.240 position:50.00%,middle align:middle size:80.00% line:79.33% - Yo mañana me voy al pueblo. - ¿Por qué? 660 00:40:16.400 --> 00:40:18.000 position:50.00%,middle align:middle size:80.00% line:84.67% Porque esto no es para mí, Carlota. 661 00:40:18.160 --> 00:40:20.760 position:50.00%,middle align:middle size:80.00% line:79.33% Me he perdido, se me ha roto la maleta, me han robado el bolso. 662 00:40:20.920 --> 00:40:22.440 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Te han robado? - Sí, pero da igual. 663 00:40:22.600 --> 00:40:23.960 position:50.00%,middle align:middle size:80.00% line:84.67% Yo no quiero estar aquí. 664 00:40:24.120 --> 00:40:26.760 position:50.00%,middle align:middle size:80.00% line:79.33% Ay, Marga, ¿tú sabes cuánto tiempo estuve fingiendo 665 00:40:26.920 --> 00:40:28.320 position:50.00%,middle align:middle size:80.00% line:84.67% que iba a clase de labores? 666 00:40:28.600 --> 00:40:29.880 position:50.00%,middle align:middle size:80.00% line:84.67% Un año. 667 00:40:30.040 --> 00:40:32.400 position:50.00%,middle align:middle size:80.00% line:79.33% Un año mientras me pagaba las clases de mecanografía. 668 00:40:32.560 --> 00:40:35.800 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Y por qué? No quería que mis padres se enteraran de que quería trabajar. 669 00:40:35.960 --> 00:40:37.640 position:50.00%,middle align:middle size:80.00% line:84.67% Tenía miedo de que me echaran de casa. 670 00:40:39.320 --> 00:40:40.600 position:50.00%,middle align:middle size:80.00% line:84.67% Vaya, lo siento. 671 00:40:41.440 --> 00:40:42.760 position:50.00%,middle align:middle size:80.00% line:84.67% Pero ¿lo siento por qué? 672 00:40:43.560 --> 00:40:45.120 position:50.00%,middle align:middle size:80.00% line:84.67% De lo siento nada, no. 673 00:40:45.520 --> 00:40:48.440 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Sabes cuál es el lado bueno? Que ya no tengo miedo. 674 00:40:50.160 --> 00:40:51.080 position:50.00%,middle align:middle size:80.00% line:84.67% De verdad, Carlota, 675 00:40:51.240 --> 00:40:53.560 position:50.00%,middle align:middle size:80.00% line:79.33% que no sé cómo te puedes tomar las cosas así de bien. 676 00:40:53.720 --> 00:40:55.840 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Y qué quieres que haga, que me ponga a llorar 677 00:40:56.000 --> 00:40:58.760 position:50.00%,middle align:middle size:80.00% line:79.33% porque mis padres prefieren sus ideas a una hija como yo? 678 00:40:58.920 --> 00:41:00.680 position:50.00%,middle align:middle size:80.00% line:84.67% ¿De qué iba a servir? De nada. Mira. 679 00:41:00.840 --> 00:41:02.400 position:50.00%,middle align:middle size:80.00% line:84.67% Hay dos maneras de ver la vida. 680 00:41:02.560 --> 00:41:05.440 position:50.00%,middle align:middle size:80.00% line:79.33% Puedes ver el lado negativo, ponerte a llorar, protestar... 681 00:41:05.600 --> 00:41:07.000 position:50.00%,middle align:middle size:80.00% line:84.67% O puedes ver el lado divertido, 682 00:41:07.160 --> 00:41:09.240 position:50.00%,middle align:middle size:80.00% line:84.67% disfrutar, sonreír, y tirar hacia delante. 683 00:41:09.400 --> 00:41:10.800 position:50.00%,middle align:middle size:80.00% line:84.67% Y ese es el que he elegido yo. 684 00:41:12.440 --> 00:41:15.120 position:50.00%,middle align:middle size:80.00% line:79.33% Todo en la vida tiene algo bueno, hasta Madrid. 685 00:41:16.280 --> 00:41:18.480 position:50.00%,middle align:middle size:80.00% line:84.67% Aunque... no lo veas. 686 00:41:22.160 --> 00:41:24.600 position:50.00%,middle align:middle size:80.00% line:84.67% Si no robo esa caja durante la llamada, 687 00:41:25.720 --> 00:41:28.920 position:50.00%,middle align:middle size:80.00% line:79.33% creo que vas a tener que buscarte otro hogar. 688 00:41:31.480 --> 00:41:33.400 position:50.00%,middle align:middle size:80.00% line:84.67% Beltrán no me va a dar más tiempo. 689 00:41:35.520 --> 00:41:37.600 position:50.00%,middle align:middle size:80.00% line:84.67% O entrego ese dinero mañana a la una... 690 00:41:39.480 --> 00:41:41.160 position:50.00%,middle align:middle size:80.00% line:84.67% Esta es mi última oportunidad. 691 00:41:43.040 --> 00:41:46.200 position:50.00%,middle align:middle size:80.00% line:79.33% Extra, extra. Hoy se realiza la primera llamada a América. 692 00:41:46.360 --> 00:41:49.960 position:50.00%,middle align:middle size:80.00% line:79.33% Extra, extra. Hoy el rey llamará a América. 693 00:42:00.200 --> 00:42:01.880 position:50.00%,middle align:middle size:80.00% line:84.67% Mario, ¿puedo hablar contigo? 694 00:42:02.040 --> 00:42:04.520 position:50.00%,middle align:middle size:80.00% line:79.33% - Tengo mucho trabajo, ahora no. - He estado toda la noche 695 00:42:04.680 --> 00:42:06.560 position:50.00%,middle align:middle size:80.00% line:84.67% en vilo sin saber si estabas bien. 696 00:42:07.120 --> 00:42:09.040 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Dónde has estado? - Caminando. 697 00:42:10.320 --> 00:42:11.640 position:50.00%,middle align:middle size:80.00% line:84.67% Toda la noche... 698 00:42:11.800 --> 00:42:14.160 position:50.00%,middle align:middle size:80.00% line:79.33% Toda la noche. Tenía muchas cosas en las que pensar. 699 00:42:15.480 --> 00:42:16.520 position:50.00%,middle align:middle size:80.00% line:84.67% Mario... 700 00:42:16.680 --> 00:42:19.560 position:50.00%,middle align:middle size:80.00% line:79.33% Ángeles, no voy a volver a discutir contigo y menos aquí. 701 00:42:20.120 --> 00:42:23.720 position:50.00%,middle align:middle size:80.00% line:79.33% Es que no quiero discutir, de verdad, solo quiero que hablemos. 702 00:42:23.880 --> 00:42:25.000 position:50.00%,middle align:middle size:80.00% line:84.67% Por favor. 703 00:42:35.320 --> 00:42:36.360 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Quieres hablar? 704 00:42:37.480 --> 00:42:38.520 position:50.00%,middle align:middle size:80.00% line:84.67% Habla. 705 00:42:39.120 --> 00:42:41.600 position:50.00%,middle align:middle size:80.00% line:84.67% Mario, de verdad, ¿tú no puedes entender 706 00:42:41.760 --> 00:42:45.080 position:50.00%,middle align:middle size:80.00% line:79.33% que me haya molestado que me pidas que deje mi trabajo? 707 00:42:45.240 --> 00:42:47.400 position:50.00%,middle align:middle size:80.00% line:79.33% No me lo puedo creer. Pero ¿tú qué te piensas, 708 00:42:47.560 --> 00:42:50.560 position:50.00%,middle align:middle size:80.00% line:79.33% que yo no me he dado cuenta de lo mucho que te gusta tu trabajo? 709 00:42:50.720 --> 00:42:53.000 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Lo que te está costando dejar todo esto? 710 00:42:53.800 --> 00:42:55.320 position:50.00%,middle align:middle size:80.00% line:84.67% Pero nosotros tenemos una hija, 711 00:42:55.480 --> 00:42:58.400 position:50.00%,middle align:middle size:80.00% line:79.33% Ángeles, y yo no quiero que a mi hija la cuide una desconocida, 712 00:42:58.560 --> 00:42:59.960 position:50.00%,middle align:middle size:80.00% line:84.67% quiero que la cuide su madre. 713 00:43:00.120 --> 00:43:01.640 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Te has parado a pensar lo que sufro 714 00:43:01.800 --> 00:43:04.080 position:50.00%,middle align:middle size:80.00% line:79.33% porque no le puedo dar a mi familia lo que se merece? 715 00:43:04.240 --> 00:43:06.080 position:50.00%,middle align:middle size:80.00% line:84.67% Te prometí que no te iba a faltar de nada, 716 00:43:06.240 --> 00:43:07.720 position:50.00%,middle align:middle size:80.00% line:84.67% que te iba a tratar como una reina. 717 00:43:07.880 --> 00:43:10.280 position:50.00%,middle align:middle size:80.00% line:79.33% ¿Y qué he conseguido? Que te pases el día trabajando, 718 00:43:10.440 --> 00:43:11.800 position:50.00%,middle align:middle size:80.00% line:84.67% cogiendo llamadas. 719 00:43:15.480 --> 00:43:18.440 position:50.00%,middle align:middle size:80.00% line:79.33% Ángeles, tú te has convencido de que todo esto está bien. 720 00:43:18.600 --> 00:43:19.680 position:50.00%,middle align:middle size:80.00% line:84.67% No lo está. 721 00:43:22.280 --> 00:43:23.640 position:50.00%,middle align:middle size:80.00% line:84.67% Déjame hacerte feliz. 722 00:43:25.080 --> 00:43:26.080 position:50.00%,middle align:middle size:80.00% line:84.67% Por favor. 723 00:43:28.960 --> 00:43:31.040 position:50.00%,middle align:middle size:80.00% line:84.67% Seamos la familia que siempre soñamos ser. 724 00:43:44.040 --> 00:43:46.400 position:50.00%,middle align:middle size:80.00% line:10.00% [puerta abriéndose] 725 00:43:47.320 --> 00:43:48.440 position:50.00%,middle align:middle size:80.00% line:84.67% [Carlos] Buenos días. 726 00:43:58.120 --> 00:44:01.160 position:50.00%,middle align:middle size:80.00% line:79.33% - ¿Se puede saber qué estás buscando? - Las llaves de la caja fuerte, 727 00:44:01.320 --> 00:44:02.960 position:50.00%,middle align:middle size:80.00% line:84.67% que me las olvidé aquí o las perdí ayer, 728 00:44:03.120 --> 00:44:05.040 position:50.00%,middle align:middle size:80.00% line:79.33% donde quiera que haya estado toda la noche. 729 00:44:05.200 --> 00:44:08.240 position:50.00%,middle align:middle size:80.00% line:79.33% Algún día deberías de probar a beber dos copas menos de coñac. 730 00:44:08.400 --> 00:44:11.080 position:50.00%,middle align:middle size:80.00% line:79.33% - Le vendría bien a tu memoria. - Y fatal a mi alma. 731 00:44:11.240 --> 00:44:13.480 position:50.00%,middle align:middle size:80.00% line:79.33% Es libre, no puedo estar poniéndole cortapisas. 732 00:44:14.280 --> 00:44:15.360 position:50.00%,middle align:middle size:80.00% line:84.67% Ya. 733 00:44:15.600 --> 00:44:17.960 position:50.00%,middle align:middle size:80.00% line:79.33% Y tú algún deberías probar a soltar alguna sonrisa. 734 00:44:19.600 --> 00:44:20.760 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Estás bien? 735 00:44:23.360 --> 00:44:24.880 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Recuerdas a Alba? 736 00:44:25.040 --> 00:44:26.120 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Alba? 737 00:44:31.480 --> 00:44:33.000 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Tu amor de la adolescencia, 738 00:44:33.160 --> 00:44:35.240 position:50.00%,middle align:middle size:80.00% line:84.67% la que desapareció sin despedirse, 739 00:44:35.400 --> 00:44:37.080 position:50.00%,middle align:middle size:80.00% line:84.67% la que se escapó corriendo de ti? 740 00:44:37.240 --> 00:44:38.560 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Esa? No. 741 00:44:39.360 --> 00:44:41.440 position:50.00%,middle align:middle size:80.00% line:79.33% - Ayer me pareció verla. - ¿Otra vez? 742 00:44:41.600 --> 00:44:42.720 position:50.00%,middle align:middle size:80.00% line:84.67% Se parecía mucho. 743 00:44:43.280 --> 00:44:45.360 position:50.00%,middle align:middle size:80.00% line:84.67% Francisco, la gente en diez años cambia. 744 00:44:46.320 --> 00:44:48.000 position:50.00%,middle align:middle size:80.00% line:84.67% Mucho. Mírate tú. 745 00:44:48.560 --> 00:44:50.440 position:50.00%,middle align:middle size:80.00% line:84.67% [ríe] 746 00:44:50.520 --> 00:44:52.080 position:50.00%,middle align:middle size:80.00% line:84.67% Sí, tienes razón, olvídalo. 747 00:44:52.360 --> 00:44:53.800 position:50.00%,middle align:middle size:80.00% line:84.67% Ha sido una tontería. 748 00:44:54.480 --> 00:44:56.560 position:50.00%,middle align:middle size:80.00% line:84.67% Además, hay mucho que hacer. 749 00:44:57.920 --> 00:44:59.440 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Lo suficientemente elegante 750 00:44:59.600 --> 00:45:01.960 position:50.00%,middle align:middle size:80.00% line:79.33% como para ser inmortalizado junto a su majestad? 751 00:45:02.400 --> 00:45:03.640 position:50.00%,middle align:middle size:80.00% line:84.67% Si no tuvieras esas ojeras, 752 00:45:03.800 --> 00:45:05.600 position:50.00%,middle align:middle size:80.00% line:84.67% hasta creería que eres un hombre formal. 753 00:45:06.720 --> 00:45:08.560 position:50.00%,middle align:middle size:80.00% line:84.67% Hay... mucho que hacer. 754 00:45:09.120 --> 00:45:10.960 position:50.00%,middle align:middle size:80.00% line:84.67% [pisa fuerte] Orden del día. 755 00:45:11.120 --> 00:45:12.640 position:50.00%,middle align:middle size:80.00% line:84.67% Antes de nada, comunicarle a Sara 756 00:45:12.800 --> 00:45:14.960 position:50.00%,middle align:middle size:80.00% line:79.33% quién será la encargada de efectuar la llamada. 757 00:45:15.120 --> 00:45:16.160 position:50.00%,middle align:middle size:80.00% line:84.67% Ya me he encargado yo. 758 00:45:17.160 --> 00:45:18.440 position:50.00%,middle align:middle size:80.00% line:84.67% Personalmente. 759 00:45:24.320 --> 00:45:25.360 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Qué? 760 00:45:25.840 --> 00:45:27.920 position:50.00%,middle align:middle size:80.00% line:79.33% - Siento el retraso. - ¿Te lo has pensado mejor? 761 00:45:28.280 --> 00:45:29.280 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Qué está pasando? 762 00:45:29.440 --> 00:45:32.280 position:50.00%,middle align:middle size:80.00% line:79.33% Ah, van a anunciar la telefonista que va a hacer la llamada del rey. 763 00:45:32.440 --> 00:45:34.840 position:50.00%,middle align:middle size:80.00% line:79.33% Se lo darán a Ángeles, es la que tiene más experiencia. 764 00:45:35.000 --> 00:45:36.000 position:50.00%,middle align:middle size:80.00% line:84.67% Señoritas... 765 00:45:36.160 --> 00:45:38.400 position:50.00%,middle align:middle size:80.00% line:84.67% No vamos a demorar más este momento. 766 00:45:38.560 --> 00:45:40.080 position:50.00%,middle align:middle size:80.00% line:84.67% Todas saben que hoy se llevará a cabo 767 00:45:40.240 --> 00:45:41.920 position:50.00%,middle align:middle size:80.00% line:84.67% la conexión transoceánica entre el rey 768 00:45:42.080 --> 00:45:44.280 position:50.00%,middle align:middle size:80.00% line:84.67% y el presidente de los EE. UU. 769 00:45:44.440 --> 00:45:47.320 position:50.00%,middle align:middle size:80.00% line:79.33% Hay que designar a la telefonista que llevará a cabo dicha conexión. 770 00:45:47.480 --> 00:45:48.880 position:50.00%,middle align:middle size:80.00% line:84.67% Es una gran responsabilidad, 771 00:45:49.040 --> 00:45:51.680 position:50.00%,middle align:middle size:80.00% line:79.33% así que espero que la persona elegida se lo tome como tal. 772 00:45:51.840 --> 00:45:55.400 position:50.00%,middle align:middle size:80.00% line:79.33% La telefonista elegida es... Lidia Aguilar. 773 00:45:55.880 --> 00:45:57.000 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Yo? 774 00:45:58.080 --> 00:45:59.320 position:50.00%,middle align:middle size:80.00% line:84.67% No puedo hacerlo. 775 00:45:59.480 --> 00:46:00.760 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Perdone, señorita Aguilar? 776 00:46:00.920 --> 00:46:04.320 position:50.00%,middle align:middle size:80.00% line:79.33% Acabo de llegar, no conozco el puesto, debería hacerlo alguien 777 00:46:04.480 --> 00:46:05.440 position:50.00%,middle align:middle size:80.00% line:84.67% con más experiencia. 778 00:46:05.600 --> 00:46:07.520 position:50.00%,middle align:middle size:80.00% line:79.33% - Ángeles, por ejemplo. - Estoy de acuerdo. 779 00:46:07.680 --> 00:46:10.160 position:50.00%,middle align:middle size:80.00% line:79.33% Pero la decisión no ha sido mía, son órdenes de Dirección. 780 00:46:10.320 --> 00:46:12.000 position:50.00%,middle align:middle size:80.00% line:84.67% Espero que cumpla con su cometido. 781 00:46:12.840 --> 00:46:14.840 position:50.00%,middle align:middle size:80.00% line:84.67% Aquí tiene, el protocolo de actuación. 782 00:46:15.000 --> 00:46:17.240 position:50.00%,middle align:middle size:80.00% line:79.33% Apréndaselo como si le fuese la vida en ello. 783 00:46:17.400 --> 00:46:18.960 position:50.00%,middle align:middle size:80.00% line:84.67% Porque le aseguro que le va. 784 00:46:20.360 --> 00:46:21.480 position:50.00%,middle align:middle size:80.00% line:84.67% El resto de centralitas 785 00:46:21.640 --> 00:46:24.160 position:50.00%,middle align:middle size:80.00% line:79.33% se desconectarán durante un periodo de 10 minutos. 786 00:46:24.320 --> 00:46:26.800 position:50.00%,middle align:middle size:80.00% line:79.33% Así que todas ustedes no tendrán que hacer otra cosa más 787 00:46:26.960 --> 00:46:28.400 position:50.00%,middle align:middle size:80.00% line:84.67% que presenciar este momento. 788 00:46:34.880 --> 00:46:37.520 position:50.00%,middle align:middle size:80.00% line:79.33% - No voy a hacerlo. - Señorita, el protocolo es sencillo. 789 00:46:37.680 --> 00:46:39.760 position:50.00%,middle align:middle size:80.00% line:84.67% Y ayer se manejaba bien con las clavijas. 790 00:46:39.920 --> 00:46:40.920 position:50.00%,middle align:middle size:80.00% line:84.67% No me refiero... 791 00:46:41.080 --> 00:46:43.960 position:50.00%,middle align:middle size:80.00% line:79.33% Si falla cualquier cosa, habrá más telefonistas para cubrirla. 792 00:46:48.400 --> 00:46:50.520 position:50.00%,middle align:middle size:80.00% line:79.33% Es un momento muy importante para la compañía. 793 00:46:50.680 --> 00:46:53.400 position:50.00%,middle align:middle size:80.00% line:79.33% Vas a ponerlo en peligro para conquistar a una telefonista. 794 00:46:53.560 --> 00:46:55.120 position:50.00%,middle align:middle size:80.00% line:84.67% Me estás hablando como mi padre. 795 00:46:55.280 --> 00:46:58.600 position:50.00%,middle align:middle size:80.00% line:79.33% ¿De verdad piensas eso, piensas que lo hago solo para conquistarla? 796 00:46:58.760 --> 00:46:59.760 position:50.00%,middle align:middle size:80.00% line:84.67% Carlos... 797 00:46:59.920 --> 00:47:02.200 position:50.00%,middle align:middle size:80.00% line:79.33% Me gusta la imagen que proyecta para la empresa. 798 00:47:02.360 --> 00:47:04.720 position:50.00%,middle align:middle size:80.00% line:79.33% Vamos, es una chica ambiciosa. Creo que lo hará bien. 799 00:47:04.880 --> 00:47:06.360 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Por qué no me consultaste nada? 800 00:47:06.520 --> 00:47:08.440 position:50.00%,middle align:middle size:80.00% line:84.67% No sabía que tenía que consultarte todo. 801 00:47:10.480 --> 00:47:12.200 position:50.00%,middle align:middle size:80.00% line:84.67% Además, de las selecciones de personal 802 00:47:12.360 --> 00:47:13.600 position:50.00%,middle align:middle size:80.00% line:84.67% siempre me he encargado yo. 803 00:47:15.680 --> 00:47:17.800 position:50.00%,middle align:middle size:80.00% line:84.67% Confía en mí, por favor. 804 00:47:19.520 --> 00:47:21.840 position:50.00%,middle align:middle size:80.00% line:79.33% La expectación es máxima entre los medios reunidos 805 00:47:22.000 --> 00:47:24.440 position:50.00%,middle align:middle size:80.00% line:79.33% en esta décima planta de la compañía telefónica. 806 00:47:24.600 --> 00:47:27.520 position:50.00%,middle align:middle size:80.00% line:79.33% Hoy, a las 12 en punto del mediodía, se producirá 807 00:47:27.680 --> 00:47:29.720 position:50.00%,middle align:middle size:80.00% line:84.67% un hecho que marcará un antes y un después 808 00:47:29.880 --> 00:47:30.880 position:50.00%,middle align:middle size:80.00% line:84.67% en la historia: 809 00:47:31.040 --> 00:47:33.640 position:50.00%,middle align:middle size:80.00% line:79.33% la primera llamada telefónica intercontinental realizada 810 00:47:33.800 --> 00:47:34.880 position:50.00%,middle align:middle size:80.00% line:84.67% desde nuestro país 811 00:47:35.040 --> 00:47:37.560 position:50.00%,middle align:middle size:80.00% line:79.33% y para la que se cuenta con dos anfitriones de lujo 812 00:47:37.720 --> 00:47:39.400 position:50.00%,middle align:middle size:80.00% line:84.67% a ambos lados del Atlántico. 813 00:47:41.760 --> 00:47:42.880 position:50.00%,middle align:middle size:80.00% line:84.67% [vítores y aplausos] 814 00:47:43.040 --> 00:47:44.480 position:50.00%,middle align:middle size:80.00% line:84.67% Su majestad, Alfonso XIII, 815 00:47:44.640 --> 00:47:47.280 position:50.00%,middle align:middle size:80.00% line:79.33% cuya llegada al palacio de la Compañía de Telefonía 816 00:47:47.440 --> 00:47:49.120 position:50.00%,middle align:middle size:80.00% line:84.67% estamos a punto de presenciar. 817 00:47:49.280 --> 00:47:51.600 position:50.00%,middle align:middle size:80.00% line:84.67% [vítores y aplausos] 818 00:47:52.720 --> 00:47:55.360 position:50.00%,middle align:middle size:80.00% line:79.33% Conversará con el presidente Coolidge en una conexión 819 00:47:55.520 --> 00:47:56.640 position:50.00%,middle align:middle size:80.00% line:84.67% que no hace sino confirmar 820 00:47:56.800 --> 00:47:59.280 position:50.00%,middle align:middle size:80.00% line:79.33% las buenas relaciones existentes entre ambos países. 821 00:48:02.240 --> 00:48:04.040 position:50.00%,middle align:middle size:80.00% line:84.67% [Alba] No tenía miedo a asumir ese reto, 822 00:48:04.200 --> 00:48:06.920 position:50.00%,middle align:middle size:80.00% line:79.33% pero hacerlo complicaba mi principal objetivo. 823 00:48:10.120 --> 00:48:12.720 position:50.00%,middle align:middle size:80.00% line:79.33% En aquel instante en el que el mundo entero 824 00:48:12.880 --> 00:48:16.280 position:50.00%,middle align:middle size:80.00% line:79.33% estaría pendiente del gran acontecimiento histórico, 825 00:48:17.040 --> 00:48:18.560 position:50.00%,middle align:middle size:80.00% line:84.67% yo robaría la caja fuerte. 826 00:48:19.240 --> 00:48:20.840 position:50.00%,middle align:middle size:80.00% line:84.67% Acabaría con el chantaje de Beltrán 827 00:48:21.000 --> 00:48:23.920 position:50.00%,middle align:middle size:80.00% line:79.33% y huiría en busca de la nueva vida con la que siempre había soñado, 828 00:48:24.880 --> 00:48:28.280 position:50.00%,middle align:middle size:80.00% line:84.67% lejos de mi pasado... y del que un día 829 00:48:28.440 --> 00:48:30.360 position:50.00%,middle align:middle size:80.00% line:84.67% había sido el amor de mi vida. 830 00:48:45.120 --> 00:48:46.600 position:50.00%,middle align:middle size:80.00% line:84.67% Ya es la hora, padre. 831 00:48:46.920 --> 00:48:48.000 position:50.00%,middle align:middle size:80.00% line:84.67% Bien. 832 00:48:48.160 --> 00:48:51.440 position:50.00%,middle align:middle size:80.00% line:79.33% Majestad, por favor, tiene el mundo a sus pies. 833 00:49:11.000 --> 00:49:14.080 position:50.00%,middle align:middle size:80.00% line:79.33% Buenos días. Compañía de Telefonía, ¿en qué puedo ayudarle? 834 00:49:14.240 --> 00:49:16.080 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Sí? ¿Operadora? 835 00:49:16.440 --> 00:49:18.120 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Podría hacer el favor 836 00:49:18.280 --> 00:49:21.480 position:50.00%,middle align:middle size:80.00% line:84.67% de comunicarme con Washington, EE. UU.? 837 00:49:21.640 --> 00:49:24.280 position:50.00%,middle align:middle size:80.00% line:84.67% Número 0002, con el presidente. 838 00:49:24.440 --> 00:49:26.160 position:50.00%,middle align:middle size:80.00% line:84.67% Por supuesto, su majestad. 839 00:49:26.320 --> 00:49:27.400 position:50.00%,middle align:middle size:80.00% line:84.67% Le comunico. 840 00:49:27.720 --> 00:49:29.560 position:50.00%,middle align:middle size:80.00% line:84.67% Manténgase a la espera, por favor. 841 00:49:41.000 --> 00:49:43.040 position:50.00%,middle align:middle size:80.00% line:84.67% [bobina] 842 00:50:04.720 --> 00:50:06.280 position:50.00%,middle align:middle size:80.00% line:84.67% He perdido la línea. 843 00:50:06.440 --> 00:50:09.440 position:50.00%,middle align:middle size:80.00% line:79.33% [Francisco] Hemos revisado todos los circuitos. Tiene que haber señal. 844 00:50:09.600 --> 00:50:10.840 position:50.00%,middle align:middle size:80.00% line:84.67% [Sara] Algo está haciendo mal. 845 00:50:12.600 --> 00:50:13.880 position:50.00%,middle align:middle size:80.00% line:84.67% [Alba] No hay señal. 846 00:50:14.040 --> 00:50:16.880 position:50.00%,middle align:middle size:80.00% line:79.33% - [Alba] No hay señal. - Clavija uno, clavija... 847 00:50:17.040 --> 00:50:18.160 position:50.00%,middle align:middle size:80.00% line:84.67% [Ángeles] Está apagada. 848 00:50:38.240 --> 00:50:39.560 position:50.00%,middle align:middle size:80.00% line:84.67% [en inglés] ¿Hola? 849 00:50:40.320 --> 00:50:41.960 position:50.00%,middle align:middle size:80.00% line:84.67% ¿Washington DC? 850 00:50:51.160 --> 00:50:54.160 position:50.00%,middle align:middle size:80.00% line:79.33% Un día para la historia, el triunfo de las comunicaciones. 851 00:50:56.240 --> 00:50:58.840 position:50.00%,middle align:middle size:80.00% line:79.33% El mundo en manos de una mujer: una telefonista. 852 00:50:59.000 --> 00:51:01.480 position:50.00%,middle align:middle size:80.00% line:79.33% [Alfonso XIII en inglés] Gracias. Muchas gracias, señor... 853 00:51:01.640 --> 00:51:03.400 position:50.00%,middle align:middle size:80.00% line:84.67% Todos celebraban ese hecho insólito 854 00:51:03.560 --> 00:51:05.360 position:50.00%,middle align:middle size:80.00% line:84.67% sintiéndose parte de un nuevo mundo. 855 00:51:05.520 --> 00:51:07.760 position:50.00%,middle align:middle size:80.00% line:84.67% Aunque algunas ya empezaban a comprender 856 00:51:07.920 --> 00:51:09.920 position:50.00%,middle align:middle size:80.00% line:84.67% que ese nuevo mundo no era tan idílico 857 00:51:10.080 --> 00:51:12.360 position:50.00%,middle align:middle size:80.00% line:84.67% ni tan sencillo como habrían deseado. 858 00:51:12.520 --> 00:51:16.800 position:50.00%,middle align:middle size:80.00% line:79.33% Que implicaría renuncias familiares, soledad... 859 00:51:17.720 --> 00:51:20.560 position:50.00%,middle align:middle size:80.00% line:79.33% Porque la independencia siempre tiene un precio. 860 00:51:22.760 --> 00:51:25.360 position:50.00%,middle align:middle size:80.00% line:84.67% El de Gimena fue su vida, y el mío... 861 00:51:26.400 --> 00:51:28.400 position:50.00%,middle align:middle size:80.00% line:84.67% estar condenada a romper las normas. 862 00:52:03.000 --> 00:52:04.160 position:50.00%,middle align:middle size:80.00% line:84.67% Detente. 863 00:52:14.720 --> 00:52:16.000 position:50.00%,middle align:middle size:80.00% line:84.67% Sé quién eres. 864 00:52:23.520 --> 00:52:25.840 position:50.00%,middle align:middle size:80.00% line:79.33% Diez años no son suficientes para olvidarte... 865 00:52:27.320 --> 00:52:28.600 position:50.00%,middle align:middle size:80.00% line:84.67% Alba. webvtt-py-0.5.1/tests/samples/no_captions.vtt000066400000000000000000000000061462607655700213550ustar00rootroot00000000000000WEBVTTwebvtt-py-0.5.1/tests/samples/one_caption.srt000066400000000000000000000000571462607655700213400ustar00rootroot000000000000001 00:00:00,500 --> 00:00:07,000 Caption text #1webvtt-py-0.5.1/tests/samples/one_caption.vtt000066400000000000000000000000651462607655700213440ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1webvtt-py-0.5.1/tests/samples/sample.sbv000066400000000000000000000003551462607655700203060ustar00rootroot000000000000000:00:00.378,0:00:11.378 Caption text #1 0:00:11.378,0:00:12.305 Caption text #2 0:00:13.489,0:00:15.489 Caption text #3 (line 1) Caption text #3 (line 2) 0:00:15.489,0:00:16.234 Caption text #4 0:00:17.484,0:00:16.543 Caption text #5webvtt-py-0.5.1/tests/samples/sample.srt000066400000000000000000000003631462607655700203230ustar00rootroot000000000000001 00:00:00,500 --> 00:00:07,000 Caption text #1 2 00:00:07,000 --> 00:00:11,890 Caption text #2 3 00:00:11,890 --> 00:00:16,320 Caption text #3 4 00:00:16,320 --> 00:00:21,580 Caption text #4 5 00:00:21,580 --> 00:00:23,880 Caption text #5webvtt-py-0.5.1/tests/samples/sample.vtt000066400000000000000000000013751462607655700203340ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7 00:00:30.280 --> 00:00:36.510 Caption text #8 00:00:36.510 --> 00:00:38.870 Caption text #9 00:00:38.870 --> 00:00:45.000 Caption text #10 00:00:45.000 --> 00:00:47.000 Caption text #11 00:00:47.000 --> 00:00:50.970 Caption text #12 00:00:50.970 --> 00:00:54.440 Caption text #13 00:00:54.440 --> 00:00:58.600 Caption text #14 00:00:58.600 --> 00:01:01.350 Caption text #15 00:01:01.350 --> 00:01:04.300 Caption text #16webvtt-py-0.5.1/tests/samples/styles.vtt000066400000000000000000000003151462607655700203670ustar00rootroot00000000000000WEBVTT STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } STYLE ::cue(b) { color: peachpuff; } 00:00:00.000 --> 00:00:10.000 - Hello world.webvtt-py-0.5.1/tests/samples/styles_with_comments.vtt000066400000000000000000000005271462607655700233340ustar00rootroot00000000000000WEBVTT NOTE Sample of comments with styles STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } NOTE This is the second block of styles NOTE Multiline comment for the same second block of styles STYLE ::cue(b) { color: peachpuff; } 00:00:00.000 --> 00:00:10.000 - Hello world.webvtt-py-0.5.1/tests/samples/two_captions.sbv000066400000000000000000000001621462607655700215320ustar00rootroot000000000000000:00:00.378,0:00:11.378 Caption text #1 0:00:11.378,0:00:12.305 Caption text #2 (line 1) Caption text #2 (line 2)webvtt-py-0.5.1/tests/samples/using_identifiers.vtt000066400000000000000000000004611462607655700225600ustar00rootroot00000000000000WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 second caption 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 4 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6webvtt-py-0.5.1/tests/samples/youtube_dl.vtt000066400000000000000000000012111462607655700212130ustar00rootroot00000000000000WEBVTT Kind: captions Language: en Style: ::cue(c.colorCCCCCC) { color: rgb(204,204,204); } ::cue(c.colorE5E5E5) { color: rgb(229,229,229); } ## 00:04:46.070 --> 00:04:46.470 align:start position:0% yeah 00:04:46.470 --> 00:05:04.080 align:start position:0% yeah what 00:05:04.080 --> 00:05:05.069 align:start position:0% this<00:05:04.199> will<00:05:04.379> happen<00:05:04.620> is<00:05:04.860> I'm<00:05:05.069> telling 00:05:05.069 --> 00:05:05.400 align:start position:0% this will happen is I'm telling webvtt-py-0.5.1/tests/test_cli.py000066400000000000000000000136631462607655700170330ustar00rootroot00000000000000import unittest import tempfile import os import pathlib import textwrap from webvtt.cli import main class CLITestCase(unittest.TestCase): def test_cli(self): vtt_file = ( pathlib.Path(__file__).resolve().parent / 'samples' / 'sample.vtt' ) with tempfile.TemporaryDirectory() as temp_dir: main(['segment', str(vtt_file.resolve()), '-o', temp_dir]) _, dirs, files = next(os.walk(temp_dir)) self.assertEqual(len(dirs), 0) self.assertEqual(len(files), 8) for expected_file in ('prog_index.m3u8', 'fileSequence0.webvtt', 'fileSequence1.webvtt', 'fileSequence2.webvtt', 'fileSequence3.webvtt', 'fileSequence4.webvtt', 'fileSequence5.webvtt', 'fileSequence6.webvtt', ): self.assertIn(expected_file, files) self.assertEqual( (pathlib.Path(temp_dir) / 'prog_index.m3u8').read_text(), textwrap.dedent( ''' #EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-VERSION:3 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:30.00000 fileSequence0.webvtt #EXTINF:30.00000 fileSequence1.webvtt #EXTINF:30.00000 fileSequence2.webvtt #EXTINF:30.00000 fileSequence3.webvtt #EXTINF:30.00000 fileSequence4.webvtt #EXTINF:30.00000 fileSequence5.webvtt #EXTINF:30.00000 fileSequence6.webvtt #EXT-X-ENDLIST ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence0.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence1.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence2.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence3.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:27.280 --> 00:00:30.280 Caption text #7 00:00:30.280 --> 00:00:36.510 Caption text #8 00:00:36.510 --> 00:00:38.870 Caption text #9 00:00:38.870 --> 00:00:45.000 Caption text #10 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence4.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:38.870 --> 00:00:45.000 Caption text #10 00:00:45.000 --> 00:00:47.000 Caption text #11 00:00:47.000 --> 00:00:50.970 Caption text #12 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence5.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:47.000 --> 00:00:50.970 Caption text #12 00:00:50.970 --> 00:00:54.440 Caption text #13 00:00:54.440 --> 00:00:58.600 Caption text #14 00:00:58.600 --> 00:01:01.350 Caption text #15 ''' ).lstrip()) self.assertEqual( (pathlib.Path(temp_dir) / 'fileSequence6.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:58.600 --> 00:01:01.350 Caption text #15 00:01:01.350 --> 00:01:04.300 Caption text #16 ''' ).lstrip()) webvtt-py-0.5.1/tests/test_models.py000066400000000000000000000355031462607655700175440ustar00rootroot00000000000000import unittest from webvtt.models import Timestamp, Caption, Style from webvtt.errors import MalformedCaptionError class TestTimestamp(unittest.TestCase): def test_instantiation(self): timestamp = Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) self.assertEqual(timestamp.hours, 1) self.assertEqual(timestamp.minutes, 12) self.assertEqual(timestamp.seconds, 23) self.assertEqual(timestamp.milliseconds, 500) def test_from_string(self): timestamp = Timestamp.from_string('01:24:11.670') self.assertEqual(timestamp.hours, 1) self.assertEqual(timestamp.minutes, 24) self.assertEqual(timestamp.seconds, 11) self.assertEqual(timestamp.milliseconds, 670) def test_from_string_single_digits(self): timestamp = Timestamp.from_string('1:2:7.670') self.assertEqual(timestamp.hours, 1) self.assertEqual(timestamp.minutes, 2) self.assertEqual(timestamp.seconds, 7) self.assertEqual(timestamp.milliseconds, 670) def test_from_string_missing_hours(self): timestamp = Timestamp.from_string('24:11.670') self.assertEqual(timestamp.hours, 0) self.assertEqual(timestamp.minutes, 24) self.assertEqual(timestamp.seconds, 11) self.assertEqual(timestamp.milliseconds, 670) def test_from_string_wrong_minutes(self): with self.assertRaises(MalformedCaptionError): Timestamp.from_string('01:76:11.670') def test_from_string_wrong_seconds(self): with self.assertRaises(MalformedCaptionError): Timestamp.from_string('01:24:87.670') def test_from_string_wrong_type(self): with self.assertRaises(MalformedCaptionError): Timestamp.from_string(1234) def test_from_string_wrong_value(self): with self.assertRaises(MalformedCaptionError): Timestamp.from_string('01:24:11:670') def test_to_tuple(self): self.assertEqual( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ).to_tuple(), (1, 12, 23, 500) ) def test_equality(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) == Timestamp.from_string('01:12:23.500') ) def test_not_equality(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) != Timestamp.from_string('01:12:23.600') ) def test_greater_than(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=600 ) > Timestamp.from_string('01:12:23.500') ) def test_less_than(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) < Timestamp.from_string('01:12:23.600') ) def test_greater_or_equal_than(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) >= Timestamp.from_string('01:12:23.500') ) self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=600 ) >= Timestamp.from_string('01:12:23.500') ) def test_less_or_equal_than(self): self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) <= Timestamp.from_string('01:12:23.500') ) self.assertTrue( Timestamp( hours=1, minutes=12, seconds=23, milliseconds=500 ) <= Timestamp.from_string('01:12:23.600') ) def test_repr(self): timestamp = Timestamp( hours=1, minutes=12, seconds=45, milliseconds=320 ) self.assertEqual( repr(timestamp), '' ) class TestCaption(unittest.TestCase): def test_instantiation(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) self.assertEqual(caption.start, '00:00:07.000') self.assertEqual(caption.end, '00:00:11.890') self.assertEqual(caption.text, 'Hello test!') self.assertEqual(caption.identifier, 'A test caption') def test_timestamp_wrong_type(self): with self.assertRaises(MalformedCaptionError): Caption( start=1234, end='00:00:11.890', text='Hello test!', identifier='A test caption' ) def test_identifier_is_optional(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', ) self.assertIsNone(caption.identifier) def test_multi_lines(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!\nThis is the second line', identifier='A test caption' ) self.assertEqual(caption.text, 'Hello test!\nThis is the second line') self.assertListEqual( caption.lines, ['Hello test!', 'This is the second line'] ) def test_multi_lines_accepts_list(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text=['Hello test!', 'This is the second line'], identifier='A test caption' ) self.assertEqual(caption.text, 'Hello test!\nThis is the second line') self.assertListEqual( caption.lines, ['Hello test!', 'This is the second line'] ) def test_cuetags(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text=[ 'Hello test!', 'This is the second line' ], identifier='A test caption' ) self.assertEqual(caption.text, 'Hello test!\nThis is the second line') self.assertEqual( caption.raw_text, 'Hello test!\n' 'This is the second line' ) def test_in_seconds(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text=['Hello test!', 'This is the second line'], identifier='A test caption' ) self.assertEqual(caption.start_in_seconds, 7) self.assertEqual(caption.end_in_seconds, 11) def test_wrong_start_timestamp(self): self.assertRaises( MalformedCaptionError, Caption, start='1234', end='00:00:11.890', text='Hello Test!' ) def test_wrong_type_start_timestamp(self): self.assertRaises( MalformedCaptionError, Caption, start=1234, end='00:00:11.890', text='Hello Test!' ) def test_wrong_end_timestamp(self): self.assertRaises( MalformedCaptionError, Caption, start='00:00:07.000', end='1234', text='Hello Test!' ) def test_wrong_type_end_timestamp(self): self.assertRaises( MalformedCaptionError, Caption, start='00:00:07.000', end=1234, text='Hello Test!' ) def test_equality(self): caption1 = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) caption2 = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) self.assertTrue(caption1 == caption2) caption1 = Caption( start='00:00:02.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) caption2 = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) self.assertFalse(caption1 == caption2) self.assertFalse( Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) == 1234 ) def test_repr(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) self.assertEqual( repr(caption), "" ) def test_str(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) self.assertEqual( str(caption), '00:00:07.000 00:00:11.890 Hello test!' ) def test_accept_comments(self): caption = Caption( start='00:00:07.000', end='00:00:11.890', text='Hello test!', identifier='A test caption' ) caption.comments.append('One comment') caption.comments.append('Another comment') self.assertListEqual( caption.comments, ['One comment', 'Another comment'] ) def test_timestamp_update(self): c = Caption('00:00:00.500', '00:00:07.000') c.start = '00:00:01.750' c.end = '00:00:08.250' self.assertEqual(c.start, '00:00:01.750') self.assertEqual(c.end, '00:00:08.250') def test_timestamp_format(self): c = Caption('01:02:03.400', '02:03:04.500') self.assertEqual(c.start, '01:02:03.400') self.assertEqual(c.end, '02:03:04.500') c = Caption('02:03.400', '03:04.500') self.assertEqual(c.start, '00:02:03.400') self.assertEqual(c.end, '00:03:04.500') def test_update_text(self): c = Caption(text='Caption line #1') c.text = 'Caption line #1 updated' self.assertEqual( c.text, 'Caption line #1 updated' ) def test_update_text_multiline(self): c = Caption(text='Caption line #1') c.text = 'Caption line #1\nCaption line #2' self.assertEqual( len(c.lines), 2 ) self.assertEqual( c.text, 'Caption line #1\nCaption line #2' ) def test_update_text_wrong_type(self): c = Caption(text='Caption line #1') self.assertRaises( AttributeError, setattr, c, 'text', 123 ) def test_manipulate_lines(self): c = Caption(text=['Caption line #1', 'Caption line #2']) c.lines[0] = 'Caption line #1 updated' self.assertEqual( c.lines[0], 'Caption line #1 updated' ) def test_malformed_start_timestamp(self): self.assertRaises( MalformedCaptionError, Caption, '01:00' ) def test_voice_span(self): caption = Caption(text='Hello there!') self.assertEqual(caption.text, 'Hello there!') self.assertEqual(caption.raw_text, 'Hello there!') self.assertEqual(caption.voice, 'Homer Simpson') def test_voice_span_with_classes(self): caption = Caption(text='I am Lisa') self.assertEqual(caption.text, 'I am Lisa') self.assertEqual( caption.raw_text, 'I am Lisa' ) self.assertEqual(caption.voice, 'Lisa Simpson') def test_voice_span_is_invalid(self): caption = Caption(text='I like tests') self.assertEqual(caption.voice, 'Homer Simpson') def test_voice_span_removed(self): caption = Caption(text='I like tests') self.assertEqual(caption.text, 'I like tests') self.assertEqual(caption.raw_text, 'I like tests') self.assertEqual(caption.voice, 'Homer Simpson') caption.text = 'This is a test' self.assertEqual(caption.text, 'This is a test') self.assertEqual(caption.raw_text, 'This is a test') self.assertIsNone(caption.voice) class TestStyle(unittest.TestCase): def test_instantiation(self): style = Style(text='::cue(b) {\ncolor: peachpuff;\n}') self.assertEqual(style.text, '::cue(b) {\ncolor: peachpuff;\n}') self.assertListEqual( style.lines, ['::cue(b) {', 'color: peachpuff;', '}'] ) def test_text_accept_list_of_strings(self): style = Style(text=['::cue(b) {', 'color: peachpuff;', '}']) self.assertEqual(style.text, '::cue(b) {\ncolor: peachpuff;\n}') self.assertListEqual( style.lines, ['::cue(b) {', 'color: peachpuff;', '}'] ) def test_accept_comments(self): style = Style(text='::cue(b) {\ncolor: peachpuff;\n}') style.comments.append('One comment') style.comments.append('Another comment') self.assertListEqual( style.comments, ['One comment', 'Another comment'] ) def test_get_text(self): style = Style(['::cue(b) {', ' color: peachpuff;', '}']) self.assertEqual( style.text, '::cue(b) {\n color: peachpuff;\n}' ) webvtt-py-0.5.1/tests/test_sbv.py000066400000000000000000000077471462607655700170640ustar00rootroot00000000000000import unittest import textwrap from webvtt import sbv from webvtt.errors import MalformedFileError from webvtt.models import Caption class TestSBVCueBlock(unittest.TestCase): def test_is_valid(self): self.assertTrue(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500,00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertTrue(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 0:0:0.500,0:0:7.000 Caption #1 ''').strip().split('\n')) ) self.assertTrue(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500,00:00:07.000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500 --> 00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00.500,00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00.500,00:00:07.000 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 1 Caption #1 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' Caption #1 ''').strip().split('\n')) ) self.assertFalse(sbv.SBVCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500,00:00:07.000 ''').strip().split('\n')) ) def test_from_lines(self): cue_block = sbv.SBVCueBlock.from_lines(textwrap.dedent(''' 00:00:00.500,00:00:07.000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n') ) self.assertEqual( cue_block.start, '00:00:00.500' ) self.assertEqual( cue_block.end, '00:00:07.000' ) self.assertEqual( cue_block.payload, ['Caption #1 line 1', 'Caption #1 line 2'] ) def test_from_lines_shorter_timestamps(self): cue_block = sbv.SBVCueBlock.from_lines(textwrap.dedent(''' 0:1:2.500,0:1:03.800 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n') ) self.assertEqual( cue_block.start, '0:1:2.500' ) self.assertEqual( cue_block.end, '0:1:03.800' ) class TestSBVModule(unittest.TestCase): def test_parse_invalid_format(self): self.assertRaises( MalformedFileError, sbv.parse, textwrap.dedent(''' 1 00:00:00.500,00:00:07.000 Caption text #1 00:00:07.000,00:00:11.890 Caption text #2 ''').strip().split('\n') ) def test_parse_captions(self): captions = sbv.parse( textwrap.dedent(''' 00:00:00.500,00:00:07.000 Caption #1 00:00:07.000,00:00:11.890 Caption #2 line 1 Caption #2 line 2 ''').strip().split('\n') ) self.assertEqual(len(captions), 2) self.assertIsInstance(captions[0], Caption) self.assertIsInstance(captions[1], Caption) self.assertEqual( str(captions[0]), '00:00:00.500 00:00:07.000 Caption #1' ) self.assertEqual( str(captions[1]), r'00:00:07.000 00:00:11.890 Caption #2 line 1\n' 'Caption #2 line 2' ) webvtt-py-0.5.1/tests/test_segmenter.py000066400000000000000000000236571462607655700202610ustar00rootroot00000000000000import os import unittest import tempfile import pathlib import textwrap from webvtt import segmenter PATH_TO_SAMPLES = pathlib.Path(__file__).resolve().parent / 'samples' class TestSegmenter(unittest.TestCase): def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() def tearDown(self): self.temp_dir.cleanup() def test_segmentation_with_defaults(self): segmenter.segment(PATH_TO_SAMPLES / 'sample.vtt', self.temp_dir.name) _, dirs, files = next(os.walk(self.temp_dir.name)) self.assertEqual(len(dirs), 0) self.assertEqual(len(files), 8) for expected_file in ('prog_index.m3u8', 'fileSequence0.webvtt', 'fileSequence1.webvtt', 'fileSequence2.webvtt', 'fileSequence3.webvtt', 'fileSequence4.webvtt', 'fileSequence5.webvtt', 'fileSequence6.webvtt', ): self.assertIn(expected_file, files) output_path = pathlib.Path(self.temp_dir.name) self.assertEqual( (output_path / 'prog_index.m3u8').read_text(), textwrap.dedent( ''' #EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-VERSION:3 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:30.00000 fileSequence0.webvtt #EXTINF:30.00000 fileSequence1.webvtt #EXTINF:30.00000 fileSequence2.webvtt #EXTINF:30.00000 fileSequence3.webvtt #EXTINF:30.00000 fileSequence4.webvtt #EXTINF:30.00000 fileSequence5.webvtt #EXTINF:30.00000 fileSequence6.webvtt #EXT-X-ENDLIST ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence0.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence1.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence2.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence3.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:27.280 --> 00:00:30.280 Caption text #7 00:00:30.280 --> 00:00:36.510 Caption text #8 00:00:36.510 --> 00:00:38.870 Caption text #9 00:00:38.870 --> 00:00:45.000 Caption text #10 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence4.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:38.870 --> 00:00:45.000 Caption text #10 00:00:45.000 --> 00:00:47.000 Caption text #11 00:00:47.000 --> 00:00:50.970 Caption text #12 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence5.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:47.000 --> 00:00:50.970 Caption text #12 00:00:50.970 --> 00:00:54.440 Caption text #13 00:00:54.440 --> 00:00:58.600 Caption text #14 00:00:58.600 --> 00:01:01.350 Caption text #15 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence6.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:58.600 --> 00:01:01.350 Caption text #15 00:01:01.350 --> 00:01:04.300 Caption text #16 ''' ).lstrip() ) def test_segmentation_with_custom_values(self): segmenter.segment( webvtt_path=PATH_TO_SAMPLES / 'sample.vtt', output=self.temp_dir.name, seconds=30, mpegts=800000 ) _, dirs, files = next(os.walk(self.temp_dir.name)) self.assertEqual(len(dirs), 0) self.assertEqual(len(files), 4) for expected_file in ('prog_index.m3u8', 'fileSequence0.webvtt', 'fileSequence1.webvtt', 'fileSequence2.webvtt', ): self.assertIn(expected_file, files) output_path = pathlib.Path(self.temp_dir.name) self.assertEqual( (output_path / 'prog_index.m3u8').read_text(), textwrap.dedent( ''' #EXTM3U #EXT-X-TARGETDURATION:30 #EXT-X-VERSION:3 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:30.00000 fileSequence0.webvtt #EXTINF:30.00000 fileSequence1.webvtt #EXTINF:30.00000 fileSequence2.webvtt #EXT-X-ENDLIST ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence0.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:800000,LOCAL:00:00:00.000 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 00:00:21.580 --> 00:00:23.880 Caption text #5 00:00:23.880 --> 00:00:27.280 Caption text #6 00:00:27.280 --> 00:00:30.280 Caption text #7 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence1.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:800000,LOCAL:00:00:00.000 00:00:27.280 --> 00:00:30.280 Caption text #7 00:00:30.280 --> 00:00:36.510 Caption text #8 00:00:36.510 --> 00:00:38.870 Caption text #9 00:00:38.870 --> 00:00:45.000 Caption text #10 00:00:45.000 --> 00:00:47.000 Caption text #11 00:00:47.000 --> 00:00:50.970 Caption text #12 00:00:50.970 --> 00:00:54.440 Caption text #13 00:00:54.440 --> 00:00:58.600 Caption text #14 00:00:58.600 --> 00:01:01.350 Caption text #15 ''' ).lstrip() ) self.assertEqual( (output_path / 'fileSequence2.webvtt').read_text(), textwrap.dedent( ''' WEBVTT X-TIMESTAMP-MAP=MPEGTS:800000,LOCAL:00:00:00.000 00:00:58.600 --> 00:01:01.350 Caption text #15 00:01:01.350 --> 00:01:04.300 Caption text #16 ''' ).lstrip() ) def test_segment_with_no_captions(self): segmenter.segment( webvtt_path=PATH_TO_SAMPLES / 'no_captions.vtt', output=self.temp_dir.name ) _, dirs, files = next(os.walk(self.temp_dir.name)) self.assertEqual(len(dirs), 0) self.assertEqual(len(files), 1) self.assertIn('prog_index.m3u8', files) self.assertEqual( (pathlib.Path(self.temp_dir.name) / 'prog_index.m3u8').read_text(), textwrap.dedent( ''' #EXTM3U #EXT-X-TARGETDURATION:10 #EXT-X-VERSION:3 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-ENDLIST ''' ).lstrip() ) webvtt-py-0.5.1/tests/test_srt.py000066400000000000000000000106361462607655700170710ustar00rootroot00000000000000import unittest import io import textwrap from webvtt import srt from webvtt.errors import MalformedFileError from webvtt.models import Caption class TestSRTCueBlock(unittest.TestCase): def test_is_valid(self): self.assertTrue(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption #1 ''').strip().split('\n')) ) self.assertTrue(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 00:00:00,500 --> 00:00:07,000 Caption #1 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00.500 --> 00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 1 Caption #1 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' Caption #1 ''').strip().split('\n')) ) self.assertFalse(srt.SRTCueBlock.is_valid(textwrap.dedent(''' 00:00:00,500 --> 00:00:07,000 ''').strip().split('\n')) ) def test_from_lines(self): cue_block = srt.SRTCueBlock.from_lines(textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n') ) self.assertEqual(cue_block.index, '1') self.assertEqual( cue_block.start, '00:00:00,500' ) self.assertEqual( cue_block.end, '00:00:07,000' ) self.assertEqual( cue_block.payload, ['Caption #1 line 1', 'Caption #1 line 2'] ) class TestSRTModule(unittest.TestCase): def test_parse_invalid_format(self): self.assertRaises( MalformedFileError, srt.parse, textwrap.dedent(''' 00:00:00,500 --> 00:00:07,000 Caption text #1 00:00:07,000 --> 00:00:11,890 Caption text #2 ''').strip().split('\n') ) def test_parse_captions(self): captions = srt.parse( textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption #1 2 00:00:07,000 --> 00:00:11,890 Caption #2 line 1 Caption #2 line 2 ''').strip().split('\n') ) self.assertEqual(len(captions), 2) self.assertIsInstance(captions[0], Caption) self.assertIsInstance(captions[1], Caption) self.assertEqual( str(captions[0]), '00:00:00.500 00:00:07.000 Caption #1' ) self.assertEqual( str(captions[1]), r'00:00:07.000 00:00:11.890 Caption #2 line 1\n' 'Caption #2 line 2' ) def test_write(self): out = io.StringIO() captions = [ Caption(start='00:00:00.500', end='00:00:07.000', text='Caption #1' ), Caption(start='00:00:07.000', end='00:00:11.890', text=['Caption #2 line 1', 'Caption #2 line 2' ] ) ] captions[0].comments.append('Comment for the first caption') captions[1].comments.append('Comment for the second caption') srt.write(out, captions) out.seek(0) self.assertEqual( out.read(), textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption #1 2 00:00:07,000 --> 00:00:11,890 Caption #2 line 1 Caption #2 line 2 ''').strip() ) webvtt-py-0.5.1/tests/test_utils.py000066400000000000000000000015531462607655700174170ustar00rootroot00000000000000import unittest import tempfile from webvtt.utils import FileWrapper, CODEC_BOMS class TestUtils(unittest.TestCase): def test_open_file(self): with tempfile.NamedTemporaryFile('w') as f: f.write('Hello test!') f.flush() with FileWrapper.open(f.name) as fw: self.assertEqual(fw.file.read(), 'Hello test!') self.assertEqual(fw.file.encoding, 'utf-8') def test_open_file_with_bom(self): for encoding, bom in CODEC_BOMS.items(): with tempfile.NamedTemporaryFile('wb') as f: f.write(bom) f.write('Hello test'.encode(encoding)) f.flush() with FileWrapper.open(f.name) as fw: self.assertEqual(fw.file.read(), 'Hello test') self.assertEqual(fw.file.encoding, encoding) webvtt-py-0.5.1/tests/test_vtt.py000066400000000000000000000372311462607655700170760ustar00rootroot00000000000000import unittest import textwrap import io from webvtt import vtt from webvtt.errors import MalformedFileError from webvtt.models import Caption, Style class TestWebVTTCueBlock(unittest.TestCase): def test_is_valid(self): self.assertTrue( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500 --> 00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500 --> 00:00:07.000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' identifier 00:00:00.500 --> 00:00:07.000 Caption #1 ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' identifier 00:00:00.500 --> 00:00:07.000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500 00:00:07.000 Caption #1 line 1 ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTCueBlock.is_valid(textwrap.dedent(''' 00:00:00.500 --> 00:00:07.000 ''').strip().split('\n')) ) def test_from_lines(self): cue_block = vtt.WebVTTCueBlock.from_lines(textwrap.dedent(''' identifier 00:00:00.500 --> 00:00:07.000 Caption #1 line 1 Caption #1 line 2 ''').strip().split('\n') ) self.assertEqual(cue_block.identifier, 'identifier') self.assertEqual(cue_block.start, '00:00:00.500') self.assertEqual(cue_block.end, '00:00:07.000') self.assertListEqual( cue_block.payload, ['Caption #1 line 1', 'Caption #1 line 2'] ) class TestWebVTTCommentBlock(unittest.TestCase): def test_is_valid(self): self.assertTrue( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' NOTE This is a one line comment ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' NOTE This is a another one line comment ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' NOTE This is a multi-line comment taking two lines ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' This is not a comment ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' # This is not a comment ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTCommentBlock.is_valid(textwrap.dedent(''' // This is not a comment ''').strip().split('\n')) ) def test_from_lines(self): comment = vtt.WebVTTCommentBlock.from_lines(textwrap.dedent(''' NOTE This is a one line comment ''').strip().split('\n') ) self.assertEqual(comment.text, 'This is a one line comment') comment = vtt.WebVTTCommentBlock.from_lines(textwrap.dedent(''' NOTE This is a multi-line comment taking two lines ''').strip().split('\n') ) self.assertEqual( comment.text, 'This is a multi-line comment\ntaking two lines' ) class TestWebVTTStyleBlock(unittest.TestCase): def test_is_valid(self): self.assertTrue( vtt.WebVTTStyleBlock.is_valid(textwrap.dedent(''' STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ''').strip().split('\n')) ) self.assertTrue( vtt.WebVTTStyleBlock.is_valid(textwrap.dedent(''' STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ::cue(b) { color: peachpuff; } ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTStyleBlock.is_valid(textwrap.dedent(''' STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ::cue(b) { color: peachpuff; } ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTStyleBlock.is_valid(textwrap.dedent(''' STYLE ::cue--> { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ''').strip().split('\n')) ) self.assertFalse( vtt.WebVTTStyleBlock.is_valid(textwrap.dedent(''' ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ''').strip().split('\n')) ) def test_from_lines(self): style = vtt.WebVTTStyleBlock.from_lines(textwrap.dedent(''' STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ::cue(b) { color: peachpuff; } ''').strip().split('\n') ) self.assertEqual( style.text, textwrap.dedent(''' ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } ::cue(b) { color: peachpuff; } ''').strip() ) class TestVTTModule(unittest.TestCase): def test_parse_invalid_format(self): self.assertRaises( MalformedFileError, vtt.parse, textwrap.dedent(''' 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 ''').strip().split('\n') ) def test_parse_captions(self): output = vtt.parse( textwrap.dedent(''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 line 1 Caption text #2 line 2 ''').strip().split('\n') ) captions = output.captions styles = output.styles self.assertEqual(len(captions), 2) self.assertEqual(len(styles), 0) self.assertIsInstance(captions[0], Caption) self.assertIsInstance(captions[1], Caption) self.assertEqual( str(captions[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(captions[1]), r'00:00:07.000 00:00:11.890 Caption text #2 line 1\n' 'Caption text #2 line 2' ) def test_parse_styles(self): output = vtt.parse( textwrap.dedent(''' WEBVTT STYLE ::cue { color: white; } STYLE ::cue(.important) { color: red; font-weight: bold; } 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip().split('\n') ) captions = output.captions styles = output.styles self.assertEqual(len(captions), 1) self.assertEqual(len(styles), 2) self.assertIsInstance(styles[0], Style) self.assertIsInstance(styles[1], Style) self.assertEqual( str(styles[0].text), textwrap.dedent(''' ::cue { color: white; } ''').strip() ) self.assertEqual( str(styles[1].text), textwrap.dedent(''' ::cue(.important) { color: red; font-weight: bold; } ''').strip(), ) def test_parse_content(self): output = vtt.parse( textwrap.dedent(''' WEBVTT NOTE This is a testing sample NOTE We can see two header comments, a style comment and finally a footer comments STYLE ::cue { background-image: linear-gradient(to bottom, dimgray); color: papayawhip; } NOTE the following style needs review STYLE ::cue { color: white; } NOTE Comment for the first caption 00:00:00.500 --> 00:00:07.000 Caption text #1 NOTE Comment for the second caption that is very long 00:00:07.000 --> 00:00:11.890 Caption text #2 line 1 Caption text #2 line 2 NOTE Copyright 2024 NOTE end of file ''').strip().split('\n') ) captions = output.captions styles = output.styles self.assertEqual(len(captions), 2) self.assertEqual(len(styles), 2) self.assertIsInstance(captions[0], Caption) self.assertIsInstance(captions[1], Caption) self.assertIsInstance(styles[0], Style) self.assertIsInstance(styles[1], Style) self.assertEqual( str(captions[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(captions[1]), r'00:00:07.000 00:00:11.890 Caption text #2 line 1\n' 'Caption text #2 line 2' ) self.assertEqual( str(styles[0].text), textwrap.dedent(''' ::cue { background-image: linear-gradient(to bottom, dimgray); color: papayawhip; } ''').strip() ) self.assertEqual( str(styles[1].text), textwrap.dedent(''' ::cue { color: white; } ''').strip() ) self.assertEqual( styles[0].comments, [] ) self.assertEqual( styles[1].comments, ['the following style needs review'] ) self.assertEqual( captions[0].comments, ['Comment for the first caption'] ) self.assertEqual( captions[1].comments, ['Comment for the second caption\nthat is very long'] ) self.assertListEqual(output.header_comments, ['This is a testing sample', 'We can see two header comments, a style\n' 'comment and finally a footer comments' ] ) self.assertListEqual(output.footer_comments, ['Copyright 2024', 'end of file' ] ) def test_write(self): out = io.StringIO() captions = [ Caption(start='00:00:00.500', end='00:00:07.000', text='Caption #1' ), Caption(start='00:00:07.000', end='00:00:11.890', text=['Caption #2 line 1', 'Caption #2 line 2' ] ) ] styles = [ Style('::cue(b) {\n color: peachpuff;\n}'), Style('::cue {\n color: papayawhip;\n}') ] captions[0].comments.append('Comment for the first caption') captions[1].comments.append('Comment for the second caption') styles[1].comments.append( 'Comment for the second style\nwith two lines' ) header_comments = ['header comment', 'begin of the file'] footer_comments = ['footer comment', 'end of file'] vtt.write( out, captions, styles, header_comments, footer_comments ) out.seek(0) self.assertEqual( out.read(), textwrap.dedent(''' WEBVTT NOTE header comment NOTE begin of the file STYLE ::cue(b) { color: peachpuff; } NOTE Comment for the second style with two lines STYLE ::cue { color: papayawhip; } NOTE Comment for the first caption 00:00:00.500 --> 00:00:07.000 Caption #1 NOTE Comment for the second caption 00:00:07.000 --> 00:00:11.890 Caption #2 line 1 Caption #2 line 2 NOTE footer comment NOTE end of file ''').strip() ) def test_to_str(self): captions = [ Caption(start='00:00:00.500', end='00:00:07.000', text='Caption #1' ), Caption(start='00:00:07.000', end='00:00:11.890', text=['Caption #2 line 1', 'Caption #2 line 2' ] ) ] styles = [ Style('::cue(b) {\n color: peachpuff;\n}'), Style('::cue {\n color: papayawhip;\n}') ] captions[0].comments.append('Comment for the first caption') captions[1].comments.append('Comment for the second caption') styles[1].comments.append( 'Comment for the second style\nwith two lines' ) header_comments = ['header comment', 'begin of the file'] footer_comments = ['footer comment', 'end of file'] self.assertEqual( vtt.to_str( captions, styles, header_comments, footer_comments ), textwrap.dedent(''' WEBVTT NOTE header comment NOTE begin of the file STYLE ::cue(b) { color: peachpuff; } NOTE Comment for the second style with two lines STYLE ::cue { color: papayawhip; } NOTE Comment for the first caption 00:00:00.500 --> 00:00:07.000 Caption #1 NOTE Comment for the second caption 00:00:07.000 --> 00:00:11.890 Caption #2 line 1 Caption #2 line 2 NOTE footer comment NOTE end of file ''').strip() ) webvtt-py-0.5.1/tests/test_webvtt.py000066400000000000000000001322201462607655700175660ustar00rootroot00000000000000import unittest import os import io import textwrap import warnings import tempfile import pathlib import webvtt from webvtt.models import Caption, Style from webvtt.utils import CODEC_BOMS from webvtt.errors import MalformedFileError PATH_TO_SAMPLES = pathlib.Path(__file__).resolve().parent / 'samples' class TestWebVTT(unittest.TestCase): def test_from_string(self): vtt = webvtt.from_string(textwrap.dedent(""" WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 line 1 Caption text #2 line 2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 """).strip() ) self.assertEqual(len(vtt), 4) self.assertEqual( str(vtt[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(vtt[1]), r'00:00:07.000 00:00:11.890 Caption text #2 line 1\n' 'Caption text #2 line 2' ) self.assertEqual( str(vtt[2]), '00:00:11.890 00:00:16.320 Caption text #3' ) self.assertEqual( str(vtt[3]), '00:00:16.320 00:00:21.580 Caption text #4' ) def test_write_captions(self): out = io.StringIO() vtt = webvtt.read(PATH_TO_SAMPLES / 'one_caption.vtt') new_caption = Caption(start='00:00:07.000', end='00:00:11.890', text=['New caption text line1', 'New caption text line2' ] ) vtt.captions.append(new_caption) vtt.write(out) out.seek(0) self.assertEqual( out.read(), textwrap.dedent(''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 New caption text line1 New caption text line2 ''').strip() + '\n' ) def test_write_captions_in_srt(self): out = io.StringIO() vtt = webvtt.read(PATH_TO_SAMPLES / 'one_caption.vtt') new_caption = Caption(start='00:00:07.000', end='00:00:11.890', text=['New caption text line1', 'New caption text line2' ] ) vtt.captions.append(new_caption) vtt.write(out, format='srt') out.seek(0) self.assertEqual( out.read(), textwrap.dedent(''' 1 00:00:00,500 --> 00:00:07,000 Caption text #1 2 00:00:07,000 --> 00:00:11,890 New caption text line1 New caption text line2 ''').strip() ) def test_write_captions_in_srt_no_cuetags(self): """https://github.com/glut23/webvtt-py/issues/56""" out = io.StringIO() vtt = webvtt.read(PATH_TO_SAMPLES / 'cue_tags.vtt') vtt.write(out, format='srt') out.seek(0) self.assertEqual( out.read(), textwrap.dedent(''' 1 00:00:16,500 --> 00:00:18,500 When the moon hits your eye 2 00:00:18,500 --> 00:00:20,500 Like a big-a pizza pie 3 00:00:20,500 --> 00:00:21,500 That's amore ''').strip() ) def test_write_captions_in_unsupported_format(self): self.assertRaises( ValueError, webvtt.WebVTT().write, io.StringIO(), format='ttt' ) def test_save_captions(self): with tempfile.NamedTemporaryFile('w', suffix='.vtt') as f: f.write((PATH_TO_SAMPLES / 'one_caption.vtt').read_text()) f.flush() vtt = webvtt.read(f.name) new_caption = Caption(start='00:00:07.000', end='00:00:11.890', text=['New caption text line1', 'New caption text line2' ] ) vtt.captions.append(new_caption) vtt.save() f.flush() self.assertEqual( pathlib.Path(f.name).read_text(), textwrap.dedent(''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 New caption text line1 New caption text line2 ''').strip() + '\n' ) def test_srt_conversion(self): with tempfile.TemporaryDirectory() as td: with open(pathlib.Path(td) / 'one_caption.srt', 'w') as f: f.write((PATH_TO_SAMPLES / 'one_caption.srt').read_text()) webvtt.from_srt( pathlib.Path(td) / 'one_caption.srt' ).save() self.assertTrue( os.path.exists(pathlib.Path(td) / 'one_caption.vtt') ) self.assertEqual( (pathlib.Path(td) / 'one_caption.vtt').read_text(), textwrap.dedent(''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_sbv_conversion(self): with tempfile.TemporaryDirectory() as td: with open(pathlib.Path(td) / 'two_captions.sbv', 'w') as f: f.write( (PATH_TO_SAMPLES / 'two_captions.sbv').read_text() ) webvtt.from_sbv( pathlib.Path(td) / 'two_captions.sbv' ).save() self.assertTrue( os.path.exists(pathlib.Path(td) / 'two_captions.vtt') ) self.assertEqual( (pathlib.Path(td) / 'two_captions.vtt').read_text(), textwrap.dedent(''' WEBVTT 00:00:00.378 --> 00:00:11.378 Caption text #1 00:00:11.378 --> 00:00:12.305 Caption text #2 (line 1) Caption text #2 (line 2) ''').strip() + '\n' ) def test_save_to_other_location(self): with tempfile.TemporaryDirectory() as td: webvtt.read( PATH_TO_SAMPLES / 'one_caption.vtt' ).save(td) self.assertTrue( os.path.exists(pathlib.Path(td) / 'one_caption.vtt') ) def test_save_specific_filename(self): with tempfile.TemporaryDirectory() as td: webvtt.read( PATH_TO_SAMPLES / 'one_caption.vtt' ).save( pathlib.Path(td) / 'one_caption_new.vtt' ) self.assertTrue( os.path.exists(pathlib.Path(td) / 'one_caption_new.vtt') ) def test_save_specific_filename_no_extension(self): with tempfile.TemporaryDirectory() as td: webvtt.read( PATH_TO_SAMPLES / 'one_caption.vtt' ).save( pathlib.Path(td) / 'one_caption_new' ) self.assertTrue( os.path.exists(pathlib.Path(td) / 'one_caption_new.vtt') ) def test_from_buffer(self): with open(PATH_TO_SAMPLES / 'sample.vtt', 'r', encoding='utf-8') as f: vtt = webvtt.from_buffer(f) self.assertEqual(len(vtt), 16) self.assertEqual( str(vtt), textwrap.dedent(''' 00:00:00.500 00:00:07.000 Caption text #1 00:00:07.000 00:00:11.890 Caption text #2 00:00:11.890 00:00:16.320 Caption text #3 00:00:16.320 00:00:21.580 Caption text #4 00:00:21.580 00:00:23.880 Caption text #5 00:00:23.880 00:00:27.280 Caption text #6 00:00:27.280 00:00:30.280 Caption text #7 00:00:30.280 00:00:36.510 Caption text #8 00:00:36.510 00:00:38.870 Caption text #9 00:00:38.870 00:00:45.000 Caption text #10 00:00:45.000 00:00:47.000 Caption text #11 00:00:47.000 00:00:50.970 Caption text #12 00:00:50.970 00:00:54.440 Caption text #13 00:00:54.440 00:00:58.600 Caption text #14 00:00:58.600 00:01:01.350 Caption text #15 00:01:01.350 00:01:04.300 Caption text #16 ''' ).strip()) def test_deprecated_read_buffer(self): with open(PATH_TO_SAMPLES / 'sample.vtt', 'r', encoding='utf-8') as f: with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('always') vtt = webvtt.read_buffer(f) self.assertIsInstance(vtt.captions, list) self.assertEqual( 'Deprecated: use from_buffer instead.', str(warns[-1].message) ) def test_read_memory_buffer(self): buffer = io.StringIO( (PATH_TO_SAMPLES / 'sample.vtt').read_text() ) self.assertIsInstance( webvtt.from_buffer(buffer).captions, list ) def test_read_memory_buffer_carriage_return(self): """https://github.com/glut23/webvtt-py/issues/29""" buffer = io.StringIO(textwrap.dedent('''\ WEBVTT\r \r 00:00:00.500 --> 00:00:07.000\r Caption text #1\r \r 00:00:07.000 --> 00:00:11.890\r Caption text #2\r \r 00:00:11.890 --> 00:00:16.320\r Caption text #3\r ''') ) self.assertEqual( len(webvtt.from_buffer(buffer).captions), 3 ) def test_read_malformed_buffer(self): malformed_payloads = ['', 'MOCK MALFORMED CONTENT'] for payload in malformed_payloads: buffer = io.StringIO(payload) with self.assertRaises(MalformedFileError): webvtt.from_buffer(buffer) def test_read_buffer_for_vtt_content(self): buffer = io.StringIO(textwrap.dedent('''\ WEBVTT\r \r 00:00:00.500 --> 00:00:07.000\r Caption text #1\r \r 00:00:07.000 --> 00:00:11.890\r Caption text #2\r \r 00:00:11.890 --> 00:00:16.320\r Caption text #3\r ''') ) vtt = webvtt.from_buffer(buffer, format='vtt') self.assertEqual(len(vtt), 3) self.assertEqual( str(vtt[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(vtt[1]), '00:00:07.000 00:00:11.890 Caption text #2' ) self.assertEqual( str(vtt[2]), '00:00:11.890 00:00:16.320 Caption text #3' ) def test_read_buffer_for_srt_content(self): buffer = io.StringIO(textwrap.dedent('''\ 0\r 00:00:00,500 --> 00:00:07,000\r Caption text #1\r \r 1\r 00:00:07,000 --> 00:00:11,890\r Caption text #2\r \r 2\r 00:00:11,890 --> 00:00:16,320\r Caption text #3\r ''') ) vtt = webvtt.from_buffer(buffer, format='srt') self.assertEqual(len(vtt), 3) self.assertEqual( str(vtt[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(vtt[1]), '00:00:07.000 00:00:11.890 Caption text #2' ) self.assertEqual( str(vtt[2]), '00:00:11.890 00:00:16.320 Caption text #3' ) def test_read_buffer_for_sbv_content(self): buffer = io.StringIO(textwrap.dedent('''\ 00:00:00.500,00:00:07.000\r Caption text #1\r \r 00:00:07.000,00:00:11.890\r Caption text #2\r \r 00:00:11.890,00:00:16.320\r Caption text #3\r ''') ) vtt = webvtt.from_buffer(buffer, format='sbv') self.assertEqual(len(vtt), 3) self.assertEqual( str(vtt[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(vtt[1]), '00:00:07.000 00:00:11.890 Caption text #2' ) self.assertEqual( str(vtt[2]), '00:00:11.890 00:00:16.320 Caption text #3' ) def test_read_buffer_unsupported_format(self): self.assertRaises( ValueError, webvtt.from_buffer, io.StringIO(), format='ttt' ) def test_read_bytesio_buffer_for_srt_content(self): buffer = io.BytesIO(textwrap.dedent('''\ 0\r 00:00:00,500 --> 00:00:07,000\r Caption text #1\r \r 1\r 00:00:07,000 --> 00:00:11,890\r Caption text #2\r \r 2\r 00:00:11,890 --> 00:00:16,320\r Caption text #3\r ''').encode('utf-8') ) vtt = webvtt.from_buffer(buffer, format='srt') self.assertEqual(len(vtt), 3) self.assertEqual( str(vtt[0]), '00:00:00.500 00:00:07.000 Caption text #1' ) self.assertEqual( str(vtt[1]), '00:00:07.000 00:00:11.890 Caption text #2' ) self.assertEqual( str(vtt[2]), '00:00:11.890 00:00:16.320 Caption text #3' ) def test_captions(self): captions = webvtt.read(PATH_TO_SAMPLES / 'sample.vtt').captions self.assertIsInstance( captions, list ) self.assertEqual(len(captions), 16) def test_sequence_iteration(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'sample.vtt') self.assertIsInstance(vtt[0], Caption) self.assertEqual(len(vtt), len(vtt.captions)) def test_save_no_filename(self): self.assertRaises( webvtt.errors.MissingFilenameError, webvtt.WebVTT().save ) def test_save_with_path_to_dir_no_filename(self): with tempfile.TemporaryDirectory() as td: self.assertRaises( webvtt.errors.MissingFilenameError, webvtt.WebVTT().save, td ) def test_set_styles_from_text(self): style = Style('::cue(b) {\n color: peachpuff;\n}') self.assertListEqual( style.lines, ['::cue(b) {', ' color: peachpuff;', '}'] ) def test_save_identifiers(self): with tempfile.NamedTemporaryFile('w', suffix='.vtt') as f: webvtt.read( PATH_TO_SAMPLES / 'using_identifiers.vtt' ).save( f.name ) self.assertListEqual( pathlib.Path(f.name).read_text().splitlines(), [ 'WEBVTT', '', '00:00:00.500 --> 00:00:07.000', 'Caption text #1', '', 'second caption', '00:00:07.000 --> 00:00:11.890', 'Caption text #2', '', '00:00:11.890 --> 00:00:16.320', 'Caption text #3', '', '4', '00:00:16.320 --> 00:00:21.580', 'Caption text #4', '', '00:00:21.580 --> 00:00:23.880', 'Caption text #5', '', '00:00:23.880 --> 00:00:27.280', 'Caption text #6' ] ) def test_save_updated_identifiers(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'using_identifiers.vtt') vtt.captions[0].identifier = 'first caption' vtt.captions[1].identifier = None vtt.captions[3].identifier = '44' last_caption = Caption(start='00:00:27.280', end='00:00:29.200', text='Caption text #7' ) last_caption.identifier = 'last caption' vtt.captions.append(last_caption) with tempfile.NamedTemporaryFile('w', suffix='.vtt') as f: vtt.save(f.name) self.assertListEqual( pathlib.Path(f.name).read_text().splitlines(), [ 'WEBVTT', '', 'first caption', '00:00:00.500 --> 00:00:07.000', 'Caption text #1', '', '00:00:07.000 --> 00:00:11.890', 'Caption text #2', '', '00:00:11.890 --> 00:00:16.320', 'Caption text #3', '', '44', '00:00:16.320 --> 00:00:21.580', 'Caption text #4', '', '00:00:21.580 --> 00:00:23.880', 'Caption text #5', '', '00:00:23.880 --> 00:00:27.280', 'Caption text #6', '', 'last caption', '00:00:27.280 --> 00:00:29.200', 'Caption text #7' ] ) def test_content_formatting(self): """ Verify that content property returns the correctly formatted webvtt. """ captions = [ Caption(start='00:00:00.500', end='00:00:07.000', text=['Caption test line 1', 'Caption test line 2'] ), Caption(start='00:00:08.000', end='00:00:15.000', text=['Caption test line 3', 'Caption test line 4'] ), ] self.assertEqual( webvtt.WebVTT(captions=captions).content, textwrap.dedent(""" WEBVTT 00:00:00.500 --> 00:00:07.000 Caption test line 1 Caption test line 2 00:00:08.000 --> 00:00:15.000 Caption test line 3 Caption test line 4 """).strip() + '\n' ) def test_repr(self): test_file = PATH_TO_SAMPLES / 'sample.vtt' self.assertEqual( repr(webvtt.read(test_file)), f"" ) def test_str(self): self.assertEqual( str(webvtt.read(PATH_TO_SAMPLES / 'sample.vtt')), textwrap.dedent(""" 00:00:00.500 00:00:07.000 Caption text #1 00:00:07.000 00:00:11.890 Caption text #2 00:00:11.890 00:00:16.320 Caption text #3 00:00:16.320 00:00:21.580 Caption text #4 00:00:21.580 00:00:23.880 Caption text #5 00:00:23.880 00:00:27.280 Caption text #6 00:00:27.280 00:00:30.280 Caption text #7 00:00:30.280 00:00:36.510 Caption text #8 00:00:36.510 00:00:38.870 Caption text #9 00:00:38.870 00:00:45.000 Caption text #10 00:00:45.000 00:00:47.000 Caption text #11 00:00:47.000 00:00:50.970 Caption text #12 00:00:50.970 00:00:54.440 Caption text #13 00:00:54.440 00:00:58.600 Caption text #14 00:00:58.600 00:01:01.350 Caption text #15 00:01:01.350 00:01:04.300 Caption text #16 """).strip() ) def test_parse_invalid_file(self): self.assertRaises( MalformedFileError, webvtt.read, PATH_TO_SAMPLES / 'invalid.vtt' ) def test_file_not_found(self): self.assertRaises( FileNotFoundError, webvtt.read, 'nowhere' ) def test_total_length(self): self.assertEqual( webvtt.read(PATH_TO_SAMPLES / 'sample.vtt').total_length, 64 ) def test_total_length_no_captions(self): self.assertEqual( webvtt.WebVTT().total_length, 0 ) def test_parse_empty_file(self): self.assertRaises( MalformedFileError, webvtt.read, PATH_TO_SAMPLES / 'empty.vtt' ) def test_parse_invalid_timeframe_line(self): good_captions = len( webvtt.read(PATH_TO_SAMPLES / 'invalid_timeframe.vtt').captions ) self.assertEqual(good_captions, 6) def test_parse_invalid_timeframe_in_cue_text(self): vtt = webvtt.read( PATH_TO_SAMPLES / 'invalid_timeframe_in_cue_text.vtt' ) self.assertEqual(2, len(vtt.captions)) self.assertEqual('Caption text #3', vtt.captions[1].text) def test_parse_get_caption_data(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'one_caption.vtt') self.assertEqual(vtt.captions[0].start_in_seconds, 0) self.assertEqual(vtt.captions[0].start, '00:00:00.500') self.assertEqual(vtt.captions[0].end_in_seconds, 7) self.assertEqual(vtt.captions[0].end, '00:00:07.000') self.assertEqual(vtt.captions[0].lines[0], 'Caption text #1') self.assertEqual(len(vtt.captions[0].lines), 1) def test_caption_without_timeframe(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'missing_timeframe.vtt') self.assertEqual(len(vtt.captions), 6) def test_caption_without_cue_text(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'missing_caption_text.vtt') self.assertEqual(len(vtt.captions), 4) def test_timestamps_format(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'sample.vtt') self.assertEqual(vtt.captions[2].start, '00:00:11.890') self.assertEqual(vtt.captions[2].end, '00:00:16.320') def test_parse_timestamp(self): self.assertEqual( Caption(start='02:03:11.890').start_in_seconds, 7391 ) def test_captions_attribute(self): self.assertListEqual(webvtt.WebVTT().captions, []) def test_metadata_headers(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'metadata_headers.vtt') self.assertEqual(len(vtt.captions), 2) def test_metadata_headers_multiline(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'metadata_headers_multiline.vtt') self.assertEqual(len(vtt.captions), 2) def test_parse_identifiers(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'using_identifiers.vtt') self.assertEqual(len(vtt.captions), 6) self.assertEqual(vtt.captions[1].identifier, 'second caption') self.assertEqual(vtt.captions[2].identifier, None) self.assertEqual(vtt.captions[3].identifier, '4') def test_parse_comments(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'comments.vtt') self.assertEqual(len(vtt.captions), 3) self.assertListEqual( vtt.captions[0].lines, ['- Ta en kopp varmt te.', '- Det är inte varmt.'] ) self.assertListEqual( vtt.captions[0].comments, [] ) self.assertListEqual( vtt.captions[1].comments, [] ) self.assertEqual( vtt.captions[2].text, '- Ta en kopp' ) self.assertListEqual( vtt.captions[2].comments, ['This last line may not translate well.'] ) self.assertListEqual( vtt.header_comments, ['This translation was done by Kyle so that\n' 'some friends can watch it with their parents.' ] ) self.assertListEqual( vtt.footer_comments, ['end of file'] ) def test_parse_styles(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'styles.vtt') self.assertEqual(len(vtt.captions), 1) self.assertEqual( vtt.styles[0].text, '::cue {\n background-image: linear-gradient(to bottom, ' 'dimgray, lightgray);\n color: papayawhip;\n}' ) def test_parse_styles_with_comments(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'styles_with_comments.vtt') self.assertEqual(len(vtt.captions), 1) self.assertEqual(len(vtt.styles), 2) self.assertEqual( vtt.styles[0].comments, [] ) self.assertEqual( vtt.styles[0].text, '::cue {\n' ' background-image: linear-gradient(to bottom, dimgray, ' 'lightgray);\n' ' color: papayawhip;\n' '}' ) self.assertEqual( vtt.styles[1].comments, ['This is the second block of styles', 'Multiline comment for the same\nsecond block of styles' ] ) self.assertEqual( vtt.styles[1].text, '::cue(b) {\n' ' color: peachpuff;\n' '}' ) self.assertListEqual( vtt.header_comments, ['Sample of comments with styles'] ) self.assertListEqual( vtt.footer_comments, [] ) def test_multiple_comments_everywhere(self): vtt = webvtt.WebVTT.from_string(textwrap.dedent(""" WEBVTT NOTE Test file NOTE this file is testing multiple comments in different places STYLE ::cue { background-image: linear-gradient(to bottom, dimgray, lightgray); color: papayawhip; } NOTE this style uses nice color NOTE check how it looks before proceeding STYLE ::cue(b) { color: peachpuff; } 00:00:00.500 --> 00:00:07.000 Caption text #1 NOTE next caption has two lines NOTE needs review 00:00:07.000 --> 00:00:11.890 Caption text #2 line 1 Caption text #2 line 2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 NOTE Copyright 2024 NOTE this is the end of the file """).strip() ) self.assertListEqual( vtt.header_comments, ['Test file', 'this file is testing multiple comments\nin different places' ] ) self.assertListEqual( vtt.footer_comments, ['Copyright 2024', 'this is the end of the file' ] ) self.assertListEqual( vtt.captions[0].comments, [] ) self.assertListEqual( vtt.captions[1].comments, ['next caption has two lines', 'needs review' ] ) self.assertListEqual( vtt.captions[2].comments, [] ) self.assertListEqual( vtt.captions[3].comments, [] ) self.assertListEqual( vtt.styles[0].comments, [] ) self.assertListEqual( vtt.styles[1].comments, ['this style uses nice color', 'check how it looks before proceeding' ] ) def test_comments_in_new_file(self): with tempfile.NamedTemporaryFile('r', suffix='.vtt') as f: vtt = webvtt.WebVTT() vtt.header_comments.append('This is a header comment') vtt.header_comments.append( 'where we can see a\ntwo line comment' ) vtt.styles.append( Style('::cue(b) {\n color: peachpuff;\n}') ) style = Style('::cue {\n color: papayawhip;\n}') style.comments.append('Another style to test\nthe look and feel') style.comments.append('Please check') vtt.styles.append(style) vtt.captions.append( Caption(start='00:00:00.500', end='00:00:07.000', text='Caption #1', ) ) caption = Caption(start='00:00:07.000', end='00:00:11.890', text='Caption #2' ) caption.comments.append( 'Second caption may be a bit off\nand needs checking' ) caption.comments.append('Confirm if it displays correctly') vtt.captions.append(caption) vtt.footer_comments.append('This is a footer comment') vtt.footer_comments.append( 'where we can also see a\ntwo line comment' ) vtt.save(f.name) self.assertEqual( f.read(), textwrap.dedent(''' WEBVTT NOTE This is a header comment NOTE where we can see a two line comment STYLE ::cue(b) { color: peachpuff; } NOTE Another style to test the look and feel NOTE Please check STYLE ::cue { color: papayawhip; } 00:00:00.500 --> 00:00:07.000 Caption #1 NOTE Second caption may be a bit off and needs checking NOTE Confirm if it displays correctly 00:00:07.000 --> 00:00:11.890 Caption #2 NOTE This is a footer comment NOTE where we can also see a two line comment ''').strip() ) def test_clean_cue_tags(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'cue_tags.vtt') self.assertEqual( vtt.captions[1].text, 'Like a big-a pizza pie' ) self.assertEqual( vtt.captions[2].text, 'That\'s amore' ) def test_parse_captions_with_bom(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'captions_with_bom.vtt') self.assertEqual(len(vtt.captions), 4) def test_empty_lines_are_not_included_in_result(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'netflix_chicas_del_cable.vtt') self.assertEqual(vtt.captions[0].text, "[Alba] En 1928,") self.assertEqual( vtt.captions[-2].text, "Diez años no son suficientes\npara olvidarte..." ) def test_can_parse_youtube_dl_files(self): vtt = webvtt.read(PATH_TO_SAMPLES / 'youtube_dl.vtt') self.assertEqual( "this will happen is I'm telling\n ", vtt.captions[2].text ) def test_parse_voice_spans(self): vtt = webvtt.from_string(textwrap.dedent(""" WEBVTT 00:00:00.000 --> 00:00:00.800 Knock knock 00:00:02.100 --> 00:00:06.500 Who's there? 00:00:10.530 --> 00:00:11.090 Atish """).strip() ) self.assertEqual(len(vtt), 3) self.assertEqual( str(vtt[0]), '00:00:00.000 00:00:00.800 Knock knock' ) self.assertEqual( vtt[0].voice, 'Lisa Simpson' ) self.assertEqual( str(vtt[1]), '00:00:02.100 00:00:06.500 Who\'s there?' ) self.assertEqual( vtt[1].voice, 'Homer Simpson' ) self.assertEqual( str(vtt[2]), '00:00:10.530 00:00:11.090 Atish' ) self.assertEqual( vtt[2].voice, 'Lisa Simpson' ) def test_parse_caption_not_a_voice_span(self): vtt = webvtt.from_string(textwrap.dedent(""" WEBVTT 00:00:00.000 --> 00:00:00.800 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_save_file_with_bom_keeps_bom(self): with tempfile.NamedTemporaryFile('r', suffix='.vtt') as f: webvtt.read( PATH_TO_SAMPLES / 'captions_with_bom.vtt' ).save(f.name) self.assertEqual( f.read(), textwrap.dedent(f''' {CODEC_BOMS['utf-8'].decode()}WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 ''').strip() + '\n' ) def test_save_file_with_bom_removes_bom_if_requested(self): with tempfile.NamedTemporaryFile('r', suffix='.vtt') as f: webvtt.read( PATH_TO_SAMPLES / 'captions_with_bom.vtt' ).save(f.name, add_bom=False) self.assertEqual( f.read(), textwrap.dedent(f''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 00:00:07.000 --> 00:00:11.890 Caption text #2 00:00:11.890 --> 00:00:16.320 Caption text #3 00:00:16.320 --> 00:00:21.580 Caption text #4 ''').strip() + '\n' ) def test_save_file_with_encoding(self): with tempfile.NamedTemporaryFile('rb', suffix='.vtt') as f: webvtt.read( PATH_TO_SAMPLES / 'one_caption.vtt' ).save(f.name, encoding='utf-32-le' ) self.assertEqual( f.read().decode('utf-32-le'), textwrap.dedent(''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_save_file_with_encoding_and_bom(self): with tempfile.NamedTemporaryFile('rb', suffix='.vtt') as f: webvtt.read( PATH_TO_SAMPLES / 'one_caption.vtt' ).save(f.name, encoding='utf-32-le', add_bom=True ) self.assertEqual( f.read().decode('utf-32-le'), textwrap.dedent(f''' {CODEC_BOMS['utf-32-le'].decode('utf-32-le')}WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_save_new_file_utf_8_default_encoding_no_bom(self): with tempfile.NamedTemporaryFile('r', suffix='.vtt') as f: vtt = webvtt.WebVTT() vtt.captions.append( Caption(start='00:00:00.500', end='00:00:07.000', text='Caption text #1' ) ) vtt.save(f.name) self.assertEqual(vtt.encoding, 'utf-8') self.assertEqual( f.read(), textwrap.dedent(f''' WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_save_new_file_utf_8_default_encoding_with_bom(self): with tempfile.NamedTemporaryFile('r', suffix='.vtt') as f: vtt = webvtt.WebVTT() vtt.captions.append( Caption(start='00:00:00.500', end='00:00:07.000', text='Caption text #1' ) ) vtt.save(f.name, add_bom=True ) self.assertEqual(vtt.encoding, 'utf-8') self.assertEqual( f.read(), textwrap.dedent(f''' {CODEC_BOMS['utf-8'].decode()}WEBVTT 00:00:00.500 --> 00:00:07.000 Caption text #1 ''').strip() + '\n' ) def test_iter_slice(self): vtt = webvtt.read( PATH_TO_SAMPLES / 'sample.vtt' ) slice_of_captions = vtt.iter_slice(start='00:00:11.000', end='00:00:27.000' ) for expected_caption in (vtt.captions[2], vtt.captions[3], vtt.captions[4] ): self.assertIs(expected_caption, next(slice_of_captions)) with self.assertRaises(StopIteration): next(slice_of_captions) def test_iter_slice_no_start_time(self): vtt = webvtt.read( PATH_TO_SAMPLES / 'sample.vtt' ) slice_of_captions = vtt.iter_slice(end='00:00:27.000') for expected_caption in (vtt.captions[0], vtt.captions[1], vtt.captions[2], vtt.captions[3], vtt.captions[4] ): self.assertIs(expected_caption, next(slice_of_captions)) with self.assertRaises(StopIteration): next(slice_of_captions) def test_iter_slice_no_end_time(self): vtt = webvtt.read( PATH_TO_SAMPLES / 'sample.vtt' ) slice_of_captions = vtt.iter_slice(start='00:00:47.000') for expected_caption in (vtt.captions[11], vtt.captions[12], vtt.captions[13], vtt.captions[14], vtt.captions[15] ): self.assertIs(expected_caption, next(slice_of_captions)) with self.assertRaises(StopIteration): next(slice_of_captions) webvtt-py-0.5.1/webvtt/000077500000000000000000000000001462607655700150135ustar00rootroot00000000000000webvtt-py-0.5.1/webvtt/__init__.py000066400000000000000000000007151462607655700171270ustar00rootroot00000000000000"""Main webvtt package.""" __version__ = '0.5.1' __author__ = 'Alejandro Mendez' __author_email__ = 'amendez23@gmail.com' from .webvtt import WebVTT from . import segmenter from .models import Caption, Style # noqa __all__ = ['WebVTT', 'Caption', 'Style'] read = WebVTT.read read_buffer = WebVTT.read_buffer from_buffer = WebVTT.from_buffer from_srt = WebVTT.from_srt from_sbv = WebVTT.from_sbv from_string = WebVTT.from_string segment = segmenter.segment webvtt-py-0.5.1/webvtt/cli.py000066400000000000000000000024721462607655700161410ustar00rootroot00000000000000"""CLI module.""" import argparse import typing from . import segmenter def main(argv: typing.Optional[typing.Sequence] = None): """ Segment WebVTT file from command line. :param argv: command line arguments """ arguments = argparse.ArgumentParser( description='Segment WebVTT files.' ) arguments.add_argument( 'command', choices=['segment'], help='command to perform' ) arguments.add_argument( 'file', metavar='PATH', help='WebVTT file' ) arguments.add_argument( '-o', '--output', metavar='PATH', help='output directory' ) arguments.add_argument( '-d', '--target-duration', metavar='NUMBER', type=int, default=segmenter.DEFAULT_SECONDS, help='target duration of each segment in seconds, default: 10' ) arguments.add_argument( '-m', '--mpegts', metavar='NUMBER', type=int, default=segmenter.DEFAULT_MPEGTS, help='presentation timestamp value, default: 900000' ) args = arguments.parse_args(argv) segmenter.segment( args.file, args.output, args.target_duration, args.mpegts ) if __name__ == '__main__': main() # pragma: no cover webvtt-py-0.5.1/webvtt/errors.py000066400000000000000000000004271462607655700167040ustar00rootroot00000000000000"""Errors module.""" class MalformedFileError(Exception): """File is not in the right format.""" class MalformedCaptionError(Exception): """Caption not in the right format.""" class MissingFilenameError(Exception): """Missing a filename when saving to disk.""" webvtt-py-0.5.1/webvtt/models.py000066400000000000000000000162321462607655700166540ustar00rootroot00000000000000"""Models module.""" import re import typing from .errors import MalformedCaptionError class Timestamp: """Representation of a timestamp.""" PATTERN = re.compile(r'(?:(\d{1,2}):)?(\d{1,2}):(\d{1,2})\.(\d{3})') def __init__( self, hours: int = 0, minutes: int = 0, seconds: int = 0, milliseconds: int = 0 ): """Initialize.""" self.hours = hours self.minutes = minutes self.seconds = seconds self.milliseconds = milliseconds def __str__(self): """Return the string representation of the timestamp.""" return ( f'{self.hours:02d}:{self.minutes:02d}:{self.seconds:02d}' f'.{self.milliseconds:03d}' ) def to_tuple(self) -> typing.Tuple[int, int, int, int]: """Return the timestamp in tuple form.""" return self.hours, self.minutes, self.seconds, self.milliseconds def __repr__(self): """Return the string representation of the caption.""" return (f'<{self.__class__.__name__} ' f'hours={self.hours} ' f'minutes={self.minutes} ' f'seconds={self.seconds} ' f'milliseconds={self.milliseconds}>' ) def __eq__(self, other): """Compare equality with other object.""" return self.to_tuple() == other.to_tuple() def __ne__(self, other): """Compare a not equality with other object.""" return self.to_tuple() != other.to_tuple() def __gt__(self, other): """Compare greater than with other object.""" return self.to_tuple() > other.to_tuple() def __lt__(self, other): """Compare less than with other object.""" return self.to_tuple() < other.to_tuple() def __ge__(self, other): """Compare greater or equal with other object.""" return self.to_tuple() >= other.to_tuple() def __le__(self, other): """Compare less or equal with other object.""" return self.to_tuple() <= other.to_tuple() @classmethod def from_string(cls, value: str) -> 'Timestamp': """Return a `Timestamp` instance from a string value.""" if type(value) is not str: raise MalformedCaptionError(f'Invalid timestamp {value!r}') match = re.match(cls.PATTERN, value) if not match: raise MalformedCaptionError(f'Invalid timestamp {value!r}') hours = int(match.group(1) or 0) minutes = int(match.group(2)) seconds = int(match.group(3)) milliseconds = int(match.group(4)) if minutes > 59 or seconds > 59: raise MalformedCaptionError(f'Invalid timestamp {value!r}') return cls(hours, minutes, seconds, milliseconds) def in_seconds(self) -> int: """Return the timestamp in seconds.""" return (self.hours * 3600 + self.minutes * 60 + self.seconds ) class Caption: """Representation of a caption.""" CUE_TEXT_TAGS = re.compile('<.*?>') VOICE_SPAN_PATTERN = re.compile(r']+)>') def __init__(self, start: typing.Optional[str] = None, end: typing.Optional[str] = None, text: typing.Optional[typing.Union[str, typing.Sequence[str] ]] = None, identifier: typing.Optional[str] = None ): """ Initialize. :param start: start time of the caption :param end: end time of the caption :param text: the text of the caption :param identifier: optional identifier """ text = text or [] self.start = start or '00:00:00.000' self.end = end or '00:00:00.000' self.identifier = identifier self.lines = (text.splitlines() if isinstance(text, str) else list(text) ) self.comments: typing.List[str] = [] def __repr__(self): """Return the string representation of the caption.""" cleaned_text = self.text.replace('\n', '\\n') return (f'<{self.__class__.__name__} ' f'start={self.start!r} ' f'end={self.end!r} ' f'text={cleaned_text!r} ' f'identifier={self.identifier!r}>' ) def __str__(self): """Return a readable representation of the caption.""" cleaned_text = self.text.replace('\n', '\\n') return f'{self.start} {self.end} {cleaned_text}' def __eq__(self, other): """Compare equality with another object.""" if not isinstance(other, type(self)): return False return (self.start == other.start and self.end == other.end and self.raw_text == other.raw_text and self.identifier == other.identifier ) @property def start(self): """Return the start time of the caption.""" return str(self.start_time) @start.setter def start(self, value: str): """Set the start time of the caption.""" self.start_time = Timestamp.from_string(value) @property def end(self): """Return the end time of the caption.""" return str(self.end_time) @end.setter def end(self, value: str): """Set the end time of the caption.""" self.end_time = Timestamp.from_string(value) @property def start_in_seconds(self) -> int: """Return the start time of the caption in seconds.""" return self.start_time.in_seconds() @property def end_in_seconds(self): """Return the end time of the caption in seconds.""" return self.end_time.in_seconds() @property def raw_text(self) -> str: """Return the text of the caption (including cue tags).""" return '\n'.join(self.lines) @property def text(self) -> str: """Return the text of the caption (without cue tags).""" return re.sub(self.CUE_TEXT_TAGS, '', self.raw_text) @text.setter def text(self, value: str): """Set the text of the captions.""" if not isinstance(value, str): raise AttributeError( f'String value expected but received {value}.' ) self.lines = value.splitlines() @property def voice(self) -> typing.Optional[str]: """Return the voice span if present.""" if self.lines and self.lines[0].startswith(' bool: """ Validate the lines for a match of a cue time block. :param lines: the lines to be validated :returns: true for a matching cue time block """ return bool( len(lines) >= 2 and re.match(cls.CUE_TIMINGS_PATTERN, lines[0]) and lines[1].strip() ) @classmethod def from_lines( cls, lines: typing.Sequence[str] ) -> 'SBVCueBlock': """ Create a `SBVCueBlock` from lines of text. :param lines: the lines of text :returns: `SBVCueBlock` instance """ match = re.match(cls.CUE_TIMINGS_PATTERN, lines[0]) assert match is not None payload = lines[1:] return cls(match.group(1), match.group(2), payload) def parse(lines: typing.Sequence[str]) -> typing.List[Caption]: """ Parse SBV captions from lines of text. :param lines: lines of text :returns: list of `Caption` objects """ if not _is_valid_content(lines): raise MalformedFileError('Invalid format') return _parse_captions(lines) def _is_valid_content(lines: typing.Sequence[str]) -> bool: """ Validate lines of text for valid SBV content. :param lines: lines of text :returns: true for a valid SBV content """ if len(lines) < 2: return False first_block = next(utils.iter_blocks_of_lines(lines)) return bool(first_block and SBVCueBlock.is_valid(first_block)) def _parse_captions(lines: typing.Sequence[str]) -> typing.List[Caption]: """ Parse captions from the text. :param lines: lines of text :returns: list of `Caption` objects """ captions = [] for block_lines in utils.iter_blocks_of_lines(lines): if not SBVCueBlock.is_valid(block_lines): continue cue_block = SBVCueBlock.from_lines(block_lines) captions.append(Caption(cue_block.start, cue_block.end, cue_block.payload )) return captions webvtt-py-0.5.1/webvtt/segmenter.py000066400000000000000000000071251462607655700173630ustar00rootroot00000000000000"""Segmenter module.""" import typing import os import pathlib from math import ceil, floor from .webvtt import WebVTT, Caption DEFAULT_MPEGTS = 900000 DEFAULT_SECONDS = 10 # default number of seconds per segment def segment( webvtt_path: str, output: str, seconds: int = DEFAULT_SECONDS, mpegts: int = DEFAULT_MPEGTS ): """ Segment a WebVTT captions file. :param webvtt_path: the path to the file :param output: the path to the destination folder :param seconds: the number of seconds for each segment :param mpegts: value for the MPEG-TS """ captions = WebVTT.read(webvtt_path).captions output_folder = pathlib.Path(output) os.makedirs(output_folder, exist_ok=True) segments = slice_segments(captions, seconds) write_segments(output_folder, segments, mpegts) write_manifest(output_folder, segments, seconds) def slice_segments( captions: typing.Sequence[Caption], seconds: int ) -> typing.List[typing.List[Caption]]: """ Slice segments of captions based on seconds per segment. :param captions: the captions :param seconds: seconds per segment :returns: list of lists of `Caption` objects """ total_segments = ( 0 if not captions else int(ceil(captions[-1].end_in_seconds / seconds)) ) segments: typing.List[typing.List[Caption]] = [ [] for _ in range(total_segments) ] for c in captions: segment_index_start = floor(c.start_in_seconds / seconds) segments[segment_index_start].append(c) # Also include a caption in other segments based on the end time. segment_index_end = floor(c.end_in_seconds / seconds) if segment_index_end > segment_index_start: for i in range(segment_index_start + 1, segment_index_end + 1): segments[i].append(c) return segments def write_segments( output_folder: pathlib.Path, segments: typing.Iterable[typing.Iterable[Caption]], mpegts: int ): """ Write the segments to the output folder. :param output_folder: folder where the segment files will be stored :param segments: the segments of `Caption` objects :param mpegts: value for the MPEG-TS """ for index, segment in enumerate(segments): segment_file = output_folder / f'fileSequence{index}.webvtt' with open(segment_file, 'w', encoding='utf-8') as f: f.write('WEBVTT\n') f.write(f'X-TIMESTAMP-MAP=MPEGTS:{mpegts},' 'LOCAL:00:00:00.000\n' ) for caption in segment: f.write('\n{} --> {}\n'.format(caption.start, caption.end)) f.writelines(f'{line}\n' for line in caption.lines) def write_manifest( output_folder: pathlib.Path, segments: typing.Iterable[typing.Iterable[Caption]], seconds: int ): """ Write the manifest in the output folder. :param output_folder: folder where the manifest will be stored :param segments: the segments of `Caption` objects :param seconds: the seconds per segment """ manifest_file = output_folder / 'prog_index.m3u8' with open(manifest_file, 'w', encoding='utf-8') as f: f.write('#EXTM3U\n') f.write(f'#EXT-X-TARGETDURATION:{seconds}\n') f.write('#EXT-X-VERSION:3\n') f.write('#EXT-X-PLAYLIST-TYPE:VOD\n') for index, _ in enumerate(segments): f.write('#EXTINF:30.00000\n') f.write(f'fileSequence{index}.webvtt\n') f.write('#EXT-X-ENDLIST\n') webvtt-py-0.5.1/webvtt/srt.py000066400000000000000000000072271462607655700162050ustar00rootroot00000000000000"""SRT format module.""" import typing import re from .models import Caption from .errors import MalformedFileError from . import utils class SRTCueBlock: """Representation of a cue timing block.""" CUE_TIMINGS_PATTERN = re.compile( r'\s*(\d+:\d{2}:\d{2},\d{3})\s*-->\s*(\d+:\d{2}:\d{2},\d{3})' ) def __init__( self, index: str, start: str, end: str, payload: typing.Sequence[str] ): """ Initialize. :param start: start time :param end: end time :param payload: caption text """ self.index = index self.start = start self.end = end self.payload = payload @classmethod def is_valid( cls, lines: typing.Sequence[str] ) -> bool: """ Validate the lines for a match of a cue time block. :param lines: the lines to be validated :returns: true for a matching cue time block """ return bool( len(lines) >= 3 and lines[0].isdigit() and re.match(cls.CUE_TIMINGS_PATTERN, lines[1]) ) @classmethod def from_lines( cls, lines: typing.Sequence[str] ) -> 'SRTCueBlock': """ Create a `SRTCueBlock` from lines of text. :param lines: the lines of text :returns: `SRTCueBlock` instance """ index = lines[0] match = re.match(cls.CUE_TIMINGS_PATTERN, lines[1]) assert match is not None payload = lines[2:] return cls(index, match.group(1), match.group(2), payload) def parse(lines: typing.Sequence[str]) -> typing.List[Caption]: """ Parse SRT captions from lines of text. :param lines: lines of text :returns: list of `Caption` objects """ if not is_valid_content(lines): raise MalformedFileError('Invalid format') return parse_captions(lines) def is_valid_content(lines: typing.Sequence[str]) -> bool: """ Validate lines of text for valid SBV content. :param lines: lines of text :returns: true for a valid SBV content """ return bool( len(lines) >= 3 and lines[0].isdigit() and '-->' in lines[1] and lines[2].strip() ) def parse_captions(lines: typing.Sequence[str]) -> typing.List[Caption]: """ Parse captions from the text. :param lines: lines of text :returns: list of `Caption` objects """ captions: typing.List[Caption] = [] for block_lines in utils.iter_blocks_of_lines(lines): if not SRTCueBlock.is_valid(block_lines): continue cue_block = SRTCueBlock.from_lines(block_lines) cue_block.start, cue_block.end = map( lambda x: x.replace(',', '.'), (cue_block.start, cue_block.end)) captions.append(Caption(cue_block.start, cue_block.end, cue_block.payload )) return captions def write( f: typing.IO[str], captions: typing.Iterable[Caption] ): """ Write captions to an output. :param f: file or file-like object :param captions: Iterable of `Caption` objects """ output = [] for index, caption in enumerate(captions, start=1): output.extend([ f'{index}', '{} --> {}'.format(*map(lambda x: x.replace('.', ','), (caption.start, caption.end)) ), *caption.text.splitlines(), '' ]) f.write('\n'.join(output).rstrip()) webvtt-py-0.5.1/webvtt/utils.py000066400000000000000000000055251462607655700165340ustar00rootroot00000000000000"""Utils module.""" import typing import codecs CODEC_BOMS = { 'utf-8': codecs.BOM_UTF8, 'utf-32-le': codecs.BOM_UTF32_LE, 'utf-32-be': codecs.BOM_UTF32_BE, 'utf-16-le': codecs.BOM_UTF16_LE, 'utf-16-be': codecs.BOM_UTF16_BE } class FileWrapper: """File handling functionality with built-in support for Byte OrderMark.""" def __init__( self, file_path: str, mode: typing.Optional[str] = None, encoding: typing.Optional[str] = None ): """ Initialize. :param file_path: path to the file :param mode: mode in which the file is opened :param encoding: name of the encoding used to decode the file """ self.file_path = file_path self.mode = mode or 'r' self.bom_encoding = self.detect_bom_encoding(file_path) self.encoding = (self.bom_encoding or encoding or 'utf-8' ) @classmethod def open( cls, file_path: str, mode: typing.Optional[str] = None, encoding: typing.Optional[str] = None ) -> 'FileWrapper': """ Open a file. :param file_path: path to the file :param mode: mode in which the file is opened :param encoding: name of the encoding used to decode the file """ return cls(file_path, mode, encoding) def __enter__(self): """Enter context.""" self.file = open( file=self.file_path, mode=self.mode, encoding=self.encoding ) if self.bom_encoding: self.file.seek(len(CODEC_BOMS[self.bom_encoding])) return self def __exit__(self, exc_type, exc_val, exc_tb): """Exit context.""" self.file.close() @staticmethod def detect_bom_encoding(file_path: str) -> typing.Optional[str]: """ Detect the encoding of a file based on the presence of the BOM. :param file_path: path to the file :returns: the encoding if BOM is found or None. """ with open(file_path, mode='rb') as f: first_bytes = f.read(4) for encoding, bom in CODEC_BOMS.items(): if first_bytes.startswith(bom): return encoding return None def iter_blocks_of_lines( lines: typing.Iterable[str] ) -> typing.Generator[typing.List[str], None, None]: """ Iterate blocks of text. :param lines: lines of text. """ current_text_block = [] for line in lines: if line.strip(): current_text_block.append(line) elif current_text_block: yield current_text_block current_text_block = [] if current_text_block: yield current_text_block webvtt-py-0.5.1/webvtt/vtt.py000066400000000000000000000251761462607655700162150ustar00rootroot00000000000000"""VTT format module.""" import re import typing from dataclasses import dataclass from .errors import MalformedFileError from .models import Caption, Style from . import utils @dataclass class ParserOutput: """Output of parser.""" styles: typing.List[Style] captions: typing.List[Caption] header_comments: typing.List[str] footer_comments: typing.List[str] @classmethod def from_data( cls, data: typing.Mapping[str, typing.Any] ) -> 'ParserOutput': """ Return a `ParserOutput` instance from the provided data. :param data: data from the parser :returns: an instance of `ParserOutput` """ items = data.get('items', []) return cls( captions=[it for it in items if isinstance(it, Caption)], styles=[it for it in items if isinstance(it, Style)], header_comments=data.get('header_comments', []), footer_comments=data.get('footer_comments', []) ) class WebVTTCueBlock: """Representation of a cue timing block.""" CUE_TIMINGS_PATTERN = re.compile( r'\s*((?:\d+:)?\d{2}:\d{2}.\d{3})\s*-->\s*((?:\d+:)?\d{2}:\d{2}.\d{3})' ) def __init__( self, identifier, start, end, payload ): """ Initialize. :param start: start time :param end: end time :param payload: caption text """ self.identifier = identifier self.start = start self.end = end self.payload = payload @classmethod def is_valid( cls, lines: typing.Sequence[str] ) -> bool: """ Validate the lines for a match of a cue time block. :param lines: the lines to be validated :returns: true for a matching cue time block """ return bool( ( len(lines) >= 2 and re.match(cls.CUE_TIMINGS_PATTERN, lines[0]) and "-->" not in lines[1] ) or ( len(lines) >= 3 and "-->" not in lines[0] and re.match(cls.CUE_TIMINGS_PATTERN, lines[1]) and "-->" not in lines[2] ) ) @classmethod def from_lines( cls, lines: typing.Iterable[str] ) -> 'WebVTTCueBlock': """ Create a `WebVTTCueBlock` from lines of text. :param lines: the lines of text :returns: `WebVTTCueBlock` instance """ identifier = None start = None end = None payload = [] for line in lines: timing_match = re.match(cls.CUE_TIMINGS_PATTERN, line) if timing_match: start = timing_match.group(1) end = timing_match.group(2) elif not start: identifier = line else: payload.append(line) return cls(identifier, start, end, payload) @staticmethod def format_lines(caption: Caption) -> typing.List[str]: """ Return the lines for a cue block. :param caption: the `Caption` instance :returns: list of lines for a cue block """ return [ '', *(identifier for identifier in {caption.identifier} if identifier), f'{caption.start} --> {caption.end}', *caption.lines ] class WebVTTCommentBlock: """Representation of a comment block.""" COMMENT_PATTERN = re.compile(r'NOTE\s(.*?)\Z', re.DOTALL) def __init__(self, text: str): """ Initialize. :param text: comment text """ self.text = text @classmethod def is_valid( cls, lines: typing.Sequence[str] ) -> bool: """ Validate the lines for a match of a comment block. :param lines: the lines to be validated :returns: true for a matching comment block """ return bool(lines and lines[0].startswith('NOTE')) @classmethod def from_lines( cls, lines: typing.Iterable[str] ) -> 'WebVTTCommentBlock': """ Create a `WebVTTCommentBlock` from lines of text. :param lines: the lines of text :returns: `WebVTTCommentBlock` instance """ match = cls.COMMENT_PATTERN.match('\n'.join(lines)) return cls(text=match.group(1).strip() if match else '') @staticmethod def format_lines(lines: str) -> typing.List[str]: """ Return the lines for a comment block. :param lines: comment lines :returns: list of lines for a comment block """ list_of_lines = lines.split('\n') if len(list_of_lines) == 1: return [f'NOTE {lines}'] return ['NOTE', *list_of_lines] class WebVTTStyleBlock: """Representation of a style block.""" STYLE_PATTERN = re.compile(r'STYLE\s(.*?)\Z', re.DOTALL) def __init__(self, text: str): """ Initialize. :param text: style text """ self.text = text @classmethod def is_valid( cls, lines: typing.Sequence[str] ) -> bool: """ Validate the lines for a match of a style block. :param lines: the lines to be validated :returns: true for a matching style block """ return (len(lines) >= 2 and lines[0] == 'STYLE' and not any(line.strip() == '' or '-->' in line for line in lines) ) @classmethod def from_lines( cls, lines: typing.Iterable[str] ) -> 'WebVTTStyleBlock': """ Create a `WebVTTStyleBlock` from lines of text. :param lines: the lines of text :returns: `WebVTTStyleBlock` instance """ match = cls.STYLE_PATTERN.match('\n'.join(lines)) return cls(text=match.group(1).strip() if match else '') @staticmethod def format_lines(lines: typing.List[str]) -> typing.List[str]: """ Return the lines for a style block. :param lines: style lines :returns: list of lines for a style block """ return ['STYLE', *lines] def parse( lines: typing.Sequence[str] ) -> ParserOutput: """ Parse VTT captions from lines of text. :param lines: lines of text :returns: object `ParserOutput` with all parsed items """ if not is_valid_content(lines): raise MalformedFileError('Invalid format') return parse_items(lines) def is_valid_content(lines: typing.Sequence[str]) -> bool: """ Validate lines of text for valid VTT content. :param lines: lines of text :returns: true for a valid VTT content """ return bool(lines and lines[0].startswith('WEBVTT')) def parse_items( lines: typing.Sequence[str] ) -> ParserOutput: """ Parse items from the text. :param lines: lines of text :returns: an object `ParserOutput` with all parsed items """ header_comments: typing.List[str] = [] items: typing.List[typing.Union[Caption, Style]] = [] comments: typing.List[WebVTTCommentBlock] = [] for block_lines in utils.iter_blocks_of_lines(lines): item = parse_item(block_lines) if item: item.comments = [comment.text for comment in comments] comments = [] items.append(item) elif WebVTTCommentBlock.is_valid(block_lines): comments.append(WebVTTCommentBlock.from_lines(block_lines)) if items: header_comments, items[0].comments = items[0].comments, header_comments return ParserOutput.from_data( {'items': items, 'header_comments': header_comments, 'footer_comments': [comment.text for comment in comments] } ) def parse_item( lines: typing.Sequence[str] ) -> typing.Union[Caption, Style, None]: """ Parse an item from lines of text. :param lines: lines of text :returns: An item (Caption or Style) if found, otherwise None """ if WebVTTCueBlock.is_valid(lines): cue_block = WebVTTCueBlock.from_lines(lines) return Caption(cue_block.start, cue_block.end, cue_block.payload, cue_block.identifier ) if WebVTTStyleBlock.is_valid(lines): return Style(WebVTTStyleBlock.from_lines(lines).text) return None def write( f: typing.IO[str], captions: typing.Iterable[Caption], styles: typing.Iterable[Style], header_comments: typing.Iterable[str], footer_comments: typing.Iterable[str] ): """ Write captions to an output. :param f: file or file-like object :param captions: Iterable of `Caption` objects :param styles: Iterable of `Style` objects :param header_comments: the comments for the header :param footer_comments: the comments for the footer """ f.write( to_str(captions, styles, header_comments, footer_comments ) ) def to_str( captions: typing.Iterable[Caption], styles: typing.Iterable[Style], header_comments: typing.Iterable[str], footer_comments: typing.Iterable[str] ) -> str: """ Convert captions to a string with webvtt format. :param captions: the iterable of `Caption` objects :param styles: the iterable of `Style` objects :param header_comments: the comments for the header :param footer_comments: the comments for the footer :returns: String of the content in WebVTT format. """ output = ['WEBVTT'] for comment in header_comments: output.extend([ '', *WebVTTCommentBlock.format_lines(comment) ]) for style in styles: for comment in style.comments: output.extend([ '', *WebVTTCommentBlock.format_lines(comment) ]) output.extend([ '', *WebVTTStyleBlock.format_lines(style.lines) ]) for caption in captions: for comment in caption.comments: output.extend([ '', *WebVTTCommentBlock.format_lines(comment) ]) output.extend(WebVTTCueBlock.format_lines(caption)) if not footer_comments: output.append('') for comment in footer_comments: output.extend([ '', *WebVTTCommentBlock.format_lines(comment) ]) return '\n'.join(output) webvtt-py-0.5.1/webvtt/webvtt.py000066400000000000000000000272721462607655700167120ustar00rootroot00000000000000"""WebVTT module.""" import os import io import typing import warnings from functools import partial from . import vtt, utils from . import srt from . import sbv from .models import Caption, Style, Timestamp from .errors import MissingFilenameError DEFAULT_ENCODING = 'utf-8' class WebVTT: """ Parse captions in WebVTT format and also from other formats like SRT. To read WebVTT: WebVTT.read('captions.vtt') For other formats: WebVTT.from_srt('captions.srt') WebVTT.from_sbv('captions.sbv') """ def __init__( self, file: typing.Optional[str] = None, captions: typing.Optional[typing.List[Caption]] = None, styles: typing.Optional[typing.List[Style]] = None, header_comments: typing.Optional[typing.List[str]] = None, footer_comments: typing.Optional[typing.List[str]] = None, ): """ Initialize. :param file: the path of the WebVTT file :param captions: the list of captions :param styles: the list of styles :param header_comments: list of comments for the start of the file :param footer_comments: list of comments for the bottom of the file """ self.file = file self.captions = captions or [] self.styles = styles or [] self.header_comments = header_comments or [] self.footer_comments = footer_comments or [] self._has_bom = False self.encoding = DEFAULT_ENCODING def __len__(self): """Return the number of captions.""" return len(self.captions) def __getitem__(self, index): """Return a caption by index.""" return self.captions[index] def __repr__(self): """Return the string representation of the WebVTT file.""" return (f'<{self.__class__.__name__} file={self.file!r} ' f'encoding={self.encoding!r}>' ) def __str__(self): """Return a readable representation of the WebVTT content.""" return '\n'.join(str(c) for c in self.captions) @classmethod def read( cls, file: str, encoding: typing.Optional[str] = None ) -> 'WebVTT': """ Read a WebVTT captions file. :param file: the file path :param encoding: encoding of the file :returns: a `WebVTT` instance """ with utils.FileWrapper.open(file, encoding=encoding) as fw: instance = cls.from_buffer(fw.file) if fw.bom_encoding: instance.encoding = fw.bom_encoding instance._has_bom = True return instance @classmethod def read_buffer( cls, buffer: typing.Iterator[str] ) -> 'WebVTT': """ Read WebVTT captions from a file-like object. This method is DEPRECATED. Use from_buffer instead. Such file-like object may be the return of an io.open call, io.StringIO object, tempfile.TemporaryFile object, etc. :param buffer: the file-like object to read captions from :returns: a `WebVTT` instance """ warnings.warn( 'Deprecated: use from_buffer instead.', DeprecationWarning ) return cls.from_buffer(buffer) @classmethod def from_buffer( cls, buffer: typing.Union[typing.Iterable[str], io.BytesIO], format: str = 'vtt' ) -> 'WebVTT': """ Read WebVTT captions from a file-like object. Such file-like object may be the return of an io.open call, io.StringIO object, tempfile.TemporaryFile object, etc. :param buffer: the file-like object to read captions from :param format: the format of the data (vtt, srt or sbv) :returns: a `WebVTT` instance """ if isinstance(buffer, io.BytesIO): buffer = (line.decode('utf-8') for line in buffer) _cls = partial(cls, file=getattr(buffer, 'name', None)) if format == 'vtt': output = vtt.parse(cls._get_lines(buffer)) return _cls( captions=output.captions, styles=output.styles, header_comments=output.header_comments, footer_comments=output.footer_comments ) if format == 'srt': return _cls( captions=srt.parse(cls._get_lines(buffer)) ) if format == 'sbv': return _cls( captions=sbv.parse(cls._get_lines(buffer)) ) raise ValueError(f'Format {format} is not supported.') @classmethod def from_srt( cls, file: str, encoding: typing.Optional[str] = None ) -> 'WebVTT': """ Read captions from a file in SubRip format. :param file: the file path :param encoding: encoding of the file :returns: a `WebVTT` instance """ with utils.FileWrapper.open(file, encoding=encoding) as fw: return cls( file=fw.file.name, captions=srt.parse(cls._get_lines(fw.file)) ) @classmethod def from_sbv( cls, file: str, encoding: typing.Optional[str] = None ) -> 'WebVTT': """ Read captions from a file in YouTube SBV format. :param file: the file path :param encoding: encoding of the file :returns: a `WebVTT` instance """ with utils.FileWrapper.open(file, encoding=encoding) as fw: return cls( file=fw.file.name, captions=sbv.parse(cls._get_lines(fw.file)), ) @classmethod def from_string(cls, string: str) -> 'WebVTT': """ Read captions from a string. :param string: the captions in a string :returns: a `WebVTT` instance """ output = vtt.parse(cls._get_lines(string.splitlines())) return cls( captions=output.captions, styles=output.styles, header_comments=output.header_comments, footer_comments=output.footer_comments ) @staticmethod def _get_lines(lines: typing.Iterable[str]) -> typing.List[str]: """ Return cleaned lines from an iterable of lines. :param lines: iterable of lines :returns: a list of cleaned lines """ return [line.rstrip('\n\r') for line in lines] def _get_destination_file( self, destination_path: typing.Optional[str] = None, extension: str = 'vtt' ) -> str: """ Return the destination file based on the provided params. :param destination_path: optional destination path :param extension: the extension of the file :returns: the destination file :raises MissingFilenameError: if destination path cannot be determined """ if not destination_path and not self.file: raise MissingFilenameError if not destination_path and self.file: destination_path = ( f'{os.path.splitext(self.file)[0]}.{extension}' ) assert destination_path is not None target = os.path.join(os.getcwd(), destination_path) if os.path.isdir(target): if not self.file: raise MissingFilenameError # store the file in specified directory base_name = os.path.splitext(os.path.basename(self.file))[0] new_filename = f'{base_name}.{extension}' return os.path.join(target, new_filename) if target[-4:].lower() != f'.{extension}': target = f'{target}.{extension}' # store the file in the specified full path return target def save( self, output: typing.Optional[str] = None, encoding: typing.Optional[str] = None, add_bom: typing.Optional[bool] = None ): """ Save the WebVTT captions to a file. :param output: destination path of the file :param encoding: encoding of the file :param add_bom: save the file with Byte Order Mark :raises MissingFilenameError: if output cannot be determined """ self.file = self._get_destination_file(output) encoding = encoding or self.encoding if add_bom is None and self._has_bom: add_bom = True with open(self.file, 'w', encoding=encoding) as f: if add_bom and encoding in utils.CODEC_BOMS: f.write(utils.CODEC_BOMS[encoding].decode(encoding)) vtt.write( f, self.captions, self.styles, self.header_comments, self.footer_comments ) def save_as_srt( self, output: typing.Optional[str] = None, encoding: typing.Optional[str] = DEFAULT_ENCODING ): """ Save the WebVTT captions to a file in SubRip format. :param output: destination path of the file :param encoding: encoding of the file :raises MissingFilenameError: if output cannot be determined """ self.file = self._get_destination_file(output, extension='srt') with open(self.file, 'w', encoding=encoding) as f: srt.write(f, self.captions) def write( self, f: typing.IO[str], format: str = 'vtt' ): """ Save the WebVTT captions to a file-like object. :param f: destination file-like object :param format: the format to use (`vtt` or `srt`) :raises MissingFilenameError: if output cannot be determined """ if format == 'vtt': return vtt.write(f, self.captions, self.styles, self.header_comments, self.footer_comments ) if format == 'srt': return srt.write(f, self.captions) raise ValueError(f'Format {format} is not supported.') def iter_slice( self, start: typing.Optional[str] = None, end: typing.Optional[str] = None ) -> typing.Generator[Caption, None, None]: """ Iterate a slice of the captions based on a time range. :param start: start timestamp of the range :param end: end timestamp of the range :returns: generator of Captions """ start_time = Timestamp.from_string(start) if start else None end_time = Timestamp.from_string(end) if end else None for caption in self.captions: if ( (not start_time or caption.start_time >= start_time) and (not end_time or caption.end_time <= end_time) ): yield caption @property def total_length(self): """Returns the total length of the captions.""" if not self.captions: return 0 return ( self.captions[-1].end_in_seconds - self.captions[0].start_in_seconds ) @property def content(self) -> str: """ Return the webvtt capions as string. This property is useful in cases where the webvtt content is needed but no file-like destination is required. Storage in DB for instance. """ return vtt.to_str( self.captions, self.styles, self.header_comments, self.footer_comments )