Guest User

Untitled

a guest
Mar 23rd, 2018
238
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.21 KB | None | 0 0
  1. #!/bin/bash
  2.  
  3. # Sora のマルチストリーム機能を使用し、かつ、録画機能を使用した場合に作成される録画情報のメタファイルを元に、映像と音声を 1 ファイルに合成します
  4.  
  5.  
  6. # 録画の途中で解像度が変越された場合は、映像や音声が飛んだり、途中で停止等することがあります(頻繁に解像度が変更されるため Chrome 非推奨)
  7. # 合成元の録画ファイルが壊れている場合は、映像や音声が飛んだり、途中で停止等することがあります
  8.  
  9. # TODO: video のみの映像が含まれる場合の合成
  10. # TODO: audio のみの映像が含まれる場合の合成
  11. # TODO: 合成時の video コーデック指定
  12. # TODO: 合成時の audio コーデック指定
  13. # TODO: 任意のオプションの指定
  14.  
  15.  
  16. #video_codec=libopenh264
  17. video_codec=libx264
  18. audio_codec=libmp3lame
  19.  
  20. usage() {
  21. echo "usage: muxer.sh [-f metadata_file_path] [-c column]" 1>&2
  22. exit 1
  23. }
  24.  
  25. init() {
  26. metadata=$( cat $metadata_file_path )
  27.  
  28. created_at=$( echo $metadata | jq .created_at )
  29. channel_id=$( echo $metadata | jq -r .channel_id )
  30. archives=( $( echo $metadata | jq -c ".archives | sort_by(.start_time_offset) | .[]" ) )
  31. video_archives=()
  32. for archive in ${archives[@]}
  33. do
  34. file_path=$( echo $archive | jq -r .file_path )
  35. ffprobe $file_path 2>&1 | grep Video >/dev/null
  36. if [ "$?" = "0" ];
  37. then
  38. video_archives=( ${video_archives[@]} $archive )
  39. fi
  40. done
  41.  
  42. audio_archives=()
  43. for archive in ${archives[@]}
  44. do
  45. file_path=$( echo $archive | jq -r .file_path )
  46. ffprobe $file_path 2>&1 | grep Audio > /dev/null
  47. if [ "$?" = "0" ];
  48. then
  49. audio_archives=( ${audio_archives[@]} $archive )
  50. fi
  51. done
  52.  
  53. last_client_id=( $( echo $metadata | jq -c -r ".archives | max_by(.stop_time_offset) | .client_id" ) )
  54.  
  55. audio_file="${channel_id}.mp3"
  56. video_file="${channel_id}.mp4"
  57. }
  58.  
  59.  
  60. # 音声の合成
  61. # 音声と映像の合成を一コマンドで実行すると Buffer queue overflow, dropping. が発生して、映像または音声が飛び飛びになるため、はじめに音声の合成のみをおこない、次に映像と合成済みの音声の合成をおこなう
  62.  
  63. # ffmpeg
  64. # -i /home/user/sora/_build/dev/rel/sora/archive/3ec96de5-abad-4b19-ae6a-6514010051b1.webm
  65. # -i /home/user/sora/_build/dev/rel/sora/archive/041d743f-3b91-4790-b0b4-8b653bbc1ada.webm
  66. # -i /home/user/sora/_build/dev/rel/sora/archive/e238f67a-55bb-480d-984f-4a03aec25a4a.webm
  67. # -filter_complex "
  68. # [0:a] adelay=10 [a0];
  69. # [1:a] adelay=15000 [a1];
  70. # [2:a] adelay=417000 [a2];
  71. # [a0][a1][a2] amix=inputs=3:duration=longest:dropout_transition=0 [a]
  72. # "
  73. # -c:a libmp3lame
  74. # -map "[a]"
  75. # sora.mp3
  76.  
  77. mix_audio() {
  78. args=""
  79. index=0
  80. filter_complex=""
  81. tags=""
  82.  
  83. for archive in ${audio_archives[@]}
  84. do
  85. metadata_file_path=$( echo $archive | jq -r .metadata_file_path )
  86. file_path=$( cat $metadata_file_path | jq -r .file_path )
  87. args="$args -i $file_path"
  88. tag="[audio${index}]"
  89.  
  90. filter_complex="$filter_complex $( audio_filter_complex $index $tag $archive )"
  91. tags="${tags}${tag}"
  92. index=$(( $index + 1 ))
  93. done
  94.  
  95. filter_complex="$filter_complex $tags amix=inputs=${#audio_archives[@]}:duration=longest:dropout_transition=0 [audio]"
  96. args="$args -filter_complex \"${filter_complex}\" \
  97. -c:a $audio_codec \
  98. -map \"[audio]\" \
  99. $audio_file"
  100.  
  101.  
  102. bash -c "ffmpeg $args"
  103. }
  104.  
  105. audio_filter_complex() {
  106. index=$1
  107. tag=$2
  108. archive=$3
  109.  
  110. delay=$( echo $archive | jq .start_time_offset )
  111. tag="[audio${index}]"
  112. if [ "$delay" -eq "0" ];
  113. then
  114. # adelay は 0 以上を指定する必要があるためとりあえず 10 msec を指定
  115. # TODO(yoshida): 10msec の映像とのズレが発生するため調整が必要
  116. echo "[${index}:a] adelay=$(( $delay + 10 )) $tag;"
  117. else
  118. # 開始時間は msec で指定する
  119. echo "[${index}:a] adelay=$(( $delay * 1000 )) $tag;"
  120. fi
  121. }
  122.  
  123. # 映像と音声の合成
  124.  
  125. # ffmpeg
  126. # -i /home/user/sora/_build/dev/rel/sora/archive/3ec96de5-abad-4b19-ae6a-6514010051b1.webm
  127. # -i /home/user/sora/_build/dev/rel/sora/archive/041d743f-3b91-4790-b0b4-8b653bbc1ada.webm
  128. # -i /home/user/sora/_build/dev/rel/sora/archive/e238f67a-55bb-480d-984f-4a03aec25a4a.webm
  129. # -i sora.mp3
  130. # -filter_complex "
  131. # [0:v] setpts=PTS-STARTPTS+0/TB, scale=640x480 [v0];
  132. # [1:v] setpts=PTS-STARTPTS+15/TB, scale=640x480 [v1];
  133. # [2:v] setpts=PTS-STARTPTS+417/TB, scale=640x480 [v2];
  134. # color=c=black@0.2:size=1280x960 [b];
  135. # [b][v0] overlay=shortest=0:x=0:y=0 [t0];
  136. # [t0][v1] overlay=shortest=1:x=640:y=0 [t1];
  137. # [t1][v2] overlay=shortest=0:x=0:y=480 [v]
  138. # "
  139. # -c:v libx264
  140. # -c:a copy
  141. # -map "3:a"
  142. # -map "[v]"
  143. # sora.mp4
  144.  
  145. mux() {
  146. # 解像度の最大値を全体の動画サイズのベースとして採用する
  147. base_width=0
  148. base_height=0
  149. for archive in ${video_archives[@]}
  150. do
  151. metadata_file_path=$( echo $archive | jq -r .metadata_file_path )
  152. width=$( cat $metadata_file_path | jq -r .video.width )
  153. height=$( cat $metadata_file_path | jq -r .video.height )
  154. if [ "$base_width" -lt "$width" ];
  155. then
  156. base_width=$width
  157. fi
  158.  
  159. if [ "$base_height" -lt "$height" ];
  160. then
  161. base_height=$height
  162. fi
  163. done
  164.  
  165.  
  166. # サイズの計算
  167. video_width=$(( $base_width * $column ))
  168. video_height=$(( $base_height * $(( $(( ${#video_archives[@]} + $(( $column - 1 )) )) / $column )) ))
  169. video_size=${video_width}x${video_height}
  170.  
  171. args=""
  172. filter_complex="color=c=black@0.2:size=${video_size} [base];"
  173.  
  174. index=0
  175. for archive in ${video_archives[@]}
  176. do
  177. metadata_file_path=$( echo $archive | jq -r .metadata_file_path )
  178. file_path=$( cat $metadata_file_path | jq -r .file_path )
  179. metadata=$( cat $metadata_file_path )
  180.  
  181. width=$( echo $metadata | jq .video.width )
  182. height=$( echo $metadata | jq .video.height )
  183. args="$args -i $file_path"
  184.  
  185. delay=$( echo $archive | jq .start_time_offset )
  186. tag="[video${index}]"
  187.  
  188. if [ "$delay" -eq "0" ];
  189. then
  190. # TODO: 0 のままでは 10msec の音声とのズレが発生するため調整する
  191. filter_complex="$filter_complex [${index}:v] setpts=PTS-STARTPTS/TB, scale=${width}x${height} $tag;"
  192. else
  193. filter_complex="$filter_complex [${index}:v] setpts=PTS-STARTPTS+${delay}/TB, scale=${width}x${height} $tag;"
  194. fi
  195.  
  196. index=$(( $index + 1 ))
  197. done
  198.  
  199.  
  200. index=0
  201. for archive in ${video_archives[@]}
  202. do
  203. tag="[video${index}]"
  204.  
  205. if [ "$index" -eq "0" ];
  206. then
  207. target_tag="[t${index}]"
  208. next_target_tag="[base]"
  209. else
  210. # 最後だけ shortest と tag が異なる
  211. if [ $(( ${#video_archives[@]} - 1 )) -eq $index ];
  212. then
  213. target_tag="[video]"
  214. else
  215. target_tag="[t${index}]"
  216. fi
  217. fi
  218.  
  219. x=$(( $base_width * $(( $index % $column )) ))
  220. y=$(( $base_height * $(( $index / $column )) ))
  221.  
  222. client_id=$( echo $archive | jq -r .client_id )
  223. if [ "$client_id" = "$last_client_id" ];
  224. then
  225. shortest=1
  226. else
  227. shortest=0
  228. fi
  229.  
  230. filter_complex="$filter_complex ${next_target_tag}${tag} overlay=shortest=${shortest}:x=${x}:y=${y} $target_tag"
  231.  
  232. # 最後以外は ; が必要
  233. if [ $(( ${#video_archives[@]} - 1 )) -ne $index ];
  234. then
  235. filter_complex="${filter_complex};"
  236. fi
  237.  
  238. next_target_tag=$target_tag
  239. index=$(( $index + 1 ))
  240. done
  241.  
  242. if [ -e $audio_file ];
  243. then
  244. args="$args -i $audio_file"
  245. args="$args -filter_complex \"${filter_complex}\" \
  246. -c:v $video_codec \
  247. -c:a copy \
  248. -map \"${#video_archives[@]}:a\" \
  249. -map \"[video]\" \
  250. $video_file"
  251. else
  252. args="$args -filter_complex \"${filter_complex}\" \
  253. -c:v $video_codec \
  254. -map \"[video]\" \
  255. $video_file"
  256. fi
  257.  
  258. echo $args
  259.  
  260. bash -c "ffmpeg $args"
  261. }
  262.  
  263.  
  264. while getopts f:c:h opt
  265. do
  266. case $opt in
  267. "f" ) metadata_file_path="$OPTARG" ;;
  268. "c" ) column="$OPTARG" ;;
  269. "h" ) usage ;;
  270. esac
  271. done
  272.  
  273. if [ -z "$metadata_file_path" ];
  274. then
  275. usage
  276. fi
  277.  
  278. # TODO: 整数以外の値が指定された場合の処理の追加
  279. if [ -z "$column" ];
  280. then
  281. column=2
  282. elif [ "$column" -lt "1" ];
  283. then
  284. usage
  285. fi
  286.  
  287. init
  288. mix_audio
  289. mux
Add Comment
Please, Sign In to add comment