This tool makes it easy to batch upload your self-made photos and videos to Wikimedia Commons.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
7.9 KiB

# commons-upload-tool based on
# purpose: batch upload your self-made photos and videos to Wikimedia Commons
# author: mtheiler
# date: 2022-04-20
# further down there you are able to insert your own defaults e.g. username, ...
import argparse
import getpass
import logging
import re
import os
from datetime import date, datetime
from pathlib import Path
from textwrap import dedent
from typing import Any
# PIL is the Python Imaging Library which provides the python interpreter with image editing capabilities
from PIL import Image
from PIL.Image import Exif
from PIL.ExifTags import TAGS
from PIL.ExifTags import GPSTAGS
from rich.logging import RichHandler
# see:
from pwiki import wgen
from import Wiki
log = logging.getLogger(__name__)
_MTC_FILE = Path.home() / ".scu.px.txt"
def _make_wiki(*args: Any) -> Wiki:
"""Convienence method, creates a new `Wiki` pointed at Commons with `args`
Wiki: The Wiki resulting object
return Wiki("", *args)
def getExifTags(rawEXIF):
"""Gets Exif Tags from the raw EXIF data of the image
and extracts DateTime and the GPS data """
exifTags = {} # place to store the EXIF data
gpsTags = {} # place to store the GPS data
#pulling out the EXIF tags.
for tag, value in rawEXIF.items():
decoded = TAGS.get(tag,tag)
exifTags[decoded] = value
dateTime = exifTags['DateTimeDigitized']
rawGPS = exifTags['GPSInfo']
# Pulling out the GPS specific tags.
# see:
for gpstag , value in rawGPS.items():
decoded = TAGS.get(gpstag,gpstag)
gpsTags[decoded] = value
latitude_direction=gpsTags[1] # N or S
(lat_degrees, lat_minutes, lat_seconds) = gpsTags[2] # latitude
longitude_direction=gpsTags[3] # W or E
(long_degrees, long_minutes, long_seconds) = gpsTags[4] # longitude
# see:
commons_location= dedent(f"""\
return (dateTime, commons_location)
def _main() -> None:
"""Main driver, to be run if this script is invoked directly."""
for lg in (logging.getLogger("pwiki"), log):
verbose = True
cli_parser = argparse.ArgumentParser(description="Simple Commons Uploader 2 (MTh)")
cli_parser.add_argument('--user', type=str, help="username to use")
cli_parser.add_argument('--pw', type=str, help="password to use")
cli_parser.add_argument('--cat', type=str, help="commons category to use")
cli_parser.add_argument('--fnx', type=str, help="part of the filename on commons")
cli_parser.add_argument("-i", action='store_true', help="force interactive login")
cli_parser.add_argument("--wgen", action='store_true', help="run wgen password manager")
cli_parser.add_argument('dirs', metavar='folders', type=Path, nargs='*', help='folders with files to upload')
# uncomment any of the following lines to set your own defaults
# local directory to find pictures to upload
# myTest1 = Path('./test')
# local list of directories.
# cli_parser.set_defaults(dirs=[myTest1])
# this string will become part of the constructed filename on Commons
# cli_parser.set_defaults(fnx='test')
# Commons Category
# cli_parser.set_defaults(cat='testCategory')
# Wikipedia Username
# cli_parser.set_defaults(user='my username')
# Wikipedia Password
# cli_parser.set_defaults(pw='my very secret password') # Password
args = cli_parser.parse_args()
if args.fnx:
if args.wgen:
wgen.setup_px(_MTC_FILE, False)
if args.i:
wiki = _make_wiki(input("Please login to continue.\nUsername: "), getpass.getpass())
elif args.user:
if not
log.critical("No password specified, please pass the --pw flag with a pasword.")
wiki = _make_wiki(args.user,
elif _MTC_FILE.is_file():
wiki = _make_wiki(*wgen.load_px(_MTC_FILE).popitem())
wiki = _make_wiki()
if not result:
log.critical(f"Category:{commomsCategory} does not exists")
if not args.dirs:
log.critical("You didn't specify and directories to upload!")
ext_list = {"." + e for e in wiki.uploadable_filetypes()}
fails = []
uploadedFileNames: List[str]
uploadedFileNames = []
for base_dir in args.dirs:
if not base_dir.is_dir():
i = 1
if verbose:
for f in base_dir.iterdir():
if not f.is_file() or (file_ext := f.suffix.lower()) not in ext_list:
# construct an image description from filename without path and without suffix
imageDescription = (os.path.basename(f)).removesuffix(file_ext)
# date
timestamp = None
if file_ext in (".jpg", ".jpeg"):
with as img:
rawEXIF = img.getexif() #Get the EXIF data from the image.
exifDateTime, gpsLocation = getExifTags(rawEXIF)
d, time = exifDateTime.split()
myDate=d.replace(':', '-')
timestamp = f"{myDate} {time}"
except Exception as e:
log.warning("Could not parse EXIF for %s", f, exc_info=True)
desc = dedent(f"""\
|date={timestamp or datetime.fromtimestamp(f.stat().st_mtime).strftime('%Y-%m-%d %H:%M:%S')}
# [[Category:Files by {wiki.username}]]""")
if verbose:
print("This will be saved on Commons:")
if verbose:
# todo Argument einführen, wenn gesetzt, dann kein Upload
# Upload to Commons using pwiki, beim Test auskommentiert
if not wiki.upload(f, filenameCommons, desc, summary="fileupload with python-script"):
i += 1
if fails:
log.warning("Failed to upload %d files: %s", len(fails), fails)
else:"Finished with no failures")
# wiki.save_cookies() mth
# append filenames to gallery
text4Gallery = f"""\n\n== {commomsCategory} {myDate} ==\n"""
text4Gallery += "\n<gallery widths=200>"
for oneUploadedFilename in uploadedFileNames:
text4Gallery += f"""\n{oneUploadedFilename} | {oneUploadedFilename}"""
text4Gallery += "\n</gallery>"
if verbose:
print('Gallery=', myGallery)
wiki.edit(myGallery, append=text4Gallery, summary="edit gallery with python-script")
if __name__ == '__main__':