// Youtube where the video is downloaded, converted to the free and
// open Theora format and then displayed in an HTML5 video element.
// Requires Firefogg for transcoding since youtube only supports the
// proprietary formats.
//
// Portions Copyright (c) 2009, Christopher Blizzard
//
// Based heavily on Youtube without Flash Autio
// http://userscripts.org/scripts/show/50771
//
// Portions Copyright (c) 2009, Arne Schneck, themiddleman
//
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// ==UserScript==
// @name                HTML5 and Theora Youtube
// @namespace	        http://www.0xdeadbeef.com/theoratube/
// @description	        Downloads a youtube video, converts it to Theora and then plays that video using the HTML5 video element.  Requires Firefogg to transcode.
// @include		http://youtube.*/watch*
// @include		http://*.youtube.*/watch*
// ==/UserScript==

// Check to make sure that we have Firefogg installed.
GM_log("Checking for Firefogg installation.");

if (typeof(Firefogg) == 'undefined') {
    GM_log("Not installed.");
    alert("Please install the Firefogg extension from http://firefogg.org");
    return;
}

GM_log("Firefogg installed.");

// Great, Firefogg is installed.

// Check to see what formats we could offer.
var formatsAvailable = new Array(     5,       6,       34,        18,        35,        22);
var formatsAvailableNames = new Array("Low 5", "Low 6", "High 34", "High 18", "High 35", "HD 22");

// http://blog.jimmyr.com/High_Quality_on_Youtube_11_2008.php
// http://digitalramblings-0612.blogspot.com/2008/12/youtube-formats-2-fmt-hack.html

function PreferencesManager() {
    this.preferencesId = "preferences";
    this.parentLocation = document.getElementById('watch-ratings-views');

    this.writePreferences = function() {
        var prefbar = document.createElement("div");
        prefbarHtml = '<div id="' + this.preferencesId + '" style="border:1px solid #000000; width:300px; margin-left:auto; margin-right:auto; padding:3px; margin-top:10px; display:none">'
        + '<h3>Youtube Without Flash Preferences</h3>'
        + '<div title="If the default quality is not available the next best quality video will be played">'
        + 'Default quality'
        + '<select id="preferencesDefaultQuality">';
        for(var i = 0; i < formatsAvailable.length; i++) {
            prefbarHtml += '<option value="' + formatsAvailable[i] + '">' + formatsAvailableNames[i] + '</option>'
        }
        prefbarHtml += '<option value="flash">View Flash</option>'
        + '</select></div>'
        + '<p align="right">'
        + '<a class="link" id="cancelPreferencesLink">Cancel</a> '
        + '<a class="link" id="savePreferencesLink">Save</a>'
        + '</p>'
        + '</div>';
        
        prefbar.innerHTML = prefbarHtml;
        
        this.parentLocation.parentNode.insertBefore(prefbar,this.parentLocation);
        document.getElementById("preferencesDefaultQuality").value = GM_getValue("defaultQuality", "35");

        var savePreferencesLink = document.getElementById('savePreferencesLink');
        savePreferencesLink.addEventListener("click", savePreferences, true);
        var cancelPreferencesLink = document.getElementById('cancelPreferencesLink');
        cancelPreferencesLink.addEventListener("click", cancelPreferences, true);
    };
    
    this.loadPreferences = function() {
        document.getElementById("preferencesDefaultQuality").value = getDefaultQuality();
    };
    
    this.savePreferences = function() {
        this.setDefaultQuality(document.getElementById("preferencesDefaultQuality").value);
    };
    
    this.setPreferencesVisible = function(visible) {
        if(visible) {
            document.getElementById(this.preferencesId).style.display = "";
        }
        else {
            document.getElementById(this.preferencesId).style.display = "none";
        }
    };
    
    this.getDefaultQuality = function() {
        var defaultDefaultQuality;
        if(document.getElementById("preferencesDefaultQuality") == null) {
            defaultDefaultQuality = '35';
        }
        else {
            defaultDefaultQuality = document.getElementById("preferencesDefaultQuality").value;
        }
            
        return GM_getValue("defaultQuality", defaultDefaultQuality);
    };
    
    this.setDefaultQuality = function(quality) {
        GM_setValue("defaultQuality", quality);
    };
    
    // This doesn't work on top where it belongs.
    this.writePreferences();
}

var preferencesManager = new PreferencesManager();

function showPreferences() {
    preferencesManager.setPreferencesVisible(true);
}

function savePreferences() {
    preferencesManager.savePreferences();
    preferencesManager.setPreferencesVisible(false);
}

function cancelPreferences() {
    preferencesManager.setPreferencesVisible(false);
}

//http://diveintogreasemonkey.org/patterns/add-css.html
function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    try {
        style.innerHTML = css;
    } catch(x) { style.innerText = css; }
    head.appendChild(style);
}

// Add css for links.
// This is probably the best way to make text look like links, using the
// normal ways wont work in GM because there is nowhere to put 
// "return false;" in the link.
addGlobalStyle(".link{color:#0033CC;}" + 
        ".link:hover{cursor: pointer; text-decoration:underline;}");

var defaultQuality = preferencesManager.getDefaultQuality();
var playerDiv = document.getElementById("watch-player-div");
var playerDivLoad = playerDiv.innerHTML; // For restoring flash.

// Figure out what formats are actually offered.
var args = unsafeWindow.yt.config_.SWF_ARGS;
var urlmap = decodeURIComponent(args['fmt_url_map']).split(",");
var urlsAvailable = new Array(6);

// This download code is based mostly on this bookmarklet, but seems
// to work very well:
// http://googlesystem.blogspot.com/2008/04/download-youtube-videos-as-mp4-files.html

var video_id = "";
var video_hash = "";

// Extract the hash and the video id
try {
    video_id = args['video_id'];
    video_hash = args['t'];
}
catch(e) {
    return;
}

GM_log("video id is " + video_id);
GM_log("video_hash is " + video_hash);

for(var i = 0; i < urlmap.length; i++) {
    var test = urlmap[i].split("|",2);
    
    for(var j = 0; j < formatsAvailable.length; j++) {
        if(test[0] == formatsAvailable[j]) {
            GM_log("Found format " + test[0] + " at url " + test[1]);
	    urlsAvailable[j] = 'http://www.youtube.com/get_video?fmt=' + test[0] +
		'&video_id=' + video_id +
		'&t=' + video_hash;
	    GM_log("final url is " + urlsAvailable[j]);
        }
    }
}

if(defaultQuality != "flash") {
    // If they don't want flash clear it asap so it doesn't start autoplaying.
    playerDiv.innerHTML = "";
}

// Can tell when we're in wide mode and when we're not.
function inWideMode() {
    if (unsafeWindow._hasclass(unsafeWindow._gel('baseDiv'), 'watch-wide-mode'))
	return true;
    return false;
}

// Function that's called when someone clicks on the widescreen button above the video
function resizePlayer() {

    // If we don't have the flash player, set the size of the div and the player inside
    var playerdiv = document.getElementById('no-flash-player');
    if (!(playerdiv == null)) {
	var width = 640;
	var height = 385;
	if (inWideMode()) {
	    GM_log("wide mode");
	    width = 960;
	    height = 505;
        } else {
	    GM_log("not wide mode");
        }

	playerdiv.style.width = width + "px";
	playerdiv.style.height = height + "px";

	// If we have an actual video element, best fit it
	var vplayer = document.getElementById('the-video-player');
	if (!(vplayer == null)) {
	    vplayer.style.width = width + "px";
	    vplayer.style.height = height + "px";
	}
    }
    else {
	GM_log("player not found");
    }
}

// document.getElementById('player-toggle-switch').addEventListener('click', resizePlayer, false);

function foggCheckProgress() {
    //    GM_log(fogg.state);
    //    GM_log(fogg.progress());

    // See if we can start encoding
    if (fogg.state == "downloaded") {
	GM_log("finished downloading");
	fogg.encode(JSON.stringify({'videoQuality': 10, 'audioQuality': 5}));
    }

    if (fogg.state == "downloading" || fogg.state == "encoding") {
	var el = document.getElementById("progress-text");
	var st = "";
	if (fogg.state == "downloading") {
	    st = "Downloading...";
	}
	else {
	    st = "Encoding...";
	}

	el.innerHTML = st + " " + parseInt(fogg.progress() * 100) + '%';
        setTimeout(foggCheckProgress, 500);
    }
    else if (fogg.state == "encoding done") {
	GM_log("finished encoding");
	var el = document.getElementById("no-flash-player");
	var preview_url = fogg.previewUrl;
	el.innerHTML = '<video id="the-video-player" controls="true"></video>';
	resizePlayer();
	var vplayer = document.getElementById("the-video-player");
	vplayer.addEventListener("loadedmetadata", resizePlayer, false);
	vplayer.src = preview_url;
	vplayer.load();
    }
    else {
	var el = document.getElementById("progress-text");
	el.innerHTML = "Something failed. :(";
    }
}

var fogg = null;

function writePlayer(quality) {
    // XXX stop any existing download + encode process - firefogg makes that kind of hard!

    // Set up a placeholder div until we finish downloading + encoding the video.
    playerDiv.innerHTML = '<div id="no-flash-player" style="color: #FFFFFF; background-color: #000000; border-color: #CCCCCC; border-style: solid; border: 1px; margin-left: auto; margin-right: auto; text-align: center;"><div id="progress-container" style="padding-top: 175px;"><h1 id="progress-text">Starting download...</h1></div></div>';
    resizePlayer();

    // Kick off a download + encode process with firefogg.
    var url = urlsAvailable[formatsAvailable.indexOf(quality)] + '&begin=0';
    GM_log("creating new fogg " + typeof(Firefogg.wrappedJSObject));
    fogg = new unsafeWindow.Firefogg;
    GM_log("starting download of " + url);
    fogg.selectVideoUrl(url);
    setTimeout(foggCheckProgress, 500);
}


function restoreFlash() {
    playerDiv.innerHTML = playerDivLoad;
}

var haveFlash;
var noplayerDiv = document.getElementById("watch-noplayer-div");
if(noplayerDiv == null) {
    haveFlash = true;
} 
else {
    haveFlash = false;
}

var linkbar = document.createElement("div");
var linkViewFlash = "";
var linkViewPreferences = "";
var downloadLinks = "";
var playLinks = "";

for(var i = 0; i < formatsAvailable.length; i++) {
    if(typeof(urlsAvailable[i]) != "undefined") {
        downloadLinks += '| <a href="' + urlsAvailable[i] + '&begin=0">' + 
                formatsAvailableNames[i] + '</a> ';
        
        playLinks += '| <a class="link" id="play' + formatsAvailableNames[i] + '">' + 
                formatsAvailableNames[i] + '</a> ';
    }
}

if(haveFlash) {
    linkViewFlash = ' &diams; <a class="link" id="restoreFlash">View Flash</a>';
}

linkViewPreferences = '<a class="link" id="preferencesLink">Preferences</a>';

linkbar.innerHTML = '<div id="dlbar" style="padding-top: 8px;">'
    + 'Download '
    + downloadLinks
    + ' &diams; View with Theora '
    + playLinks
    + linkViewFlash
    + '<div style="float:right;">' + linkViewPreferences + '</div>'
    + '</div>';

mainarea = document.getElementById('watch-ratings-views');
mainarea.parentNode.insertBefore(linkbar,mainarea);

for(var i = 0; i < formatsAvailable.length; i++) {
    if(typeof(urlsAvailable[i]) != "undefined") {
        var id = i;
        var playLink = document.getElementById('play' + formatsAvailableNames[i]);
        playLink.addEventListener("click", function(event) { writePlayer(formatsAvailable[id]) }, true);
    }
}

if(haveFlash) {
    var restoreFlashLink = document.getElementById('restoreFlash');
    restoreFlashLink.addEventListener("click", restoreFlash, true);
}

var preferencesLink = document.getElementById('preferencesLink');
preferencesLink.addEventListener("click", showPreferences, true);

// Finally, write the player, if the desired format is not available we 
// keep going down in quality until we find one.
if(defaultQuality != "flash") {
    var defaultQualityId = formatsAvailable.indexOf(parseInt(defaultQuality));
    while(typeof(urlsAvailable[defaultQualityId]) == "undefined") {
        defaultQualityId--;
        if(defaultQualityId < 0) {
            break;// Just in case something goes wrong.
        }
        //alert(defaultQualityId);
    }
    writePlayer(parseInt(formatsAvailable[defaultQualityId]));
}

