/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.publishing.youtube;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.google.gdata.client.media.ResumableGDataFileUploader;
import com.google.gdata.client.uploader.ProgressListener;
import com.google.gdata.client.uploader.ResumableHttpFileUploader;
import com.google.gdata.client.youtube.YouTubeService;
import com.google.gdata.data.media.MediaFileSource;
import com.google.gdata.data.media.mediarss.MediaCategory;
import com.google.gdata.data.media.mediarss.MediaDescription;
import com.google.gdata.data.media.mediarss.MediaKeywords;
import com.google.gdata.data.media.mediarss.MediaTitle;
import com.google.gdata.data.youtube.VideoEntry;
import com.google.gdata.data.youtube.YouTubeMediaGroup;
import com.google.gdata.data.youtube.YouTubeNamespace;
import com.google.gdata.util.ServiceException;
public class YouTubePublishAction extends ActionExecuterAbstractBase
{
private final static Log log = LogFactory.getLog(YouTubePublishAction.class);
public static final String NAME = "publish_youtube";
public static final String RESUMABLE_UPLOAD_URL = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads";
/** Time interval at which upload task will notify about the progress */
private static final int PROGRESS_UPDATE_INTERVAL = 1000;
/** Max size for each upload chunk */
private static final int DEFAULT_CHUNK_SIZE = 10000000;
private NodeService nodeService;
private ContentService contentService;
private TaggingService taggingService;
private YouTubePublishingHelper youTubeHelper;
public void setYouTubeHelper(YouTubePublishingHelper youTubeHelper)
{
this.youTubeHelper = youTubeHelper;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
public void setTaggingService(TaggingService taggingService)
{
this.taggingService = taggingService;
}
@Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef)
{
YouTubeService service = youTubeHelper.getYouTubeServiceForNode(actionedUponNodeRef);
if (service != null)
{
try
{
uploadVideo(service, actionedUponNodeRef);
}
catch(Exception ex)
{
log.error("Failed to send asset to YouTube", ex);
throw new AlfrescoRuntimeException("exception.publishing.youtube.publishFailed", ex);
}
}
}
private void uploadVideo(YouTubeService service, NodeRef nodeRef) throws IOException, ServiceException,
InterruptedException
{
ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (reader.exists())
{
File contentFile;
boolean deleteContentFileOnCompletion = false;
if (FileContentReader.class.isAssignableFrom(reader.getClass()))
{
//Grab the content straight from the content store if we can...
contentFile = ((FileContentReader)reader).getFile();
}
else
{
//...otherwise copy it to a temp file and use the copy...
File tempDir = TempFileProvider.getLongLifeTempDir("youtube");
contentFile = TempFileProvider.createTempFile("youtube", "", tempDir);
reader.getContent(contentFile);
deleteContentFileOnCompletion = true;
}
MediaFileSource ms = new MediaFileSource(contentFile, reader.getMimetype());
String videoName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String videoTitle = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE);
if (videoTitle == null || videoTitle.length() == 0)
{
videoTitle = videoName;
}
String videoDescription = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION);
if (videoDescription == null || videoDescription.length() == 0)
{
videoDescription = videoTitle;
}
VideoEntry newEntry = new VideoEntry();
YouTubeMediaGroup mg = newEntry.getOrCreateMediaGroup();
mg.addCategory(new MediaCategory(YouTubeNamespace.CATEGORY_SCHEME, "Tech"));
mg.setTitle(new MediaTitle());
mg.getTitle().setPlainTextContent(videoTitle);
mg.setKeywords(new MediaKeywords());
List tags = taggingService.getTags(nodeRef);
for (String tag : tags)
{
mg.getKeywords().addKeyword(tag);
}
mg.setDescription(new MediaDescription());
mg.getDescription().setPlainTextContent(videoDescription);
FileUploadProgressListener listener = new FileUploadProgressListener(videoName);
ResumableGDataFileUploader uploader = new ResumableGDataFileUploader.Builder(service, new URL(
RESUMABLE_UPLOAD_URL), ms, newEntry).title(videoTitle).trackProgress(listener,
PROGRESS_UPDATE_INTERVAL).chunkSize(DEFAULT_CHUNK_SIZE).build();
uploader.start();
while (!uploader.isDone())
{
Thread.sleep(PROGRESS_UPDATE_INTERVAL);
}
switch (uploader.getUploadState())
{
case COMPLETE:
VideoEntry entry = uploader.getResponse(VideoEntry.class);
String videoId = entry.getMediaGroup().getVideoId();
String contentUrl = entry.getMediaGroup().getContents().get(0).getUrl();
String playerUrl = entry.getMediaGroup().getPlayer().getUrl();
if (log.isDebugEnabled())
{
log.debug("Video content uploaded successfully: " + videoName);
log.debug("YouTube video id is " + videoId);
log.debug("YouTube content URL is " + contentUrl);
log.debug("YouTube video player URL is " + playerUrl);
}
nodeService.setProperty(nodeRef, YouTubePublishingModel.PROP_ASSET_ID, videoId);
nodeService.setProperty(nodeRef, YouTubePublishingModel.PROP_PLAYER_URL, playerUrl);
break;
case CLIENT_ERROR:
log.error("Video content failed to upload: " + videoName);
break;
default:
log.warn("Unknown upload state. Video content may not have uploaded: " + videoName + "("
+ uploader.getUploadState() + ") :" + nodeRef);
break;
}
if (deleteContentFileOnCompletion)
{
contentFile.delete();
}
}
}
@Override
protected void addParameterDefinitions(List paramList)
{
}
/**
* A {@link ProgressListener} implementation to track upload progress. The
* listener can track multiple uploads at the same time.
*/
private class FileUploadProgressListener implements ProgressListener
{
String videoName;
public FileUploadProgressListener(String videoName)
{
this.videoName = videoName;
}
public synchronized void progressChanged(ResumableHttpFileUploader uploader)
{
switch (uploader.getUploadState())
{
case COMPLETE:
log.info("Upload Completed: " + videoName);
break;
case CLIENT_ERROR:
log.error("Upload Failed: " + videoName);
break;
case IN_PROGRESS:
log.info(videoName + String.format(" %3.0f", uploader.getProgress() * 100) + "%");
break;
case NOT_STARTED:
log.info("Upload Not Started: " + videoName);
break;
}
}
}
}