├── Makefile ├── .gitignore ├── test ├── flags.sh └── flags.txt ├── LICENSE ├── README.md ├── video-convert └── imosh /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | bash test/flags.sh > test/flags.txt 3 | .PHONY: test 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | -------------------------------------------------------------------------------- /test/flags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u 4 | 5 | COMMAND="$(dirname "${BASH_SOURCE}")/../video-convert" 6 | 7 | run() { 8 | local name="${1}"; shift 9 | echo "=== ${name} ===" 10 | "${COMMAND}" "$@" --ffmpeg=echo input.mp4 11 | echo 12 | } 13 | 14 | run 'Output' --output=output.mp4 15 | run 'Copy video stream' \ 16 | --output=output.mp4 --video_codec=copy 17 | run 'Copy audio stream' \ 18 | --output=output.mp4 --audio_codec=copy 19 | run 'Threads' --output=output.mp4 --threads=4 20 | run 'Deinterlace' --output=output.mp4 --deinterlace 21 | run 'Subtitle' --output=output.mp4 --subtitle=4 22 | run 'Second audio' --output=output.mp4 --audio=3 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kentaro IMAJO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | A tool to convert movie using ffmpeg. 5 | 6 | Supported ffmpeg version 7 | ------------------------ 8 | 9 | * ffmpeg 2.3 10 | 11 | Usage 12 | ===== 13 | 14 | Convert a movie file to an iPhone compatible movie file 15 | ------------------------------------------------------- 16 | 17 | ```sh 18 | $ video-convert --output=output.m4v input.vob 19 | ``` 20 | 21 | If an input file and the output flag are given, the video-convert command converts the input file and outputs its result to the output. 22 | 23 | Show streams 24 | ------------ 25 | 26 | ```sh 27 | $ video-convert input.vob 28 | ``` 29 | 30 | If the output flag is not specified, the video-convert command shows the list of streams. 31 | 32 | Show late streams 33 | ----------------- 34 | 35 | ```sh 36 | $ video-convert --probesize=100 37 | ``` 38 | 39 | The probesize flag expands the size of blocks to probe. 40 | The default size is 1, and the value multiplies the value. 41 | 42 | Show subtitles 43 | -------------- 44 | 45 | ```sh 46 | $ video-convert --subtitle=4 --output=output.m4v input.vob 47 | ``` 48 | 49 | The subtitle flag makes the command overlay the subtitle stream specified with the flag to a video stream. 50 | 51 | Deinterlace 52 | ----------- 53 | 54 | ```sh 55 | $ video-convert --deinterlace 56 | ``` 57 | 58 | The deinterlace flag makes the command use a deinterlace filter. 59 | -------------------------------------------------------------------------------- /test/flags.txt: -------------------------------------------------------------------------------- 1 | === Output === 2 | -i input.mp4 -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec libfaac -ac 2 -ar 48000 -ab 128k -f ipod output.mp4 3 | 4 | === Copy video stream === 5 | -i input.mp4 -vcodec copy -acodec libfaac -ac 2 -ar 48000 -ab 128k -f ipod output.mp4 6 | 7 | === Copy audio stream === 8 | -i input.mp4 -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec copy -f ipod output.mp4 9 | 10 | === Threads === 11 | -threads 4 -i input.mp4 -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec libfaac -ac 2 -ar 48000 -ab 128k -f ipod output.mp4 12 | 13 | === Deinterlace === 14 | -i input.mp4 -deinterlace -filter:v yadif -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec libfaac -ac 2 -ar 48000 -ab 128k -f ipod output.mp4 15 | 16 | === Subtitle === 17 | -i input.mp4 -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec libfaac -ac 2 -ar 48000 -ab 128k -filter_complex [0:4]setpts=PTS+0/TB[SUB];[0:v][SUB]overlay -f ipod output.mp4 18 | 19 | === Second audio === 20 | -i input.mp4 -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -direct-pred 3 -fast-pskip 1 -acodec libfaac -ac 2 -ar 48000 -ab 128k -f ipod output.mp4 21 | 22 | -------------------------------------------------------------------------------- /video-convert: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Enable alsologtostderr so that LOG INFO is shown. 4 | : ${IMOSH_FLAGS_alsologtostderr:=1} 5 | 6 | source "$(dirname "${BASH_SOURCE}")"/imosh || exit 1 7 | 8 | DEFINE_int threads 0 'Number of threads.' 9 | DEFINE_int probesize 1 \ 10 | 'Probe size multiplier. Some stream such as a subtitle stream may not be' \ 11 | 'found because the stream does not exist in the beginning. This flag' \ 12 | 'extends the size to probe.' 13 | DEFINE_bool deinterlace false 'Enables deinterlace.' 14 | DEFINE_int subtitle '-1' "Subtitle's stream ID to overlay." 15 | DEFINE_string video 'v' \ 16 | "Video's stream ID. Use the first stream of audio streams." 17 | DEFINE_string video_codec 'libx264' 'Video codec.' 18 | DEFINE_string audio 'a' \ 19 | "Audio's stream ID. Use the first stream of audio streams." 20 | DEFINE_string audio_codec 'libfaac' 'Audio codec.' 21 | DEFINE_string output '' \ 22 | 'Video output. This command shows stream details if this is not specified.' 23 | DEFINE_string ffmpeg 'ffmpeg' "ffmpeg's command path." 24 | 25 | eval "${IMOSH_INIT}" 26 | 27 | FFMPEG_PREPROCESS_FLAGS=() 28 | FFMPEG_FLAGS=() 29 | FFMPEG_POSTPROCESS_FLAGS=() 30 | 31 | if [ "$#" = 0 ]; then 32 | LOG FATAL 'Input file(s) must be specified.' 33 | fi 34 | 35 | run_ffmpeg() { 36 | local flags=() 37 | if [ "${#FFMPEG_PREPROCESS_FLAGS[@]}" != 0 ]; then 38 | flags+=("${FFMPEG_PREPROCESS_FLAGS[@]}") 39 | fi 40 | if [ "${#FFMPEG_FLAGS[@]}" != 0 ]; then 41 | flags+=("${FFMPEG_FLAGS[@]}") 42 | fi 43 | if [ "${#FFMPEG_POSTPROCESS_FLAGS[@]}" != 0 ]; then 44 | flags+=("${FFMPEG_POSTPROCESS_FLAGS[@]}") 45 | fi 46 | 47 | LOG INFO 'Running: ffmpeg' "${flags[@]}" 48 | "${FLAGS_ffmpeg}" "${flags[@]}" 49 | } 50 | 51 | set_input() { 52 | local input 53 | for input in "$@"; do 54 | FFMPEG_FLAGS+=('-i' "${input}") 55 | done 56 | } 57 | 58 | set_probe_flags() { 59 | if (( FLAGS_probesize <= 1 )); then return; fi 60 | # Increases size and duration to probe streams. 61 | FFMPEG_PREPROCESS_FLAGS+=( 62 | '-probesize' "$(expr "${FLAGS_probesize}" '*' 5)M" 63 | '-analyzeduration' "$(expr "${FLAGS_probesize}" '*' 5)M" 64 | ) 65 | } 66 | 67 | set_video_flags() { 68 | if [ "${FLAGS_video}" = '' ]; then return; fi 69 | if (( FLAGS_deinterlace )); then 70 | FFMPEG_FLAGS+=( 71 | '-deinterlace' '-filter:v' 'yadif') 72 | fi 73 | 74 | case "${FLAGS_video_codec}" in 75 | 'libx264') 76 | FFMPEG_FLAGS+=( 77 | -vcodec libx264 -y -bufsize 512k -coder 1 -g 250 78 | -flags +loop -partitions +parti8x8+parti4x4+partp8x8+partb8x8 79 | -me_method hex -subq 7 -me_range 16 -g 250 -keyint_min 25 80 | -sc_threshold 40 81 | -i_qfactor 0.71 -b_strategy 1 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 82 | -direct-pred 3 -fast-pskip 1 83 | ) 84 | ;; 85 | 'copy') 86 | FFMPEG_FLAGS+=(-vcodec copy) 87 | ;; 88 | *) 89 | LOG FATAL "no such video codec: ${FLAGS_video_codec}" 90 | ;; 91 | esac 92 | 93 | local filters=() 94 | if (( FLAGS_subtitle > 0 )); then 95 | filters+=( 96 | "[0:${FLAGS_subtitle}]setpts=PTS+0/TB[SUB]" 97 | "[0:${FLAGS_video}][SUB]overlay") 98 | fi 99 | if [ "${#filters[@]}" != 0 ]; then 100 | FFMPEG_POSTPROCESS_FLAGS+=(-filter_complex "$(php::implode ';' filters)") 101 | fi 102 | } 103 | 104 | set_audio_flags() { 105 | if [ "${FLAGS_audio}" = '' ]; then return; fi 106 | 107 | case "${FLAGS_audio_codec}" in 108 | 'libfaac') 109 | FFMPEG_FLAGS+=(-acodec libfaac -ac 2 -ar 48000 -ab 128k) 110 | ;; 111 | 'copy') 112 | FFMPEG_FLAGS+=(-acodec copy) 113 | ;; 114 | *) 115 | LOG FATAL "no such audio codec: ${FLAGS_audio_codec}" 116 | ;; 117 | esac 118 | } 119 | 120 | set_output_flags() { 121 | if [ "${FLAGS_output}" = '' ]; then return; fi 122 | 123 | if (( FLAGS_threads > 0 )); then 124 | FFMPEG_PREPROCESS_FLAGS+=(-threads "${FLAGS_threads}") 125 | fi 126 | 127 | set_video_flags 128 | set_audio_flags 129 | 130 | FFMPEG_POSTPROCESS_FLAGS+=(-f ipod) 131 | FFMPEG_POSTPROCESS_FLAGS+=("${FLAGS_output}") 132 | } 133 | 134 | main() { 135 | set_input "$@" 136 | set_probe_flags 137 | set_output_flags 138 | run_ffmpeg 139 | } 140 | 141 | LOG INFO "$@" 142 | main "$@" 143 | -------------------------------------------------------------------------------- /imosh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Last update: 2014-08-10 22:26:03 +0900 (9317500) 3 | # 4 | # imosh - Libraries for BASH. 5 | 6 | if ! shopt login_shell >/dev/null; then 7 | set -e -u 8 | fi 9 | 10 | if [ "${__IMOSH_IS_LOADED+loaded}" == 'loaded' ]; then 11 | return 12 | fi 13 | __IMOSH_IS_LOADED=loaded 14 | 15 | print() { 16 | printf "%s" "$*" 17 | } 18 | 19 | __IMOSH_PROGRAM_NAME="${0##*/}" 20 | __IMOSH_HOSTNAME="$(hostname -s)" 21 | __IMOSH_USER="${USER}" 22 | __IMOSH_LOG_PREFIX="${__IMOSH_PROGRAM_NAME}.${__IMOSH_HOSTNAME}.${__IMOSH_USER}" 23 | __IMOSH_LOG_DIR="${TMPDIR:-/tmp}" 24 | __IMOSH_LOG_SUFFIX="$(date +'%Y%m%d.%H%M%S').$$" 25 | 26 | imosh::internal::log_file() { 27 | local severity="${1}" 28 | local path='' 29 | path+="${__IMOSH_LOG_DIR}/" 30 | path+="${__IMOSH_LOG_PREFIX}.${severity}.${__IMOSH_LOG_SUFFIX}" 31 | print "${path}" 32 | } 33 | 34 | # Close descriptors for logs beforehand for BASH3's bug. 35 | exec 101>&- 102>&- 103>&- 104>&- 36 | 37 | if [ -w "${__IMOSH_LOG_DIR}" ]; then 38 | exec 101>"$(imosh::internal::log_file INFO)" 39 | exec 102>"$(imosh::internal::log_file WARNING)" 40 | exec 103>"$(imosh::internal::log_file ERROR)" 41 | exec 104>"$(imosh::internal::log_file FATAL)" 42 | else 43 | exec 101>/dev/null 102>/dev/null 103>/dev/null 104>/dev/null 44 | echo "failed to open files to write logs: ${__IMOSH_LOG_DIR}" >&2 45 | fi 46 | 47 | imosh::get_child_processes() { 48 | local ppid="$1" 49 | ps -axo ppid,pid | awk "{ if (\$1 == ${ppid}) print \$2; }" 50 | } 51 | 52 | imosh::internal::style() { 53 | echo -en "\\033[${1}m" 54 | } 55 | 56 | IMOSH_STYLE_DEFAULT="$(imosh::internal::style '0')" 57 | IMOSH_COLOR_DEFAULT="$(imosh::internal::style '0;39')" 58 | IMOSH_COLOR_BLACK="$(imosh::internal::style '0;30')" 59 | IMOSH_COLOR_RED="$(imosh::internal::style '0;31')" 60 | IMOSH_COLOR_GREEN="$(imosh::internal::style '0;32')" 61 | IMOSH_COLOR_YELLOW="$(imosh::internal::style '0;33')" 62 | IMOSH_COLOR_BLUE="$(imosh::internal::style '0;34')" 63 | IMOSH_COLOR_MAGENTA="$(imosh::internal::style '0;35')" 64 | IMOSH_COLOR_CYAN="$(imosh::internal::style '0;36')" 65 | IMOSH_COLOR_WHITE="$(imosh::internal::style '0;37')" 66 | 67 | imosh::shell_escape() { 68 | local arg 69 | local search="'" 70 | local replace="'\"'\"'" 71 | for arg in "$@"; do 72 | arg="${arg//${search}/${replace}}" 73 | echo -n "'${arg}'" 74 | done 75 | } 76 | 77 | imosh::on_exit() { 78 | echo "$@" >>"${__IMOSH_CORE_TMPDIR}/on_exit.sh" 79 | } 80 | 81 | imosh::internal::error_handler() { 82 | imosh::stack_trace "error status: $?" 83 | } 84 | 85 | imosh::internal::exit_handler() { 86 | LOG INFO "finalizing..." 87 | 88 | set +e 89 | if [ -f "${__IMOSH_CORE_TMPDIR}/on_exit.sh" ]; then 90 | source "${__IMOSH_CORE_TMPDIR}/on_exit.sh" 91 | fi 92 | rm -rf "${__IMOSH_CORE_TMPDIR}" 93 | 94 | # Close log pipes and remove unused log files. 95 | exec 101>&- 102>&- 103>&- 104>&- 96 | local severity='' 97 | for severity in INFO WARNING ERROR FATAL; do 98 | local path="$(imosh::internal::log_file "${severity}")" 99 | if [ ! -s "${path}" ]; then 100 | rm "${path}" 101 | fi 102 | done 103 | } 104 | 105 | imosh::internal::signal_handler() { 106 | local signal="$1" 107 | LOG ERROR "$(imosh::stack_trace "terminated by signal: ${signal}" 2>&1)" 108 | trap - "${signal}" 109 | kill -s "${signal}" $$ 110 | } 111 | 112 | trap imosh::internal::exit_handler EXIT 113 | trap imosh::internal::error_handler ERR 114 | if ! shopt login_shell >/dev/null; then 115 | for signal in SIGHUP SIGINT SIGPIPE SIGTERM \ 116 | SIGXCPU SIGXFSZ SIGUSR1 SIGUSR2; do 117 | trap "imosh::internal::signal_handler ${signal}" "${signal}" 118 | done 119 | fi 120 | 121 | LOG() { 122 | local level="$1" 123 | shift 124 | 125 | case "${level}" in 126 | INFO|WARNING|ERROR|FATAL) :;; 127 | *) LOG FATAL "no such log level: ${level}" 128 | esac 129 | local datetime="$(date +'%m%d %T.%N')" 130 | # For systems not supporting %N in date. 131 | datetime="${datetime/.N/.000000}" 132 | datetime="${datetime:0:20}" 133 | local pid="$$" 134 | if php::isset __IMOSH_LOG_PID; then 135 | pid="${__IMOSH_LOG_PID}" 136 | fi 137 | local message=( 138 | "${level:0:1}${datetime}" 139 | "${pid}" 140 | "${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}]" 141 | "$*") 142 | message="${message[*]}" 143 | if [ "${level}" == 'FATAL' ]; then 144 | message+=$'\n' 145 | message+="$(imosh::stack_trace '*** Check failure stack trace: ***' 2>&1)" 146 | fi 147 | local logtostderr=0 148 | if php::isset FLAGS_logtostderr; then 149 | logtostderr="${FLAGS_logtostderr}" 150 | fi 151 | local alsologtostderr=0 152 | if php::isset FLAGS_alsologtostderr; then 153 | alsologtostderr="${FLAGS_alsologtostderr}" 154 | fi 155 | case "${level}" in 156 | INFO) 157 | if (( logtostderr || alsologtostderr )); then 158 | echo "${message}" >&2 159 | fi 160 | if (( ! logtostderr )); then 161 | echo "${message}" >&101 162 | fi 163 | ;; 164 | WARNING) 165 | if (( logtostderr || alsologtostderr )); then 166 | echo "${message}" >&2 167 | fi 168 | if (( ! logtostderr )); then 169 | echo "${message}" >&101 170 | echo "${message}" >&102 171 | fi 172 | ;; 173 | ERROR) 174 | echo "${message}" >&2 175 | if (( ! logtostderr )); then 176 | echo "${message}" >&101 177 | echo "${message}" >&102 178 | echo "${message}" >&103 179 | fi 180 | ;; 181 | FATAL) 182 | echo "${message}" >&2 183 | if (( ! logtostderr )); then 184 | echo "${message}" >&101 185 | echo "${message}" >&102 186 | echo "${message}" >&103 187 | echo "${message}" >&104 188 | fi 189 | exit 1 190 | ;; 191 | esac 192 | } 193 | 194 | # Parses arguments without getopt. 195 | imosh::internal::parse_args() { 196 | local class_name="$1"; shift 197 | 198 | local upper_class_name="$(php::strtoupper "${class_name}")" 199 | local arg arg_name arg_value 200 | IMOSH_ARGV=() 201 | IMOSH_ARGS=() 202 | while [ "$#" != '0' ]; do 203 | local arg="$1" 204 | shift 205 | if [ "${arg:0:1}" != '-' ]; then 206 | IMOSH_ARGV+=("${arg}") 207 | continue 208 | fi 209 | if [[ "${arg}" =~ ^-[0-9] ]]; then 210 | IMOSH_ARGV+=("${arg}") 211 | continue 212 | fi 213 | if [ "${arg}" == '--' ]; then 214 | IMOSH_ARGV+=("$@") 215 | break 216 | fi 217 | case "${arg}" in 218 | --*) arg="${arg:2}";; 219 | -*) arg="${arg:1}";; 220 | esac 221 | arg_name="${arg%%=*}" 222 | if [[ ! "${arg_name}" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then 223 | LOG FATAL "${class_name} name is bad: ${arg_name}" 224 | fi 225 | arg_value="${arg:${#arg_name}}" 226 | if [ "${arg_value:0:1}" != '=' ]; then 227 | if [ "${arg_name:0:2}" == 'no' ]; then 228 | if php::isset "${upper_class_name}S_${arg_name:2}"; then 229 | if [ "${class_name}" == 'flag' ] && \ 230 | [ "$(imosh::internal::flag_type "${arg_name:2}")" != 'bool' ]; then 231 | LOG FATAL "the ${arg_name:2} flag is not a bool flag" 232 | fi 233 | IMOSH_ARGS+=("${upper_class_name}S_${arg_name:2}=0") 234 | continue 235 | fi 236 | fi 237 | if php::isset "${upper_class_name}S_${arg_name}"; then 238 | if [ "${class_name}" == 'flag' ] && \ 239 | [ "$(imosh::internal::flag_type "${arg_name}")" != 'bool' ]; then 240 | LOG FATAL "the ${arg_name} flag is not a bool flag" 241 | fi 242 | IMOSH_ARGS+=("${upper_class_name}S_${arg_name}=1") 243 | continue 244 | fi 245 | LOG FATAL "no such bool ${class_name} is defined:" \ 246 | "(${upper_class_name}S_)${arg_name}" 247 | fi 248 | arg_value="${arg_value:1}" 249 | if php::isset "${upper_class_name}S_${arg_name}"; then 250 | if [ "${class_name}" == 'flag' ]; then 251 | if ! imosh::internal::convert_type \ 252 | "$(imosh::internal::flag_type "${arg_name}")" \ 253 | "${arg_value}" >/dev/null; then 254 | LOG FATAL "the ${arg_name} flag has an invalid value: ${arg_value}" 255 | else 256 | arg_value="$(imosh::internal::convert_type \ 257 | "$(imosh::internal::flag_type "${arg_name}")" \ 258 | "${arg_value}")" 259 | fi 260 | fi 261 | IMOSH_ARGS+=("${upper_class_name}S_${arg_name}=${arg_value}") 262 | continue 263 | fi 264 | LOG FATAL "no such ${class_name} is defined:" \ 265 | "(${upper_class_name}S_)${arg_name}" 266 | done 267 | } 268 | 269 | readonly IMOSH_PARSE_ARGUMENTS=' 270 | local IMOSH_ARGV IMOSH_ARGS 271 | imosh::internal::parse_args arg "$@" 272 | if [ "${#IMOSH_ARGS[@]}" -ne 0 ]; then 273 | readonly "${IMOSH_ARGS[@]}" 274 | fi 275 | if [ "${#IMOSH_ARGV[@]}" -ne 0 ]; then 276 | set -- "${IMOSH_ARGV[@]}" 277 | fi' 278 | 279 | php::addslashes() { 280 | if [ "$#" -ne 1 ]; then 281 | LOG FATAL 'php::addslashes requires one argument.' 282 | fi 283 | 284 | local subject="${1}" 285 | subject="$(php::str_replace '\' '\\' "${subject}")" 286 | subject="$(php::str_replace "'" "\\'" "${subject}")" 287 | print "${subject}" 288 | } 289 | 290 | php::array_map() { 291 | if [ "$#" -ne 2 ]; then 292 | LOG FATAL 'php::array_map requires two arguments.' 293 | fi 294 | 295 | local __array_map_callback="${1}" 296 | local __array_map_name="${2}" 297 | eval "local __array_map_values=(\"\${${__array_map_name}[@]}\")" 298 | local __array_map_i=0 __array_map_size="${#__array_map_values[@]}" 299 | while (( __array_map_i < __array_map_size )); do 300 | local __array_map_value="${__array_map_values[${__array_map_i}]}" 301 | __array_map_value="$("${__array_map_callback}" "${__array_map_value}")" 302 | eval "${__array_map_name}[${__array_map_i}]='$( 303 | php::addslashes "${__array_map_value}")'" 304 | (( __array_map_i += 1 )) || true 305 | done 306 | } 307 | 308 | php::array_unique() { 309 | if [ "$#" -ne 1 ]; then 310 | LOG FATAL 'php::array_unique requires one argument.' 311 | fi 312 | 313 | local __array_unique_name="${1}" 314 | eval "local __array_unique_values=(\"\${${__array_unique_name}[@]}\")" 315 | php::sort __array_unique_values 316 | local __array_unique_i=1 __array_unique_size="${#__array_unique_values[@]}" 317 | while (( __array_unique_i < __array_unique_size )); do 318 | local __array_unique_last_i=0 319 | (( __array_unique_last_i = __array_unique_i - 1 )) || true 320 | if [ "${__array_unique_values[${__array_unique_i}]}" == \ 321 | "${__array_unique_values[${__array_unique_last_i}]}" ]; then 322 | unset __array_unique_values["${__array_unique_last_i}"] 323 | fi 324 | (( __array_unique_i += 1 )) || true 325 | done 326 | eval "${__array_unique_name}=(\"\${__array_unique_values[@]}\")" 327 | } 328 | 329 | php::bin2hex() { 330 | if [ "$#" -eq 0 ]; then 331 | od -An -tx1 | tr -d ' \n' 332 | else 333 | print "$*" | php::bin2hex 334 | fi 335 | } 336 | 337 | php::explode() { 338 | local __explode_name="${1}" 339 | local __explode_delimiter="${2}" 340 | local __explode_value="${3}" 341 | 342 | __explode_value="$( 343 | php::str_replace "${__explode_delimiter}" $'\x02' "${__explode_value}")" 344 | eval "IFS=\$'\\x02' ${__explode_name}=(\$__explode_value)" 345 | } 346 | 347 | php::hex2bin() { 348 | local message="$( 349 | print "$*" | tr -c -d '[0-9a-fA-F]' | fold -w 2 \ 350 | | sed -e 's/^/\\x/' | tr -d '[:space:]')" 351 | printf "${message}" 352 | } 353 | 354 | php::implode() { 355 | local __implode_delimiter="${1}" 356 | local __implode_name="${2}" 357 | 358 | eval "local __implode_values=(\"\${${__implode_name}[@]}\")" 359 | local __implode_size="${#__implode_values[@]}" 360 | local i=0 361 | local output='' 362 | while (( i < __implode_size )); do 363 | if (( i != 0 )); then output+="${__implode_delimiter}"; fi 364 | output+="${__implode_values[${i}]}" 365 | (( i += 1 )) || true 366 | done 367 | print "${output}" 368 | } 369 | 370 | # CAVEATS: 371 | # isset returns true for uninitialized variables in BASH 3, and returns 372 | # false for them in BASH 4. 373 | php::isset() { 374 | local variable_name="$1" 375 | 376 | if [ "$(eval echo '${'"${variable_name}"'+set}')" == '' ]; then 377 | return 1 378 | fi 379 | return 0 380 | } 381 | 382 | php::ltrim() { 383 | local value="$*" 384 | print "${value#"${value%%[![:space:]]*}"}" 385 | } 386 | 387 | php::md5() { 388 | if which openssl >/dev/null 2>/dev/null; then 389 | print "${1}" | openssl md5 -binary | php::bin2hex 390 | elif which md5sum >/dev/null 2>/dev/null; then 391 | print "${1}" | md5sum -b | php::bin2hex 392 | else 393 | LOG FATAL 'no command for md5 is found.' 394 | fi 395 | } 396 | 397 | php::ord() { 398 | printf '%d' \'"${1}" 399 | } 400 | 401 | php::preg_match() { 402 | local __preg_match_pattern="${1}" 403 | local __preg_match_subject="${2}" 404 | local __preg_match_name='' 405 | 406 | if [ "$#" -ge 3 ]; then __preg_match_name="${3}"; fi 407 | local __preg_match_result 408 | if ! php::internal::run " 409 | if (!preg_match('$(php::addslashes "${__preg_match_pattern}")', 410 | '$(php::addslashes "${__preg_match_subject}")', 411 | \$match)) { return 1; } 412 | echo implode(\"\\x02\", \$match); return 0;" __preg_match_result; then 413 | return 1 414 | else 415 | php::explode "${__preg_match_name}" $'\x02' "${__preg_match_result}" 416 | fi 417 | } 418 | 419 | php::rand() { 420 | if [ "$#" -eq 0 ]; then 421 | php::rand 0 2147483647 422 | return 423 | fi 424 | if [ "$#" -ne 2 ]; then 425 | LOG FATAL 'php::rand requires zero or two arguments.' 426 | fi 427 | 428 | local min="${1}" max="${2}" range=0 429 | (( range = max - min + 1 )) || true 430 | if [ "${range}" -lt 1 ]; then 431 | LOG FATAL "min must be larger than max: min=${min}, max=${max}" 432 | fi 433 | if [ "${range}" -eq 1 ]; then 434 | print "${min}" 435 | return 436 | fi 437 | local rand=0 438 | (( rand = RANDOM ^ (RANDOM << 8) ^ 439 | (RANDOM << 16) ^ (RANDOM << 24) ^ 440 | (RANDOM << 32) ^ (RANDOM << 40) ^ 441 | (RANDOM << 48) ^ (RANDOM << 56) )) || true 442 | (( rand = min + ( rand % range + range ) % range )) || true 443 | print "${rand}" 444 | } 445 | 446 | php::rtrim() { 447 | local value="$*" 448 | print "${value%"${value##*[![:space:]]}"}" 449 | } 450 | 451 | php::internal::set_pivot() { 452 | local pivot_index=0 453 | (( pivot_index = left + (right - left) / 2 )) || true 454 | local x="${__sort_values[${left}]}" 455 | local y="${__sort_values[${pivot_index}]}" 456 | local z="${__sort_values[${right}]}" 457 | 458 | if [ "${x}" \< "${y}" ]; then 459 | if [ "${y}" \< "${z}" ]; then 460 | pivot="${y}" 461 | elif [ "${z}" \< "${x}" ]; then 462 | pivot="${x}" 463 | else 464 | pivot="${z}" 465 | fi 466 | else 467 | if [ "${z}" \< "${y}" ]; then 468 | pivot="${y}" 469 | elif [ "${x}" \< "${z}" ]; then 470 | pivot="${x}" 471 | else 472 | pivot="${z}" 473 | fi 474 | fi 475 | } 476 | 477 | php::internal::quick_sort() { 478 | local left="${1}" right="${2}" 479 | local i="${left}" j="${right}" 480 | if [ "${left}" -ge "${right}" ]; then return; fi 481 | local pivot='' 482 | php::internal::set_pivot 483 | while :; do 484 | while [ "${__sort_values[${i}]}" \< "${pivot}" ]; do 485 | (( i += 1 )) || true 486 | done 487 | while [ "${pivot}" \< "${__sort_values[${j}]}" ]; do 488 | (( j -= 1 )) || true 489 | done 490 | if [ "${i}" -ge "${j}" ]; then break; fi 491 | local value="${__sort_values[${i}]}" 492 | __sort_values["${i}"]="${__sort_values[${j}]}" 493 | __sort_values["${j}"]="${value}" 494 | (( i += 1 )) || true 495 | (( j -= 1 )) || true 496 | done 497 | (( i -= 1 )) || true 498 | (( j += 1 )) || true 499 | php::internal::quick_sort "${left}" "${i}" 500 | php::internal::quick_sort "${j}" "${right}" 501 | } 502 | 503 | php::sort() { 504 | local __sort_name="${1}" 505 | eval "local __sort_values=(\"\${${__sort_name}[@]}\")" 506 | local __sort_size="${#__sort_values[@]}" 507 | (( __sort_size -= 1 )) || true 508 | php::internal::quick_sort 0 "${__sort_size}" 509 | eval "${__sort_name}=(\"\${__sort_values[@]}\")" 510 | } 511 | 512 | # Usage: 513 | # php::str_replace 514 | php::str_replace() { 515 | local search="${1}" 516 | local replace="${2}" 517 | local subject="${3}" 518 | 519 | print "${subject//${search}/${replace}}" 520 | } 521 | 522 | php::strtolower() { 523 | print "$1" | tr '[A-Z]' '[a-z]' 524 | } 525 | 526 | php::strtoupper() { 527 | print "$1" | tr '[a-z]' '[A-Z]' 528 | } 529 | 530 | php::trim() { 531 | local value="$*" 532 | print "$(php::ltrim "$(php::rtrim "${value}")")" 533 | } 534 | 535 | # Shows a stack trace. Arguments are used as a message. 536 | imosh::stack_trace() { 537 | local max_depth="${#BASH_LINENO[@]}" 538 | local i=0 539 | if [ "$*" == '' ]; then 540 | echo 'imosh::stack_trace is called' >&2 541 | else 542 | echo "$*" >&2 543 | fi 544 | while (( i < max_depth - 1 )); do 545 | local lineno="${BASH_LINENO[$((i))]}" 546 | local file="${BASH_SOURCE[$((i+1))]}" 547 | local function="${FUNCNAME[$((i+1))]}" 548 | echo " at ${function} (${file}:${lineno})" >&2 549 | (( i += 1 )) || true 550 | done 551 | } 552 | 553 | # __IMOSH_FLAGS_TYPE_= 554 | # __IMOSH_FLAGS_DESCRIPTION_= 555 | # __IMOSH_FLAGS_ALIASES=(from:to ...) 556 | 557 | imosh::internal::convert_type() { 558 | local type="$1"; shift 559 | local value="$*" 560 | 561 | case "${type}" in 562 | int) 563 | if [[ "${value}" =~ ^-?[0-9]+$ ]]; then 564 | print "${value}" 565 | else 566 | return 1 567 | fi 568 | ;; 569 | string) 570 | print "${value}" 571 | ;; 572 | bool) 573 | case "${value}" in 574 | 1|T|t|[Tt]rue) print 1;; 575 | 0|F|f|[Ff]alse) print 0;; 576 | *) return 1;; 577 | esac 578 | ;; 579 | variant) 580 | print "${value}" 581 | ;; 582 | *) LOG FATAL "no such type: ${type}";; 583 | esac 584 | } 585 | 586 | imosh::internal::flag_type() { 587 | local name="$1" 588 | 589 | if [ "$#" -ne 1 ]; then 590 | LOG FATAL 'flag_type requires 1 arugument.' 591 | fi 592 | eval print '${__IMOSH_FLAGS_TYPE_'"${name}"'}' 593 | } 594 | 595 | imosh::internal::define_flag() { 596 | local type="$1"; shift 597 | 598 | local ARGS_alias='' ARGS_alias_flag=0 599 | eval "${IMOSH_PARSE_ARGUMENTS}" 600 | 601 | if [ "$#" -lt 3 ]; then 602 | LOG FATAL 'DEFINE_${type} requires 3+ arguments.' 603 | fi 604 | local name="$1"; shift 605 | local default_value="$1"; shift 606 | local description="$*" 607 | 608 | # Change the default value based on its corresponding environment variable. 609 | if php::isset "IMOSH_FLAGS_${name}"; then 610 | default_value="$(eval print "\${IMOSH_FLAGS_${name}}")" 611 | fi 612 | if ! imosh::internal::convert_type \ 613 | "${type}" "${default_value}" >/dev/null; then 614 | LOG FATAL "${type}'s default value should be ${type}: ${default_value}" 615 | fi 616 | default_value="$(imosh::internal::convert_type "${type}" "${default_value}")" 617 | if php::isset "__IMOSH_FLAGS_TYPE_${name}"; then 618 | LOG FATAL "already defined flag: ${name}" 619 | fi 620 | eval "FLAGS_${name}=$(imosh::shell_escape "${default_value}")" 621 | eval "__IMOSH_FLAGS_TYPE_${name}=${type}" 622 | if [ "${ARGS_alias}" != '' ]; then 623 | imosh::internal::define_flag "${type}" --alias_flag \ 624 | "${ARGS_alias}" "${default_value}" "${description}" 625 | eval "__IMOSH_FLAGS_ALIASES+=( \ 626 | $(imosh::shell_escape "${name}:${ARGS_alias}"))" 627 | fi 628 | if (( ! ARGS_alias_flag )); then 629 | local escaped_default_value='' 630 | case "${type}" in 631 | int) escaped_default_value="${default_value}";; 632 | bool) 633 | if (( default_value )); then 634 | escaped_default_value='true' 635 | else 636 | escaped_default_value='false' 637 | fi 638 | ;; 639 | *) escaped_default_value="$(imosh::shell_escape "${default_value}")";; 640 | esac 641 | eval "__IMOSH_FLAGS_DEFAULT_${name}=$( 642 | imosh::shell_escape "--${name}=${escaped_default_value}")" 643 | if [ "${ARGS_alias}" != '' ]; then 644 | description+=" (Alias: --${ARGS_alias})" 645 | fi 646 | eval "__IMOSH_FLAGS_DESCRIPTION_${name}=$( 647 | imosh::shell_escape "${description}")" 648 | __IMOSH_FLAGS+=("${name}") 649 | fi 650 | } 651 | 652 | DEFINE_string() { imosh::internal::define_flag string "$@"; } 653 | DEFINE_int() { imosh::internal::define_flag int "$@"; } 654 | DEFINE_bool() { imosh::internal::define_flag bool "$@"; } 655 | DEFINE_double() { imosh::internal::define_flag double "$@"; } 656 | 657 | imosh::internal::man() { 658 | echo ".TH ${0##*/} 1"; echo 659 | echo '.SH SYNOPSIS' 660 | echo ".B ${0##*/}"; echo '[\fIOPTIONS\fP] [\fIargs...\fP]'; echo 661 | echo '.SH OPTIONS'; 662 | for flag_name in "${__IMOSH_FLAGS[@]}"; do 663 | echo '.TP' 664 | echo -n '\fB' 665 | eval "echo -n \"\${__IMOSH_FLAGS_DEFAULT_${flag_name}}\"" 666 | echo '\fP' 667 | eval "echo \"\${__IMOSH_FLAGS_DESCRIPTION_${flag_name}}\"" 668 | echo 669 | done 670 | } 671 | 672 | imosh::internal::help() { 673 | echo "Usage: ${0} [options ...] [args ...]" 674 | echo "Options:" 675 | for flag_name in "${__IMOSH_FLAGS[@]}"; do 676 | eval "echo -n \" \${__IMOSH_FLAGS_DEFAULT_${flag_name}}:\"" 677 | eval "echo \" \${__IMOSH_FLAGS_DESCRIPTION_${flag_name}}\"" 678 | done 679 | } 680 | 681 | imosh::internal::init() { 682 | imosh::internal::parse_args flag "$@" 683 | if [ "${#IMOSH_ARGS[@]}" -ne 0 ]; then 684 | eval "${IMOSH_ARGS[@]}" 685 | fi 686 | if [ "${#__IMOSH_FLAGS_ALIASES[@]}" -ne 0 ]; then 687 | for alias in "${__IMOSH_FLAGS_ALIASES[@]}"; do 688 | eval "FLAGS_${alias%%:*}=\"\${FLAGS_${alias#*:}}\"" 689 | unset "FLAGS_${alias#*:}" 690 | done 691 | fi 692 | if [ "${#IMOSH_ARGS[@]}" -ne 0 ]; then 693 | readonly "${IMOSH_ARGS[@]}" 694 | fi 695 | if (( FLAGS_help )); then 696 | if [ -t 1 ]; then 697 | local man_file="${__IMOSH_CORE_TMPDIR}/man" 698 | imosh::internal::man >"${man_file}" 699 | man "${man_file}" 700 | else 701 | imosh::internal::help >&2 702 | fi 703 | exit 0 704 | fi 705 | } 706 | 707 | readonly IMOSH_INIT=' 708 | set -e -u 709 | imosh::internal::init "$@" 710 | if [ "${#IMOSH_ARGV[@]}" -ne 0 ]; then 711 | set -- "${IMOSH_ARGV[@]}" 712 | fi' 713 | 714 | __IMOSH_FLAGS=() 715 | __IMOSH_FLAGS_ALIASES=() 716 | 717 | DEFINE_bool --alias=h help false 'Print this help message and exit.' 718 | DEFINE_bool 'alsologtostderr' false \ 719 | 'Log messages go to stderr in addition to logfiles.' 720 | DEFINE_bool 'logtostderr' false \ 721 | 'Log messages go to stderr instead of logfiles.' 722 | 723 | imosh::mktemp() { 724 | TMPDIR="${TMPDIR%/}" 725 | export IMOSH_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/imosh.XXXXXX")" 726 | if [ "${IMOSH_TMPDIR}" == '' -o "${IMOSH_TMPDIR}" == '/' ]; then 727 | LOG FATAL 'failed to create a temporary directory.' 728 | fi 729 | 730 | export __IMOSH_CORE_TMPDIR="${IMOSH_TMPDIR}/.imosh" 731 | mkdir "${__IMOSH_CORE_TMPDIR}" 732 | imosh::on_exit "rm -rf ${IMOSH_TMPDIR}" 733 | } 734 | 735 | TMPDIR="${TMPDIR:-/tmp}" 736 | export TMPDIR="${TMPDIR%/}" 737 | imosh::mktemp 738 | 739 | __IMOSH_PHP_EXECUTER_PID='' 740 | 741 | php::internal::kill() { 742 | if (( FLAGS_disown_php )); then 743 | if [ "${__IMOSH_PHP_EXECUTER_PID}" = "$$" ]; then 744 | # Make sure that the target process exists. 745 | if kill -0 "$(cat "${__IMOSH_PHP_PID}")" 2>/dev/null; then 746 | php::internal::run 'exit(0);' 747 | kill -TERM "$(cat "${__IMOSH_PHP_PID}")" 2>/dev/null 748 | fi 749 | fi 750 | fi 751 | } 752 | 753 | php::internal::run() { 754 | local __php_code="$1" 755 | local __php_name='' 756 | if [ "$#" -ge 2 ]; then __php_name="$2"; fi 757 | 758 | local __php_line __php_return_code 759 | local __php_new_line=$'\n' 760 | __php_code="${__php_code//@/@@}" 761 | __php_code="${__php_code//${__php_new_line}/@n}" 762 | if (( FLAGS_disown_php )); then 763 | php::internal::start 764 | printf "%s\n" "${__php_code}" >&111 765 | read __php_line <&110 766 | read __php_return_code <&110 767 | else 768 | while :; do 769 | read __php_line 770 | read __php_return_code 771 | break 772 | done < <(printf "%s\n" "${__php_code}" | php::internal::start) 773 | fi 774 | 775 | if [ "${__php_name}" != '' ]; then 776 | eval "${__php_name}=${__php_line//@/\\}" 777 | fi 778 | if [ "${__php_return_code}" == '' ]; then return 1; fi 779 | return "${__php_return_code}" 780 | } 781 | 782 | php::internal::start() { 783 | if (( FLAGS_disown_php )); then 784 | if [ "${__IMOSH_PHP_EXECUTER_PID}" = "$$" ]; then 785 | # Make sure that the target process exists. 786 | if kill -0 "$(cat "${__IMOSH_PHP_PID}")" 2>/dev/null; then 787 | return; 788 | fi 789 | fi 790 | exec 111>&- 110<&- 791 | __IMOSH_PHP_STDIN="$(mktemp "${__IMOSH_CORE_TMPDIR}/php_stdin.XXXXXX")" 792 | __IMOSH_PHP_STDOUT="$(mktemp "${__IMOSH_CORE_TMPDIR}/php_stdout.XXXXXX")" 793 | __IMOSH_PHP_EXECUTER_PID="$$" 794 | __IMOSH_PHP_PID="$(mktemp "${__IMOSH_CORE_TMPDIR}/php_pid.XXXXXX")" 795 | rm "${__IMOSH_PHP_STDIN}" "${__IMOSH_PHP_STDOUT}" 796 | fi 797 | local php_script="$(mktemp "${__IMOSH_CORE_TMPDIR}/php_script.XXXXXX")" 798 | cat << 'EOM' >"${php_script}" 799 | '@r', "\n" => '@n', '\\' => '@@', 805 | '"' => '@x22', "'" => "@x27", '@' => '@x40'); 806 | 807 | while (($line = fgets(STDIN)) !== FALSE) { 808 | $line = strtr($line, array('@@' => '@', '@n' => "\n")); 809 | ob_start(); 810 | $value = eval($line); 811 | $output = ob_get_clean(); 812 | echo "\$'" . strtr($output, $translate) . "'\n"; 813 | echo intval($value) . "\n"; 814 | if (isset($argv[1]) && $argv[1] == 'once') exit(0); 815 | } 816 | 817 | EOM 818 | LOG INFO 'Starting to run php...' 819 | if (( ! FLAGS_disown_php )); then 820 | php "${php_script}" once 821 | return 822 | fi 823 | mkfifo "${__IMOSH_PHP_STDIN}" 824 | mkfifo "${__IMOSH_PHP_STDOUT}" 825 | bash -c "nohup php '${php_script}' \ 826 | <'${__IMOSH_PHP_STDIN}' \ 827 | >'${__IMOSH_PHP_STDOUT}' & 828 | echo \$! >'${__IMOSH_PHP_PID}'" 829 | LOG INFO "Opening PHP's STDIN..." 830 | exec 111>"${__IMOSH_PHP_STDIN}" 831 | LOG INFO "Opening PHP's STDOUT..." 832 | exec 110<"${__IMOSH_PHP_STDOUT}" 833 | } 834 | 835 | DEFINE_bool disown_php false 'Disown a PHP process.' 836 | 837 | php::stop() { 838 | php::internal::kill 839 | } 840 | 841 | php::start() { 842 | if (( FLAGS_disown_php )); then 843 | php::internal::start 844 | fi 845 | } 846 | 847 | LOG INFO 'imosh is ready.' 848 | 849 | --------------------------------------------------------------------------------