Esempio n. 1
0
class AudioFormat(GrooveClass):
    """Groove Audio Format"""
    _ffitype = 'struct GrooveAudioFormat *'

    sample_rate = utils.property_convert('sample_rate',
                                         from_cdef=int,
                                         doc="""Sample rate in Hz""")

    channel_layout = utils.property_convert(
        'channel_layout',
        from_cdef=ChannelLayout.__values__.get,
        doc="""ChanelLayout for the audio format""")

    sample_format = utils.property_convert(
        'sample_fmt',
        from_cdef=SampleFormat.__values__.get,
        doc="""SampleFormat for the audio format""")

    def __init__(self):
        self._obj = ffi.new(self._ffitype)

    def __eq__(self, rhs):
        if not isinstance(rhs, AudioFormat):
            return False
        return lib.groove_audio_formats_equal(self._obj, rhs._obj) == 1

    def clone(self, other):
        """Set this AudioFormat equal to another format"""
        self.sample_rate = other.sample_rate
        self.channel_layout = other.channel_layout
        self.sample_format = other.sample_format
Esempio n. 2
0
class LoudnessDetector(GrooveClass):
    """pass"""
    _ffitype = 'struct GrooveLoudnessDetector *'

    info_queue_size = utils.property_convert(
        'info_queue_size',
        int,
        doc="""Maximum number of items to store in this LoudnessDetector's
        queue

        This defaults to MAX_INT, meaning that the loudness detector will cause
        the decoder to decode the entire playlist. If you want to instead, for
        example, obtain loudness info at the same time as playback, you might
        set this value to 1.
        """)

    sink_buffer_size = utils.property_convert(
        'sink_buffer_size',
        int,
        doc="""How big the sink buffer should be, in sample frames

        LoudnessDetector defaults this to 8192
        """)

    disable_album = utils.property_convert(
        'disable_album',
        bool,
        doc="""Set True to only compute track loudness

        This is faster and requires less memory than computing both.
        LoudnessDetector defaults this to False
        """)

    @property
    def playlist(self):
        """Playlist to generate loudness info for"""
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        if self._playlist:
            assert lib.groove_loudness_detector_detach(self._obj) == 0
            self._playlist = None

        if value is not None:
            assert lib.groove_loudness_detector_attach(self._obj,
                                                       value._obj) == 0
            self._playlist = value

    def __init__(self):
        # TODO: error handling
        obj = lib.groove_loudness_detector_create()
        assert obj != ffi.NULL
        self._obj = ffi.gc(obj, lib.groove_loudness_detector_destroy)
        self._playlist = None

    def __del__(self):
        # Make sure playlist gets detached before we loose the obj
        if self.playlist is not None:
            self.playlist = None

    def __iter__(self):
        info_obj = ffi.new('struct GrooveLoudnessDetectorInfo *')
        pitem = True

        while pitem:
            status = lib.groove_loudness_detector_info_get(
                self._obj, info_obj, True)
            assert status >= 0
            if status != 1:
                break

            loudness = float(info_obj.loudness)
            peak = float(info_obj.peak)
            duration = float(info_obj.duration)

            if info_obj.item == ffi.NULL:
                pitem = None
            else:
                pitem = self.playlist._pitem(info_obj.item)

            yield LoudnessDetectorInfo(loudness, peak, duration, pitem)

    def info_peek(self, block=False):
        """Check if info is ready"""
        result = lib.groove_loudness_detector_info_peek(self._obj, block)
        assert result >= 0
        return bool(result)

    def position(self):
        """Get the current position of the printer head

        Returns:
            A tuple of (playlist_item, seconds). If the playlist is empty
            playlist_item will be None and seconds will be -1.0
        """
        pitem_obj_ptr = ffi.new('struct GroovePlaylistItem **')
        seconds = ffi.new('double *')
        lib.groove_loudness_detector_position(self._obj, pitem_obj_ptr,
                                              seconds)
        if pitem_obj_ptr[0] == ffi.NULL:
            pitem = None
        else:
            pitem = self.playlist._pitem(pitem_obj_ptr[0])
        return pitem, float(seconds[0])
Esempio n. 3
0
class Sink(GrooveClass):
    """Groove Sink"""
    _ffitype = 'struct GrooveSink *'
    BufferClass = Buffer

    disable_resample = utils.property_convert(
        'disable_resample',
        bool,
        doc="""Set this flag to ignore audio_format.

        If you set this flag, the buffers you pull from this sink could have
        any audio format.
        """)

    buffer_sample_count = utils.property_convert(
        'buffer_sample_count',
        int,
        doc="""Number of frames to pull into a buffer

        If set to the default of 0, groove will choose a sample count based on
        efficiency.
        """)

    buffer_size = utils.property_convert(
        'buffer_size',
        int,
        doc="""Buffer queue size in frames, default 8192""")

    @property
    def audio_format(self):
        """Set this to the audio format you want the sink to output"""
        return AudioFormat._from_obj(self._obj.format)

    @property
    def gain(self):
        """Volume adjustment for the audio sink

        It is recommended to leave this at 1.0 and adjust the playlist/item
        gain instead
        """
        return self._obj.gain

    @gain.setter
    def gain(self, value):
        lib.groove_sink_set_gain(self._obj, value)

    @property
    def playlist(self):
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        # TODO: better exception handling
        if self._obj.playlist != ffi.NULL:
            assert lib.groove_sink_detach(self._obj) == 0
        if value is not None:
            assert lib.groove_sink_attach(self._obj, value._obj) == 0
        self._playlist = value

    @property
    def bytes_per_sec(self):
        """Automatically computed from audio format when attached"""
        return self._obj.bytes_per_sec

    @classmethod
    def _from_obj(cls, obj):
        instance, created = super(Sink, cls)._from_obj(obj)
        if created:
            # TODO: is this safe? libgroove uses these callbacks internally
            #       but when it does I think the sink is not exposed
            instance._attach_callbacks()
        return instance, created

    def __init__(self):
        # TODO: better exception handling
        obj = lib.groove_sink_create()
        assert obj != ffi.NULL
        self._obj = ffi.gc(obj, lib.groove_sink_destroy)
        self._attach_callbacks()
        self._playlist = None

    def _attach_callbacks(self):
        self._obj.flush = lib.groove_sink_callback_flush
        self._obj.purge = lib.groove_sink_callback_purge
        self._obj.pause = lib.groove_sink_callback_pause
        self._obj.play = lib.groove_sink_callback_play

    def on_flush(self):
        """Called when the audio queue is flushed.

        For example if you seek to a different location in the song.
        """
        pass

    def on_purge(self, playlist_item):
        """Called when a playlist item is deleted.

        Take this opportunity to remove all references to the PlaylistItem.
        """
        pass

    def on_pause(self):
        """Called when a playlist is paused"""
        pass

    def on_play(self):
        """Called when a playlist is played"""
        pass

    def buffer_peek(self, block=True):
        """Returns True if a buffer is ready, False if not"""
        value = lib.groove_sink_buffer_peek(self._obj, block)
        assert value >= 0
        return value == 1

    def get_buffer(self, block=False):
        """Get the buffer on the sink

        If no buffer is ready, this raises `groove.Buffer.NotReady`
        If the end of the playlist is reached, this raises `groove.Buffer.End`
        If block is True and no buffer is ready, this may block indefinately
        """
        # TODO: add timeout, might have to be done in libgroove to be safe
        buff_obj_ptr = ffi.new('struct GrooveBuffer **')
        value = lib.groove_sink_buffer_get(self._obj, buff_obj_ptr, block)
        assert value >= 0

        if value == _constants.GROOVE_BUFFER_NO:
            raise Buffer.NotReady()
        elif value == _constants.GROOVE_BUFFER_END:
            raise Buffer.End()
        elif value == _constants.GROOVE_BUFFER_YES:
            buff = self.BufferClass._from_obj(buff_obj_ptr[0])
            buff.sink = self
            return buff

        raise Exception('Unknown value %s from groove_sink_buffer_get' % value)
Esempio n. 4
0
class Encoder(GrooveClass):
    """Groove Encoder"""
    _ffitype = 'struct GrooveEncoder *'

    BufferClass = Buffer

    format_short_name = utils.property_char_ptr(
        'format_short_name', """Short name of the format

        Optional - choose a short name for the format to help libgroove choose
        which format to use. Use `avconfig -formats` to get a list of possibilities
        """)

    codec_short_name = utils.property_char_ptr(
        'codec_short_name', """Short name of the codec

        Optional - choose a short name for the codec to help libgroove choose
        which codec to use. Use `avconfig -codecs` to get a list of possibilities
        """)

    filename = utils.property_char_ptr(
        'filename', """An example filename

        Optional - provide an example filename to help libgroove guess which
        format/codec to use.
        """)

    mime_type = utils.property_char_ptr(
        'mime_type', """A mime type string

        Optional - provide a mime type string to help libgroove guess which
        format/codec to use.
        """)

    bit_rate = utils.property_convert(
        'bit_rate',
        int,
        doc="""Target encoding quality in bits per second

        Select encoding quality by choosing a target bit rate in bits per
        second. Note that typically you see this expressed in "kbps", such as
        320kbps or 128kbps. Surprisingly, in this circumstance 1 kbps is 1000
        bps, *not* 1024 bps as you would expect. This defaults to 256000.
        """)

    sink_buffer_size = utils.property_convert(
        'sink_buffer_size',
        int,
        doc="""How big the sink buffer should be, in sample frames.

        This defaults to 8192.
        """)

    encoded_buffer_size = utils.property_convert(
        'encoded_buffer_size',
        int,
        doc="""How big the encoded buffer should be, in bytes.

        This defaults to 16384.
        """)

    @property
    def actual_audio_format(self):
        fmt_obj = ffi.addressof(self._obj.actual_audio_format)
        fmt, _ = AudioFormat._from_obj(fmt_obj)
        return fmt

    @property
    def target_audio_format(self):
        fmt_obj = ffi.addressof(self._obj.target_audio_format)
        fmt, _ = AudioFormat._from_obj(fmt_obj)
        return fmt

    @property
    def gain(self):
        """The volume adjustment to make to this player.

        It is recommended to leave this at 1.0 and instead adjust the gain of
        the underlying playlist.
        """
        return float(self._obj.gain)

    @gain.setter
    def gain(self, value):
        lib.groove_encoder_set_gain(self._obj, value)

    @property
    def playlist(self):
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        # TODO: better exception handling
        if self._playlist:
            assert lib.groove_encoder_detach(self._obj) == 0
            self._playlist = None

        assert lib.groove_encoder_attach(self._obj, value._obj) == 0
        self._playlist = value

    def __init__(self):
        # TODO: better exception handling
        obj = lib.groove_encoder_create()
        assert obj != ffi.NULL
        self._obj = ffi.gc(obj, lib.groove_encoder_destroy)
        self._playlist = None

    @property
    def disable_resample(self):
        """Set this flag to ignore audio_format.

        If you set this flag, the buffers you pull from this could have
        any audio format.
        """
        return self._obj.disable_resample

    @disable_resample.setter
    def disable_resample(self, value):
        if value:
            self._obj.disable_resample = 1

    @property
    def buffer_sample_count(self):
        """Number of frames to pull into a buffer

        If set to the default of 0, groove will choose a sample count based on
        efficiency.
        """
        return self._obj.buffer_sample_count

    @buffer_sample_count.setter
    def buffer_sample_count(self, value):
        self._obj.buffer_sample_count = value

    @property
    def buffer_size(self):
        """Buffer queue size in frames, default 8192"""
        return self._obj.buffer_size

    @buffer_size.setter
    def buffer_size(self, value):
        self._obj.buffer_size = value

    @property
    def gain(self):
        """Volume adjustment for the audio

        It is recommended to leave this at 1.0 and adjust the playlist/item
        gain instead
        """
        return self._obj.gain

    @gain.setter
    def gain(self, value):
        lib.groove_encoder_set_gain(self._obj, value)

    @property
    def playlist(self):
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        # TODO: better exception handling
        if self._obj.playlist != ffi.NULL:
            assert lib.groove_encoder_detach(self._obj) == 0
        if value is not None:
            assert lib.groove_encoder_attach(self._obj, value._obj) == 0
        self._playlist = value

    @property
    def bytes_per_sec(self):
        """Automatically computed from audio format when attached"""
        return self._obj.bytes_per_sec

    def buffer_peek(self, block=True):
        """Returns True if a buffer is ready, False if not"""
        value = lib.groove_encoder_buffer_peek(self._obj, 1 if block else 0)
        assert value >= 0
        return value == 1

    def get_buffer(self, block=False):
        """Get the buffer on the encoder

        If no buffer is ready, this raises `groove.Buffer.NotReady`
        If the end of the playlist is reached, this raises `groove.Buffer.End`
        If block is True and no buffer is ready, this may block indefinately
        """
        # TODO: add timeout, might have to be done in libgroove to be safe
        buff_obj_ptr = ffi.new('struct GrooveBuffer **')
        value = lib.groove_encoder_buffer_get(self._obj, buff_obj_ptr,
                                              1 if block else 0)
        assert value >= 0

        if value == _constants.GROOVE_BUFFER_NO:
            raise Buffer.NotReady()
        elif value == _constants.GROOVE_BUFFER_END:
            raise Buffer.End()
        elif value == _constants.GROOVE_BUFFER_YES:
            buff, _ = self.BufferClass._from_obj(buff_obj_ptr[0])
            buff.encoder = self
            return buff

        raise Exception('Unknown value %s from groove_encoder_buffer_get' %
                        value)

    def get_tags(self, flags=0):
        """Get the tags for an encoder

        Args:
            flags (int)  Bitmask of tag flags

        Returns:
            A dictionary of `name: value` pairs. Both `name` and `value` will
            be type `bytes`.
        """
        # Have to make a GrooveTag** so cffi doesn't try to sizeof GrooveTag
        gtag_ptr = ffi.new('struct GrooveTag **')
        gtag = gtag_ptr[0]
        tags = OrderedDict()
        while True:
            gtag = lib.groove_encoder_metadata_get(self._obj, b'', gtag, flags)
            if gtag == ffi.NULL:
                break

            key = ffi.string(lib.groove_tag_key(gtag))
            value = ffi.string(lib.groove_tag_value(gtag))
            tags[key] = value

        return tags

    def set_tags(self, tagdict, flags=0):
        """Shortcut to set each flag in tagdict

        This will overwrite existing tags, but will not delete existing tags
        that are not listed in tagdict. To delete a tag, set its value to
        `None`
        """
        for k, v in tagdict.items():
            self.set_tag(k, v, flags)

    def set_tag(self, key, value, flags=0):
        """Set tag `key` to `value`

        If `value` is `None`, the tag will be deleted. `key` and `value` must
        be type `bytes`.
        """
        if value is None:
            value = ffi.NULL

        status = lib.groove_encoder_metadata_set(self._obj, key, value, flags)
        return status

    def decode_position(self):
        """Get the current position of the decode head

        Returns:
            A tuple of (playlist_item, seconds). If the playlist is empty
            playlist_item will be None and seconds will be -1.0
        """
        pitem_obj_ptr = ffi.new('struct GroovePlaylistItem **')
        seconds = ffi.new('double *')
        lib.groove_encoder_position(self._obj, pitem_obj_ptr, seconds)
        return self._pitem(pitem_obj_ptr[0]), float(seconds[0])
Esempio n. 5
0
class Fingerprinter(GrooveClass):
    """Use this to find out the unique id of an audio track"""
    _ffitype = 'struct GrooveFingerprinter *'

    @classmethod
    def encode(cls, fp):
        """Compress and base64-encode a raw fingerprint"""
        # TODO: error handling
        efp_obj_ptr = ffi.new('char **')
        fp_obj = ffi.new('int32_t[]', fp)
        assert lib.groove_fingerprinter_encode(fp_obj, len(fp), efp_obj_ptr) == 0

        # copy the result to python and free the c obj
        result = ffi.string(efp_obj_ptr[0])
        lib.groove_fingerprinter_dealloc(efp_obj_ptr[0])

        return result

    @classmethod
    def decode(cls, encoded_fp):
        """Uncompress and base64-decode a raw fingerprint"""
        efp_obj = ffi.new('char[]', encoded_fp)
        fp_obj_ptr = ffi.new('int32_t **')
        size_obj_ptr = ffi.new('int *')
        assert lib.groove_fingerprinter_decode(efp_obj, fp_obj_ptr, size_obj_ptr) == 0

        # copy the result to python and free the c obj
        fp_obj = fp_obj_ptr[0]
        result = [int(fp_obj[n]) for n in range(size_obj_ptr[0])]
        lib.groove_fingerprinter_dealloc(fp_obj)

        return result

    info_queue_size = utils.property_convert('info_queue_size', int,
        doc="""Maximum number of items to store in this Fingerprinter's queue

        This defaults to MAX_INT, meaning that fingerprinter will cause the
        decoder to decode the entire playlist. If you want instead, for
        example, obtain fingerprints at the same time as playback, you might
        set this value to 1.
        """)

    sink_buffer_size = utils.property_convert('sink_buffer_size', int,
        doc="""How big the sink buffer should be, in sample frames

        This defaults to 8192.
        """)

    @property
    def playlist(self):
        """Playlist to generate fingerprints for"""
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        if self._playlist:
            assert lib.groove_fingerprinter_detach(self._obj) == 0
            self._playlist = None

        if value is not None:
            assert lib.groove_fingerprinter_attach(self._obj, value._obj) == 0
            self._playlist = value

    def __init__(self, base64_encode=True):
        # TODO: error handling
        obj = lib.groove_fingerprinter_create()
        assert obj != ffi.NULL
        self._obj = ffi.gc(obj, lib.groove_fingerprinter_destroy)
        self._playlist = None
        self.base64_encode = base64_encode

    def __del__(self):
        # Make sure playlist gets detached before we loose the obj
        if self.playlist is not None:
            self.playlist = None

    def __iter__(self):
        info_obj = ffi.new('struct GrooveFingerprinterInfo *');
        while True:
            status = lib.groove_fingerprinter_info_get(self._obj, info_obj, True)
            assert status >= 0
            if status != 1 or info_obj.item == ffi.NULL:
                break

            fp_obj = info_obj.fingerprint
            fp_size_obj = info_obj.fingerprint_size

            if self.base64_encode:
                efp_obj_ptr = ffi.new('char **')
                assert lib.groove_fingerprinter_encode(fp_obj, fp_size_obj, efp_obj_ptr) == 0
                fp = ffi.string(efp_obj_ptr[0])
                lib.groove_fingerprinter_dealloc(efp_obj_ptr[0])
            else:
                fp = [int(fp_obj[n]) for n in range(fp_size_obj)]

            duration = float(info_obj.duration)
            pitem = self.playlist._pitem(info_obj.item)
            lib.groove_fingerprinter_free_info(info_obj)
            yield FingerprinterInfo(fp, duration, pitem)

    def info_peek(self, block=False):
        """Check if info is ready"""
        result = lib.groove_fingerprinter_info_peek(self._obj, block)
        assert result >= 0
        return bool(result)

    def position(self):
        """Get the current position of the printer head

        Returns:
            A tuple of (playlist_item, seconds). If the playlist is empty
            playlist_item will be None and seconds will be -1.0
        """
        pitem_obj_ptr = ffi.new('struct GroovePlaylistItem **')
        seconds = ffi.new('double *')
        lib.groove_fingerprinter_position(self._obj, pitem_obj_ptr, seconds)
        if pitem_obj_ptr[0] == ffi.NULL:
            pitem = None
        else:
            pitem = self.playlist._pitem(pitem_obj_ptr[0])
        return pitem, float(seconds[0])
Esempio n. 6
0
class Player(GrooveClass):
    _ffitype = 'struct GroovePlayer *'
    dummy_device = Device(_constants.GROOVE_PLAYER_DUMMY_DEVICE, 'dummy')
    default_device = Device(_constants.GROOVE_PLAYER_DEFAULT_DEVICE, 'default')

    @classmethod
    def list_devices(cls):
        """Get the list of available devices

        This may trigger a complete redetect of available hardware
        """
        devices = [
            Player.dummy_device,
            Player.default_device,
        ]

        device_count = lib.groove_device_count()
        for n in range(device_count):
            name = lib.groove_device_name(n)
            if name == ffi.NULL:
                continue
            name = ffi.string(name).decode('utf-8')
            devices.append(Device(n, name))

        return devices

    device_buffer_size = utils.property_convert('device_buffer_size', int,
        doc="""How big the device buffer should be, in sample frames

        Must be a power of 2, defaults to 1024
        """)

    sink_buffer_size = utils.property_convert('sink_buffer_size', int,
        doc="""How big the sink buffer should be, in sample frames

        Defaults to 8192
        """)

    use_exact_audio_format = utils.property_convert('use_exact_audio_format', bool,
        doc="""Force the device to play with the format of the media

        If true, `target_audio_format` and `actual_audio_format` are ignored
        and no resampling, channel layout remapping, or sample format
        conversion will occur. The audio device will be reopened with exact
        parameters whenever necessary.
        """)

    @property
    def device(self):
        """Device for playback, defaults to Player.dummy_device"""
        return self._device

    @device.setter
    def device(self, value):
        self._obj.device_index = value.index
        self._device = value

    @property
    def target_audio_format(self):
        """The desired audio format to open the device with

        Defaults to 44100Hz, signed 16-bit int, stereo
        These are preferences; if a setting cannot be used, a substitute will
        be used instead. `actual_audio_format` is set to the actual values.
        """
        fmt, _ = AudioFormat._from_obj(self._obj.target_audio_format)
        return fmt

    @property
    def actual_audio_format(self):
        """Set to the actual audio format you get when you open the device"""
        fmt, _ = AudioFormat._from_obj(self._obj.actual_audio_format)
        return fmt

    @property
    def gain(self):
        """The volume adjustment to make to this player

        It is recommended to leave this at 1.0 and instead adjust the gain of
        the underlying playlist.
        """
        return self._obj.gain

    @gain.setter
    def gain(self, value):
        assert lib.groove_player_set_gain(self._obj, value) == 0

    @property
    def playlist(self):
        """Playlist to play"""
        return self._playlist

    @playlist.setter
    def playlist(self, value):
        if self._playlist:
            assert lib.groove_player_detach(self._obj) == 0
            self._playlist = None

        if value is not None:
            assert lib.groove_player_attach(self._obj, value._obj) == 0
            self._playlist = value

    def __init__(self):
        # TODO: error handling
        obj = lib.groove_player_create()
        assert obj != ffi.NULL
        self._obj = ffi.gc(obj, lib.groove_player_destroy)
        self._playlist = None
        self.device = Player.dummy_device

    def __del__(self):
        # Make sure playlist gets detached before we loose the obj
        if self.playlist is not None:
            self.playlist = None

    def event_get(self, block=False):
        """Get player event"""
        event_obj = ffi.new('union GroovePlayerEvent *')
        result = lib.groove_player_event_get(self._obj, event_obj, block)
        assert result >= 0

        if result == 0:
            return None

        return PlayerEvent.__values__[event_obj.type]

    def event_peek(self, block=False):
        """Check if event is ready"""
        result = lib.groove_player_event_peek(self._obj, block)
        assert result >= 0
        return bool(result)

    def position(self):
        """Get the current position of the printer head

        Returns:
            A tuple of (playlist_item, seconds). If the playlist is empty
            playlist_item will be None and seconds will be -1.0
        """
        pitem_obj_ptr = ffi.new('struct GroovePlaylistItem **')
        seconds = ffi.new('double *')
        lib.groove_player_position(self._obj, pitem_obj_ptr, seconds)
        if pitem_obj_ptr[0] == ffi.NULL:
            pitem = None
        else:
            pitem = self.playlist._pitem(pitem_obj_ptr[0])
        return pitem, float(seconds[0])