-- vim:set ft=lua:
dofile(mg.script_name:gsub('[^\\/]*$','')..'util.lua')

-- トランスコードするかどうか(する場合はreadex.exeとffmpeg.exeを用意すること)
XCODE=GetVarInt(mg.request_info.query_string, 'xcode',0,1)~=0
-- デフォルト設定
-- フィルタオプション
XFILTER='-vf yadif=0:-1:1'
XFILTER_CINEMA='-vf pullup -r 24000/1001'
-- ffmpeg変換オプション($FILTERはフィルタオプションに置換)
-- libvpxの例:リアルタイム変換と画質が両立するようにビットレート-bと計算量-cpu-usedを調整する
XOPT='-vcodec libvpx -b:v 896k -quality realtime -cpu-used 1 $FILTER -s 512x288 -acodec libvorbis -ab 128k -f webm -'
XOPT_mp4='-vcodec libx264 -profile:v main -level 31 -b:v 896k -maxrate 4M -bufsize 4M -preset veryfast -g 120 $FILTER -s 512x288 -acodec aac -ab 128k -f mp4 -movflags frag_keyframe+empty_moov -'
XOPT_nvenc='-vcodec h264_nvenc -profile:v main -level 31 -b:v 1408k -maxrate 8M -bufsize 8M -preset medium -g 120 $FILTER -s 1280x720 -acodec aac -ab 128k -f mp4 -movflags frag_keyframe+empty_moov -'
-- 出力バッファの量(bytes。asyncbuf.exeを用意すること。変換負荷や通信のむらを吸収する)
XBUF=0
-- 転送開始前に変換しておく量(bytes)
XPREPARE=0

-- コマンドはEDCBのToolsフォルダにあるものを優先する
ini='Setting\\HttpPublic.ini'
tools=edcb.GetPrivateProfile('SET','ModulePath','','Common.ini')..'\\Tools\\'
ffmpeg=edcb.GetPrivateProfile('SET','ffmpeg',tools..'ffmpeg.exe',ini)
ffprobe=edcb.GetPrivateProfile('SET','ffprobe',tools..'ffprobe.exe',ini)
tsreadex=edcb.GetPrivateProfile('SET','tsreadex',tools..'tsreadex.exe',ini)
asyncbuf=edcb.GetPrivateProfile('SET','asyncbuf',tools..'asyncbuf.exe',ini)

-- 設定読み込み
XOPT=edcb.GetPrivateProfile('MOVIE',(mg.get_var(mg.request_info.query_string,'quality') or ''),XOPT,ini)
XBUF=tonumber(edcb.GetPrivateProfile('SET','XBUF',XBUF,ini))
-- mp4判定 (XOPTにmp4が含まれているはず)
mp4=XOPT:match('mp4')
-- iosの場合強制的にmp4
if Check_iOS() and not mp4 then
  XOPT=nvenc and XOPT_nvenc or XOPT_mp4
  mp4=true
end
XEXT=mp4 and '.mp4' or '.webm'

r=edcb.GetRecFilePath((GetVarInt(mg.request_info.query_string,'reid') or 0))
v=edcb.GetRecFileInfo((GetVarInt(mg.request_info.query_string,'id') or 0))

if r then
  fpath=r
  if XCODE then
    -- トランスコード中のファイルがあればそっち使う
    f=edcb.FindFile(fpath..XEXT, 1)
    if f then
      fpath=fpath..XEXT
      XCODE=false
    end
  end
elseif v then
  fpath=v.recFilePath
else
  fpath=nil
  faddr=mg.get_var(mg.request_info.query_string,'fname')
  if faddr then
    if mg.get_var(mg.request_info.query_string,'public') then
      fpath=DocumentToNativePath(faddr)
    else
      -- 冗長表現の可能性を潰す
      faddr=edcb.Convert('utf-8','utf-8',faddr):gsub('[\\/]+','\\')
      for i,v in ipairs(GetLibraryPathList()) do
        -- ライブラリ配下にあるか＋禁止文字と正規化のチェック
        v=(v..'\\'):gsub('[\\/]+','\\')
        if faddr:sub(1,#v):lower()==v:lower() and not faddr:sub(#v+1):find('[\0-\x1f\x7f:*?"<>|]') and not faddr:sub(#v+1):find('%.\\') then
          fpath=faddr
          library=true
          break
        end
      end
    end
  end
end

if fpath then
  offset=tonumber(mg.get_var(mg.request_info.query_string,'offset')) or 0
  audio2=GetVarInt(mg.request_info.query_string,'audio2',0,1) or 0
  dual=GetVarInt(mg.request_info.query_string,'dual',0,2)
  dual=dual==1 and ' -dual_mono_mode main' or dual==2 and ' -dual_mono_mode sub' or ''
  filter=GetVarInt(mg.request_info.query_string,'cinema')==1 and XFILTER_CINEMA or XFILTER
end

debug=GetVarInt(mg.request_info.query_string,'debug',0,5) or 0
f=nil
if fpath then
  fname='xcode'..(fpath:match('%.[0-9A-Za-z]+$') or '')
  fnamets='xcode'..edcb.GetPrivateProfile('SET','TSExt','.ts','EpgTimerSrv.ini'):lower()
  -- 拡張子を限定
  if fname:lower()==fnamets or library then
    f=edcb.io.open(fpath, 'rb')
    if f then
      meta=mg.get_var(mg.request_info.query_string,'meta')
      if meta then
        fsec,fsize=GetDurationSec(f,fpath)
        ff=edcb.FindFile and edcb.FindFile(ffprobe, 1)
        if ff then
          meta={duration=fsec, audio={}}
          offset=math.floor(fsize*10/100/188)*188
          local fp=edcb.io.popen('""'..tsreadex..'" -s '..offset..' "'..fpath..'" | "'
            ..ffprobe..'" -i pipe:0 -hide_banner -show_format 2>&1"', 'rb')
          if fp then
            for v in (fp:read('*a') or ''):gmatch(':%sAudio:%s(.-)\r\n') do
               table.insert(meta.audio,v)
            end
            fp:close()
          end
        else
          meta={duration=fsec}
        end
        f:close()
      else
        if offset~=0 then
          fsec,fsize=GetDurationSec(f,fpath)
          if offset~=100 and SeekSec(f,fsec*offset/100,fsec,fsize) then
            offset=f:seek('cur',0) or 0
          else
            offset=math.floor(fsize*offset/100/188)*188
          end
        end
        if XCODE then
          f:close()
          -- 容量確保の可能性があるときは -m 1 オプションで対象ファイルを終端判定する
          sync=edcb.GetPrivateProfile('SET','KeepDisk',0,'EpgTimerSrv.ini')~='0'
          f=edcb.io.popen('""'..tsreadex..'" -z emwui-movie -s '..offset..' -l 16384 -t 4'..(sync and ' -m 1' or '')..' -x 18/38/39 -n -1 -b 1 -c 2 -u 2 "'..fpath..'" | "'
            ..ffmpeg..'"'..dual..' -i pipe:0 -map 0:v:0 -map 0:a:'..audio2..' '..XOPT:gsub('$FILTER',filter)..(debug>0 and ' -hide_banner -loglevel error 2>&1' or '')
            ..(XBUF>0 and ' | "'..asyncbuf..'" '..XBUF..' '..XPREPARE or '')..'"', 'rb')
          fname='xcode'..XEXT
        else
          -- 容量確保には未対応
          f:seek('set', offset)
        end
      end
    end
  end
end

if debug>0 then
  ct='<?xml version="1.0" encoding="UTF-8" ?'..'><entry><fpath>'
    ..fpath..'</fpath><errorcode>'
    ..({'MEDIA_ERR_ABORTED','MEDIA_ERR_NETWORK','MEDIA_ERR_DECODE','MEDIA_ERR_SRC_NOT_SUPPORTED','NETWORK_NO_SOURCE'})[debug]..'</errorcode>'
  if not edcb.FindFile(fpath, 1) then
    ct=ct..'<error>ファイルが見つかりませんでした</error>'
  else
    if XCODE then
      if not f then
        ct=ct..'<error>変換できませんでした</error>'
      else
        log=f:read(512):match('(.*)\r\n.-$')
        f:close()
      end
      if edcb.FindFile(ffmpeg, 1) then
        ct=ct..'<ffmpeg><version>'
          ..edcb.io.popen('""'..ffmpeg..'" -version"'):read('*a'):match('ffmpeg version (.-)%s')..'</version>'
          ..(log and '<errorlog>'..log..'</errorlog>' or '')
          ..'<XOPT>'..XOPT:gsub('$FILTER',(filter or ''))..'</XOPT><offset>'
          ..(offset or '')..'</offset><audio2>'
          ..(audio2 or '')..'</audio2><dual>'
          ..(dual or '')..'</dual>'
          ..'</ffmpeg><ffprobe>'
          ..(edcb.FindFile(ffprobe, 1) and 'OK' or 'NG')..'</ffprobe><buffer>'
          ..XBUF..'bytes'
          ..(XBUF>0 and '<asyncbuf>'..(edcb.FindFile(asyncbuf, 1) and 'OK' or 'NG')..'</asyncbuf>' or '')
          ..'</buffer>'
      else
        ct=ct..'<ffmpeg>NO</ffmpeg>'
      end
    else
      ct=ct..'<XCODE>NO</XCODE>'
    end
  end
  ct=ct..'</entry>'
  mg.write(Response(200,'text/xml', 'utf-8', #ct) ..'\r\n'..(mg.request_info.request_method~='HEAD' and ct or ''))
elseif meta then
  ct='<?xml version="1.0" encoding="UTF-8" ?><entry>'..(type(meta)=='table' and (meta.duration~=0 and '<duration>'..meta.duration..'</duration>' or '')..'<audio>'..#meta.audio..'</audio>' or '<err>なし</err>')..'</entry>'
  mg.write(Response(200,'text/xml', 'utf-8', #ct) ..'\r\n'..(mg.request_info.request_method~='HEAD' and ct or ''))
elseif not f then
  mg.write(Response(404,'text/html','utf-8')..'\r\n')
else
  mg.write(Response(200,mg.get_mime_type(fname))..'Content-Disposition: filename='..fname..'\r\n\r\n')
  if mg.request_info.request_method~='HEAD' then
    retry=0
    while true do
      buf=f:read(48128)
      if buf and #buf ~= 0 then
        retry=0
        if not mg.write(buf) then
          -- キャンセルされた
          mg.cry('canceled')
          break
        end
      else
        -- 終端に達した。4秒間この状態が続けば対象ファイルへの追記が終息したとみなす
        retry=retry+1
        if XCODE or retry > 20 then
          mg.cry('end')
          break
        end
        edcb.Sleep(200)
      end
    end
  end
  f:close()
end
