diff --git a/programs/__init__.py b/programs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/programs/convert.py b/programs/convert.py new file mode 100644 index 0000000..87d890d --- /dev/null +++ b/programs/convert.py @@ -0,0 +1,10 @@ +from utils.ffmpeg_operations import run_ffmpeg_command + +def convert_video(input_file, output_file, format): + command = [ + "ffmpeg", + "-i", input_file, + output_file + ] + run_ffmpeg_command(command) + print(f"Video converted to {format} format and saved as {output_file}") diff --git a/programs/extract_frames.py b/programs/extract_frames.py new file mode 100644 index 0000000..f693d91 --- /dev/null +++ b/programs/extract_frames.py @@ -0,0 +1,11 @@ +from utils.ffmpeg_operations import run_ffmpeg_command + +def extract_frames(input_file, output_folder, frame_rate): + command = [ + "ffmpeg", + "-i", input_file, + "-vf", f"fps={frame_rate}", + f"{output_folder}/frame_%04d.png" + ] + run_ffmpeg_command(command) + print(f"Frames extracted from {input_file} at {frame_rate} fps and saved to {output_folder}") diff --git a/programs/resize.py b/programs/resize.py new file mode 100644 index 0000000..1af0c83 --- /dev/null +++ b/programs/resize.py @@ -0,0 +1,12 @@ +from utils.ffmpeg_operations import run_ffmpeg_command + +def resize_video(input_file, output_file, width, height): + command = [ + "ffmpeg", + "-i", input_file, + "-vf", f"scale={width}:{height}", + output_file + ] + + run_ffmpeg_command(command) + print(f"Video resized to {width}x{height} and saved as {output_file}") diff --git a/programs/silence_extraction.py b/programs/silence_extraction.py new file mode 100644 index 0000000..ead11fb --- /dev/null +++ b/programs/silence_extraction.py @@ -0,0 +1,39 @@ +import sys +from videogrep import parse_transcript, create_supercut + + +def slncx_main(min_d, max_d, adj, input_file, output_file): + print('DID WE MAKE IT^&(#(*&#(*&#(*&W#(*#&(*#&(*#&(*#&#*(&#))') + print(input_file) + # the min and max duration of silences to extract + min_duration = min_d #0.1 + max_duration = max_d #1000.0 + + # value to to trim off the end of each clip + adjuster = adj #0.0 + + filenames = input_file + + silences = [] + + try: + for filename in filenames: + timestamps = parse_transcript(filename) + + words = [] + print(timestamps) + for sentence in timestamps: + words += sentence['words'] + + for word1, word2 in zip(words[:-2], words[1:]): + start = word1['end'] + end = word2['start'] - adjuster + duration = end - start + if duration > min_duration and duration < max_duration: + silences.append({'start': start, 'end': end, 'file': filename}) + + create_supercut(silences, f"{output_file}.mp4") + return "ok" + except Exception as e: + print(e) + return e \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6be71d5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,57 @@ +annotated-types==0.6.0 +beautifulsoup4==4.12.3 +blis==0.7.11 +Brotli==1.1.0 +catalogue==2.0.10 +certifi==2024.2.2 +cffi==1.16.0 +charset-normalizer==3.3.2 +click==8.1.7 +cloudpathlib==0.16.0 +colorama==0.4.6 +confection==0.1.4 +cupy-cuda11x==12.3.0 +cymem==2.0.8 +decorator==4.4.2 +en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl#sha256=86cc141f63942d4b2c5fcee06630fd6f904788d2f0ab005cce45aadb8fb73889 +fastrlock==0.8.2 +idna==3.7 +imageio==2.34.1 +imageio-ffmpeg==0.4.9 +Jinja2==3.1.4 +langcodes==3.4.0 +language_data==1.2.0 +marisa-trie==1.1.1 +MarkupSafe==2.1.5 +moviepy==1.0.3 +murmurhash==1.0.10 +mutagen==1.47.0 +numpy==1.26.4 +packaging==24.0 +pillow==10.3.0 +preshed==3.0.9 +proglog==0.1.10 +pycparser==2.22 +pycryptodomex==3.20.0 +pydantic==2.7.1 +pydantic_core==2.18.2 +pydub==0.25.1 +requests==2.31.0 +smart-open==6.4.0 +soupsieve==2.5 +spacy==3.7.4 +spacy-legacy==3.0.12 +spacy-loggers==1.0.5 +srsly==2.4.8 +srt==3.5.3 +thinc==8.2.3 +tqdm==4.66.4 +typer==0.9.4 +typing_extensions==4.11.0 +urllib3==2.2.1 +videogrep==2.3.0 +vosk==0.3.45 +wasabi==1.1.2 +weasel==0.3.4 +websockets==12.0 +yt-dlp==2024.4.9 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/ffmpeg_operations.py b/utils/ffmpeg_operations.py new file mode 100644 index 0000000..c609b7d --- /dev/null +++ b/utils/ffmpeg_operations.py @@ -0,0 +1,7 @@ +import subprocess + +def run_ffmpeg_command(command): + try: + subprocess.run(command, check=True) + except subprocess.CalledProcessError as e: + print(f"An error occurred: {e}") \ No newline at end of file diff --git a/videobeaux.py b/videobeaux.py new file mode 100644 index 0000000..4f62a97 --- /dev/null +++ b/videobeaux.py @@ -0,0 +1,133 @@ +import typer +import yaml +from pathlib import Path + +from programs import silence_extraction, resize, convert, extract_frames + +config_file = Path(__file__).parent / "config.yaml" + +def load_config(): + with open(config_file, 'r') as file: + return yaml.safe_load(file) + +config = load_config() + +app = typer.Typer() + +@app.command() +def silence_xtraction( + min_d: int = typer.Option(None, help="Width of the output video"), + max_d: int = typer.Option(None, help="Height of the output video"), + adj: int = typer.Option(None, help="Height of the output video"), + input_file: str = typer.Option(None, help="Input video file"), + output_file: str = typer.Option(None, help="Output video file") +): + params = { + "min_d": min_d, + "max_d": max_d, + "adj": adj, + "input_file": input_file, + "output_file": output_file, + + } + defaults = config['silence_x'] + params = {key: params.get(key) or defaults[key] for key in defaults} + + silence_extraction.slncx_main(**params) + +@app.command() +def resize_video( + input_file: str = typer.Option(None, help="Input video file"), + output_file: str = typer.Option(None, help="Output video file"), + width: int = typer.Option(None, help="Width of the output video"), + height: int = typer.Option(None, help="Height of the output video") +): + """ + Resize a video to the given width and height. + """ + params = { + "input_file": input_file, + "output_file": output_file, + "width": width, + "height": height + } + defaults = config['resize'] + params = {key: params.get(key) or defaults[key] for key in defaults} + + resize.resize_video(**params) + +@app.command() +def convert_video( + input_file: str = typer.Option(None, help="Input video file"), + output_file: str = typer.Option(None, help="Output video file"), + format: str = typer.Option(None, help="Format of the output video") +): + """ + Convert a video to a different format. + """ + if not input_file: + input_file = config['convert']['input_file'] + if not output_file: + output_file = config['convert']['output_file'] + if not format: + format = config['convert']['format'] + + convert.convert_video(input_file, output_file, format) + +@app.command() +def extract_frames( + input_file: str = typer.Option(None, help="Input video file"), + output_folder: str = typer.Option(None, help="Output folder for frames"), + frame_rate: int = typer.Option(None, help="Frame rate for extracting frames") +): + """ + Extract frames from a video at the specified frame rate. + """ + if not input_file: + input_file = config['extract_frames']['input_file'] + if not output_folder: + output_folder = config['extract_frames']['output_folder'] + if not frame_rate: + frame_rate = config['extract_frames']['frame_rate'] + + extract_frames.extract_frames(input_file, output_folder, frame_rate) + +if __name__ == "__main__": + app() + + +'''def main(): + + parser = argparse.ArgumentParser(description="VideoBeaux - It's You're Best Friend") + subparsers = parser.add_subparsers(title='Subcommands', dest='command', help='Sub-command help') + + + # Program selection + #add_parser = subparsers.add_parser('program', help='Add a new task') + #add_parser.add_argument('task', type=str, help='The task to add') + + # Project Management + #prjmgmt_parser = subparsers.add_parser('project', help='Add a new task') + #prjmgmt_parser.add_argument('--input_file', dest='infile', type=str, help='Full path to input file') # todo - use a path defined in config + #prjmgmt_parser.add_argument('--output_file', dest='outfile', type=str, help='filename of output file that will be save in videobeaux root dir') # todo - use a path defined in config + + + + + # Silence Xtraction + silencextraction_parser = subparsers.add_parser('silence-xtraction', help='extracts silence from a given video') + silencextraction_parser.add_argument('--min_d', dest='mind', type=int, help='Minimum duration of a silent chunk') + silencextraction_parser.add_argument('--max_d', dest='maxd', type=int, help='Maximum duration of a silent chunk') + silencextraction_parser.add_argument('--adj', dest='adj', type=int, help='Maximum duration of a silent chunk') + silencextraction_parser.add_argument('--input_file', dest='infile', type=str, help='Full path to input file') # todo - use a path defined in config + silencextraction_parser.add_argument('--output_file', dest='outfile', type=str, help='filename of output file that will be save in videobeaux root dir') # todo - use a path defined in config + + args = parser.parse_args() + + if args.command == 'silence-xtraction': + silence_extraction.slncx_main(args.mind, args.maxd, args.adj, args.infile, args.outfile) + + else: + parser.print_help() + +'''