View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.publication.youtube;
23  
24  import org.opencastproject.publication.youtube.auth.ClientCredentials;
25  import org.opencastproject.publication.youtube.auth.OAuth2CredentialFactory;
26  import org.opencastproject.publication.youtube.auth.OAuth2CredentialFactoryImpl;
27  import org.opencastproject.util.data.Collections;
28  
29  import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
30  import com.google.api.client.googleapis.media.MediaHttpUploader;
31  import com.google.api.client.http.InputStreamContent;
32  import com.google.api.client.http.javanet.NetHttpTransport;
33  import com.google.api.client.json.jackson2.JacksonFactory;
34  import com.google.api.services.youtube.YouTube;
35  import com.google.api.services.youtube.YouTubeRequest;
36  import com.google.api.services.youtube.model.Playlist;
37  import com.google.api.services.youtube.model.PlaylistItem;
38  import com.google.api.services.youtube.model.PlaylistItemListResponse;
39  import com.google.api.services.youtube.model.PlaylistItemSnippet;
40  import com.google.api.services.youtube.model.PlaylistListResponse;
41  import com.google.api.services.youtube.model.PlaylistSnippet;
42  import com.google.api.services.youtube.model.PlaylistStatus;
43  import com.google.api.services.youtube.model.ResourceId;
44  import com.google.api.services.youtube.model.SearchListResponse;
45  import com.google.api.services.youtube.model.Video;
46  import com.google.api.services.youtube.model.VideoListResponse;
47  import com.google.api.services.youtube.model.VideoSnippet;
48  import com.google.api.services.youtube.model.VideoStatus;
49  
50  import org.apache.commons.lang3.ArrayUtils;
51  
52  import java.io.BufferedInputStream;
53  import java.io.File;
54  import java.io.FileInputStream;
55  import java.io.IOException;
56  import java.util.LinkedList;
57  import java.util.List;
58  
59  public class YouTubeAPIVersion3ServiceImpl implements YouTubeAPIVersion3Service {
60  
61    private YouTube youTube;
62  
63    @Override
64    public void initialize(final ClientCredentials credentials) throws IOException {
65      final OAuth2CredentialFactory factory = new OAuth2CredentialFactoryImpl();
66      final GoogleCredential credential = factory.getGoogleCredential(credentials);
67      youTube = new YouTube.Builder(new NetHttpTransport(), new JacksonFactory(), credential).build();
68    }
69  
70    @Override
71    public Video addVideoToMyChannel(final VideoUpload videoUpload) throws IOException {
72      final Video videoObjectDefiningMetadata = new Video();
73      final VideoStatus status = new VideoStatus();
74      status.setPrivacyStatus(videoUpload.getPrivacyStatus());
75      videoObjectDefiningMetadata.setStatus(status);
76      // Metadata lives in VideoSnippet
77      final VideoSnippet snippet = new VideoSnippet();
78      snippet.setTitle(videoUpload.getTitle());
79      snippet.setDescription(videoUpload.getDescription());
80      final String[] tags = videoUpload.getTags();
81      if (ArrayUtils.isNotEmpty(tags)) {
82        snippet.setTags(Collections.list(tags));
83      }
84      // Attach metadata to video object.
85      videoObjectDefiningMetadata.setSnippet(snippet);
86  
87      final File videoFile = videoUpload.getVideoFile();
88      final BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(videoFile));
89      final InputStreamContent mediaContent = new InputStreamContent("video/*", inputStream);
90      mediaContent.setLength(videoFile.length());
91  
92      final YouTube.Videos.Insert videoInsert = youTube.videos()
93          .insert("snippet,statistics,status", videoObjectDefiningMetadata, mediaContent);
94      final MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
95  
96      uploader.setDirectUploadEnabled(false);
97      uploader.setProgressListener(videoUpload.getProgressListener());
98      return execute(videoInsert);
99    }
100 
101   @Override
102   public Playlist createPlaylist(final String title, final String description, final String... tags)
103           throws IOException {
104     final PlaylistSnippet playlistSnippet = new PlaylistSnippet();
105     playlistSnippet.setTitle(title);
106     playlistSnippet.setDescription(description);
107     if (tags.length > 0) {
108       playlistSnippet.setTags(Collections.list(tags));
109     }
110     // Playlists are always public. The videos therein might be private.
111     final PlaylistStatus playlistStatus = new PlaylistStatus();
112     playlistStatus.setPrivacyStatus("public");
113 
114     // Create playlist with metadata and status.
115     final Playlist youTubePlaylist = new Playlist();
116     youTubePlaylist.setSnippet(playlistSnippet);
117     youTubePlaylist.setStatus(playlistStatus);
118 
119     // The first argument tells the API what to return when a successful insert has been executed.
120     final YouTube.Playlists.Insert command = youTube.playlists().insert("snippet,status", youTubePlaylist);
121     return execute(command);
122   }
123 
124   @Override
125   public PlaylistItem addPlaylistItem(final String playlistId, final String videoId) throws IOException {
126     // Resource type (video,playlist,channel) needs to be set along with resource id.
127     final ResourceId resourceId = new ResourceId();
128     resourceId.setKind("youtube#video");
129     resourceId.setVideoId(videoId);
130 
131     // Set the required snippet properties.
132     final PlaylistItemSnippet playlistItemSnippet = new PlaylistItemSnippet();
133     playlistItemSnippet.setTitle("First video in the test playlist");
134     playlistItemSnippet.setPlaylistId(playlistId);
135     playlistItemSnippet.setResourceId(resourceId);
136 
137     // Create the playlist item.
138     final PlaylistItem playlistItem = new PlaylistItem();
139     playlistItem.setSnippet(playlistItemSnippet);
140 
141     // The first argument tells the API what to return when a successful insert has been executed.
142     final YouTube.PlaylistItems.Insert playlistItemsInsertCommand
143         = youTube.playlistItems().insert("snippet,contentDetails", playlistItem);
144     return execute(playlistItemsInsertCommand);
145   }
146 
147   @Override
148   public void removeVideoFromPlaylist(final String playlistId, final String videoId) throws IOException {
149     final PlaylistItem playlistItem = findPlaylistItem(playlistId, videoId);
150     final YouTube.PlaylistItems.Delete deleteRequest = youTube.playlistItems().delete(playlistItem.getId());
151     execute(deleteRequest);
152   }
153 
154   @Override
155   public void removeMyVideo(final String videoId) throws Exception {
156     final YouTube.Videos.Delete deleteRequest = youTube.videos().delete(videoId);
157     execute(deleteRequest);
158   }
159 
160   @Override
161   public void removeMyPlaylist(final String playlistId) throws IOException {
162     final YouTube.Playlists.Delete deleteRequest = youTube.playlists().delete(playlistId);
163     execute(deleteRequest);
164   }
165 
166   @Override
167   public SearchListResponse searchMyVideos(final String queryTerm, final String pageToken, final long maxResults)
168           throws IOException {
169     final YouTube.Search.List search = youTube.search().list("id,snippet");
170     if (pageToken != null) {
171       search.set("pageToken", pageToken);
172     }
173     search.setQ(queryTerm);
174     search.setType("video");
175     search.setForMine(true);
176     search.setMaxResults(maxResults);
177     search.setFields("items(id,kind,snippet),nextPageToken,pageInfo,prevPageToken,tokenPagination");
178     return execute(search);
179   }
180 
181   @Override
182   public Video getVideoById(final String videoId) throws IOException {
183     final YouTube.Videos.List search = youTube.videos().list("id,snippet");
184     search.setId(videoId);
185     search.setFields("items(id,kind,snippet),nextPageToken,pageInfo,prevPageToken,tokenPagination");
186     final VideoListResponse response = execute(search);
187     return response.getItems().isEmpty() ? null : response.getItems().get(0);
188   }
189 
190   @Override
191   public Playlist getMyPlaylistByTitle(final String title) throws IOException {
192     final String trimmedTitle = title.trim();
193     boolean searchedAllPlaylist = false;
194     Playlist playlist = null;
195     String nextPageToken = null;
196     while (!searchedAllPlaylist) {
197       final PlaylistListResponse searchResult = getMyPlaylists(nextPageToken, 50);
198       for (final Playlist p : searchResult.getItems()) {
199         if (p.getSnippet().getTitle().trim().equals(trimmedTitle)) {
200           playlist = p;
201           break;
202         }
203       }
204       nextPageToken = searchResult.getNextPageToken();
205       searchedAllPlaylist = nextPageToken == null;
206     }
207     return playlist;
208   }
209 
210   @Override
211   public PlaylistListResponse getMyPlaylists(final String pageToken, final long maxResults) throws IOException {
212     final YouTube.Playlists.List search = youTube.playlists().list("id,snippet");
213     if (pageToken != null) {
214       search.set("pageToken", pageToken);
215     }
216     search.setMaxResults(maxResults);
217     search.setMine(true);
218     search.setFields("items(id,snippet),kind,nextPageToken,pageInfo,prevPageToken,tokenPagination");
219     return execute(search);
220   }
221 
222   @Override
223   public PlaylistItemListResponse getPlaylistItems(
224       final String playlistId,
225       final String pageToken,
226       final long maxResults
227   ) throws IOException {
228     final YouTube.PlaylistItems.List search = youTube.playlistItems().list("id,snippet");
229     search.setPlaylistId(playlistId);
230     search.setPageToken(pageToken);
231     search.setMaxResults(maxResults);
232     search.setFields("items(id,kind,snippet),nextPageToken,pageInfo,prevPageToken,tokenPagination");
233     return execute(search);
234   }
235 
236   /**
237    * @param playlistId may not be {@code null}
238    * @param videoId may not be {@code null}
239    * @return null when not found.
240    * @throws IOException when transaction fails.
241    */
242   private PlaylistItem findPlaylistItem(final String playlistId, final String videoId) throws IOException {
243     final List<PlaylistItem> playlistItems = getAllPlaylistItems(playlistId);
244     PlaylistItem playlistItem = null;
245     for (final PlaylistItem next : playlistItems) {
246       final String id = next.getSnippet().getResourceId().getVideoId();
247       if (videoId.equals(id)) {
248         playlistItem = next;
249         break;
250       }
251     }
252     return playlistItem;
253   }
254 
255   /**
256    *
257    * @param playlistId may not be {@code null}
258    * @return zero or more playlist items.
259    * @throws IOException  when transaction fails.
260    */
261   private List<PlaylistItem> getAllPlaylistItems(final String playlistId) throws IOException {
262     final List<PlaylistItem> playlistItems = new LinkedList<PlaylistItem>();
263     boolean done = false;
264     String nextPageToken = null;
265     while (!done) {
266       final PlaylistItemListResponse searchResult = getPlaylistItems(playlistId, nextPageToken, 50);
267       playlistItems.addAll(searchResult.getItems());
268       nextPageToken = searchResult.getNextPageToken();
269       done = nextPageToken == null;
270     }
271     return playlistItems;
272   }
273 
274   /**
275    * @see com.google.api.services.youtube.YouTubeRequest#execute()
276    * @param command may not be {@code null}
277    * @param <T> type of request.
278    * @return result; may be {@code null}
279    * @throws IOException  when transaction fails.
280    */
281   private <T> T execute(final YouTubeRequest<T> command) throws IOException {
282     return command.execute();
283   }
284 }