Switched to using decodebin2 to do the demuxing and (optionally) decoding.
[~jspiros/hylia-transcoder.git] / hylia-transcoder.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 # known issues:
5 #       - gstreamer demuxes xvid as video/x-xvid, but mpegtsmuxer can only accept video/mpeg. capssetter doesn't fix this.
6 #       - MPEG-4 video can't seem to be muxed in properly, anyway.
7 #       - Doesn't seem to work with the PS3...
8
9 import sys, os, time, thread
10 import glib, gobject, pygst
11 import argparse
12 pygst.require('0.10')
13 import gst
14
15
16 loop = glib.MainLoop()
17
18
19 class Main(object):
20         def __init__(self, parser, args):
21                 self.parser = parser
22                 self.args = args
23                 
24                 self.transcoder = gst.Pipeline('transcoder')
25                 
26                 self.input_file = gst.element_factory_make('filesrc', 'input-file')
27                 self.input_file.set_property('location', self.args.input)
28                 
29                 self.decoder = gst.element_factory_make('decodebin2', 'decoder')
30                 self.decoder.connect('autoplug-continue', self.decoder_autoplug_continue)
31                 self.decoder.connect('pad-added', self.decoder_pad_added)
32                 self.decoder.connect('no-more-pads', self.decoder_no_more_pads)
33                 
34                 queues = []
35                 
36                 self.video_input_queue = gst.element_factory_make('queue2', 'video-input-queue')
37                 queues.append(self.video_input_queue)
38                 self.video_output_queue = gst.element_factory_make('queue2', 'video-output-queue')
39                 queues.append(self.video_output_queue)
40                 
41                 self.audio_input_queue = gst.element_factory_make('queue2', 'audio-input-queue')
42                 queues.append(self.audio_input_queue)
43                 self.audio_output_queue = gst.element_factory_make('queue2', 'audio-output-queue')
44                 queues.append(self.audio_output_queue)
45                 
46                 self.muxer = gst.element_factory_make('mpegtsmux', 'muxer')
47                 self.output_file = gst.element_factory_make('filesink', 'output-file')
48                 self.output_file.set_property('location', self.args.output)
49                 
50                 self.transcoder.add(
51                         self.input_file,
52                         self.decoder,
53                         self.video_input_queue,
54                         self.video_output_queue,
55                         self.audio_input_queue,
56                         self.audio_output_queue,
57                         self.muxer,
58                         self.output_file
59                 )
60                 
61                 for queue in queues:
62                         queue.set_property('max-size-buffers', 0)
63                         queue.set_property('max-size-time', 0)
64                 
65                 gst.element_link_many(self.input_file, self.decoder)
66                 
67                 bus = self.transcoder.get_bus()
68                 bus.add_signal_watch()
69                 bus.connect('message', self.on_message)
70         
71         def decoder_autoplug_continue(self, decoder, pad, caps):
72                 caps_string = caps.to_string()
73                 if caps_string.startswith('video/x-h264'):
74                         return False
75                 elif caps_string.startswith('audio/x-ac3'):
76                         return False
77                 elif caps_string.startswith('audio/mpeg'): # could be AAC, MP3, MP2
78                         return False
79                 return True
80         
81         def decoder_pad_added(self, decoder, pad):
82                 caps_string = pad.get_caps().to_string()
83                 if caps_string.startswith('video'):
84                         pad.link(self.video_input_queue.get_pad('sink'))
85                         if caps_string.startswith('video/x-h264'):
86                                 #h264parse = gst.element_factory_make('h264parse', 'h264parse')
87                                 #h264parse.set_property('output-format', 1)
88                                 #self.transcoder.add(h264parse)
89                                 #gst.element_link_many(self.video_input_queue, h264parse, self.video_output_queue, self.muxer)
90                                 gst.element_link_many(self.video_input_queue, self.video_output_queue, self.muxer)
91                                 #h264parse.set_state(gst.STATE_PLAYING)
92                         else:
93                                 video_encoder = gst.element_factory_make('ffenc_mpeg4', 'video-encoder')
94                                 video_encoder.set_property('bitrate', (2048*1000))
95                                 self.transcoder.add(video_encoder)
96                                 gst.element_link_many(self.video_input_queue, video_encoder, self.video_output_queue, self.muxer)
97                                 video_encoder.set_state(gst.STATE_PLAYING)
98                 elif caps_string.startswith('audio'):
99                         pad.link(self.audio_input_queue.get_pad('sink'))
100                         if caps_string.startswith('audio/x-ac3'):
101                                 ac3parse = gst.element_factory_make('ac3parse', 'ac3parse')
102                                 self.transcoder.add(ac3parse)
103                                 gst.element_link_many(self.audio_input_queue, ac3parse, self.audio_output_queue, self.muxer)
104                                 ac3parse.set_state(gst.STATE_PLAYING)
105                         elif caps_string.startswith('audio/mpeg'):
106                                 gst.element_link_many(self.audio_input_queue, self.audio_output_queue, self.muxer)
107                         else:
108                                 audioconvert = gst.element_factory_make('audioconvert', 'audioconvert')
109                                 audio_encoder = gst.element_factory_make('ffenc_mp2', 'audio-encoder')
110                                 self.transcoder.add(audioconvert, audio_encoder)
111                                 gst.element_link_many(self.audio_input_queue, audioconvert, audio_encoder, self.audio_output_queue, self.muxer)
112                                 audioconvert.set_state(gst.STATE_PLAYING)
113                                 audio_encoder.set_state(gst.STATE_PLAYING)
114         
115         def decoder_no_more_pads(self, decoder):
116                 gst.element_link_many(self.muxer, self.output_file)
117                 self.transcoder.set_state(gst.STATE_PLAYING)
118         
119         def on_message(self, bus, message):
120                 t = message.type
121                 if t == gst.MESSAGE_EOS:
122                         self.transcoder.set_state(gst.STATE_NULL)
123                         self.playmode = False
124                 elif t == gst.MESSAGE_ERROR:
125                         self.transcoder.set_state(gst.STATE_NULL)
126                         err, debug = message.parse_error()
127                         logging.debug("Error: %s" % err, debug)
128                         self.playmode = False
129         
130         def start(self):
131                 self.playmode = True
132                 self.transcoder.set_state(gst.STATE_PAUSED)
133                 while self.playmode:
134                         time.sleep(1)
135                 
136                 time.sleep(1)
137                 loop.quit()
138
139
140 if __name__ == "__main__":
141         parser = argparse.ArgumentParser(description='Hylia intelligent media transcoder.')
142         parser.add_argument('input', help='the path or URL of media to be transcoded')
143         parser.add_argument('output', help='the path where transcoded data will be sent (defaults to /dev/null for testing)', nargs='?', default='/dev/null')
144         args = parser.parse_args()
145         if os.path.isfile(args.input):
146                 mainclass = Main(parser, args)
147                 thread.start_new_thread(mainclass.start, ())
148                 gobject.threads_init()
149                 loop.run()
150         else:
151                 parser.error('Invalid input "%s"' % args.input)