私はこのような考えをしました。特定の条件を確認するbashスクリプトを実行し、それを使用してffmpeg
ディレクトリ内のすべてのビデオをすべての形式からすべての形式に変換すると、.mkv
うまく機能します!
問題は、for file in
ループが再帰的に機能しないことです。https://stackoverflow.com/questions/4638874/how-to-loop-through-a-directory-recursively)
しかし、私は「パイプライン」をほとんど理解しておらず、例を見て不確実性を一部解消できることを期待しています。
このシーンが頭の中に浮かび上がったのですが、理解するのに多くの役に立ったと思います。
次のbashスクリプトの断片があるとしましょう。
for file in *.mkv *avi *mp4 *flv *ogg *mov; do
target="${file%.*}.mkv"
ffmpeg -i "$file" "$target" && rm -rf "$file"
done
これは、現在のディレクトリの*.mkv *avi *mp4 *flv *ogg *mov
拡張子を検索し、出力を宣言してから.mkv
ソースファイルを削除し、出力を元のビデオと同じフォルダに保存することです。
これを再帰的に実行するようにどのように変換できますか?を使用している場合、
find
変数をどこで宣言しますか$file
?どこに報告する必要がありますか$target
?find
本当にすべて一行でしょうか?$file
条件付きチェックを実行する必要があるため、ファイルを変数に渡す必要があります。そして、(1)が成功すると仮定すると、「出力は元のビデオを持つ同じフォルダに保存する必要があります」という要件が満たされているかどうかを確認できますか?
答え1
POSIXを使って以下を見つけてください。
find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o \
-name '*ogg' -o -name '*mov' \) -exec sh -c '
for file do
target="${file%.*}.mkv"
echo ffmpeg -i "$file" "$target"
done' sh {} +
echo
使用したいコマンドに置き換えます。
GNU検索またはBSD検索がある場合は、次のものを使用できます-regex
。
find . -regex '.*\.\(mkv\|avi\|mp4\|flv\|ogg\|mov\)'
答え2
次のコードがあります。
for file in *.mkv *avi *mp4 *flv *ogg *mov; do target="${file%.*}.mkv" ffmpeg -i "$file" "$target" && rm -rf "$file" done
現在のディレクトリで実行されます。これを再帰プロセスに切り替えるには、いくつかのオプションがあります。最も簡単な(IMO)提案どおりに使用することですfind
。 forの構文はfind
非常に「Unixに似ています」が、ここでの原則は、各パラメータがANDまたはOR条件と共に適用できることです。ここで私たちはこのファイル名が一致するか、このファイル名が一致する場合は印刷します。ファイル名パターンは引用符で囲まれているため、シェルはこれを取得できません(シェルはすべての引用符で囲まれていないパターンを拡張する役割を果たすため、引用符で囲まれていないパターンがあり、そのパターンが*.mp4
現在のjaneeyre.mp4
ディレクトリにある場合、シェルはアイテム*.mp4
を置き換えますそして、find
ユーザーは-name janeeyre.mp4
あなたが望むものの代わりにそれを見るでしょう;もしあなたが1つ以上の名前と一致するならば、-name *.mp4
それはより悪くなります*.mp4
...) \
、あなたが望むならば'('
:)。
find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o -name '*ogg' -o -name '*mov' \) -print
while
出力は、各ファイルを順番に処理するループの入力に供給する必要があります。
while IFS= read file ## IFS= prevents "read" stripping whitespace
do
target="${file%.*}.mkv"
ffmpeg -i "$file" "$target" && rm -rf "$file"
done
残っているのは、|
出力がfind
ループの入力になるように2つの部分を一緒にパイプすることですwhile
。
このコードをテストするときは、プレフィックスを付けることをお勧めしffmpeg
ますrm
。echo
会議実行され、どのパスが使用されるか。
echo
テストを推奨する内容を含む最終結果は次のとおりです。
find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o -name '*ogg' -o -name '*mov' \) -print |
while IFS= read file ## IFS= prevents "read" stripping whitespace
do
target="${file%.*}.mkv"
echo ffmpeg -i "$file" "$target" && echo rm -rf "$file"
done
答え3
パイプなしのサンプルスニペット(パスをパラメータとして提供すると仮定):
#!/bin/bash
backup_dir=/backup/
OIFS="$IFS"
IFS=$'\n'
files="$(find "$1" -type f -name '*.mkv' -or -name '*.avi' -or -name '*.mp4' -or -name '*.ogg' -or -name '*.mov' -or -name '*.flv')"
for f in $files; do
# get path
d="${f%/*}"
# get filename
b="$(basename "$f")"
ttarget="${b%.*}.mkv"
# this is your final target
target="$d/$ttarget"
echo $target
# mv $f "$backup_dir"
done
IFS="$OIFS"
シェルはこの変数を読み取り、デフォルトIFS
設定は(space
、、、tab
)です。newline
次に、出力の各文字を調べますfind
。したがって、1つを見つけると、space
ファイル名の終わりだと思います(スペースを含むファイル、たとえば「Sin City.avi」は、「Sin」と「City.avi」という2つのファイルとして扱われます)。したがって、IFS=$'\n' を使用して入力をnewlines
。最後に、変数IFS
に保存されている古い$OIFS
(デフォルト)変数を復元します。
あるいは、コメントで提案したように、より良いアプローチは次のとおりです。
#!/bin/bash
backup_dir=/backup/
find "$1" -type f \( -name '*.mkv' -or -name '*.avi' -or -name '*.mp4' -or -name '*.ogg' -or -name '*.mov' -or -name '*.flv' \) -print0 | while IFS= read -r -d '' f
do
# get path
d="${f%/*}"
# get filename
b="$(basename "$f")"
ttarget="${b%.*}.mkv"
# this is your final target
target="$d/$ttarget"
echo $target
# mv $f "$backup_dir"
done
答え4
Unixへようこそ:)
主な質問への回答で扱わないいくつかの小さな質問:
シェルスクリプトには、空白のファイル名が原因で多くの問題が発生するため、少し粗い部分があります。ほとんどすべてのファイル名は改行文字で区切られています(幸いにもわざわざこれを行う人はいません)。グローバル文字(たとえば、、および)を含む[
ファイル]
名*
も問題になることがあります。時には、次の基準を満たす読みにくいシェルコードを書くことが価値がない場合があります。WoolwichのBashGuide、自分が使用したり、ワンタイムで使用したい場合は、ファイル名を知ることは当然です。
変数を宣言する場所:
シェル変数は宣言する必要はありません。 Bashでは、shopt -o nounset
参照変数と未設定変数をエラーに設定できますが、これは宣言されていないものとは異なります。変数を設定しないと便利です。シェル関数内ですべての一時変数を宣言して、local foo bar baz;
シェル環境を変数に乱すか、または悪くは同じ名前の呼び出し元の変数を踏まないようにすることをお勧めします。
私は「パイプライン」をほとんど理解していません。
シェルを使用する場合、一括データ転送は、データを標準出力に印刷することによって行われます。パイプはそのデータを標準入力にデータを読み取る別のプログラムに送信します(そして通常は標準出力に何かを印刷します)。コマンド置換を使用して、出力をシェル変数としてキャプチャできます$()
。たとえばfor i in $( locate foo | grep bar );do echo "$i"; done
。 (多くのシェルコードのように注意しないと、ファイル名が空白で壊れます。read
信頼できるスクリプトを作成するにはこれを使用します。) locate
印刷、grep
読み取り、印刷、およびシェルの読み取りgrep
(シェルは次のように出力を取得します。grepを実行しgrep
、その出力をシェルによって生成されたパイプの入力に接続します(シェルはパイプの出力を読み取ります)。
パイプはプログラムがファイルに書き込むかのように動作する方法ですが、実際には小さなバッファに書き込むことです。パイプから読み取られるプロセスは、データが利用可能なときにシステムコールを返します。read(2)
これは、パイプのもう一方の端にデータが書き込まれたときにのみ発生します。
シェルの|
その他$()
の構文要素は、プログラム間、プログラムとシェル間のパイプ接続を確立する方法をシェルに伝えるために使用されます。
シェルプログラミングの悪いイディオムを学ぶのは簡単です。多くの明らかなものと古い作業方法は、奇妙なファイル名によって崩れる可能性のあるトラップを隠しているからです。例を見るhttp://mywiki.wooledge.org/BashFAQ/001。
入力するのが難しくない限り、奇妙なファイル名をめちゃくちゃにする方法を学ぶよりも、最初から安全なスクリプト方法を学ぶ方が良いでしょう。 :)
多くのGNUユーティリティには、レコード区切り文字としてASCII NUL(0バイト、ファイル名、またはテキストには現れません)を使用する-0オプションがあります。たとえば、検索出力の1つの「行」をソートされた入力の複数行に変換することなくfind
データを転送できます。sort
bashには区切られた行を読み取る方法がないため、データをシェル変数に保存しようとすると、これはあまり役に立ちません\0
。 (これはIFSに有効な値ではないと思います。)
とにかく、シェルがデータをコードで処理しないようにするのは、単語の区切りが実際に必要でない限り、可能なすべてを常に二重引用符で囲む理由です。複雑なシェルコードを見るのが面倒な場合は、bashコンプリートコードを見てください。 (プログラム可能な完成を処理し、ls --colo => --color
解凍のために* .zipファイルを完成または完成するなど、巧妙なタスクを実行できます。)set -x
そして、タブを押します。 (実行追跡をオフにするには+ xを設定します。)
Re:forループ:*.mkv
パターンの1つとして、これらの入力ファイルにsource = destを提供します。 ffmpeg
各ファイルの出力ファイルを上書きするように求められます。
また、オーディオを実際にトランスコードする必要がありますか? -c:a copy
良い考えかもしれません。一般に、ビデオビットレートがより重要である。-preset slow
(またはslower
)を使用すると、veryslow
ビットレートごとにより高い品質を得ることができますが、CPU使用率がより多く消費されます。また-crf 20
(デフォルト値23)。 https://trac.ffmpeg.org/wiki/Encode/H.264。 Bashスクリプトとは何の関係もないので、すでに知っていて無視してください。しかし、もしかして... -c:v libx264
mkvで出力するとき:Pがデフォルト値なので大丈夫です。