Daniel@0
|
1 # -*- coding: utf-8 -*-
|
Daniel@0
|
2 #
|
Daniel@0
|
3 # Copyright (c) 2013 Paul Brossier <piem@piem.org>
|
Daniel@0
|
4
|
Daniel@0
|
5 # This file is part of TimeSide.
|
Daniel@0
|
6
|
Daniel@0
|
7 # TimeSide is free software: you can redistribute it and/or modify
|
Daniel@0
|
8 # it under the terms of the GNU General Public License as published by
|
Daniel@0
|
9 # the Free Software Foundation, either version 2 of the License, or
|
Daniel@0
|
10 # (at your option) any later version.
|
Daniel@0
|
11
|
Daniel@0
|
12 # TimeSide is distributed in the hope that it will be useful,
|
Daniel@0
|
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Daniel@0
|
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Daniel@0
|
15 # GNU General Public License for more details.
|
Daniel@0
|
16
|
Daniel@0
|
17 # You should have received a copy of the GNU General Public License
|
Daniel@0
|
18 # along with TimeSide. If not, see <http://www.gnu.org/licenses/>.
|
Daniel@0
|
19
|
Daniel@0
|
20 # Author: Paul Brossier <piem@piem.org>
|
Daniel@0
|
21
|
Daniel@0
|
22 from timeside.core import implements, interfacedoc
|
Daniel@0
|
23 from timeside.analyzer.core import Analyzer
|
Daniel@0
|
24 from timeside.api import IAnalyzer
|
Daniel@0
|
25 import re
|
Daniel@0
|
26 import subprocess
|
Daniel@0
|
27 import numpy as np
|
Daniel@0
|
28
|
Daniel@0
|
29
|
Daniel@0
|
30 def simple_host_process(argslist):
|
Daniel@0
|
31 """Call vamp-simple-host"""
|
Daniel@0
|
32
|
Daniel@0
|
33 vamp_host = 'vamp-simple-host'
|
Daniel@0
|
34 command = [vamp_host]
|
Daniel@0
|
35 command.extend(argslist)
|
Daniel@0
|
36 # try ?
|
Daniel@0
|
37 stdout = subprocess.check_output(
|
Daniel@0
|
38 command, stderr=subprocess.STDOUT).splitlines()
|
Daniel@0
|
39
|
Daniel@0
|
40 return stdout
|
Daniel@0
|
41
|
Daniel@0
|
42
|
Daniel@0
|
43 # Raise an exception if Vamp Host is missing
|
Daniel@0
|
44 from timeside.exceptions import VampImportError
|
Daniel@0
|
45 try:
|
Daniel@0
|
46 simple_host_process(['-v'])
|
Daniel@0
|
47 WITH_VAMP = True
|
Daniel@0
|
48 except OSError:
|
Daniel@0
|
49 WITH_VAMP = False
|
Daniel@0
|
50 raise VampImportError
|
Daniel@0
|
51
|
Daniel@0
|
52
|
Daniel@0
|
53 class VampSimpleHostDML(Analyzer):
|
Daniel@0
|
54
|
Daniel@0
|
55 """Vamp plugins library interface analyzer"""
|
Daniel@0
|
56
|
Daniel@0
|
57 implements(IAnalyzer)
|
Daniel@0
|
58
|
Daniel@0
|
59 def __init__(self, plugin_list=None):
|
Daniel@0
|
60 super(VampSimpleHostDML, self).__init__()
|
Daniel@0
|
61 if plugin_list is None:
|
Daniel@0
|
62 plugin_list = self.get_plugins_list()
|
Daniel@0
|
63 #plugin_list = [['vamp-example-plugins', 'percussiononsets', 'detectionfunction']]
|
Daniel@0
|
64
|
Daniel@0
|
65 self.plugin_list = plugin_list
|
Daniel@0
|
66
|
Daniel@0
|
67 @interfacedoc
|
Daniel@0
|
68 def setup(self, channels=None, samplerate=None,
|
Daniel@0
|
69 blocksize=None, totalframes=None):
|
Daniel@0
|
70 super(VampSimpleHostDML, self).setup(
|
Daniel@0
|
71 channels, samplerate, blocksize, totalframes)
|
Daniel@0
|
72
|
Daniel@0
|
73 @staticmethod
|
Daniel@0
|
74 @interfacedoc
|
Daniel@0
|
75 def id():
|
Daniel@0
|
76 return "vamp_simple_host_dml"
|
Daniel@0
|
77
|
Daniel@0
|
78 @staticmethod
|
Daniel@0
|
79 @interfacedoc
|
Daniel@0
|
80 def name():
|
Daniel@0
|
81 return "Vamp Plugins Host for DML"
|
Daniel@0
|
82
|
Daniel@0
|
83 @staticmethod
|
Daniel@0
|
84 @interfacedoc
|
Daniel@0
|
85 def unit():
|
Daniel@0
|
86 return ""
|
Daniel@0
|
87
|
Daniel@0
|
88 def process(self, frames, eod=False):
|
Daniel@0
|
89 pass
|
Daniel@0
|
90 return frames, eod
|
Daniel@0
|
91
|
Daniel@0
|
92 def post_process(self):
|
Daniel@0
|
93 #plugin = 'vamp-example-plugins:amplitudefollower:amplitude'
|
Daniel@0
|
94
|
Daniel@0
|
95 wavfile = self.mediainfo()['uri'].split('file://')[-1]
|
Daniel@0
|
96
|
Daniel@0
|
97 for plugin_line in self.plugin_list:
|
Daniel@0
|
98
|
Daniel@0
|
99 plugin = ':'.join(plugin_line)
|
Daniel@0
|
100 (time, duration, value) = self.vamp_plugin(plugin, wavfile)
|
Daniel@0
|
101 if value is None:
|
Daniel@0
|
102 return
|
Daniel@0
|
103
|
Daniel@0
|
104 if duration is not None:
|
Daniel@0
|
105 plugin_res = self.new_result(
|
Daniel@0
|
106 data_mode='value', time_mode='segment')
|
Daniel@0
|
107 plugin_res.data_object.duration = duration
|
Daniel@0
|
108 else:
|
Daniel@0
|
109 plugin_res = self.new_result(
|
Daniel@0
|
110 data_mode='value', time_mode='event')
|
Daniel@0
|
111
|
Daniel@0
|
112 plugin_res.data_object.time = time
|
Daniel@0
|
113 plugin_res.data_object.value = value
|
Daniel@0
|
114
|
Daniel@0
|
115 # Fix strat, duration issues if audio is a segment
|
Daniel@0
|
116 # if self.mediainfo()['is_segment']:
|
Daniel@0
|
117 # start_index = np.floor(self.mediainfo()['start'] *
|
Daniel@0
|
118 # self.result_samplerate /
|
Daniel@0
|
119 # self.result_stepsize)
|
Daniel@0
|
120 #
|
Daniel@0
|
121 # stop_index = np.ceil((self.mediainfo()['start'] +
|
Daniel@0
|
122 # self.mediainfo()['duration']) *
|
Daniel@0
|
123 # self.result_samplerate /
|
Daniel@0
|
124 # self.result_stepsize)
|
Daniel@0
|
125 #
|
Daniel@0
|
126 # fixed_start = (start_index * self.result_stepsize /
|
Daniel@0
|
127 # self.result_samplerate)
|
Daniel@0
|
128 # fixed_duration = ((stop_index - start_index) * self.result_stepsize /
|
Daniel@0
|
129 # self.result_samplerate)
|
Daniel@0
|
130 #
|
Daniel@0
|
131 # plugin_res.audio_metadata.start = fixed_start
|
Daniel@0
|
132 # plugin_res.audio_metadata.duration = fixed_duration
|
Daniel@0
|
133 #
|
Daniel@0
|
134 # value = value[start_index:stop_index + 1]
|
Daniel@0
|
135 plugin_res.id_metadata.id += '.' + '.'.join(plugin_line[1:])
|
Daniel@0
|
136 plugin_res.id_metadata.name += ' ' + \
|
Daniel@0
|
137 ' '.join(plugin_line[1:])
|
Daniel@0
|
138
|
Daniel@0
|
139 self.process_pipe.results.add(plugin_res)
|
Daniel@0
|
140
|
Daniel@0
|
141 @staticmethod
|
Daniel@0
|
142 def vamp_plugin(plugin, wavfile):
|
Daniel@0
|
143
|
Daniel@0
|
144 args = [plugin, wavfile]
|
Daniel@0
|
145
|
Daniel@0
|
146 stdout = simple_host_process(args) # run vamp-simple-host
|
Daniel@0
|
147
|
Daniel@0
|
148 stderr = stdout[0:8] # stderr containing file and process information
|
Daniel@0
|
149 res = stdout[8:] # stdout containg the feature data
|
Daniel@0
|
150
|
Daniel@0
|
151 if len(res) == 0:
|
Daniel@0
|
152 return ([], [], [])
|
Daniel@0
|
153
|
Daniel@0
|
154 # Parse stderr to get blocksize and stepsize
|
Daniel@0
|
155 blocksize_info = stderr[4]
|
Daniel@0
|
156
|
Daniel@0
|
157 import re
|
Daniel@0
|
158 # Match agianst pattern 'Using block size = %d, step size = %d'
|
Daniel@0
|
159 m = re.match(
|
Daniel@0
|
160 'Using block size = (\d+), step size = (\d+)', blocksize_info)
|
Daniel@0
|
161
|
Daniel@0
|
162 blocksize = int(m.groups()[0])
|
Daniel@0
|
163 stepsize = int(m.groups()[1])
|
Daniel@0
|
164 # Get the results
|
Daniel@0
|
165
|
Daniel@0
|
166 # how are types defined in this? what types can timeside use?
|
Daniel@0
|
167 # value = np.asfarray([line.split(': ')[1] for line in res if (len(line.split(': ')) > 1)])
|
Daniel@0
|
168 value = [line.split(': ')[1] for line in res if (len(line.split(': ')) > 1)]
|
Daniel@0
|
169 value = [re.sub("[^0-9\.\s]","",x) for x in value]
|
Daniel@0
|
170 value = np.asfarray([[x.split(' ')[0] for x in value], [x.split(' ')[1] for x in value]]) # only get the first thing in the string
|
Daniel@0
|
171 time = np.asfarray([r.split(':')[0].split(',')[0] for r in res])
|
Daniel@0
|
172
|
Daniel@0
|
173 time_len = len(res[0].split(':')[0].split(','))
|
Daniel@0
|
174 if time_len == 1:
|
Daniel@0
|
175 # event
|
Daniel@0
|
176 duration = None
|
Daniel@0
|
177 elif time_len == 2:
|
Daniel@0
|
178 # segment
|
Daniel@0
|
179 duration = np.asfarray(
|
Daniel@0
|
180 [r.split(':')[0].split(',')[1] for r in res])
|
Daniel@0
|
181
|
Daniel@0
|
182 return (time, duration, value)
|
Daniel@0
|
183
|
Daniel@0
|
184 @staticmethod
|
Daniel@0
|
185 def get_plugins_list():
|
Daniel@0
|
186 arg = ['--list-outputs']
|
Daniel@0
|
187 stdout = simple_host_process(arg)
|
Daniel@0
|
188
|
Daniel@0
|
189 return [line.split(':')[1:] for line in stdout]
|