#!/usr/bin/env python # # Copyright (C) 2009 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Contains a client to communicate with the YouTube servers. A quick and dirty port of the YouTube GDATA 1.0 Python client libraries to version 2.0 of the GDATA library. """ # __author__ = 's.@google.com (John Skidgel)' import logging import gdata.client import gdata.youtube.data import atom.data import atom.http_core # Constants # ----------------------------------------------------------------------------- YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL = 'https://www.google.com/youtube/accounts/ClientLogin' YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime', 'flv') YOUTUBE_QUERY_VALID_TIME_PARAMETERS = ('today', 'this_week', 'this_month', 'all_time') YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('published', 'viewCount', 'rating', 'relevance') YOUTUBE_QUERY_VALID_RACY_PARAMETERS = ('include', 'exclude') YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS = ('1', '5', '6') YOUTUBE_STANDARDFEEDS = ('most_recent', 'recently_featured', 'top_rated', 'most_viewed','watch_on_mobile') YOUTUBE_UPLOAD_TOKEN_URI = 'http://gdata.youtube.com/action/GetUploadToken' YOUTUBE_SERVER = 'gdata.youtube.com/feeds/api' YOUTUBE_SERVICE = 'youtube' YOUTUBE_VIDEO_FEED_URI = 'http://%s/videos' % YOUTUBE_SERVER YOUTUBE_USER_FEED_URI = 'http://%s/users/' % YOUTUBE_SERVER # Takes a youtube video ID. YOUTUBE_CAPTION_FEED_URI = 'http://gdata.youtube.com/feeds/api/videos/%s/captions' # Takes a youtube video ID and a caption track ID. YOUTUBE_CAPTION_URI = 'http://gdata.youtube.com/feeds/api/videos/%s/captiondata/%s' YOUTUBE_CAPTION_MIME_TYPE = 'application/vnd.youtube.timedtext; charset=UTF-8' # Classes # ----------------------------------------------------------------------------- class Error(Exception): """Base class for errors within the YouTube service.""" pass class RequestError(Error): """Error class that is thrown in response to an invalid HTTP Request.""" pass class YouTubeError(Error): """YouTube service specific error class.""" pass class YouTubeClient(gdata.client.GDClient): """Client for the YouTube service. Performs a partial list of Google Data YouTube API functions, such as retrieving the videos feed for a user and the feed for a video. YouTube Service requires authentication for any write, update or delete actions. """ api_version = '2' auth_service = YOUTUBE_SERVICE auth_scopes = ['http://%s' % YOUTUBE_SERVER, 'https://%s' % YOUTUBE_SERVER] def get_videos(self, uri=YOUTUBE_VIDEO_FEED_URI, auth_token=None, desired_class=gdata.youtube.data.VideoFeed, **kwargs): """Retrieves a YouTube video feed. Args: uri: A string representing the URI of the feed that is to be retrieved. Returns: A YouTubeVideoFeed if successfully retrieved. """ return self.get_feed(uri, auth_token=auth_token, desired_class=desired_class, **kwargs) GetVideos = get_videos def get_user_feed(self, uri=None, username=None): """Retrieve a YouTubeVideoFeed of user uploaded videos. Either a uri or a username must be provided. This will retrieve list of videos uploaded by specified user. The uri will be of format "http://gdata.youtube.com/feeds/api/users/{username}/uploads". Args: uri: An optional string representing the URI of the user feed that is to be retrieved. username: An optional string representing the username. Returns: A YouTubeUserFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a username to the GetYouTubeUserFeed() method. """ if uri is None and username is None: raise YouTubeError('You must provide at least a uri or a username ' 'to the GetYouTubeUserFeed() method') elif username and not uri: uri = '%s%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'uploads') return self.get_feed(uri, desired_class=gdata.youtube.data.VideoFeed) GetUserFeed = get_user_feed def get_video_entry(self, uri=None, video_id=None, auth_token=None, **kwargs): """Retrieve a YouTubeVideoEntry. Either a uri or a video_id must be provided. Args: uri: An optional string representing the URI of the entry that is to be retrieved. video_id: An optional string representing the ID of the video. Returns: A YouTubeVideoFeed if successfully retrieved. Raises: YouTubeError: You must provide at least a uri or a video_id to the GetYouTubeVideoEntry() method. """ if uri is None and video_id is None: raise YouTubeError('You must provide at least a uri or a video_id ' 'to the get_youtube_video_entry() method') elif video_id and uri is None: uri = '%s/%s' % (YOUTUBE_VIDEO_FEED_URI, video_id) return self.get_feed(uri, desired_class=gdata.youtube.data.VideoEntry, auth_token=auth_token, **kwargs) GetVideoEntry = get_video_entry def get_caption_feed(self, uri): """Retrieve a Caption feed of tracks. Args: uri: A string representing the caption feed's URI to be retrieved. Returns: A YouTube CaptionFeed if successfully retrieved. """ return self.get_feed(uri, desired_class=gdata.youtube.data.CaptionFeed) GetCaptionFeed = get_caption_feed def get_caption_track(self, track_url, client_id, developer_key, auth_token=None, **kwargs): http_request = atom.http_core.HttpRequest(uri = track_url, method = 'GET') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Authorization': authsub, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } return self.request(http_request=http_request, **kwargs) GetCaptionTrack = get_caption_track def create_track(self, video_id, title, language, body, client_id, developer_key, auth_token=None, title_type='text', **kwargs): """Creates a closed-caption track and adds to an existing YouTube video. """ new_entry = gdata.youtube.data.TrackEntry( content = gdata.youtube.data.TrackContent(text = body, lang = language)) uri = YOUTUBE_CAPTION_FEED_URI % video_id http_request = atom.http_core.HttpRequest(uri = uri, method = 'POST') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Content-Type': YOUTUBE_CAPTION_MIME_TYPE, 'Content-Language': language, 'Slug': title, 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } http_request.add_body_part(body, http_request.headers['Content-Type']) return self.request(http_request = http_request, desired_class = new_entry.__class__, **kwargs) CreateTrack = create_track def delete_track(self, video_id, track, client_id, developer_key, auth_token=None, **kwargs): """Deletes a track.""" if isinstance(track, gdata.youtube.data.TrackEntry): track_id_text_node = track.get_id().split(':') track_id = track_id_text_node[3] else: track_id = track uri = YOUTUBE_CAPTION_URI % (video_id, track_id) http_request = atom.http_core.HttpRequest(uri = uri, method = 'DELETE') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } return self.request(http_request=http_request, **kwargs) DeleteTrack = delete_track def update_track(self, video_id, track, body, client_id, developer_key, auth_token=None, **kwargs): """Updates a closed-caption track for an existing YouTube video. """ track_id_text_node = track.get_id().split(':') track_id = track_id_text_node[3] uri = YOUTUBE_CAPTION_URI % (video_id, track_id) http_request = atom.http_core.HttpRequest(uri = uri, method = 'PUT') dev_key = 'key=' + developer_key authsub = 'AuthSub token="' + str(auth_token) + '"' http_request.headers = { 'Content-Type': YOUTUBE_CAPTION_MIME_TYPE, 'Authorization': authsub, 'GData-Version': self.api_version, 'X-GData-Client': client_id, 'X-GData-Key': dev_key } http_request.add_body_part(body, http_request.headers['Content-Type']) return self.request(http_request = http_request, desired_class = track.__class__, **kwargs) UpdateTrack = update_track