按:当时尽心尽力了,现在看起来还是图样图森破。
发布到博客预备 TOC 的时候加上的标题
一样开坑。该帖将讲述《内网环境下,FGBT贴内附视频、音乐的黑科技介绍》的原理,有基础的同学可以参照本篇开拓自己的思维……
何谓“基础”?很难讲啊……嗯总之是相关领域基础中的基础就对了。
我会尽量写得详细,并兼顾易懂性。欢迎评论。
(好像操作部分也讲了一些……原理不纯了诶)
分为三个部分:
- 基础篇
- 附件法
- iHome 法
两种方法各自的效果如下。这里仅作为音乐试听器,不过同样也可以作为视频预览工具使用。
附件法(进度条守身如玉)
来源:Pearl White Story——君に届け 2ND SEASON(好想告诉你 第二季)
iHome 法(进度条可以调戏)
来源:風花——はつゆきさくら(初雪樱)
另外(有点相关的)吐槽:
- すま的自谦过度了。
- 这个只是我爆发一个小时的成果的原理部分,请不要给放膝盖,如果放了的话我肯定会被技术帝如lansure、晴朗园界、CFan、工口之主等人,以及各种版主啊不爽的群众啊追杀。为了我的生命安全,请大家多考虑一下……
- 我居然抢到了视频编号233,23333333333333333333333333333333333333333333333333333333333333333333333333
基础篇
内容:
- HTML
- ASP 页面/PHP 页面的参数调用
- 扩展名与文件内容的关系
- 串流与流媒体
- 站点的相对路径
- 流
一、HTML
对网页稍稍有点了解的同学应该知道 HTML(Hypertext Markup Language,超文本标记语言)。网页浏览器识别的内容,大多是以 HTML 为载体的。
一个页面,也就是 HTML 文档的结构,是典型的树状结构。例如:
<html>
<h1>
标题一
</h1>
<body>
<!-- 下面这一行会创建一个链接 -->
<a href="http://www.baidu.com">链接文字</a>
<foo />
</body>
</html>
这些用“<>
”括起来的,就是树的节点(node),这些东西叫做标签(tag)。如果节点中还有内容(HTML 默认内容是文本),就需要以<...>
开头,以</...>
结束,例如上例的<body>...</body>
;否则,在节点内以“/>
”结束即可,如上例的<foo />
。表示一个页面元素(一般是能显示出来的界面元素)的,就简称为元素(element)。
一个节点可以带有属性。属性只属于描述的节点。例如上例节点<a>
有一个属性,名称为“href
”,值为“http://www.baidu.com
”。综合<a>
标签的语法,href
属性即为链接指向的目标。
现实中的例子,可以在任意页面上右键,单击“查看源代码”。
二、ASP 页面/PHP 页面的参数调用
到这里可能有的同学要问了,那么为什么我们会见到 .aspx(ASP .NET 页面文件),就像花园这样呢?略了解的同学会知道,ASP 页面和 HTML 还是有一点差别的。
这是因为,HTML 其实是一个相对静态的语言,所描述的只是一个静态的页面。即使加入了 JavaScript/VBScript,应变能力还是有限。由此人们想出了一种方法,让页面返回一个 HTML 页面。
采用这种方法的典型例子如 ASP/ASP .NET,以及 PHP。当你向服务器发起请求连接到相应的页面时,相应的 IIS/Apache 服务器会动态执行该页面代码,并输出一个 HTML 页面。这样,虽然地址栏里还是 .asp/.aspx/.php,但是读取到的仍然是一个 HTML 页面。说白了就是源页面写了一个 HTML 文件(当然实际上有点区别)。
那么我们就可以将相应的源页面看做是程序,有入口点(entry point),可以有输入参数。这个在普通的程序中很常见,例如 C/C++ 的
int main(int argc, const char* argv[])
C# 的
void Main(string[] args)
等等。那么在这些页面中是怎么传递参数的呢?
是这个样子的:
http://www.somesite.com/somepage.aspx?param1=value1¶m2=value2¶m3=value3
在上面的例子中,我们就向 somepage.aspx 传入了三个参数 param1
、param2
和 param3
,值分别为 value1
、value2
和 value3
。
没错,是在页面后以“?
”作为分隔符表示输入开始,参数之间用“&
”分开。
例如,未来花园的种子搜索页面
http://buaabt.cn/showseeds.aspx?keywords=Hello%20World&state=
传入两个参数,keywords
和 state
,值为“Hello World
”(注意之间的空格,编入 URL 中时要转义)和空值。
或者我们直接搜索种子产生的页面
http://buaabt.cn/showseeds.aspx?keywords=%u8F93%u5165%u641C%u7D22%u5173%u952E%u5B57%3A%u79CD%u5B50%u6807%u9898
输入的内容就是“输入搜索关键字:种子标题
”(原 URL 进行 UNICODE 反转义之后的结果)。
由此也不难理解为什么会出现 URL 转义,以及 Base64 编码——这些都是让复杂的内容得以转化为字符串,在地址栏或者页面中传播。
之后的附件方法会用到该原理,因此在这里列出来。
三、扩展名与文件内容的关系
计算机新手会认为,似乎变了扩展名(extension,也叫“后缀名”),文件就发生了变动一样。其实,文件内容是不变的。那么扩展名是干什么用的呢?
扩展名是用来表示一种约定的,它建议操作系统的壳(shell)采用某种方式来解读该文件。例如,在 Windows 下,我们双击(不只是双击,更多应用请参见其他文档)一个 .txt 文件的时候,Windows 的壳,也就是资源管理器,会到注册表的 HKEY_CLASSES_ROOT
中寻找与 .txt 扩展名相关的信息,并找到“我应该用记事本通过命令调用的方式打开该文件”的结果,于是我们就看到记事本打开了一个文档。如果我们换一个扩展名,操作系统的壳会按照新的约定来解读这个文件。
为什么要提这个呢?那是以为操作帖里有重命名的操作。重命名的时候,千万不要认为文件变了。同时,似乎 Windows Media Player 只通过扩展名来识别文件而不尝试读取文件头,因此可能要非常注意这一点。
四、串流(streaming)与流媒体(stream media)
我们一般在磁盘上看到的文件是十分完整的——游戏、音乐、电影,等等。但是在网络中,需要实时传输的场合,例如视频电话,就需要用到串流技术。
串流技术的原理很简单。可以串流播放的文件就是流媒体文件,其十分简化的结构模型如下:
文件头(可选)-数据包-数据包-数据包-数据包-数据包-数据包-数据包-……-数据包-文件尾(可选)
我们看到听到的信息,如视频和音频,它的信息就包含在数据包里。我们不断收到数据包,本机的解码器不断解码,然后呈现出来,就成了我们所见到的东西。
(说起来简单,如何做数据包的同步,以及协议和帧设计,其实是很麻烦的……)
只要以帧(frame)为基础的文件类型,一般都可以转化为流媒体。常见的有:MP3(MPEG Audio Layer 3)、WMA(Windows Media - Audio,基于 ASF)、WMV(Windows Media - Video,基于 ASF)、RA(RealAudio)、RM(RealVideo)、Flash 视频(Flash Video)、ASF(Advanced Streaming Format)等等。
例如,iHome 上的直播就是采用 ASF 作为载体的。其特点是,并不存在完整的文件,而只有缓冲区(体现为文件或者内存数据块),并随着时间不断更新,直到停止播放时被删除/释放。
为什么要提这个呢?这是因为未来花园支持嵌入流媒体文件链接,这样如果通过某种方式(例如只上协议链接),或许也可以通过这种方式播放大文件(未试验成功)。
友情提示:建议不要嵌入 RealMedia 相关文件(.ra/.rm/.rmvb 等等),因为许多人的计算机上是没有 RealPlayer 插件的,而且有些浏览器对这些插件的控制十分严格。
五、站点的相对路径
有经验的同学应该知道,在多数操作系统中,“.”表示当前路径,“..”表示上一级路径。而且有同学也会注意到,在学习 C 语言文件操作的时候,示例里一般是这么写的:
FILE *fp;
fp = fopen("filename.ext", "r");
...
fclose(fp);
有没有人注意过,为什么我们写的是“filename.ext”,操作系统却能准确地找到我们所指的文件呢?(前提是,小白们将文件放在了正确的地方……见下文。)
先讲一下两个概念:绝对路径与相对路径。绝对路径(absolute path)指的是指定了完整路径的路径(例如 C:\Windows\System32\cmd.exe``,/usr/some_dir/file
)。其他的就是相对路径(relative path)。
那是因为在程序运行时,有当前目录(current directory)的概念。在进行文件操作时,操作系统会检测所输入的路径是绝对路径还是相对路径,如果是绝对路径则直接寻找相应的文件,否则按照一定规则在各个目录中寻找。例如,在 Windows 中,是“当前目录→%SYSTEM%
→%WINDOWS%
→%PATH%
中的各个目录”。(插一句,在运行程序的时候,工作目录(working directory)指的就是将被设为默认当前目录的目录。)
接着前面的例子,按照该规则,操作系统最终找到了我们所指的文件,然后打开/创建。
当然,我们也可以这么指定:
fp = fopen("dir1/dir2/dir3/filename.ext", "r");
这样的含义是,按照之前的规则进行搜索时,要加上子目录 /dir1/dir2/dir3
(注意斜杠的位置)。
网站的链接也采用了这个概念。例如,我们会在一些地方发现这样的链接(这个在之后的页面分析中会有用):
/level1/level2/page.htm
这个含义和刚才讲过的很相似,指的是在当前域名的基础上,加上这个相对路径所得到的最终地址。
以未来花园为例,其域名为 buaabt.cn
。那么我们再指明一个相对路径 /showtopic-374580.aspx
(注意斜杠的位置),那么得到的就是下面的链接:
buaabt.cn/showtopic-374580.aspx
浏览器同样遵循这个规范,于是将这个相对路径映射为最终的地址(若未指明协议,默认为 HTTP)。到我们需要打开那个链接的时候,会打开正确的地址。
六、流(stream)
在这个帖子里3190_iHome进行了实验,雷米莉亚·斯卡雷特(用户,不是大小姐ww)指出了“不要调戏进度条”。
其实进度条能否被调戏,和流的性质是有关系的。
什么是“流”呢?我觉得,将其形象地想象成水流就好了,访问者看上去数据像在数据流入(读)流出(写)。流的实质是缓冲区(buffer),随读写需要,访问者从这块缓冲区读数据,或者往这块缓冲区里写数据。对于可读的流,缓冲区的数据量低于一定值时,会从源处加载数据以尽量“充满”这个流,直到无剩余数据可读;对于可写的流,如果写入的数据超过了缓冲区大小,则将其真正地写入目标(也可以指定即时的操作)。当一个流关闭时,缓冲区将被释放,所含数据被清空,对于可写流,数据将被写入。内部实现可以用缓冲区指针的方式,所以合理用 C 中的文件函数就可以模拟 C++ 中的 istream
和 ostream
,毕竟是差不多的。操作者可以一直调用读取或者写入操作,不用间断(需要相应支持),因此才像连续的水流一样。
流有多种,只需要数据+缓冲区,就可以作为流。例如,本机的文件流、网络的传输文件流、作进程内部传输数据用的字符流,等等。
既然有读写,那么是否还支持这两个特征呢:获取/设置流长度、定位(seek)?答案是,未必。有的流是支持的,有的流不支持,视源的性质与实际编写的代码而定。例如,基于本地文件的流一般是支持定位的,只需要移动文件读写指针就好了嘛。但是网络传输流一般不支持,服务器只负责将数据块连续地传给用户,并不需要实时响应用户的定位操作。(问:那么断点续传怎么实现呢?答:如果被调用方提供了初始位置参数,返回指定初始位置开始的流,那么就可以实现断点续传。)
我们在播放器中拖动进度条,其实就是在请求相应位置开始的流。所以,附件法返回的流不支持定位,也就不能调戏进度条;但是 iHome 法是基于网络文件的(这个文件可是实实在在的),所以这个流支持定位,也就可以调戏进度条啦!
附件法(以 FLV 为例)
请先准备一个 5 MB 以内的 FLV 视频文件。相关的压制技术不在该原理的讨论范围内。
其他大部分格式,因为作者懒,没有测试(除了 WMA 测试未通过)。
将该视频作为附件上传(利用花园的附件功能)。如果要问为什么在选择文件的对话框中找不到的话,那是因为花园系统设置了一个扩展名的过滤器(filter),只显示支持的文件。什么叫“支持的文件”呢?就是那些在花园的环境下经常用到,而且合理合法的文件格式啦。例如 .rar/.zip 是压缩文件,.ssa/.ass/.srt 是字幕文件(多见于电影版和动漫版),都是花园众人常常下载资源之后需要的。
那么有没有办法“蒙骗”花园系统,让其能支持自己的文件呢?有:在基础篇中就讲过,修改文件扩展名,不影响文件内容。而且,当前花园的系统只认扩展名,不检验文件内容是否真的是声称的类型,因此我们可以钻这个空子。
可以选择添加新的扩展名(双扩展名文件,有些防护软件会出现警告)、直接更改扩展名等方法。具体看个人喜好。更改之后就可以上传了。
友情提示:请注意,要真正地修改其扩展名,请在资源管理器的选项中取消选择“隐藏已知类型文件的扩展名”。同时,不要改为图片类的扩展名,因为如果如此,花园系统会尝试加载图片;当然加载会失败,那上传也就失败了。
上传完毕之后,我们可以看到帖子中生成了一个附件代码:
[/attach]645110[/attach]
(为了解除附件引用,我在第一个attach前加了一个“/”,否则会作为代码中的附件出现,囧。实际使用时请去掉这个斜杠。)
直接浏览帖子时看起来像这样:
在保存帖子之后,我们可以看到attach
标签的作用:指导后台在用户请求该页面的时候生成一个指向该附件文件本身的链接。
可以看到,链接的格式非常直观。这个链接的作用是,向域名根目录下的 attachment.aspx
请求指定附件代码(attachmentid
参数的意义)的文件。此时,返回的是一个文件流。一般的浏览器会获取正确的文件名,并显示出来。(这里插一句,有些同学用迅雷之类的软件下载花园的种子,文件名显示为 attachment.aspx
,就是因为迅雷未正确解析花园传来的文件流。在种子下载上放弃迅雷吧孩子们!)
然后我们就可以利用花园的“插入 Flash 文件”功能,或者flash
标签,就像一般的插入播放器地址一样,插入该视频文件。
[flash]/attachment.aspx?attachmentid=645110[/flash]
为什么插入成这个样子,请阅读基础篇的“站点的相对路径”一节。
如果插入为完整路径(buaabt.cn/bt.buaa6.edu.cn
/ipv4.buaabt.cn
开头),会造成部分用户访问异常。例如插入限定为 IPv4 的地址时,使用 IPv6 的用户无法访问该链接,也就无法播放该视频;反之亦然。花园的地址自动转换仅限于url标签。(感谢lansure指出这一点。)
等一下,虽然离成功很近了,但是还差一点!由于某种原因,flash
标签的解析有点问题。如果直接使用上面的地址,会发现播放器无法播放,即使你的附件的确是 FLV 视频文件。此时要给 attachment.aspx
一个新参数 filename
,限定输出的文件名。文件名自定,只要是以 .flv 结尾就可以了。修改之后如下:
[flash]/attachment.aspx?attachmentid=645110&filename=pearl_white_story.flv[/flash]
附件的方法到这里为止,就介绍完毕了。最后,由于我们直接与 attachment.aspx
交互,因此也可以指定别的附件的附件 ID 哦。附件 ID 的获取方法在上面图示里已经出现了。
这里是效果:
[flash=600,20]/attachment.aspx?attachmentid=645110&filename=pearl_white_story.flv[/flash]
iHome 法(以 FLV 为例)
请先准备一个 100 MB 以内的 FLV 视频文件。相关的压制技术不在该原理的讨论范围内。
其他大部分格式,因为作者懒,没有测试(除了 WMA 测试未通过)。
首先,我们需要构造(说得这么高端……就是“找”啦)一个数据源。选择 iHome 提供的视频上传应用并上传一个视频。
在上传之后,在新鲜事中打开刚刚上传的视频,记录下地址,例如本例:http://i.buaa.edu.cn/plugin.php?pluginid=video&ac=view&vid=233
。注意如果想保护个人隐私,请删除自己相关的新鲜事记录。
接下来就到了页面解析的阶段了。我们来查看一下页面的信息。不同浏览器的界面有所不同,不过操作和原理是类似的。这里我们以 Google Chrome 为例讲解。
我们的核心目标是:获取视频的真正地址。之后此处所有的操作都是围绕着这个目标展开的。
我们可以选择直接看源代码(适合老手),或者借用浏览器自带的调试功能——审查元素(适合新手)。前者么,其实看到源代码,一切都了然了,就不用多讲解了。后者适合新手的原因是浏览器会自动整理代码风格、缩进等,比较直观方便,本质上和前者是一样的。
马上看到了这个页面的结构,由许多分块元素(<div>
标签所指示的)所组成。鼠标在其上移动的时候,页面会实时展示该分块代码所对应的页面元素,因此定位非常方便。
想想,哪里会用到视频地址呢?很显然是播放器嘛。对网页里的视频播放器稍微有点了解的同学会知道,这些大多是基于 Flash 技术(少部分是 Silverlight)制作的。知道历史的同学也应该知道 Flash 的发展史,Shockwave→Macromedia→Adobe。因此我们的关键词也确定了,开始寻找有用的信息。
很快我们就能发现,这一段可能有很大的帮助:
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0" width="660px" height="495px">
<param name="movie" value="ihome.swf">
<param name="quality" value="high">
<param name="allowFullScreen" value="true">
<param name="FlashVars" value="vcastr_file=attachment/201409/26/3663_1411697963lE29.flv&LogoText=ihome&TextColor=0x0000FF">
<embed src="ihome.swf" allowfullscreen="true" flashvars="vcastr_file=attachment/201409/26/3663_1411697963lE29.flv&LogoText=ihome&TextColor=0x0000FF" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="660px" height="495px">
</object>
是不是看到什么了?嗯,很好。
可能有过单机程序开发经历的同学会觉得有点奇怪,为什么没有显式的加载文件代码呢?
原来是作为参数传递了过去。这里出现了两次,第一次是在<object>
节点的<param>
子节点处,名称为“FlashVars
”;第二次是在<embed>
元素的 flashvars
属性里。(对于新手,如何解析这个链接,请参见基础篇的“ASP 页面/PHP 页面的参数传递”。)
我们可以见到最重要的一个参数是 vcastr_file
,值是 attachment/201409/26/3663_1411697963lE29.flv
。(“&
”是“&
”字符的转义符,这里位于一个 URL 中,因此会这么出现。其他两个参数猜测是 iHome 播放器的水印参数,不过目前还没看到有什么用途。)
然后参见“站点的相对路径”节,根据当前的域名 i.buaa.edu.cn
,我们就可以拼出视频的完整地址:
http://i.buaa.edu.cn/attachment/201409/26/3663_1411697963lE29.flv
然后嵌入未来花园的 Flash 播放器:
[flash]http://i.buaa.edu.cn/attachment/201409/26/3663_1411697963lE29.flv[/flash]
大功告成。
这里是效果:
[flash=600,20]http://i.buaa.edu.cn/attachment/201409/26/3663_1411697963lE29.flv[/flash]