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