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
RevLine 
[32]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
[33]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
[32]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}'"
[33]62CMD_STNDLST_GET = 'v4l2-ctl -d %s --list-standards'
[32]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
[33]120def get_standards(*args):
121    val = commands.getoutput(CMD_STNDLST_GET % args)
[32]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
[33]267        self._standards = get_standards(device)
[32]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.