def _create_syncmap(self, text_map): """ Create a sync map out of the provided interval map, and store it in the task object. Return a success bool flag. """ self._log("Creating SyncMap") self._log("Number of fragments: %d" % len(text_map)) if len(text_map) != len(self.task.text_file.fragments): return False try: sync_map = SyncMap() i = 0 head = 0 if self.task.configuration.is_audio_file_head_length != None: head = gf.safe_float(self.task.configuration.is_audio_file_head_length, 0) for fragment in self.task.text_file.fragments: start = head + text_map[i][0] end = head + text_map[i][1] sync_map_frag = SyncMapFragment(fragment, start, end) sync_map.append(sync_map_frag) i += 1 self.task.sync_map = sync_map return True except: return False
def _compute_head_process_tail(self, audio_file_mfcc): """ Set the audio file head or tail, by either reading the explicit values from the Task configuration, or using SD to determine them. This function returns the lengths, in seconds, of the (head, process, tail). :rtype: tuple (float, float, float) """ head_length = self.task.configuration["i_a_head"] process_length = self.task.configuration["i_a_process"] tail_length = self.task.configuration["i_a_tail"] head_max = self.task.configuration["i_a_head_max"] head_min = self.task.configuration["i_a_head_min"] tail_max = self.task.configuration["i_a_tail_max"] tail_min = self.task.configuration["i_a_tail_min"] if ( (head_length is not None) or (process_length is not None) or (tail_length is not None) ): self.log(u"Setting explicit head process tail") else: self.log(u"Detecting head tail...") sd = SD(audio_file_mfcc, self.task.text_file, rconf=self.rconf, logger=self.logger) head_length = TimeValue("0.000") process_length = None tail_length = TimeValue("0.000") if (head_min is not None) or (head_max is not None): self.log(u"Detecting HEAD...") head_length = sd.detect_head(head_min, head_max) self.log([u"Detected HEAD: %.3f", head_length]) self.log(u"Detecting HEAD... done") if (tail_min is not None) or (tail_max is not None): self.log(u"Detecting TAIL...") tail_length = sd.detect_tail(tail_min, tail_max) self.log([u"Detected TAIL: %.3f", tail_length]) self.log(u"Detecting TAIL... done") self.log(u"Detecting head tail... done") self.log([u"Head: %s", gf.safe_float(head_length, None)]) self.log([u"Process: %s", gf.safe_float(process_length, None)]) self.log([u"Tail: %s", gf.safe_float(tail_length, None)]) return (head_length, process_length, tail_length)
def _compute_head_process_tail(self, audio_file_mfcc): """ Set the audio file head or tail, by either reading the explicit values from the Task configuration, or using SD to determine them. This function returns the lengths, in seconds, of the (head, process, tail). :rtype: tuple (float, float, float) """ head_length = self.task.configuration["i_a_head"] process_length = self.task.configuration["i_a_process"] tail_length = self.task.configuration["i_a_tail"] head_max = self.task.configuration["i_a_head_max"] head_min = self.task.configuration["i_a_head_min"] tail_max = self.task.configuration["i_a_tail_max"] tail_min = self.task.configuration["i_a_tail_min"] if ((head_length is not None) or (process_length is not None) or (tail_length is not None)): self.log(u"Setting explicit head process tail") else: self.log(u"Detecting head tail...") sd = SD(audio_file_mfcc, self.task.text_file, rconf=self.rconf, logger=self.logger) head_length = TimeValue("0.000") process_length = None tail_length = TimeValue("0.000") if (head_min is not None) or (head_max is not None): self.log(u"Detecting HEAD...") head_length = sd.detect_head(head_min, head_max) self.log([u"Detected HEAD: %.3f", head_length]) self.log(u"Detecting HEAD... done") if (tail_min is not None) or (tail_max is not None): self.log(u"Detecting TAIL...") tail_length = sd.detect_tail(tail_min, tail_max) self.log([u"Detected TAIL: %.3f", tail_length]) self.log(u"Detecting TAIL... done") self.log(u"Detecting head tail... done") self.log([u"Head: %s", gf.safe_float(head_length, None)]) self.log([u"Process: %s", gf.safe_float(process_length, None)]) self.log([u"Tail: %s", gf.safe_float(tail_length, None)]) return (head_length, process_length, tail_length)
def __str__(self): accumulator = "" accumulator += "File path: %s\n" % self.file_path accumulator += "File size (bytes): %s\n" % gf.safe_int(self.file_size) accumulator += "Audio length (s): %s\n" % gf.safe_float(self.audio_length) accumulator += "Audio format: %s\n" % self.audio_format accumulator += "Audio sample rate: %s\n" % gf.safe_int(self.audio_sample_rate) accumulator += "Audio channels: %s" % gf.safe_int(self.audio_channels) return accumulator
def __unicode__(self): msg = [ u"File path: %s" % self.file_path, u"File size (bytes): %s" % gf.safe_int(self.file_size), u"Audio length (s): %s" % gf.safe_float(self.audio_length), u"Audio format: %s" % self.audio_format, u"Audio sample rate: %s" % gf.safe_int(self.audio_sample_rate), u"Audio channels: %s" % gf.safe_int(self.audio_channels), u"Samples capacity: %s" % gf.safe_int(self.__samples_capacity), u"Samples length: %s" % gf.safe_int(self.__samples_length), ] return u"\n".join(msg)
def __str__(self): accumulator = "" accumulator += "File path: %s\n" % self.file_path accumulator += "File size (bytes): %s\n" % gf.safe_int(self.file_size) accumulator += "Audio length (s): %s\n" % gf.safe_float( self.audio_length) accumulator += "Audio format: %s\n" % self.audio_format accumulator += "Audio sample rate: %s\n" % gf.safe_int( self.audio_sample_rate) accumulator += "Audio channels: %s" % gf.safe_int( self.audio_channels) return accumulator
def test_safe_float(self): tests = [ ["3.14", 1.23, 3.14], [" 3.14", 1.23, 3.14], [" 3.14 ", 1.23, 3.14], ["3.14f", 1.23, 1.23], ["0x3.14", 1.23, 1.23], ["", 1.23, 1.23], ["foo", 1.23, 1.23], [None, 1.23, 1.23], ] for test in tests: self.assertEqual(gf.safe_float(test[0], test[1]), test[2])
def test_safe_float(self): tests = [ ("3.14", 1.23, 3.14), (" 3.14", 1.23, 3.14), (" 3.14 ", 1.23, 3.14), ("3.14f", 1.23, 1.23), ("0x3.14", 1.23, 1.23), ("", 1.23, 1.23), ("foo", 1.23, 1.23), (None, 1.23, 1.23), ] for test in tests: self.assertEqual(gf.safe_float(test[0], test[1]), test[2])
def _translate_text_map(self, text_map, real_full_wave_length): """ Translate the text_map by adding head and tail dummy fragments """ if len(text_map) == 0: self._log("No fragments in the text_map", Logger.CRITICAL) return (False, None) translated = [] head = gf.safe_float(self.task.configuration.is_audio_file_head_length, 0) translated.append([0, head, None, None]) end = 0 for element in text_map: start, end, fragment_id, fragment_text = element start += head end += head translated.append([start, end, fragment_id, fragment_text]) translated.append([end, real_full_wave_length, None, None]) return (True, translated)
def read_properties(self): """ Populate this object by reading the audio properties of the file at the given path. Currently this function uses :class:`aeneas.ffprobewrapper.FFPROBEWrapper` to get the audio file properties. """ self._log("Reading properties") # check the file can be read if self.file_path is None: raise AttributeError("File path is None") if not os.path.isfile(self.file_path): self._log(["File '%s' cannot be read", self.file_path], Logger.CRITICAL) raise OSError("File cannot be read") # get the file size self._log(["Getting file size for '%s'", self.file_path]) self.file_size = os.path.getsize(self.file_path) self._log( ["File size for '%s' is '%d'", self.file_path, self.file_size]) # get the audio properties self._log("Reading properties with FFPROBEWrapper...") prober = FFPROBEWrapper(logger=self.logger) properties = prober.read_properties(self.file_path) self._log("Reading properties with FFPROBEWrapper... done") # save relevant properties in results inside the audiofile object self.audio_length = gf.safe_float( properties[FFPROBEWrapper.STDOUT_DURATION]) self._log(["Stored audio_length: '%s'", self.audio_length]) self.audio_format = properties[FFPROBEWrapper.STDOUT_CODEC_NAME] self._log(["Stored audio_format: '%s'", self.audio_format]) self.audio_sample_rate = gf.safe_int( properties[FFPROBEWrapper.STDOUT_SAMPLE_RATE]) self._log(["Stored audio_sample_rate: '%s'", self.audio_sample_rate]) self.audio_channels = gf.safe_int( properties[FFPROBEWrapper.STDOUT_CHANNELS]) self._log(["Stored audio_channels: '%s'", self.audio_channels])
def read_properties(self): """ Populate this object by reading the audio properties of the file at the given path. Currently this function uses :class:`aeneas.ffprobewrapper.FFPROBEWrapper` to get the audio file properties. """ self._log("Reading properties") # check the file can be read if self.file_path is None: raise AttributeError("File path is None") if not os.path.isfile(self.file_path): self._log(["File '%s' cannot be read", self.file_path], Logger.CRITICAL) raise OSError("File cannot be read") # get the file size self._log(["Getting file size for '%s'", self.file_path]) self.file_size = os.path.getsize(self.file_path) self._log(["File size for '%s' is '%d'", self.file_path, self.file_size]) # get the audio properties self._log("Reading properties with FFPROBEWrapper...") prober = FFPROBEWrapper(logger=self.logger) properties = prober.read_properties(self.file_path) self._log("Reading properties with FFPROBEWrapper... done") # save relevant properties in results inside the audiofile object self.audio_length = gf.safe_float(properties[FFPROBEWrapper.STDOUT_DURATION]) self._log(["Stored audio_length: '%s'", self.audio_length]) self.audio_format = properties[FFPROBEWrapper.STDOUT_CODEC_NAME] self._log(["Stored audio_format: '%s'", self.audio_format]) self.audio_sample_rate = gf.safe_int(properties[FFPROBEWrapper.STDOUT_SAMPLE_RATE]) self._log(["Stored audio_sample_rate: '%s'", self.audio_sample_rate]) self.audio_channels = gf.safe_int(properties[FFPROBEWrapper.STDOUT_CHANNELS]) self._log(["Stored audio_channels: '%s'", self.audio_channels])
def _cut_head_tail(self, audio_file_path): """ Set the audio file head or tail, suitably cutting the audio file on disk, and setting the corresponding parameters in the task configuration. Return a success bool flag """ self._log("Setting head and/or tail") try: configuration = self.task.configuration head_length = configuration.is_audio_file_head_length process_length = configuration.is_audio_file_process_length detect_head_min = configuration.is_audio_file_detect_head_min detect_head_max = configuration.is_audio_file_detect_head_max detect_tail_min = configuration.is_audio_file_detect_tail_min detect_tail_max = configuration.is_audio_file_detect_tail_max # explicit head or process? explicit = (head_length is not None) or (process_length is not None) # at least one detect parameter? detect = ((detect_head_min is not None) or (detect_head_max is not None) or (detect_tail_min is not None) or (detect_tail_max is not None)) if explicit or detect: # we need to load the audio data audio_file = AudioFile(audio_file_path, logger=self.logger) audio_file.load_data() if explicit: self._log("Explicit head or process") else: self._log( "No explicit head or process => detecting head/tail") head = 0.0 if (detect_head_min is not None) or (detect_head_max is not None): self._log("Detecting head...") detect_head_min = gf.safe_float( detect_head_min, gc.SD_MIN_HEAD_LENGTH) detect_head_max = gf.safe_float( detect_head_max, gc.SD_MAX_HEAD_LENGTH) self._log(["detect_head_min is %.3f", detect_head_min]) self._log(["detect_head_max is %.3f", detect_head_max]) sd = SD(audio_file, self.task.text_file, logger=self.logger) head = sd.detect_head(detect_head_min, detect_head_max) self._log(["Detected head: %.3f", head]) tail = 0.0 if (detect_tail_min is not None) or (detect_tail_max is not None): self._log("Detecting tail...") detect_tail_max = gf.safe_float( detect_tail_max, gc.SD_MAX_TAIL_LENGTH) detect_tail_min = gf.safe_float( detect_tail_min, gc.SD_MIN_TAIL_LENGTH) self._log(["detect_tail_min is %.3f", detect_tail_min]) self._log(["detect_tail_max is %.3f", detect_tail_max]) sd = SD(audio_file, self.task.text_file, logger=self.logger) tail = sd.detect_tail(detect_tail_min, detect_tail_max) self._log(["Detected tail: %.3f", tail]) # sanity check head_length = max(0, head) process_length = max(0, audio_file.audio_length - tail - head) # we need to set these values # in the config object for later use self.task.configuration.is_audio_file_head_length = head_length self.task.configuration.is_audio_file_process_length = process_length self._log(["Set head_length: %.3f", head_length]) self._log(["Set process_length: %.3f", process_length]) if head_length is not None: # in case we are reading from config object head_length = float(head_length) if process_length is not None: # in case we are reading from config object process_length = float(process_length) # note that str() is necessary, as one might be None self._log( ["is_audio_file_head_length is %s", str(head_length)]) self._log([ "is_audio_file_process_length is %s", str(process_length) ]) self._log("Trimming audio data...") audio_file.trim(head_length, process_length) self._log("Trimming audio data... done") self._log("Writing audio file...") audio_file.write(audio_file_path) self._log("Writing audio file... done") audio_file.clear_data() else: # nothing to do self._log("No explicit head/process or detect head/tail") self._log("Setting head and/or tail: succeeded") return True except Exception as e: self._log("Setting head and/or tail: failed") self._log(["Message: %s", str(e)]) return False
def perform_command(self): """ Perform command and return the appropriate exit code. :rtype: int """ if len(self.actual_arguments) < 4: return self.print_help() text_format = gf.safe_unicode(self.actual_arguments[0]) if text_format == u"list": text = gf.safe_unicode(self.actual_arguments[1]) elif text_format in TextFileFormat.ALLOWED_VALUES: text = self.actual_arguments[1] if not self.check_input_file(text): return self.ERROR_EXIT_CODE else: return self.print_help() l1_id_regex = self.has_option_with_value(u"--l1-id-regex") l2_id_regex = self.has_option_with_value(u"--l2-id-regex") l3_id_regex = self.has_option_with_value(u"--l3-id-regex") id_regex = self.has_option_with_value(u"--id-regex") class_regex = self.has_option_with_value(u"--class-regex") sort = self.has_option_with_value(u"--sort") backwards = self.has_option([u"-b", u"--backwards"]) quit_after = gf.safe_float(self.has_option_with_value(u"--quit-after"), None) start_fragment = gf.safe_int(self.has_option_with_value(u"--start"), None) end_fragment = gf.safe_int(self.has_option_with_value(u"--end"), None) parameters = { gc.PPN_TASK_IS_TEXT_MUNPARSED_L1_ID_REGEX: l1_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L2_ID_REGEX: l2_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L3_ID_REGEX: l3_id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_CLASS_REGEX: class_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_REGEX: id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_SORT: sort, } if (text_format == TextFileFormat.MUNPARSED) and ((l1_id_regex is None) or (l2_id_regex is None) or (l3_id_regex is None)): self.print_error(u"You must specify --l1-id-regex and --l2-id-regex and --l3-id-regex for munparsed format") return self.ERROR_EXIT_CODE if (text_format == TextFileFormat.UNPARSED) and (id_regex is None) and (class_regex is None): self.print_error(u"You must specify --id-regex and/or --class-regex for unparsed format") return self.ERROR_EXIT_CODE language = gf.safe_unicode(self.actual_arguments[2]) output_file_path = self.actual_arguments[3] if not self.check_output_file(output_file_path): return self.ERROR_EXIT_CODE text_file = self.get_text_file(text_format, text, parameters) if text_file is None: self.print_error(u"Unable to build a TextFile from the given parameters") return self.ERROR_EXIT_CODE elif len(text_file) == 0: self.print_error(u"No text fragments found") return self.ERROR_EXIT_CODE text_file.set_language(language) self.print_info(u"Read input text with %d fragments" % (len(text_file))) if start_fragment is not None: self.print_info(u"Slicing from index %d" % (start_fragment)) if end_fragment is not None: self.print_info(u"Slicing to index %d" % (end_fragment)) text_slice = text_file.get_slice(start_fragment, end_fragment) self.print_info(u"Synthesizing %d fragments" % (len(text_slice))) if quit_after is not None: self.print_info(u"Stop synthesizing upon reaching %.3f seconds" % (quit_after)) try: synt = Synthesizer(rconf=self.rconf, logger=self.logger) synt.synthesize( text_slice, output_file_path, quit_after=quit_after, backwards=backwards ) self.print_success(u"Created file '%s'" % output_file_path) synt.clear_cache() return self.NO_ERROR_EXIT_CODE except ImportError as exc: tts = self.rconf[RuntimeConfiguration.TTS] if tts == Synthesizer.AWS: self.print_error(u"You need to install Python module boto3 to use the AWS Polly TTS API wrapper. Run:") self.print_error(u"$ pip install boto3") self.print_error(u"or, to install for all users:") self.print_error(u"$ sudo pip install boto3") elif tts == Synthesizer.NUANCE: self.print_error(u"You need to install Python module requests to use the Nuance TTS API wrapper. Run:") self.print_error(u"$ pip install requests") self.print_error(u"or, to install for all users:") self.print_error(u"$ sudo pip install requests") else: self.print_error(u"An unexpected error occurred while synthesizing text:") self.print_error(u"%s" % exc) except Exception as exc: self.print_error(u"An unexpected error occurred while synthesizing text:") self.print_error(u"%s" % exc) return self.ERROR_EXIT_CODE
def perform_command(self): """ Perform command and return the appropriate exit code. :rtype: int """ if len(self.actual_arguments) < 4: return self.print_help() text_format = gf.safe_unicode(self.actual_arguments[0]) if text_format == u"list": text = gf.safe_unicode(self.actual_arguments[1]) elif text_format in TextFileFormat.ALLOWED_VALUES: text = self.actual_arguments[1] if not self.check_input_file(text): return self.ERROR_EXIT_CODE else: return self.print_help() l1_id_regex = self.has_option_with_value(u"--l1-id-regex") l2_id_regex = self.has_option_with_value(u"--l2-id-regex") l3_id_regex = self.has_option_with_value(u"--l3-id-regex") id_regex = self.has_option_with_value(u"--id-regex") class_regex = self.has_option_with_value(u"--class-regex") sort = self.has_option_with_value(u"--sort") backwards = self.has_option([u"-b", u"--backwards"]) quit_after = gf.safe_float(self.has_option_with_value(u"--quit-after"), None) start_fragment = gf.safe_int(self.has_option_with_value(u"--start"), None) end_fragment = gf.safe_int(self.has_option_with_value(u"--end"), None) parameters = { gc.PPN_TASK_IS_TEXT_MUNPARSED_L1_ID_REGEX: l1_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L2_ID_REGEX: l2_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L3_ID_REGEX: l3_id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_CLASS_REGEX: class_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_REGEX: id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_SORT: sort, } if (text_format == TextFileFormat.MUNPARSED) and ( (l1_id_regex is None) or (l2_id_regex is None) or (l3_id_regex is None)): self.print_error( u"You must specify --l1-id-regex and --l2-id-regex and --l3-id-regex for munparsed format" ) return self.ERROR_EXIT_CODE if (text_format == TextFileFormat.UNPARSED) and ( id_regex is None) and (class_regex is None): self.print_error( u"You must specify --id-regex and/or --class-regex for unparsed format" ) return self.ERROR_EXIT_CODE language = gf.safe_unicode(self.actual_arguments[2]) output_file_path = self.actual_arguments[3] if not self.check_output_file(output_file_path): return self.ERROR_EXIT_CODE text_file = self.get_text_file(text_format, text, parameters) if text_file is None: self.print_error( u"Unable to build a TextFile from the given parameters") return self.ERROR_EXIT_CODE elif len(text_file) == 0: self.print_error(u"No text fragments found") return self.ERROR_EXIT_CODE text_file.set_language(language) self.print_info(u"Read input text with %d fragments" % (len(text_file))) if start_fragment is not None: self.print_info(u"Slicing from index %d" % (start_fragment)) if end_fragment is not None: self.print_info(u"Slicing to index %d" % (end_fragment)) text_slice = text_file.get_slice(start_fragment, end_fragment) self.print_info(u"Synthesizing %d fragments" % (len(text_slice))) if quit_after is not None: self.print_info(u"Stop synthesizing upon reaching %.3f seconds" % (quit_after)) try: synt = Synthesizer(rconf=self.rconf, logger=self.logger) synt.synthesize(text_slice, output_file_path, quit_after=quit_after, backwards=backwards) self.print_success(u"Created file '%s'" % output_file_path) synt.clear_cache() return self.NO_ERROR_EXIT_CODE except ImportError as exc: tts = self.rconf[RuntimeConfiguration.TTS] if tts == Synthesizer.AWS: self.print_error( u"You need to install Python module boto3 to use the AWS Polly TTS API wrapper. Run:" ) self.print_error(u"$ pip install boto3") self.print_error(u"or, to install for all users:") self.print_error(u"$ sudo pip install boto3") elif tts == Synthesizer.NUANCE: self.print_error( u"You need to install Python module requests to use the Nuance TTS API wrapper. Run:" ) self.print_error(u"$ pip install requests") self.print_error(u"or, to install for all users:") self.print_error(u"$ sudo pip install requests") else: self.print_error( u"An unexpected error occurred while synthesizing text:") self.print_error(u"%s" % exc) except Exception as exc: self.print_error( u"An unexpected error occurred while synthesizing text:") self.print_error(u"%s" % exc) return self.ERROR_EXIT_CODE
def __unicode__(self): msg = [ u"File path: %s" % self.file_path, u"Audio length (s): %s" % gf.safe_float(self.audio_length), ] return u"\n".join(msg)
def perform_command(self): """ Perform command and return the appropriate exit code. :rtype: int """ if len(self.actual_arguments) < 4: return self.print_help() text_format = gf.safe_unicode(self.actual_arguments[0]) if text_format == u"list": text = gf.safe_unicode(self.actual_arguments[1]) elif text_format in TextFileFormat.ALLOWED_VALUES: text = self.actual_arguments[1] if not self.check_input_file(text): return self.ERROR_EXIT_CODE else: return self.print_help() l1_id_regex = self.has_option_with_value(u"--l1-id-regex") l2_id_regex = self.has_option_with_value(u"--l2-id-regex") l3_id_regex = self.has_option_with_value(u"--l3-id-regex") id_regex = self.has_option_with_value(u"--id-regex") class_regex = self.has_option_with_value(u"--class-regex") sort = self.has_option_with_value(u"--sort") parameters = { gc.PPN_TASK_IS_TEXT_MUNPARSED_L1_ID_REGEX : l1_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L2_ID_REGEX : l2_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L3_ID_REGEX : l3_id_regex, gc.PPN_JOB_IS_TEXT_UNPARSED_ID_REGEX : id_regex, gc.PPN_JOB_IS_TEXT_UNPARSED_CLASS_REGEX : class_regex, gc.PPN_JOB_IS_TEXT_UNPARSED_ID_SORT : sort } if (text_format == TextFileFormat.MUNPARSED) and ((l1_id_regex is None) or (l2_id_regex is None) or (l3_id_regex is None)): self.print_error(u"You must specify --l1-id-regex and --l2-id-regex and --l3-id-regex for munparsed format") return self.ERROR_EXIT_CODE if (text_format == TextFileFormat.UNPARSED) and (id_regex is None) and (class_regex is None): self.print_error(u"You must specify --id-regex and/or --class-regex for unparsed format") return self.ERROR_EXIT_CODE language = gf.safe_unicode(self.actual_arguments[2]) audio_file_path = self.actual_arguments[3] if not self.check_input_file(audio_file_path): return self.ERROR_EXIT_CODE text_file = self.get_text_file(text_format, text, parameters) if text_file is None: self.print_error(u"Unable to build a TextFile from the given parameters") return self.ERROR_EXIT_CODE elif len(text_file) == 0: self.print_error(u"No text fragments found") return self.ERROR_EXIT_CODE text_file.set_language(language) self.print_info(u"Read input text with %d fragments" % (len(text_file))) self.print_info(u"Reading audio...") try: audio_file_mfcc = AudioFileMFCC(audio_file_path, rconf=self.rconf, logger=self.logger) except AudioFileConverterError: self.print_error(u"Unable to call the ffmpeg executable '%s'" % (self.rconf[RuntimeConfiguration.FFMPEG_PATH])) self.print_error(u"Make sure the path to ffmpeg is correct") return self.ERROR_EXIT_CODE except (AudioFileUnsupportedFormatError, AudioFileNotInitializedError): self.print_error(u"Cannot read file '%s'" % (audio_file_path)) self.print_error(u"Check that its format is supported by ffmpeg") return self.ERROR_EXIT_CODE except Exception as exc: self.print_error(u"An unexpected error occurred while reading the audio file:") self.print_error(u"%s" % exc) return self.ERROR_EXIT_CODE self.print_info(u"Reading audio... done") self.print_info(u"Running VAD...") audio_file_mfcc.run_vad() self.print_info(u"Running VAD... done") min_head = gf.safe_float(self.has_option_with_value(u"--min-head"), None) max_head = gf.safe_float(self.has_option_with_value(u"--max-head"), None) min_tail = gf.safe_float(self.has_option_with_value(u"--min-tail"), None) max_tail = gf.safe_float(self.has_option_with_value(u"--max-tail"), None) self.print_info(u"Detecting audio interval...") start_detector = SD(audio_file_mfcc, text_file, rconf=self.rconf, logger=self.logger) start, end = start_detector.detect_interval(min_head, max_head, min_tail, max_tail) self.print_info(u"Detecting audio interval... done") self.print_result(audio_file_mfcc.audio_length, start, end) return self.NO_ERROR_EXIT_CODE
def read_properties(self, audio_file_path): """ Read the properties of an audio file and return them as a dictionary. Example: :: d["index"]=0 d["codec_name"]=mp3 d["codec_long_name"]=MP3 (MPEG audio layer 3) d["profile"]=unknown d["codec_type"]=audio d["codec_time_base"]=1/44100 d["codec_tag_string"]=[0][0][0][0] d["codec_tag"]=0x0000 d["sample_fmt"]=s16p d["sample_rate"]=44100 d["channels"]=1 d["channel_layout"]=mono d["bits_per_sample"]=0 d["id"]=N/A d["r_frame_rate"]=0/0 d["avg_frame_rate"]=0/0 d["time_base"]=1/14112000 d["start_pts"]=0 d["start_time"]=0.000000 d["duration_ts"]=1545083190 d["duration"]=109.487188 d["bit_rate"]=128000 d["max_bit_rate"]=N/A d["bits_per_raw_sample"]=N/A d["nb_frames"]=N/A d["nb_read_frames"]=N/A d["nb_read_packets"]=N/A d["DISPOSITION:default"]=0 d["DISPOSITION:dub"]=0 d["DISPOSITION:original"]=0 d["DISPOSITION:comment"]=0 d["DISPOSITION:lyrics"]=0 d["DISPOSITION:karaoke"]=0 d["DISPOSITION:forced"]=0 d["DISPOSITION:hearing_impaired"]=0 d["DISPOSITION:visual_impaired"]=0 d["DISPOSITION:clean_effects"]=0 d["DISPOSITION:attached_pic"]=0 :param audio_file_path: the path of the audio file to analyze :type audio_file_path: string (path) :rtype: dict """ # test if we can read the file at audio_file_path if not os.path.isfile(audio_file_path): self._log(["Input file '%s' cannot be read", audio_file_path], Logger.CRITICAL) raise OSError("Input file cannot be read") # call ffprobe arguments = [] arguments += [gc.FFPROBE_PATH] arguments += self.FFPROBE_PARAMETERS arguments += [audio_file_path] self._log(["Calling with arguments '%s'", arguments]) proc = subprocess.Popen(arguments, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) (stdoutdata, stderrdata) = proc.communicate() proc.stdout.close() proc.stdin.close() proc.stderr.close() self._log("Call completed") # if no output, raise error if (stdoutdata is None) or (len(stderrdata) == 0): msg = "No output for '%s'" % audio_file_path self._log(msg, Logger.CRITICAL) raise Exception(msg) # dictionary for the results results = dict() results[self.STDOUT_CHANNELS] = None results[self.STDOUT_CODEC_NAME] = None results[self.STDOUT_DURATION] = None results[self.STDOUT_SAMPLE_RATE] = None # scan the first audio stream the ffprobe stdout output # TODO more robust parsing # TODO deal with multiple audio streams for line in stdoutdata.splitlines(): if line == self.STDOUT_END_STREAM: self._log("Reached end of the stream") break elif len(line.split("=")) == 2: key, value = line.split("=") results[key] = value self._log(["Found property '%s'='%s'", key, value]) # convert duration to float if self.STDOUT_DURATION in results: self._log(["Found duration: '%s'", results[self.STDOUT_DURATION]]) results[self.STDOUT_DURATION] = gf.safe_float(results[self.STDOUT_DURATION], None) else: self._log("No duration found in stdout") # if audio_length is still None, try scanning ffprobe stderr output try: if results[self.STDOUT_DURATION] is None: pattern = re.compile(self.STDERR_DURATION_REGEX) for line in stderrdata.splitlines(): match = pattern.search(line) if match is not None: self._log(["Found matching line '%s'", line]) v_h = int(match.group(1)) v_m = int(match.group(2)) v_s = int(match.group(3)) v_f = float("0." + match.group(4)) v_length = v_h * 3600 + v_m * 60 + v_s + v_f results[self.STDOUT_DURATION] = v_length self._log(["Extracted duration '%f'", v_length]) break except ValueError: self._log("ValueError exception while parsing stderr") except TypeError: self._log("TypeError exception while parsing stderr") if results[self.STDOUT_DURATION] is None: self._log("No duration found in stdout or stderr", Logger.CRITICAL) raise ValueError("Cannot determine duration of the input file") # return dictionary self._log("Returning dict") return results
def _cut_head_tail(self, audio_file_path): """ Set the audio file head or tail, suitably cutting the audio file on disk, and setting the corresponding parameters in the task configuration. Return a success bool flag """ self._log("Setting head and/or tail") try: configuration = self.task.configuration head_length = configuration.is_audio_file_head_length process_length = configuration.is_audio_file_process_length detect_head_min = configuration.is_audio_file_detect_head_min detect_head_max = configuration.is_audio_file_detect_head_max detect_tail_min = configuration.is_audio_file_detect_tail_min detect_tail_max = configuration.is_audio_file_detect_tail_max # explicit head or process? explicit = (head_length is not None) or (process_length is not None) # at least one detect parameter? detect = ( (detect_head_min is not None) or (detect_head_max is not None) or (detect_tail_min is not None) or (detect_tail_max is not None) ) if explicit or detect: # we need to load the audio data audio_file = AudioFile(audio_file_path, logger=self.logger) audio_file.load_data() if explicit: self._log("Explicit head or process") else: self._log("No explicit head or process => detecting head/tail") head = 0.0 if (detect_head_min is not None) or (detect_head_max is not None): self._log("Detecting head...") detect_head_min = gf.safe_float(detect_head_min, gc.SD_MIN_HEAD_LENGTH) detect_head_max = gf.safe_float(detect_head_max, gc.SD_MAX_HEAD_LENGTH) self._log(["detect_head_min is %.3f", detect_head_min]) self._log(["detect_head_max is %.3f", detect_head_max]) sd = SD(audio_file, self.task.text_file, logger=self.logger) head = sd.detect_head(detect_head_min, detect_head_max) self._log(["Detected head: %.3f", head]) tail = 0.0 if (detect_tail_min is not None) or (detect_tail_max is not None): self._log("Detecting tail...") detect_tail_max = gf.safe_float(detect_tail_max, gc.SD_MAX_TAIL_LENGTH) detect_tail_min = gf.safe_float(detect_tail_min, gc.SD_MIN_TAIL_LENGTH) self._log(["detect_tail_min is %.3f", detect_tail_min]) self._log(["detect_tail_max is %.3f", detect_tail_max]) sd = SD(audio_file, self.task.text_file, logger=self.logger) tail = sd.detect_tail(detect_tail_min, detect_tail_max) self._log(["Detected tail: %.3f", tail]) # sanity check head_length = max(0, head) process_length = max(0, audio_file.audio_length - tail - head) # we need to set these values # in the config object for later use self.task.configuration.is_audio_file_head_length = head_length self.task.configuration.is_audio_file_process_length = process_length self._log(["Set head_length: %.3f", head_length]) self._log(["Set process_length: %.3f", process_length]) if head_length is not None: # in case we are reading from config object head_length = float(head_length) if process_length is not None: # in case we are reading from config object process_length = float(process_length) # note that str() is necessary, as one might be None self._log(["is_audio_file_head_length is %s", str(head_length)]) self._log(["is_audio_file_process_length is %s", str(process_length)]) self._log("Trimming audio data...") audio_file.trim(head_length, process_length) self._log("Trimming audio data... done") self._log("Writing audio file...") audio_file.write(audio_file_path) self._log("Writing audio file... done") audio_file.clear_data() else: # nothing to do self._log("No explicit head/process or detect head/tail") self._log("Setting head and/or tail: succeeded") return True except Exception as e: self._log("Setting head and/or tail: failed") self._log(["Message: %s", str(e)]) return False
def perform_command(self): """ Perform command and return the appropriate exit code. :rtype: int """ if len(self.actual_arguments) < 4: return self.print_help() text_format = gf.safe_unicode(self.actual_arguments[0]) if text_format == u"list": text = gf.safe_unicode(self.actual_arguments[1]) elif text_format in TextFileFormat.ALLOWED_VALUES: text = self.actual_arguments[1] if not self.check_input_file(text): return self.ERROR_EXIT_CODE else: return self.print_help() l1_id_regex = self.has_option_with_value(u"--l1-id-regex") l2_id_regex = self.has_option_with_value(u"--l2-id-regex") l3_id_regex = self.has_option_with_value(u"--l3-id-regex") id_regex = self.has_option_with_value(u"--id-regex") class_regex = self.has_option_with_value(u"--class-regex") sort = self.has_option_with_value(u"--sort") parameters = { gc.PPN_TASK_IS_TEXT_MUNPARSED_L1_ID_REGEX: l1_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L2_ID_REGEX: l2_id_regex, gc.PPN_TASK_IS_TEXT_MUNPARSED_L3_ID_REGEX: l3_id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_CLASS_REGEX: class_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_REGEX: id_regex, gc.PPN_TASK_IS_TEXT_UNPARSED_ID_SORT: sort, } if (text_format == TextFileFormat.MUNPARSED) and ( (l1_id_regex is None) or (l2_id_regex is None) or (l3_id_regex is None)): self.print_error( u"You must specify --l1-id-regex and --l2-id-regex and --l3-id-regex for munparsed format" ) return self.ERROR_EXIT_CODE if (text_format == TextFileFormat.UNPARSED) and ( id_regex is None) and (class_regex is None): self.print_error( u"You must specify --id-regex and/or --class-regex for unparsed format" ) return self.ERROR_EXIT_CODE language = gf.safe_unicode(self.actual_arguments[2]) audio_file_path = self.actual_arguments[3] if not self.check_input_file(audio_file_path): return self.ERROR_EXIT_CODE text_file = self.get_text_file(text_format, text, parameters) if text_file is None: self.print_error( u"Unable to build a TextFile from the given parameters") return self.ERROR_EXIT_CODE elif len(text_file) == 0: self.print_error(u"No text fragments found") return self.ERROR_EXIT_CODE text_file.set_language(language) self.print_info(u"Read input text with %d fragments" % (len(text_file))) self.print_info(u"Reading audio...") try: audio_file_mfcc = AudioFileMFCC(audio_file_path, rconf=self.rconf, logger=self.logger) except AudioFileConverterError: self.print_error(u"Unable to call the ffmpeg executable '%s'" % (self.rconf[RuntimeConfiguration.FFMPEG_PATH])) self.print_error(u"Make sure the path to ffmpeg is correct") return self.ERROR_EXIT_CODE except (AudioFileUnsupportedFormatError, AudioFileNotInitializedError): self.print_error(u"Cannot read file '%s'" % (audio_file_path)) self.print_error(u"Check that its format is supported by ffmpeg") return self.ERROR_EXIT_CODE except Exception as exc: self.print_error( u"An unexpected error occurred while reading the audio file:") self.print_error(u"%s" % exc) return self.ERROR_EXIT_CODE self.print_info(u"Reading audio... done") self.print_info(u"Running VAD...") audio_file_mfcc.run_vad() self.print_info(u"Running VAD... done") min_head = gf.safe_float(self.has_option_with_value(u"--min-head"), None) max_head = gf.safe_float(self.has_option_with_value(u"--max-head"), None) min_tail = gf.safe_float(self.has_option_with_value(u"--min-tail"), None) max_tail = gf.safe_float(self.has_option_with_value(u"--max-tail"), None) self.print_info(u"Detecting audio interval...") start_detector = SD(audio_file_mfcc, text_file, rconf=self.rconf, logger=self.logger) start, end = start_detector.detect_interval(min_head, max_head, min_tail, max_tail) self.print_info(u"Detecting audio interval... done") self.print_result(audio_file_mfcc.audio_length, start, end) return self.NO_ERROR_EXIT_CODE
def read_properties(self, audio_file_path): """ Read the properties of an audio file and return them as a dictionary. Example: :: d["index"]=0 d["codec_name"]=mp3 d["codec_long_name"]=MP3 (MPEG audio layer 3) d["profile"]=unknown d["codec_type"]=audio d["codec_time_base"]=1/44100 d["codec_tag_string"]=[0][0][0][0] d["codec_tag"]=0x0000 d["sample_fmt"]=s16p d["sample_rate"]=44100 d["channels"]=1 d["channel_layout"]=mono d["bits_per_sample"]=0 d["id"]=N/A d["r_frame_rate"]=0/0 d["avg_frame_rate"]=0/0 d["time_base"]=1/14112000 d["start_pts"]=0 d["start_time"]=0.000000 d["duration_ts"]=1545083190 d["duration"]=109.487188 d["bit_rate"]=128000 d["max_bit_rate"]=N/A d["bits_per_raw_sample"]=N/A d["nb_frames"]=N/A d["nb_read_frames"]=N/A d["nb_read_packets"]=N/A d["DISPOSITION:default"]=0 d["DISPOSITION:dub"]=0 d["DISPOSITION:original"]=0 d["DISPOSITION:comment"]=0 d["DISPOSITION:lyrics"]=0 d["DISPOSITION:karaoke"]=0 d["DISPOSITION:forced"]=0 d["DISPOSITION:hearing_impaired"]=0 d["DISPOSITION:visual_impaired"]=0 d["DISPOSITION:clean_effects"]=0 d["DISPOSITION:attached_pic"]=0 :param audio_file_path: the path of the audio file to analyze :type audio_file_path: string (path) :rtype: dict """ # test if we can read the file at audio_file_path if not os.path.isfile(audio_file_path): self._log(["Input file '%s' cannot be read", audio_file_path], Logger.CRITICAL) raise OSError("Input file cannot be read") # call ffprobe arguments = [] arguments += [gc.FFPROBE_PATH] arguments += self.FFPROBE_PARAMETERS arguments += [audio_file_path] self._log(["Calling with arguments '%s'", arguments]) proc = subprocess.Popen( arguments, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) (stdoutdata, stderrdata) = proc.communicate() proc.stdout.close() proc.stdin.close() proc.stderr.close() self._log("Call completed") # if no output, raise error if (stdoutdata is None) or (len(stderrdata) == 0): msg = "No output for '%s'" % audio_file_path self._log(msg, Logger.CRITICAL) raise Exception(msg) # dictionary for the results results = dict() results[self.STDOUT_CHANNELS] = None results[self.STDOUT_CODEC_NAME] = None results[self.STDOUT_DURATION] = None results[self.STDOUT_SAMPLE_RATE] = None # scan the first audio stream the ffprobe stdout output # TODO more robust parsing # TODO deal with multiple audio streams for line in stdoutdata.splitlines(): if line == self.STDOUT_END_STREAM: self._log("Reached end of the stream") break elif len(line.split("=")) == 2: key, value = line.split("=") results[key] = value self._log(["Found property '%s'='%s'", key, value]) # convert duration to float if self.STDOUT_DURATION in results: self._log(["Found duration: '%s'", results[self.STDOUT_DURATION]]) results[self.STDOUT_DURATION] = gf.safe_float( results[self.STDOUT_DURATION], None ) else: self._log("No duration found in stdout") # if audio_length is still None, try scanning ffprobe stderr output try: if results[self.STDOUT_DURATION] is None: pattern = re.compile(self.STDERR_DURATION_REGEX) for line in stderrdata.splitlines(): match = pattern.search(line) if match is not None: self._log(["Found matching line '%s'", line]) v_h = int(match.group(1)) v_m = int(match.group(2)) v_s = int(match.group(3)) v_f = float("0." + match.group(4)) v_length = v_h * 3600 + v_m * 60 + v_s + v_f results[self.STDOUT_DURATION] = v_length self._log(["Extracted duration '%f'", v_length]) break except ValueError: self._log("ValueError exception while parsing stderr") except TypeError: self._log("TypeError exception while parsing stderr") if results[self.STDOUT_DURATION] is None: self._log("No duration found in stdout or stderr", Logger.CRITICAL) raise ValueError("Cannot determine duration of the input file") # return dictionary self._log("Returning dict") return results
def test_safe_float_07(self): default = 1.23 expected = 1.23 value = "0x3.14" result = safe_float(value, default) self.assertEqual(result, expected)