1

I've made this class:

class AudioSegmentCustom(AudioSegment):

    def fade_override(self, seg, fade_len=100):
        seg1, seg2 = AudioSegment._sync(self, seg)
        final = seg1[:-fade_len]
        a_fin = seg1[-fade_len:].fade(to_gain=-120, start=0, end=float('inf'))
        a_fin *= seg2[:fade_len]
        return (final + a_fin) + seg2[fade_len:]

The problem I'm facing is when I create some AudioSegmentCustom variables, if I "add" them, the add operation returns its original parent type = AudioSegment

Thus the following code doesn't work:

final = AudioSegmentCustom.from_mp3(mp3_src) + AudioSegment.from_mp3(mp3_other)
final = final.fade_override(...blabla...)

because I get:

'AudioSegment' object has no attribute 'fade_override'

...even though I've started with a AudioSegmentCustom object, I end with AudioSegment "only" object. What is the way to "force" the type of a newly created object?

Just in case you need it:

class AudioSegment(object):
    def __add__(self, arg):
        if isinstance(arg, AudioSegment):
            return self.append(arg, crossfade=0)
        else:
            return self.apply_gain(arg)
dhke
  • 15,008
  • 2
  • 39
  • 56
Olivier Pons
  • 15,363
  • 26
  • 117
  • 213
  • You should probably include the code for `AudioSegment.__add__`. – Lev Levitsky May 08 '16 at 10:37
  • It just returns an AudioSegment object. Why do you need it? Anyway just updated my question – Olivier Pons May 08 '16 at 10:38
  • Because one way would be to tweak it so that it creates instances of the same class as `self`. If you don't want to change it, then you'll surely need to write a separate `AudioSegmentCustom.__add__` method or to construct the `AudioSegmentCustom` elsewhere. – Lev Levitsky May 08 '16 at 10:40
  • 2
    Looks like the problem is [`AudioSegment._spawn()`](https://github.com/jiaaro/pydub/blob/master/pydub/audio_segment.py#L335). It unconditionally returns a bare-bones `AudioSegment` instance, even when subclassed. If it returned `self.__class__(...)` instead, your problem would be solved. Since `_spawn()` is not a class method, you can override it in `AudioSegmentCustom`. It's not a nice solution, but it should do the job. – dhke May 08 '16 at 10:44
  • I've just plain- copy/pasted the _spawn function, modified it to your suggestion and it works. May I ask you to answer with a few lines of code so I can check your answer as valid? Thank you very much. – Olivier Pons May 08 '16 at 10:51

1 Answers1

1

Looks like the problem is AudioSegment._spawn().

It unconditionally returns a bare-bones AudioSegment instance. Since it is a normal method, you can override it in AudioSegmentCustom:

def _spawn(self, data, overrides={}):
    """
    Creates a new audio segment using the metadata from the current one
    and the data passed in. Should be used whenever an AudioSegment is
    being returned by an operation that would alters the current one,
    since AudioSegment objects are immutable.
    """
    # accept lists of data chunks
    if isinstance(data, list):
        data = b''.join(data)

    # accept file-like objects
    if hasattr(data, 'read'):
        if hasattr(data, 'seek'):
            data.seek(0)
        data = data.read()

    metadata = {
        'sample_width': self.sample_width,
        'frame_rate': self.frame_rate,
        'frame_width': self.frame_width,
        'channels': self.channels
    }
    metadata.update(overrides)
    return self.__class__(data=data, metadata=metadata)

The copy&paste is certainly not a good practice, but it does the job.

Note, however, that it introduces an asymmetry, because AudioSegmentCustom + AudioSegment returns an AudioSegmentCustom, while AudioSegment + AudioSegmentCustom returns an AudioSegment. This --once more-- can be fixed by additionally providing __radd__() in AudioSegmentCustom. It will be called before AudioSegment.__add__().

dhke
  • 15,008
  • 2
  • 39
  • 56
  • 1
    You can define `__radd__()` to avoid this asymmetry: as [the docs](https://docs.python.org/2/reference/datamodel.html#object.__radd__) point out, in the case where the right operand is a subclass of the left, Python will call the right's `__radd__` first. – Daniel Roseman May 08 '16 at 12:00
  • The [respective change](https://github.com/jiaaro/pydub/commit/40bd30c81e12664d849de78fcf8bd43359d16c72) has been merged into pydub and the hack should be no longer necessary for future releases. – dhke May 11 '16 at 15:55