summaryrefslogtreecommitdiff
path: root/src/home/camera/util.py
blob: 39bfcd306ec35a30a85e668d7f0e5b36f7b54c93 (plain)
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
import asyncio
import os.path
import logging
import psutil

from ..util import chunks
from ..config import config

_logger = logging.getLogger(__name__)
_temporary_fixing = '.temporary_fixing.mp4'


def _get_ffmpeg_path() -> str:
    return 'ffmpeg' if 'ffmpeg' not in config else config['ffmpeg']['path']


def time2seconds(time: str) -> int:
    time, frac = time.split('.')
    frac = int(frac)

    h, m, s = [int(i) for i in time.split(':')]

    return round(s + m*60 + h*3600 + frac/1000)


async def ffmpeg_recreate(filename: str):
    filedir = os.path.dirname(filename)
    tempname = os.path.join(filedir, _temporary_fixing)
    mtime = os.path.getmtime(filename)

    args = [_get_ffmpeg_path(), '-nostats', '-loglevel', 'error', '-i', filename, '-c', 'copy', '-y', tempname]
    proc = await asyncio.create_subprocess_exec(*args,
                                                stdout=asyncio.subprocess.PIPE,
                                                stderr=asyncio.subprocess.PIPE)
    stdout, stderr = await proc.communicate()
    if proc.returncode != 0:
        _logger.error(f'fix_timestamps({filename}): ffmpeg returned {proc.returncode}, stderr: {stderr.decode().strip()}')

    if os.path.isfile(tempname):
        os.unlink(filename)
        os.rename(tempname, filename)
        os.utime(filename, (mtime, mtime))
        _logger.info(f'fix_timestamps({filename}): OK')
    else:
        _logger.error(f'fix_timestamps({filename}): temp file \'{tempname}\' does not exists, fix failed')


async def ffmpeg_cut(input: str,
                     output: str,
                     start_pos: int,
                     duration: int):
    args = [_get_ffmpeg_path(), '-nostats', '-loglevel', 'error', '-i', input,
            '-ss', str(start_pos), '-t', str(duration),
            '-c', 'copy', '-y', output]
    proc = await asyncio.create_subprocess_exec(*args,
                                                stdout=asyncio.subprocess.PIPE,
                                                stderr=asyncio.subprocess.PIPE)
    stdout, stderr = await proc.communicate()
    if proc.returncode != 0:
        _logger.error(f'ffmpeg_cut({input}, start_pos={start_pos}, duration={duration}): ffmpeg returned {proc.returncode}, stderr: {stderr.decode().strip()}')
    else:
        _logger.info(f'ffmpeg_cut({input}): OK')


def dvr_scan_timecodes(timecodes: str) -> list[tuple[int, int]]:
    tc_backup = timecodes

    timecodes = timecodes.split(',')
    if len(timecodes) % 2 != 0:
        raise DVRScanInvalidTimecodes(f'invalid number of timecodes. input: {tc_backup}')

    timecodes = list(map(time2seconds, timecodes))
    timecodes = list(chunks(timecodes, 2))

    # sort out invalid fragments (dvr-scan returns them sometimes, idk why...)
    timecodes = list(filter(lambda f: f[0] < f[1], timecodes))
    if not timecodes:
        raise DVRScanInvalidTimecodes(f'no valid timecodes. input: {tc_backup}')

    # https://stackoverflow.com/a/43600953
    timecodes.sort(key=lambda interval: interval[0])
    merged = [timecodes[0]]
    for current in timecodes:
        previous = merged[-1]
        if current[0] <= previous[1]:
            previous[1] = max(previous[1], current[1])
        else:
            merged.append(current)

    return merged


class DVRScanInvalidTimecodes(Exception):
    pass


def has_handle(fpath):
    for proc in psutil.process_iter():
        try:
            for item in proc.open_files():
                if fpath == item.path:
                    return True
        except Exception:
            pass

    return False