채널의 모든 동영상을 가져 오는 YouTube API


201

YouTube 채널 이름 (API 사용) 별 비디오 목록이 필요합니다.

아래 API를 사용하여 채널 목록 (채널 이름 만)을 얻을 수 있습니다.

https://gdata.youtube.com/feeds/api/channels?v=2&q=tendulkar

아래는 채널의 직접 링크입니다

https://www.youtube.com/channel/UCqAEtEr0A0Eo2IVcuWBfB9g

또는

WWW.YouTube.com/channel/HC-8jgBP-4rlI

이제 >> UCqAEtEr0A0Eo2IVcuWBfB9g 또는 HC-8jgBP-4rlI 비디오가 필요합니다.

우리는 시도했다

https://gdata.youtube.com/feeds/api/videos?v=2&uploader=partner&User=UC7Xayrf2k0NZiz3S04WuDNQ https://gdata.youtube.com/feeds/api/videos?v=2&uploader=partner&q=UC7Xayrf2k0NZiz3S04WuDNQ

그러나 도움이되지 않습니다.

채널에 게시 된 모든 비디오가 필요합니다. 채널에 업로드 된 동영상은 여러 사용자가 올 수 있으므로 사용자 매개 변수를 제공해도 도움이되지 않을 것 같습니다


내 비디오 파일에 액세스 할 수 있습니까? API를 통해 로그인 할 때 본질적으로 내 자신의 콘텐츠를 다운로드하려면 !!!
— filthy_wizard

답변:


218

YouTube Data API 를 봐야 합니다 . API에 액세스하는 방법에 대한 설명서가 있습니다. 클라이언트 라이브러리 도 찾을 수 있습니다 .

직접 요청할 수도 있습니다. 다음은 채널에서 최신 동영상을 검색하는 URL 예입니다.

https://www.googleapis.com/youtube/v3/search?key={your_key_here}&channelId={channel_id_here}&part=snippet,id&order=date&maxResults=20

그 후 JSON비디오 ID와 세부 정보가 포함 된를 받게 되고 다음과 같이 비디오 URL을 구성 할 수 있습니다.

http://www.youtube.com/watch?v={video_id_here}

29
첫 번째 maxResults=20동영상 (최대 50 개) 만 반환 하지만 전체 채널 카탈로그 는 반환 하지 않습니다. 더 많은 결과를 원하면 여기에 설명 된대로 pageToken을 사용 하십시오 .
— Fábio Perez

2
Romulus Urakagi Ts'ai : 비디오를 요청하는 동안 채널 ID를 제공하는 채널 필터입니다.
— akshay

11
다음 페이지 토큰을 사용하여 채널에서 최대 500 개의 비디오를 얻을 수 있습니다.
— Tony Paternite 1

3
@TonyPaternite 500 개 이상의 비디오를 얻으려면 어떻게해야합니까? 현재이 문제에 직면하고 있습니다.
— Raja

3
문제는 여기에 잘 설명되어 있습니다. code.google.com/p/gdata-issues/issues/detail?id=4282 다른 날짜 범위로 시도하는 것이 더 많은 동영상을 얻는 방법 중 하나입니다.
— Raja

120

먼저 사용자 / 채널의 업로드를 나타내는 재생 목록의 ID를 가져와야합니다.

https://developers.google.com/youtube/v3/docs/channels/list#try-it

forUsername={username}매개 변수를 사용하여 사용자 이름을 지정 mine=true하거나 자신을 얻도록 지정할 수 있습니다 (먼저 인증해야 함). part=contentDetails재생 목록을 보려면 포함 하십시오.

GET https://www.googleapis.com/youtube/v3/channels?part=contentDetails&forUsername=jambrose42&key={YOUR_API_KEY}

결과에 "relatedPlaylists"포함됩니다 "likes"및 "uploads"재생 목록. 그 잡아 "upload"재생 목록 ID. 또한 "id"나중에 참조 할 수 있도록 channelID입니다.

다음으로 해당 재생 목록에있는 동영상 목록을 가져옵니다.

https://developers.google.com/youtube/v3/docs/playlistItems/list#try-it

재생 목록을 삭제하십시오.

GET https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&maxResults=50&playlistId=UUpRmvjdu3ixew5ahydZ67uA&key={YOUR_API_KEY}


1
이 새로운 세상에서 누구나 YouTube 사용자 이름을 사용할 수있는 것은 아닙니다. 그들 중 일부는
— Google+

1
@kristopolous 모든 YouTube 채널에는 채널 ID가 있습니다. Google plus를 통해 YouTube 페이지로 이동하면 Google plus 사용자 ID가 채널 링크로 사용됩니다. YouTube 동영상에서 채널로 이동 한 경우 대신 YouTube의 채널 ID 속성을 사용합니다.
— Ignat

19
이것은 완벽 해요. 특히 100 개가 아닌 2 개의 할당량 포인트 만 사용하기 때문에 ( 검색 통화가 소비합니다).
— JP de la Torre

1
somteimes ( developers.google.com/youtube/v3/docs/playlistItems/list#try-it ) 작동하지만 때로는 동일한 uploadId에 대해 404가 발생하고 무슨 일이 일어나고 있는지 모릅니다.
— ishandutta2007

1
playlistItems 엔드 포인트의 @jambrose는 publishAfter / publishBefore와 같은 일부 날짜 범위 매개 변수를 사용하는 방법입니다. 나는 노력하지 않고 그것을 시도했다. 그래서 나는 채널의 모든 비디오를 가져 오는 방법.
— 가혹한 sachdev

80

다음은 채널에있는 모든 동영상을 나열하는 방법을 보여주는 Google 개발자의 동영상입니다.v3 YouTube API .

두 단계가 있습니다 :

  1. '업로드'ID를 얻기 위해 채널을 쿼리합니다. 예 :https://www.googleapis.com/youtube/v3/channels?id={channel Id}&key={API key}&part=contentDetails

  2. 이 "업로드"ID를 사용하여 비디오 목록을 가져 오려면 PlaylistItems를 쿼리하십시오. 예 :https://www.googleapis.com/youtube/v3/playlistItems?playlistId={"uploads" Id}&key={API key}&part=snippet&maxResults=50


1
주어진 채널에 대해 ID를 "업로드"하여 변경할 수 있습니까?
— ishandutta2007

업로드는 channelId와 동일하지만 매우 일관되지 않은 API 인 것 같습니다. 일부 stackoverflow.com/questions/43568521/…
— ishandutta2007

1
@ ishandutta2007 업로드 ID가 채널 ID와 다릅니다.
— vicke4

@ 피터 완벽한 솔루션 얘들 아, 감사합니다 @ virtualmic
— Aaska 파텔

잘 작동합니다. 하지만 여러 채널 ID가 있으므로 어떻게 API로 보낼 수
— 있습니까

32

채널 목록을 얻으려면 :

forUserName으로 채널 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/channels?part=snippet,contentDetails,statistics&forUsername=Apple&key=

채널 ID 별로 채널 목록을 가져 옵니다 .

https://www.googleapis.com/youtube/v3/channels/?part=snippet,contentDetails,statistics&id=UCE_M8A5yxnLfW0KghEeajjw&key=

채널 가져 오기 섹션 :

https://www.googleapis.com/youtube/v3/channelSections?part=snippet,contentDetails&channelId=UCE_M8A5yxnLfW0KghEeajjw&key=

재생 목록을 얻으려면 :

채널 ID로 재생 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/playlists?part=snippet,contentDetails&channelId=UCq-Fj5jknLsUf-MWSy4_brA&maxResults=50&key=

가져 오기 재생 목록 으로 pageToken을 가진 채널 ID를 :

https://www.googleapis.com/youtube/v3/playlists?part=snippet,contentDetails&channelId=UCq-Fj5jknLsUf-MWSy4_brA&maxResults=50&key= & pageToken = CDIQAA

PlaylistItems를 가져 오려면 :

PlayListId로 PlaylistItems 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails&maxResults=25&playlistId=PLHFlHpPjgk70Yv3kxQvkDEO5n5tMQia5I&key=

비디오를 얻으려면 :

동영상 ID로 동영상 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics&id=YxLCwfA1cLw&key=

여러 개의 동영상으로 동영상 목록 가져 오기 id :

https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails,statistics&id=YxLCwfA1cLw,Qgy6LaO3SB0,7yPJXGO2Dcw&key=

의견 목록 받기

비디오 ID로 댓글 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&videoId=el **** kQak & key = A ********** k

채널 ID 별로 댓글 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&channelId=U ***** Q & key = AI ******** k

allThreadsRelatedToChannelId로 댓글 목록 가져 오기 :

https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&allThreadsRelatedToChannelId=UC ***** ntcQ & key = AI ***** k

여기에 모든 API는 Get 접근 방식입니다.

채널 ID를 기준으로 모든 동영상을 직접 가져 오지는 않습니다. 여기서 중요한 점입니다.

통합 https://developers.google.com/youtube/v3/quickstart/ios?ver=swift


여러 채널 ID를 API에 보내는 방법, 쉼표로 구분 된 채널 ID를 사용할 수
— 있습니까

API를 사용하여 최고의 10/50/100 YouTube 채널을 얻으려면 어떻게해야합니까?
— Rajeshwar

10

아래는 특별한 패키지가 필요하지 않은 파이썬 대안입니다. 채널 ID를 제공하면 해당 채널의 비디오 링크 목록이 반환됩니다. 작동 하려면 API 키 가 필요합니다 .

import urllib
import json

def get_all_video_in_channel(channel_id):
    api_key = YOUR API KEY

    base_video_url = 'https://www.youtube.com/watch?v='
    base_search_url = 'https://www.googleapis.com/youtube/v3/search?'

    first_url = base_search_url+'key={}&channelId={}&part=snippet,id&order=date&maxResults=25'.format(api_key, channel_id)

    video_links = []
    url = first_url
    while True:
        inp = urllib.urlopen(url)
        resp = json.load(inp)

        for i in resp['items']:
            if i['id']['kind'] == "youtube#video":
                video_links.append(base_video_url + i['id']['videoId'])

        try:
            next_page_token = resp['nextPageToken']
            url = first_url + '&pageToken={}'.format(next_page_token)
        except:
            break
    return video_links

어떻게 작동합니까? 작은 따옴표 ''를 사용하여 API KEY를 api_key 변수에 붙여 넣은 다음 채널 ID를 전달하는 함수를 호출 한 다음 파이썬 프로그램을 실행했지만 아무 일도 일어나지 않습니다.
— Joffrey Baratheon

@JoffreyBaratheon 함수는 배열을 반환하므로 변수에 변수를 포함해야합니다. 예를 들면 : video_list = get_all_video_in_channel("ABC123EFG456")당신은 배열을 사용하여보고 인쇄 할 수 있습니다print(video_list)
— phapha의 PHA

7

채널 아래의 모든 동영상 ID를 반환하는 코드는 다음과 같습니다.

<?php 
    $baseUrl = 'https://www.googleapis.com/youtube/v3/';
    // https://developers.google.com/youtube/v3/getting-started
    $apiKey = 'API_KEY';
    // If you don't know the channel ID see below
    $channelId = 'CHANNEL_ID';

    $params = [
        'id'=> $channelId,
        'part'=> 'contentDetails',
        'key'=> $apiKey
    ];
    $url = $baseUrl . 'channels?' . http_build_query($params);
    $json = json_decode(file_get_contents($url), true);

    $playlist = $json['items'][0]['contentDetails']['relatedPlaylists']['uploads'];

    $params = [
        'part'=> 'snippet',
        'playlistId' => $playlist,
        'maxResults'=> '50',
        'key'=> $apiKey
    ];
    $url = $baseUrl . 'playlistItems?' . http_build_query($params);
    $json = json_decode(file_get_contents($url), true);

    $videos = [];
    foreach($json['items'] as $video)
        $videos[] = $video['snippet']['resourceId']['videoId'];

    while(isset($json['nextPageToken'])){
        $nextUrl = $url . '&pageToken=' . $json['nextPageToken'];
        $json = json_decode(file_get_contents($nextUrl), true);
        foreach($json['items'] as $video)
            $videos[] = $video['snippet']['resourceId']['videoId'];
    }
    print_r($videos);

참고 : 로그인 후 https://www.youtube.com/account_advanced 에서 채널 ID를 얻을 수 있습니다 .


1
이것은 확실히 작동하는 깨끗한 솔루션입니다. 현재 프로덕션 환경에서 사용 중입니다.
— Codex73

7

다음과 같이 시도하십시오. 도움이 될 수 있습니다.

https://gdata.youtube.com/feeds/api/videos?author=cnn&v=2&orderby=updated&alt=jsonc&q=news

채널 이름과 "q"를 지정하여 검색 키워드를 지정할 수 있습니다.


해당 API에 대한 설명서는 어디에 있습니까?
— Macabeus

1
이 API는 더 이상 사용할 수 없습니다 : "YouTube 고객 데이터 API (V2)은 월 공식적 4 2014로 사용되지 않습니다"
— 브루스 밴을 Kooij 보낸 데르


6

여기 및 다른 곳에서 공유 된 참조 덕분에 채널의 모든 비디오를 얻는 데 사용할 수있는 온라인 스크립트 / 도구를 만들었습니다.

그것은에 API 호출을 결합 youtube.channels.list, playlistItems, videos. 재귀 함수를 사용하여 유효한 응답을 얻을 때 비동기 콜백이 다음 반복을 실행하도록합니다.

또한 한 번에 실제 요청 수를 제한하여 YouTube API 규칙을 위반하지 않도록합니다. 단축 된 스 니펫을 공유 한 다음 전체 코드에 대한 링크를 공유합니다. 다음 50 개의 결과를 가져 오는 응답에 오는 nextPageToken 값을 사용하여 통화 당 최대 50 개의 결과를 얻었습니다.

function getVideos(nextPageToken, vidsDone, params) {
    $.getJSON("https://www.googleapis.com/youtube/v3/playlistItems", {
        key: params.accessKey,
        part: "snippet",
        maxResults: 50,
        playlistId: params.playlistId,
        fields: "items(snippet(publishedAt, resourceId/videoId, title)), nextPageToken",
        pageToken: ( nextPageToken || '')
        },
        function(data) {
            // commands to process JSON variable, extract the 50 videos info

            if ( vidsDone < params.vidslimit) {

                // Recursive: the function is calling itself if
                // all videos haven't been loaded yet
                getVideos( data.nextPageToken, vidsDone, params);

            }
             else {
                 // Closing actions to do once we have listed the videos needed.
             }
    });
}

여기에는 id, title, 게시 날짜 등을 포함한 비디오의 기본 목록이 있습니다. 그러나 조회수 및 좋아요와 같은 각 동영상의 세부 정보를 얻으려면에 API 호출을해야 videos합니다.

// Looping through an array of video id's
function fetchViddetails(i) {
    $.getJSON("https://www.googleapis.com/youtube/v3/videos", {
        key: document.getElementById("accesskey").value,
        part: "snippet,statistics",
        id: vidsList[i]
        }, function(data) {

            // Commands to process JSON variable, extract the video
            // information and push it to a global array
            if (i < vidsList.length - 1) {
                fetchViddetails(i+1) // Recursive: calls itself if the
                                     //            list isn't over.
            }
});

참고 항목 여기에 전체 코드 , 그리고 여기에 라이브 버전 . (편집 : 고정 github 링크)
편집 : 종속성 : JQuery, Papa.parse


1
이것이 효과가 있습니까? GitHub 페이지가 사라진 후 API 키와 채널 ID를 필드에 입력하면 오류가 발생합니다.
— Joffrey Baratheon

@JoffreyBaratheon은 깨진 github 링크를 지적 해 주셔서 감사합니다. 방금 달렸어요. 작동합니다. 재생 목록 ID를 얻는 것은 까다 롭습니다. pls 참조 : github.com/answerquest/answerquest.github.io/issues/2
— Nikhil VJ

좋은 작업. 업데이트 된 github 링크와 매우 잘 작동합니다. 당신은 jquery와 papaparse.min.js가 필요할 것입니다
— Bludau Media

@thE_iNviNciblE 그것을 가져 주셔서 감사합니다. 그것에 대해 언급했다.
— Nikhil VJ

@ nikhil-vj 실제 시나리오에서는 사용할 수 없으므로 클라이언트 브라우저에 YouTube API 토큰을 제공해서는 안됩니다. 그러나 프로그래밍 교육을 위해 아주 좋은 직업 :-)
— Bludau Media

4

이 질문에 대답하는 모든 사람은 500 비디오 제한 으로 인해 문제가 있으므로 Python 3 에서 youtube_dl 을 사용하는 대체 솔루션이 있습니다. 또한 API 키가 필요하지 않습니다 .

  1. youtube_dl을 설치하십시오. sudo pip3 install youtube-dl
  2. 타겟 채널의 채널 ID를 찾으십시오 . ID는 UC로 시작합니다. 채널의 C를 업로드 용 U (예 : UU ...)로 바꾸면 업로드 재생 목록 입니다.
  3. youtube-dl 의 재생 목록 다운로더 기능 을 사용하십시오 . 이상적으로는 재생 목록의 모든 비디오를 기본값 인 메타 데이터 만 다운로드하지는 않는 것이 좋습니다.

예 (경고-수십 분이 소요됨) :

import youtube_dl, pickle

             # UCVTyTA7-g9nopHeHbeuvpRA is the channel id (1517+ videos)
PLAYLIST_ID = 'UUVTyTA7-g9nopHeHbeuvpRA'  # Late Night with Seth Meyers

with youtube_dl.YoutubeDL({'ignoreerrors': True}) as ydl:

    playd = ydl.extract_info(PLAYLIST_ID, download=False)

    with open('playlist.pickle', 'wb') as f:
        pickle.dump(playd, f, pickle.HIGHEST_PROTOCOL)

    vids = [vid for vid in playd['entries'] if 'A Closer Look' in vid['title']]
    print(sum('Trump' in vid['title'] for vid in vids), '/', len(vids))

1
API 키가 필요하지 않기 때문에 이것이 가장 좋은 대답이라고 생각합니다. 보다 자동화하려면를 사용할 수 있습니다 @for /f "usebackq tokens=2 delims=: " %a in (`dl-list.py^|findstr information`) do @echo https://www.youtube.com/watch?v=%a. 비디오의 모든 URL을 인쇄합니다. PLAYLIST_ID재생 목록 또는 채널 ID 일 수 있습니다.
— cdlvcdlv

당신은 그것을 추론 할 수는 있지만 코드를로 저장했다는 것을 잊었습니다 dl-list.py.
— cdlvcdlv

2

더 이상 사용되지 않는 API 버전 2를 사용하면 (UCqAEtEr0A0Eo2IVcuWBfB9g 채널의) 업로드 URL은 다음과 같습니다.

https://gdata.youtube.com/feeds/users/UCqAEtEr0A0Eo2IVcuWBfB9g/uploads

API 버전 3이 있습니다.


UCqAEtEr0A0Eo2IVcuWBfB9g의 경우 작동하지만 단일 사용자가 업로드했기 때문이라고 생각합니다. 어떻게 HC-8jgBP-4rlI에 대한
— 라젠 드라 Dewani

오늘까지 내 메시지를 확인하지 않았습니다. 네가 옳아. 사용자 채널에만 해당됩니다.
— Als

9
연결이 끊어졌습니다.
— user3078414

2

최근에 채널에서 모든 동영상을 검색해야했습니다. YouTube 개발자 문서에 따르면 https://developers.google.com/youtube/v3/docs/playlistItems/list

function playlistItemsListByPlaylistId($service, $part, $params) {
    $params = array_filter($params);
    $response = $service->playlistItems->listPlaylistItems(
        $part,
        $params
    );

    print_r($response);
}

playlistItemsListByPlaylistId($service,
    'snippet,contentDetails',
    array('maxResults' => 25, 'playlistId' => 'id of "uploads" playlist'));

어디 $service당신입니다Google_Service_YouTube 목적 ?

채널에서 실제로 업로드 한 모든 동영상이있는 '업로드'재생 목록을 검색하려면 채널에서 정보를 가져와야합니다. https://developers.google.com/youtube/v3/docs/channels/list

이 API를 처음 사용하는 경우 코드 샘플을 기본 스 니펫에서 전체 샘플로 바꾸는 것이 좋습니다.

따라서 채널에서 모든 동영상을 검색하는 기본 코드는 다음과 같습니다.

class YouTube
{
    const       DEV_KEY = 'YOUR_DEVELOPPER_KEY';
    private     $client;
    private     $youtube;
    private     $lastChannel;

    public function __construct()
    {
        $this->client = new Google_Client();
        $this->client->setDeveloperKey(self::DEV_KEY);
        $this->youtube = new Google_Service_YouTube($this->client);
        $this->lastChannel = false;
    }

    public function getChannelInfoFromName($channel_name)
    {
        if ($this->lastChannel && $this->lastChannel['modelData']['items'][0]['snippet']['title'] == $channel_name)
        {
            return $this->lastChannel;
        }
        $this->lastChannel = $this->youtube->channels->listChannels('snippet, contentDetails, statistics', array(
            'forUsername' => $channel_name,
        ));
        return ($this->lastChannel);
    }

    public function getVideosFromChannelName($channel_name, $max_result = 5)
    {
        $this->getChannelInfoFromName($channel_name);
        $params = [
            'playlistId' => $this->lastChannel['modelData']['items'][0]['contentDetails']['relatedPlaylists']['uploads'],
            'maxResults'=> $max_result,
        ];
        return ($this->youtube->playlistItems->listPlaylistItems('snippet,contentDetails', $params));
    }
}

$yt = new YouTube();
echo '<pre>' . print_r($yt->getVideosFromChannelName('CHANNEL_NAME'), true) . '</pre>';

제목, 동영상 URL, 동영상을 식별하는 이미지, 좋아요 / 댓글 개수가 포함 된 모든 동영상을 가져 옵니까? 이것이 어떻게 출판되는지에 대한 정보가 도움이 될 것입니다. 감사.
— Khom Nazid 2016 년

1

Python의 샘플 솔루션 . 이 비디오에서 가져온 도움말 : video 다른 답변과 마찬가지로 업로드 ID는 먼저 채널 ID에서 검색해야합니다.

urllib.request 가져 오기
수입 json

키 = "YOUR_YOUTUBE_API_v3_BROWSER_KEY"

# 채널 목록 : 채널 ID 또는 사용자 이름 ( "id"또는 "forUsername")을 붙여 넣을 경우 언급
ytids = [[ "bbcnews", "forUsername"], [ "UCjq4pjKj9X4W9i7UnYShpVg", "id"]]

뉴스 제목 = []
ytid, ytid의 ytparam :
    urld = "https://www.googleapis.com/youtube/v3/channels?part=contentDetails&"+ytparam+"="+ytid+"&key="+key
    urllib.request.urlopen (urld)을 URL로 사용 :
        datad = json.loads (url.read ())
    uploadsdet = datad [ '항목']
    # 채널 ID에서 ID 업로드
    uploadid = uploadsdet [0] [ 'contentDetails'] [ 'relatedPlaylists'] [ '업로드']

    # 검색 목록
    urld = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&maxResults=50&playlistId="+uploadid+"&key="+key
    urllib.request.urlopen (urld)을 URL로 사용 :
        datad = json.loads (url.read ())

    datad [ 'items']의 데이터 :
        ntitle = 데이터 [ 'snippet'] [ 'title']
        nlink = 데이터 [ 'contentDetails'] [ 'videoId']
        newstitles.append ([nlink, ntitle])

뉴스 제목의 링크, 제목 :
    인쇄 (링크, 제목)

-6

설명서에 설명 된대로 ( link ) 채널 리소스 유형 및 작업 목록을 사용하여 채널의 모든 비디오를 가져올 수 있습니다. 이 작업은 'channel id'인수를 사용하여 수행해야합니다.


22
말하기는 쉽지만, 좋은 예제로 대답을 해보지 않겠습니까 ???
— akshay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.