Dosyalar çoğunlukla iki farklı sınıfa ayrılır: Metin dosyaları ve ikili dosyalar. Eğer bir dosyayı bir metin düzenleyici ile açtığınızda herhangi bir dilde yazılmış ‘okunabilir’ bir metin görüyorsanız, o dosya bir metin dosyasıdır.
İkili dosyalar ise, metin dosyalarının aksine, metin düzenleyicilerle açılamayan, açılmaya çalışıldığında ise çoğunlukla anlamsız karakterler içeren bir dosya türüdür. Resim dosyaları, müzik dosyaları, video dosyaları, MS Office dosyaları, LibreOffice dosyaları, OpenOffice dosyaları, vb. ikili dosyalara örnektir.
İşin aslı sadece tek bir dosya türü vardır: İkili dosyalar (binary files). Yani bilgisayarlar açısından bütün dosyalar, içlerinde ne olursa olsun, birer ikili dosyadır ve içlerinde sadece 0’ları ve 1’leri barındırır.
Hatırlarsanız metin dosyalarını açmak için temel olarak şöyle bir komut kullanıyorduk:
f = open(dosya_adı, 'r')
Bir metin dosyasını değil de, ikili bir dosyayı açmak için ise şu komutu kullanacağız:
f = open(dosya_adı, 'rb')
Metin dosyaları ile ikili dosyalar arasında önemli bir fark bulunur: Bir metin dosyasındaki ufak değişiklikler dosyanın okunamaz hale gelmesine yol açmaz. Olabilecek en kötü şey, değiştirilen karakterin okunamaz hale gelmesidir. Ancak ikili dosyalarda ufak değişiklikler dosyanın tümden bozulmasına yol açabilir.
Yani eğer siz ikili bir dosyayı ‘rb’ yerine sadece ‘r’ gibi bir kiple açarsanız dosyanın bozulmasına yol açabilirsiniz. İkili bir dosyayı ‘rb’ (veya ‘wb’, ‘ab’, ‘xb’, vb.) gibi bir kipte açtığınızda Python satır sonlarına herhangi bir değiştirme-dönüştürme işlemi uygulamaz. Böylece dosya bozulma riskiyle karşı karşıya kalmaz.
İkili Dosyalarla Örnekler
Gelin isterseniz bu noktada birkaç örnek verelim.
PDF Dosyalarından Bilgi Alma
Tıpkı resim, müzik ve video dosyaları gibi, PDF dosyaları da birer ikili dosyadır. O halde hemen önümüze bir PDF dosyası alalım ve bu dosyayı okuma kipinde açalım:
<strong>>>> </strong>f = open("falanca.pdf", "rb"
Şimdi de bu dosyadan 10 baytlık bir veri okuyalım:
<strong>>>> </strong>f.read(10)
b'%PDF-1.3\n4'
Bu çıktıda gördüğünüz ‘b’ işaretine şimdilik takılmayın. Biz bu harfin, elimizdeki verinin bayt türünde bir veri olduğunu gösteren bir işaret olduğunu bilelim yeter.
Gördüğünüz gibi, bir PDF dosyasının ilk birkaç baytını okuyarak hem dosyanın bir PDF belgesi olduğunu teyit edebiliyoruz, hem de bu PDF belgesinin, hangi PDF sürümü ile oluşturulduğunu anlayabiliyoruz.
Eğer biz bu belgeyi bir ikili dosya olarak değil de bir metin dosyası olarak açmaya çalışsaydık şöyle bir hata alacaktık:
>>> f = open("falanca.pdf")
>>> okunan = f.read()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python33\lib\encodings\cp1254.py", line 23, in decode
return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 527: char
acter maps to <undefined>
Gelin bu PDF belgesi üzerinde biraz daha çalışalım.
PDF belgelerinde, o belge hakkında bazı önemli bilgiler veren birtakım özel etiketler bulunur. Bu etiketler şunlardır:
Etiket | Anlamı |
/Creator | Belgeyi oluşturan yazılım |
/Producer | Belgeyi PDF’e çeviren yazılım |
/Title | Belgenin başlığı |
/Author | Belgenin yazarı |
/Subject | Belgenin konusu |
/Keywords | Belgenin anahtar kelimeleri |
/CreationDate | Belgenin oluşturulma zamanı |
/ModDate | Belgenin değiştirilme zamanı |
Bu etiketlerin tamamı bütün PDF dosyalarında tanımlı değildir. Ama özellikle /Producer etiketi her PDF dosyasında bulunur.
Şimdi örnek olması bakımından elimize bir PDF dosyası alalım ve bunu güzelce okuyalım:
>>> f = open("falanca.pdf", "rb")
>>> okunan = f.read()
Şimdi de /Producer ifadesinin dosya içinde geçtiği noktanın sıra numarasını bulalım.
>>> producer_index = okunan.index(b"/Producer")
Burada /Producer ifadesinin başına ‘b’ harfini yerleştirmeyi unutmuyoruz. Çünkü şu anda yaptığımız işlem ikili bir dosya içinde geçen birtakım baytları arama işlemidir.
producer_index değişkeni, ‘/Producer’ ifadesinin ilk baytının dosya içindeki konumunu tutuyor. Kontrol edelim:
>>> producer_index
4077883
Bu değerin gerçekten de ‘/Producer’ ifadesinin ilk baytını depoladığını teyit edelim:
>>> okunan[producer_index]
47
Daha önce de dediğimiz gibi, bilgisayarlar yalnızca sayıları görür. Bu sayının hangi karaktere karşılık geldiğini bulmak için chr()
fonksiyonundan yararlanabilirsiniz:
>>> chr(okunan[producer_index])
'/'
Gördüğünüz gibi, gerçekten de producer_index değişkeni ‘/Producer’ ifadesinin ilk baytının dosya içindeki konumunu gösteriyor. Biz bu konumu ve bu konumun 50-60 bayt ötesini sorgularsak, PDF belgesini üreten yazılımın adına ulaşabiliriz.
>>> okunan[producer_index:producer_index+50]
b'/Producer (Acrobat Distiller 2.0 for Macintosh)\r/T'
Hatta eğer bu çıktı üzerine split()
metodunu uygularsak, çıktıyı daha kullanışlı bir hale getirebiliriz:
>>> producer = okunan[producer_index:producer_index+50].split()
>>> producer
[b'/Producer', b'(Acrobat', b'Distiller', b'2.0', b'for', b'Macintosh)', b'/T']
Bu şekilde, ihtiyacımız olan bilginin istediğimiz parçasına kolayca ulaşabiliriz:
>>> producer[0]
b'/Producer'
>>> producer[1]
b'(Acrobat'
>>> producer[1:3]
[b'(Acrobat', b'Distiller']
Elbette bu yöntem, bir PDF dosyasından gerekli etiketleri almanın en iyi yöntemi değildir. Ama henüz Python bilgimiz bu kadarını yapmamıza müsaade ediyor. Ancak yine de, yukarıda örnek, bir ikili dosyadan nasıl veri alınacağı konusunda size iyi bir fikir verecektir.
Resim Dosyalarının Türünü Tespit Etme
Bir resim dosyasının hangi türde olduğunu anlayabilmek için ilgili dosyanın ilk birkaç baytını okumanız yeterlidir. Bu birkaç bayt içinde o resim dosyasının türüne dair bilgileri bulabilirsiniz.
JPEG
JPEG biçimi ile ilgili bilgileri http://www.jpeg.org adresinde bulabilirsiniz. JPEG dosya biçimini daha iyi anlamanızı sağlayacak yardımcı kaynak ise şudur:
Yukarıda verdiğimiz adreslerdeki bilgilere göre bir JPEG dosyasının en başında şu veriler bulunur:
FF D8 FF E0 ? ? 4A 46 49 46 00
Ancak eğer ilgili JPEG dosyası bir CANON fotograf makinesi ile oluşturulmuşsa bu veri dizisi şöyle de olabilir:
FF D8 FF E0 ? ? 45 78 69 66 00
Burada soru işareti ile gösterdiğimiz kısım, yani dosyanın 5. ve 6. baytları farklı JPEG dosyalarında birbirinden farklı olabilir.
Dolayısıyla bir JPEG dosyasını başka resim dosyalarından ayırabilmek için dosyanın ilk dört baytına bakmamız, sonraki iki baytı atlamamız ve bunlardan sonra gelen beş baytı kontrol etmemiz yeterli olacaktır.
Yukarıda gördükleriniz birer on altılı (hex) sayıdır. Bunlar onlu düzende sırasıyla şu sayılara karşılık gelir:
255 216 255 224 ? ? 74 70 73 70 0
255 216 255 224 ? ? 45 78 69 66 0 #canon
Bu diziler içinde özellikle şu dört sayı bizi yakından ilgilendiriyor:
74 70 73 70
45 78 69 66 #canon
Bu sayılar sırasıyla ‘J’, ‘F’, ‘I’, ‘F’ ve ‘E’, ‘x’, ‘i’, ‘f’ harflerine karşılık gelir. Yani bir JPEG dosyasını ayırt edebilmek için ilgili dosyanın 7-10 arası baytlarının ne olduğuna bakmamız yeterli olacaktır. Eğer bu aralıkta ‘JFIF’ veya ‘Exif’ ifadeleri varsa, o dosya bir JPEG dosyasıdır. Buna göre şöyle bir kod yazabiliriz:
f = open(dosya_adı, 'rb')
data = f.read(10)
if data[6:11] in [b"JFIF", b"Exif"]:
print("Bu dosya JPEG!")
else:
print("Bu dosya JPEG değil!")
Burada herhangi bir resim dosyasının ilk on baytını okuduk öncelikle:
data = f.read(10)
Daha sonra okuduğumuz kısmın 7 ila 10. baytları arasında kalan verinin ne olduğuna bakıyoruz:
if data[6:11] in [b"JFIF", b"Exif"]:
...
Eğer ilgili aralıkta ‘JFIF’ veya ‘Exif’ baytları yer alıyorsa bu dosyanın bir JPEG dosyası olduğuna karar veriyoruz.
Yukarıdaki kodları elinizdeki bir JPEG dosyasına uygulayarak kendi kendinize pratik yapabilirsiniz.
Mesela benim elimde d1.jpg, d2.jpg ve d3.jpeg adlı üç farklı JPEG dosyası var:
dosyalar = ["d1.jpg", "d2.jpg", "d3.jpeg"]
Bu dosyaların ilk onar baytını okuyorum:
for f in dosyalar:
okunan = open(f, 'rb').read(10)
print(okunan)
Buradan şu çıktıyı alıyorum:
d1.jpg b'\xff\xd8\xff\xe0\x00\x10JFIF'
d2.jpg b'\xff\xd8\xff\xe1T\xaaExif'
d3.jpeg b'\xff\xd8\xff\xe0\x00\x10JFIF'
Gördüğünüz gibi bu çıktılar yukarıda JPEG dosyalarına ilişkin olarak verdiğimiz bayt dizilimi ile uyuşuyor. Mesela ilk dosyayı ele alalım:
d1.jpg b'\xff\xd8\xff\xe0\x00\x10JFIF'
Burada şu baytlar var:
\xff \xd8 \xff \xe0 \x00 \x10 J F I F
Sayıların başındaki \x işaretleri bunların birer on altılı sayı olduğunu gösteren bir işarettir. Dolayısıyla yukarıdakileri daha net inceleyebilmek için şöyle de yazabiliriz:
ff d8 ff e0 00 10 J F I F
Şimdi de ikinci dosyanın çıktısını ele alalım:
d2.jpg b'\xff\xd8\xff\xe1T\xaaExif'
Burada da şu baytlar var:
ff d8 ff e1T aa E x i f
İşte dosyaların türünü ayırt etmek için bu çıktılardaki son dört baytı kontrol etmemiz yeterli olacaktır:
for f in dosyalar:
okunan = open(f, 'rb').read(10)
if okunan[6:11] in [b'JFIF', b'Exif']:
print("Evet {} adlı dosya bir JPEG!".format(f))
else:
print("{} JPEG değil!".format(f))
Bu kodları elinizde bulunan farklı türdeki dosyalara uygulayarak, aldığınız çıktıları inceleyebilirsiniz.
PNG
PNG dosya biçiminin teknik şartnamesine http://www.libpng.org/pub/png/spec/ adresinden ulaşabilirsiniz.
Ayrıca yardımcı kaynak olarak da http://www.fileformat.info/format/png/egff.htm adresindeki belgeyi kullanabilirsiniz.
Şartnamade, http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature sayfasındaki bilgiye göre bir PNG dosyasının ilk 8 baytı mutlaka aşağıdaki değerleri içeriyor:
onlu değer | 137 80 78 71 13 10 26 10 |
on altılı değer | 89 50 4e 47 0d 0a 1a 0a |
karakter değeri | \211 P N G \r \n \032 \n |
Şimdi elimize herhangi bir PNG dosyası alarak bu durumu teyit edelim:
>>> f = open("falanca.png", "rb")
>>> okunan = f.read(8)
Şartnamede de söylendiği gibi, bir PNG dosyasını öteki türlerden ayırt edebilmek için dosyanın ilk 8 baytına bakmamız yeterli olacaktır. O yüzden biz de yukarıdaki kodlarda sadece ilk 8 baytı okumakla yetindik.
>>> okunan
b'\x89PNG\r\n\x1a\n'
Bu değerin, şartnamedeki karakter değeri ile aynı olup olmadığını sorgulayarak herhangi bir dosyanın PNG olup olmadığına karar verebilirsiniz:
>>> okunan == b"\211PNG\r\n\032\n"
True
Dolayısıyla şuna benzer bir kod yazarak, farklı resim dosyalarının türünü tespit edebilirsiniz:
for f in dosyalar:
okunan = open(f, 'rb').read(10)
if okunan[6:11] in [b'JFIF', b'Exif']:
print("{} adlı dosya bir JPEG!".format(f))
elif okunan[:8] == b"\211PNG\r\n\032\n":
print("{} adlı dosya bir PNG!".format(f))
else:
print("Türü bilinmeyen dosya: {}".format(f))
Bu kodlarda bir resim dosyasının ilk 10 baytını okuduk. 7-11 arası baytların içinde ‘JFIF’ veya ‘Exif’ baytları varsa o dosyanın bir JPEG olduğuna; ilk 8 bayt b”211PNGrn032n” adlı bayt dizisine eşitse de o dosyanın bir PNG olduğuna karar veriyoruz.
GIF
GIF şartnamesine http://www.w3.org/Graphics/GIF/spec-gif89a.txt adresinden ulaşabilirsiniz.
Bir dosyanın GIF olup olmadığına karar verebilmek için ilk 3 baytını okumanız yeterli olacaktır. Standart bir GIF dosyasının ilk üç baytı ‘G’, ‘I’ ve ‘F’ karakterlerinden oluşur.
Dosyanın sonraki 3 baytı ise GIF’in sürüm numarasını verir. 18.08.2020 itibariyle GIF standardının şu sürümleri bulunmaktadır:
- 87a – Mayıs 1987
- 89a – Temmuz 1989
Dolayısıyla standart bir GIF dosyasının ilk 6 baytı şöyledir:
‘GIF87a’ veya ‘GIF89a’
for f in dosyalar:
okunan = open(f, 'rb').read(10)
if okunan[6:11] in [b'JFIF', b'Exif']:
print("{} adlı dosya bir JPEG!".format(f))
elif okunan[:8] == b"\211PNG\r\n\032\n":
print("{} adlı dosya bir PNG!".format(f))
elif okunan[:3] == b'GIF':
print("{} adlı dosya bir GIF!".format(f))
else:
print("Türü bilinmeyen dosya: {}".format(f))
TIFF
TIFF şartnamesine http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf adresinden ulaşabilirsiniz. Bu şartnameye göre bir TIFF dosyası şunlardan herhangi biri ile başlar:
- ‘II’
- ‘MM’
Dolayısıyla, bir TIFF dosyasını tespit edebilmek için dosyanın ilk 2 baytına bakmanız yeterli olacaktır:
for f in dosyalar:
okunan = open(f, 'rb').read(10)
if okunan[6:11] in [b'JFIF', b'Exif']:
print("{} adlı dosya bir JPEG!".format(f))
elif okunan[:8] == b"\211PNG\r\n\032\n":
print("{} adlı dosya bir PNG!".format(f))
elif okunan[:3] == b'GIF':
print("{} adlı dosya bir GIF!".format(f))
elif okunan[:2] in [b'II', b'MM']:
print("{} adlı dosya bir TIFF!".format(f))
else:
print("Türü bilinmeyen dosya: {}".format(f))
BMP
BMP türündeki resim dosyalarına ilişkin bilgi için http://www.digitalpreservation.gov/formats/fdd/fdd000189.shtml adresine başvurabilirsiniz.
Buna göre, BMP dosyaları ‘BM’ ile başlar. Yani:
for f in dosyalar:
okunan = open(f, 'rb').read(10)
if okunan[6:11] in [b'JFIF', b'Exif']:
print("{} adlı dosya bir JPEG!".format(f))
elif okunan[:8] == b"\211PNG\r\n\032\n":
print("{} adlı dosya bir PNG!".format(f))
elif okunan[:3] == b'GIF':
print("{} adlı dosya bir GIF!".format(f))
elif okunan[:2] in [b'II', b'MM']:
print("{} adlı dosya bir TIFF!".format(f))
elif okunan[:2] in [b'BM']:
print("{} adlı dosya bir BMP!".format(f))
else:
print("Türü bilinmeyen dosya: {}".format(f))
Gördüğünüz gibi ikili dosyalar, baytların özel bir şekilde dizildiği ve özel bir şekilde yorumlandığı bir dosya türüdür. Dolayısıyla ikili dosyalarla çalışabilmek için, ikili dosyanın bayt dizilimini yakından tanımak gerekiyor.
Bu eğitim seti Kaynak tarafından oluşturulmuştur.