多くの jpeg ファイル (500000 以上) を含むディレクトリ ツリーをチェックして、それらが破損していないことを確認するための基本的なプログラムを作成しました (ファイルの約 3-5% が何らかの形で破損しているようです)。ファイル (破損したものも含む) を検索し、その情報をデータベースに保存します。
問題の jpeg ファイルは Windows システム上にあり、cifs 経由で Linux ボックスにマウントされています。ほとんどの場合、サイズは約 4 メガバイトですが、わずかに大きいか小さいものもあります。
プログラムを実行すると、しばらくはかなりうまく機能しているように見えますが、その後、以下のエラーで失敗します。これは、約 1100 個のファイルを処理した後でした (エラーは、4.5 MB のファイルを開こうとしたときに問題が発生したことを示していました)。
これで、このエラーをキャッチして続行または再試行できることを理解しましたが、そもそもなぜそれが発生しているのか、そしてキャッチして再試行することで実際に問題が解決するのか、または再試行でスタックするだけなのか (もちろん再試行を制限しない限り、ファイルはスキップされます)。
これを実行するために、debian システムで「Python 2.7.5+」を使用しています。システムには少なくとも 4 ギガ (おそらく 8) の RAM があり、top はスクリプトが実行中の任意の時点で RAM の 1% 未満および CPU の 3% 未満を使用していると報告しています。同様に、このスクリプトが実行する jpeginfo も同様に少量のメモリと CPU を使用しています。
ファイルを読み取るときにメモリを使いすぎないようにするために、別の質問に対するこの回答で与えられたアプローチを採用しました: https://stackoverflow.com/a/1131255/289545
また、「jpeginfo」コマンドが「[OK]」応答を探している while ループになっていることにも注意してください。これは、"jpeginfo" がファイルを見つけられないと判断した場合に 0 を返し、subprocess.check_output 呼び出しによってエラー状態と見なされないためです。
最初の試行で jpeginfo が特定のファイルを見つけられなかったように見えるという事実が関連している可能性があるかどうか疑問に思いましたが (関連していると思われます)、返されたエラーは、ファイルが見つからないというよりもメモリを割り当てられないことを示しています。
エラー:
Traceback (most recent call last):
File "/home/m3z/jpeg_tester", line 95, in <module>
main()
File "/home/m3z/jpeg_tester", line 32, in __init__
self.recurse(self.args.dir, self.scan)
File "/home/m3z/jpeg_tester", line 87, in recurse
cmd(os.path.join(root, name))
File "/home/m3z/jpeg_tester", line 69, in scan
with open(filepath) as f:
IOError: [Errno 12] Cannot allocate memory: '/path/to/file name.jpg'
完全なプログラム コード:
1 #!/usr/bin/env python
2
3 import os
4 import time
5 import subprocess
6 import argparse
7 import hashlib
8 import oursql as sql
9
10
11
12 class main:
13 def __init__(self):
14 parser = argparse.ArgumentParser(description='Check jpeg files in a given directory for errors')
15 parser.add_argument('dir',action='store', help="absolute path to the directory to check")
16 parser.add_argument('-r, --recurse', dest="recurse", action='store_true', help="should we check subdirectories")
17 parser.add_argument('-s, --scan', dest="scan", action='store_true', help="initiate scan?")
18 parser.add_argument('-i, --index', dest="index", action='store_true', help="should we index the files?")
19
20 self.args = parser.parse_args()
21 self.results = []
22
23 if not self.args.dir.startswith("/"):
24 print "dir must be absolute"
25 quit()
26
27 if self.args.index:
28 self.db = sql.connect(host="localhost",user="...",passwd="...",db="fileindex")
29 self.cursor = self.db.cursor()
30
31 if self.args.recurse:
32 self.recurse(self.args.dir, self.scan)
33 else:
34 self.scan(self.args.dir)
35
36 if self.db:
37 self.db.close()
38
39 for line in self.results:
40 print line
41
42
43
44 def scan(self, dirpath):
45 print "Scanning %s" % (dirpath)
46 filelist = os.listdir(dirpath)
47 filelist.sort()
48 total = len(filelist)
49 index = 0
50 for filen in filelist:
51 if filen.lower().endswith(".jpg") or filen.lower().endswith(".jpeg"):
52 filepath = os.path.join(dirpath, filen)
53 index = index+1
54 if self.args.scan:
55 try:
56 procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip()
57 while "[OK]" not in procresult:
58 time.sleep(0.5)
59 print "\tRetrying %s" % (filepath)
60 procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip()
61 print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,procresult)
62 except subprocess.CalledProcessError, e:
63 os.renames(filepath, os.path.join(dirpath, "dodgy",filen))
64 filepath = os.path.join(dirpath, "dodgy", filen)
65 self.results.append("Trouble with: %s" % (filepath))
66 print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,e.output.strip())
67 if self.args.index:
68 sha1 = hashlib.sha1()
69 with open(filepath) as f:
70 while True:
71 data = f.read(8192)
72 if not data:
73 break
74 sha1.update(data)
75 sqlcmd = ("INSERT INTO `index` (`sha1`,`path`,`filename`) VALUES (?, ?, ?);", (buffer(sha1.digest()), dirpath, filen))
76 self.cursor.execute(*sqlcmd)
77
78
79 def recurse(self, dirpath, cmd, on_files=False):
80 for root, dirs, files in os.walk(dirpath):
81 if on_files:
82 for name in files:
83 cmd(os.path.join(root, name))
84 else:
85 cmd(root)
86 for name in dirs:
87 cmd(os.path.join(root, name))
88
89
90
91
92
93
94 if __name__ == "__main__":
95 main()