17 Commits

Author SHA1 Message Date
kelciour 129a948f02 Revert "Replace Popen with check_call"
This reverts commit 34a99fdec0.
2021-02-14 17:31:02 +03:00
kelciour 34a99fdec0 Replace Popen with check_call 2020-10-20 16:39:56 +03:00
kelciour 15db01096f Escape double quotes in filenames 2020-10-20 15:50:28 +03:00
kelciour 588fa17eb2 Use --sort-files with rg 2020-10-20 15:08:42 +03:00
kelciour b8326b9c73 Map only video and audio streams 2020-10-10 13:09:20 +03:00
kelciour 93fe7d3fd7 Swith to default encoding preset 2020-10-09 21:16:15 +03:00
kelciour 5bb2e7cb0b Create output folder if it doesn't exist 2020-10-09 21:13:04 +03:00
kelciour 98e4a79a4d Check subprocess call 2020-10-09 21:12:57 +03:00
kelciour 811aceae81 Replace str with list for Popen 2020-10-09 20:44:22 +03:00
kelciour 605676b13e Update README 2020-10-08 16:53:07 +03:00
kelciour 6d1d2bdc4f Don't remove extra audio tracks 2020-10-08 16:46:36 +03:00
kelciour b1182e88a0 Use -o as the output folder for video and audio fragments 2020-10-08 16:45:58 +03:00
kelciour 447973e773 Update README 2020-10-05 19:30:05 +03:00
kelciour 983d302eaa Add support for ripgrep 2020-10-05 19:22:16 +03:00
kelciour c1c2a424f4 Pass a list of arguments to Popen 2020-10-05 18:42:29 +03:00
kelciour 64fba5c94c Improve --demo option 2020-10-05 17:59:41 +03:00
kelciour a2ea5cbd28 Split on \n only' 2020-10-05 17:10:04 +03:00
2 changed files with 97 additions and 47 deletions
+14 -12
View File
@@ -30,28 +30,30 @@ Here's a quick demo how to set up and run ```videogrep.bat``` on Windows ([YouTu
### Additional Options:
* ```-ph GAP_BETWEEN_PHRASES, --phrases```
move the start time of the clip to the beginning of the current phrase (default=1.25 seconds)
* ```-l SECONDS, --limit```
maximum phrase's duration (default=60 seconds)
* ```-p SECONDS, --padding```
padding in seconds to add to the start and the end of each clip (default=0.0 seconds)
* ```-e SECONDS, --ending```
play only matching lines (or phrases)
* ```-r, --randomize```
randomize the clips
* ```-o FILENAME, --output```
write the \'grep\' output to the file
* ```-d, --demo```
only show grep results
* ```-a, --audio```
create audio fragments
* ```-v, --video```
create video fragments
* ```-s, --subtitles```
create subtitles
* ```-vs, --video-sub```
create video fragments with hardcoded subtitles
* ```-s, --subtitles```
create subtitles for fragments
* ```-o DIRNAME, --output```
the output folder for audio and video fragments (default=.)
* ```-d, --demo```
only show grep results
* ```-g FILENAME, --grep-output```
write the \'grep\' output to the file
* ```-ph GAP_BETWEEN_PHRASES, --phrases```
move the start time of the clip to the beginning of the current phrase (default=1.25 seconds)
* ```-l SECONDS, --limit```
maximum phrase\'s duration (default=60 seconds)
* ```-m OPTIONS, --mpv-options```
mpv player options
@@ -88,8 +90,8 @@ Here's example video how it looks like (YouTube):
# Requirements
* python 2.7
* grep
* python 3
* grep or [ripgrep](https://github.com/BurntSushi/ripgrep) (for non-ASCII languages)
* mpv
* ffmpeg
+83 -35
View File
@@ -4,6 +4,7 @@
import os
import random
import re
import shutil
import sys
import subprocess
import time
@@ -163,7 +164,14 @@ def get_fragment_filename(phrase):
s = s[:max_filename_length] + "..."
return re.sub(r'(?u)[^-\w\'\.]', '', s)
def create_fragments(search_phrase, clips, export_mode):
def subprocess_call(args):
try:
output = subprocess.check_output(args, stderr=subprocess.STDOUT, universal_newlines=True)
except subprocess.CalledProcessError as e:
print("\n\n", e.output)
sys.exit(1)
def create_fragments(search_phrase, clips, export_mode, output_dir):
idx = 1
update_progress(0, 0, len(clips))
@@ -173,23 +181,23 @@ def create_fragments(search_phrase, clips, export_mode):
if len(clips) > 1:
fragment_filename += "_" + str(idx).zfill(3)
fragment_filename = os.path.join(output_dir, fragment_filename)
ss = clip_start
to = clip_end
t = to - ss
t_fade = 0.2
af = "afade=t=in:st=%s:d=%s,afade=t=out:st=%s:d=%s" % (0, t_fade, t - t_fade, t_fade)
video_encoding_mode = "ultrafast"
video_encoding_mode = "medium"
if export_mode["audio"]:
cmd = " ".join(["ffmpeg", "-y", "-ss", str(ss), "-i", '"' + video_file + '"', "-loglevel", "quiet", "-t", str(t), "-map", "0:a:0", "-af", af, '"' + fragment_filename + ".mp3" + '"'])
p = subprocess.Popen(cmd)
p.wait()
cmd = ["ffmpeg", "-y", "-ss", str(ss), "-i", video_file, "-t", str(t), "-af", af, fragment_filename + ".mp3"]
subprocess_call(cmd)
if export_mode["video"]:
cmd = " ".join(["ffmpeg", "-y", "-ss", str(ss), "-i", '"' + video_file + '"', "-strict", "-2", "-loglevel", "quiet", "-t", str(t), "-map", "0:v:0", "-map", "0:a:0", "-c:v", "libx264", "-preset", video_encoding_mode, "-c:a", "aac", "-ac", "2", "-af", af, '"' + fragment_filename + ".mp4" + '"'])
p = subprocess.Popen(cmd)
p.wait()
cmd = ["ffmpeg", "-y", "-ss", str(ss), "-i", video_file, "-strict", "-2", "-t", str(t), "-map", "0:v", "-map", "0:a", "-c:v", "libx264", "-preset", video_encoding_mode, "-c:a", "aac", "-ac", "2", "-af", af, fragment_filename + ".mp4"]
subprocess_call(cmd)
if export_mode["video-sub"]:
srt_style = "FontName=Arial,FontSize=22"
@@ -201,12 +209,11 @@ def create_fragments(search_phrase, clips, export_mode):
srt_filename = srt_filename.replace(",", "\\\\\\,")
srt_filename = srt_filename.replace("'", "\\\\\\'")
vf = "\"" + "subtitles=" + srt_filename + ":force_style='" + srt_style + "',setpts=PTS-STARTPTS" + "\""
vf = "subtitles=" + srt_filename + ":force_style='" + srt_style + "',setpts=PTS-STARTPTS"
af = "afade=t=in:st=%s:d=%s,afade=t=out:st=%s:d=%s,asetpts=PTS-STARTPTS" % (ss, t_fade, to - t_fade, t_fade)
cmd = " ".join(["ffmpeg", "-y", "-ss", str(ss), "-i", '"' + video_file + '"', "-strict", "-2", "-loglevel", "quiet", "-t", str(t), "-map", "0:v:0", "-map", "0:a:0", "-c:v", "libx264", "-preset", video_encoding_mode, "-c:a", "aac", "-ac", "2", "-vf", vf, "-af", af, "-copyts", '"' + fragment_filename + ".sub.mp4" + '"'])
p = subprocess.Popen(cmd)
p.wait()
cmd = ["ffmpeg", "-y", "-ss", str(ss), "-i", video_file, "-strict", "-2", "-t", str(t), "-map", "0:v", "-map", "0:a", "-c:v", "libx264", "-preset", video_encoding_mode, "-c:a", "aac", "-ac", "2", "-vf", vf, "-af", af, "-copyts", fragment_filename + ".sub.mp4"]
subprocess_call(cmd)
if export_mode["subtitles"]:
subtitles_filename = video_file.rsplit('.', 1)[0] + ".srt"
@@ -236,6 +243,7 @@ def play_clips(clips, ending_mode, mpv_options):
with open(pipe_name, "wb", 0) as f_pipe:
for clip_filename, clip_start, clip_end in clips:
clip_filename = clip_filename.replace("\\","/")
clip_filename = clip_filename.replace("\"", "\\\"")
cmd = ["loadfile", '"' + clip_filename + '"']
if ending_mode:
@@ -256,25 +264,50 @@ def play_clips(clips, ending_mode, mpv_options):
p.kill()
return
def main(media_dir, search_phrase, phrase_mode, phrases_gap, padding, limit, output_file, ending_mode, randomize_mode, demo_mode, mpv_options, audio_mode, video_mode, video_with_sub_mode, subtitles_mode):
search_phrase_in_grep = "\"(?s)\(\d\d:\d\d:\d\d,\d\d\d\, \d\d:\d\d:\d\d,\d\d\d\)\\t[^\\n]*" + search_phrase + "[^\\n]*\""
def print_match(media_dir, filename, line, attrs={"prev_filename": None}):
if filename.startswith(media_dir):
filename = filename.replace(media_dir + os.sep, '', 1)
cmd = " ".join(["grep", "-r", "-z", "-o", "-i", "--include", "*.txt", "-P", search_phrase_in_grep, '"' + media_dir + '"'])
if attrs["prev_filename"] != filename:
print()
print('-', filename)
print()
attrs["prev_filename"] = filename
line = line.replace('\t', ' ')
print(line)
def main(media_dir, search_phrase, phrase_mode, phrases_gap, padding, limit, output_dir, grep_file, ending_mode, randomize_mode, demo_mode, mpv_options, audio_mode, video_mode, video_with_sub_mode, subtitles_mode):
search_phrase_in_grep = "(?s)\(\d\d:\d\d:\d\d,\d\d\d\, \d\d:\d\d:\d\d,\d\d\d\)\\t[^\\n]*" + search_phrase + "[^\\n]*"
rg = shutil.which('rg')
if rg:
cmd = ["rg", "--sort-files", "--no-heading", "--null-data", "-N", "-o", "-i", "-g", "*.txt", "-P", search_phrase_in_grep, media_dir]
else:
cmd = ["grep", "-r", "-z", "-o", "-i", "--include", "*.txt", "-P", search_phrase_in_grep, media_dir]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, bufsize=-1)
output, error = p.communicate()
media_dir = os.path.abspath(media_dir).replace('\\', '/').replace('/', os.sep)
if p.returncode == 0:
matches = output.rstrip("\x00").split("\x00")
if output_file != None:
with open(output_file, 'w') as f_results:
if grep_file != None:
with open(grep_file, 'w') as f_results:
f_results.write("\n".join(matches))
clips = []
for match in matches:
filename, line = match.split(".txt:", 1)
lines = line.splitlines()
filename, line = match.strip().split(".txt:", 1)
filename = os.path.abspath(filename).replace('\\', '/').replace('/', os.sep)
if demo_mode:
print_match(media_dir, filename, line)
lines = line.split('\n')
def get_line_timings(line):
sub_timing, sub_content = line.split("\t", 1)
@@ -359,20 +392,23 @@ def main(media_dir, search_phrase, phrase_mode, phrases_gap, padding, limit, out
for ext in movie_extensions:
movie_filename = filename + "." + ext
if os.path.isfile(movie_filename):
clips.append((os.path.abspath(movie_filename), phrase_start - padding, phrase_end + padding))
clips.append((movie_filename, phrase_start - padding, phrase_end + padding))
break
if demo_mode:
print()
if phrase_mode:
print("Number of matches:", len(clips))
clips = list(OrderedDict.fromkeys(clips)) # delete dublicates
print("Number of clips:", len(clips))
if randomize_mode:
random.shuffle(clips)
if audio_mode or video_mode or video_with_sub_mode or subtitles_mode:
create_fragments(search_phrase, clips, {"audio": audio_mode, "video": video_mode, "video-sub": video_with_sub_mode, "subtitles": subtitles_mode})
create_fragments(search_phrase, clips, {"audio": audio_mode, "video": video_mode, "video-sub": video_with_sub_mode, "subtitles": subtitles_mode}, output_dir)
elif not demo_mode:
play_clips(clips, ending_mode, mpv_options)
@@ -430,7 +466,7 @@ def parse_args(argv):
print("Search phrase can't be empty")
sys.exit(1)
args = {"padding": 0, "limit": 60, "output_file": None, "phrase_mode": False, "phrases_gap":1.25, "search_phrase":search_phrase, "ending_mode":False, "randomize_mode":False, "demo_mode":False, "mpv_options":"", "audio_mode":False, "video_mode":False, "video_with_sub_mode":False, "subtitles_mode":False }
args = {"padding": 0, "limit": 60, "output_dir": ".", "grep_file": None, "phrase_mode": False, "phrases_gap":1.25, "search_phrase":search_phrase, "ending_mode":False, "randomize_mode":False, "demo_mode":False, "mpv_options":"", "audio_mode":False, "video_mode":False, "video_with_sub_mode":False, "subtitles_mode":False }
argv = argv[:-1]
idx = 0
@@ -450,10 +486,17 @@ def parse_args(argv):
return False
args["limit"] = int(argv[idx + 1])
idx += 1
elif argv[idx] == "--grep-output" or argv[idx] == "-g":
if idx + 1 >= len(argv):
return False
args["grep_file"] = argv[idx + 1]
idx += 1
elif argv[idx] == "--output" or argv[idx] == "-o":
if idx + 1 >= len(argv):
return False
args["output_file"] = argv[idx + 1]
args["output_dir"] = os.path.abspath(argv[idx + 1])
if not os.path.exists(args["output_dir"]):
os.makedirs(args["output_dir"])
idx += 1
elif argv[idx] == "--ending" or argv[idx] == "-e":
args["ending_mode"] = True
@@ -497,9 +540,13 @@ def validate_args(args):
if not os.path.isdir(args["media_dir"]):
print("ERROR: '{}' is not a folder".format(args["media_dir"]))
return False
if args["output_file"]:
if os.path.isdir(args["output_file"]):
print("ERROR: '{}' can't be a folder".format(args["output_file"]))
if args["output_dir"]:
if os.path.exists(args["output_dir"]) and not os.path.isdir(args["output_dir"]):
print("ERROR: '{}' is not a folder".format(args["output_dir"]))
return False
if args["grep_file"]:
if os.path.isdir(args["grep_file"]):
print("ERROR: '{}' can't be a folder".format(args["grep_file"]))
return False
return True
@@ -509,17 +556,18 @@ def print_usage():
print("Init: playphrase -i <media_dir> _init_")
print()
print("Additional options:")
print("-ph GAP_BETWEEN_PHRASES, --phrases", " ", "move the start time of the clip to the beginning of the current phrase (default=1.25 seconds)")
print("-l SECONDS, --limit", " ", "maximum phrase's duration (default=60 seconds)")
print("-p SECONDS, --padding", " ", "padding in seconds to add to the start and the end of each clip (default=0.0 seconds)")
print("-e SECONDS, --ending", " ", "play only matching lines (or phrases)")
print("-r, --randomize", " ", "randomize the clips")
print("-o FILENAME, --output", " ", "write the 'grep' output to the file")
print("-d, --demo", " ", "only show grep results")
print("-a, --audio", " ", "create audio fragments")
print("-v, --video", " ", "create video fragments")
print("-s, --subtitles", " ", "create subtitles")
print("-vs, --video-sub", " ", "create video fragments with hardcoded subtitles")
print("-s, --subtitles", " ", "create subtitles for fragments")
print("-o DIRNAME, --output", " ", "the output folder for audio and video fragments (default=.)")
print("-d, --demo", " ", "only show grep results")
print("-g FILENAME, --grep-output", "", "write the 'grep' output to the file")
print("-ph GAP_BETWEEN_PHRASES, --phrases", "", "move the start time of the clip to the beginning of the current phrase (default=1.25 seconds)")
print("-l SECONDS, --limit", " ", "maximum phrase's duration (default=60 seconds)")
print("-m OPTIONS, --mpv-options", " ", "mpv player options")
if __name__ == '__main__':
@@ -543,4 +591,4 @@ if __name__ == '__main__':
if need_update(args["media_dir"]):
print("WARNING: number of '.srt' and '.txt' files doesn't match. Maybe use 'playphrase -i <media_dir> _init_'.")
main(args["media_dir"], args["search_phrase"], args["phrase_mode"], args["phrases_gap"], args["padding"], args["limit"], args["output_file"], args["ending_mode"], args["randomize_mode"], args["demo_mode"], args["mpv_options"], args["audio_mode"], args["video_mode"], args["video_with_sub_mode"], args["subtitles_mode"])
main(args["media_dir"], args["search_phrase"], args["phrase_mode"], args["phrases_gap"], args["padding"], args["limit"], args["output_dir"], args["grep_file"], args["ending_mode"], args["randomize_mode"], args["demo_mode"], args["mpv_options"], args["audio_mode"], args["video_mode"], args["video_with_sub_mode"], args["subtitles_mode"])