here is the script we used:
# -*- coding:utf-8 -*-
" " "
Import YouTube feed into Plone using sc.embedder as content type.
Copyright 2015-2016, Simples Consultoria.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston,
MA 02111-1307 USA.
" " "
from my.proyect.controlpanel import IYouTubeSettings
from my.proyect.logger import logger
from collections import namedtuple
from DateTime import DateTime
from gdata.youtube.service import YouTubeService
from plone import api
from plone.i18n.normalizer.interfaces import IIDNormalizer
from plone.namedfile.file import NamedBlobImage
from plone.protect.interfaces import IDisableCSRFProtection
from Products.CMFPlone.utils import safe_unicode
from Products.Five.browser import BrowserView
from zExceptions import BadRequest
from zope.component import queryUtility
from zope.interface import alsoProvides
import json
import urllib
BASE_URI = 'https://www.youtube.com/feeds/videos.xml?channel_id='
EMBED_TEMPLATE = '<iframe width="{width}" height="{height}" src="https://www.youtube.com/embed/{id}"></iframe>'
fields = [
'id', # w_sqsoiHkbc
'title',
'description',
'url', # https://www.youtube.com/watch?v=w_sqsoiHkbc
'thumbnail',
'width',
'height',
'creation_date',
'modification_date',
]
Video = namedtuple('Video', fields)
class ProcessYouTubeFeedView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
def setup(self):
alsoProvides(self.request, IDisableCSRFProtection)
self.channel_id = api.portal.get_registry_record(
IYouTubeSettings.__identifier__ + '.youtube_channel_id')
if not self.channel_id:
raise BadRequest(u'No channel ID specified on YouTube configlet.')
logger.info(u'Using channel_id = ' + self.channel_id)
self.feed_uri = BASE_URI + self.channel_id
self.folder = self._get_folder()
def _get_folder(self):
"""Return default folder to fill with YouTube Feed entry.
Create folder if needed.
"""
try:
portal = api.portal.get()
return portal['videos']
except KeyError:
logger.info(u'Default folder not found; creating it')
with api.env.adopt_roles(['Manager']):
folder = api.content.create(
container=portal,
type='Folder',
title='Videos',
exclude_from_nav=True,
)
api.content.transition(obj=folder, transition='publish')
return folder
def _get_feed(self):
"""Get feed with YouTube Service."""
logger.info(u'Getting video feed from YouTube')
yt_service = YouTubeService()
feed = yt_service.GetYouTubeVideoFeed(self.feed_uri)
if api.env.test_mode():
feed.entry = feed.entry[:1] # return only one entry when running tests
logger.info(u'Feed contains {0} entries'.format(len(feed.entry)))
return feed
def _get_thumbnail(self, url):
logger.info(u'Getting thumbnail from: ' + url)
try:
data = urllib.urlopen(url).read()
return NamedBlobImage(data=data)
except IOError:
logger.warning(u'Connection error')
pass
def _get_entry_data(self, entry):
return Video(
id=entry.id.text.split(':')[2],
title=safe_unicode(entry.media.title.text),
description=safe_unicode(entry.media.description.text),
url=entry.link[0].href,
thumbnail=self._get_thumbnail(entry.media.thumbnail[0].url),
width=int(entry.media.thumbnail[0].width),
height=int(entry.media.thumbnail[0].height),
creation_date=entry.published.text,
modification_date=DateTime(entry.updated.text),
)
def _create_video(self, entry):
"""Create new video based on feed entry."""
logger.info(u'Creating new video from feed entry')
util = queryUtility(IIDNormalizer)
video = self._get_entry_data(entry)
last_created = self._get_creation_date_from_latest_video()
if video.creation_date <= last_created:
return 0
video_id = util.normalize(video.title)
if video_id in self.folder:
return 0
embed_html = EMBED_TEMPLATE.format(
width=video.width, height=video.height, id=video.id)
with api.env.adopt_roles(['Manager']):
obj = api.content.create(
container=self.folder,
type='sc.embedder',
id=video_id,
title=video.title,
description=video.description,
url=video.url,
width=video.width,
height=video.height,
embed_html=embed_html,
image=video.thumbnail,
creation_date=DateTime(video.creation_date),
modification_date=video.modification_date,
)
api.content.transition(obj=obj, transition='publish')
logger.info(u'Video "{0}" created and published'.format(video.title))
return 1
def _get_creation_date_from_latest_video(self):
"""Return the creation date of the most recent video in the catalog."""
catalog = api.portal.get_tool('portal_catalog')
folder_path = '/'.join(self.folder.getPhysicalPath())
results = catalog(
portal_type='sc.embedder', sort_on='created', sort_order='reverse', sort_limit=1,
path={'query': folder_path, 'depth': 1})[:1]
if results:
# convert to UTC as that's the time zone used by YouTube entries
title, date = results[0].Title, results[0].created.toZone('UTC').ISO()
msg = u'Latest video available ("{0}") was created on {1}; importing from newer entries only'
logger.info(msg.format(safe_unicode(title), date))
else:
# no videos in catalog; we use earliest date possible
date = DateTime(0).toZone('UTC').ISO()
return date
def _update_feed_entries(self, feed):
"""Update feed entries if needed."""
totals = {'total': len(feed.entry), 'created': 0}
# add only videos created since last time
for entry in feed.entry:
totals['created'] += self._create_video(entry)
if totals['created'] > 0:
logger.info(u'{0} videos added.'.format(totals['created']))
else:
logger.info(u'No videos added.')
return totals
def __call__(self):
"""Process the YouTube feed."""
self.setup()
feed = self._get_feed()
totals = self._update_feed_entries(feed)
return totals
this is the ZCML registration:
<browser:page
name="process-youtube-feed"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".youtube.ProcessYouTubeFeedView"
permission="cmf.AddPortalContent"
/>
and this is the buildout configuration for a special instance:
[instance-youtube]
<=instance
event-log-level = INFO
port-base = 9
z2-log-level = INFO
zodb-cache-size = 5000
zope-conf-additional =
<clock-server>
method /Plone/@@process-youtube-feed
period 3600
user <user>
password <password>
host localhost:8089
</clock-server>
replace user and password there and create an user with enough privileges to add content.