import ast import datetime import json import re import uuid import logging import base64 import subprocess import tempfile import easywebdav import os import os.path import requests import hashlib from PIL import Image,ImageDraw,ImageFont from odoo import api, fields, models, _ from odoo import tools from odoo.exceptions import ValidationError from datetime import date from datetime import datetime from dateutil.relativedelta import relativedelta from pyffmpeg import FFmpeg from tuya_iot import TuyaOpenAPI, TUYA_LOGGER from tuya_connector import TuyaOpenAPI, TUYA_LOGGER from webdav4.client import Client from webdav4.fsspec import WebdavFileSystem import sys TUYA_LOGGER.setLevel(logging.DEBUG) _logger = logging.getLogger(__name__) class dssbinaries(models.Model): @api.model def create(self,vals): result = super().create(vals) return result _name = "dss.binaries" _description = "DigitalSignage Dateien" # _inherit = ['mail.thread','mail.activity.mixin'] _rec_name = "binary_id" # _inherit = ['mail.thread', 'mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') date_create = fields.Date('Erstellungsdatum',default=lambda self: self._default_create_date()) date_lastedit = fields.Date('Änderungsdatum') user_create = fields.Char('Erstellungsuser',default=lambda self: self._default_create_user()) user_lastedit = fields.Char('Änderungsuser') binary_id = fields.Char(string="Binary ID", required=True, help = "Das ist eine eindeutige ID für diese Dateityp. Sie wird automatisch generiert und sollte nicht manuell geändert werden.", default=lambda self: self._default_binary_id()) binary_name = fields.Char(string="Binary Name", required=True, help = "Der Name des Dateityp, die Sie hochladen möchten. Dieser Name wird in der Benutzeroberfläche angezeigt.") binary_mediatype = fields.Many2one('dss.mediatypes', string="Binary Mediatype", required=True, help = "Der Mediatyp, der für diese Datei verwendet werden soll. Dies kann z.B. 'Video', 'Bild' oder 'Audio' sein.") binary_mediatype_can_gen_stamp = fields.Boolean(related='binary_mediatype.can_gen_stamp') binary_mediatype_name = fields.Char(related='binary_mediatype.medianame', string="Mediatype Name", store=True, help = "Der Name des Mediatyps, der für diese Datei verwendet wird. Dies wird automatisch aus dem Mediatypen-Feld abgerufen") binary_mediatype_def_filename = fields.Char(related='binary_mediatype.filepartname', string="Mediatype Default Dateiname", help = "Der Standard-Dateiname für diesen Mediatyp. Dies wird automatisch aus dem Mediatypen-Feld abgerufen") binary_binary = fields.Binary(string="Binary Datei", help = "Die eigentliche Datei, die Sie hochladen möchten. Diese Datei wird in der Datenbank und Cloud gespeichert") binary_binary_t = fields.Binary(string="Binary Temp Datei", help = "Die eigentliche Datei, die Sie hochladen möchten. Diese Datei wird in der Datenbank und Cloud gespeichert.") binary_filename = fields.Char(string="Binary Dateiname", help = "Der Name der hochgeladenen Datei. Dies wird automatisch aus dem Dateinamen der hochgeladenen Datei abgerufen.",compute="_compute_binary_filename", store=True) binary_type = fields.Char(string="Binary Typ", help = "Der Typ den die hochgeladenen Datei haben sollte.") binary_cloud_link = fields.Char(string="Cloud Link", help = "Der Link zur Datei in der Cloud, wenn die Datei in der Cloud gespeichert ist.") binary_size = fields.Integer(string="Binary Größe", help = "Die Größe der hochgeladenen Datei in Bytes.") binary_description = fields.Text(string="Binary Beschreibung", help = "Eine Beschreibung der hochgeladenen Datei. Dies kann Informationen über den Inhalt der Datei, den Verwendungszweck oder andere relevante Details enthalten.") binary_issaved = fields.Boolean(string="Binary Gespeichert", default=False, help = "Ein Flag, das angibt, ob die Datei in der Datenbank und Cloud gespeichert ist. Wenn dies aktiviert ist, wird die Datei in der Datenbank und Cloud gespeichert.") binary_secured_ro = fields.Boolean(string="Gesperrt", default=False, help = "Ein Flag, das angibt, ob die Datei gesperrt ist. Wenn dies aktiviert ist, kann die Datei nicht mehr bearbeitet oder gelöscht werden. Sie wird dann auch nicht in Übertragungen oder Ansichten beachtet.") binary_used_ro = fields.Boolean(string="Genutzt", default=False, help = "Ein Flag, das angibt, ob die Datei in einer Übertragung/Ausstrahlung verwendet wird. Wenn dies aktiviert ist, kann die Datei nicht mehr gelöscht oder überarbeitet werden.") binary_contract = fields.Many2one('dss.contracts', string="Vertrag", help = "Der Vertrag, zu dem diese Datei gehört. Dies kann verwendet werden, um Dateien zu organisieren und zu verwalten, die mit bestimmten Verträgen verbunden sind.") @api.depends('binary_binary') @api.onchange('binary_binary') def _compute_binary_filename(self): """Compute the binary filename based on the uploaded file.""" _logger.info("Binary getting Filename - B_"+str(self.id)) if len(self)>0: for record in self: if record.binary_binary: # Extract the filename from the binary content extension = 'jpg' # Default extension if isinstance(self.binary_binary, str): filedata = base64.b64decode(self.binary_binary.split("base64,")[-1]) else: filedata = base64.b64decode(self.binary_binary) filedataStr = str(filedata) _logger.info("Binary getting Filename - B_"+str(self.id)+" - ["+str(filedataStr[0:23])+"]") if filedataStr[0:12] == "b'\\x89PNG\\": extension = 'png' elif filedataStr[0:17] == "b'\\xff\\xd8\\xff": extension = 'jpg' elif filedataStr[0:8] == "b'GIF89a": extension = 'gif' elif filedataStr[0:22] == "b'\\x49\\x49\\x2a\\x00": extension = 'tif' elif filedataStr[0:27] == "b'\\x25\\x50\\x44\\x46\\x2d": extension = 'pdf' elif filedataStr[0:23] == "b'\\x00\\x00\\x00 ftypisom": extension = 'mp4' elif filedataStr[0:22] == "b'\\x00\\x00\\x00\\x18ftyp": extension = 'mp4' elif filedataStr[0:12] == "b'\\x42\\x4d": extension = 'bmp' record.binary_filename = f"{record.binary_mediatype.filepartname}{record.binary_contract.contract_auto_id}.{extension}" @api.model def _default_uuid(self): return str(uuid.uuid4()) @api.model def _default_create_date(self): return datetime.now().strftime('%Y-%m-%d %H:%M:%S') @api.model def _default_create_user(self): return self.env.user.name if self.env.user else 'System' @api.model def _default_binary_id(self): # Generate a unique binary ID based on the current timestamp and a random UUID return f"Datei_{str(uuid.uuid4())[:8]}" @api.onchange('binary_binary') def _onchange_binary_binary(self): """Update binary size and type when binary content is changed.""" _logger.info("New Binary B_"+str(self.id)) existing_binary = False if not self._origin.binary_binary: existing_binary = False else: existing_binary = True if not self.binary_contract.cloudlink: raise ValidationError(_("Der Vertrag hat keinen Cloudlink. Bitte überprüfen Sie den Vertrag.")) cloudpath = str(self.binary_contract.cloudlink) _logger.info("New Binary checking Cloudlink B_"+str(self.id)+" - "+str(cloudpath)) cloudpath = cloudpath + '/' + str(self.binary_mediatype.cloudlink) client = Client("https://cloud.logumedia.de/remote.php/dav/files/OdooDav/", auth=("odooClient@logumedia.de", "lm2020#OdooDav")) _logger.info("New Binary checking Cloudlink for cont : "+str(cloudpath)) cloudexists = False if client.exists(cloudpath): # Convert self.binary_binary to bytes-like object if isinstance(self.binary_binary, str): filedata = base64.b64decode(self.binary_binary.split("base64,")[-1]) else: filedata = base64.b64decode(self.binary_binary) filedataStr = str(filedata) _logger.info("New Binary Cloudlink exists - Saving File - B_"+str(self.id)+" - "+str(cloudpath)+" - " + str(filedata)+' - ' + filedataStr[0:8]) try: decoded_data = filedata except base64.binascii.Error as e: if 'Incorrect padding' in str(e): filedataStr += b'=' * (4 - len(filedataStr) % 4) decoded_data = base64.b64decode(filedataStr) if filedataStr[0:10] == "b'\x89PNG": extension = 'png' else: extension = 'jpg' _logger.info("New Binary Cloudlink exists - Saving File - B_"+str(self.id)+" - "+str(cloudpath)+" - " + str(base64.b64encode(decoded_data))) ##infile = open('/tmp/'+filename, "w+b") #infile = tempfile.NamedTemporaryFile(delete=False) ##infile.write(filedata) ##client.upload_fileobj(infile, f"{cloudpath}/{filename}") ##infile.close() ##infile.delete() # Delete the temporary file after upload cloudexists = True else: _logger.info("New Binary Cloudlink does not exists B_"+str(self.id)+" - "+str(cloudpath)) raise ValidationError(_("Der Cloudlink im Vertrag ist nicht vorhanden. Bitte überprüfen Sie den Link ggf. anlegen.")) if cloudexists: if self.binary_binary: # Calculate the size of the binary content self.binary_size = len(filedata) # Determine the file type based on the binary content self.binary_cloud_link = f"{cloudpath}/{self.binary_filename}" self.binary_issaved = True else: self.binary_size = 0 self.binary_type = '' self.binary_cloud_link = '' self.binary_issaved = False def dload(self): """Download the binary file.""" if not self.binary_binary: raise ValidationError(_("No binary file to download.")) # Create a temporary file to store the downloaded content temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=self.binary_filename) temp_file.write(base64.b64decode(self.binary_binary)) temp_file.close() # Return the file for download return { 'type': 'ir.actions.act_url', 'url': f'/web/content/{self.id}/{self.binary_filename}?download=true&filename={self.binary_filename}', 'target': 'self', } def dedit(self): """Edit the binary file.""" return { 'type': 'ir.actions.act_window', 'res_model': 'dss.binaries', 'view_mode': 'form', 'res_id': self.id, 'target': 'new', } def resend(self): """Resend the binary file to the contract's partner email.""" if not self.binary_contract: raise ValidationError(_("No contract associated with this binary file.")) if not self.binary_contract.project.standortpartner: raise ValidationError(_("No partner associated with the contract.")) if not self.binary_contract.project.standortpartner.email: raise ValidationError(_("The partner does not have an email address.")) # Send email with the binary file as attachment def genstamp(self): _logger.info('Create Stamps for Binary: B_'+str(self.id)+' '+str(id)) # Bildparameter definieren Medium = self.binary_mediatype if Medium and Medium.can_gen_stamp and self.binary_contract: _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Medium : M_'+str(Medium.id)+' - '+str(Medium.medianame)) # Bildgröße und Hintergrundfarbe festlegen background_color = (255, 255, 255) # Weißer Hintergrund (RGB) # Neues Bild erstellen maxh = Medium.maxsize_h maxw = Medium.maxsize_w image = Image.new("RGB", (maxw, maxh), background_color) d = ImageDraw.Draw(image) Zeilen = Medium.maxsize_h // 6 zposy = 2 if self.binary_contract.client: text = self.binary_contract.contact_company_name _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Medium : M_'+str(Medium.id)+' - Text1: '+str(text)) if len(text) > 35: # Trenne den Text an einem Leerzeichen nahe bei Position 40 idx = text.rfind(' ', 0, 35) if idx == -1: idx = 35 text2 = text[:idx] bcount = len(text2) if text2 else 1 fonts = maxw // (bcount ) *1.8 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, 1*Zeilen), text2, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = text[idx:] zposy = 2 if len(text) > 35: # Trenne den Text an einem Leerzeichen nahe bei Position 40 idx = text.rfind(' ', 0, 35) if idx == -1: idx = 35 text2 = text[:idx] # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, zposy*Zeilen), text2, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = text[idx:] zposy = 3 else: bcount = len(text) if text else 1 fonts = maxw // (bcount ) *1.8 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, zposy*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = self.binary_contract.contact_street bcount = len(text) if text else 1 Zeilen = maxh // 6 fonts = maxw // (bcount ) *1.2 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-Regular.ttf", fonts) d.text((maxw // 2, 4*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = self.binary_contract.contact_zip+' '+self.binary_contract.contact_city bcount = len(text) if text else 1 Zeilen = maxh // 6 fonts = maxw // (bcount ) *1.2 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-Regular.ttf", fonts) d.text((maxw // 2, 5*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck # Bild speichern filename = Medium.filepartname+self.binary_contract.contract_auto_id+".png" _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Feld : F_'+str(Medium.id)+' - Filename: '+str(filename)) image.save(filename) _logger.info("Stamp file created "+str(filename)) _logger.info("Copy file to Binaries B_"+str(self)) try: self.binary_binary = base64.b64encode(open(filename, "rb").read()) self.binary_filename = filename self.binary_type = 'image/png' self.binary_size = os.path.getsize(filename) self.binary_issaved = False self.binary_secured_ro = False self.binary_used_ro = False finally: _logger.info("Binary inserted "+str(self)) cloudpath = self.binary_contract.cloudlink if not cloudpath: raise ValidationError(_("Der Vertrag hat keinen Cloudlink. Bitte überprüfen Sie den Vertrag.")) if not Medium.cloudlink: raise ValidationError(_("Der Mediatyp hat keinen Cloudlink. Bitte überprüfen Sie den Mediatyp.")) cloudpath = cloudpath + '/' + str(Medium.cloudlink) _logger.info("Copy file to Cloud Path : "+str(cloudpath)) try: client = Client("https://cloud.logumedia.de/remote.php/dav/files/OdooDav/", auth=("odooClient@logumedia.de", "lm2020#OdooDav")) try: #client.mkdir(new_folder) #_logger.info("Make Cloud Path : "+str(new_folder)) client.upload_file(filename, f"{cloudpath}/{filename}") except Exception as e: _logger.info("Make Cloud Path error : "+str(e)) finally: self.binary_issaved = True _logger.info("File copied to Cloud Path : "+str(cloudpath)) return True