본문

IT로그/프로그래밍

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

1. 시작하기 전에 


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를 다시 확인합니다.  

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

<업로드 중일때의 화면>


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

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

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

댓글 24

  • 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>

  • 온오프
    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

    비밀댓글입니다

티스토리 툴바