Skip navigation.

Fat R笔记……与减肥无关

Fat awful terrible Rubbish-bin

Opera: Youtube视频下载(Part 2)

, , , , ,

虽然有了那两个菜单项使得下载更为方便了,但还有更方便的方案——User script:

// ==UserScript==
// @name             Show Youtube flv URLs
// @author           Returner
// @include          http://*youtube.com/*
// ==/UserScript==

var autoLoad = false;   //用这个变量控制页面加载后是否调用loadAll()
vidCol = new Array();
urlCol = new Array();

function getURL(vid,sURL)  //读取并分析视频播放页面,获取flv URL
{
  var req = new XMLHttpRequest();
  if (req) {
    req.onreadystatechange = function() {
      if (req.readyState == 4) {
        if (req.status == 200) {
          if (document.getElementById(vid).innerHTML.indexOf('Retrieving')!=0)
            return; //如果Retrieving的时候Content Box的内容改变了,则不写回获得到的URL
          var res = req.responseText;
          var basei = res.indexOf('/watch_fullscreen?');
          if (basei>0){
            document.getElementById(vid).innerHTML = '<strong>Download flv</strong>';    
            document.getElementById(vid).href='http://www.youtube.com/get_video?' + 
              res.substring(basei+18,res.indexOf('&fs=1&title=', basei));
          }
          else
            alert('No video found');
        }else{
          document.getElementById(vid).innerHTML = 'Failed to get flv URL';
        }
      }
    };
    document.getElementById(vid).innerHTML = 'Retrieving flv URL...';
    req.open('GET', sURL);
    req.send(null);
  }
}

function loadAll()//获取页面中所有视频的flv标签(会造成同时请求多个页面)
{
  for (var i in vidCol)   getURL(vidCol[i],urlCol[i]);  
}

function addl()
{
  var links=document.links;
  var j = 0;  
  var indexHelp = -1;  
  if (!links.length) return;
  for (var i = 0, thisLink; thisLink = links[i]; i++){
    //忽略href=""的链接(一般都是有onclick属性的链接?)
    //忽略有onclick属性的链接
    //处理的URL必须是播放页面URL(http://.../watch?v=...)
    //忽略Play all videos链接
    //忽略图片链接
    if(String(thisLink) != location.href &&
       thisLink.getAttribute('onclick')==null &&
       String(thisLink).indexOf("&feature=PlayList") < 0 &&
       thisLink.innerHTML.indexOf("<IMG") < 0 &&
       String(thisLink).match(/^http\S+\/watch\?v/i)!= null
       ){
      var nextLink = links[i+1];      
      if(nextLink && !String(nextLink.getAttribute('id')).search(/^vid[0-9]+$/)){
        nextLink.outerHTML = nextLink.outerHTML.replace(/vid[0-9]+/g,'vid'+i);
      }else{
        var addHTML = '<br><a ID=vid'+i+' href="javascript:getURL(\''+'vid'+i+
          '\',\''+thisLink+'\')"><strong>Get flv URL</strong></a>';
        thisLink.insertAdjacentHTML("afterEnd",addHTML);//插入链接
      }
      urlCol[j] = thisLink;  //保存以供loadAll()使用
      vidCol[j++] = 'vid'+i;  //保存以供loadAll()使用
    } else if (indexHelp < 0 && String(thisLink).indexOf("/t/help_center") >= 0) {
      indexHelp = i;  //寻找页面顶部的Help链接
    } 
  }
  if(j==0) return;  //if no watch_url is found, exit
  //no need to use 'else' - that would be an extra command
  //and the earlier 'return' removes the need for 'else' here  
  if(indexHelp > 0){  //if the 'Help' link is found
    var addedLink = null;
    if (addedLink = document.getElementById('getAllflv')){
      addedLink.innerHTML = '<b>Retrieve all (' + j + ') flv URL'+(j>1?'s':'')+'</b>';
      //如果已有Retrieve all链接,则更新之
    }else{
      var helpLink = links[indexHelp];
      var strClass = helpLink.getAttribute('class');    
      if (strClass) strClass = 'class="'+strClass+'"';
      else strClass='';
      helpLink.insertAdjacentHTML("afterEnd",
        '<span id="flvSpan" class="utilDelim">|</span><a ' + strClass +
        ' href="javascript:loadAll()" id="getAllflv"><b>Retrieve all (' + j +
        ') flv URL'+(j>1?'s':'')+'</b></a>'
        );//在Help链接后面插入Retrieve all链接
    }
  }
  if(autoLoad)  loadAll();

}

//content box process
var htmlBak = new Array();    
  
function preShiftImage(bar_id){
  var IBrowser = imageBrowsers[bar_id];
  for (var i=0; i<IBrowser.display_array.length; i++)
    htmlBak[i] = document.getElementById("title1_" + IBrowser.root_div_id + "_" + i).innerHTML;
    //备份content box切换图片前的对应标题文字链接
}

function postShiftImage(bar_id){   
  var IBrowser = imageBrowsers[bar_id];  
  for (var i=0; i<IBrowser.display_array.length; i++){    
    var vid = htmlBak[i].match(/ID=\"?vid(\d+)/i)[1];
    if(!vid) continue;
    var newTitle = IBrowser.display_array[i].title1;
    var addHTML = '<br><a ID=vid'+vid+' href="javascript:getURL(\''+'vid'+vid+
      '\',\''+newTitle.match(/\/watch\?v[^\"\']+/i)+
      '\')"><strong>Get flv URL</strong></a>'; //更新vid
    document.getElementById("title1_" + 
      IBrowser.root_div_id + "_" + i).innerHTML = newTitle + addHTML;
      //在标题后重新添加Get flv URL链接
  }
}

function contentBoxProc()
{
  var imgsCB = document.images;
  var strClick;
  if (!imgsCB.length) return;
  for (var i = 0, thisIMG; thisIMG = imgsCB[i]; i++){
    if ((strClick = thisIMG.getAttribute('onclick')) &&
        strClick.indexOf('preShiftImage') == -1 ){        
        var idx = strClick.replace(/(;shift\S+(\(\S+\)))/i,
          ";preShiftImage$2$1;postShiftImage$2");
        thisIMG.setAttribute('onclick',idx); //在shiftImage()前后插入上面两个函数     
    }    
  }
}

//如果移植到Opera 8甚至其它浏览器,应该删掉监听DOMContentLoaded这段代码
  window.opera.addEventListener('AfterEvent.DOMContentLoaded',
    function (e) {
         addl();
         //contentBoxProc();
    },
    false);

  window.addEventListener('load',
    function (e) {      
         addl();
         contentBoxProc();
    },
    false);

  脚本监听Opera 9新增的DOMContentLoaded事件,在页面载入后运行。同时监听Load事件,在页面完全加载(包括图像、Content Box)后再次执行。开头的注释中指定了应用脚本和不应用脚本的页面URL(之前在视频播放页面是不应用脚本的,现在去掉这个限制了)。DOMContentLoaded事件触发后,执行的是addl()函数。该函数扫描页面中的所有link,对符合条件的link(包含/watch?v=,不包含feature=PlayList,不是图像链接),构造一个对应的"Get flv URL"链接,链接的href为
javascript:getURL('vid#',LinkURL)

这个脚本函数调用。其中,每个URL都有一个独立的vid#,#为链接的"序号",LinkURL为找到的链接的原始URL。利用
links[i].insertAdjacentHTML
将链接插入到找到的文本链接(正常情况下,这个链接就是视频的标题)后面。注意,如果用
innerHTML+="</a><br><a href=...>Get flv URL</a>"

这样是不正确的,前面的</a>会被自动去掉。虽然最终显示出来的结果没有问题,但是实质上是<a>标签的嵌套,还是不太爽...

  枚举链接的同时,也会分析链接是不是页面顶部的那个Help链接,如果是,则把它的index记下来(indexHelp)。在处理完所有链接,并至少发现了一个正确的文本链接(j>0),则在Help链接后面添加一个Retrieve all flv URLs链接,href也是一个脚本,执行loadAll()函数。

  loadAll()的功能是自动执行所有视频链接的getURL()函数。如果变量var autoLoad = true,在执行完addl后会自动执行loadAll()。如果没有设置autoLoad为true,可通过两种方式手动执行loadAll():一是在地址栏输入javascript:loadAll(),二是点击Help旁边新添加的那个链接。

  当页面完全加载后,触发Load事件。这次主要是针对DOMContentLoaded触发是还没有完全生成(显示Loading)的Content Box,再次执行addl()进行全页面扫描(嗯,这种做法其实并不高效),然后执行contentBoxProc(),在Contetn Box的左右箭头的onclick脚本里面插入一些东西。

  这样,对addl()来说,就要解决一个问题:如何避免重复插入“Get flv URL”链接。程序采用的解决方法很简单,看看当前处理的链接的下一个链接是否存在vid#这样的ID值。如果不存在,即可放心地去添加链接,如果存在,就更新一下它的vid#(前面说了,#是一个数字,为链接的“序号”,如果这次addl()在前面插入了新的链接,则这个已存在的链接的vid也要做相应修改,以免跟前面插入的新链接的vid重复)。

  由于可能找到更多的flv播放链接,所以也要更新一下Help旁边的Retrieve all链接。程序通过
document.getElementById('getAllflv')

来判断是否已经存在Retrieve all链接。如果存在,则只要重写一下它的innerHTML,如果不存在,则添加(第二次一般都不会执行到“不存在”这种情况,除非Help链接本身不存在,从而第一次就没有把Retrieve all链接插进去)。

  至于preShiftImage()和postShiftImage(),由于点击Content Box的左右箭头,会执行一个shiftImage函数,重写Content Box的内容,因此编写了这两个函数,实现shiftImage后重新添加“Get flv URL”链接。这两个函数用到了youtube本身关于Content Box的函数的一些东西(可参考shiftImage()的实现)。



  页面出现Get flv URL链接后,就可以通过点击这些链接来得到对应视频的下载URL了。当点击页面上的Get flv URL时,执行getURL函数。getURL函数通过vid参数可知道点击的是哪个链接(即链接的ID),sURL参数则是对应的视频播放页面的URL。它获得flv URL的方式与前面的那个菜单命令相同,不过考虑到可能会同时执行多个getURL(同时获取多个视频的URL ),采用了异步执行的方式。函数一开始把链接的文本改写成"Retrieving flv URL..."提示用户等待,等服务器返回200时,则再改成"Download flv",并把链接的href由调用getURL()的脚本改成flv的URL。接下来用户就可以用这个链接来保存flv文件了。如果服务器返回其它值,则文本该为"Failed to get flv URL",不改变href,这样用户可以再次通过点击链接来重新执行getURL()。


在这里下载最新版本的脚本:
youtubeshowurl.js

Opera模仿Maxthon超级拖曳的鼠标手势嗯……亲爱的学校,俺又回来了……

How to use Quote function:

  1. Select some text
  2. Click on the Quote link

Write a comment

Comment
(BBcode and HTML is turned off for anonymous user comments.)

If you can't read the words, press the small reload icon.


Smilies