Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ coverage.xml
.hypothesis/
.pytest_cache/


# Translations
*.mo
*.pot
Expand Down Expand Up @@ -146,6 +147,7 @@ dmypy.json
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
test.py

# AWS User-specific
.idea/**/aws.xml
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The video-hash-values for identical or near-duplicate videos are the same or sim

## <img src="https://github.githubassets.com/images/icons/emoji/unicode/1f3d7.png" width="20"></img> Installation

To use this software, you must have [FFmpeg](https://ffmpeg.org/) installed. Please read [how to install FFmpeg](https://github.com/akamhy/videohash/wiki/Install-FFmpeg,-but-how%3F) if you don't already know how.
To use this software, you must have [FFmpeg](https://ffmpeg.org/) installed. Please read [how to install FFmpeg](https://github.com/demmenie/videohash2/wiki/Install-FFmpeg,-but-how%3F) if you don't already know how.

#### Install videohash2

Expand Down Expand Up @@ -121,9 +121,9 @@ False
>>>
```

**Extended Usage** : <https://github.com/akamhy/videohash/wiki/Extended-Usage>
**Extended Usage** : <https://github.com/demmenie/videohash2/wiki/Extended-Usage>

**API Reference** : <https://github.com/akamhy/videohash/wiki/API-Reference>
**API Reference** : <https://github.com/demmenie/videohash2/wiki/API-Reference>

--------------------------------------------------------------------------

Expand Down
13 changes: 9 additions & 4 deletions tests/test_framesextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@
def test_all():
video_path = os.path.join(script_path, os.path.pardir, "assets", "rocket.mkv")
output_dir = create_and_return_temporary_directory()
video_length = 52.3
interval = 1
ffmpeg_path = None
FramesExtractor(video_path, output_dir, interval=interval, ffmpeg_path=ffmpeg_path)
FramesExtractor(video_path, output_dir, video_length,
interval=interval, ffmpeg_path=ffmpeg_path)

with pytest.raises(FileNotFoundError):
video_path = os.path.join(script_path, "thisvideodoesnotexist.mp4")
output_dir = create_and_return_temporary_directory()
FramesExtractor(video_path, output_dir, interval=1, ffmpeg_path=None)
FramesExtractor(video_path, output_dir, video_length, interval=1,
ffmpeg_path=None)

with pytest.raises(FFmpegNotFound):
video_path = os.path.join(script_path, os.path.pardir, "assets", "rocket.mkv")
output_dir = create_and_return_temporary_directory()
ffmpeg_path = os.path.join(output_dir, "ffmpeg")
FramesExtractor(video_path, output_dir, interval=1, ffmpeg_path=ffmpeg_path)
FramesExtractor(video_path, output_dir, video_length, interval=1,
ffmpeg_path=ffmpeg_path)

with pytest.raises(FramesExtractorOutPutDirDoesNotExist):
video_path = os.path.join(script_path, "../assets/rocket.mkv")
output_dir = os.path.join(script_path, "thisdirdoesnotexist/")
FramesExtractor(video_path, output_dir, interval=1, ffmpeg_path=None)
FramesExtractor(video_path, output_dir, video_length, interval=1,
ffmpeg_path=None)
2 changes: 1 addition & 1 deletion videohash2/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
)

__url__ = "https://demmenie.github.io/videohash2/"
__version__ = "3.0.3"
__version__ = "3.1.0"
__status__ = "production"
__author__ = "Akash Mahanty and Chico Demmenie"
__author_email__ = "cdemmenie@gmail.com"
Expand Down
54 changes: 32 additions & 22 deletions videohash2/framesextractor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import re
import math
import shlex
from shutil import which
from subprocess import PIPE, Popen, check_output
from subprocess import PIPE, DEVNULL, Popen, check_output
from typing import Optional, Union

from .exceptions import (
Expand All @@ -26,6 +27,7 @@ def __init__(
self,
video_path: str,
output_dir: str,
video_length: float,
interval: Union[int, float] = 1,
ffmpeg_path: Optional[str] = None,
) -> None:
Expand Down Expand Up @@ -53,6 +55,7 @@ def __init__(
"""
self.video_path = video_path
self.output_dir = output_dir
self.video_length = video_length
self.interval = interval
self.ffmpeg_path = ""
if ffmpeg_path:
Expand Down Expand Up @@ -114,7 +117,8 @@ def detect_crop(
video_path: Optional[str] = None,
frames: int = 3,
ffmpeg_path: Optional[str] = None,
) -> str:
video_length: float = 2
) -> list:
"""
Detects the the amount of cropping to remove black bars.

Expand Down Expand Up @@ -144,19 +148,26 @@ def detect_crop(
7200,
14400,
]


crop_list = []

for start_time in time_start_list:

# Stopping the loop if we go beyond the end length of the video.
# We round the video length up to make sure we do get the whole
# video.
if start_time > math.ceil(video_length):
break

command = f'"{ffmpeg_path}" -ss {start_time} -i "{video_path}" -vframes {frames} -vf cropdetect -f null -'

process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
process = Popen(shlex.split(command), stdin=DEVNULL, stdout=PIPE, stderr=PIPE)

output, error = process.communicate()

matches = re.findall(
r"crop\=[0-9]{1,4}:[0-9]{1,4}:[0-9]{1,4}:[0-9]{1,4}",
r"crop\=[1-9][0-9]{0,3}:[1-9][0-9]{0,3}:[0-9]{1,4}:[0-9]{1,4}",
(output.decode() + error.decode()),
)

Expand All @@ -167,9 +178,9 @@ def detect_crop(
if len(crop_list) > 0:
mode = max(crop_list, key=crop_list.count)

crop = " "
crop = []
if mode:
crop = f" -vf {mode} "
crop = ["-vf", mode]

return crop

Expand All @@ -185,6 +196,7 @@ def extract(self) -> None:

ffmpeg_path = self.ffmpeg_path
video_path = self.video_path
video_length = self.video_length
output_dir = self.output_dir

if os.name == "posix":
Expand All @@ -193,25 +205,23 @@ def extract(self) -> None:
output_dir = shlex.quote(self.output_dir)

crop = FramesExtractor.detect_crop(
video_path=video_path, frames=3, ffmpeg_path=ffmpeg_path
video_path=video_path, frames=3, ffmpeg_path=ffmpeg_path,
video_length=video_length
)

command = (
f'"{ffmpeg_path}"'
+ " -i "
+ f'"{video_path}"'
+ f"{crop}"
+ " -s 144x144 "
+ " -r "
+ str(self.interval)
+ " "
+ '"'
+ output_dir
+ "video_frame_%07d.jpeg"
+ '"'
)
command = [
str(ffmpeg_path),
"-i",
str(video_path),
*crop,
"-s",
"144x144",
"-r",
str(self.interval),
str(output_dir)+"video_frame_%07d.jpeg",
]

process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
process = Popen(command, stdin=DEVNULL, stdout=PIPE, stderr=PIPE)
output, error = process.communicate()

ffmpeg_output = output.decode()
Expand Down
9 changes: 3 additions & 6 deletions videohash2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,18 @@ def does_path_exists(path: str) -> bool:
If a directory is supplied then check if it exists.
If a file is supplied then check if it exists.

Directory ends with "/" on posix or "\" in windows and files do not.

If directory/file exists returns True else returns False

:return: True if dir or file exists else False.

:rtype: bool
"""
if path.endswith("/") or path.endswith("\\"):
# it's directory
return os.path.isdir(path)
if os.path.isdir(path) or os.path.isfile(path):
return os.path.exists(path)

else:
# it's file
return os.path.isfile(path)
return False


def create_and_return_temporary_directory() -> str:
Expand Down
22 changes: 18 additions & 4 deletions videohash2/videohash.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
storage_path: Optional[str] = None,
download_worst: bool = False,
frame_interval: Union[int, float] = 1,
do_not_copy: Optional[bool] = True,
) -> None:
"""
:param path: Absolute path of the input video file.
Expand Down Expand Up @@ -74,6 +75,7 @@ def __init__(

self._storage_path = self.storage_path
self.download_worst = download_worst
self.do_not_copy = do_not_copy
self.frame_interval = frame_interval

self.task_uid = VideoHash._get_task_uid()
Expand All @@ -82,7 +84,14 @@ def __init__(

self._copy_video_to_video_dir()

FramesExtractor(self.video_path, self.frames_dir, interval=self.frame_interval)
self.video_duration = video_duration(self.video_path)

FramesExtractor(
self.video_path,
self.frames_dir,
video_length=self.video_duration,
interval=self.frame_interval,
)

self.collage_path = os.path.join(self.collage_dir, "collage.jpg")

Expand All @@ -104,7 +113,6 @@ def __init__(
self.image = Image.open(self.collage_path)
self.bits_in_hash = 64
self.similar_percentage = 15
self.video_duration = video_duration(self.video_path)

self._calc_hash()

Expand Down Expand Up @@ -289,7 +297,10 @@ def _copy_video_to_video_dir(self) -> None:

self.video_path = os.path.join(self.video_dir, (f"video.{extension}"))

shutil.copyfile(self.path, self.video_path)
if self.do_not_copy:
os.symlink(self.path, self.video_path)
else:
shutil.copyfile(self.path, self.video_path)

if self.url:

Expand All @@ -309,7 +320,10 @@ def _copy_video_to_video_dir(self) -> None:

self.video_path = f"{self.video_dir}video.{extension}"

shutil.copyfile(downloaded_file, self.video_path)
if self.do_not_copy:
os.symlink(downloaded_file, self.video_path)
else:
shutil.copyfile(downloaded_file, self.video_path)

def _create_required_dirs_and_check_for_errors(self) -> None:
"""
Expand Down