]> git.basschouten.com Git - openhab-addons.git/blob
c006d5b5cc210cca2908a57d0c346dca7734508c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.ipcamera.internal;
14
15 import static org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.*;
16
17 import java.io.BufferedReader;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.TimeUnit;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.ipcamera.internal.IpCameraBindingConstants.FFmpegFormat;
31 import org.openhab.binding.ipcamera.internal.handler.IpCameraHandler;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * The {@link Ffmpeg} class is responsible for handling multiple ffmpeg conversions which are used for many tasks
39  *
40  *
41  * @author Matthew Skinner - Initial contribution
42  */
43
44 @NonNullByDefault
45 public class Ffmpeg {
46     private final Logger logger = LoggerFactory.getLogger(getClass());
47     private IpCameraHandler ipCameraHandler;
48     private @Nullable Process process = null;
49     private String ffmpegCommand = "";
50     private FFmpegFormat format;
51     private List<String> commandArrayList = new ArrayList<String>();
52     private IpCameraFfmpegThread ipCameraFfmpegThread = new IpCameraFfmpegThread();
53     private int keepAlive = 8;
54     private boolean running = false;
55     private String password;
56
57     public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
58             String input, String outArguments, String output, String username, String password) {
59         this.format = format;
60         this.password = password;
61         ipCameraHandler = handle;
62         String altInput = input;
63         // Input can be snapshots not just rtsp or http
64         if (!password.isEmpty() && !input.contains("@") && input.contains("rtsp")) {
65             String credentials = username + ":" + password + "@";
66             // will not work for https: but currently binding does not use https
67             altInput = input.substring(0, 7) + credentials + input.substring(7);
68         }
69         if (inputArguments.isEmpty()) {
70             ffmpegCommand = "-i " + altInput + " " + outArguments + " " + output;
71         } else {
72             ffmpegCommand = inputArguments + " -i " + altInput + " " + outArguments + " " + output;
73         }
74         Collections.addAll(commandArrayList, ffmpegCommand.trim().split("\\s+"));
75         // ffmpegLocation may have a space in its folder
76         commandArrayList.add(0, ffmpegLocation);
77     }
78
79     public void setKeepAlive(int numberOfEightSeconds) {
80         // We poll every 8 seconds due to mjpeg stream requirement.
81         if (keepAlive == -1 && numberOfEightSeconds > 1) {
82             return;// When set to -1 this will not auto turn off stream.
83         }
84         keepAlive = numberOfEightSeconds;
85     }
86
87     public void checkKeepAlive() {
88         if (keepAlive <= -1) {
89             return;
90         } else if (--keepAlive == 0) {
91             stopConverting();
92         }
93     }
94
95     private class IpCameraFfmpegThread extends Thread {
96         private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
97         public int countOfMotions;
98
99         IpCameraFfmpegThread() {
100             setDaemon(true);
101         }
102
103         private void gifCreated() {
104             // Without a small delay, Pushover sends no file 10% of time.
105             ipCameraHandler.setChannelState(CHANNEL_RECORDING_GIF, DecimalType.ZERO);
106             ipCameraHandler.setChannelState(CHANNEL_GIF_HISTORY_LENGTH,
107                     new DecimalType(++ipCameraHandler.gifHistoryLength));
108         }
109
110         private void mp4Created() {
111             ipCameraHandler.setChannelState(CHANNEL_RECORDING_MP4, DecimalType.ZERO);
112             ipCameraHandler.setChannelState(CHANNEL_MP4_HISTORY_LENGTH,
113                     new DecimalType(++ipCameraHandler.mp4HistoryLength));
114         }
115
116         @Override
117         public void run() {
118             try {
119                 process = Runtime.getRuntime().exec(commandArrayList.toArray(new String[commandArrayList.size()]));
120                 Process localProcess = process;
121                 if (localProcess != null) {
122                     InputStream errorStream = localProcess.getErrorStream();
123                     InputStreamReader errorStreamReader = new InputStreamReader(errorStream);
124                     BufferedReader bufferedReader = new BufferedReader(errorStreamReader);
125                     String line = null;
126                     while ((line = bufferedReader.readLine()) != null) {
127                         if (format.equals(FFmpegFormat.RTSP_ALARMS)) {
128                             logger.debug("{}", line);
129                             if (line.contains("lavfi.")) {
130                                 if (countOfMotions == 4) {
131                                     ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
132                                 } else {
133                                     countOfMotions++;
134                                 }
135                             } else if (line.contains("speed=")) {
136                                 if (countOfMotions > 0) {
137                                     countOfMotions--;
138                                     countOfMotions--;
139                                     if (countOfMotions <= 0) {
140                                         ipCameraHandler.noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
141                                     }
142                                 }
143                             } else if (line.contains("silence_start")) {
144                                 ipCameraHandler.noAudioDetected();
145                             } else if (line.contains("silence_end")) {
146                                 ipCameraHandler.audioDetected();
147                             }
148                         } else {
149                             logger.debug("{}", line);
150                         }
151                     }
152                 }
153             } catch (IOException e) {
154                 logger.warn("An error occured trying to process the messages from FFmpeg.");
155             } finally {
156                 switch (format) {
157                     case GIF:
158                         threadPool.schedule(this::gifCreated, 800, TimeUnit.MILLISECONDS);
159                         break;
160                     case RECORD:
161                         threadPool.schedule(this::mp4Created, 800, TimeUnit.MILLISECONDS);
162                         break;
163                     default:
164                         break;
165                 }
166             }
167         }
168     }
169
170     public void startConverting() {
171         if (!ipCameraFfmpegThread.isAlive()) {
172             ipCameraFfmpegThread = new IpCameraFfmpegThread();
173             logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand.replaceAll(password, "********"));
174             ipCameraFfmpegThread.start();
175             running = true;
176             if (format.equals(FFmpegFormat.HLS)) {
177                 ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.ON);
178             }
179         }
180         if (keepAlive != -1) {
181             keepAlive = 8;
182         }
183     }
184
185     public boolean getIsAlive() {
186         return running;
187     }
188
189     public void stopConverting() {
190         if (ipCameraFfmpegThread.isAlive()) {
191             logger.debug("Stopping ffmpeg {} now when keepalive is:{}", format, keepAlive);
192             Process localProcess = process;
193             if (localProcess != null) {
194                 localProcess.destroyForcibly();
195                 running = false;
196             }
197             if (format.equals(FFmpegFormat.HLS)) {
198                 if (keepAlive == -1) {
199                     logger.warn("HLS stopped when Stream should be running non stop, restarting HLS now.");
200                     startConverting();
201                     return;
202                 } else {
203                     ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.OFF);
204                 }
205             }
206             keepAlive = 8;
207         }
208     }
209 }