Skip to content

Commit 267ccf4

Browse files
committed
Reqrote most of audiotranscode to make it easier to add new backends and so on
1 parent 083da70 commit 267ccf4

File tree

14 files changed

+296
-430
lines changed

14 files changed

+296
-430
lines changed

__init__.py

Lines changed: 0 additions & 162 deletions
This file was deleted.

audiotranscode/__init__.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/python3
2+
import subprocess
3+
import re
4+
import os
5+
import time
6+
7+
class Transcoder(object):
8+
devnull = open(os.devnull,'w')
9+
MimeTypes = {
10+
'mp3' : 'audio/mpeg',
11+
'ogg' : 'audio/ogg',
12+
'flac' : 'audio/flac',
13+
'aac' : 'audio/aac',
14+
}
15+
def __init__(self):
16+
self.command = ['']
17+
18+
def available(self):
19+
try:
20+
subprocess.Popen([self.command[0]],stdout=devnull, stderr=devnull)
21+
return True
22+
except OSError:
23+
return False
24+
25+
class Encoder(Transcoder):
26+
def __init__(self,filetype,command):
27+
self.filetype = filetype
28+
self.mimetype = Transcoder.MimeTypes[filetype]
29+
self.command = command
30+
31+
def encode(self, decoder_process, bitrate):
32+
cmd = self.command[:]
33+
if 'BITRATE' in cmd:
34+
cmd[cmd.index('BITRATE')] = str(bitrate)
35+
return subprocess.Popen(cmd,
36+
stdin=decoder_process.stdout,
37+
stdout=subprocess.PIPE,
38+
stderr=Transcoder.devnull
39+
)
40+
41+
def __str__(self):
42+
return "<Encoder type='%s' cmd='%s'>"%(self.filetype,str(' '.join(self.command)))
43+
44+
class Decoder(Transcoder):
45+
def __init__(self,filetype,command):
46+
self.filetype = filetype
47+
self.mimetype = Transcoder.MimeTypes[filetype]
48+
self.command = command
49+
50+
def decode(self, filepath):
51+
cmd = self.command[:]
52+
if 'INPUT' in cmd:
53+
cmd[cmd.index('INPUT')] = filepath
54+
return subprocess.Popen(cmd,
55+
stdout=subprocess.PIPE,
56+
stderr=Transcoder.devnull
57+
)
58+
59+
def __str__(self):
60+
return "<Decoder type='%s' cmd='%s'>"%(self.filetype,str(' '.join(self.command)))
61+
62+
63+
class TranscodeError(Exception):
64+
def __init__(self, value):
65+
self.value = value
66+
def __str__(self):
67+
return repr(self.value)
68+
69+
class EncodeError(TranscodeError):
70+
def __init__(self, value):
71+
self.value = value
72+
def __str__(self):
73+
return repr(self.value)
74+
75+
class DecodeError(TranscodeError):
76+
def __init__(self, value):
77+
self.value = value
78+
def __str__(self):
79+
return repr(self.value)
80+
81+
class AudioTranscode:
82+
Encoders = [
83+
#encoders take input from stdin and write output to stout
84+
Encoder('ogg', ['oggenc', '-b','BITRATE','-']),
85+
Encoder('mp3', ['lame','-b','BITRATE','-','-']),
86+
#Encoder('ogg', ['ffmpeg', '-i', '-', '-f', 'ogg', '-acodec', 'libvorbis', '-ab', 'BITRATE', '-']),
87+
Encoder('mp3', ['ffmpeg', '-i', '-', '-f', 'mp3', '-acodec', 'libmp3lame', '-ab', 'BITRATE', '-']),
88+
Encoder('aac', ['faac','-b','BITRATE','-P','-o','-','-']),
89+
]
90+
Decoders = [
91+
#filepath must be appendable!
92+
Decoder('mp3' , ['mpg123', '-w', '-', 'INPUT']),
93+
Decoder('mp3' , ['ffmpeg', '-i', 'INPUT', '-f', 'wav', '-acodec', 'pcm_s16le', '-']),
94+
Decoder('ogg' , ['oggdec', '-Q','-b', '16', '-o', '-', 'INPUT']),
95+
Decoder('ogg' , ['ffmpeg', '-i', 'INPUT', '-f', 'wav', '-acodec', 'pcm_s16le', '-']),
96+
Decoder('flac' , ['flac', '-F','-d', '-c', 'INPUT']),
97+
Decoder('aac' , ['faad', '-w', 'INPUT']),
98+
#Decoder('mp3' , ['mplayer','-vc','null','-vo','null','-ao' 'pcm:waveheader:fast:file=-']),
99+
]
100+
101+
def __init__(self,debug=False):
102+
self.debug = debug
103+
self.availableEncoders = list(filter(lambda x:x.available,AudioTranscode.Encoders))
104+
self.availableDecoders = list(filter(lambda x:x.available,AudioTranscode.Decoders))
105+
self.bitrate = {'mp3':160, 'ogg': 128, 'aac': 128}
106+
107+
def availableEncoderFormats(self):
108+
return set(map(lambda x:x.filetype, self.availableEncoders))
109+
110+
def availableDecoderFormats(self):
111+
return set(map(lambda x:x.filetype, self.availableDecoders))
112+
113+
def _filetype(filepath):
114+
if '.' in filepath:
115+
return filepath.lower()[filepath.rindex('.')+1:]
116+
117+
def _decode(self, filepath, decoder=None):
118+
if not os.path.exists(filepath):
119+
filepath = os.path.abspath(filepath)
120+
raise DecodeError('File not Found! Cannot decode "file" %s'%filepath)
121+
filetype = AudioTranscode._filetype(filepath)
122+
if not filetype in self.availableDecoderFormats():
123+
raise DecodeError('No decoder available to handle filetype %s'%filetype)
124+
elif not decoder:
125+
for d in self.availableDecoders:
126+
if d.filetype == filetype:
127+
decoder = d
128+
break
129+
if self.debug:
130+
print(decoder)
131+
return decoder.decode(filepath)
132+
133+
def _encode(self, audio_format, decoder_process, bitrate=None,encoder=None):
134+
if not audio_format in self.availableEncoderFormats():
135+
raise EncodeError('No encoder available to handle audio format %s'%audio_format)
136+
if not bitrate:
137+
bitrate = self.bitrate.get(audio_format)
138+
if not bitrate:
139+
bitrate = 128
140+
if not encoder:
141+
for e in self.availableEncoders:
142+
if e.filetype == audio_format:
143+
encoder = e
144+
break
145+
if self.debug:
146+
print(encoder)
147+
return encoder.encode(decoder_process, bitrate)
148+
149+
def transcode(self, in_file, out_file, bitrate=None):
150+
print(out_file)
151+
audioformat = AudioTranscode._filetype(out_file)
152+
with open(out_file, 'wb') as fh:
153+
for data in self.transcodeStream(in_file,audioformat,bitrate):
154+
fh.write(data)
155+
fh.close()
156+
157+
def transcodeStream(self, filepath, newformat,bitrate=None,encoder=None,decoder=None):
158+
decoder_process = None
159+
encoder_process = None
160+
try:
161+
decoder_process = self._decode(filepath, decoder)
162+
encoder_process = self._encode(newformat, decoder_process,bitrate=bitrate,encoder=encoder)
163+
while encoder_process.poll() == None:
164+
data = encoder_process.stdout.read()
165+
if data == None:
166+
time.sleep(0.1) #wait for new data...
167+
break
168+
yield data
169+
except Exception as e:
170+
#pass on exception, but clean up
171+
raise e
172+
finally:
173+
if decoder_process and decoder_process.poll() == None:
174+
if decoder_process.stderr:
175+
decoder_process.stderr.close()
176+
if decoder_process.stdout:
177+
decoder_process.stdout.close()
178+
if decoder_process.stdin:
179+
decoder_process.stdin.close()
180+
decoder_process.terminate()
181+
if encoder_process:
182+
encoder_process.stdout.read()
183+
encoder_process.stdout.close()
184+
if encoder_process.stdin:
185+
encoder_process.stdin.close()
186+
if encoder_process.stderr:
187+
encoder_process.stderr.close()
188+
encoder_process.wait()

0 commit comments

Comments
 (0)