Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
VideoUtils.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2015-2019 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.coreutils;
20
21import com.google.common.io.Files;
22import java.awt.image.BufferedImage;
23import java.io.File;
24import java.io.IOException;
25import java.nio.file.Paths;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.List;
29import java.util.SortedSet;
30import java.util.TreeSet;
31import java.util.logging.Level;
32import org.netbeans.api.progress.ProgressHandle;
33import org.opencv.core.Mat;
34import org.opencv.highgui.VideoCapture;
35import org.openide.util.NbBundle;
36import org.sleuthkit.autopsy.casemodule.Case;
37import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
38import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
39import static org.sleuthkit.autopsy.coreutils.ImageUtils.isMediaThumbnailSupported;
40import org.sleuthkit.autopsy.datamodel.ContentUtils;
41import org.sleuthkit.datamodel.AbstractFile;
42
46public class VideoUtils {
47
48 private static final List<String> SUPPORTED_VIDEO_EXTENSIONS
49 = Arrays.asList("mov", "m4v", "flv", "mp4", "3gp", "avi", "mpg", //NON-NLS
50 "mpeg", "asf", "divx", "rm", "moov", "wmv", "vob", "dat", //NON-NLS
51 "m1v", "m2v", "m4v", "mkv", "mpe", "yop", "vqa", "xmv", //NON-NLS
52 "mve", "wtv", "webm", "vivo", "vc1", "seq", "thp", "san", //NON-NLS
53 "mjpg", "smk", "vmd", "sol", "cpk", "sdp", "sbg", "rtsp", //NON-NLS
54 "rpl", "rl2", "r3d", "mlp", "mjpeg", "hevc", "h265", "265", //NON-NLS
55 "h264", "h263", "h261", "drc", "avs", "pva", "pmp", "ogg", //NON-NLS
56 "nut", "nuv", "nsv", "mxf", "mtv", "mvi", "mxg", "lxf", //NON-NLS
57 "lvf", "ivf", "mve", "cin", "hnm", "gxf", "fli", "flc", //NON-NLS
58 "flx", "ffm", "wve", "uv2", "dxa", "dv", "cdxl", "cdg", //NON-NLS
59 "bfi", "jv", "bik", "vid", "vb", "son", "avs", "paf", "mm", //NON-NLS
60 "flm", "tmv", "4xm"); //NON-NLS
61
62 private static final SortedSet<String> SUPPORTED_VIDEO_MIME_TYPES = new TreeSet<>(
63 Arrays.asList("application/x-shockwave-flash",
64 "video/x-m4v",
65 "video/x-flv",
66 "video/quicktime",
67 "video/avi",
68 "video/msvideo",
69 "video/x-msvideo", //NON-NLS
70 "video/mp4",
71 "video/x-ms-wmv",
72 "video/mpeg",
73 "video/asf")); //NON-NLS
74
75 public static List<String> getSupportedVideoExtensions() {
77 }
78
79 public static SortedSet<String> getSupportedVideoMimeTypes() {
80 return Collections.unmodifiableSortedSet(SUPPORTED_VIDEO_MIME_TYPES);
81 }
82
83 private static final int CV_CAP_PROP_POS_MSEC = 0;
84 private static final int CV_CAP_PROP_FRAME_COUNT = 7;
85 private static final int CV_CAP_PROP_FPS = 5;
86
87 private static final double[] FRAME_GRAB_POS_RATIO = { 0.50, 0.25, 0.75, 0.01 };
88
89 static final Logger LOGGER = Logger.getLogger(VideoUtils.class.getName());
90
91 private VideoUtils() {
92 }
93
104 public static File getVideoFileInTempDir(AbstractFile file) throws NoCurrentCaseException {
105 return Paths.get(Case.getCurrentCaseThrows().getTempDirectory(), "videos", file.getId() + "." + file.getNameExtension()).toFile(); //NON-NLS
106 }
107
108 public static boolean isVideoThumbnailSupported(AbstractFile file) {
109 return isMediaThumbnailSupported(file, "video/", SUPPORTED_VIDEO_MIME_TYPES, SUPPORTED_VIDEO_EXTENSIONS);
110 }
111
122 @NbBundle.Messages({"# {0} - file name",
123 "VideoUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
124 static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
125 java.io.File tempFile;
126 try {
127 tempFile = getVideoFileInTempDir(file);
128 } catch (NoCurrentCaseException ex) {
129 LOGGER.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
130 return null;
131 }
132 if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
133 ProgressHandle progress = ProgressHandle.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName()));
134 progress.start(100);
135 try {
136 Files.createParentDirs(tempFile);
137 if (Thread.interrupted()) {
138 return null;
139 }
140 ContentUtils.writeToFile(file, tempFile, progress, null, true);
141 } catch (IOException ex) {
142 LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS
143 } finally {
144 progress.finish();
145 }
146 }
147 VideoCapture videoFile = new VideoCapture(); // will contain the video
148 BufferedImage bufferedImage = null;
149
150 try {
151 if (!videoFile.open(tempFile.toString())) {
152 LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS
153 return null;
154 }
155 double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
156 double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
157 if (fps <= 0 || totalFrames <= 0) {
158 LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
159 return null;
160 }
161 if (Thread.interrupted()) {
162 return null;
163 }
164
165 double duration = 1000 * (totalFrames / fps); //total milliseconds
166
167 /*
168 * Four attempts are made to grab a frame from a video. The first
169 * attempt at 50% will give us a nice frame in the middle that gets
170 * to the heart of the content. If that fails, the next positions
171 * tried will be 25% and 75%. After three failed attempts, 1% will
172 * be tried in a last-ditch effort, the idea being the video may be
173 * corrupt and that our best chance at retrieving a frame is early
174 * on in the video.
175 *
176 * If no frame can be retrieved, no thumbnail will be created.
177 */
178 int[] framePositions = new int[] {
179 (int) (duration * FRAME_GRAB_POS_RATIO[0]),
180 (int) (duration * FRAME_GRAB_POS_RATIO[1]),
181 (int) (duration * FRAME_GRAB_POS_RATIO[2]),
182 (int) (duration * FRAME_GRAB_POS_RATIO[3]),
183 };
184
185 Mat imageMatrix = new Mat();
186
187 for (int i=0; i < framePositions.length; i++) {
188 if (!videoFile.set(CV_CAP_PROP_POS_MSEC, framePositions[i])) {
189 LOGGER.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
190 // If we can't set the time, continue to the next frame position and try again.
191 continue;
192 }
193 // Read the frame into the image/matrix.
194 if (!videoFile.read(imageMatrix)) {
195 LOGGER.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
196 // If the image is bad for some reason, continue to the next frame position and try again.
197 continue;
198 }
199
200 break;
201 }
202
203 // If the image is empty, return since no buffered image can be created.
204 if (imageMatrix.empty()) {
205 return null;
206 }
207
208 int matrixColumns = imageMatrix.cols();
209 int matrixRows = imageMatrix.rows();
210
211 // Convert the matrix that contains the frame to a buffered image.
212 if (bufferedImage == null) {
213 bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
214 }
215
216 byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
217 imageMatrix.get(0, 0, data); //copy the image to data
218
219 //todo: this looks like we are swapping the first and third channels. so we can use BufferedImage.TYPE_3BYTE_BGR
220 if (imageMatrix.channels() == 3) {
221 for (int k = 0; k < data.length; k += 3) {
222 byte temp = data[k];
223 data[k] = data[k + 2];
224 data[k + 2] = temp;
225 }
226 }
227
228 bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
229 } finally {
230 videoFile.release(); // close the file}
231 }
232 if (Thread.interrupted()) {
233 return null;
234 }
235 return bufferedImage == null ? null : ScalrWrapper.resizeFast(bufferedImage, iconSize);
236 }
237
248 @Deprecated
249 public static File getTempVideoFile(AbstractFile file) {
250 try {
251 return getVideoFileInTempDir(file);
252 } catch (NoCurrentCaseException ex) {
253 // Mimic the old behavior.
254 throw new IllegalStateException(ex);
255 }
256 }
257
258}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static boolean isVideoThumbnailSupported(AbstractFile file)
static List< String > getSupportedVideoExtensions()
static final List< String > SUPPORTED_VIDEO_EXTENSIONS
static final SortedSet< String > SUPPORTED_VIDEO_MIME_TYPES
static File getVideoFileInTempDir(AbstractFile file)
static SortedSet< String > getSupportedVideoMimeTypes()
static File getTempVideoFile(AbstractFile file)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.