Commit 925bcbb2 authored by Archit Tamarapu's avatar Archit Tamarapu
Browse files

[ci] add first verison loudness measurement job

parent 507fd13e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
variables:
  # note: GitLab cannot reference variables defined by users in the include ref:, we need to use a YAML anchor for this
  # see https://docs.gitlab.com/ci/yaml/includes/#use-variables-with-include for more information
  IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF main
  IVAS_CODEC_CI_REF: &IVAS_CODEC_CI_REF tmu/loudness-measurement
  # If you need to set some config variable only in a local branch, then add an overwrite here
  # One example is DISABLE_HRTF - this will be set on a branch which is about to be merged and will be removed in a subsequent second MR
  # this is more easily done directly here in the child repo
+43 −0
Original line number Diff line number Diff line
{
    "afspPath": "not_needed",
    "utilPath": "/tools",
    "inpaths": {
        "MONO": "/usr/local/testv/pinknoise/MONO.wav",
        "STEREO": "/usr/local/testv/pinknoise/STEREO.wav",
        "FOA": "/usr/local/testv/pinknoise/FOA.wav",
        "HOA2": "/usr/local/testv/pinknoise/HOA2.wav",
        "HOA3": "/usr/local/testv/pinknoise/HOA3.wav",
        "SBA": "/usr/local/testv/pinknoise/HOA3.wav",
        "MASA1TC": "/usr/local/testv/pinknoise/MASA1DIR1.wav",
        "MASA2TC": "/usr/local/testv/pinknoise/MASA2DIR2.wav",
        "5_1": "/usr/local/testv/pinknoise/5_1.wav",
        "5_1_2": "/usr/local/testv/pinknoise/5_1_2.wav",
        "5_1_4": "/usr/local/testv/pinknoise/5_1_4.wav",
        "7_1": "/usr/local/testv/pinknoise/7_1.wav",
        "7_1_4": "/usr/local/testv/pinknoise/7_1_4.wav",
        "ISM1": "/usr/local/testv/pinknoise/ISM1.wav",
        "ISM2": "/usr/local/testv/pinknoise/ISM2.wav",
        "ISM3": "/usr/local/testv/pinknoise/ISM3.wav",
        "ISM4": "/usr/local/testv/pinknoise/ISM4.wav",
        "OMASA_ISM1_1TC": "/usr/local/testv/pinknoise/ISM1MASA1DIR1.wav",
        "OMASA_ISM1_2TC": "/usr/local/testv/pinknoise/ISM1MASA2DIR2.wav",
        "OMASA_ISM2_1TC": "/usr/local/testv/pinknoise/ISM2MASA1DIR1.wav",
        "OMASA_ISM2_2TC": "/usr/local/testv/pinknoise/ISM2MASA2DIR2.wav",
        "OMASA_ISM3_1TC": "/usr/local/testv/pinknoise/ISM3MASA1DIR1.wav",
        "OMASA_ISM3_2TC": "/usr/local/testv/pinknoise/ISM3MASA2DIR2.wav",
        "OMASA_ISM4_1TC": "/usr/local/testv/pinknoise/ISM4MASA1DIR1.wav",
        "OMASA_ISM4_2TC": "/usr/local/testv/pinknoise/ISM4MASA2DIR2.wav",
        "OSBA_ISM1_FOA":      "/usr/local/testv/pinknoise/ISM1SBA1.wav",
        "OSBA_ISM1_HOA2":     "/usr/local/testv/pinknoise/ISM1SBA2.wav",
        "OSBA_ISM1_HOA3":     "/usr/local/testv/pinknoise/ISM1SBA3.wav",
        "OSBA_ISM2_FOA":      "/usr/local/testv/pinknoise/ISM2SBA1.wav",
        "OSBA_ISM2_HOA2":     "/usr/local/testv/pinknoise/ISM2SBA2.wav",
        "OSBA_ISM2_HOA3":     "/usr/local/testv/pinknoise/ISM2SBA3.wav",
        "OSBA_ISM3_FOA":      "/usr/local/testv/pinknoise/ISM3SBA1.wav",
        "OSBA_ISM3_HOA2":     "/usr/local/testv/pinknoise/ISM3SBA2.wav",
        "OSBA_ISM3_HOA3":     "/usr/local/testv/pinknoise/ISM3SBA3.wav",
        "OSBA_ISM4_FOA":      "/usr/local/testv/pinknoise/ISM4SBA1.wav",
        "OSBA_ISM4_HOA2":     "/usr/local/testv/pinknoise/ISM4SBA2.wav",
        "OSBA_ISM4_HOA3":     "/usr/local/testv/pinknoise/ISM4SBA3.wav"
    }
}
+214 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
import pandas as pd
import logging
import re
import sys
import time
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed

sys.path.append("./ivas-processing-scripts")
from ivas_processing_scripts.audiotools.wrappers.bs1770 import get_loudness
from ivas_processing_scripts.audiotools.audio import fromfile
from ivas_processing_scripts.utils import progressbar_update, spinner

logging.basicConfig(level=logging.WARNING)

INPUT_FOLDER_BASE = Path(__file__).parent.joinpath("testv", "pinknoise")
OUTPUT_FOLDER = Path(__file__).parent.parent.joinpath("out/dec")
FORMATS = [
    "MONO",
    "STEREO",
    "5_1",
    "5_1_2",
    "5_1_4",
    "7_1",
    "7_1_4",
    "ISM1",
    "ISM2",
    "ISM3",
    "ISM4",
    "MASA1DIR1",
    "MASA1DIR2",
    "MASA2DIR1",
    "MASA2DIR2",
    "FOA",
    "HOA2",
    "HOA3",
    "ISM1SBA1",
    "ISM2SBA1",
    "ISM3SBA1",
    "ISM4SBA1",
    "ISM1SBA2",
    "ISM2SBA2",
    "ISM3SBA2",
    "ISM4SBA2",
    "ISM1SBA3",
    "ISM2SBA3",
    "ISM3SBA3",
    "ISM4SBA3",
    "ISM1MASA1DIR1",
    "ISM2MASA1DIR1",
    "ISM3MASA1DIR1",
    "ISM4MASA1DIR1",
    "ISM1MASA1DIR2",
    "ISM2MASA1DIR2",
    "ISM3MASA1DIR2",
    "ISM4MASA1DIR2",
    "ISM1MASA2DIR1",
    "ISM2MASA2DIR1",
    "ISM3MASA2DIR1",
    "ISM4MASA2DIR1",
    "ISM1MASA2DIR2",
    "ISM2MASA2DIR2",
    "ISM3MASA2DIR2",
    "ISM4MASA2DIR2",
]

FORMAT_2_FILE = {
    fmt: path
    for fmt, path in zip(
        FORMATS, [INPUT_FOLDER_BASE.joinpath(f"{f}.wav") for f in FORMATS]
    )
}
PATTERN_BITRATE = re.compile(r"b([\d_]*|all)(_dtx)?_(swb|wb|fb)")
RESULT_OUTPUT_FILE = Path(__file__).parent.parent.joinpath("loudness.csv")


def get_metadata_from_outfile_name(outfile, infile_stem):
    outfile_tail = outfile.name.replace(f"{infile_stem}_", "", 1)
    mode_string, dec, outformat, suffix = outfile_tail.split(".")
    assert dec == "dec"
    assert suffix == "wav"

    match = re.search(PATTERN_BITRATE, mode_string)
    assert match is not None
    bitrate_str, dtx, bandwidth = match.groups()
    bitrate = float(bitrate_str.replace("_", "."))

    return outformat, bitrate, dtx is not None, bandwidth


def process_output_file(outfile, infile, format, input_loudness, input_loudness_format):
    """Process a single output file"""
    try:
        outformat, bitrate, dtx, bandwidth = get_metadata_from_outfile_name(
            outfile, infile.stem
        )

        if outformat == "EXT":
            return None

        output_audio = fromfile(outformat.upper(), outfile)
        output_loudness, _, output_loudness_format = get_loudness(output_audio)

        return {
            "infile": infile.name,
            "format": format,
            "input_loudness": input_loudness,
            "input_loudness_format": input_loudness_format,
            "outformat": outformat,
            "bitrate": bitrate,
            "bandwidth": bandwidth,
            "dtx": dtx,
            "output_loudness": output_loudness,
            "output_loudness_format": output_loudness_format,
        }
    except (AssertionError, ValueError) as e:
        print(f"\n⚠️  Skipping {outfile.stem}: {e}", file=sys.stderr)
        return None


def main():
    formats = FORMAT_2_FILE.keys()
    results = []

    input_audio_cache = {}
    tasks = []

    for format in formats:
        infile = FORMAT_2_FILE[format]
        output_folder = OUTPUT_FOLDER

        # Load and cache input audio once per format
        if format not in input_audio_cache:
            input_audio = fromfile(format, infile)
            input_loudness, _, input_loudness_format = get_loudness(input_audio)
            input_audio_cache[format] = (input_loudness, input_loudness_format)

        input_loudness, input_loudness_format = input_audio_cache[format]

        # Find all output files for this format
        output_files = [
            f
            for f in output_folder.glob("*.dec.*.wav")
            if f.stem.startswith(infile.stem)
        ]

        for outfile in output_files:
            tasks.append(
                (outfile, infile, format, input_loudness, input_loudness_format)
            )

    print(f"Found {len(tasks)} files to process across {len(formats)} format(s)\n")
    if not len(tasks):
        print("Nothing to do, exiting...")
        exit(-1)

    # Process in parallel with progress bar
    print(f"⏳ Processing {len(tasks)} audio files:")
    start_time = time.time()
    completed = 0
    total = len(tasks)

    progressbar_update(0, total, width=50)

    with ThreadPoolExecutor(max_workers=8) as executor:
        # Submit all tasks
        futures = {
            executor.submit(process_output_file, *task): task[0] for task in tasks
        }

        # Process with animated spinner and progress bar
        while futures:
            done = set()
            for future in as_completed(futures, timeout=0.1):
                done.add(future)
                outfile = futures[future]

                try:
                    result = future.result()
                    if result is not None:
                        results.append(result)
                except Exception as e:
                    print(f"\n❌ Error processing {outfile.name}: {e}", file=sys.stderr)
                    # Reprint progress bar after error
                    progressbar_update(completed, total, width=50)

                completed += 1
                progressbar_update(completed, total, width=50)

            # Remove completed futures
            for future in done:
                del futures[future]

            # Animate spinner even when waiting
            if futures:
                spinner()

    print()  # New line after progress bar

    # Save results
    elapsed = time.time() - start_time
    print(f"💾 Saving {len(results)} results to {RESULT_OUTPUT_FILE}")
    df = pd.DataFrame(results)
    df.to_csv(RESULT_OUTPUT_FILE, index=False)

    print(
        f"✅ Processed {len(results)}/{len(tasks)} files successfully in {elapsed:.1f}s"
    )
    print(f"   Average: {len(results)/elapsed:.1f} files/second")


if __name__ == "__main__":
    main()
+132 B

File added.

No diff preview for this file type.

+133 B

File added.

No diff preview for this file type.

Loading