source: trunk/tvctl/src/tvctl.py @ 32

Revision 32, 12.9 KB checked in by atzm, 14 years ago (diff)
  • initial
  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2009-2010, Atzm WATANABE
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# 1. Redistributions of source code must retain the above copyright notice,
11#    this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27#
28# $Id$
29
30import sys
31import os
32import os.path
33import glob
34import commands
35import gettext
36import gobject
37import gtk
38import gst
39
40from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError
41from cStringIO import StringIO
42
43CMD_CH = 'ivtv-tune -d %s -t %s -c %d > /dev/null 2>&1'
44CMD_BL = 'v4l2-ctl -d %s -t %s > /dev/null 2>&1'
45CMD_BL_GET = 'v4l2-ctl -d %s -T'
46CMD_IN = 'v4l2-ctl -d %s --set-input %d > /dev/null 2>&1'
47CMD_IN_GET = "v4l2-ctl -d %s --get-input | awk '{print $4}'"
48CMD_INLST_GET = 'v4l2-ctl -d %s --list-input'
49CMD_CTRL_SET = 'v4l2-ctl -d %s -c %s=%d'
50CMD_CTRL_GET = "v4l2-ctl -d %s -C %s | awk -F': ' '{print $2}'"
51CMD_STND_SET = 'v4l2-ctl -d %s -s %d'
52CMD_STND_GET = "v4l2-ctl -d %s -S | head -1 | awk -F= '{print $2}'"
53CMD_STNDLST_GET = 'v4l2-ctl --list-standards'
54
55gettext.install('tvctl')
56
57config = SafeConfigParser()
58config.readfp(
59    StringIO("""
60[gui]
61channel_max:   12
62channel_width: 3
63"""))
64config.read(os.path.expanduser('~/.tvctl'))
65
66
67def v4l2_ctl_get(*args):
68    return commands.getoutput(CMD_CTRL_GET % args)
69
70
71def v4l2_ctl_set(*args):
72    return commands.getoutput(CMD_CTRL_SET % args)
73
74
75def get_inputs(*args):
76    val = commands.getoutput(CMD_INLST_GET % args)
77    _dict = {}
78
79    for item in [v.split('\n') for v in val.split('\n\n')]:
80        _input = _name = None
81
82        for i in item:
83            key, value = [v.strip() for v in i.split(':', 1)]
84            key = key.lower()
85
86            if key == 'input':
87                _input = int(value)
88            elif key == 'name':
89                _name = value
90
91        if None not in [_input, _name]:
92            _dict[_input] = _name
93
94    return _dict
95
96
97def get_audio_mode(*args):
98    val = commands.getoutput(CMD_BL_GET % args)
99
100    for item in [v.split('\n') for v in val.split('\n\n')]:
101        for i in item:
102            key, value = [v.strip() for v in i.split(':', 1)]
103            key = key.lower()
104
105            if key == 'current audio mode':
106                return value
107
108    raise RuntimeError(val)
109
110
111def get_standards():
112    val = commands.getoutput(CMD_STNDLST_GET)
113    _list = [v.split('\n') for v in val.split('\n\n')]
114
115    for item in _list:
116        _index = _id = _name = None
117
118        for i in item:
119            key, value = [v.strip() for v in i.split(':', 1)]
120            key = key.lower()
121
122            # index is zero origin :-)
123            if key == 'index':
124                _index = int(value)
125            elif key == 'id':
126                _id = int(value, 16)
127            elif key == 'name':
128                _name = value
129
130        if None not in [_index, _id, _name]:
131            _list[_index] = (_id, _name)
132
133    return _list
134
135
136class ChannelTable(gtk.Frame):
137    def __init__(self, device='/dev/video0',
138                 max_ch=config.getint('gui', 'channel_max'),
139                 width=config.getint('gui', 'channel_width')):
140
141        gtk.Frame.__init__(self, label=_('Channel'))
142        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
143        self.set_border_width(3)
144
145        self._tooltips = gtk.Tooltips()
146        self._table = gtk.Table(width, max_ch / width)
147        self._device = device
148        self._buttons = {}
149
150        _prev_radio_button = None
151
152        for _ch in range(max_ch):
153            ch = _ch + 1
154            col = _ch % width
155            row = _ch / width
156            ch = str(ch)
157
158            b = gtk.RadioButton(group=_prev_radio_button, label=ch)
159            b.set_mode(False)
160            b.connect('toggled', self._button_toggled, ch)
161
162            try:
163                tip = config.get('channel_alias', ch)
164                self._tooltips.set_tip(b, tip)
165            except (NoSectionError, NoOptionError):
166                pass
167
168            self._buttons[ch] = _prev_radio_button = b
169            self._table.attach(b, col, col + 1, row, row + 1,
170                               gtk.FILL, gtk.FILL, 2, 2)
171
172        self.add(self._table)
173
174    def _button_toggled(self, button, ch):
175        if not button.get_active():
176            return True
177
178        freq = 'japan-bcast'
179        if config.has_option('channel_assign', ch):
180            freq, ch = config.get('channel_assign', ch).split()
181
182        os.system(CMD_CH % (self._device, freq, int(ch)))
183
184
185class AudioModeTable(gtk.Frame):
186    def __init__(self, device='/dev/video0'):
187        gtk.Frame.__init__(self, label=_('Audio Mode'))
188        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
189        self.set_border_width(3)
190
191        self._vbox = gtk.VBox(True, 2)
192        self._device = device
193        self._buttons = {}
194        self._table = {
195            _('Mono'):      'mono',
196            _('Stereo'):    'stereo',
197            _('Lang1'):     'lang1',
198            _('Lang2'):     'lang2',
199            _('Bilingual'): 'bilingual',
200        }
201
202        _cur_mode = get_audio_mode(device)
203        _prev_radio_button = None
204
205        for label in sorted(self._table.keys()):
206            mode = self._table[label]
207            b = gtk.RadioButton(group=_prev_radio_button, label=label)
208            if mode == _cur_mode:
209                b.set_active(True)
210            b.set_mode(False)
211            b.connect('toggled', self._button_toggled, mode)
212            self._buttons[label] = _prev_radio_button = b
213            self._vbox.pack_start(b)
214
215        self.add(self._vbox)
216
217    def _button_toggled(self, button, mode):
218        if not button.get_active():
219            return True
220        os.system(CMD_BL % (self._device, mode))
221
222
223class InputTable(gtk.Frame):
224    def __init__(self, device='/dev/video0'):
225        gtk.Frame.__init__(self, label=_('Input'))
226        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
227        self.set_border_width(3)
228
229        self._vbox = gtk.VBox(True, 2)
230        self._device = device
231        self._buttons = {}
232        self._table = get_inputs(device)
233
234        _cur_input = int(commands.getoutput(CMD_IN_GET % device))
235        _prev_radio_button = None
236
237        for num in sorted(self._table.keys()):
238            label = self._table[num]
239            b = gtk.RadioButton(group=_prev_radio_button, label=label)
240            if num == _cur_input:
241                b.set_active(True)
242            b.set_mode(False)
243            b.connect('toggled', self._button_toggled, num)
244            self._buttons[label] = _prev_radio_button = b
245            self._vbox.pack_start(b)
246
247        self.add(self._vbox)
248
249    def _button_toggled(self, button, num):
250        if not button.get_active():
251            return True
252        os.system(CMD_IN % (self._device, num))
253
254
255class StandardsComboBox(gtk.ComboBox):
256    def __init__(self, device='/dev/video0'):
257        self._device = device
258        self._standards = get_standards()
259        self._list_store = gtk.ListStore(gobject.TYPE_STRING)
260        self._cell = gtk.CellRendererText()
261
262        gtk.ComboBox.__init__(self, self._list_store)
263        self.pack_start(self._cell, True)
264        self.add_attribute(self._cell, 'text', 0)
265
266        for _id, _name in self._standards:
267            self.append_text(_name)
268
269        self._set_active_by_id(
270            int(commands.getoutput(CMD_STND_GET % self._device), 16))
271
272        self.connect('changed', self._changed)
273
274    def _set_active_by_id(self, _id):
275        for i in range(len(self._standards)):
276            if self._standards[i][0] == _id:
277                self.set_active(i)
278                break
279
280    def _changed(self, combo_box):
281        os.system(CMD_STND_SET % (self._device, combo_box.get_active()))
282
283
284class V4L2ControlScale(gtk.Frame):
285    def __init__(self, label, ctrl, max_val, device='/dev/video0'):
286        self._device = device
287        self._label = label
288        self._ctrl = ctrl
289        self._max_val = max_val
290
291        gtk.Frame.__init__(self, label=self._label)
292        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
293        self.set_border_width(3)
294
295        current = v4l2_ctl_get(self._device, self._ctrl)
296        default = int(float(current) / self._max_val * 100)
297
298        self._adj = gtk.Adjustment(default, 0, 100, 1, 10, 0)
299        self._adj.connect('value-changed', self._adj_value_changed)
300
301        self._hscale = gtk.HScale(self._adj)
302        self._hscale.set_value_pos(gtk.POS_RIGHT)
303        self._hscale.set_digits(0)
304
305        self.vbox = gtk.VBox(False, 0)
306        self.vbox.pack_start(self._hscale)
307
308        self.add(self.vbox)
309
310    def _adj_value_changed(self, adj):
311        val = self._max_val * (adj.get_value() / 100)
312        return v4l2_ctl_set(self._device, self._ctrl, val)
313
314
315class VolumeScale(V4L2ControlScale):
316    def __init__(self, device='/dev/video0'):
317        V4L2ControlScale.__init__(self, device=device, label=_('Volume'),
318                                  ctrl='volume', max_val=65535)
319
320        mute = bool(int(v4l2_ctl_get(self._device, 'mute')))
321
322        self._mute_button = gtk.CheckButton(label=_('Mute'))
323        self._mute_button.set_active(mute)
324        self._mute_button.connect('toggled', self._button_toggled)
325        self.get_child().pack_start(self._mute_button)
326
327    def _button_toggled(self, button):
328        mute = int(button.get_active())
329        v4l2_ctl_set(self._device, 'mute', mute)
330
331
332class PlayButton(gtk.Button):
333    def __init__(self, device='/dev/video0'):
334        gtk.Button.__init__(self)
335
336        self._image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
337                                               gtk.ICON_SIZE_BUTTON)
338        self.set_property('image', self._image)
339
340        self._player = gst.element_factory_make('playbin2', 'player')
341        self._player.set_property('uri', 'file://' + device)
342
343        bus = self._player.get_bus()
344        bus.add_signal_watch()
345        bus.connect('message', self._caught_message)
346
347        self.connect('clicked', self._button_clicked)
348
349    def _start(self):
350        self._player.set_state(gst.STATE_PLAYING)
351        self._image.set_from_stock(gtk.STOCK_MEDIA_STOP, gtk.ICON_SIZE_BUTTON)
352
353    def _stop(self):
354        self._player.set_state(gst.STATE_NULL)
355        self._image.set_from_stock(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_BUTTON)
356
357    def _button_clicked(self, button):
358        if self._image.get_stock()[0] == gtk.STOCK_MEDIA_PLAY:
359            self._start()
360        else:
361            self._stop()
362
363    def _caught_message(self, bus, msg):
364        if msg.type in [gst.MESSAGE_EOS, gst.MESSAGE_ERROR]:
365            self._stop()
366
367
368class DeviceNotebook(gtk.Notebook):
369    def __init__(self):
370        gtk.Notebook.__init__(self)
371        self._devices = glob.glob('/dev/video?')
372        self._devices.sort()
373
374        for d in self._devices:
375            hbox = gtk.HBox(False, 5)
376            hbox.pack_start(ChannelTable(device=d))
377            hbox.pack_start(AudioModeTable(device=d))
378            hbox.pack_start(InputTable(device=d))
379
380            hbox2 = gtk.HBox(False, 5)
381            hbox2.pack_start(PlayButton(device=d))
382            hbox2.pack_start(StandardsComboBox(device=d))
383
384            volume = VolumeScale(device=d)
385            balance = V4L2ControlScale(device=d, label=_('Balance'),
386                                       ctrl='balance', max_val=65535)
387            brightness = V4L2ControlScale(device=d, label=_('Brightness'),
388                                          ctrl='brightness', max_val=255)
389            contrast = V4L2ControlScale(device=d, label=_('Contrast'),
390                                        ctrl='contrast', max_val=127)
391
392            vbox = gtk.VBox(False, 0)
393            vbox.pack_start(hbox)
394            vbox.pack_start(hbox2)
395            vbox.pack_start(volume)
396            vbox.pack_start(balance)
397            vbox.pack_start(brightness)
398            vbox.pack_start(contrast)
399
400            self.append_page(vbox, gtk.Label(os.path.basename(d)))
401
402
403def main():
404    notebook = DeviceNotebook()
405
406    window = gtk.Window()
407    window.set_title(_('TV Controller'))
408    window.connect('destroy', lambda w: gtk.main_quit())
409
410    window.add(notebook)
411    window.show_all()
412    gtk.main()
413
414
415if __name__ == '__main__':
416    main()
Note: See TracBrowser for help on using the repository browser.