Skip to content

Commit

Permalink
Added timecode formatting functionality in HH:MM:SS.nnnnn format.
Browse files Browse the repository at this point in the history
  • Loading branch information
Breakthrough committed Nov 17, 2014
1 parent 756c532 commit 8969f46
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 30 deletions.
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
[<img src="https://github.com/Breakthrough/PySceneDetect/raw/resources/images/threshold-param-example.png" alt="parameters in threshold mode" width="360" />](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:
Expand Down
82 changes: 64 additions & 18 deletions scenedetect.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import numpy


VERSION_STRING = '0.2.0-alpha'
VERSION_STRING = '0.2.1-alpha'
ABOUT_STRING = """
PySceneDetect %s
-----------------------------------------------
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -178,31 +181,74 @@ 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:
scene_list.append((last_fade[1], last_fade[2]))

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.
Expand Down

0 comments on commit 8969f46

Please sign in to comment.