Beispiel #1
0
def get_system_info():
    python_version_info = "Python " + str(sys.version_info[0]) + "." + str(sys.version_info[1])+ "." + str(sys.version_info[2])
    ffmpeg_cmd_version = 'cmd /c "' + ConfigGetter.get_configs("ffmpeg_path") + '" -version'
    java_cmd_version = 'cmd /c "' + ConfigGetter.get_configs("java_path") + '" -version'
    pipeline_version = re.sub(r".+org\.daisy\.pipeline_", "Daisy Pipeline v. ", ConfigGetter.get_configs("pipeline_path"))

    ffmpeg_version = ""
    raw_output_ffmpeg = ExternalProgramCaller.run_external_command(ffmpeg_cmd_version).splitlines()
    for line in raw_output_ffmpeg:
        if re.search(r" Copyright.+", line):
            ffmpeg_version = re.sub(r' Copyright.+', '', line)
            ffmpeg_version = re.sub(r'ffmpeg', 'FFmpeg', ffmpeg_version)
            break
    
    java_version = ""
    raw_output_java = ExternalProgramCaller.run_external_command(java_cmd_version).splitlines()
    for line in raw_output_java:
        if re.search(r".+version.+", line):
            java_version = "Java " + re.sub(r'"', '', line)
            break
    
    system_info_list = [python_version_info, ffmpeg_version, java_version, pipeline_version]
    #print(system_info_list)

    return system_info_list
    def encode_audio(ncc, output_path):
        # pipeline-cli.bat scripts\modify_improve\dtb\DTBAudioEncoder.taskScript "--input=path\ncc.html"
        # "--output=outpath" "--bitrate=48"
        pipeline_path = pathlib.Path(
            ConfigGetter.get_configs("pipeline_path")).absolute()
        if pipeline_path.exists():
            cmd_encode = ".\\pipeline-cli.bat scripts\\modify_improve\\dtb\\DTBAudioEncoder.taskScript"

            target_kbps = ConfigGetter.get_configs("target_kbps")
            if not target_kbps == "32" and not target_kbps == "48" and not target_kbps == "64" and not target_kbps == "128":
                print(
                    "  !Error while reading target_kbps from config.txt, using default value!"
                )
                target_kbps = "48"

            cmd_encode_audio = 'C: & cd ' + str(pipeline_path) + ' & ' + cmd_encode \
                               + ' "--input=' + ncc + '" "--output=' + output_path + '"  "--bitrate=' + target_kbps + '"'
            os.system(cmd_encode_audio)

            print("Validating encoded Daisy book...")

            output_ncc = pathlib.Path.joinpath(pathlib.Path(output_path),
                                               "ncc.html")
            validator_checks = DaisyScrutinizer.validate_daisy(str(output_ncc))

            if len(validator_checks[0]) == 0 and len(validator_checks[1]) == 0:
                print("No errors or warning reported.")
            else:
                for err in validator_checks[0]:
                    print(err)
                for warn in validator_checks[1]:
                    print(warn)

        else:
            print("Daisy pipeline not found! Skipping audio encoding!")
    def compare_lufs(audio_files):
        max_volume_level_flux = ConfigGetter.get_configs(
            "max_volume_level_flux")
        try:
            max_volume_level_flux = int(max_volume_level_flux)
        except:
            print(
                "  !Error while reading max_volume_level_flux from config.txt, using default value!"
            )
            max_volume_level_flux = 1

        lufs_flux = []
        i = 1
        while i < len(audio_files):
            audio_f_comp = audio_files[i - 1]
            if (audio_f_comp.lufs * -1) - (audio_files[i].lufs * -1) > (1 * max_volume_level_flux) \
                    or (audio_f_comp.lufs * -1) - (audio_files[i].lufs * -1) < (-1 * max_volume_level_flux):
                lufs_flux.append(
                    "Noticeable change in volume compared to previous audiofile on "
                    + audio_files[i].name + "." + audio_files[i].f_type +
                    "! (Change " + str(audio_f_comp.lufs) + " LUFS -> " +
                    str(audio_files[i].lufs) + " LUFS.)")
            i += 1

        return lufs_flux
Beispiel #4
0
 def check_audio_ext_paths():
     ffmpeg_path = ConfigGetter.get_configs("ffmpeg_path")
     if ffmpeg_path != "ffmpeg":
         ffmpeg_path = str(pathlib.Path(ffmpeg_path).absolute())
     if which(ffmpeg_path) is None:
         #print("FFmpeg not found at " + str(ffmpeg_path))
         return False
     else:
         return True
Beispiel #5
0
 def check_daisy_ext_paths():
     programs_found = True
     
     pipeline_path = pathlib.Path(ConfigGetter.get_configs("pipeline_path"), "pipeline-cli.bat").absolute()
     if not pipeline_path.exists():
         programs_found = False
         #print("Pipeline not found at " + str(pipeline_path))
     
     return programs_found
    def validate_daisy(ncc):
        errors = []
        warnings = []

        pipeline_path = pathlib.Path(
            ConfigGetter.get_configs("pipeline_path")).absolute()
        if not pipeline_path.exists():
            print("Daisy validator not found! Skipping validation!")
            errors.append("Validator not found at location " +
                          str(pipeline_path) + "! CAN NOT VALIDATE DAISY!")
            results = [errors, warnings]
            return results

        cmd_validator = ".\\pipeline-cli.bat scripts\\verify\\Daisy202DTBValidator.taskScript"

        # NOTE! It seems there is a bug in pipeline, so that the pipelinecli can not be run from any other location,
        # than from where it is installed. Trying to run it from any other location causes "Could not
        # find or load main class"-error. As a workaround in this program the location
        # is changed before any pipeline commands. The workaround does following:
        #
        # cmd /c ...                                  # cmd starts in what ever drive it is called from...
        # C:                                          # ...but cmd has to be on drive 'C:'
        # cd pipeline_path                            # ...or "cd pipeline_path" command won't work
        # pipeline commands

        cmd_validate = 'cmd /c "C: & cd ' + str(
            pipeline_path) + ' & ' + cmd_validator + ' "--input=' + ncc + '"" '
        validator_output = ExternalProgramCaller.run_external_command(
            cmd_validate).splitlines()

        for line in validator_output:
            # print(line)
            if re.search(r".ERROR, Validator. ", line):
                line = re.sub(r".ERROR, Validator. ", "", line)
                errors.append(line)
            elif re.search(r".WARNING, Validator. ", line):
                line = re.sub(r".WARNING, Validator. ", "", line)
                warnings.append(line)

        results = [errors, warnings]
        return results
Beispiel #7
0
    def write_report(audiobook, report_out_path, critical_errors, errors, warnings):
        validation_report = open(str(report_out_path), "x", encoding="utf-8")
        program_versions = get_system_info()
        skip_daisy_checks = False
        if not ConfigGetter.get_configs("daisy_validation") == "1":
            skip_daisy_checks = True

        skip_audio_checks = False
        if not ConfigGetter.get_configs("audio_validation") == "1":
            skip_audio_checks = True

        validation_report.write("<!DOCTYPE html>\n"
                                + "<html lang=""en"">\n"
                                + "<head>\n"
                                + "<meta charset=""UTF-8"">\n"
                                + "<title>VALIDATION REPORT</title>\n"
                                + "</head>\n"
                                + "<body>\n")

        if not skip_daisy_checks:
            validation_report.write("<h1>Validation report for dtb " + audiobook.dc_title + " / "
                                    + audiobook.dc_creator + " (" + audiobook.dc_identifier + ")</h1>\n\n")

            validation_report.write("<p style=""font-size:22px;"">\n"
                                    + "<b>Title:</b> " + audiobook.dc_title + "<br />\n"
                                    + "<b>Author:</b> " + audiobook.dc_creator + "<br />\n"
                                    + "<b>Narrator:</b> " + audiobook.ncc_narrator + "<br />\n"
                                    + "<b>Id:</b> " + audiobook.dc_identifier + "<br /></p>\n\n")
        else:
            validation_report.write("<h1>Validation report for dtb " + audiobook.path + "</h1>")

        validation_report.write("<h2>Validation summary:</h2>\n\n")

        if len(critical_errors) > 0:
            validation_report.write("<h3>Critical errors:</h3>\n")

            validation_report.write("<pre style=""font-size:18px;"">\n")

            for err in critical_errors:
                validation_report.write("    " + err + "\n")
            validation_report.write("</pre>\n\n")

        if len(errors) > 0:
            validation_report.write("<h3>Errors:</h3>\n")

            validation_report.write("<pre style=""font-size:18px;"">\n")

            for err in errors:
                validation_report.write("    " + err + "\n")
            validation_report.write("</pre>\n\n")

        if len(warnings) > 0:
            validation_report.write("<h3>Warnings:</h3>\n")

            validation_report.write("<pre style=""font-size:18px;"">\n")

            for err in warnings:
                validation_report.write("    " + err + "\n")
            validation_report.write("</pre>\n\n")

        if len(critical_errors) == 0 and len(errors) == 0 and len(warnings) == 0:
            validation_report.write("<p style=""font-size:22px;"">\n"
                                    + "No errors or warnings found. Congratulations!</p>\n\n")

        validation_report.write("<h2>Detailed information</h2>\n")

        if not skip_audio_checks:
            validation_report.write("<h3>Audio stats</h3>\n")
            validation_report.write("<pre style=""font-size:18px;"">\n")
            validation_report.write("    Avg. LUFS: " + format(audiobook.lufs, ".2f") + " LUFS\n")
            validation_report.write("    Avg. pkdb: " + format(audiobook.pkdb, ".2f") + " dB\n")
            validation_report.write("    Avg. tpkdb: " + format(audiobook.tpkdb, ".2f") + " dB\n")
            validation_report.write("    Avg. snr: " + format(audiobook.snr, ".2f") + " dB\n")
            validation_report.write("    KBPS: " + format(audiobook.kbps, ".0f") + "\n\n")

            for audio_f in audiobook.files:
                if isinstance(audio_f, AudioFile):
                    # format(float(silence_length), ".3f")

                    validation_report.write("    " + audio_f.name + "." + audio_f.f_type
                                            + ", " + format(audio_f.lufs, ".2f") + " LUFS, "
                                            + "peak " + format(audio_f.pkdb, ".2f")
                                            + " dB, tpkdb " + format(audio_f.tpkdb, ".2f")
                                            + " dB, snr " + format(audio_f.snr, ".2f") + " dB\n")

            validation_report.write("</pre>\n\n")

        if not skip_daisy_checks:
            validation_report.write("<h3>Filestructure</h3>\n")

            validation_report.write("<pre style=""font-size:18px;"">\n")
            validation_report.write("    <b>ncc.html files:</b> " + str(audiobook.ncc_count) + "\n")
            validation_report.write("    <b>audiofiles:</b> " + str(audiobook.audio_file_count) + "\n")
            validation_report.write("    <b>smil-files:</b> " + str(audiobook.smil_count) + "\n")
            validation_report.write("    <b>master.smil files:</b> " + str(audiobook.master_smil_count) + "\n")
            validation_report.write("    <b>image files:</b> " + str(audiobook.image_count) + "\n")
            validation_report.write("    <b>css files:</b> " + str(audiobook.css_count) + "\n")
            validation_report.write("    <b>other files:</b> " + str(audiobook.other_filetype_count) + "\n")
            validation_report.write("</pre>\n\n")

            validation_report.write("<h3>Metadata</h3>\n")

            validation_report.write("<pre style=""font-size:18px;"">\n")
            validation_report.write("    <b>dc:title:</b> " + audiobook.dc_title + "\n")
            validation_report.write("    <b>dc:creator:</b> " + audiobook.dc_creator + "\n")
            validation_report.write("    <b>dc:date:</b> " + audiobook.dc_date + "\n")
            validation_report.write("    <b>dc:identifier:</b> " + audiobook.dc_identifier + "\n")
            validation_report.write("    <b>dc:language:</b> " + audiobook.dc_language + "\n")
            validation_report.write("    <b>dc:publisher:</b> " + audiobook.dc_publisher + "\n")
            validation_report.write("    <b>dc:source:</b> " + audiobook.dc_source + "\n")
            validation_report.write("    <b>ncc:narrator:</b> " + audiobook.ncc_narrator + "\n")
            validation_report.write("    <b>ncc:producer:</b> " + audiobook.ncc_producer + "\n")
            validation_report.write("    <b>dc:format:</b> " + audiobook.dc_format + "\n")
            validation_report.write("    <b>ncc:generator:</b> " + audiobook.ncc_generator + "\n")
            validation_report.write("    <b>prod:prevGenerator:</b> " + audiobook.prod_prev_generator + "\n")
            validation_report.write("    <b>ncc:pageNormal:</b> " + audiobook.ncc_page_normal + "\n")
            validation_report.write("    <b>ncc:pageFront:</b> " + audiobook.ncc_page_front + "\n")
            validation_report.write("    <b>ncc:pageSpecial:</b> " + audiobook.ncc_page_special + "\n")
            validation_report.write("    <b>ncc:depth:</b> " + audiobook.ncc_depth + "\n")
            validation_report.write("    <b>ncc:charset:</b> " + audiobook.ncc_charset + "\n")
            validation_report.write("    <b>ncc:multimediaType:</b> " + audiobook.ncc_multimedia_type + "\n")
            validation_report.write("    <b>ncc:tocItems:</b> " + audiobook.ncc_toc_items + "\n")
            validation_report.write("    <b>ncc:totalTime:</b> " + audiobook.ncc_total_time + "\n")
            validation_report.write("    <b>ncc:files:</b> " + audiobook.ncc_files + "\n")
            validation_report.write("</pre>\n\n")

        validation_report.write("<h2>END OF VALIDATION REPORT</h2>\n\n")

        validation_report.write('<p style="width: 100%; text-align: center; font-size: 12px; font-style: italic; font-weight: bold;">')
        validation_report.write("CeliaDTBValidator v. 0.9.3 (" + program_versions[0] + "; " + program_versions[1])
        validation_report.write("; " + program_versions[2] + "; " + program_versions[3] + ")</p>\n")

        validation_report.write("</body>\n"
                                + "</html>")

        validation_report.close()
Beispiel #8
0
    def __init__(self, path):
        self.path = path
        self.kbps = 0.0
        self.lufs = 0.0
        self.pkdb = 0.0
        self.tpkdb = 0.0
        self.snr = 0.0
        self.files = []
        self.filename_prefix = ""
        self.daisy_validation_errors = []
        self.daisy_validation_warnings = []

        skip_daisy_checks = False
        if not ConfigGetter.get_configs("daisy_validation") == "1":
            skip_daisy_checks = True

        skip_audio_checks = False
        if not ConfigGetter.get_configs("audio_validation") == "1":
            skip_audio_checks = True

        if skip_audio_checks and skip_daisy_checks:
            input("Confuration set to skip daisy AND audio checks! Nothing to do...")
            sys.exit()

        print("Processing files...")

        files_in_folder = FileScrutinizer.list_files_in_folder(self.path)
        i = 0
        for f in files_in_folder:
            i += 1
            f_info = FileScrutinizer.find_fname_ftype(f)
            print("Processing file " + f_info[0] + "." + f_info[1] + " (" + str(i) + "/" + str(len(files_in_folder)) + ")")
            if (f_info[1].lower() == "wav" or f_info[1].lower() == "mp3") and not skip_audio_checks:
                new_file = AudioFile(f, f_info[0], f_info[1])
            else:
                new_file = GenericFile(f, f_info[0], f_info[1])
            self.files.append(new_file)

        self.ncc_count = 0
        self.audio_file_count = 0
        self.smil_count = 0
        self.master_smil_count = 0
        self.image_count = 0
        self.css_count = 0
        self.other_filetype_count = 0
        self.pagemark_errors = []

        file_count = DaisyScrutinizer.count_daisy_files(self.files)
        self.ncc_count = file_count[0]
        self.audio_file_count = file_count[1]
        self.smil_count = file_count[2]
        self.master_smil_count = file_count[3]
        self.image_count = file_count[4]
        self.css_count = file_count[5]
        self.other_filetype_count = file_count[6]

        if not skip_audio_checks:
            for f in self.files:
                if isinstance(f, AudioFile):
                    self.lufs += f.lufs
                    self.pkdb += f.pkdb
                    self.tpkdb += f.tpkdb
                    self.snr += f.snr
                    self.kbps += f.kbps

            self.lufs = self.lufs / self.audio_file_count
            self.pkdb = self.pkdb / self.audio_file_count
            self.tpkdb = self.tpkdb / self.audio_file_count
            self.snr = self.snr / self.audio_file_count
            self.kbps = self.kbps / self.audio_file_count

        # Daisy Checks...
        if not skip_daisy_checks:
            print("Processing book metadata...")

            charencoding = FileScrutinizer.get_encoding_tag(self.path + "/ncc.html")
            metadata_list = DaisyScrutinizer.get_metadata(self.path + "/ncc.html", charencoding)

            self.dc_title = metadata_list[0]
            self.dc_creator = metadata_list[1]
            self.dc_date = metadata_list[2]
            self.dc_identifier = metadata_list[3]
            self.dc_language = metadata_list[4]
            self.dc_publisher = metadata_list[5]
            self.dc_source = metadata_list[6]
            self.ncc_narrator = metadata_list[7]
            self.ncc_producer = metadata_list[8]
            self.dc_format = metadata_list[9]
            self.ncc_generator = metadata_list[10]
            self.ncc_page_normal = metadata_list[11]
            self.ncc_page_front = metadata_list[12]
            self.ncc_page_special = metadata_list[13]
            self.ncc_depth = metadata_list[14]
            self.ncc_charset = metadata_list[15]
            self.ncc_multimedia_type = metadata_list[16]
            self.ncc_toc_items = metadata_list[17]
            self.ncc_total_time = metadata_list[18]
            self.ncc_files = metadata_list[19]
            self.prod_prev_generator = ""

            for prev_gen in metadata_list[20]:
                if self.prod_prev_generator == "":
                    self.prod_prev_generator = prev_gen
                else:
                    self.prod_prev_generator = self.prod_prev_generator + ", " + prev_gen

            self.filename_prefix = FileScrutinizer.get_filename_prefix(self.files)

            self.pagemark_errors = []

            self.pagemark_errors = DaisyScrutinizer.find_pagemark_errors(self.files)

            self.first_smil_audio_references = DaisyScrutinizer.check_first_smil(self.files)

            print("Running daisy validator...")
            daisy_validation = DaisyScrutinizer.validate_daisy(self.path + "/ncc.html")
            self.daisy_validation_errors = daisy_validation[0]
            self.daisy_validation_warnings = daisy_validation[1]

        else:
            print("Skipping Daisy checks.")
            self.dc_title = ""
            self.dc_creator = ""
            self.dc_date = ""
            self.dc_identifier = ""
            self.dc_language = ""
            self.dc_publisher = ""
            self.dc_source = ""
            self.ncc_narrator = ""
            self.ncc_producer = ""
            self.dc_format = ""
            self.ncc_generator = ""
            self.ncc_page_normal = ""
            self.ncc_page_front = ""
            self.ncc_page_special = ""
            self.ncc_depth = ""
            self.ncc_charset = ""
            self.ncc_multimedia_type = ""
            self.ncc_toc_items = ""
            self.ncc_total_time = ""
            self.ncc_files = ""
            self.prod_prev_generator = ""

            self.filename_prefix = FileScrutinizer.get_filename_prefix(self.files)

            self.pagemark_errors = []

            self.first_smil_audio_references = 0

            self.daisy_validation_errors = []
            self.daisy_validation_warnings = []
    def find_silence(f_path):
        print(
            "     Checking for unexpected silences at start, end or middle of file"
        )
        silence_db = ConfigGetter.get_configs("silence_db")
        if silence_db == "":
            silence_db = "-26"

        try:
            silence_db = int(silence_db)
        except:
            print(
                "      !Error while reading silence_db from config.txt, using default value!"
            )
            silence_db = -26

        silence_check = []

        no_start_silence = True
        start_silence_max = ConfigGetter.get_configs("start_silence_max")
        if not re.search(r'^\d:\d\d\.\d\d\d$', start_silence_max.strip()):
            print(
                "      !Error while reading start_silence_max from config.txt, using default value!"
            )
            start_silence_max = "0:01.001"
        else:
            start_silence_max = start_silence_max.strip()

        #ffmpeg_path = str(pathlib.Path(ConfigGetter.get_configs("ffmpeg_path")))
        ffmpeg_path = ConfigGetter.get_configs("ffmpeg_path")
        if ffmpeg_path != "ffmpeg":
            ffmpeg_path = str(pathlib.Path(ffmpeg_path).absolute())
        # print("FFmpeg at " + ffmpeg_path)
        start_silence_max_ffmpeg_cmd = 'cmd /c ""' + ffmpeg_path + '" -ss 00:00:00 -nostats -i "' + f_path + '" -to 00:0' + start_silence_max + ' -filter_complex ebur128=peak=true -f null - 2>&1"'

        raw_output = ExternalProgramCaller.run_external_command(
            start_silence_max_ffmpeg_cmd).splitlines()
        if get_peak(raw_output) < silence_db:
            no_start_silence = False

        silence_at_end = True
        end_silence_min = ConfigGetter.get_configs("end_silence_min")

        if not re.search(r'^\d:\d\d\.\d\d\d$', end_silence_min.strip()):
            print(
                "      !Error while reading end_silence_min from config.txt, using default value!"
            )
            end_silence_min = "0:01.801"
        else:
            end_silence_min = end_silence_min.strip()

        end_silence_min_ffmpeg_cmd = 'cmd /c ""' + ffmpeg_path + '" -sseof -00:0' + end_silence_min + ' -nostats -i "' + f_path + '" -filter_complex ebur128=peak=true -f null - 2>&1"'

        raw_output = ExternalProgramCaller.run_external_command(
            end_silence_min_ffmpeg_cmd).splitlines()

        if get_peak(raw_output) > silence_db:
            silence_at_end = False

        no_unexpected_silence_at_end = True
        end_silence_max = ConfigGetter.get_configs("end_silence_max")

        if not re.search(r'^\d:\d\d\.\d\d\d$', end_silence_max.strip()):
            print(
                "      !Error while reading end_silence_max from config.txt, using default value!"
            )
            end_silence_max = "0:15.001"
        else:
            end_silence_max = end_silence_max.strip()

        end_silence_max_ffmpeg_cmd = 'cmd /c ""' + ffmpeg_path + '" -sseof -00:0' + end_silence_max + ' -nostats -i "' + f_path + '" -filter_complex ebur128=peak=true -f null - 2>&1"'

        raw_output = ExternalProgramCaller.run_external_command(
            end_silence_max_ffmpeg_cmd).splitlines()

        if get_peak(raw_output) < silence_db:
            no_unexpected_silence_at_end = False

        mid_silence_max = ConfigGetter.get_configs("mid_silence_max")

        try:
            mid_silence_max_int = int(mid_silence_max)
            mid_silence_max = str(mid_silence_max_int)
        except:
            print(
                "      !Error while reading mid_silence_max from config.txt, using default value!"
            )
            mid_silence_max = "7"

        mid_silences = []
        mid_silence_cmd = 'cmd /c ""' + ffmpeg_path + '" -nostats -i "' \
                          + f_path + '" -af silencedetect=noise=' + str(silence_db) + 'dB:d=' \
                          + mid_silence_max + ' -f null - 2>&1 "'
        #print(mid_silence_cmd)

        raw_output = ExternalProgramCaller.run_external_command(
            mid_silence_cmd).splitlines()

        for line in raw_output:
            #print(line)
            if re.search(r".+silence_end.+", line):
                line = re.sub(r".+silence_end: ", r"", line)
                pieces = line.split(" | ")
                silence_length = re.sub(r"silence_duration: ", r"", pieces[1])
                silence_at = float(pieces[0])
                silence_at_hour = int(silence_at // 3600)
                silence_at_hour_str = str(silence_at_hour)
                silence_at_min = int(
                    (silence_at - silence_at_hour * 60 * 60) // 60)
                if silence_at_min < 10:
                    silence_at_min_str = "0" + str(silence_at_min)
                else:
                    silence_at_min_str = str(silence_at_min)
                silence_at_sec_ms = silence_at - (silence_at_hour * 60 *
                                                  60) - (silence_at_min * 60)
                silence_at_sec_ms_tmp = str(silence_at_sec_ms).split(".")
                silence_at_sec = int(silence_at_sec_ms_tmp[0])
                silence_at_ms = float("0." + silence_at_sec_ms_tmp[1])
                silence_at_ms = float(format(silence_at_ms, ".3f"))
                silence_at_sec_ms = silence_at_sec + silence_at_ms
                if silence_at_sec_ms < 10:
                    silence_at_sec_ms_str = "0" + format(
                        silence_at_sec_ms, ".3f")
                else:
                    silence_at_sec_ms_str = format(silence_at_sec_ms, ".3f")
                silence_at_fmt = silence_at_hour_str + ":" + silence_at_min_str + ":" + silence_at_sec_ms_str

                mid_silences.append("Unexpected silence found at " +
                                    silence_at_fmt + " (Silence duration: " +
                                    format(float(silence_length), ".3f") +
                                    " sec)")

        silence_check.append(no_start_silence)
        silence_check.append(silence_at_end)
        silence_check.append(no_unexpected_silence_at_end)
        silence_check.append(mid_silences)

        return silence_check
    def get_stats(f_path):
        print("     Calculating lufs & peak levels")

        stats = []

        lufs = 0.0
        pkdb = 0.0
        tpkdb = 0.0
        snr = 0.0
        kbps = 0.0

        # general stats with ffmpeg
        ffmpeg_path = ConfigGetter.get_configs("ffmpeg_path")
        if ffmpeg_path != "ffmpeg":
            ffmpeg_path = str(pathlib.Path(ffmpeg_path).absolute())

    # print("FFmpeg at " + ffmpeg_path)
        ffmpeg_cmd_lufs = 'cmd /c ""' + ffmpeg_path + '" -nostats -i "' + f_path + '" -filter_complex ebur128=peak=true -f null - 2>&1"'
        ffmpeg_cmd_stats = 'cmd /c ""' + ffmpeg_path + '" -i "' + f_path + '" -af astats -f null - 2>&1"'
        #input(ffmpeg_cmd_stats)

        raw_output_lufs = ExternalProgramCaller.run_external_command(
            ffmpeg_cmd_lufs).splitlines()
        raw_output_stats = ExternalProgramCaller.run_external_command(
            ffmpeg_cmd_stats).splitlines()

        rms_trough_db = 0.0
        rms_peak_db = 0.0

        for line in reversed(raw_output_lufs):
            #input(line)
            if re.search(r".+Peak", line):
                line = re.sub(r".+Peak: *", "", line)
                tpkdb = float(re.sub(r" dBFS", "", line))
            if re.search(r" *I: .+ LUFS", line):
                line = re.sub(r" *I: *", "", line)
                line = re.sub(r" LUFS", "", line)
                lufs = round(float(line), 2)
                break

        for line in raw_output_lufs:
            if re.search(r".+bitrate:", line):
                line = re.sub(r".+bitrate: *", r"", line)
                line = re.sub(r" kb.+", r"", line)
                kbps = float(line)
                break

        for line in reversed(raw_output_stats):
            if re.search(r".+Peak level dB", line):
                line = re.sub(r".+Peak level dB: ", "", line)
                pkdb = round(float(line), 2)
                break
            if re.search(r".+RMS trough dB", line):
                line = re.sub(r".+RMS trough dB: ", "", line)
                rms_trough_db = round(float(line), 2)

            if re.search(r".+RMS peak dB", line):
                line = re.sub(r".+RMS peak dB: ", "", line)
                rms_peak_db = round(float(line), 2)

        snr = rms_peak_db - rms_trough_db

        stats.append(lufs)
        stats.append(pkdb)
        stats.append(tpkdb)
        stats.append(snr)
        stats.append(kbps)

        return stats