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)
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)
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)
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
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
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
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
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 _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))
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)
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]
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 )
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)
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
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")
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")
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
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
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