From f9598da2fc832bdfaf2e032c702b9061ac53611a Mon Sep 17 00:00:00 2001 From: yomguy Date: Thu, 3 Mar 2011 18:20:12 +0000 Subject: [PATCH] make flac encoder streaming, add test for flac, bugfixes on encoders --- timeside/encoder/__init__.py | 3 +- timeside/encoder/flac.py | 195 +++++++++++++++----------------- timeside/encoder/mp3.py | 13 ++- timeside/encoder/ogg.py | 13 ++- timeside/encoder/wav.py | 44 +++++-- timeside/tests/api/test_flac.py | 13 +++ 6 files changed, 157 insertions(+), 124 deletions(-) create mode 100644 timeside/tests/api/test_flac.py diff --git a/timeside/encoder/__init__.py b/timeside/encoder/__init__.py index 4698489..6eacec5 100644 --- a/timeside/encoder/__init__.py +++ b/timeside/encoder/__init__.py @@ -4,6 +4,7 @@ from core import * from ogg import * from wav import * from mp3 import * -from m4a import * +from flac import * +#from m4a import * #from timeside.encoder.flac import * diff --git a/timeside/encoder/flac.py b/timeside/encoder/flac.py index 0753441..0cb6e03 100644 --- a/timeside/encoder/flac.py +++ b/timeside/encoder/flac.py @@ -19,122 +19,111 @@ # Author: Guillaume Pellerin -import os -import string -import subprocess - -from timeside.encoder.core import * +from timeside.core import Processor, implements, interfacedoc from timeside.api import IEncoder -from tempfile import NamedTemporaryFile +from numpy import array, frombuffer, getbuffer, float32 -class FlacEncoder(EncoderCore): - """Defines methods to encode to FLAC""" +import pygst +pygst.require('0.10') +import gst +import gobject +gobject.threads_init() +class FlacEncoder(Processor): + """ gstreamer-based FLAC encoder """ implements(IEncoder) - def __init__(self): - self.quality_default = '-5' - self.dub2args_dict = {'creator': 'artist', - 'relation': 'album' - } + def __init__(self, output, streaming=False): + if isinstance(output, basestring): + self.filename = output + else: + self.filename = None + self.streaming = streaming + + if not self.filename and self.streaming: + raise Exception('Must give an output') + + @interfacedoc + def setup(self, channels=None, samplerate=None, nframes=None): + super(FlacEncoder, self).setup(channels, samplerate, nframes) + # TODO open file for writing + # the output data format we want + pipe = ''' appsrc name=src max-bytes=32768 block=true + ! audioconvert + ! flacenc + ''' + if self.filename and self.streaming: + pipe += ''' + ! queue2 name=q0 ! tee name=tee + tee. ! queue name=q1 ! appsink name=app sync=false + tee. ! queue name=q2 ! filesink location=%s + ''' % self.filename + + elif self.filename : + pipe += '! filesink location=%s ' % self.filename + else: + pipe += '! appsink name=app sync=false ' + + self.pipeline = gst.parse_launch(pipe) + # store a pointer to appsrc in our encoder object + self.src = self.pipeline.get_by_name('src') + # store a pointer to appsink in our encoder object + self.app = self.pipeline.get_by_name('app') + + srccaps = gst.Caps("""audio/x-raw-float, + endianness=(int)1234, + channels=(int)%s, + width=(int)32, + rate=(int)%d""" % (int(channels), int(samplerate))) + self.src.set_property("caps", srccaps) + + # start pipeline + self.pipeline.set_state(gst.STATE_PLAYING) @staticmethod + @interfacedoc def id(): - return "flacenc" + return "gst_flac_enc" + + @staticmethod + @interfacedoc + def description(): + return "FLAC GStreamer based encoder" - def format(self): - return 'FLAC' + @staticmethod + @interfacedoc + def format(): + return "FLAC" - def file_extension(self): - return 'flac' + @staticmethod + @interfacedoc + def file_extension(): + return "flac" - def mime_type(self): + @staticmethod + @interfacedoc + def mime_type(): return 'audio/x-flac' - def description(self): - return """ - Free Lossless Audio Codec (FLAC) is a file format for lossless audio - data compression. - """ - - def get_file_info(self): - try: - file1, file2 = os.popen4('metaflac --list "'+self.dest+'"') - info = [] - for line in file2.readlines(): - info.append(clean_word(line[:-1])) - self.info = info - return self.info - except: - raise IOError('EncoderError: metaflac is not installed or ' + \ - 'file does not exist.') - - def write_tags(self, file): - from mutagen.flac import FLAC - media = FLAC(file) - for tag in self.metadata: - name = tag[0] - value = clean_word(tag[1]) - if name in self.dub2args_dict.keys(): - name = self.dub2args_dict[name] - if name == 'comment': - media['DESCRIPTION'] = unicode(value) - else: - media[name] = unicode(value) - try: - media.save() - except: - raise IOError('EncoderError: cannot write tags.') - - - def get_args(self,options=None): - """Get process options and return arguments for the encoder""" - args = [] - if not options is None: - self.options = options - if not ('verbose' in self.options and self.options['verbose'] != '0'): - args.append('-s') - if 'flac_quality' in self.options: - args.append('-f ' + self.options['flac_quality']) - else: - args.append('-f ' + self.quality_default) + @interfacedoc + def set_metadata(self, metadata): + #TODO: + pass + + @interfacedoc + def process(self, frames, eod=False): + buf = self.numpy_array_to_gst_buffer(frames) + self.src.emit('push-buffer', buf) + if self.streaming: + pull = self.app.emit('pull-buffer') + if eod: self.src.emit('end-of-stream') + if not self.streaming: + return frames, eod else: - args.append('-s -f ' + self.quality_default) - - #for tag in self.metadata.keys(): - #value = clean_word(self.metadata[tag]) - #args.append('-c %s="%s"' % (tag, value)) - #if tag in self.dub2args_dict.keys(): - #arg = self.dub2args_dict[tag] - #args.append('-c %s="%s"' % (arg, value)) - - return args - - def process(self, source, metadata, options=None): - buffer_size = 0xFFFF - self.metadata= metadata - self.options = options - args = self.get_args() - args = ' '.join(args) - ext = self.file_extension() - temp_file = NamedTemporaryFile() - command = 'flac %s - -o %s ' % (args, temp_file.name) - - stream = self.core_process(command, source) - - for __chunk in stream: - #temp_file.write(__chunk) - #temp_file.flush() - pass - - #self.write_tags(temp_file.name) - - while True: - __chunk = temp_file.read(buffer_size) - if len(__chunk) == 0: - break - yield __chunk - - temp_file.close() + return pull, eod + def numpy_array_to_gst_buffer(self, frames): + """ gstreamer buffer to numpy array conversion """ + buf = gst.Buffer(getbuffer(frames)) + return buf diff --git a/timeside/encoder/mp3.py b/timeside/encoder/mp3.py index 9bd27f2..6baba12 100644 --- a/timeside/encoder/mp3.py +++ b/timeside/encoder/mp3.py @@ -58,14 +58,14 @@ class Mp3Encoder(Processor): if self.filename and self.streaming: pipe += ''' ! queue2 name=q0 ! tee name=tee - tee. ! queue name=q1 ! appsink name=app + tee. ! queue name=q1 ! appsink name=app sync=false tee. ! queue name=q2 ! filesink location=%s ''' % self.filename elif self.filename : - pipe += '! filesink location=%s' % self.filename + pipe += '! filesink location=%s ' % self.filename else: - pipe += '! appsink name=app' + pipe += '! appsink name=app sync=false ' self.pipeline = gst.parse_launch(pipe) # store a pointer to appsrc in our encoder object @@ -118,9 +118,12 @@ class Mp3Encoder(Processor): buf = self.numpy_array_to_gst_buffer(frames) self.src.emit('push-buffer', buf) if self.streaming: - frames = self.app.emit('pull-buffer') + pull = self.app.emit('pull-buffer') if eod: self.src.emit('end-of-stream') - return frames, eod + if not self.streaming: + return frames, eod + else: + return pull, eod def numpy_array_to_gst_buffer(self, frames): """ gstreamer buffer to numpy array conversion """ diff --git a/timeside/encoder/ogg.py b/timeside/encoder/ogg.py index 25d224d..1d2bbb6 100644 --- a/timeside/encoder/ogg.py +++ b/timeside/encoder/ogg.py @@ -57,14 +57,14 @@ class VorbisEncoder(Processor): if self.filename and self.streaming: pipe += ''' ! queue2 name=q0 ! tee name=tee - tee. ! queue name=q1 ! appsink name=app + tee. ! queue name=q1 ! appsink name=app sync=false tee. ! queue name=q2 ! filesink location=%s ''' % self.filename elif self.filename : - pipe += '! filesink location=%s' % self.filename + pipe += '! filesink location=%s ' % self.filename else: - pipe += '! appsink name=app' + pipe += '! appsink name=app sync=false ' self.pipeline = gst.parse_launch(pipe) # store a pointer to appsrc in our encoder object @@ -117,9 +117,12 @@ class VorbisEncoder(Processor): buf = self.numpy_array_to_gst_buffer(frames) self.src.emit('push-buffer', buf) if self.streaming: - frames = self.app.emit('pull-buffer') + pull = self.app.emit('pull-buffer') if eod: self.src.emit('end-of-stream') - return frames, eod + if not self.streaming: + return frames, eod + else: + return pull, eod def numpy_array_to_gst_buffer(self, frames): """ gstreamer buffer to numpy array conversion """ diff --git a/timeside/encoder/wav.py b/timeside/encoder/wav.py index b49e5b0..1ff5a1b 100644 --- a/timeside/encoder/wav.py +++ b/timeside/encoder/wav.py @@ -35,24 +35,43 @@ class WavEncoder(Processor): """ gstreamer-based encoder """ implements(IEncoder) - def __init__(self, output): - self.file = None + def __init__(self, output, streaming=False): if isinstance(output, basestring): self.filename = output else: - raise Exception("Streaming not supported") + self.filename = None + self.streaming = streaming + + if not self.filename and self.streaming: + raise Exception('Must give an output') @interfacedoc def setup(self, channels=None, samplerate=None, nframes=None): super(WavEncoder, self).setup(channels, samplerate, nframes) # TODO open file for writing # the output data format we want - self.pipeline = gst.parse_launch(''' appsrc name=src - ! audioconvert - ! wavenc - ! filesink location=%s ''' % self.filename) - # store a pointer to appsink in our encoder object + pipe = ''' appsrc name=src + ! audioconvert + ! wavenc + ''' + if self.filename and self.streaming: + pipe += ''' + ! queue2 name=q0 ! tee name=tee + tee. ! queue name=q1 ! appsink name=app sync=false + tee. ! queue name=q2 ! filesink location=%s + ''' % self.filename + + elif self.filename : + pipe += '! filesink location=%s ' % self.filename + else: + pipe += '! appsink name=app sync=false ' + + self.pipeline = gst.parse_launch(pipe) + # store a pointer to appsrc in our encoder object self.src = self.pipeline.get_by_name('src') + # store a pointer to appsink in our encoder object + self.app = self.pipeline.get_by_name('app') + srccaps = gst.Caps("""audio/x-raw-float, endianness=(int)1234, channels=(int)%s, @@ -62,7 +81,7 @@ class WavEncoder(Processor): # start pipeline self.pipeline.set_state(gst.STATE_PLAYING) - + @staticmethod @interfacedoc def id(): @@ -97,8 +116,13 @@ class WavEncoder(Processor): def process(self, frames, eod=False): buf = self.numpy_array_to_gst_buffer(frames) self.src.emit('push-buffer', buf) + if self.streaming: + pull = self.app.emit('pull-buffer') if eod: self.src.emit('end-of-stream') - return frames, eod + if not self.streaming: + return frames, eod + else: + return pull, eod def numpy_array_to_gst_buffer(self, frames): """ gstreamer buffer to numpy array conversion """ diff --git a/timeside/tests/api/test_flac.py b/timeside/tests/api/test_flac.py new file mode 100644 index 0000000..ee590c4 --- /dev/null +++ b/timeside/tests/api/test_flac.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from timeside.decoder import * +from timeside.encoder import * +import os.path + +source = os.path.join(os.path.dirname(__file__), "../samples/sweep.wav") +dest = os.path.join(os.path.dirname(__file__), "../results/sweep_wav.flac") + +decoder = FileDecoder(source) +encoder = FlacEncoder(dest) + +(decoder | encoder).run() -- 2.39.5