예제 #1
0
def update_youtube_statuses(self):
    """
    Update the status of recently uploaded YouTube videos and upload captions if complete
    """
    youtube = YouTubeApi()
    videos_processing = YouTubeVideo.objects.filter(status=YouTubeStatus.UPLOADED)
    for yt_video in videos_processing:
        try:
            yt_video.status = youtube.video_status(yt_video.id)
            yt_video.save()
            if yt_video.status == YouTubeStatus.PROCESSED:
                for subtitle in yt_video.video.videosubtitle_set.all():
                    youtube.upload_caption(subtitle, yt_video.id)
        except IndexError:
            # Video might be a dupe or deleted, mark it as failed and continue to next one.
            yt_video.status = YouTubeStatus.FAILED
            yt_video.save()
            log.exception(
                "Status of YoutubeVideo not found.",
                youtubevideo_id=yt_video.id,
                youtubevideo_video_id=yt_video.video_id,
            )
        except HttpError as error:
            if API_QUOTA_ERROR_MSG in error.content.decode("utf-8"):
                # Don't raise the error, task will try on next run until daily quota is reset
                break
            raise
예제 #2
0
def remove_youtube_caption(self, video_id, language):
    """
    Remove Youtube captions not matching a video's subtitle language)
    """
    video = Video.objects.get(id=video_id)
    captions = YouTubeApi().list_captions(video.youtube_id)
    if language in captions.keys():
        YouTubeApi().delete_caption(captions[language])
예제 #3
0
def upload_youtube_caption(self, caption_id):
    """
    Upload a video caption file to YouTube
    """
    caption = VideoSubtitle.objects.get(id=caption_id)
    yt_video = YouTubeVideo.objects.get(video=caption.video)
    youtube = YouTubeApi()
    youtube.upload_caption(caption, yt_video.id)
예제 #4
0
def test_upload_video(mocker):
    """
    Test that the upload_video task calls the YouTube API execute method
    """
    videofile = VideoFileFactory()
    youtube_id = "M6LymW_8qVk"
    video_upload_response = {
        "id": youtube_id,
        "kind": "youtube#video",
        "snippet": {
            "description": "Testing description",
            "title": "Testing123"
        },
        "status": {
            "uploadStatus": "uploaded"
        },
    }
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).videos.return_value.insert.return_value.next_chunk.side_effect = [
        (None, None),
        (None, video_upload_response),
    ]
    response = YouTubeApi().upload_video(videofile.video)
    assert response == video_upload_response
예제 #5
0
def test_video_status(mocker):
    """
    Test that the 'video_status' method returns the correct value from the API response
    """
    expected_status = "processed"
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).videos.return_value.list.return_value.execute.return_value = {
        "etag":
        '"ld9biNPKjAjgjV7EZ4EKeEGrhao/Lf7oS5V-Gjw0XHBBKFJRpn60z3w"',
        "items": [{
            "etag":
            '"ld9biNPKjAjgjV7EZ4EKeEGrhao/-UL82wRXbq3YJiMZuZpqCWKoq6Q"',
            "id": "wAjoqsZng_M",
            "kind": "youtube#video",
            "status": {
                "embeddable": True,
                "license": "youtube",
                "privacyStatus": "unlisted",
                "publicStatsViewable": True,
                "uploadStatus": expected_status,
            },
        }],
        "kind":
        "youtube#videoListResponse",
        "pageInfo": {
            "resultsPerPage": 1,
            "totalResults": 1
        },
    }
    assert YouTubeApi().video_status("foo") == expected_status
    youtube_mocker().videos.return_value.list.assert_called_once_with(
        id="foo", part="status")
예제 #6
0
def test_list_captions(mocker):
    """
    Test that the 'list_captions' method executes a YouTube API request and returns a dict with correct values
    """
    key = "en"
    value = "Srnr982VEC79QzEBGcBOL_UFmu9U2e-JgOw-EWIxJXEB5Bjltl3Yvg="
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).captions.return_value.list.return_value.execute.return_value = {
        "etag":
        "foo",
        "items": [{
            "id": value,
            "kind": "youtube#caption",
            "snippet": {
                "audioTrackType": "unknown",
                "language": key,
                "lastUpdated": "2017-11-15T14:53:21.839Z",
                "name": "English",
                "videoId": "3h-0mkTVbRg",
            },
        }],
        "kind":
        "youtube#captionListResponse",
    }
    assert YouTubeApi().list_captions("foo") == {key: value}
    youtube_mocker().captions.return_value.list.assert_called_once_with(
        videoId="foo", part="snippet")
예제 #7
0
def test_delete_video(mocker):
    """
    Test that the 'delete_video' method executes a YouTube API deletion request and returns the status code
    """
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).videos.return_value.delete.return_value.execute.return_value = 204
    assert YouTubeApi().delete_video("foo") == 204
    youtube_mocker().videos.return_value.delete.assert_called_with(id="foo")
예제 #8
0
def remove_youtube_video(self, video_id):
    """
    Delete a video from Youtube
    """
    try:
        YouTubeApi().delete_video(video_id)
    except HttpError as error:
        if error.resp.status == 404:
            log.info("Not found on Youtube, already deleted?", video_id=video_id)
        else:
            raise
예제 #9
0
def test_upload_errors_retryable(mocker, error, retryable):
    """
    Test that uploads are retried 10x for retryable exceptions
    """
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    mocker.patch("cloudsync.youtube.time")
    videofile = VideoFileFactory()
    youtube_mocker(
    ).videos.return_value.insert.return_value.next_chunk.side_effect = (error)
    with pytest.raises(Exception) as exc:
        YouTubeApi().upload_video(videofile.video)
    assert str(exc.value).startswith("Retried YouTube upload 10x") == retryable
예제 #10
0
def test_upload_video_no_id(mocker):
    """
    Test that the upload_video task fails if the response contains no id
    """
    videofile = VideoFileFactory()
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).videos.return_value.insert.return_value.next_chunk.return_value = (
        None,
        {},
    )
    with pytest.raises(YouTubeUploadException):
        YouTubeApi().upload_video(videofile.video)
예제 #11
0
def upload_youtube_videos():
    """
    Upload public videos one at a time to YouTube (if not already there) until the daily maximum is reached.
    """
    yt_queue = (
        Video.objects.filter(is_public=True)
        .filter(status=VideoStatus.COMPLETE)
        .filter(youtubevideo__id__isnull=True)
        .exclude(collection__stream_source=StreamSource.CLOUDFRONT)
        .order_by("-created_at")[: settings.YT_UPLOAD_LIMIT]
    )
    for video in yt_queue.all():
        youtube_video = YouTubeVideo.objects.create(video=video)
        try:
            youtube = YouTubeApi()
            response = youtube.upload_video(video)
            youtube_video.id = response["id"]
            youtube_video.status = response["status"]["uploadStatus"]
            youtube_video.save()
        except HttpError as error:
            log.exception(
                "HttpError uploading video to Youtube",
                video_hexkey=video.hexkey,
                status=youtube_video.status,
            )
            if API_QUOTA_ERROR_MSG in error.content.decode("utf-8"):
                break
        except:  # pylint: disable=bare-except
            log.exception(
                "Error uploading video to Youtube",
                video_hexkey=video.hexkey,
                status=youtube_video.status,
            )
        finally:
            # If anything went wrong with the upload, delete the YouTubeVideo object.
            # Another upload attempt will be made the next time the task is run.
            if youtube_video.id is None:
                youtube_video.delete()
예제 #12
0
def test_upload_caption_calls_insert(mocker):
    """
    Test that the upload_caption task calls insert_caption for a YouTube video if no caption for that language exists
    """
    subtitle = VideoSubtitleFactory()
    caption_id = "foo"
    caption_response = {"id": caption_id}
    mocker.patch("cloudsync.youtube.YouTubeApi.list_captions", return_value={})
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    youtube_mocker(
    ).captions.return_value.insert.return_value.next_chunk.return_value = (
        None,
        caption_response,
    )
    response = YouTubeApi().upload_caption(subtitle, caption_id)
    assert response == caption_response
예제 #13
0
def test_upload_video_long_fields(mocker):
    """
    Test that the upload_youtube_video task truncates title and description if too long
    """
    title = "".join(random.choice(string.ascii_lowercase) for c in range(105))
    desc = "".join(random.choice(string.ascii_lowercase) for c in range(5005))
    video = VideoFactory.create(title=title,
                                description=desc,
                                is_public=True,
                                status=VideoStatus.COMPLETE)
    VideoFileFactory(video=video)
    mocker.patch("cloudsync.youtube.resumable_upload")
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    mock_upload = youtube_mocker().videos.return_value.insert
    YouTubeApi().upload_video(video)
    called_args, called_kwargs = mock_upload.call_args
    assert called_kwargs["body"]["snippet"]["title"] == title[:100]
    assert called_kwargs["body"]["snippet"]["description"] == desc[:5000]
예제 #14
0
def test_youtube_settings(mocker, settings):
    """
    Test that Youtube object creation uses YT_* settings for credentials
    """
    settings.YT_ACCESS_TOKEN = "yt_access_token"
    settings.YT_CLIENT_ID = "yt_client_id"
    settings.YT_CLIENT_SECRET = "yt_secret"
    settings.YT_REFRESH_TOKEN = "yt_refresh"
    mock_oauth = mocker.patch(
        "cloudsync.youtube.oauth2client.client.GoogleCredentials")
    YouTubeApi()
    mock_oauth.assert_called_with(
        settings.YT_ACCESS_TOKEN,
        settings.YT_CLIENT_ID,
        settings.YT_CLIENT_SECRET,
        settings.YT_REFRESH_TOKEN,
        None,
        "https://accounts.google.com/o/oauth2/token",
        None,
    )