diff --git a/README.md b/README.md index fbb87fb..a51a187 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The `ffmpeg4discord.py` script takes a video file as its input and encodes it to The `TwoPass()` Class showcases a 2-pass encoding methodology for the `ffmpeg-python` library, which is not well-documented on the web. The Class also supports a few ffmpeg video filters, like cropping and resolution scaling. It can be used in a variety of different audio/video workflows. -## Usage +## Installation and Usage You must first have `ffmpeg` installed on your system. `ffmpeg` needs to be registered in your PATH. Install the required Python packages, which includes `ffmpeg-python`, with: @@ -18,7 +18,7 @@ Call the script with: The included Batch file for Windows users, `encode.bat`, allows for drag and drop functionality. Be sure to edit the Batch file before dragging your video files on top of it. -### Special install instructions for Windows users +### Special instructions for Windows users If you do not have ffmpeg installed, you can use the included `windows_setup.py` file to do about 90% of the installation. @@ -50,6 +50,8 @@ This script downloads ffmpeg, extracts it into the current directory, and launch - Example: `255x0x1410x1080` - From the top-left of your video, this example goes 255 pixels to the right, 0 pixels down, and it carves out a 1410x1080 section of the video. - [ffmpeg crop documentation](https://ffmpeg.org/ffmpeg-filters.html#Examples-61) +- `--web` + - A Boolean flag. No value is needed after the flag. See [Web UI](#web-ui) for more information on the Web UI. - `--config` - Example: `custom_run_config.json` - Path to a json file containing the configuration for the above parameters. This config file takes precedence over all of the other flags. @@ -106,3 +108,25 @@ python D:/ffmpeg4discord/ffmpeg4discord.py 000050-000145.mp4 \ The example above takes a 5120x1440 resolution video as its input. The script trims the video from 00:00:50 to 00:01:45 (specified in the [file name](https://github.com/zfleeman/ffmpeg4discord#file-name-formatting)). It crops a 2560x1440 section starting at 1280 pixels from the top-left and 0 pixels down (`-c`). The output file will be located in `D:/shadowplay/` (`-o`) with a new resolution of 1920x1080 (`-r`), and it will be 50MB (`-s`). The audio bitrate will be reduced to 48k (`-a`) as well, but that's probably going to sound terrible. ![](https://i.imgur.com/WJXA723.png) + +## Web UI + +The Web UI can be activated by adding `--web` to your `ffmpeg4discord.py` call. + +``` +python "C:/path/to/ffmpeg4discord.py" cool_clip.mp4 -r 1280x720 -s 20 --web +``` + +That command will spin up a Flask server on your local machine and launch a rendered webpage with the video as the centerpiece. The flags you provide to the `python` statement will fill in the defaults for the form. You can override/replace the values. + +You can drag the video playhead to different portions of the video and click the "Set Start/End Time" buttons to specify the section of the video you want to be clipped out. You can also use the range sliders underneath the buttons if you prefer. A "Preview Selection" button is provided for your convenience, and it does what it sounds like. + +https://github.com/zfleeman/ffmpeg4discord/assets/1808564/ff323bcb-4747-437b-808f-ce48b8c269ce + +The Flask server doesn't automatically stop itself, yet, so you'll have to handle that by closing the terminal it leaves hanging. + +## Thanks! + +Yes, this is a simple collection of Python files using FFmpeg tricks that is masquerading as a robust Audio/Video tool. But! I use this nearly every day to quickly share videos with people on various messaging apps that have built-in video players. I don't have to share a link that embeds a video player this way, and I guess that's important to me? + +I like working on this! Enjoy! diff --git a/ffmpeg4discord.py b/ffmpeg4discord.py index 3fc594f..42b29c4 100644 --- a/ffmpeg4discord.py +++ b/ffmpeg4discord.py @@ -1,21 +1,98 @@ +import sys import os +from glob import glob +import webbrowser +from flask import Flask, render_template, url_for, request +from random import randint +import time +import threading +from pathlib import Path + +sys.dont_write_bytecode = True from utils.arguments import get_args from twopass import TwoPass + # get args from the command line args = get_args() +web = args.pop("web") +path = Path(args["filename"]).resolve() +args["filename"] = path -# instantiate the TwoPass class and save our target file size for comparison in the loop +# instantiate the TwoPass class twopass = TwoPass(**args) -end_fs = args["target_filesize"] -while twopass.run() >= end_fs: + +def twopass_loop(target_filesize: float): + while twopass.run() >= target_filesize: + print( + f"\nThe output file size ({round(twopass.output_filesize, 2)}MB) is still above the target of {target_filesize}MB.\nRestarting...\n" + ) + os.remove(twopass.output_filename) + + # adjust the class's target file size to set a lower bitrate for the next run + twopass.target_filesize -= 0.2 + print( - f"\nThe output file size ({round(twopass.output_filesize, 2)}MB) is still above the target of {end_fs}MB.\nRestarting...\n" + f"\nSUCCESS!!\nThe smaller file ({round(twopass.output_filesize, 2)}MB) is located at {twopass.output_filename}" ) - os.remove(twopass.output_filename) - # adjust the class's target file size to set a lower bitrate for the next run - twopass.target_filesize -= 0.2 -print(f"\nSUCCESS!!\nThe smaller file ({round(twopass.output_filesize, 2)}MB) is located at {twopass.output_filename}") +def seconds_to_timestamp(seconds: int): + hours, remainder = divmod(seconds, 3600) + minutes, seconds = divmod(remainder, 60) + + # Use f-strings to format the timestamp + timestamp = f"{hours:02d}:{minutes:02d}:{seconds:02d}" + + return timestamp + + +def open_browser(): + time.sleep(0.5) + webbrowser.open(f"http://localhost:{port}") + + +if web: + app = Flask(__name__, static_folder=path.parent) + + @app.route("/") + def index(): + return render_template( + "web.html", + filename=url_for("static", filename=path.name), + resolution=twopass.resolution, + target_filesize=twopass.target_filesize, + audio_br=twopass.audio_br, + crop=twopass.crop, + output_dir=twopass.output_dir, + ) + + @app.route("/encode", methods=["POST"]) + def form_twopass(): + # generate new times from the selection + ss = int(request.form.get("startTime")) + to = int(request.form.get("endTime")) + twopass.length = to - ss + twopass.times = {"ss": seconds_to_timestamp(ss), "to": seconds_to_timestamp(to)} + target_filesize = float(request.form.get("target_filesize")) + + # update TwoPass from web form + twopass.resolution = request.form.get("resolution") + twopass.target_filesize = target_filesize + twopass.audio_br = float(request.form.get("audio_br")) * 1000 + twopass.crop = request.form.get("crop") + twopass.output_dir = request.form.get("output_dir") + + twopass_loop(target_filesize) + + for file in glob("ffmpeg2pass*"): + os.remove(file) + + return f"Your compressed video file ({round(twopass.output_filesize, 2)}MB) is located at {Path(twopass.output_filename).resolve()}" + + port = randint(5000, 6000) + threading.Thread(target=open_browser, name="Open Browser").start() + app.run("0.0.0.0", port=port) +else: + twopass_loop(args["target_filesize"]) diff --git a/requirements.txt b/requirements.txt index 2b17fe3..569c442 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ ffmpeg-python pyperclip +flask diff --git a/templates/web.html b/templates/web.html new file mode 100644 index 0000000..c76874b --- /dev/null +++ b/templates/web.html @@ -0,0 +1,144 @@ + + +
+ + +Drag the video playhead, and use the buttons below to set the Start and End times.
+