~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to platform/osx/plat/frontends/widgets/quicktime.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2010 Participatory Culture Foundation
3
 
#
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.
8
 
#
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.
13
 
#
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
17
 
#
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
20
 
# library.
21
 
#
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.
28
 
 
29
 
import os
30
 
import glob
31
 
import logging
32
 
 
33
 
from objc import pathForFramework, loadBundleFunctions
34
 
from Foundation import *
35
 
from QTKit import *
36
 
 
37
 
from miro import app
38
 
from miro import prefs
39
 
from miro import config
40
 
from miro import player
41
 
from miro import iso_639
42
 
from miro.plat import utils
43
 
from miro.plat import bundle
44
 
from miro.plat import qtcomp
45
 
from miro.plat import script_codes
46
 
from miro.plat.frontends.widgets import threads
47
 
from miro.plat.frontends.widgets.helpers import NotificationForwarder
48
 
from miro.util import copy_subtitle_file
49
 
 
50
 
###############################################################################
51
 
 
52
 
script_codes.patch_iso_639_map()
53
 
 
54
 
###############################################################################
55
 
 
56
 
qt_framework = pathForFramework("QuickTime.framework")
57
 
qt_bundle = NSBundle.bundleWithPath_(qt_framework)
58
 
loadBundleFunctions(qt_bundle, globals(), (('GetMediaLanguage', 's^^{}'),))
59
 
 
60
 
###############################################################################
61
 
 
62
 
def register_components():
63
 
    bundlePath = bundle.getBundlePath()
64
 
    componentsDirectoryPath = os.path.join(bundlePath, 'Contents', 'Components')
65
 
    components = glob.glob(os.path.join(componentsDirectoryPath, '*.component'))
66
 
    for component in components:
67
 
        cmpName = os.path.basename(component)
68
 
        ok = qtcomp.register(component.encode('utf-8'))
69
 
        if ok:
70
 
            logging.info('Successfully registered embedded component: %s' % cmpName)
71
 
        else:
72
 
            logging.warn('Error while registering embedded component: %s' % cmpName)
73
 
 
74
 
###############################################################################
75
 
 
76
 
class WarmupProgressHandler(NSObject):
77
 
    def init(self):
78
 
        self = super(WarmupProgressHandler, self).init()
79
 
        self.complete = False
80
 
        return self
81
 
    def loadStateChanged_(self, notification):
82
 
        self.handleLoadStateForMovie_(notification.object())
83
 
    def handleInitialLoadStateForMovie_(self, movie):
84
 
        load_state = movie.attributeForKey_(QTMovieLoadStateAttribute).longValue()
85
 
        if load_state < QTMovieLoadStateComplete:
86
 
            NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
87
 
                warmup_handler, 
88
 
                'loadStateChanged:', 
89
 
                QTMovieLoadStateDidChangeNotification, 
90
 
                warmup_movie)
91
 
        else:
92
 
            self.handleLoadStateForMovie_(movie)
93
 
    def handleLoadStateForMovie_(self, movie):
94
 
        load_state = movie.attributeForKey_(QTMovieLoadStateAttribute).longValue()
95
 
        if load_state == QTMovieLoadStateComplete:
96
 
            logging.info("QuickTime warm up complete")
97
 
            NSNotificationCenter.defaultCenter().removeObserver_(self)
98
 
            NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
99
 
                10, self, 'releaseWarmupMovie:', None, False)
100
 
            self.complete = True
101
 
    def releaseWarmupMovie_(self, timer):
102
 
        logging.info("Releasing warmup movie.")
103
 
        global warmup_movie
104
 
        del warmup_movie
105
 
 
106
 
warmup_handler = WarmupProgressHandler.alloc().init()
107
 
warmup_movie = None
108
 
 
109
 
def warm_up():
110
 
    logging.info('Warming up QuickTime')
111
 
    rsrcPath = bundle.getBundleResourcePath()
112
 
 
113
 
    attributes = NSMutableDictionary.dictionary()
114
 
    attributes['QTMovieFileNameAttribute'] = os.path.join(rsrcPath, 'warmup.mov')
115
 
    attributes['QTMovieOpenAsyncRequiredAttribute'] = True
116
 
    attributes['QTMovieDelegateAttribute'] = None
117
 
 
118
 
    global warmup_movie
119
 
    if utils.get_pyobjc_major_version() == 2:
120
 
        warmup_movie, error = QTMovie.movieWithAttributes_error_(attributes, None)
121
 
    else:
122
 
        warmup_movie, error = QTMovie.movieWithAttributes_error_(attributes)
123
 
    
124
 
    if error is not None:
125
 
        logging.warn("QuickTime Warm Up failed: %s" % error)
126
 
    else:
127
 
        warmup_handler.handleInitialLoadStateForMovie_(warmup_movie)
128
 
 
129
 
###############################################################################
130
 
 
131
 
def qttime2secs(qttime):
132
 
    timeScale = qttimescale(qttime)
133
 
    if timeScale == 0:
134
 
        return 0.0
135
 
    timeValue = qttimevalue(qttime)
136
 
    return timeValue / float(timeScale)
137
 
 
138
 
def qttimescale(qttime):
139
 
    if isinstance(qttime, tuple):
140
 
        return qttime[1]
141
 
    else:
142
 
        return qttime.timeScale
143
 
 
144
 
def qttimevalue(qttime):
145
 
    if isinstance(qttime, tuple):
146
 
        return qttime[0]
147
 
    else:
148
 
        return qttime.timeValue
149
 
 
150
 
###############################################################################
151
 
# The QTMediaTypeSubtitle and QTMediaTypeClosedCaption media types are only
152
 
# available in OS X 10.6 so we emulate them if they aren't defined.
153
 
 
154
 
try:
155
 
    dummy = QTMediaTypeSubtitle
156
 
except:
157
 
    QTMediaTypeSubtitle = u'sbtl'
158
 
 
159
 
try:
160
 
    dummy = QTMediaTypeClosedCaption
161
 
except:
162
 
    QTMediaTypeClosedCaption = u'clcp'
163
 
 
164
 
###############################################################################
165
 
 
166
 
class Player(player.Player):
167
 
 
168
 
    def __init__(self, supported_media_types):
169
 
        player.Player.__init__(self)
170
 
        self.supported_media_types = supported_media_types
171
 
        self.movie_notifications = None
172
 
        self.movie = None
173
 
        self.item_info = None
174
 
 
175
 
    def reset(self):
176
 
        threads.warn_if_not_on_main_thread('quicktime.Player.reset')
177
 
        if self.movie_notifications is not None:
178
 
            self.movie_notifications.disconnect()
179
 
        self.movie_notifications = None
180
 
        self.movie = None
181
 
        self.item_info = None
182
 
 
183
 
    def set_item(self, item_info, callback, errback, force_subtitles=False):
184
 
        threads.warn_if_not_on_main_thread('quicktime.Player.set_item')
185
 
        qtmovie = self.get_movie_from_file(item_info.video_path)
186
 
        self.reset()
187
 
        if qtmovie is not None:
188
 
            self.movie = qtmovie
189
 
            self.item_info = item_info
190
 
            self.movie_notifications = NotificationForwarder.create(self.movie)
191
 
            self.movie_notifications.connect(self.handle_movie_notification, QTMovieDidEndNotification)
192
 
            self.setup_subtitles(force_subtitles)
193
 
            callback()
194
 
        else:
195
 
            errback()
196
 
 
197
 
    def get_movie_from_file(self, path):
198
 
        osfilename = utils.filename_type_to_os_filename(path)
199
 
        url = NSURL.fileURLWithPath_(osfilename)
200
 
        if utils.get_pyobjc_major_version() == 2:
201
 
            qtmovie, error = QTMovie.movieWithURL_error_(url, None)
202
 
        else:
203
 
            qtmovie, error = QTMovie.movieWithURL_error_(url)
204
 
        if qtmovie is None or error is not None:
205
 
            return None
206
 
        if not self.can_open_file(qtmovie):
207
 
            return None
208
 
        return qtmovie
209
 
 
210
 
    def can_open_file(self, qtmovie):
211
 
        threads.warn_if_not_on_main_thread('quicktime.Player.can_open_file')
212
 
        can_open = False
213
 
        duration = utils.qttimevalue(qtmovie.duration())
214
 
        
215
 
        if qtmovie is not None and duration > 0:
216
 
            allTracks = qtmovie.tracks()
217
 
            if len(qtmovie.tracks()) > 0:
218
 
                # Make sure we have at least one track with a non zero length
219
 
                allMedia = [track.media() for track in allTracks]
220
 
                for media in allMedia:
221
 
                    mediaType = media.attributeForKey_(QTMediaTypeAttribute)
222
 
                    mediaDuration = utils.qttimevalue(media.attributeForKey_(QTMediaDurationAttribute).QTTimeValue())
223
 
                    if mediaType in self.supported_media_types and mediaDuration > 0:
224
 
                        can_open = True
225
 
                        break
226
 
 
227
 
        return can_open
228
 
 
229
 
    def setup_subtitles(self, force_subtitles):
230
 
        if config.get(prefs.ENABLE_SUBTITLES) or force_subtitles:
231
 
            default_track = self.get_enabled_subtitle_track()
232
 
            if default_track is None:
233
 
                tracks = self.get_subtitle_tracks()
234
 
                if len(tracks) > 0:
235
 
                    self.enable_subtitle_track(tracks[0])
236
 
        else:
237
 
            self.disable_subtitles()
238
 
 
239
 
    def get_subtitle_tracks(self):
240
 
        tracks = list()
241
 
        if self.movie is not None:
242
 
            for track in self.movie.tracks():
243
 
                if self.is_subtitle_track(track):
244
 
                    media = track.media().quickTimeMedia()
245
 
                    lang = GetMediaLanguage(media)
246
 
                    display_name = track.attributeForKey_(QTTrackDisplayNameAttribute)
247
 
                    if lang == 32767:    # 32764 = langUndefined
248
 
                        name = display_name
249
 
                    else:
250
 
                        lang_info = iso_639.find(lang, "script-code")
251
 
                        if lang_info is None:
252
 
                            name = display_name
253
 
                        else:
254
 
                            name = lang_info["name"]
255
 
                    is_enabled = track.attributeForKey_(QTTrackEnabledAttribute) == 1
256
 
                    track_id = track.attributeForKey_(QTTrackIDAttribute)
257
 
                    tracks.append((track_id, name, is_enabled))
258
 
        return tracks
259
 
 
260
 
    def _find_track(self, key, value):
261
 
        if self.movie is not None:
262
 
            for track in self.movie.tracks():
263
 
                if self.is_subtitle_track(track):
264
 
                    if track.attributeForKey_(key) == value:
265
 
                        return track
266
 
        return None
267
 
 
268
 
    def get_enabled_subtitle_track(self):
269
 
        return self._find_track(QTTrackEnabledAttribute, 1)
270
 
 
271
 
    def get_subtitle_track_by_name(self, name):
272
 
        return self._find_track(QTTrackDisplayNameAttribute, name)
273
 
 
274
 
    def get_subtitle_track_by_id(self, track_id):
275
 
        return self._find_track(QTTrackIDAttribute, track_id)
276
 
 
277
 
    def is_subtitle_track(self, track):
278
 
        layer = track.attributeForKey_(QTTrackLayerAttribute)
279
 
        media_type = track.attributeForKey_(QTTrackMediaTypeAttribute)
280
 
        return (layer == -1 and media_type == QTMediaTypeVideo) or media_type in [QTMediaTypeSubtitle, QTMediaTypeClosedCaption]
281
 
 
282
 
    def enable_subtitle_track(self, track_id):
283
 
        current = self.get_enabled_subtitle_track()
284
 
        if current is not None:
285
 
            current.setAttribute_forKey_(0, QTTrackEnabledAttribute)
286
 
        to_enable = self.get_subtitle_track_by_id(track_id)
287
 
        if to_enable is not None:
288
 
            to_enable.setAttribute_forKey_(1, QTTrackEnabledAttribute)
289
 
 
290
 
    def disable_subtitles(self):
291
 
        track = self.get_enabled_subtitle_track()
292
 
        if track is not None:
293
 
            track.setAttribute_forKey_(0, QTTrackEnabledAttribute)
294
 
 
295
 
    def select_subtitle_file(self, sub_path, handle_successful_select):
296
 
        def handle_ok():
297
 
            handle_successful_select()
298
 
        def handle_err():
299
 
            app.playback_manager.stop()
300
 
        copy_subtitle_file(sub_path, self.item_info.video_path)
301
 
        self.set_item(self.item_info, handle_ok, handle_err, True)
302
 
 
303
 
    def set_volume(self, volume):
304
 
        if self.movie:
305
 
            self.movie.setVolume_(volume)
306
 
 
307
 
    def get_elapsed_playback_time(self):
308
 
        qttime = self.movie.currentTime()
309
 
        return utils.qttime2secs(qttime)
310
 
 
311
 
    def get_total_playback_time(self):
312
 
        if self.movie is None:
313
 
            return 0
314
 
        qttime = self.movie.duration()
315
 
        return utils.qttime2secs(qttime)
316
 
 
317
 
    def skip_forward(self):
318
 
        current = self.get_elapsed_playback_time()
319
 
        duration = self.get_total_playback_time()
320
 
        pos = min(duration, current + 30.0)
321
 
        self.seek_to(pos / duration)
322
 
 
323
 
    def skip_backward(self):
324
 
        current = self.get_elapsed_playback_time()
325
 
        duration = self.get_total_playback_time()
326
 
        pos = max(0, current - 15.0)
327
 
        self.seek_to(pos / duration)
328
 
 
329
 
    def seek_to(self, position):
330
 
        qttime = self.movie.duration()
331
 
        if isinstance(qttime, tuple):
332
 
            qttime = (qttime[0] * position, qttime[1], qttime[2])
333
 
        else:
334
 
            qttime.timeValue = qttime.timeValue * position
335
 
        self.movie.setCurrentTime_(qttime)
336
 
 
337
 
    def play_from_time(self, resume_time=0):
338
 
        self.seek_to(resume_time / self.get_total_playback_time())
339
 
        self.play()
340
 
 
341
 
    def set_playback_rate(self, rate):
342
 
        self.movie.setRate_(rate)
343
 
 
344
 
    def handle_movie_notification(self, notification):
345
 
        if notification.name() == QTMovieDidEndNotification and not app.playback_manager.is_suspended:
346
 
            app.playback_manager.on_movie_finished()
347
 
 
348
 
###############################################################################