In the beginning there was darkness

This commit is contained in:
Felipe Martin 2018-09-26 23:44:04 +02:00
parent 5f3c1587af
commit e281f4e781
Signed by: fmartingr
GPG Key ID: 716BC147715E716F
3 changed files with 301 additions and 0 deletions

16
Pipfile Normal file
View File

@ -0,0 +1,16 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pyexif = "*"
exifread = "*"
mutagen = "*"
[dev-packages]
ipdb = "*"
yapf = "*"
[requires]
python_version = "3.7"

173
Pipfile.lock generated Normal file
View File

@ -0,0 +1,173 @@
{
"_meta": {
"hash": {
"sha256": "b9868bf53ffede6873397a5c3e4f890178f2f8e1c2c9b2cbcb408ff086e3e04e"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"exifread": {
"hashes": [
"sha256:4aa9d227db5c4cd65d87520076140a6f84e33363e08e649b87c7afec6bab60ab",
"sha256:79e244f2eb466709029e8806fe5e2cdd557870c3db5f68954db0ef548d9320ad"
],
"index": "pypi",
"version": "==2.1.2"
},
"mutagen": {
"hashes": [
"sha256:2ea9c900a05fa7f5f4c5bd9fc1475d7d576532e13b2f79b694452b997ff67200"
],
"index": "pypi",
"version": "==1.41.1"
},
"pyexif": {
"hashes": [
"sha256:da2377f987fc221a91ec6d369296d1310c5e9239abe525786e380f97270ddebd"
],
"index": "pypi",
"version": "==0.2.1"
}
},
"develop": {
"appnope": {
"hashes": [
"sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
"sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
],
"markers": "sys_platform == 'darwin'",
"version": "==0.1.0"
},
"backcall": {
"hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
"sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
],
"version": "==0.1.0"
},
"decorator": {
"hashes": [
"sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82",
"sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c"
],
"version": "==4.3.0"
},
"ipdb": {
"hashes": [
"sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a"
],
"index": "pypi",
"version": "==0.11"
},
"ipython": {
"hashes": [
"sha256:007dcd929c14631f83daff35df0147ea51d1af420da303fd078343878bd5fb62",
"sha256:b0f2ef9eada4a68ef63ee10b6dde4f35c840035c50fd24265f8052c98947d5a4"
],
"version": "==6.5.0"
},
"ipython-genutils": {
"hashes": [
"sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8",
"sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"
],
"version": "==0.2.0"
},
"jedi": {
"hashes": [
"sha256:b409ed0f6913a701ed474a614a3bb46e6953639033e31f769ca7581da5bd1ec1",
"sha256:c254b135fb39ad76e78d4d8f92765ebc9bf92cbc76f49e97ade1d5f5121e1f6f"
],
"version": "==0.12.1"
},
"parso": {
"hashes": [
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
"sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24"
],
"version": "==0.3.1"
},
"pexpect": {
"hashes": [
"sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba",
"sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b"
],
"markers": "sys_platform != 'win32'",
"version": "==4.6.0"
},
"pickleshare": {
"hashes": [
"sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
"sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
],
"version": "==0.7.5"
},
"prompt-toolkit": {
"hashes": [
"sha256:1df952620eccb399c53ebb359cc7d9a8d3a9538cb34c5a1344bdbeb29fbcc381",
"sha256:3f473ae040ddaa52b52f97f6b4a493cfa9f5920c255a12dc56a7d34397a398a4",
"sha256:858588f1983ca497f1cf4ffde01d978a3ea02b01c8a26a8bbc5cd2e66d816917"
],
"version": "==1.0.15"
},
"ptyprocess": {
"hashes": [
"sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
"sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
],
"version": "==0.6.0"
},
"pygments": {
"hashes": [
"sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
"sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
],
"version": "==2.2.0"
},
"simplegeneric": {
"hashes": [
"sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
],
"version": "==0.8.1"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"traitlets": {
"hashes": [
"sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835",
"sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"
],
"version": "==4.3.2"
},
"wcwidth": {
"hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
],
"version": "==0.1.7"
},
"yapf": {
"hashes": [
"sha256:b96815bd0bbd2ab290f2ae9e610756940b17a0523ef2f6b2d31da749fc395137",
"sha256:cebb6faf35c9027c08996c07831b8971f3d67c0eb615269f66dfd7e6815fdc2a"
],
"index": "pypi",
"version": "==0.24.0"
}
}
}

112
porg.py Normal file
View File

@ -0,0 +1,112 @@
from dataclasses import dataclass
from datetime import datetime
import hashlib
import mimetypes
import os.path
import exifread
import mutagen
# Config
SOURCE_PATH = '/Volumes/MEDIA/Photos'
TARGET_PATH = '/Volumes/MEDIA/Pictures'
# Globals
file_list = []
@dataclass
class File:
path: str
@property
def type(self):
"""Retrieves the file mimetype by extension"""
if not getattr(self, '_type', False):
self._type, _ = mimetypes.guess_type(self.path)
if not self._type:
print(f"Can't guess type of file {self.path}")
return self._type
@property
def is_image(self):
return 'image' in self.type
@property
def is_video(self):
return 'video' in self.type
@property
def exif(self):
"""
Retrieve EXIF data from the file and merge it with wathever mutagen finds in there for video files.
"""
if not getattr(self, '_exif', False):
self._exif = exifread.process_file(open(self.path, 'rb'))
if self.is_video:
self._exif.update(mutagen.File(self.path))
return self._exif
@property
def datetime(self):
"""
Retrieves original creation date for the picture trying exif data first, filename guessing and finally
modification date. Make sure your pictures are exported unmodified so the file attributes maintain their
original values for this to work.
"""
if self.is_image:
date, time = self.exif['EXIF DateTimeOriginal'].values.split()
return datetime(*(int(x) for x in date.split(':') + time.split(':')))
if self.is_video:
# Apple iPhone tag
try:
return datetime.strptime(self.exif.get('©day')[0], '%Y-%m-%dT%H:%M:%S%z')
except TypeError:
pass
# Tag not found, try to guess datetime from filename
# Format: YYYY-MM-DD HH.MM.SS.ext
try:
name, _ = os.path.basename(self.path).rsplit('.', maxsplit=1)
date, time = name.split(' ')
return datetime(*(int(x) for x in date.split('-') + time.split('.')))
except ValueError:
raise
# Last resort, use file creation/modification date
stat = os.stat(self.path)
try:
return stat.st_birthtime
except AttributeError:
# Linux: No easy way to get creation dates here,
# so we'll settle for when its content was last modified.
return stat.st_mtime
@property
def checksum(self):
if not getattr(self, '_checksum', False):
digest = hashlib.sha1()
with open(self.path, 'rb') as handler:
digest.update(handler.read())
self._checksum = digest.hexdigest()
return self._checksum
def read_path():
for path, directories, files in os.walk(SOURCE_PATH):
for filename in files:
if not filename.startswith('.') and filename not in ['.', '..']:
yield File(path=os.path.join(path, filename))
def get_target_path(fileobj):
return os.path.join(TARGET_PATH, str(fileobj.datetime.year), '%02d' % fileobj.datetime.month)
if __name__ == '__main__':
for fileobj in read_path():
target_path = get_target_path(fileobj)
os.makedirs(target_path, exist_ok=True)