##このサンプルについて
nodeからYoutube Data APIを呼んでd3とectを使用しサムネイルHTMLファイルを静的に生成する。
生成した静的ページは以下のリンクを参照。
http://www.sfpgmr.net/test/Youtube/0001/
##使用しているライブラリ
- q.js
- d3.js
- ect
- Bootstrap
##このサンプルについて
nodeからYoutube Data APIを呼んでd3とectを使用しサムネイルHTMLファイルを静的に生成する。
生成した静的ページは以下のリンクを参照。
http://www.sfpgmr.net/test/Youtube/0001/
##使用しているライブラリ
<!DOCTYPE html> | |
<html vocab="http://schema.org"> | |
<head> | |
<title><%= @title %></title> | |
<meta charset="utf-8" /> | |
<meta name="description" content="<%- @description %>" /> | |
<meta name="keywords" content="<%- @keywords %>" /> | |
<meta name="author" content="<%- @author %>" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> | |
<% content 'header-script' %> | |
<script type="text/javascript"> | |
if (!location.href.match(/localhost/)) { | |
var _gaq = _gaq || []; | |
_gaq.push(['_setAccount', 'UA-15457703-9']); | |
_gaq.push(['_trackPageview']); | |
(function () { | |
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | |
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | |
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | |
})(); | |
} | |
</script> | |
</head> | |
<body> | |
<nav class="navbar navbar-default navbar-fixed-top " role="navigation"> | |
<div class="container"> | |
<!-- Brand and toggle get grouped for better mobile display --> | |
<div class="navbar-header"> | |
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse"> | |
<span class="sr-only">Toggle navigation</span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
</button> | |
<div><a class="navbar-brand" href="#" property="headLine" id="headLine" style="margin-top:auto;margin-bottom:auto;"><%-@header %></a></div> | |
</div> | |
<div class="collapse navbar-collapse" id="navbar-collapse"> | |
<% content 'nav-content' %> | |
</div> | |
</div> | |
</nav> | |
<article id="article" typeof="Article"> | |
<header class="container"><h2><%-@title %></h2></header> | |
<% content 'article' %> | |
</article> | |
<footer id="footer" class="navbar navbar-default navbar-fixed-bottom"> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-3" property="creator" id="creator"> | |
<div typeof="person" id="creatorPerson"> | |
<span property="name" id="creatorName">By <a href="http://www.enoie.net/">S.F.</a></span> | |
</div> | |
</div> | |
<div class="col-xs-3 text-center"><% content 'footer-content' %></div> | |
<div class="col-xs-6 text-right"><small><time id="date" property="dc:date" datetime="<%-@datetime %>"><%= @datestr %></time></small></div> | |
</div> | |
</div> | |
</footer> | |
<% content 'etc-content' %> | |
</body> | |
</html> |
<% extend 'template_base.html' %> | |
<% block 'article' : %> | |
<div property="articleBody" id="articleBody" class="container"> | |
<%- @articleBody %> | |
</div> | |
<% end %> | |
<% block 'header-script' : %> | |
<link rel="stylesheet" href="css/main.min.css" /> | |
<% end %> | |
<% block 'nav-content' : %> | |
<p class="navbar-text navbar-right"><a href="/" class="navbar-link"><span class="glyphicon glyphicon-home"></span>ホーム</a></p> | |
<% end %> | |
<% block 'footer-content' : %> | |
<% end %> | |
<% block 'etc-content' : %> | |
<% end %> |
// | |
// Youtube API Sample | |
// | |
//The MIT License(MIT) | |
//Copyright(c) 2014 Satoshi Fujiwara | |
// | |
//Permission is hereby granted, free of charge, to any person obtaining a copy | |
//of this software and associated documentation files(the "Software"), to deal | |
//in the Software without restriction, including without limitation the rights | |
//to use, copy, modify, merge, publish, distribute, sublicense, and / or sell | |
//copies of the Software, and to permit persons to whom the Software is | |
//furnished to do so, subject tothe following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
//all copies or substantial portions of the Software. | |
// | |
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE | |
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
//THE SOFTWARE. | |
var util = require('util'); | |
var https = require('https'); | |
var fs = require('fs'); | |
var d3 = require('d3'); | |
var q = require('q'); | |
var ect = require('ect'); | |
var readFile = q.nfbind(fs.readFile); | |
var writeFile = q.nfbind(fs.writeFile); | |
q.all([readFile('../apikey.json', 'utf-8'), readFile('../config.json', 'utf-8')]) | |
.spread(function (keys, config) { | |
keys = JSON.parse(keys); | |
config = JSON.parse(config); | |
var json = q.nfbind(d3.json); | |
var result = []; | |
var defer = q.defer(); | |
function getData(pageToken) { | |
var pt = ''; | |
if (pageToken) { | |
pt = '&pageToken=' + pageToken; | |
} | |
callYoutubeAPI('https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=UCgwM0kBBsDRMZDhhWuTNR-g&order=date&maxResults=50' + pt + '&key=' + keys.youtube) | |
.then(function (d) { | |
result = result.concat(dt.items); | |
if (dt.nextPageToken) { | |
getData(dt.nextPageToken); | |
} else { | |
defer.resolve(); | |
} | |
}); | |
} | |
getData(); | |
defer.promise.then(function () { | |
var thumb = d3.select('body').selectAll('div') | |
.data(result) | |
.enter() | |
.append('div') | |
.classed({ 'col-xs-12': true, 'col-md-4': true, 'col-lg-3': true }) | |
.append('div') | |
.classed('thumbnail', true) | |
.style('height', '400px') | |
.style('overflow', 'auto'); | |
console.log(result.length); | |
thumb.append('a') | |
.attr('href', function (d) { | |
var contentsUrl = 'https://www.youtube.com/'; | |
if (d.id.kind == 'youtube#playlist') { | |
contentsUrl += 'playlist?list=' + d.id.playlistId; | |
} else if (d.id.kind == 'youtube#video') { | |
contentsUrl += 'watch?v=' + d.id.videoId; | |
} else if (d.id.kind == 'youtube#channel') { | |
contentsUrl += 'channel/' + d.id.channelId; | |
} | |
return contentsUrl; | |
}) | |
.attr('target', '_blank') | |
.append('img') | |
.attr('src', function (d) { return d.snippet.thumbnails.high.url; }) | |
.attr('alt', function (d) { return d.snippet.title; }); | |
var cap = thumb | |
.append('div') | |
.classed('caption', true); | |
cap.append('h4').text(function (d) { return d.snippet.title; }); | |
cap.append('p').text(function (d) { return d.snippet.description; }); | |
var renderer = ect({ root : './' }); | |
var now = new Date(); | |
var data = { | |
header : 'Youtube Test', | |
title : 'Youtube Test 0001 - 動画をサムネイルする', | |
description: 'Youtube Test 0001 - 動画をサムネイルする', | |
keywords : 'Youtube,d3.js', | |
author: 'sfpgmr', | |
articleBody: d3.select('body').html(), | |
datetime:now.toISOString(), | |
datestr:now.toISOString() | |
}; | |
//console.log(d3.select('body').html()); | |
return writeFile(config.contentRoot + '/test/Youtube/0001' + '/index.html', renderer.render('template_yt0001.html', data), 'utf-8'); | |
}); | |
return defer.promise; | |
}) | |
.catch(function (e) { | |
console.log(e); | |
}) | |
.done(function () { | |
console.log('処理終了'); | |
}); | |
function callYoutubeAPI(url) { | |
var d = q.defer(); | |
https.get(url, function (res) { | |
var body = ''; | |
res.setEncoding('utf8'); | |
res.on('data', function (chunk) { | |
body += chunk; | |
}); | |
res.on('end', function (res) { | |
// ret = JSON.parse(body); | |
d.resolve(body); | |
body = void (0); | |
}); | |
}).on('error', function (e) { | |
console.log(e); | |
d.reject(e); | |
}); | |
return d.promise; | |
} |