본문

IT로그/프로그래밍

[웹 사이트에서 YouTube 업로드 API 사용하기] - 2. YouTube 업로드 구현 및 테스트

1. 시작하기 전에 

  • 이 글은 어디까지나 기록을 위한 테스트 수준의 코드이며 실제 서비스로 사용하려면 추가적인 구현과 고려해야할 사항들이 많습니다. 참고용으로만 이용해주시길 바랍니다.
  • Spring MVC를 이용하여 제작된 웹사이트를 기본으로 설명하고 있지만 구현 자체는 html과 javascript를 이용하기 때문에 Spring 프레임워크의 이용과는 무관합니다. 
  • 해당 방법으로 구현시 각 브라우저에서 인증한 구글 계정으로 업로드가 진행됩니다.
  • 2018/07/20 - [IT로그/프로그래밍] - [YouTube 업로드 API 사용하기] - 1. 클라이언트 ID 생성 를 참고하여 클라이언트 ID를 먼저 생성해야 합니다.



2. 리소스 다운로드 및 프로젝트에 추가

https://github.com/youtube/api-samples

위 링크에서 소스파일을 다운로드하면 언어별로 유튜브 api를 구현되어있습니다.

그중에서 자바스크립트용 코드를 이용할건데 먼저 자바스크립트 폴더에서 다음 파일들을 스프링 프로젝트에 추가해줍니다.

js 파일 : auth.js , cors_upload.js, upload_video.js

css 파일 : upload_video.css


3. 테스트 페이지 작성

<youtubeUploadTest.jsp>

위에서 추가한 리소스 파일들의 링크와 부트스트랩 + fontawesome의 CDN을 페이지에 등록.

<html>

<head>
   <title>Youtube Upload Test</title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <link rel="stylesheet" href="/resources/css/upload_video.css">
   <!-- 부트스트랩 & FontAwesome -->
   <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" integrity="sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B" crossorigin="anonymous">
   <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ" crossorigin="anonymous">
</head>

<style>
  #inputarea{
     border: 1px solid #ccc;
     padding : 10px;
     margin-top: 30px;
     height: 400px;
  }
	
  #main-container {
     padding : 50px 20px;
  }

</style>

<body>
    
  <div id="main-container">
     ...
  </div>
    
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js" integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em" crossorigin="anonymous"></script>

  <script src="//apis.google.com/js/client:plusone.js"></script>
  <script src="/resources/js/cors_upload.js"></script>
  <script src="/resources/js/upload_video.js"></script>
  <script src="/resources/js/auth.js"></script>
</body>
</html>


<youtubeUploadTest.jsp의 main-container>

<버튼>

<button class="btn btn-primary" data-toggle="modal" data-target="#youtubeModal"><i class="fab fa-youtube" ></i></button>


<modal>

상단 IMPORTANT 주석 아래에 있는 span 태그 data-clientid 값에 이전 글에서 생성한 클라이언트 ID를 입력

<div id="youtubeModal" class="modal modal-outline-secondary fade" role="dialog">
   <div class="modal-dialog">
     <!-- Modal content -->
     <div class="modal-content">
       <div class="modal-header">
         <h4 class="modal-title">Youtube Upload</h4>
         <button type="button" class="close" data-dismiss="modal">&times;</button>
       </div>
            
       <div class="modal-body">
         <div class="form-group">
            <span id="signinButton" class="pre-sign-in">
            <!-- IMPORTANT: Replace the value of the <code>data-clientid</code>
            attribute in the following tag with your project's client ID. -->
              <span
                class="g-signin"
                data-callback="signinCallback"
                data-clientid="your clientID" 
                data-cookiepolicy="single_host_origin"
                data-scope="https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube">
              </span>
            </span>
                </div>
            
                <div class="post-sign-in">             
                <div class="form-group">
                    <label for="title">제목:</label>
                    <input id="title" class="form-control" type="text" placeholder="동영상 제목">
                </div>
                <div class="form-group">
                    <label for="description">설명:</label>
                    <textarea id="description" class="form-control" placeholder="동영상 설명"></textarea>
                </div>
                <div class="form-group">
                    <label for="privacy-status">공개 설정:</label>
                    <select id="privacy-status">
                    <option value="public">공개</option>
                    <option value="unlisted" selected>미등록</option>
                    <option value="private">비공개</option>
                    </select>
                </div>
            
                <div>
                
                <div class="form-group">
                    <div class="input-group mb-3">
                    <div class="input-group-prepend">
                        <span class="input-group-text">Upload</span>
                    </div>
                    <div class="custom-file">
                        <input type="file" class="custom-file-input" id="file-youtube" accept="video/*" onchange="$('label[for=file-youtube]').text($(this).val().split('\\').pop())">
                        <label class="custom-file-label" for="file-youtube">파일 선택</label>
                    </div>
                    </div>
                </div>	
                
                <div class="form-group text-right">
                    <button id="btn-ytb-upload" class="btn btn-outline-primary">업로드하기</button>
                </div>
                
                <div class="during-upload">
                    <div class="progress">
                        <div id='progress-bar-youtube' class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
                    </div>
                    <span id="bytes-transferred"></span>/<span id="total-bytes"></span> bytes   
                </div>
            
                <div class="post-upload">
                    <p>Uploaded video with id <span id="video-id"></span>. Polling for status...</p>
                    <ul id="post-upload-status"></ul>
                </div>
                
                </div>			    
                </div>
            </div>
        
            <div class="modal-footer justify-content-start">
                <p id="disclaimer">By uploading a video, you certify that you own all rights to the content or that you are authorized by the owner to make the content publicly available on YouTube, and that it otherwise complies with the YouTube Terms of Service located at <a href="https://www.youtube.com/t/terms" target="_blank">https://www.youtube.com/t/terms</a></p>
            </div>
        </div>
    </div>
</div>


<에디터>

유튜브에 동영상을 업로드 한 후 게시판 첨부까지의 작업을 테스트 하기 위해 간단한 에디터를 만들었습니다.

<div id="inputarea" contenteditable="true">
   <p><br></p>
</div>


4. js 파일 수정

<auth.js>

OAUTH2_CLIENT_ID에 클라이언트 ID 등록.

 

<upload_video.js>

default 태그가 'youtube-cors-upload'로 되어있는데 다른 태그로 변경하려면  40번 라인의 this.tags 에 등록된 값을 변경.

 

인증정보가 있을경우 실행되는 코드입니다.

'#button' -> modal 내 업로드 버튼의 id 값으로 변경.

이 글의 코드에서는 '#btn-ytb-upload' 으로 변경

 

동영상의 메타데이터를 설정하는 부분입니다.

원본 소스파일 metadata의 description과 privacyStatus를 위와 같이 수정해줍니다.

 

- 업로드버튼을 클릭 했을때의 처리

<수정 전>


<수정 후>

UploadVideo.prototype.handleUploadClicked = function() {
if($('#file-youtube').get(0).files.length === 0) {
	alert('업로드할 동영상을 선택해주세요.');
	return;
}

if($('#title').val() === '') {
	alert('동영상의 제목을 입력해주세요.');
	return;
}
$('#btn-ytb-upload').attr('disabled', true);
this.uploadFile($('#file-youtube').get(0).files[0]);
};

원본 소스 코드에서는 업로드 버튼을 비활성화 시키고 동영상 파일 데이터를 uploadFile 메소드로 넘겨서 업로드를 진행합니다. 

이 과정에서 추가적으로 필요한 작업이 있다면 추가하면 됩니다.

이 글에서는 동영상 제목과 동영상 파일이 없는 경우 업로드를 막기 위한 작업을 추가했습니다.


- 업로드가 진행될 때와 완료 후 처리

<수정 전>


<수정 후 onProgress>

   onProgress: function(data) {

      var currentTime = Date.now();
      var bytesUploaded = data.loaded;
      var totalBytes = data.total;

      // The times are in millis, so we need to divide by 1000 to get seconds.
      var bytesPerSecond = bytesUploaded / ((currentTime - this.uploadStartTime) / 1000);
      var estimatedSecondsRemaining = (totalBytes - bytesUploaded) / bytesPerSecond;
      var percentageComplete = Math.floor(bytesUploaded/ totalBytes * 100);

//      $('#upload-progress').attr({
//        value: bytesUploaded,
//        max: totalBytes
//      });
       
  $('#progress-bar-youtube').css('width', percentageComplete+'%');
  $('#progress-bar-youtube').attr("aria-valuenow", percentageComplete);
  $('#progress-bar-youtube').text(percentageComplete+"%");	
 
//$('#percent-transferred').text(percentageComplete);
  $('#bytes-transferred').text(bytesUploaded);
  $('#total-bytes').text(totalBytes);

  $('.during-upload').show();
}.bind(this), 

업로드가 진행될때 호출되는 메소드 입니다.

업로드 진행 정보를 표시하는 작업을 하는데 부트스트랩의 커스텀 progress bar로 변경 후 그에 맞게 수정하였습니다.


<수정 후 onComplete>

onComplete: function(data) {
      var uploadResponse = JSON.parse(data);
      this.videoId = uploadResponse.id;
      //var videoThumb = uploadResponse.snippet.thumbnails.high.url;
      //$('#video-id').text(this.videoId);
      $('#inputarea').append('<p class="youtube"><iframe width="560" height="315" src="https://www.youtube.com/embed/' + this.videoId + '" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe><br></p><br>'); //게시글에 등록
      modalDataInit();      
//      $('.post-upload').show();
//      this.pollForVideoStatus();
    }.bind(this)

업로드가 완료됐을때 호출되는 메소드 입니다.

업로드가 완료되면 video ID값을 이용하여 iframe 태그로 만든 다음 게시글에 입력하도록 수정하였습니다.

이 글의 코드에서는 실행을 막아놓은 pollForVideoStatus 메소드는 업로드 이후 영상 처리상태를 체크하는 메소드입니다.

이 메소드의 영상 처리 완료시 진행되는 부분에서 게시글에 등록하도록 구현해도 됩니다만 처리과정에서 걸리는 시간이 조금 긴 편이기 때문에 등록화면에서 사용자를 계속 기다리게 하는것보다는 등록 후 다른 작업을 하며 기다릴수 있도록 onComplete 메소드에서 게시글에 등록하고 업로드 과정을 마치도록 수정했습니다.


- 업로드 이후 modal 초기화

function modalDataInit(){
	
    jQuery('#youtubeModal').modal('hide');
    //$('#id').modal('hide'); 의 경우 외부 js파일에서 제대로 실행이 되지 않아 $대신 jQuery 사용

    $('#title').val("");
    $('#description').val("");
    $('#file-youtube').val("");
    $('label[for=file-youtube]').text("파일 선택");
    
    $('#progress-bar-youtube').css('width', '0%');
    $('#progress-bar-youtube').attr("aria-valuenow", 0);
    $('#progress-bar-youtube').text("");	
    
    $('#btn-ytb-upload').attr('disabled', false);
    
    $('#bytes-transferred').text(0);
    $('#total-bytes').text(0);
    $('.during-upload').hide();
    
}

업로드가 진행되며 변경되었던 요소들의 값을 초기화하는 메소드를 추가했습니다.


5. YouTube 업로드 테스트

모달 버튼을 클릭하면 보이는 구글로그인 버튼을 눌러 인증을 진행합니다.
아무것도 뜨지 않는다면 브라우저에서 구글로 접속하여 로그아웃한 뒤 진행하면 됩니다.
구글 로그인 버튼을 눌렀을때 401에러가 나온다면 auth.js 파일과 modal에 입력한 클라이언트 ID를 다시 확인합니다.  

인증이 성공적으로 완료되면 아래와 같이 업로드 폼이 보여집니다.

<업로드 중일때의 화면>


업로드가 완료되면 에디터에 입력이 됩니다.

업로드 직후에는 아직 영상처리과정이 진행중이기 때문에 정상적으로 표시되지 않습니다.

영상처리과정이 완료된 후 재생을 하면 정상적으로 재생이 됩니다.

댓글 41

  • 2018.10.17 11:31 신고

    잘보고 가요^^

  • 2018.11.01 17:02 신고

    구글 로그인까지 성공을 했고 그 다음 동영상 첨부를 하고 업로드를 누르니깐 Invalid value for: 미등록 is not a valid value 이라는 경고창이 뜹니다. 해결방안을 몰라서 댓글을 남깁니다.

    • 2018.11.01 17:33 신고


      <option value="public">공개</option>
      <option value="unlisted" selected>미등록</option>
      <option value="private">비공개</option>

      에서 "공개", "미등록", "비공개"은 사용자에게 보여지기 위한 부분이고 value 부분은 서버로 전달 될 실제 데이터 값입니다. 오류 내용을 보면 value 에도 "미등록"으로 설정되어있는것 같습니다.

    • 2018.11.01 17:47

      비밀댓글입니다

    • 2018.11.01 17:58 신고

      포스트에 오류가 있었네요. 죄송합니다.

      upload_video.js 파일의 uploadFile 메소드에서

      privacyStatus: $('#privacy-status option:selected').text() 를

      privacyStatus: $('#privacy-status option:selected').val() 로 변경하시면 됩니다.

    • 질문
      2018.11.01 18:12 신고

      파일 메소드를 수정해서 일단 경고창이 안나오는데 Unauthorized 이라는 문구 경고창이 나옵니다.

    • 2018.11.01 18:21 신고

      알려주신 링크에서 테스트 결과 업로드가 제대로 되는데 계정쪽 문제인것 같습니다.

      정확히 어떤 문제로 인증에러가 나는지는 응답데이터를 보아야 알 수 있을것 같습니다.

    • 2018.11.01 18:36 신고

      uploadFile 메소드의 metadata 아래에 uploader 부분의 onError에서

      try {
      var errorResponse = JSON.parse(data);
      //message = errorResponse.error.message;
      message = errorResponse.error;
      } finally {
      alert(message);
      }

      로 변경하고 나오는 메세지를 혹시 알려주실수 있으실까요?

    • 질문
      2018.11.01 19:52 신고

      [object Object]
      이라고 나옵니다.
      늦은시간 까지 죄송해요

    • 2018.11.01 20:39 신고

      음...
      message = JSON.stringify(errorResponse.error);

      이렇게 한번 더 부탁드릴게요

    • 질문
      2018.11.01 20:48 신고

      {"errors":
      [{"domain":"youtube.header","reason":"youtubeSignupRequired","message"
      :"Unauthorized","locationType":"header","location":"Authorization"}],"c
      ode":401,"message":"Unauthorized"}

      이라고 나옵니다

    • 2018.11.01 20:50 신고

      youtubeSignupRequired 인것으로 보아 해당 구글 계정으로 유튜브 가입이 안되어있는것 같습니다.
      유튜브에 접속하셔서 채널을 생성하신 후 다시 테스트 해보시고 댓글 남겨주세요.

    • 질문
      2018.11.01 21:15 신고

      정상적으로 작동 됩니다.
      정말로 감사합니다~

    • 2018.11.01 21:23 신고

      해결이 되어서 다행입니다 :)

  • 2018.11.19 19:23 신고



    다 똑같이 진행 하였는데.... 찾아보기 버튼에서 파일 선택하는 창이 안뜨네요?

    • 2018.11.19 19:38 신고

      알려주신 링크로 가서 확인해보니 리소스 파일들( .css / .js )이 제대로 로드가 안되고 있습니다.
      리소스 파일들의 링크를 본인의 프로젝트에 맞게 수정하셔야 합니다.

    • 2018.11.19 19:51 신고

      테스트 페이지에서 4개의 리소스 링크를 수정하시면 됩니다.

      <link rel="stylesheet" href="/resources/css/upload_video.css">

      <script src="/resources/js/cors_upload.js"></script>

      <script src="/resources/js/upload_video.js"></script>

      <script src="/resources/js/auth.js"></script>

    • 마스터
      2019.02.22 16:49 신고

      Upload 나 Browse 버튼이 아닌 파일선택 을 누르면 창 열리네요

  • 온오프
    2018.11.20 16:41 신고

    넵...정말 감사합니다. php에서 잘되네요

  • php
    2018.11.21 18:08 신고

    업로드한 동영상 링크 받을려고하는데 어떻게 받나요??

    • 2018.11.21 18:16 신고

      공유링크를 말씀하시는건가요??
      동영상에 있는 공유버튼을 누르시면 공유링크를 확인하실 수 있습니다.

  • php
    2018.11.21 18:19 신고

    그게 아니구 제 가 만든 페이지 에서 동영상 업로드 후 에 링크만 따로 데이터 베이스에 저장 할려고 합니다.
    그래서 업로드후 방금 업로드한 동영상의 공유 링크 주소를 바로 인풋값으로 넣을려고 하는데 방법이 있을까요??ㅠㅠ

    • 2018.11.21 18:29 신고

      음.. 일단 이 글에서는 업로드한 동영상의 id값을 받아서 태그나 링크를 생성하고 있는데요.
      어떤 용도의 링크가 필요하신지는 모르겠으나 유튜브에 업로드한 영상은 링크의 형태가 아닌 동영상 id 값을 리턴하고 이용하는 형태입니다.
      따라서 동영상 id 값을 이용해서 필요한 링크를 생성 후 db에 저장하시면 될것 같습나다.

    • 2018.11.21 18:32 신고

      공유링크의 경우 현재는
      'https://youtu.be/id값'
      의 형식으로 생성하시면 됩니다.

  • 2018.11.21 18:53

    비밀댓글입니다

  • ㅜㅜ
    2019.01.21 15:42 신고

    혹시 썸네일 변경이나 수정은 어떻게 해야되는지 아시나요...?ㅠㅠ...
    썸네일 추가 코드가 너무 막막해서 며칠째하고있는데 자바밖에 예시가 없어서..흡...ㅠ

    • 2019.01.21 15:50 신고

      유튜브에서 동영상을 업로드할때 썸네일을 설정할 수 있는 것처럼 그런 옵션을 말하신거라면 현재로써는 도움을 드릴수가 없겠네요 ㅠㅠ

  • 2019.01.22 11:33 신고

    알려주신 방법대로 했는데 링크의 스샷처럼 글자가 깨지고 업로드 한 직후에 영상이 화면에 나타나지 않아요..
    유튜브 채널에 보면 등록은 되어있는데 어떻게 해야될까요?

    • 2019.01.22 14:04 신고

      유튜브에 영상을 업로드해보면 업로드 완료후에 영상 인코딩등 후처리과정이 있습니다. 그동안은 영상이 제대로 표시되지 않고 이 과정이 완료되면 그때부터는 제대로 표시됩니다.

    • 미미
      2019.01.22 15:48 신고

      아 그렇군요 해결했습니다 감사합니다

  • 궁금이
    2019.02.14 14:34 신고

    먼저 좋은 정보 감사드립니다. ^^
    웹페이지에서 동영상 업로드를 할 때마다 유튜브 계정의 아이디 비밀번호를 입력하는지 아니면 소스코드에 등록된 id/pw를 이용해 입력없이 업로드가 되는지 궁금합니다.

    • 2019.02.14 17:22 신고

      소스코드에는 구글 아이디가 아닌 api 사용을 위해 구글에서 발급해주는 api 클라이언트 아이디를 입력하는것이구요.

      계정 인증에 관한 부분은 구글에서 자체적으로 진행되는데 현재 사용하는 브라우저에 로그인 되어있는 구글 계정을 이용하게끔 구현되어 있습니다. 그렇기 때문에 구글에 로그인이 되어있으면 업로드할때 로그인 과정이 없으며 각 사용자 본인의 계정으로 업로드를 할수 있습니다.

  • 밝은 미래
    2019.02.15 13:58 신고

    그대로 시키는 대로 다 했습니다 그런데 슬프게도 서비스 약관 위반으로 공개 상태가 거부됨으로 나옵니다 현재 유튜스 서비스 약관상 외부에서 업로드 하는 것이 불가능한 것인가요?

    • 2019.02.15 14:07 신고

      음.. 생각해볼수 있는건 테스트용으로 업로드하신 영상이 유튜브 약관에 위반하는 어떠한 문제로 거부된 것 같은데 유튜브상에서도 확인이 필요할것 같습니다.

  • 2019.02.22 16:01

    비밀댓글입니다

    • 2019.02.22 16:14 신고

      아마 로그인 이후 안넘어가는 상황에서 브라우저의 콘솔에 오류메세지가 떴을것 같은데 확인부탁드립니다.

    • 마스터
      2019.02.22 16:24 신고

      위에 글이 안보이네요

    • 2019.02.22 16:25 신고

      비밀댓글로 하면 같이 보이는줄 알았네요 ㅠㅠ 수정했습니다.

    • 2019.02.22 16:31

      비밀댓글입니다

    • 2019.02.22 16:38 신고

      auth.js 파일의 handleAuthResult 메소드에 log 찍어서 authResult에 성공이든 에러든 데이터가 제대로 담겨서 넘어오는지 확인부탁드려요.

    • 마스터
      2019.02.22 16:46 신고

      급 다시 되네요.. 덕분에 잘해결했습니다.

      감사합니다.

티스토리 툴바