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

Revision 33, 13.2 KB checked in by atzm, 14 years ago (diff)
  • add authorship
  • fixed, list-standards with device
  • Property svn:executable set to *
  • Property svn:keywords set to Id Author Revision
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
43__author__ = '$Author$'
44__copyright__ = 'Copyright 2009-2010, Atzm WATANABE'
45__credits__ = [__author__]
46__license__ = 'BSD-2'
47__version__ = '$Revision$'
48__maintainer__ = __author__
49__email__ = 'atzm@atzm.org'
50__status__ = 'Development'
51
52CMD_CH = 'ivtv-tune -d %s -t %s -c %d > /dev/null 2>&1'
53CMD_BL = 'v4l2-ctl -d %s -t %s > /dev/null 2>&1'
54CMD_BL_GET = 'v4l2-ctl -d %s -T'
55CMD_IN = 'v4l2-ctl -d %s --set-input %d > /dev/null 2>&1'
56CMD_IN_GET = "v4l2-ctl -d %s --get-input | awk '{print $4}'"
57CMD_INLST_GET = 'v4l2-ctl -d %s --list-input'
58CMD_CTRL_SET = 'v4l2-ctl -d %s -c %s=%d'
59CMD_CTRL_GET = "v4l2-ctl -d %s -C %s | awk -F': ' '{print $2}'"
60CMD_STND_SET = 'v4l2-ctl -d %s -s %d'
61CMD_STND_GET = "v4l2-ctl -d %s -S | head -1 | awk -F= '{print $2}'"
62CMD_STNDLST_GET = 'v4l2-ctl -d %s --list-standards'
63
64gettext.install('tvctl')
65
66config = SafeConfigParser()
67config.readfp(
68    StringIO("""
69[gui]
70channel_max:   12
71channel_width: 3
72"""))
73config.read(os.path.expanduser('~/.tvctl'))
74
75
76def v4l2_ctl_get(*args):
77    return commands.getoutput(CMD_CTRL_GET % args)
78
79
80def v4l2_ctl_set(*args):
81    return commands.getoutput(CMD_CTRL_SET % args)
82
83
84def get_inputs(*args):
85    val = commands.getoutput(CMD_INLST_GET % args)
86    _dict = {}
87
88    for item in [v.split('\n') for v in val.split('\n\n')]:
89        _input = _name = None
90
91        for i in item:
92            key, value = [v.strip() for v in i.split(':', 1)]
93            key = key.lower()
94
95            if key == 'input':
96                _input = int(value)
97            elif key == 'name':
98                _name = value
99
100        if None not in [_input, _name]:
101            _dict[_input] = _name
102
103    return _dict
104
105
106def get_audio_mode(*args):
107    val = commands.getoutput(CMD_BL_GET % args)
108
109    for item in [v.split('\n') for v in val.split('\n\n')]:
110        for i in item:
111            key, value = [v.strip() for v in i.split(':', 1)]
112            key = key.lower()
113
114            if key == 'current audio mode':
115                return value
116
117    raise RuntimeError(val)
118
119
120def get_standards(*args):
121    val = commands.getoutput(CMD_STNDLST_GET % args)
122    _list = [v.split('\n') for v in val.split('\n\n')]
123
124    for item in _list:
125        _index = _id = _name = None
126
127        for i in item:
128            key, value = [v.strip() for v in i.split(':', 1)]
129            key = key.lower()
130
131            # index is zero origin :-)
132            if key == 'index':
133                _index = int(value)
134            elif key == 'id':
135                _id = int(value, 16)
136            elif key == 'name':
137                _name = value
138
139        if None not in [_index, _id, _name]:
140            _list[_index] = (_id, _name)
141
142    return _list
143
144
145class ChannelTable(gtk.Frame):
146    def __init__(self, device='/dev/video0',
147                 max_ch=config.getint('gui', 'channel_max'),
148                 width=config.getint('gui', 'channel_width')):
149
150        gtk.Frame.__init__(self, label=_('Channel'))
151        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
152        self.set_border_width(3)
153
154        self._tooltips = gtk.Tooltips()
155        self._table = gtk.Table(width, max_ch / width)
156        self._device = device
157        self._buttons = {}
158
159        _prev_radio_button = None
160
161        for _ch in range(max_ch):
162            ch = _ch + 1
163            col = _ch % width
164            row = _ch / width
165            ch = str(ch)
166
167            b = gtk.RadioButton(group=_prev_radio_button, label=ch)
168            b.set_mode(False)
169            b.connect('toggled', self._button_toggled, ch)
170
171            try:
172                tip = config.get('channel_alias', ch)
173                self._tooltips.set_tip(b, tip)
174            except (NoSectionError, NoOptionError):
175                pass
176
177            self._buttons[ch] = _prev_radio_button = b
178            self._table.attach(b, col, col + 1, row, row + 1,
179                               gtk.FILL, gtk.FILL, 2, 2)
180
181        self.add(self._table)
182
183    def _button_toggled(self, button, ch):
184        if not button.get_active():
185            return True
186
187        freq = 'japan-bcast'
188        if config.has_option('channel_assign', ch):
189            freq, ch = config.get('channel_assign', ch).split()
190
191        os.system(CMD_CH % (self._device, freq, int(ch)))
192
193
194class AudioModeTable(gtk.Frame):
195    def __init__(self, device='/dev/video0'):
196        gtk.Frame.__init__(self, label=_('Audio Mode'))
197        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
198        self.set_border_width(3)
199
200        self._vbox = gtk.VBox(True, 2)
201        self._device = device
202        self._buttons = {}
203        self._table = {
204            _('Mono'):      'mono',
205            _('Stereo'):    'stereo',
206            _('Lang1'):     'lang1',
207            _('Lang2'):     'lang2',
208            _('Bilingual'): 'bilingual',
209        }
210
211        _cur_mode = get_audio_mode(device)
212        _prev_radio_button = None
213
214        for label in sorted(self._table.keys()):
215            mode = self._table[label]
216            b = gtk.RadioButton(group=_prev_radio_button, label=label)
217            if mode == _cur_mode:
218                b.set_active(True)
219            b.set_mode(False)
220            b.connect('toggled', self._button_toggled, mode)
221            self._buttons[label] = _prev_radio_button = b
222            self._vbox.pack_start(b)
223
224        self.add(self._vbox)
225
226    def _button_toggled(self, button, mode):
227        if not button.get_active():
228            return True
229        os.system(CMD_BL % (self._device, mode))
230
231
232class InputTable(gtk.Frame):
233    def __init__(self, device='/dev/video0'):
234        gtk.Frame.__init__(self, label=_('Input'))
235        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
236        self.set_border_width(3)
237
238        self._vbox = gtk.VBox(True, 2)
239        self._device = device
240        self._buttons = {}
241        self._table = get_inputs(device)
242
243        _cur_input = int(commands.getoutput(CMD_IN_GET % device))
244        _prev_radio_button = None
245
246        for num in sorted(self._table.keys()):
247            label = self._table[num]
248            b = gtk.RadioButton(group=_prev_radio_button, label=label)
249            if num == _cur_input:
250                b.set_active(True)
251            b.set_mode(False)
252            b.connect('toggled', self._button_toggled, num)
253            self._buttons[label] = _prev_radio_button = b
254            self._vbox.pack_start(b)
255
256        self.add(self._vbox)
257
258    def _button_toggled(self, button, num):
259        if not button.get_active():
260            return True
261        os.system(CMD_IN % (self._device, num))
262
263
264class StandardsComboBox(gtk.ComboBox):
265    def __init__(self, device='/dev/video0'):
266        self._device = device
267        self._standards = get_standards(device)
268        self._list_store = gtk.ListStore(gobject.TYPE_STRING)
269        self._cell = gtk.CellRendererText()
270
271        gtk.ComboBox.__init__(self, self._list_store)
272        self.pack_start(self._cell, True)
273        self.add_attribute(self._cell, 'text', 0)
274
275        for _id, _name in self._standards:
276            self.append_text(_name)
277
278        self._set_active_by_id(
279            int(commands.getoutput(CMD_STND_GET % self._device), 16))
280
281        self.connect('changed', self._changed)
282
283    def _set_active_by_id(self, _id):
284        for i in range(len(self._standards)):
285            if self._standards[i][0] == _id:
286                self.set_active(i)
287                break
288
289    def _changed(self, combo_box):
290        os.system(CMD_STND_SET % (self._device, combo_box.get_active()))
291
292
293class V4L2ControlScale(gtk.Frame):
294    def __init__(self, label, ctrl, max_val, device='/dev/video0'):
295        self._device = device
296        self._label = label
297        self._ctrl = ctrl
298        self._max_val = max_val
299
300        gtk.Frame.__init__(self, label=self._label)
301        self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
302        self.set_border_width(3)
303
304        current = v4l2_ctl_get(self._device, self._ctrl)
305        default = int(float(current) / self._max_val * 100)
306
307        self._adj = gtk.Adjustment(default, 0, 100, 1, 10, 0)
308        self._adj.connect('value-changed', self._adj_value_changed)
309
310        self._hscale = gtk.HScale(self._adj)
311        self._hscale.set_value_pos(gtk.POS_RIGHT)
312        self._hscale.set_digits(0)
313
314        self.vbox = gtk.VBox(False, 0)
315        self.vbox.pack_start(self._hscale)
316
317        self.add(self.vbox)
318
319    def _adj_value_changed(self, adj):
320        val = self._max_val * (adj.get_value() / 100)
321        return v4l2_ctl_set(self._device, self._ctrl, val)
322
323
324class VolumeScale(V4L2ControlScale):
325    def __init__(self, device='/dev/video0'):
326        V4L2ControlScale.__init__(self, device=device, label=_('Volume'),
327                                  ctrl='volume', max_val=65535)
328
329        mute = bool(int(v4l2_ctl_get(self._device, 'mute')))
330
331        self._mute_button = gtk.CheckButton(label=_('Mute'))
332        self._mute_button.set_active(mute)
333        self._mute_button.connect('toggled', self._button_toggled)
334        self.get_child().pack_start(self._mute_button)
335
336    def _button_toggled(self, button):
337        mute = int(button.get_active())
338        v4l2_ctl_set(self._device, 'mute', mute)
339
340
341class PlayButton(gtk.Button):
342    def __init__(self, device='/dev/video0'):
343        gtk.Button.__init__(self)
344
345        self._image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
346                                               gtk.ICON_SIZE_BUTTON)
347        self.set_property('image', self._image)
348
349        self._player = gst.element_factory_make('playbin2', 'player')
350        self._player.set_property('uri', 'file://' + device)
351
352        bus = self._player.get_bus()
353        bus.add_signal_watch()
354        bus.connect('message', self._caught_message)
355
356        self.connect('clicked', self._button_clicked)
357
358    def _start(self):
359        self._player.set_state(gst.STATE_PLAYING)
360        self._image.set_from_stock(gtk.STOCK_MEDIA_STOP, gtk.ICON_SIZE_BUTTON)
361
362    def _stop(self):
363        self._player.set_state(gst.STATE_NULL)
364        self._image.set_from_stock(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_BUTTON)
365
366    def _button_clicked(self, button):
367        if self._image.get_stock()[0] == gtk.STOCK_MEDIA_PLAY:
368            self._start()
369        else:
370            self._stop()
371
372    def _caught_message(self, bus, msg):
373        if msg.type in [gst.MESSAGE_EOS, gst.MESSAGE_ERROR]:
374            self._stop()
375
376
377class DeviceNotebook(gtk.Notebook):
378    def __init__(self):
379        gtk.Notebook.__init__(self)
380        self._devices = glob.glob('/dev/video?')
381        self._devices.sort()
382
383        for d in self._devices:
384            hbox = gtk.HBox(False, 5)
385            hbox.pack_start(ChannelTable(device=d))
386            hbox.pack_start(AudioModeTable(device=d))
387            hbox.pack_start(InputTable(device=d))
388
389            hbox2 = gtk.HBox(False, 5)
390            hbox2.pack_start(PlayButton(device=d))
391            hbox2.pack_start(StandardsComboBox(device=d))
392
393            volume = VolumeScale(device=d)
394            balance = V4L2ControlScale(device=d, label=_('Balance'),
395                                       ctrl='balance', max_val=65535)
396            brightness = V4L2ControlScale(device=d, label=_('Brightness'),
397                                          ctrl='brightness', max_val=255)
398            contrast = V4L2ControlScale(device=d, label=_('Contrast'),
399                                        ctrl='contrast', max_val=127)
400
401            vbox = gtk.VBox(False, 0)
402            vbox.pack_start(hbox)
403            vbox.pack_start(hbox2)
404            vbox.pack_start(volume)
405            vbox.pack_start(balance)
406            vbox.pack_start(brightness)
407            vbox.pack_start(contrast)
408
409            self.append_page(vbox, gtk.Label(os.path.basename(d)))
410
411
412def main():
413    notebook = DeviceNotebook()
414
415    window = gtk.Window()
416    window.set_title(_('TV Controller'))
417    window.connect('destroy', lambda w: gtk.main_quit())
418
419    window.add(notebook)
420    window.show_all()
421    gtk.main()
422
423
424if __name__ == '__main__':
425    main()
Note: See TracBrowser for help on using the repository browser.