Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

"どこでも YouTube" を実現する Greasemonkey スクリプト YouTube Anywhere

YouTube の動画を鑑賞・収集する上で便利で素敵なサービスがいろいろと登場しています。

ただまぁ僕の Web ブラウズのやり方は「livedoor Reader + YouTube + はてなブックマークで最速動画ウォッチング」で紹介したように、厳選したサービスを使い込む感じで、それ以外はまぁひと様のリンクとか、自分とこのブックマークに蓄積したもの経由でしか YouTube の動画を見ないだろうな、とか思うわけです。
そんな折り、かせいさんとこ - del.icio.us上のYouTubeへのリンクを直接見たりダウンロードできるGreasemonkey にて公開されていたスクリプトに触発されて、僕も似たようなものを作ってみました。YouTube の動画へのリンクがあると、そのリンクの脇に YouTube アイコンを表示、アイコンをクリックすると HATENA-TUBE ライクなプレイヤが現われるってなもの。
YouTube player on Hatena::Bookmark
デフォルトでは del.icio.usはてなブックマーク上のみで動作するよう設定してありますが、「ツール -> Manage UserScripts」から include するドメインを増やすよう設定すれば、いろんなところで使えます。
まぁ、サイトによってはスタイルとかがかなりと悲惨なことにもなりかねませんが、そのへんは見なかったことにして、あまり気にしないでください。

追記。
このスクリプトは「antipop - Update: LDR で最速動画ウォッチング + YouTube Anywhere」で更新されています。そちらのエントリも合せてご覧ください。

インストール: youtube_anywhere.user.js

// ==UserScript==
//
// @name          YouTube Anywhere
// @namespace     http://antipop.gs/ns/greasemonkey/youtube_anywhere
// @include       http://b.hatena.ne.jp/*
// @include       http://del.icio.us/*
//
// ==/UserScript==
//
// ==Configuration==
//
// add other domains to @include for more use
//
// - include YouTube.
// @include       *youtube.com/*
//
// - include whole the web
// @include       *
//
//==/Configuration==
//
//==Acknowledgments==
//
// Bitcons >> SOME RANDOM DUDE
//  - http://somerandomdude.net/srd-projects/bitcons/
//
//==/Acknowledgments==
//
// ==Copyright==
//
// Copyright (C) 2006 by Kentaro Kuribayashi
//
// This script is distributed under the CCPL by-sa
//  - See: http://creativecommons.org/licenses/by-sa/2.5/
//
// ==/Copyright==
//

(function(){
    var playerContainerId = 'GM_youtube_player';
    var playerWidth  = 425;
    var playerHeight = 350;
    var overlayId    = 'GM_overlay';
    var regexp       = new RegExp('^http://(?:www\\.)?youtube\\.com/(?:watch)?\\?.*v=([^&]+).*$', 'i');

    $A(document.links).forEach(function (link) {
        if (link.href.match(regexp)) {
            var videoId    = RegExp.$1;
            var videoTitle = link.innerHTML.replace(/<.+?>/g, '');
            var icon = $C('img', {
                'src'   : 'http://www.youtube.com/favicon.ico',
                'alt'   : 'YouTube',
                'title' : 'watch this movie',
                'style' : {
                    'marginRight' : '0.5em',
                    'cursor'      : 'pointer',
                },
                'event' : [{
                    'name'     : 'click',
                    'callback' : function () { showPlayer(videoId, videoTitle); },
                    'flag'     : false,
                }],
            });
            link.parentNode.insertBefore(icon, link);
        }
    });

    function $ (id) { return document.getElementById(id); };

    function $A (arg) {
        var array = [];
        for (var i = 0; i < arg.length; i++) {
            array.push(arg[i]);
        }
        return array;
    }

    function $C (tag, prop) {
        var element = document.createElement(tag);
        for (var i in prop) {
            if (i == 'style') {
                for (var p in prop[i]) {
                    element.style[p] = prop[i][p];
                }
            }
            else if (i == 'event') {
                prop[i].forEach(function (e) {
                    element.addEventListener(e.name, e.callback, e.flag);
                });
            }
            else {
                element[i] = prop[i];
            }
        }
        return element;
    };

    function createPlayerContainer (videoId, videoTitle) {
        var playerContainer = $C('div', {
            'id'     : playerContainerId,
            'style'  : {
                'position'        : 'fixed',
                'zIndex'          : '100',
                'backgroundColor' : '#ffffff',
                'padding'         : '0.5em 1.5em',
            },
        });

        var playerHeader = createPlayerHeader(videoTitle);
        var player       = createPlayer(videoId);
        var playerFooter = createPlayerFooter(videoId, videoTitle, player);

        [playerHeader, player, playerFooter].forEach(function (e) {
             playerContainer.appendChild(e);
        });

        return playerContainer;
    }

    function createPlayerHeader (videoTitle) {
        var playerHeader = $C('p', {
            'style' : {
                'fontWeight' : 'bold',
                'margin'     : '0.5em 2em 0.5em 0',
            }
        });

        var closeButton = $C('img', {
            'id'    : 'GM_close_utton',
            'src'   : getImageUrl('close'), 
            'title' : 'Hide this player',
            'style' : {
                'display'         : 'block',
                'position'        : 'absolute',
                'top'             : '5px',
                'right'           : '5px',
                'cursor'          : 'pointer',
                'backgroundColor' : '#00cc00',
            },
            'event' : [
                {
                    'name'     : 'click',
                    'callback' : function () { hidePlayer(); },
                    'flag'     : false,
                },
                {
                    'name'     : 'mouseover',
                    'callback' : function () { $('GM_close_utton').style.backgroundColor = '#ffd700'; },
                    'flag'     : false,
                },
                {
                    'name'     : 'mouseout',
                    'callback' : function () { $('GM_close_utton').style.backgroundColor = '#00cc00'; },
                    'flag'     : false,
                },
            ],
        });

        [document.createTextNode(videoTitle), closeButton,].forEach(function (e) {
            playerHeader.appendChild(e);
        });

        return playerHeader;
    }

    function createPlayerFooter (videoId, videoTitle, player) {
        var playerFooter = $C('div', {
            'style' : {
                'padding' : '0.5em 0',
            }
        });

        var linkContainer = $C('p', {style : { 'textAlign' : 'center', 'margin' : '0', }});
        var youtubeUri    = ['http://www.youtube.com/watch?v=', videoId].join('');
        [
            {
                'id'       : 'GM_download',
                'title'    : 'download this video',
                'getUri'   : ['http://youtubech.com/test/read.cgi?dl=', videoId].join(''),
                'label'    : 'Download',
                'image'    : 'download',
            },
            {
                'id'       : 'GM_post_to_hatena',
                'title'    : 'post to Hatena::Bookmark',
                'getUri'   : ['http://b.hatena.ne.jp/add?mode=confirm&url=', encodeURIComponent(youtubeUri)].join(''),
                'label'    : 'Hatena',
                'image'    : 'arrow',
            },
            {
                'id'       : 'GM_post_to_delicious',
                'title'    : 'post to del.icio.us',
                'getUri'   : ['http://del.icio.us/post?v=4;url=', encodeURIComponent(youtubeUri), ';title=', encodeURIComponent(videoTitle)].join(''),
                'label'    : 'del.icio.us',
                'image'    : 'arrow',
            },
            {
                'id'       : 'GM_post_to_qooqle',
                'title'    : 'post to qooqle',
                'getUri'   : ['http://clippers.qooqle.jp/post?url=', encodeURIComponent(youtubeUri)].join(''),
                'label'    : 'Qooqle',
                'image'    : 'arrow',
            },
        ].forEach(function (i) {
            var link = $C('span', {
                'id'    : i.id,
                'title' : i.title,
                'style' : {
                    'color'         : '#00cc00',
                    'textIndent'    : '20px',
                    'verticalAlign' : 'middle',
                    'marginRight'   : '0.5em',
                    'cursor'        : 'pointer',
                },
                'event' : [
                    {
                        'name'     : 'click',
                        'callback' : function () { window.open(i.getUri); },
                        'flag'     : false,
                    },
                    {
                        'name'     : 'mouseover',
                        'callback' : function () { $(i.id).style.color = '#ffc125'; },
                        'flag'     : false,
                    },
                    {
                        'name'     : 'mouseout',
                        'callback' : function () { $(i.id).style.color = '#00cc00'; },
                        'flag'     : false,
                    },
                ],
            });

            var icon = $C('img', {
                'src'   : getImageUrl(i.image),
                'style' : {
                    'marginRight'     : '0.5em',
                    'backgroundColor' : '#00cc00',
                },
            });

            [icon, document.createTextNode(i.label)].forEach(function (e) {
                link.appendChild(e);
            });
            linkContainer.appendChild(link);
        });

        playerFooter.appendChild(linkContainer);

        [
            {
                'label' : 'URI:',
                'id'    : 'youtube_uri',
                'value' : ['http://www.youtube.com/watch?v=', videoId].join(''),
            },
            {
                'label' : 'Player:',
                'id'    : 'youtube_embed',
                'value' : player.innerHTML,
            },
            {
                'label' : 'Hatena:',
                'id'    : 'hatena_notation',
                'value' : ['[http://youtubech.com/test/read.cgi', encodeURIComponent(['?dl=', videoId, '&.flv'].join('')), ':movie]'].join(''),
            },
        ].forEach(function (i) {
            var inputContainer = $C('p', { 'style' : { 'margin': '0.2em 0 0 0'} });
            var label = $C('label', {
                'for'   : i.id,
            });
            label.innerHTML = i.label;
            var input = $C('input', {
                'id'    : i.id,
                'style' : {
                    'border'   : 'solid 1px #333333',
                    'width'    : '80%',
                    'position' : 'absolute',
                    'right'    : '1.5em',
                },
                'event'   : [
                    {
                        'name'     : 'focus',
                        'callback' : function () { $(i.id).select(); },
                        'flag'     : false,
                    },
                    {
                        'name'     : 'click',
                        'callback' : function () { $(i.id).select(); },
                        'flag'     : false,
                    },
                ]
            });
            input.value = i.value;

            [label, input].forEach(function (e) {
               inputContainer.appendChild(e);
            });
            playerFooter.appendChild(inputContainer);
        });

        return playerFooter;
    }

    function createPlayer (videoId) {
        var videoSrc = ['http://www.youtube.com/v/', videoId].join('');

        var div    = $C('div', {});
        var object = $C('object', {
            'width'  : playerWidth,
            'height' : playerHeight,
        });
        var param  = $C('param',  {
            'name'   : 'movie',
            'value'  : videoSrc,
        });
        var embed  = $C('embed',  {
            'width'  : playerWidth,
            'height' : playerHeight,
            'type'   : 'application/x-shockwave-flash',
            'src'    : videoSrc,
        });

        [param, embed].forEach(function (e) { object.appendChild(e); });
        div.appendChild(object);

        return div;
    }

    function showPlayer (videoId, videoTitle) {
        var playerContainer  = createPlayerContainer(videoId, videoTitle);
        var overlay = $C('div', {
            id     : overlayId,
            style  : {
                'position'        : 'fixed',
                'top'             : '0%',
                'left'            : '0%',
                'width'           : '100%',
                'height'          : '100%',
                'zIndex'          : '99',
                'backgroundImage' : ['url("', getImageUrl('overlay'), '")'].join(''),
            },
            event  : [{
                'name'     : 'click',
                'callback' : function () { hidePlayer(); },
                'flag'     : false,
            }]
        });

        [overlay, playerContainer].forEach(function (e) {
            document.body.appendChild(e);
        });

        centering($(playerContainerId), 0, -10);
    }

    function hidePlayer () {
        [$(playerContainerId), $(overlayId)].forEach(function (e) {
            e.parentNode.removeChild(e);
        });
    }

    function centering (element, x, y) {
        with (element.style) { 
            left = ((window.innerWidth  - element.offsetWidth)  / 2 + (x)) + 'px';
            top  = ((window.innerHeight - element.offsetHeight) / 2 + (y)) + 'px';
        }
    }

    function getImageUrl (name) {
        var images = {
            'close'    : "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%01%03%00%00%00%25%3Dm%22%00%00%00%03sBIT%08%08%08%DB%E1O%E0%00%00%00%06PLTE%FF%FF%FF%FF%FF%FFU%7C%F5l%00%00%00%02tRNS%FF%00%E5%B70J%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00!tEXtSoftware%00Macromedia%20Fireworks%204.0%EA%26'u%00%00%00%16tEXtCreation%20Time%0006%2F02%2F06%3Eg%BF%E4%00%00%00%22IDATx%9Cc%F8%FF%9F%01%8E%3E%9Fg%F8%D8%CF%F0C%9E%E1%8F%3D%08%01%19%40.P%10I%0D%00%8C%AA%1B%19%A3%7D%C5%A0%00%00%00%00IEND%AEB%60%82",
            'download' : "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%01%03%00%00%00%25%3Dm%22%00%00%00%03sBIT%08%08%08%DB%E1O%E0%00%00%00%06PLTE%FF%FF%FF%FF%FF%FFU%7C%F5l%00%00%00%02tRNS%FF%00%E5%B70J%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00!tEXtSoftware%00Macromedia%20Fireworks%204.0%EA%26'u%00%00%00%16tEXtCreation%20Time%0006%2F02%2F06%3Eg%BF%E4%00%00%00'IDATx%9Cc%F8%FF%9F%01%82%FE%D5C%D1%07~%86%1F%F2%0C%7F%EC%19%BE%E53%FC%BE%CF%F0w%3FH%10%A6%0C%00p%88%1A5%81%FD%3F%D0%00%00%00%00IEND%AEB%60%82",
            'arrow'    : "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%01%03%00%00%00%25%3Dm%22%00%00%00%03sBIT%08%08%08%DB%E1O%E0%00%00%00%06PLTE%FF%FF%FF%FF%FF%FFU%7C%F5l%00%00%00%02tRNS%FF%00%E5%B70J%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%D2%DD~%FC%00%00%00!tEXtSoftware%00Macromedia%20Fireworks%204.0%EA%26'u%00%00%00%16tEXtCreation%20Time%0006%2F02%2F06%3Eg%BF%E4%00%00%00%26IDATx%9Cc%F8%FF%9F%01%8E%3E%F0%83%D0%7F~%86%7F%FC%0C%7F%FC%19~%9Cg%F8x%9E%E1%F3y%06%245%00p%20%1A%9E%26%CAE%0A%00%00%00%00IEND%AEB%60%82",
            'overlay'  : "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%01%00%00%00%01%01%03%00%00%00%25%DBV%CA%00%00%00%03sBIT%08%08%08%DB%E1O%E0%00%00%00%06PLTE%FF%FF%FF%00%00%00U%C2%D3~%00%00%00%02tRNS%00%BB*%20%A7%3C%00%00%00%09pHYs%00%00%1B%BC%00%00%1B%BC%01%BA%B7%A0%BB%00%00%00!tEXtSoftware%00Macromedia%20Fireworks%204.0%EA%26'u%00%00%00%0AIDATx%9Cch%00%00%00%82%00%81w%CDr%B6%00%00%00%00IEND%AEB%60%82",
        };

        return images[name];
    }
})();