[
  {
    "path": ".gitignore",
    "content": "/testrec/ \n\n*.flv\n*.mp4\n*.ts\n*.mkv\n*.part\n*.mpg\n*.webm\n\n\n*~\n\n*.exe\n*.dll\n*.exe.config\n\n*.xml\n*.m3u8\n\n*.bin\n*.zip\n*.txt\n*.conf\n\n*.db\n*.pem\n*.ass\n*.sqlite\n*.sqlite3\n*-journal\n*.sqlite3-shm\n*.sqlite3-wal\nNico/*\n!changelog.txt\n*.orig\nlv*\n\n\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.16-alpine as builder\n\nRUN apk add --no-cache \\\n        build-base \\\n        git\n\nCOPY . /tmp/livedl\n\nRUN cd /tmp/livedl/src && \\\n    go build livedl.go\n\n\n\nFROM alpine:3.8 \n\nRUN apk add --no-cache \\\n        ca-certificates \\\n        ffmpeg \\\n        openssl\n\nCOPY --from=builder /tmp/livedl/src/livedl /usr/local/bin/\n\nWORKDIR /livedl\n\nVOLUME /livedl\n\nENTRYPOINT [ \"livedl\", \"--no-chdir\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 himananiito\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Readme.md",
    "content": "# livedl\r\n新配信(HTML5)に対応したニコ生録画ツール。ニコ生以外のサイトにも対応予定\r\n\r\n## 使い方\r\nhttps://himananiito.hatenablog.jp/entry/livedl\r\nを参照\r\n\r\n## Windowsでのビルド(exeを作成するためにDockerを利用)\r\n### Step1\r\n`docker-compose` が実行できるようにDocker Desktop for Windowsをインストールする。\r\n\r\n### Step2\r\nターミナルで\r\n```\r\nbuild\\windows\r\n```\r\nに移動する。\r\n\r\n### Step3\r\n```\r\ndocker-compose up --build\r\n```\r\nを実行するとプロジェクトのトップディレクトリに `livedl.exe` が作成される。\r\n\r\n## Linux(Ubuntu)でのビルド方法\r\n```\r\ncat /etc/os-release\r\nNAME=\"Ubuntu\"\r\nVERSION=\"16.04.2 LTS (Xenial Xerus)\"\r\n```\r\n\r\n### Go実行環境のインストール　（無い場合）\r\n```\r\nhttps://golang.org/doc/install\r\nに従う\r\n```\r\n\r\n### gitをインストール　（無い場合）\r\n```\r\nsudo apt-get install git\r\n```\r\n\r\n### gccなどのビルドツールをインストール　（無い場合）\r\n```\r\nsudo apt-get install build-essential\r\n```\r\n\r\n### livedlのソースを取得\r\n```\r\ngit clone https://github.com/himananiito/livedl.git\r\n```\r\n\r\n### livedlのコンパイル\r\n\r\nディレクトリを移動\r\n```\r\ncd livedl\r\n```\r\n\r\n#### (オプション)最新のコードをビルドする場合\r\n```\r\ngit checkout master\r\n```\r\n\r\nビルドする\r\n```\r\ngo build src/livedl.go\r\n```\r\n\r\n```\r\n./livedl -h\r\nlivedl (20180807.22-linux)\r\n```\r\n\r\n## Windows(32bit及び64bit上での32bit向け)コンパイル方法\r\n\r\n### gccのインストール\r\n\r\ngcc には必ず以下を使用すること。\r\n\r\nhttp://tdm-gcc.tdragon.net/download\r\n\r\n環境変数で（例）`C:\\TDM-GCC-64\\bin`が他のgccより優先されるように設定すること。\r\n\r\n### 必要なgoのモジュール\r\n\r\nlinuxの説明に倣ってインストールする。\r\n\r\n### コンパイル\r\n\r\nPowerSellで、`build-386.ps1` を実行する。または以下を実行する。\r\n\r\n```\r\nset-item env:GOARCH -value 386\r\nset-item env:CGO_ENABLED -value 1\r\ngo build -o livedl.x86.exe src/livedl.go\r\n```\r\n\r\n### 32bit環境で`x509: certificate signed by unknown authority`が出る\r\n\r\n動けばいいのであればオプションで以下を指定する。\r\n\r\n`-http-skip-verify=on`\r\n\r\n## コンテナで実行\r\n\r\n### livedlのソースを取得\r\n```\r\ngit clone https://github.com/himananiito/livedl.git\r\ncd livedl\r\ngit checkout master # Or another version that supports docker (contains Dockerfile)\r\n```\r\n\r\n### イメージ作成\r\n```\r\ndocker build -t livedl .\r\n```\r\n\r\n### イメージの使い方\r\n\r\n- 出力フォルダを/livedlにマウント\r\n\r\n```\r\ndocker run --rm -it -v \"$(pwd):/livedl\" livedl \"https://live.nicovideo.jp/watch/...\"\r\n```\r\n\r\n以上\r\n"
  },
  {
    "path": "build/windows/Dockerfile",
    "content": "FROM golang:1.16-alpine\r\n\r\nRUN apk add mingw-w64-gcc\r\n\r\nCOPY ./src/ /livedl/src/\r\n\r\nRUN \\\r\n    cd /livedl/src/ && \\\r\n    GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o livedl.exe livedl.go\r\n\r\nCMD cp /livedl/src/livedl.exe /mnt/\r\n"
  },
  {
    "path": "build/windows/docker-compose.yml",
    "content": "version: '3'\r\nservices:\r\n  livedl-win:\r\n    build:\r\n      context: ../..\r\n      dockerfile: ./build/windows/Dockerfile\r\n    volumes:\r\n        - ../..:/mnt"
  },
  {
    "path": "build-386.ps1",
    "content": "\nset-item env:GOARCH -value 386\nset-item env:CGO_ENABLED -value 1\n\ngo build -o livedl.x86.exe src/livedl.go\n"
  },
  {
    "path": "build.ps1",
    "content": "rm livedl.exe\r\ngo run updatebuildno.go\r\ngo build src/livedl.go\r\n.\\build-386.ps1\r\ngo build livedl-logger.go\r\n\r\n# hide local path\r\nperl replacelocal.pl\r\n\r\n# Generate Readme.txt\r\nperl readme-gen.pl\r\n\r\n# livedl test run(nico)\r\n$process = Start-Process -FilePath livedl.exe -ArgumentList '-nicotestrun -nicotesttimeout 7 -nicotestfmt \"testrec/?UNAME?/?PID?-?UNAME?-?TITLE?\"' -PassThru\r\n$process.WaitForExit(1000 * 61)\r\n$process.Kill()\r\n\r\n$process = Start-Process -FilePath livedl.x86.exe -ArgumentList '-nicotestrun -nicotesttimeout 7 -nicotestfmt \"testrec/?UNAME?/?PID?-?UNAME?-?TITLE?\"' -PassThru\r\n$process.WaitForExit(1000 * 30)\r\n$process.Kill()\r\n\r\n$dir = \"livedl\"\r\n$zip = \"$dir.zip\"\r\nif(Test-Path -PathType Leaf $zip) {\r\n\trm $zip\r\n}\r\nif(Test-Path -PathType Container $dir) {\r\n\trmdir -Recurse $dir\r\n}\r\nmkdir $dir\r\ncp livedl.exe $dir\r\ncp livedl.x86.exe $dir\r\ncp livedl-logger.exe $dir\r\ncp Readme.txt $dir\r\n\r\ncp livedl-gui.exe $dir\r\ncp livedl-gui.exe.config $dir\r\ncp Newtonsoft.Json.dll $dir\r\ncp Newtonsoft.Json.xml $dir\r\n\r\nCompress-Archive -Path $dir -DestinationPath $zip\r\n\r\nif(Test-Path -PathType Container $dir) {\r\n\trmdir -Recurse $dir\r\n}\r\n\r\n"
  },
  {
    "path": "changelog.txt",
    "content": "﻿更新履歴\r\n\r\n20181215.35\r\n・-nico-ts-start-minオプションの追加\r\n・win32bit版のビルドを追加\r\n・-http-skip-verifyオプションを保存できるようにした\r\n・ライセンスをMITにした\r\n\r\n20181107.34\r\n・[ニコ生] (暫定)TEMPORARILY_CROWDEDで録画終了するようにした\r\n・ファイル名が半角ドットで終わる場合に全角ドットにした\r\n・[YouTubeLive] コメントの改行をCRLFにした\r\n・[ニコ生TS] タイムシフトの録画を指定した再生時間(秒)から開始するオプション追加(merged)\r\n・[ニコ生TS] 32bitで終了しない問題を修正(merged)\r\n\r\n20181008.33\r\n・[Youtube] チャットが取得できない問題を修正\r\n・[Youtube] Streamlinkでダウンロードできない場合にyoutube-dlを使うようにした\r\n・[Youtube] コメントファイルを書き出せるようにした。\r\n・#15 [ニコ生コメント] 出力をCRLFにした。/hbコマンドを出さないオプションを追加\r\n\r\n20181003.32\r\n・#14 ★緊急 [ニコ生] 新配信録画のプレイリスト取得にウェイトが入らない問題を修正\r\n・#9 [ニコ生TS] プレイリストの最後で無限ループしてしまう問題を修正\r\n・YoutubeLiveコメント対応中(未完了)\r\n・[実験的] -yt-api-key オプションの追加（未使用）\r\n\r\n20180925.31\r\n・#8 [ツイキャス] 「c:」から始まるユーザ名が録画できない問題を修正\r\n・#11 [ツイキャス] 実行直後またはリトライ中にエラーで終了する問題を修正\r\n・#10 [ツイキャス] -tcas-retry-intervalが効かない問題を修正\r\n・#12 [ニコ生] タイムシフトで先頭のセグメント(seqno=0)が取得できない問題を修正\r\n"
  },
  {
    "path": "livedl-logger.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"bufio\"\r\n\t\"regexp\"\r\n\t\"sync\"\r\n\t\"os\"\r\n\t\"os/exec\"\r\n)\r\n\r\nfunc main() {\r\n\targs := os.Args[1:]\r\n\tvar vid string\r\n\tfor _, s := range args {\r\n\t\tif ma := regexp.MustCompile(`(lv\\d{9,})`).FindStringSubmatch(s); len(ma) > 0 {\r\n\t\t\tvid = ma[1]\r\n\t\t}\r\n\t}\r\n\r\n\targs = append(args, \"-nicoDebug\")\r\n\tcmd := exec.Command(\"livedl\", args...)\r\n\r\n\tif vid == \"\" {\r\n\t\tcmd.Stdout = os.Stdout\r\n\t\tcmd.Stderr = os.Stderr\r\n\t\tcmd.Run()\r\n\t} else {\r\n\t\tstdout, err := cmd.StdoutPipe()\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tstderr, err := cmd.StderrPipe()\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tname := fmt.Sprintf(\"log/%s.txt\", vid)\r\n\t\tos.MkdirAll(\"log\", os.ModePerm)\r\n\t\tf, err := os.Create(name)\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer f.Close()\r\n\r\n\t\tvar mtx sync.Mutex\r\n\t\tappend := func(s string) {\r\n\t\t\tmtx.Lock()\r\n\t\t\tdefer mtx.Unlock()\r\n\t\t\tf.WriteString(s)\r\n\t\t}\r\n\r\n\t\tgo func() {\r\n\t\t\trdr := bufio.NewReader(stdout)\r\n\t\t\tfor {\r\n\t\t\t\ts, err := rdr.ReadString('\\n')\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tfmt.Print(s)\r\n\t\t\t\tappend(s)\r\n\t\t\t}\r\n\t\t\tdefer stdout.Close()\r\n\t\t}()\r\n\t\tgo func() {\r\n\t\t\trdr := bufio.NewReader(stderr)\r\n\t\t\tfor {\r\n\t\t\t\ts, err := rdr.ReadString('\\n')\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tappend(s)\r\n\t\t\t}\r\n\t\t\tdefer stderr.Close()\r\n\t\t}()\r\n\t\tcmd.Run()\r\n\t}\r\n}\r\n"
  },
  {
    "path": "readme-gen.pl",
    "content": "use strict;\r\nuse warnings;\r\nuse v5.20;\r\n\r\nopen my $f, \"-|\", \"livedl\", \"-h\" or die;\r\nundef $/;\r\nmy $s = <$f>;\r\nclose $f;\r\n\r\n$s =~ s{livedl\\s*\\((\\d+\\.\\d+)[^\\r\\n]*}{livedl ($1)} or die;\r\nmy $ver = $1;\r\n\r\n$s =~ s{chdir:[^\\n]*\\n}{};\r\n\r\nopen my $g, \"changelog.txt\" or die;\r\nmy $t = <$g>;\r\nclose $g;\r\n\r\n$t =~ s{\\$latest}{$ver} or die;\r\n\r\nopen my $h, \">\", \"changelog.txt\" or die;\r\nprint $h $t;\r\nclose $h;\r\n\r\nopen my $o, \">\", \"Readme.txt\" or die;\r\nsay $o $s;\r\nsay $o \"\";\r\nsay $o $t;\r\nclose $o;\r\n"
  },
  {
    "path": "replacelocal.pl",
    "content": "# perl\r\n# livedl.exe内のローカルパスの文字列を隠す\r\nuse strict;\r\nuse v5.20;\r\n\r\nfor my $file(\"livedl.exe\", \"livedl.x86.exe\", \"livedl-logger.exe\") {\r\n\topen my $f, \"<:raw\", $file or die;\r\n\tundef $/;\r\n\tmy $s = <$f>;\r\n\tclose $f;\r\n\r\n\tsay \"$0: $file\";\r\n\r\n\tmy %h = ();\r\n\r\n\twhile($s =~ m{(?<=\\0)[^\\0]{5,512}\\.go(?=\\0)|(?<=[[:cntrl:]])_/[A-Z]_/[^\\0]{5,512}}g) {\r\n\t\tmy $s = $&;\r\n\t\tif($s =~ m{\\A(.*(?:/Users/.+?/go/src|/Go/src))(/.*)\\z}s or\r\n\t\t$s =~ m{\\A(.*(?=/livedl/src/))(/.*)\\z}s) {\r\n\t\t\tmy($all, $p, $f) = ($s, $1, $2);\r\n\r\n\t\t\tmy $p2 = $p;\r\n\t\t\t$p2 =~ s{.}{*}gs;\r\n\t\t\t#$h{$all} = $p2 . $f;\r\n\r\n\t\t\t#say $p;\r\n\t\t\t$h{$p} = $p2;\r\n\t\t}\r\n\t}\r\n\r\n\tfor my $k (sort{$a cmp $b} keys %h) {\r\n\t\tmy $k2 = $k;\r\n\t\t$k2 =~ s{/}{\\\\}g;\r\n\r\n\t\tmy $r = quotemeta $k;\r\n\t\tmy $r2 = quotemeta $k2;\r\n\r\n\t\tsay \"$k => $h{$k}\";\r\n\r\n\t\t$s =~ s{$r}{$h{$k}}g;\r\n\t\t$s =~ s{$r2}{$h{$k}}g;\r\n\t}\r\n\r\n\topen $f, \">:raw\", $file or die;\r\n\tprint $f $s;\r\n\tclose $f;\r\n\r\n\tsleep 1;\r\n}\r\n"
  },
  {
    "path": "src/amf/amf.go",
    "content": "package amf\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"io\"\r\n\r\n\t\"github.com/himananiito/livedl/amf/amf0\"\r\n\t\"github.com/himananiito/livedl/amf/amf_t\"\r\n)\r\n\r\nfunc SwitchToAmf3() amf_t.SwitchToAmf3 {\r\n\treturn amf_t.SwitchToAmf3{}\r\n}\r\n\r\nfunc EncodeAmf0(data []interface{}, asEcmaArray bool) ([]byte, error) {\r\n\treturn amf0.Encode(data, asEcmaArray)\r\n}\r\n\r\nfunc Amf0EcmaArray(data map[string]interface{}) amf_t.AMF0EcmaArray {\r\n\treturn amf_t.AMF0EcmaArray{\r\n\t\tData: data,\r\n\t}\r\n}\r\n\r\n// paddingHint: zero padded before AMF data\r\nfunc DecodeAmf0(data []byte, paddingHint ...bool) (res []interface{}, err error) {\r\n\trdr := bytes.NewReader(data)\r\n\tvar seek1 bool\r\n\tfor _, h := range paddingHint {\r\n\t\tif h {\r\n\t\t\tseek1 = true\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\tif seek1 {\r\n\t\trdr.Seek(1, io.SeekStart)\r\n\t}\r\n\tres, err = amf0.DecodeAll(rdr)\r\n\tif err != nil {\r\n\t\tif seek1 {\r\n\t\t\t// retry\r\n\t\t\trdr.Seek(0, io.SeekStart)\r\n\t\t\tres, err = amf0.DecodeAll(rdr)\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/amf/amf0/amf0.go",
    "content": "package amf0\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"encoding/binary\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"log\"\r\n\t\"math\"\r\n\r\n\t\"github.com/himananiito/livedl/amf/amf3\"\r\n\t\"github.com/himananiito/livedl/amf/amf_t\"\r\n)\r\n\r\nfunc encodeNumber(num float64, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(0); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tbits := math.Float64bits(num)\r\n\tbytes := make([]byte, 8)\r\n\tbinary.BigEndian.PutUint64(bytes, bits)\r\n\tif _, err = buff.Write(bytes); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc encodeBoolean(b bool, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(1); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tvar val byte\r\n\tif b {\r\n\t\tval = 1\r\n\t}\r\n\r\n\tif err = buff.WriteByte(val); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc encodeUtf8(s string, buff *bytes.Buffer) (err error) {\r\n\tbs := []byte(s)\r\n\tif len(bs) > 0xffff {\r\n\t\terr = fmt.Errorf(\"string too large\")\r\n\t\treturn\r\n\t}\r\n\r\n\tb0 := make([]byte, 2)\r\n\tbinary.BigEndian.PutUint16(b0, uint16(len(bs)))\r\n\tif _, err = buff.Write(b0); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif _, err = buff.Write(bs); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc encodeString(s string, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(2); err != nil {\r\n\t\treturn\r\n\t}\r\n\terr = encodeUtf8(s, buff)\r\n\treturn\r\n}\r\nfunc encodeObject(obj map[string]interface{}, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(3); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tfor k, v := range obj {\r\n\t\tif err = encodeUtf8(k, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif _, err = encode(v, false, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tif _, err = buff.Write([]byte{0, 0, 9}); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc encodeNull(buff *bytes.Buffer) error {\r\n\treturn buff.WriteByte(5)\r\n}\r\nfunc encodeSwitchToAmf3(buff *bytes.Buffer) error {\r\n\treturn buff.WriteByte(0x11)\r\n}\r\nfunc encodeEcmaArray(data map[string]interface{}, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(8); err != nil {\r\n\t\treturn\r\n\t}\r\n\tbuf4 := make([]byte, 4)\r\n\tbinary.BigEndian.PutUint32(buf4, uint32(len(data)))\r\n\tif _, err = buff.Write(buf4); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tfor k, v := range data {\r\n\t\tif err = encodeUtf8(k, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif _, err = encode(v, true, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tif _, err = buff.Write([]byte{0, 0, 9}); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc encode(data interface{}, asEcmaArray bool, buff *bytes.Buffer) (toAmf3 bool, err error) {\r\n\tswitch data.(type) {\r\n\tcase string:\r\n\t\terr = encodeString(data.(string), buff)\r\n\tcase float64:\r\n\t\terr = encodeNumber(data.(float64), buff)\r\n\tcase int:\r\n\t\terr = encodeNumber(float64(data.(int)), buff)\r\n\tcase bool:\r\n\t\terr = encodeBoolean(data.(bool), buff)\r\n\tcase map[string]interface{}:\r\n\t\tif asEcmaArray {\r\n\t\t\terr = encodeEcmaArray(data.(map[string]interface{}), buff)\r\n\t\t} else {\r\n\t\t\terr = encodeObject(data.(map[string]interface{}), buff)\r\n\t\t}\r\n\tcase []interface{}:\r\n\t\tm := make(map[string]interface{})\r\n\t\tfor i, d := range data.([]interface{}) {\r\n\t\t\tk := fmt.Sprintf(\"%d\", i)\r\n\t\t\tm[k] = d\r\n\t\t}\r\n\t\terr = encodeEcmaArray(m, buff)\r\n\tcase nil:\r\n\t\terr = encodeNull(buff)\r\n\tcase amf_t.SwitchToAmf3:\r\n\t\ttoAmf3 = true\r\n\t\terr = encodeSwitchToAmf3(buff)\r\n\tdefault:\r\n\t\tlog.Fatalf(\"amf0/encode %#v\", data)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc Encode(data []interface{}, asEcmaArray bool) (b []byte, err error) {\r\n\tbuff := bytes.NewBuffer(nil)\r\n\tfor i, d := range data {\r\n\t\tvar toAmf3 bool\r\n\t\tif toAmf3, err = encode(d, asEcmaArray, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif toAmf3 {\r\n\t\t\tb2, e := amf3.Encode(data[i+1:])\r\n\t\t\tif e != nil {\r\n\t\t\t\terr = e\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tb = append(b, buff.Bytes()...)\r\n\t\t\tb = append(b, b2...)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tb = buff.Bytes()\r\n\treturn\r\n}\r\n\r\ntype objectEnd struct{}\r\n\r\nfunc decodeString(rdr *bytes.Reader) (str string, err error) {\r\n\tbuf := make([]byte, 2)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tlen := (int(buf[0]) << 8) | int(buf[1])\r\n\tif len > 0 {\r\n\t\tbuf := make([]byte, len)\r\n\t\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tstr = string(buf)\r\n\t}\r\n\treturn\r\n}\r\nfunc decodeNumber(rdr *bytes.Reader) (res float64, err error) {\r\n\tbuf := make([]byte, 8)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tu64 := binary.BigEndian.Uint64(buf)\r\n\tres = math.Float64frombits(u64)\r\n\treturn\r\n}\r\nfunc decodeBoolean(rdr *bytes.Reader) (res bool, err error) {\r\n\tbuf := make([]byte, 1)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif buf[0] == 0 {\r\n\t\tres = false\r\n\t} else {\r\n\t\tres = true\r\n\t}\r\n\treturn\r\n}\r\nfunc decodeObject(rdr *bytes.Reader) (res map[string]interface{}, err error) {\r\n\tres = make(map[string]interface{})\r\n\tfor {\r\n\t\tkey, e := decodeString(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tval, e := decodeOne(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif key == \"\" {\r\n\t\t\tswitch val.(type) {\r\n\t\t\tcase objectEnd:\r\n\t\t\t\treturn\r\n\t\t\tdefault:\r\n\t\t\t\tlog.Fatalf(\"decodeObject: parse error; Not object-end, %+s\", val)\r\n\t\t\t}\r\n\t\t}\r\n\t\tres[key] = val\r\n\t}\r\n\treturn\r\n}\r\nfunc decodeEcmaArray(rdr *bytes.Reader) (res map[string]interface{}, err error) {\r\n\tbuf := make([]byte, 4)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\t//count := binary.BigEndian.Uint32(buf)\r\n\t//log.Printf(\"decodeEcmaArray: Count: %v\", count)\r\n\tres, err = decodeObject(rdr)\r\n\r\n\treturn\r\n}\r\nfunc decodeStrictArray(rdr *bytes.Reader) (res []interface{}, err error) {\r\n\tbuf := make([]byte, 4)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tcount := binary.BigEndian.Uint32(buf)\r\n\tfor i := uint32(0); i < count; i++ {\r\n\t\tre, e := decodeOne(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, re)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc decodeOne(rdr *bytes.Reader) (res interface{}, err error) {\r\n\tbuf := make([]byte, 1)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tswitch buf[0] {\r\n\tcase 0: // Number\r\n\t\tres, err = decodeNumber(rdr)\r\n\tcase 1: // Boolean\r\n\t\tres, err = decodeBoolean(rdr)\r\n\tcase 2: // String\r\n\t\tres, err = decodeString(rdr)\r\n\tcase 3:\r\n\t\tres, err = decodeObject(rdr)\r\n\tcase 5: // Null\r\n\t\tres = nil\r\n\tcase 6: // undefined\r\n\t\tres = nil\r\n\tcase 8: // ECMA Array\r\n\t\tres, err = decodeEcmaArray(rdr)\r\n\r\n\tcase 9: // Object End\r\n\t\tres = objectEnd{}\r\n\tcase 10:\r\n\t\tres, err = decodeStrictArray(rdr)\r\n\tcase 0x11: // Switch to AMF3\r\n\t\tdat, e := amf3.DecodeAll(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = amf_t.AMF3{Data: dat}\r\n\tdefault:\r\n\t\terr = fmt.Errorf(\"Not implemented: type=%d\", buf[0])\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc DecodeAll(rdr *bytes.Reader) (res []interface{}, err error) {\r\n\tfor rdr.Len() > 0 {\r\n\t\tre, e := decodeOne(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tswitch re.(type) {\r\n\t\tcase amf_t.AMF3:\r\n\t\t\tres = append(res, re.(amf_t.AMF3).Data...)\r\n\t\tdefault:\r\n\t\t\tres = append(res, re)\r\n\t\t}\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/amf/amf3/amf3.go",
    "content": "package amf3\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"io\"\r\n\t\"log\"\r\n\t\"fmt\"\r\n)\r\n\r\nfunc decodeU29(rdr *bytes.Reader) (res int, err error) {\r\n\tfor i := 0; i < 4; i++ {\r\n\t\tvar num byte\r\n\t\tif num, err = rdr.ReadByte(); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tvar flg bool\r\n\t\tvar val uint8\r\n\t\tif i == 3 {\r\n\t\t\tval = num\r\n\t\t} else {\r\n\t\t\tflg = (num & 0x80) == 0x80\r\n\t\t\tval = (num & 0x7f)\r\n\t\t}\r\n\r\n\t\tswitch i {\r\n\t\t\tcase 0:\r\n\t\t\t\tres = int(val)\r\n\t\t\tcase 3:\r\n\t\t\t\tres = (res << 8) | int(val)\r\n\t\t\tdefault:\r\n\t\t\t\tres = (res << 7) | int(val)\r\n\t\t}\r\n\t\tif (! flg) {\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\treturn\r\n}\r\n\r\n// UTF-8-vr\r\nfunc decodeString(rdr *bytes.Reader) (str string, err error) {\r\n\t// UTF-8-vr = U29S-ref\r\n\t// UTF-8-vr = U29S-value *(UTF8-char)\r\n\tu29, err := decodeU29(rdr)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tflag := (u29 & 1) != 0\r\n\tlen := u29 >> 1\r\n\r\n\tif (! flag) {\r\n\t\t// string reference table index\r\n\t\tlog.Fatalf(\"[FIXME] not implemented: UTF-8-vr = U29S-ref\")\r\n\t} else {\r\n\t\tbuf := make([]byte, len)\r\n\t\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tstr = string(buf)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc assocOrUtf8Empty(rdr *bytes.Reader) (key string, val interface{}, err error) {\r\n\tkey, err = decodeString(rdr)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif key == \"\" {\r\n\t\t//fmt.Printf(\"assocOrUtf8Empty: string is empty\\n\")\r\n\t\treturn\r\n\t}\r\n\tval, err = decodeOne(rdr)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\t//log.Fatalf(\"assocOrUtf8Empty: key=%v, val=%v\", key, val)\r\n\treturn\r\n}\r\n\r\nfunc decodeOne(rdr *bytes.Reader) (res interface{}, err error) {\r\n\tformat, err := rdr.ReadByte()\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tswitch format {\r\n\t\tcase 6: // string-marker\r\n\t\t\tres, err = decodeString(rdr)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\tcase 9: // array-marker\r\n\t\t// array-marker U29O-ref\r\n\t\t// # array-marker U29A-value (UTF-8-empty | * (assoc-value) UTF-8-empty) * (value-type)\r\n\t\t// array-marker U29A-value * (assoc-value) UTF-8-empty * (value-type)\r\n\t\t// array-marker U29A-value UTF-8-empty * (value-type)\r\n\t\t\tu29, _ := decodeU29(rdr)\r\n\t\t\tflag := u29 & 1 != 0\r\n\t\t\tcount := u29 >> 1\r\n\t\t\tif (! flag) {\r\n\t\t\t\tlog.Fatalf(\"[FIXME] not implemented: array-type = array-marker U29O-ref\")\r\n\t\t\t}\r\n\t\t\tif count == 0 { // [FIXME] condition OK?\r\n\t\t\t\t// associative, terminated by empty string\r\n\t\t\t\tassoc := make(map[string]interface{})\r\n\t\t\t\tfor {\r\n\t\t\t\t\tk, v, e := assocOrUtf8Empty(rdr)\r\n\t\t\t\t\tif e != nil {\r\n\t\t\t\t\t\t//fmt.Printf(\"## amf3 associative: %+v\\n\", e)\r\n\t\t\t\t\t\terr = e\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif k == \"\" {\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t}\r\n\t\t\t\t\tassoc[k] = v\r\n\t\t\t\t\t//log.Printf(\"AMF3 array: %v = %v\", k, v)\r\n\t\t\t\t}\r\n\t\t\t\tres = assoc\r\n\t\t\t}\r\n\t\t\t//log.Fatalf(\"AMF3 array: len: %d\", count)\r\n\t\tdefault:\r\n\t\tlog.Printf(\"%v\\n\", res)\r\n\t\tlog.Fatalf(\"Not implemented: %d\", format)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc DecodeAll(rdr *bytes.Reader) (res []interface{}, err error) {\r\n\tfor rdr.Len() > 0 {\r\n\t\tre, e := decodeOne(rdr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, re)\r\n\t}\r\n\treturn\r\n}\r\n\r\n\r\nfunc encodeU29(num int, buff *bytes.Buffer) (err error) {\r\n\tif (0 <= num && num <= 0x7f) {\r\n\t\tif err = buff.WriteByte( byte(num & 0x7f) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else if (0x80 <= num && num <= 0x3fff) {\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 7) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(num & 0x7f) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else if (0x4000 <= num && num <= 0x1fffff) {\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 14) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 7) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(num & 0x7f) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else if (0x200000 <= num && num <= 0x3fffffff) {\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 22) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 15) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(0x80 | ((num >> 7) & 0x7f)) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif err = buff.WriteByte( byte(num & 0xff) ); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\terr = fmt.Errorf(\"u29 overflow\")\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc encodeU28Flag(num int, flag bool, buff *bytes.Buffer) (err error) {\r\n\tif flag {\r\n\t\terr = encodeU29(((num << 1) | 1), buff)\r\n\t} else {\r\n\t\terr = encodeU29((num << 1), buff)\r\n\t}\r\n\treturn\r\n}\r\n\r\n\r\nfunc encodeArray(data []interface {}, buff *bytes.Buffer) (err error) {\r\n\t// array-marker\r\n\tif err = buff.WriteByte(9); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// U29A-value; count of the dense portin of the Array\r\n\tif err = encodeU28Flag(len(data), true, buff); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// UTF-8-empty\r\n\tif err = buff.WriteByte(1); err != nil {\r\n\t\treturn\r\n\t}\r\n\tfor _, v := range data {\r\n\t\tif err = encode(v, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc encodeStringArray(data []string, buff *bytes.Buffer) error {\r\n\tvar list []interface{}\r\n\tfor _, v := range data {\r\n\t\tlist = append(list, v)\r\n\t}\r\n\treturn encodeArray(list, buff)\r\n}\r\nfunc encodeString(data string, buff *bytes.Buffer) (err error) {\r\n\tif err = buff.WriteByte(6); err != nil {\r\n\t\treturn\r\n\t}\r\n\tbstr := []byte(data)\r\n\t// U29S-value\r\n\tif err = encodeU28Flag(len(bstr), true, buff); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif _, err = buff.Write(bstr); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc encode(data interface{}, buff *bytes.Buffer) (err error) {\r\n\tswitch data.(type) {\r\n\t\tcase string:\r\n\t\t\terr = encodeString(data.(string), buff)\r\n\t\tcase []string:\r\n\t\t\terr = encodeStringArray(data.([]string), buff)\r\n\t\tdefault:\r\n\t\t\tlog.Fatalf(\"amf0/encode %#v\", data)\r\n\t}\r\n\treturn\r\n}\r\nfunc Encode(data []interface{}) (b []byte, err error) {\r\n\tbuff := bytes.NewBuffer(nil)\r\n\tfor _, data := range data {\r\n\t\tif err = encode(data, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tb = buff.Bytes()\r\n\treturn\r\n}"
  },
  {
    "path": "src/amf/amf_t/amf_t.go",
    "content": "package amf_t\r\n\r\ntype AMF3 struct {\r\n\tData []interface{}\r\n}\r\n\r\ntype SwitchToAmf3 struct {\r\n\r\n}\r\n\r\ntype AMF0EcmaArray struct {\r\n\tData map[string]interface {}\r\n}\r\n"
  },
  {
    "path": "src/buildno/buildno.go",
    "content": "\npackage buildno\n\nvar BuildDate = \"20181215\"\nvar BuildNo = \"35\"\n"
  },
  {
    "path": "src/buildno/funcs.go",
    "content": "package buildno\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"runtime\"\r\n)\r\n\r\nfunc GetBuildNo() string {\r\n\treturn fmt.Sprintf(\r\n\t\t\"%v.%v-%s-%s\",\r\n\t\tBuildDate,\r\n\t\tBuildNo,\r\n\t\truntime.GOOS,\r\n\t\truntime.GOARCH,\r\n\t)\r\n}\r\n"
  },
  {
    "path": "src/cryptoconf/cryptoconf.go",
    "content": "package cryptoconf\r\n\r\nimport (\r\n\t\"golang.org/x/crypto/sha3\"\r\n\t\"crypto/aes\"\r\n\t\"crypto/cipher\"\r\n\t\"crypto/rand\"\r\n\t\"io\"\r\n\t\"io/ioutil\"\r\n\t\"os\"\r\n\t\"encoding/json\"\r\n\t\"fmt\"\r\n\t\"log\"\r\n)\r\n\r\nfunc Set(dataSet map[string]string, fileName, pass string) (err error) {\r\n\tvar data map[string]interface{}\r\n\tif _, test := os.Stat(fileName); test == nil {\r\n\t\tdata, err = Load(fileName, pass)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\tdata = map[string]interface{}{}\r\n\t}\r\n\tfor key, val := range dataSet {\r\n\t\tdata[key] = val\r\n\t}\r\n\r\n\tdigest := sha3.Sum256([]byte(pass))\r\n\tblock, err := aes.NewCipher(digest[:])\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\taesgcm, err := cipher.NewGCM(block)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err.Error())\r\n\t}\r\n\r\n\tnonceSize := aesgcm.NonceSize()\r\n\t// Never use more than 2^32 random nonces with a given key because of the risk of a repeat.\r\n\tnonce := make([]byte, nonceSize)\r\n\tif _, err := io.ReadFull(rand.Reader, nonce); err != nil {\r\n\t\tlog.Fatalln(err.Error())\r\n\t}\r\n\r\n\tplaintext, err := json.Marshal(data)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil)\r\n\t//fmt.Printf(\"%#v\\n\", ciphertext)\r\n\r\n\tfile, err := os.Create(fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer file.Close()\r\n\tif _, err = file.Write(ciphertext); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc Load(file, pass string) (data map[string]interface{}, err error) {\r\n\tb, err := ioutil.ReadFile(file)\r\n\tif err != nil {\r\n\t\terr = nil\r\n\t\treturn\r\n\t}\r\n\r\n\tdigest := sha3.Sum256([]byte(pass))\r\n\tblock, err := aes.NewCipher(digest[:])\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\taesgcm, err := cipher.NewGCM(block)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err.Error())\r\n\t}\r\n\r\n\tnonceSize := aesgcm.NonceSize()\r\n\r\n\tnonce, ciphertext := b[:nonceSize], b[nonceSize:]\r\n\r\n\tplaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)\r\n\tif err != nil {\r\n\t\terr = fmt.Errorf(\"Password wrong for config: %s\", file)\r\n\t\treturn\r\n\t}\r\n\r\n\t////fmt.Printf(\"%s\\n\", plaintext)\r\n\tdata = map[string]interface{}{}\r\n\terr = json.Unmarshal(plaintext, &data)\r\n\r\n\treturn\r\n}"
  },
  {
    "path": "src/defines/constant.go",
    "content": "\r\npackage defines\r\n\r\nvar Twitter = \"@himananiito\"\r\nvar Email = \"himananiito@yahoo.co.jp\"\r\n"
  },
  {
    "path": "src/files/files.go",
    "content": "package files\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\t\"strings\"\r\n\t\"regexp\"\r\n)\r\n\r\nfunc RemoveExtention(fileName string) string {\r\n\te := filepath.Ext(fileName)\r\n\tbase := strings.TrimSuffix(fileName, e)\r\n\treturn base\r\n}\r\nfunc ChangeExtention(fileName, ext string) string {\r\n\te := filepath.Ext(fileName)\r\n\tbase := strings.TrimSuffix(fileName, e)\r\n\treturn base + \".\" + ext\r\n}\r\n\r\nfunc MkdirByFileName(fileName string) (err error) {\r\n\tdir := filepath.Dir(fileName)\r\n\terr = os.MkdirAll(dir, os.ModePerm)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc GetFileNameNext(name string) (fileName string, err error) {\r\n\tfileName = name\r\n\t_, test := os.Stat(fileName)\r\n\tif test == nil {\r\n\t\t// file Exists\r\n\t\text := filepath.Ext(fileName)\r\n\t\tbase := strings.TrimSuffix(fileName, ext)\r\n\r\n\t\tvar i int\r\n\t\tfor i = 2; i < 10000000 ; i++ {\r\n\t\t\tfileName = fmt.Sprintf(\"%s-%d%s\", base, i, ext)\r\n\t\t\t_, test := os.Stat(fileName)\r\n\t\t\tif test != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t\terr = fmt.Errorf(\"too many files: %s\", name)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc ReplaceForbidden(name string) (fileName string) {\r\n\tfileName = name\r\n\tfileName = regexp.MustCompile(`\\\\`).ReplaceAllString(fileName, \"￥\")\r\n\tfileName = regexp.MustCompile(`/`).ReplaceAllString(fileName, \"∕\")\r\n\tfileName = regexp.MustCompile(`:`).ReplaceAllString(fileName, \"：\")\r\n\tfileName = regexp.MustCompile(`\\*`).ReplaceAllString(fileName, \"＊\")\r\n\tfileName = regexp.MustCompile(`\\?`).ReplaceAllString(fileName, \"？\")\r\n\tfileName = regexp.MustCompile(`\"`).ReplaceAllString(fileName, `゛`)\r\n\tfileName = regexp.MustCompile(`<`).ReplaceAllString(fileName, \"＜\")\r\n\tfileName = regexp.MustCompile(`>`).ReplaceAllString(fileName, \"＞\")\r\n\tfileName = regexp.MustCompile(`\\|`).ReplaceAllString(fileName, \"｜\")\r\n\r\n\tfileName = regexp.MustCompile(`）`).ReplaceAllString(fileName, \")\")\r\n\tfileName = regexp.MustCompile(`（`).ReplaceAllString(fileName, \"(\")\r\n\r\n\tfileName = regexp.MustCompile(`\\p{Zs}+`).ReplaceAllString(fileName, \" \")\r\n\tfileName = regexp.MustCompile(`\\A\\p{Zs}+|\\p{Zs}+\\z`).ReplaceAllString(fileName, \"\")\r\n\r\n\t// 末尾が.であるようなファイルは作れない\r\n\tfileName = regexp.MustCompile(`\\.\\p{Zs}*\\z`).ReplaceAllString(fileName, \"．\")\r\n\r\n\treturn\r\n}"
  },
  {
    "path": "src/flvs/flv.go",
    "content": "package flvs\r\n\r\nimport (\r\n\t\"os\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"encoding/binary\"\r\n\t\"bytes\"\r\n\t\"bufio\"\r\n)\r\n\r\ntype Flv struct {\r\n\tfilename string\r\n\tfile *os.File\r\n\twriter *bufio.Writer\r\n\tstartAt int\r\n\taudioTimestamp int\r\n\tvideoTimestamp int\r\n}\r\nfunc (flv *Flv) Flush() {\r\n\tif flv.writer != nil {\r\n\t\tflv.writer.Flush()\r\n\t}\r\n}\r\nfunc (flv *Flv) Close() {\r\n\tflv.Flush()\r\n\tif flv.file != nil {\r\n\t\tflv.file.Close()\r\n\t}\r\n}\r\nfunc Open(name string) (flv *Flv, err error) {\r\n\tfile, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tflv = &Flv {\r\n\t\tfilename: name,\r\n\t\tfile: file,\r\n\t\taudioTimestamp: -1,\r\n\t\tvideoTimestamp: -1,\r\n\t}\r\n\r\n\tstat, err := file.Stat()\r\n\tif err != nil {\r\n\r\n\t}\r\n\r\n\t// FLV header\r\n\tsz := stat.Size()\r\n\tif sz == 0 {\r\n\t\tif err = flv.writeHeader(); err != nil {\r\n\t\t\tflv.Close()\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\tif err = flv.testHeader(); err != nil {\r\n\t\tflv.Close()\r\n\t\treturn\r\n\t}\r\n\r\n\r\n\tflv.lastPacketTimestamp()\r\n\r\n\tif _, err = flv.file.Seek(0, 2); err != nil {\r\n\t\treturn\r\n\t}\r\n\tts := flv.GetLastTimestamp()\r\n\tif ts != 0 {\r\n\t\tfmt.Printf(\"[info] Seek point: %d\\n\", ts)\r\n\t}\r\n\r\n\r\n\tflv.writer = bufio.NewWriterSize(file, 256*1024)\r\n\r\n\r\n\treturn\r\n}\r\nfunc (flv *Flv) AudioExists() bool {\r\n\treturn flv.audioTimestamp >= 0\r\n}\r\nfunc (flv *Flv) VideoExists() bool {\r\n\treturn flv.videoTimestamp >= 0\r\n}\r\nfunc (flv *Flv) testHeader() (err error) {\r\n\tif _, err = flv.file.Seek(0, 0); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tb := make([]byte, 9)\r\n\t_, err = io.ReadFull(flv.file, b); if err != nil {\r\n\t\treturn\r\n\t}\r\n\tif \"FLV\" != string(b[0:3]) {\r\n\t\terr = fmt.Errorf(\"magic number is not FLV\")\r\n\t\treturn\r\n\t}\r\n\toffset := binary.BigEndian.Uint32(b[5:9])\r\n\tflv.startAt = int(offset)\r\n\r\n\treturn\r\n}\r\n\r\nfunc intToBE24(num int) (data []byte) {\r\n\ttmp := make([]byte, 4)\r\n\tbinary.BigEndian.PutUint32(tmp, uint32(num))\r\n\tdata = append(data, tmp[1:]...)\r\n\treturn\r\n}\r\nfunc intToBE32(num int) (data []byte) {\r\n\ttmp := make([]byte, 4)\r\n\tbinary.BigEndian.PutUint32(tmp, uint32(num))\r\n\tdata = append(data, tmp[:]...)\r\n\treturn\r\n}\r\n\r\nfunc (flv *Flv) writePacket(tag byte, rdr *bytes.Buffer, ts int) (err error) {\r\n\tbuff := bytes.NewBuffer(nil)\r\n\r\n\tdataSize := intToBE24(rdr.Len())\r\n\ttagSize := intToBE32(11 + rdr.Len())\r\n\r\n\t// TagType\r\n\tif err = buff.WriteByte(tag); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// DataSize\r\n\tif _, err = buff.Write(dataSize); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// Timestamp\r\n\ttsBytes := intToBE32(ts)\r\n\tif _, err = buff.Write(tsBytes[1:4]); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// (TimestampExtended)\r\n\tif err = buff.WriteByte(tsBytes[0]); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// StreamID\r\n\tif _, err = buff.Write([]byte{0, 0, 0}); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// header\r\n\tif _, err = io.Copy(flv.writer, buff); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// data\r\n\tif _, err = io.Copy(flv.writer, rdr); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// PreviousTagSize\r\n\tif _, err = flv.writer.Write(tagSize); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc (flv *Flv) WriteAudio(rdr *bytes.Buffer, ts int) (err error) {\r\n\tif ts > flv.audioTimestamp {\r\n\t\tflv.audioTimestamp = ts\r\n\t\terr = flv.writePacket(8, rdr, ts)\r\n\t}\r\n\treturn\r\n}\r\nfunc (flv *Flv) WriteVideo(rdr *bytes.Buffer, ts int) (err error) {\r\n\tif ts > flv.videoTimestamp {\r\n\t\tflv.videoTimestamp = ts\r\n\t\terr = flv.writePacket(9, rdr, ts)\r\n\t}\r\n\treturn\r\n}\r\nfunc (flv *Flv) WriteMetaData(rdr *bytes.Buffer, ts int) (err error) {\r\n\terr = flv.writePacket(18, rdr, ts)\r\n\treturn\r\n}\r\n\r\nfunc (flv *Flv) GetLastTimestamp() int {\r\n\tvar min int\r\n\tif flv.audioTimestamp > flv.videoTimestamp {\r\n\t\tmin = flv.videoTimestamp\r\n\t} else {\r\n\t\tmin = flv.audioTimestamp\r\n\t}\r\n\tif min < 0 {\r\n\t\treturn 0\r\n\t}\r\n\treturn min\r\n}\r\n\r\nfunc (flv *Flv) lastPacketTimestamp() (err error) {\r\n\tdefer flv.file.Seek(0, 2)\r\n\r\n\tif _, err = flv.file.Seek(-4, 2); err != nil {\r\n\t\tfmt.Printf(\"flv.lastPacketTimestamp: %#v\\n\", err)\r\n\t\treturn\r\n\t}\r\n\r\n\tb0 := make([]byte, 4)\r\n\tb1 := make([]byte, 11)\r\n\r\n\tvar audioFound bool\r\n\tvar videoFound bool\r\n\tfor !(audioFound && videoFound) {\r\n\t\t_, err = io.ReadFull(flv.file, b0); if err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tsize := binary.BigEndian.Uint32(b0)\r\n\t\t//fmt.Printf(\"size: %d\\n\", size)\r\n\t\tif size == 0 {\r\n\t\t\tbreak\r\n\t\t}\r\n\r\n\t\tif _, err = flv.file.Seek(-(int64(size) + 4), 1); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t_, err = io.ReadFull(flv.file, b1); if err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tts :=\r\n\t\t\t(int(b1[7]) << 24) |\r\n\t\t\t(int(b1[4]) << 16) |\r\n\t\t\t(int(b1[5]) <<  8) |\r\n\t\t\t(int(b1[6])      )\r\n\t\t//fmt.Printf(\"ts: %d\\n\", ts)\r\n\r\n\t\tif b1[0] == 8 {\r\n\t\t\tflv.audioTimestamp = ts\r\n\t\t\taudioFound = true\r\n\t\t} else if b1[0] == 9 {\r\n\t\t\tflv.videoTimestamp = ts\r\n\t\t\tvideoFound = true\r\n\t\t}\r\n\r\n\t\tif _, err = flv.file.Seek(-(11 + 4), 1); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc (flv *Flv) writeHeader() (err error) {\r\n\t_, err = flv.file.Write([]byte{\r\n\t\t'F', 'L', 'V',\r\n\t\t1, // FLV version 1\r\n\t\t5, // Audio+Video tags are present\r\n\t\t0, 0, 0, 9, // DataOffset = 9\r\n\t\t0, 0, 0, 0, // PreviousTagSize0\r\n\t})\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/go.mod",
    "content": "module github.com/himananiito/livedl\n\ngo 1.16\n\nrequire (\n\tgithub.com/gin-gonic/gin v1.7.1\n\tgithub.com/gorilla/websocket v1.4.2\n\tgithub.com/mattn/go-sqlite3 v1.14.7\n\tgolang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e\n)\n\nreplace github.com/himananiito/livedl => ./\n"
  },
  {
    "path": "src/go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8=\ngithub.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=\ngithub.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=\ngithub.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=\ngithub.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=\ngithub.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=\ngithub.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=\ngithub.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=\ngithub.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=\ngithub.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=\ngithub.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=\ngithub.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=\ngithub.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=\ngithub.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=\ngithub.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=\ngolang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\n"
  },
  {
    "path": "src/gorman/gorman.go",
    "content": "package gorman\r\n\r\nimport (\r\n\t\"sync\"\r\n)\r\n\r\ntype GoroutineManager struct {\r\n\tchannels map[chan struct{}] struct{}\r\n\tmtxChan sync.Mutex\r\n\r\n\tmtxWg sync.Mutex\r\n\twg sync.WaitGroup\r\n\r\n\tcodeChecker func(code int)\r\n}\r\n\r\nfunc NewManager() *GoroutineManager {\r\n\treturn &GoroutineManager{\r\n\t\tchannels: map[chan struct{}] struct{}{},\r\n\t}\r\n}\r\nfunc WithChecker(f func(int)) *GoroutineManager {\r\n\treturn &GoroutineManager{\r\n\t\tchannels: map[chan struct{}] struct{}{},\r\n\t\tcodeChecker: f,\r\n\t}\r\n}\r\nfunc (gm *GoroutineManager) addChan(c chan struct{}) {\r\n\tgm.mtxChan.Lock()\r\n\tdefer gm.mtxChan.Unlock()\r\n\tgm.channels[c] = struct{}{}\r\n}\r\nfunc (gm *GoroutineManager) delChan(c chan struct{}) {\r\n\tgm.mtxChan.Lock()\r\n\tdefer gm.mtxChan.Unlock()\r\n\tdelete(gm.channels, c)\r\n}\r\nfunc (gm *GoroutineManager) Cancel() {\r\n\tgm.mtxChan.Lock()\r\n\tdefer gm.mtxChan.Unlock()\r\n\tfor c, _ := range gm.channels {\r\n\t\tclose(c)\r\n\t\tdelete(gm.channels, c)\r\n\t}\r\n}\r\nfunc (gm *GoroutineManager) Count() int {\r\n\tgm.mtxChan.Lock()\r\n\tdefer gm.mtxChan.Unlock()\r\n\treturn len(gm.channels)\r\n}\r\nfunc (gm *GoroutineManager) Go(f func(<-chan struct{}) int) {\r\n\tgm.wg.Add(1)\r\n\tstopChan := make(chan struct{}, 1)\r\n\tgm.addChan(stopChan)\r\n\r\n\tgo func(){\r\n\t\tdefer gm.wg.Done()\r\n\t\tcode := f(stopChan)\r\n\t\tgm.delChan(stopChan)\r\n\t\tif gm.codeChecker != nil {\r\n\t\t\tgm.codeChecker(code)\r\n\t\t}\r\n\t}()\r\n}\r\nfunc (gm *GoroutineManager) RegisterCodeChecker(f func(int)) {\r\n\tgm.codeChecker = f\r\n}\r\nfunc (gm *GoroutineManager) Wait() {\r\n\tgm.wg.Wait()\r\n}\r\n"
  },
  {
    "path": "src/httpbase/httpbase.go",
    "content": "package httpbase\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"crypto/tls\"\r\n\t\"crypto/x509\"\r\n\t\"encoding/json\"\r\n\t\"encoding/pem\"\r\n\t\"errors\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"io/ioutil\"\r\n\t\"net/http\"\r\n\t\"net/url\"\r\n\t\"os\"\r\n\t\"strings\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/buildno\"\r\n\t\"github.com/himananiito/livedl/defines\"\r\n)\r\n\r\nfunc GetUserAgent() string {\r\n\treturn fmt.Sprintf(\r\n\t\t\"livedl/%s (contact: twitter=%s, email=%s)\",\r\n\t\tbuildno.GetBuildNo(),\r\n\t\tdefines.Twitter,\r\n\t\tdefines.Email,\r\n\t)\r\n}\r\n\r\nvar Client = &http.Client{\r\n\tTimeout: time.Duration(5) * time.Second,\r\n\tCheckRedirect: func(req *http.Request, via []*http.Request) (err error) {\r\n\t\tif req != nil && via != nil && len(via) > 0 {\r\n\t\t\tif len(via) >= 10 {\r\n\t\t\t\treturn errors.New(\"stopped after 10 redirects\")\r\n\t\t\t}\r\n\t\t\treq.Header = via[0].Header\r\n\t\t}\r\n\t\treturn nil\r\n\t},\r\n}\r\n\r\nfunc checkTransport() bool {\r\n\tif Client.Transport == nil {\r\n\t\tClient.Transport = &http.Transport{}\r\n\t}\r\n\tswitch Client.Transport.(type) {\r\n\tcase *http.Transport:\r\n\t\treturn true\r\n\t}\r\n\treturn false\r\n}\r\nfunc checkTLSClientConfig() bool {\r\n\tif !checkTransport() {\r\n\t\treturn false\r\n\t}\r\n\r\n\tif Client.Transport.(*http.Transport).TLSClientConfig == nil {\r\n\t\tClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{}\r\n\t}\r\n\r\n\treturn true\r\n}\r\nfunc SetRootCA(file string) (err error) {\r\n\tif !checkTLSClientConfig() {\r\n\t\terr = fmt.Errorf(\"SetRootCA: check failed\")\r\n\t\treturn\r\n\t}\r\n\r\n\tdat, err := ioutil.ReadFile(file)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// try decode pem\r\n\tvar nDecode int\r\n\tfor len(dat) > 0 {\r\n\t\tblock, d := pem.Decode(dat)\r\n\t\tif block == nil {\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tdat = d\r\n\t\tnDecode++\r\n\t\tif block.Type != \"CERTIFICATE\" || len(block.Headers) != 0 {\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\taddCert(block.Bytes)\r\n\t}\r\n\tif nDecode < 1 {\r\n\t\taddCert(dat)\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc addCert(dat []byte) (err error) {\r\n\tcerts, err := x509.ParseCertificates(dat)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif certs == nil {\r\n\t\terr = fmt.Errorf(\"ParseCertificates failed\")\r\n\t\treturn\r\n\t}\r\n\r\n\tif len(certs) > 0 {\r\n\t\tif Client.Transport.(*http.Transport).TLSClientConfig.RootCAs == nil {\r\n\t\t\tClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = x509.NewCertPool()\r\n\t\t}\r\n\t}\r\n\r\n\tfor _, cert := range certs {\r\n\t\tClient.Transport.(*http.Transport).TLSClientConfig.RootCAs.AddCert(cert)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc SetSkipVerify(skip bool) (err error) {\r\n\tif checkTLSClientConfig() {\r\n\t\tClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = skip\r\n\t} else {\r\n\t\terr = fmt.Errorf(\"SetSkipVerify(%#v): check failed\", skip)\r\n\t}\r\n\treturn\r\n}\r\nfunc SetProxy(rawurl string) (err error) {\r\n\tif !checkTransport() {\r\n\t\treturn fmt.Errorf(\"SetProxy(%#v): check failed\", rawurl)\r\n\t}\r\n\r\n\tu, err := url.Parse(rawurl)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tClient.Transport.(*http.Transport).Proxy = http.ProxyURL(u)\r\n\treturn\r\n}\r\n\r\nfunc httpBase(method, uri string, header map[string]string, body io.Reader) (resp *http.Response, err, neterr error) {\r\n\treq, err := http.NewRequest(method, uri, body)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treq.Header.Set(\"User-Agent\", GetUserAgent())\r\n\r\n\tfor k, v := range header {\r\n\t\treq.Header.Set(k, v)\r\n\t}\r\n\r\n\tresp, neterr = Client.Do(req)\r\n\tif neterr != nil {\r\n\t\tif strings.Contains(neterr.Error(), \"x509: certificate signed by unknown\") {\r\n\t\t\tfmt.Println(neterr)\r\n\t\t\tos.Exit(10)\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc Get(uri string, header map[string]string) (*http.Response, error, error) {\r\n\treturn httpBase(\"GET\", uri, header, nil)\r\n}\r\nfunc PostForm(uri string, header map[string]string, val url.Values) (*http.Response, error, error) {\r\n\tif header == nil {\r\n\t\theader = make(map[string]string)\r\n\t}\r\n\theader[\"Content-Type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\r\n\treturn httpBase(\"POST\", uri, header, strings.NewReader(val.Encode()))\r\n}\r\nfunc reqJson(method, uri string, header map[string]string, data interface{}) (\r\n\t*http.Response, error, error) {\r\n\tencoded, err := json.Marshal(data)\r\n\tif err != nil {\r\n\t\treturn nil, err, nil\r\n\t}\r\n\r\n\tif header == nil {\r\n\t\theader = make(map[string]string)\r\n\t}\r\n\theader[\"Content-Type\"] = \"application/json\"\r\n\r\n\treturn httpBase(method, uri, header, bytes.NewReader(encoded))\r\n}\r\nfunc PostJson(uri string, header map[string]string, data interface{}) (*http.Response, error, error) {\r\n\treturn reqJson(\"POST\", uri, header, data)\r\n}\r\nfunc PutJson(uri string, header map[string]string, data interface{}) (*http.Response, error, error) {\r\n\treturn reqJson(\"PUT\", uri, header, data)\r\n}\r\nfunc PostData(uri string, header map[string]string, data io.Reader) (*http.Response, error, error) {\r\n\tif header == nil {\r\n\t\theader = make(map[string]string)\r\n\t}\r\n\treturn httpBase(\"POST\", uri, header, data)\r\n}\r\nfunc GetBytes(uri string, header map[string]string) (code int, buff []byte, err, neterr error) {\r\n\tresp, err, neterr := Get(uri, header)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif neterr != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\r\n\tbuff, neterr = ioutil.ReadAll(resp.Body)\r\n\tif neterr != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tcode = resp.StatusCode\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/httpsub/httpsub.go",
    "content": "\r\npackage httpsub\r\n\r\nimport (\r\n\t\"net/http\"\r\n\t\"os\"\r\n\t\"sync\"\r\n\t\"log\"\r\n\t\"io\"\r\n\t\"fmt\"\r\n\t\"bytes\"\r\n)\r\n\r\ntype SubDownloader struct {\r\n\tmethod string\r\n\turi string\r\n\tdata []byte\r\n\tHeader map[string]string\r\n\tRangeSize int64\r\n\tBuffSize int64\r\n\tfileName string\r\n\tfile *os.File\r\n\tnumConcurrent int\r\n\tchRunning chan bool\r\n\tmtx sync.Mutex\r\n\twg sync.WaitGroup\r\n\tchLength chan int64\r\n}\r\nfunc (sub *SubDownloader) Concurrent(c int) {\r\n\tsub.numConcurrent = c\r\n}\r\nfunc Get(uri, fileName string) (sub *SubDownloader) {\r\n\tsub = &SubDownloader{\r\n\t\tmethod: \"GET\",\r\n\t\turi: uri,\r\n\t\tfileName: fileName,\r\n\t}\r\n\treturn\r\n}\r\nfunc (sub *SubDownloader) Close() {\r\n\tsub.mtx.Lock()\r\n\tdefer sub.mtx.Unlock()\r\n\tif sub.file != nil {\r\n\t\tsub.file.Close()\r\n\t\tsub.file = nil\r\n\t}\r\n}\r\nfunc (sub *SubDownloader) open() {\r\n\tf, err := os.Create(sub.fileName)\r\n\tif err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tsub.file = f\r\n}\r\nfunc (sub *SubDownloader) write(pos int64, rdr io.Reader) (err error) {\r\n\tsub.mtx.Lock()\r\n\tdefer sub.mtx.Unlock()\r\n\t//fmt.Printf(\"write %d\\n\", pos)\r\n\tif sub.file == nil {\r\n\t\tsub.open()\r\n\t}\r\n\tif _, err = sub.file.Seek(pos, 0); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\tif _, err = io.Copy(sub.file, rdr); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\treturn\r\n}\r\nfunc (sub *SubDownloader) subrange(pos int64) {\r\n\t//fmt.Printf(\"start subrange pos(%d), size(%d) \\n\", pos, sub.RangeSize)\r\n\tsub.wg.Add(1)\r\n\tsub.chRunning <- true\r\n\tgo func() {\r\n\t\tdefer func() {\r\n\t\t\t<-sub.chRunning\r\n\t\t\tsub.wg.Done()\r\n\t\t}()\r\n\t\tdata := bytes.NewBuffer(sub.data)\r\n\t\treq, _ := http.NewRequest(sub.method, sub.uri, data)\r\n\t\treq.Header.Set(\"Range\", fmt.Sprintf(\"bytes=%d-%d\", pos, pos + sub.RangeSize - 1))\r\n\r\n\t\tclient := new(http.Client)\r\n\t\tresp, err := client.Do(req)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer resp.Body.Close()\r\n\r\n\t\tswitch resp.StatusCode {\r\n\t\tcase 206:\r\n\t\tdefault:\r\n\t\t\tlog.Fatalf(\"StatusCode is %v\\n\", resp.StatusCode)\r\n\t\t}\r\n\t\tsub.chLength <- resp.ContentLength\r\n\r\n\t\tbuff := new(bytes.Buffer)\r\n\t\twbytes := int64(0)\r\n\t\tfor {\r\n\t\t\tn, _ := io.CopyN(buff, resp.Body, sub.BuffSize)\r\n\t\t\t//fmt.Printf(\"buff size is %d\\n\", buff.Len())\r\n\t\t\tif n > 0 {\r\n\t\t\t\tsub.write(pos + wbytes, buff)\r\n\t\t\t\twbytes += n\r\n\t\t\t} else {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}()\r\n}\r\nfunc (sub *SubDownloader) Wait() {\r\n\tsub.chRunning = make(chan bool, sub.numConcurrent)\r\n\tsub.chLength = make(chan int64, 10)\r\n\r\n\tif sub.RangeSize <= 0 {\r\n\t\tsub.RangeSize = 10*1000*1000\r\n\t}\r\n\r\n\tif sub.BuffSize <= 0 {\r\n\t\tsub.BuffSize = 3*1000*1000\r\n\t}\r\n\r\n\tpos := int64(0)\r\n\tfor {\r\n\t\tsub.subrange(pos)\r\n\t\tlength := <-sub.chLength\r\n\t\tfmt.Printf(\"Downloading %v: %v-%v\\n\", sub.fileName, pos, pos + length - 1)\r\n\t\tif length == sub.RangeSize {\r\n\t\t\tpos += length\r\n\t\t} else {\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\tsub.wg.Wait()\r\n\tsub.Close()\r\n}\r\n"
  },
  {
    "path": "src/livedl.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\t\"regexp\"\r\n\t\"strings\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/niconico\"\r\n\t\"github.com/himananiito/livedl/options\"\r\n\t\"github.com/himananiito/livedl/twitcas\"\r\n\t\"github.com/himananiito/livedl/youtube\"\r\n\t\"github.com/himananiito/livedl/zip2mp4\"\r\n)\r\n\r\nfunc main() {\r\n\tvar baseDir string\r\n\tif regexp.MustCompile(`\\AC:\\\\.*\\\\Temp\\\\go-build[^\\\\]*\\\\[^\\\\]+\\\\exe\\\\[^\\\\]*\\.exe\\z`).MatchString(os.Args[0]) {\r\n\t\t// go runで起動時\r\n\t\tpwd, e := os.Getwd()\r\n\t\tif e != nil {\r\n\t\t\tfmt.Println(e)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tbaseDir = pwd\r\n\t} else {\r\n\t\t//pa, e := filepath.Abs(os.Args[0])\r\n\t\tpa, e := os.Executable()\r\n\t\tif e != nil {\r\n\t\t\tfmt.Println(e)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// symlinkを追跡する\r\n\t\tfor {\r\n\t\t\tsl, e := os.Readlink(pa)\r\n\t\t\tif e != nil {\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t\tpa = sl\r\n\t\t}\r\n\t\tbaseDir = filepath.Dir(pa)\r\n\t}\r\n\r\n\topt := options.ParseArgs()\r\n\r\n\t// chdir if not disabled\r\n\tif !opt.NoChdir {\r\n\t\tfmt.Printf(\"chdir: %s\\n\", baseDir)\r\n\t\tif e := os.Chdir(baseDir); e != nil {\r\n\t\t\tfmt.Println(e)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\t// http\r\n\tif opt.HttpRootCA != \"\" {\r\n\t\tif err := httpbase.SetRootCA(opt.HttpRootCA); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tif opt.HttpSkipVerify {\r\n\t\tif err := httpbase.SetSkipVerify(true); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tif opt.HttpProxy != \"\" {\r\n\t\tif err := httpbase.SetProxy(opt.HttpProxy); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\tswitch opt.Command {\r\n\tdefault:\r\n\t\tfmt.Printf(\"Unknown command: %v\\n\", opt.Command)\r\n\t\tos.Exit(1)\r\n\r\n\tcase \"TWITCAS\":\r\n\t\tvar doneTime int64\r\n\t\tfor {\r\n\t\t\tdone, dbLocked := twitcas.TwitcasRecord(opt.TcasId, \"\")\r\n\t\t\tif dbLocked {\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t\tif !opt.TcasRetry {\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\r\n\t\t\tif opt.TcasRetryTimeoutMinute < 0 {\r\n\r\n\t\t\t} else if done {\r\n\t\t\t\tdoneTime = time.Now().Unix()\r\n\r\n\t\t\t} else {\r\n\t\t\t\tif doneTime == 0 {\r\n\t\t\t\t\tdoneTime = time.Now().Unix()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdelta := time.Now().Unix() - doneTime\r\n\t\t\t\t\tvar minutes int\r\n\t\t\t\t\tif opt.TcasRetryTimeoutMinute == 0 {\r\n\t\t\t\t\t\tminutes = options.DefaultTcasRetryTimeoutMinute\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tminutes = opt.TcasRetryTimeoutMinute\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif minutes > 0 {\r\n\t\t\t\t\t\tif delta > int64(minutes*60) {\r\n\t\t\t\t\t\t\tbreak\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar interval int\r\n\t\t\tif opt.TcasRetryInterval <= 0 {\r\n\t\t\t\tinterval = options.DefaultTcasRetryInterval\r\n\t\t\t} else {\r\n\t\t\t\tinterval = opt.TcasRetryInterval\r\n\t\t\t}\r\n\t\t\tselect {\r\n\t\t\tcase <-time.After(time.Duration(interval) * time.Second):\r\n\t\t\t}\r\n\t\t}\r\n\r\n\tcase \"YOUTUBE\":\r\n\t\terr := youtube.Record(opt.YoutubeId, opt.YtNoStreamlink, opt.YtNoYoutubeDl)\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t}\r\n\r\n\tcase \"NICOLIVE\":\r\n\t\thlsPlaylistEnd, dbname, err := niconico.Record(opt)\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\tos.Exit(1)\r\n\t\t}\r\n\t\tif hlsPlaylistEnd && opt.NicoAutoConvert {\r\n\t\t\tdone, nMp4s, err := zip2mp4.ConvertDB(dbname, opt.ConvExt, opt.NicoSkipHb)\r\n\t\t\tif err != nil {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t\tos.Exit(1)\r\n\t\t\t}\r\n\t\t\tif done {\r\n\t\t\t\tif nMp4s == 1 {\r\n\t\t\t\t\tif 1 <= opt.NicoAutoDeleteDBMode {\r\n\t\t\t\t\t\tos.Remove(dbname)\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if 1 < nMp4s {\r\n\t\t\t\t\tif 2 <= opt.NicoAutoDeleteDBMode {\r\n\t\t\t\t\t\tos.Remove(dbname)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\tcase \"NICOLIVE_TEST\":\r\n\t\tif err := niconico.TestRun(opt); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\tos.Exit(1)\r\n\t\t}\r\n\r\n\tcase \"ZIP2MP4\":\r\n\t\tif err := zip2mp4.Convert(opt.ZipFile); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\tos.Exit(1)\r\n\t\t}\r\n\r\n\tcase \"DB2MP4\":\r\n\t\tif strings.HasSuffix(opt.DBFile, \".yt.sqlite3\") {\r\n\t\t\tzip2mp4.YtComment(opt.DBFile)\r\n\r\n\t\t} else if opt.ExtractChunks {\r\n\t\t\tif _, err := zip2mp4.ExtractChunks(opt.DBFile, opt.NicoSkipHb); err != nil {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t\tos.Exit(1)\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\tif _, _, err := zip2mp4.ConvertDB(opt.DBFile, opt.ConvExt, opt.NicoSkipHb); err != nil {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t\tos.Exit(1)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/log4gui/log4gui.go",
    "content": "package log4gui\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"encoding/json\"\r\n)\r\n\r\nfunc print(k, v string) {\r\n\tbs, e := json.Marshal(map[string]string{\r\n\t\tk: v,\r\n\t})\r\n\tif(e != nil) {\r\n\t\tfmt.Println(e)\r\n\t\treturn\r\n\t}\r\n\tfmt.Println(\"$\" + string(bs) + \"$\")\r\n}\r\nfunc Info(s string) {\r\n\tprint(\"Info\", s)\r\n}\r\nfunc Error(s string) {\r\n\tprint(\"Error\", s)\r\n}"
  },
  {
    "path": "src/niconico/jikken.gox",
    "content": "\r\n\r\npackage niconico\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"os\"\r\n\t\"time\"\r\n\t\"os/signal\"\r\n\t\"syscall\"\r\n\t\"net/http\"\r\n\t\"io/ioutil\"\r\n\t\"log\"\r\n\t\"encoding/json\"\r\n\t\"bytes\"\r\n\t\"../options\"\r\n\t\"../obj\"\r\n\t\"../files\"\r\n)\r\n\r\n\r\nfunc getActionTrackId() (actionTrackId string, err error) {\r\n\turi := \"https://public.api.nicovideo.jp/v1/action-track-ids.json\"\r\n\treq, _ := http.NewRequest(\"POST\", uri, nil)\r\n\r\n\treq.Header.Set(\"Content-Type\", \"application/json\")\r\n\r\n\tclient := new(http.Client)\r\n\tresp, e := client.Do(req)\r\n\tif e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\tbs, err := ioutil.ReadAll(resp.Body)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t}\r\n\r\n\tvar props interface{}\r\n\tif err = json.Unmarshal(bs, &props); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t//obj.PrintAsJson(props)\r\n\r\n\tdata, ok := obj.FindString(props, \"data\")\r\n\tif (! ok) {\r\n\t\terr = fmt.Errorf(\"actionTrackId not found\")\r\n\t}\r\n\tactionTrackId = data\r\n\r\n\treturn\r\n}\r\n\r\nfunc jikkenWatching(opt options.Option, actionTrackId string, isArchive bool) (props interface{}, err error) {\r\n\r\n\tstr, _ := json.Marshal(OBJ{\r\n\t\t\"actionTrackId\": actionTrackId,\r\n\t\t\"isBroadcaster\": false,\r\n\t\t\"isLowLatencyStream\": true,\r\n\t\t\"streamCapacity\": \"superhigh\",\r\n\t\t\"streamProtocol\": \"https\",\r\n\t\t\"streamQuality\": \"auto\", // high, auto\r\n\t})\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\treturn\r\n\t}\r\n\r\n\tdata := bytes.NewReader(str)\r\n\r\n\tvar uri string\r\n\tif isArchive {\r\n\t\turi = fmt.Sprintf(\"https://api.cas.nicovideo.jp/v1/services/live/programs/%s/watching-archive\", opt.NicoLiveId)\r\n\t} else {\r\n\t\turi = fmt.Sprintf(\"https://api.cas.nicovideo.jp/v1/services/live/programs/%s/watching\", opt.NicoLiveId)\r\n\t}\r\n\treq, _ := http.NewRequest(\"POST\", uri, data)\r\n\r\n\t//if opt.NicoSession != \"\" {\r\n\t\treq.Header.Set(\"Cookie\", \"user_session=\" + opt.NicoSession)\r\n\t//}\r\n\treq.Header.Set(\"Accept\", \"application/json\")\r\n\treq.Header.Set(\"Content-Type\", \"application/json\")\r\n\treq.Header.Set(\"Origin\", \"https://cas.nicovideo.jp\")\r\n\treq.Header.Set(\"X-Connection-Environment\", \"ethernet\")\r\n\treq.Header.Set(\"X-Frontend-Id\", \"91\")\r\n\r\n\tclient := new(http.Client)\r\n\tresp, e := client.Do(req)\r\n\tif e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\tbs, err := ioutil.ReadAll(resp.Body)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t}\r\n\r\n\tif err = json.Unmarshal([]byte(bs), &props); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t//obj.PrintAsJson(props)\r\n\r\n\treturn\r\n}\r\n\r\n\r\nfunc jikkenPut(opt options.Option, actionTrackId string) (forbidden, notOnAir bool, err error) {\r\n\tstr, _ := json.Marshal(OBJ{\r\n\t\t\"actionTrackId\": actionTrackId,\r\n\t\t\"isBroadcaster\": false,\r\n\t})\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t}\r\n\tfmt.Printf(\"\\n%s\\n\\n\", str)\r\n\r\n\tdata := bytes.NewReader(str)\r\n\r\n\turi := fmt.Sprintf(\"https://api.cas.nicovideo.jp/v1/services/live/programs/%s/watching\", opt.NicoLiveId)\r\n\treq, _ := http.NewRequest(\"PUT\", uri, data)\r\n\r\n\t//if opt.NicoSession != \"\" {\r\n\t\treq.Header.Set(\"Cookie\", \"user_session=\" + opt.NicoSession)\r\n\t//}\r\n\treq.Header.Set(\"Accept\", \"application/json\")\r\n\treq.Header.Set(\"Content-Type\", \"application/json\")\r\n\treq.Header.Set(\"Origin\", \"https://cas.nicovideo.jp\")\r\n\treq.Header.Set(\"X-Frontend-Id\", \"91\")\r\n\r\n\tclient := new(http.Client)\r\n\tresp, e := client.Do(req)\r\n\tif e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\tbs, err := ioutil.ReadAll(resp.Body)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t}\r\n\r\n\tvar props interface{}\r\n\tif err = json.Unmarshal([]byte(bs), &props); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t//obj.PrintAsJson(props)\r\n\r\n\tif code, ok := obj.FindString(props, \"meta\", \"errorCode\"); ok {\r\n\t\tswitch code {\r\n\t\tcase \"FORBIDDEN\":\r\n\t\t\tforbidden = true\r\n\t\t\treturn\r\n\t\tcase \"PROGRAM_NOT_ONAIR\":\r\n\t\t\tnotOnAir = true\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\n\r\nfunc jikkenHousou(nicoliveProgramId, title, userId, nickname, communityId string, opt options.Option, isArchive bool) (err error) {\r\n\r\n\tchInterrupt := make(chan os.Signal, 10)\r\n\tsignal.Notify(chInterrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)\r\n\r\n\tactionTrackId, err := getActionTrackId()\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t}\r\n\r\n\tmedia := &NicoMedia{}\r\n\r\n\tdefer func() {\r\n\t\tif media.zipWriter != nil {\r\n\t\t\tmedia.zipWriter.Close()\r\n\t\t}\r\n\t}()\r\n\r\n\ttitle = files.ReplaceForbidden(title)\r\n\tnickname = files.ReplaceForbidden(nickname)\r\n\tmedia.fileName = fmt.Sprintf(\"%s-%s-%s.zip\", nicoliveProgramId, nickname, title)\r\n\r\n\r\n\tvar nLast int\r\n\tL_main: for {\r\n\t\tselect {\r\n\t\tcase <-chInterrupt:\r\n\t\t\tbreak L_main\r\n\t\tdefault:\r\n\t\t}\r\n\t\tprops, e := jikkenWatching(opt, actionTrackId, isArchive)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\tlog.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif uri, ok := obj.FindString(props, \"data\", \"streamServer\", \"url\"); ok {\r\n\t\t\t//fmt.Println(uri)\r\n\r\n\t\t\tis403, e := media.SetPlaylist(uri)\r\n\t\t\tif is403 {\r\n\t\t\t\tbreak L_main\r\n\t\t\t}\r\n\t\t\tif e != nil {\r\n\t\t\t\terr = e\r\n\t\t\t\tlog.Println(e)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tL_loc: for i := 0; true; i++ {\r\n\t\t\tselect {\r\n\t\t\tcase <-chInterrupt:\r\n\t\t\t\tbreak L_main\r\n\t\t\tdefault:\r\n\t\t\t}\r\n\r\n\t\t\tis403, e := media.GetMedias()\r\n\t\t\tif is403 {\r\n\t\t\t\tn := media.getNumChunk()\r\n\t\t\t\tif n != nLast {\r\n\t\t\t\t\tnLast = n\r\n\t\t\t\t\tbreak L_loc\r\n\t\t\t\t} else {\r\n\t\t\t\t\tbreak L_main\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif e != nil {\r\n\t\t\t\tlog.Println(e)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif i > 60 {\r\n\t\t\t\tforbidden, notOnAir, e := jikkenPut(opt, actionTrackId)\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\terr = e\r\n\t\t\t\t\tlog.Println(e)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tif notOnAir {\r\n\t\t\t\t\tbreak L_main\r\n\t\t\t\t}\r\n\t\t\t\tif forbidden {\r\n\t\t\t\t\tbreak L_loc\r\n\t\t\t\t}\r\n\t\t\t\ti = 0\r\n\t\t\t}\r\n\t\t\tselect {\r\n\t\t\tcase <-chInterrupt:\r\n\t\t\t\tbreak L_main\r\n\t\t\tcase <-time.After(1 * time.Second):\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif media.zipWriter != nil {\r\n\t\tmedia.zipWriter.Close()\r\n\t}\r\n\r\n\tsignal.Stop(chInterrupt)\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/niconico/nico.go",
    "content": "package niconico\r\n\r\nimport (\r\n\t\"bufio\"\r\n\t\"encoding/xml\"\r\n\t\"fmt\"\r\n\t\"io/ioutil\"\r\n\t\"net\"\r\n\t\"net/http\"\r\n\t_ \"net/http/pprof\"\r\n\t\"net/url\"\r\n\t\"os\"\r\n\t\"os/signal\"\r\n\t\"regexp\"\r\n\t\"runtime\"\r\n\t\"strconv\"\r\n\t\"strings\"\r\n\t\"syscall\"\r\n\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/options\"\r\n)\r\n\r\nfunc NicoLogin(opt options.Option) (err error) {\r\n\tid, pass, _, _ := options.LoadNicoAccount(opt.NicoLoginAlias)\r\n\r\n\tif id == \"\" || pass == \"\" {\r\n\t\terr = fmt.Errorf(\"Login ID/Password not set. Use -nico-login \\\"<id>,<password>\\\"\")\r\n\t\treturn\r\n\t}\r\n\r\n\tresp, err, neterr := httpbase.PostForm(\r\n\t\t\"https://account.nicovideo.jp/api/v1/login\",\r\n\t\tnil,\r\n\t\turl.Values{\"mail_tel\": {id}, \"password\": {pass}, \"site\": {\"nicoaccountsdk\"}},\r\n\t)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif neterr != nil {\r\n\t\terr = neterr\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\r\n\tbody, err := ioutil.ReadAll(resp.Body)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tif ma := regexp.MustCompile(`<session_key>(.+?)</session_key>`).FindSubmatch(body); len(ma) > 0 {\r\n\t\toptions.SetNicoSession(opt.NicoLoginAlias, string(ma[1]))\r\n\r\n\t\tfmt.Println(\"login success\")\r\n\t} else {\r\n\t\terr = fmt.Errorf(\"login failed: session_key not found\")\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc Record(opt options.Option) (hlsPlaylistEnd bool, dbName string, err error) {\r\n\r\n\tfor i := 0; i < 2; i++ {\r\n\t\t// load session info\r\n\t\tif opt.NicoSession == \"\" || i > 0 {\r\n\t\t\t_, _, opt.NicoSession, _ = options.LoadNicoAccount(opt.NicoLoginAlias)\r\n\t\t}\r\n\r\n\t\tif !opt.NicoRtmpOnly {\r\n\t\t\tvar done bool\r\n\t\t\tvar notLogin bool\r\n\t\t\tvar reserved bool\r\n\t\t\tdone, hlsPlaylistEnd, notLogin, reserved, dbName, err = NicoRecHls(opt)\r\n\t\t\tif done {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif notLogin {\r\n\t\t\t\tfmt.Println(\"not_login\")\r\n\t\t\t\tif err = NicoLogin(opt); err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t\tif reserved {\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif !opt.NicoHlsOnly {\r\n\t\t\tnotLogin, e := NicoRecRtmp(opt)\r\n\t\t\tif e != nil {\r\n\t\t\t\terr = e\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif notLogin {\r\n\t\t\t\tfmt.Println(\"not_login\")\r\n\t\t\t\tif err = NicoLogin(opt); err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbreak\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc TestRun(opt options.Option) (err error) {\r\n\r\n\tgo func() {\r\n\t\tfmt.Println(http.ListenAndServe(\"localhost:6060\", nil))\r\n\t}()\r\n\r\n\tif false {\r\n\t\tch := make(chan os.Signal, 10)\r\n\t\tsignal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)\r\n\t\tgo func() {\r\n\t\t\t<-ch\r\n\t\t\tos.Exit(0)\r\n\t\t}()\r\n\t}\r\n\r\n\topt.NicoRtmpIndex = map[int]bool{\r\n\t\t0: true,\r\n\t}\r\n\r\n\tvar nextId func() string\r\n\r\n\tif opt.NicoLiveId == \"\" {\r\n\t\t// niconama alert\r\n\r\n\t\tif opt.NicoTestTimeout <= 0 {\r\n\t\t\topt.NicoTestTimeout = 12\r\n\t\t}\r\n\r\n\t\tresp, e, nete := httpbase.Get(\"https://live.nicovideo.jp/api/getalertinfo\", nil)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif nete != nil {\r\n\t\t\terr = nete\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer resp.Body.Close()\r\n\r\n\t\tswitch resp.StatusCode {\r\n\t\tcase 200:\r\n\t\tdefault:\r\n\t\t\terr = fmt.Errorf(\"StatusCode is %v\", resp.StatusCode)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\ttype Alert struct {\r\n\t\t\tUser     string `xml:\"user_id\"`\r\n\t\t\tUserHash string `xml:\"user_hash\"`\r\n\t\t\tAddr     string `xml:\"ms>addr\"`\r\n\t\t\tPort     string `xml:\"ms>port\"`\r\n\t\t\tThread   string `xml:\"ms>thread\"`\r\n\t\t}\r\n\t\tstatus := &Alert{}\r\n\t\tdat, _ := ioutil.ReadAll(resp.Body)\r\n\t\tresp.Body.Close()\r\n\r\n\t\terr = xml.Unmarshal(dat, status)\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(string(dat))\r\n\t\t\tfmt.Printf(\"error: %v\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\traddr, e := net.ResolveTCPAddr(\"tcp\", fmt.Sprintf(\"%s:%s\", status.Addr, status.Port))\r\n\t\tif e != nil {\r\n\t\t\tfmt.Printf(\"%v\\n\", e)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tconn, e := net.DialTCP(\"tcp\", nil, raddr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer conn.Close()\r\n\r\n\t\tmsg := fmt.Sprintf(`<thread thread=\"%s\" version=\"20061206\" res_from=\"-1\"/>%c`, status.Thread, 0)\r\n\t\tif _, err = conn.Write([]byte(msg)); err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\trdr := bufio.NewReader(conn)\r\n\r\n\t\tchLatest := make(chan string, 1000)\r\n\t\tgo func() {\r\n\t\t\tfor {\r\n\t\t\t\ts, e := rdr.ReadString(0)\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\tfmt.Println(e)\r\n\t\t\t\t\terr = e\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t//fmt.Println(s)\r\n\t\t\t\tif ma := regexp.MustCompile(`>(\\d+),\\S+,\\S+<`).FindStringSubmatch(s); len(ma) > 0 {\r\n\t\t\t\tL0:\r\n\t\t\t\t\tfor {\r\n\t\t\t\t\t\tselect {\r\n\t\t\t\t\t\tcase <-chLatest:\r\n\t\t\t\t\t\tdefault:\r\n\t\t\t\t\t\t\tbreak L0\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tchLatest <- ma[1]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}()\r\n\r\n\t\tnextId = func() string {\r\n\t\tL1:\r\n\t\t\tfor {\r\n\t\t\t\tselect {\r\n\t\t\t\tcase <-chLatest:\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tbreak L1\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn <-chLatest\r\n\t\t}\r\n\r\n\t} else {\r\n\t\t// start from NicoLiveId\r\n\t\tvar id int64\r\n\t\tif ma := regexp.MustCompile(`\\Alv(\\d+)\\z`).FindStringSubmatch(opt.NicoLiveId); len(ma) > 0 {\r\n\t\t\tif id, err = strconv.ParseInt(ma[1], 10, 64); err != nil {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tfmt.Println(\"TestRun: NicoLiveId not specified\")\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tnextId = func() (s string) {\r\n\t\t\ts = fmt.Sprintf(\"%d\", id)\r\n\t\t\tid++\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\tif opt.NicoTestTimeout <= 0 {\r\n\t\topt.NicoTestTimeout = 3\r\n\t}\r\n\r\n\t//chErr := make(chan error)\r\n\tvar NFCount int\r\n\tvar endCount int\r\n\tfor {\r\n\t\topt.NicoLiveId = fmt.Sprintf(\"lv%s\", nextId())\r\n\r\n\t\tfmt.Fprintf(os.Stderr, \"start test: %s\\n\", opt.NicoLiveId)\r\n\t\tfmt.Fprintf(os.Stderr, \"# NumGoroutine: %d\\n\", runtime.NumGoroutine())\r\n\r\n\t\tvar msg string\r\n\t\t_, _, err = Record(opt)\r\n\t\tif err != nil {\r\n\t\t\tif ma := regexp.MustCompile(`\\AError\\s+code:\\s*(\\S+)`).FindStringSubmatch(err.Error()); len(ma) > 0 {\r\n\t\t\t\tmsg = ma[1]\r\n\t\t\t\tswitch ma[1] {\r\n\t\t\t\tcase \"notfound\", \"closed\", \"comingsoon\", \"timeshift_ticket_exhaust\":\r\n\t\t\t\tcase \"deletedbyuser\", \"deletedbyvisor\", \"violated\":\r\n\t\t\t\tcase \"usertimeshift\", \"tsarchive\", \"require_community_member\",\r\n\t\t\t\t\t\"noauth\", \"full\", \"premium_only\", \"selected-country\":\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"unknown: %s\\n\", ma[1])\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t} else if strings.Contains(err.Error(), \"closed network\") {\r\n\t\t\t\tmsg = \"OK\"\r\n\t\t\t} else {\r\n\t\t\t\tfmt.Fprintln(os.Stderr, err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tmsg = \"OK\"\r\n\t\t}\r\n\r\n\t\tfmt.Fprintf(os.Stderr, \"%s: %s\\n---------\\n\", opt.NicoLiveId, msg)\r\n\r\n\t\tendCount++\r\n\t\tif endCount > 100 {\r\n\t\t\tbreak\r\n\t\t}\r\n\r\n\t\tif msg == \"notfound\" {\r\n\t\t\tNFCount++\r\n\t\t} else {\r\n\t\t\tNFCount = 0\r\n\t\t}\r\n\t\tif NFCount >= 10 {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/niconico/nico_db.go",
    "content": "package niconico\r\n\r\nimport (\r\n\t\"database/sql\"\r\n\t\"fmt\"\r\n\t\"log\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\t\"strings\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/files\"\r\n)\r\n\r\nvar SelMedia = `SELECT\r\n\tseqno, bandwidth, size, data FROM media\r\n\tWHERE IFNULL(notfound, 0) == 0 AND data IS NOT NULL\r\n\tORDER BY seqno`\r\n\r\nvar SelComment = `SELECT\r\n\tvpos,\r\n\tdate,\r\n\tdate_usec,\r\n\tIFNULL(no, -1) AS no,\r\n\tIFNULL(anonymity, 0) AS anonymity,\r\n\tuser_id,\r\n\tcontent,\r\n\tIFNULL(mail, \"\") AS mail,\r\n\tIFNULL(premium, 0) AS premium,\r\n\tIFNULL(score, 0) AS score,\r\n\tthread,\r\n\tIFNULL(origin, \"\") AS origin,\r\n\tIFNULL(locale, \"\") AS locale\r\n\tFROM comment\r\n\tORDER BY date2`\r\n\r\nfunc (hls *NicoHls) dbOpen() (err error) {\r\n\tdb, err := sql.Open(\"sqlite3\", hls.dbName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\thls.db = db\r\n\r\n\t_, err = hls.db.Exec(`\r\n\t\tPRAGMA synchronous = OFF;\r\n\t\tPRAGMA journal_mode = WAL;\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\terr = hls.dbCreate()\r\n\tif err != nil {\r\n\t\thls.db.Close()\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc (hls *NicoHls) dbCreate() (err error) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\r\n\t// table media\r\n\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS media (\r\n\t\tseqno     INTEGER PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tcurrent   INTEGER,\r\n\t\tposition  REAL,\r\n\t\tnotfound  INTEGER,\r\n\t\tbandwidth INTEGER,\r\n\t\tsize      INTEGER,\r\n\t\tdata      BLOB\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS media0 ON media(seqno);\r\n\tCREATE INDEX IF NOT EXISTS media1 ON media(position);\r\n\t---- for debug ----\r\n\tCREATE INDEX IF NOT EXISTS media100 ON media(size);\r\n\tCREATE INDEX IF NOT EXISTS media101 ON media(notfound);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// table comment\r\n\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS comment (\r\n\t\tvpos      INTEGER NOT NULL,\r\n\t\tdate      INTEGER NOT NULL,\r\n\t\tdate_usec INTEGER NOT NULL,\r\n\t\tdate2     INTEGER NOT NULL,\r\n\t\tno        INTEGER,\r\n\t\tanonymity INTEGER,\r\n\t\tuser_id   TEXT NOT NULL,\r\n\t\tcontent   TEXT NOT NULL,\r\n\t\tmail      TEXT,\r\n\t\tpremium   INTEGER,\r\n\t\tscore     INTEGER,\r\n\t\tthread    TEXT,\r\n\t\torigin    TEXT,\r\n\t\tlocale    TEXT,\r\n\t\thash      TEXT UNIQUE NOT NULL\r\n\t)`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS comment0 ON comment(hash);\r\n\t---- for debug ----\r\n\tCREATE INDEX IF NOT EXISTS comment100 ON comment(date2);\r\n\tCREATE INDEX IF NOT EXISTS comment101 ON comment(no);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// kvs media\r\n\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS kvs (\r\n\t\tk TEXT PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tv BLOB\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\t_, err = hls.db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS kvs0 ON kvs(k);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t//hls.__dbBegin()\r\n\r\n\treturn\r\n}\r\n\r\n// timeshift\r\nfunc (hls *NicoHls) dbSetPosition() {\r\n\thls.dbExec(`UPDATE media SET position = ? WHERE seqno=?`,\r\n\t\thls.playlist.position,\r\n\t\thls.playlist.seqNo,\r\n\t)\r\n}\r\n\r\n// timeshift\r\nfunc (hls *NicoHls) dbGetLastPosition() (res float64) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\r\n\thls.db.QueryRow(\"SELECT position FROM media ORDER BY POSITION DESC LIMIT 1\").Scan(&res)\r\n\treturn\r\n}\r\n\r\n//func (hls *NicoHls) __dbBegin() {\r\n//\treturn\r\n///////////////////////////////////////////\r\n//hls.db.Exec(`BEGIN TRANSACTION`)\r\n//}\r\n//func (hls *NicoHls) __dbCommit(t time.Time) {\r\n//\treturn\r\n///////////////////////////////////////////\r\n\r\n//// Never hls.dbMtx.Lock()\r\n//var start int64\r\n//hls.db.Exec(`COMMIT; BEGIN TRANSACTION`)\r\n//if t.UnixNano() - hls.lastCommit.UnixNano() > 500000000 {\r\n//\tlog.Printf(\"Commit: %s\\n\", hls.dbName)\r\n//}\r\n//hls.lastCommit = t\r\n//}\r\nfunc (hls *NicoHls) dbCommit() {\r\n\t//\thls.dbMtx.Lock()\r\n\t//\tdefer hls.dbMtx.Unlock()\r\n\r\n\t//\thls.__dbCommit(time.Now())\r\n}\r\nfunc (hls *NicoHls) dbExec(query string, args ...interface{}) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN]dbExec: %d(ms):%s\\n\", debug_Now(), t, query)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\tif _, err := hls.db.Exec(query, args...); err != nil {\r\n\t\tfmt.Printf(\"dbExec %#v\\n\", err)\r\n\t\t//hls.db.Exec(\"COMMIT\")\r\n\t\thls.db.Close()\r\n\t\tos.Exit(1)\r\n\t}\r\n}\r\n\r\nfunc (hls *NicoHls) dbKVSet(k string, v interface{}) {\r\n\tquery := `INSERT OR REPLACE INTO kvs (k,v) VALUES (?,?)`\r\n\thls.startDBGoroutine(func(sig <-chan struct{}) int {\r\n\t\thls.dbExec(query, k, v)\r\n\t\treturn OK\r\n\t})\r\n}\r\n\r\nfunc (hls *NicoHls) dbInsertReplaceOrIgnore(table string, data map[string]interface{}, replace bool) {\r\n\tvar keys []string\r\n\tvar qs []string\r\n\tvar args []interface{}\r\n\r\n\tfor k, v := range data {\r\n\t\tkeys = append(keys, k)\r\n\t\tqs = append(qs, \"?\")\r\n\t\targs = append(args, v)\r\n\t}\r\n\r\n\tvar replaceOrIgnore string\r\n\tif replace {\r\n\t\treplaceOrIgnore = \"REPLACE\"\r\n\t} else {\r\n\t\treplaceOrIgnore = \"IGNORE\"\r\n\t}\r\n\r\n\tquery := fmt.Sprintf(\r\n\t\t`INSERT OR %s INTO %s (%s) VALUES (%s)`,\r\n\t\treplaceOrIgnore,\r\n\t\ttable,\r\n\t\tstrings.Join(keys, \",\"),\r\n\t\tstrings.Join(qs, \",\"),\r\n\t)\r\n\r\n\thls.startDBGoroutine(func(sig <-chan struct{}) int {\r\n\t\thls.dbExec(query, args...)\r\n\t\treturn OK\r\n\t})\r\n}\r\n\r\nfunc (hls *NicoHls) dbInsert(table string, data map[string]interface{}) {\r\n\thls.dbInsertReplaceOrIgnore(table, data, false)\r\n}\r\nfunc (hls *NicoHls) dbReplace(table string, data map[string]interface{}) {\r\n\thls.dbInsertReplaceOrIgnore(table, data, true)\r\n}\r\n\r\n// timeshift\r\nfunc (hls *NicoHls) dbGetFromWhen() (res_from int, when float64) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\tvar date2 int64\r\n\tvar no int\r\n\r\n\thls.db.QueryRow(\"SELECT date2, no FROM comment ORDER BY date2 ASC LIMIT 1\").Scan(&date2, &no)\r\n\tres_from = no\r\n\tif res_from <= 0 {\r\n\t\tres_from = 1\r\n\t}\r\n\r\n\tif date2 == 0 {\r\n\t\tvar endTime float64\r\n\t\thls.db.QueryRow(`SELECT v FROM kvs WHERE k = \"endTime\"`).Scan(&endTime)\r\n\r\n\t\twhen = endTime\r\n\t} else {\r\n\t\twhen = float64(date2) / (1000 * 1000)\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc WriteComment(db *sql.DB, fileName string, skipHb bool) {\r\n\r\n\trows, err := db.Query(SelComment)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\treturn\r\n\t}\r\n\tdefer rows.Close()\r\n\r\n\tfileName = files.ChangeExtention(fileName, \"xml\")\r\n\r\n\tdir := filepath.Dir(fileName)\r\n\tbase := filepath.Base(fileName)\r\n\tbase, err = files.GetFileNameNext(base)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n\tfileName = filepath.Join(dir, base)\r\n\tf, err := os.Create(fileName)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\tdefer f.Close()\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `<?xml version=\"1.0\" encoding=\"UTF-8\"?>`)\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `<packet>`)\r\n\r\n\tfor rows.Next() {\r\n\t\tvar vpos int64\r\n\t\tvar date int64\r\n\t\tvar date_usec int64\r\n\t\tvar no int64\r\n\t\tvar anonymity int64\r\n\t\tvar user_id string\r\n\t\tvar content string\r\n\t\tvar mail string\r\n\t\tvar premium int64\r\n\t\tvar score int64\r\n\t\tvar thread string\r\n\t\tvar origin string\r\n\t\tvar locale string\r\n\t\terr = rows.Scan(\r\n\t\t\t&vpos,\r\n\t\t\t&date,\r\n\t\t\t&date_usec,\r\n\t\t\t&no,\r\n\t\t\t&anonymity,\r\n\t\t\t&user_id,\r\n\t\t\t&content,\r\n\t\t\t&mail,\r\n\t\t\t&premium,\r\n\t\t\t&score,\r\n\t\t\t&thread,\r\n\t\t\t&origin,\r\n\t\t\t&locale,\r\n\t\t)\r\n\t\tif err != nil {\r\n\t\t\tlog.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// skip /hb\r\n\t\tif (premium > 1) && skipHb && strings.HasPrefix(content, \"/hb \") {\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\tif vpos < 0 {\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\tline := fmt.Sprintf(\r\n\t\t\t`<chat thread=\"%s\" vpos=\"%d\" date=\"%d\" date_usec=\"%d\" user_id=\"%s\"`,\r\n\t\t\tthread,\r\n\t\t\tvpos,\r\n\t\t\tdate,\r\n\t\t\tdate_usec,\r\n\t\t\tuser_id,\r\n\t\t)\r\n\r\n\t\tif no >= 0 {\r\n\t\t\tline += fmt.Sprintf(` no=\"%d\"`, no)\r\n\t\t}\r\n\t\tif anonymity != 0 {\r\n\t\t\tline += fmt.Sprintf(` anonymity=\"%d\"`, anonymity)\r\n\t\t}\r\n\t\tif mail != \"\" {\r\n\t\t\tmail = strings.Replace(mail, `\"`, \"&quot;\", -1)\r\n\t\t\tmail = strings.Replace(mail, \"&\", \"&amp;\", -1)\r\n\t\t\tmail = strings.Replace(mail, \"<\", \"&lt;\", -1)\r\n\t\t\tline += fmt.Sprintf(` mail=\"%s\"`, mail)\r\n\t\t}\r\n\t\tif origin != \"\" {\r\n\t\t\torigin = strings.Replace(origin, `\"`, \"&quot;\", -1)\r\n\t\t\torigin = strings.Replace(origin, \"&\", \"&amp;\", -1)\r\n\t\t\torigin = strings.Replace(origin, \"<\", \"&lt;\", -1)\r\n\t\t\tline += fmt.Sprintf(` origin=\"%s\"`, origin)\r\n\t\t}\r\n\t\tif premium != 0 {\r\n\t\t\tline += fmt.Sprintf(` premium=\"%d\"`, premium)\r\n\t\t}\r\n\t\tif score != 0 {\r\n\t\t\tline += fmt.Sprintf(` score=\"%d\"`, score)\r\n\t\t}\r\n\t\tif locale != \"\" {\r\n\t\t\tlocale = strings.Replace(locale, `\"`, \"&quot;\", -1)\r\n\t\t\tlocale = strings.Replace(locale, \"&\", \"&amp;\", -1)\r\n\t\t\tlocale = strings.Replace(locale, \"<\", \"&lt;\", -1)\r\n\t\t\tline += fmt.Sprintf(` locale=\"%s\"`, locale)\r\n\t\t}\r\n\t\tline += \">\"\r\n\t\tcontent = strings.Replace(content, \"&\", \"&amp;\", -1)\r\n\t\tcontent = strings.Replace(content, \"<\", \"&lt;\", -1)\r\n\t\tline += content\r\n\t\tline += \"</chat>\"\r\n\t\tfmt.Fprintf(f, \"%s\\r\\n\", line)\r\n\t}\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `</packet>`)\r\n}\r\n\r\n// ts\r\nfunc (hls *NicoHls) dbGetLastMedia(i int) (res []byte) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\thls.db.QueryRow(\"SELECT data FROM media WHERE seqno = ?\", i).Scan(&res)\r\n\treturn\r\n}\r\nfunc (hls *NicoHls) dbGetLastSeqNo() (res int64) {\r\n\thls.dbMtx.Lock()\r\n\tdefer hls.dbMtx.Unlock()\r\n\thls.db.QueryRow(\"SELECT seqno FROM media ORDER BY seqno DESC LIMIT 1\").Scan(&res)\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/niconico/nico_hls.go",
    "content": "package niconico\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"html\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"math\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/himananiito/livedl/files\"\n\t\"github.com/himananiito/livedl/gorman\"\n\t\"github.com/himananiito/livedl/httpbase\"\n\t\"github.com/himananiito/livedl/objs\"\n\t\"github.com/himananiito/livedl/options\"\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"golang.org/x/crypto/sha3\"\n)\n\ntype OBJ = map[string]interface{}\n\ntype playlist struct {\n\turi                *url.URL\n\turiMaster          *url.URL\n\turiTimeshiftMaster *url.URL\n\tbandwidth          int\n\tnextTime           time.Time\n\tformat             string\n\twithoutFormat      bool\n\tseqNo              int\n\tposition           float64\n}\ntype NicoHls struct {\n\twsapi int\n\n\tstartDelay int\n\tplaylist   playlist\n\n\tnicoliveProgramId string\n\twebSocketUrl      string\n\tmyUserId          string\n\n\tcommentStarted    bool\n\tmtxCommentStarted sync.Mutex\n\n\tchInterrupt  chan os.Signal\n\tnInterrupt   int\n\tmtxInterrupt sync.Mutex\n\n\tmtxRestart  sync.Mutex\n\trestartMain bool\n\tquality     string\n\n\terrNumChunk   int\n\terrRestartCnt int\n\n\tdbName     string\n\tdb         *sql.DB\n\tdbMtx      sync.Mutex\n\tlastCommit time.Time\n\n\tisTimeshift        bool\n\ttimeshiftStart     float64\n\tfastTimeshift      bool\n\tultrafastTimeshift bool\n\n\tfastTimeshiftOrig      bool\n\tultrafastTimeshiftOrig bool\n\n\tfinish      bool\n\tcommentDone bool\n\n\tNicoSession string\n\tlimitBw     int\n\tlimitBwOrig int\n\n\tnicoDebug     bool\n\tmsgErrorCount int\n\tmsgErrorSeqNo int\n\tmemdb         *sql.DB\n\tmemdbMtx      sync.Mutex\n\tseqNo500      int\n\tcnt500        int\n\tbw500         int\n\n\tmtxWg sync.Mutex\n\n\tgmPlst *gorman.GoroutineManager\n\tgmCmnt *gorman.GoroutineManager\n\tgmDB   *gorman.GoroutineManager\n\tgmMain *gorman.GoroutineManager\n}\n\nfunc debug_Now() string {\n\treturn time.Now().Format(\"2006/01/02-15:04:05\")\n}\nfunc NewHls(opt options.Option, prop map[string]interface{}) (hls *NicoHls, err error) {\n\n\tnicoliveProgramId, ok := prop[\"nicoliveProgramId\"].(string)\n\tif !ok {\n\t\terr = fmt.Errorf(\"nicoliveProgramId is not string\")\n\t\treturn\n\t}\n\n\twebSocketUrl, ok := prop[\"//webSocketUrl\"].(string)\n\tif !ok {\n\t\terr = fmt.Errorf(\"webSocketUrl is not string\")\n\t\treturn\n\t}\n\n\twsapi := 2\n\tif m := regexp.MustCompile(`/wsapi/v1/`).FindStringSubmatch(webSocketUrl); len(m) > 0 {\n\t\twsapi = 1\n\t\tlog.Println(\"wsapi: 1\")\n\t}\n\n\tmyUserId, _ := prop[\"//myId\"].(string)\n\tif myUserId == \"\" {\n\t\tmyUserId = \"NaN\"\n\t}\n\n\tvar timeshift bool\n\tif status, ok := prop[\"status\"].(string); ok && status == \"ENDED\" {\n\t\ttimeshift = true\n\t}\n\n\tif wsapi == 2 && false && !timeshift {\n\t\tif m := regexp.MustCompile(`/watch/([^?]+)`).FindStringSubmatch(webSocketUrl); len(m) > 0 {\n\t\t\tnicoliveProgramId = m[1]\n\t\t}\n\t\twebSocketUrl = strings.Replace(webSocketUrl, \"/wsapi/v2/\", \"/wsapi/v1/\", 1)\n\t\twsapi = 1\n\t\tlog.Println(\"wsapi: 1\")\n\t}\n\n\tvar pid string\n\tif nicoliveProgramId, ok := prop[\"nicoliveProgramId\"]; ok {\n\t\tpid, _ = nicoliveProgramId.(string)\n\t}\n\n\tvar uname string // ユーザ名\n\tvar uid string   // ユーザID\n\tvar cname string // コミュ名 or チャンネル名\n\tvar cid string   // コミュID or チャンネルID\n\n\tvar pt string\n\tif providerType, ok := prop[\"providerType\"]; ok {\n\t\tif pt, ok = providerType.(string); ok {\n\t\t\tif pt == \"official\" {\n\t\t\t\tuname = \"official\"\n\t\t\t\tuid = \"official\"\n\t\t\t\tcname = \"official\"\n\t\t\t\tcid = \"official\"\n\t\t\t}\n\t\t}\n\t}\n\n\t// ユーザ名\n\tif userName, ok := prop[\"userName\"]; ok {\n\t\tuname, _ = userName.(string)\n\t}\n\n\t// ユーザID\n\tif userPageUrl, ok := prop[\"userPageUrl\"]; ok {\n\t\tif u, ok := userPageUrl.(string); ok {\n\t\t\tif m := regexp.MustCompile(`/user/(\\d+)`).FindStringSubmatch(u); len(m) > 0 {\n\t\t\t\tuid = m[1]\n\t\t\t\tprop[\"userId\"] = uid\n\t\t\t}\n\t\t}\n\t}\n\tif uid == \"\" && pt == \"channel\" {\n\t\tuid = \"channel\"\n\t}\n\n\t// コミュ名\n\tif socName, ok := prop[\"socName\"]; ok {\n\t\tcname, _ = socName.(string)\n\t}\n\n\t// コミュID\n\tif comId, ok := prop[\"comId\"]; ok {\n\t\tcid, _ = comId.(string)\n\t}\n\tif cid == \"\" {\n\t\tif socId, ok := prop[\"socId\"]; ok {\n\t\t\tcid, _ = socId.(string)\n\t\t}\n\t}\n\n\tvar title string\n\tif t, ok := prop[\"title\"]; ok {\n\t\ttitle, _ = t.(string)\n\t}\n\n\tvar beginTime int64\n\tif t, ok := prop[\"beginTime\"]; ok {\n\t\tif bt, ok := t.(float64); ok {\n\t\t\tbeginTime = int64(bt)\n\t\t}\n\t}\n\ttBegin := time.Unix(beginTime, 0)\n\tsYear := fmt.Sprintf(\"%04d\", tBegin.Year())\n\tsMonth := fmt.Sprintf(\"%02d\", tBegin.Month())\n\tsDay := fmt.Sprintf(\"%02d\", tBegin.Day())\n\tsDay8 := fmt.Sprintf(\"%04d%02d%02d\", tBegin.Year(), tBegin.Month(), tBegin.Day())\n\tsDay6 := fmt.Sprintf(\"%02d%02d%02d\", tBegin.Year()%100, tBegin.Month(), tBegin.Day())\n\tsHour := fmt.Sprintf(\"%02d\", tBegin.Hour())\n\tsMinute := fmt.Sprintf(\"%02d\", tBegin.Minute())\n\tsSecond := fmt.Sprintf(\"%02d\", tBegin.Second())\n\tsTime6 := fmt.Sprintf(\"%02d%02d%02d\", tBegin.Hour(), tBegin.Minute(), tBegin.Second())\n\tsTime4 := fmt.Sprintf(\"%02d%02d\", tBegin.Hour(), tBegin.Minute())\n\n\t// \"${PID}-${UNAME}-${TITLE}\"\n\tdbName := opt.NicoFormat\n\tdbName = strings.Replace(dbName, \"?PID?\", files.ReplaceForbidden(pid), -1)\n\tdbName = strings.Replace(dbName, \"?UNAME?\", files.ReplaceForbidden(uname), -1)\n\tdbName = strings.Replace(dbName, \"?UID?\", files.ReplaceForbidden(uid), -1)\n\tdbName = strings.Replace(dbName, \"?CNAME?\", files.ReplaceForbidden(cname), -1)\n\tdbName = strings.Replace(dbName, \"?CID?\", files.ReplaceForbidden(cid), -1)\n\tdbName = strings.Replace(dbName, \"?TITLE?\", files.ReplaceForbidden(title), -1)\n\t// date,time\n\tdbName = strings.Replace(dbName, \"?YEAR?\", sYear, -1)\n\tdbName = strings.Replace(dbName, \"?MONTH?\", sMonth, -1)\n\tdbName = strings.Replace(dbName, \"?DAY?\", sDay, -1)\n\tdbName = strings.Replace(dbName, \"?DAY8?\", sDay8, -1)\n\tdbName = strings.Replace(dbName, \"?DAY6?\", sDay6, -1)\n\tdbName = strings.Replace(dbName, \"?HOUR?\", sHour, -1)\n\tdbName = strings.Replace(dbName, \"?MINUTE?\", sMinute, -1)\n\tdbName = strings.Replace(dbName, \"?SECOND?\", sSecond, -1)\n\tdbName = strings.Replace(dbName, \"?TIME6?\", sTime6, -1)\n\tdbName = strings.Replace(dbName, \"?TIME4?\", sTime4, -1)\n\n\tif timeshift {\n\t\tdbName = dbName + \"(TS)\"\n\t}\n\tdbName = dbName + \".sqlite3\"\n\n\tfiles.MkdirByFileName(dbName)\n\n\thls = &NicoHls{\n\t\twsapi: wsapi,\n\n\t\tnicoliveProgramId: nicoliveProgramId,\n\t\twebSocketUrl:      webSocketUrl,\n\t\tmyUserId:          myUserId,\n\n\t\tquality: \"abr\",\n\t\tdbName:  dbName,\n\n\t\tisTimeshift:        timeshift,\n\t\tfastTimeshift:      opt.NicoFastTs || opt.NicoUltraFastTs,\n\t\tultrafastTimeshift: opt.NicoUltraFastTs,\n\n\t\tNicoSession: opt.NicoSession,\n\t\tlimitBw:     opt.NicoLimitBw,\n\t\tlimitBwOrig: opt.NicoLimitBw,\n\t\tnicoDebug:   opt.NicoDebug,\n\n\t\tgmPlst: gorman.WithChecker(func(c int) { hls.checkReturnCode(c) }),\n\t\tgmCmnt: gorman.WithChecker(func(c int) { hls.checkReturnCode(c) }),\n\t\tgmDB:   gorman.WithChecker(func(c int) { hls.checkReturnCode(c) }),\n\t\tgmMain: gorman.WithChecker(func(c int) { hls.checkReturnCode(c) }),\n\n\t\ttimeshiftStart: opt.NicoTsStart,\n\t}\n\n\thls.fastTimeshiftOrig = hls.fastTimeshift\n\thls.ultrafastTimeshiftOrig = hls.ultrafastTimeshift\n\n\tfor i := 0; i < 2; i++ {\n\t\terr := hls.dbOpen()\n\t\tif err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"able to open\") {\n\t\t\t\tlog.Fatalln(err)\n\t\t\t}\n\t\t} else if _, err := os.Stat(hls.dbName); err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tfmt.Printf(\"can't open: %s\\n\", hls.dbName)\n\t\thls.dbName = fmt.Sprintf(\"%s.sqlite3\", pid)\n\t}\n\n\tif err := hls.memdbOpen(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\n\t// 放送情報をdbに入れる。自身のユーザ情報は入れない\n\t// dbに入れたくないデータはキーの先頭を//としている\n\tfor k, v := range prop {\n\t\tif !strings.HasPrefix(k, \"//\") {\n\t\t\thls.dbKVSet(k, v)\n\t\t}\n\t}\n\n\treturn\n}\nfunc (hls *NicoHls) Close() {\n\thls.dbCommit()\n\tif hls.db != nil {\n\t\thls.db.Close()\n\t}\n\tif hls.memdb != nil {\n\t\thls.memdb.Close()\n\t}\n}\n\n// Comment method\n\nfunc (hls *NicoHls) commentHandler(tag string, attr interface{}) (err error) {\n\tattrMap, ok := attr.(map[string]interface{})\n\tif !ok {\n\t\terr = fmt.Errorf(\"[FIXME] commentHandler: not a map: %#v\", attr)\n\t\treturn\n\t}\n\t//fmt.Printf(\"%#v\\n\", attrMap)\n\tif vpos_f, ok := attrMap[\"vpos\"].(float64); ok {\n\t\tvpos := int64(vpos_f)\n\t\tvar date int64\n\t\tif d, ok := attrMap[\"date\"].(float64); ok {\n\t\t\tdate = int64(d)\n\t\t}\n\t\tvar date_usec int64\n\t\tif d, ok := attrMap[\"date_usec\"].(float64); ok {\n\t\t\tdate_usec = int64(d)\n\t\t}\n\t\tdate2 := (date * 1000 * 1000) + date_usec\n\t\tvar user_id string\n\t\tif s, ok := attrMap[\"user_id\"].(string); ok {\n\t\t\tuser_id = s\n\t\t}\n\t\tvar content string\n\t\tif s, ok := attrMap[\"content\"].(string); ok {\n\t\t\tcontent = s\n\t\t}\n\t\tcalc_s := fmt.Sprintf(\"%d,%d,%d,%s,%s\", vpos, date, date_usec, user_id, content)\n\t\thash := fmt.Sprintf(\"%x\", sha3.Sum256([]byte(calc_s)))\n\n\t\tvar thread string\n\t\tif d, ok := attrMap[\"thread\"].(float64); ok {\n\t\t\tthread = fmt.Sprintf(\"%.f\", d)\n\t\t} else if s, ok := attrMap[\"thread\"].(string); ok {\n\t\t\tthread = s\n\t\t}\n\n\t\thls.dbInsert(\"comment\", map[string]interface{}{\n\t\t\t\"vpos\":      attrMap[\"vpos\"],\n\t\t\t\"date\":      attrMap[\"date\"],\n\t\t\t\"date_usec\": attrMap[\"date_usec\"],\n\t\t\t\"date2\":     date2,\n\t\t\t\"no\":        attrMap[\"no\"],\n\t\t\t\"anonymity\": attrMap[\"anonymity\"],\n\t\t\t\"user_id\":   attrMap[\"user_id\"],\n\t\t\t\"content\":   attrMap[\"content\"],\n\t\t\t\"mail\":      attrMap[\"mail\"],\n\t\t\t\"premium\":   attrMap[\"premium\"],\n\t\t\t\"score\":     attrMap[\"score\"],\n\t\t\t\"thread\":    thread,\n\t\t\t\"origin\":    attrMap[\"origin\"],\n\t\t\t\"locale\":    attrMap[\"locale\"],\n\t\t\t\"hash\":      hash,\n\t\t})\n\t} else {\n\t\tif d, ok := attrMap[\"thread\"].(float64); ok {\n\t\t\thls.dbKVSet(\"comment/thread\", fmt.Sprintf(\"%.f\", d))\n\t\t} else if s, ok := attrMap[\"thread\"].(string); ok {\n\t\t\thls.dbKVSet(\"comment/thread\", s)\n\t\t}\n\t}\n\n\treturn\n}\n\n// return code\nconst (\n\tOK = iota\n\tINTERRUPT\n\tMAIN_WS_ERROR\n\tMAIN_DISCONNECT\n\tMAIN_END_PROGRAM\n\tMAIN_INVALID_STREAM_QUALITY\n\tMAIN_TEMPORARILY_ERROR\n\tPLAYLIST_END\n\tPLAYLIST_403\n\tPLAYLIST_ERROR\n\tDELAY\n\tCOMMENT_WS_ERROR\n\tCOMMENT_SAVE_ERROR\n\tCOMMENT_DONE\n\tGOT_SIGNAL\n\tERROR_SHUTDOWN\n\tNETWORK_ERROR\n)\n\nfunc (hls *NicoHls) stopPCGoroutines() {\n\thls.stopPGoroutines()\n\thls.stopCGoroutines()\n}\nfunc (hls *NicoHls) stopAllGoroutines() {\n\thls.stopPGoroutines()\n\thls.stopCGoroutines()\n\thls.stopMGoroutines()\n}\nfunc (hls *NicoHls) stopPGoroutines() {\n\thls.gmPlst.Cancel()\n}\nfunc (hls *NicoHls) stopCGoroutines() {\n\thls.gmCmnt.Cancel()\n}\nfunc (hls *NicoHls) stopMGoroutines() {\n\thls.gmMain.Cancel()\n}\nfunc (hls *NicoHls) working() bool {\n\treturn hls.gmPlst.Count() > 0 || hls.gmCmnt.Count() > 0 || hls.gmDB.Count() > 0\n}\n\nfunc (hls *NicoHls) stopInterrupt() {\n\tif hls.chInterrupt != nil {\n\t\tsignal.Stop(hls.chInterrupt)\n\t}\n}\nfunc (hls *NicoHls) startInterrupt() {\n\tif hls.chInterrupt == nil {\n\t\thls.chInterrupt = make(chan os.Signal, 10)\n\t\tsignal.Notify(hls.chInterrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)\n\t}\n\n\thls.startMGoroutine(func(sig <-chan struct{}) int {\n\t\tselect {\n\t\tcase <-hls.chInterrupt:\n\t\t\thls.IncrInterrupt()\n\t\t\tfmt.Printf(\"Interrupt count: %d\\n\", hls.nInterrupt)\n\t\t\tgo func() {\n\t\t\t\thls.dbCommit()\n\t\t\t}()\n\t\t\tif hls.nInterrupt >= 2 {\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t\treturn INTERRUPT\n\t\tcase <-sig:\n\t\t\treturn GOT_SIGNAL\n\t\t}\n\t})\n}\nfunc (hls *NicoHls) IncrInterrupt() {\n\thls.mtxInterrupt.Lock()\n\tdefer hls.mtxInterrupt.Unlock()\n\thls.nInterrupt++\n}\nfunc (hls *NicoHls) interrupted() bool {\n\thls.mtxInterrupt.Lock()\n\tdefer hls.mtxInterrupt.Unlock()\n\treturn hls.nInterrupt != 0\n}\n\nfunc (hls *NicoHls) getStartDelay() int {\n\thls.mtxRestart.Lock()\n\tdefer hls.mtxRestart.Unlock()\n\n\treturn hls.startDelay\n}\nfunc (hls *NicoHls) markRestartMain(delay int) {\n\thls.mtxRestart.Lock()\n\tdefer hls.mtxRestart.Unlock()\n\n\tif (!hls.restartMain) && (!hls.finish) {\n\t\thls.startDelay = delay\n\t\thls.restartMain = true\n\t}\n}\nfunc (hls *NicoHls) checkReturnCode(code int) {\n\t// NEVER restart goroutines here except interrupt handler\n\tswitch code {\n\tcase NETWORK_ERROR, MAIN_TEMPORARILY_ERROR:\n\t\tdelay := hls.getStartDelay()\n\t\tif delay < 1 {\n\t\t\thls.markRestartMain(1)\n\t\t} else if delay < 3 {\n\t\t\thls.markRestartMain(3)\n\t\t} else if delay < 13 {\n\t\t\t// if 3,4,5..12\n\t\t\thls.markRestartMain(delay + 1)\n\t\t} else {\n\t\t\thls.markRestartMain(60)\n\t\t}\n\t\thls.stopPCGoroutines()\n\n\tcase DELAY:\n\t\t//log.Println(\"delay\")\n\tcase PLAYLIST_403:\n\t\t// 番組終了時、websocketでEND_PROGRAMが来るよりも先にこうなるが、\n\t\t// END_PROGRAMを受信するにはwebsocketの再接続が必要\n\t\t//log.Println(\"403\")\n\t\tif !hls.interrupted() {\n\t\t\thls.markRestartMain(0)\n\t\t}\n\t\thls.stopPGoroutines()\n\n\tcase PLAYLIST_END:\n\t\tfmt.Println(\"playlist end.\")\n\t\thls.finish = true\n\t\tif hls.isTimeshift {\n\t\t\tif hls.commentDone {\n\t\t\t\thls.stopPCGoroutines()\n\t\t\t} else if !hls.getCommentStarted() {\n\t\t\t\thls.stopPCGoroutines()\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"waiting comment\")\n\t\t\t}\n\t\t} else {\n\t\t\thls.stopPCGoroutines()\n\t\t}\n\n\tcase MAIN_WS_ERROR:\n\t\thls.stopPGoroutines()\n\n\tcase MAIN_DISCONNECT:\n\t\thls.stopPCGoroutines()\n\n\tcase MAIN_END_PROGRAM:\n\t\thls.finish = true\n\t\thls.stopPCGoroutines()\n\n\tcase MAIN_INVALID_STREAM_QUALITY:\n\t\thls.markRestartMain(0)\n\t\thls.stopPGoroutines()\n\n\tcase PLAYLIST_ERROR:\n\t\thls.stopPCGoroutines()\n\n\tcase COMMENT_WS_ERROR:\n\t\t//log.Println(\"comment websocket error\")\n\t\thls.stopCGoroutines()\n\n\tcase COMMENT_SAVE_ERROR:\n\t\t//log.Println(\"comment save error\")\n\t\thls.stopCGoroutines()\n\n\tcase INTERRUPT:\n\t\thls.startInterrupt()\n\t\thls.stopPCGoroutines()\n\n\tcase ERROR_SHUTDOWN:\n\t\thls.stopPCGoroutines()\n\n\tcase COMMENT_DONE:\n\t\thls.commentDone = true\n\t\tif hls.finish {\n\t\t\thls.stopPCGoroutines()\n\t\t}\n\n\tcase OK:\n\t}\n}\n\n// Of playlist\nfunc (hls *NicoHls) startPGoroutine(f func(<-chan struct{}) int) {\n\tif !hls.interrupted() {\n\t\thls.gmPlst.Go(f)\n\t}\n}\n\n// Of comment\nfunc (hls *NicoHls) startCGoroutine(f func(<-chan struct{}) int) {\n\tif !hls.interrupted() {\n\t\thls.gmCmnt.Go(f)\n\t}\n}\n\n// Of DB\nfunc (hls *NicoHls) startDBGoroutine(f func(<-chan struct{}) int) {\n\tif !hls.interrupted() {\n\t\thls.gmDB.Go(f)\n\t}\n}\n\n// Of main\nfunc (hls *NicoHls) startMGoroutine(f func(<-chan struct{}) int) {\n\thls.gmMain.Go(f)\n}\n\nfunc (hls *NicoHls) waitRestartMain() bool {\n\tpc, _, _, ok := runtime.Caller(1)\n\tif ok {\n\t\tfn := runtime.FuncForPC(pc)\n\t\tif !strings.HasSuffix(fn.Name(), \".Wait\") {\n\t\t\tlog.Printf(\"[FIXME] Don't call waitRestartMain from %s\\n\", fn.Name())\n\t\t}\n\t}\n\n\thls.waitPGoroutines()\n\n\thls.mtxRestart.Lock()\n\tdefer hls.mtxRestart.Unlock()\n\tif hls.restartMain {\n\t\thls.restartMain = false\n\t\t//hls.wgPlaylist = &sync.WaitGroup{}\n\t\thls.startMain()\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (hls *NicoHls) waitPGoroutines() {\n\thls.gmPlst.Wait()\n}\nfunc (hls *NicoHls) waitCGoroutines() {\n\thls.gmCmnt.Wait()\n}\nfunc (hls *NicoHls) waitDBGoroutines() {\n\thls.gmDB.Wait()\n}\nfunc (hls *NicoHls) waitMGoroutines() {\n\thls.gmMain.Wait()\n}\nfunc (hls *NicoHls) waitAllGoroutines() {\n\thls.waitPGoroutines()\n\thls.waitCGoroutines()\n\thls.waitDBGoroutines()\n\thls.waitMGoroutines()\n}\n\nfunc (hls *NicoHls) getwaybackkey(threadId string) (waybackkey string, neterr, err error) {\n\n\turi := fmt.Sprintf(\"https://live.nicovideo.jp/api/getwaybackkey?thread=%s\", url.QueryEscape(threadId))\n\tresp, err, neterr := httpbase.Get(uri, map[string]string{\"Cookie\": \"user_session=\" + hls.NicoSession})\n\tif err != nil {\n\t\treturn\n\t}\n\tif neterr != nil {\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tdat, neterr := ioutil.ReadAll(resp.Body)\n\tif neterr != nil {\n\t\treturn\n\t}\n\n\twaybackkey = strings.TrimPrefix(string(dat), \"waybackkey=\")\n\treturn\n}\nfunc (hls *NicoHls) getTsCommentFromWhen() (res_from int, when float64) {\n\treturn hls.dbGetFromWhen()\n}\n\nfunc (hls *NicoHls) setCommentStarted(val bool) {\n\thls.mtxCommentStarted.Lock()\n\tdefer hls.mtxCommentStarted.Unlock()\n\thls.commentStarted = val\n}\nfunc (hls *NicoHls) getCommentStarted() bool {\n\thls.mtxCommentStarted.Lock()\n\tdefer hls.mtxCommentStarted.Unlock()\n\treturn hls.commentStarted\n}\nfunc (hls *NicoHls) startComment(messageServerUri, threadId, waybackkey string) {\n\tif (!hls.getCommentStarted()) && (!hls.commentDone) {\n\t\thls.setCommentStarted(true)\n\n\t\thls.startCGoroutine(func(sig <-chan struct{}) int {\n\t\t\tdefer func() {\n\t\t\t\thls.setCommentStarted(false)\n\t\t\t}()\n\n\t\t\tvar err error\n\n\t\t\t// here blocks several seconds\n\t\t\tconn, _, err := websocket.DefaultDialer.Dial(\n\t\t\t\tmessageServerUri,\n\t\t\t\tmap[string][]string{\n\t\t\t\t\t\"Origin\":                 []string{\"https://live2.nicovideo.jp\"},\n\t\t\t\t\t\"Sec-WebSocket-Protocol\": []string{\"msg.nicovideo.jp#json\"},\n\t\t\t\t\t\"User-Agent\":             []string{httpbase.GetUserAgent()},\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\tlog.Println(\"comment connect:\", err)\n\t\t\t\t}\n\t\t\t\treturn COMMENT_WS_ERROR\n\t\t\t}\n\t\t\tvar wsMtx sync.Mutex\n\t\t\twriteJson := func(d interface{}) error {\n\t\t\t\twsMtx.Lock()\n\t\t\t\tdefer wsMtx.Unlock()\n\t\t\t\treturn conn.WriteJSON(d)\n\t\t\t}\n\n\t\t\thls.startCGoroutine(func(sig <-chan struct{}) int {\n\t\t\t\t<-sig\n\t\t\t\tif conn != nil {\n\t\t\t\t\tconn.Close()\n\t\t\t\t}\n\t\t\t\treturn OK\n\t\t\t})\n\n\t\t\thls.startCGoroutine(func(sig <-chan struct{}) int {\n\t\t\t\tfor !hls.interrupted() {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-time.After(60 * time.Second):\n\t\t\t\t\t\tif conn != nil {\n\t\t\t\t\t\t\tif err := writeJson(\"\"); err != nil {\n\t\t\t\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\t\t\t\tlog.Println(\"comment send null:\", err)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn COMMENT_WS_ERROR\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn OK\n\t\t\t\t\t\t}\n\t\t\t\t\tcase <-sig:\n\t\t\t\t\t\treturn GOT_SIGNAL\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn OK\n\t\t\t})\n\n\t\t\tvar mtxChatTime sync.Mutex\n\t\t\tvar _chatCount int64\n\t\t\tincChatCount := func() {\n\t\t\t\tmtxChatTime.Lock()\n\t\t\t\tdefer mtxChatTime.Unlock()\n\t\t\t\t_chatCount++\n\t\t\t}\n\t\t\tgetChatCount := func() int64 {\n\t\t\t\tmtxChatTime.Lock()\n\t\t\t\tdefer mtxChatTime.Unlock()\n\t\t\t\treturn _chatCount\n\t\t\t}\n\n\t\t\tif hls.isTimeshift {\n\n\t\t\t\thls.startCGoroutine(func(sig <-chan struct{}) int {\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\tfmt.Println(\"Comment done.\")\n\t\t\t\t\t}()\n\n\t\t\t\t\tvar pre int64\n\t\t\t\t\tvar finishHint int\n\t\t\t\t\tfor !hls.interrupted() {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase <-time.After(1 * time.Second):\n\t\t\t\t\t\t\tc := getChatCount()\n\t\t\t\t\t\t\tif c == 0 || c == pre {\n\n\t\t\t\t\t\t\t\t_, when := hls.getTsCommentFromWhen()\n\n\t\t\t\t\t\t\t\t//fmt.Printf(\"getTsCommentFromWhen %f %d\\n\", when, res_from)\n\n\t\t\t\t\t\t\t\terr = writeJson([]OBJ{\n\t\t\t\t\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"rs:1\"}},\n\t\t\t\t\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"ps:5\"}},\n\t\t\t\t\t\t\t\t\tOBJ{\"thread\": OBJ{\n\t\t\t\t\t\t\t\t\t\t\"fork\":        0,\n\t\t\t\t\t\t\t\t\t\t\"nicoru\":      0,\n\t\t\t\t\t\t\t\t\t\t\"res_from\":    -1000,\n\t\t\t\t\t\t\t\t\t\t\"scores\":      1,\n\t\t\t\t\t\t\t\t\t\t\"thread\":      threadId,\n\t\t\t\t\t\t\t\t\t\t\"user_id\":     hls.myUserId,\n\t\t\t\t\t\t\t\t\t\t\"version\":     \"20061206\",\n\t\t\t\t\t\t\t\t\t\t\"waybackkey\":  waybackkey,\n\t\t\t\t\t\t\t\t\t\t\"when\":        when + 1,\n\t\t\t\t\t\t\t\t\t\t\"with_global\": 1,\n\t\t\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"pf:5\"}},\n\t\t\t\t\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"rf:1\"}},\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t} else if c < pre+100 {\n\t\t\t\t\t\t\t\t// 通常,1000カウント弱増えるが、少ししか増えない場合\n\t\t\t\t\t\t\t\tfinishHint++\n\t\t\t\t\t\t\t\tif finishHint > 2 {\n\t\t\t\t\t\t\t\t\treturn COMMENT_DONE\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfinishHint = 0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tpre = c\n\n\t\t\t\t\t\tcase <-sig:\n\t\t\t\t\t\t\treturn GOT_SIGNAL\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn COMMENT_DONE\n\t\t\t\t})\n\n\t\t\t} else {\n\t\t\t\terr = writeJson([]OBJ{\n\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"rs:0\"}},\n\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"ps:0\"}},\n\t\t\t\t\tOBJ{\"thread\": OBJ{\n\t\t\t\t\t\t\"fork\":        0,\n\t\t\t\t\t\t\"nicoru\":      0,\n\t\t\t\t\t\t\"res_from\":    -1000,\n\t\t\t\t\t\t\"scores\":      1,\n\t\t\t\t\t\t\"thread\":      threadId,\n\t\t\t\t\t\t\"user_id\":     hls.myUserId,\n\t\t\t\t\t\t\"version\":     \"20061206\",\n\t\t\t\t\t\t\"with_global\": 1,\n\t\t\t\t\t}},\n\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"pf:0\"}},\n\t\t\t\t\tOBJ{\"ping\": OBJ{\"content\": \"rf:0\"}},\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\tlog.Println(\"comment send first:\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn COMMENT_WS_ERROR\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor !hls.interrupted() {\n\t\t\t\tselect {\n\t\t\t\tcase <-sig:\n\t\t\t\t\treturn GOT_SIGNAL\n\t\t\t\tdefault:\n\t\t\t\t\tvar res interface{}\n\t\t\t\t\t// Blocks here\n\t\t\t\t\tif err = conn.ReadJSON(&res); err != nil {\n\t\t\t\t\t\treturn COMMENT_WS_ERROR\n\t\t\t\t\t}\n\n\t\t\t\t\t//fmt.Printf(\"debug %#v\\n\", res)\n\n\t\t\t\t\tif data, ok := objs.Find(res, \"chat\"); ok {\n\t\t\t\t\t\tif err := hls.commentHandler(\"chat\", data); err != nil {\n\t\t\t\t\t\t\treturn COMMENT_SAVE_ERROR\n\t\t\t\t\t\t}\n\t\t\t\t\t\tincChatCount()\n\n\t\t\t\t\t} else if data, ok := objs.Find(res, \"thread\"); ok {\n\t\t\t\t\t\tif err := hls.commentHandler(\"thread\", data); err != nil {\n\t\t\t\t\t\t\treturn COMMENT_SAVE_ERROR\n\t\t\t\t\t\t}\n\n\t\t\t\t\t} else if _, ok := objs.Find(res, \"ping\"); ok {\n\t\t\t\t\t\t// nop\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"[FIXME] Unknown Message: %#v\\n\", res)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn OK\n\t\t})\n\t}\n}\n\nfunc urlJoin(base *url.URL, uri string) (res *url.URL, err error) {\n\tu, e := url.Parse(uri)\n\tif e != nil {\n\t\terr = e\n\t\treturn\n\t}\n\tres = base.ResolveReference(u)\n\treturn\n}\n\nfunc getStringBase(uri string, header map[string]string) (s string, code int, t int64, err, neterr error) {\n\tstart := time.Now().UnixNano()\n\tdefer func() {\n\t\tt = (time.Now().UnixNano() - start) / (1000 * 1000)\n\t}()\n\n\tresp, err, neterr := httpbase.Get(uri, header)\n\tif err != nil {\n\t\treturn\n\t}\n\tif neterr != nil {\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tbs, neterr := ioutil.ReadAll(resp.Body)\n\tif neterr != nil {\n\t\treturn\n\t}\n\n\ts = string(bs)\n\n\tcode = resp.StatusCode\n\n\treturn\n}\nfunc getString(uri string) (s string, code int, t int64, err, neterr error) {\n\treturn getStringBase(uri, nil)\n}\nfunc getStringHeader(uri string, header map[string]string) (s string, code int, t int64, err, neterr error) {\n\treturn getStringBase(uri, header)\n}\nfunc postStringHeader(uri string, header map[string]string, val url.Values) (s string, code int, t int64, err, neterr error) {\n\tstart := time.Now().UnixNano()\n\tdefer func() {\n\t\tt = (time.Now().UnixNano() - start) / (1000 * 1000)\n\t}()\n\n\tresp, err, neterr := httpbase.PostForm(uri, header, val)\n\tif err != nil {\n\t\treturn\n\t}\n\tif neterr != nil {\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tbs, neterr := ioutil.ReadAll(resp.Body)\n\tif neterr != nil {\n\t\treturn\n\t}\n\n\ts = string(bs)\n\n\tcode = resp.StatusCode\n\n\treturn\n}\n\nfunc getBytes(uri string) (code int, buff []byte, t int64, err, neterr error) {\n\tstart := time.Now().UnixNano()\n\tdefer func() {\n\t\tt = (time.Now().UnixNano() - start) / (1000 * 1000)\n\t}()\n\n\tresp, err, neterr := httpbase.Get(uri, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\tif neterr != nil {\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tbuff, neterr = ioutil.ReadAll(resp.Body)\n\tif neterr != nil {\n\t\treturn\n\t}\n\n\tcode = resp.StatusCode\n\n\treturn\n}\n\nfunc (hls *NicoHls) saveMedia(seqno int, uri string) (is403, is404, is500 bool, neterr, err error) {\n\n\tvar timePassed []int64\n\tif hls.nicoDebug {\n\t\ttimePassed = append(timePassed, time.Now().UnixNano())\n\n\t\tstart := time.Now().UnixNano()\n\t\tdefer func() {\n\t\t\tnow := time.Now().UnixNano()\n\t\t\ttimePassed = append(timePassed, now)\n\t\t\tt := (now - start) / (1000 * 1000)\n\t\t\tfmt.Fprintf(os.Stderr, \"%s:saveMedia: seqno=%d, total %d(ms) %v\\n\", debug_Now(), seqno, t, timePassed)\n\t\t}()\n\t}\n\n\tcode, buff, millisec, err, neterr := getBytes(uri)\n\tif hls.nicoDebug {\n\t\tfmt.Fprintf(os.Stderr, \"%s:getBytes@saveMedia: seqno=%d, code=%v, err=%v, neterr=%v, %v(ms), len=%v\\n\",\n\t\t\tdebug_Now(), seqno, code, err, neterr, millisec, len(buff))\n\t}\n\tif err != nil || neterr != nil {\n\t\treturn\n\t}\n\n\tswitch code {\n\tcase 403:\n\t\tis403 = true\n\t\treturn\n\tcase 404:\n\t\tdata := map[string]interface{}{\n\t\t\t\"seqno\":    seqno,\n\t\t\t\"current\":  hls.playlist.seqNo,\n\t\t\t\"notfound\": 1,\n\t\t}\n\t\tif hls.nicoDebug {\n\t\t\ttimePassed = append(timePassed, time.Now().UnixNano())\n\t\t}\n\t\thls.dbInsert(\"media\", data)\n\t\tif hls.nicoDebug {\n\t\t\ttimePassed = append(timePassed, time.Now().UnixNano())\n\t\t}\n\t\thls.memdbSet404(seqno)\n\t\tis404 = true\n\t\treturn\n\tcase 500:\n\t\tis500 = true\n\t\treturn\n\tcase 200:\n\t\t// OK\n\t}\n\n\tdata := map[string]interface{}{\n\t\t\"seqno\":     seqno,\n\t\t\"current\":   hls.playlist.seqNo,\n\t\t\"size\":      len(buff),\n\t\t\"bandwidth\": hls.playlist.bandwidth,\n\t\t\"data\":      buff,\n\t}\n\n\tif seqno == hls.playlist.seqNo {\n\t\tif hls.isTimeshift {\n\t\t\tdata[\"position\"] = hls.playlist.position\n\t\t}\n\t}\n\n\tif hls.nicoDebug {\n\t\ttimePassed = append(timePassed, time.Now().UnixNano())\n\t}\n\thls.dbReplace(\"media\", data)\n\tif hls.nicoDebug {\n\t\ttimePassed = append(timePassed, time.Now().UnixNano())\n\t}\n\thls.memdbSet200(seqno)\n\n\treturn\n}\n\nfunc (hls *NicoHls) getPlaylist(argUri *url.URL) (is403, isEnd, is500 bool, neterr, err error) {\n\tu := argUri.String()\n\tm3u8, code, millisec, err, neterr := getString(u)\n\tif hls.nicoDebug {\n\t\tfmt.Fprintf(os.Stderr, \"%s:getPlaylist: code=%v, err=%v, neterr=%v, %v(ms) >>>%s<<<\\n\",\n\t\t\tdebug_Now(), code, err, neterr, millisec, m3u8)\n\t}\n\tif err != nil || neterr != nil {\n\t\treturn\n\t}\n\n\tswitch code {\n\tcase 200:\n\tcase 403:\n\t\tis403 = true\n\t\treturn\n\tdefault:\n\t\tif 500 <= code && code <= 599 {\n\t\t\tif strings.Contains(u, \"playlist.m3u8\") || !strings.Contains(u, \"master.m3u8\") {\n\t\t\t\tif hls.seqNo500 == hls.playlist.seqNo {\n\t\t\t\t\thls.cnt500++\n\t\t\t\t\tif hls.cnt500 >= 3 {\n\t\t\t\t\t\tif hls.bw500 == hls.playlist.bandwidth {\n\t\t\t\t\t\t\terr = fmt.Errorf(\"# playlist code=%v, hls.bw500=%v, hls.playlist.bandwidth=%v\",\n\t\t\t\t\t\t\t\tcode, hls.bw500, hls.playlist.bandwidth,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thls.bw500 = hls.playlist.bandwidth\n\t\t\t\t\t\t\tfmt.Printf(\"Changing limitBw: %v -> %v\\n\", hls.limitBw, hls.playlist.bandwidth-1)\n\t\t\t\t\t\t\thls.limitBw = hls.playlist.bandwidth - 1\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\thls.seqNo500 = hls.playlist.seqNo\n\t\t\t\t\thls.cnt500 = 1\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// master.m3u8が500\n\t\t\t\thls.seqNo500 = -1\n\t\t\t\thls.cnt500 = 0\n\t\t\t\thls.bw500 = -1\n\t\t\t\thls.limitBw = hls.limitBwOrig\n\t\t\t}\n\n\t\t\tis500 = true\n\t\t\treturn\n\t\t}\n\t\tfmt.Printf(\"#### playlist code: %d: %s\\n\", code, argUri.String())\n\t\terr = fmt.Errorf(\"playlist code: %d: %s\", code, argUri.String())\n\t\treturn\n\t}\n\n\tre := regexp.MustCompile(`#EXT-X-MEDIA-SEQUENCE:(\\d+)`)\n\tma := re.FindStringSubmatch(m3u8)\n\tif len(ma) > 0 {\n\n\t\t// Index m3u8\n\n\t\t// #CURRENT-POSITION:0.0\n\t\t// #DMC-CURRENT-POSITION:0.0\n\t\tvar currentPos float64\n\t\tif ma := regexp.MustCompile(`#(?:DMC-)?CURRENT-POSITION:([\\+\\-]?\\d+(?:\\.\\d+)?(?:[eE][\\+\\-]?\\d+)?)`).\n\t\t\tFindStringSubmatch(m3u8); len(ma) > 0 {\n\t\t\tif hls.isTimeshift {\n\t\t\t\tn, e := strconv.ParseFloat(ma[1], 64)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcurrentPos = n\n\t\t\t\thls.playlist.position = currentPos\n\t\t\t} else {\n\t\t\t\t// timeshiftじゃないのにCURRENT-POSITIONがあれば終了\n\t\t\t\tisEnd = true\n\t\t\t\treturn\n\t\t\t}\n\n\t\t} else {\n\t\t\tif hls.isTimeshift {\n\t\t\t\tcurrentPos = hls.timeshiftStart\n\t\t\t}\n\t\t}\n\n\t\t// 総時間\n\t\tvar streamDuration float64\n\t\tif hls.isTimeshift {\n\t\t\tif ma := regexp.MustCompile(`#(?:DMC-)?STREAM-DURATION:([\\+\\-]?\\d+(?:\\.\\d+)?(?:[eE][\\+\\-]?\\d+)?)`).\n\t\t\t\tFindStringSubmatch(m3u8); len(ma) > 0 {\n\t\t\t\tn, e := strconv.ParseFloat(ma[1], 64)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstreamDuration = n\n\t\t\t}\n\t\t}\n\n\t\tvar seqStart int\n\n\t\tseqStart, err = strconv.Atoi(ma[1])\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\thls.playlist.seqNo = seqStart\n\n\t\tre := regexp.MustCompile(`#EXTINF:([\\+\\-]?\\d+(?:\\.\\d+)?(?:[eE][\\+\\-]?\\d+)?)[^\\n]*\\n(\\S+)`)\n\t\tma := re.FindAllStringSubmatch(m3u8, -1)\n\n\t\tif len(ma) == 0 {\n\t\t\tlog.Println(\"No medias in playlist\")\n\t\t\thls.playlist.nextTime = time.Now().Add(time.Second)\n\t\t\treturn\n\t\t}\n\n\t\ttype seq_t struct {\n\t\t\tseqno    int\n\t\t\tduration float64\n\t\t\turi      string\n\t\t}\n\t\tvar seqlist []seq_t\n\n\t\tvar seqMax int\n\t\tvar totalDuration float64\n\t\tfor i, a := range ma {\n\t\t\tvar duration float64\n\t\t\tseqno := i + hls.playlist.seqNo\n\t\t\tif seqno > seqMax {\n\t\t\t\tseqMax = seqno\n\t\t\t}\n\n\t\t\tif hls.isTimeshift || i == 0 {\n\t\t\t\td, e := strconv.ParseFloat(a[1], 64)\n\t\t\t\tif e != nil {\n\t\t\t\t\terr = e\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif hls.isTimeshift {\n\t\t\t\t\tduration = d\n\t\t\t\t\ttotalDuration += d\n\t\t\t\t} else {\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\tif d > 3 {\n\t\t\t\t\t\t\tfmt.Printf(\"debug: found EXTINF=%v\\n\", d)\n\t\t\t\t\t\t\td = 2.0\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\td = d + 0.5\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt := time.Duration(float64(time.Second) * d)\n\t\t\t\t\t\thls.playlist.nextTime = time.Now().Add(t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\turi, e := urlJoin(argUri, a[2])\n\t\t\tif e != nil {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tseqlist = append(seqlist, seq_t{\n\t\t\t\tseqno:    seqno,\n\t\t\t\tduration: duration,\n\t\t\t\turi:      uri.String(),\n\t\t\t})\n\n\t\t\t// メディアのURLがシーケンス番号の部分だけが変わる形式かどうか\n\t\t\tif (!hls.isTimeshift) && (!hls.playlist.withoutFormat) {\n\t\t\t\tf := strings.Replace(\n\t\t\t\t\tstrings.Replace(uri.String(), \"%\", \"%%\", -1),\n\t\t\t\t\tfmt.Sprintf(\"%d.ts?\", seqno),\n\t\t\t\t\t\"%d.ts?\",\n\t\t\t\t\t1,\n\t\t\t\t)\n\n\t\t\t\tif hls.playlist.format == \"\" {\n\t\t\t\t\thls.playlist.format = f\n\n\t\t\t\t} else if hls.playlist.format != f {\n\t\t\t\t\tfmt.Println(m3u8)\n\t\t\t\t\tfmt.Println(\"[FIXME] media format changed\")\n\t\t\t\t\thls.playlist.withoutFormat = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif hls.isTimeshift {\n\t\t\tif !hls.ultrafastTimeshift {\n\t\t\t\ttd := seqlist[0].duration * float64(time.Second)\n\t\t\t\thls.playlist.nextTime = time.Now().Add(time.Duration(td))\n\t\t\t}\n\t\t}\n\n\t\t// prints Current SeqNo\n\t\tif hls.isTimeshift {\n\t\t\tsec := int(hls.playlist.position)\n\t\t\tvar pos string\n\t\t\tif sec >= 3600 {\n\t\t\t\tpos += fmt.Sprintf(\"%02d:%02d:%02d\", sec/3600, (sec%3600)/60, sec%60)\n\t\t\t} else {\n\t\t\t\tpos += fmt.Sprintf(\"%02d:%02d\", sec/60, sec%60)\n\t\t\t}\n\t\t\tfmt.Printf(\"Current SeqNo: %d, Pos: %s\\n\", hls.playlist.seqNo, pos)\n\n\t\t} else {\n\t\t\tfmt.Printf(\"Current SeqNo: %d\\n\", hls.playlist.seqNo)\n\t\t}\n\n\t\tminSeq := math.MaxInt32\n\t\tmaxSeq := -1\n\t\tif (!hls.isTimeshift) && (!hls.playlist.withoutFormat) {\n\t\t\t// 404になるまで後ろに戻ってチャンクを取得する\n\t\t\tif hls.nicoDebug {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:start chunks(back)\\n\", debug_Now())\n\t\t\t}\n\t\t\tfor i := hls.playlist.seqNo - 1; i >= 0; i-- {\n\t\t\t\tif hls.memdbGetStopBack(i) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tu := fmt.Sprintf(hls.playlist.format, i)\n\t\t\t\tvar is404 bool\n\t\t\t\tis403, is404, _, neterr, err = hls.saveMedia(i, u)\n\t\t\t\tif neterr != nil || err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif is403 {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif i > maxSeq {\n\t\t\t\t\tmaxSeq = i\n\t\t\t\t}\n\t\t\t\tif i < minSeq {\n\t\t\t\t\tminSeq = i\n\t\t\t\t}\n\n\t\t\t\tif is404 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// m3u8の通りにチャンクを取得する\n\t\tif hls.nicoDebug {\n\t\t\tfmt.Fprintf(os.Stderr, \"%s:start chunks(normal)\\n\", debug_Now())\n\t\t}\n\n\t\t// 一時的に倍速モードを切っているかもしれないので戻す\n\t\tif hls.isTimeshift && (0 < hls.playlist.seqNo && hls.playlist.seqNo < 10) {\n\t\t\thls.fastTimeshift = hls.fastTimeshiftOrig\n\t\t\thls.ultrafastTimeshift = hls.ultrafastTimeshiftOrig\n\t\t}\n\n\t\tif hls.isTimeshift {\n\t\t\thls.timeshiftStart = currentPos - 0.49\n\t\t}\n\n\t\tvar found404 bool\n\t\tfor _, seq := range seqlist {\n\t\t\tif hls.isTimeshift {\n\t\t\t\thls.timeshiftStart += seq.duration\n\t\t\t}\n\n\t\t\tif hls.memdbCheck200(seq.seqno) {\n\t\t\t\tif seq.seqno == hls.playlist.seqNo {\n\t\t\t\t\tif hls.isTimeshift {\n\t\t\t\t\t\thls.dbSetPosition()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar is404 bool\n\t\t\tis403, is404, is500, neterr, err = hls.saveMedia(seq.seqno, seq.uri)\n\t\t\tif neterr != nil || err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif is404 {\n\t\t\t\tfmt.Printf(\"sequence 404: %d\\n\", seq.seqno)\n\t\t\t\tfound404 = true\n\t\t\t}\n\t\t\tif is403 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// TS時、先頭(SeqNo=0)で500となる時があるが\n\t\t\t// Seekしなければ次回に取得可能なので一時的に倍速モードを切る\n\t\t\tif is500 && hls.fastTimeshift && (seq.seqno == 0) {\n\t\t\t\tfmt.Println(\"[WARN] disabled fastTimeshift\")\n\n\t\t\t\thls.fastTimeshift = false\n\t\t\t\thls.ultrafastTimeshift = false\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif seq.seqno < minSeq {\n\t\t\t\tminSeq = seq.seqno\n\t\t\t}\n\t\t\tif !found404 {\n\t\t\t\tmaxSeq = seq.seqno\n\t\t\t}\n\t\t}\n\n\t\tif minSeq != math.MaxInt32 && maxSeq > 0 {\n\t\t\tfor i := minSeq; i <= maxSeq; i++ {\n\t\t\t\thls.memdbSetStopBack(i)\n\t\t\t}\n\t\t\thls.memdbDelete(hls.playlist.seqNo)\n\t\t}\n\n\t\tif strings.Contains(m3u8, \"#EXT-X-ENDLIST\") {\n\t\t\tisEnd = true\n\t\t\treturn\n\t\t}\n\n\t\tif hls.isTimeshift {\n\t\t\td := streamDuration - (currentPos + totalDuration)\n\t\t\tif d < 1.0 {\n\t\t\t\tisEnd = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\t// Master m3u8\n\t\tre := regexp.MustCompile(`#EXT-X-STREAM-INF:(?:[^\\n]*[^\\n\\w-])?BANDWIDTH=(\\d+)[^\\n]*\\n(\\S+)`)\n\t\tma := re.FindAllStringSubmatch(m3u8, -1)\n\t\tif len(ma) > 0 {\n\t\t\tvar maxBw int\n\t\t\tvar uri *url.URL\n\t\t\tfor _, a := range ma {\n\t\t\t\tbw, err := strconv.Atoi(a[1])\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tset := func() {\n\t\t\t\t\tmaxBw = bw\n\t\t\t\t\turi, err = urlJoin(argUri, a[2])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Println(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif maxBw == 0 {\n\t\t\t\t\tset()\n\n\t\t\t\t} else if hls.limitBw > 0 {\n\t\t\t\t\t// with limit\n\t\t\t\t\t// もし現在値が制限を超えていたら、現在値より小さければセット。\n\t\t\t\t\tif hls.limitBw < maxBw && bw < maxBw {\n\t\t\t\t\t\tset()\n\n\t\t\t\t\t\t// 現在値が制限以下で、制限を超えないかつ現在値より大きければセット。\n\t\t\t\t\t} else if maxBw <= hls.limitBw && bw <= hls.limitBw && maxBw < bw {\n\t\t\t\t\t\tset()\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\t\t\t\t\t// without limit\n\t\t\t\t\tif maxBw < bw {\n\t\t\t\t\t\tset()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif uri == nil {\n\t\t\t\terr = fmt.Errorf(\"playlist uri not defined\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"BANDWIDTH: %d\\n\", maxBw)\n\t\t\thls.playlist.bandwidth = maxBw\n\t\t\tif hls.isTimeshift && hls.fastTimeshift {\n\n\t\t\t} else {\n\t\t\t\thls.playlist.uriMaster = argUri\n\t\t\t\thls.playlist.uri = uri\n\t\t\t}\n\t\t\treturn hls.getPlaylist(uri)\n\n\t\t} else {\n\t\t\tlog.Println(\"playlist error\")\n\t\t}\n\t}\n\treturn\n}\n\nfunc (hls *NicoHls) startPlaylist(uri string) {\n\thls.startPGoroutine(func(sig <-chan struct{}) int {\n\t\thls.playlist = playlist{}\n\t\t//hls.playlist.uri = uri\n\t\tu, e := url.Parse(uri)\n\t\tif e != nil {\n\t\t\treturn PLAYLIST_ERROR\n\t\t}\n\n\t\thls.playlist.uri = u\n\t\tif hls.isTimeshift {\n\t\t\thls.playlist.uriTimeshiftMaster = u\n\t\t}\n\n\t\tif hls.isTimeshift {\n\t\t\tif hls.timeshiftStart == 0 {\n\t\t\t\thls.timeshiftStart = hls.dbGetLastPosition()\n\t\t\t}\n\t\t\tu := hls.playlist.uriTimeshiftMaster.String()\n\t\t\tu = regexp.MustCompile(`&start=\\d+(?:\\.\\d*)?`).ReplaceAllString(u, \"\")\n\t\t\tu += fmt.Sprintf(\"&start=%f\", hls.timeshiftStart)\n\t\t\turi, _ := url.Parse(u)\n\t\t\thls.playlist.uri = uri\n\t\t}\n\n\t\tfor !hls.interrupted() {\n\t\t\tvar dur time.Duration\n\t\t\tif hls.playlist.nextTime.IsZero() {\n\t\t\t\tdur = 0\n\t\t\t} else {\n\t\t\t\tnow := time.Now()\n\t\t\t\tdur = hls.playlist.nextTime.Sub(now)\n\t\t\t}\n\n\t\t\t// 181002\n\t\t\tif dur < time.Second {\n\t\t\t\tdur = time.Second\n\t\t\t}\n\n\t\t\tif hls.nicoDebug {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:time.After()=%v(sec)\\n\", debug_Now(), float64(dur)/float64(time.Second))\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-time.After(dur):\n\t\t\t\tvar uri *url.URL\n\t\t\t\tif hls.isTimeshift && hls.fastTimeshift {\n\t\t\t\t\tu := hls.playlist.uriTimeshiftMaster.String()\n\t\t\t\t\tu = regexp.MustCompile(`&start=\\d+(?:\\.\\d*)?`).ReplaceAllString(u, \"\")\n\t\t\t\t\tu += fmt.Sprintf(\"&start=%f\", hls.timeshiftStart)\n\t\t\t\t\turi, _ = url.Parse(u)\n\t\t\t\t} else {\n\t\t\t\t\turi = hls.playlist.uri\n\t\t\t\t}\n\n\t\t\t\t//fmt.Println(uri)\n\n\t\t\t\tis403, isEnd, is500, neterr, err := hls.getPlaylist(uri)\n\t\t\t\tif neterr != nil {\n\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\tlog.Println(\"playlist:\", e)\n\t\t\t\t\t}\n\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t}\n\t\t\t\tif is500 {\n\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\tlog.Println(\"playlist(500):\", e)\n\t\t\t\t\t}\n\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\tlog.Println(\"playlist:\", e)\n\t\t\t\t\t}\n\t\t\t\t\treturn PLAYLIST_ERROR\n\t\t\t\t}\n\t\t\t\tif is403 {\n\t\t\t\t\treturn PLAYLIST_403\n\t\t\t\t}\n\t\t\t\tif isEnd {\n\t\t\t\t\treturn PLAYLIST_END\n\t\t\t\t}\n\n\t\t\tcase <-sig:\n\t\t\t\treturn GOT_SIGNAL\n\t\t\t}\n\t\t}\n\t\treturn OK\n\t})\n}\nfunc (hls *NicoHls) startMain() {\n\tif hls.wsapi == 1 {\n\t\thls.startMainV1()\n\t\treturn\n\t}\n\n\t// エラー時はMAIN_*を返すこと\n\thls.startPGoroutine(func(sig <-chan struct{}) int {\n\t\tif hls.nicoDebug {\n\t\t\tfmt.Fprintf(os.Stderr, \"%s:startMain: delay = %d(sec)\\n\", debug_Now(), hls.startDelay)\n\t\t}\n\n\t\tselect {\n\t\tcase <-time.After(time.Duration(hls.startDelay) * time.Second):\n\t\tcase <-sig:\n\t\t\treturn GOT_SIGNAL\n\t\t}\n\n\t\tif hls.nicoDebug {\n\t\t\tfmt.Fprintf(os.Stderr, \"%s:start dial main(%s)\\n\", debug_Now(), hls.webSocketUrl)\n\t\t}\n\t\tconn, _, err := websocket.DefaultDialer.Dial(\n\t\t\thls.webSocketUrl,\n\t\t\tmap[string][]string{\n\t\t\t\t\"User-Agent\": []string{httpbase.GetUserAgent()},\n\t\t\t},\n\t\t)\n\t\tif err != nil {\n\t\t\treturn NETWORK_ERROR\n\t\t}\n\t\tvar wsMtx sync.Mutex\n\t\twriteJson := func(d interface{}) error {\n\t\t\twsMtx.Lock()\n\t\t\tdefer wsMtx.Unlock()\n\t\t\treturn conn.WriteJSON(d)\n\t\t}\n\n\t\t// debug\n\t\tif false {\n\t\t\tlog.Printf(\"start ws error tsst\")\n\t\t\thls.startPGoroutine(func(sig <-chan struct{}) int {\n\t\t\t\tselect {\n\t\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\t\tconn.Close()\n\t\t\t\t\treturn OK\n\t\t\t\tcase <-sig:\n\t\t\t\t\treturn GOT_SIGNAL\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\thls.startPGoroutine(func(sig <-chan struct{}) int {\n\t\t\t<-sig\n\t\t\tif conn != nil {\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t\treturn OK\n\t\t})\n\n\t\terr = writeJson(OBJ{\n\t\t\t\"type\": \"startWatching\",\n\t\t\t\"data\": OBJ{\n\t\t\t\t\"stream\": OBJ{\n\t\t\t\t\t\"quality\":  hls.quality, //\"abr\", // high\n\t\t\t\t\t\"protocol\": \"hls\",\n\t\t\t\t\t\"latency\":  \"high\",\n\t\t\t\t},\n\t\t\t\t\"room\": OBJ{\n\t\t\t\t\t\"protocol\":    \"webSocket\",\n\t\t\t\t\t\"commentable\": true,\n\t\t\t\t},\n\t\t\t\t\"reconnect\": true,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tif !hls.interrupted() {\n\t\t\t\tlog.Println(\"websocket getpermit write:\", err)\n\t\t\t}\n\t\t\treturn NETWORK_ERROR\n\t\t}\n\n\t\tvar playlistStarted bool\n\t\tvar watchingStarted bool\n\t\tvar watchinginterval int\n\t\tfor !hls.interrupted() {\n\t\t\tselect {\n\t\t\tcase <-sig:\n\t\t\t\treturn GOT_SIGNAL\n\t\t\tdefault:\n\t\t\t}\n\t\t\tvar res interface{}\n\t\t\terr = conn.ReadJSON(&res)\n\t\t\tif err != nil {\n\t\t\t\tif (!hls.interrupted()) && (!hls.finish) {\n\t\t\t\t\tlog.Println(\"websocket read:\", err)\n\t\t\t\t}\n\t\t\t\treturn NETWORK_ERROR\n\t\t\t}\n\t\t\tif hls.nicoDebug {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:ReadJSON => %v\\n\", debug_Now(), res)\n\t\t\t}\n\n\t\t\t_type, ok := objs.FindString(res, \"type\")\n\t\t\tif !ok {\n\t\t\t\tfmt.Printf(\"type not found\\n\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch _type {\n\t\t\t//case \"watch\":\n\t\t\t//if cmd, ok := objs.FindString(res, \"body\", \"command\"); ok {\n\t\t\t//switch cmd {\n\t\t\tcase \"seat\":\n\t\t\t\tif _arr, ok := objs.FindFloat64(res, \"data\", \"keepIntervalSec\"); ok {\n\t\t\t\t\tarr := []interface{}{_arr}\n\t\t\t\t\tfor _, intf := range arr {\n\t\t\t\t\t\tif str, ok := intf.(float64); ok {\n\t\t\t\t\t\t\tnum := int(str)\n\t\t\t\t\t\t\tif num > 0 {\n\t\t\t\t\t\t\t\t//hls.SetInterval(num)\n\t\t\t\t\t\t\t\twatchinginterval = num\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!watchingStarted) && watchinginterval > 0 {\n\t\t\t\t\twatchingStarted = true\n\t\t\t\t\thls.startPGoroutine(func(sig <-chan struct{}) int {\n\t\t\t\t\t\tfor {\n\t\t\t\t\t\t\tselect {\n\t\t\t\t\t\t\tcase <-time.After(time.Duration(watchinginterval) * time.Second):\n\t\t\t\t\t\t\t\terr := writeJson(OBJ{\n\t\t\t\t\t\t\t\t\t\"type\": \"keepSeat\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\t\t\t\t\tlog.Println(\"websocket watching:\", err)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcase <-sig:\n\t\t\t\t\t\t\t\treturn GOT_SIGNAL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\tcase \"stream\":\n\t\t\t\tif uri, ok := objs.FindString(res, \"data\", \"uri\"); ok {\n\t\t\t\t\tif (!playlistStarted) && uri != \"\" {\n\t\t\t\t\t\tplaylistStarted = true\n\t\t\t\t\t\thls.startPlaylist(uri)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase \"disconnect\":\n\t\t\t\t// print params\n\t\t\t\tif _arr, ok := objs.FindString(res, \"data\", \"reason\"); ok {\n\t\t\t\t\tarr := []interface{}{0, _arr}\n\t\t\t\t\tfmt.Printf(\"%v\\n\", arr)\n\t\t\t\t\tif len(arr) >= 2 {\n\t\t\t\t\t\tif s, ok := arr[1].(string); ok {\n\t\t\t\t\t\t\tswitch s {\n\t\t\t\t\t\t\tcase \"END_PROGRAM\":\n\t\t\t\t\t\t\t\treturn MAIN_END_PROGRAM\n\t\t\t\t\t\t\tcase \"SERVICE_TEMPORARILY_UNAVAILABLE\", \"INTERNAL_SERVERERROR\":\n\t\t\t\t\t\t\t\treturn MAIN_TEMPORARILY_ERROR\n\t\t\t\t\t\t\tcase \"TOO_MANY_CONNECTIONS\":\n\t\t\t\t\t\t\t\treturn MAIN_DISCONNECT\n\t\t\t\t\t\t\tcase \"TEMPORARILY_CROWDED\":\n\t\t\t\t\t\t\t\treturn MAIN_END_PROGRAM\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn MAIN_DISCONNECT\n\n\t\t\tcase \"room\":\n\t\t\t\t// comment\n\t\t\t\tmessageServerUri, ok := objs.FindString(res, \"data\", \"messageServer\", \"uri\")\n\t\t\t\tif !ok {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tthreadId, ok := objs.FindString(res, \"data\", \"threadId\")\n\t\t\t\tif !ok {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\twaybackkey, _ := objs.FindString(res, \"data\", \"waybackkey\")\n\t\t\t\thls.startComment(messageServerUri, threadId, waybackkey)\n\n\t\t\tcase \"statistics\":\n\t\t\tcase \"permit\":\n\t\t\tcase \"serverTime\":\n\t\t\tcase \"schedule\":\n\t\t\t\t// nop\n\t\t\t\t//default:\n\t\t\t\t//\tfmt.Printf(\"%#v\\n\", res)\n\t\t\t\t//\tfmt.Printf(\"unknown command: %s\\n\", cmd)\n\t\t\t\t//} // end switch \"command\"\n\t\t\t\t//}\n\t\t\t\t// \"watch\"\n\n\t\t\tcase \"ping\":\n\t\t\t\terr := writeJson(OBJ{\n\t\t\t\t\t\"type\": \"pong\",\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !hls.interrupted() {\n\t\t\t\t\t\tlog.Println(\"websocket watching:\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t}\n\t\t\tcase \"error\":\n\t\t\t\tcode, ok := objs.FindString(res, \"data\", \"code\")\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Printf(\"Unknown error: %#v\\n\", res)\n\t\t\t\t\treturn ERROR_SHUTDOWN\n\t\t\t\t}\n\n\t\t\t\t// https://nicolive.cdn.nimg.jp/relive/front_assets/scripts/nicolib.4bb8b62b35.js\n\t\t\t\tswitch code {\n\t\t\t\tcase \"INVALID_STREAM_QUALITY\":\n\t\t\t\t\t// webSocket自体を再接続しないと、コメントサーバが取得できない\n\t\t\t\t\tswitch hls.quality {\n\t\t\t\t\tcase \"abr\":\n\t\t\t\t\t\thls.quality = \"high\"\n\t\t\t\t\t\treturn MAIN_INVALID_STREAM_QUALITY\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn ERROR_SHUTDOWN\n\t\t\t\t\t}\n\t\t\t\t//case\n\t\t\t\t//\t\"INTERNAL_SERVERERROR\",\n\t\t\t\t//\t\"CONTENT_NOT_READY\", // 終了後に出ることがある\n\t\t\t\t//\t\"CONNECT_ERROR\": // 終了後に出ることがある\n\t\t\t\t//\treturn NETWORK_ERROR\n\t\t\t\t//case\n\t\t\t\t//\t\"INVALID_BROADCAST_ID\",\n\t\t\t\t//\t\"BROADCAST_NOT_FOUND\",\n\t\t\t\t//\t\"NO_THREAD_AVAILABLE\",\n\t\t\t\t//\t\"NO_ROOM_AVAILABLE\",\n\t\t\t\t//\t\"NO_PERMISSION\":\n\t\t\t\t//\treturn ERROR_SHUTDOWN\n\t\t\t\tcase \"INVALID_MESSAGE\":\n\t\t\t\t\t// 公式のTSで送られてきた。単純に無視する。\n\t\t\t\tdefault:\n\t\t\t\t\t//\tlog.Printf(\"Unknown error: %s\\n%#v\\n\", code, res)\n\t\t\t\t\t//\treturn ERROR_SHUTDOWN\n\t\t\t\t\tfmt.Printf(\"error code: %v\\n\", code)\n\t\t\t\t\tif hls.msgErrorSeqNo == hls.playlist.seqNo {\n\t\t\t\t\t\thls.msgErrorCount++\n\t\t\t\t\t} else {\n\t\t\t\t\t\thls.msgErrorSeqNo = hls.playlist.seqNo\n\t\t\t\t\t\thls.msgErrorCount = 1\n\t\t\t\t\t}\n\t\t\t\t\tif hls.msgErrorCount >= 3 {\n\t\t\t\t\t\treturn ERROR_SHUTDOWN\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn NETWORK_ERROR\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\tlog.Printf(\"Unknown type: %s\\n%#v\\n\", _type, res)\n\t\t\t} // end switch \"type\"\n\t\t} // for ReadJSON\n\t\treturn OK\n\t})\n}\n\nfunc (hls *NicoHls) startMainV1() {\n\treturn // old startMain\n}\n\nfunc (hls *NicoHls) serve(hlsPort int) {\n\thls.startMGoroutine(func(sig <-chan struct{}) int {\n\t\tgin.SetMode(gin.ReleaseMode)\n\t\tgin.DefaultErrorWriter = ioutil.Discard\n\t\tgin.DefaultWriter = ioutil.Discard\n\t\trouter := gin.Default()\n\n\t\trouter.GET(\"\", func(c *gin.Context) {\n\t\t\tseqno := hls.dbGetLastSeqNo()\n\t\t\tbody := fmt.Sprintf(\n\t\t\t\t`#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:1\n#EXT-X-MEDIA-SEQUENCE:%d\n\n#EXTINF:1.0,\n/ts/%d/test.ts\n\n#EXTINF:1.0,\n/ts/%d/test.ts\n\n#EXTINF:1.0,\n/ts/%d/test.ts\n\n`, seqno-2, seqno-2, seqno-1, seqno)\n\t\t\tc.Data(http.StatusOK, \"application/x-mpegURL\", []byte(body))\n\t\t\treturn\n\t\t})\n\n\t\trouter.GET(\"/ts/:idx/test.ts\", func(c *gin.Context) {\n\t\t\ti, _ := strconv.Atoi(c.Param(\"idx\"))\n\t\t\tb := hls.dbGetLastMedia(i)\n\t\t\tc.Data(http.StatusOK, \"video/MP2T\", b)\n\t\t\treturn\n\t\t})\n\n\t\tsrv := &http.Server{\n\t\t\tAddr:           fmt.Sprintf(\"127.0.0.1:%d\", hlsPort),\n\t\t\tHandler:        router,\n\t\t\tReadTimeout:    10 * time.Second,\n\t\t\tWriteTimeout:   10 * time.Second,\n\t\t\tMaxHeaderBytes: 1 << 20,\n\t\t}\n\n\t\tchLocal := make(chan struct{})\n\t\tidleConnsClosed := make(chan struct{})\n\t\tdefer func() {\n\t\t\tclose(chLocal)\n\t\t}()\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-chLocal:\n\t\t\tcase <-sig:\n\t\t\t}\n\t\t\tif err := srv.Shutdown(context.Background()); err != nil {\n\t\t\t\tlog.Printf(\"srv.Shutdown: %v\\n\", err)\n\t\t\t}\n\t\t\tclose(idleConnsClosed)\n\t\t}()\n\n\t\t// クライアントはlocalhostでなく127.0.0.1で接続すること\n\t\t// localhostは遅いため\n\t\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\t\tlog.Printf(\"srv.ListenAndServe: %v\\n\", err)\n\t\t}\n\n\t\t<-idleConnsClosed\n\t\treturn OK\n\t})\n}\n\nfunc (hls *NicoHls) Wait(testTimeout, hlsPort int) {\n\n\thls.startInterrupt()\n\tdefer hls.stopInterrupt()\n\n\tif testTimeout > 0 {\n\t\thls.startMGoroutine(func(sig <-chan struct{}) int {\n\t\t\tselect {\n\t\t\tcase <-sig:\n\t\t\t\treturn GOT_SIGNAL\n\t\t\tcase <-time.After(time.Duration(testTimeout) * time.Second):\n\t\t\t\thls.chInterrupt <- syscall.Signal(1000)\n\t\t\t\treturn OK\n\t\t\t}\n\t\t})\n\t}\n\n\tif hlsPort > 0 {\n\t\thls.serve(hlsPort)\n\t}\n\n\thls.startMain()\n\tfor hls.working() {\n\t\tif hls.waitRestartMain() {\n\t\t\tcontinue\n\t\t}\n\t\thls.stopPCGoroutines()\n\t\thls.waitCGoroutines()\n\t}\n\n\thls.stopAllGoroutines()\n\thls.waitAllGoroutines()\n\n\treturn\n}\n\nfunc postTsRsv0(opt options.Option) (err error) {\n\tif ma := regexp.MustCompile(`lv(\\d+)`).FindStringSubmatch(opt.NicoLiveId); len(ma) > 0 {\n\t\tif err = postTsRsvBase(0, ma[1], opt.NicoSession); err != nil {\n\t\t\treturn\n\t\t}\n\t\terr = postTsRsvBase(1, ma[1], opt.NicoSession)\n\t}\n\treturn\n}\nfunc postTsRsv1(opt options.Option) (err error) {\n\tif ma := regexp.MustCompile(`lv(\\d+)`).FindStringSubmatch(opt.NicoLiveId); len(ma) > 0 {\n\t\terr = postTsRsvBase(1, ma[1], opt.NicoSession)\n\t}\n\treturn\n}\nfunc postTsRsvBase(num int, vid, session string) (err error) {\n\tvar uri string\n\tif num == 0 {\n\t\turi = fmt.Sprintf(\"https://live.nicovideo.jp/api/watchingreservation?mode=watch_num&vid=%s\", vid)\n\t} else {\n\t\turi = fmt.Sprintf(\"https://live.nicovideo.jp/api/watchingreservation?mode=confirm_watch_my&vid=%s\", vid)\n\t}\n\n\theader := map[string]string{\n\t\t\"Cookie\": \"user_session=\" + session,\n\t}\n\tdat0, _, _, err, neterr := getStringHeader(uri, header)\n\tif err != nil || neterr != nil {\n\t\tif err == nil {\n\t\t\terr = neterr\n\t\t}\n\t\treturn\n\t}\n\n\tvar token string\n\tif ma := regexp.MustCompile(\n\t\t`TimeshiftActions\\.(doRegister|confirmToWatch|moveWatch)\\(['\"].*?['\"]\\s*(?:,\\s*['\"](.+?)['\"])`).\n\t\tFindStringSubmatch(dat0); len(ma) > 0 {\n\t\tif len(ma) > 2 {\n\t\t\ttoken = ma[2]\n\t\t}\n\t} else if strings.Contains(dat0, \"視聴済み\") {\n\t\terr = fmt.Errorf(\"postTsRsv: already watched\")\n\t\treturn\n\t} else {\n\t\tfmt.Printf(\"postTsRsv: token not found: >>>%s<<<\\n\", dat0)\n\t\terr = fmt.Errorf(\"postTsRsv: token not found\")\n\t\treturn\n\t}\n\n\t// \"X-Requested-With\": \"XMLHttpRequest\",\n\t// \"Origin\": \"https://live.nicovideo.jp\",\n\t// \"Referer\": fmt.Sprintf(\"https://live.nicovideo.jp/gate/%s\", opt.NicoLiveId),\n\t// \"X-Prototype-Version\": \"1.6.0.3\",\n\n\tvar vals url.Values\n\tif num == 0 {\n\t\tvals = url.Values{\n\t\t\t\"mode\":       []string{\"overwrite\"},\n\t\t\t\"vid\":        []string{vid},\n\t\t\t\"token\":      []string{token},\n\t\t\t\"rec_pos\":    []string{\"\"},\n\t\t\t\"rec_engine\": []string{\"\"},\n\t\t\t\"rec_id\":     []string{\"\"},\n\t\t\t\"_\":          []string{\"\"},\n\t\t}\n\t} else {\n\t\tvals = url.Values{\n\t\t\t\"accept\": []string{\"true\"},\n\t\t\t\"mode\":   []string{\"use\"},\n\t\t\t\"vid\":    []string{vid},\n\t\t\t\"token\":  []string{token},\n\t\t\t\"_\":      []string{\"\"},\n\t\t}\n\t}\n\n\tdat1, _, _, err, neterr := postStringHeader(\"https://live.nicovideo.jp/api/watchingreservation\", header, vals)\n\tif err != nil || neterr != nil {\n\t\tif err == nil {\n\t\t\terr = neterr\n\t\t}\n\t\treturn\n\t}\n\tif (!strings.Contains(dat1, \"status=\\\"ok\\\"\")) && (!strings.Contains(dat1, \"\\\"regist_finished\\\"\")) {\n\t\tfmt.Printf(\"postTsRsv: status not ok: >>>%s<<<\\n\", dat1)\n\t\terr = fmt.Errorf(\"postTsRsv: status not ok\")\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc getProps(opt options.Option) (props interface{}, isFlash, notLogin, tsRsv0, tsRsv1 bool, err error) {\n\n\theader := map[string]string{}\n\tif opt.NicoSession != \"\" {\n\t\theader[\"Cookie\"] = \"user_session=\" + opt.NicoSession\n\t}\n\n\turi := fmt.Sprintf(\"https://live2.nicovideo.jp/watch/%s\", opt.NicoLiveId)\n\tdat, _, _, err, neterr := getStringHeader(uri, header)\n\tif err != nil || neterr != nil {\n\t\tif err == nil {\n\t\t\terr = neterr\n\t\t}\n\t\treturn\n\t}\n\n\t// ログイン判定\n\tif opt.NicoSession == \"\" {\n\t\tnotLogin = true\n\t} else if ma := regexp.MustCompile(`login_status['\"]*\\s*[=:]\\s*['\"](.*?)['\"]`).FindStringSubmatch(dat); len(ma) > 0 {\n\t\tswitch string(ma[1]) {\n\t\tcase \"not_login\":\n\t\t\tnotLogin = true\n\t\tcase \"login\":\n\t\t\tnotLogin = false\n\t\tdefault:\n\t\t\tfmt.Printf(\"[FIXME] login_status = %s\\n\", ma[1])\n\t\t}\n\t} else {\n\t\tnotLogin = true\n\t}\n\n\t// 新配信 + nicocas\n\tif ma := regexp.MustCompile(`data-props=\"(.+?)\"`).FindStringSubmatch(dat); len(ma) > 0 {\n\t\tstr := html.UnescapeString(string(ma[1]))\n\t\tif err = json.Unmarshal([]byte(str), &props); err != nil {\n\t\t\treturn\n\t\t}\n\t\treturn\n\t} else if strings.Contains(dat, \"nicoliveplayer.swf\") {\n\t\t// 旧Flashプレイヤー\n\t\tisFlash = true\n\t} else if regexp.MustCompile(`この番組は.{1,50}に終了`).MatchString(dat) {\n\t\t// タイムシフト予約ボタン\n\t\tif ma := regexp.MustCompile(`Nicolive\\.WatchingReservation\\.register`).FindStringSubmatch(dat); len(ma) > 0 {\n\t\t\tfmt.Printf(\"timeshift reservation required\\n\")\n\t\t\ttsRsv0 = true\n\t\t\treturn\n\t\t}\n\t\tif ma := regexp.MustCompile(`Nicolive\\.WatchingReservation\\.confirm`).FindStringSubmatch(dat); len(ma) > 0 {\n\t\t\tfmt.Printf(\"timeshift reservation required\\n\")\n\t\t\ttsRsv1 = true\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc NicoRecHls(opt options.Option) (done, playlistEnd, notLogin, reserved bool, dbName string, err error) {\n\n\t//http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 32\n\n\t//var props interface{}\n\t//var isFlash bool\n\t//var tsRsv bool\n\tprops, isFlash, notLogin, tsRsv0, tsRsv1, err := getProps(opt)\n\tif err != nil {\n\t\t//fmt.Println(err)\n\t\treturn\n\t}\n\n\tif notLogin {\n\t\tif opt.NicoLoginOnly {\n\t\t\t// 要ログイン\n\t\t\treturn\n\t\t} else {\n\t\t\t// 非ログインでも録画可能なら再ログイン不要\n\t\t\tnotLogin = false\n\t\t}\n\t}\n\n\t// TS予約必要\n\tif (tsRsv0 || tsRsv1) && opt.NicoForceResv {\n\t\tif tsRsv0 {\n\t\t\terr = postTsRsv0(opt)\n\t\t} else {\n\t\t\terr = postTsRsv1(opt)\n\t\t}\n\t\tif err == nil {\n\t\t\treserved = true\n\t\t}\n\t\treturn\n\t}\n\n\tif isFlash {\n\t\tfmt.Println(\"Flash page detected.\")\n\t\treturn\n\t}\n\n\tif false {\n\t\tobjs.PrintAsJson(props)\n\t\tos.Exit(9)\n\t}\n\n\tproplist := map[string][]string{\n\t\t// \"broadcaster\" // nicocas\n\t\t\"cas-userName\":    []string{\"broadcaster\", \"nickname\"}, // ユーザ名\n\t\t\"cas-userPageUrl\": []string{\"broadcaster\", \"pageUrl\"},  // \"https://www.nicovideo.jp/user/\\d+\"\n\t\t// \"community\"\n\t\t\"comId\": []string{\"community\", \"id\"}, // \"co\\d+\"\n\t\t// \"program\"\n\t\t\"beginTime\": []string{\"program\", \"beginTime\"}, // integer\n\t\t//\"broadcastId\":       []string{\"program\", \"broadcastId\"},         // \"\\d+\"\n\t\t\"description\":       []string{\"program\", \"description\"},         // 放送説明\n\t\t\"endTime\":           []string{\"program\", \"endTime\"},             // integer\n\t\t\"isFollowerOnly\":    []string{\"program\", \"isFollowerOnly\"},      // bool\n\t\t\"isPrivate\":         []string{\"program\", \"isPrivate\"},           // bool\n\t\t\"mediaServerType\":   []string{\"program\", \"mediaServerType\"},     // \"DMC\"\n\t\t\"nicoliveProgramId\": []string{\"program\", \"nicoliveProgramId\"},   // \"lv\\d+\"\n\t\t\"openTime\":          []string{\"program\", \"openTime\"},            // integer\n\t\t\"providerType\":      []string{\"program\", \"providerType\"},        // \"community\"\n\t\t\"status\":            []string{\"program\", \"status\"},              //\n\t\t\"userName\":          []string{\"program\", \"supplier\", \"name\"},    // ユーザ名\n\t\t\"userPageUrl\":       []string{\"program\", \"supplier\", \"pageUrl\"}, // \"https://www.nicovideo.jp/user/\\d+\"\n\t\t\"title\":             []string{\"program\", \"title\"},               // title\n\t\t// \"site\"\n\t\t\"nicocas\":        []string{\"site\", \"nicocas\"},                //\n\t\t\"//webSocketUrl\": []string{\"site\", \"relive\", \"webSocketUrl\"}, // \"ws://...\"\n\t\t\"serverTime\":     []string{\"site\", \"serverTime\"},             // integer\n\t\t// \"socialGroup\"\n\t\t\"socDescription\": []string{\"socialGroup\", \"description\"}, // コミュ説明\n\t\t\"socId\":          []string{\"socialGroup\", \"id\"},          // \"co\\d+\" or \"ch\\d+\"\n\t\t\"socLevel\":       []string{\"socialGroup\", \"level\"},       // integer\n\t\t\"socName\":        []string{\"socialGroup\", \"name\"},        // community name\n\t\t\"socType\":        []string{\"socialGroup\", \"type\"},        // \"community\"\n\t\t// \"user\"\n\t\t\"accountType\":  []string{\"user\", \"accountType\"}, // \"premium\"\n\t\t\"//myId\":       []string{\"user\", \"id\"},          // \"\\d+\"\n\t\t\"isLoggedIn\":   []string{\"user\", \"isLoggedIn\"},  // bool\n\t\t\"//myNickname\": []string{\"user\", \"nickname\"},    // string\n\t}\n\n\tkv := map[string]interface{}{}\n\tfor k, a := range proplist {\n\t\tv, ok := objs.Find(props, a...)\n\t\tif ok {\n\t\t\tkv[k] = v\n\n\t\t\tif opt.NicoDebug {\n\t\t\t\tfmt.Println(k, v)\n\t\t\t\tfmt.Println(\"----------\")\n\t\t\t}\n\t\t}\n\t}\n\n\tvar nicocas bool\n\tif _, ok := kv[\"nicocas\"]; ok {\n\t\tnicocas = true\n\t}\n\n\tif nicocas {\n\t\tfmt.Println(\"nicocas not supported.\")\n\t\treturn\n\n\t} else {\n\t\tfor _, k := range []string{\n\t\t\t\"nicoliveProgramId\",\n\t\t\t\"//webSocketUrl\",\n\t\t\t//\"//myId\",\n\t\t} {\n\t\t\tif _, ok := kv[k]; !ok {\n\t\t\t\tfmt.Printf(\"%v not found\\n\", k)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tif opt.NicoFormat == \"\" {\n\t\t\topt.NicoFormat = \"?PID?-?UNAME?-?TITLE?\"\n\t\t}\n\n\t\thls, e := NewHls(opt, kv)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t\tdefer hls.Close()\n\n\t\thls.Wait(opt.NicoTestTimeout, opt.NicoHlsPort)\n\n\t\tdbName = hls.dbName\n\t\tplaylistEnd = hls.finish\n\t\tdone = true\n\t}\n\n\t/*\n\t\tpageUrl, _ := objs.FindString(props, \"broadcaster\", \"pageUrl\")\n\n\t\tif regexp.MustCompile(`\\Ahttps?://cas\\.nicovideo\\.jp/.*?/.*`).MatchString(pageUrl) {\n\t\t\t// 実験放送\n\t\t\tuserId, ok := objs.FindString(props, \"broadcaster\", \"id\")\n\t\t\tif ! ok {\n\t\t\t\tfmt.Printf(\"userId not found\")\n\t\t\t}\n\n\t\t\tnickname, ok := objs.FindString(props, \"broadcaster\", \"nickname\")\n\t\t\tif ! ok {\n\t\t\t\tfmt.Printf(\"nickname not found\")\n\t\t\t}\n\n\t\t\tvar isArchive bool\n\t\t\tswitch status {\n\t\t\t\tcase \"ENDED\":\n\t\t\t\t\tisArchive = true\n\t\t\t}\n\n\t\t}\n\n\t\tlog4gui.Info(fmt.Sprintf(\"isLoggedIn: %v, user_id: %s, nickname: %s\", isLoggedIn, user_id, nickname))\n\t*/\n\n\treturn\n}\n"
  },
  {
    "path": "src/niconico/nico_mem_db.go",
    "content": "package niconico\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"time\"\r\n\t\"os\"\r\n\t\"database/sql\"\r\n)\r\n\r\nfunc (hls *NicoHls) memdbOpen() (err error) {\r\n\tdb, err := sql.Open(\"sqlite3\", \"file::memory:?mode=memory&cache=shared\")\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\thls.memdb = db\r\n\r\n\terr = hls.memdbCreate()\r\n\tif err != nil {\r\n\t\thls.memdb.Close()\r\n\t}\r\n\r\n\tif hls.db != nil {\r\n\t\trows, e := hls.db.Query(`SELECT * FROM\r\n\t\t\t(SELECT seqno, IFNULL(notfound, 0), IFNULL(size, 0) FROM media ORDER BY seqno DESC LIMIT 10) ORDER BY seqno`)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tdefer rows.Close()\r\n\r\n\t\tvar found404 bool\r\n\t\tfor rows.Next() {\r\n\t\t\tvar seqno int\r\n\t\t\tvar notfound bool\r\n\t\t\tvar size int\r\n\t\t\terr = rows.Scan(&seqno, &notfound, &size)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif notfound || size == 0 {\r\n\t\t\t\thls.memdbSet404(seqno)\r\n\t\t\t\tfound404 = true\r\n\t\t\t} else {\r\n\t\t\t\thls.memdbSet200(seqno)\r\n\t\t\t}\r\n\t\t\tif (! found404) {\r\n\t\t\t\thls.memdbSetStopBack(seqno)\r\n\t\t\t\tif hls.nicoDebug {\r\n\t\t\t\t\tfmt.Fprintf(os.Stderr, \"memdbSetStopBack(%d)\\n\", seqno)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc (hls *NicoHls) memdbCreate() (err error) {\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\t_, err = hls.memdb.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS media (\r\n\t\tseqno     INTEGER PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tis200     INTEGER,\r\n\t\tis404     INTEGER,\r\n\t\tstopback  INTEGER\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = hls.memdb.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS media0 ON media(seqno);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc (hls *NicoHls) memdbSetStopBack(seqno int) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbSetStopBack: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\t_, err := hls.memdb.Exec(`\r\n\t\tINSERT OR IGNORE INTO media (seqno, stopback) VALUES (?, 1);\r\n\t\tUPDATE media SET stopback = 1 WHERE seqno=?;\r\n\t`, seqno, seqno)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t}\r\n}\r\nfunc (hls *NicoHls) memdbGetStopBack(seqno int) (res bool) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbGetStopBack: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\thls.memdb.QueryRow(\"SELECT IFNULL(stopback, 0) FROM media WHERE seqno=?\", seqno).Scan(&res)\r\n\treturn\r\n}\r\nfunc (hls *NicoHls) memdbSet200(seqno int) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbSet200: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\thls.memdb.Exec(`INSERT OR REPLACE INTO media (seqno, is200) VALUES (?, 1)`, seqno)\r\n}\r\nfunc (hls *NicoHls) memdbSet404(seqno int) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbSet404: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\thls.memdb.Exec(`INSERT OR REPLACE INTO media (seqno, is404) VALUES (?, 1)`, seqno)\r\n}\r\nfunc (hls *NicoHls) memdbCheck200(seqno int) (res bool) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbCheck200: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\thls.memdb.QueryRow(\"SELECT IFNULL(is200, 0) FROM media WHERE seqno=?\", seqno).Scan(&res)\r\n\treturn\r\n}\r\nfunc (hls *NicoHls) memdbDelete(seqno int) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbDelete: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\tmin := seqno - 100\r\n\thls.memdb.Exec(`DELETE FROM media WHERE seqno < ?`, min)\r\n}\r\nfunc (hls *NicoHls) memdbCount() (res int) {\r\n\tif hls.nicoDebug {\r\n\t\tstart := time.Now().UnixNano()\r\n\t\tdefer func() {\r\n\t\t\tt := (time.Now().UnixNano() - start) / (1000 * 1000)\r\n\t\t\tif t > 100 {\r\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s:[WARN][MEMDB]memdbCount: %d(ms)\\n\", debug_Now(), t)\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n\r\n\thls.memdbMtx.Lock()\r\n\tdefer hls.memdbMtx.Unlock()\r\n\r\n\thls.memdb.QueryRow(\"SELECT COUNT(seqno) FROM media\").Scan(&res)\r\n\treturn\r\n}"
  },
  {
    "path": "src/niconico/nico_rtmp.go",
    "content": "package niconico\r\n\r\nimport (\r\n\t\"encoding/xml\"\r\n\t\"fmt\"\r\n\t\"io/ioutil\"\r\n\t\"log\"\r\n\t\"net/url\"\r\n\t\"regexp\"\r\n\t\"strings\"\r\n\t\"sync\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/amf\"\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/options\"\r\n\t\"github.com/himananiito/livedl/rtmps\"\r\n)\r\n\r\ntype Content struct {\r\n\tId   string `xml:\"id,attr\"`\r\n\tText string `xml:\",chardata\"`\r\n}\r\ntype Tickets struct {\r\n\tName string `xml:\"name,attr\"`\r\n\tText string `xml:\",chardata\"`\r\n}\r\ntype Status struct {\r\n\tTitle                 string    `xml:\"stream>title\"`\r\n\tCommunityId           string    `xml:\"stream>default_community\"`\r\n\tId                    string    `xml:\"stream>id\"`\r\n\tProvider              string    `xml:\"stream>provider_type\"`\r\n\tIsArchive             bool      `xml:\"stream>archive\"`\r\n\tIsArchivePlayerServer bool      `xml:\"stream>is_archiveplayserver\"`\r\n\tQues                  []string  `xml:\"stream>quesheet>que\"`\r\n\tContents              []Content `xml:\"stream>contents_list>contents\"`\r\n\tIsPremium             bool      `xml:\"user>is_premium\"`\r\n\tUrl                   string    `xml:\"rtmp>url\"`\r\n\tTicket                string    `xml:\"rtmp>ticket\"`\r\n\tTickets               []Tickets `xml:\"tickets>stream\"`\r\n\tErrorCode             string    `xml:\"error>code\"`\r\n\tstreams               []Stream\r\n\tchStream              chan struct{}\r\n\twg                    *sync.WaitGroup\r\n}\r\ntype Stream struct {\r\n\toriginUrl    string\r\n\tstreamName   string\r\n\toriginTicket string\r\n}\r\n\r\nfunc (status *Status) quesheet() {\r\n\tstream := make(map[string][]Stream)\r\n\tplayType := make(map[string]string)\r\n\r\n\t// timeshift; <quesheet> tag\r\n\tre_pub := regexp.MustCompile(`\\A/publish\\s+(\\S+)\\s+(?:(\\S+?),)?(\\S+?)(?:\\?(\\S+))?\\z`)\r\n\tre_play := regexp.MustCompile(`\\A/play\\s+(\\S+)\\s+(\\S+)\\z`)\r\n\r\n\tfor _, q := range status.Ques {\r\n\t\t// /publish lv* /content/*/lv*_*_1_*.f4v\r\n\t\tif ma := re_pub.FindStringSubmatch(q); len(ma) >= 5 {\r\n\t\t\tstream[ma[1]] = append(stream[ma[1]], Stream{\r\n\t\t\t\toriginUrl:    ma[2],\r\n\t\t\t\tstreamName:   ma[3],\r\n\t\t\t\toriginTicket: ma[4],\r\n\t\t\t})\r\n\r\n\t\t\t// /play ...\r\n\t\t} else if ma := re_play.FindStringSubmatch(q); len(ma) > 0 {\r\n\t\t\t// /play case:sp:rtmp:lv*_s_lv*,mobile:rtmp:lv*_s_lv*_sub1,premium:rtmp:lv*_s_lv*_sub1,default:rtmp:lv*_s_lv* main\r\n\t\t\tif strings.HasPrefix(ma[1], \"case:\") {\r\n\t\t\t\ts0 := ma[1]\r\n\t\t\t\ts0 = strings.TrimPrefix(s0, \"case:\")\r\n\t\t\t\tcases := strings.Split(s0, \",\")\r\n\t\t\t\t// sp:rtmp:lv*_s_lv*\r\n\t\t\t\tre := regexp.MustCompile(`\\A(\\S+?):rtmp:(\\S+?)\\z`)\r\n\t\t\t\tfor _, c := range cases {\r\n\t\t\t\t\tif ma := re.FindStringSubmatch(c); len(ma) > 0 {\r\n\t\t\t\t\t\tplayType[ma[1]] = ma[2]\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// /play rtmp:lv* main\r\n\t\t\t} else {\r\n\t\t\t\tre := regexp.MustCompile(`\\Artmp:(\\S+?)\\z`)\r\n\t\t\t\tif ma := re.FindStringSubmatch(ma[1]); len(ma) > 0 {\r\n\t\t\t\t\tplayType[\"default\"] = ma[1]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpt, ok := playType[\"premium\"]\r\n\tif ok && status.IsPremium {\r\n\t\ts, ok := stream[pt]\r\n\t\tif ok {\r\n\t\t\tstatus.streams = s\r\n\t\t}\r\n\t} else {\r\n\t\tpt, ok := playType[\"default\"]\r\n\t\tif ok {\r\n\t\t\ts, ok := stream[pt]\r\n\t\t\tif ok {\r\n\t\t\t\tstatus.streams = s\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\nfunc (status *Status) initStreams() {\r\n\r\n\tif len(status.streams) > 0 {\r\n\t\treturn\r\n\t}\r\n\r\n\t//if status.isOfficialLive() {\r\n\tstatus.contentsOfficialLive()\r\n\t//} else if status.isLive() {\r\n\tstatus.contentsNonOfficialLive()\r\n\t//} else {\r\n\tstatus.quesheet()\r\n\t//}\r\n\r\n\treturn\r\n}\r\nfunc (status *Status) getFileName(index int) (name string) {\r\n\tif len(status.streams) == 1 {\r\n\t\t//name = fmt.Sprintf(\"%s.flv\", status.Id)\r\n\t\tname = fmt.Sprintf(\"%s-%s-%s.flv\", status.Id, status.CommunityId, status.Title)\r\n\t} else if len(status.streams) > 1 {\r\n\t\t//name = fmt.Sprintf(\"%s-%d.flv\", status.Id, 1 + index)\r\n\t\tname = fmt.Sprintf(\"%s-%s-%s#%d.flv\", status.Id, status.CommunityId, status.Title, 1+index)\r\n\t} else {\r\n\t\tlog.Fatalf(\"No stream\")\r\n\t}\r\n\tname = files.ReplaceForbidden(name)\r\n\treturn\r\n}\r\nfunc (status *Status) contentsNonOfficialLive() {\r\n\tre := regexp.MustCompile(`\\A(?:rtmp:)?(rtmp\\w*://\\S+?)(?:,(\\S+?)(?:\\?(\\S+))?)?\\z`)\r\n\r\n\t// Live (not timeshift); <contents_list> tag\r\n\tfor _, c := range status.Contents {\r\n\t\tif ma := re.FindStringSubmatch(c.Text); len(ma) > 0 {\r\n\t\t\tstatus.streams = append(status.streams, Stream{\r\n\t\t\t\toriginUrl:    ma[1],\r\n\t\t\t\tstreamName:   ma[2],\r\n\t\t\t\toriginTicket: ma[3],\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n\r\n}\r\nfunc (status *Status) contentsOfficialLive() {\r\n\r\n\ttickets := make(map[string]string)\r\n\tfor _, t := range status.Tickets {\r\n\t\ttickets[t.Name] = t.Text\r\n\t}\r\n\r\n\tfor _, c := range status.Contents {\r\n\t\tif strings.HasPrefix(c.Text, \"case:\") {\r\n\t\t\tc.Text = strings.TrimPrefix(c.Text, \"case:\")\r\n\r\n\t\t\tfor _, c := range strings.Split(c.Text, \",\") {\r\n\t\t\t\tc, e := url.PathUnescape(c)\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\tfmt.Printf(\"%v\\n\", e)\r\n\t\t\t\t}\r\n\r\n\t\t\t\tre := regexp.MustCompile(`\\A(\\S+?):(?:limelight:|akamai:)?(\\S+),(\\S+)\\z`)\r\n\t\t\t\tif ma := re.FindStringSubmatch(c); len(ma) > 0 {\r\n\t\t\t\t\tfmt.Printf(\"\\n%#v\\n\", ma)\r\n\t\t\t\t\tswitch ma[1] {\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tfmt.Printf(\"unknown contents case %#v\\n\", ma[1])\r\n\t\t\t\t\tcase \"mobile\":\r\n\t\t\t\t\tcase \"middle\":\r\n\t\t\t\t\tcase \"default\":\r\n\t\t\t\t\t\tstatus.Url = ma[2]\r\n\t\t\t\t\t\tt, ok := tickets[ma[3]]\r\n\t\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\t\tfmt.Printf(\"not found %s\\n\", ma[3])\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tfmt.Printf(\"%s\\n\", t)\r\n\t\t\t\t\t\tstatus.streams = append(status.streams, Stream{\r\n\t\t\t\t\t\t\tstreamName:   ma[3],\r\n\t\t\t\t\t\t\toriginTicket: t,\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunc (status *Status) relayStreamName(i, offset int) (s string) {\r\n\ts = regexp.MustCompile(`[^/\\\\]+\\z`).FindString(status.streams[i].streamName)\r\n\tif offset >= 0 {\r\n\t\ts += fmt.Sprintf(\"_%d\", offset)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc (status *Status) streamName(i, offset int) (name string, err error) {\r\n\tif status.isOfficialLive() {\r\n\t\tif i >= len(status.streams) {\r\n\t\t\terr = fmt.Errorf(\"(status *Status) streamName(i int): Out of index: %d\\n\", i)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tname = status.streams[i].streamName\r\n\t\tif status.streams[i].originTicket != \"\" {\r\n\t\t\tname += \"?\" + status.streams[i].originTicket\r\n\t\t}\r\n\t\treturn\r\n\r\n\t} else if status.isOfficialTs() {\r\n\t\tname = status.streams[i].streamName\r\n\t\tname = regexp.MustCompile(`(?i:\\.flv)$`).ReplaceAllString(name, \"\")\r\n\t\tif regexp.MustCompile(`(?i:\\.(?:f4v|mp4))$`).MatchString(name) {\r\n\t\t\tname = \"mp4:\" + name\r\n\t\t} else if regexp.MustCompile(`(?i:\\.raw)$`).MatchString(name) {\r\n\t\t\tname = \"raw:\" + name\r\n\t\t}\r\n\r\n\t} else {\r\n\t\tname = status.relayStreamName(i, offset)\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc (status *Status) tcUrl() (url string, err error) {\r\n\tif status.Url != \"\" {\r\n\t\turl = status.Url\r\n\t\treturn\r\n\t} else {\r\n\t\tstatus.contentsOfficialLive()\r\n\t}\r\n\r\n\tif status.Url != \"\" {\r\n\t\turl = status.Url\r\n\t\treturn\r\n\t}\r\n\r\n\terr = fmt.Errorf(\"tcUrl not found\")\r\n\treturn\r\n}\r\nfunc (status *Status) isTs() bool {\r\n\treturn status.IsArchive\r\n}\r\nfunc (status *Status) isLive() bool {\r\n\treturn (!status.IsArchive)\r\n}\r\nfunc (status *Status) isOfficialLive() bool {\r\n\treturn (status.Provider == \"official\") && (!status.IsArchive)\r\n}\r\nfunc (status *Status) isOfficialTs() bool {\r\n\tif status.IsArchive {\r\n\t\tswitch status.Provider {\r\n\t\tcase \"official\":\r\n\t\t\treturn true\r\n\t\tcase \"channel\":\r\n\t\t\treturn status.IsArchivePlayerServer\r\n\t\t}\r\n\t}\r\n\treturn false\r\n}\r\n\r\nfunc (st Stream) relayStreamName(offset int) (s string) {\r\n\ts = regexp.MustCompile(`[^/\\\\]+\\z`).FindString(st.streamName)\r\n\tif offset >= 0 {\r\n\t\ts += fmt.Sprintf(\"_%d\", offset)\r\n\t}\r\n\treturn\r\n}\r\nfunc (st Stream) noticeStreamName(offset int) (s string) {\r\n\ts = st.streamName\r\n\ts = regexp.MustCompile(`(?i:\\.flv)$`).ReplaceAllString(s, \"\")\r\n\tif regexp.MustCompile(`(?i:\\.(?:f4v|mp4))$`).MatchString(s) {\r\n\t\ts = \"mp4:\" + s\r\n\t} else if regexp.MustCompile(`(?i:\\.raw)$`).MatchString(s) {\r\n\t\ts = \"raw:\" + s\r\n\t}\r\n\r\n\tif st.originTicket != \"\" {\r\n\t\ts += \"?\" + st.originTicket\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc (status *Status) recStream(index int, opt options.Option) (err error) {\r\n\tdefer func() {\r\n\t\t<-status.chStream\r\n\t\tstatus.wg.Done()\r\n\t}()\r\n\r\n\tstream := status.streams[index]\r\n\r\n\ttcUrl, err := status.tcUrl()\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\trtmp, err := rtmps.NewRtmp(\r\n\t\t// tcUrl\r\n\t\ttcUrl,\r\n\t\t// swfUrl\r\n\t\t\"http://live.nicovideo.jp/nicoliveplayer.swf?180116154229\",\r\n\t\t// pageUrl\r\n\t\t\"http://live.nicovideo.jp/watch/\"+status.Id,\r\n\t\t// option\r\n\t\tstatus.Ticket,\r\n\t)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer rtmp.Close()\r\n\r\n\tfileName, err := files.GetFileNameNext(status.getFileName(index))\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\trtmp.SetFlvName(fileName)\r\n\r\n\ttryRecord := func() (incomplete bool, err error) {\r\n\r\n\t\tif err = rtmp.Connect(); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// default: 2500000\r\n\t\t//if err = rtmp.SetPeerBandwidth(100*1000*1000, 0); err != nil {\r\n\t\tif err = rtmp.SetPeerBandwidth(2500000, 0); err != nil {\r\n\t\t\tfmt.Printf(\"SetPeerBandwidth: %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif err = rtmp.WindowAckSize(2500000); err != nil {\r\n\t\t\tfmt.Printf(\"WindowAckSize: %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif err = rtmp.CreateStream(); err != nil {\r\n\t\t\tfmt.Printf(\"CreateStream %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif err = rtmp.SetBufferLength(0, 2000); err != nil {\r\n\t\t\tfmt.Printf(\"SetBufferLength: %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tvar offset int\r\n\t\tif status.IsArchive {\r\n\t\t\toffset = 0\r\n\t\t} else {\r\n\t\t\toffset = -2\r\n\t\t}\r\n\r\n\t\tif status.isOfficialTs() {\r\n\t\t\tfor i := 0; true; i++ {\r\n\t\t\t\tif i > 30 {\r\n\t\t\t\t\terr = fmt.Errorf(\"sendFileRequest: No response\")\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tdata, e := rtmp.Command(\r\n\t\t\t\t\t\"sendFileRequest\", []interface{}{\r\n\t\t\t\t\t\tnil,\r\n\t\t\t\t\t\tamf.SwitchToAmf3(),\r\n\t\t\t\t\t\t[]string{\r\n\t\t\t\t\t\t\tstream.streamName,\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t})\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\terr = e\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar resCnt int\r\n\t\t\t\tswitch data.(type) {\r\n\t\t\t\tcase map[string]interface{}:\r\n\t\t\t\t\tresCnt = len(data.(map[string]interface{}))\r\n\t\t\t\tcase map[int]interface{}:\r\n\t\t\t\t\tresCnt = len(data.(map[int]interface{}))\r\n\t\t\t\tcase []interface{}:\r\n\t\t\t\t\tresCnt = len(data.([]interface{}))\r\n\t\t\t\tcase []string:\r\n\t\t\t\t\tresCnt = len(data.([]string))\r\n\t\t\t\t}\r\n\t\t\t\tif resCnt > 0 {\r\n\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t\ttime.Sleep(10 * time.Second)\r\n\t\t\t}\r\n\r\n\t\t} else if !status.isOfficialLive() {\r\n\t\t\t// /publishの第二引数\r\n\t\t\t// streamName(param1:String)\r\n\t\t\t// 「,」で区切る\r\n\t\t\t// ._originUrl, streamName(playStreamName)\r\n\t\t\t// streamName に、「?」がついてるなら originTickt となる\r\n\t\t\t// streamName の.flvは削除する\r\n\t\t\t// streamNameが/\\.(f4v|mp4)$/iなら、頭にmp4:をつける\r\n\t\t\t// /\\.raw$/iなら、raw:をつける。\r\n\t\t\t// relayStreamName: streamNameの頭からスラッシュまでを削除したもの\r\n\r\n\t\t\t_, err = rtmp.Command(\r\n\t\t\t\t\"nlPlayNotice\", []interface{}{\r\n\t\t\t\t\tnil,\r\n\t\t\t\t\t// _connection.request.originUrl\r\n\t\t\t\t\tstream.originUrl,\r\n\r\n\t\t\t\t\t// this._connection.request.playStreamRequest\r\n\t\t\t\t\t// originticket あるなら\r\n\t\t\t\t\t// playStreamName ? this._originTicket\r\n\t\t\t\t\t// 無いなら playStreamName\r\n\t\t\t\t\tstream.noticeStreamName(offset),\r\n\r\n\t\t\t\t\t// var _loc1_:String = this._relayStreamName;\r\n\t\t\t\t\t// if(this._offset != -2)\r\n\t\t\t\t\t// {\r\n\t\t\t\t\t// _loc1_ = _loc1_ + (\"_\" + this.offset);\r\n\t\t\t\t\t// }\r\n\t\t\t\t\t// user nama: String 'lvxxxxxxxxx'\r\n\t\t\t\t\t// user kako: lvxxxxxxxxx_xxxxxxxxxxxx_1_xxxxxx.f4v_0\r\n\t\t\t\t\tstream.relayStreamName(offset),\r\n\r\n\t\t\t\t\t// seek offset\r\n\t\t\t\t\t// user nama: -2, user kako: 0\r\n\t\t\t\t\toffset,\r\n\t\t\t\t})\r\n\t\t\tif err != nil {\r\n\t\t\t\tfmt.Printf(\"nlPlayNotice %v\\n\", err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif err = rtmp.SetBufferLength(1, 3600*1000); err != nil {\r\n\t\t\tfmt.Printf(\"SetBufferLength: %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// No return\r\n\t\trtmp.SetFixAggrTimestamp(true)\r\n\r\n\t\t// user kako: lv*********_************_*_******.f4v_0\r\n\t\t// official or channel ts: mp4:/content/********/lv*********_************_*_******.f4v\r\n\t\t//if err = rtmp.Play(status.origin.playStreamName(status.isTsOfficial(), offset)); err != nil {\r\n\t\tstreamName, err := status.streamName(index, offset)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif status.isOfficialTs() {\r\n\t\t\tts := rtmp.GetTimestamp()\r\n\t\t\tif ts > 1000 {\r\n\t\t\t\terr = rtmp.PlayTime(streamName, ts-1000)\r\n\t\t\t} else {\r\n\t\t\t\terr = rtmp.PlayTime(streamName, -5000)\r\n\t\t\t}\r\n\r\n\t\t} else if status.isTs() {\r\n\t\t\trtmp.SetFlush(true)\r\n\t\t\terr = rtmp.PlayTime(streamName, -5000)\r\n\r\n\t\t} else {\r\n\t\t\terr = rtmp.Play(streamName)\r\n\t\t}\r\n\t\tif err != nil {\r\n\t\t\tfmt.Printf(\"Play: %v\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// Non-recordedなタイムシフトでseekしても、timestampが変わるだけで\r\n\t\t// 最初からの再生となってしまうのでやらないこと\r\n\r\n\t\t// 公式のタイムシフトでSeekしてもタイムスタンプがおかしい\r\n\r\n\t\tif opt.NicoTestTimeout > 0 {\r\n\t\t\t// test mode\r\n\t\t\t_, incomplete, err = rtmp.WaitTest(opt.NicoTestTimeout)\r\n\t\t} else {\r\n\t\t\t// normal mode\r\n\t\t\t_, incomplete, err = rtmp.Wait()\r\n\t\t}\r\n\t\treturn\r\n\t} // end func\r\n\r\n\t//ticketTime := time.Now().Unix()\r\n\t//rtmp.SetNoSeek(false)\r\n\tfor i := 0; i < 10; i++ {\r\n\t\tincomplete, e := tryRecord()\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\tfmt.Printf(\"%v\\n\", e)\r\n\t\t\treturn\r\n\t\t} else if incomplete && status.isOfficialTs() {\r\n\t\t\tfmt.Println(\"incomplete\")\r\n\t\t\ttime.Sleep(3 * time.Second)\r\n\r\n\t\t\t// update ticket\r\n\t\t\tif true {\r\n\t\t\t\t//if time.Now().Unix() > ticketTime + 60 {\r\n\t\t\t\t//ticketTime = time.Now().Unix()\r\n\t\t\t\tif ticket, e := getTicket(opt); e != nil {\r\n\t\t\t\t\terr = e\r\n\t\t\t\t\treturn\r\n\t\t\t\t} else {\r\n\t\t\t\t\trtmp.SetConnectOpt(ticket)\r\n\t\t\t\t}\r\n\t\t\t\t//}\r\n\t\t\t}\r\n\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\tbreak\r\n\t}\r\n\r\n\tfmt.Printf(\"done\\n\")\r\n\treturn\r\n}\r\n\r\nfunc (status *Status) recAllStreams(opt options.Option) (err error) {\r\n\r\n\tstatus.initStreams()\r\n\r\n\tvar MaxConn int\r\n\tif opt.NicoRtmpMaxConn == 0 {\r\n\t\tif status.isOfficialTs() {\r\n\t\t\tMaxConn = 1\r\n\t\t} else {\r\n\t\t\tMaxConn = 4\r\n\t\t}\r\n\t} else if opt.NicoRtmpMaxConn < 0 {\r\n\t\tMaxConn = 1\r\n\t} else {\r\n\t\tMaxConn = opt.NicoRtmpMaxConn\r\n\t}\r\n\r\n\tstatus.wg = &sync.WaitGroup{}\r\n\tstatus.chStream = make(chan struct{}, MaxConn)\r\n\r\n\tticketTime := time.Now().Unix()\r\n\r\n\tfor index, _ := range status.streams {\r\n\t\tif opt.NicoRtmpIndex != nil {\r\n\t\t\tif tes, ok := opt.NicoRtmpIndex[index]; !ok || !tes {\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// blocks here\r\n\t\tstatus.chStream <- struct{}{}\r\n\t\tstatus.wg.Add(1)\r\n\r\n\t\tgo status.recStream(index, opt)\r\n\r\n\t\tnow := time.Now().Unix()\r\n\t\tif now > ticketTime+60 {\r\n\t\t\tticketTime = now\r\n\t\t\tif ticket, e := getTicket(opt); e != nil {\r\n\t\t\t\terr = e\r\n\t\t\t\treturn\r\n\t\t\t} else {\r\n\t\t\t\tstatus.Ticket = ticket\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tstatus.wg.Wait()\r\n\r\n\treturn\r\n}\r\n\r\nfunc getTicket(opt options.Option) (ticket string, err error) {\r\n\tstatus, notLogin, err := getStatus(opt)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif status.Ticket != \"\" {\r\n\t\tticket = status.Ticket\r\n\t} else {\r\n\t\tif notLogin {\r\n\t\t\terr = fmt.Errorf(\"notLogin\")\r\n\t\t} else {\r\n\t\t\terr = fmt.Errorf(\"Ticket not found\")\r\n\t\t}\r\n\t}\r\n\treturn\r\n}\r\nfunc getStatus(opt options.Option) (status *Status, notLogin bool, err error) {\r\n\tvar uri string\r\n\r\n\t// experimental\r\n\tif opt.NicoStatusHTTPS {\r\n\t\turi = fmt.Sprintf(\"https://ow.live.nicovideo.jp/api/getplayerstatus?v=%s\", opt.NicoLiveId)\r\n\t} else {\r\n\t\turi = fmt.Sprintf(\"http://watch.live.nicovideo.jp/api/getplayerstatus?v=%s\", opt.NicoLiveId)\r\n\t}\r\n\r\n\theader := make(map[string]string, 4)\r\n\tif opt.NicoSession != \"\" {\r\n\t\theader[\"Cookie\"] = \"user_session=\" + opt.NicoSession\r\n\t}\r\n\r\n\t// experimental\r\n\t//if opt.NicoStatusHTTPS {\r\n\t//\treq.Header.Set(\"User-Agent\", \"Niconico/1.0 (Unix; U; iPhone OS 10.3.3; ja-jp; nicoiphone; iPhone5,2) Version/6.65\")\r\n\t//}\r\n\r\n\tresp, err, neterr := httpbase.Get(uri, header)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif neterr != nil {\r\n\t\terr = neterr\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\r\n\tdat, _ := ioutil.ReadAll(resp.Body)\r\n\tstatus = &Status{}\r\n\terr = xml.Unmarshal(dat, status)\r\n\tif err != nil {\r\n\t\t//fmt.Println(string(dat))\r\n\t\tfmt.Printf(\"error: %v\", err)\r\n\t\treturn\r\n\t}\r\n\r\n\tswitch status.ErrorCode {\r\n\tcase \"\":\r\n\tcase \"notlogin\":\r\n\t\tnotLogin = true\r\n\tdefault:\r\n\t\terr = fmt.Errorf(\"Error code: %s\\n\", status.ErrorCode)\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc NicoRecRtmp(opt options.Option) (notLogin bool, err error) {\r\n\tstatus, notLogin, err := getStatus(opt)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif notLogin {\r\n\t\treturn\r\n\t}\r\n\r\n\tstatus.recAllStreams(opt)\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/objs/objs.go",
    "content": "\r\npackage objs\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"encoding/json\"\r\n)\r\n\r\nfunc PrintAsJson(data interface{}) {\r\n\tjson, err := json.MarshalIndent(data, \"\", \"  \")\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tfmt.Println(string(json))\r\n}\r\nfunc Find(intf interface{}, keylist... string) (res interface{}, ok bool) {\r\n\tres = intf\r\n\tif len(keylist) == 0 {\r\n\t\tok = true\r\n\t\treturn\r\n\t}\r\n\tfor i, k := range keylist {\r\n\t\tvar test bool\r\n\t\t//var obj map[string]interface{}\r\n\t\tswitch res.(type) {\r\n\t\tcase map[string]interface{}:\r\n\t\t\tres, test = res.(map[string]interface{})[k]\r\n\t\t\tif (! test) {\r\n\t\t\t\tok = false\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\tcase []interface{}:\r\n\t\t\tfor _, o := range res.([]interface{}) {\r\n\t\t\t\t_res, _ok := Find(o, keylist[i:]...)\r\n\t\t\t\tif _ok {\r\n\t\t\t\t\tres = _res\r\n\t\t\t\t\tok = _ok\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tok = true\r\n\treturn\r\n}\r\nfunc FindFloat64(intf interface{}, keylist... string) (res float64, ok bool) {\r\n\tval, ok := Find(intf, keylist...)\r\n\tif !ok {\r\n\t\treturn\r\n\t}\r\n\tres, ok = val.(float64)\r\n\treturn\r\n}\r\nfunc FindString(intf interface{}, keylist... string) (res string, ok bool) {\r\n\tval, ok := Find(intf, keylist...)\r\n\tif !ok {\r\n\t\treturn\r\n\t}\r\n\tres, ok = val.(string)\r\n\treturn\r\n}\r\nfunc FindBool(intf interface{}, keylist... string) (res bool, ok bool) {\r\n\tval, ok := Find(intf, keylist...)\r\n\tif !ok {\r\n\t\treturn\r\n\t}\r\n\tres, ok = val.(bool)\r\n\treturn\r\n}\r\nfunc FindArray(intf interface{}, keylist... string) (res []interface{}, ok bool) {\r\n\tval, ok := Find(intf, keylist...)\r\n\tif !ok {\r\n\t\treturn\r\n\t}\r\n\tres, ok = val.([]interface{})\r\n\treturn\r\n}\r\n\r\n"
  },
  {
    "path": "src/options/options.go",
    "content": "package options\r\n\r\nimport (\r\n\t\"database/sql\"\r\n\t\"fmt\"\r\n\t\"io/ioutil\"\r\n\t\"log\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\t\"regexp\"\r\n\t\"strconv\"\r\n\t\"strings\"\r\n\r\n\t\"github.com/himananiito/livedl/buildno\"\r\n\t\"github.com/himananiito/livedl/cryptoconf\"\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"golang.org/x/crypto/sha3\"\r\n)\r\n\r\nvar DefaultTcasRetryTimeoutMinute = 5 // TcasRetryTimeoutMinute\r\nvar DefaultTcasRetryInterval = 60     // TcasRetryInterval\r\n\r\ntype Option struct {\r\n\tCommand                string\r\n\tNicoLiveId             string\r\n\tNicoStatusHTTPS        bool\r\n\tNicoSession            string\r\n\tNicoLoginAlias         string\r\n\tNicoRtmpMaxConn        int\r\n\tNicoRtmpOnly           bool\r\n\tNicoRtmpIndex          map[int]bool\r\n\tNicoHlsOnly            bool\r\n\tNicoLoginOnly          bool\r\n\tNicoTestTimeout        int\r\n\tTcasId                 string\r\n\tTcasRetry              bool\r\n\tTcasRetryTimeoutMinute int // 再試行を終了する時間(初回終了または録画終了からの時間「分」)\r\n\tTcasRetryInterval      int // 再試行を行うまでの待ち時間\r\n\tYoutubeId              string\r\n\tConfFile               string // deprecated\r\n\tConfPass               string // deprecated\r\n\tZipFile                string\r\n\tDBFile                 string\r\n\tNicoHlsPort            int\r\n\tNicoLimitBw            int\r\n\tNicoTsStart            float64\r\n\tNicoFormat             string\r\n\tNicoFastTs             bool\r\n\tNicoUltraFastTs        bool\r\n\tNicoAutoConvert        bool\r\n\tNicoAutoDeleteDBMode   int  // 0:削除しない 1:mp4が分割されなかったら削除 2:分割されても削除\r\n\tNicoDebug              bool // デバッグ情報の記録\r\n\tConvExt                string\r\n\tExtractChunks          bool\r\n\tNicoForceResv          bool // 終了番組の上書きタイムシフト予約\r\n\tYtNoStreamlink         bool\r\n\tYtNoYoutubeDl          bool\r\n\tNicoSkipHb             bool // コメント出力時に/hbコマンドを出さない\r\n\tHttpRootCA             string\r\n\tHttpSkipVerify         bool\r\n\tHttpProxy              string\r\n\tNoChdir                bool\r\n}\r\n\r\nfunc getCmd() (cmd string) {\r\n\tcmd = filepath.Base(os.Args[0])\r\n\text := filepath.Ext(cmd)\r\n\tcmd = strings.TrimSuffix(cmd, ext)\r\n\treturn\r\n}\r\nfunc versionStr() string {\r\n\tcmd := filepath.Base(os.Args[0])\r\n\text := filepath.Ext(cmd)\r\n\tcmd = strings.TrimSuffix(cmd, ext)\r\n\treturn fmt.Sprintf(`%s (%s)`, cmd, buildno.GetBuildNo())\r\n}\r\nfunc version() {\r\n\tfmt.Println(versionStr())\r\n\tos.Exit(0)\r\n}\r\nfunc Help(verbose ...bool) {\r\n\tcmd := filepath.Base(os.Args[0])\r\n\text := filepath.Ext(cmd)\r\n\tcmd = strings.TrimSuffix(cmd, ext)\r\n\r\n\tformat := `%s (%s)\r\nUsage:\r\n%s [COMMAND] options... [--] FILE\r\n\r\nCOMMAND:\r\n  -nico    ニコニコ生放送の録画\r\n  -tcas    ツイキャスの録画\r\n  -yt      YouTube Liveの録画\r\n  -d2m     録画済みのdb(.sqlite3)をmp4に変換する(-db-to-mp4)\r\n\r\nオプション/option:\r\n  -h         ヘルプを表示\r\n  -vh        全てのオプションを表示\r\n  -v         バージョンを表示\r\n  -no-chdir  起動する時chdirしない\r\n  --         後にオプションが無いことを指定\r\n\r\nニコニコ生放送録画用オプション:\r\n  -nico-login <id>,<password>    (+) ニコニコのIDとパスワードを指定する\r\n  -nico-session <session>        Cookie[user_session]を指定する\r\n  -nico-login-only=on            (+) 必ずログイン状態で録画する\r\n  -nico-login-only=off           (+) 非ログインでも録画可能とする(デフォルト)\r\n  -nico-hls-only                 録画時にHLSのみを試す\r\n  -nico-hls-only=on              (+) 上記を有効に設定\r\n  -nico-hls-only=off             (+) 上記を無効に設定(デフォルト)\r\n  -nico-rtmp-only                録画時にRTMPのみを試す\r\n  -nico-rtmp-only=on             (+) 上記を有効に設定\r\n  -nico-rtmp-only=off            (+) 上記を無効に設定(デフォルト)\r\n  -nico-rtmp-max-conn <num>      RTMPの同時接続数を設定\r\n  -nico-rtmp-index <num>[,<num>] RTMP録画を行うメディアファイルの番号を指定\r\n  -nico-hls-port <portnum>       [実験的] ローカルなHLSサーバのポート番号\r\n  -nico-limit-bw <bandwidth>     (+) HLSのBANDWIDTHの上限値を指定する。0=制限なし\r\n  -nico-format \"FORMAT\"          (+) 保存時のファイル名を指定する\r\n  -nico-fast-ts                  倍速タイムシフト録画を行う(新配信タイムシフト)\r\n  -nico-fast-ts=on               (+) 上記を有効に設定\r\n  -nico-fast-ts=off              (+) 上記を無効に設定(デフォルト)\r\n  -nico-auto-convert=on          (+) 録画終了後自動的にMP4に変換するように設定\r\n  -nico-auto-convert=off         (+) 上記を無効に設定\r\n  -nico-auto-delete-mode 0       (+) 自動変換後にデータベースファイルを削除しないように設定(デフォルト)\r\n  -nico-auto-delete-mode 1       (+) 自動変換でMP4が分割されなかった場合のみ削除するように設定\r\n  -nico-auto-delete-mode 2       (+) 自動変換でMP4が分割されても削除するように設定\r\n  -nico-force-reservation=on     (+) 視聴にタイムシフト予約が必要な場合に自動的に上書きする\r\n  -nico-force-reservation=off    (+) 自動的にタイムシフト予約しない(デフォルト)\r\n  -nico-skip-hb=on               (+) コメント書き出し時に/hbコマンドを出さない\r\n  -nico-skip-hb=off              (+) コメント書き出し時に/hbコマンドも出す(デフォルト)\r\n  -nico-ts-start <num>           タイムシフトの録画を指定した再生時間(秒)から開始する\r\n  -nico-ts-start-min <num>       タイムシフトの録画を指定した再生時間(分)から開始する\r\n\r\nツイキャス録画用オプション:\r\n  -tcas-retry=on                 (+) 録画終了後に再試行を行う\r\n  -tcas-retry=off                (+) 録画終了後に再試行を行わない\r\n  -tcas-retry-timeout            (+) 再試行を開始してから終了するまでの時間（分)\r\n                                     -1で無限ループ。デフォルト: 5分\r\n  -tcas-retry-interval           (+) 再試行を行う間隔（秒）デフォルト: 60秒\r\n\r\nYoutube live録画用オプション:\r\n  -yt-api-key <key>              (+) YouTube Data API v3 keyを設定する(未使用)\r\n  -yt-no-streamlink=on           (+) Streamlinkを使用しない\r\n  -yt-no-streamlink=off          (+) Streamlinkを使用する(デフォルト)\r\n  -yt-no-youtube-dl=on           (+) youtube-dlを使用しない\r\n  -yt-no-youtube-dl=off          (+) youtube-dlを使用する(デフォルト)\r\n\r\n変換オプション:\r\n  -extract-chunks=off            (+) -d2mで動画ファイルに書き出す(デフォルト)\r\n  -extract-chunks=on             (+) [上級者向] 各々のフラグメントを書き出す(大量のファイルが生成される)\r\n  -conv-ext=mp4                  (+) -d2mで出力の拡張子を.mp4とする(デフォルト)\r\n  -conv-ext=ts                   (+) -d2mで出力の拡張子を.tsとする\r\n\r\nHTTP関連\r\n  -http-skip-verify=on           (+) TLS証明書の認証をスキップする (32bit版対策)\r\n  -http-skip-verify=off          (+) TLS証明書の認証をスキップしない (デフォルト)\r\n\r\n\r\n(+)のついたオプションは、次回も同じ設定が使用されることを示す。\r\n\r\nFILE:\r\n  ニコニコ生放送/nicolive:\r\n    http://live2.nicovideo.jp/watch/lvXXXXXXXXX\r\n    lvXXXXXXXXX\r\n  ツイキャス/twitcasting:\r\n    https://twitcasting.tv/XXXXX\r\n`\r\n\tfmt.Printf(format, cmd, buildno.GetBuildNo(), cmd)\r\n\r\n\tfor _, b := range verbose {\r\n\t\tif b {\r\n\t\t\tfmt.Print(`\r\n旧オプション:\r\n  -conf-pass <password> [廃止] 設定ファイルのパスワード\r\n  -z2m                  録画済みのzipをmp4に変換する(-zip-to-mp4)\r\n  -nico-status-https    -\r\n\r\nデバッグ用オプション:\r\n  -nico-test-run           ニコ生テストラン\r\n  -nico-test-timeout <num> ニコ生テストランでの各放送のタイムアウト\r\n  -nico-test-format        フォーマット、保存しない\r\n  -nico-ufast-ts           TS保存にウェイトを入れない\r\n  -nico-debug              デバッグ用ログ出力する\r\n\r\nHTTP関連\r\n  -http-root-ca <file>    ルート証明書ファイルを指定(pem/der)\r\n  -http-skip-verify       TLS証明書の認証をスキップする\r\n  -http-proxy <proxy url> [警告] proxyを設定する\r\n[警告] 情報流出に注意。信頼できるproxy serverのみに使用すること。\r\n\r\n`)\r\n\t\t\tbreak\r\n\t\t}\r\n\t}\r\n\r\n\tos.Exit(0)\r\n}\r\n\r\nfunc dbConfSet(db *sql.DB, k string, v interface{}) {\r\n\tquery := `INSERT OR REPLACE INTO conf (k,v) VALUES (?,?)`\r\n\r\n\tif _, err := db.Exec(query, k, v); err != nil {\r\n\t\tlog.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n}\r\n\r\nfunc SetNicoLogin(hash, user, pass string) (err error) {\r\n\tdb, err := dbAccountOpen()\r\n\tif err != nil {\r\n\t\tif db != nil {\r\n\t\t\tdb.Close()\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\t_, err = db.Exec(`\r\n\t\tINSERT OR IGNORE INTO niconico (alias, user, pass) VALUES(?, ?, ?);\r\n\t\tUPDATE niconico SET user = ?, pass = ? WHERE alias = ?\r\n\t`, hash, user, pass, user, pass, hash)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\tfmt.Printf(\"niconico account saved.\\n\")\r\n\treturn\r\n}\r\nfunc SetNicoSession(hash, session string) (err error) {\r\n\tdb, err := dbAccountOpen()\r\n\tif err != nil {\r\n\t\tif db != nil {\r\n\t\t\tdb.Close()\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\t_, err = db.Exec(`\r\n\t\tINSERT OR IGNORE INTO niconico (alias, session) VALUES(?, ?);\r\n\t\tUPDATE niconico SET session = ? WHERE alias = ?\r\n\t`, hash, session, session, hash)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc LoadNicoAccount(alias string) (user, pass, session string, err error) {\r\n\tdb, err := dbAccountOpen()\r\n\tif err != nil {\r\n\t\tif db != nil {\r\n\t\t\tdb.Close()\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tdb.QueryRow(`SELECT user, pass, IFNULL(session, \"\") FROM niconico WHERE alias = ?`, alias).Scan(&user, &pass, &session)\r\n\treturn\r\n}\r\nfunc SetYoutubeApiKey(key string) (err error) {\r\n\tdb, err := dbAccountOpen()\r\n\tif err != nil {\r\n\t\tif db != nil {\r\n\t\t\tdb.Close()\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\t_, err = db.Exec(`\r\n\t\tINSERT OR IGNORE INTO youtubeapikey (id, key) VALUES(1, ?);\r\n\t\tUPDATE youtubeapikey SET key = ? WHERE id = 1\r\n\t`, key, key)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\tfmt.Printf(\"Youtube API KEY saved.\\n\")\r\n\treturn\r\n}\r\nfunc LoadYoutubeApiKey() (key string, err error) {\r\n\tdb, err := dbAccountOpen()\r\n\tif err != nil {\r\n\t\tif db != nil {\r\n\t\t\tdb.Close()\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tdb.QueryRow(`SELECT IFNULL(key, \"\") FROM youtubeapikey WHERE id = 1`).Scan(&key)\r\n\tif key == \"\" {\r\n\t\terr = fmt.Errorf(\"apikey not found\")\r\n\t}\r\n\treturn\r\n}\r\nfunc dbAccountOpen() (db *sql.DB, err error) {\r\n\r\n\tbase := func() string {\r\n\t\tif b := os.Getenv(\"LIVEDL_DIR\"); b != \"\" {\r\n\t\t\treturn b\r\n\t\t}\r\n\t\tif b := os.Getenv(\"APPDATA\"); b != \"\" {\r\n\t\t\treturn fmt.Sprintf(\"%s/livedl\", b)\r\n\t\t}\r\n\t\tif b := os.Getenv(\"HOME\"); b != \"\" {\r\n\t\t\treturn fmt.Sprintf(\"%s/.livedl\", b)\r\n\t\t}\r\n\t\treturn \"\"\r\n\t}()\r\n\tif base == \"\" {\r\n\t\tlog.Fatalln(\"basedir for account not defined\")\r\n\t}\r\n\r\n\tname := fmt.Sprintf(\"%s/account.db\", base)\r\n\tfiles.MkdirByFileName(name)\r\n\tdb, err = sql.Open(\"sqlite3\", name)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\treturn\r\n\t}\r\n\r\n\t// niconico\r\n\t_, err = db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS niconico (\r\n\t\talias TEXT PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tuser TEXT NOT NULL,\r\n\t\tpass TEXT NOT NULL,\r\n\t\tsession TEXT\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS niconico0 ON niconico(alias);\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS niconico1 ON niconico(user);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// youtube API key\r\n\t_, err = db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS youtubeapikey (\r\n\t\tid PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tkey TEXT\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS youtubeapikey0 ON youtubeapikey(id);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc dbOpen() (db *sql.DB, err error) {\r\n\tdb, err = sql.Open(\"sqlite3\", \"conf.db\")\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.Exec(`\r\n\tCREATE TABLE IF NOT EXISTS conf (\r\n\t\tk TEXT PRIMARY KEY NOT NULL UNIQUE,\r\n\t\tv BLOB\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.Exec(`\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS conf0 ON conf(k);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc ParseArgs() (opt Option) {\r\n\t//dbAccountOpen()\r\n\tdb, err := dbOpen()\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\terr = db.QueryRow(`\r\n\t\tSELECT\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoFormat\"), \"\"),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoLimitBw\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoLoginOnly\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoHlsOnly\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoRtmpOnly\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoFastTs\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoLoginAlias\"), \"\"),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoAutoConvert\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoAutoDeleteDBMode\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"TcasRetry\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"TcasRetryTimeoutMinute\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"TcasRetryInterval\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"ConvExt\"), \"\"),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"ExtractChunks\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoForceResv\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"YtNoStreamlink\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"YtNoYoutubeDl\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"NicoSkipHb\"), 0),\r\n\t\tIFNULL((SELECT v FROM conf WHERE k == \"HttpSkipVerify\"), 0);\r\n\t`).Scan(\r\n\t\t&opt.NicoFormat,\r\n\t\t&opt.NicoLimitBw,\r\n\t\t&opt.NicoLoginOnly,\r\n\t\t&opt.NicoHlsOnly,\r\n\t\t&opt.NicoRtmpOnly,\r\n\t\t&opt.NicoFastTs,\r\n\t\t&opt.NicoLoginAlias,\r\n\t\t&opt.NicoAutoConvert,\r\n\t\t&opt.NicoAutoDeleteDBMode,\r\n\t\t&opt.TcasRetry,\r\n\t\t&opt.TcasRetryTimeoutMinute,\r\n\t\t&opt.TcasRetryInterval,\r\n\t\t&opt.ConvExt,\r\n\t\t&opt.ExtractChunks,\r\n\t\t&opt.NicoForceResv,\r\n\t\t&opt.YtNoStreamlink,\r\n\t\t&opt.YtNoYoutubeDl,\r\n\t\t&opt.NicoSkipHb,\r\n\t\t&opt.HttpSkipVerify,\r\n\t)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n\r\n\targs := os.Args[1:]\r\n\tvar match []string\r\n\r\n\ttype Parser struct {\r\n\t\tre *regexp.Regexp\r\n\t\tcb func() error\r\n\t}\r\n\r\n\tnextArg := func() (str string, err error) {\r\n\t\tif len(args) <= 0 {\r\n\t\t\tif len(match[0]) > 0 {\r\n\t\t\t\terr = fmt.Errorf(\"%v: value required\", match[0])\r\n\t\t\t} else {\r\n\t\t\t\terr = fmt.Errorf(\"value required\")\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tstr = args[0]\r\n\t\t\targs = args[1:]\r\n\t\t}\r\n\r\n\t\treturn\r\n\t}\r\n\r\n\tparseList := []Parser{\r\n\t\tParser{regexp.MustCompile(`\\A(?i)(?:--?|/)(?:\\?|h|help)\\z`), func() error {\r\n\t\t\tHelp()\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)(?:--?|/)v(?:\\?|h|help)\\z`), func() error {\r\n\t\t\tHelp(true)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?(?:v|version)\\z`), func() error {\r\n\t\t\tversion()\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(https?://(?:[^/]*@)?(?:[^/]*\\.)*nicovideo\\.jp(?::[^/]*)?/(?:[^/]*?/)*)?(lv\\d+)(?:\\?.*)?\\z`), func() error {\r\n\t\t\tswitch opt.Command {\r\n\t\t\tdefault:\r\n\t\t\t\tfmt.Printf(\"Use \\\"--\\\" option for FILE for %s\\n\", opt.Command)\r\n\t\t\t\tHelp()\r\n\t\t\tcase \"\", \"NICOLIVE\":\r\n\t\t\t\topt.NicoLiveId = match[2]\r\n\t\t\t\topt.Command = \"NICOLIVE\"\r\n\t\t\tcase \"NICOLIVE_TEST\":\r\n\t\t\t\topt.NicoLiveId = match[2]\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A--?conf-?pass\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\topt.ConfPass = str\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\Ahttps?://twitcasting\\.tv/([^/]+)(?:/.*)?\\z`), func() error {\r\n\t\t\topt.TcasId = match[1]\r\n\t\t\topt.Command = \"TWITCAS\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?tcas-?retry(?:=(on|off))\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.TcasRetry = true\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.TcasRetry = false\r\n\t\t\t}\r\n\t\t\tdbConfSet(db, \"TcasRetry\", opt.TcasRetry)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?tcas-?retry-?timeout(?:-?minutes?)?\\z`), func() error {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--tcas-retry-timeout: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.TcasRetryTimeoutMinute = num\r\n\t\t\tdbConfSet(db, \"TcasRetryTimeoutMinute\", opt.TcasRetryTimeoutMinute)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?tcas-?retry-?interval\\z`), func() error {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--tcas-retry-interval: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\tif num <= 0 {\r\n\t\t\t\treturn fmt.Errorf(\"--tcas-retry-interval: Invalid: %d: greater than 1\\n\", num)\r\n\t\t\t}\r\n\r\n\t\t\topt.TcasRetryInterval = num\r\n\t\t\tdbConfSet(db, \"TcasRetryInterval\", opt.TcasRetryInterval)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\Ahttps?://(?:[^/]*\\.)*youtube\\.com/(?:.*\\W)?v=([\\w-]+)(?:[^\\w-].*)?\\z`), func() error {\r\n\t\t\topt.YoutubeId = match[1]\r\n\t\t\topt.Command = \"YOUTUBE\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico\\z`), func() error {\r\n\t\t\topt.Command = \"NICOLIVE\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?test-?run\\z`), func() error {\r\n\t\t\topt.Command = \"NICOLIVE_TEST\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?test-?timeout\\z`), func() error {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-test-timeout: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\tif num <= 0 {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-test-timeout: Invalid: %d: must be greater than or equal to 1\\n\", num)\r\n\t\t\t}\r\n\t\t\topt.NicoTestTimeout = num\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?tcas\\z`), func() error {\r\n\t\t\topt.Command = \"TWITCAS\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?(?:yt|youtube|youtube-live)\\z`), func() error {\r\n\t\t\topt.Command = \"YOUTUBE\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?(?:z|zip)-?(?:2|to)-?(?:m|mp4)\\z`), func() error {\r\n\t\t\topt.Command = \"ZIP2MP4\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?(?:d|db|sqlite3?)-?(?:2|to)-?(?:m|mp4)\\z`), func() error {\r\n\t\t\topt.Command = \"DB2MP4\"\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?login-?only(?:=(on|off))?\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoLoginOnly = true\r\n\t\t\t\tdbConfSet(db, \"NicoLoginOnly\", opt.NicoLoginOnly)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoLoginOnly = false\r\n\t\t\t\tdbConfSet(db, \"NicoLoginOnly\", opt.NicoLoginOnly)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoLoginOnly = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?hls-?only(?:=(on|off))?\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoHlsOnly = true\r\n\t\t\t\tdbConfSet(db, \"NicoHlsOnly\", opt.NicoHlsOnly)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoHlsOnly = false\r\n\t\t\t\tdbConfSet(db, \"NicoHlsOnly\", opt.NicoHlsOnly)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoHlsOnly = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?rtmp-?only(?:=(on|off))?\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoRtmpOnly = true\r\n\t\t\t\tdbConfSet(db, \"NicoRtmpOnly\", opt.NicoRtmpOnly)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoRtmpOnly = false\r\n\t\t\t\tdbConfSet(db, \"NicoRtmpOnly\", opt.NicoRtmpOnly)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoRtmpOnly = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?fast-?ts(?:=(on|off))?\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoFastTs = true\r\n\t\t\t\tdbConfSet(db, \"NicoFastTs\", opt.NicoFastTs)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoFastTs = false\r\n\t\t\t\tdbConfSet(db, \"NicoFastTs\", opt.NicoFastTs)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoFastTs = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?auto-?convert(?:=(on|off))?\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoAutoConvert = true\r\n\t\t\t\tdbConfSet(db, \"NicoAutoConvert\", opt.NicoAutoConvert)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoAutoConvert = false\r\n\t\t\t\tdbConfSet(db, \"NicoAutoConvert\", opt.NicoAutoConvert)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoAutoConvert = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?auto-?delete-?mode\\z`), func() error {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-auto-delete-mode: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\tif num < 0 || 2 < num {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-auto-delete-mode: Invalid: %d: one of 0, 1, 2\\n\", num)\r\n\t\t\t}\r\n\r\n\t\t\topt.NicoAutoDeleteDBMode = num\r\n\t\t\tdbConfSet(db, \"NicoAutoDeleteDBMode\", opt.NicoAutoDeleteDBMode)\r\n\r\n\t\t\treturn nil\r\n\t\t}},\r\n\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?(?:u|ultra)fast-?ts\\z`), func() error {\r\n\t\t\topt.NicoUltraFastTs = true\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?rtmp-?index\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tar := strings.Split(str, \",\")\r\n\t\t\tif len(ar) > 0 {\r\n\t\t\t\topt.NicoRtmpIndex = make(map[int]bool)\r\n\t\t\t}\r\n\t\t\tfor _, s := range ar {\r\n\t\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\treturn fmt.Errorf(\"--nico-rtmp-index: Not a number: %s\\n\", s)\r\n\t\t\t\t}\r\n\t\t\t\tif num <= 0 {\r\n\t\t\t\t\treturn fmt.Errorf(\"--nico-rtmp-index: Invalid: %d: must be greater than or equal to 1\\n\", num)\r\n\t\t\t\t}\r\n\t\t\t\topt.NicoRtmpIndex[num-1] = true\r\n\t\t\t}\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?status-?https\\z`), func() error {\r\n\t\t\t// experimental\r\n\t\t\topt.NicoStatusHTTPS = true\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?hls-?port\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-hls-port: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\tif num <= 0 {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-hls-port: Invalid: %d: must be greater than or equal to 1\\n\", num)\r\n\t\t\t}\r\n\t\t\topt.NicoHlsPort = num\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?limit-?bw\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-limit-bw: Not a number: %s\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.NicoLimitBw = num\r\n\t\t\tdbConfSet(db, \"NicoLimitBw\", opt.NicoLimitBw)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?ts-?start\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-ts-start: Not a number %s\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.NicoTsStart = float64(num)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?ts-?start-?min\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tnum, err := strconv.Atoi(s)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-ts-start-min: Not a number %s\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.NicoTsStart = float64(num * 60)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?(?:format|fmt)\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tif s == \"\" {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-format: null string not allowed\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.NicoFormat = s\r\n\t\t\tdbConfSet(db, \"NicoFormat\", opt.NicoFormat)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?test-?(?:format|fmt)\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn err\r\n\t\t\t}\r\n\t\t\tif s == \"\" {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-test-format: null string not allowed\\n\", s)\r\n\t\t\t}\r\n\t\t\topt.NicoFormat = s\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?login\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tar := strings.SplitN(str, \",\", 2)\r\n\t\t\tif len(ar) >= 2 && ar[0] != \"\" {\r\n\t\t\t\tloginId := ar[0]\r\n\t\t\t\tloginPass := ar[1]\r\n\t\t\t\topt.NicoLoginAlias = fmt.Sprintf(\"%x\", sha3.Sum256([]byte(loginId)))\r\n\t\t\t\tSetNicoLogin(opt.NicoLoginAlias, loginId, loginPass)\r\n\t\t\t\tdbConfSet(db, \"NicoLoginAlias\", opt.NicoLoginAlias)\r\n\r\n\t\t\t} else {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-login: <id>,<password>\")\r\n\t\t\t}\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?session\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\topt.NicoSession = str\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?load-?session\\z`), func() (err error) {\r\n\t\t\tname, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tb, err := ioutil.ReadFile(name)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif ma := regexp.MustCompile(`(\\S+)`).FindSubmatch(b); len(ma) > 0 {\r\n\t\t\t\topt.NicoSession = string(ma[1])\r\n\t\t\t} else {\r\n\t\t\t\terr = fmt.Errorf(\"--nico-load-session: load failured\")\r\n\t\t\t}\r\n\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?rtmp-?max-?conn\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tnum, err := strconv.Atoi(str)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn fmt.Errorf(\"--nico-rtmp-max-conn %v: %v\", str, err)\r\n\t\t\t}\r\n\t\t\topt.NicoRtmpMaxConn = num\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?debug\\z`), func() error {\r\n\t\t\topt.NicoDebug = true\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i).+\\.zip\\z`), func() (err error) {\r\n\t\t\tswitch opt.Command {\r\n\t\t\tcase \"\", \"ZIP2MP4\":\r\n\t\t\t\topt.Command = \"ZIP2MP4\"\r\n\t\t\t\topt.ZipFile = match[0]\r\n\t\t\tdefault:\r\n\t\t\t\treturn fmt.Errorf(\"%s: Use -- option before \\\"%s\\\"\", opt.Command, match[0])\r\n\t\t\t}\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i).+\\.sqlite3\\z`), func() (err error) {\r\n\t\t\tswitch opt.Command {\r\n\t\t\tcase \"\", \"DB2MP4\":\r\n\t\t\t\topt.Command = \"DB2MP4\"\r\n\t\t\t\topt.DBFile = match[0]\r\n\t\t\tdefault:\r\n\t\t\t\treturn fmt.Errorf(\"%s: Use -- option before \\\"%s\\\"\", opt.Command, match[0])\r\n\t\t\t}\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?conv-?ext(?:=(mp4|ts))\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"mp4\") {\r\n\t\t\t\topt.ConvExt = \"mp4\"\r\n\t\t\t} else if strings.EqualFold(match[1], \"ts\") {\r\n\t\t\t\topt.ConvExt = \"ts\"\r\n\t\t\t}\r\n\t\t\tdbConfSet(db, \"ConvExt\", opt.ConvExt)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?extract(?:-?chunks)?(?:=(on|off))\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.ExtractChunks = true\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.ExtractChunks = false\r\n\t\t\t}\r\n\t\t\tdbConfSet(db, \"ExtractChunks\", opt.ExtractChunks)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?force-?(?:re?sv|reservation)(?:=(on|off))\\z`), func() error {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoForceResv = true\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoForceResv = false\r\n\t\t\t}\r\n\t\t\tdbConfSet(db, \"NicoForceResv\", opt.NicoForceResv)\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?yt-?api-?key\\z`), func() (err error) {\r\n\t\t\ts, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif s == \"\" {\r\n\t\t\t\treturn fmt.Errorf(\"--yt-api-key: null string not allowed\\n\", s)\r\n\t\t\t}\r\n\t\t\terr = SetYoutubeApiKey(s)\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?yt-?no-?streamlink(?:=(on|off))?\\z`), func() (err error) {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.YtNoStreamlink = true\r\n\t\t\t\tdbConfSet(db, \"YtNoStreamlink\", opt.YtNoStreamlink)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.YtNoStreamlink = false\r\n\t\t\t\tdbConfSet(db, \"YtNoStreamlink\", opt.YtNoStreamlink)\r\n\t\t\t} else {\r\n\t\t\t\topt.YtNoStreamlink = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?yt-?no-?youtube-?dl(?:=(on|off))?\\z`), func() (err error) {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.YtNoYoutubeDl = true\r\n\t\t\t\tdbConfSet(db, \"YtNoYoutubeDl\", opt.YtNoYoutubeDl)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.YtNoYoutubeDl = false\r\n\t\t\t\tdbConfSet(db, \"YtNoYoutubeDl\", opt.YtNoYoutubeDl)\r\n\t\t\t} else {\r\n\t\t\t\topt.YtNoYoutubeDl = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?nico-?skip-?hb(?:=(on|off))?\\z`), func() (err error) {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.NicoSkipHb = true\r\n\t\t\t\tdbConfSet(db, \"NicoSkipHb\", opt.NicoSkipHb)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.NicoSkipHb = false\r\n\t\t\t\tdbConfSet(db, \"NicoSkipHb\", opt.NicoSkipHb)\r\n\t\t\t} else {\r\n\t\t\t\topt.NicoSkipHb = true\r\n\t\t\t}\r\n\t\t\treturn nil\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?http-?root-?ca\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\topt.HttpRootCA = str\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?http-?skip-?verify(?:=(on|off))?\\z`), func() (err error) {\r\n\t\t\tif strings.EqualFold(match[1], \"on\") {\r\n\t\t\t\topt.HttpSkipVerify = true\r\n\t\t\t\tdbConfSet(db, \"HttpSkipVerify\", opt.HttpSkipVerify)\r\n\t\t\t} else if strings.EqualFold(match[1], \"off\") {\r\n\t\t\t\topt.HttpSkipVerify = false\r\n\t\t\t\tdbConfSet(db, \"HttpSkipVerify\", opt.HttpSkipVerify)\r\n\t\t\t} else {\r\n\t\t\t\topt.HttpSkipVerify = true\r\n\t\t\t}\r\n\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?http-?proxy\\z`), func() (err error) {\r\n\t\t\tstr, err := nextArg()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif !strings.Contains(str, \"://\") {\r\n\t\t\t\tstr = \"http://\" + str\r\n\t\t\t}\r\n\t\t\topt.HttpProxy = str\r\n\t\t\treturn\r\n\t\t}},\r\n\t\tParser{regexp.MustCompile(`\\A(?i)--?no-?chdir\\z`), func() (err error) {\r\n\t\t\topt.NoChdir = true\r\n\t\t\treturn\r\n\t\t}},\r\n\t}\r\n\r\n\tcheckFILE := func(arg string) bool {\r\n\t\tswitch opt.Command {\r\n\t\tdefault:\r\n\t\t\t//fmt.Printf(\"command not specified: -- \\\"%s\\\"\\n\", arg)\r\n\t\t\t//os.Exit(1)\r\n\t\tcase \"YOUTUBE\":\r\n\t\t\tif ma := regexp.MustCompile(`v=([\\w-]+)`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.YoutubeId = ma[1]\r\n\t\t\t\treturn true\r\n\t\t\t} else if ma := regexp.MustCompile(`\\A([\\w-]+)\\z`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.YoutubeId = ma[1]\r\n\t\t\t\treturn true\r\n\t\t\t} else {\r\n\t\t\t\tfmt.Printf(\"Not YouTube id: %s\\n\", arg)\r\n\t\t\t\tos.Exit(1)\r\n\t\t\t}\r\n\t\tcase \"NICOLIVE\":\r\n\t\t\tif ma := regexp.MustCompile(`(lv\\d+)`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.NicoLiveId = ma[1]\r\n\t\t\t\treturn true\r\n\t\t\t}\r\n\t\tcase \"TWITCAS\":\r\n\t\t\tif opt.TcasId != \"\" {\r\n\t\t\t\tfmt.Printf(\"Unknown option: %s\\n\", arg)\r\n\t\t\t\tHelp()\r\n\t\t\t}\r\n\t\t\tif ma := regexp.MustCompile(`(?:.*/)?([^/]+)\\z`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.TcasId = ma[1]\r\n\t\t\t\treturn true\r\n\t\t\t}\r\n\t\tcase \"ZIP2MP4\":\r\n\t\t\tif ma := regexp.MustCompile(`(?i)\\.zip`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.ZipFile = arg\r\n\t\t\t\treturn true\r\n\t\t\t}\r\n\t\tcase \"DB2MP4\":\r\n\t\t\tif ma := regexp.MustCompile(`(?i)\\.sqlite3`).FindStringSubmatch(arg); len(ma) > 0 {\r\n\t\t\t\topt.DBFile = arg\r\n\t\t\t\treturn true\r\n\t\t\t}\r\n\t\t\treturn false\r\n\t\t} // end switch\r\n\t\treturn false\r\n\t}\r\n\r\nLB_ARG:\r\n\tfor len(args) > 0 {\r\n\t\targ, _ := nextArg()\r\n\r\n\t\tif arg == \"--\" {\r\n\t\t\tswitch len(args) {\r\n\t\t\tcase 0:\r\n\t\t\t\tfmt.Printf(\"argument not specified after \\\"--\\\"\\n\")\r\n\t\t\t\tos.Exit(1)\r\n\t\t\tdefault:\r\n\t\t\t\tfmt.Printf(\"too many arguments after \\\"--\\\": %v\\n\", args)\r\n\t\t\t\tos.Exit(1)\r\n\t\t\tcase 1:\r\n\t\t\t\targ, _ := nextArg()\r\n\t\t\t\tcheckFILE(arg)\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\tfor _, p := range parseList {\r\n\t\t\t\tif match = p.re.FindStringSubmatch(arg); len(match) > 0 {\r\n\t\t\t\t\tif e := p.cb(); e != nil {\r\n\t\t\t\t\t\tfmt.Println(e)\r\n\t\t\t\t\t\tos.Exit(1)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcontinue LB_ARG\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif ok := checkFILE(arg); !ok {\r\n\t\t\t\tfmt.Printf(\"Unknown option: %v\\n\", arg)\r\n\t\t\t\tHelp()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif opt.ConfFile == \"\" {\r\n\t\topt.ConfFile = fmt.Sprintf(\"%s.conf\", getCmd())\r\n\t}\r\n\r\n\t// [deprecated]\r\n\t// load session info\r\n\tif data, e := cryptoconf.Load(opt.ConfFile, opt.ConfPass); e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t} else {\r\n\t\tloginId, _ := data[\"NicoLoginId\"].(string)\r\n\t\tif loginId != \"\" {\r\n\t\t\tloginPass, _ := data[\"NicoLoginPass\"].(string)\r\n\t\t\thash := fmt.Sprintf(\"%x\", sha3.Sum256([]byte(loginId)))\r\n\t\t\tSetNicoLogin(hash, loginId, loginPass)\r\n\t\t\tif opt.NicoLoginAlias == \"\" {\r\n\t\t\t\topt.NicoLoginAlias = hash\r\n\t\t\t\tdbConfSet(db, \"NicoLoginAlias\", opt.NicoLoginAlias)\r\n\t\t\t}\r\n\t\t\tos.Remove(opt.ConfFile)\r\n\t\t}\r\n\t}\r\n\r\n\t// prints\r\n\tswitch opt.Command {\r\n\tcase \"NICOLIVE\":\r\n\t\tfmt.Printf(\"Conf(NicoLoginOnly): %#v\\n\", opt.NicoLoginOnly)\r\n\t\tfmt.Printf(\"Conf(NicoFormat): %#v\\n\", opt.NicoFormat)\r\n\t\tfmt.Printf(\"Conf(NicoLimitBw): %#v\\n\", opt.NicoLimitBw)\r\n\t\tfmt.Printf(\"Conf(NicoHlsOnly): %#v\\n\", opt.NicoHlsOnly)\r\n\t\tfmt.Printf(\"Conf(NicoRtmpOnly): %#v\\n\", opt.NicoRtmpOnly)\r\n\t\tfmt.Printf(\"Conf(NicoFastTs): %#v\\n\", opt.NicoFastTs)\r\n\t\tfmt.Printf(\"Conf(NicoAutoConvert): %#v\\n\", opt.NicoAutoConvert)\r\n\t\tif opt.NicoAutoConvert {\r\n\t\t\tfmt.Printf(\"Conf(NicoAutoDeleteDBMode): %#v\\n\", opt.NicoAutoDeleteDBMode)\r\n\t\t\tfmt.Printf(\"Conf(ExtractChunks): %#v\\n\", opt.ExtractChunks)\r\n\t\t\tfmt.Printf(\"Conf(ConvExt): %#v\\n\", opt.ConvExt)\r\n\t\t}\r\n\t\tfmt.Printf(\"Conf(NicoForceResv): %#v\\n\", opt.NicoForceResv)\r\n\t\tfmt.Printf(\"Conf(NicoSkipHb): %#v\\n\", opt.NicoSkipHb)\r\n\r\n\tcase \"YOUTUBE\":\r\n\t\tfmt.Printf(\"Conf(YtNoStreamlink): %#v\\n\", opt.YtNoStreamlink)\r\n\t\tfmt.Printf(\"Conf(YtNoYoutubeDl): %#v\\n\", opt.YtNoYoutubeDl)\r\n\r\n\tcase \"TWITCAS\":\r\n\t\tfmt.Printf(\"Conf(TcasRetry): %#v\\n\", opt.TcasRetry)\r\n\t\tfmt.Printf(\"Conf(TcasRetryTimeoutMinute): %#v\\n\", opt.TcasRetryTimeoutMinute)\r\n\t\tfmt.Printf(\"Conf(TcasRetryInterval): %#v\\n\", opt.TcasRetryInterval)\r\n\tcase \"DB2MP4\":\r\n\t\tfmt.Printf(\"Conf(ExtractChunks): %#v\\n\", opt.ExtractChunks)\r\n\t\tfmt.Printf(\"Conf(ConvExt): %#v\\n\", opt.ConvExt)\r\n\t}\r\n\tfmt.Printf(\"Conf(HttpSkipVerify): %#v\\n\", opt.HttpSkipVerify)\r\n\r\n\tif opt.NicoDebug {\r\n\t\tfmt.Printf(\"Conf(NicoDebug): %#v\\n\", opt.NicoDebug)\r\n\t}\r\n\r\n\t// check\r\n\tswitch opt.Command {\r\n\tcase \"\":\r\n\t\tfmt.Printf(\"Command not specified\\n\")\r\n\t\tHelp()\r\n\tcase \"YOUTUBE\":\r\n\t\tif opt.YoutubeId == \"\" {\r\n\t\t\tHelp()\r\n\t\t}\r\n\tcase \"NICOLIVE\":\r\n\t\tif opt.NicoLiveId == \"\" {\r\n\t\t\tHelp()\r\n\t\t}\r\n\tcase \"NICOLIVE_TEST\":\r\n\tcase \"TWITCAS\":\r\n\t\tif opt.TcasId == \"\" {\r\n\t\t\tHelp()\r\n\t\t}\r\n\tcase \"ZIP2MP4\":\r\n\t\tif opt.ZipFile == \"\" {\r\n\t\t\tHelp()\r\n\t\t}\r\n\tcase \"DB2MP4\":\r\n\t\tif opt.DBFile == \"\" {\r\n\t\t\tHelp()\r\n\t\t}\r\n\tdefault:\r\n\t\tfmt.Printf(\"[FIXME] options.go/argcheck for %s\\n\", opt.Command)\r\n\t\tos.Exit(1)\r\n\t}\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/procs/base/base.go",
    "content": "package base\r\n\r\nimport (\r\n\t\"io\"\r\n\t\"os\"\r\n\t\"os/exec\"\r\n)\r\n\r\nfunc Open(cmdList *[]string, stdinEn, stdoutEn, stdErrEn, consoleEn bool, args []string) (cmd *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser, err error) {\r\n\r\n\tfor i, cmdName := range *cmdList {\r\n\t\tcmd = exec.Command(cmdName, args...)\r\n\r\n\t\tif stdinEn {\r\n\t\t\tstdin, err = cmd.StdinPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif stdoutEn {\r\n\t\t\tstdout, err = cmd.StdoutPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif consoleEn {\r\n\t\t\t\tcmd.Stdout = os.Stdout\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif stdErrEn {\r\n\t\t\tstderr, err = cmd.StderrPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif consoleEn {\r\n\t\t\t\tcmd.Stderr = os.Stderr\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif err = cmd.Start(); err != nil {\r\n\t\t\tcontinue\r\n\t\t} else {\r\n\t\t\tif i != 0 {\r\n\t\t\t\t*cmdList = []string{cmdName}\r\n\t\t\t}\r\n\t\t\t//fmt.Printf(\"CMD: %#v\\n\", cmd.Args)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\t// prog not found\r\n\tcmd = nil\r\n\treturn\r\n}"
  },
  {
    "path": "src/procs/ffmpeg/ffmpeg.go",
    "content": "package ffmpeg\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"os/exec\"\r\n\r\n\t\"github.com/himananiito/livedl/procs/base\"\r\n)\r\n\r\nvar cmdList = []string{\r\n\t\"./bin/ffmpeg/bin/ffmpeg\",\r\n\t\"./bin/ffmpeg/ffmpeg\",\r\n\t\"./bin/ffmpeg\",\r\n\t\"./ffmpeg/bin/ffmpeg\",\r\n\t\"./ffmpeg/ffmpeg\",\r\n\t\"./ffmpeg\",\r\n\t\"ffmpeg\",\r\n}\r\n\r\nfunc Open(opt ...string) (cmd *exec.Cmd, stdin io.WriteCloser, err error) {\r\n\tcmd, stdin, _, _, err = base.Open(&cmdList, true, false, false, true, opt)\r\n\tif cmd == nil {\r\n\t\terr = fmt.Errorf(\"ffmpeg not found\")\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/procs/kill.go",
    "content": "package procs\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"log\"\r\n\t\"runtime\"\r\n\r\n\t\"github.com/himananiito/livedl/procs/base\"\r\n)\r\n\r\nfunc Kill(pid int) {\r\n\tif runtime.GOOS == \"windows\" {\r\n\t\toptions := []string{\r\n\t\t\t\"/PID\", fmt.Sprintf(\"%v\", pid),\r\n\t\t\t\"/T\",\r\n\t\t\t\"/F\",\r\n\t\t}\r\n\t\tlist := []string{\"taskkill\"}\r\n\t\tif taskkill, _, _, _, err := base.Open(&list, false, false, false, false, options); err == nil {\r\n\t\t\ttaskkill.Wait()\r\n\t\t}\r\n\r\n\t} else {\r\n\t\tlog.Fatalf(\"[FIXME] Kill for %v not supported\", runtime.GOOS)\r\n\t}\r\n}\r\n"
  },
  {
    "path": "src/procs/streamlink/streamlink.go",
    "content": "package streamlink\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"os/exec\"\r\n\r\n\t\"github.com/himananiito/livedl/procs/base\"\r\n)\r\n\r\nvar cmdList = []string{\r\n\t\"./bin/streamlink/streamlink\",\r\n\t\"./bin/Streamlink/Streamlink\",\r\n\t\"./bin/streamlink\",\r\n\t\"./bin/Streamlink\",\r\n\t\"./streamlink/streamlink\",\r\n\t\"./Streamlink/Streamlink\",\r\n\t\"./Streamlink\",\r\n\t\"streamlink\",\r\n\t\"Streamlink\",\r\n}\r\n\r\nfunc Open(opt ...string) (cmd *exec.Cmd, stdout, stderr io.ReadCloser, err error) {\r\n\tcmd, _, stdout, stderr, err = base.Open(&cmdList, false, true, true, false, opt)\r\n\tif cmd == nil {\r\n\t\terr = fmt.Errorf(\"streamlink not found\")\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/procs/youtube_dl/youtube-dl.go",
    "content": "package youtube_dl\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"os/exec\"\r\n\r\n\t\"github.com/himananiito/livedl/procs/base\"\r\n)\r\n\r\nvar cmdList = []string{\r\n\t\"./bin/youtube-dl/youtube-dl\",\r\n\t\"./bin/youtube-dl\",\r\n\t\"./youtube-dl/youtube-dl\",\r\n\t\"./youtube-dl\",\r\n\t\"youtube-dl\",\r\n}\r\n\r\nfunc Open(opt ...string) (cmd *exec.Cmd, stdout, stderr io.ReadCloser, err error) {\r\n\tcmd, _, stdout, stderr, err = base.Open(&cmdList, false, true, true, false, opt)\r\n\tif cmd == nil {\r\n\t\terr = fmt.Errorf(\"youtube-dl not found\")\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/rtmps/message.go",
    "content": "package rtmps\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"encoding/binary\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"log\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/amf\"\r\n)\r\n\r\nconst (\r\n\tTID_SETCHUNKSIZE     = 1\r\n\tTID_ABORT            = 2\r\n\tTID_ACKNOWLEDGEMENT  = 3\r\n\tTID_USERCONTROL      = 4\r\n\tTID_WINDOW_ACK_SIZE  = 5\r\n\tTID_SETPEERBANDWIDTH = 6\r\n\tTID_AUDIO            = 8\r\n\tTID_VIDEO            = 9\r\n\tTID_AMF3COMMAND      = 17\r\n\tTID_AMF0COMMAND      = 20\r\n\tTID_AMF0DATA         = 18\r\n\tTID_AMF3DATA         = 15\r\n\tTID_AGGREGATE        = 22\r\n)\r\n\r\nconst (\r\n\tUC_STREAMBEGIN      = 0\r\n\tUC_STREAMEOF        = 1\r\n\tUC_STREAMDRY        = 2\r\n\tUC_SETBUFFERLENGTH  = 3\r\n\tUC_STREAMISRECORDED = 4\r\n\tUC_PINGREQUEST      = 6\r\n\tUC_PINGRESPONSE     = 7\r\n\r\n\tUC_BUFFEREMPTY = 31\r\n\tUC_BUFFERREADY = 32\r\n)\r\n\r\nfunc intToBE16(num int) (data []byte) {\r\n\ttmp := make([]byte, 2)\r\n\tbinary.BigEndian.PutUint16(tmp, uint16(num))\r\n\tdata = append(data, tmp[:]...)\r\n\treturn\r\n}\r\nfunc intToBE24(num int) (data []byte) {\r\n\ttmp := make([]byte, 4)\r\n\tbinary.BigEndian.PutUint32(tmp, uint32(num))\r\n\tdata = append(data, tmp[1:]...)\r\n\treturn\r\n}\r\nfunc intToBE32(num int) (data []byte) {\r\n\ttmp := make([]byte, 4)\r\n\tbinary.BigEndian.PutUint32(tmp, uint32(num))\r\n\tdata = append(data, tmp[:]...)\r\n\treturn\r\n}\r\nfunc intToLE32(num int) (data []byte) {\r\n\ttmp := make([]byte, 4)\r\n\tbinary.LittleEndian.PutUint32(tmp, uint32(num))\r\n\tdata = append(data, tmp[:]...)\r\n\treturn\r\n}\r\n\r\nfunc chunkBasicHeader(fmt, csid int) (data []byte) {\r\n\tif 2 <= csid && csid <= 63 {\r\n\t\tb := byte(((fmt & 3) << 6) | (csid & 0x3F))\r\n\t\tdata = append(data, b)\r\n\r\n\t} else if 64 <= csid && csid <= 319 {\r\n\t\tb0 := byte((fmt & 3) << 6)\r\n\t\tb1 := byte(csid - 64)\r\n\t\tdata = append(data, b0, b1)\r\n\r\n\t} else if 320 <= csid && csid <= 65599 {\r\n\t\tb0 := byte(((fmt & 3) << 6) | 1)\r\n\t\tb1 := byte((csid & 0xFF) - 64)\r\n\t\tb2 := byte(csid >> 8)\r\n\t\tdata = append(data, b0, b1, b2)\r\n\r\n\t} else {\r\n\t\tlog.Printf(\"[FIXME] Chunk basic header: csid out of range: %d\", csid)\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nvar start = millisec()\r\n\r\nfunc millisec() int64 {\r\n\treturn time.Now().UnixNano() / int64(time.Millisecond)\r\n}\r\nfunc getTime() int {\r\n\tdelta := millisec() - start\r\n\treturn int(delta)\r\n}\r\n\r\nfunc type0(buff *bytes.Buffer, csId int, typeId byte, streamId int, length int) {\r\n\tbuff.Write(chunkBasicHeader(0, csId))\r\n\r\n\t// timestamp\r\n\t//buff.Write(intToBE24(getTime()))\r\n\tbuff.Write(intToBE24(0))\r\n\t// message length\r\n\tbuff.Write(intToBE24(length))\r\n\t// message type id\r\n\tbuff.WriteByte(typeId)\r\n\t// Stream ID\r\n\tbuff.Write(intToLE32(streamId))\r\n\t// body\r\n\t//buff.Write(body)\r\n\r\n\treturn\r\n}\r\nfunc type3(buff *bytes.Buffer, csId int) {\r\n\tbuff.Write(chunkBasicHeader(3, csId))\r\n}\r\nfunc encodeAcknowledgement(asz int) (buff *bytes.Buffer, err error) {\r\n\tbuff = bytes.NewBuffer(nil)\r\n\tbsz := intToBE32(asz)\r\n\ttype0(buff, 2, TID_ACKNOWLEDGEMENT, 0, len(bsz))\r\n\tif _, err = buff.Write(bsz); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc encodeWindowAckSize(asz int) (buff *bytes.Buffer, err error) {\r\n\tbuff = bytes.NewBuffer(nil)\r\n\tbsz := intToBE32(asz)\r\n\ttype0(buff, 2, TID_WINDOW_ACK_SIZE, 0, len(bsz))\r\n\tif _, err = buff.Write(bsz); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc encodeSetPeerBandwidth(wsz, lim int) (buff *bytes.Buffer, err error) {\r\n\tbuff = bytes.NewBuffer(nil)\r\n\tb := intToBE32(wsz)\r\n\tb = append(b, byte(lim))\r\n\ttype0(buff, 2, TID_SETPEERBANDWIDTH, 0, len(b))\r\n\tif _, err = buff.Write(b); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc encodePingResponse(timestamp int) (buff *bytes.Buffer, err error) {\r\n\tbuff = bytes.NewBuffer(nil)\r\n\r\n\tvar body []byte\r\n\tbody = append(body, intToBE16(UC_PINGRESPONSE)...)\r\n\tbody = append(body, intToBE32(timestamp)...)\r\n\r\n\ttype0(buff, 2, TID_USERCONTROL, 0, len(body))\r\n\tif _, err = buff.Write(body); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc encodeSetBufferLength(streamId, length int) (buff *bytes.Buffer, err error) {\r\n\tbuff = bytes.NewBuffer(nil)\r\n\r\n\tvar body []byte\r\n\tbody = append(body, intToBE16(UC_SETBUFFERLENGTH)...)\r\n\tbody = append(body, intToBE32(streamId)...)\r\n\tbody = append(body, intToBE32(int(length))...)\r\n\r\n\ttype0(buff, 2, TID_USERCONTROL, 0, len(body))\r\n\tif _, err = buff.Write(body); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc amf0Command(chunkSize, csId, streamId int, body []byte) (wbuff *bytes.Buffer, err error) {\r\n\twbuff = bytes.NewBuffer(nil)\r\n\trbuff := bytes.NewBuffer(body)\r\n\r\n\ttype0(wbuff, csId, TID_AMF0COMMAND, streamId, rbuff.Len())\r\n\tif chunkSize < rbuff.Len() {\r\n\t\tif _, err = io.CopyN(wbuff, rbuff, int64(chunkSize)); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\tif _, err = io.CopyN(wbuff, rbuff, int64(rbuff.Len())); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\tfor rbuff.Len() > 0 {\r\n\t\ttype3(wbuff, csId)\r\n\r\n\t\tif chunkSize < rbuff.Len() {\r\n\t\t\tif _, err = io.CopyN(wbuff, rbuff, int64(chunkSize)); err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif _, err = io.CopyN(wbuff, rbuff, int64(rbuff.Len())); err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t//log.Fatalf(\"amf0Command %#v\", wbuff)\r\n\r\n\treturn\r\n}\r\n\r\nfunc decodeFmtCsId(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tb0 := make([]byte, 1)\r\n\tmsg.hdrLength++\r\n\t_, err = io.ReadFull(rdr, b0)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tformat := (int(b0[0]) >> 6) & 3\r\n\tcsId := int(b0[0]) & 0x3F\r\n\tswitch csId {\r\n\tcase 0:\r\n\t\tb1 := make([]byte, 1)\r\n\t\tmsg.hdrLength++\r\n\t\tif _, err = io.ReadFull(rdr, b1); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tcsId = int(b1[0]) + 64\r\n\r\n\tcase 1:\r\n\t\tb1 := make([]byte, 2)\r\n\t\tmsg.hdrLength += 2\r\n\t\tif _, err = io.ReadFull(rdr, b1); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tcsId = (int(b1[1]) << 8) | (int(b1[0]) + 64)\r\n\t}\r\n\r\n\tmsg.format = format\r\n\tmsg.csId = csId\r\n\tif !msg.readingBody {\r\n\t\tmsg.formatOrigin = format\r\n\t\tmsg.csIdOrigin = csId\r\n\t}\r\n\t// fmt.Printf(\"debug format type %v csid %v\\n\", format, csId)\r\n\treturn\r\n}\r\n\r\nfunc decodeInt8(rdr io.Reader) (num int, err error) {\r\n\tbuf := make([]byte, 1)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tnum = int(buf[0])\r\n\treturn\r\n}\r\nfunc decodeBEInt16(rdr io.Reader) (num int, err error) {\r\n\tbuf := make([]byte, 2)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tnum = (int(buf[0]) << 8) | int(buf[1])\r\n\treturn\r\n}\r\nfunc decodeBEInt24(rdr io.Reader) (num int, err error) {\r\n\tbuf := make([]byte, 3)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tnum = (int(buf[0]) << 16) | (int(buf[1]) << 8) | int(buf[2])\r\n\treturn\r\n}\r\nfunc decodeBEInt32(rdr io.Reader) (num int, err error) {\r\n\tbuf := make([]byte, 4)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tnum = (int(buf[0]) << 24) | (int(buf[1]) << 16) | (int(buf[2]) << 8) | int(buf[3])\r\n\treturn\r\n}\r\nfunc decodeLEInt32(rdr io.Reader) (num int, err error) {\r\n\tbuf := make([]byte, 4)\r\n\tif _, err = io.ReadFull(rdr, buf); err != nil {\r\n\t\treturn\r\n\t}\r\n\tnum = (int(buf[3]) << 24) | (int(buf[2]) << 16) | (int(buf[1]) << 8) | int(buf[0])\r\n\treturn\r\n}\r\n\r\nfunc decodeTimestamp(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tmsg.hdrLength += 3\r\n\ttimestamp, err := decodeBEInt24(rdr)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tmsg.timestampField = timestamp\r\n\r\n\treturn\r\n}\r\nfunc decodeTimestampEX(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tmsg.hdrLength += 4\r\n\ttimestamp, err := decodeBEInt32(rdr)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\t//fmt.Printf(\"decodeTimestampEX %v\\n\", timestamp)\r\n\tif !msg.readingBody {\r\n\t\tmsg.timestampEx = timestamp\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc decodeMsgLength(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tmsg.hdrLength += 3\r\n\tlength, err := decodeBEInt24(rdr)\r\n\tmsg.msgLength = length\r\n\treturn\r\n}\r\nfunc decodeMsgType(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tmsg.hdrLength += 1\r\n\tmsg_t, err := decodeInt8(rdr)\r\n\tmsg.msgTypeId = msg_t\r\n\treturn\r\n}\r\nfunc decodeStreamId(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tmsg.hdrLength += 4\r\n\tsid, err := decodeLEInt32(rdr)\r\n\tmsg.msgStreamId = sid\r\n\treturn\r\n}\r\n\r\nfunc decodeType0(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tif err = decodeTimestamp(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif err = decodeMsgLength(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif err = decodeMsgType(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\terr = decodeStreamId(rdr, msg)\r\n\treturn\r\n}\r\nfunc decodeType1(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tif err = decodeTimestamp(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\tif err = decodeMsgLength(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\terr = decodeMsgType(rdr, msg)\r\n\treturn\r\n}\r\nfunc decodeType2(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\terr = decodeTimestamp(rdr, msg)\r\n\treturn\r\n}\r\n\r\ntype rtmpMsg struct {\r\n\tformat          int\r\n\tformatOrigin    int\r\n\tcsId            int\r\n\tcsIdOrigin      int\r\n\ttimestampField  int\r\n\ttimestampDelta  int\r\n\ttimestampEx     int\r\n\ttimestampActual int\r\n\tmsgLength       int\r\n\tmsgTypeId       int\r\n\tmsgStreamId     int\r\n\tbodyBuff        *bytes.Buffer\r\n\r\n\treadingBody bool\r\n\thdrLength   int\r\n\tsplitCount  int\r\n}\r\n\r\nfunc readChunkBody(rdr io.Reader, msg *rtmpMsg, csz int) (err error) {\r\n\r\n\tif msg.bodyBuff == nil {\r\n\t\tmsg.bodyBuff = bytes.NewBuffer(nil)\r\n\t}\r\n\trem := msg.msgLength - msg.bodyBuff.Len()\r\n\t//fmt.Printf(\"readChunkBody: %v %v\\n\", msg.msgLength, msg.bodyBuff.Len())\r\n\tif rem > csz {\r\n\t\t_, err = io.CopyN(msg.bodyBuff, rdr, int64(csz))\r\n\t} else {\r\n\t\t_, err = io.CopyN(msg.bodyBuff, rdr, int64(rem))\r\n\t}\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc decodeHeader(rdr io.Reader, msg *rtmpMsg) (err error) {\r\n\tif err = decodeFmtCsId(rdr, msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\tswitch msg.format {\r\n\tcase 0:\r\n\t\tif err = decodeType0(rdr, msg); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase 1:\r\n\t\tif err = decodeType1(rdr, msg); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase 2:\r\n\t\tif err = decodeType2(rdr, msg); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase 3:\r\n\t\tif msg.readingBody {\r\n\t\t\tmsg.splitCount++\r\n\t\t\tif msg.csId != msg.csIdOrigin {\r\n\t\t\t\terr = &DecodeError{\r\n\t\t\t\t\tFun: \"decodeHeader\",\r\n\t\t\t\t\tMsg: fmt.Sprintf(\"msg.csId(%d) != msg.csIdOrigin(%d)\", msg.csId, msg.csIdOrigin),\r\n\t\t\t\t}\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\tdefault:\r\n\t\terr = &DecodeError{\r\n\t\t\tFun: \"decodeHeader\",\r\n\t\t\tMsg: fmt.Sprintf(\"Unknown fmt: %v\", msg.format),\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc decodeSetChunkSize(rbuff *bytes.Buffer) (csz int, err error) {\r\n\tnum, e := decodeBEInt32(rbuff)\r\n\tif e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t}\r\n\tcsz = num & 0x7fffffff\r\n\treturn\r\n}\r\n\r\nfunc decodeWindowAckSize(rbuff *bytes.Buffer) (asz int, err error) {\r\n\tasz, e := decodeBEInt32(rbuff)\r\n\tif e != nil {\r\n\t\terr = e\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc decodeSetPeerBandwidth(rbuff *bytes.Buffer) (res []int, err error) {\r\n\twsz, err := decodeBEInt32(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tlim, err := decodeInt8(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tres = append(res, wsz, lim)\r\n\treturn\r\n}\r\nfunc decodeUserControl(rbuff *bytes.Buffer) (res []int, err error) {\r\n\tevt, err := decodeBEInt16(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tres = append(res, evt)\r\n\tswitch evt {\r\n\tcase UC_BUFFEREMPTY, UC_BUFFERREADY: // Buffer Empty, Buffer Ready\r\n\t\t// http://repo.or.cz/w/rtmpdump.git/blob/8880d1456b282ee79979adbe7b6a6eb8ad371081:/librtmp/rtmp.c#l2787\r\n\r\n\tcase\r\n\t\tUC_STREAMBEGIN,\r\n\t\tUC_STREAMEOF,\r\n\t\tUC_STREAMDRY,\r\n\t\tUC_STREAMISRECORDED,\r\n\t\tUC_PINGREQUEST,\r\n\t\tUC_PINGRESPONSE:\r\n\t\t// 4-byte stream id\r\n\t\tnum, e := decodeBEInt32(rbuff)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, num)\r\n\r\n\tcase UC_SETBUFFERLENGTH:\r\n\t\t// 4-byte stream id\r\n\t\tsid, e := decodeBEInt32(rbuff)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, sid)\r\n\t\t// 4-byte buffer length\r\n\t\tbsz, e := decodeBEInt32(rbuff)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, bsz)\r\n\r\n\tdefault:\r\n\t\terr = &DecodeError{\r\n\t\t\tFun: \"decodeUserControl\",\r\n\t\t\tMsg: fmt.Sprintf(\"Unknown User control: %v\", evt),\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\ntype message struct {\r\n\tmsg_t     int\r\n\ttimestamp int\r\n\tdata      *bytes.Buffer\r\n}\r\n\r\nfunc decodeMessage(rbuff *bytes.Buffer) (res message, err error) {\r\n\tmsg_t, err := decodeInt8(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tplen, err := decodeBEInt24(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tts_0, err := decodeBEInt24(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tts_1, err := decodeInt8(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tts := (ts_1 << 24) | ts_0\r\n\r\n\t// stream id\r\n\t_, err = decodeBEInt24(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\t//fmt.Printf(\"debug decodeMessage: type(%v) len(%v) ts(%v)\\n\", msg_t, plen, ts_0)\r\n\tbuff := bytes.NewBuffer(nil)\r\n\tif _, err = io.CopyN(buff, rbuff, int64(plen)); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// backPointer\r\n\t_, err = decodeBEInt32(rbuff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tres = message{\r\n\t\tmsg_t:     msg_t,\r\n\t\ttimestamp: ts,\r\n\t\tdata:      buff,\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc decodeAggregate(rbuff *bytes.Buffer) (res []message, err error) {\r\n\tfor rbuff.Len() > 0 {\r\n\t\tmsg, e := decodeMessage(rbuff)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tres = append(res, msg)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc decodeOne(rdr io.Reader, csz int, info map[int]chunkInfo) (ts int, msg_t int, res interface{}, rsz int, err error) {\r\n\tmsg := rtmpMsg{}\r\n\r\n\t// rtmp header\r\n\tif err = decodeHeader(rdr, &msg); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// restore fields from previous chunk header\r\n\r\n\tvar prevChunk chunkInfo\r\n\tif msg.formatOrigin != 0 {\r\n\t\tvar ok bool\r\n\t\tif prevChunk, ok = info[msg.csIdOrigin]; !ok {\r\n\t\t\terr = &DecodeError{\r\n\t\t\t\tFun: \"decodeOne\",\r\n\t\t\t\tMsg: fmt.Sprintf(\"Not exists previous chunk(csId = %v)\", msg.csIdOrigin),\r\n\t\t\t}\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\t//fmt.Printf(\"debug decodeOne msg.timestampField %d\\n\", msg.timestampField)\r\n\tif (msg.timestampField == 0xffffff) || ((msg.formatOrigin == 3) && (prevChunk.timestampField == 0xffffff)) {\r\n\t\tif err = decodeTimestampEX(rdr, &msg); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\t//fmt.Printf(\"%#v\\n\", msg)\r\n\t\tswitch msg.formatOrigin {\r\n\t\tcase 0:\r\n\t\t\tmsg.timestampActual = msg.timestampEx\r\n\t\t\tmsg.timestampDelta = msg.timestampEx\r\n\t\tcase 1, 2:\r\n\t\t\tmsg.timestampActual = prevChunk.timestampActual + msg.timestampEx\r\n\t\t\tmsg.timestampDelta = msg.timestampEx\r\n\t\tcase 3:\r\n\t\t\tmsg.timestampActual = msg.timestampEx\r\n\t\t\tmsg.timestampDelta = msg.timestampEx\r\n\t\t\tmsg.timestampField = 0xffffff\r\n\t\t}\r\n\t} else {\r\n\t\tswitch msg.formatOrigin {\r\n\t\tcase 0:\r\n\t\t\tmsg.timestampActual = msg.timestampField\r\n\t\t\tmsg.timestampDelta = msg.timestampField\r\n\t\tcase 1, 2:\r\n\t\t\tmsg.timestampActual = prevChunk.timestampActual + msg.timestampField\r\n\t\t\tmsg.timestampDelta = msg.timestampField\r\n\t\tcase 3:\r\n\t\t\tmsg.timestampActual = prevChunk.timestampActual + prevChunk.timestampDelta\r\n\t\t\tmsg.timestampDelta = prevChunk.timestampDelta\r\n\t\t}\r\n\t}\r\n\r\n\tswitch msg.formatOrigin {\r\n\tcase 1:\r\n\t\tmsg.msgStreamId = prevChunk.msgStreamId\r\n\tcase 2, 3:\r\n\t\tmsg.msgLength = prevChunk.msgLength\r\n\t\tmsg.msgTypeId = prevChunk.msgTypeId\r\n\t\tmsg.msgStreamId = prevChunk.msgStreamId\r\n\t}\r\n\r\n\tinfo[msg.csId] = chunkInfo{\r\n\t\ttimestampField:  msg.timestampField,\r\n\t\ttimestampDelta:  msg.timestampDelta,\r\n\t\ttimestampActual: msg.timestampActual,\r\n\t\tmsgLength:       msg.msgLength,\r\n\t\tmsgTypeId:       msg.msgTypeId,\r\n\t\tmsgStreamId:     msg.msgStreamId,\r\n\t}\r\n\r\n\tts = msg.timestampActual\r\n\r\n\tmsg.readingBody = true\r\n\r\n\t// rtmp payload\r\n\tfor {\r\n\t\tif err = readChunkBody(rdr, &msg, csz); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif msg.msgLength <= msg.bodyBuff.Len() {\r\n\t\t\tbreak\r\n\t\t}\r\n\r\n\t\t//if err = decodeFmtCsId(rdr, &msg); err != nil {\r\n\t\tif err = decodeHeader(rdr, &msg); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// timestamp extended\r\n\t\tif msg.timestampField == 0xffffff {\r\n\t\t\tif err = decodeTimestampEX(rdr, &msg); err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t//fmt.Printf(\"debug rtmp decodeOne: %#v\\n\", msg)\r\n\t// read byte count\r\n\trsz = msg.hdrLength + msg.msgLength\r\n\r\n\tmsg_t = msg.msgTypeId\r\n\tswitch msg.msgTypeId {\r\n\tcase TID_AGGREGATE:\r\n\t\tif res, err = decodeAggregate(msg.bodyBuff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\tcase TID_AUDIO, TID_VIDEO:\r\n\t\tres = msg.bodyBuff\r\n\r\n\tcase TID_WINDOW_ACK_SIZE:\r\n\t\tif res, err = decodeWindowAckSize(msg.bodyBuff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_SETPEERBANDWIDTH:\r\n\t\tif res, err = decodeSetPeerBandwidth(msg.bodyBuff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_AMF0COMMAND:\r\n\t\tif res, err = amf.DecodeAmf0(msg.bodyBuff.Bytes()); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_AMF3COMMAND:\r\n\t\tif res, err = amf.DecodeAmf0(msg.bodyBuff.Bytes(), true); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_AMF0DATA:\r\n\t\tif res, err = amf.DecodeAmf0(msg.bodyBuff.Bytes()); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_SETCHUNKSIZE:\r\n\t\tif res, err = decodeSetChunkSize(msg.bodyBuff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tcase TID_USERCONTROL:\r\n\t\tif res, err = decodeUserControl(msg.bodyBuff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\tdefault:\r\n\t\terr = &DecodeError{\r\n\t\t\tFun: \"decodeOne\",\r\n\t\t\tMsg: fmt.Sprintf(\"msgTypeId: not implement: %v\\n%#v\", msg.msgTypeId, msg.bodyBuff.Bytes()),\r\n\t\t}\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/rtmps/rtmp.go",
    "content": "package rtmps\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"io/ioutil\"\r\n\t\"math/rand\"\r\n\t\"net\"\r\n\t\"regexp\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/amf\"\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/flvs\"\r\n\t\"github.com/himananiito/livedl/objs\"\r\n)\r\n\r\ntype DecodeError struct {\r\n\tFun string\r\n\tMsg string\r\n}\r\n\r\nfunc (e *DecodeError) Error() string {\r\n\treturn fmt.Sprintf(\"%s: %s\", e.Fun, e.Msg)\r\n}\r\n\r\ntype chunkInfo struct {\r\n\ttimestampField  int\r\n\ttimestampDelta  int\r\n\ttimestampActual int\r\n\tmsgLength       int\r\n\tmsgTypeId       int\r\n\tmsgStreamId     int\r\n}\r\n\r\ntype Rtmp struct {\r\n\tproto      string // No reset\r\n\taddress    string // No reset\r\n\tapp        string // No reset\r\n\ttcUrl      string // No reset\r\n\tswfUrl     string // No reset\r\n\tpageUrl    string // No reset\r\n\tconnectOpt []interface{}\r\n\r\n\tconn          *net.TCPConn      // RESET_ON_CONNECT\r\n\tchunkSizeSend int               // RESET_ON_CONNECT\r\n\tchunkSizeRecv int               // RESET_ON_CONNECT\r\n\ttransactionId int               // RESET_ON_CONNECT\r\n\twindowSize    int               // RESET_ON_CONNECT\r\n\tchunkInfo     map[int]chunkInfo // RESET_ON_CONNECT\r\n\r\n\treadCount      int // RESET_ON_CONNECT\r\n\ttotalReadBytes int // RESET_ON_CONNECT\r\n\tisRecorded     bool\r\n\r\n\ttimestamp int // NO_RESET\r\n\tduration  int\r\n\r\n\tflvName string\r\n\tflv     *flvs.Flv\r\n\r\n\tfixAggrTimestamp bool\r\n\tstreamId         int\r\n\tnextLogTs        int\r\n\r\n\tVideoExists bool\r\n\tnoSeek      bool\r\n\tflush       bool\r\n\r\n\tstartTime int\r\n}\r\n\r\nfunc NewRtmp(tc, swf, page string, opt ...interface{}) (rtmp *Rtmp, err error) {\r\n\tre := regexp.MustCompile(`\\A(\\w+)://([^/\\s]+)/(\\S+)\\z`)\r\n\tmstr := re.FindStringSubmatch(tc)\r\n\tif mstr == nil {\r\n\t\terr = fmt.Errorf(\"tcUrl incorrect: %v\", tc)\r\n\t\treturn\r\n\t}\r\n\r\n\trtmp = &Rtmp{\r\n\t\tproto:      mstr[1],\r\n\t\taddress:    mstr[2],\r\n\t\tapp:        mstr[3],\r\n\t\ttcUrl:      tc,\r\n\t\tswfUrl:     swf,\r\n\t\tpageUrl:    page,\r\n\t\tconnectOpt: opt,\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) Connect() (err error) {\r\n\tif rtmp.conn != nil {\r\n\t\trtmp.conn.Close()\r\n\t\trtmp.conn = nil\r\n\t\ttime.Sleep(3)\r\n\t}\r\n\r\n\trtmp.windowSize = 2500000\r\n\trtmp.chunkInfo = make(map[int]chunkInfo)\r\n\trtmp.chunkSizeSend = 128\r\n\trtmp.chunkSizeRecv = 128\r\n\trtmp.transactionId = 1\r\n\r\n\trtmp.readCount = 0\r\n\trtmp.totalReadBytes = 0\r\n\r\n\terr = rtmp.connect(\r\n\t\trtmp.app,\r\n\t\trtmp.tcUrl,\r\n\t\trtmp.swfUrl,\r\n\t\trtmp.pageUrl,\r\n\t\trtmp.connectOpt...,\r\n\t)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) SetFlush(b bool) {\r\n\trtmp.flush = b\r\n}\r\nfunc (rtmp *Rtmp) SetNoSeek(b bool) {\r\n\trtmp.noSeek = b\r\n}\r\nfunc (rtmp *Rtmp) SetConnectOpt(opt ...interface{}) {\r\n\trtmp.connectOpt = opt\r\n}\r\nfunc (rtmp *Rtmp) connect(app, tc, swf, page string, opt ...interface{}) (err error) {\r\n\r\n\traddr, err := net.ResolveTCPAddr(\"tcp\", rtmp.address)\r\n\tif err != nil {\r\n\t\tfmt.Printf(\"%v\\n\", err)\r\n\t\treturn\r\n\t}\r\n\r\n\tswitch rtmp.proto {\r\n\tcase \"rtmp\":\r\n\t\tconn, e := net.DialTCP(\"tcp\", nil, raddr)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\trtmp.conn = conn\r\n\r\n\tdefault:\r\n\t\terr = fmt.Errorf(\"Unknown protocol: %v\", rtmp.proto)\r\n\t\treturn\r\n\t}\r\n\r\n\terr = handshake(rtmp.conn)\r\n\tif err != nil {\r\n\t\trtmp.conn.Close()\r\n\t\treturn\r\n\t}\r\n\r\n\tvar data []interface{}\r\n\tdata = append(data, map[string]interface{}{\r\n\t\t\"app\":            app,\r\n\t\t\"flashVer\":       \"WIN 29,0,0,113\",\r\n\t\t\"swfUrl\":         swf,\r\n\t\t\"tcUrl\":          tc,\r\n\t\t\"fpad\":           false,\r\n\t\t\"capabilities\":   239,\r\n\t\t\"audioCodecs\":    0xFFF, //3575,\r\n\t\t\"videoCodecs\":    0xFF,  //252,\r\n\t\t\"videoFunction\":  1,\r\n\t\t\"pageUrl\":        page,\r\n\t\t\"objectEncoding\": 3,\r\n\t})\r\n\r\n\tfor _, o := range opt {\r\n\t\tdata = append(data, o)\r\n\t}\r\n\r\n\t_, err = rtmp.Command(\"connect\", data)\r\n\r\n\treturn\r\n}\r\n\r\nconst (\r\n\tNORMAL = iota\r\n\tCOMMAND\r\n\tPAUSE\r\n\tTEST\r\n)\r\n\r\nfunc (rtmp *Rtmp) wait(findTrId int, pause bool, testTimeout int) (done, incomplete bool, trData interface{}, err error) {\r\n\tvar mode int\r\n\tvar endUnix int64\r\n\tvar endTime time.Time\r\n\tif findTrId >= 0 {\r\n\t\tmode = COMMAND\r\n\t} else if pause {\r\n\t\tmode = PAUSE\r\n\t} else if testTimeout > 0 {\r\n\t\tmode = TEST\r\n\t\tendUnix = time.Now().Unix() + int64(testTimeout)\r\n\t\tendTime = time.Unix(endUnix, 0)\r\n\t}\r\n\r\n\tif mode != COMMAND {\r\n\t\tfindTrId = -1\r\n\t}\r\n\r\n\tfor {\r\n\t\tif mode == TEST {\r\n\t\t\trtmp.conn.SetReadDeadline(endTime)\r\n\t\t} else {\r\n\t\t\trtmp.conn.SetReadDeadline(time.Now().Add(300 * time.Second))\r\n\t\t}\r\n\t\t__done, __incomplete, trFound, pause, __trData, e := rtmp.recvChunk(findTrId, pause)\r\n\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\t\tif __done || __incomplete {\r\n\t\t\tdone = __done\r\n\t\t\tincomplete = __incomplete\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tswitch mode {\r\n\t\tcase COMMAND:\r\n\t\t\tif trFound {\r\n\t\t\t\ttrData = __trData\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\tcase PAUSE:\r\n\t\t\tif pause {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\tcase TEST:\r\n\t\t\tif time.Now().Unix() >= endUnix {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunc (rtmp *Rtmp) WaitPause() (done, incomplete bool, err error) {\r\n\tdone, incomplete, _, err = rtmp.wait(-1, true, -1)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) WaitTest(testTimeout int) (done, incomplete bool, err error) {\r\n\tdone, incomplete, _, err = rtmp.wait(-1, false, testTimeout)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) Wait() (done, incomplete bool, err error) {\r\n\tdone, incomplete, _, err = rtmp.wait(-1, false, -1)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) waitCommand(findTrId int) (done, incomplete bool, trData interface{}, err error) {\r\n\tdone, incomplete, trData, err = rtmp.wait(findTrId, false, -1)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) SetFlvName(name string) {\r\n\trtmp.flvName = name\r\n}\r\nfunc (rtmp *Rtmp) openFlv(incr bool) (err error) {\r\n\tif rtmp.flvName == \"\" {\r\n\t\terr = fmt.Errorf(\"FLV file name not set: call SetFlvName(string)\")\r\n\t\treturn\r\n\t}\r\n\tvar fileName string\r\n\tif incr {\r\n\t\tif fileName, err = files.GetFileNameNext(rtmp.flvName); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\tfileName = rtmp.flvName\r\n\t}\r\n\tflv, err := flvs.Open(fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\trtmp.flv = flv\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) GetTimestamp() int {\r\n\treturn rtmp.timestamp\r\n}\r\nfunc (rtmp *Rtmp) SetTimestamp(t int) {\r\n\trtmp.timestamp = t\r\n}\r\nfunc (rtmp *Rtmp) writeMetaData(body map[string]interface{}, ts int) (err error) {\r\n\r\n\tif rtmp.flv == nil {\r\n\t\tif err = rtmp.openFlv(false); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\t//buf := new(bytes.Buffer)\r\n\tdata := []interface{}{}\r\n\tdata = append(data, \"onMetaData\")\r\n\tdata = append(data, body)\r\n\r\n\tdat, err := amf.EncodeAmf0(data, true)\r\n\t//fmt.Printf(\"writeMetaData %v %#v\\n\", ts, dat)\r\n\trdr := bytes.NewBuffer(dat)\r\n\terr = rtmp.flv.WriteMetaData(rdr, ts)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) writeAudio(rdr *bytes.Buffer, ts int) (err error) {\r\n\tif rtmp.flv == nil {\r\n\t\tif err = rtmp.openFlv(false); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\terr = rtmp.flv.WriteAudio(rdr, ts)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) writeVideo(rdr *bytes.Buffer, ts int) (err error) {\r\n\tif rtmp.flv == nil {\r\n\t\tif err = rtmp.openFlv(false); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t} /*else if (!rtmp.flv.VideoExists() && rtmp.flv.AudioExists()) && ts > 1000 {\r\n\t\tif err = rtmp.openFlv(true); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}*/\r\n\terr = rtmp.flv.WriteVideo(rdr, ts)\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) SetFixAggrTimestamp(sw bool) {\r\n\trtmp.fixAggrTimestamp = sw\r\n}\r\nfunc (rtmp *Rtmp) CheckStatus(label string, ts int, data interface{}, waitPause bool) (done, incomplete, pauseFound bool, err error) {\r\n\tcode, ok := objs.FindString(data, \"code\")\r\n\tif !ok {\r\n\t\terr = fmt.Errorf(\"%s: code Not found\", label)\r\n\t\treturn\r\n\t}\r\n\r\n\tswitch code {\r\n\tcase \"NetStream.Pause.Notify\":\r\n\t\tif waitPause {\r\n\t\t\tpauseFound = true\r\n\t\t}\r\n\tcase \"NetStream.Unpause.Notify\":\r\n\tcase \"NetStream.Play.Stop\":\r\n\tcase \"NetStream.Play.Complete\":\r\n\t\tfmt.Printf(\"NetStream.Play.Complete: last timestamp: %d(flv)\\n\", rtmp.flv.GetLastTimestamp())\r\n\t\tif (ts + 1000) > rtmp.duration {\r\n\t\t\tdone = true\r\n\t\t} else {\r\n\t\t\tincomplete = true\r\n\t\t}\r\n\tcase \"NetStream.Play.Start\":\r\n\tcase \"NetStream.Play.Reset\":\r\n\tcase \"NetStream.Seek.Notify\":\r\n\tcase \"NetStream.Play.Failed\":\r\n\t\tdone = true\r\n\tdefault:\r\n\t\tfmt.Printf(\"[FIXME] Unknown Code: %s\\n\", code)\r\n\t}\r\n\treturn\r\n}\r\n\r\n// trId: transaction id to find\r\nfunc (rtmp *Rtmp) recvChunk(findTrId int, waitPause bool) (done, incomplete, trFound, pauseFound bool, trData interface{}, err error) {\r\n\tts, msg_t, res, rdbytes, err := decodeOne(rtmp.conn, rtmp.chunkSizeRecv, rtmp.chunkInfo)\r\n\tif err != nil {\r\n\t\tswitch err.(type) {\r\n\t\tcase *net.OpError:\r\n\t\t\treturn\r\n\t\tcase *DecodeError:\r\n\t\t\t// データを受信したが、パースエラーとなった場合はやり直したい\r\n\t\t\tfmt.Printf(\"Please retry: RTMP: %v\\n\", err.Error())\r\n\t\t\tincomplete = true\r\n\t\t\terr = nil\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\treturn\r\n\t}\r\n\tts = ts + rtmp.startTime\r\n\r\n\t// byte counter for acknowledgement\r\n\trtmp.totalReadBytes += rdbytes\r\n\trtmp.readCount += rdbytes\r\n\tif rtmp.readCount >= (rtmp.windowSize / 2) {\r\n\t\trtmp.readCount = 0\r\n\t\tif err = rtmp.acknowledgement(); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\t// print play timestamp\r\n\tif true {\r\n\t\tif rtmp.duration > 0 {\r\n\t\t\tswitch msg_t {\r\n\t\t\tcase TID_AUDIO, TID_VIDEO, TID_AGGREGATE:\r\n\t\t\t\tif ts >= rtmp.nextLogTs {\r\n\t\t\t\t\tfmt.Printf(\"#%8d/%d(%4.1f%%) : %s\\n\", ts, rtmp.duration, float64(ts)/float64(rtmp.duration)*100, rtmp.flvName)\r\n\t\t\t\t\trtmp.nextLogTs = ts + 10000\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tswitch msg_t {\r\n\t\t\tcase TID_AUDIO, TID_VIDEO, TID_AGGREGATE:\r\n\t\t\t\tif ts >= rtmp.nextLogTs {\r\n\t\t\t\t\tfmt.Printf(\"#%8d : %s\\n\", ts, rtmp.flvName)\r\n\t\t\t\t\trtmp.nextLogTs = ts + 10000\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tswitch msg_t {\r\n\tcase TID_AUDIO:\r\n\t\tif ts > rtmp.timestamp {\r\n\t\t\trtmp.timestamp = ts\r\n\t\t}\r\n\t\tif err = rtmp.writeAudio(res.(*bytes.Buffer), ts); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\tcase TID_VIDEO:\r\n\t\tif ts > rtmp.timestamp {\r\n\t\t\trtmp.timestamp = ts\r\n\t\t}\r\n\t\tif err = rtmp.writeVideo(res.(*bytes.Buffer), ts); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\tcase TID_AGGREGATE:\r\n\t\tif ts > rtmp.timestamp {\r\n\t\t\trtmp.timestamp = ts\r\n\t\t}\r\n\t\tvar fstTs int\r\n\t\tfor i, v := range res.([]message) {\r\n\t\t\tvar tsAggr int\r\n\t\t\tif rtmp.fixAggrTimestamp {\r\n\t\t\t\tvar delta int\r\n\t\t\t\tif i == 0 {\r\n\t\t\t\t\tfstTs = v.timestamp\r\n\t\t\t\t}\r\n\t\t\t\tdelta = v.timestamp - fstTs\r\n\t\t\t\ttsAggr = ts + delta\r\n\t\t\t\t//fmt.Printf(\"FixAggrTs: fixed(%d), delta(%d), ts(%d), mts(%d)\\n\", tsAggr, delta, ts, v.timestamp)\r\n\t\t\t} else {\r\n\t\t\t\tif i == 0 {\r\n\t\t\t\t\tif ts != v.timestamp {\r\n\t\t\t\t\t\terr = fmt.Errorf(\"aggregate timestamp incorrect: ts:(%v) vs aggr[0].ts(%v)\", ts, v.timestamp)\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\ttsAggr = v.timestamp\r\n\t\t\t}\r\n\r\n\t\t\tif /*rtmp.isRecorded &&*/ rtmp.duration > 0 {\r\n\t\t\t\tswitch v.msg_t {\r\n\t\t\t\tcase TID_AUDIO, TID_VIDEO:\r\n\t\t\t\t\t// fmt.Printf(\" %8d/%d(%4.1f%%) : %2d\\n\", tsAggr, rtmp.duration, float64(tsAggr)/float64(rtmp.duration)*100, v.msg_t)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tswitch v.msg_t {\r\n\t\t\tcase TID_AUDIO:\r\n\t\t\t\t// audio\r\n\t\t\t\tif err = rtmp.writeAudio(v.data, tsAggr); err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\tcase TID_VIDEO:\r\n\t\t\t\t// video\r\n\t\t\t\tif err = rtmp.writeVideo(v.data, tsAggr); err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\tcase TID_AMF0DATA, TID_AMF3DATA:\r\n\t\tobjs.PrintAsJson(res)\r\n\t\tlist, ok := res.([]interface{})\r\n\t\tif !ok {\r\n\t\t\terr = fmt.Errorf(\"result AMF Data is not array\")\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif len(list) >= 2 {\r\n\t\t\tname, ok := list[0].(string)\r\n\t\t\tif !ok {\r\n\t\t\t\terr = fmt.Errorf(\"result AMF Data[0] is not string\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tswitch name {\r\n\t\t\tcase \"onPlayStatus\":\r\n\t\t\t\tdone, incomplete, pauseFound, err = rtmp.CheckStatus(\"onPlayStatus\", ts, list[1], waitPause)\r\n\r\n\t\t\tcase \"onMetaData\":\r\n\t\t\t\tdur, ok := objs.FindFloat64(list[1], \"duration\")\r\n\t\t\t\tif ok {\r\n\t\t\t\t\trtmp.duration = int(dur * 1000)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif rtmp.isRecorded {\r\n\t\t\t\t\t\tfmt.Println(\"[WARN] onMetaData: duration not found\")\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif meta, ok := list[1].(map[string]interface{}); ok {\r\n\t\t\t\t\trtmp.writeMetaData(meta, ts)\r\n\t\t\t\t}\r\n\r\n\t\t\t\t_, ok = objs.Find(list[1], \"videoframerate\")\r\n\t\t\t\tif ok {\r\n\t\t\t\t\trtmp.VideoExists = true\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\tcase TID_AMF0COMMAND, TID_AMF3COMMAND:\r\n\t\tobjs.PrintAsJson(res)\r\n\r\n\t\tlist, ok := res.([]interface{})\r\n\t\tif !ok {\r\n\t\t\terr = fmt.Errorf(\"result AMF Command is not array\")\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif len(list) >= 3 {\r\n\t\t\tname, ok := list[0].(string)\r\n\t\t\tif !ok {\r\n\t\t\t\terr = fmt.Errorf(\"result AMF Command name is not string\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\ttrIdFloat, ok := list[1].(float64)\r\n\t\t\tif !ok {\r\n\t\t\t\terr = fmt.Errorf(\"result AMF Command transaction id is not number\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\ttrId := int(trIdFloat)\r\n\t\t\tif (trId > 0) && (trId == findTrId) {\r\n\t\t\t\ttrFound = true\r\n\t\t\t\tif len(list) >= 4 {\r\n\t\t\t\t\ttrData = list[3]\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tswitch name {\r\n\t\t\tcase \"_error\", \"close\":\r\n\t\t\t\terr = fmt.Errorf(\"AMF command not success: transaction id(%d) -> %s\", trId, name)\r\n\t\t\t\treturn\r\n\t\t\tcase \"onStatus\":\r\n\t\t\t\tdone, incomplete, pauseFound, err = rtmp.CheckStatus(\"onStatus\", ts, list[3], waitPause)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\tcase TID_SETCHUNKSIZE:\r\n\t\trtmp.chunkSizeRecv = res.(int)\r\n\r\n\tcase TID_WINDOW_ACK_SIZE:\r\n\t\trtmp.windowSize = res.(int)\r\n\r\n\tcase TID_USERCONTROL:\r\n\t\tswitch res.([]int)[0] {\r\n\t\tcase UC_PINGREQUEST:\r\n\t\t\t//fmt.Printf(\"ping request %d\\n\", res.([]int)[1])\r\n\t\t\tif err = rtmp.pingResponse(res.([]int)[1]); err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\tcase UC_STREAMBEGIN:\r\n\t\t\trtmp.streamId = res.([]int)[1]\r\n\r\n\t\tcase UC_STREAMISRECORDED:\r\n\t\t\tfmt.Printf(\"stream is recorded\\n\")\r\n\t\t\trtmp.isRecorded = true\r\n\r\n\t\tcase UC_BUFFEREMPTY:\r\n\t\t\tif rtmp.isRecorded {\r\n\t\t\t\tfmt.Printf(\"required Seek: %d\\n\", rtmp.timestamp)\r\n\t\t\t\t// <-- test\r\n\t\t\t\trtmp.PauseRaw()\r\n\t\t\t\tincomplete = true\r\n\t\t\t\treturn\r\n\t\t\t\t// test -->\r\n\r\n\t\t\t\tif rtmp.noSeek {\r\n\t\t\t\t\tincomplete = true\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tts := rtmp.timestamp - 10000\r\n\t\t\t\tif ts < 0 {\r\n\t\t\t\t\tts = 0\r\n\t\t\t\t}\r\n\t\t\t\tdone, incomplete, err = rtmp.PauseUnpause(ts)\r\n\t\t\t\tif done || incomplete || err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\t//rtmp.Seek(ts)\r\n\t\t\t}\r\n\t\t}\r\n\tdefault:\r\n\t\t//fmt.Printf(\"got: %8d %d %#v\\n\", ts, msg_t, res)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc (rtmp *Rtmp) Close() (err error) {\r\n\tif rtmp.conn != nil {\r\n\t\terr = rtmp.conn.Close()\r\n\t}\r\n\tif rtmp.flv != nil {\r\n\t\trtmp.flv.Close()\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc (rtmp *Rtmp) SetPeerBandwidth(wsz, lim int) (err error) {\r\n\tbuff, err := encodeSetPeerBandwidth(wsz, lim)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif _, err = buff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc (rtmp *Rtmp) pingResponse(timestamp int) (err error) {\r\n\tbuff, err := encodePingResponse(timestamp)\r\n\tif _, err = buff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) acknowledgement() (err error) {\r\n\tbuff, err := encodeAcknowledgement(rtmp.totalReadBytes)\r\n\tif _, err = buff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) WindowAckSize(asz int) (err error) {\r\n\tbuff, err := encodeWindowAckSize(asz)\r\n\tif _, err = buff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) SetBufferLength(streamId, len int) (err error) {\r\n\tbuff, err := encodeSetBufferLength(streamId, len)\r\n\tif _, err = buff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n\r\n// command name, transaction ID, and command object\r\nfunc (rtmp *Rtmp) Command(name string, args []interface{}) (trData interface{}, err error) {\r\n\tvar trId int\r\n\tvar csId int\r\n\tvar streamId int\r\n\tswitch name {\r\n\tcase \"connect\":\r\n\t\trtmp.transactionId = 1\r\n\t\ttrId = rtmp.transactionId\r\n\t\tcsId = 3\r\n\t\tstreamId = 0\r\n\r\n\tcase \"play\", \"seek\", \"pause\", \"pauseRaw\":\r\n\t\ttrId = 0\r\n\t\tcsId = 8\r\n\t\tstreamId = 1\r\n\r\n\tdefault:\r\n\t\t// createStream, call, close, ...\r\n\t\trtmp.transactionId++\r\n\t\ttrId = rtmp.transactionId\r\n\t\tcsId = 3\r\n\t\tstreamId = 0\r\n\t}\r\n\tcmd := []interface{}{name, trId}\r\n\tcmd = append(cmd, args...)\r\n\tobjs.PrintAsJson(cmd)\r\n\tbody, err := amf.EncodeAmf0(cmd, false)\r\n\twbuff, err := amf0Command(rtmp.chunkSizeSend, csId, streamId, body)\r\n\r\n\tif _, err = wbuff.WriteTo(rtmp.conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tif trId > 0 {\r\n\t\tif _, _, trData, err = rtmp.waitCommand(trId); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc (rtmp *Rtmp) Unpause(timestamp int) (err error) {\r\n\tvar data []interface{}\r\n\tdata = append(data, nil)\r\n\tdata = append(data, false)\r\n\tdata = append(data, timestamp)\r\n\r\n\t_, err = rtmp.Command(\"pause\", data)\r\n\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) Pause(timestamp int) (err error) {\r\n\tvar data []interface{}\r\n\tdata = append(data, nil)\r\n\tdata = append(data, true)\r\n\tdata = append(data, timestamp)\r\n\r\n\t_, err = rtmp.Command(\"pause\", data)\r\n\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) PauseRaw() (err error) {\r\n\t_, err = rtmp.Command(\"pauseRaw\", []interface{}{\r\n\t\tnil,\r\n\t\ttrue,\r\n\t\t0,\r\n\t})\r\n\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) PauseUnpause(timestamp int) (done, incomplete bool, err error) {\r\n\tif err = rtmp.Pause(timestamp); err != nil {\r\n\t\treturn\r\n\t}\r\n\tfmt.Println(\"paused\")\r\n\tdone, incomplete, err = rtmp.WaitPause()\r\n\tif done || incomplete || err != nil {\r\n\t\treturn\r\n\t}\r\n\tfmt.Println(\"wait pause\")\r\n\tif err = rtmp.Unpause(timestamp); err != nil {\r\n\t\treturn\r\n\t}\r\n\tfmt.Println(\"Unpaused\")\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) PlayTime(stream string, timestamp int) (err error) {\r\n\r\n\trtmp.startTime = timestamp\r\n\tif rtmp.startTime < 0 {\r\n\t\trtmp.startTime = 0\r\n\t}\r\n\t//fmt.Printf(\"debug rtmp.startTime: %d\\n\", rtmp.startTime)\r\n\r\n\tvar data []interface{}\r\n\tdata = append(data, nil)\r\n\tdata = append(data, stream)\r\n\r\n\tdata = append(data, timestamp) // Start\r\n\t// NicoOfficialTs, Never append Duration and flush\r\n\tif rtmp.flush {\r\n\t\tdata = append(data, -1)   // Duration\r\n\t\tdata = append(data, true) // flush\r\n\t}\r\n\r\n\t_, err = rtmp.Command(\"play\", data)\r\n\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) Play(stream string) error {\r\n\treturn rtmp.PlayTime(stream, -5000)\r\n}\r\nfunc (rtmp *Rtmp) Seek(timestamp int) (err error) {\r\n\t//fmt.Printf(\"debug Seek to %d\\n\", timestamp)\r\n\tvar data []interface{}\r\n\tdata = append(data, nil)\r\n\tdata = append(data, timestamp)\r\n\r\n\t_, err = rtmp.Command(\"seek\", data)\r\n\r\n\t//fmt.Printf(\"debug Seek done\\n\")\r\n\treturn\r\n}\r\nfunc (rtmp *Rtmp) CreateStream() (err error) {\r\n\tvar data []interface{}\r\n\tdata = append(data, nil)\r\n\r\n\t_, err = rtmp.Command(\"createStream\", data)\r\n\r\n\treturn\r\n}\r\n\r\nfunc handshake(conn *net.TCPConn) (err error) {\r\n\r\n\twbuff := bytes.NewBuffer(nil)\r\n\r\n\t// C0\r\n\twbuff.WriteByte(3)\r\n\t// C1\r\n\trnd := rand.New(rand.NewSource(time.Now().UnixNano()))\r\n\tio.CopyN(wbuff, rnd, 1536)\r\n\r\n\t// Send C0+C1\r\n\tif _, err = wbuff.WriteTo(conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// Recv S0\r\n\tif _, err = io.CopyN(ioutil.Discard, conn, 1); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// Recv S1\r\n\tif _, err = io.CopyN(wbuff, conn, 1536); err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t// Send C2(=S1)\r\n\tif _, err = wbuff.WriteTo(conn); err != nil {\r\n\t\treturn\r\n\t}\r\n\t// Recv S2\r\n\tif _, err = io.CopyN(ioutil.Discard, conn, 1536); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/twitcas/twicas.go",
    "content": "package twitcas\r\n\r\nimport (\r\n\t\"database/sql\"\r\n\t\"encoding/json\"\r\n\t\"errors\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"io/ioutil\"\r\n\t\"net/http\"\r\n\t\"net/url\"\r\n\t\"os\"\r\n\t\"os/exec\"\r\n\t\"time\"\r\n\r\n\t\"github.com/gorilla/websocket\"\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/procs/ffmpeg\"\r\n\t_ \"github.com/mattn/go-sqlite3\"\r\n)\r\n\r\ntype Twitcas struct {\r\n\tConn *websocket.Conn\r\n}\r\n\r\nfunc connectStream(proto, host, mode string, id uint64, proxy string) (conn *websocket.Conn, err error) {\r\n\tstreamUrl := fmt.Sprintf(\r\n\t\t//case A.InnerFrame:return\"i\";\r\n\t\t//case A.Pframe:return\"p\";\r\n\t\t//case A.DisposableProfile:return\"bd\";\r\n\t\t//case A.Bframe:return\"b\";\r\n\t\t//case A.Any:return\"any\";\r\n\t\t//case A.KeyFrame:default:return\"k\"}\r\n\t\t//\"%s://%s/ws.app/stream/%d/fmp4/k/0/1?mode=%s\",\r\n\t\t\"%s://%s/ws.app/stream/%d/fmp4/bd/1/1500?mode=%s\",\r\n\t\tproto, host, id, mode,\r\n\t)\r\n\t// fmt.Println(streamUrl)\r\n\r\n\tvar origin string\r\n\tif proto == \"wss\" {\r\n\t\torigin = fmt.Sprintf(\"https://%s\", host)\r\n\t} else {\r\n\t\torigin = fmt.Sprintf(\"http://%s\", host)\r\n\t}\r\n\r\n\theader := http.Header{}\r\n\theader.Set(\"Origin\", origin)\r\n\t//header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\")\r\n\theader.Set(\"User-Agent\", httpbase.GetUserAgent())\r\n\r\n\ttimeout, _ := time.ParseDuration(\"10s\")\r\n\tdialer := websocket.Dialer{\r\n\t\tHandshakeTimeout: timeout,\r\n\t}\r\n\tif proxy != \"\" {\r\n\t\tdialer.Proxy = func(req *http.Request) (u *url.URL, err error) {\r\n\t\t\tvar proxyUrl string\r\n\t\t\tif proto == \"wss\" {\r\n\t\t\t\tproxyUrl = fmt.Sprintf(\"https://%s\", proxy)\r\n\t\t\t} else {\r\n\t\t\t\tproxyUrl = fmt.Sprintf(\"http://%s\", proxy)\r\n\t\t\t}\r\n\t\t\treturn url.ParseRequestURI(proxyUrl)\r\n\t\t}\r\n\t}\r\n\r\n\tconn, _, err = dialer.Dial(streamUrl, header)\r\n\r\n\treturn\r\n}\r\n\r\nfunc getStream(user, proxy string) (conn *websocket.Conn, movieId uint64, err error) {\r\n\turl := fmt.Sprintf(\r\n\t\t\"https://twitcasting.tv/streamserver.php?target=%s&mode=client\",\r\n\t\tuser,\r\n\t)\r\n\r\n\ttype StreamServer struct {\r\n\t\tMovie struct {\r\n\t\t\tId   uint64 `json:\"id\"`\r\n\t\t\tLive bool   `json:\"live\"`\r\n\t\t} `json:\"movie\"`\r\n\t\tFmp4 struct {\r\n\t\t\tHost         string `json:\"host\"`\r\n\t\t\tProto        string `json:\"proto\"`\r\n\t\t\tSource       bool   `json:\"source\"`\r\n\t\t\tMobileSource bool   `json:\"mobilesource\"`\r\n\t\t} `json:\"fmp4\"`\r\n\t}\r\n\r\n\treq, err := http.NewRequest(\"GET\", url, nil)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tclient := new(http.Client)\r\n\tclient.Timeout, _ = time.ParseDuration(\"10s\")\r\n\r\n\tresp, err := client.Do(req)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tdefer resp.Body.Close()\r\n\trespBytes, err := ioutil.ReadAll(resp.Body)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\t//fmt.Printf(\"debug %s\\n\", string(respBytes))\r\n\r\n\tdata := new(StreamServer)\r\n\r\n\terr = json.Unmarshal(respBytes, data)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tif !data.Movie.Live {\r\n\t\t// movie not active\r\n\t\terr = errors.New(user + \" --> \" + \"Offline or User Not Found\")\r\n\t\treturn\r\n\t} else {\r\n\t\tvar mode string\r\n\t\tif data.Fmp4.Source {\r\n\t\t\t// StreamQuality.High\r\n\t\t\tmode = \"main\"\r\n\t\t} else if data.Fmp4.MobileSource {\r\n\t\t\t// StreamQuality.Middle\r\n\t\t\tmode = \"mobilesource\"\r\n\t\t} else {\r\n\t\t\t// StreamQuality.Low\r\n\t\t\tmode = \"base\"\r\n\t\t}\r\n\r\n\t\tif data.Fmp4.Proto != \"\" && data.Fmp4.Host != \"\" && data.Movie.Id != 0 {\r\n\t\t\tconn, err = connectStream(data.Fmp4.Proto, data.Fmp4.Host, mode, data.Movie.Id, proxy)\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tmovieId = data.Movie.Id\r\n\t\t} else {\r\n\t\t\terr = errors.New(user + \" --> \" + \"No Stream Defined\")\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc createFileUser(user string, movieId uint64) (f *os.File, filename string, err error) {\r\n\tuser = files.ReplaceForbidden(user)\r\n\tfilename = fmt.Sprintf(\"%s_%d.mp4\", user, movieId)\r\n\tfor i := 2; i < 1000; i++ {\r\n\t\t_, err := os.Stat(filename)\r\n\t\tif err != nil {\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tfilename = fmt.Sprintf(\"%s_%d_%d.mp4\", user, movieId, i)\r\n\t}\r\n\tf, err = os.Create(filename)\r\n\treturn\r\n}\r\n\r\n// FIXME: return codeの整理\r\nfunc TwitcasRecord(user, proxy string) (done, dbLocked bool) {\r\n\tconn, movieId, err := getStream(user, proxy)\r\n\tif err != nil {\r\n\t\tfmt.Printf(\"@err getStream: %v\\n\", err)\r\n\t\treturn\r\n\t}\r\n\tif conn == nil {\r\n\t\tfmt.Println(\"[FIXME] conn is nil\")\r\n\t\treturn\r\n\t}\r\n\tdefer conn.Close()\r\n\r\n\tdbName := fmt.Sprintf(\"tmp/tcas-%v-lock.db\", movieId)\r\n\tfiles.MkdirByFileName(dbName)\r\n\tdb, err := sql.Open(\"sqlite3\", dbName)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\t_, err = db.Exec(`BEGIN EXCLUSIVE`)\r\n\tif err != nil {\r\n\t\tdbLocked = true\r\n\t\treturn\r\n\t}\r\n\tdefer os.Remove(dbName)\r\n\r\n\t//func Open(opt... string) (cmd *exec.Cmd, stdin io.WriteCloser, err error) {\r\n\tvar cmd *exec.Cmd\r\n\tvar stdin io.WriteCloser\r\n\r\n\tvar fileOpened bool\r\n\r\n\tfilenameBase := fmt.Sprintf(\"%s_%d.ts\", user, movieId)\r\n\tfilenameBase = files.ReplaceForbidden(filenameBase) // fixed #8\r\n\r\n\tcloseFF := func() {\r\n\t\tif stdin != nil {\r\n\t\t\tstdin.Close()\r\n\t\t}\r\n\t\tif cmd != nil {\r\n\t\t\tcmd.Wait()\r\n\t\t}\r\n\t\tstdin = nil\r\n\t\tcmd = nil\r\n\t}\r\n\r\n\topenFF := func() (err error) {\r\n\t\tcloseFF()\r\n\r\n\t\tfilename, err := files.GetFileNameNext(filenameBase)\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tc, in, err := ffmpeg.Open(\"-i\", \"-\", \"-c\", \"copy\", \"-y\", filename)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tcmd = c\r\n\t\tstdin = in\r\n\r\n\t\tfileOpened = true\r\n\t\treturn\r\n\t}\r\n\r\n\tfor {\r\n\t\tconn.SetReadDeadline(time.Now().Add(60 * time.Second))\r\n\t\tmessageType, data, err := conn.ReadMessage()\r\n\t\tif err != nil {\r\n\t\t\tfmt.Printf(\"@err ReadMessage: %v\\n\\n\", err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif messageType == 2 {\r\n\t\t\tif cmd == nil || stdin == nil {\r\n\t\t\t\tif err = openFF(); err != nil {\r\n\t\t\t\t\tfmt.Println(err)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tdefer closeFF()\r\n\t\t\t}\r\n\r\n\t\t\tif _, err := stdin.Write(data); err != nil {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t} else if messageType == 1 {\r\n\r\n\t\t\ttype TextMessage struct {\r\n\t\t\t\tCode int `json:\"code\"`\r\n\t\t\t}\r\n\t\t\tmsg := new(TextMessage)\r\n\t\t\terr = json.Unmarshal(data, msg)\r\n\t\t\tif err != nil {\r\n\t\t\t\t// json decode error\r\n\t\t\t\tfmt.Printf(\"@err %v\\n\", err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif (msg.Code == 100) || (msg.Code == 101) || (msg.Code == 110) {\r\n\t\t\t\t// ignore\r\n\t\t\t} else if msg.Code == 400 { // invalid_parameter\r\n\t\t\t\treturn\r\n\t\t\t} else if msg.Code == 401 { // passcode_required\r\n\t\t\t\treturn\r\n\t\t\t} else if msg.Code == 403 { //access_forbidden\r\n\t\t\t\treturn\r\n\t\t\t} else if msg.Code == 500 { // offline\r\n\t\t\t\treturn\r\n\t\t\t} else if msg.Code == 503 { // server_error\r\n\t\t\t\treturn\r\n\t\t\t} else if msg.Code == 504 { // live_ended\r\n\t\t\t\tbreak\r\n\t\t\t} else {\r\n\t\t\t\tfmt.Printf(\"@FIXME %v\\n\\n\", string(data))\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tcloseFF()\r\n\r\n\tdone = fileOpened\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/youtube/comment.go",
    "content": "package youtube\r\n\r\nimport (\r\n\t\"context\"\r\n\t\"database/sql\"\r\n\t\"encoding/json\"\r\n\t\"fmt\"\r\n\t\"log\"\r\n\t\"os\"\r\n\t\"path/filepath\"\r\n\t\"strconv\"\r\n\t\"strings\"\r\n\t\"sync\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/gorman\"\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/objs\"\r\n\t_ \"github.com/mattn/go-sqlite3\"\r\n)\r\n\r\nfunc getComment(gm *gorman.GoroutineManager, ctx context.Context, sig <-chan struct{}, isReplay bool, continuation, name string) (done bool) {\r\n\r\n\tdbName := files.ChangeExtention(name, \"yt.sqlite3\")\r\n\tdb, err := dbOpen(ctx, dbName)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tmtx := &sync.Mutex{}\r\n\r\n\ttestContinuation, count, _ := dbGetContinuation(ctx, db, mtx)\r\n\tif testContinuation != \"\" {\r\n\t\tcontinuation = testContinuation\r\n\t}\r\n\r\n\tvar printTime int64\r\n\r\nMAINLOOP:\r\n\tfor {\r\n\t\tselect {\r\n\t\tcase <-ctx.Done():\r\n\t\t\tbreak MAINLOOP\r\n\t\tcase <-sig:\r\n\t\t\tbreak MAINLOOP\r\n\t\tdefault:\r\n\t\t}\r\n\t\ttimeoutMs, _done, err, neterr := func() (timeoutMs int, _done bool, err, neterr error) {\r\n\t\t\tvar uri string\r\n\t\t\tif isReplay {\r\n\t\t\t\turi = fmt.Sprintf(\"https://www.youtube.com/live_chat_replay?continuation=%s&pbj=1\", continuation)\r\n\t\t\t} else {\r\n\t\t\t\turi = fmt.Sprintf(\"https://www.youtube.com/live_chat/get_live_chat?continuation=%s&pbj=1\", continuation)\r\n\t\t\t}\r\n\r\n\t\t\tcode, buff, err, neterr := httpbase.GetBytes(uri, map[string]string{\r\n\t\t\t\t\"Cookie\":     Cookie,\r\n\t\t\t\t\"User-Agent\": UserAgent,\r\n\t\t\t})\r\n\t\t\tif err != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif neterr != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif code != 200 {\r\n\t\t\t\tneterr = fmt.Errorf(\"Status code: %v\\n\", code)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tvar data interface{}\r\n\t\t\terr = json.Unmarshal(buff, &data)\r\n\t\t\tif err != nil {\r\n\t\t\t\terr = fmt.Errorf(\"json decode error\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tliveChatContinuation, ok := objs.Find(data, \"response\", \"continuationContents\", \"liveChatContinuation\")\r\n\t\t\tif !ok {\r\n\t\t\t\terr = fmt.Errorf(\"(response liveChatContinuation) not found\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tif actions, ok := objs.FindArray(liveChatContinuation, \"actions\"); ok {\r\n\t\t\t\tvar videoOffsetTimeMsec string\r\n\r\n\t\t\t\tfor _, a := range actions {\r\n\t\t\t\t\tvar item interface{}\r\n\t\t\t\t\tvar ok bool\r\n\t\t\t\t\titem, ok = objs.Find(a, \"addChatItemAction\", \"item\")\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\titem, ok = objs.Find(a, \"addLiveChatTickerItemAction\", \"item\")\r\n\t\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\t\titem, ok = objs.Find(a, \"replayChatItemAction\", \"actions\", \"addChatItemAction\", \"item\")\r\n\t\t\t\t\t\t\tif ok {\r\n\t\t\t\t\t\t\t\tvideoOffsetTimeMsec, _ = objs.FindString(a, \"replayChatItemAction\", \"videoOffsetTimeMsec\")\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\t//objs.PrintAsJson(a)\r\n\t\t\t\t\t\t//fmt.Println(\"(actions item) not found\")\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar liveChatMessageRenderer interface{}\r\n\t\t\t\t\tliveChatMessageRenderer, ok = objs.Find(item, \"liveChatTextMessageRenderer\")\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\tliveChatMessageRenderer, ok = objs.Find(item, \"liveChatPaidMessageRenderer\")\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tauthorExternalChannelId, _ := objs.FindString(liveChatMessageRenderer, \"authorExternalChannelId\")\r\n\t\t\t\t\tauthorName, _ := objs.FindString(liveChatMessageRenderer, \"authorName\", \"simpleText\")\r\n\t\t\t\t\tid, ok := objs.FindString(liveChatMessageRenderer, \"id\")\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\t\t\t\t\tmessage, _ := objs.FindString(liveChatMessageRenderer, \"message\", \"simpleText\")\r\n\t\t\t\t\ttimestampUsec, ok := objs.FindString(liveChatMessageRenderer, \"timestampUsec\")\r\n\t\t\t\t\tif !ok {\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif false {\r\n\t\t\t\t\t\tfmt.Printf(\"%v \", videoOffsetTimeMsec)\r\n\t\t\t\t\t\tfmt.Printf(\"%v %v %v %v %v\\n\", timestampUsec, authorName, authorExternalChannelId, message, id)\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tdbInsert(ctx, gm, db, mtx,\r\n\t\t\t\t\t\tid,\r\n\t\t\t\t\t\ttimestampUsec,\r\n\t\t\t\t\t\tvideoOffsetTimeMsec,\r\n\t\t\t\t\t\tauthorName,\r\n\t\t\t\t\t\tauthorExternalChannelId,\r\n\t\t\t\t\t\tmessage,\r\n\t\t\t\t\t\tcontinuation,\r\n\t\t\t\t\t\tcount,\r\n\t\t\t\t\t)\r\n\t\t\t\t\tcount++\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// アーカイブ時、20秒毎に進捗を表示\r\n\t\t\t\tif videoOffsetTimeMsec != \"\" {\r\n\t\t\t\t\tnow := time.Now().Unix()\r\n\t\t\t\t\tif now-printTime > 20 {\r\n\t\t\t\t\t\tprintTime = now\r\n\t\t\t\t\t\tif msec, e := strconv.ParseInt(videoOffsetTimeMsec, 10, 64); e == nil {\r\n\t\t\t\t\t\t\ttotal := msec / 1000\r\n\t\t\t\t\t\t\thour := total / 3600\r\n\t\t\t\t\t\t\tmin := (total % 3600) / 60\r\n\t\t\t\t\t\t\tsec := (total % 3600) % 60\r\n\t\t\t\t\t\t\tfmt.Printf(\"comment pos: %02d:%02d:%02d\\n\", hour, min, sec)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t//fmt.Println(\"------------\")\r\n\t\t\t}\r\n\r\n\t\t\tif continuations, ok := objs.Find(liveChatContinuation, \"continuations\"); ok {\r\n\t\t\t\t//objs.PrintAsJson(continuations)\r\n\r\n\t\t\t\tif c, ok := objs.FindString(continuations, \"timedContinuationData\", \"continuation\"); ok {\r\n\t\t\t\t\tcontinuation = c\r\n\t\t\t\t} else if c, ok := objs.FindString(continuations, \"liveChatReplayContinuationData\", \"continuation\"); ok {\r\n\t\t\t\t\tcontinuation = c\r\n\t\t\t\t} else if c, ok := objs.FindString(continuations, \"invalidationContinuationData\", \"continuation\"); ok {\r\n\t\t\t\t\tcontinuation = c\r\n\t\t\t\t} else if c, ok := objs.FindString(continuations, \"playerSeekContinuationData\", \"continuation\"); ok {\r\n\t\t\t\t\tif isReplay {\r\n\t\t\t\t\t\t_done = true\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcontinuation = c\r\n\t\t\t\t} else {\r\n\t\t\t\t\tobjs.PrintAsJson(continuations)\r\n\t\t\t\t\terr = fmt.Errorf(\"(liveChatContinuation continuation) not found\")\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif t, ok := objs.FindString(continuations, \"timedContinuationData\", \"timeoutMs\"); ok {\r\n\t\t\t\t\ttimeout, err := strconv.Atoi(t)\r\n\t\t\t\t\tif err != nil {\r\n\t\t\t\t\t\ttimeoutMs = timeout\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if t, ok := objs.FindString(continuations, \"invalidationContinuationData\", \"continuation\"); ok {\r\n\t\t\t\t\ttimeout, err := strconv.Atoi(t)\r\n\t\t\t\t\tif err != nil {\r\n\t\t\t\t\t\ttimeoutMs = timeout\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t} else {\r\n\t\t\t\tobjs.PrintAsJson(liveChatContinuation)\r\n\t\t\t\terr = fmt.Errorf(\"(liveChatContinuation>continuations) not found\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\treturn\r\n\t\t}()\r\n\t\tif err != nil {\r\n\t\t\tfmt.Println(err)\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tif neterr != nil {\r\n\t\t\tfmt.Println(neterr)\r\n\t\t\tbreak\r\n\t\t}\r\n\t\tif _done {\r\n\t\t\tdone = true\r\n\t\t\tbreak MAINLOOP\r\n\t\t}\r\n\r\n\t\tif timeoutMs < 1000 {\r\n\t\t\tif isReplay {\r\n\t\t\t\ttimeoutMs = 1000\r\n\t\t\t} else {\r\n\t\t\t\ttimeoutMs = 6000\r\n\t\t\t}\r\n\t\t}\r\n\t\ttime.Sleep(time.Duration(timeoutMs) * time.Millisecond)\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc dbOpen(ctx context.Context, name string) (db *sql.DB, err error) {\r\n\tdb, err = sql.Open(\"sqlite3\", name)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.ExecContext(ctx, `\r\n\t\tPRAGMA synchronous = OFF;\r\n\t\tPRAGMA journal_mode = WAL;\r\n\t`)\r\n\tif err != nil {\r\n\t\tdb.Close()\r\n\t\treturn\r\n\t}\r\n\r\n\terr = dbCreate(ctx, db)\r\n\tif err != nil {\r\n\t\tdb.Close()\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc dbCreate(ctx context.Context, db *sql.DB) (err error) {\r\n\t// table media\r\n\r\n\t_, err = db.ExecContext(ctx, `\r\n\tCREATE TABLE IF NOT EXISTS comment (\r\n\t\tid                  TEXT PRIMARY KEY NOT NULL UNIQUE,\r\n\t\ttimestampUsec       INTEGER NOT NULL,\r\n\t\tvideoOffsetTimeMsec INTEGER,\r\n\t\tauthorName          TEXT,\r\n\t\tchannelId           TEXT,\r\n\t\tmessage             TEXT,\r\n\t\tcontinuation        TEXT,\r\n\t\tcount               INTEGER NOT NULL\r\n\t)\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\t_, err = db.ExecContext(ctx, `\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS comment0 ON comment(id);\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS comment1 ON comment(timestampUsec);\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS comment2 ON comment(videoOffsetTimeMsec);\r\n\tCREATE UNIQUE INDEX IF NOT EXISTS comment3 ON comment(count);\r\n\t`)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\treturn\r\n}\r\n\r\nfunc dbInsert(ctx context.Context, gm *gorman.GoroutineManager, db *sql.DB, mtx *sync.Mutex,\r\n\tid, timestampUsec, videoOffsetTimeMsec, authorName, authorExternalChannelId, message, continuation string, count int) {\r\n\r\n\tusec, err := strconv.ParseInt(timestampUsec, 10, 64)\r\n\tif err != nil {\r\n\t\tfmt.Printf(\"ParseInt error: %s\\n\", timestampUsec)\r\n\t\treturn\r\n\t}\r\n\tvar offset interface{}\r\n\tif videoOffsetTimeMsec == \"\" {\r\n\t\toffset = nil\r\n\t} else {\r\n\t\tn, err := strconv.ParseInt(videoOffsetTimeMsec, 10, 64)\r\n\t\tif err != nil {\r\n\t\t\toffset = nil\r\n\t\t} else {\r\n\t\t\toffset = n\r\n\t\t}\r\n\t}\r\n\r\n\tquery := `INSERT OR IGNORE INTO comment\r\n\t\t(id, timestampUsec, videoOffsetTimeMsec, authorName, channelId, message, continuation, count) VALUES (?,?,?,?,?,?,?,?)`\r\n\r\n\tgm.Go(func(<-chan struct{}) int {\r\n\t\tmtx.Lock()\r\n\t\tdefer mtx.Unlock()\r\n\r\n\t\tif _, err := db.ExecContext(ctx, query,\r\n\t\t\tid, usec, offset, authorName, authorExternalChannelId, message, continuation, count,\r\n\t\t); err != nil {\r\n\t\t\tif err.Error() != \"context canceled\" {\r\n\t\t\t\tfmt.Println(err)\r\n\t\t\t}\r\n\t\t\treturn 1\r\n\t\t}\r\n\t\treturn 0\r\n\t})\r\n\r\n\treturn\r\n}\r\n\r\nfunc dbGetContinuation(ctx context.Context, db *sql.DB, mtx *sync.Mutex) (res string, cnt int, err error) {\r\n\tmtx.Lock()\r\n\tdefer mtx.Unlock()\r\n\r\n\terr = db.QueryRowContext(ctx, \"SELECT continuation, count FROM comment ORDER BY count DESC LIMIT 1\").Scan(&res, &cnt)\r\n\treturn\r\n}\r\n\r\nvar SelComment = `SELECT\r\n\ttimestampUsec,\r\n\tIFNULL(videoOffsetTimeMsec, -1),\r\n\tauthorName,\r\n\tchannelId,\r\n\tmessage\r\n\tFROM comment\r\n\tORDER BY timestampUsec\r\n`\r\n\r\nfunc WriteComment(db *sql.DB, fileName string) {\r\n\r\n\trows, err := db.Query(SelComment)\r\n\tif err != nil {\r\n\t\tlog.Println(err)\r\n\t\treturn\r\n\t}\r\n\tdefer rows.Close()\r\n\r\n\tfileName = files.ChangeExtention(fileName, \"xml\")\r\n\r\n\tdir := filepath.Dir(fileName)\r\n\tbase := filepath.Base(fileName)\r\n\tbase, err = files.GetFileNameNext(base)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n\tfileName = filepath.Join(dir, base)\r\n\tf, err := os.Create(fileName)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\tdefer f.Close()\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `<?xml version=\"1.0\" encoding=\"UTF-8\"?>`)\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `<packet>`)\r\n\r\n\tfirstOffsetUsec := int64(-1)\r\n\r\n\tfor rows.Next() {\r\n\t\tvar timestampUsec int64\r\n\t\tvar videoOffsetTimeMsec int64\r\n\t\tvar authorName string\r\n\t\tvar channelId string\r\n\t\tvar message string\r\n\r\n\t\terr = rows.Scan(\r\n\t\t\t&timestampUsec,\r\n\t\t\t&videoOffsetTimeMsec,\r\n\t\t\t&authorName,\r\n\t\t\t&channelId,\r\n\t\t\t&message,\r\n\t\t)\r\n\t\tif err != nil {\r\n\t\t\tlog.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tvar vpos int64\r\n\t\tif videoOffsetTimeMsec >= 0 {\r\n\t\t\tvpos = videoOffsetTimeMsec / 10\r\n\t\t} else {\r\n\t\t\tif firstOffsetUsec < 0 {\r\n\t\t\t\tfirstOffsetUsec = timestampUsec\r\n\t\t\t}\r\n\t\t\tdiff := timestampUsec - firstOffsetUsec\r\n\t\t\tvpos = diff / (10 * 1000)\r\n\t\t}\r\n\r\n\t\tline := fmt.Sprintf(\r\n\t\t\t`<chat vpos=\"%d\" date=\"%d\" date_usec=\"%d\" user_id=\"%s\"`,\r\n\t\t\tvpos,\r\n\t\t\t(timestampUsec / (1000 * 1000)),\r\n\t\t\t(timestampUsec % (1000 * 1000)),\r\n\t\t\tchannelId,\r\n\t\t)\r\n\r\n\t\tline += \">\"\r\n\t\tmessage = strings.Replace(message, \"&\", \"&amp;\", -1)\r\n\t\tmessage = strings.Replace(message, \"<\", \"&lt;\", -1)\r\n\t\tline += message\r\n\t\tline += \"</chat>\"\r\n\t\tfmt.Fprintf(f, \"%s\\r\\n\", line)\r\n\t}\r\n\tfmt.Fprintf(f, \"%s\\r\\n\", `</packet>`)\r\n}\r\n"
  },
  {
    "path": "src/youtube/youtube.go",
    "content": "package youtube\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t//\t\"net/http\"\r\n\t//\t\"io/ioutil\"\r\n\t\"bufio\"\r\n\t\"context\"\r\n\t\"encoding/json\"\r\n\t\"html\"\r\n\t\"os\"\r\n\t\"os/signal\"\r\n\t\"regexp\"\r\n\t\"strings\"\r\n\t\"sync\"\r\n\t\"syscall\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/gorman\"\r\n\t\"github.com/himananiito/livedl/httpbase\"\r\n\t\"github.com/himananiito/livedl/objs\"\r\n\t\"github.com/himananiito/livedl/procs\"\r\n\t\"github.com/himananiito/livedl/procs/streamlink\"\r\n\t\"github.com/himananiito/livedl/procs/youtube_dl\"\r\n)\r\n\r\nvar Cookie = \"PREF=f1=50000000&f4=4000000&hl=en\"\r\nvar UserAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\"\r\n\r\nvar split = func(data []byte, atEOF bool) (advance int, token []byte, err error) {\r\n\tfor i := 0; i < len(data); i++ {\r\n\t\tif data[i] == '\\n' {\r\n\t\t\treturn i + 1, data[:i+1], nil\r\n\t\t}\r\n\t\tif data[i] == '\\r' {\r\n\t\t\tif (i + 1) == len(data) {\r\n\t\t\t\treturn 0, nil, nil\r\n\t\t\t}\r\n\t\t\tif data[i+1] == '\\n' {\r\n\t\t\t\treturn i + 2, data[:i+2], nil\r\n\t\t\t}\r\n\t\t\treturn i + 1, data[:i+1], nil\r\n\t\t}\r\n\t}\r\n\r\n\tif atEOF && len(data) > 0 {\r\n\t\treturn len(data), data, nil\r\n\t}\r\n\r\n\treturn 0, nil, nil\r\n}\r\n\r\nfunc getChatContinuation(buff []byte) (isReplay bool, continuation string, err error) {\r\n\r\n\tif ma := regexp.MustCompile(`(?s)\\Wwindow\\[\"ytInitialData\"\\]\\p{Zs}*=\\p{Zs}*({.*?})\\p{Zs}*;`).FindSubmatch(buff); len(ma) > 1 {\r\n\t\tvar data interface{}\r\n\t\terr = json.Unmarshal(ma[1], &data)\r\n\t\tif err != nil {\r\n\t\t\terr = fmt.Errorf(\"ytInitialData parse error\")\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t//objs.PrintAsJson(data);\r\n\r\n\t\tliveChatRenderer, ok := objs.Find(data,\r\n\t\t\t\"contents\",\r\n\t\t\t\"twoColumnWatchNextResults\",\r\n\t\t\t\"conversationBar\",\r\n\t\t\t\"liveChatRenderer\",\r\n\t\t)\r\n\t\tif !ok {\r\n\t\t\terr = fmt.Errorf(\"liveChatRenderer not found\")\r\n\t\t\treturn\r\n\t\t}\r\n\t\tisReplay, _ = objs.FindBool(liveChatRenderer, \"isReplay\")\r\n\r\n\t\tsubMenuItems, ok := objs.FindArray(liveChatRenderer,\r\n\t\t\t\"header\",\r\n\t\t\t\"liveChatHeaderRenderer\",\r\n\t\t\t\"viewSelector\",\r\n\t\t\t\"sortFilterSubMenuRenderer\",\r\n\t\t\t\"subMenuItems\",\r\n\t\t)\r\n\t\tif !ok {\r\n\t\t\terr = fmt.Errorf(\"subMenuItems not found\")\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tfor _, item := range subMenuItems {\r\n\t\t\ttitle, _ := objs.FindString(item, \"title\")\r\n\t\t\t//selected, _ := objs.FindBool(item, \"selected\")\r\n\t\t\tc, _ := objs.FindString(item, \"continuation\", \"reloadContinuationData\", \"continuation\")\r\n\r\n\t\t\tif (title != \"\") && (!strings.Contains(title, \"Top\")) {\r\n\t\t\t\tcontinuation = c\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tcontinuation = c\r\n\t\t}\r\n\r\n\t} else {\r\n\t\terr = fmt.Errorf(\"ytInitialData not found\")\r\n\t\treturn\r\n\t}\r\n\r\n\tif continuation == \"\" {\r\n\t\terr = fmt.Errorf(\"continuation not found\")\r\n\t}\r\n\treturn\r\n}\r\n\r\nfunc getInfo(buff []byte) (title, ucid, author string, err error) {\r\n\tvar data interface{}\r\n\tre := regexp.MustCompile(`(?s)\\Wytplayer\\.config\\p{Zs}*=\\p{Zs}*({.*?})\\p{Zs}*;`)\r\n\tif ma := re.FindSubmatch(buff); len(ma) > 1 {\r\n\t\tstr := html.UnescapeString(string(ma[1]))\r\n\t\tif err = json.Unmarshal([]byte(str), &data); err != nil {\r\n\t\t\terr = fmt.Errorf(\"ytplayer parse error\")\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\terr = fmt.Errorf(\"ytplayer.config not found\")\r\n\t\treturn\r\n\t}\r\n\r\n\t//objs.PrintAsJson(data); return\r\n\r\n\ttitle, ok := objs.FindString(data, \"args\", \"title\")\r\n\tif !ok {\r\n\t\terr = fmt.Errorf(\"title not found\")\r\n\t\treturn\r\n\t}\r\n\tucid, _ = objs.FindString(data, \"args\", \"ucid\")\r\n\tauthor, _ = objs.FindString(data, \"args\", \"author\")\r\n\treturn\r\n}\r\n\r\nfunc execStreamlink(gm *gorman.GoroutineManager, uri, name string) (notSupport bool, err error) {\r\n\tcmd, stdout, stderr, err := streamlink.Open(uri, \"best\", \"--retry-max\", \"10\", \"-o\", name)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer stdout.Close()\r\n\tdefer stderr.Close()\r\n\r\n\tchStdout := make(chan string, 10)\r\n\tchStderr := make(chan string, 10)\r\n\tchEof := make(chan struct{}, 2)\r\n\r\n\t// stdout\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tdefer func() {\r\n\t\t\tchEof <- struct{}{}\r\n\t\t}()\r\n\t\tscanner := bufio.NewScanner(stdout)\r\n\t\tscanner.Split(split)\r\n\r\n\t\tfor scanner.Scan() {\r\n\t\t\tchStdout <- scanner.Text()\r\n\t\t}\r\n\r\n\t\treturn 0\r\n\t})\r\n\r\n\t// stderr\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tdefer func() {\r\n\t\t\tchEof <- struct{}{}\r\n\t\t}()\r\n\t\tscanner := bufio.NewScanner(stderr)\r\n\t\tscanner.Split(split)\r\n\r\n\t\tfor scanner.Scan() {\r\n\t\t\tchStderr <- scanner.Text()\r\n\t\t}\r\n\r\n\t\treturn 0\r\n\t})\r\n\r\n\t// outputs\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tfor {\r\n\t\t\tvar s string\r\n\t\t\tselect {\r\n\t\t\tcase s = <-chStdout:\r\n\t\t\tcase s = <-chStderr:\r\n\t\t\tcase <-chEof:\r\n\t\t\t\treturn 0\r\n\t\t\t}\r\n\r\n\t\t\tif strings.HasPrefix(s, \"[cli][error]\") {\r\n\t\t\t\tfmt.Print(s)\r\n\r\n\t\t\t\tnotSupport = true\r\n\t\t\t\tprocs.Kill(cmd.Process.Pid)\r\n\t\t\t\tbreak\r\n\t\t\t} else if strings.HasPrefix(s, \"Traceback (most recent call last):\") {\r\n\t\t\t\tfmt.Print(s)\r\n\r\n\t\t\t\tnotSupport = true\r\n\t\t\t\t//procs.Kill(cmd.Process.Pid)\r\n\t\t\t\t//break\r\n\t\t\t} else {\r\n\t\t\t\tfmt.Print(s)\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn 0\r\n\t})\r\n\r\n\tcmd.Wait()\r\n\r\n\treturn\r\n}\r\n\r\nfunc execYoutube_dl(gm *gorman.GoroutineManager, uri, name string) (err error) {\r\n\tdefer func() {\r\n\t\tpart := name + \".part\"\r\n\t\tif _, test := os.Stat(part); test == nil {\r\n\t\t\tif _, test := os.Stat(name); test != nil {\r\n\t\t\t\tos.Rename(part, name)\r\n\t\t\t}\r\n\t\t}\r\n\t}()\r\n\r\n\tcmd, stdout, stderr, err := youtube_dl.Open(\"--no-mtime\", \"--no-color\", \"-o\", name, uri)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer stdout.Close()\r\n\tdefer stderr.Close()\r\n\r\n\tchStdout := make(chan string, 10)\r\n\tchStderr := make(chan string, 10)\r\n\tchEof := make(chan struct{}, 2)\r\n\r\n\t// stdout\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tdefer func() {\r\n\t\t\tchEof <- struct{}{}\r\n\t\t}()\r\n\t\tscanner := bufio.NewScanner(stdout)\r\n\t\tscanner.Split(split)\r\n\r\n\t\tfor scanner.Scan() {\r\n\t\t\tchStdout <- scanner.Text()\r\n\t\t}\r\n\r\n\t\treturn 0\r\n\t})\r\n\r\n\t// stderr\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tdefer func() {\r\n\t\t\tchEof <- struct{}{}\r\n\t\t}()\r\n\t\tscanner := bufio.NewScanner(stderr)\r\n\t\tscanner.Split(split)\r\n\r\n\t\tfor scanner.Scan() {\r\n\t\t\tchStderr <- scanner.Text()\r\n\t\t}\r\n\r\n\t\treturn 0\r\n\t})\r\n\r\n\t// outputs\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tvar old int64\r\n\t\tfor {\r\n\t\t\tvar s string\r\n\t\t\tselect {\r\n\t\t\tcase s = <-chStdout:\r\n\t\t\tcase s = <-chStderr:\r\n\t\t\tcase <-chEof:\r\n\t\t\t\treturn 0\r\n\t\t\t}\r\n\r\n\t\t\tif strings.HasPrefix(s, \"[https @ \") {\r\n\t\t\t\t// ffmpeg unwanted logs\r\n\t\t\t} else {\r\n\t\t\t\tif strings.HasPrefix(s, \"[download]\") {\r\n\t\t\t\t\tvar now = time.Now().UnixNano()\r\n\t\t\t\t\tif now-old > 2*1000*1000*1000 {\r\n\t\t\t\t\t\told = now\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tfmt.Print(s)\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn 0\r\n\t})\r\n\r\n\tcmd.Wait()\r\n\treturn\r\n}\r\n\r\nvar COMMENT_DONE = 1000\r\n\r\nfunc Record(id string, ytNoStreamlink, ytNoYoutube_dl bool) (err error) {\r\n\r\n\turi := fmt.Sprintf(\"https://www.youtube.com/watch?v=%s\", id)\r\n\tcode, buff, err, neterr := httpbase.GetBytes(uri, map[string]string{\r\n\t\t\"Cookie\":     Cookie,\r\n\t\t\"User-Agent\": UserAgent,\r\n\t})\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tif neterr != nil {\r\n\t\treturn\r\n\t}\r\n\tif code != 200 {\r\n\t\tneterr = fmt.Errorf(\"Status code: %v\\n\", code)\r\n\t\treturn\r\n\t}\r\n\r\n\ttitle, ucid, author, err := getInfo(buff)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tif false {\r\n\t\tfmt.Println(ucid)\r\n\t}\r\n\r\n\tisReplay, continuation, err := getChatContinuation(buff)\r\n\r\n\torigName := fmt.Sprintf(\"%s-%s_%s.mp4\", author, title, id)\r\n\torigName = files.ReplaceForbidden(origName)\r\n\tname, err := files.GetFileNameNext(origName)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\r\n\tfmt.Println(name)\r\n\r\n\tmtxComDone := &sync.Mutex{}\r\n\tvar commentDone bool\r\n\r\n\tvar gm *gorman.GoroutineManager\r\n\tvar gmCom *gorman.GoroutineManager\r\n\r\n\tgm = gorman.WithChecker(func(c int) {\r\n\t\tswitch c {\r\n\t\tcase 0:\r\n\t\tdefault:\r\n\t\t\tgm.Cancel()\r\n\t\t\tif gmCom != nil {\r\n\t\t\t\tgmCom.Cancel()\r\n\t\t\t}\r\n\t\t}\r\n\t})\r\n\r\n\tgmCom = gorman.WithChecker(func(c int) {\r\n\t\tswitch c {\r\n\t\tcase 0:\r\n\t\tcase COMMENT_DONE:\r\n\t\t\tfunc() {\r\n\t\t\t\tmtxComDone.Lock()\r\n\t\t\t\tdefer mtxComDone.Unlock()\r\n\t\t\t\tcommentDone = true\r\n\t\t\t}()\r\n\t\tdefault:\r\n\t\t\tgmCom.Cancel()\r\n\t\t}\r\n\t})\r\n\r\n\tchInterrupt := make(chan os.Signal, 10)\r\n\tsignal.Notify(chInterrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)\r\n\tdefer signal.Stop(chInterrupt)\r\n\r\n\tctx, cancel := context.WithCancel(context.Background())\r\n\r\n\tvar interrupt bool\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tselect {\r\n\t\tcase <-chInterrupt:\r\n\t\t\tinterrupt = true\r\n\t\tcase <-c:\r\n\t\t}\r\n\r\n\t\tcancel()\r\n\t\tgm.Cancel()\r\n\t\treturn 1\r\n\t})\r\n\r\n\tif continuation != \"\" {\r\n\t\tgmCom.Go(func(c <-chan struct{}) int {\r\n\t\t\tgetComment(gmCom, ctx, c, isReplay, continuation, origName)\r\n\t\t\tfmt.Printf(\"\\ncomment done\\n\")\r\n\t\t\treturn COMMENT_DONE\r\n\t\t})\r\n\t}\r\n\r\n\tgm.Go(func(c <-chan struct{}) int {\r\n\t\tselect {\r\n\t\tcase <-c:\r\n\t\t\tcancel()\r\n\t\t}\r\n\t\treturn 0\r\n\t})\r\n\r\n\tvar retry bool\r\n\tif !ytNoStreamlink {\r\n\t\tretry, err = execStreamlink(gm, uri, name)\r\n\t}\r\n\tif !interrupt {\r\n\t\tif err != nil || retry || (ytNoStreamlink && (!ytNoYoutube_dl)) {\r\n\t\t\texecYoutube_dl(gm, uri, name)\r\n\t\t}\r\n\t}\r\n\r\n\tif continuation != \"\" {\r\n\t\tif isReplay {\r\n\t\t\tif !commentDone {\r\n\t\t\t\tfmt.Printf(\"\\nwaiting comment\\n\")\r\n\t\t\t\tgmCom.Wait()\r\n\t\t\t} else {\r\n\t\t\t\tgmCom.Wait()\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\tgmCom.Cancel()\r\n\t\t\tgmCom.Wait()\r\n\t\t}\r\n\t}\r\n\r\n\tgm.Cancel()\r\n\tgm.Wait()\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/youtube/youtube.gox",
    "content": "package youtube\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"net/http\"\r\n\r\n\t\"io/ioutil\"\r\n\t\"regexp\"\r\n\t\"encoding/json\"\r\n\t\"html\"\r\n\t\"strings\"\r\n\t\"net/url\"\r\n\t\"os\"\r\n\t\"strconv\"\r\n\t\"bytes\"\r\n\t\"os/exec\"\r\n\t\"os/signal\"\r\n\t\"archive/zip\"\r\n\t\"sync\"\r\n\t\"../obj\"\r\n\t\"io\"\r\n\t\"../files\"\r\n\r\n\t\"../httpsub\"\r\n\t\"../zip2mp4\"\r\n\t\"log\"\r\n)\r\n\r\ntype YtDash struct {\r\n\tSeqNo int\r\n\tSeqNoFound bool\r\n\tSeqNoBack int\r\n\tVAddr string\r\n\tVQuery url.Values\r\n\tAAddr string\r\n\tAQuery url.Values\r\n\tTsFile *os.File\r\n\tFFCmd *exec.Cmd\r\n\tFFBuffer *bytes.Buffer\r\n\tTryBack bool\r\n\tStartBack bool\r\n\tChEnd chan bool\r\n\tChEndBack chan bool\r\n\tzipFile *os.File\r\n\tzipWriter *zip.Writer\r\n\tmZip sync.Mutex\r\n\tfileName string\r\n\tTitle string\r\n\tId string\r\n}\r\nfunc (yt *YtDash) SetFileName(fileName string) {\r\n\tyt.fileName = files.ReplaceForbidden(fileName)\r\n}\r\nfunc (yt *YtDash) fetch(isVideo, isBack bool) (fileName string, err error) {\r\n\r\n\tvar addr string\r\n\tvar query url.Values\r\n\tvar sn int\r\n\r\n\tif isVideo {\r\n\t\taddr = yt.VAddr\r\n\t\tquery = yt.VQuery\r\n\t} else {\r\n\t\taddr = yt.AAddr\r\n\t\tquery = yt.AQuery\r\n\t}\r\n\r\n\tif isBack && (! yt.SeqNoFound) {\r\n\t\terr = fmt.Errorf(\"isBack && (! SeqNoFound)\")\r\n\t\treturn\r\n\t}\r\n\r\n\tif yt.SeqNoFound {\r\n\t\t//fmt.Printf(\"SQ set to %d\\n\", yt.SeqNo)\r\n\t\tif isBack {\r\n\t\t\tsn = yt.SeqNoBack\r\n\t\t} else {\r\n\t\t\tsn = yt.SeqNo\r\n\t\t}\r\n\t\tquery.Set(\"sq\", fmt.Sprintf(\"%d\", sn))\r\n\r\n\t\t//fmt.Printf(\"%v\\n\", query)\r\n\t}\r\n\r\n\turi := fmt.Sprintf(\"%s?%s\", addr, query.Encode())\r\n\treq, _ := http.NewRequest(\"GET\", uri, nil)\r\n\r\n\tclient := new(http.Client)\r\n\tresp, err := client.Do(req)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\r\n\tswitch resp.StatusCode {\r\n\tcase 200:\r\n\tdefault:\r\n\t\terr = fmt.Errorf(\"StatusCode is %v\\n%v\\n%v\", resp.StatusCode, uri, query)\r\n\t\treturn\r\n\t}\r\n\r\n\tswitch query.Get(\"source\") {\r\n\tcase \"yt_live_broadcast\":\r\n\r\n\t\tbs, e := ioutil.ReadAll(resp.Body)\r\n\t\tif e != nil {\r\n\t\t\terr = e\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif (! yt.SeqNoFound) && (! isBack) {\r\n\r\n\t\t\tif ma := regexp.MustCompile(`Sequence-Number\\s*:\\s*(\\d+)`).FindSubmatch(bs); len(ma) > 0 {\r\n\t\t\t\tsn, err = strconv.Atoi(string(ma[1]))\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\terr = fmt.Errorf(\"Sequence-Number Not a Number: %v\", ma)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tyt.SeqNo = sn\r\n\t\t\t\tyt.SeqNoBack = sn - 1\r\n\t\t\t\tyt.SeqNoFound = true\r\n\t\t\t\tfmt.Printf(\"start SeqNo: %d\\n\", sn)\r\n\r\n\t\t\t} else {\r\n\t\t\t\terr = fmt.Errorf(\"Sequence-Number Not found\")\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tyt.RecordBack()\r\n\t\t}\r\n\r\n\t\tif isVideo {\r\n\t\t\tfileName = fmt.Sprintf(\"video-%d.mp4\", sn)\r\n\t\t} else {\r\n\t\t\tfileName = fmt.Sprintf(\"audio-%d.mp4\", sn)\r\n\t\t}\r\n\r\n\t\tbuff := bytes.NewBuffer(bs)\r\n\t\tif err = yt.WriteZip(fileName, buff); err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc (yt *YtDash) fetchVideo() (string, error) {\r\n\treturn yt.fetch(true, false)\r\n}\r\nfunc (yt *YtDash) fetchAudio() (string, error) {\r\n\treturn yt.fetch(false, false)\r\n}\r\nfunc (yt *YtDash) IncrSeqNo() {\r\n\tyt.SeqNo++\r\n}\r\nfunc (yt *YtDash) fetchVideoBack() (string, error) {\r\n\treturn yt.fetch(true, true)\r\n}\r\nfunc (yt *YtDash) fetchAudioBack() (string, error) {\r\n\treturn yt.fetch(false, true)\r\n}\r\nfunc (yt *YtDash) DecrSeqNoBack() {\r\n\tyt.SeqNoBack--\r\n}\r\nfunc (yt *YtDash) RecordYoutube() {\r\n\tvar vname string\r\n\tvar aname string\r\n\tfunc() {\r\n\t\turi := fmt.Sprintf(\"%s?%s\", yt.VAddr, yt.VQuery.Encode())\r\n\t\tvname = fmt.Sprintf(\"%s(%s)-v.mp4\", yt.Title, yt.Id)\r\nfmt.Println(uri)\r\n\t\tsub := httpsub.Get(uri, vname)\r\n\t\tsub.Concurrent(4)\r\n\t\tsub.Wait()\r\n\t}()\r\n\tfunc() {\r\n\t\turi := fmt.Sprintf(\"%s?%s\", yt.AAddr, yt.AQuery.Encode())\r\n\t\taname = fmt.Sprintf(\"%s(%s)-a.mp4\", yt.Title, yt.Id)\r\n\t\tsub := httpsub.Get(uri, aname)\r\n\t\tsub.Concurrent(4)\r\n\t\tsub.Wait()\r\n\t}()\r\n\tif zip2mp4.FFmpegExists() {\r\n\t\texts := []string{\"mp4\", \"mkv\"}\r\n\t\tfor _, ext := range exts {\r\n\t\t\toname := fmt.Sprintf(\"%s(%s).%s\", yt.Title, yt.Id, ext)\r\n\t\t\tif zip2mp4.MergeVA(vname, aname, oname) {\r\n\t\t\t\tos.Remove(vname)\r\n\t\t\t\tos.Remove(aname)\r\n\t\t\t\treturn\r\n\t\t\t} else {\r\n\t\t\t\tos.Remove(oname)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// ffmpeg Not exists OR merge NG\r\n\tfv, e := os.Open(vname)\r\n\tif e != nil {\r\n\t\tlog.Fatalln(e)\r\n\t}\r\n\tyt.WriteZip(\"video.mp4\", fv)\r\n\tfv.Close()\r\n\r\n\tfa, e := os.Open(aname)\r\n\tif e != nil {\r\n\t\tlog.Fatalln(e)\r\n\t}\r\n\tyt.WriteZip(\"audio.mp4\", fa)\r\n\tfa.Close()\r\n\r\n\tos.Remove(vname)\r\n\tos.Remove(aname)\r\n\r\n}\r\nfunc (yt *YtDash) Wait() {\r\n\r\n\tyt.ChEnd = make(chan bool)\r\n\tyt.ChEndBack = make(chan bool)\r\n\r\n\tswitch yt.VQuery.Get(\"source\") {\r\n\tcase \"youtube\":\r\n\t\tyt.RecordYoutube()\r\n\r\n\tcase \"yt_live_broadcast\":\r\n\t\tyt.RecordForward()\r\n\t\t<-yt.ChEnd\r\n\t\tif yt.StartBack {\r\n\t\t\t<-yt.ChEndBack\r\n\t\t}\r\n\t}\r\n}\r\nfunc (yt *YtDash) Close() {\r\n\tif yt.zipWriter != nil {\r\n\t\tyt.zipWriter.Close()\r\n\t}\r\n\tif yt.zipFile != nil {\r\n\t\tyt.zipFile.Close()\r\n\t}\r\n}\r\nfunc (yt *YtDash) OpenFile() (err error) {\r\n\r\n\tfileName, err := files.GetFileNameNext(yt.fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tfile, err := os.Create(fileName)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\tyt.zipFile = file\r\n\r\n\tyt.zipWriter = zip.NewWriter(file)\r\n\r\n\tchSig := make(chan os.Signal, 1)\r\n\tsignal.Notify(chSig, os.Interrupt)\r\n\tgo func() {\r\n\t\t<-chSig\r\n\t\tyt.mZip.Lock()\r\n\t\tdefer yt.mZip.Unlock()\r\n\t\tif yt.zipWriter != nil {\r\n\t\t\tyt.zipWriter.Close()\r\n\t\t}\r\n\t\tos.Exit(0)\r\n\t}()\r\n\treturn\r\n}\r\n\r\nfunc (yt *YtDash) WriteZip(name string, rdr io.Reader) (err error) {\r\n\tyt.mZip.Lock()\r\n\tdefer yt.mZip.Unlock()\r\n\r\n\tif yt.zipFile == nil || yt.zipWriter == nil {\r\n\t\tyt.OpenFile()\r\n\t}\r\n\r\n\twr, err := yt.zipWriter.Create(name)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tif _, err = io.Copy(wr, rdr); err != nil {\r\n\t\treturn\r\n\t}\r\n\treturn\r\n}\r\nfunc (yt *YtDash) RecordForward() {\r\n\tgo func() {\r\n\t\tdefer func() {\r\n\t\t\tclose(yt.ChEnd)\r\n\t\t}()\r\n\t\tfor {\r\n\t\t\tvfile, err := yt.fetchVideo()\r\n\t\t\tif err != nil {\r\n\t\t\t\tfmt.Printf(\"RecordForward: %v\\n\", err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tafile, err := yt.fetchAudio()\r\n\t\t\tif err != nil {\r\n\t\t\t\tfmt.Printf(\"RecordForward: %v\\n\", err)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tif true {\r\n\t\t\t\tfmt.Printf(\"%s, %s\\n\", vfile, afile)\r\n\t\t\t}\r\n\t\t\tyt.IncrSeqNo()\r\n\t\t}\r\n\t}()\r\n}\r\nfunc (yt *YtDash) RecordBack() {\r\n\tif yt.TryBack && (! yt.StartBack) {\r\n\t\tyt.StartBack = true\r\n\t\tgo func() {\r\n\t\t\tdefer func() {\r\n\t\t\t\tclose(yt.ChEndBack)\r\n\t\t\t}()\r\n\t\t\tfor yt.SeqNoBack >= 0 {\r\n\t\t\t\tvfile, err := yt.fetchVideoBack()\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\tfmt.Printf(\"RecordBack: %v\\n\", err)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tafile, err := yt.fetchAudioBack()\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\tfmt.Printf(\"RecordBack: %v\\n\", err)\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tif true {\r\n\t\t\t\t\tfmt.Printf(\"%s, %s\\n\", vfile, afile)\r\n\t\t\t\t}\r\n\t\t\t\tyt.DecrSeqNoBack()\r\n\t\t\t}\r\n\t\t}()\r\n\t}\r\n}\r\n\r\nfunc Record(id string) (err error) {\r\n\turi := fmt.Sprintf(\"https://www.youtube.com/watch?v=%s\", id)\r\n\treq, _ := http.NewRequest(\"GET\", uri, nil)\r\n\r\n\tclient := new(http.Client)\r\n\tresp, err := client.Do(req)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn\r\n\t}\r\n\tdefer resp.Body.Close()\r\n\tdat, _ := ioutil.ReadAll(resp.Body)\r\n\r\n\tvar a interface{}\r\n\r\n\tre := regexp.MustCompile(`\\Wytplayer\\.config\\p{Zs}*=\\p{Zs}*({.*?})\\p{Zs}*;`)\r\n\tif ma := re.FindSubmatch(dat); len(ma) > 0 {\r\n\t\tstr := html.UnescapeString(string(ma[1]))\r\n\t\tif err = json.Unmarshal([]byte(str), &a); err != nil {\r\n\t\t\tfmt.Println(str)\r\n\t\t\tfmt.Println(err)\r\n\t\t\treturn\r\n\t\t}\r\n\t} else {\r\n\t\tfmt.Println(\"ytplayer.config not found\")\r\n\t\treturn\r\n\t}\r\n\r\n\t// debug print\r\n\t//obj.PrintAsJson(a)\r\n\r\n\ttitle, ok := obj.FindString(a, \"args\", \"title\")\r\n\tif (! ok) {\r\n\t\tfmt.Println(\"title not found\")\r\n\t\treturn\r\n\t}\r\n\r\n\tres, ok := obj.FindString(a, \"args\", \"adaptive_fmts\")\r\n\tif (! ok) {\r\n\t\tif res, ok := obj.FindString(a, \"args\", \"hlsvp\"); ok {\r\n\t\t\tfmt.Printf(\"hls: %s\\n\", res)\r\n\t\t\treturn\r\n\t\t}\r\n\t\tobj.PrintAsJson(a)\r\n\t\treturn\r\n\t}\r\n\r\n\tvar maxVideoBr int\r\n\tvar maxAudioBr int\r\n\tvar videoUrl string\r\n\tvar audioUrl string\r\n\tvar qualityLabel string\r\n\tfor _, s := range strings.Split(res, \",\") {\r\n\t\t//fmt.Println(s)\r\n\t\tf, e := url.ParseQuery(s)\r\n\t\t//obj.PrintAsJson(f)\r\n\t\t//fmt.Println(f)\r\n\t\tif e != nil {\r\n\t\t\tfmt.Println(e)\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// type\r\n\t\t// bitrate\r\n\t\tt := f.Get(\"type\")\r\n\t\tbr, err := strconv.Atoi(f.Get(\"bitrate\"))\r\n\t\tif err != nil {\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\tif strings.HasPrefix(t, \"video\") {\r\n\t\t\tif br > maxVideoBr {\r\n\t\t\t\tmaxVideoBr = br\r\n\t\t\t\tvideoUrl = f.Get(\"url\")\r\n\t\t\t\tqualityLabel = f.Get(\"quality_label\")\r\n\t\t\t}\r\n\t\t} else if strings.HasPrefix(t, \"audio\") {\r\n\t\t\tif br > maxAudioBr {\r\n\t\t\t\tmaxAudioBr = br\r\n\t\t\t\taudioUrl = f.Get(\"url\")\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfmt.Printf(\"Quality: %s\\n\", qualityLabel)\r\n\r\n\tvarr := strings.SplitN(videoUrl, \"?\", 2)\r\n\tif len(varr) != 2 {\r\n\t\treturn\r\n\t}\r\n\taarr := strings.SplitN(audioUrl, \"?\", 2)\r\n\tif len(aarr) != 2 {\r\n\t\treturn\r\n\t}\r\n\r\n\tyt := new(YtDash)\r\n\tdefer yt.Close()\r\n\r\n\tyt.Id = id\r\n\tyt.Title = files.ReplaceForbidden(title)\r\n\r\n\tyt.SetFileName(fmt.Sprintf(\"%s(%s).zip\", title, id))\r\n\r\n\tyt.VAddr = varr[0]\r\n\tvQuery, e := url.ParseQuery(varr[1])\r\n\tif e != nil {\r\n\t\treturn\r\n\t}\r\n\tyt.VQuery = vQuery\r\n\r\n\t//obj.PrintAsJson(vQuery)\r\n\t//fmt.Println(yt.VAddr + \"?\" + vQuery.Encode())\r\n\r\n\tyt.AAddr = aarr[0]\r\n\taQuery, e := url.ParseQuery(aarr[1])\r\n\tif e != nil {\r\n\t\treturn\r\n\t}\r\n\tyt.AQuery = aQuery\r\n\r\n\tyt.TryBack = true\r\n\tyt.Wait()\r\n\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "src/zip2mp4/zip2mp4.go",
    "content": "package zip2mp4\r\n\r\nimport (\r\n\t\"archive/zip\"\r\n\t\"bytes\"\r\n\t\"database/sql\"\r\n\t\"fmt\"\r\n\t\"io\"\r\n\t\"io/ioutil\"\r\n\t\"log\"\r\n\t\"os\"\r\n\t\"os/exec\"\r\n\t\"regexp\"\r\n\t\"sort\"\r\n\t\"strconv\"\r\n\t\"time\"\r\n\r\n\t\"github.com/himananiito/livedl/files\"\r\n\t\"github.com/himananiito/livedl/log4gui\"\r\n\t\"github.com/himananiito/livedl/niconico\"\r\n\t\"github.com/himananiito/livedl/procs/ffmpeg\"\r\n\t\"github.com/himananiito/livedl/youtube\"\r\n\t_ \"github.com/mattn/go-sqlite3\"\r\n)\r\n\r\ntype ZipMp4 struct {\r\n\tZipName       string\r\n\tMp4NameOpened string\r\n\tmp4List       []string\r\n\r\n\tFFMpeg  *exec.Cmd\r\n\tFFStdin io.WriteCloser\r\n}\r\n\r\nvar cmdListFF = []string{\r\n\t\"./bin/ffmpeg/ffmpeg\",\r\n\t\"./bin/ffmpeg\",\r\n\t\"./ffmpeg/ffmpeg\",\r\n\t\"./ffmpeg\",\r\n\t\"ffmpeg\",\r\n}\r\nvar cmdListMP42TS = []string{\r\n\t\"./bin/bento4/bin/mp42ts\",\r\n\t\"./bento4/bin/mp42ts\",\r\n\t\"./bento4/mp42ts\",\r\n\t\"./bin/bento4/mp42ts\",\r\n\t\"./bin/mp42ts\",\r\n\t\"./mp42ts\",\r\n\t\"mp42ts\",\r\n}\r\n\r\n// return cmd = nil if cmd not exists\r\nfunc openProg(cmdList *[]string, stdinEn, stdoutEn, stdErrEn, consoleEn bool, args []string) (cmd *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) {\r\n\r\n\tfor i, cmdName := range *cmdList {\r\n\t\tcmd = exec.Command(cmdName, args...)\r\n\r\n\t\tvar err error\r\n\t\tif stdinEn {\r\n\t\t\tstdin, err = cmd.StdinPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatalln(err)\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif stdoutEn {\r\n\t\t\tstdout, err = cmd.StdoutPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatalln(err)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif consoleEn {\r\n\t\t\t\tcmd.Stdout = os.Stdout\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif stdErrEn {\r\n\t\t\tstderr, err = cmd.StderrPipe()\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatalln(err)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif consoleEn {\r\n\t\t\t\tcmd.Stderr = os.Stderr\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif err = cmd.Start(); err != nil {\r\n\t\t\tcontinue\r\n\t\t} else {\r\n\t\t\tif i != 0 {\r\n\t\t\t\t*cmdList = []string{cmdName}\r\n\t\t\t}\r\n\t\t\t//fmt.Printf(\"CMD: %#v\\n\", cmd.Args)\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\tcmd = nil\r\n\treturn\r\n}\r\nfunc MergeVA(vFileName, aFileName, oFileName string) bool {\r\n\tcmd, _, _, _ := openProg(&cmdListFF, false, false, false, true, []string{\r\n\t\t\"-i\", vFileName,\r\n\t\t\"-i\", aFileName,\r\n\t\t\"-c\", \"copy\",\r\n\t\t\"-y\",\r\n\t\toFileName,\r\n\t})\r\n\tif cmd == nil {\r\n\t\treturn false\r\n\t}\r\n\tif err := cmd.Wait(); err != nil {\r\n\t\tfmt.Println(err)\r\n\t\treturn false\r\n\t}\r\n\treturn true\r\n}\r\nfunc FFmpegExists() bool {\r\n\tcmd, _, _, _ := openProg(&cmdListFF, false, false, false, false, []string{\"-version\"})\r\n\tif cmd == nil {\r\n\t\treturn false\r\n\t}\r\n\tcmd.Wait()\r\n\treturn true\r\n}\r\nfunc GetFormat(fileName string) (vFormat, aFormat string) {\r\n\tcmd, _, stdout, stderr := openProg(&cmdListFF, false, true, true, false, []string{\"-i\", fileName})\r\n\tif cmd == nil {\r\n\t\treturn\r\n\t}\r\n\tb1, _ := ioutil.ReadAll(stdout)\r\n\tb2, _ := ioutil.ReadAll(stderr)\r\n\tcmd.Wait()\r\n\r\n\ts := string(b1) + string(b2)\r\n\tif ma := regexp.MustCompile(`(?i)Stream\\s+#.+?:\\s+Video:\\s+(.*?),`).FindStringSubmatch(s); len(ma) > 0 {\r\n\t\tvFormat = ma[1]\r\n\t}\r\n\tif ma := regexp.MustCompile(`(?i)Stream\\s+#.+?:\\s+Audio:\\s+(.*?),`).FindStringSubmatch(s); len(ma) > 0 {\r\n\t\taFormat = ma[1]\r\n\t}\r\n\r\n\treturn\r\n}\r\nfunc openFFMpeg(stdinEn, stdoutEn, stdErrEn, consoleEn bool, args []string) (cmd *exec.Cmd, stdin io.WriteCloser, stdout, stderr io.ReadCloser) {\r\n\treturn openProg(&cmdListFF, stdinEn, stdoutEn, stdErrEn, consoleEn, args)\r\n}\r\nfunc openMP42TS(consoleEn bool, args []string) (cmd *exec.Cmd) {\r\n\tcmd, _, _, _ = openProg(&cmdListMP42TS, false, false, false, consoleEn, args)\r\n\treturn\r\n}\r\nfunc (z *ZipMp4) Wait() {\r\n\r\n\tif z.FFStdin != nil {\r\n\t\tz.FFStdin.Close()\r\n\t}\r\n\r\n\tif z.FFMpeg != nil {\r\n\t\tif err := z.FFMpeg.Wait(); err != nil {\r\n\t\t\tlog.Fatalln(err)\r\n\t\t}\r\n\t\tz.FFMpeg = nil\r\n\t}\r\n}\r\nfunc (z *ZipMp4) CloseFFInput() {\r\n\tz.FFStdin.Close()\r\n}\r\nfunc (z *ZipMp4) OpenFFMpeg(ext string) {\r\n\t//\r\n\tz.Wait()\r\n\r\n\tif ext == \"\" {\r\n\t\text = \"mp4\"\r\n\t}\r\n\tname := files.ChangeExtention(z.ZipName, ext)\r\n\tname, err := files.GetFileNameNext(name)\r\n\tif err != nil {\r\n\t\tfmt.Println(err)\r\n\t\tos.Exit(1)\r\n\t}\r\n\tz.Mp4NameOpened = name\r\n\tz.mp4List = append(z.mp4List, name)\r\n\r\n\tcmd, stdin, err := ffmpeg.Open(\r\n\t\t\"-i\", \"-\",\r\n\t\t\"-c\", \"copy\",\r\n\t\t//\"-movflags\", \"faststart\", // test\r\n\t\t\"-y\",\r\n\t\tname,\r\n\t)\r\n\tif err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\r\n\tz.FFMpeg = cmd\r\n\tz.FFStdin = stdin\r\n}\r\n\r\nfunc (z *ZipMp4) FFInputCombFromFile(videoFile, audioFile string) {\r\n\r\n\tvTs := fmt.Sprintf(\"%s.ts\", videoFile)\r\n\tcmdV := openMP42TS(false, []string{\r\n\t\tvideoFile,\r\n\t\tvTs,\r\n\t})\r\n\tif cmdV == nil {\r\n\t\tfmt.Println(\"mp42ts not found OR command failed\")\r\n\t\tos.Exit(1)\r\n\t}\r\n\tdefer os.Remove(vTs)\r\n\r\n\taTs := fmt.Sprintf(\"%s.ts\", audioFile)\r\n\tcmdA := openMP42TS(false, []string{\r\n\t\taudioFile,\r\n\t\taTs,\r\n\t})\r\n\tif cmdA == nil {\r\n\t\tfmt.Println(\"mp42ts not found OR command failed\")\r\n\t\tos.Exit(1)\r\n\t}\r\n\tdefer os.Remove(aTs)\r\n\r\n\tif err := cmdV.Wait(); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\tif err := cmdA.Wait(); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n\r\n\tcmd, _, stdout, _ := openFFMpeg(false, true, false, false, []string{\r\n\t\t\"-i\", vTs,\r\n\t\t\"-i\", aTs,\r\n\t\t\"-c\", \"copy\",\r\n\t\t\"-f\", \"mpegts\",\r\n\t\t\"-\",\r\n\t})\r\n\tif cmd == nil {\r\n\t\tlog.Fatalln(\"ffmpeg not installed\")\r\n\t}\r\n\r\n\tz.FFInput(stdout)\r\n\r\n\tif err := cmd.Wait(); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n}\r\nfunc (z *ZipMp4) FFInput(rdr io.Reader) {\r\n\tif _, err := io.Copy(z.FFStdin, rdr); err != nil {\r\n\t\tlog.Fatalln(err)\r\n\t}\r\n}\r\n\r\ntype Index struct {\r\n\tint\r\n}\r\ntype Chunk struct {\r\n\tVideoIndex *Index\r\n\tAudioIndex *Index\r\n\tVAIndex    *Index\r\n}\r\n\r\nfunc Convert(fileName string) (err error) {\r\n\tzr, err := zip.OpenReader(fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\r\n\tchunks := make(map[int64]Chunk)\r\n\r\n\tfor i, r := range zr.File {\r\n\t\t//fmt.Printf(\"X %v %v\\n\", i, r.Name)\r\n\r\n\t\tif ma := regexp.MustCompile(`\\Avideo-(\\d+)\\.\\w+\\z`).FindStringSubmatch(r.Name); len(ma) > 0 {\r\n\t\t\tnum, err := strconv.ParseInt(string(ma[1]), 10, 64)\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatal(err)\r\n\t\t\t}\r\n\t\t\tif v, ok := chunks[num]; ok {\r\n\t\t\t\tv.VideoIndex = &Index{i}\r\n\t\t\t\tchunks[num] = v\r\n\t\t\t} else {\r\n\t\t\t\tchunks[num] = Chunk{VideoIndex: &Index{i}}\r\n\t\t\t}\r\n\r\n\t\t\t//fmt.Printf(\"V %v %v\\n\", i, r.Name)\r\n\t\t} else if ma := regexp.MustCompile(`\\Aaudio-(\\d+)\\.\\w+\\z`).FindStringSubmatch(r.Name); len(ma) > 0 {\r\n\t\t\tnum, err := strconv.ParseInt(string(ma[1]), 10, 64)\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatal(err)\r\n\t\t\t}\r\n\t\t\tif v, ok := chunks[num]; ok {\r\n\t\t\t\tv.AudioIndex = &Index{i}\r\n\t\t\t\tchunks[num] = v\r\n\t\t\t} else {\r\n\t\t\t\tchunks[num] = Chunk{AudioIndex: &Index{i}}\r\n\t\t\t}\r\n\t\t\t//fmt.Printf(\"A %v %v\\n\", num, r.Name)\r\n\t\t} else if ma := regexp.MustCompile(`\\A(\\d+)\\.\\w+\\z`).FindStringSubmatch(r.Name); len(ma) > 0 {\r\n\t\t\tnum, err := strconv.ParseInt(string(ma[1]), 10, 64)\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatal(err)\r\n\t\t\t}\r\n\t\t\tif v, ok := chunks[num]; ok {\r\n\t\t\t\tv.VAIndex = &Index{i}\r\n\t\t\t\tchunks[num] = v\r\n\t\t\t} else {\r\n\t\t\t\tchunks[num] = Chunk{VAIndex: &Index{i}}\r\n\t\t\t}\r\n\t\t\t//fmt.Printf(\"V+A %v %v\\n\", num, r.Name)\r\n\t\t} else {\r\n\t\t\tfmt.Printf(\"%v %v\\n\", i, r.Name)\r\n\t\t\tlog4gui.Info(fmt.Sprintf(\"Unsupported zip: %s\", fileName))\r\n\t\t\tos.Exit(1)\r\n\t\t}\r\n\t}\r\n\r\n\tkeys := make([]int64, 0, len(chunks))\r\n\tfor k := range chunks {\r\n\t\tkeys = append(keys, k)\r\n\t}\r\n\r\n\tsort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })\r\n\r\n\tvar tmpVideoName string\r\n\tvar tmpAudioName string\r\n\r\n\tvar zm *ZipMp4\r\n\tdefer func() {\r\n\t\tif zm != nil {\r\n\t\t\tzm.CloseFFInput()\r\n\t\t\tzm.Wait()\r\n\t\t}\r\n\t}()\r\n\r\n\tzm = &ZipMp4{ZipName: fileName}\r\n\tzm.OpenFFMpeg(\"mp4\")\r\n\r\n\tprevIndex := int64(-1)\r\n\tfor _, key := range keys {\r\n\t\tif prevIndex >= 0 {\r\n\t\t\tif key != prevIndex+1 {\r\n\t\t\t\t// [FIXME] reopen new mp4file?\r\n\t\t\t\t//return fmt.Errorf(\"\\n\\nError: seq skipped: %d --> %d\\n\\n\", prevIndex, key)\r\n\r\n\t\t\t\tfmt.Printf(\"\\nSeqNo. skipped: %d --> %d\\n\", prevIndex, key)\r\n\t\t\t\tif zm != nil {\r\n\t\t\t\t\tzm.CloseFFInput()\r\n\t\t\t\t\tzm.Wait()\r\n\t\t\t\t}\r\n\t\t\t\tzm = &ZipMp4{ZipName: fileName}\r\n\t\t\t\tzm.OpenFFMpeg(\"mp4\")\r\n\t\t\t}\r\n\t\t}\r\n\t\tprevIndex = key\r\n\r\n\t\tif chunks[key].VAIndex != nil {\r\n\t\t\tr, e := zr.File[chunks[key].VAIndex.int].Open()\r\n\t\t\tif e != nil {\r\n\t\t\t\tlog.Fatalln(e)\r\n\t\t\t}\r\n\t\t\tzm.FFInput(r)\r\n\t\t\tr.Close()\r\n\r\n\t\t} else if chunks[key].VideoIndex != nil && chunks[key].AudioIndex != nil {\r\n\r\n\t\t\tif tmpVideoName == \"\" {\r\n\t\t\t\tf, e := ioutil.TempFile(\".\", \"__temp-\")\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\tlog.Fatalln(e)\r\n\t\t\t\t}\r\n\t\t\t\tf.Close()\r\n\t\t\t\ttmpVideoName = f.Name()\r\n\t\t\t}\r\n\t\t\tif tmpAudioName == \"\" {\r\n\t\t\t\tf, e := ioutil.TempFile(\".\", \"__temp-\")\r\n\t\t\t\tif e != nil {\r\n\t\t\t\t\tlog.Fatalln(e)\r\n\t\t\t\t}\r\n\t\t\t\tf.Close()\r\n\t\t\t\ttmpAudioName = f.Name()\r\n\t\t\t}\r\n\r\n\t\t\t// open temporary file\r\n\t\t\ttmpVideo, err := os.Create(tmpVideoName)\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatalln(err)\r\n\t\t\t}\r\n\t\t\ttmpAudio, err := os.Create(tmpAudioName)\r\n\t\t\tif err != nil {\r\n\t\t\t\tlog.Fatalln(err)\r\n\t\t\t}\r\n\r\n\t\t\t// copy Video to file\r\n\t\t\trv, e := zr.File[chunks[key].VideoIndex.int].Open()\r\n\t\t\tif e != nil {\r\n\t\t\t\tlog.Fatalln(e)\r\n\t\t\t}\r\n\t\t\tif _, e := io.Copy(tmpVideo, rv); e != nil {\r\n\t\t\t\tlog.Fatalln(e)\r\n\t\t\t}\r\n\t\t\trv.Close()\r\n\t\t\ttmpVideo.Close()\r\n\r\n\t\t\t// copy Audio to file\r\n\t\t\tra, e := zr.File[chunks[key].AudioIndex.int].Open()\r\n\t\t\tif e != nil {\r\n\t\t\t\tlog.Fatalln(e)\r\n\t\t\t}\r\n\t\t\tif _, e := io.Copy(tmpAudio, ra); e != nil {\r\n\t\t\t\tlog.Fatalln(e)\r\n\t\t\t}\r\n\t\t\tra.Close()\r\n\t\t\ttmpAudio.Close()\r\n\r\n\t\t\t// combine video + audio using ffmpeg(+mp42ts)\r\n\t\t\tzm.FFInputCombFromFile(tmpVideoName, tmpAudioName)\r\n\t\t\tos.Remove(tmpVideoName)\r\n\t\t\tos.Remove(tmpAudioName)\r\n\t\t} else {\r\n\t\t\tif (chunks[key].VideoIndex == nil && chunks[key].AudioIndex != nil) ||\r\n\t\t\t\t(chunks[key].VideoIndex != nil && chunks[key].AudioIndex == nil) {\r\n\t\t\t\tfmt.Printf(\"\\nIncomplete sequence. skipped: %d\\n\", key)\r\n\t\t\t\tif zm != nil {\r\n\t\t\t\t\tzm.CloseFFInput()\r\n\t\t\t\t\tzm.Wait()\r\n\t\t\t\t}\r\n\t\t\t\tzm = &ZipMp4{ZipName: fileName}\r\n\t\t\t\tzm.OpenFFMpeg(\"mp4\")\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tzm.CloseFFInput()\r\n\tzm.Wait()\r\n\tfmt.Printf(\"\\nfinish: %s\\n\", zm.Mp4NameOpened)\r\n\r\n\treturn\r\n}\r\n\r\nfunc ExtractChunks(fileName string, skipHb bool) (done bool, err error) {\r\n\tdb, err := sql.Open(\"sqlite3\", fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tniconico.WriteComment(db, fileName, skipHb)\r\n\r\n\trows, err := db.Query(niconico.SelMedia)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer rows.Close()\r\n\r\n\tdir := files.RemoveExtention(fileName)\r\n\tif err = files.MkdirByFileName(dir + \"/\"); err != nil {\r\n\t\treturn\r\n\t}\r\n\tvar printTime int64\r\n\tfor rows.Next() {\r\n\t\tvar seqno int64\r\n\t\tvar bw int\r\n\t\tvar size int\r\n\t\tvar data []byte\r\n\t\terr = rows.Scan(&seqno, &bw, &size, &data)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t\tname := fmt.Sprintf(\"%s/%d.ts\", dir, seqno)\r\n\t\t// print\r\n\t\tnow := time.Now().Unix()\r\n\t\tif now != printTime {\r\n\t\t\tprintTime = now\r\n\t\t\tfmt.Println(name)\r\n\t\t}\r\n\r\n\t\terr = func() (e error) {\r\n\t\t\tf, e := os.Create(name)\r\n\t\t\tif e != nil {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tdefer f.Close()\r\n\t\t\t_, e = f.Write(data)\r\n\t\t\treturn\r\n\t\t}()\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\t}\r\n\r\n\tdone = true\r\n\treturn\r\n}\r\n\r\nfunc ConvertDB(fileName, ext string, skipHb bool) (done bool, nMp4s int, err error) {\r\n\tdb, err := sql.Open(\"sqlite3\", fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tniconico.WriteComment(db, fileName, skipHb)\r\n\r\n\tvar zm *ZipMp4\r\n\tdefer func() {\r\n\t\tif zm != nil {\r\n\t\t\t//zm.CloseFFInput()\r\n\t\t\tzm.Wait()\r\n\t\t}\r\n\t}()\r\n\r\n\tzm = &ZipMp4{ZipName: fileName}\r\n\tzm.OpenFFMpeg(ext)\r\n\r\n\trows, err := db.Query(niconico.SelMedia)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer rows.Close()\r\n\r\n\tprevBw := -1\r\n\tprevIndex := int64(-1)\r\n\tfor rows.Next() {\r\n\t\tvar seqno int64\r\n\t\tvar bw int\r\n\t\tvar size int\r\n\t\tvar data []byte\r\n\t\terr = rows.Scan(&seqno, &bw, &size, &data)\r\n\t\tif err != nil {\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t// チャンクが飛んでいる場合はファイルを分ける\r\n\t\t// BANDWIDTHが変わる場合はファイルを分ける\r\n\t\tif (prevIndex >= 0 && seqno != prevIndex+1) || (prevBw >= 0 && bw != prevBw) {\r\n\t\t\tif bw != prevBw {\r\n\t\t\t\tfmt.Printf(\"\\nBandwitdh changed: %d --> %d\\n\\n\", prevBw, bw)\r\n\t\t\t} else {\r\n\t\t\t\tfmt.Printf(\"\\nSeqNo. skipped: %d --> %d\\n\\n\", prevIndex, seqno)\r\n\t\t\t}\r\n\r\n\t\t\t//if zm != nil {\r\n\t\t\t//\tzm.CloseFFInput()\r\n\t\t\t//\tzm.Wait()\r\n\t\t\t//}\r\n\t\t\tzm.OpenFFMpeg(ext)\r\n\t\t}\r\n\t\tprevBw = bw\r\n\t\tprevIndex = seqno\r\n\r\n\t\tzm.FFInput(bytes.NewBuffer(data))\r\n\t}\r\n\r\n\t//zm.CloseFFInput()\r\n\tzm.Wait()\r\n\tfmt.Printf(\"\\nfinish:\\n\")\r\n\tfor _, s := range zm.mp4List {\r\n\t\tfmt.Println(s)\r\n\t}\r\n\tdone = true\r\n\tnMp4s = len(zm.mp4List)\r\n\r\n\treturn\r\n}\r\n\r\nfunc YtComment(fileName string) (done bool, err error) {\r\n\tdb, err := sql.Open(\"sqlite3\", fileName)\r\n\tif err != nil {\r\n\t\treturn\r\n\t}\r\n\tdefer db.Close()\r\n\r\n\tyoutube.WriteComment(db, fileName)\r\n\treturn\r\n}\r\n"
  },
  {
    "path": "updatebuildno.go",
    "content": "package main\r\n\r\nimport (\r\n\t\"log\"\r\n\t\"os\"\r\n\t\"io/ioutil\"\r\n\t\"time\"\r\n\t\"strconv\"\r\n\t\"regexp\"\r\n\t\"fmt\"\r\n)\r\n\r\nfunc main() {\r\n\tf, err := os.OpenFile(\"src/buildno/buildno.go\", os.O_RDWR, 0755)\r\n\tif err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tdefer f.Close()\r\n\r\n\tif _, err := f.Seek(0, 0); err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tdata, err := ioutil.ReadAll(f)\r\n\tif err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\r\n\tvar buildNo int64\r\n\tif ma := regexp.MustCompile(`BuildNo\\s*=\\s*\"(\\d+)\"`).FindSubmatch(data); len(ma) > 0 {\r\n\t\tbuildNo, err = strconv.ParseInt(string(ma[1]), 10, 64)\r\n\t\tif err != nil {\r\n\t\t\tlog.Fatal(err)\r\n\t\t}\r\n\t} else {\r\n\t\tlog.Fatal(\"BuildNo not match\")\r\n\t}\r\n\tbuildNo++\r\n\r\n\tvar now = time.Now()\r\n\tbuildDate := fmt.Sprintf(\"%04d%02d%02d\",\r\n\t\tnow.Year(),\r\n\t\tnow.Month(),\r\n\t\tnow.Day(),\r\n\t)\r\n\r\n\tfmt.Printf(\"%v.%v\\n\", buildDate, buildNo)\r\n\r\n\tif _, err := f.Seek(0, 0); err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\tif err := f.Truncate(0); err != nil {\r\n\t\tlog.Fatal(err)\r\n\t}\r\n\r\n\tf.WriteString(fmt.Sprintf(`\r\npackage buildno\r\n\r\nvar BuildDate = \"%s\"\r\nvar BuildNo = \"%d\"\r\n`, buildDate, buildNo))\r\n\r\n}\r\n"
  }
]