Pythonで初アプリ(JPGバックアップ+縮小化)
WindowsのPCのData用HDDが3TBになった時、過去のバックアップを整理しないでどかっと移した結果、いろんなフォルダにいろんなファイルが散在する結果になってしまった。特に、画像は1つにまとめて、Macでも共有したい。でも、Macは256gbなので、縮小しないとはいらないと言うことで、自分で作ることにしました。
JAVAにしようかPythonにしようか・・・今から取り組むならPythonだろうと言うことで、Python(EclipseのPyDEV)とwxGladeで作ってみました。wxGladeはPython形式で保存するタイプを選択しました。(メリット、そのまま続けてコードを書ける。デメリット、Generateする度に、自動生成部分をwxGladeが上書きしてくれるので、コード上での修正はクリアされるので、極力GUI上で設定しないとならない)
できたのが、これ。余計なボタン(Make Small Pict)があるけど、前述の理由で消せない。Searchボタンで全部動かしてしまった。
西暦の年毎にフォルダを作って、その下に日付別のフォルダを作って、該当するファイルをコピーしていきます。ファイル名が同じ場合は、日付けが異なれば、ファイル名を変えて保存し、同じ場合はファイルサイズが大きい方を残す様にしています。それに合わせて、小さいファイル(長辺を1600に固定)を作ります。

コードは、こちら。汚いし、ズルズルと繋がっていて見辛いですが、逆に一覧性があり、あとで振り返る時に便利なので、このままにしておきます。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 0.8.0b3 on Sat Mar 3 13:40:06 2018
#
import os
import shutil
import wx
import time
from datetime import datetime
import json
from PIL import Image
from PIL.ExifTags import TAGS
# begin wxGlade: dependencies
# end wxGlade
# begin wxGlade: extracode
# end wxGlade
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MyFrame.__init__
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((640, 480))
self.buttonTopFolder = wx.Button(self, wx.ID_ANY, "Set Top Folder")
self.labelTopFolder = wx.StaticText(self, wx.ID_ANY, self.readJson("Top"))
self.buttonTargetFolder = wx.Button(self, wx.ID_ANY, "Set Target Folder")
self.labelTargetFolder = wx.StaticText(self, wx.ID_ANY, self.readJson("Target"))
self.buttonSearch = wx.Button(self, wx.ID_ANY, "Search")
self.labelNumOfJPG = wx.StaticText(self, wx.ID_ANY, "0\t", style=wx.ALIGN_CENTER)
self.labelNumOfFiles = wx.StaticText(self, wx.ID_ANY, "0", style=wx.ALIGN_CENTER)
self.buttonSetSmallFolder = wx.Button(self, wx.ID_ANY, "Set a Folder for Small Pict")
self.labelSmallFolder = wx.StaticText(self, wx.ID_ANY, self.readJson("Small"), style=wx.ALIGN_CENTER | wx.ALIGN_RIGHT)
self.buttonMakeSmall = wx.Button(self, wx.ID_ANY, "Make Small Pict")
self.gaugeProgress = wx.Gauge(self, wx.ID_ANY, 10)
self.listFiles = wx.ListCtrl(self, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_BUTTON, self.onClick_buttonTopFolder, self.buttonTopFolder)
self.Bind(wx.EVT_BUTTON, self.onClick_buttonTargetFolder, self.buttonTargetFolder)
self.Bind(wx.EVT_BUTTON, self.onClick_buttonSearch, self.buttonSearch)
self.Bind(wx.EVT_BUTTON, self.onClick_buttonSetSmallFolder, self.buttonSetSmallFolder)
self.Bind(wx.EVT_BUTTON, self.onClick_buttonMakeSmall, self.buttonMakeSmall)
#self.importJson()
# end wxGlade
def __set_properties(self):
# begin wxGlade: MyFrame.__set_properties
self.SetTitle("frame")
self.buttonTopFolder.SetMinSize((160, 20))
self.labelTopFolder.SetMinSize((480, 20))
self.labelTopFolder.SetBackgroundColour(wx.Colour(194, 240, 220))
self.buttonTargetFolder.SetMinSize((160, 20))
self.labelTargetFolder.SetMinSize((480, 20))
self.labelTargetFolder.SetBackgroundColour(wx.Colour(238, 196, 249))
self.buttonSearch.SetMinSize((160, 20))
self.labelNumOfJPG.SetMinSize((120, 20))
self.labelNumOfJPG.SetBackgroundColour(wx.Colour(186, 235, 243))
self.labelNumOfFiles.SetMinSize((120, 20))
self.labelNumOfFiles.SetBackgroundColour(wx.Colour(253, 253, 151))
self.buttonSetSmallFolder.SetMinSize((160, 20))
self.labelSmallFolder.SetMinSize((160, 20))
self.labelSmallFolder.SetBackgroundColour(wx.Colour(185, 255, 188))
self.buttonMakeSmall.SetMinSize((160, 20))
self.gaugeProgress.SetMinSize((160, 20))
self.listFiles.SetMinSize((640, 420))
self.listFiles.AppendColumn("path", format=wx.LIST_FORMAT_LEFT, width=320)
self.listFiles.AppendColumn("DateTime", format=wx.LIST_FORMAT_LEFT, width=160)
self.listFiles.AppendColumn("FileSize(KB)", format=wx.LIST_FORMAT_LEFT, width=120)
self.listFiles.AppendColumn("Target Folder", format=wx.LIST_FORMAT_LEFT, width=240)
# end wxGlade
def __do_layout(self):
# begin wxGlade: MyFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_9 = wx.BoxSizer(wx.HORIZONTAL)
sizer_5 = wx.BoxSizer(wx.HORIZONTAL)
sizer_6 = wx.BoxSizer(wx.HORIZONTAL)
sizer_8 = wx.BoxSizer(wx.HORIZONTAL)
sizer_7 = wx.BoxSizer(wx.HORIZONTAL)
sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3.Add(self.buttonTopFolder, 0, wx.ALIGN_CENTER, 0)
sizer_3.Add(self.labelTopFolder, 0, wx.ALIGN_CENTER | wx.ALL, 2)
sizer_2.Add(sizer_3, 1, wx.EXPAND, 0)
sizer_4.Add(self.buttonTargetFolder, 0, wx.ALIGN_CENTER, 0)
sizer_4.Add(self.labelTargetFolder, 0, wx.ALIGN_CENTER | wx.ALL, 2)
sizer_2.Add(sizer_4, 1, wx.EXPAND, 0)
sizer_5.Add(self.buttonSearch, 0, wx.ALIGN_CENTER, 0)
labelTitle1 = wx.StaticText(self, wx.ID_ANY, "Num of Jpeg", style=wx.ALIGN_CENTER)
labelTitle1.SetMinSize((120, 20))
labelTitle1.SetBackgroundColour(wx.Colour(186, 235, 243))
sizer_7.Add(labelTitle1, 0, wx.ALIGN_CENTER, 0)
sizer_7.Add(self.labelNumOfJPG, 0, wx.ALIGN_CENTER | wx.ALL, 1)
sizer_6.Add(sizer_7, 1, wx.EXPAND, 0)
labelTitle2 = wx.StaticText(self, wx.ID_ANY, "Num of Files", style=wx.ALIGN_CENTER)
labelTitle2.SetMinSize((120, 20))
labelTitle2.SetBackgroundColour(wx.Colour(253, 253, 151))
sizer_8.Add(labelTitle2, 0, wx.ALIGN_CENTER, 0)
sizer_8.Add(self.labelNumOfFiles, 0, wx.ALIGN_CENTER | wx.ALL, 1)
sizer_6.Add(sizer_8, 1, wx.EXPAND, 0)
sizer_5.Add(sizer_6, 1, wx.EXPAND, 0)
sizer_2.Add(sizer_5, 1, wx.EXPAND, 0)
sizer_9.Add(self.buttonSetSmallFolder, 0, wx.ALIGN_CENTER, 0)
sizer_9.Add(self.labelSmallFolder, 0, wx.ALIGN_CENTER, 0)
sizer_9.Add(self.buttonMakeSmall, 0, wx.ALIGN_CENTER, 0)
sizer_9.Add(self.gaugeProgress, 0, wx.ALIGN_CENTER | wx.EXPAND, 0)
sizer_2.Add(sizer_9, 1, wx.EXPAND, 0)
sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
sizer_1.Add(self.listFiles, 1, wx.ALL | wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
# end wxGlade
def onClick_buttonTopFolder(self, event): # wxGlade: MyFrame.<event_handler>
topDir = wx.DirDialog(self, u'フォルダを選択してください', style=wx.DD_DEFAULT_STYLE, defaultPath=self.labelTopFolder.GetLabel())
if topDir.ShowModal() == wx.ID_OK:
self.labelTopFolder.SetLabel(topDir.GetPath())
self.writeJson()
topDir.Destroy()
def onClick_buttonTargetFolder(self, event): # wxGlade: MyFrame.<event_handler>
targetDir = wx.DirDialog(None, u'フォルダを選択してください', style=wx.DD_DEFAULT_STYLE, defaultPath=self.labelTargetFolder.GetLabel())
if targetDir.ShowModal() == wx.ID_OK:
self.labelTargetFolder.SetLabel(targetDir.GetPath())
self.writeJson()
targetDir.Destroy()
def onClick_buttonSearch(self, event): # wxGlade: MyFrame.<event_handler>
listOfFiles = list(find_all_files(self.labelTopFolder.GetLabel()))
numLists = len(listOfFiles)
self.listFiles.DeleteAllItems()
col = 0
dirTarX = ""
self.gaugeProgress.SetValue(0)
for dr in listOfFiles:
if (dr.endswith(".jpg") or dr.endswith(".JPG")):
self.listFiles.InsertItem(col, dr)
ex1 = getExifImageDate(dr,"DateTimeOriginal")
orgW, orgH = ex1[0].size
if orgW > orgH:
tarW = 1600
tarH = int(1600 * orgH / orgW)
else:
tarH = 1600
tarW = int(1600 * orgW / orgH)
dttm1 = datetime.strptime(ex1[1], '%Y:%m:%d %H:%M:%S') if ex1[1] != "NoData" else datetime.fromtimestamp(os.path.getmtime(dr))
dt0 = os.path.getatime(dr)
self.listFiles.SetItem(col, 1, dttm1.strftime('%Y/%m/%d %H:%M:%S'))
flsz1 = os.path.getsize(dr)
self.listFiles.SetItem(col, 2, '{:,}'.format(int(flsz1/1024)))
dirTar = self.labelTargetFolder.GetLabel() + getTaregtFolder(dttm1)
dirTar2 = self.labelSmallFolder.GetLabel() + getTaregtFolder(dttm1)
self.listFiles.SetItem(col, 3, dirTar)
if dirTar != dirTarX:
if not os.path.exists(dirTar):
os.makedirs(dirTar)
if not os.path.exists(dirTar2):
os.makedirs(dirTar2)
fileTar = getFolderFile(dirTar, os.path.basename(dr))
fileTar2 = getFolderFile(dirTar2, os.path.basename(dr))
if os.path.exists(fileTar):
flsz0 = os.path.getsize(fileTar)
dttm0 = datetime.strptime(ex1[1], '%Y:%m:%d %H:%M:%S') if ex1[1] != "NoData" else datetime.fromtimestamp(os.path.getmtime(dr))
if dttm0 != dttm1:
nameBase = os.path.basename(fileTar)
nameBody, nameExt = os.path.splitext(nameBase)
cnt = 0
fileNew1 = getFolderFile(dirTar, nameBody + str(cnt) + nameExt)
fileNew2 = getFolderFile(dirTar2, nameBody + str(cnt) + nameExt)
while not os.path.exists(fileNew1):
cnt += 1
fileNew1 = getFolderFile(dirTar, nameBody + str(cnt) + nameExt)
fileNew2 = getFolderFile(dirTar, nameBody + str(cnt) + nameExt)
shutil.copyfile(dr, fileNew1)
os.utime(fileNew1, (dt0, dttm1.timestamp()))
smallPict = ex1[0].resize((tarW, tarH), Image.LANCZOS)
smallPict.save(fileNew2)
os.utime(fileNew2, (dt0, dttm1.timestamp()))
elif flsz0 > flsz1:
shutil.copyfile(dr, fileTar)
os.utime(fileTar, (dt0, dttm1.timestamp()))
smallPict = ex1[0].resize((tarW, tarH), Image.LANCZOS)
smallPict.save(fileTar2)
os.utime(fileTar2, (dt0, dttm1.timestamp()))
else:
shutil.copyfile(dr, fileTar)
os.utime(fileTar, (dt0, dttm1.timestamp()))
smallPict = ex1[0].resize((tarW, tarH), Image.LANCZOS)
smallPict.save(fileTar2)
os.utime(fileTar2, (dt0, dttm1.timestamp()))
ex1 = {}
col += 1
if col % int(numLists/10) == 0:
self.gaugeProgress.SetValue(int(col * 10 / numLists))
dirTarX = dirTar
self.labelNumOfFiles.SetLabel(str(numLists))
self.labelNumOfJPG.SetLabel(str(col))
def onClick_buttonSetSmallFolder(self, event): # wxGlade: MyFrame.<event_handler>
smallDir = wx.DirDialog(None, u'フォルダを選択してください', style=wx.DD_DEFAULT_STYLE, defaultPath=self.labelSmallFolder.GetLabel())
if smallDir.ShowModal() == wx.ID_OK:
self.labelSmallFolder.SetLabel(smallDir.GetPath())
self.writeJson()
smallDir.Destroy()
def onClick_buttonMakeSmall(self, event): # wxGlade: MyFrame.<event_handler>
print("Event handler 'onClick_buttonMakeSmall' not implemented!")
event.Skip()
def readJson(self, lbl):
fileJson = 'foldersW.json' if os.name == 'nt' else 'foldersM.json'
try:
f = open(fileJson, 'r')
except FileNotFoundError:
return ""
data_json=json.load(f)
return data_json[lbl]
def writeJson(self):
fileJson = 'foldersW.json' if os.name == 'nt' else 'foldersM.json'
f = open(fileJson, 'w')
json_data = {'Top' : self.labelTopFolder.GetLabel(), 'Target' : self.labelTargetFolder.GetLabel(), 'Small' : self.labelSmallFolder.GetLabel()}
json.dump(json_data, f)
# end of class MyFrame
def getExifDate(file):
im = Image.open(file)
try:
exif = im._getexif()
ts1 = exif.items()
except AttributeError:
return "NoData"
exif_table = {}
for tag_id, value in exif.items():
tag = TAGS.get(tag_id, tag_id)
exif_table[tag] = value
return exif_table.get("DateTimeOriginal")
def getExifImageDate(file, tagName):
im = Image.open(file)
try:
exif = im._getexif()
ts1 = exif.items()
except AttributeError:
return (im, "NoData")
exif_table = {}
for tag_id, value in exif.items():
tag = TAGS.get(tag_id, tag_id)
exif_table[tag] = value
return (im, exif_table.get(tagName))
def getTaregtFolder(dttm):
strDigit = "\\" if os.name == 'nt' else "/"
return strDigit + str(dttm.year) + strDigit + dttm.strftime('%Y_%m_%d')
def getFolderFile(nameFolder, nameFile):
strDigit = "\\" if os.name == 'nt' else "/"
return nameFolder + strDigit + nameFile
def find_all_files(directory):
for root, dirs, files in os.walk(directory):
yield root
for file in files:
yield os.path.join(root, file)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
# end of class MyApp
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()