#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Author: Hendrik Schutter, localhorst@mosad.xyz Date of creation: 2022/11/16 Date of last modification: 2022/11/16 pip install qrcode """ import re import os import sys import time import subprocess import shlex import shutil import dataclasses import glob import PIL from PIL import Image from PIL import ImageFont from PIL import ImageDraw import datetime import json import qrcode @dataclasses.dataclass class DriveData: drive_index: int drive_state: str #none, deleted, shredded modelfamiliy: str modelname: str capacity: int #in bytes serialnumber: str power_on_hours: int #in hours power_cycle: int smart_error_count: int shred_timestamp: int #unix timestamp shred_duration: int #in seconds @dataclasses.dataclass class DriveDataPrintable: modelfamiliy: str #max lenght 25 modelname: str #max lenght 25 capacity: str #max lenght 25, in human-readable format with unit (GB/TB) serialnumber: str #max lenght 25 power_on_hours: str #max lenght 25, in hours and days and years power_cycle: str #max lenght 25 smart_error_count: str #max lenght 25 shred_timestamp: str #max lenght 25, human-readable shred_duration: str #max lenght 25, human-readable @dataclasses.dataclass class ReHddInfo: link: str version: str @dataclasses.dataclass class DriveDataJson: drive: DriveData rehdd: ReHddInfo rehdd_info = ReHddInfo("https://git.mosad.xyz/localhorst/reHDD", "bV0.2.2") # read this from rehdd process temp_drive = DriveData(drive_index=0, drive_state="shredded", modelfamiliy="Toshiba 2.5\\ HDD MK..65GSSX",\ modelname="TOSHIBA MK3265GSDX", capacity=343597383680, serialnumber="YG6742U56UDRL123", power_on_hours=7074,\ power_cycle=4792, smart_error_count=1, shred_timestamp=1647937421, shred_duration=81718) def get_font_path_regular(): path = "/usr/share/fonts" files = glob.glob(path + "/**/DejaVuSans.ttf", recursive = True) return files[0] def get_font_path_bold(): path = "/usr/share/fonts" files = glob.glob(path + "/**/DejaVuSans-Bold.ttf", recursive = True) return files[0] def human_readable_capacity(size, decimal_places=0): for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']: if size < 1024.0 or unit == 'PiB': break size /= 1024.0 return f"{size:.{decimal_places}f} {unit}" def human_readable_power_on_hours(hours, decimal_places=2): return str(hours) + "h or " + str(int(hours/24)) + "d or " + str("{:.2f}".format(float(hours/24/365))) + "y" def cut_string(max_lenght, data): if (len(data) > max_lenght): return data[0:(max_lenght-4)] + " ..." else: return data def format_to_printable(drive): #print(cut_string(20, re.sub(r"[^a-zA-Z0-9. ]", "", drive.modelfamiliy))) #print(cut_string(20, re.sub(r"[^a-zA-Z0-9. ]", "", drive.modelname))) #print(cut_string(20, human_readable_capacity(drive.capacity))) #print(cut_string(20, re.sub(r"[^a-zA-Z0-9.-_]", "", drive.serialnumber))) #print(cut_string(30, human_readable_power_on_hours(drive.power_on_hours))) #print(cut_string(10, str(drive.power_cycle))) #print(cut_string(10, str(drive.smart_error_count))) #print(cut_string(30, datetime.datetime.utcfromtimestamp(drive.shred_timestamp).strftime('%Y-%m-%d %H:%M:%S'))) #print(cut_string(30, str(datetime.timedelta(seconds = drive.shred_duration)))) return DriveDataPrintable( cut_string(20, re.sub(r"[^a-zA-Z0-9. ]", "", drive.modelfamiliy)),\ cut_string(20, re.sub(r"[^a-zA-Z0-9. ]", "", drive.modelname)),\ cut_string(20, human_readable_capacity(drive.capacity)),\ cut_string(16, re.sub(r"[^a-zA-Z0-9.-_]", "", drive.serialnumber)),\ cut_string(30, human_readable_power_on_hours(drive.power_on_hours)),\ cut_string(10, str(drive.power_cycle)),\ cut_string(10, str(drive.smart_error_count)),\ cut_string(30, datetime.datetime.utcfromtimestamp(drive.shred_timestamp).strftime('%Y-%m-%d %H:%M:%S')),\ cut_string(30, str(datetime.timedelta(seconds = drive.shred_duration)))) def draw_text(drawable, printable_data, text_x_offset): font_file_regular = get_font_path_regular() font_file_bold = get_font_path_bold() font_size = 20 text_y_offset = 10 text_y_offset_increment = 25 value_colum_x_offset = 120 drawable.text((text_x_offset, text_y_offset), printable_data.serialnumber,(0),font=ImageFont.truetype(font_file_bold, 30)) text_y_offset += 40 drawable.text((text_x_offset, text_y_offset), "Familiy: ",(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.modelfamiliy,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Model: ",(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.modelname,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Hours: " ,(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.power_on_hours,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Cycles: ",(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.power_cycle,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Errors: ", (0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.smart_error_count,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Shred on: ",(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.shred_timestamp,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), "Duration: " ,(0),font=ImageFont.truetype(font_file_bold, font_size)) drawable.text((text_x_offset+value_colum_x_offset, text_y_offset), printable_data.shred_duration,(0),font=ImageFont.truetype(font_file_regular, font_size)) text_y_offset += text_y_offset_increment drawable.text((text_x_offset, text_y_offset), printable_data.capacity,(0),font=ImageFont.truetype(font_file_bold, font_size*3)) def draw_outline(drawable, margin, width, output_width, output_height): #upper drawable.line([(margin,margin), (output_width -margin ,margin)], fill=None, width=width, joint=None) #left drawable.line([(margin,margin), (margin ,output_height-margin)], fill=None, width=width, joint=None) #right drawable.line([(output_width-margin,margin), (output_width-margin ,output_height-margin)], fill=None, width=width, joint=None) #lower drawable.line([(margin,output_height - margin), (output_width-margin,output_height -margin)], fill=None, width=width, joint=None) def draw_qr_code(image, data): qr_img = qrcode.make(data) qr_img.thumbnail((291, 291), Image.Resampling.LANCZOS) image.paste(qr_img, (7, 7)) def main(): output_width = 696 output_height = 300 printable_data = format_to_printable(temp_drive) qr_data = DriveDataJson(temp_drive, rehdd_info) json_qr_daten = json.dumps(dataclasses.asdict(qr_data)) #print(printable_data) text_x_offset= 300 output_image = Image.new('1', (output_width, output_height), "white") draw = ImageDraw.Draw(output_image) draw_outline(draw, 1, 4, output_width, output_height) draw_text(draw, printable_data, text_x_offset) draw_qr_code(output_image, str(json_qr_daten).replace(" ", "")) output_image.save("output.png") if __name__ == "__main__": main()