<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>润物无声 &#187; 程序设计</title>
	<atom:link href="http://blog.zhourunsheng.com/category/%e8%bd%af%e4%bb%b6%e5%bc%80%e5%8f%91/%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhourunsheng.com</link>
	<description>天空一朵雨做的云</description>
	<lastBuildDate>Sat, 08 May 2021 05:17:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.41</generator>
	<item>
		<title>手机安装账户同步服务</title>
		<link>http://blog.zhourunsheng.com/2014/07/%e6%89%8b%e6%9c%ba%e5%ae%89%e8%a3%85%e8%b4%a6%e6%88%b7%e5%90%8c%e6%ad%a5%e6%9c%8d%e5%8a%a1/</link>
		<comments>http://blog.zhourunsheng.com/2014/07/%e6%89%8b%e6%9c%ba%e5%ae%89%e8%a3%85%e8%b4%a6%e6%88%b7%e5%90%8c%e6%ad%a5%e6%9c%8d%e5%8a%a1/#comments</comments>
		<pubDate>Mon, 21 Jul 2014 01:14:14 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[Sony]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1905</guid>
		<description><![CDATA[<p>【前提】 手机需要root过，不然没有权限拷贝apk到 /system/app 目录下面。 【步骤】 通过手机 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/07/%e6%89%8b%e6%9c%ba%e5%ae%89%e8%a3%85%e8%b4%a6%e6%88%b7%e5%90%8c%e6%ad%a5%e6%9c%8d%e5%8a%a1/">手机安装账户同步服务</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<h3>【前提】</h3>
<p>手机需要root过，不然没有权限拷贝apk到 /system/app 目录下面。</p>
<h3>【步骤】</h3>
<p>通过手机的【设置】--【关于手机】 查看当前系统的Android版本，例如我的手机是4.2.2。<br />
在 <a href="http://wiki.rootzwiki.com/Google_Apps">http://wiki.rootzwiki.com/Google_Apps</a> 中下载对应的服务包。<br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/07/recommended_package_android.png" alt="" width="434" height="314" /></p>
<p>里面包含了很多的服务(system\app)app，我们只需要安装服务框架，登录服务，日历同步，通讯录同步，邮件同步即可。</p>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/07/install_apks.png" alt="" /><br />
拷贝到 \system\app 系统目录中。<br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/07/copyapks.png" alt="" /></p>
<p><b>【账户同步】</b><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/07/google_sync.png" alt="" /></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/07/%e6%89%8b%e6%9c%ba%e5%ae%89%e8%a3%85%e8%b4%a6%e6%88%b7%e5%90%8c%e6%ad%a5%e6%9c%8d%e5%8a%a1/">手机安装账户同步服务</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/07/%e6%89%8b%e6%9c%ba%e5%ae%89%e8%a3%85%e8%b4%a6%e6%88%b7%e5%90%8c%e6%ad%a5%e6%9c%8d%e5%8a%a1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu 制作一键安装盘（四）</title>
		<link>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e5%9b%9b%ef%bc%89/</link>
		<comments>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e5%9b%9b%ef%bc%89/#comments</comments>
		<pubDate>Fri, 23 May 2014 02:13:15 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[ISO]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1896</guid>
		<description><![CDATA[<p>本文测试原生的Kubuntu系统，基于原生Ubuntu系统，安装KDE环境后制作安装盘请参照文章《 Ubunt [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e5%9b%9b%ef%bc%89/">Ubuntu 制作一键安装盘（四）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文测试原生的Kubuntu系统，基于原生Ubuntu系统，安装KDE环境后制作安装盘请参照文章《 <a style="color: #69aa35;" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%E5%88%B6%E4%BD%9C%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E7%9B%98%EF%BC%88%E4%B8%89%EF%BC%89/">Ubuntu 制作一键安装盘（三）</a>》。</p>
<p>版本：Kubuntu 12.04<br />
下载地址：<a href="http://www.kubuntu.org/getkubuntu/download-lts">http://www.kubuntu.org/getkubuntu/download-lts</a></p>
<p>【更新源(主要是下载速度的因素)】<br />
deb http://mirrors.163.com/ubuntu/ precise main restricted universe multiverse<br />
deb http://mirrors.163.com/ubuntu/ precise-security main restricted universe multiverse<br />
deb http://mirrors.163.com/ubuntu/ precise-updates main restricted universe multiverse<br />
deb http://mirrors.163.com/ubuntu/ precise-proposed main restricted universe multiverse<br />
deb http://mirrors.163.com/ubuntu/ precise-backports main restricted universe multiverse<br />
deb-src http://mirrors.163.com/ubuntu/ precise main restricted universe multiverse<br />
deb-src http://mirrors.163.com/ubuntu/ precise-security main restricted universe multiverse<br />
deb-src http://mirrors.163.com/ubuntu/ precise-updates main restricted universe multiverse<br />
deb-src http://mirrors.163.com/ubuntu/ precise-proposed main restricted universe multiverse<br />
deb-src http://mirrors.163.com/ubuntu/ precise-backports main restricted universe multiverse</p>
<p><span style="color: #3665ee;"># Remastersys Precise</span><br />
<span style="color: #3665ee;">deb http://www.remastersys.com/ubuntu precise main</span></p>
<p>【添加公钥】<br />
wget -O - http://www.remastersys.com/ubuntu/remastersys.gpg.key | sudo apt-key add -</p>
<p>【安装 remastersys】<br />
sudo apt-get update; sudo apt-get install remastersys</p>
<p>【测试文件 (careytest/test.test)】</p>
<ul>
<li>/home 目录，存放用户相关配置信息</li>
<li>/etc 目录，存放全局配置信息</li>
<li>/usr/bin/ 目录，存放安装文件</li>
<li>/usr/lib/ 目录，存放库文件</li>
<li>/var/spool/ 目录，存放运行相关文件</li>
</ul>
<p>【采用 backup 方式备份全盘资料】<br />
sudo remastersys backup</p>
<p>【ISO 镜像文件测试】</p>
<ul>
<li>live 模式测试，文件全部存在，内容一致</li>
<li>install 安装测试，文件全部都在，内容一致，</li>
</ul>
<blockquote><p><span style="color: #ff0000;">注：在安装过程中自己创建的用户被丢弃</span></p></blockquote>
<p>【ISC* 镜像文件测试】</p>
<ul>
<li><b><span style="color: #41ad1c;">测试通过</span></b></li>
</ul>
<p>【总结】</p>
<ul>
<li>如果是GNOME的主题，采用原生Ubuntu系统</li>
<li>如果是KDE的主题，采用原生KUbuntu系统</li>
</ul>
<p>【参考资料】</p>
<ul>
<li>Creating Custom Kubuntu Using Remastersys:<a href="http://www.youtube.com/watch?v=wAvZr5z6Y-Q">http://www.youtube.com/watch?v=wAvZr5z6Y-Q</a></li>
<li><a href="http://www.psychocats.net/ubuntu/remastersys">http://www.psychocats.net/ubuntu/remastersys</a></li>
<li>《 <a style="color: #69aa35;" href="https://app.yinxiang.com/shard/s1/nl/113357/1ddcc9aa-7154-4d99-ab46-ddfb036586cc">Remaster your system with Remastersys – tobrunet.ch Techblog</a>》</li>
<li>《 <a style="color: #69aa35;" href="https://app.yinxiang.com/shard/s1/nl/113357/39c45340-8a46-41ce-bbb8-52ed84d53775">Free Imaging software - CloneZilla &amp; PartImage - Tutorial</a>》</li>
</ul>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e5%9b%9b%ef%bc%89/">Ubuntu 制作一键安装盘（四）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e5%9b%9b%ef%bc%89/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu 制作一键安装盘（三）</title>
		<link>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%89%ef%bc%89/</link>
		<comments>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%89%ef%bc%89/#comments</comments>
		<pubDate>Fri, 23 May 2014 02:07:06 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[ISO]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1892</guid>
		<description><![CDATA[<p>Ubuntu gnome主题的安装光盘制作请参照文章《Ubuntu 制作一键安装盘（二）》，本文进行KDE桌面 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%89%ef%bc%89/">Ubuntu 制作一键安装盘（三）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>Ubuntu gnome主题的安装光盘制作请参照文章《<a style="color: #69aa35;" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%E5%88%B6%E4%BD%9C%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E7%9B%98%EF%BC%88%E4%BA%8C%EF%BC%89/">Ubuntu 制作一键安装盘（二）</a>》，本文进行KDE桌面环境的安装测试。</p>
<p><b><span style="color: #ff0000;">注意：经过测试 remastersys 不能很好的支持GNOME发行版安装的KDE的桌面环境。</span></b></p>
<p>【安装KDE桌面环境】</p>
<ol>
<li>sudo apt-get install kubuntu-desktop kde-standard</li>
<li>简体中文支持：sudo apt-get install language-pack-kde-zh-hans language-pack-kde-zh-hant</li>
</ol>
<p>【步骤】</p>
<ul>
<li>制作发布安装盘 sudo remastersys dist</li>
<li>live 模式测试, 版本 Ubuntu 12.04 KDE 32位</li>
</ul>
<blockquote><p>结果：进入KDE登录界面，因为custom账户没有密码，无法登录进去系统。</p></blockquote>
<ul>
<li>live 模式测试, 版本 Ubuntu 12.04 KDE 64位</li>
</ul>
<blockquote><p>结果：可以进入GNOME主题系统，但是因为缺少/var/spool/isc* 工作目录，后台测试程序不完整。</p></blockquote>
<p>考虑解决方案(isc* 为测试程序，工作目录在/var/spool/isc*)：<br />
1. 修改 /usr/bin/remastersys 脚本文件，加入自己的处理，例如拷贝必要的文件<br />
<code><br />
log_msg "Copying iscxxx files"<br />
service iscxxx stop &amp;&gt; /dev/null<br />
cp -rf /var/spool/isc* $WORKDIR/dummysys/var/spool/ &amp;&gt; /dev/null<br />
</code></p>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_update_pro.png" alt="" width="1233" height="204" /></p>
<p>2. sudo remastersys dist</p>
<blockquote><p><span style="color: #3665ee;">结果2：可以进入GNOME主题系统，前后台程序运行正常。</span></p></blockquote>
<ul>
<li>install 模式测试，无法进行系统的安装，没有安装过程，直接进入live模式</li>
</ul>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%89%ef%bc%89/">Ubuntu 制作一键安装盘（三）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%89%ef%bc%89/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Ubuntu 制作一键安装盘（二）</title>
		<link>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%ba%8c%ef%bc%89/</link>
		<comments>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%ba%8c%ef%bc%89/#comments</comments>
		<pubDate>Thu, 22 May 2014 02:11:55 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[ISO]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1888</guid>
		<description><![CDATA[<p>本文采用 Remastersys 制作安装盘，使用relinux制作安装盘，请参照上一篇文章 《Ubuntu  [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%ba%8c%ef%bc%89/">Ubuntu 制作一键安装盘（二）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文采用 Remastersys 制作安装盘，使用relinux制作安装盘，请参照上一篇文章 《<a style="color: #69aa35;" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%E5%88%B6%E4%BD%9C%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E7%9B%98%EF%BC%88%E4%B8%80%EF%BC%89/">Ubuntu 制作一键安装盘（一）</a>》。<br />
<span id="more-1888"></span></p>
<p>【步骤】</p>
<ul>
<li>修改源 /etc/apt/sources.list ，加入</li>
</ul>
<p><code># Remastersys Precise<br />
deb http://www.remastersys.com/ubuntu precise main</code></p>
<ul>
<li>安装RemasterSys，sudo apt-get update; sudo apt-get install remastersys，</li>
</ul>
<p><code>如果出现GPG错误，请参照文章最后[错误解决]。<br />
错误解决的另一种方法, Add repository key:<br />
wget -O - http://www.remastersys.com/ubuntu/remastersys.gpg.key | sudo apt-key add -</code></p>
<ul>
<li>进行备份</li>
<li>
<ul>
<li>sudo remastersys dist cdfs</li>
</ul>
</li>
</ul>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_distcdfs.png" alt="" width="1366" height="744" /></p>
<ul>
<li>
<ul>
<li>sudo remastersys dist iso custom.iso，生成 iso文件，大约 1.2GB</li>
</ul>
</li>
</ul>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_distiso.png" alt="" width="1366" height="744" /></p>
<ul>
<li>
<ul>
<li>拷贝出来 sudo cp /home/remastersys/remastersys/custom.iso /media/sf_vmshare/</li>
</ul>
</li>
<li>新建虚拟机进行测试，运行 live 模式，guest账户登录，测试程序可以正常运行，测试通过</li>
</ul>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_ubuntu_live.png" alt="" /></p>
<ul>
<li>系统安装测试，测试通过</li>
</ul>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_ubuntu_install.png" alt="" /><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_ubuntu_run.png" alt="" /></p>
<ul>
<li>可选："sudo remastersys dist" 方式 live 光盘测试,测试通过</li>
</ul>
<p><b><span style="color: #ff0000;">注：</span></b><br />
<b><span style="color: #ff0000;">更简便的方法可以采用 sudo remastersys backup，它可以同时备份用户账号信息。</span></b><br />
<b><span style="color: #ff0000;">采用 sudo remastersys dist，上面两步的合集。</span></b></p>
<p>【使用帮助】<br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_help.png" alt="" /></p>
<p>【参考资料】</p>
<ul>
<li>官网：<a href="http://www.remastersys.com/">http://www.remastersys.com/</a></li>
<li>百科Remastersys: <a href="http://baike.baidu.com/view/3051607.htm">http://baike.baidu.com/view/3051607.htm</a></li>
<li><a href="http://wiki.ubuntu.org.cn/Remastersys">http://wiki.ubuntu.org.cn/Remastersys</a></li>
</ul>
<p>【错误解决】</p>
<p>在安装更新时，运行命令行sudo apt-get update 或者运行更新管理器的时候，出现:</p>
<p>W: GPG 错误：http://ppa.launchpad.net precise Release: 由于没有公钥，无法验证下列签名： NO_PUBKEY <span style="text-decoration: underline;"><span style="color: #7600d8;">EAA903E3A2F4C039</span></span>错误，</p>
<p>解决方法：</p>
<p>在终端中运行：sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys <span style="text-decoration: underline;"><span style="color: #7600d8;">EAA903E3A2F4C039</span></span> 即可。</p>
<p>示例：<br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/remastersys_no_pubkey_error.png" alt="" width="1350" height="245" /></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%ba%8c%ef%bc%89/">Ubuntu 制作一键安装盘（二）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%ba%8c%ef%bc%89/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Ubuntu 制作一键安装盘（一）</title>
		<link>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%80%ef%bc%89/</link>
		<comments>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%80%ef%bc%89/#comments</comments>
		<pubDate>Thu, 22 May 2014 01:50:19 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[ISO]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1884</guid>
		<description><![CDATA[<p>基于 Ubuntu系统开发了一套系统，为了方便装机的便利性，制作一键安装盘。 注意：利用如下方法可以正常做出安 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%80%ef%bc%89/">Ubuntu 制作一键安装盘（一）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>基于 Ubuntu系统开发了一套系统，为了方便装机的便利性，制作一键安装盘。</p>
<p><span style="color: #ff0000;"><b>注意：利用如下方法可以正常做出安装盘，但是安装过程中会出现错误，另外运行live cd的时候也会出现如下low graphic的问题，暂时无解，第一次尝试失败。</b></span><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/system_running_lowgf.png" alt="" /><br />
<span id="more-1884"></span></p>
<p>【步骤】</p>
<ul>
<li>下载软件 relinux，<a href="https://launchpad.net/relinux">https://launchpad.net/relinux</a>，例如 relinux_0.4a1-5_i386.deb</li>
<li>安装 sudo dpkg -i relinux_0.4a1-5_i386.deb</li>
<li>依赖错误修复 sudo apt-get install -f</li>
<li>语言环境（简体中文）设定 echo $LANG; export LANG='zh_CN.UTF-8'; echo $LANG</li>
<li>制作镜像 sudo relinux, 暂时保留默认设定</li>
</ul>
<p><img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/relinux_welcome.png" alt="" /><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/relinux_configuration.png" alt="" /><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/relinux_osweaver.png" alt="" /><br />
<img src="http://blog.zhourunsheng.com/wp-content/uploads/2014/05/relinux_end.png" alt="" /></p>
<ul>
<li>查看 iso 文件，大小 2.0GB</li>
</ul>
<p><code>zhours@zhours-VirtualBox:~/relinux$ ll /home/relinux/<br />
总用量 1923372<br />
drwxr-xr-x  4 root root       4096  5月 12 15:09 ./<br />
drwxr-xr-x  4 root root       4096  5月 12 14:31 ../<br />
-rw-r--r--  1 root root 1969508352  5月 12 15:09 custom.iso<br />
-rw-r--r--  1 root root         47  5月 12 15:11 custom.iso.md5<br />
drwxr-xr-x  6 root root       4096  5月 12 15:05 .ISO_STRUCTURE/<br />
drwxr-xr-x 12 root root       4096  5月 12 14:31 .TMPSYS/<br />
zhours@zhours-VirtualBox:~/relinux$</code><br />
【参考资料】</p>
<ul>
<li>官方：<a href="https://launchpad.net/relinux">https://launchpad.net/relinux</a></li>
<li>视频：<a href="http://www.youtube.com/watch?v=3q6mibDs4Fo">http://www.youtube.com/watch?v=3q6mibDs4Fo</a></li>
<li>制作发布包《 <a style="color: #69aa35;" href="https://app.yinxiang.com/shard/s1/nl/113357/db2011d1-36ab-4135-bfe9-fabbf9bdafe5">Creating Your Own Distributable Ubuntu DVD (Relinux) | HowtoForge - Linux Howtos and Tutorials</a>》</li>
</ul>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%80%ef%bc%89/">Ubuntu 制作一键安装盘（一）</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2014/05/ubuntu-%e5%88%b6%e4%bd%9c%e4%b8%80%e9%94%ae%e5%ae%89%e8%a3%85%e7%9b%98%ef%bc%88%e4%b8%80%ef%bc%89/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>备忘：C与C++之间的互相调用</title>
		<link>http://blog.zhourunsheng.com/2013/09/%e5%a4%87%e5%bf%98%ef%bc%9ac%e4%b8%8ec%e4%b9%8b%e9%97%b4%e7%9a%84%e4%ba%92%e7%9b%b8%e8%b0%83%e7%94%a8/</link>
		<comments>http://blog.zhourunsheng.com/2013/09/%e5%a4%87%e5%bf%98%ef%bc%9ac%e4%b8%8ec%e4%b9%8b%e9%97%b4%e7%9a%84%e4%ba%92%e7%9b%b8%e8%b0%83%e7%94%a8/#comments</comments>
		<pubDate>Fri, 20 Sep 2013 06:03:12 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[C]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1853</guid>
		<description><![CDATA[<p>最新在做Ubuntu上面的C++开发，其中用到了部分C库，那么就涉及到C和C++之间的混合调用，C++调用C语 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/09/%e5%a4%87%e5%bf%98%ef%bc%9ac%e4%b8%8ec%e4%b9%8b%e9%97%b4%e7%9a%84%e4%ba%92%e7%9b%b8%e8%b0%83%e7%94%a8/">备忘：C与C++之间的互相调用</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>最新在做Ubuntu上面的C++开发，其中用到了部分C库，那么就涉及到C和C++之间的混合调用，C++调用C语言比较方便，反之则要稍作变通。</p>
<h2>1. C++ 调用 C 语言</h2>
<p>头文件包含：</p>
<pre>extern "C"
{
#include "iec61850_server.h"
#include "iso_server.h"
#include "acse.h"
#include "thread.h"
#include "model.h"
}</pre>
<p>函数调用：</p>
<pre>IedServer_observeDataAttribute(iedServer, (DataAttribute*)mmsi.dmm, observerCallback);</pre>
<p>回调函数observerCallback的声明和实现如下：</p>
<pre>extern "C"
{
  void observerCallback(DataAttribute* dataAttribute)
  {
    call_Lib_daObserverCallback(mod, dataAttribute);
  }
}</pre>
<h2>2. C 调用 C++ 语言</h2>
<p>在C语言的函数中是无法直接调用C++代码的，如果要调用，需要做一个wrapper，例如call_Lib_daObserverCallback，它的声明和实现如下：</p>
<pre>extern "C" void call_Lib_daObserverCallback(Lib* p, DataAttribute* dataAttribute) // wrapper function
{
   p-&gt;daObserverCallback(dataAttribute);
}</pre>
<p>daObserverCallback 才是我们C++代码的实现：</p>
<pre>void Lib::daObserverCallback(DataAttribute* dataAttribute)
{
  map&lt;string, MMSINFO&gt;::iterator it;
  // ...
}</pre>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/09/%e5%a4%87%e5%bf%98%ef%bc%9ac%e4%b8%8ec%e4%b9%8b%e9%97%b4%e7%9a%84%e4%ba%92%e7%9b%b8%e8%b0%83%e7%94%a8/">备忘：C与C++之间的互相调用</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/09/%e5%a4%87%e5%bf%98%ef%bc%9ac%e4%b8%8ec%e4%b9%8b%e9%97%b4%e7%9a%84%e4%ba%92%e7%9b%b8%e8%b0%83%e7%94%a8/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Python开发之性能优化</title>
		<link>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/#comments</comments>
		<pubDate>Sat, 31 Aug 2013 02:45:21 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1834</guid>
		<description><![CDATA[<p>不论什么语言的开发，都会涉及到下面的几个问题，代码的运行时间，性能出现的瓶颈，内存的占用情况，是否有内存的泄露 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/">Python开发之性能优化</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>不论什么语言的开发，都会涉及到下面的几个问题，代码的运行时间，性能出现的瓶颈，内存的占用情况，是否有内存的泄露等等，同样Python也不例外，本文介绍了几个优秀的Python工具，可以实现如上所述问题的检测机制，通过动态运行跟踪代码的执行情况，从而发现问题点。</p>
<p>While it’s not always the case that every Python program you write will require a rigorous performance analysis, it is reassuring to know that there are a wide variety of tools in Python’s ecosystem that one can turn to when the time arises.</p>
<p>Analyzing a program’s performance boils down to answering 4 basic questions:</p>
<ol>
<li>How fast is it running?</li>
<li>Where are the speed bottlenecks?</li>
<li>How much memory is it using?</li>
<li>Where is memory leaking?</li>
</ol>
<p>Below, we’ll dive into the details of answering these questions using some awesome tools.<span id="more-1834"></span></p>
<h3 id="coarse-grain-timing-with-time">Coarse grain timing with time（代码执行时间）</h3>
<p>Let’s begin by using a quick and dirty method of timing our code: the good old unix utility<em>time</em>.</p>
<pre>$ time python yourprogram.py

real    0m1.028s
user    0m0.001s
sys     0m0.003s</pre>
<p>The meaning between the three output measurements are detailed in this <a href="http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1">stackoverflow article</a>, but in short</p>
<ul>
<li>real - refers to the actual elasped time</li>
<li>user - refers to the amount of cpu time spent outside of kernel</li>
<li>sys - refers to the amount of cpu time spent inside kernel specific functions</li>
</ul>
<p>You can get a sense of how many cpu cycles your program used up regardless of other programs running on the system by adding together the <em>sys</em> and <em>user</em> times.</p>
<p>If the sum of <em>sys</em> and <em>user</em> times is much less than <em>real</em> time, then you can guess that most your program’s performance issues are most likely related to IO waits.</p>
<h3 id="fine-grain-timing-with-a-timing-context-manager">Fine grain timing with a timing context manager</h3>
<p>Our next technique involves direct instrumentation of the code to get access to finer grain timing information. Here’s a small snippet I’ve found invaluable for making ad-hoc timing measurements:</p>
<p><em>timer.py</em></p>
<pre>import time

class Timer(object):
    def __init__(self, verbose=False):
        self.verbose = verbose

    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        self.end = time.time()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000  # millisecs
        if self.verbose:
            print 'elapsed time: %f ms' % self.msecs</pre>
<p>In order to use it, wrap blocks of code that you want to time with Python’s <em>with</em> keyword and this <em>Timer</em> context manager. It will take care of starting the timer when your code block begins execution and stopping the timer when your code block ends.</p>
<p>Here’s an example use of the snippet:</p>
<pre>from timer import Timer
from redis import Redis
rdb = Redis()

with Timer() as t:
    rdb.lpush("foo", "bar")
print "=&gt; elasped lpush: %s s" % t.secs

with Timer as t:
    rdb.lpop("foo")
print "=&gt; elasped lpop: %s s" % t.secs</pre>
<p>I’ll often log the outputs of these timers to a file in order to see how my program’s performance evolves over time.</p>
<h3 id="line-by-line-timing-and-execution-frequency-with-a-profiler">Line-by-line timing and execution frequency with a profiler（代码行运行时间分析）</h3>
<p>Robert Kern has a nice project called <a href="http://packages.python.org/line_profiler/">line_profiler</a> which I often use to see how fast and how often each line of code is running in my scripts.</p>
<p>To use it, you’ll need to install the python package via pip:</p>
<pre>$ pip install line_profiler</pre>
<p>Once installed you’ll have access to a new module called “line_profiler” as well as an executable script “kernprof.py”.</p>
<p>To use this tool, first modify your source code by decorating the function you want to measure with the <em>@profile</em> decorator. Don’t worry, you don’t have to import anyting in order to use this decorator. The <em>kernprof.py</em> script automatically injects it into your script’s runtime during execution.</p>
<p><em>primes.py</em></p>
<pre>@profile
def primes(n): 
    if n==2:
        return [2]
    elif n&lt;2:
        return []
    s=range(3,n+1,2)
    mroot = n ** 0.5
    half=(n+1)/2-1
    i=0
    m=3
    while m &lt;= mroot:
        if s[i]:
            j=(m*m-3)/2
            s[j]=0
            while j&lt;half:
                s[j]=0
                j+=m
        i=i+1
        m=2*i+3
    return [2]+[x for x in s if x]
primes(100)</pre>
<p>Once you’ve gotten your code setup with the <em>@profile</em> decorator, use <em>kernprof.py</em> to run your script.</p>
<pre>$ kernprof.py -l -v fib.py</pre>
<p>The <em>-l</em> option tells kernprof to inject the <em>@profile</em> decorator into your script’s builtins, and <em>-v</em> tells kernprof to display timing information once you’re script finishes. Here’s one the output should look like for the above script:</p>
<pre>Wrote profile results to primes.py.lprof
Timer unit: 1e-06 s

File: primes.py
Function: primes at line 2
Total time: 0.00019 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     2                                           @profile
     3                                           def primes(n): 
     4         1            2      2.0      1.1      if n==2:
     5                                                   return [2]
     6         1            1      1.0      0.5      elif n&lt;2:
     7                                                   return []
     8         1            4      4.0      2.1      s=range(3,n+1,2)
     9         1           10     10.0      5.3      mroot = n ** 0.5
    10         1            2      2.0      1.1      half=(n+1)/2-1
    11         1            1      1.0      0.5      i=0
    12         1            1      1.0      0.5      m=3
    13         5            7      1.4      3.7      while m &lt;= mroot:
    14         4            4      1.0      2.1          if s[i]:
    15         3            4      1.3      2.1              j=(m*m-3)/2
    16         3            4      1.3      2.1              s[j]=0
    17        31           31      1.0     16.3              while j&lt;half:
    18        28           28      1.0     14.7                  s[j]=0
    19        28           29      1.0     15.3                  j+=m
    20         4            4      1.0      2.1          i=i+1
    21         4            4      1.0      2.1          m=2*i+3
    22        50           54      1.1     28.4      return [2]+[x for x in s if x]</pre>
<p>Look for lines with a high amount of hits or a high time interval. These are the areas where optimizations can yield the greatest improvements.</p>
<h3 id="how-much-memory-does-it-use">How much memory does it use?（内存占用）</h3>
<p>Now that we have a good grasp on timing our code, let’s move on to figuring out how much memory our programs are using. Fortunately for us, Fabian Pedregosa has implemented a nice<a href="https://github.com/fabianp/memory_profiler">memory profiler</a> modeled after Robert Kern’s line_profiler.</p>
<p>First install it via pip:</p>
<pre>$ pip install -U memory_profiler
$ pip install psutil</pre>
<p>(Installing the <em>psutil</em> package here is recommended because it greatly improves the performance of the memory_profiler).</p>
<p>Like line_profiler, memory_profiler requires that you decorate your function of interest with an<em>@profile</em> decorator like so:</p>
<pre>@profile
def primes(n): 
    ...
    ...</pre>
<p>To see how much memory your function uses run the following:</p>
<pre>$ python -m memory_profiler primes.py</pre>
<p>You should see output that looks like this once your program exits:</p>
<pre>Filename: primes.py

Line #    Mem usage  Increment   Line Contents
==============================================
     2                           @profile
     3    7.9219 MB  0.0000 MB   def primes(n): 
     4    7.9219 MB  0.0000 MB       if n==2:
     5                                   return [2]
     6    7.9219 MB  0.0000 MB       elif n&lt;2:
     7                                   return []
     8    7.9219 MB  0.0000 MB       s=range(3,n+1,2)
     9    7.9258 MB  0.0039 MB       mroot = n ** 0.5
    10    7.9258 MB  0.0000 MB       half=(n+1)/2-1
    11    7.9258 MB  0.0000 MB       i=0
    12    7.9258 MB  0.0000 MB       m=3
    13    7.9297 MB  0.0039 MB       while m &lt;= mroot:
    14    7.9297 MB  0.0000 MB           if s[i]:
    15    7.9297 MB  0.0000 MB               j=(m*m-3)/2
    16    7.9258 MB -0.0039 MB               s[j]=0
    17    7.9297 MB  0.0039 MB               while j&lt;half:
    18    7.9297 MB  0.0000 MB                   s[j]=0
    19    7.9297 MB  0.0000 MB                   j+=m
    20    7.9297 MB  0.0000 MB           i=i+1
    21    7.9297 MB  0.0000 MB           m=2*i+3
    22    7.9297 MB  0.0000 MB       return [2]+[x for x in s if x]</pre>
<h3 id="wheres-the-memory-leak">Where’s the memory leak?（内存泄露）</h3>
<p>The cPython interpreter uses reference counting as it’s main method of keeping track of memory. This means that every object contains a counter, which is incremented when a reference to the object is stored somewhere, and decremented when a reference to it is deleted. When the counter reaches zero, the cPython interpreter knows that the object is no longer in use so it deletes the object and deallocates the occupied memory.</p>
<p>A memory leak can often occur in your program if references to objects are held even though the object is no longer in use.</p>
<p>The quickest way to find these “memory leaks” is to use an awesome tool called <a href="http://mg.pov.lt/objgraph/">objgraph</a>written by Marius Gedminas. This tool allows you to see the number of objects in memory and also locate all the different places in your code that hold references to these objects.</p>
<p>To get started, first install <em>objgraph</em>:</p>
<pre>pip install objgraph</pre>
<p>Once you have this tool installed, insert into your code a statement to invoke the debugger:</p>
<pre>import pdb; pdb.set_trace()</pre>
<h5 id="which-objects-are-the-most-common">Which objects are the most common?</h5>
<p>At run time, you can inspect the top 20 most prevalent objects in your program by running:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_most_common_types()

MyBigFatObject             20000
tuple                      16938
function                   4310
dict                       2790
wrapper_descriptor         1181
builtin_function_or_method 934
weakref                    764
list                       634
method_descriptor          507
getset_descriptor          451
type                       439</pre>
<h5 id="which-objects-have-been-added-or-deleted">Which objects have been added or deleted?</h5>
<p>We can also see which objects have been added or deleted between two points in time:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_growth()
.
.
.
(pdb) objgraph.show_growth()   # this only shows objects that has been added or deleted since last show_growth() call

traceback                4        +2
KeyboardInterrupt        1        +1
frame                   24        +1
list                   667        +1
tuple                16969        +1</pre>
<h5 id="what-is-referencing-this-leaky-object">What is referencing this leaky object?（资源引用计数）</h5>
<p>Continuing down this route, we can also see where references to any given object is being held. Let’s take as an example the simple program below:</p>
<pre>x = [1]
y = [x, [x], {"a":x}]
import pdb; pdb.set_trace()</pre>
<p>To see what is holding a reference to the variable <em>x</em>, run the <em>objgraph.show_backref()</em>function:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_backref([x], filename="/tmp/backrefs.png")</pre>
<p>The output of that command should be a PNG image stored at <em>/tmp/backrefs.png</em> and it should look something like this:</p>
<p><img alt="back refrences" src="http://www.huyng.com/media/3011/backrefs.png" width="574" height="428" /></p>
<p>The box at the bottom with red lettering is our object of interest. We can see that it’s referenced by the symbol <em>x</em> once and by the list <em>y</em> three times. If <em>x</em> is the object causing a memory leak, we can use this method to see why it’s not automatically being deallocated by tracking down all of its references.</p>
<p>So to review, <a href="http://mg.pov.lt/objgraph/">objgraph</a> allows us to:</p>
<ul>
<li>show the top N objects occupying our python program’s memory</li>
<li>show what objects have been deleted or added over a period of time</li>
<li>show all references to a given object in our script</li>
</ul>
<h3 id="effort-vs-precision">Effort vs precision</h3>
<p>In this post, I’ve shown you how to use several tools to analyze a python program’s performance. Armed with these tools and techniques you should have all the information required to track down most memory leaks as well as identify speed bottlenecks in a Python program.</p>
<p>As with many other topics, running a performance analysis means balancing the tradeoffs between effort and precision. When in doubt, implement the simplest solution that will suit your current needs.</p>
<h5 id="refrences">Refrences</h5>
<ul>
<li><a href="http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1">stack overflow - time explained</a></li>
<li><a href="http://packages.python.org/line_profiler/">line_profiler</a></li>
<li><a href="https://github.com/fabianp/memory_profiler">memory_profiler</a></li>
<li><a href="http://mg.pov.lt/objgraph/">objgraph</a></li>
</ul>
<p>文章参考：<a href="http://www.huyng.com/posts/python-performance-analysis/" target="_blank">http://www.huyng.com/posts/python-performance-analysis/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/">Python开发之性能优化</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu升级源备忘</title>
		<link>http://blog.zhourunsheng.com/2013/08/ubuntu%e5%8d%87%e7%ba%a7%e6%ba%90%e5%a4%87%e5%bf%98/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/ubuntu%e5%8d%87%e7%ba%a7%e6%ba%90%e5%a4%87%e5%bf%98/#comments</comments>
		<pubDate>Fri, 30 Aug 2013 06:29:10 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1826</guid>
		<description><![CDATA[<p>ubuntu的版本支持的时间都有限, 如果过了支持的时间，更新源会被停用，那么只能采取old-releases [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/ubuntu%e5%8d%87%e7%ba%a7%e6%ba%90%e5%a4%87%e5%bf%98/">Ubuntu升级源备忘</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>ubuntu的版本支持的时间都有限, 如果过了支持的时间，更新源会被停用，那么只能采取old-releases源了。</p>
<p>查看当前系统的版本信息</p>
<pre>chs@chs-VirtualBox:~/project/openscada$ sudo lsb_release -a
[sudo] password for chs:
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 11.04
Release: 11.04
Codename: natty</pre>
<p>可以发现当前的系统版本是11.04，代号为natty。</p>
<p>old-releases源信息如下</p>
<pre>deb http://old-releases.ubuntu.com/ubuntu/ natty main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ natty-security main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ natty-updates main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ natty-proposed main restricted universe multiverse
deb http://old-releases.ubuntu.com/ubuntu/ natty-backports main restricted universe multiverse
deb-src http://old-releases.ubuntu.com/ubuntu/ natty main restricted universe multiverse
deb-src http://old-releases.ubuntu.com/ubuntu/ natty-security main restricted universe multiverse
deb-src http://old-releases.ubuntu.com/ubuntu/ natty-updates main restricted universe multiverse
deb-src http://old-releases.ubuntu.com/ubuntu/ natty-proposed main restricted universe multiverse
deb-src http://old-releases.ubuntu.com/ubuntu/ natty-backports main restricted universe multiverse</pre>
<p>修改更新源，替换成上面的源信息</p>
<pre>chs@chs-VirtualBox:~/project/openscada$sudo vim /etc/apt/sources.list</pre>
<p>源更新</p>
<pre>chs@chs-VirtualBox:~/project/openscada$sudo apt-get update</pre>
<p>其他过期版本的old-releases源信息只需替换如上代号（natty）部分即可。</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/ubuntu%e5%8d%87%e7%ba%a7%e6%ba%90%e5%a4%87%e5%bf%98/">Ubuntu升级源备忘</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/ubuntu%e5%8d%87%e7%ba%a7%e6%ba%90%e5%a4%87%e5%bf%98/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu SVN客户端连接windows VisualSVN SSL证书问题</title>
		<link>http://blog.zhourunsheng.com/2013/08/ubuntu-svn%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5windows-visualsvn-ssl%e8%af%81%e4%b9%a6%e9%97%ae%e9%a2%98/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/ubuntu-svn%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5windows-visualsvn-ssl%e8%af%81%e4%b9%a6%e9%97%ae%e9%a2%98/#comments</comments>
		<pubDate>Fri, 30 Aug 2013 02:29:50 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[SVN]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1822</guid>
		<description><![CDATA[<p>问题 英文：svn: OPTIONS of 'https://server.domain.local/svn/ [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/ubuntu-svn%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5windows-visualsvn-ssl%e8%af%81%e4%b9%a6%e9%97%ae%e9%a2%98/">Ubuntu SVN客户端连接windows VisualSVN SSL证书问题</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<h2>问题</h2>
<p>英文：svn: OPTIONS of 'https://server.domain.local/svn/repo': <strong>SSL handshake failed: SSL error:</strong><br />
<strong>Key usage violation in certificate has been detected</strong>. (https://server.domain.local)</p>
<p>中文：SSL handshake failed: SSL 错误：在证书中检测到违规的密钥用法。</p>
<h2>解决方案</h2>
<h2>Symptoms</h2>
<p>Subversion clients receive the following error message when attempting to connect to VisualSVN Server:</p>
<div>svn: OPTIONS of 'https://server.domain.local/svn/repo': SSL handshake failed: SSL error:<br />
Key usage violation in certificate has been detected. (https://server.domain.local)</div>
<p>You may experience the issue if both of the following conditions are met:</p>
<ul>
<li>VisualSVN Server has a self-signed certificate applied and</li>
<li>Subversion client is built against the GnuTLS library.</li>
</ul>
<div id="ctl00_ctl00_ctl00_Content_Content_Content_BreakoutControl2">
<div><img alt="Note" src="http://www.visualsvn.com/images/note.png" /></div>
<div>NoteGnuTLS library is an alternative to OpenSSL. Most Subversion clients for Windows are built against OpenSSL and are not affected by this issue. While some Subversion packages (available mostly on Linux-based operating systems such as Ubuntu and Debian) are built against GnuTLS and are affected.</div>
</div>
<h2>Technical background</h2>
<p>During the initial setup VisualSVN Server 2.5 generates a self-signed certificate and adds it to the Trusted Root Certification Authorities store on the local machine. To avoid possible security issues, VisualSVN Server makes this self-signed certificate to be valid for server authentication only (by specifying the 'Key Usage' extension). Subversion clients built against GnuTLS don't recognize such certificate and the error occurs.</p>
<h2>Workaround</h2>
<p>It's not recommended to use a self-signed certificate in a production environment. We advise to use a certificate issued by your domain or a third-party certificate authority instead of a self-signed one.</p>
<p>If you have to use a self-signed certificate please follow the instruction to generate a cerificate without specifying 'Key Usage' extension:</p>
<ol>
<li>Add the following registry value to the Windows registry（修改注册表，添加一条dword类型的键值）:
<ul>
<li>for 32-bit system:
<pre>[HKEY_LOCAL_MACHINE\SOFTWARE\VisualSVN\VisualSVN Server]
"CreateGnuTLSCompatibleCertificate"=dword:00000001</pre>
</li>
<li>for 64-bit system:
<pre>[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\VisualSVN\VisualSVN Server]
"CreateGnuTLSCompatibleCertificate"=dword:00000001</pre>
</li>
</ul>
</li>
<li>Start <b>VisualSVN Server Manager</b>.</li>
<li>Go to <b>Action | Properties | Certificate</b>.</li>
<li>Click <b>Change certificate...（重新生成新证书）</b> and follow the wizard instructions to generate a new self-signed certificate.</li>
</ol>
<p>The certificate will be generated without the 'Key Usage' extension and will be compatible both with GnuTLS and OpenSSL.</p>
<p>参考资料</p>
<p>1. <a href="http://www.visualsvn.com/support/topic/00056/" target="_blank">http://www.visualsvn.com/support/topic/00056/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/ubuntu-svn%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5windows-visualsvn-ssl%e8%af%81%e4%b9%a6%e9%97%ae%e9%a2%98/">Ubuntu SVN客户端连接windows VisualSVN SSL证书问题</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/ubuntu-svn%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5windows-visualsvn-ssl%e8%af%81%e4%b9%a6%e9%97%ae%e9%a2%98/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>搭建基于Python的视频服务器</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/#comments</comments>
		<pubDate>Sat, 24 Aug 2013 02:57:27 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Video]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1819</guid>
		<description><![CDATA[<p>本文基于Python后台搭建了一个视频服务器，集视频的上传，视频的格式转化，视频的播放于一体，后台基于Djan [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/">搭建基于Python的视频服务器</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文基于Python后台搭建了一个视频服务器，集视频的上传，视频的格式转化，视频的播放于一体，后台基于Django框架和Amazon S3的存储，视频格式的转换基于Encoding.com的在线服务，消息队列基于RabbitMQ，视频上传和转换处理完毕后，采用浏览器的Html5播放，采用了Video.js。</p>
<p>Stickyworld's consultation web app has supported video for a long time but it's been hosted via a YouTube embed. When we started building the new version of the web app we wanted to take control of the video content and also free our users from YouTube's terms of service.<span id="more-1819"></span></p>
<p>I personally had worked on projects with clients in the past which did video transcoding and it was never something easy to achieve. It takes a lot to accept every video, audio and container format under the sun and output them into various video formats that the web knows and loves.</p>
<p>With that in mind we decided the conversion process would be handled by<a href="http://www.encoding.com/r?id=22129">Encoding.com</a>. They let you encode your first Gigabyte of video with them for free and then have a tiered pricing system there after.</p>
<p>Throughout the development of the code below I would upload a two-second, 178KB video to test that everything was working. When the exceptions stopped being raised, I tested larger and more exotic files.</p>
<h2>Stage 1: The User uploads a video</h2>
<p>At the moment the new codebase just has a quick-and-dirty HTML5-based uploading mechanism. This is the CoffeeScript for uploading from the client to the server:</p>
<pre>$scope.upload_slide = (upload_slide_form) -&gt;
    file = document.getElementById("slide_file").files[0]
    reader = new FileReader()
    reader.readAsDataURL file
    reader.onload = (event) -&gt;
      result = event.target.result
      fileName = document.getElementById("slide_file").files[0].name
      $.post "/world/upload_slide",
        data: result
        name: fileName
        room_id: $scope.room.id
        (response_data) -&gt;
          if response_data.success? is not yes
            console.error "There was an error uploading the file", response_data
          else
            console.log "Upload successful", response_data
    reader.onloadstart = -&gt;
      console.log "onloadstart"
    reader.onprogress = (event) -&gt;
      console.log "onprogress", event.total, event.loaded, (event.loaded / event.total) * 100
    reader.onabort = -&gt;
      console.log "onabort"
    reader.onerror = -&gt;
      console.log "onerror"
    reader.onloadend = (event) -&gt;
      console.log "onloadend", event</pre>
<p>It would be nice to loop through <tt>("slide_file").files</tt> and upload each file individually instead of just the first file. This is somewhere we'll be addressing soon.</p>
<h2>Stage 2: Validate and upload to S3</h2>
<p>On the backend we're running Django and RabbitMQ. The key modules we're using are:</p>
<pre>$ pip install 'Django&gt;=1.5.2' 'django-celery&gt;=3.0.21' \
    'django-storages&gt;=1.1.8' 'lxml&gt;=3.2.3' 'python-magic&gt;=0.4.3'</pre>
<p>I created two models: <tt>SlideUploadQueue</tt> to store references to all the uploads and <tt>SlideVideoMedia</tt> to store all the references to the processed videos.</p>
<pre>class SlideUploadQueue(models.Model):
    created_by = models.ForeignKey(User)
    created_time = models.DateTimeField(db_index=True)
    original_file = models.FileField(
        upload_to=filename_sanitiser, blank=True, default='')
    media_type = models.ForeignKey(MediaType)
    encoding_com_tracking_code = models.CharField(
        default='', max_length=24, blank=True)

    STATUS_AWAITING_DATA = 0
    STATUS_AWAITING_PROCESSING = 1
    STATUS_PROCESSING = 2
    STATUS_AWAITING_3RD_PARTY_PROCESSING = 5
    STATUS_FINISHED = 3
    STATUS_FAILED = 4

    STATUS_LIST = (
        (STATUS_AWAITING_DATA, 'Awaiting Data'),
        (STATUS_AWAITING_PROCESSING, 'Awaiting processing'),
        (STATUS_PROCESSING, 'Processing'),
        (STATUS_AWAITING_3RD_PARTY_PROCESSING,
            'Awaiting 3rd-party processing'),
        (STATUS_FINISHED, 'Finished'),
        (STATUS_FAILED, 'Failed'),
    )

    status = models.PositiveSmallIntegerField(
        default=STATUS_AWAITING_DATA, choices=STATUS_LIST)

    class Meta:
        verbose_name = 'Slide'
        verbose_name_plural = 'Slide upload queue'

    def save(self, *args, **kwargs):
        if not self.created_time:
            self.created_time = \
                datetime.utcnow().replace(tzinfo=pytz.utc)

        return super(SlideUploadQueue, self).save(*args, **kwargs)

    def __unicode__(self):
        if self.id is None:
            return 'new &lt;SlideUploadQueue&gt;'
        return '&lt;SlideUploadQueue&gt; %d' % self.id

class SlideVideoMedia(models.Model):
    converted_file = models.FileField(
        upload_to=filename_sanitiser, blank=True, default='')

    FORMAT_MP4 = 0
    FORMAT_WEBM = 1
    FORMAT_OGG = 2
    FORMAT_FL9 = 3
    FORMAT_THUMB = 4

    supported_formats = (
        (FORMAT_MP4, 'MPEG 4'),
        (FORMAT_WEBM, 'WebM'),
        (FORMAT_OGG, 'OGG'),
        (FORMAT_FL9, 'Flash 9 Video'),
        (FORMAT_THUMB, 'Thumbnail'),
    )

    mime_types = (
        (FORMAT_MP4, 'video/mp4'),
        (FORMAT_WEBM, 'video/webm'),
        (FORMAT_OGG, 'video/ogg'),
        (FORMAT_FL9, 'video/mp4'),
        (FORMAT_THUMB, 'image/jpeg'),
    )

    format = models.PositiveSmallIntegerField(
        default=FORMAT_MP4, choices=supported_formats)

    class Meta:
        verbose_name = 'Slide video'
        verbose_name_plural = 'Slide videos'

    def __unicode__(self):
        if self.id is None:
            return 'new &lt;SlideVideoMedia&gt;'
        return '&lt;SlideVideoMedia&gt; %d' % self.id</pre>
<p>Our models use a <tt>filename_sanitiser</tt> method in each <tt>models.FileField</tt> field to automatically adjust filenames into a <tt>&lt;model&gt;/&lt;uuid4&gt;.&lt;extention&gt;</tt>format. This sanitises each filename and makes sure they're unique. To add to that, we used signed URLs that expire so we can control who is served our content and for how long.</p>
<pre>def filename_sanitiser(instance, filename):
    folder = instance.__class__.__name__.lower()
    ext = 'jpg'

    if '.' in filename:
        t_ext = filename.split('.')[-1].strip().lower()

        if t_ext != '':
            ext = t_ext

    return '%s/%s.%s' % (folder, str(uuid.uuid4()), ext)</pre>
<p>A file uploaded as <tt>testing.mov</tt> would turn into <tt>https://our-bucket.s3.amazonaws.com/slideuploadqueue/3fe27193-e87f-4244-9aa2-66409f70ebd3.mov</tt> and would be uploaded by Django Storages.</p>
<p>In our backend endpoint where video is uploaded to from the browser we validate the uploaded content using <a href="https://github.com/ahupp/python-magic">Magic</a>. This detects what kind of file it is based on it's contents:</p>
<pre>@verify_auth_token
@return_json
def upload_slide(request):
    file_data = request.POST.get('data', '')
    file_data = base64.b64decode(file_data.split(';base64,')[1])
    description = magic.from_buffer(file_data)</pre>
<p>So if <tt>description</tt> matches something like <tt>MPEG v4 system</tt> or <tt>Apple QuickTime movie</tt> then we know it's suitable for transcoding. If it isn't something like the above, we can flag it up with the user.</p>
<p>Next, we'll save the video into a our <tt>SlideUploadQueue</tt> model and send a job off to RabbitMQ. Because we're using Django Storages, it'll be uploaded to Amazon S3 automatically.</p>
<pre>slide_upload = SlideUploadQueue()
...
slide_upload.status = SlideUploadQueue.STATUS_AWAITING_PROCESSING
slide_upload.save()
slide_upload.original_file.\
    save('anything.%s' % file_ext, ContentFile(file_data))
slide_upload.save()

task = ConvertRawSlideToSlide()
task.delay(slide_upload)</pre>
<h2>Stage 3: Send the video to a 3rd party</h2>
<p>RabbitMQ will take over and handle the <tt>task.delay(slide_upload)</tt> call.</p>
<p>Here all we're doing is sending <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> a URL of our video and instructions on what output formats we want. They'll give us a job number in return which we'll use to check up on the progress on the transcoding at a later point.</p>
<pre>class ConvertRawSlideToSlide(Task):
    queue = 'backend_convert_raw_slides'
    ...
    def _handle_video(self, slide_upload):
        mp4 = {
            'output': 'mp4',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'mpeg4',
            'profile': 'main',
            'vcodecparameters': 'no',
            'audio_codec': 'libfaac',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'mp4',
            'hint': 'no',
        }

        webm = {
            'output': 'webm',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_sample_rate': '44100',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libvpx',
            'profile': 'baseline',
            'vcodecparameters': 'no',
            'audio_codec': 'libvorbis',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'preset': '6',
            'file_extension': 'webm',
            'acbr': 'no',
        }

        ogg = {
            'output': 'ogg',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_sample_rate': '44100',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libtheora',
            'profile': 'baseline',
            'vcodecparameters': 'no',
            'audio_codec': 'libvorbis',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'ogg',
            'acbr': 'no',
        }

        flv = {
            'output': 'fl9',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libx264',
            'profile': 'high',
            'vcodecparameters': 'no',
            'audio_codec': 'libfaac',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'mp4',
        }

        thumbnail = {
            'output': 'thumbnail',
            'time': '5',
            'video_codec': 'mjpeg',
            'keep_aspect_ratio': 'yes',
            'file_extension': 'jpg',
        }

        encoder = Encoding(settings.ENCODING_API_USER_ID,
            settings.ENCODING_API_USER_KEY)
        resp = encoder.add_media(source=[slide_upload.original_file.url],
            formats=[mp4, webm, ogg, flv, thumbnail])

        media_id = None

        if resp is not None and resp.get('response') is not None:
            media_id = resp.get('response').get('MediaID')

        if media_id is None:
            slide_upload.status = SlideUploadQueue.STATUS_FAILED
            slide_upload.save()
            log.error('Unable to communicate with encoding.com')
            return False

        slide_upload.encoding_com_tracking_code = media_id
        slide_upload.status = \
            SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING
        slide_upload.save()
        return True</pre>
<p><a href="http://www.encoding.com/r?id=22129">Encoding.com</a> recommended some <a href="https://github.com/djworth/python-encoding/blob/master/encoding/__init__.py">less-than-ideal python wrappers</a> for communicating with their service. I added a few fixes into the module but there is still work to be done to get it to a state I'm happy with. Below is what this wrapper currently looks like in our codebase:</p>
<pre>import httplib
from lxml import etree
import urllib
from xml.parsers.expat import ExpatError
import xmltodict

ENCODING_API_URL = 'manage.encoding.com:80'

class Encoding(object):

    def __init__(self, userid, userkey, url=ENCODING_API_URL):
        self.url = url
        self.userid = userid
        self.userkey = userkey

    def get_media_info(self, action='GetMediaInfo', ids=[],
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'mediaid': ','.join(ids),
        }

        query = self._build_tree(etree.Element('query'), nodes)
        results = self._execute_request(query, headers)

        return self._parse_results(results)

    def get_status(self, action='GetStatus', ids=[], extended='no',
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'extended': extended,
            'mediaid': ','.join(ids),
        }

        query = self._build_tree(etree.Element('query'), nodes)
        results = self._execute_request(query, headers)

        return self._parse_results(results)

    def add_media(self, action='AddMedia', source=[], notify='', formats=[],
        instant='no',
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'source': source,
            'notify': notify,
            'instant': instant,
        }

        query = self._build_tree(etree.Element('query'), nodes)

        for format in formats:
            format_node = self._build_tree(etree.Element('format'), format)
            query.append(format_node)

        results = self._execute_request(query, headers)
        return self._parse_results(results)

    def _build_tree(self, node, data):
        for k, v in data.items():
            if isinstance(v, list):
                for item in v:
                    element = etree.Element(k)
                    element.text = item
                    node.append(element)
            else:
                element = etree.Element(k)
                element.text = v
                node.append(element)

        return node

    def _execute_request(self, xml, headers, path='', method='POST'):
        params = urllib.urlencode({'xml': etree.tostring(xml)})

        conn = httplib.HTTPConnection(self.url)
        conn.request(method, path, params, headers)
        response = conn.getresponse()
        data = response.read()
        conn.close()
        return data

    def _parse_results(self, results):
        try:
            return xmltodict.parse(results)
        except ExpatError, e:
            print 'Error parsing encoding.com response'
            print e
            return None</pre>
<p>Left on the todo list include HTTPS-only transmission with strict validation of <a href="http://www.encoding.com/r?id=22129">Encoding.com</a>'s SSL certificate and to write some unit tests (tickets, tickets and more tickets).</p>
<h2>Stage 4: Download all new video formats</h2>
<p>We have a periodic job running every 15 seconds via RabbitMQ which is checking up on the progress of the video transcoding:</p>
<pre>class CheckUpOnThirdParties(PeriodicTask):
    run_every = timedelta(seconds=settings.THIRD_PARTY_CHECK_UP_INTERVAL)
    ...
    def _handle_encoding_com(self, slides):
        format_lookup = {
            'mp4': SlideVideoMedia.FORMAT_MP4,
            'webm': SlideVideoMedia.FORMAT_WEBM,
            'ogg': SlideVideoMedia.FORMAT_OGG,
            'fl9': SlideVideoMedia.FORMAT_FL9,
            'thumbnail': SlideVideoMedia.FORMAT_THUMB,
        }

        encoder = Encoding(settings.ENCODING_API_USER_ID,
            settings.ENCODING_API_USER_KEY)

        job_ids = [item.encoding_com_tracking_code for item in slides]
        resp = encoder.get_status(ids=job_ids)

        if resp is None:
            log.error('Unable to check up on encoding.com')
            return False</pre>
<p>We'll go through the response from <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> validating each piece of the response as we go along:</p>
<pre>if resp.get('response') is None:
    log.error('Unable to get response node from encoding.com')
    return False

resp_id = resp.get('response').get('id')

if resp_id is None:
    log.error('Unable to get media id from encoding.com')
    return False

slide = SlideUploadQueue.objects.filter(
    status=SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING,
    encoding_com_tracking_code=resp_id)

if len(slide) != 1:
    log.error('Unable to find a single record for %s' % resp_id)
    return False

resp_status = resp.get('response').get('status')

if resp_status is None:
    log.error('Unable to get status from encoding.com')
    return False

if resp_status != u'Finished':
    log.debug("%s isn't finished, will check back later" % resp_id)
    return True

formats = resp.get('response').get('format')

if formats is None:
    log.error("No output formats were found. Something's wrong.")
    return False

for format in formats:
    try:
        assert format.get('status') == u'Finished', \
        "%s is not finished. Something's wrong." % format.get('id')

        output = format.get('output')
        assert output in ('mp4', 'webm', 'ogg', 'fl9',
            'thumbnail'), 'Unknown output format %s' % output

        s3_dest = format.get('s3_destination')
        assert 'http://encoding.com.result.s3.amazonaws.com/'\
            in s3_dest, 'Suspicious S3 url: %s' % s3_dest

        https_link = \
            'https://s3.amazonaws.com/encoding.com.result/%s' %\
            s3_dest.split('/')[-1]
        file_ext = https_link.split('.')[-1].strip()

        assert len(file_ext) &gt; 0,\
            'Unable to get file extension from %s' % https_link

        count = SlideVideoMedia.objects.filter(slide_upload=slide,
            format=format_lookup[output]).count()

        if count != 0:
            print 'There is already a %s file for this slide' % output
            continue

        content = self.download_content(https_link)

        assert content is not None,\
            'There is no content for %s' % format.get('id')
    except AssertionError, e:
        log.error('A format did not pass all assertions: %s' % e)
        continue</pre>
<p>At this point we've asserted everything is as it should be a we can save each of the videos:</p>
<pre>media = SlideVideoMedia()
media.format = format_lookup[output]
media.converted_file.save('blah.%s' % file_ext, ContentFile(content))
media.save()</pre>
<h2>Stage 5: Video via HTML5</h2>
<p>On our frontend we've created a page with an HTML5 video element. We're using <a href="http://www.videojs.com/">video.js</a> to display the video in the best-supported format for each browser.</p>
<pre>➫ bower install video.js
bower caching git://github.com/videojs/video.js-component.git
bower cloning git://github.com/videojs/video.js-component.git
bower fetching video.js
bower checking out video.js#v4.0.3
bower copying /home/mark/.bower/cache/video.js/5ab058cd60c5615aa38e8e706cd0f307
bower installing video.js#4.0.3</pre>
<p>In our <tt>index.jade</tt> file we include it's dependencies:</p>
<pre>!!! 5
html(lang="en", class="no-js")
  head
    meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
    ...
    link(rel='stylesheet', type='text/css', href='/components/video-js-4.1.0/video-js.css')
    script(type='text/javascript', src='/components/video-js-4.1.0/video.js')</pre>
<p>In a Angular.js/JADE-based template we've included a <tt>&lt;video&gt;</tt> tag and it's <tt>&lt;source&gt;</tt> children tags. There is also a <tt>poster</tt> element that will show a static image of the video we've transcoded from the first few moments of the video.</p>
<pre>#main.span12
    video#example_video_1.video-js.vjs-default-skin(controls, preload="auto", width="640", height="264", poster="{{video_thumbnail}}", data-setup='{"example_option":true}', ng-show="videos")
        source(ng-repeat="video in videos", src="{{video.src}}", type="{{video.type}}")</pre>
<p>This will print out every video format we've converted into, each as it's own <tt>&lt;source&gt;</tt> tag. <a href="http://www.videojs.com/">Video.js</a> will decide which of them to play based on the browser the user is using.</p>
<p>We still have a lot of work to do around fallback support, building unit tests and improving the robustness of our <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> service wrapper. If this sort of work interests you please do <a href="http://uk.linkedin.com/in/marklitwintschik">get in touch</a>.</p>
<p>文章节选：<a href="http://techblog.stickyworld.com/video-with-python.html" target="_blank">http://techblog.stickyworld.com/video-with-python.html</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/">搭建基于Python的视频服务器</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
