def test_frames_getting(): from ffvideo import VideoStream vs = VideoStream(v0) f1 = vs.current() # first frame f2 = vs.next() assert f2.timestamp > f1.timestamp f = vs[0] # first frame assert f.frameno == 0 f = vs.get_frame_no(100) # f = vs[100] # assert f.frameno == 100 f = vs.get_frame_no(100) f = vs.get_frame_at_sec(1) assert f.timestamp - 1 < 0.1 f = vs.get_frame_at_pts(133000) assert f.width == vs.frame_width assert f.height == vs.frame_height assert f.mode == vs.frame_mode
class Movie(QtCore.QObject): """This class represent the video data stream""" frameChanged = QtCore.pyqtSignal() endOfVideo = QtCore.pyqtSignal() def __init__(self, fileName=None): """Initialize the video stream and open the video file if a fileName is provided. :param filename: a string containing the name of the file to be opened. """ self.rawBuffer=None self.source=None super(Movie, self).__init__() if(fileName is not None): self.setMovie(fileName) self.timer = None self.frame = None self.isPlaying = False self.frameRate = 0 self.frameNumber = 0 def reset(self): """Reset the movie object. Remove the source.""" self.rawBuffer=None self.source=None self.timer = None self.frame = None self.isPlaying = False self.frameRate = 0 self.frameNumber = 0 def setMovie(self, fileName): """Open a video file. :param filename: a string containing the name of the file to be opened. :raise: TypeError: The fileName is not a string. """ if(isinstance(fileName, basestring)): self.fileName = u''.join(fileName).encode('utf-8') self.source = VideoStream(self.fileName) else: raise TypeError('fileName must be a string') self.frameRate = self.source.framerate self.frameNumber = self.source.duration*1000/self.source.framerate self.timer = QtCore.QTimer() self.timer.setInterval(1000.0/self.frameRate) self.timer.setSingleShot(False) self.timer.timeout.connect(self.frameMustChange) def resetMovie(self): """Reset the video stream.""" self.source = VideoStream(self.fileName) def play(self): """Start to read the video stream.""" self.isPlaying = True self.timer.start() def pause(self): """Pause the reading of the video stream.""" self.isPlaying = False self.timer.stop() def frameMustChange(self): """Slot called when it is time to load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ self.readNextFrame() def toggleState(self): """Toggle between playing and pausing the video.""" if(self.isPlaying==True): self.pause() else: self.play() def jumpToFrame(self, position): """Modify the position in the video. :param position: a value between 0 and 1 corresponding to the position in the video. 0 is the beginning and 1 is the end. """ if(position>1.0): position = 1.0 elif(position<0.001): position = 0.001 frame = self.source.get_frame_at_sec(position*self.source.duration).ndarray return frame def readNextFrame(self): """Load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ try: self.rawBuffer = self.source.next().ndarray() except ffvideo.NoMoreData: self.isPlaying = False self.pause() self.rawBuffer = None self.endOfVideo.emit() self.frameChanged.emit() def readNCFrame(self, number): """Load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ position = self.source.current().frameno try: self.rawBuffer = self.source.get_frame_no(position+number).ndarray() except ffvideo.NoMoreData: self.isPlaying = False self.pause() self.endOfVideo.emit() if(self.source.current().frameno>=self.source.duration*self.source.framerate): self.isPlaying = False self.pause() self.endOfVideo.emit() self.frameChanged.emit() def currentPositionRatio(self): """Returns the position in the video. :return: a value between 0 and 1 representing the position in the video. 0 is the beginning and 1 is the end. """ if(self.source is not None and self.source.current() is not None): result = self.source.current().timestamp/self.source.duration if(result>1.0): return 1.0 elif(result<0.0): return 0.0 else: return result else: return 1.0 def getFrameNumber(self): """Returns the number of frame in the video. :return: An integer representing the number of frame in the video. """ return int(self.source.duration*self.source.framerate) def getFrameSize(self): return (self.source.frame_width, self.source.frame_height) def getEllapsedTime(self): """Returns the number ellapsed time in the video. :return: An integer representing the number of frame in the video. """ return self.source.current().timestamp
class videoExplorer(object): """ Class for access of recorded video files Allows to filter video files in a root folder to be retrieved by date """ def __init__(self, verbose=False, rootPath="/run/media/peter/Elements/peter/data/"): """ Args: verbose (bool): switch verbosity on (prints some status messages) rootPath (string): root directory to be scanned for video files """ self.rootPath = rootPath self.start = 0 self.end = 0 self.fileList = [] self.nightList = [] self.dayList = [] self.verbose = verbose self.vs = None self.frameMode = None self.frame = None def setTimeRange(self, start, end): """ set range of video explorer (works similar to a slice()) :note:`The range is inclusive for the lower and upper end of the range` Args: start (datetime object): beginning of time range end(datetime object): end of time range """ self.start = start self.end = end def setRootPath(self, root): """ Set root path of object Test: asdad """ self.rootPath = root def parseFiles(self): """ parses files in the main path and returns list of all files fitting the start - end criteria make sure that before calling this function: - start and end datetimes were set - mainPath is set .. seealso:: :func:`setTimeRange()` :func:`setRootPath()` """ if self.start == 0 or self.end == 0: raise ValueError("start or end value not set properly") self.fileList = [] for root, dirs, files in os.walk(self.rootPath): for fn in files: ################################################################ TODO: sort files by file name fileDT = self.fileName2DateTime(fn) if fileDT == -1: ## file is no mp4 file of interest continue ################################################################ TODO: add option to load files without filter if self.start <= fileDT and self.end >= fileDT: ## if timestamp is within the given range, add to list self.fileList.append([fileDT, root + r'/' + fn]) self.filterDayVideos() self.filterNightVideos() def filterDayVideos(self): """ generates list (self.nightList) of videos that were recorded during day (between 10am and 11pm) """ self.dayList = [] for vid in self.fileList: hour = vid[0].hour if hour >= 10 and hour < 23: self.dayList.append(vid) def filterNightVideos(self): """ generates list (self.nightList) of videos that were recorded during night (between 11pm and 10am) """ self.nightList = [] for vid in self.fileList: hour = vid[0].hour if hour < 10 or hour >= 23: self.nightList.append(vid) def getPathsOfList(self, list): """ returns a list of pure filenames without the corresponding datetime """ out = [] for item in list: out.append(item[1]) return out def getDatesOfList(self, list): """ returns a list of pure datetime without the corresponding paths """ out = [item[0] for item in list] return out def fileName2DateTime(self, fn, ending="mp4"): """ converts filename of video file to python datetime object Args: fn (string): filename Returns: out (datetime object): conversion of filename """ basename = os.path.basename(fn) parts = re.split("[.]", basename) date = re.split("[-]", parts[0]) time = re.split("[-]", parts[1]) if fn[-len(ending):] != ending: return -1 if len(date) != 3: raise ValueError("mp4 file without proper date part discovered") return -1 if len(time) < 3: raise ValueError("mp4 file without proper time part discovered") return -1 out = dt.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2])) return out def findInteruptions(self, fileList): """ separates the filelist into ranges of consequent films, i.e. each range represents a batch of videos that share the same background model :note:`it is assumed that each consequtive file is 1 min long` Args: fileList (list of [datetime, path]) each element of this list can be generated by :func:`fileName2DateTime` Returns: ranges of datetime that can be passed directly into :func:`setTimeRange` """ # set file chunk size to 1 minute chunkSize = dt.time(0, 1) fileList = sorted(fileList) rngs = [] start = None stop = None if len(fileList) < 2: raise ValueError("There are less then 2 files in the list. That usually means that a wrong folder was selected.") for i in range(len(fileList) - 1): if start is None: start = fileList[i][0] if (not (fileList[i + 1][0] - fileList[i][0]) <= dt.timedelta(minutes=1))\ or fileList[i + 1][0].second != 0: stop = fileList[i][0] elif (not (fileList[i + 1][0] - fileList[i][0]) == dt.timedelta(minutes=1))\ or fileList[i + 1][0].second != 0: stop = fileList[i][0] if stop is not None: rngs += [[start, stop, i]] start = None stop = None stop = fileList[i+1][0] rngs += [[start, stop, i]] return rngs def getRandomFrame(self, pathList, info=False, frameMode='L'): """ returns the first frame from a random video of the list Args: pathList (List of Strings): paths to video files info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ file = pathList[random.randint(0, len(pathList) - 1)] frameNo = random.randint(0, 1600 - 1) if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode frame = self.vs.next().ndarray() if info: return [frame, file] else: return frame def getFrame(self, file, frameNo=0, info=False, frameMode='L'): """ returns the given frame from a given video Args: file (String): path to video file frameNo (int): frame number to be returned info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) if not self.vs \ or self.frameMode != frameMode \ or self.vs.filename != file: self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode frame = self.vs.get_frame_no(frameNo).ndarray() if info: return [frame, file] else: return frame def next(self): """ returns next frame in opened video file Call getFrame() or getRandomFrame() first Args: info (bool): return frame and filename .. seealso:: :func:`getFrame` :func:`getRandomFrame` """ if self.vs is None: raise AttributeError("no video stream defined. (It might be that" +\ " the last frame was captured before)") try: frame = self.vs.next().ndarray() except ffvideo.NoMoreData: self.vs = None frame = None raise StopIteration return frame def setVideoStream(self, file, info=False, frameMode='L'): """ returns the first frame from a random video of the list Args: pathList (List of Strings): paths to video files info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ frameNo = random.randint(0, 1600 - 1) if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) if not self.frame is None: del self.frame if not self.vs is None: del self.vs self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode def __iter__(self): # rewind ffvideo thingy if self.vs is not None: self.vs.__iter__() return self @staticmethod def splitFolders(path): """ splits path into its folders and returns them in a list. .. note:: Last entry of the returned list will be the basename of the path if applicable Args: path (string): path to split Returns: folders (list of strings): list of folders in path Source: http://stackoverflow.com/questions/3167154/how-to-split-a-dos-path-into-its-components-in-python """ folders=[] while 1: path,folder=os.path.split(path) if folder!="": folders.append(folder) else: if path!="": folders.append(path) break folders.reverse() return folders @staticmethod def retrieveVideoLength(filename, initialStepSize=10000): """ Finds video length by accessing it bruteforce """ idx = initialStepSize modi = initialStepSize vE = videoExplorer() reached_end = False while modi > 0: while True: try: vE.getFrame(filename, frameNo=idx, frameMode='RGB') except ffvideo.FFVideoError: if modi == 1: idx -= modi reached_end = True break except StopIteration: if modi == 1: idx -= modi reached_end = True break if reached_end and modi > 1: modi /= 2 idx += modi modi /= 2 idx -= modi return idx + 1 # + 1 to make it the same behaviour as len() @staticmethod def findFileInList(lst, filename): """ returns the position of the file within the list of paths """ return [a for a,b in enumerate(lst) if b[1].endswith(filename)] @staticmethod def findClosestDate(dateList, date): min(DTlist,key=lambda date : abs(dt-date))
class videoExplorer(object): """ Class for access of recorded video files Allows to filter video files in a root folder to be retrieved by date """ def __init__(self, verbose=False, rootPath="/run/media/peter/Elements/peter/data/"): """ Args: verbose (bool): switch verbosity on (prints some status messages) rootPath (string): root directory to be scanned for video files """ self.rootPath = rootPath self.start = 0 self.end = 0 self.fileList = [] self.nightList = [] self.dayList = [] self.verbose = verbose self.vs = None self.frameMode = None self.frame = None def setTimeRange(self, start, end): """ set range of video explorer (works similar to a slice()) :note:`The range is inclusive for the lower and upper end of the range` Args: start (datetime object): beginning of time range end(datetime object): end of time range """ self.start = start self.end = end def setRootPath(self, root): """ Set root path of object Test: asdad """ self.rootPath = root def parseFiles(self): """ parses files in the main path and returns list of all files fitting the start - end criteria make sure that before calling this function: - start and end datetimes were set - mainPath is set .. seealso:: :func:`setTimeRange()` :func:`setRootPath()` """ if self.start == 0 or self.end == 0: raise ValueError("start or end value not set properly") self.fileList = [] for root, dirs, files in os.walk(self.rootPath): for fn in files: ################################################################ TODO: sort files by file name fileDT = self.fileName2DateTime(fn) if fileDT == -1: ## file is no mp4 file of interest continue ################################################################ TODO: add option to load files without filter if self.start <= fileDT and self.end >= fileDT: ## if timestamp is within the given range, add to list self.fileList.append([fileDT, root + r'/' + fn]) self.filterDayVideos() self.filterNightVideos() def filterDayVideos(self): """ generates list (self.nightList) of videos that were recorded during day (between 10am and 11pm) """ self.dayList = [] for vid in self.fileList: hour = vid[0].hour if hour >= 10 and hour < 23: self.dayList.append(vid) def filterNightVideos(self): """ generates list (self.nightList) of videos that were recorded during night (between 11pm and 10am) """ self.nightList = [] for vid in self.fileList: hour = vid[0].hour if hour < 10 or hour >= 23: self.nightList.append(vid) def getPathsOfList(self, list): """ returns a list of pure filenames without the corresponding datetime """ out = [] for item in list: out.append(item[1]) return out def getDatesOfList(self, list): """ returns a list of pure datetime without the corresponding paths """ out = [item[0] for item in list] return out def fileName2DateTime(self, fn, ending="mp4"): """ converts filename of video file to python datetime object Args: fn (string): filename Returns: out (datetime object): conversion of filename """ basename = os.path.basename(fn) parts = re.split("[.]", basename) date = re.split("[-]", parts[0]) time = re.split("[-]", parts[1]) if fn[-len(ending):] != ending: return -1 if len(date) != 3: raise ValueError("mp4 file without proper date part discovered") return -1 if len(time) < 3: raise ValueError("mp4 file without proper time part discovered") return -1 out = dt.datetime(int(date[0]), int(date[1]), int(date[2]), int(time[0]), int(time[1]), int(time[2])) return out def findInteruptions(self, fileList): """ separates the filelist into ranges of consequent films, i.e. each range represents a batch of videos that share the same background model :note:`it is assumed that each consequtive file is 1 min long` Args: fileList (list of [datetime, path]) each element of this list can be generated by :func:`fileName2DateTime` Returns: ranges of datetime that can be passed directly into :func:`setTimeRange` """ # set file chunk size to 1 minute chunkSize = dt.time(0, 1) fileList = sorted(fileList) rngs = [] start = None stop = None if len(fileList) < 2: raise ValueError( "There are less then 2 files in the list. That usually means that a wrong folder was selected." ) for i in range(len(fileList) - 1): if start is None: start = fileList[i][0] if (not (fileList[i + 1][0] - fileList[i][0]) <= dt.timedelta(minutes=1))\ or fileList[i + 1][0].second != 0: stop = fileList[i][0] elif (not (fileList[i + 1][0] - fileList[i][0]) == dt.timedelta(minutes=1))\ or fileList[i + 1][0].second != 0: stop = fileList[i][0] if stop is not None: rngs += [[start, stop, i]] start = None stop = None stop = fileList[i + 1][0] rngs += [[start, stop, i]] return rngs def getRandomFrame(self, pathList, info=False, frameMode='L'): """ returns the first frame from a random video of the list Args: pathList (List of Strings): paths to video files info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ file = pathList[random.randint(0, len(pathList) - 1)] frameNo = random.randint(0, 1600 - 1) if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode frame = self.vs.next().ndarray() if info: return [frame, file] else: return frame def getFrame(self, file, frameNo=0, info=False, frameMode='L'): """ returns the given frame from a given video Args: file (String): path to video file frameNo (int): frame number to be returned info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) if not self.vs \ or self.frameMode != frameMode \ or self.vs.filename != file: self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode frame = self.vs.get_frame_no(frameNo).ndarray() if info: return [frame, file] else: return frame def next(self): """ returns next frame in opened video file Call getFrame() or getRandomFrame() first Args: info (bool): return frame and filename .. seealso:: :func:`getFrame` :func:`getRandomFrame` """ if self.vs is None: raise AttributeError("no video stream defined. (It might be that" +\ " the last frame was captured before)") try: frame = self.vs.next().ndarray() except ffvideo.NoMoreData: self.vs = None frame = None raise StopIteration return frame def setVideoStream(self, file, info=False, frameMode='L'): """ returns the first frame from a random video of the list Args: pathList (List of Strings): paths to video files info (bool): return frame and filename frameMode (String): Switch of color mode: - 'RGB': color representation - 'L': gray-scale representation - 'F': ??? Returns: frame (ndarray): decoded video frame """ frameNo = random.randint(0, 1600 - 1) if self.verbose: print "processing frame {0} of video {1}".format(frameNo, file) if not self.frame is None: del self.frame if not self.vs is None: del self.vs self.vs = VideoStream(file, frame_mode=frameMode, exact_seek=True) self.frameMode = frameMode def __iter__(self): # rewind ffvideo thingy if self.vs is not None: self.vs.__iter__() return self @staticmethod def splitFolders(path): """ splits path into its folders and returns them in a list. .. note:: Last entry of the returned list will be the basename of the path if applicable Args: path (string): path to split Returns: folders (list of strings): list of folders in path Source: http://stackoverflow.com/questions/3167154/how-to-split-a-dos-path-into-its-components-in-python """ folders = [] while 1: path, folder = os.path.split(path) if folder != "": folders.append(folder) else: if path != "": folders.append(path) break folders.reverse() return folders @staticmethod def retrieveVideoLength(filename, initialStepSize=10000): """ Finds video length by accessing it bruteforce """ idx = initialStepSize modi = initialStepSize vE = videoExplorer() reached_end = False while modi > 0: while True: try: vE.getFrame(filename, frameNo=idx, frameMode='RGB') except ffvideo.FFVideoError: if modi == 1: idx -= modi reached_end = True break except StopIteration: if modi == 1: idx -= modi reached_end = True break if reached_end and modi > 1: modi /= 2 idx += modi modi /= 2 idx -= modi return idx + 1 # + 1 to make it the same behaviour as len() @staticmethod def findFileInList(lst, filename): """ returns the position of the file within the list of paths """ return [a for a, b in enumerate(lst) if b[1].endswith(filename)] @staticmethod def findClosestDate(dateList, date): min(DTlist, key=lambda date: abs(dt - date))
class Movie(QtCore.QObject): """This class represent the video data stream""" frameChanged = QtCore.pyqtSignal() endOfVideo = QtCore.pyqtSignal() def __init__(self, fileName=None): """Initialize the video stream and open the video file if a fileName is provided. :param filename: a string containing the name of the file to be opened. """ self.rawBuffer = None self.source = None super(Movie, self).__init__() if (fileName is not None): self.setMovie(fileName) self.timer = None self.frame = None self.isPlaying = False self.frameRate = 0 self.frameNumber = 0 def reset(self): """Reset the movie object. Remove the source.""" self.rawBuffer = None self.source = None self.timer = None self.frame = None self.isPlaying = False self.frameRate = 0 self.frameNumber = 0 def setMovie(self, fileName): """Open a video file. :param filename: a string containing the name of the file to be opened. :raise: TypeError: The fileName is not a string. """ if (isinstance(fileName, basestring)): self.fileName = u''.join(fileName).encode('utf-8') self.source = VideoStream(self.fileName) else: raise TypeError('fileName must be a string') self.frameRate = self.source.framerate self.frameNumber = self.source.duration * 1000 / self.source.framerate self.timer = QtCore.QTimer() self.timer.setInterval(1000.0 / self.frameRate) self.timer.setSingleShot(False) self.timer.timeout.connect(self.frameMustChange) def resetMovie(self): """Reset the video stream.""" self.source = VideoStream(self.fileName) def play(self): """Start to read the video stream.""" self.isPlaying = True self.timer.start() def pause(self): """Pause the reading of the video stream.""" self.isPlaying = False self.timer.stop() def frameMustChange(self): """Slot called when it is time to load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ self.readNextFrame() def toggleState(self): """Toggle between playing and pausing the video.""" if (self.isPlaying == True): self.pause() else: self.play() def jumpToFrame(self, position): """Modify the position in the video. :param position: a value between 0 and 1 corresponding to the position in the video. 0 is the beginning and 1 is the end. """ if (position > 1.0): position = 1.0 elif (position < 0.001): position = 0.001 frame = self.source.get_frame_at_sec(position * self.source.duration).ndarray return frame def readNextFrame(self): """Load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ try: self.rawBuffer = self.source.next().ndarray() except ffvideo.NoMoreData: self.isPlaying = False self.pause() self.rawBuffer = None self.endOfVideo.emit() self.frameChanged.emit() def readNCFrame(self, number): """Load the next frame. :raise: Exception: The file cannot be read because the codec is not supported or the video is compressed. """ position = self.source.current().frameno try: self.rawBuffer = self.source.get_frame_no(position + number).ndarray() except ffvideo.NoMoreData: self.isPlaying = False self.pause() self.endOfVideo.emit() if (self.source.current().frameno >= self.source.duration * self.source.framerate): self.isPlaying = False self.pause() self.endOfVideo.emit() self.frameChanged.emit() def currentPositionRatio(self): """Returns the position in the video. :return: a value between 0 and 1 representing the position in the video. 0 is the beginning and 1 is the end. """ if (self.source is not None and self.source.current() is not None): result = self.source.current().timestamp / self.source.duration if (result > 1.0): return 1.0 elif (result < 0.0): return 0.0 else: return result else: return 1.0 def getFrameNumber(self): """Returns the number of frame in the video. :return: An integer representing the number of frame in the video. """ return int(self.source.duration * self.source.framerate) def getFrameSize(self): return (self.source.frame_width, self.source.frame_height) def getEllapsedTime(self): """Returns the number ellapsed time in the video. :return: An integer representing the number of frame in the video. """ return self.source.current().timestamp