]> git.basschouten.com Git - openhab-addons.git/blob
d0741d303cd172d1367ee5638a856c11020a02a4
[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     private String password;
57
58     public Ffmpeg(IpCameraHandler handle, FFmpegFormat format, String ffmpegLocation, String inputArguments,
59             String input, String outArguments, String output, String username, String password) {
60         this.format = format;
61         this.password = password;
62         ipCameraHandler = handle;
63         String altInput = input;
64         // Input can be snapshots not just rtsp or http
65         if (!password.isEmpty() && !input.contains("@") && input.contains("rtsp")) {
66             String credentials = username + ":" + password + "@";
67             // will not work for https: but currently binding does not use https
68             altInput = input.substring(0, 7) + credentials + input.substring(7);
69         }
70         if (inputArguments.isEmpty()) {
71             ffmpegCommand = "-i " + altInput + " " + outArguments + " " + output;
72         } else {
73             ffmpegCommand = inputArguments + " -i " + altInput + " " + outArguments + " " + output;
74         }
75         Collections.addAll(commandArrayList, ffmpegCommand.trim().split("\\s+"));
76         // ffmpegLocation may have a space in its folder
77         commandArrayList.add(0, ffmpegLocation);
78     }
79
80     public void setKeepAlive(int numberOfEightSeconds) {
81         // We poll every 8 seconds due to mjpeg stream requirement.
82         if (keepAlive == -1 && numberOfEightSeconds > 1) {
83             return;// When set to -1 this will not auto turn off stream.
84         }
85         keepAlive = numberOfEightSeconds;
86     }
87
88     public void checkKeepAlive() {
89         if (keepAlive <= -1) {
90             return;
91         } else if (--keepAlive == 0) {
92             stopConverting();
93         }
94     }
95
96     private class IpCameraFfmpegThread extends Thread {
97         private ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
98         public int countOfMotions;
99
100         IpCameraFfmpegThread() {
101             setDaemon(true);
102         }
103
104         private void gifCreated() {
105             // Without a small delay, Pushover sends no file 10% of time.
106             ipCameraHandler.setChannelState(CHANNEL_RECORDING_GIF, DecimalType.ZERO);
107             ipCameraHandler.setChannelState(CHANNEL_GIF_HISTORY_LENGTH,
108                     new DecimalType(++ipCameraHandler.gifHistoryLength));
109         }
110
111         private void mp4Created() {
112             ipCameraHandler.setChannelState(CHANNEL_RECORDING_MP4, DecimalType.ZERO);
113             ipCameraHandler.setChannelState(CHANNEL_MP4_HISTORY_LENGTH,
114                     new DecimalType(++ipCameraHandler.mp4HistoryLength));
115         }
116
117         @Override
118         public void run() {
119             try {
120                 process = Runtime.getRuntime().exec(commandArrayList.toArray(new String[commandArrayList.size()]));
121                 Process localProcess = process;
122                 if (localProcess != null) {
123                     InputStream errorStream = localProcess.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.replaceAll(password, "********"));
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 when keepalive is:{}", format, keepAlive);
193             Process localProcess = process;
194             if (localProcess != null) {
195                 localProcess.destroyForcibly();
196                 running = false;
197             }
198             if (format.equals(FFmpegFormat.HLS)) {
199                 if (keepAlive == -1) {
200                     logger.warn("HLS stopped when Stream should be running non stop, restarting HLS now.");
201                     startConverting();
202                     return;
203                 } else {
204                     ipCameraHandler.setChannelState(CHANNEL_START_STREAM, OnOffType.OFF);
205                 }
206             }
207             keepAlive = 8;
208         }
209     }
210 }