run.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/env python3
  2. """
  3. Module Docstring
  4. """
  5. __author__ = "Nikola Kotur"
  6. __version__ = "0.1"
  7. __license__ = "MIT"
  8. import os
  9. import sys
  10. import argparse
  11. import threading
  12. import tempfile
  13. import subprocess
  14. from pprint import pprint as pp
  15. import pyudev
  16. from logzero import logger
  17. class USBDetector():
  18. ''' Monitor udev for detection of usb '''
  19. def __init__(self, handler, source_dir, dest_dir, ftp_args):
  20. ''' Initiate the object '''
  21. self.usb_plugged_in = handler
  22. self.source_dir = source_dir
  23. self.dest_dir = dest_dir
  24. self.ftp_args = ftp_args
  25. thread = threading.Thread(target=self._work)
  26. thread.daemon = True
  27. thread.start()
  28. thread.join()
  29. def _work(self):
  30. ''' Runs the actual loop to detect the events '''
  31. self.context = pyudev.Context()
  32. self.monitor = pyudev.Monitor.from_netlink(self.context)
  33. self.monitor.filter_by(subsystem='block')
  34. # this is module level logger, can be ignored
  35. logger.info("Starting to monitor for usb")
  36. self.monitor.start()
  37. for device in iter(self.monitor.poll, None):
  38. logger.debug("Got USB event: %s", device.action)
  39. if device.action == 'add' and device.device_type == 'partition':
  40. # some function to run on insertion of usb
  41. self.usb_plugged_in(device, self.source_dir, self.dest_dir, self.ftp_args)
  42. def execute(shell_command):
  43. shell_command = shell_command.split(' ')
  44. try:
  45. res = subprocess.check_output(shell_command, stderr=subprocess.PIPE)
  46. return res.decode('utf-8')
  47. except subprocess.CalledProcessError as e:
  48. print('exit code: {}'.format(e.returncode))
  49. print('stdout: {}'.format(e.output.decode(sys.getfilesystemencoding())))
  50. print('stderr: {}'.format(e.stderr.decode(sys.getfilesystemencoding())))
  51. return None
  52. def parse_video_files(usb_dir, base_dir, extension):
  53. files = {}
  54. for file in os.listdir(os.path.join(usb_dir, base_dir)):
  55. if file.endswith(extension):
  56. full_path = (os.path.join(usb_dir, base_dir, file))
  57. file_parts = file.split('_')
  58. part_date = file_parts[0]
  59. if part_date not in files:
  60. files[part_date] = []
  61. files[part_date].append(full_path)
  62. return files
  63. def join_video_files(dest, sources):
  64. with tempfile.TemporaryDirectory() as tmpdirname:
  65. list_file = os.path.join(tmpdirname, 'list')
  66. with open(list_file, 'w') as lfh:
  67. for source_line in sources:
  68. lfh.write("file '%s'\n" % source_line)
  69. ffmpeg_line = 'ffmpeg -safe 0 -f concat -i %s -c copy %s' % (list_file, dest)
  70. output = execute(ffmpeg_line)
  71. if output != None:
  72. logger.info('Removing source files')
  73. for s_file in sources:
  74. os.remove(s_file)
  75. return True
  76. return False
  77. def plugged_in(dev, source_dir, dest_dir, ftp):
  78. logger.info('Detected %s (%s)', dev.device_node, dev.device_type)
  79. with tempfile.TemporaryDirectory() as tmpdirname:
  80. logger.info('Mounting %s on %s', dev.device_node, tmpdirname)
  81. output = execute('mount %s %s' % (dev.device_node, tmpdirname))
  82. # Join files
  83. joined_files = []
  84. for file_group, files in parse_video_files(tmpdirname, source_dir, 'MP4').items():
  85. logger.info('Joining video group %s', file_group)
  86. outfile_name = '%s.mp4' % file_group
  87. outfile_path = os.path.join(dest_dir, outfile_name)
  88. if join_video_files(outfile_path, files):
  89. joined_files.append(outfile_path)
  90. logger.info('Umounting %s from %s', dev.device_node, tmpdirname)
  91. output = execute('umount %s' % (dev.device_node))
  92. # Beam files to FTP
  93. for up_file in joined_files:
  94. logger.info('Uploading %s to %s', up_file, ftp['host'])
  95. execute('ncftpput -u %s -p %s %s %s %s' % (ftp['user'], ftp['password'], ftp['host'], ftp['path'], up_file))
  96. logger.info('Done')
  97. def main(args):
  98. """ Main entry point of the app """
  99. ftp_args = {
  100. 'host': args.ftp_host,
  101. 'path': args.ftp_path,
  102. 'user': args.ftp_user,
  103. 'password': args.ftp_password,
  104. }
  105. usb_detect = USBDetector(plugged_in, args.source, args.destination, ftp_args)
  106. if __name__ == "__main__":
  107. """ This is executed when run from the command line """
  108. parser = argparse.ArgumentParser()
  109. # Optional argument flag which defaults to False
  110. parser.add_argument("-f", "--flag", action="store_true", default=False)
  111. # Directory options
  112. parser.add_argument("-s", "--source", help="source directory with dashcam videos", action="store", dest="source", default="DCIM/Movie")
  113. parser.add_argument("-d", "--destination", help="destination directory for joined dashcam videos", action="store", dest="destination", default="/var/dashcam")
  114. # FTP options
  115. parser.add_argument("--ftp-host", help="ftp host (server)", action="store", default="192.168.88.242")
  116. parser.add_argument("--ftp-path", help="ftp path", action="store", default="/dashcam")
  117. parser.add_argument("--ftp-user", help="ftp user", action="store", default="dashcam")
  118. parser.add_argument("--ftp-password", help="ftp user", action="store", default="")
  119. # Optional verbosity counter (eg. -v, -vv, -vvv, etc.)
  120. parser.add_argument(
  121. "-v",
  122. "--verbose",
  123. action="count",
  124. default=0,
  125. help="Verbosity (-v, -vv, etc)")
  126. # Specify output of "--version"
  127. parser.add_argument(
  128. "--version",
  129. action="version",
  130. version="%(prog)s (version {version})".format(version=__version__))
  131. args = parser.parse_args()
  132. main(args)