Printing the Tapestry of Game of Thrones on a Fiscal Printer Using Python

Once, in one of the projects, a fiscal printer fell into my hands. Every day we come across these devices when we make payments in stores, but few people realize what they really are. I won’t go into the details of their work, I’ll just say that these are the things that print receipts with purchase data on special thermal paper (yes, almost all fiscal printers have no ink!).



I had to figure out how to get the state of functioning of the fiscal printer and its internal settings. The task has long been completed, and the fiscal printer was abandoned for a long time in the far corner ... Until the idea came to my mind to do some work: D



Such printers allow you to print monochrome pictures. When I had enough of enough prints of seals, emblems and photographs of colleagues, I decided to wave to the print of a long tapestry based on the series in which they constantly killed someone with the words “winter is near” .



The output was such a video:





Detailed steps for printing a tapestry in python under the cat below.



In this article, I will briefly describe the procedure with detailed comments, assuming that the output will be a training article with several useful practical skills:





Download the tapestry video from youtube



It is done very simply using the pytube library, you just need to decide on the index of the video stream that you are going to download.



Function for downloading video from youtube:
import time, pytube #         def load_bmp_from_video(video_url, filename): t1 = time.clock() #     video = pytube.YouTube(video_url) #        streams = video.streams.all() for stream in streams: print(stream) #       ,  18: 360p mp4 video.streams.get_by_itag(18).download("./", filename = filename ) t2 = time.clock() #      print('download done', t2-t1) #       got.mp4   360x640 load_bmp_from_video(video_url = 'https://www.youtube.com/watch?v=aZV4PclhHeA&', filename = 'got')
      
      





When the for stream in streams: print(stream)



line is executed, we see in the console a list of all the video streams contained in the video:



 <Stream: itag = "22" mime_type = "video / mp4" res = "720p" fps = "30fps" vcodec = "avc1.64001F" acodec = "mp4a.40.2">
 <Stream: itag = "43" mime_type = "video / webm" res = "360p" fps = "30fps" vcodec = "vp8.0" acodec = "vorbis">
 <Stream: itag = "18" mime_type = "video / mp4" res = "360p" fps = "30fps" vcodec = "avc1.42001E" acodec = "mp4a.40.2">
 <Stream: itag = "137" mime_type = "video / mp4" res = "1080p" fps = "30fps" vcodec = "avc1.640028">
 <Stream: itag = "248" mime_type = "video / webm" res = "1080p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "136" mime_type = "video / mp4" res = "720p" fps = "30fps" vcodec = "avc1.4d401f">
 <Stream: itag = "247" mime_type = "video / webm" res = "720p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "135" mime_type = "video / mp4" res = "480p" fps = "30fps" vcodec = "avc1.4d401e">
 <Stream: itag = "244" mime_type = "video / webm" res = "480p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "134" mime_type = "video / mp4" res = "360p" fps = "30fps" vcodec = "avc1.4d401e">
 <Stream: itag = "243" mime_type = "video / webm" res = "360p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "133" mime_type = "video / mp4" res = "240p" fps = "30fps" vcodec = "avc1.4d4015">
 <Stream: itag = "242" mime_type = "video / webm" res = "240p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "160" mime_type = "video / mp4" res = "144p" fps = "30fps" vcodec = "avc1.4d400c">
 <Stream: itag = "278" mime_type = "video / webm" res = "144p" fps = "30fps" vcodec = "vp9">
 <Stream: itag = "140" mime_type = "audio / mp4" abr = "128kbps" acodec = "mp4a.40.2">
 <Stream: itag = "249" mime_type = "audio / webm" abr = "50kbps" acodec = "opus">
 <Stream: itag = "250" mime_type = "audio / webm" abr = "70kbps" acodec = "opus">
 <Stream: itag = "251" mime_type = "audio / webm" abr = "160kbps" acodec = "opus">


I selected thread with id 18 because it is a small resolution - we still print it on a check tape, and download faster))



Create a long monochrome tapestry image for printing on a fiscal printer



For video processing, we need the well-known OpenCV library and Pillow (a modern fork of PIL) (although here instead of OpenCV we could use the avconv utility from the libav tool, more about it in the last section of this article). Many thanks to the author for writing python or python-opencv , which is a python wheel, installed via PIP and does not require installing OpenCV itself ( hooray! ).



A fiscal printer can only print special images - monochrome bmp files of a fixed width of 528 pixels (but unlimited length, ho-ho-ho!). In addition, the tapestry image in the video clip is constantly moving, so we need to carefully cut the frames so that we get one long picture.



All this is done by the following function:
 import os, cv2, numpy as np from PIL import Image #            def save_frames_from_vide(filename): #          real_filename = filename.rsplit('.', 1)[0] #         for file in os.listdir('./'): if file.startswith('frame'): os.remove('./' + file) #         frames_list = [] vidcap = cv2.VideoCapture(filename) try: success, frame = vidcap.read() count = 1 while success: # and count < 500: #    #     1  100,     if count in [1, 100, 30945, 31000] or count % 370 == 0: #      (  ) mono_frame = frame if count == 370: mono_frame = mono_frame[0:mono_frame.shape[0], 172:mono_frame.shape[1]] if count == 30710: mono_frame = mono_frame[0:mono_frame.shape[0], 0:mono_frame.shape[1] - 200] mono_frame = mono_frame[20:-20, :] frames_list.append(mono_frame) print('read a new frame: ', success, count) success, frame = vidcap.read() count += 1 finally: vidcap.release() #     gobelin = np.concatenate((frames_list), axis = 1) #   -    cv2.imwrite('%s.png' % real_filename, gobelin) #        image = Image.open('%s.png' % real_filename) #  1        bmp fn = lambda x : 255 if x > 135 else 0 image = image.convert('L').point(fn, mode = '1') #       528  coef = 528. / image.height new_w = int(image.width * coef) new_h = int(image.height * coef) image = image.resize((new_w, new_h)) #    270         image = image.transpose(Image.ROTATE_270) image.save('%s_for_print.bmp' % real_filename) #       'got.png'  'got_for_print.bmp' save_frames_from_vide('got.mp4')
      
      







At the output, we get a long picture with the image of the whole tapestry, only a fragment is shown below, the original picture has a width of 55,000 pixels and does not pass according to the publication rules:







But such an image is obtained after monochrome transformations, only without rotation:







We print a tapestry on a fiscal printer



I have a specific fiscal printer of the Atol fprint-22 model at my disposal, but the general rules apply to other fiscal printer models. Moreover, my fiscal book is quite ancient and does not yet support the new-fangled requirements of FZ-54 (I recall that after the entry into force of this law, all fiscal officers were obliged to send data through the OFD to the tax office, which entailed pain and suffering - a flashing of each device).



A small digression about fiscal printers. They relate to POS devices - this is all kinds of peripherals for the needs of trade, which are connected to a PC and integrated into a single accounting and payment system. Of these well-known devices, you have definitely seen barcode scanners and credit card payment terminals. For all of these devices, a unified UnifiedPOS interaction protocol was invented.



In short, this is a separate topic and a very narrow circle of specialists involved in POS devices. The situation is complicated by the fact that most of these devices are designed for operation exclusively under Windows through COM objects - dll files with a very poor documentary description of functionality. Although I heard about the cash systems running under FreeBSD, but I haven’t seen anything like that while working with POS devices, it’s good that I didn’t need a detailed immersion in the world of Retail POS business processes ...



Therefore, the procedure for working with most of these devices is as follows:





Since I have an ancient fiscal book at my disposal, the driver for it is used exactly the 8th version. Now the manufacturer has added a driver of the 10th version, which greatly simplifies working with a fiscal printer through a separate python wrapper module, which is good news.



The following code demonstrates a function that connects to a fiscal printer using the above-described algorithm, produces a beep, and prints the previously received monochrome tapestry image. We will need to install pywin32 .



The code turned out to be quite long and boring, so I put it under the spoiler:
 import win32com.client from _winreg import HKEY_CURRENT_USER, OpenKey, EnumValue #         class FiscallError(Exception) #     COM  , #  COM     def fiscal_print(filename): driver = None try: #      #         8.16. try: hKey = OpenKey(HKEY_CURRENT_USER, r"Software\Atol\Drivers\8.0\KKM\Devices") except Exception as err: raise FiscallError('      ' + '  ' + '   FPrint22-') #       , #       com  try: device_name,device_connect_params,device_connect_dt=EnumValue(hKey,0) except Exception as err: raise FiscallError('     ' + '     ' + '  FPrint22-') #       try: connect_dir = dict([tup.split(u'=') for tup in device_connect_params]) except Exception as err: raise FiscallError('     ' + '    FPrint22-') #    COM  try: driver = win32com.client.Dispatch("AddIn.FPrnM8") except Exception as err: raise FiscallError(' COM  AddIn.FPrnM8   ' + ' FPrint22-    , ' + '    ') #         add_code = driver.AddDevice() if driver.ResultCode != 0: raise FiscallError('     FPrint22-' + ' [ %s] - %s'% (driver.ResultCode, driver.ResultDescription)) #       COM  driver.Model = connect_dir['Model'] driver.PortNumber = connect_dir['PortNumber'] driver.UseAccessPassword = connect_dir['UseAccessPassword'] driver.DefaultPassword = connect_dir['UseAccessPassword'] driver.PortNumber = connect_dir['PortNumber'] driver.BaudRate = connect_dir['BaudRate'] #         #       COM  driver.DeviceEnabled = 1 #         GetStatus, #       . 61    v8.0 res = driver.GetStatus() if driver.ResultCode != 0: raise FiscallError('     FPrint22- ' + '[ %s] - %s' % (driver.ResultCode, driver.ResultDescription)) ###   #  ,       #   ,       driver.Beep() #  ,      (528) print('driver.PixelLineLength:', driver.PixelLineLength) # !!!       # (    COM ) driver.Alignment = 0 driver.LeftMargin = 0 driver.PrintPurpose = 1 driver.AutoSize = False driver.Scale = 100 #      driver.FileName = filename #    bmp  driver.PrintBitmapFromFile #        10  for i in range(10): driver.PrintString() #      driver.FullCut() # ! except FiscallError as err: raise err except Exception as err: raise FiscallError('    ' + '   FPrint22- - %s' % str(err)) finally: if driver: driver.DeviceEnabled = 0 fiscal_print('got_for_print.bmp')
      
      







The output was such a manuscript, a performance on “A Song of Ice and Fire”:







The printer at the end completely howled, printed slowly and dimly, and then completely finished printing only the final scene - no fiscal printer has ever seen such a load: D



It remains to prepare the video and post on the social network. As an audio series, I found an amateur composition on the 8bit network - the title theme of the series . The idea was to superimpose one another without using a video editor at all, I will write about this later in the final part of the article.



We mount the resulting video clip for publication in social networks



For these purposes, there is a very useful and powerful console tool that replaces the whole video editor - libav . It includes avconf and ffmpeg utilities for working with video and audio files. Honestly, for me this tool was a real discovery, I recommend it to everyone!



The basic idea of ​​installation:





For these purposes, I wrote a script to run on the command line, which can be executed both bash in linux and bat in win (the differences are indicated in the script comments):
 #     avconv -ss 00:00:10 -i got_print.mov -t 00:06:00 -c:v copy got_print_tmp.mov #      ( win) (echo file 'got_8bit.mp3' & echo file 'got_8bit.mp3' & echo file 'got_8bit.mp3') > list.txt #      ( linux) # cat list.txt # file 'got_8bit.mp3' # file 'got_8bit.mp3' # file 'got_8bit.mp3' #     3  ffmpeg -f concat -i list.txt -acodec copy got_8bit_3.mp3 #         avconv -i got_print_tmp.mov -i got_8bit_3.mp3 -c copy got_print_final.mov #       mp4 avconv -i got_print_final.mov -c:v libx264 got_print_final.mp4 #    (windows) del got_8bit_3.mp3 del got_print_tmp.mov #    (linux) # rm got_8bit_3.mp3 # rm got_print_tmp.mov
      
      







That's all, the video is created:





PS: my first article on Habré, planned to write a short article for a start, cut as much as I could) I hope that the reading was pleasant, and the result of my work - interesting)



All Articles