]> git.basschouten.com Git - openhab-addons.git/blob
6f61ee15afc1d3a2dab33045b092c658fdf03f95
[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 seconds) {
79         if (seconds == -1) {
80             keepAlive = -1;
81         } else {// We now poll every 8 seconds due to mjpeg stream requirement.
82             keepAlive = 8; // 64 seconds approx.
83         }
84     }
85
86     public void checkKeepAlive() {
87         if (keepAlive <= -1) {
88             return;
89         } else if (keepAlive == 0) {
90             stopConverting();
91         } else {
92             keepAlive--;
93         }
94         return;
95     }
96
97     private class IpCameraFfmpegThread extends Thread {
98         private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
99         public int countOfMotions;
100
101         IpCameraFfmpegThread() {
102             setDaemon(true);
103         }
104
105         private void gifCreated() {
106             // Without a small delay, Pushover sends no file 10% of time.
107             ipCameraHandler.setChannelState(CHANNEL_RECORDING_GIF, DecimalType.ZERO);
108             ipCameraHandler.setChannelState(CHANNEL_GIF_HISTORY_LENGTH,
109                     new DecimalType(++ipCameraHandler.gifHistoryLength));
110         }
111
112         private void mp4Created() {
113             ipCameraHandler.setChannelState(CHANNEL_RECORDING_MP4, DecimalType.ZERO);
114             ipCameraHandler.setChannelState(CHANNEL_MP4_HISTORY_LENGTH,
115                     new DecimalType(++ipCameraHandler.mp4HistoryLength));
116         }
117
118         @Override
119         public void run() {
120             try {
121                 process = Runtime.getRuntime().exec(commandArrayList.toArray(new String[commandArrayList.size()]));
122                 if (process != null) {
123                     InputStream errorStream = process.getErrorStream();
124                     InputStreamReader errorStreamReader = new InputStreamReader(errorStream);
125                     BufferedReader bufferedReader = new BufferedReader(errorStreamReader);
126                     String line = null;
127                     while ((line = bufferedReader.readLine()) != null) {
128                         if (format.equals(FFmpegFormat.RTSP_ALARMS)) {
129                             logger.debug("{}", line);
130                             if (line.contains("lavfi.")) {
131                                 if (countOfMotions == 4) {
132                                     ipCameraHandler.motionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
133                                 } else {
134                                     countOfMotions++;
135                                 }
136                             } else if (line.contains("speed=")) {
137                                 if (countOfMotions > 0) {
138                                     countOfMotions--;
139                                     countOfMotions--;
140                                     if (countOfMotions <= 0) {
141                                         ipCameraHandler.noMotionDetected(CHANNEL_FFMPEG_MOTION_ALARM);
142                                     }
143                                 }
144                             } else if (line.contains("silence_start")) {
145                                 ipCameraHandler.noAudioDetected();
146                             } else if (line.contains("silence_end")) {
147                                 ipCameraHandler.audioDetected();
148                             }
149                         } else {
150                             logger.debug("{}", line);
151                         }
152                     }
153                 }
154             } catch (IOException e) {
155                 logger.warn("An error occured trying to process the messages from FFmpeg.");
156             } finally {
157                 switch (format) {
158                     case GIF:
159                         threadPool.schedule(this::gifCreated, 800, TimeUnit.MILLISECONDS);
160                         break;
161                     case RECORD:
162                         threadPool.schedule(this::mp4Created, 800, TimeUnit.MILLISECONDS);
163                         break;
164                     default:
165                         break;
166                 }
167             }
168         }
169     }
170
171     public void startConverting() {
172         if (!ipCameraFfmpegThread.isAlive()) {
173             ipCameraFfmpegThread = new IpCameraFfmpegThread();
174             logger.debug("Starting ffmpeg with this command now:{}", ffmpegCommand);
175             ipCameraFfmpegThread.start();
176             running = true;
177             if (format.equals(FFmpegFormat.HLS)) {
178                 ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.ON);
179             }
180         }
181         if (keepAlive != -1) {
182             keepAlive = 8;
183         }
184     }
185
186     public boolean getIsAlive() {
187         return running;
188     }
189
190     public void stopConverting() {
191         if (ipCameraFfmpegThread.isAlive()) {
192             logger.debug("Stopping ffmpeg {} now", format);
193             running = false;
194             if (process != null) {
195                 process.destroyForcibly();
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 }