1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
39
from miro.gtcache import gettext as _
40
from miro.plat import resources
42
from miro import config
43
from miro import prefs
44
from miro.frontends.widgets import menus
45
from miro.frontends.widgets.widgetconst import MAX_VOLUME
46
from miro.util import copy_subtitle_file
49
libvlc = ctypes.cdll.libvlc
50
libvlccore = ctypes.cdll.libvlccore
52
# Pick out functions that start with "__". It's very awkward to use them
54
vlc_object_release = libvlccore.__vlc_object_release
56
config_GetInt = libvlccore.__config_GetInt
57
config_PutInt = libvlccore.__config_PutInt
58
config_GetFloat = libvlccore.__config_GetFloat
59
config_PutFloat = libvlccore.__config_PutFloat
60
config_GetPsz = libvlccore.__config_GetPsz
61
config_PutPsz = libvlccore.__config_PutPsz
64
# set up the function signatures
65
libvlc_MediaStateChanged = 5
67
(libvlc_NothingSpecial,
74
libvlc_Error) = range(8)
77
EnableWindow = ctypes.windll.user32.EnableWindow
79
class VLCError(Exception):
82
class VLCException(ctypes.Structure):
84
('raised', ctypes.c_int),
85
('code', ctypes.c_int),
86
('message', ctypes.c_char_p)
90
ctypes.Structure.__init__(self)
91
libvlc.libvlc_exception_init(self.ref())
94
return ctypes.byref(self)
99
libvlc.libvlc_exception_clear(self.ref())
100
raise VLCError(repr(self.code) + " " + repr(msg))
102
class VLCEvent(ctypes.Structure):
104
('type', ctypes.c_int),
105
('p_obj', ctypes.c_void_p),
106
('arg1', ctypes.c_int),
107
('arg2', ctypes.c_int),
110
class VLCTrackDescription(ctypes.Structure):
111
# The libvlc_track_description_t structure type is
112
# self-referencing so we have to specify the fields after the
116
VLCTrackDescription._fields_ = [
117
('id', ctypes.c_int),
118
('name', ctypes.c_char_p),
119
('next', ctypes.POINTER(VLCTrackDescription))
122
libvlc.libvlc_video_get_spu_description.restype = ctypes.POINTER(
124
libvlc.libvlc_video_get_track_description.restype = ctypes.POINTER(
127
VLC_EVENT_CALLBACK = ctypes.CFUNCTYPE(
128
None, ctypes.POINTER(VLCEvent), ctypes.c_void_p)
130
def make_string_list(args):
131
ArgsArray = ctypes.c_char_p * len(args)
132
return ArgsArray(*args)
134
STOPPED, PAUSED, PLAYING = range(3)
136
class VLCSniffer(object):
138
plugin_dir = os.path.join(resources.appRoot(), 'vlc-plugins')
139
self.exc = VLCException()
141
# Note: if you need vlc output to stdout, remove the --quiet
142
# from the list of arguments. Also, you can add -vvv.
145
'--nostats', '--intf', 'dummy', '--volume=0',
146
'--no-video-title-show', '--plugin-path', plugin_dir
148
self.vlc = libvlc.libvlc_new(
149
len(vlc_args), make_string_list(vlc_args), self.exc.ref())
151
self.media_player = libvlc.libvlc_media_player_new(
152
self.vlc, self.exc.ref())
154
self._callback_ref = VLC_EVENT_CALLBACK(self.event_callback)
155
self._filename = None
156
self.media_playing = None
157
self.callback_info = None
159
self._hidden_window = gtk.gdk.Window(
160
None, x=0, y=0, width=1, height=1,
161
window_type=gtk.gdk.WINDOW_TOPLEVEL,
162
wclass=gtk.gdk.INPUT_OUTPUT, event_mask=0)
163
libvlc.libvlc_media_player_set_hwnd(
164
self.media_player, self._hidden_window.handle, self.exc.ref())
168
logging.info("shutting down VLC Sniffer")
169
libvlc.libvlc_media_player_release(self.media_player)
170
libvlc.libvlc_release(self.vlc)
172
def event_callback(self, p_event, p_user_data):
174
# Copy the values from event, the memory might be freed by the
175
# time handle_event gets called.
180
gobject.idle_add(self.handle_event, obj, type_, arg1, arg2)
182
def handle_event(self, obj, type_, state, arg2):
183
if type_ != libvlc_MediaStateChanged:
185
if obj != self.media_playing:
187
if self.callback_info is None:
188
# We the video has already been opened (successfully or
190
if state == libvlc_Ended:
191
app.playback_manager.on_movie_finished()
194
# We are waiting to see if the video opens successfully
195
if state in (libvlc_Error, libvlc_Ended):
197
elif state == libvlc_Playing:
198
libvlc.libvlc_media_player_pause(
199
self.media_player, self.exc.ref())
203
def _open_success(self):
204
# FIXME - sometimes _open_success is called, but callback_info
205
# is None. not sure why this happens.
206
item_type = "failure"
207
if self.callback_info:
208
video_tracks = libvlc.libvlc_video_get_track_count(
209
self.media_player, self.exc.ref())
214
audio_tracks = libvlc.libvlc_audio_get_track_count(
215
self.media_player, self.exc.ref())
223
elif audio_tracks > 0:
226
item_type = "unplayable"
228
libvlc.libvlc_media_player_stop(self.media_player, self.exc.ref())
230
except VLCError, vlce:
231
logging.warning("sniffer reset failed: %s", vlce)
232
self.callback_info[0](item_type)
233
self.callback_info = None
234
self.media_playing = None
236
def _open_failure(self):
238
libvlc.libvlc_media_player_stop(self.media_player, self.exc.ref())
240
except VLCError, vlce:
241
logging.warning("sniffer reset failed: %s", vlce)
242
self.callback_info[1]()
243
self.callback_info = None
244
self.media_playing = None
246
def select_file(self, iteminfo, success_callback, error_callback):
247
"""starts playing the specified file"""
248
filename = iteminfo.video_path
250
# filenames coming in are unicode objects, VLC expects utf-8
252
filename = filename.encode('utf-8')
253
self._filename = filename
254
self.callback_info = (success_callback, error_callback)
255
self.play_state = STOPPED
257
media = libvlc.libvlc_media_new(self.vlc, ctypes.c_char_p(filename),
261
raise AssertionError(
262
"libvlc_media_new returned NULL for %s" % filename)
263
event_manager = libvlc.libvlc_media_event_manager(media,
266
libvlc.libvlc_event_attach(event_manager,
267
libvlc_MediaStateChanged,
273
libvlc.libvlc_media_player_set_media(self.media_player,
278
libvlc.libvlc_media_release(media)
279
self.media_playing = media
280
# We want to load the media to test if we can play it. The
281
# best way that I can see to do that is to play it, then pause
282
# once we see it's opened in the event_callack method.
283
libvlc.libvlc_media_player_play(self.media_player, self.exc.ref())
286
libvlc.libvlc_media_player_pause(self.media_player, self.exc.ref())
289
class VLCRenderer(object):
291
logging.info("Initializing VLC")
292
plugin_dir = os.path.join(resources.appRoot(), 'vlc-plugins')
293
self.exc = VLCException()
295
# Note: if you need vlc output to stdout, remove the --quiet
296
# from the list of arguments.
298
"vlc", '--quiet', '--nostats', '--intf', 'dummy',
299
'--no-video-title-show', '--plugin-path', plugin_dir
301
self.vlc = libvlc.libvlc_new(len(vlc_args),
302
make_string_list(vlc_args), self.exc.ref())
304
self.vlc_instance = libvlc.libvlc_get_vlc_instance(self.vlc)
305
self.media_player = libvlc.libvlc_media_player_new(self.vlc,
308
self._callback_ref = VLC_EVENT_CALLBACK(self.event_callback)
309
self.play_from_time = None
310
self.play_state = STOPPED
311
self._duration = None
312
self._filename = None
314
self.media_playing = None
315
self.callback_info = None
316
self._change_subtitle_timout = None
317
self.subtitle_info = []
318
self._hidden_window = gtk.gdk.Window(
319
None, x=0, y=0, width=1, height=1,
320
window_type=gtk.gdk.WINDOW_TOPLEVEL,
321
wclass=gtk.gdk.INPUT_OUTPUT, event_mask=0)
325
logging.info("shutting down VLC")
327
libvlc.libvlc_media_player_release(self.media_player)
328
vlc_object_release(self.vlc_instance)
329
libvlc.libvlc_release(self.vlc)
332
def event_callback(self, p_event, p_user_data):
334
# Copy the values from event, the memory might be freed by the
335
# time handle_event gets called.
340
gobject.idle_add(self.handle_event, obj, type_, arg1, arg2)
342
def handle_event(self, obj, type_, arg1, arg2):
343
if type_ == libvlc_MediaStateChanged:
344
self._handle_state_change(obj, arg1)
346
logging.warn("Unknown VLC event type: %s", type_)
348
def _handle_state_change(self, obj, state):
349
if obj != self.media_playing:
351
if self.callback_info is None:
352
# We the video has already been opened (successfully or
354
if state == libvlc_Ended:
355
app.playback_manager.on_movie_finished()
358
# We are waiting to see if the video opens successfully
359
if state in (libvlc_Error, libvlc_Ended):
361
elif state == libvlc_Playing:
362
libvlc.libvlc_media_player_pause(self.media_player,
367
def _length_check(self, attempt=0):
368
# sometimes garbage data will appear to open, but it VLC won't
369
# actually play anything. Use the length to double check that
370
# we actually will play. We try three attempts because
371
# sometimes it takes a bit to figure out the length.
375
if self._file_type != 'video':
376
# for items the user has marked as audio, disable video
378
self._disable_video()
380
length = libvlc.libvlc_media_player_get_length(
381
self.media_player, self.exc.ref())
387
gobject.timeout_add(500, self._length_check, attempt+1)
389
def _open_success(self):
390
# FIXME - sometimes _open_success is called, but callback_info
391
# is None. not sure why this happens.
392
self.setup_subtitles()
393
if self.callback_info:
394
self.callback_info[0]()
395
self.callback_info = None
397
def _open_failure(self):
398
logging.info("_open_failure\n%s", "".join(traceback.format_stack()))
399
self.callback_info[1]()
400
self.callback_info = None
401
self.media_playing = None
403
def _disable_video(self):
404
desc = libvlc.libvlc_video_get_track_description(
405
self.media_player, self.exc.ref())
407
# the 1st description should be "Disable"
409
track_id = desc.contents.id
410
libvlc.libvlc_track_description_release(desc)
411
libvlc.libvlc_video_set_track(self.media_player, track_id,
415
def set_widget(self, widget):
416
hwnd = widget.persistent_window.handle
417
libvlc.libvlc_media_player_set_hwnd(self.media_player, hwnd,
421
widget.add_events(gtk.gdk.EXPOSURE_MASK)
422
widget.connect('expose-event', self._on_expose)
423
EnableWindow(hwnd, 0)
425
def unset_widget(self):
426
libvlc.libvlc_media_player_set_hwnd(
427
self.media_player, self._hidden_window.handle, self.exc.ref())
430
def _on_expose(self, widget, event):
431
gc = widget.style.black_gc
432
widget.persistent_window.draw_rectangle(
433
gc, True, event.area.x, event.area.y,
434
event.area.width, event.area.height)
436
def select_file(self, iteminfo, callback, errback):
437
"""starts playing the specified file"""
438
filename = iteminfo.video_path
440
# filenames coming in are unicode objects, VLC expects utf-8
442
filename = filename.encode('utf-8')
443
self._filename = filename
444
self._file_type = iteminfo.file_type
445
self.subtitle_info = []
446
self.callback_info = (callback, errback)
447
self.play_from_time = None
448
self.play_state = STOPPED
450
media = libvlc.libvlc_media_new(self.vlc, ctypes.c_char_p(filename),
454
raise AssertionError(
455
"libvlc_media_new returned NULL for %s" % filename)
456
event_manager = libvlc.libvlc_media_event_manager(media,
459
libvlc.libvlc_event_attach(event_manager, libvlc_MediaStateChanged,
460
self._callback_ref, None, self.exc.ref())
463
libvlc.libvlc_media_player_set_media(self.media_player, media,
467
libvlc.libvlc_media_release(media)
468
self.media_playing = media
469
self.setup_subtitle_font()
470
# We want to load the media to test if we can play it. The
471
# best way that I can see to do that is to play it, then pause
472
# once we see it's opened in the event_callack method.
473
libvlc.libvlc_media_player_play(self.media_player, self.exc.ref())
475
# For unknown reasons, sometimes we don't see the state
476
# changed event if they happen quickly enough. To work around
477
# that, check the initial state of the media player.
478
state = libvlc.libvlc_media_player_get_state(self.media_player,
481
self._handle_state_change(self.media_playing, state)
484
if self.play_state == PLAYING:
486
libvlc.libvlc_media_player_play(self.media_player, self.exc.ref())
488
self.play_state = PLAYING
489
if self.play_from_time is not None:
490
self.set_current_time(self.play_from_time)
491
self.play_from_time = None
494
if self.play_state == PAUSED:
496
libvlc.libvlc_media_player_pause(self.media_player, self.exc.ref())
498
self.play_state = PAUSED
501
if self.play_state == STOPPED:
503
self.callback_info = None
504
self.media_playing = None
505
libvlc.libvlc_media_player_stop(self.media_player, self.exc.ref())
507
self.play_state = STOPPED
508
self.subtitle_info = []
512
self.play_from_time = None
513
self.play_state = STOPPED
515
def get_current_time(self):
516
t = libvlc.libvlc_media_player_get_time(
517
self.media_player, self.exc.ref())
521
logging.warn("exception getting time: %s" % e)
526
def set_current_time(self, seconds):
527
if not self.play_state == PLAYING:
528
self.play_from_time = seconds
530
t = int(seconds * 1000)
532
# I have no clue why this this is, but setting time=1
533
# (1/1000th of a second) fixes #15079
535
libvlc.libvlc_media_player_set_time(
536
self.media_player, ctypes.c_longlong(t), self.exc.ref())
540
logging.warn("exception setting current time %s" % e)
542
def get_duration(self):
543
# self._duration = (filename, duration)
544
if self._duration and self._duration[0] == self._filename:
545
return self._duration[1]
547
length = libvlc.libvlc_media_player_get_length(
548
self.media_player, self.exc.ref())
552
logging.warn("exception getting duration: %s" % e)
555
self._duration = (self._filename, length / 1000.0)
556
return self._duration[1]
558
def set_volume(self, volume):
559
volume = int(200 * volume / MAX_VOLUME)
560
libvlc.libvlc_audio_set_volume(self.vlc, volume, self.exc.ref())
563
def get_volume(self, volume):
564
rv = libvlc.libvlc_audio_get_volume(self.vlc, self.exc.ref())
568
def set_rate(self, rate):
569
logging.info("set_rate: rate %s", rate)
570
if self._rate == rate:
573
libvlc.libvlc_media_player_set_rate(self.media_player,
574
ctypes.c_float(rate), self.exc.ref())
578
logging.warn("exception setting rate: %s" % e)
584
def setup_subtitles(self):
585
self.setup_subtitle_info()
586
if config.get(prefs.ENABLE_SUBTITLES):
587
track_index = self.get_enabled_subtitle_track()
589
count = libvlc.libvlc_video_get_spu_count(
590
self.media_player, self.exc.ref())
592
self.enable_subtitle_track(1)
594
self.disable_subtitles()
596
def setup_subtitle_font(self):
597
font_path = config.get(prefs.SUBTITLE_FONT)
598
config_PutPsz(self.vlc_instance,
599
ctypes.c_char_p('freetype-font'),
600
ctypes.c_char_p(font_path))
601
logging.info("Setting VLC subtitle font: %s", font_path)
603
def get_subtitle_tracks(self):
604
return self.subtitle_info
606
def setup_subtitle_info(self):
607
self.subtitle_info = list()
609
desc = libvlc.libvlc_video_get_spu_description(
610
self.media_player, self.exc.ref())
612
count = libvlc.libvlc_video_get_spu_count(
613
self.media_player, self.exc.ref())
616
for i in range(0, count):
617
if i > 0: # track 0 is "disabled", don't include it
618
self.subtitle_info.append((i, desc.contents.name))
619
desc = desc.contents.next
620
libvlc.libvlc_track_description_release(first_desc)
622
logging.warn("exception when getting list of subtitle tracks")
624
def get_enabled_subtitle_track(self):
625
track_index = libvlc.libvlc_video_get_spu(
626
self.media_player, self.exc.ref())
630
logging.warn("exception when getting enabled subtitle track")
634
def enable_subtitle_track(self, track_index):
635
if self._change_subtitle_timout:
636
gobject.source_remove(self._change_subtitle_timout)
637
self._change_subtitle_timout = None
638
self._set_active_subtitle_track(track_index)
640
def disable_subtitles(self):
641
self._set_active_subtitle_track(0)
643
def _set_active_subtitle_track(self, track_index):
644
count = libvlc.libvlc_video_get_spu_count(
645
self.media_player, self.exc.ref())
648
# if we're disabling subtitles but there aren't any, we
650
if track_index == 0 and count == 0:
653
if track_index >= count:
654
logging.warn("Subtitle track too high: %s (count: %s)",
657
libvlc.libvlc_video_set_spu(self.media_player, track_index,
662
logging.warn("exception when setting subtitle track: %s", e)
664
def select_subtitle_file(self, item, sub_path, handle_successful_select):
666
sub_path = copy_subtitle_file(sub_path, item.video_path)
668
# FIXME - need a better way to deal with this. when this
669
# happens, then the subtitle file isn't in the right place
670
# for VLC to pick it up on the next playback forcing the
671
# user to select it again.
673
logging.exception("exception thrown when copying subtitle file")
675
sub_path = sub_path.encode('utf-8')
676
res = libvlc.libvlc_video_set_subtitle_file(
677
self.media_player, ctypes.c_char_p(sub_path), self.exc.ref())
681
logging.warn("exception when setting subtitle track to file: %s", e)
683
handle_successful_select()
684
# 1 is the track of the external file, don't select it quite yet
685
# because VLC might not be ready. (#12858)
686
self._change_subtitle_timout = gobject.timeout_add(100,
687
self.handle_change_subtitle_timout)
689
def handle_change_subtitle_timout(self):
690
self._change_subtitle_timout = None
691
self.setup_subtitle_info()
692
self.enable_subtitle_track(1)
694
def select_subtitle_encoding(self, encoding):
695
if self.media_playing is None:
699
config_PutPsz(self.vlc_instance,
700
ctypes.c_char_p('subsdec-encoding'),
701
ctypes.c_char_p(encoding))
702
logging.info("Setting VLC subtitle encoding: %s", encoding)
704
def setup_subtitle_encoding_menu(self, menubar):
705
menus.add_subtitle_encoding_menu(menubar, _('Eastern European'),
706
('Latin-7', _('Baltic')),
707
('Windows-1257', _('Baltic')),
708
('Latin-2', _('Eastern European')),
709
('Windows-1250', _('Eastern European')),
710
('KOI8-R', _('Russian')),
711
('Latin-10', _('South-Eastern European')),
712
('KOI8-U', _('Ukrainian')),
715
menus.add_subtitle_encoding_menu(menubar, _('Western European'),
716
('Latin-8', _('Celtic')),
717
('Windows-1252', _('Western European')),
718
('Latin-3', _('Esperanto')),
719
('ISO 8859-7', _('Greek')),
720
('Windows-1253', _('Greek')),
723
menus.add_subtitle_encoding_menu(menubar, _('East Asian'),
724
('GB18030', _('Universal Chinese')),
725
('ISO-2022-CN-EXT', _('Simplified Chinese')),
726
('EUC-CN', _('Simplified Chinese Unix')),
727
('7-bits JIS/ISO-2022-JP-2', _('Japanese')),
728
('EUC-JP', _('Japanese Unix')),
729
('Shift JIS', _('Japanese')),
730
('EUC-KR/CP949', _('Korean')),
731
('ISO-2022-KR', _('Korean')),
732
('Big5', _('Traditional Chinese')),
733
('EUC-TW', _('Traditional Chinese Unix')),
734
('HKSCS', _('Hong-Kong Supplementary')),
737
menus.add_subtitle_encoding_menu(menubar, _('SE and SW Asian'),
738
('ISO 8859-9', _('Turkish')),
739
('Windows-1254', _('Turkish')),
740
('Windows-874', _('Thai')),
741
('VISCII', _('Vietnamese')),
742
('Windows-1258', _('Vietnamese')),
745
menus.add_subtitle_encoding_menu(menubar, _('Middle Eastern'),
746
('ISO 8859-6', _('Arabic')),
747
('Windows-1256', _('Arabic')),
748
('ISO 8859-8', _('Hebrew')),
749
('Windows-1255', _('Hebrew')),
752
menus.add_subtitle_encoding_menu(menubar, _('Unicode'),
753
('UTF-8', _('Universal')),
754
('UTF-16', _('Universal')),
755
('UTF-16BE', _('Universal')),
756
('UTF-16LE', _('Universal')),
761
_sniffer = VLCSniffer()
763
def get_item_type(item_info, success_callback, error_callback):
764
_sniffer.select_file(item_info, success_callback, error_callback)