2022-11-16 21:37:01 +01:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Author: Hendrik Schutter, localhorst@mosad.xyz
Date of creation : 2022 / 11 / 16
2022-11-23 18:38:27 +01:00
Date of last modification : 2022 / 11 / 23
2022-11-16 21:37:01 +01:00
"""
import re
2022-11-22 20:10:45 +01:00
import dataclasses
2022-11-16 21:37:01 +01:00
import glob
import datetime
2022-11-22 20:10:45 +01:00
import json
import qrcode
2022-11-23 18:38:27 +01:00
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
2022-11-16 21:37:01 +01:00
2022-11-22 20:10:45 +01:00
@dataclasses.dataclass
2022-11-16 21:37:01 +01:00
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
2022-11-22 20:10:45 +01:00
@dataclasses.dataclass
2022-11-16 21:37:01 +01:00
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
2022-11-22 20:10:45 +01:00
@dataclasses.dataclass
class ReHddInfo :
link : str
version : str
@dataclasses.dataclass
class DriveDataJson :
drive : DriveData
rehdd : ReHddInfo
2022-11-16 21:37:01 +01:00
2022-11-22 20:54:11 +01:00
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 ( ) :
2022-11-16 21:37:01 +01:00
path = " /usr/share/fonts "
files = glob . glob ( path + " /**/DejaVuSans-Bold.ttf " , recursive = True )
return files [ 0 ]
2022-12-07 18:00:12 +01:00
def human_readable_capacity_1024 ( size , decimal_places = 0 ) :
2022-11-16 21:37:01 +01:00
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 } "
2022-12-07 18:00:12 +01:00
def human_readable_capacity_1000 ( size , decimal_places = 0 ) :
for unit in [ ' B ' , ' KB ' , ' MB ' , ' GB ' , ' TB ' , ' PB ' ] :
if size < 1000.0 or unit == ' PB ' :
break
size / = 1000.0
return f " { size : . { decimal_places } f } { unit } "
2022-11-16 21:37:01 +01:00
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 "
2023-01-19 19:54:34 +01:00
def cut_string ( max_lenght , data , direction ) :
2022-11-16 21:37:01 +01:00
if ( len ( data ) > max_lenght ) :
2023-01-19 19:54:34 +01:00
if ( direction == " end " ) :
return data [ 0 : ( max_lenght - 4 ) ] + " ... "
elif ( direction == " start " ) :
return " ... " + data [ ( len ( data ) - max_lenght + 4 ) : ]
else :
return cut_string ( max_lenght , data , " end " )
2022-11-16 21:37:01 +01:00
else :
return data
def format_to_printable ( drive ) :
2022-11-22 20:54:11 +01:00
return DriveDataPrintable (
2023-01-19 19:54:34 +01:00
cut_string ( 20 , re . sub ( r " [^a-zA-Z0-9. ] " , " " , drive . modelfamiliy ) , " end " ) , \
cut_string ( 20 , re . sub ( r " [^a-zA-Z0-9. ] " , " " , drive . modelname ) , " end " ) , \
cut_string ( 20 , human_readable_capacity_1000 ( drive . capacity ) , " end " ) , \
cut_string ( 16 , re . sub ( r " [^a-zA-Z0-9.-_] " , " " , drive . serialnumber ) , " start " ) , \
cut_string ( 30 , human_readable_power_on_hours ( drive . power_on_hours ) , " end " ) , \
cut_string ( 10 , str ( drive . power_cycle ) , " end " ) , \
cut_string ( 10 , str ( drive . smart_error_count ) , " end " ) , \
cut_string ( 30 , datetime . datetime . utcfromtimestamp ( drive . shred_timestamp ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) , " end " ) , \
cut_string ( 30 , str ( datetime . timedelta ( seconds = drive . shred_duration ) ) , " end " ) )
2022-11-16 21:37:01 +01:00
2022-11-22 20:10:45 +01:00
def draw_text ( drawable , printable_data , text_x_offset ) :
2022-11-23 18:38:27 +01:00
try :
font_file_regular = get_font_path_regular ( )
font_file_bold = get_font_path_bold ( )
except Exception as ex :
print ( " unable to find font: " + str ( ex ) )
return
2022-11-20 21:13:44 +01:00
font_size = 20
2022-11-22 20:54:11 +01:00
text_y_offset = 10
2022-11-22 20:10:45 +01:00
text_y_offset_increment = 25
2022-11-22 20:54:11 +01:00
value_colum_x_offset = 120
2022-11-22 20:10:45 +01:00
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
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 ) )
2022-11-22 20:10:45 +01:00
text_y_offset + = text_y_offset_increment
2022-11-22 20:54:11 +01:00
drawable . text ( ( text_x_offset , text_y_offset ) , printable_data . capacity , ( 0 ) , font = ImageFont . truetype ( font_file_bold , font_size * 3 ) )
2022-11-20 21:13:44 +01:00
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 )
2022-11-22 20:10:45 +01:00
def draw_qr_code ( image , data ) :
qr_img = qrcode . make ( data )
2022-11-22 20:54:11 +01:00
qr_img . thumbnail ( ( 291 , 291 ) , Image . Resampling . LANCZOS )
2022-11-22 20:10:45 +01:00
image . paste ( qr_img , ( 7 , 7 ) )
2022-11-20 21:13:44 +01:00
2022-11-16 21:37:01 +01:00
2022-11-23 18:38:27 +01:00
def generate_image ( drive , rehdd_info , output_file ) :
output_width = 696 #in px set by used paper
output_height = 300 #in px
text_x_offset = 300 #in px
2022-11-16 21:37:01 +01:00
2022-11-23 18:38:27 +01:00
qr_data = DriveDataJson ( drive , rehdd_info )
try :
json_qr_daten = json . dumps ( dataclasses . asdict ( qr_data ) )
except Exception as ex :
print ( " unable to generate json: " + str ( ex ) )
return
2022-11-16 21:37:01 +01:00
2022-11-23 18:38:27 +01:00
try :
printable_data = format_to_printable ( drive )
except Exception as ex :
print ( " unable to format data: " + str ( ex ) )
return
2023-01-19 19:54:34 +01:00
#print(printable_data.serialnumber)
2022-11-22 20:10:45 +01:00
2022-11-23 18:38:27 +01:00
#create black and white (binary) image with white background
2022-11-16 21:37:01 +01:00
output_image = Image . new ( ' 1 ' , ( output_width , output_height ) , " white " )
2022-11-23 18:38:27 +01:00
#create draw pane
2022-11-16 21:37:01 +01:00
draw = ImageDraw . Draw ( output_image )
2022-11-22 20:54:11 +01:00
draw_outline ( draw , 1 , 4 , output_width , output_height )
2022-11-22 20:10:45 +01:00
draw_text ( draw , printable_data , text_x_offset )
draw_qr_code ( output_image , str ( json_qr_daten ) . replace ( " " , " " ) )
2022-11-16 21:37:01 +01:00
2022-11-23 18:38:27 +01:00
output_image . save ( output_file )
def main ( ) :
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 , \
2023-01-19 19:54:34 +01:00
serialnumber = " YG6742U56UDRL123456789ABCDEFGJKL " , \
2022-11-23 18:38:27 +01:00
power_on_hours = 7074 , \
power_cycle = 4792 , \
smart_error_count = 1 , \
shred_timestamp = 1647937421 , \
shred_duration = 81718 )
generate_image ( temp_drive , rehdd_info , " output.png " )
2022-11-16 21:37:01 +01:00
if __name__ == " __main__ " :
main ( )