Toy Project:自動汚い言葉フィルタの作成


✔目標
Google CloudのSTT(Speech-to-Text)を利用して、汚い言葉を自動的にフィルタリングするプログラムを作成したいです.
(出典:YouTube-パン型発展途上国/学習目的)
1. STT(Speech-To-Text)
音声インタフェースによるテキストデータの抽出
google doc
  • 音声からテキストへの3つの主要な音声認識方法があります.
  • 同期識別(REST,GRPC)は、オーディオデータをSpeech-to-Text APIに送信し、データを識別し、すべてのオーディオを処理した後に結果を確認する.同期認識要求の対象は、1分未満の音声データに限られる.
  • 非同期識別(RESTおよびGRPC)は、音声データをテキストAPIに送信し、長期的な実行を開始する.この操作を使用して、定期的に識別結果をポーリングします.最大480分のオーディオデータについては、非同期リクエストを使用します.
  • ストリーム識別(GRPCのみ)は、GRPC双方向ストリームにおいて提供されるオーディオデータを識別することができる.ストリーミング要求は、例えばマイクロフォンからリアルタイムオーディオを取得するなど、リアルタイムで感知することを目的とする.ストリーム識別は、オーディオキャプチャ中に中間結果を提供するため、話を続けるときに結果を表示できます.
  • デフォルトでは、Google Cloud STT APIはクライアントサーバ構造です.
    Google APIサーバにリクエストを送信すると、返信が届きます.
    2.コード説明
    ① Load Dependencies
    from google.cloud import speech_v1
    from pydub import AudioSegment # 간단한 오디오 편집 패키지
    import numpy as np
    import io
    ②_① Speech to Text (STT) function
    def sample_recognize(local_file_path):
        # instantiates a client
        client = speech_v1.SpeechClient() # 실제 클라이언트 만듬
        language_code = "ko-KR"
        sample_rate_hertz = 44100
        encoding = speech_v1.RecognitionConfig.AudioEncoding.ENCODING_UNSPECIFIED
        config = { 
            # (config의 arg 값을 설정)
            "language_code": language_code, 
            "sample_rate_hertz": sample_rate_hertz,
            "encoding": encoding, 
            "enable_word_time_offsets": True,
            "use_enhanced": True,
        }
    speech_v1.SpeechClient()実際のクライアントを作成する部分です.このクライアント関数を使用して、サーバにリクエストが送信されます.STTは基本的にオーディオデータを受信してテキスト形式に変換するため、オーディオデータについて基本的な説明が必要である.また,関数を介してサーバにオーディオデータを送信する場合,データだけでなくconfigも送信する.したがって、オーディオデータとともに伝達されるconfig値を設定することもできる.
    < argument >
    🔎 language_code: 실제로 입력 받는 오디오 파일의 언어 (Document를 통해 각 언어에 대한 key값 찾을 수 있음)
    🔎 sample rate: 오디오 데이터를 저장할 때 1초에 몇 개의 샘플로 쪼개서 저장하는지 의미
    🔎 encoding: 실제로 받는 오디오 파일의 타입
    🔎 enable_word_time_offsets: 단어 별로 해당 단어가 나온 시간 보여줌
    🔎 use_enhanced: 좀 더 향상된 STT model 사용할 수 있음
    ②_② Speech to Text (STT) function
        with io.open(local_file_path, "rb") as f:
            content = f.read()
        audio = {"content": content}
        response = client.recognize(config=config, audio=audio) 
        timeline, swear_timeline, words = [], [], []
        
        # swear_timeline: 욕 한 타임라인, words: 나온 단어들
        for result in response.results:
            alternative = result.alternatives[0]
            print(u"Transcript: {}".format(alternative.transcript))
            
            for word in alternative.words:
                timeline.append([
                    int(word.start_time.seconds * 1000 + word.start_time.seconds * (10**-6)),
                    int(word.end_time.seconds * 1000 + word.end_time.seconds * (10**-6))
                ]) # 단어의 시작 시간과 끝 시간 저장
                words.append(word.word) # 모든 단어 저장
                
                if '씨발' in word.word:
                    swear_timeline.append([
                        int(word.start_time.seconds * 1000 + word.start_time.seconds * (10**-6)),
                        int(word.end_time.seconds * 1000 + word.end_time.seconds * (10**-6))
                    ]) # 욕이 있으면 해당 욕의 타임라인 저장
        return timeline, swear_timeline, words
    client.認識(config=config,audio=audio)は、オーディオと定義されたconfigをクライアントに送信する部分である.
    クライアントだったのかこう書いてあります

    上記のエラーのため、検索後、ライブラリの更新(config=config、audio=audio)に伴い、parameterのキーワードがこのように使用されるべきであることがわかりました.だから、client.arrect(config=config,audio=audio)に変更することで、対応するエラーを解決できます.
    単語の開始時間と終了時間を保存するために、タイムライン、swave timeline、wordsをリスト形式に設定します.ワードに汚い言葉がある場合は、汚い言葉の開始時間と終了時間をswen timelineに保存します.
    ②_③ Speech to Text (STT) function
    timeline, swear_timeline, words = sample_recognize('sound/sound.mp3')
    print('timeline:', timeline)
    print('words:', words)
    print('swear_timeline:', swear_timeline)
    # --> 이 부분에서 삐 소리를 넣기 위해 따로 욕 타임라인 저장

    テキストの結果はよく出ました.(汚い言葉の出力部分がちょっと見苦しいので隠してしまいましたが・・・😅)
    時間軸の出力において、[00000]は「起床」が0から1秒の間で、[0001000]が1から1秒の間で「これ」を意味する.
    [📺私の小さなテレビ
    これはmp 3ファイルのオリジナルビデオです.面白いので一度見てもいいです.
    ③ Load Original Audio File
    sound = AudioSegment.from_file('sound/sound.mp3', format='mp3')
    print(len(sound))
    sound # 12.4초 짜리 오디오 파일
    PydubのAudioSegment.from fileでmp 3ファイルを読み込みます.
    from fileセクションにファイルが存在しますが.
    [Errno 2] 지정된 파일을 찾을 수 없습니다
    こんなエラーが発生しました.原因はわかりませんが、検索で
    conda install -c conda-forge ffmpeg
    これをインストールして解決しました.
    ④ Create Beep Sound
    def create_beep(duration):
        sps = 44100
        freq_hz = 1000.0
        vol = 0.5
        esm = np.arange(duration / 1000 * sps)
        wf = np.sin(2 * np.pi * esm * freq_hz / sps)
        wf_quiet = wf * vol
        wf_int = np.int16(wf_quiet * 32767)
        beep = AudioSegment(
            wf_int.tobytes(), 
            frame_rate=sps,
            sample_width=wf_int.dtype.itemsize, 
            channels=1
        )
        return beep
    beep = create_beep(duration=1000)
    beep
    汚い言葉
    ⑤ Overlay
    mixed_final = sound
    for i in range(len(swear_timeline)):
        beep = create_beep(duration=swear_timeline[i][1] - swear_timeline[i][0])
        mixed_final = mixed_final.overlay(beep, position=swear_timeline[i][0], gain_during_overlay=-20)
    mixed_final
    AudioSegment.オーディオAにオーディオBをoverlay()で追加します.
    < argument >
    🔎 position: 오디오B를 어디에 넣을지 위치 설정
    🔎 gain_during_overlay: 오리지널 사운드(오디오A)를 얼마나 줄일 것인지 설정
    ⑥ Export
    mixed_final.export('sound/result.mp3', format='mp3')
    AudioSegment.オーディオセグメントをexport()でファイルにエクスポートします.
    結果的には、精度は高くないものの、ある程度まではいけるようです.後ろに汚い言葉がたくさんあって、汚い言葉ではない他の言葉も処理されたり、汚い言葉の部分が処理されなかったりします.私たちはもっと正確な濾過方法を学ぶべきだと思います.
    3.完全なコード
    # Load Dependencies
    from google.cloud import speech_v1
    from pydub import AudioSegment # 간단한 오디오 편집 패키지
    import numpy as np
    import io
    
    # Speech to Text (STT)
    def sample_recognize(local_file_path):
        
        # instantiates a client
        client = speech_v1.SpeechClient()
    
        language_code = "ko-KR"
        sample_rate_hertz = 44100
    
        encoding = speech_v1.RecognitionConfig.AudioEncoding.ENCODING_UNSPECIFIED
        
        config = {
            "language_code": language_code,
            "sample_rate_hertz": sample_rate_hertz,
            "encoding": encoding,
            "enable_word_time_offsets": True,
            "use_enhanced": True,
        }
        
        with io.open(local_file_path, "rb") as f:
            content = f.read()
        
        audio = {"content": content}
    
        response = client.recognize(config=config, audio=audio)
        
        timeline, swear_timeline, words = [], [], []
    
        for result in response.results:
            alternative = result.alternatives[0]
            print(u"Transcript: {}".format(alternative.transcript))
            
            for word in alternative.words:
                timeline.append([
                    int(word.start_time.seconds * 1000 + word.start_time.seconds * (10**-6)),
                    int(word.end_time.seconds * 1000 + word.end_time.seconds * (10**-6))
                ])
                
                words.append(word.word) # 모든 단어 저장
    
                if '씨발' in word.word:
                    swear_timeline.append([
                        int(word.start_time.seconds * 1000 + word.start_time.seconds * (10**-6)),
                        int(word.end_time.seconds * 1000 + word.end_time.seconds * (10**-6))
                    ])
                    
        return timeline, swear_timeline, words
    
    
    timeline, swear_timeline, words = sample_recognize('sound/sound.mp3')
    
    print('timeline:', timeline)
    print('words:', words)
    print('swear_timeline:', swear_timeline)
    
    
    # Load Original Audio File
    sound = AudioSegment.from_file('sound/sound.mp3', format='mp3')
    print(len(sound))
    sound
    
    
    # Create Beep Sound
    def create_beep(duration):
        sps = 44100
        freq_hz = 1000.0
        vol = 0.5
    
        esm = np.arange(duration / 1000 * sps)
        wf = np.sin(2 * np.pi * esm * freq_hz / sps)
        wf_quiet = wf * vol
        wf_int = np.int16(wf_quiet * 32767)
    
        beep = AudioSegment(
            wf_int.tobytes(), 
            frame_rate=sps,
            sample_width=wf_int.dtype.itemsize, 
            channels=1
        )
        return beep
    
    beep = create_beep(duration=1000)
    beep
    
    # Overlay Partially
    # 첫번째 욕설 부분에 삐 소리 넣어보기
    i = 0
    mixed = sound.overlay(beep, position=swear_timeline[i][0], gain_during_overlay=-20)
    mixed
    
    # Result
    # 욕설 전체에 삐 소리 넣기
    mixed_final = sound
    
    for i in range(len(swear_timeline)):
        beep = create_beep(duration=swear_timeline[i][1] - swear_timeline[i][0])
        mixed_final = mixed_final.overlay(beep, position=swear_timeline[i][0], gain_during_overlay=-20)
        
    mixed_final
    
    # Export
    mixed_final.export('sound/result.mp3', format='mp3')