diff --git a/README.md b/README.md index fc3ba256..36e26d79 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ PySceneDetect Video Scene Detection and Analysis Tool ---------------------------------------------------------- -PySceneDetect is a command-line tool, written in Python and using OpenCV, which analyzes a video, looking for scene changes or cuts. The output timecodes can then be used with another tool (e.g. `ffmpeg`, `mkvmerge`) to split the video into individual clips. A frame-by-frame analysis can also be generated, to help with determining optimal threshold values or detecting patterns/other analysis methods for a particular video. +PySceneDetect is a command-line tool, written in Python and using OpenCV, which analyzes a video, looking for scene changes or cuts. The output timecodes can then be used with another tool (e.g. `mkvmerge`, `ffmpeg`) to split the video into individual clips. A frame-by-frame analysis can also be generated for a video, to help with determining optimal threshold values or detecting patterns/other analysis methods for a particular video. -Note that PySceneDetect is currently in alpha (see Current Status below for details). +Note that PySceneDetect is currently in alpha; see Current Features & Roadmap below for details. Download & Requirements ---------------------------------------------------------- -You can download [PySceneDetect from here](https://github.com/Breakthrough/PySceneDetect/releases); to run it, you will need: +The latest version of PySceneDetect (`v0.2.1-alpha`) can be [downloaded here](https://github.com/Breakthrough/PySceneDetect/releases); to run it, you will need: - [Python 2 / 3](https://www.python.org/) (tested on 2.7.X, untested but should work on 3.X) - - OpenCV Python Module (can usually be found in Linux package repos already, Windows users can find [prebuilt binaries for Python 2.7 here](http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv)) + - OpenCV Python Module (usually found in Linux package repos as `python-opencv`, Windows users can find [prebuilt binaries for Python 2.7 here](http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv)) - [Numpy](http://sourceforge.net/projects/numpy/) -To ensure you have all the requirements, open a `python` interpreter, and ensure you can `import numpy` and `import cv2` without any errors. +To ensure you have all the requirements, open a `python` interpreter, and ensure you can `import numpy` and `import cv2` without any errors. You can download a test video and view the expected output [from the resources branch](https://github.com/Breakthrough/PySceneDetect/tree/resources/tests) (see the end of the Usage section below for details). Usage @@ -37,34 +37,38 @@ To perform threshold-based analysis, with a threshold intensity of 16, and a mat ./scenedetect.py --input myvideo.mp4 --threshold 16 --minpercent 90 -Detailed descriptions of the above parameters, as well as their default values, can be obtained by using the `--help` flag. Visual example of the parameters used in threshold mode: +Detailed descriptions of the above parameters, as well as their default values, can be obtained by using the `--help` flag. -![parameters in threshold mode](https://github.com/Breakthrough/PySceneDetect/raw/resources/images/threshold-param-example.png) +Below is a visual example of the parameters used in threshold mode (click for full-view): -You can download the file `testvideo.mp4` as well as the expected output `testvideo-results.txt` [from here](https://github.com/Breakthrough/PySceneDetect/tree/resources/tests). +[parameters in threshold mode](https://github.com/Breakthrough/PySceneDetect/raw/resources/images/threshold-param-example.png) +You can download the file `testvideo.mp4`, as well as the expected output `testvideo-results.txt`, [from the resources branch](https://github.com/Breakthrough/PySceneDetect/tree/resources/tests), for testing the operation of the program. Data for the above graph was obtained by running PySceneDetect on `testvideo.mp4` in analysis mode (coming soon). -Current Status + +Current Features & Roadmap ---------------------------------------------------------- -See [the Releases page](https://github.com/Breakthrough/PySceneDetect/releases) for a list of all versions, changes, and download links. The latest stable release of PySceneDetect is `v0.2.0-alpha`. +Visit [the Releases page](https://github.com/Breakthrough/PySceneDetect/releases) for a list of all versions, changes, and download links. Feel free to submit any bugs/issues or feature requests to [the Issue Tracker](https://github.com/Breakthrough/PySceneDetect/issues). ### Current Features - analyzes passed video file for changes in intensity/content (currently based on mean pixel value/brightness) - detects fade-in and fade-out based on user-defined threshold - exports list of scenes to .CSV file (both timecodes and frame numbers) + - exports timecodes in `mkvmerge` format: `HH:MM:SS.nnnnn`, comma-separated ### In Process - - export timecodes in multiple formats to match popular applications - - `mkvmerge` format: `HH:MM:SS.nnnnn`, comma-separated + - add output-suppression mode for better automation with external scripts/programs + - analysis mode to export frame-by-frame video metrics - adaptive or user-defined bias for fade in/out interpolation ### Planned Features - export scenes in chapter/XML format - content-aware scene detection + - additional timecode formats You can find additional information regarding PySceneDetect at the following URL: diff --git a/scenedetect.py b/scenedetect.py index 14bb24ad..77e3c13d 100755 --- a/scenedetect.py +++ b/scenedetect.py @@ -41,7 +41,7 @@ import numpy -VERSION_STRING = '0.2.0-alpha' +VERSION_STRING = '0.2.1-alpha' ABOUT_STRING = """ PySceneDetect %s ----------------------------------------------- @@ -83,12 +83,13 @@ def analyze_video_threshold(cap, threshold, min_percent, block_size, show_output fade_names = ("OUT", "IN ") min_percent = min_percent / 100.0 last_frame_amt = None + h_rule = '-----------------------------------------------------' if show_output: print '' - print '----------------------------------------' - print ' FADE TYPE | TIME | FRAME # ' - print '----------------------------------------' + print h_rule + print ' FADE TYPE | TIME | FRAME # | TIMECODE ' + print h_rule while True: # Get next frame from video. @@ -127,18 +128,18 @@ def analyze_video_threshold(cap, threshold, min_percent, block_size, show_output pos_frames = cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) fade_list.append((fade_type, pos_msec, pos_frames)) if show_output: - print " %s | %9d ms | %10d" % ( - fade_names[fade_type], pos_msec, pos_frames ) + pos_tc = get_timecode_string(pos_msec, False) + print " %s | %9d ms | %10d | %s " % ( + fade_names[fade_type], pos_msec, pos_frames, pos_tc ) last_frame_amt = curr_frame_amt if show_output: - print '-----------------------------------------' + print h_rule print '' return fade_list - def generate_scene_list(cap, fade_list, csv_out = None, include_last = False, show_output = True): """ Creates a list of scenes from a sorted list of fades in/out. @@ -156,14 +157,16 @@ def generate_scene_list(cap, fade_list, csv_out = None, include_last = False, sh Returns: A list of scenes as tuples in the form (time, frame number). """ + h_rule = '------------------------------------------------------' + if csv_out: - csv_out.write("scene,timecode(ms),frame\n") + csv_out.write("scene,timecode,frame,time (ms)\n") if show_output: print '' - print '----------------------------------------' - print ' SCENE # | TIME | FRAME # ' - print '----------------------------------------' + print h_rule + print ' SCENE # | TIME | FRAME # | TIMECODE ' + print h_rule scene_list = [] scene_list.append((0,0)) # Scenes in form (timecode, frame number) @@ -178,12 +181,13 @@ def generate_scene_list(cap, fade_list, csv_out = None, include_last = False, sh cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) ) ) last_fade = None + tc_list = [] for fade in fade_list: # We create a new scene for each fade-in we detect. if (fade[0] == 1 and last_fade): scene_list.append( ((fade[1] + last_fade[1]) / 2.0, - (fade[2] + last_fade[2]) / 2 ) ) + (fade[2] + last_fade[2]) / 2.0 ) ) last_fade = fade if include_last and last_fade[0] == 0: @@ -191,18 +195,60 @@ def generate_scene_list(cap, fade_list, csv_out = None, include_last = False, sh if csv_out or show_output: for scene_idx in range(len(scene_list)): + pos_tc = get_timecode_string(scene_list[scene_idx][0]) if csv_out: - csv_out.write("%d,%f,%d\n" % ( - scene_idx, scene_list[scene_idx][0], scene_list[scene_idx][1]) ) + csv_out.write("%d,%s,%f,%d\n" % ( + scene_idx, pos_tc, scene_list[scene_idx][1], scene_list[scene_idx][0]) ) if show_output: - print " %3d | %9d ms | %10d" % ( - scene_idx, scene_list[scene_idx][0], scene_list[scene_idx][1] ) + print " %3d | %9d ms | %10d | %s" % ( + scene_idx, scene_list[scene_idx][0], scene_list[scene_idx][1], pos_tc ) + if scene_idx > 0: + tc_list.append(pos_tc) if show_output: - print '-----------------------------------------' + print h_rule print '' + print 'Comma-separated timecode list (e.g. for use with mkvmerge):' + print ','.join(tc_list) + print '' + if csv_out: + csv_out.write("\nComma-separated timecode list (e.g. for use with mkvmerge):\n") + csv_out.write(','.join(tc_list)) + csv_out.write('\n') return scene_list +def get_timecode_string(time_msec, show_msec = True): + """ Formats a time, in ms, into a timecode of the form HH:MM:SS.nnnnn. + + This is the default timecode format used by mkvmerge for splitting a video. + + Args: + time_msec: Integer representing milliseconds from start of video. + show_msec: If False, omits the milliseconds part from the output. + Returns: + A string with a formatted timecode (HH:MM:SS.nnnnn). + """ + out_nn, timecode_str = int(time_msec), '' + + base_msec = 1000 * 60 * 60 # 1 hour in ms + out_HH = int(time_msec / base_msec) + out_nn -= out_HH * base_msec + + base_msec = 1000 * 60 # 1 minute in ms + out_MM = int(time_msec / base_msec) + out_nn -= out_MM * base_msec + + base_msec = 1000 # 1 second in ms + out_SS = int(time_msec / base_msec) + out_nn -= out_SS * base_msec + + if show_msec: + timecode_str = "%02d:%02d:%02d.%d" % (out_HH, out_MM, out_SS, out_nn) + else: + timecode_str = "%02d:%02d:%02d" % (out_HH, out_MM, out_SS) + + return timecode_str + def int_type_check(min_val, max_val = None, metavar = None): """ Creates an argparse type for a range-limited integer.