forked from ppamidimarri/TeslaCamMerge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTCMConstants.py
190 lines (167 loc) · 7.71 KB
/
TCMConstants.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import logging
import logging.handlers
import os
import subprocess
import re
import sys
import signal
# Location where the TeslaCamMerge directory is present. Must NOT include trailing /.
PROJECT_PATH = '/home/pavan' # Must contain the directory called TeslaCamMerge (where you cloned this repository), as well as filebrowser.db
PROJECT_USER = 'pavan' # User ID for the application to run as. This user needs to have read permission on all the paths listed here, plus write permission on the SSD and CIFS share
# Locations of the stored footage on the SSD. MUST include trailing /. PROJECT_USER must have read-write permissions on all these paths.
FULL_PATH = '/home/pavan/Footage/Full/' # Where the merged files are stored
RAW_PATH = '/home/pavan/Footage/Raw/' # Where the raw footage from TeslaCam is stored
FAST_PATH = '/home/pavan/Footage/Fast/' # Where the fast preview files are stored
UPLOAD_LOCAL_PATH = '/home/pavan/Footage/Upload/' # Any files placed in this directory will be uploaded to Google Drive
SSD_MOUNT_POINT = '/home/pavan' # Mount point for the SSD
# Location of CIFS share. MUST include trailing /. PROJECT_USER must have read-write permissions.
SHARE_PATH = '/samba/fjnuser/'
# rclone configuration entry for Google Drive. UPLOAD_REMOTE_PATH
# should be a properly-configured entry in your rclone.conf file.
# Any subdirectory must already exist on Google Drive.
UPLOAD_REMOTE_PATH = 'gdrive:/TeslaCam'
# Number of days to keep videos: applies to raw, full and fast videos.
# Videos that are older than these and in the FULL_PATH, FAST_PATH and
# RAW_PATH locations are automatically deleted by removeOld.service
# To keep videos longer, move them to any other directory, or move to
# the UPLOAD_PATH so they are automatically backed up to cloud storage.
DAYS_TO_KEEP = 30
# Filename for an html file with statistics about TeslaCamMerge.
# If STATS_FILENAME is not empty, the application will generate a
# file in the footage directory (i.e. one level up from RAW_PATH)
# that shows how many videos are in which folder, and the overall
# disk usage. It then converts the HTML into an image using
# cutycapt. If the image is successfully created, it then deletes
# the HTML file. Stats are generated when current timestamp's minute
# matches one of the values in STATS_FREQUENCY, so if you want
# stats updated more frequently, add more numbers between 0 and
# 59 to the list.
STATS_FILENAME = 'stats.html'
STATS_IMAGE = 'stats.png'
STATS_FREQUENCY = [0, 30]
STATS_TIMESTAMP_FORMAT = '%-I:%M %p on %a %b %-d, %Y'
# Settings for application logs
LOG_PATH = '/home/pavan/log/' # Must include trailing /, PROJECT_USER needs read-write permissions
LOG_EXTENSION = '.log'
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
LOG_LEVEL = logging.INFO
# Logging settings for TimedRotatingFileHandler, refer to:
# https://docs.python.org/3.6/library/logging.handlers.html#timedrotatingfilehandler
# for details about the three supported options. The default
# is to rotate once a day and keep ten days' worth of logs.
LOG_WHEN = 'd'
LOG_INTERVAL = 1
LOG_BACKUP_COUNT = 10
# Paths of installed software, including name of the application
FFMPEG_PATH = '/usr/bin/ffmpeg' # Verify with: which ffmpeg
RCLONE_PATH = '/usr/local/bin/rclone --log-file /home/pavan/log/rclone.log' # Verify with: which rclone
FILEBROWSER_PATH = '/usr/local/bin/filebrowser' # Verify with: which filebrowser
LSOF_PATH = '/usr/bin/lsof -t' # Verify with: which lsof
DF_PATH = '/bin/df' # Verify with: which df
CUTYCAPT_PATH = '/usr/bin/cutycapt --zoom-factor=1.5' # Verify with: which cutycapt
# Video watermark timestamp format (see Python strftime reference)
WATERMARK_TIMESTAMP_FORMAT = '%b %-d\, %-I\:%M %p'
# Names of text files to be placed in RAW_PATH that will list bad input files
# created by TeslaCam. BAD_VIDEOS_FILENAME will contain the names of files that
# FFMPEG reports errors for (e.g. moov atom not found). BAD_SIZES_FILENAME
# will contain one row for each timestamps where the sizes of the three files
# drastically different (i.e. outside the range specified below in SIZE_RANGE)
BAD_VIDEOS_FILENAME = 'bad_videos.txt'
BAD_SIZES_FILENAME = 'bad_sizes.txt'
### Do not modify anything below this line ###
# Characteristics of filenames output by TeslaCam
FRONT_TEXT = 'front.mp4'
LEFT_TEXT = 'left_repeater.mp4'
RIGHT_TEXT = 'right_repeater.mp4'
FULL_TEXT = 'full.mp4'
FAST_TEXT = 'fast.mp4'
FILENAME_TIMESTAMP_FORMAT = '%Y-%m-%d_%H-%M-%S'
FILENAME_REGEX = '(\d{4}(-\d\d){2}_(\d\d-){3})(right_repeater|front|left_repeater).mp4'
FILENAME_PATTERN = re.compile(FILENAME_REGEX)
# Application management constants
SLEEP_DURATION = 60 # Seconds between looping in main tasks
SPECIAL_EXIT_CODE = 115 # Exit code used by the app, has to be non-zero for systemctl to auto-restart crashed services
SIZE_RANGE = 0.4 # Maximum size difference in percentage between video files, timsestamps with bigger size differences are not merged
FFMPEG_TIMELIMIT = 9000 # CPU time limit in seconds for FFMPEG commands to run
# Common functions
def check_permissions(path, test_write):
logger = logging.getLogger(get_basename())
if os.access(path, os.F_OK):
logger.debug("Path {0} exists".format(path))
if os.access(path, os.R_OK):
logger.debug("Can read at path {0}".format(path))
if test_write:
if os.access(path, os.W_OK):
logger.debug("Can write to path {0}".format(path))
return True
else:
logger.error("Cannot write to path {0}".format(path))
return False
else:
return True
else:
logger.error("Cannot read at path {0}".format(path))
return False
else:
logger.error("Path {0} does not exist".format(path))
return False
def check_file_for_read(file):
if os.access(file, os.F_OK):
return not file_being_written(file)
else:
logging.getLogger(get_basename()).debug(
"File {0} does not exist".format(file))
return False
def file_being_written(file):
logger = logging.getLogger(get_basename())
completed = subprocess.run("{0} {1}".format(LSOF_PATH, file), shell=True,
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if completed.stderr:
logger.error("Error running lsof on file {0}, stdout: {1}, stderr: {2}".format(
file, completed.stdout, completed.stderr))
return True # abundance of caution: if lsof won't run properly, say file is not ready for read
else:
if completed.stdout:
logger.debug("File {0} in use, stdout: {1}, stderr: {2}".format(
file, completed.stdout, completed.stderr))
return True
else:
return False
def check_file_for_write(file):
if os.access(file, os.F_OK):
logging.getLogger(get_basename()).debug("File {0} exists".format(file))
return False
else:
return True
def exit_gracefully(signum, frame):
logging.getLogger(get_basename()).info("Received signal {0}, exiting".format(signum))
exit(signum)
def get_logger():
basename = get_basename()
logger = logging.getLogger(basename)
logger.setLevel(LOG_LEVEL)
fh = logging.handlers.TimedRotatingFileHandler(
LOG_PATH + basename + LOG_EXTENSION,
when=LOG_WHEN, interval=LOG_INTERVAL,
backupCount=LOG_BACKUP_COUNT)
fh.setLevel(LOG_LEVEL)
formatter = logging.Formatter(LOG_FORMAT)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.info("Starting up")
signal.signal(signal.SIGINT, exit_gracefully)
signal.signal(signal.SIGTERM, exit_gracefully)
return logger
def get_basename():
return os.path.splitext(os.path.basename(sys.argv[0]))[0]
def convert_file_size(size):
if size <= 1024:
return "{0:-6d}B".format(size)
elif size <= 1024*1024:
return "{0:-6.1f}K".format(size/1024)
elif size <= 1024*1024*1024:
return "{0:-6.1f}M".format(size/(1024*1024))
elif size <= 1024*1024*1024*1024:
return "{0:-6.1f}G".format(size/(1024*1024*1024))
else:
return "{0:-6.1f}T".format(size/(1024*1024*1024*1024))