2016年4月22日金曜日

[動画][工作] Zenfone Selfie用VRゴーグル

経緯:
ジャイロセンサ内臓のスマートフォンなら、VR体験が可能だとわかりました。
このため、ジャイロセンサを内臓する Zenfone Selfie を購入し、数種類の市販VRゴーグルを試してみました。
でも眼鏡をかけたまま利用できるゴーグルがなかなかありません。
なので眼鏡OKなものを自作してみました。



材料は、レンズも含めてすべて百円ショップで入手しました。600円程度です。
ビューアアプリは「タオ360」を使ってます。リコーの THETA S で撮影した全天球動画もスムーズに再生できて楽しめます。
スマホ内臓の動画が数十ファイルあるためか、一覧表示が遅くて困ってますが、現時点でこれ以上のものが見つからない・・

時間がとれれば、詳細について、追記してゆきます。

2016年1月25日月曜日

[動画][管理] 指定フォルダ以下のビデオファイル情報を一括取得する

経緯:自宅には、Windowsの「記憶域」を利用した大容量NASがあり、デジタルビデオのmts動画ファイルやら、chinachuが作成したts動画ファイルが大量にあります。でもその中にはダブルクリックしても再生が始まらないファイルもあって、ディスク容量のムダになっています。(見ないのに集めるからこんなことに・・)

エンコード失敗などで生じた不良のファイルを見つけ出すためもあり、ビデオ情報を一括取得するツールを作りました。
pythonからffmpegを呼び出して動画情報を取得しています。

調べたいフォルダまたはファイルを右クリックし、「送る」の中にある下記分割スクリプト起動コマンドを指定すると、拡張子が mts、ts、mpg、mp4 等のファイルを対象に次の情報をとり、csvファイルに結果を保存します。DVD形式は対象外です。
・ファイル形式
・動画長さ(ビデオファイルに異常がある場合は「NA」)
・動画ピクセル数
・動画サンプルレート
・動画ビットレート
・動画ファイルサイズ
・動画ファイル最終更新日
・情報取得時間(秒)
・直前の非動画ファイルスキップ時間(秒)
・動画パス名
なおコマンドラインから起動する場合は、第1引数の対象フォルダパス名指定に加えて、第2引数にカンマで区切った除外パターンを指定可能です。

--------
材料:
1. Windows版python2
ライブラリが充実したスクリプト言語です。インストールし、パスを通しておきます。

2. ffmpegを利用可能にし、パスを通しておきます。新しめのバージョンであれば使えます。

3. 再生不可動画判別スクリプト起動コマンド「GetVideoInfoFromFolderV1a.cmd」
--この下から--
cd /d "%~dp0"
python GetVideoInfoFromFolderV1b.py %*
@if "%ERRORLEVEL%"=="0" timeout /t 4
@if not "%ERRORLEVEL%"=="0" pause ERROR!! see abobe.
--この上まで--

4. 再生不可動画判別スクリプト本体「GetVideoInfoFromFolderV1b.py」
--この下から--
#!/usr/bin/env python
# -*- coding: cp932 -*-

""" ##
第1引数で指定された動画ファイル(拡張子が ts,mts,mov,mpg,mp4,mpeg,m2v,flv,avi,wmv,f1v,dpg,mkv)につき
動画タイプ(拡張子),長さ(hh:mm:ss)、ビットレート、サイズ、パス名を表示する。
第1引数がフォルダの場合はサブフォルダも含めたすべての動画ファイルを処理する。
ログファイルに処理結果を追記する。

""" ##

import sys
import codecs
## MSコンソールで利用する場合のみ以下を有効にする(デフォルト指定はasciiらしい)
sys.stdin = codecs.getreader('cp932')(sys.stdin)
sys.stdout = codecs.getwriter('cp932')(sys.stdout)

## import宣言その他
import re
import os
import datetime
import locale
import subprocess
import time
import glob

##ffmpegのパス名出力部がsjisかutf-8かを設定する。(入出力文字列変換のため。今回はutf-8)
sFfmpegCharCode='utf-8'
##引数(sjis)を読み込む
aArg =sys.argv
## このスクリプトの絶対パスを得る
sNowFolder =os.path.dirname(os.path.abspath(__file__))
## 現在日時を得る
sNowDate = datetime.datetime.today().strftime('%Y%m%d%H%M%S')
## このスクリプトと同じフォルダ内に日付名でログファイルと作業用ファイルを初期化する
sLogFilePath=sNowFolder +('\\VideoInfoLog_%s.csv' % sNowDate)
## このスクリプトと同じフォルダ内に(日付+連番)名で作成する一時ファイル名を決める
sTempFilePathTop=sNowFolder +('\\_temp_%s' % (sNowDate))

## 検出対象の拡張子のパターンを定義する
re_moviename =re.compile('\.(ts|mts|mov|mpg|mp4|flv|avi|wmv|f1v|dpg|mkv)$', re.I)
## Ffmpegの出力から動画長さを抽出するパターン
# Duration: 00:59:57.36, start: 82134.638111, bitrate: 9045 kb/s
re_Duration1 =re.compile('Duration[: ]+([0-9:]+)', re.I)
## Ffmpegの出力から動画ビットレートを抽出するパターン
re_BitRate1 =re.compile('Stream.*Video.* ([0-9\.]+ kb/s)', re.I)
re_BitRate1Mkv =re.compile('^ *BPS *: *([0-9\.]+) *$')
##Ffmpegの出力から## 動画解像度を抽出するパターン
re_PixelSize =re.compile('Stream.*Video.* ([0-9]+x[0-9]+)[ \,]')
## Ffmpegの出力からフレームレートを抽出するパターン
re_FrameRate =re.compile('Stream.*Video.* ([0-9\.]+ fps)[ \,]')
## Ffmpegの出力からパス名をを抽出するパターン
re_Pathname =re.compile(r'Input.*from \'(.*)\'.?$')
## コンマ以下の秒数を検出し削除するためのパターン
re_AfterComma =re.compile(r'\.[0-9]+$')
## 数字のみで構成された文字列の判定
re_NumOnly =re.compile(r'^[0-9]+$')

## 指定ファイルに文字列を追記する関数の定義
def fAppendText(sPath, s):
 f =open(sPath, "ab")
 f.write(s)
 f.close()

## 指定ファイルの文字列を全て読み込む関数の定義
def fReadTextAllB(sPath):
 f =open(sPath, "rb")
 sOut =f.read()
 f.close()
 return(sOut)

## 秒数表記を時分秒表記に変換する(flvのDuration用)
def fSec2hms(str):
 if re_NumOnly.search(str):
  iTemp =int(str)
  str =datetime.time(iTemp /3600, (iTemp/60) %60, iTemp% 60).strftime('%H:%M:%S')
 return str

## 指定文字列のコマンドを実行する。(エラー発生時も放置。delコマンド用)
def fSystemExec(sCmd):
 proc_now = subprocess.Popen(sCmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 proc_now.wait()

## 1ファイルを処理する関数の定義
def fProcessOneFile(sPath):
 global iAllFile
 global iVideoCount
 global iVideoNACount
 global iVideoSizeSum
 global iLastEnd
 global aExcludePattern
 
 iAllFile =iAllFile +1
 aInfo =[""]
 
 m =re_moviename.search(sPath)
 if m:
  for sPattern in aExcludePattern:
   if sPath.find(sPattern)!=-1:  #除外パターンがパスに含まれている場合は処理をスキップする
    print(u"除外指定によりスキップ: \"%s\"" % sPath)
    return("")

  iStart = datetime.datetime.now()  # 処理時間計測用
  sType =m.group(1).lower()
  sDuration, sBitRate, sPixelSize, sFrameRate, sPathOut ="NA", "NA", "NA", "NA", sPath
  # UTF-8と相互変換できない機種依存文字のファイル名があれば「NG_NAME」を出力し情報取得しない。
  # ('illegal multibyte sequence' への対応)
  sPath2 =sPath.encode("cp932", "replace").decode("cp932", "replace")
  if sPath !=sPath2:
   iFileSize=0
   sTimeStamp ='1980/01/01 00:00:00'
   sDuration, sBitRate, sPixelSize, sFrameRate, sPathOut ="NA", "NG_NAME", "NG_NAME", "NG_NAME", sPath2
   print(u'次のファイル名の文字コードが不適切なのでリネームして下さい。\"%s\"' % sPath2)
  elif not os.path.isfile(sPath):
   iFileSize=0
   sTimeStamp ='1980/01/01 00:00:00'
   sDuration, sBitRate, sPixelSize, sFrameRate, sPathOut ="NA", "NO_FILE", "NO_FILE", "NO_FILE", sPath
   print(u"ファイルが存在しません。\"%s\"" % sPath)
  else:
   iFileSize=os.path.getsize(sPath)
   sTimeStamp =datetime.datetime.fromtimestamp(os.stat(sPath).st_mtime).strftime('%Y-%m-%d %H:%M:%S')
   if sType=="dpg":
    sDuration, sBitRate, sPixelSize, sFrameRate, sPathOut ="NA", "DPG", "DPG", "DPG", sPath
   aInfo =fGetVideoFileInfoWithFfmpegWithFile(sPath).splitlines()
   for line in aInfo:
    m=re_Pathname.search(line)
    if m:
     sPathOut =m.group(1)
    if sDuration =="NA":  #未検出の場合のみ判定する
     m=re_Duration1.search(line)
     if m:
      sDuration =fSec2hms(m.group(1))
    if sBitRate =="NA":  #未検出の場合のみ判定する
     m=re_BitRate1.search(line)
     if m:
      sBitRate =m.group(1)
     m=re_BitRate1Mkv.search(line)
     if m:
      sBitRate ="%.1f kb/s" % (int(m.group(1))/1000)
    if sPixelSize =="NA":  #未検出の場合のみ判定する
     m=re_PixelSize.search(line)
     if m:
      sPixelSize =m.group(1)
    if sFrameRate =="NA":  #未検出の場合のみ判定する
     m=re_FrameRate.search(line)
     if m:
      sFrameRate =m.group(1)

  iVideoCount =iVideoCount +1
  iVideoSizeSum =iVideoSizeSum +iFileSize
  if sDuration=="NA":  #再生不可の動画をカウントする (
   iVideoNACount =iVideoNACount +1
  iEnd = datetime.datetime.now()  # 処理時間計測用
  sInfo ="%s,%s,%s,%s,%s,%d,%s,%s,%s,\"%s\"" % (sType,sDuration, sPixelSize, sFrameRate, sBitRate, iFileSize, sTimeStamp,(iEnd -iStart).seconds,(iStart -iLastEnd).seconds,sPathOut)
  #情報を標準出力およびログファイルに出力する。
  fAppendText(sLogFilePath, sInfo.encode("cp932", "replace") +'\r\n' )
  print(sInfo)
  iLastEnd =iEnd  # 動画ファイルの処理終了時刻を覚えておく
 return ("")

##動画ファイルの情報を得る関数の定義(ffmpegの標準エラー出力をファイル出力後に取得、タイムアイウト有、プロセス管理不十分)
def fGetVideoFileInfoWithFfmpegWithFile(sPath):
 global iTimeOutSec
 global iTimeOutOnFfmpeg
 global iVideoCount
 global sTempFilePathTop
 
 #一時ファイルの先頭に対象動画パスを書き込む
 sTempFilePath="%s%06d.txt" % (sTempFilePathTop, iVideoCount)
 #不要となった一時ファイルを削除するコマンドを作成する
 sCmd2 =u"del /q \"%s\"" % sTempFilePath
 sStdError=""
 
 # Ffmpeg -i の標準エラー出力を一時ファイルに追記するように実行コマンドを作成する。
 fAppendText(sTempFilePath, sPath.encode('utf-8', 'replace') +'\r\n' )
 sCmd =u"ffmpeg.exe -timelimit %d -i \"%s\" >>\"%s\" 2>&1" % (iTimeOutOnFfmpeg,sPath,sTempFilePath)
 sCmdsjis =sCmd.encode("cp932", "replace")
 iStart = datetime.datetime.now()  # タイムアウト制御用
 proc = subprocess.Popen(sCmdsjis, shell=True)
 while proc.poll() is None:
  time.sleep(0.2)
  iNow = datetime.datetime.now()  # タイムアウト制御用
  if (iNow - iStart).seconds> iTimeOutSec:
   proc.kill()  #実際にはffmpegはすぐには停止しないため、未終了プロセスが増えてゆく場合がある。
   #ファイルから結果を読む
   sStdError =fReadTextAllB(sTempFilePath)
   if sFfmpegCharCode=='utf-8':
    sStdError =sStdError.decode("utf-8", "replace")  #利用中のffmpegの出力がutf-8の場合に変換する
   sStdError ="\r\n".join(sStdError.splitlines())  #利用中のffmpegのメッセージが\r\nになっていないため整形する
   #os.remove(sTempFilePath)  #利用中のffmpegが掴んで離さない場合がある。時間がたつと解放される。
   fSystemExec(sCmd2)  #不要となった一時ファイルを削除する
   return(sStdError)

 sStdOut, sStdDummy =proc.communicate()
 #ファイルから結果を読む
 sStdError =fReadTextAllB(sTempFilePath)
 if sFfmpegCharCode=='utf-8':
  sStdError =sStdError.decode("utf-8", "replace")  #利用中のffmpegの出力がutf-8の場合に変換する
 sStdError ="\r\n".join(sStdError.splitlines())  #利用中のffmpegのメッセージが\r\nになっていないため整形する
 #print("sStdError=\r\n%s" % sStdError)
 fSystemExec(sCmd2)  #不要となった一時ファイルを削除する
 return(sStdError)

##諸変数を初期化し処理を開始する
iAllFile, iVideoCount, iVideoNACount, iVideoSizeSum = 0,0,0,0
iTimeOutSec =12
iTimeOutOnFfmpeg=10
iStart = datetime.datetime.now()  # 総処理時間計測用
iLastEnd =datetime.datetime.now()
aExcludePattern=[]  # 除外パターン
try:
 # 引数の数が不適切な場合は処理を中断する
 if len(aArg)==1:
  raise Exception("010",u"引数が必要です。対象パスを指定して下さい。\n%s/" % (len(aArg), "/\n".join(aArg)))
 elif len(aArg)!=2 and len(aArg)!=3:
  print(u" Option: \"検索元フォルダ\" [\"除外パターン[,除外パターン2 . .]\" ]")
  raise Exception("020",u"Exit. (No Option)")
 # 第1引数の前後のダブルクォート文字あるいは末尾の\文字があれば除去する
 sArg1_org =re.sub(r'^"', '', aArg[1])
 sArg1_org =re.sub(r'["\\]+$', '', sArg1_org)
 sArg1_unicode =sArg1_org.decode('cp932', 'replace')
 if sArg1_org !=(sArg1_unicode.encode("cp932", "replace")):
  raise Exception("030", u"ファイル名の文字コードが不適切です。リネームして下さい。\"%s\"" % sArg1_unicode)
 """
 # 第1引数の前後のダブルクォート文字あるいは末尾の\文字があれば除去する
 sArg1_unicode =re.sub(r'^"', '', sArg1_unicode)
 sArg1_unicode =re.sub(r'["\\]+$', '', sArg1_unicode)
 """

 # 第1引数にパスが含まれていない場合は付加する。(コマンドラインで実行の場合)
 if not re.search(r'[\\:]', sArg1_unicode):
  sArg1_unicode =sNowFolder +'\\' +sArg1_unicode

 # 第2引数が指定された場合は除外パターン配列を得る。
 if len(aArg)==3:
  sArg2_unicode =aArg[2].decode('cp932', 'replace')
  sArg2_unicode =re.sub(r'^"', '', sArg2_unicode)
  sArg2_unicode =re.sub(r'["\\]+$', '', sArg2_unicode)
  aExcludePattern =re.compile("[,/]").split(sArg2_unicode)
  print("aExcludePattern=[%s], num=%d" % ("/".join(aExcludePattern), len(aExcludePattern)))
 
 #引数がファイルなのかフォルダなのかを識別する
 bParam1isFile =True if os.path.isfile(sArg1_unicode) else False
 bParam1isDir =True if os.path.isdir(sArg1_unicode) else False
 # 引数指定のフォルダ又はファイルが存在しない場合は処理を中断する
 if not (bParam1isFile or bParam1isDir):
  raise Exception("040", u"引数で指定したファイルまたはフォルダが存在しません: %s" % sArg1_unicode)

 #引数がファイル名の場合とフォルダの場合に分けて処理する
 if bParam1isFile:
  sRet =fProcessOneFile(sArg1_unicode, sArg1_org)
 elif bParam1isDir:
  for root, dirs, files in os.walk(sArg1_unicode +'\\'):
   for file in files:
    sPath =os.path.join(root, file)
    sRet =fProcessOneFile(sPath)

 iEnd = datetime.datetime.now()  # 総処理時間計測用
 sSummary =',,,,,%d,,,%s,\"iAllFile=%d iVideoCount=%d iVideoNACount=%d\"' % (iVideoSizeSum, (iEnd -iStart).seconds, iAllFile,iVideoCount,iVideoNACount)
 if len(aExcludePattern)!=0:
  sSummary =sSummary +u" 除外=" +"/".join(aExcludePattern)
 fAppendText(sLogFilePath, sSummary.encode("cp932", "replace") +'\r\n')
 print(sSummary)
 sCmd3 =u"del /q \"%s_*.*\"" % sTempFilePathTop
 fSystemExec(sCmd3)
 print("sLogFilePath=%s" % sLogFilePath)
 sys.exit(0)

except Exception, inst:
 # エラー発生時に例外の内容を出力する
 #print("len(inst.args)=%d" % len(inst.args))
 #print ("%s" % (" ".join(inst.args)))
 print (inst.args)
 sys.exit(1)
--この上まで--

上記 3.と4.のファイルは共にメモ帳で作成して任意のフォルダに置いてから、3.のショートカットを作ってこれを C:\Users\(ユーザ名)\AppData\Roaming\Microsoft\Windows\SendTo の中にコピーすれば準備完了です。
ffmpegの標準エラー出力を取り出すための一時ファイルが多数作成されます。実行中に削除しきれなかった一時ファイルは全処理終了直前に一括削除されます。
もし文字化けで止まる場合は、4.の10行目の "utf-8" を違う文字 (""でok)に書き換えて試してください。

以上。

2016年1月21日木曜日

[動画][圧縮] 動画の一括分割・圧縮(Windows上、ffmpeg利用)

経緯:昨年(2015年)10月10日に撮影した盆踊り大会の映像2時間分を、曲ごとに約20ファイルに区分けすることになりました。
これまでは高解像度のデジタルビデオ動画ファイル(1080p、mp4)から
--
1. TMPegENC VMW5で動画を圧縮し画素数を減らす(486p、mp4、gop30)
2. 圧縮済の動画を再生しつつ、開始時間・終了時間と曲名をメモ書き
3. メモに基づいてAvidemuxで動画を無劣化切断しファイル名をつける
--
の順、または
--
1. 元動画を再生しつつ、開始時間・終了時間と曲名をメモ書き
2. メモに基づいてAvidemuxで動画を無劣化切断しファイル名をつける(元動画がmp4形式の場合のみ)
3. 分割した全ファイルをhandbrakeで圧縮し画素数を減らす
--
の順で曲ごとのファイルを作っていたのですが、無劣化切断にかなり手間がかかります。(それもあって今日まで放置していたのですが・・)
音声ファイルについては以前よりフリーソフト「wavez」等を使って、時間指定のみでとても楽に無劣化分割していました。
今回、動画ファイルについても同様に一括処理することを思いつきました。
新しい手順は次のようになります。
--
1. 元動画を再生しつつ、開始時間・終了時間と曲名をカンマ区切りでメモ書き
2. メモ書きファイルを右クリック → 送る →分割スクリプト起動コマンドを指定
--
元動画と同じフォルダ内に出力サイズ864x486、画質2000kbに変換しつつ分割し保存します。
DVDより画素数は高く、スマートフォンに入れれば手軽に滑らかな動画を楽しめます。
入手したばかりのChromecast2経由でテレビ画面上でもきれいに再生できました。

--------
材料:
1. Windows版ffmpeg
音質のよいfdk-aacのモジュールを含むffmegのバイナリは配布されていないため、次を参考に作成し、パスを通しました。
「FFmpegをWindows上でビルドする」
http://k-pi.hatenablog.com/entry/2015/10/25/220120

2. Windows版python2
ライブラリが充実したスクリプト言語です。インストールし、パスを通しておきます。

3. 時間指定メモファイル
以下のように開始点・終了点・分割後ファイル名を含むカンマ区切りファイルを作成しておきます。
先頭の行のファイル名部には圧縮前動画ファイルのフルパスを記述します。コメント(#・・)の記述も可能です。
「input.csv」(下例では2曲分のファイルを出力します)
--この下から--
0,0,C:\Users\myname\Videos\20151010_みんなで踊る盆踊り_1080p.mp4
00:5:37,00:08:45,と/東京音頭_in20151010みなとまつり.mp4
00:8:57,00:12:30,た/炭坑節_in20151010みなとまつり.mp4
--この上まで--

4. 分割スクリプト起動コマンド「Video2mp4a_486p.cmd」
--この下から--
cd /d "%~dp0"
python Video2mp4b_486p.py %1
@if "%ERRORLEVEL%"=="0" timeout /t 4
@if not "%ERRORLEVEL%"=="0" pause ERROR!! see abobe.
--この上まで--

5. 分割スクリプト「Video2mp4b_486p.py」(自作、pythonスクリプト)
--この下から--
#!/usr/bin/env python
# -*- coding: cp932 -*-

"""    ##
リストファイルに記述された時間指定とファイル名にしたがって、元動画ファイルを分割・圧縮・保存する。

第1引数: リストファイル
形式例:
0,0,C:\Users\myname\Videos\20151010_みんなで踊る盆踊り_1080p.mp4
00:5:37,00:08:45,と/東京音頭_in20151010みなとまつり.mp4
00:8:57,00:12:30,た/炭坑節_in20151010みなとまつり.mp4
"""    ##

import sys
import codecs
## MSコンソールで利用する場合のみ以下を有効にする(デフォルト指定はasciiらしい)
sys.stdin = codecs.getreader('cp932')(sys.stdin)
sys.stdout = codecs.getwriter('cp932')(sys.stdout)

## import宣言その他
import re
import os
import datetime
import locale
import commands
#import traceback

## 現在パスを得る
sWorkBase="G1K"
sNowFolder =os.path.dirname(__file__)

##引数(sjis)を読み込む
aArg =sys.argv
#print("len(aArg)=%i, aArg[0]=%s" % (len(aArg), aArg[0]))

##適切な時間指定パターンを定義する
re_hms =re.compile('^([0-9]+)(:?)([0-9]*)(:?)([0-9]*)$')
##適切なファイル名パターンを定義する
re_pattern_mp4 =re.compile('.mp4$', re.I)
##適切なコメント行パターンを定義する
re_comment =re.compile('^#')
##処理中断・終了用パターンを定義する
re_exit =re.compile('^#exit', re.I)

##各行分析後格納配列
aData=[]

##時間指定文字列から秒数を得る関数の定義
def fGetSecFromHms(hms):
  global re_hms
  iRet =0
  res =re_hms.search(hms)
  if res:
    if res.group(5)!="":
      iRet =int(res.group(1))*3600 +int(res.group(3))*60 +int(res.group(5))
    elif res.group(3)!="":
      iRet =int(res.group(1))*60 +int(res.group(3))
    else:
      iRet =int(res.group(1))
  return(iRet)

##mpeg変換オプションを指定する
aFfmpegOption =[ [
    u'-y'    # OverWrite
  ,u'-ss %d'    # set the start time offset
  ,u'-i \"%s\"'    # infile
  ,u'-f mp4' # ファイルフォ ーマット(コンテナ)
  ,u'-vcodec libx264'    # ビデオ(CODEC・関連オ プション)
  ,u'-maxrate 2000k -bufsize 2000k'    # ビデ オビットレート
  ,u'-g 30'    # GOP サイズ
  ,u'-r 30000/1001'    # フレームレート
  ,u'-aspect 16:9'    # アスペクト比
  ,u'-s 864x486'    # 解像度
  ,u'-acodec libfdk_aac'    # オーディオ(CODEC・関連オプション)
  ,u'-ab 128k'    # オーディオビットレー ト
  ,u'-to %d'    # record or translate stop time
  ,u'\"%s\"'    # outfile
],[
    u'-y'    # OverWrite
  ,u'-ss %d'    # set the start time offset
  ,u'-i \"%s\"'    # infile
  ,u'-f mp4' # ファイルフォ ーマット(コンテナ)
  ,u'-vcodec libx264'    # ビデオ(CODEC・関連オ プション)
  ,u'-maxrate 2000k -bufsize 2000k'    # ビデ オビットレート
  ,u'-g 30'    # GOP サイズ
  ,u'-r 30000/1001'    # フレームレート
  ,u'-aspect 4:3'    # アスペクト比
  ,u'-s 640x480'    # 解像度
  ,u'-acodec libfdk_aac'    # オーディオ(CODEC・関連オプション)
  ,u'-ab 128k'    # オーディオビットレー ト
  ,u'-to %d'    # record or translate stop time
  ,u'\"%s\"'    # outfile
] ]
iEncFormatNum =0    #0の場合は16:9、1の場合は4:3のアスペクト比でエンコードする

##諸変数を初期化し処理を開始する
sInVideoPathName =""
sInCsvPathname =""
sOutVideoDir =""
sErrMes =""
sWarningMes =""
sCheck ="NA"
try:
  # 引数の数が不適切な場合は処理を中断する
  if len(aArg)!=2:
    raise Exception("010",u"引数の数(%d)が適切ではありません。\n%s/" % (len(aArg), "/\n".join(aArg)))
  # 引数ファイルが存在しない場合は処理を中断する
  sInCsvPathname =aArg[1]
  if not re.search(r'\\', sInCsvPathname):
    sInCsvPathname =sNowFolder +'\\' +sInCsvPathname
    print("sInCsvPathname=%s" % sInCsvPathname)
  if not (os.path.isfile(sInCsvPathname)):
    raise Exception("020", u"引数で指定したファイルが存在しません: %s" % sInCsvPathname)
  # 引数ファイルの内容を取り出し、行に分解する
  sIn =codecs.open(aArg[1], 'r', 'cp932', 'replace').read()
  aIn =sIn.splitlines()

  #各行をカンマで区切り、開始時、終了時、ファイル名に分解し、配列に格納する
  for i in range(0, len(aIn)):
    iStart =0
    iEnd =0
    sOutVideoName =""
    aIn[i] =re.sub(r"^#*[ \t]*$", "", aIn[i])
    aIn[i] =re.sub(r"[,  ]+$", "", aIn[i])
    aLine=aIn[i].split(",")
    #各データの前後のダブルクォート文字があれば除去する
    for j in range(0, len(aLine)):
      aLine[j] =re.sub(r'^"', '', aLine[j])
      aLine[j] =re.sub(r'"$', '', aLine[j])
    #exitで終わる行で処理を中断し終了する
    if re_exit.search(aLine[0]):
      break;
    #空行あるいはコメント行の場合は処理をスキップする
    if (aLine[0]=="") or (re_comment.search(aLine[0])):
      print("skip.")
      continue
    #カンマ区切りデータ数が不適切な場合は処理を中断する
    if len(aLine) !=3:
      raise Exception("030", u"第%d行にてカンマ区切りが不適切です。[%s]" % ((i+1), aIn[i]))
    #開始時秒数を得る
    if re_hms.search(aLine[0]):
      iStart =fGetSecFromHms(aLine[0])
    #時分秒の指定が不適切な場合は処理を中断する
    else:
      raise Exception("040", u'第%d行にて開始時の指定が不適切です。[%s (%s)]"' % ((i+1), aLine[0], aLine[2]))
    #終了時秒数を得る
    if re_hms.search(aLine[1]):
      iEnd =fGetSecFromHms(aLine[1])
      # 終了時の指定が開始時より先の場合は警告メッセージを追加する
      if iStart >iEnd and iEnd!=fGetSecFromHms(0):
       sWarn =u"警告:第%d行にて開始時と終了時の大小が不適切です。[%s,%s (%s)]\n" % ((i+1), aLine[0], aLine[1], aLine[2])
       sWarningMes =sWarningMes +sWarn
       continue
    #時分秒の指定が不適切な場合は処理を中断する
    else:
      raise Exception("050", u'第%d行にて終了時の指定が不適切です。[%s (%s)]"' % ((i+1), aLine[1], aLine[2]))
    #出力先ファイル名を得る
    sOutVideoName =aLine[2] +("" if re_pattern_mp4.search(aLine[2]) else ".mp4")
    #print(u"開始秒=%d, 終了秒=%d, 出力先名=%s" % (iStart, iEnd, sOutVideoName))
    aData.append([iStart, iEnd, sOutVideoName])
    if iStart==fGetSecFromHms("0") and iEnd==fGetSecFromHms("0"):    # 0,0,で始まる行は入力ファイル指定。
      sInVideoPathName =sOutVideoName
      #入力動画ファイルが存在しない場合は処理を中断する
      if not (os.path.isfile(sInVideoPathName)):
       raise Exception("060", u"入力元動画ファイル %s が存在しません。" % sInVideoPathName)
      sOutVideoDir =re.sub(r'\\[^\\]+$', '', sInVideoPathName)
    else:
      iEncFormatNum=0
      # 入力ファイル名に "-1609-"が含まれる場合はアスペクト比 16:9でエンコード
      if re.search(u"-1609-", sInVideoPathName):
       iEncFormatNum=0
      # 入力ファイル名に "-0403-"が含まれる場合はアスペクト比 4:3でエンコード
      elif re.search(u"-0403-", sInVideoPathName):
       iEncFormatNum=1
      sCmd =(("ffmpeg.exe " +" ".join(aFfmpegOption[iEncFormatNum])) % (iStart, sInVideoPathName, iEnd -iStart, sOutVideoDir +'\\' +sOutVideoName))
      print (sCmd)
      sCmd2 =sCmd.encode('cp932', 'replace')
      os.system(sCmd2)
  if sWarningMes !="":
    print ("Warning:\n%s" % sWarningMes)
    sys.exit(1)
  sys.exit(0)

except Exception, inst:
  #print(inst.args)
  print ("%s" % (" ".join(inst.args)))
  sys.exit(1)
--この上まで--

上記 4.と5.のファイルは共にメモ帳で作成し、C:\Users\(ユーザ名)\AppData\Roaming\Microsoft\Windows\SendTo の中に入れておけばokです。
なお、アニメ動画の圧縮には未対応です。フレームレート自動識別をさせればすむ話ですが。

以上。

2016年1月20日水曜日

[機器処分] ブラウン管モニタを無料で処分するには

経緯:自宅で使わなくなった15型ブラウン管モニタがあります。
処分してスペースを有効活用したいのですが、都の粗大ごみ回収では扱ってもらえません。
キーワード「ブラウン管テレビ 処分」で検索したところ、以下の業者を見つけました。

手順:
「パソコンファーム」
https://pc-farm.co.jp/

液晶モニタなどを3台以上処分する場合は、無料で引き取りに来てくれて、その際に他の電気機器などを同時に無料処分してくれるそうです。
法人、個人のどちらでも申し込めます。
早速、申し込みました。住所、電話、メールアドレスのほかに「回収希望の曜日」「回収品の内容」「証明書発行の依頼(有無)」などを入力します。
今回は15型ブラウン管モニタの他に、28型のブラウン管テレビ、故障した扇風機、圧力なべも回収してもらうことにしました。
2016年1月末までのキャンペーンだそうですが、今後も同じようなサービスをやってくれるかも。


→ 申し込んだ1時間後に電話で回収日問い合わせの電話があり、翌週月曜に引き取りに来ていただくことになりました。
「お伺いする30分から1時間前にドライバーから電話を入れます」とのことでした。

残念ながら、28型ブラウン管テレビのみ断られました。、2001年以降製造の14型から21型のみ」取り扱っているそうです。
このワイドテレビは家電リサイクル法の対象に含まれるので、例えばジャパネットたかたで買換え時の処分では約5600円かかるようです。
今回、買い替える予定は無いので、分解してブラウン管部はガラスくずとして廃棄しようかと考えています。

以上。

2016年1月19日火曜日

[動画][通信] ネットワークカメラを利用した遠隔監視 V1(Android+ JPT3815W)

経緯:友人から全方位監視可能な遠隔カメラについて相談を受け、検討しました。
最初は、全天球カメラ「theta S」とスマートフォンを利用する方法を模索しましたが、この2つだけでは無理そうなので、追加でRhaspbery2を利用することを検討しました。
その後、秋葉原をまわっているうちに、ネットワークカメラとスマートフォンを利用するほうが簡単だと気付きました。
--------
材料:
1. Wifi接続および全方位パン・チルト可能なネットワークカメラ。
今回はTENVIS JPT3815W を利用します。
(あきばおーで販売中の 恵安 C7823WIPでも構築可能と思われます。)
2. Androidスマートフォン。今回はASUS Zenfone Selfieを利用。
3. PCからカメラを遠隔操作するソフト。今回は無料の「AirDroid 」を利用しました。スマートフォンとPCにそれぞれインストールしておきます。
--------
手順
1. ネットワークカメラ JPT3815Wのリセットボタンを20秒ほど押し続けてリセットしておく。
2. 有線接続した上記カメラにPCからWebブラウザでアクセス(http://http://192.168.1.239:81/)し、ログインできることを確認しておきます。
3. 無線接続の設定をして、自宅のWifiに接続します。
手順は次の記事を参照しました。 「ネットワークカメラ Tenvis JPT3815W 無線LANで同じネットワーク上からアクセス
4. 無線接続状態で、Windows 10マシン(Corei5、2.4GHz)のTorchブラウザから、カメラビューで分解能 640x480、30fpsの表示が確認できました。
同様にWindows 7マシン(Corei5、2.6GHz)のFirefoxブラウザでも同スペックでの表示が確認できました。
5. ハードウェアバージョンは1.4、ファームウェアバージョンは1.7.15と判明しました。
6. ファームウェアアップデートを試みたところ、PC上のIP Camera Wizardでのアップデートは失敗しましたが、ブラウザ接続でのシステム管理画面からのアップデートは成功し、バージョン 1.7.25に上がりました。
7. 有線接続状態でメーカー提供のDDNS登録は失敗するため断念しました。
8. 自宅ネットワークのDNS設定は既存のものがあるので、ポート81をカメラIPに割り当てるようルータ設定をしたところ、手持ちのAndroidスマートフォンから4G通信経由でのカメラの操作に成功しました。PC用ブラウザ、携帯ブラウザの両方でモニタ動作が確認できましたが、スマートフォンの「Chromeブラウザ」ではボタン操作がうまく反映されない場合ありでした。スマートフォンにプリインストールされていた「Webブラウザ」なら少しボタン動作が改善されました。なお「戻る]操作が効かない場合はURLの最後を・・:81/index.asp に指定すると戻れます。
9. スマートフォンのテザリングを有効にし、PCをテザリングで接続すると、IPアドレスが 192.168.1.40 →192.168.1.43.35 に変更されました。
このテストを元に、有線で接続したカメラのIPアドレスを 192.168.1.239 → 192.168.43.239 に変更し、スマートフォンのテザリングを有効にしてカメラが接続できるようにした。カメラからネットワークケーブルを抜いて無線接続にし、スマートフォンのWebブラウザで192.168.43.239:81にアクセスするとカメラに接続できました。
10. PCからスマートフォンをAirDroidで接続すると、PCからテザリングで接続したカメラ映像を見て、操作できました。
11. 以上の手順で、工事現場など電源に接続したネットワークカメラとスマートフォンを、オフィスからモニターすることが可能になります。
--------
以上。

技術情報を公開します

電子工作、PC技術、スマホ利用技術などに関する情報を公開します。
主に覚え書き用ですが、お楽しみ下さい。