Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why won't it save my audio when I use this with SoundFile? #153

Open
ughstudios opened this issue Aug 1, 2022 · 4 comments
Open

Why won't it save my audio when I use this with SoundFile? #153

ughstudios opened this issue Aug 1, 2022 · 4 comments

Comments

@ughstudios
Copy link

I have the following code. The concept here is that as long as get_is_recording() is returning true, then we call "capture_frames" which simply parses the data, and adds it to a list. But when I try to write it, it's always being written to a file with 0 kb.

What's wrong here?

def save_to_disk(self) -> None:
        error = 0
        if isinstance(self.is_recording, ValueProxy):
            error = self.is_recording.value
        else:
            error = self.is_recording

        if error:
            raise RuntimeError('Cannot save to disk while still recording.')

        soundfile.write(self.filename, self.audio_frames, self.bitrate, format='WAV', subtype='PCM_16')

    def stop_recording(self) -> None:
        self.set_recording(0)
        self.save_to_disk()

    def update(self) -> None:
        while self.get_is_recording():
            self.capture_frames()
        self.stop_recording()

def capture_frames(self):
        data = self.microphone.record(self.bitrate, self.bitrate, 2)
        print(data)
        self.audio_frames.append(data)
@ughstudios
Copy link
Author

Anyone?

@Chum4k3r
Copy link

Chum4k3r commented Aug 1, 2022 via email

@ughstudios
Copy link
Author

""" provides functionality for recording the screen """
import os
import time
from typing import Optional, Union
from datetime import datetime
from loguru import logger

from multiprocess import Process, Manager  # pylint: disable=no-name-in-module
from multiprocess.managers import Value, ValueProxy
import soundcard
import soundfile
import cv2
import numpy
import pyautogui
import ffmpeg

from league_helper.decorators import function_group_register, memoize
from league_helper.qt_settings_generated import show_live_recording
from league_helper import paths
from league_helper import youtube


def get_timestamp_str() -> str:
    return datetime.now().strftime("%Y-%m-%d_%I-%M-%S_%p")


def get_video_recording_file() -> str:
    return os.path.join(paths.get_recordings_directory(), f'LOL_{get_timestamp_str()}.avi')


def get_audio_recording_file() -> str:
    return os.path.join(paths.get_recordings_directory(), f'LOL_{get_timestamp_str()}.wav')


class VideoRecorder:
    """ Do not instance directly, call get_match_recorder singleton instead """
    def __init__(self, filename: Optional[str] = get_video_recording_file()) -> None:
        self.screen_size: tuple[int, int] = pyautogui.size()
        self.codec = cv2.VideoWriter_fourcc(*'XVID')
        self.is_recording: Union[int, Value] = 0
        self.fps: int = 12
        self.filename = filename
        self.output = cv2.VideoWriter(self.filename, self.codec, self.fps, (self.screen_size))

    def set_recording(self, new_value: int) -> None:
        if isinstance(self.is_recording, ValueProxy):
            self.is_recording.value = new_value
        else:
            self.is_recording = new_value

    def get_is_recording(self) -> int:
        if isinstance(self.is_recording, ValueProxy):
            return self.is_recording.value
        return self.is_recording

    def capture_frames(self) -> None:
        screenshot = pyautogui.screenshot()
        frame = numpy.array(screenshot)
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        self.output.write(frame)
        if show_live_recording.get():
            cv2.imshow('League of Legends live recording', frame)
            cv2.waitKey(1)

    def update(self) -> None:
        while self.get_is_recording():
            self.capture_frames()
        self.stop_recording()

    def start_recording(self) -> None:
        self.set_recording(1)
        self.update()

    def stop_recording(self) -> None:
        self.set_recording(0)


class AudioRecorder:
    """ Do not instance directly, call get_match_recorder singleton instead """
    def __init__(self, filename: str = get_audio_recording_file()) -> None:
        self.bitrate = 44100
        self.audio_frames: list = []
        self.filename = filename
        self.is_recording: Union[ValueProxy, int] = 0
        self.default_speaker = soundcard.default_speaker()
        self.microphones = soundcard.all_microphones(include_loopback=True)
        for microphone in self.microphones:
            if microphone.name == self.default_speaker.name:
                self.microphone = microphone

    def set_recording(self, new_value: int) -> None:
        if isinstance(self.is_recording, ValueProxy):
            self.is_recording.value = new_value
        else:
            self.is_recording = new_value

    def get_is_recording(self) -> int:
        if isinstance(self.is_recording, ValueProxy):
            return self.is_recording.value
        return self.is_recording

    def save_to_disk(self) -> None:
        error = 0
        if isinstance(self.is_recording, ValueProxy):
            error = self.is_recording.value
        else:
            error = self.is_recording

        if error:
            raise RuntimeError('Cannot save to disk while still recording.')

        #soundfile.write(self.filename, *self.audio_frames, self.bitrate, format='WAV')

    def stop_recording(self) -> None:
        self.set_recording(0)
        self.save_to_disk()

    def update(self) -> None:
        while self.get_is_recording():
            self.capture_frames()
        self.stop_recording()

    def capture_frames(self):
        data = self.microphone.record(self.bitrate, self.bitrate, 2)
        print(data)
        soundfile.write(self.filename, data, self.bitrate, format='WAV')
        #self.audio_frames.append(data)

    def start_recording(self) -> None:
        self.set_recording(1)
        self.update()


class MatchRecorder:
    """ Do not instance directly, call get_match_recorder singleton instead """
    def __init__(self) -> None:
        self.manager = Manager()
        self.recording = self.manager.Value('i', 0)  # Set to 0 or 1 instead of True or False
        self.video_filename = self.manager.Array('b', get_video_recording_file().encode())
        self.audio_filename = self.manager.Array('b', get_audio_recording_file().encode())
        self.video_filename_length = self.manager.Value('i', len(get_video_recording_file()))
        self.audio_filename_length = self.manager.Value('i', len(get_audio_recording_file()))
        self.video_filename = get_video_recording_file()
        self.audio_filename = get_audio_recording_file()

        self.video_process = Process(target=self.start_video_recording, args=[self.video_filename, self.audio_filename_length, self.recording])  # pylint: disable=not-callable
        self.audio_process = Process(target=self.start_audio_recording, args=[self.audio_filename, self.video_filename_length, self.recording])  # pylint: disable=not-callable

    def start_recording(self) -> None:
        if self.recording.value:
            logger.info('Cannot start recording while a recording is in progress.')
            return
        self.recording.value = 1
        self.video_process.start()
        self.audio_process.start()

    def stop_recording(self) -> None:
        if not self.recording.value:
            logger.error('There is no recording to stop.')
            return
        self.recording.value = 0

    def start_video_recording(self, video_filename: str, video_filename_length: Value, is_recording: Value) -> None:
        filename = video_filename[0:video_filename_length.value]
        logger.info(f'Saving video file to: {filename}')
        video = VideoRecorder(filename)
        video.is_recording = is_recording
        video.start_recording()

    def start_audio_recording(self, audio_filename: str, audio_filename_length: Value, is_recording: Value) -> None:
        filename = audio_filename[0:audio_filename_length.value]
        logger.info(f'Saving audio file to: {filename}')
        audio = AudioRecorder(filename)
        audio.is_recording = is_recording
        audio.start_recording()


def merge_audio_and_video(video_path: str, audio_path: str) -> str:
    """ Combines the audio and video recordings into a single file
        https://ffmpeg.org/ffmpeg-filters.html#concat
        Returns the output file location
    """
    video = ffmpeg.input(video_path)
    audio = ffmpeg.input(audio_path)
    output = video_path.replace('.avi', '.mp4')
    ffmpeg.concat(video, audio, v=1, a=1).output(output).run(cmd=paths.get_ffmpeg_exe_path())
    logger.info(f'Files have been merged into a single file: {output}')
    return output


@memoize
def get_match_recorder() -> MatchRecorder:
    return MatchRecorder()


def delete_temp_recordings(recorder: MatchRecorder) -> None:
    """ After a recording has finished and has been merged with ffmpeg, we delete the separate (audio and video) recordings since we have a full recording with both merged.
        This is done to save on disk space.
    """
    filepaths = (recorder.audio_filename, recorder.video_filename)
    for filepath in filepaths:
        logger.info(f'Removing temporary recording file {filepath}')
        os.remove(filepath)


def start_recording_match() -> None:
    recorder = get_match_recorder()
    recorder.start_recording()


def stop_and_upload_match() -> None:
    recorder = get_match_recorder()
    recorder.stop_recording()
    output = merge_audio_and_video(recorder.video_filename, recorder.audio_filename)
    delete_temp_recordings(recorder)
    video_title = os.path.basename(recorder.video_filename)
    video = youtube.VideoMetaData(video_title, '', [], 'gaming', license='creativeCommon', filepath=output)
    youtube.upload_video(video)


def start():
    start_recording_match()
    time.sleep(5)
    stop_and_upload_match()

Here's an example directly from my code. Look at the function at the bottom, that's the entrypoint

@bastibe
Copy link
Owner

bastibe commented Aug 2, 2022

If you want help, put in some effort. Build a minimal example code, not a three-screen dump.

Your problem is, though, write expects a 1D or 2D numpy array, but you're providing a list of numpy arrays.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants