@ -1,9 +1,14 @@
from genericpath import exists
import os , sys
from json import loads
from turtle import width
import uuid
from asyncio_dvrip import DVRIPCam
import asyncio
from nvr_core import NVR
from nvr_types import File
import platform
import aiofiles
def app_dir ( ) :
return os . path . dirname ( os . path . abspath ( __file__ ) )
@ -38,8 +43,155 @@ class Recorder:
else :
return self . name
class TranscodeStatus :
def __init__ ( self , b64 ) - > None :
self . b64 = b64
self . uuid = str ( uuid . uuid4 )
self . h264 = 0
self . downloaded_h264_bytes = 0
self . total_h264_bytes = 0
self . avi = 0
self . mp4 = 0
self . outFile = None
self . done = False
self . outSize = 0
@property
def outName ( self ) :
if self . outFile :
return os . path . split ( self . outFile ) [ - 1 ]
return " "
async def generate_bytes ( self ) :
async with aiofiles . open ( self . outFile , " rb " ) as out :
yield await out . read ( 32 * 1024 )
class TranscodeTools :
statuses : dict [ str , TranscodeStatus ] = { }
WIN32PYTHON = " python-win32 "
def __init__ ( self , tools_directory , transcode_directory , hide_checks = True ) - > None :
self . hide_checks = hide_checks
self . tools_directory = tools_directory
self . transcode_directory = transcode_directory
if not os . path . exists ( tools_directory ) :
print ( " download git repo https://git.pblr-nyk.pro/gsd/MiskaRisa264 and place in backend folder to enable transcode tools " )
self . enabled = False
else :
python_win32_exists = self . python_win32_exists
if not python_win32_exists :
print ( " download https://www.python.org/ftp/python/3.12.3/python-3.12.3-embed-win32.zip and unzip in backend/MiskaRisa/python-win32 all contains files " )
check_exists_needed_files = self . check_exists_needed_files
if not check_exists_needed_files :
print ( " MiskaRisa264 is not fully downloaded, watch in directory to find lost files " )
check_ffmpeg = self . check_ffmpeg ( )
if not check_ffmpeg :
print ( " ffmpeg in not installed on system or on windows in not in PATH env " )
check_converter = self . check_converter ( )
if not check_converter :
print ( " failed run h264_converter.py with python-win32 " )
self . enabled = check_exists_needed_files and python_win32_exists and check_ffmpeg and check_converter
if not self . enabled :
print ( " Cannot enabled transcode tools, have a errors on init, run config_parser with --no-hide-check parameters to more info " )
print ( " Transcode tools " , " enabled " if self . enabled else " disabled " )
@property
def check_exists_needed_files ( self ) :
for file in [ " H264Play.dll " , " h264_converter.py " , " StreamReader.dll " ] :
if not os . path . exists ( os . path . join ( self . tools_directory , file ) ) :
return False
return True
@property
def python_win32_exists ( self ) :
return os . path . exists ( os . path . join ( self . tools_directory , self . WIN32PYTHON ) )
@property
def python_win32 ( self ) :
return os . path . join ( self . tools_directory , self . WIN32PYTHON , " python.exe " )
@property
def converter_script ( self ) :
return os . path . join ( self . tools_directory , " h264_converter.py " )
def check_ffmpeg ( self ) :
from subprocess import call , DEVNULL
try :
return not call ( " ffmpeg -version " . split ( ) , stdin = DEVNULL if self . hide_checks else None , stdout = DEVNULL if self . hide_checks else None , stderr = DEVNULL if self . hide_checks else None )
except :
return False
def check_converter ( self ) :
from subprocess import call , DEVNULL
try :
return not call ( f " { self . python_win32 } { self . converter_script } --help " . split ( ) , stdin = DEVNULL if self . hide_checks else None , stdout = DEVNULL if self . hide_checks else None , stderr = DEVNULL if self . hide_checks else None )
except :
return False
async def h264toavi ( self , source_file , delete_source_file = False ) :
exec_string = " "
if platform . system ( ) == " Windows " :
exec_string + = " "
elif platform . system ( ) == " Linux " :
exec_string + = " wine "
else :
raise Exception ( " Unknown platform to transcode " )
exec_string + = str ( self . converter_script ) + " "
exec_string + = str ( source_file )
proc = await asyncio . create_subprocess_exec ( exec_string . split ( ) )
await proc . wait ( )
if delete_source_file :
os . remove ( source_file )
if os . path . exists ( source_file + " .avi " ) :
return source_file + " .avi "
else :
raise Exception ( " AVI not be created " )
async def avitomp4 ( self , source_file , delete_source_file = False ) :
exec_string = f " ffmpeg -y -i { source_file } { source_file } .mp4 "
proc = await asyncio . create_subprocess_exec ( exec_string . split ( ) )
await proc . wait ( )
if delete_source_file :
os . remove ( source_file )
if os . path . exists ( source_file + " .mp4 " ) :
return source_file + " .mp4 "
else :
raise Exception ( " MP4 not be created " )
def deleteFile ( self , source_file ) :
os . remove ( source_file )
async def processing ( self , status : TranscodeStatus , file : File , nvr : NVR ) :
raw_file = os . path . join ( self . transcode_directory , status . uuid + " .h264 " )
async with aiofiles . open ( raw_file , " wb " ) as raw :
self . statuses [ status . uuid ] . total_h264_bytes = file . size
async for chunk in nvr . stream_file ( file ) :
self . statuses [ status . uuid ] . downloaded_h264_bytes + = len ( chunk )
await raw . write ( chunk )
nvr . logout ( )
self . statuses [ status . uuid ] . avi = 0
avi_file = await self . h264toavi ( raw_file )
self . statuses [ status . uuid ] . avi = 100
self . statuses [ status . uuid ] . mp4 = 0
mp4_file = await self . avitomp4 ( avi_file )
self . statuses [ status . uuid ] . mp4 = 100
self . statuses [ status . uuid ] . outFile = mp4_file
self . statuses [ status . uuid ] . done = True
self . statuses [ status . uuid ] . outSize = os . path . getsize ( mp4_file )
class Config :
def __init__ ( self , config_name = " config.json " ) - > None :
def __init__ ( self , config_name = " config.json " , args = None ) - > None :
raw = load_config ( config_name )
self . listen_address = raw . get ( " backend " , { } ) . get ( " address " , " 0.0.0.0 " )
self . listen_port = int ( raw . get ( " backend " , { } ) . get ( " port " , " 8080 " ) )
@ -51,9 +203,30 @@ class Config:
else :
for recorder in self . recorders :
print ( recorder )
self . transcode_tools : TranscodeTools = self . check_transcode_tools ( not args . no_hide_check if args != None else True )
def getRecorder ( self , index = 0 ) - > Recorder :
return self . recorders [ index ]
def getRecorders ( self ) :
return [ str ( r ) for r in self . recorders ]
return [ str ( r ) for r in self . recorders ]
def check_transcode_directory ( self ) :
t_dir = os . path . join ( app_dir ( ) , " transcode " )
if not os . path . exists ( t_dir ) :
os . mkdir ( t_dir )
return t_dir
def check_transcode_tools ( self , hide_check ) :
tools_dir = os . path . join ( app_dir ( ) , " MiskaRisa264 " )
return TranscodeTools ( tools_dir , self . check_transcode_directory , hide_check )
if __name__ == " __main__ " :
import argparse
parser = argparse . ArgumentParser ( )
parser . add_argument ( " --no-hide-check " , action = " store_true " )
args = parser . parse_args ( )
if args . no_hide_check :
Config ( args = args )
sys . exit ( 0 )