Beispiel #1
0
    def output_html_for_tuning(
            self,
            audio_file_path,
            output_file_path,
            parameters=None
    ):
        """
        Output an HTML file for fine tuning the sync map manually.

        :param string audio_file_path: the path to the associated audio file
        :param string output_file_path: the path to the output file to write
        :param dict parameters: additional parameters

        .. versionadded:: 1.3.1
        """
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Cannot output HTML file '%s'. Wrong permissions?" % (output_file_path), None, True, OSError)
        if parameters is None:
            parameters = {}
        audio_file_path_absolute = gf.fix_slash(os.path.abspath(audio_file_path))
        template_path_absolute = gf.absolute_path(self.FINETUNEAS_PATH, __file__)
        with io.open(template_path_absolute, "r", encoding="utf-8") as file_obj:
            template = file_obj.read()
        for repl in self.FINETUNEAS_REPLACEMENTS:
            template = template.replace(repl[0], repl[1])
        template = template.replace(
            self.FINETUNEAS_REPLACE_AUDIOFILEPATH,
            u"audioFilePath = \"file://%s\";" % audio_file_path_absolute
        )
        template = template.replace(
            self.FINETUNEAS_REPLACE_FRAGMENTS,
            u"fragments = (%s).fragments;" % self.json_string
        )
        if gc.PPN_TASK_OS_FILE_FORMAT in parameters:
            output_format = parameters[gc.PPN_TASK_OS_FILE_FORMAT]
            if output_format in self.FINETUNEAS_ALLOWED_FORMATS:
                template = template.replace(
                    self.FINETUNEAS_REPLACE_OUTPUT_FORMAT,
                    u"outputFormat = \"%s\";" % output_format
                )
                if output_format == "smil":
                    for key, placeholder, replacement in [
                            (
                                gc.PPN_TASK_OS_FILE_SMIL_AUDIO_REF,
                                self.FINETUNEAS_REPLACE_SMIL_AUDIOREF,
                                "audioref = \"%s\";"
                            ),
                            (
                                gc.PPN_TASK_OS_FILE_SMIL_PAGE_REF,
                                self.FINETUNEAS_REPLACE_SMIL_PAGEREF,
                                "pageref = \"%s\";"
                            ),
                    ]:
                        if key in parameters:
                            template = template.replace(
                                placeholder,
                                replacement % parameters[key]
                            )
        with io.open(output_file_path, "w", encoding="utf-8") as file_obj:
            file_obj.write(template)
Beispiel #2
0
    def output_html_for_tuning(self,
                               audio_file_path,
                               output_file_path,
                               parameters=None):
        """
        Output an HTML file for fine tuning the sync map manually.

        :param string audio_file_path: the path to the associated audio file
        :param string output_file_path: the path to the output file to write
        :param dict parameters: additional parameters

        .. versionadded:: 1.3.1
        """
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(
                u"Cannot output HTML file '%s'. Wrong permissions?" %
                (output_file_path), None, True, OSError)
        if parameters is None:
            parameters = {}
        audio_file_path_absolute = gf.fix_slash(
            os.path.abspath(audio_file_path))
        template_path_absolute = gf.absolute_path(self.FINETUNEAS_PATH,
                                                  __file__)
        with io.open(template_path_absolute, "r",
                     encoding="utf-8") as file_obj:
            template = file_obj.read()
        for repl in self.FINETUNEAS_REPLACEMENTS:
            template = template.replace(repl[0], repl[1])
        template = template.replace(
            self.FINETUNEAS_REPLACE_AUDIOFILEPATH,
            u"audioFilePath = \"file://%s\";" % audio_file_path_absolute)
        template = template.replace(
            self.FINETUNEAS_REPLACE_FRAGMENTS,
            u"fragments = (%s).fragments;" % self.json_string)
        if gc.PPN_TASK_OS_FILE_FORMAT in parameters:
            output_format = parameters[gc.PPN_TASK_OS_FILE_FORMAT]
            if output_format in self.FINETUNEAS_ALLOWED_FORMATS:
                template = template.replace(
                    self.FINETUNEAS_REPLACE_OUTPUT_FORMAT,
                    u"outputFormat = \"%s\";" % output_format)
                if output_format == "smil":
                    for key, placeholder, replacement in [
                        (gc.PPN_TASK_OS_FILE_SMIL_AUDIO_REF,
                         self.FINETUNEAS_REPLACE_SMIL_AUDIOREF,
                         "audioref = \"%s\";"),
                        (gc.PPN_TASK_OS_FILE_SMIL_PAGE_REF,
                         self.FINETUNEAS_REPLACE_SMIL_PAGEREF,
                         "pageref = \"%s\";"),
                    ]:
                        if key in parameters:
                            template = template.replace(
                                placeholder, replacement % parameters[key])
        with io.open(output_file_path, "w", encoding="utf-8") as file_obj:
            file_obj.write(template)
Beispiel #3
0
    def draw_png(self, output_file_path, h_zoom=5, v_zoom=30):
        """
        Draw the current plot to a PNG file.

        :param string output_path: the path of the output file to be written
        :param int h_zoom: the horizontal zoom
        :param int v_zoom: the vertical zoom
        :raises: ImportError: if module ``PIL`` cannot be imported
        :raises: OSError: if ``output_file_path`` cannot be written
        """
        # check that output_file_path can be written
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Cannot write to output file '%s'" % (output_file_path), None, True, OSError)

        # get widths and cumulative height, in modules
        widths = [ls.width for ls in self.labelsets]
        sum_height = sum([ls.height for ls in self.labelsets])
        if self.waveform is not None:
            widths.append(self.waveform.width)
            sum_height += self.waveform.height
        if self.timescale is not None:
            sum_height += self.timescale.height
        # in modules
        image_width = max(widths)
        image_height = sum_height
        # in pixels
        image_width_px = image_width * h_zoom
        image_height_px = image_height * v_zoom

        # build image object
        self.log([u"Building image with size (modules): %d %d", image_width, image_height])
        self.log([u"Building image with size (px):      %d %d", image_width_px, image_height_px])
        image_obj = Image.new("RGB", (image_width_px, image_height_px), color=PlotterColors.AUDACITY_BACKGROUND_GREY)
        current_y = 0
        if self.waveform is not None:
            self.log(u"Drawing waveform")
            self.waveform.draw_png(image_obj, h_zoom, v_zoom, current_y)
            current_y += self.waveform.height
        timescale_y = current_y
        if self.timescale is not None:
            # NOTE draw as the last thing
            #self.log(u"Drawing timescale")
            #self.timescale.draw_png(image_obj, h_zoom, v_zoom, current_y)
            current_y += self.timescale.height
        for labelset in self.labelsets:
            self.log(u"Drawing labelset")
            labelset.draw_png(image_obj, h_zoom, v_zoom, current_y)
            current_y += labelset.height
        if self.timescale is not None:
            self.log(u"Drawing timescale")
            self.timescale.draw_png(image_obj, h_zoom, v_zoom, timescale_y)
        self.log([u"Saving to file '%s'", output_file_path])
        image_obj.save(output_file_path)
Beispiel #4
0
    def synthesize(self,
                   text_file,
                   audio_file_path,
                   quit_after=None,
                   backwards=False):
        """
        Synthesize the text contained in the given fragment list
        into a ``wav`` file.

        Return a tuple ``(anchors, total_time, num_chars)``.

        :param text_file: the text file to be synthesized
        :type  text_file: :class:`~aeneas.textfile.TextFile`
        :param string audio_file_path: the path to the output audio file
        :param float quit_after: stop synthesizing as soon as
                                 reaching this many seconds
        :param bool backwards: if ``True``, synthesizing from the end of the text file
        :rtype: tuple
        :raises: TypeError: if ``text_file`` is ``None`` or not an instance of ``TextFile``
        :raises: OSError: if ``audio_file_path`` cannot be written
        :raises: OSError: if ``tts=custom`` in the RuntimeConfiguration and ``tts_path`` cannot be read
        :raises: ValueError: if the TTS engine has not been set yet
        """
        if text_file is None:
            self.log_exc(u"text_file is None", None, True, TypeError)
        if not isinstance(text_file, TextFile):
            self.log_exc(u"text_file is not an instance of TextFile", None,
                         True, TypeError)
        if not gf.file_can_be_written(audio_file_path):
            self.log_exc(
                u"Audio file path '%s' cannot be written" % (audio_file_path),
                None, True, OSError)
        if self.tts_engine is None:
            self.log_exc(u"Cannot select the TTS engine", None, True,
                         ValueError)

        # synthesize
        self.log(u"Synthesizing text...")
        result = self.tts_engine.synthesize_multiple(
            text_file=text_file,
            output_file_path=audio_file_path,
            quit_after=quit_after,
            backwards=backwards)
        self.log(u"Synthesizing text... done")

        # check that the output file has been written
        if not gf.file_exists(audio_file_path):
            self.log_exc(
                u"Audio file path '%s' cannot be read" % (audio_file_path),
                None, True, OSError)

        return result
Beispiel #5
0
    def check_output_file(self, path):
        """
        If the given path cannot be written, emit an error
        and return ``False``. Otherwise return ``True``.

        :param path: the path of the output file
        :type  path: string (path)
        :rtype: bool
        """
        if not gf.file_can_be_written(path):
            self.print_error(u"Unable to create file '%s'" % (path))
            self.print_error(u"Make sure the file path is written/escaped correctly and that you have write permission on it")
            return False
        return True
Beispiel #6
0
    def synthesize(
            self,
            text_file,
            audio_file_path,
            quit_after=None,
            backwards=False
    ):
        """
        Synthesize the text contained in the given fragment list
        into a ``wav`` file.

        Return a tuple ``(anchors, total_time, num_chars)``.

        :param text_file: the text file to be synthesized
        :type  text_file: :class:`~aeneas.textfile.TextFile`
        :param string audio_file_path: the path to the output audio file
        :param float quit_after: stop synthesizing as soon as
                                 reaching this many seconds
        :param bool backwards: if ``True``, synthesizing from the end of the text file
        :rtype: tuple
        :raises: TypeError: if ``text_file`` is ``None`` or not an instance of ``TextFile``
        :raises: OSError: if ``audio_file_path`` cannot be written
        :raises: OSError: if ``tts=custom`` in the RuntimeConfiguration and ``tts_path`` cannot be read
        :raises: ValueError: if the TTS engine has not been set yet
        """
        if text_file is None:
            self.log_exc(u"text_file is None", None, True, TypeError)
        if not isinstance(text_file, TextFile):
            self.log_exc(u"text_file is not an instance of TextFile", None, True, TypeError)
        if not gf.file_can_be_written(audio_file_path):
            self.log_exc(u"Audio file path '%s' cannot be written" % (audio_file_path), None, True, OSError)
        if self.tts_engine is None:
            self.log_exc(u"Cannot select the TTS engine", None, True, ValueError)

        # synthesize
        self.log(u"Synthesizing text...")
        result = self.tts_engine.synthesize_multiple(
            text_file=text_file,
            output_file_path=audio_file_path,
            quit_after=quit_after,
            backwards=backwards
        )
        self.log(u"Synthesizing text... done")

        # check that the output file has been written
        if not gf.file_exists(audio_file_path):
            self.log_exc(u"Audio file path '%s' cannot be read" % (audio_file_path), None, True, OSError)

        return result
Beispiel #7
0
    def check_output_directory(self, path):
        """
        If the given directory cannot be written, emit an error
        and return ``False``. Otherwise return ``True``.

        :param path: the path of the output directory
        :type  path: string (path)
        :rtype: bool
        """
        if not os.path.isdir(path):
            self.print_error(u"Directory '%s' does not exist" % (path))
            return False
        test_file = os.path.join(path, u"file.test")
        if not gf.file_can_be_written(test_file):
            self.print_error(u"Unable to write inside directory '%s'" % (path))
            self.print_error(u"Make sure the directory path is written/escaped correctly and that you have write permission on it")
            return False
        return True
Beispiel #8
0
 def _compose_output_file_path(self, extension, output_file_path=None):
     """
     If ``output_file_path`` is given, use it.
     Otherwise (``output_file_path`` is ``None``),
     create a temporary file with the correct extension.
     """
     self.log(u"Determining output file path...")
     if output_file_path is None:
         self.log(u"output_file_path is None: creating temp file")
         handler, output_file_path = gf.tmp_file(
             root=self.rconf[RuntimeConfiguration.TMP_PATH],
             suffix=(".%s" % extension)
         )
         gf.delete_file(handler, output_file_path)
     else:
         self.log(u"output_file_path is not None: cheking that file can be written")
         if not gf.file_can_be_written(output_file_path):
             self.log_exc(u"Path '%s' cannot be written. Wrong permissions?" % (output_file_path), None, True, OSError)
     self.log(u"Determining output file path... done")
     self.log([u"Output file path is '%s'", output_file_path])
     return output_file_path
Beispiel #9
0
 def _compose_output_file_path(self, extension, output_file_path=None):
     """
     If ``output_file_path`` is given, use it.
     Otherwise (``output_file_path`` is ``None``),
     create a temporary file with the correct extension.
     """
     self.log(u"Determining output file path...")
     if output_file_path is None:
         self.log(u"output_file_path is None: creating temp file")
         handler, output_file_path = gf.tmp_file(
             root=self.rconf[RuntimeConfiguration.TMP_PATH],
             suffix=(".%s" % extension))
         gf.delete_file(handler, output_file_path)
     else:
         self.log(
             u"output_file_path is not None: cheking that file can be written"
         )
         if not gf.file_can_be_written(output_file_path):
             self.log_exc(
                 u"Path '%s' cannot be written. Wrong permissions?" %
                 (output_file_path), None, True, OSError)
     self.log(u"Determining output file path... done")
     self.log([u"Output file path is '%s'", output_file_path])
     return output_file_path
 def test_file_can_be_written_false(self):
     path = "/foo/bar/baz"
     self.assertFalse(gf.file_can_be_written(path))
Beispiel #11
0
    def synthesize_multiple(self,
                            text_file,
                            output_file_path,
                            quit_after=None,
                            backwards=False):
        """
        Synthesize the text contained in the given fragment list
        into a WAVE file.

        Return a tuple (anchors, total_time, num_chars).

        Concrete subclasses must implement at least one
        of the following private functions:

            1. ``_synthesize_multiple_python()``
            2. ``_synthesize_multiple_c_extension()``
            3. ``_synthesize_multiple_subprocess()``

        :param text_file: the text file to be synthesized
        :type  text_file: :class:`~aeneas.textfile.TextFile`
        :param string output_file_path: the path to the output audio file
        :param quit_after: stop synthesizing as soon as
                                 reaching this many seconds
        :type quit_after: :class:`~aeneas.exacttiming.TimeValue`
        :param bool backwards: if > 0, synthesize from the end of the text file
        :rtype: tuple (anchors, total_time, num_chars)
        :raises: TypeError: if ``text_file`` is ``None`` or
                            one of the text fragments is not a Unicode string
        :raises: ValueError: if ``self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]`` is ``False``
                             and a fragment has a language code not supported by the TTS engine, or
                             if ``text_file`` has no fragments or all its fragments are empty
        :raises: OSError: if output file cannot be written to ``output_file_path``
        :raises: RuntimeError: if both the C extension and
                               the pure Python code did not succeed.
        """
        if text_file is None:
            self.log_exc(u"text_file is None", None, True, TypeError)
        if len(text_file) < 1:
            self.log_exc(u"The text file has no fragments", None, True,
                         ValueError)
        if text_file.chars == 0:
            self.log_exc(u"All fragments in the text file are empty", None,
                         True, ValueError)
        if not self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]:
            for fragment in text_file.fragments:
                if fragment.language not in self.LANGUAGE_TO_VOICE_CODE:
                    self.log_exc(
                        u"Language '%s' is not supported by the selected TTS engine"
                        % (fragment.language), None, True, ValueError)
        for fragment in text_file.fragments:
            for line in fragment.lines:
                if not gf.is_unicode(line):
                    self.log_exc(
                        u"The text file contain a line which is not a Unicode string",
                        None, True, TypeError)

        # log parameters
        if quit_after is not None:
            self.log([u"Quit after reaching %.3f", quit_after])
        if backwards:
            self.log(u"Synthesizing backwards")

        # check that output_file_path can be written
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(
                u"Cannot write to output file '%s'" % (output_file_path), None,
                True, OSError)

        # first, call Python function _synthesize_multiple_python() if available
        if self.HAS_PYTHON_CALL:
            self.log(u"Calling TTS engine via Python")
            try:
                computed, result = self._synthesize_multiple_python(
                    text_file, output_file_path, quit_after, backwards)
                if computed:
                    self.log(
                        u"The _synthesize_multiple_python call was successful, returning anchors"
                    )
                    return result
                else:
                    self.log(u"The _synthesize_multiple_python call failed")
            except Exception as exc:
                self.log_exc(
                    u"An unexpected error occurred while calling _synthesize_multiple_python",
                    exc, False, None)

        # call _synthesize_multiple_c_extension() or _synthesize_multiple_subprocess()
        self.log(u"Calling TTS engine via C extension or subprocess")
        c_extension_function = self._synthesize_multiple_c_extension if self.HAS_C_EXTENSION_CALL else None
        subprocess_function = self._synthesize_multiple_subprocess if self.HAS_SUBPROCESS_CALL else None
        return gf.run_c_extension_with_fallback(
            self.log,
            self.C_EXTENSION_NAME,
            c_extension_function,
            subprocess_function,
            (text_file, output_file_path, quit_after, backwards),
            rconf=self.rconf)
Beispiel #12
0
    def synthesize_single(self, text, language, output_file_path):
        """
        Create a mono WAVE audio file containing the synthesized text.

        The ``text`` must be a Unicode string encodable with UTF-8.

        Return the duration of the synthesized audio file, in seconds.

        Concrete subclasses can (but they are not required to) implement one
        of the following private functions:

            1. ``_synthesize_single_python()``
            2. ``_synthesize_single_c_extension()``
            3. ``_synthesize_single_subprocess()``

        :param string text: the text to synthesize
        :param language: the language to use
        :type  language: :class:`~aeneas.language.Language`
        :param string output_file_path: the path of the output audio file
        :rtype: :class:`~aeneas.timevalue.TimeValue`
        :raises: TypeError: if ``text`` is ``None`` or it is not a Unicode string
        :raises: ValueError: if ``self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]`` is ``False``
                             and ``language`` is not supported by the TTS engine
        :raises: OSError: if output file cannot be written to ``output_file_path``
        :raises: RuntimeError: if both the C extension and
                               the pure Python code did not succeed.
        """
        # check that text_file is not None
        if text is None:
            self.log_exc(u"text is None", None, True, TypeError)

        # check that text has unicode type
        if not gf.is_unicode(text):
            self.log_exc(u"text is not a Unicode string", None, True, TypeError)

        # check that output_file_path can be written
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Cannot write to output file '%s'" % (output_file_path), None, True, OSError)

        # check that the requested language is listed in language.py
        if (language not in self.LANGUAGE_TO_VOICE_CODE) and (not self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]):
            self.log_exc(u"Language '%s' is not supported by the selected TTS engine" % (language), None, True, ValueError)

        self.log([u"Synthesizing text: '%s'", text])
        self.log([u"Synthesizing language: '%s'", language])
        self.log([u"Synthesizing to file: '%s'", output_file_path])

        # return zero if text is the empty string
        if len(text) == 0:
            self.log(u"len(text) is zero: returning 0.000")
            return TimeValue("0.000")

        # language to voice code
        voice_code = self._language_to_voice_code(language)
        self.log([u"Using voice code: '%s'", voice_code])

        # first, call Python function _synthesize_single_python() if available
        if self.has_python_call:
            self.log(u"Calling TTS engine via Python")
            try:
                result = self._synthesize_single_python(text, voice_code, output_file_path)
                return result[0]
            except Exception as exc:
                self.log_exc(u"An unexpected error occurred while calling _synthesize_single_python", exc, False, None)

        # call _synthesize_single_c_extension() or _synthesize_single_subprocess()
        self.log(u"Calling TTS engine via C extension or subprocess")
        c_extension_function = self._synthesize_single_c_extension if self.has_c_extension_call else None
        subprocess_function = self._synthesize_single_subprocess if self.has_subprocess_call else None
        result = gf.run_c_extension_with_fallback(
            self.log,
            "cew",
            c_extension_function,
            subprocess_function,
            (text, voice_code, output_file_path),
            rconf=self.rconf
        )
        return result[0]
Beispiel #13
0
    def synthesize_multiple(self, text_file, output_file_path, quit_after=None, backwards=False):
        """
        Synthesize the text contained in the given fragment list
        into a WAVE file.

        Return a tuple (anchors, total_time, num_chars).

        Concrete subclasses must implement at least one
        of the following private functions:

            1. ``_synthesize_multiple_python()``
            2. ``_synthesize_multiple_c_extension()``
            3. ``_synthesize_multiple_subprocess()``

        :param text_file: the text file to be synthesized
        :type  text_file: :class:`~aeneas.textfile.TextFile`
        :param string output_file_path: the path to the output audio file
        :param quit_after: stop synthesizing as soon as
                                 reaching this many seconds
        :type quit_after: :class:`~aeneas.timevalue.TimeValue`
        :param bool backwards: if > 0, synthese from the end of the text file
        :rtype: tuple (anchors, total_time, num_chars)
        :raises: TypeError: if ``text_file`` is ``None`` or
                            one of the text fragments is not a Unicode string
        :raises: ValueError: if ``self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]`` is ``False``
                             and a fragment has a language code not supported by the TTS engine, or
                             if ``text_file`` has no fragments
        :raises: OSError: if output file cannot be written to ``output_file_path``
        :raises: RuntimeError: if both the C extension and
                               the pure Python code did not succeed.
        """
        if text_file is None:
            self.log_exc(u"text_file is None", None, True, TypeError)
        if len(text_file) < 1:
            self.log_exc(u"The text file has no fragments", None, True, ValueError)
        if not self.rconf[RuntimeConfiguration.ALLOW_UNLISTED_LANGUAGES]:
            for fragment in text_file.fragments:
                if fragment.language not in self.LANGUAGE_TO_VOICE_CODE:
                    self.log_exc(u"Language '%s' is not supported by the selected TTS engine" % (fragment.language), None, True, ValueError)
        for fragment in text_file.fragments:
            for line in fragment.lines:
                if not gf.is_unicode(line):
                    self.log_exc(u"The text file contain a line which is not a Unicode string", None, True, TypeError)

        # log parameters
        if quit_after is not None:
            self.log([u"Quit after reaching %.3f", quit_after])
        if backwards:
            self.log(u"Synthesizing backwards")

        # check that output_file_path can be written
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Cannot write to output file '%s'" % (output_file_path), None, True, OSError)

        # first, call Python function _synthesize_multiple_python() if available
        if self.has_python_call:
            self.log(u"Calling TTS engine via Python")
            try:
                computed, result = self._synthesize_multiple_python(text_file, output_file_path, quit_after, backwards)
                if computed:
                    self.log(u"The _synthesize_multiple_python call was successful, returning anchors")
                    return result
                else:
                    self.log(u"The _synthesize_multiple_python call failed")
            except Exception as exc:
                self.log_exc(u"An unexpected error occurred while calling _synthesize_multiple_python", exc, False, None)

        # call _synthesize_multiple_c_extension() or _synthesize_multiple_subprocess()
        self.log(u"Calling TTS engine via C extension or subprocess")
        c_extension_function = self._synthesize_multiple_c_extension if self.has_c_extension_call else None
        subprocess_function = self._synthesize_multiple_subprocess if self.has_subprocess_call else None
        return gf.run_c_extension_with_fallback(
            self.log,
            "cew",
            c_extension_function,
            subprocess_function,
            (text_file, output_file_path, quit_after, backwards),
            rconf=self.rconf
        )
Beispiel #14
0
 def test_file_can_be_written_false(self):
     path = "/foo/bar/baz"
     self.assertFalse(gf.file_can_be_written(path))
 def test_file_can_be_written_true(self):
     handler, path = gf.tmp_file()
     self.assertTrue(gf.file_can_be_written(path))
     gf.delete_file(handler, path)
Beispiel #16
0
    def audio_from_youtube(self,
                           source_url,
                           download=True,
                           output_file_path=None,
                           preferred_index=None,
                           largest_audio=True,
                           preferred_format=None):
        """
        Download an audio stream from a YouTube video,
        and save it to file.

        If ``download`` is ``False``, return the list
        of available audiostreams but do not download.

        Otherwise, download the audio stream best matching
        the provided parameters, as follows.
        If ``preferred_index`` is not ``None``,
        download the audio stream at that index.
        If ``largest_audio`` is ``True``,
        download the largest audiostream;
        otherwise, download the smallest audiostream.
        If ``preferred_format`` is not ``None``,
        download the audiostream having that format.
        The latter option works in combination with ``largest_audio``.

        Return the path of the downloaded file.

        :param string source_url: the URL of the YouTube video
        :param bool download: if ``True``, download the audio stream
                              best matching ``preferred_index`` or ``preferred_format``
                              and ``largest_audio``;
                              if ``False``, return the list of available audio streams
        :param string output_file_path: the path where the downloaded audio should be saved;
                                        if ``None``, create a temporary file
        :param int preferred_index: preferably download this audio stream
        :param bool largest_audio: if ``True``, download the largest audio stream available;
                                   if ``False``, download the smallest one.
        :param string preferred_format: preferably download this audio format
        :rtype: string or list of pafy audio streams
        :raises: ImportError: if ``pafy`` is not installed
        :raises: OSError: if ``output_file_path`` cannot be written
        :raises: ValueError: if ``source_url`` is not a valid YouTube URL
        """
        def select_audiostream(audiostreams):
            """ Select the audiostream best matching the given parameters. """
            if preferred_index is not None:
                if preferred_index in range(len(audiostreams)):
                    self.log([
                        u"Selecting audiostream with index %d", preferred_index
                    ])
                    return audiostreams[preferred_index]
                else:
                    self.log_warn([
                        u"Audio stream index '%d' not allowed", preferred_index
                    ])
                    self.log_warn(u"Ignoring the requested audio stream index")
            # selecting by preferred format
            streams = audiostreams
            if preferred_format is not None:
                self.log([
                    u"Selecting audiostreams by preferred format %s",
                    preferred_format
                ])
                streams = [
                    audiostream for audiostream in streams
                    if audiostream.extension == preferred_format
                ]
                if len(streams) < 1:
                    self.log([
                        u"No audiostream with preferred format %s",
                        preferred_format
                    ])
                    streams = audiostreams
            # sort by size
            streams = sorted([(audio.get_filesize(), audio)
                              for audio in streams])
            if largest_audio:
                self.log(u"Selecting largest audiostream")
                selected = streams[-1][1]
            else:
                self.log(u"Selecting smallest audiostream")
                selected = streams[0][1]
            return selected

        try:
            import pafy
        except ImportError as exc:
            self.log_exc(u"Python module pafy is not installed", exc, True,
                         ImportError)

        try:
            video = pafy.new(source_url)
        except (IOError, OSError, ValueError) as exc:
            self.log_exc(
                u"The specified source URL '%s' is not a valid YouTube URL or you are offline"
                % (source_url), exc, True, ValueError)

        if not download:
            self.log(u"Returning the list of audio streams")
            return video.audiostreams

        output_path = output_file_path
        if output_file_path is None:
            self.log(u"output_path is None: creating temp file")
            handler, output_path = gf.tmp_file(
                root=self.rconf[RuntimeConfiguration.TMP_PATH])
        else:
            if not gf.file_can_be_written(output_path):
                self.log_exc(
                    u"Path '%s' cannot be written. Wrong permissions?" %
                    (output_path), None, True, OSError)

        audiostream = select_audiostream(video.audiostreams)
        if output_file_path is None:
            gf.delete_file(handler, output_path)
            output_path += "." + audiostream.extension

        self.log([u"output_path is '%s'", output_path])
        self.log(u"Downloading...")
        audiostream.download(filepath=output_path, quiet=True)
        self.log(u"Downloading... done")
        return output_path
Beispiel #17
0
    def write(self, sync_map_format, output_file_path, parameters=None):
        """
        Write the current sync map to file in the requested format.

        Return ``True`` if the call succeeded,
        ``False`` if an error occurred.

        :param sync_map_format: the format of the sync map
        :type  sync_map_format: :class:`~aeneas.syncmap.SyncMapFormat`
        :param string output_file_path: the path to the output file to write
        :param dict parameters: additional parameters (e.g., for ``SMIL`` output)
        :raises: ValueError: if ``sync_map_format`` is ``None`` or it is not an allowed value
        :raises: TypeError: if a required parameter is missing
        :raises: OSError: if ``output_file_path`` cannot be written
        """
        def select_levels(syncmap, levels):
            """
            Select the given levels of the fragments tree,
            modifying the given syncmap (always pass a copy of it!).
            """
            self.log([u"Levels: '%s'", levels])
            if levels is None:
                return
            try:
                levels = [int(l) for l in levels if int(l) > 0]
                syncmap.fragments_tree.keep_levels(levels)
                self.log([u"Selected levels: %s", levels])
            except ValueError:
                self.log_warn(u"Cannot convert levels to list of int, returning unchanged")

        def set_head_tail_format(syncmap, head_tail_format=None):
            """
            Set the appropriate head/tail nodes of the fragments tree,
            modifying the given syncmap (always pass a copy of it!).
            """
            self.log([u"Head/tail format: '%s'", str(head_tail_format)])
            tree = syncmap.fragments_tree
            head = tree.get_child(0)
            first = tree.get_child(1)
            last = tree.get_child(-2)
            tail = tree.get_child(-1)
            # mark HEAD as REGULAR if needed
            if head_tail_format == SyncMapHeadTailFormat.ADD:
                head.value.fragment_type = SyncMapFragment.REGULAR
                self.log(u"Marked HEAD as REGULAR")
            # stretch first and last fragment timings if needed
            if head_tail_format == SyncMapHeadTailFormat.STRETCH:
                self.log([u"Stretched first.begin: %.3f => %.3f (head)", first.value.begin, head.value.begin])
                self.log([u"Stretched last.end:    %.3f => %.3f (tail)", last.value.end, tail.value.end])
                first.value.begin = head.value.begin
                last.value.end = tail.value.end
            # mark TAIL as REGULAR if needed
            if head_tail_format == SyncMapHeadTailFormat.ADD:
                tail.value.fragment_type = SyncMapFragment.REGULAR
                self.log(u"Marked TAIL as REGULAR")
            # remove all fragments that are not REGULAR
            for node in list(tree.dfs):
                if (node.value is not None) and (node.value.fragment_type != SyncMapFragment.REGULAR):
                    node.remove()

        if sync_map_format is None:
            self.log_exc(u"Sync map format is None", None, True, ValueError)
        if sync_map_format not in SyncMapFormat.CODE_TO_CLASS:
            self.log_exc(u"Sync map format '%s' is not allowed" % (sync_map_format), None, True, ValueError)
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Cannot write sync map file '%s'. Wrong permissions?" % (output_file_path), None, True, OSError)

        self.log([u"Output format:     '%s'", sync_map_format])
        self.log([u"Output path:       '%s'", output_file_path])
        self.log([u"Output parameters: '%s'", parameters])

        # select levels and head/tail format
        pruned_syncmap = self.clone()
        try:
            select_levels(pruned_syncmap, parameters[gc.PPN_TASK_OS_FILE_LEVELS])
        except:
            self.log_warn([u"No %s parameter specified", gc.PPN_TASK_OS_FILE_LEVELS])
        try:
            set_head_tail_format(pruned_syncmap, parameters[gc.PPN_TASK_OS_FILE_HEAD_TAIL_FORMAT])
        except:
            self.log_warn([u"No %s parameter specified", gc.PPN_TASK_OS_FILE_HEAD_TAIL_FORMAT])

        # create writer
        # the constructor will check for required parameters, if any
        # if some are missing, it will raise a SyncMapMissingParameterError
        writer = (SyncMapFormat.CODE_TO_CLASS[sync_map_format])(
            variant=sync_map_format,
            parameters=parameters,
            rconf=self.rconf,
            logger=self.logger
        )

        # create dir hierarchy, if needed
        gf.ensure_parent_directory(output_file_path)

        # open file for writing
        self.log(u"Writing output file...")
        with io.open(output_file_path, "w", encoding="utf-8") as output_file:
            output_file.write(writer.format(syncmap=pruned_syncmap))
        self.log(u"Writing output file... done")
Beispiel #18
0
    def write(self, sync_map_format, output_file_path, parameters=None):
        """
        Write the current sync map to file in the requested format.

        Return ``True`` if the call succeeded,
        ``False`` if an error occurred.

        :param sync_map_format: the format of the sync map
        :type  sync_map_format: :class:`~aeneas.syncmap.SyncMapFormat`
        :param string output_file_path: the path to the output file to write
        :param dict parameters: additional parameters (e.g., for ``SMIL`` output)
        :raises: ValueError: if ``sync_map_format`` is ``None`` or it is not an allowed value
        :raises: TypeError: if a required parameter is missing
        :raises: OSError: if ``output_file_path`` cannot be written
        """
        def select_levels(syncmap, levels):
            """
            Select the given levels of the fragments tree,
            modifying the given syncmap (always pass a copy of it!).
            """
            self.log([u"Levels: '%s'", levels])
            if levels is None:
                return
            try:
                levels = [int(l) for l in levels if int(l) > 0]
                syncmap.fragments_tree.keep_levels(levels)
                self.log([u"Selected levels: %s", levels])
            except ValueError:
                self.log_warn(
                    u"Cannot convert levels to list of int, returning unchanged"
                )

        def set_head_tail_format(syncmap, head_tail_format=None):
            """
            Set the appropriate head/tail nodes of the fragments tree,
            modifying the given syncmap (always pass a copy of it!).
            """
            self.log([u"Head/tail format: '%s'", str(head_tail_format)])
            tree = syncmap.fragments_tree
            head = tree.get_child(0)
            first = tree.get_child(1)
            last = tree.get_child(-2)
            tail = tree.get_child(-1)
            # mark HEAD as REGULAR if needed
            if head_tail_format == SyncMapHeadTailFormat.ADD:
                head.value.fragment_type = SyncMapFragment.REGULAR
                self.log(u"Marked HEAD as REGULAR")
            # stretch first and last fragment timings if needed
            if head_tail_format == SyncMapHeadTailFormat.STRETCH:
                self.log([
                    u"Stretched first.begin: %.3f => %.3f (head)",
                    first.value.begin, head.value.begin
                ])
                self.log([
                    u"Stretched last.end:    %.3f => %.3f (tail)",
                    last.value.end, tail.value.end
                ])
                first.value.begin = head.value.begin
                last.value.end = tail.value.end
            # mark TAIL as REGULAR if needed
            if head_tail_format == SyncMapHeadTailFormat.ADD:
                tail.value.fragment_type = SyncMapFragment.REGULAR
                self.log(u"Marked TAIL as REGULAR")
            # remove all fragments that are not REGULAR
            for node in list(tree.dfs):
                if (node.value is not None) and (node.value.fragment_type !=
                                                 SyncMapFragment.REGULAR):
                    node.remove()

        if sync_map_format is None:
            self.log_exc(u"Sync map format is None", None, True, ValueError)
        if sync_map_format not in SyncMapFormat.CODE_TO_CLASS:
            self.log_exc(
                u"Sync map format '%s' is not allowed" % (sync_map_format),
                None, True, ValueError)
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(
                u"Cannot write sync map file '%s'. Wrong permissions?" %
                (output_file_path), None, True, OSError)

        self.log([u"Output format:     '%s'", sync_map_format])
        self.log([u"Output path:       '%s'", output_file_path])
        self.log([u"Output parameters: '%s'", parameters])

        # select levels and head/tail format
        pruned_syncmap = self.clone()
        try:
            select_levels(pruned_syncmap,
                          parameters[gc.PPN_TASK_OS_FILE_LEVELS])
        except:
            self.log_warn(
                [u"No %s parameter specified", gc.PPN_TASK_OS_FILE_LEVELS])
        try:
            set_head_tail_format(
                pruned_syncmap,
                parameters[gc.PPN_TASK_OS_FILE_HEAD_TAIL_FORMAT])
        except:
            self.log_warn([
                u"No %s parameter specified",
                gc.PPN_TASK_OS_FILE_HEAD_TAIL_FORMAT
            ])

        # create writer
        # the constructor will check for required parameters, if any
        # if some are missing, it will raise a SyncMapMissingParameterError
        writer = (SyncMapFormat.CODE_TO_CLASS[sync_map_format])(
            variant=sync_map_format,
            parameters=parameters,
            rconf=self.rconf,
            logger=self.logger)

        # create dir hierarchy, if needed
        gf.ensure_parent_directory(output_file_path)

        # open file for writing
        self.log(u"Writing output file...")
        with io.open(output_file_path, "w", encoding="utf-8") as output_file:
            output_file.write(writer.format(syncmap=pruned_syncmap))
        self.log(u"Writing output file... done")
Beispiel #19
0
    def convert(self,
                input_file_path,
                output_file_path,
                head_length=None,
                process_length=None):
        """
        Convert the audio file at ``input_file_path``
        into ``output_file_path``,
        using the parameters set in the constructor
        or through the ``parameters`` property.

        You can skip the beginning of the audio file
        by specifying ``head_length`` seconds to skip
        (if it is ``None``, start at time zero),
        and you can specify to convert
        only ``process_length`` seconds
        (if it is ``None``, process the entire input file length).

        By specifying both ``head_length`` and ``process_length``,
        you can skip a portion at the beginning and at the end
        of the original input file.

        :param string input_file_path: the path of the audio file to convert
        :param string output_file_path: the path of the converted audio file
        :param float head_length: skip these many seconds
                                  from the beginning of the audio file
        :param float process_length: process these many seconds of the audio file
        :raises: :class:`~aeneas.ffmpegwrapper.FFMPEGPathError`: if the path to the ``ffmpeg`` executable cannot be called
        :raises: OSError: if ``input_file_path`` does not exist
                          or ``output_file_path`` cannot be written
        """
        # test if we can read the input file
        if not gf.file_can_be_read(input_file_path):
            self.log_exc(u"Input file '%s' cannot be read" % (input_file_path),
                         None, True, OSError)

        # test if we can write the output file
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(
                u"Output file '%s' cannot be written" % (output_file_path),
                None, True, OSError)

        # call ffmpeg
        arguments = [self.rconf[RuntimeConfiguration.FFMPEG_PATH]]
        arguments.extend(["-i", input_file_path])
        if head_length is not None:
            arguments.extend(["-ss", head_length])
        if process_length is not None:
            arguments.extend(["-t", process_length])
        if self.rconf[RuntimeConfiguration.
                      FFMPEG_SAMPLE_RATE] in self.FFMPEG_PARAMETERS_MAP:
            arguments.extend(self.FFMPEG_PARAMETERS_MAP[self.rconf[
                RuntimeConfiguration.FFMPEG_SAMPLE_RATE]])
        else:
            arguments.extend(self.FFMPEG_PARAMETERS_DEFAULT)
        arguments.append(output_file_path)
        self.log([u"Calling with arguments '%s'", arguments])
        try:
            proc = subprocess.Popen(arguments,
                                    stdout=subprocess.PIPE,
                                    stdin=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
            proc.communicate()
            proc.stdout.close()
            proc.stdin.close()
            proc.stderr.close()
        except OSError as exc:
            self.log_exc(
                u"Unable to call the '%s' ffmpeg executable" %
                (self.rconf[RuntimeConfiguration.FFMPEG_PATH]), exc, True,
                FFMPEGPathError)
        self.log(u"Call completed")

        # check if the output file exists
        if not gf.file_exists(output_file_path):
            self.log_exc(
                u"Output file '%s' was not written" % (output_file_path), None,
                True, OSError)

        # returning the output file path
        self.log([u"Returning output file path '%s'", output_file_path])
        return output_file_path
Beispiel #20
0
    def audio_from_youtube(
            self,
            source_url,
            download=True,
            output_file_path=None,
            preferred_index=None,
            largest_audio=True,
            preferred_format=None
    ):
        """
        Download an audio stream from a YouTube video,
        and save it to file.

        If ``download`` is ``False``, return the list
        of available audiostreams but do not download.

        Otherwise, download the audio stream best matching
        the provided parameters, as follows.
        If ``preferred_index`` is not ``None``,
        download the audio stream at that index.
        If ``largest_audio`` is ``True``,
        download the largest audiostream;
        otherwise, download the smallest audiostream.
        If ``preferred_format`` is not ``None``,
        download the audiostream having that format.
        The latter option works in combination with ``largest_audio``.

        Return the path of the downloaded file.

        :param string source_url: the URL of the YouTube video
        :param bool download: if ``True``, download the audio stream
                              best matching ``preferred_index`` or ``preferred_format``
                              and ``largest_audio``;
                              if ``False``, return the list of available audio streams
        :param string output_file_path: the path where the downloaded audio should be saved;
                                        if ``None``, create a temporary file
        :param int preferred_index: preferably download this audio stream
        :param bool largest_audio: if ``True``, download the largest audio stream available;
                                   if ``False``, download the smallest one.
        :param string preferred_format: preferably download this audio format
        :rtype: string or list of pafy audio streams
        :raises: ImportError: if ``pafy`` is not installed
        :raises: OSError: if ``output_file_path`` cannot be written
        :raises: ValueError: if ``source_url`` is not a valid YouTube URL
        """
        def select_audiostream(audiostreams):
            """ Select the audiostream best matching the given parameters. """
            if preferred_index is not None:
                if preferred_index in range(len(audiostreams)):
                    self.log([u"Selecting audiostream with index %d", preferred_index])
                    return audiostreams[preferred_index]
                else:
                    self.log_warn([u"Audio stream index '%d' not allowed", preferred_index])
                    self.log_warn(u"Ignoring the requested audio stream index")
            # selecting by preferred format
            streams = audiostreams
            if preferred_format is not None:
                self.log([u"Selecting audiostreams by preferred format %s", preferred_format])
                streams = [audiostream for audiostream in streams if audiostream.extension == preferred_format]
                if len(streams) < 1:
                    self.log([u"No audiostream with preferred format %s", preferred_format])
                    streams = audiostreams
            # sort by size
            streams = sorted([(audio.get_filesize(), audio) for audio in streams])
            if largest_audio:
                self.log(u"Selecting largest audiostream")
                selected = streams[-1][1]
            else:
                self.log(u"Selecting smallest audiostream")
                selected = streams[0][1]
            return selected

        try:
            import pafy
        except ImportError as exc:
            self.log_exc(u"Python module pafy is not installed", exc, True, ImportError)

        try:
            video = pafy.new(source_url)
        except (IOError, OSError, ValueError) as exc:
            self.log_exc(u"The specified source URL '%s' is not a valid YouTube URL or you are offline" % (source_url), exc, True, ValueError)

        if not download:
            self.log(u"Returning the list of audio streams")
            return video.audiostreams

        output_path = output_file_path
        if output_file_path is None:
            self.log(u"output_path is None: creating temp file")
            handler, output_path = gf.tmp_file(root=self.rconf[RuntimeConfiguration.TMP_PATH])
        else:
            if not gf.file_can_be_written(output_path):
                self.log_exc(u"Path '%s' cannot be written. Wrong permissions?" % (output_path), None, True, OSError)

        audiostream = select_audiostream(video.audiostreams)
        if output_file_path is None:
            gf.delete_file(handler, output_path)
            output_path += "." + audiostream.extension

        self.log([u"output_path is '%s'", output_path])
        self.log(u"Downloading...")
        audiostream.download(filepath=output_path, quiet=True)
        self.log(u"Downloading... done")
        return output_path
Beispiel #21
0
    def convert(
            self,
            input_file_path,
            output_file_path,
            head_length=None,
            process_length=None
    ):
        """
        Convert the audio file at ``input_file_path``
        into ``output_file_path``,
        using the parameters set in the constructor
        or through the ``parameters`` property.

        You can skip the beginning of the audio file
        by specifying ``head_length`` seconds to skip
        (if it is ``None``, start at time zero),
        and you can specify to convert
        only ``process_length`` seconds
        (if it is ``None``, process the entire input file length).

        By specifying both ``head_length`` and ``process_length``,
        you can skip a portion at the beginning and at the end
        of the original input file.

        :param string input_file_path: the path of the audio file to convert
        :param string output_file_path: the path of the converted audio file
        :param float head_length: skip these many seconds
                                  from the beginning of the audio file
        :param float process_length: process these many seconds of the audio file
        :raises: :class:`~aeneas.ffmpegwrapper.FFMPEGPathError`: if the path to the ``ffmpeg`` executable cannot be called
        :raises: OSError: if ``input_file_path`` does not exist
                          or ``output_file_path`` cannot be written
        """
        # test if we can read the input file
        if not gf.file_can_be_read(input_file_path):
            self.log_exc(u"Input file '%s' cannot be read" % (input_file_path), None, True, OSError)

        # test if we can write the output file
        if not gf.file_can_be_written(output_file_path):
            self.log_exc(u"Output file '%s' cannot be written" % (output_file_path), None, True, OSError)

        # call ffmpeg
        arguments = [self.rconf[RuntimeConfiguration.FFMPEG_PATH]]
        arguments.extend(["-i", input_file_path])
        if head_length is not None:
            arguments.extend(["-ss", head_length])
        if process_length is not None:
            arguments.extend(["-t", process_length])
        if self.rconf.sample_rate in self.FFMPEG_PARAMETERS_MAP:
            arguments.extend(self.FFMPEG_PARAMETERS_MAP[self.rconf.sample_rate])
        else:
            arguments.extend(self.FFMPEG_PARAMETERS_DEFAULT)
        arguments.append(output_file_path)
        self.log([u"Calling with arguments '%s'", arguments])
        try:
            proc = subprocess.Popen(
                arguments,
                stdout=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            proc.communicate()
            proc.stdout.close()
            proc.stdin.close()
            proc.stderr.close()
        except OSError as exc:
            self.log_exc(u"Unable to call the '%s' ffmpeg executable" % (self.rconf[RuntimeConfiguration.FFMPEG_PATH]), exc, True, FFMPEGPathError)
        self.log(u"Call completed")

        # check if the output file exists
        if not gf.file_exists(output_file_path):
            self.log_exc(u"Output file '%s' was not written" % (output_file_path), None, True, OSError)

        # returning the output file path
        self.log([u"Returning output file path '%s'", output_file_path])
        return output_file_path
Beispiel #22
0
 def test_file_can_be_written_true(self):
     handler, path = gf.tmp_file()
     self.assertTrue(gf.file_can_be_written(path))
     gf.delete_file(handler, path)