Compare commits
No commits in common. "main" and "v0.3" have entirely different histories.
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,40 +1,15 @@
|
||||
# Release Notes
|
||||
|
||||
## v0.4
|
||||
|
||||
### Add
|
||||
|
||||
- config.json.example.
|
||||
- It's now possible to add a Item Description as ALT text for Images.
|
||||
-- For this I use the Item Description from the META Tags.
|
||||
- For me its not possible to read the special LR META Tags so its now possible to add a Post text while upload it.
|
||||
- New Post text Design. (Date // Camera // Title(Text) default Tags)
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed CHANGELOG.md to new Format
|
||||
- Change README for better first Start
|
||||
|
||||
### Removed
|
||||
|
||||
- one useless / old file
|
||||
|
||||
## v0.3 - release
|
||||
|
||||
### Changed
|
||||
|
||||
- Change README for readability
|
||||
|
||||
## v0.2 - unreleased
|
||||
## 1.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- Option to delete files after upload
|
||||
- Create a Window Frontend to select Image, Add Conteten and Upload
|
||||
|
||||
## v0.1 - unreleased
|
||||
## 1.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Import all Images from Folder
|
||||
- Default Tags at the end of a Content
|
||||
- Uploade Type
|
||||
- Create new Post for each Image
|
||||
- Add default Tags at the end of a Content
|
||||
|
55
PixelfedAPI.py
Normal file
55
PixelfedAPI.py
Normal file
@ -0,0 +1,55 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
|
||||
class PixelfedAPI:
|
||||
def __init__(self, url, token):
|
||||
self.serverurl = url
|
||||
self.accesstoken = token
|
||||
|
||||
def mediaUpload(self, image):
|
||||
serverurl = self.serverurl()
|
||||
apiurl = serverurl + "/api/v1/media"
|
||||
|
||||
accessToken = self.accesstoken()
|
||||
headers = {
|
||||
"Authorization": f"Bearer {accessToken}"
|
||||
}
|
||||
|
||||
try:
|
||||
with open(image, "rb") as imageFile:
|
||||
f = {"file": imageFile}
|
||||
response = requests.post(apiurl, headers=headers, files=f)
|
||||
try:
|
||||
data = response.json()
|
||||
return(data)
|
||||
except json.decoder.JSONDecodeError:
|
||||
print("API response contains non-valid JSON data")
|
||||
#print(response.text)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"An error occurred during the API call: {e}")
|
||||
|
||||
def createNewPost(self, ImageID, ImageDescription, ImageAltDescription = ''):
|
||||
serverurl = self.serverurl()
|
||||
apiurl = serverurl + "/api/v1/statuses"
|
||||
|
||||
print(apiurl)
|
||||
|
||||
accessToken = self.accesstoken()
|
||||
headers = {
|
||||
"Authorization": f"Bearer {accessToken}"
|
||||
}
|
||||
data = {
|
||||
"status": ImageDescription,
|
||||
"media_ids": [ImageID]
|
||||
}
|
||||
try:
|
||||
response = requests.post(apiurl, headers=headers, json=data)
|
||||
try:
|
||||
responsedata = response.json()
|
||||
return(responsedata)
|
||||
except json.decoder.JSONDecodeError:
|
||||
print("API response contains non-valid JSON data")
|
||||
#print(response.text)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"An error occurred during the API call: {e}")
|
@ -4,21 +4,10 @@ import requests
|
||||
import glob
|
||||
import exifread
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, ExifTags
|
||||
from PIL.ExifTags import TAGS
|
||||
from PIL.PngImagePlugin import PngImageFile, PngInfo
|
||||
|
||||
def confirm():
|
||||
confirmMsg = input("[y]es or [n]o ")
|
||||
if confirmMsg == 'y' or confirmMsg == 'yes':
|
||||
return True
|
||||
elif confirmMsg == 'n' or confirmMsg == 'no':
|
||||
return False
|
||||
else:
|
||||
print("\n Invalid Option. Please Enter a Valid Option.")
|
||||
return confirm()
|
||||
return False
|
||||
|
||||
def load_config(file_path):
|
||||
with open(file_path, "r") as config_file:
|
||||
config_data = json.load(config_file)
|
||||
@ -38,70 +27,30 @@ def getImages(dir):
|
||||
return result
|
||||
|
||||
def getItemDescription(filename):
|
||||
img = Image.open(filename)
|
||||
exif_data = img._getexif()
|
||||
imageDescription = ""
|
||||
type = Image.open(filename)
|
||||
|
||||
if exif_data:
|
||||
for tag, value in exif_data.items():
|
||||
tag_name = TAGS.get(tag, tag)
|
||||
if str(tag_name) == "ImageDescription":
|
||||
imageDescription = str(value)
|
||||
break
|
||||
else:
|
||||
imageDescription = "This image has no description. Please let me know so that I can update it."
|
||||
else:
|
||||
raise ValueError("No EXIF data found.")
|
||||
exif_tags = open(filename, 'rb')
|
||||
tags = exifread.process_file(exif_tags)
|
||||
|
||||
return imageDescription
|
||||
exif_array = []
|
||||
|
||||
def getPostText(filename):
|
||||
postText = ""
|
||||
tags = getTags("tags.txt")
|
||||
date = ""
|
||||
cam = ""
|
||||
title = ""
|
||||
if type.format != "PNG":
|
||||
for i in tags:
|
||||
compile = i, str(tags[i])
|
||||
exif_array.append(compile)
|
||||
|
||||
img = Image.open(filename)
|
||||
exif_data = img._getexif()
|
||||
if type.format == "PNG":
|
||||
image = PngImageFile(filename)
|
||||
metadata = PngInfo()
|
||||
|
||||
if exif_data:
|
||||
for tag, value in exif_data.items():
|
||||
tag_name = TAGS.get(tag, tag)
|
||||
if tag_name == "DateTimeOriginal":
|
||||
dateTime = value
|
||||
year = dateTime[:4]
|
||||
month = dateTime[5:7]
|
||||
date = "🗓 " + year + "-" + month
|
||||
if tag_name == "Make":
|
||||
cam += "📸 " + value
|
||||
if cam != "" and tag_name == "Model":
|
||||
cam += " " + value
|
||||
if tag_name == "Object Name":
|
||||
print(value)
|
||||
title = value
|
||||
else:
|
||||
raise ValueError("No EXIF data found.")
|
||||
for i in image.text:
|
||||
compile = i, str(image.text[i])
|
||||
exif_array.append(compile)
|
||||
|
||||
if date != "":
|
||||
postText = date
|
||||
if cam != "":
|
||||
if postText != "":
|
||||
postText += " || "
|
||||
postText += cam
|
||||
if title != "":
|
||||
postText += "\n\r\n\r" + title
|
||||
elif title == "":
|
||||
print(f"No post Text found. Do you want to add a post text for file " + os.path.basename(filename) + "?")
|
||||
if confirm():
|
||||
ownPostText = input("Post text:")
|
||||
ownPostText.encode('utf-8')
|
||||
postText += "\n\r\n\r" + ownPostText
|
||||
postText += "\n\r\n\r" + tags
|
||||
Description = exif_array[0][1]
|
||||
return Description
|
||||
|
||||
return postText
|
||||
|
||||
def mediaUpload(access_token, url, file, itemDescription):
|
||||
def mediaUpload(access_token, url, file):
|
||||
api_url = url + "/api/v1/media"
|
||||
|
||||
headers = {
|
||||
@ -110,16 +59,9 @@ def mediaUpload(access_token, url, file, itemDescription):
|
||||
|
||||
try:
|
||||
with open(file, "rb") as imageFile:
|
||||
f = {
|
||||
"file": (file, imageFile)
|
||||
}
|
||||
|
||||
data = {
|
||||
"description": itemDescription.encode('utf-8')
|
||||
}
|
||||
|
||||
response = requests.post(api_url, headers=headers, files=f, data=data)
|
||||
response.raise_for_status()
|
||||
f = {"file": imageFile}
|
||||
response = requests.post(api_url, headers=headers, files=f)
|
||||
response.raise_for_status() # Wirft eine HTTPError-Exception, wenn der Statuscode nicht erfolgreich ist
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
@ -156,46 +98,31 @@ def createNewPost(access_token, url, ImageID, ImageDescription):
|
||||
|
||||
def sendImages(Token, url, imagedir, uploadType, deleteFile):
|
||||
files = getImages(imagedir)
|
||||
if not files:
|
||||
raise ValueError("No file found.")
|
||||
postText = ""
|
||||
tags = getTags("tags.txt")
|
||||
tagDescription = ""
|
||||
newFileID = ""
|
||||
itemDescription = ""
|
||||
postText = ""
|
||||
|
||||
for f in files:
|
||||
itemDescription = getItemDescription(f)
|
||||
newFile = mediaUpload(Token, url, f, itemDescription)
|
||||
newFile = mediaUpload(Token, url, f)
|
||||
description = getItemDescription(f)
|
||||
if uploadType == 0:
|
||||
postText = getPostText(f)
|
||||
tagDescription = description + ' ' + tags
|
||||
newFileID = newFile.get("id")
|
||||
print(f"----------------------------------------")
|
||||
print(f"Post Summary: ")
|
||||
print(f"ImageID: " + newFileID)
|
||||
print(f"Image Description: " + newFile.get("description"))
|
||||
print(f"Post Text: " + postText)
|
||||
print(f"----------------------------------------")
|
||||
print(f"Do you want to Publish this?")
|
||||
if confirm():
|
||||
createNewPost(Token, url, newFileID, postText)
|
||||
createNewPost(Token, url, newFileID, tagDescription)
|
||||
else:
|
||||
deleteFile = False
|
||||
print(f"File upload aborted.")
|
||||
else:
|
||||
if postText == "":
|
||||
postText = getPostText(f)
|
||||
if tagDescription == "":
|
||||
tagDescription = description + ' ' + tags
|
||||
if newFileID != "":
|
||||
newFileID = newFileID + "," + newFile.get("id")
|
||||
else:
|
||||
newFileID = newFile.get("id")
|
||||
if uploadType == 1:
|
||||
createNewPost(Token, url, newFileID, postText)
|
||||
createNewPost(Token, url, newFileID, tagDescription)
|
||||
|
||||
if deleteFile:
|
||||
for f in files:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
@ -208,7 +135,4 @@ if __name__ == "__main__":
|
||||
uploadType = config.get("upload_type")
|
||||
deleteFile = config.get("delete_file")
|
||||
|
||||
try:
|
||||
sendImages(accessToken, url, imagedir, uploadType, deleteFile)
|
||||
except ValueError as e:
|
||||
print(f"Error while uploading the image: {e}")
|
@ -3,13 +3,6 @@
|
||||
a Python app to publish all images from a folder to Pixelfed.
|
||||
This script use Image Meta Tag Description fot the Post Description.
|
||||
|
||||
## How to use
|
||||
|
||||
- create new confg file like the example one
|
||||
- execute PixelfedImporter.py
|
||||
|
||||
if you want to add some default tags create a new tags.txt file with all Default Tags. You also can add a default Text in ths File.
|
||||
|
||||
## Config
|
||||
|
||||
To use this you need a config.json if this cannot be created automatically, here is the structure:
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"server_url": "URL TO YOUR PIXELFED",
|
||||
"access_token": "YOUR ACCESS TOKEN",
|
||||
"image_path": "PATH TO YOUR IMAGES",
|
||||
"upload_type": 0|1,
|
||||
"delete_file": true|false
|
||||
}
|
270
main.py
Normal file
270
main.py
Normal file
@ -0,0 +1,270 @@
|
||||
import os
|
||||
import exifread
|
||||
import json
|
||||
import gettext
|
||||
import tkinter as tk
|
||||
from PixelfedAPI import *
|
||||
from tkinter import filedialog, Menu, Toplevel
|
||||
from PIL import Image, ImageTk, ExifTags
|
||||
from PIL.ExifTags import TAGS
|
||||
from PIL.PngImagePlugin import PngImageFile, PngInfo
|
||||
|
||||
class ImageUploaderApp:
|
||||
def __init__(self, root):
|
||||
serverURL = self.get_setting_serverURL
|
||||
serverAccesskey = self.get_setting_accessKey
|
||||
self.Pixelfed = PixelfedAPI(serverURL, serverAccesskey)
|
||||
|
||||
self.root = root
|
||||
self.root.title("Pixelfed Uploader")
|
||||
|
||||
self.image_paths = []
|
||||
self.current_index = 0
|
||||
|
||||
self.create_menu()
|
||||
|
||||
self.button_frame = tk.Frame(self.root)
|
||||
self.prev_button = tk.Button(self.button_frame, text="Previous", command=self.show_previous_image)
|
||||
self.prev_button.pack(side=tk.LEFT)
|
||||
self.canvas = tk.Canvas(self.button_frame, width=400, height=400)
|
||||
self.canvas.pack(side=tk.LEFT)
|
||||
self.next_button = tk.Button(self.button_frame, text="Next", command=self.show_next_image)
|
||||
self.next_button.pack(side=tk.RIGHT)
|
||||
self.button_frame.pack()
|
||||
|
||||
self.description_label = tk.Label(self.root, text="Post Content:")
|
||||
self.description_label.pack()
|
||||
|
||||
self.description_text = tk.Text(self.root, height=10, wrap=tk.WORD)
|
||||
self.description_text.pack()
|
||||
|
||||
self.description_length_var = tk.StringVar()
|
||||
self.description_length_label = tk.Label(self.root, textvariable=self.description_length_var)
|
||||
self.description_length_label.pack()
|
||||
|
||||
self.description_text.bind("<KeyRelease>", self.update_description_length)
|
||||
|
||||
self.tag_text_label = tk.Label(self.root, text="Tags:")
|
||||
self.tag_text_label.pack()
|
||||
|
||||
self.tag_text = tk.Text(self.root, height=5, wrap=tk.WORD)
|
||||
self.tag_text.pack()
|
||||
|
||||
defaultTags = self.getTags()
|
||||
self.tag_text.delete("1.0", tk.END)
|
||||
self.tag_text.insert(tk.END, defaultTags)
|
||||
|
||||
#self.alt_text_label = tk.Label(self.root, text="Alt-Text:")
|
||||
#self.alt_text_label.pack()
|
||||
|
||||
#self.alt_text = tk.Text(self.root, height=10, wrap=tk.WORD)
|
||||
#self.alt_text.pack()
|
||||
|
||||
#self.alt_length_var = tk.StringVar()
|
||||
#self.alt_length_label = tk.Label(self.root, textvariable=self.alt_length_var)
|
||||
#self.alt_length_label.pack()
|
||||
|
||||
#self.alt_text.bind("<KeyRelease>", self.update_alt_length)
|
||||
|
||||
self.upload_button = tk.Button(self.root, text="Upload", command=self.upload_image)
|
||||
self.upload_button.pack()
|
||||
|
||||
self.open_folder()
|
||||
|
||||
def open_folder(self):
|
||||
self.load_images()
|
||||
|
||||
def create_menu(self):
|
||||
self.menu_bar = Menu(self.root)
|
||||
self.root.config(menu=self.menu_bar)
|
||||
|
||||
self.file_menu = Menu(self.menu_bar, tearoff=0)
|
||||
self.menu_bar.add_cascade(label="File", menu=self.file_menu)
|
||||
self.file_menu.add_command(label="Open Folder", command=self.load_images)
|
||||
self.file_menu.add_command(label="Settings", command=self.open_settings)
|
||||
|
||||
def open_settings(self):
|
||||
settings_window = Toplevel(self.root)
|
||||
settings_window.title("Settings")
|
||||
settings_window.geometry("500x250")
|
||||
|
||||
server_url_label = tk.Label(settings_window, text="Server-URL:")
|
||||
server_url_label.pack()
|
||||
server_url_entry = tk.Entry(settings_window, width=50)
|
||||
server_url_entry.pack()
|
||||
|
||||
access_key_label = tk.Label(settings_window, text="API AccessKey:")
|
||||
access_key_label.pack()
|
||||
access_key_entry = tk.Entry(settings_window, width=50)
|
||||
access_key_entry.pack()
|
||||
|
||||
image_folder_label = tk.Label(settings_window, text="Default Folder:")
|
||||
image_folder_label.pack()
|
||||
image_folder_entry = tk.Entry(settings_window, width=50)
|
||||
image_folder_entry.pack()
|
||||
|
||||
enableLogVar = tk.BooleanVar()
|
||||
enableLog_label = tk.Label(settings_window, text="Log:")
|
||||
enableLog_label.pack()
|
||||
enableLog = tk.Checkbutton(settings_window, variable=enableLogVar)
|
||||
enableLog.pack()
|
||||
|
||||
save_button = tk.Button(settings_window, text="Speichern", command=lambda: self.save_settings(server_url_entry.get(), access_key_entry.get(), image_folder_entry.get(), enableLogVar.get()))
|
||||
save_button.pack()
|
||||
|
||||
config = self.load_config()
|
||||
if config:
|
||||
server_url_entry.insert(0, config.get("server_url", ""))
|
||||
access_key_entry.insert(0, config.get("access_token", ""))
|
||||
image_folder_entry.insert(0, config.get("image_path", ""))
|
||||
|
||||
def load_config(self):
|
||||
try:
|
||||
with open("config.json", "r") as config_file:
|
||||
settings = json.load(config_file)
|
||||
return settings
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
def get_config_File(self):
|
||||
config = self.load_config()
|
||||
|
||||
return config
|
||||
|
||||
def get_setting_serverURL(self):
|
||||
config = self.get_config_File()
|
||||
url = config.get("server_url")
|
||||
|
||||
return url
|
||||
|
||||
def get_setting_accessKey(self):
|
||||
config = self.get_config_File()
|
||||
accessToken = config.get("access_token")
|
||||
|
||||
return accessToken
|
||||
|
||||
def get_setting_imageFolder(self):
|
||||
config = self.get_config_File()
|
||||
imagedir = config.get("image_path")
|
||||
|
||||
return imagedir
|
||||
|
||||
def get_setting_log(self):
|
||||
config = self.get_config_File()
|
||||
isLogEnable = config.get("log")
|
||||
|
||||
return isLogEnable
|
||||
|
||||
def save_settings(self, server_url, access_key, image_path, log=False):
|
||||
settings = {
|
||||
"server_url": server_url,
|
||||
"access_token": access_key,
|
||||
"image_path": image_path,
|
||||
"log": log
|
||||
}
|
||||
|
||||
with open("config.json", "w") as config_file:
|
||||
json.dump(settings, config_file)
|
||||
|
||||
print("Settings have been saved.")
|
||||
|
||||
def update_description_length(self, event):
|
||||
description = self.description_text.get("1.0", tk.END)
|
||||
description_length = len(description)
|
||||
self.description_length_var.set(f"({description_length})")
|
||||
|
||||
def update_alt_length(self, event):
|
||||
alt = self.alt_text.get("1.0", tk.END)
|
||||
alt_length = len(alt)
|
||||
self.alt_length_var.set(f"({alt_length})")
|
||||
|
||||
def load_images(self):
|
||||
imagedir = self.get_setting_imageFolder()
|
||||
if not imagedir:
|
||||
folder_path = filedialog.askdirectory()
|
||||
else:
|
||||
folder_path = imagedir
|
||||
|
||||
if folder_path:
|
||||
self.image_paths = [os.path.join(folder_path, filename) for filename in os.listdir(folder_path) if filename.lower().endswith(('.jpg', '.jpeg'))]
|
||||
self.current_index = 0
|
||||
self.show_current_image()
|
||||
|
||||
def show_current_image(self):
|
||||
if self.image_paths:
|
||||
image_path = self.image_paths[self.current_index]
|
||||
image = Image.open(image_path)
|
||||
image.thumbnail((400, 400))
|
||||
photo = ImageTk.PhotoImage(image)
|
||||
self.canvas.create_image(0, 0, anchor=tk.NW, image=photo)
|
||||
self.canvas.image = photo
|
||||
|
||||
description = self.getItemDescription(image_path)
|
||||
self.description_text.delete("1.0", tk.END)
|
||||
self.description_text.insert(tk.END, description)
|
||||
|
||||
def getItemDescription(self, filename):
|
||||
type = Image.open(filename)
|
||||
|
||||
exif_tags = open(filename, 'rb')
|
||||
tags = exifread.process_file(exif_tags)
|
||||
|
||||
exif_array = []
|
||||
|
||||
if type.format != "PNG":
|
||||
for i in tags:
|
||||
compile = i, str(tags[i])
|
||||
exif_array.append(compile)
|
||||
|
||||
if type.format == "PNG":
|
||||
image = PngImageFile(filename)
|
||||
metadata = PngInfo()
|
||||
|
||||
for i in image.text:
|
||||
compile = i, str(image.text[i])
|
||||
exif_array.append(compile)
|
||||
|
||||
Description = exif_array[0][1]
|
||||
return Description
|
||||
|
||||
def getTags(self):
|
||||
try:
|
||||
file = open('tags.txt','r')
|
||||
fileContent = file.read()
|
||||
file.close
|
||||
return fileContent
|
||||
except Exception as e:
|
||||
print("Error reading File:", e)
|
||||
return ""
|
||||
|
||||
def show_previous_image(self):
|
||||
if self.image_paths:
|
||||
self.current_index = (self.current_index - 1) % len(self.image_paths)
|
||||
self.show_current_image()
|
||||
|
||||
def show_next_image(self):
|
||||
if self.image_paths:
|
||||
self.current_index = (self.current_index + 1) % len(self.image_paths)
|
||||
self.show_current_image()
|
||||
|
||||
def upload_image(self):
|
||||
description = self.description_text.get("1.0", tk.END)
|
||||
description = description + self.tag_text.get("1.0", tk.END)
|
||||
#alt_text = self.alt_text.get("1.0", tk.END)
|
||||
selected_image_path = self.image_paths[self.current_index]
|
||||
|
||||
media = self.Pixelfed.mediaUpload(selected_image_path)
|
||||
if media:
|
||||
mediaID = media.get("id")
|
||||
#self.Pixelfed.createNewPost(mediaID, description, alt_text)
|
||||
self.Pixelfed.createNewPost(mediaID, description)
|
||||
|
||||
print("Post created:")
|
||||
print("Content:", description)
|
||||
#print("Alt-Text:", alt_text)
|
||||
print("Image:", selected_image_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = ImageUploaderApp(root)
|
||||
root.mainloop()
|
Loading…
Reference in New Issue
Block a user