mirror of https://github.com/fmartingr/porg.git
In the beginning there was darkness
This commit is contained in:
parent
5f3c1587af
commit
e281f4e781
|
@ -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"
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
Loading…
Reference in New Issue