views:

208

answers:

3

My problem is the following: I have a 2-minute long WAV file, and my aim is to insert another WAV file (7 seconds long), at a certain point in the first WAV file (say, 0:48), essentially combining the two WAVs, using python. Unfortunately I haven't been able to figure out how to do that, and was wondering if there was some obvious solution that I was missing, or if it is even feasible to do with python. Is there perhaps a library available that might provide a solution? Thanks to all in advance.

UPDATE based on a comment by the OP:

I should have clarified that I wanted the inserted wav to "overlap" the original wav so that both would play, my apologies. Is there any way of achieving such an effect?

+1  A: 

If they're PCM-encoded then you can use wave, otherwise use something like pygst.

Ignacio Vazquez-Abrams
I had looked at wave before, but wasn't sure about how to use it in order to solve my problem. Would it be possible to point me in the direction of an example of how to use it in such a way? Thanks.
Onion
Just create a new file for writing, then read in and write out the appropriate number of frames, write out your insertion, then read and write the rest of the frames.
Ignacio Vazquez-Abrams
A: 

Here's some code to get you in the right direction:

wf = wave.open('in1.wav','rb')
wf2 = wave.open('in2.wav','rb')
wfout = wave.open('out.wav','wb')

wfout.setparams(wf.getparams())

sr = wf.getframerate()
for x in xrange(48):
    wfout.writeframes(wf.readframes(sr)
wfout.writeframes(wf2.readframes(sr))
for x in xrange(72):
    wfout.writeframes(wf.readframes(sr))

This should do what you've described in your question (adding a 1 second clip 48 seconds into a 2 minute song) as long as the waves are in the same format (same sampling rate, same number of channels, etc.). You can probably read/write bigger chunks than a second, but I did them as 1 second chunks to be safe.

Justin Peel
@Justin I would think you should always open a wav file as binary `'rb'` or `'wb'`, not text as shown in your answer.
Jon-Eric
@Jon-Eric I actually had `'rb'` and `'wb'` originally, but then I looked at the docs attached to wave and it just said `'r'` and `'w'`. I tried the code both ways and it didn't seem to make a difference. Looking in the online docs didn't really illuminate me either. It is probably safer to add the `b` so I'll change my code.
Justin Peel
Though the above code works, and the inserted wav plays at the desired time, it seems that the original wav (the 2 minute song), stops playing during the duration of the inserted wav, and then continues to play afterwards, an effect which is unwanted. I should have clarified that I wanted the inserted wav to "overlap" the original wav so that both would play, my apologies. Is there any way of achieving such an effect?
Onion
+1  A: 

Loosely based on the code by Justin, here is some other code that probably does what you want:

import wave, audioop

def merge_wav_at_offset(wav_in1, wav_in2, offset, wav_out):
    """Merge two wave files, with the second wave starting at offset seconds
    The two input wave files should have the same frame rate, channels, depth
    Also, offset should be non-negative and can be floating point."""
    wf1= wave.open(wav_in1, 'rb')
    wf2= wave.open(wav_in2, 'rb')
    wfo= wave.open(wav_out, 'wb')

    wfout.setparams(wf1.getparams())

    frame_rate = wf1.getframerate()
    sample_width= wf1.getsampwidth()
    if offset < 0:
        offset= 0
    prologue_frames= int(frame_rate*offset)
    merge_frames= wf2.getnframes()

    # prologue
    frames_to_read= prologue_frames
    while frames_to_read > 0:
        chunk_size= min(frame_rate, frames_to_read)
        wfo.writeframes(wf1.readframes(chunk_size))
        frames_to_read-= chunk_size

    # merging
    frames_to_read= merge_frames
    while frames_to_read > 0:
        chunk_size= min(frame_rate, frames_to_read)
        frames2= wf2.readframes(chunk_size)

        if frames2:
            frames1= wf1.readframes(chunk_size)
            if len(frames1) != len(frames2): # sanity check
                # obviously you should cater for this case too
                raise NotImplementedError, "offset+duration(wf2) > duration(wf1)"
            merged_frames= audioop.add(frames1, frames2, sample_width)
            wfo.writeframes(merged_frames)
        else: # early end of wf2 data; improbable but possible
            break

        frames_to_read-= chunk_size

    # epilogue
    while True:
        frames= wf1.readframes(frame_rate)
        if not frames: break
        wfo.writeframes(frames)

    for wave_file in wf1, wf2, wfo:
        wave_file.close()

I just wrote the code without testing, so it's possible that I have a bug (even syntax errors); however, my experience with Python is that often the code runs as-is;-) If you need anything more, let me know.

ΤΖΩΤΖΙΟΥ
Thanks a lot. Hadn't known about audioop.
Onion
Hmm, just one question, what would have to be done in order to make the files merge despite offset+duration(wf2) > duration(wf1) ?
Onion
@Onion: I'd say, merge `min(len(frames1),len(frames2))`, write the remaining frames (assumably from `frames2`), break out of the loop.
ΤΖΩΤΖΙΟΥ