segment_finder.py 4.6 KB
Newer Older
Matteo's avatar
update    
Matteo committed
1
2
import os
import tempfile
Matteo's avatar
update  
Matteo committed
3
4
from uuid import uuid4

matteospanio's avatar
update    
matteospanio committed
5
6
7
import numpy as np
from ffmpeg import FFmpeg

Matteo's avatar
update  
Matteo committed
8
from mpai_cae_arp.audio import AudioWave, Noise
Matteo's avatar
update    
Matteo committed
9
from mpai_cae_arp.files import File, FileType
Matteo's avatar
update  
Matteo committed
10
from mpai_cae_arp.types.irregularity import Irregularity, IrregularityFile, Source
Matteo's avatar
update    
Matteo committed
11
from mpai_cae_arp.time import frames_to_seconds, seconds_to_frames, seconds_to_string, time_to_seconds
Matteo's avatar
update  
Matteo committed
12

matteospanio's avatar
update    
matteospanio committed
13
TMP_CHANNELS_MAP = os.path.join(tempfile.gettempdir(), "mpai", "channels_map.json")
Matteo's avatar
update  
Matteo committed
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

def calculate_offset(audio: AudioWave, video: AudioWave) -> float:
    """
    Calculates the offset between two audio files based on their cross-correlation.

    Parameters
    ----------
    audio : AudioWave
        The audio file to be used as reference.
    video : AudioWave
        The audio file to be used as target.
    
    Returns
    -------
    float
    """

matteospanio's avatar
update    
matteospanio committed
31
32
33
34
35
36
    corr = np.correlate(audio.array, video.array, mode="full")
    lags = np.arange(-len(audio.array) + 1, len(video.array))
    lag_idx = np.argmax(np.abs(corr))

    return lags[lag_idx] / audio.samplerate

Matteo's avatar
update  
Matteo committed
37

matteospanio's avatar
update    
matteospanio committed
38
39
40
41
42
43
44
45
46
47
48
def get_audio_from_video(video_src: str):
    # calculate offset with ffmpeg
    # ffmpeg -i video.mov -acodec pcm_s16le -ac 2 audio.wav
    ffmpeg = (
        FFmpeg()
        .input(video_src)
        .output(
            "out.wav",
            {""}
        )
    )
Matteo's avatar
update  
Matteo committed
49
50


Matteo's avatar
update    
Matteo committed
51
def get_irregularities_from_audio(audio_src: AudioWave) -> list[Irregularity]:
Matteo's avatar
update  
Matteo committed
52
    input_channels: list[AudioWave] = []
Matteo's avatar
update    
Matteo committed
53
54
55
56
57
58

    if audio_src.channels > 1:
        for channel in range(audio_src.channels):
            input_channels.append(audio_src.get_channel(channel))
    else:
        input_channels.append(audio_src)
Matteo's avatar
update    
Matteo committed
59
60

    channels_map = {}
Matteo's avatar
update  
Matteo committed
61
62

    irreg_list: list[Irregularity] = []
Matteo's avatar
update    
Matteo committed
63
    for idx, audio in enumerate(input_channels):
Matteo's avatar
update  
Matteo committed
64
65
66
67
68
69
        for _, noise_list in audio.get_silence_slices([
            Noise("A", -50, -63),
            Noise("B", -63, -69),
            Noise("C", -69, -72)],
            length=500).items():
            for start, _ in noise_list:
Matteo's avatar
update    
Matteo committed
70
                id = uuid4()
Matteo's avatar
update  
Matteo committed
71
72
                irreg_list.append(
                    Irregularity(
Matteo's avatar
update    
Matteo committed
73
                        irregularity_ID=id,
Matteo's avatar
update  
Matteo committed
74
                        source=Source.AUDIO,
Matteo's avatar
update    
Matteo committed
75
                        time_label= seconds_to_string(frames_to_seconds(start, audio.samplerate))
Matteo's avatar
update  
Matteo committed
76
77
                    )
                )
Matteo's avatar
update    
Matteo committed
78
                channels_map[str(id)] = idx
Matteo's avatar
update    
Matteo committed
79
80

    File(TMP_CHANNELS_MAP, FileType.JSON).write_content(channels_map)
Matteo's avatar
update  
Matteo committed
81
82
83

    return irreg_list

Matteo's avatar
update    
Matteo committed
84
85
86
87
88

def create_irreg_file(audio_src: str, video_src: str) -> IrregularityFile:

    audio = AudioWave.from_file(audio_src, bufferize=True)
    
matteospanio's avatar
update    
matteospanio committed
89
90


Matteo's avatar
update    
Matteo committed
91
    offset = calculate_offset(audio, video_src)
Matteo's avatar
update    
Matteo committed
92
93
94
95
96
    irregularities = get_irregularities_from_audio(audio)

    irregularities.sort(key=lambda x: time_to_seconds(x.time_label))
    
    return IrregularityFile(irregularities=irregularities, offset=offset)
Matteo's avatar
update    
Matteo committed
97
98
99
100


def merge_irreg_files(
    file1: IrregularityFile,
Matteo's avatar
update    
Matteo committed
101
102
103
104
105
106
107
108
109
110
    file2: IrregularityFile
) -> IrregularityFile:

    match file1.offset, file2.offset:
        case None, _:
            offset=file2.offset
        case _, None:
            offset=file1.offset
        case _, _:
            offset=max(file1.offset, file2.offset)
Matteo's avatar
update    
Matteo committed
111

Matteo's avatar
update    
Matteo committed
112
113
114
115
116
    irregularities = file1.irregularities + file2.irregularities
    irregularities.sort(key=lambda x: time_to_seconds(x.time_label))

    new_file = IrregularityFile(
        irregularities=irregularities, offset=offset)
Matteo's avatar
update    
Matteo committed
117
118
119
120
121

    return new_file


def extract_audio_irregularities(
Matteo's avatar
update    
Matteo committed
122
    audio_src: str,
Matteo's avatar
update    
Matteo committed
123
    irreg_file: IrregularityFile,
Matteo's avatar
update    
Matteo committed
124
    path: str
Matteo's avatar
update    
Matteo committed
125
) -> IrregularityFile:
Matteo's avatar
update    
Matteo committed
126

Matteo's avatar
update    
Matteo committed
127
    channels_map = File(TMP_CHANNELS_MAP, FileType.JSON).get_content()
Matteo's avatar
update    
Matteo committed
128
129
130
    os.makedirs(f"{path}/AudioBlocks", exist_ok=True)

    audio = AudioWave.from_file(audio_src, bufferize=True)
Matteo's avatar
update    
Matteo committed
131
    for irreg in irreg_file.irregularities:
Matteo's avatar
update    
Matteo committed
132
133
        if channels_map.get(str(irreg.irregularity_ID)) is None:
            audio[seconds_to_frames(
Matteo's avatar
update    
Matteo committed
134
                        time_to_seconds(irreg.time_label), audio.samplerate
Matteo's avatar
update    
Matteo committed
135
                    ):seconds_to_frames(
Matteo's avatar
update    
Matteo committed
136
                        time_to_seconds(irreg.time_label), audio.samplerate)+audio.samplerate//2]\
Matteo's avatar
update    
Matteo committed
137
138
139
140
141
142
143
144
                .save(f"{path}/AudioBlocks/{irreg.irregularity_ID}.wav")
        else:
            audio.get_channel(channels_map[str(irreg.irregularity_ID)])[
                    seconds_to_frames(
                        time_to_seconds(irreg.time_label), audio.samplerate
                    ):seconds_to_frames(
                        time_to_seconds(irreg.time_label), audio.samplerate)+audio.samplerate//2]\
                .save(f"{path}/AudioBlocks/{irreg.irregularity_ID}.wav")
Matteo's avatar
update    
Matteo committed
145
        irreg.audio_block_URI = f"{path}/AudioBlocks/{irreg.irregularity_ID}.wav"
Matteo's avatar
update    
Matteo committed
146
    os.remove(TMP_CHANNELS_MAP)
Matteo's avatar
update    
Matteo committed
147
148

    return irreg_file