2018年3月30日 星期五

好想去高雄

由於被老婆大人限制人身自由,故不能隨意去想去的地方。

幸好訂購的東西已經來了,不得不說博客來的東西還是不怎麼好,邊邊有壓到的痕跡,只是不知道問題是出在誰那邊就是了,記得以前偶爾買書也是會這樣,所以我後來都去實體書店買。

趕快來安排一下,決定跟我的寶貝書放在一起,只是要放哪一格呢?





2018年3月16日 星期五

跳出當下

昨天下班到現在,一直在 parsing log 的 code 中鬼打牆

原本 code 已經寫好
故意測試一些 error case
想要把 error line 的訊息列印出來

因為 fgets 會將行尾的 \r \n 視為合法字元
如果列印出來 message 就會多斷行一次

於是寫了幾行 code 濾掉 \r \n
這時卻發現程式陷入了無限迴圈

明明只有幾行 code
不應該是 while loop 呀

後來才發現不是在這幾行 code while loop
而是因為將 \r \n 濾掉
導致昨天寫好的 code parsing rule 改變了

寫程式似乎很容易陷入盲點
總會以為 bug 是剛寫的 code 引起的

其實這樣說也沒錯
只是錯誤不一定就發生在那些 code 中

如果能夠跳出當下
將眼光放遠
不要執著在那幾行 code

現在應該已經改到下一階段了吧

這就是人生呀

2018年3月15日 星期四

c-warning: incompatible implicit declaration of built-in : function 'xxxx'

手上有一個專案
Server 和 Client 都是由我負責

由於使用了不同的程式語言
常常切換時腦袋都覺得快要打結了

更別提我自己慣用的 PHP 和 Golang
導致我常常要在這 4 種語法間切來切去

今天又回到改 Client code 的循環,這次要用 C parse log
這時就會覺得還是高階語言好用

個人習慣將相關功能寫到同一個 .c and .h
由於 parse log 是個新添加的功能
按照慣例,還是增加了新的檔案

為了處理 log 已經搞到頭昏腦脹
指標都不知道指到哪去了
偏偏又出現一堆這個 warning
因為想先完成 parse 功能
故先暫時忽略它

等到完成了 parse log 後
因為看不出來 code 那邊有問題
於是 google 了一下
才發現是沒有 include 用到的函式庫的標頭檔

由於是第一次遇到
感覺很新奇
畢竟我很少用函式沒 include

記錄一下

有興趣的可以看看 purpose 網友的文章

2018年3月13日 星期二

Use PHP to download Youtube video

最近很喜歡聽 "方宥心" 唱歌
加了好幾首 "最美的歌" 到喜歡的影片
於是就想試看看如何用程式來下載影片

網路上其實有很多線上服務可以做到
不過 RD 就是要捲起袖子自己來才有 fu

網路上查了一下
大概是利用 get_video_info 這個 api 配合影片 id
先取得影片 url ,接著就可以 download

舊的作法會再取得 'sig' 並串到 url 裡面(&singature=XXX)
但根據我 2018/03/14 的測試
url 裡面其實就有 'signature'
如果沒有,用 get_video_info 也會沒有
此時這個影片可能也不用下載了
因為一定會失敗
(但是線上服務是可以的)

不過這個 api 並不是官方 release 的
故以後還能不能用並不知道
再來也不是每個影片都能下載

步驟大概如下
1. 手動在 Youtube 喜歡的影片頁面,下載該頁面原始檔
2. 程式 parse 原始檔,找到影片的 id 及 title
3. 使用 get_video_info 取得 mp4 的下載 url
4. 使用 curl 下載檔案

github 連結


<?php

function downloadVideo($id, $title, $url, $video)
{
    $fp = fopen($video, "w");
    if (!$fp) {
        echo "$id $title can't create video file \n";
        return;
    }

    $ch = curl_init();

    if ($ch === false) {
        echo "$id $title curl_init error \n";
        fclose($fp);
        return;
    }

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST , false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 600);
    curl_setopt($ch, CURLOPT_HEADER , false);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION , true);
    curl_setopt($ch, CURLOPT_AUTOREFERER , true);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36');
    curl_setopt($ch, CURLOPT_FILE, $fp);
    $con = curl_exec($ch);
    curl_close($ch);

    if ($con === false) {
        echo "$id $title download video error \n";
        fclose($fp);
        return;
    }

    fclose($fp);
}

function processVideo($id, $title, $video)
{
    $api = sprintf("http://www.youtube.com/get_video_info?video_id=%s", $id);

    $res = file_get_contents($api);

    if ($res === '') {
        echo "$id $title res is null \n";
        return;
    }

    $params = [];
    parse_str($res, $params);

    if (!isset($params['url_encoded_fmt_stream_map'])) {
        echo "$id $title url_encoded_fmt_stream_map error \n";
        return;
    }

    $s = $params['url_encoded_fmt_stream_map'];
    $streams = explode(',', $s);

    $realURL = '';
    for ($i = 0; $i < sizeof($streams); $i++) {
        $params = [];
        parse_str($streams[$i], $params);

        if (!isset($params['url']) || !isset($params['type']) || !isset($params['quality'])) {
            echo "$id $title params error \n";
            return;
        }

        $url = urldecode($params['url']);
        $type = urldecode($params['type']);
        $quality = urldecode($params['quality']);

        if (strpos($type, 'video/mp4') == 0) {
            $realURL = $url;
            if ($quality === 'medium') {
                break;
            }
        }
    }

    if ($realURL === '') {
        echo "$id $title has no mp4 type \n";
        return;
    }

    //echo "$title, $realURL\n";

    downloadVideo($id, $title, $realURL, $video);
}

// "youtube.txt" is the file that you use browser to save the page of your favorite videos.
$html = file_get_contents('youtube.txt');

$pattern = '/window\[\"ytInitialData\"\] = ({.*});/';

if (!preg_match($pattern, $html, $matchs)) {
    echo "can't find favorites \n";
    return;
}

$s = $matchs[1];

$json = json_decode($s, true);

$songs = $json['contents']['twoColumnBrowseResultsRenderer']['tabs'][0]
              ['tabRenderer']['content']['sectionListRenderer']['contents'][0]
              ['itemSectionRenderer']['contents'][0]
              ['playlistVideoListRenderer']['contents'];

for ($i = 0; $i < sizeof($songs); $i++) {
    $title = $songs[$i]['playlistVideoRenderer']['title']['simpleText'];
    $title = mb_convert_encoding($title, "big5", "utf-8");
    $title = str_replace([' ', '?'], ['', ''], $title);

    $id = $songs[$i]['playlistVideoRenderer']['videoId'];

    //echo "$id $title \n";

    $video = sprintf("%s\\%s.mp4", __DIR__, $title);

    processVideo($id, $title, $video);

    sleep(10);
}

2018年3月2日 星期五

MongoDB PHP example

最近需要測試一下 MongoDB
雖然我很喜歡 Go 語言
但還是直覺認為用 PHP 會比較方便
 
不過網路上的文章大部份都是舊的 PHP Driver 寫法(class MongoClient)
故花點時間把它整理一下

1. 新的用法有分 Low, High Level,High Level 的 code 比較精簡,但兩個都要裝
2. High Level - https://github.com/mongodb/mongo-php-library
3. Low Level - https://github.com/mongodb/mongo-php-driver
4. 兩者都是官方 release 的(官方也有提供其他語言的 Library)
5. 我使用的版本 PHP 7.0.8, MongoDB 3.6

官方說明

High Level - Query

require 'vendor/autoload.php';

$connectStr = 'mongodb://user:password@localhost:27017/databaseName';

try {
    $client = new MongoDB\Client($connectStr);

    $collection = $client->databaseName->collectionName;
    
    $records = $collection->find([]);

    foreach ($records as $item) {
        echo "_id = $item->_id \n";
    }

} catch (MongoDB\Driver\Exception\ConnectionTimeoutException $e) {
    echo $e->getMessage() . "\n";
}

High Level - Insert

require 'vendor/autoload.php';

$connectStr = 'mongodb://user:password@localhost:27017/databaseName';

try {
    $client = new MongoDB\Client($connectStr);

    $collection = $client->databaseName->collectionName;
    $collection->insertOne(['b' => 'CCC']);

} catch (MongoDB\Driver\Exception\ConnectionTimeoutException $e) {
    echo $e->getMessage() . "\n";
}


2018年3月1日 星期四

Rufus dos 冷知識

Rufus 是一個製作 dos 開機碟的工具。

原本以為 Rufus 是把開機相關檔案包進 .h 檔案裡,後來看到網路上的文章,才知道 Rufus 的 MS-DOS 是從 Windows 裡的檔案解壓縮出來的,﹝C:\Windows\system32\diskcopy.dll﹞。

這也解釋了為什麼在 Windows 10 下無法製作 MS-DOS 開機碟,因為 Windows 10 已經沒有這個檔案了。

src/dos.c

/*
 * Rufus: The Reliable USB Formatting Utility
 * DOS boot file extraction, from the FAT12 floppy image in diskcopy.dll
 * (MS WinME DOS) or from the embedded FreeDOS resource files
 * Copyright © 2011-2017 Pete Batard 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

/* Memory leaks detection - define _CRTDBG_MAP_ALLOC as preprocessor macro */