Repository: eddieoz/reels-clips-automator
Branch: main
Commit: 12aaa4b1ecc9
Files: 13
Total size: 28.5 KB
Directory structure:
gitextract_rfwd0vkc/
├── .gitignore
├── LICENSE
├── README.md
├── reelsfy.py
├── requirements.txt
└── utils/
└── auto-subtitle/
├── .gitignore
├── LICENSE
├── README.md
├── auto_subtitle/
│ ├── __init__.py
│ ├── cli.py
│ └── utils.py
├── requirements.txt
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
launch.json
*.mp4
*.srt
*.aac
content.txt
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Edilson Osorio Jr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Reelsfy - Reels Clips Automator
Introducing Isabella Reels, the intelligent core behind Reelsfy. Inspired by the popularity of Instagram Reels, Isabella is here to transform the way you create content. She's the sister bot to Marcelo Resenha, known for assisting with YouTube video editing. With Isabella's capabilities, you can now effortlessly turn longer videos into engaging Instagram Reels.
Reelsfy is an advanced, AI-powered tool that automates the process of creating Instagram Reels from longer videos. Isabella uses a combination of computer vision to track faces, the GPT model to identify the most engaging parts of the video, and the Whisper ASR system to generate subtitles. This open-source project is perfect for content creators looking to streamline their workflow and optimize content for virality.
## Features
- Converts horizontal videos into vertical Reels, perfect for Instagram
- Downloads videos directly from YouTube or uses local video files
- Uses GPT models to identify and cut the most viral sections of the video
- Employs computer vision to track faces during the video editing process
- Generates captions using the Whisper ASR system
- Uses GPU for faster processing (optional)
## Prerequisites
- Anaconda >= 22.11.1
- Python >= 3.11
- FFMPEG >= 4.4.2
- OpenAI API Key
- A GPU is optional but recommended for faster processing
- Developed on Ubuntu 22.04
## Installation
1. Clone the git repository:
```
$ git clone https://github.com/eddieoz/reels-clips-automator.git
```
2. Create and activate a new conda environment:
```
$ conda create -n reels-clips-automator
$ conda activate reels-clips-automator
```
3. Navigate to the cloned repository's folder:
```
$ cd folder
```
4. Install the required dependencies:
```
$ python -m pip install -r requirements.txt
$ python -m pip install utils/auto-subtitle
```
5. Create a `.env` file in the root directory of the project and include your OpenAI API Key:
```
OPENAI_API_KEY='Your-OpenAI-API-key-here'
```
## Usage
To see the help:
```
$ python reelsfy.py --help
```
For a video from YouTube:
```
$ python reelsfy.py -v <youtube video url>
```
For a local file:
```
$ python reelsfy.py -f <video file>
```
Please note that videos should be approximately 20 minutes long due to the total token limit of the gpt-3.5-turbo-16k model.
## Support
For any queries or support, feel free to reach out:
- Twitter: @eddieoz
- YouTube: @eddieoz
- Zaps to Nostr: eddieoz@sats4.life
- Sats to eddieoz@zbd.gg
## Contributions
Contributions to the project are welcome! Feel free to check out the code and submit a pull request.
## License
This project is licensed under the MIT License.
## Acknowledgements
This project was inspired by the work of [NisaarAgharia's AI-Shorts-Creator](https://github.com/NisaarAgharia/AI-Shorts-Creator).
================================================
FILE: reelsfy.py
================================================
import sys
import numpy as np
from pytube import YouTube
import cv2
import subprocess
import openai
import json
from datetime import datetime
import os
from os import path
import shutil
import argparse
from dotenv import load_dotenv
load_dotenv()
openai.api_key = os.getenv('OPENAI_API_KEY')
# Download video
def download_video(url, filename):
yt = YouTube(url)
video = yt.streams.filter(file_extension='mp4').get_highest_resolution()
# Download the video
video.download(filename=filename, output_path='tmp/')
#Segment Video function
def generate_segments(response):
for i, segment in enumerate(response):
print(i, segment)
start_time = segment.get("start_time", 0).split('.')[0]
end_time = segment.get("end_time", 0).split('.')[0]
pt = datetime.strptime(start_time,'%H:%M:%S')
start_time = pt.second + pt.minute*60 + pt.hour*3600
pt = datetime.strptime(end_time,'%H:%M:%S')
end_time = pt.second + pt.minute*60 + pt.hour*3600
if end_time - start_time < 50:
end_time += (50 - (end_time - start_time))
output_file = f"output{str(i).zfill(3)}.mp4"
command = f"ffmpeg -y -hwaccel cuda -i tmp/input_video.mp4 -vf scale='1920:1080' -qscale:v '3' -b:v 6000k -ss {start_time} -to {end_time} tmp/{output_file}"
subprocess.call(command, shell=True)
def generate_short(input_file, output_file):
try:
# Interval to switch faces (in frames) (ex. 150 frames = 5 seconds, on a 30fps video)
switch_interval = 150
# Frame counter
frame_count = 0
# Index of the currently displayed face
current_face_index = 0
# Constants for cropping
CROP_RATIO_BIG = 1 # Adjust the ratio to control how much of the image (around face) is visible in the cropped video
CROP_RATIO_SMALL = 0.5
VERTICAL_RATIO = 9 / 16 # Aspect ratio for the vertical video
# Load pre-trained face detector from OpenCV
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
# Open video file
cap = cv2.VideoCapture(f"tmp/{input_file}")
# Get the frame dimensions
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f"Image frame_height {frame_height}, frame_width {frame_width}")
# Define the codec and create VideoWriter object
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(f"tmp/{output_file}", fourcc, 30, (1080, 1920)) # Adjust resolution for 9:16 aspect ratio
# success = False
while(cap.isOpened()):
# Read frame from video
ret, frame = cap.read()
if ret == True:
# If we don't have any face positions, detect the faces
# Switch faces if it's time to do so
if frame_count % switch_interval == 0:
# Convert color style from BGR to RGB
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Perform face detection
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=7, minSize=(100, 100))
if len(faces) > 0:
# Initialize trackers and variable to hold face positions
trackers = cv2.legacy.MultiTracker_create()
face_positions = []
for (x, y, w, h) in faces:
face_positions.append((x, y, w, h))
tracker = cv2.legacy.TrackerKCF_create()
tracker.init(frame, (x, y, w, h))
trackers.add(tracker, frame, (x, y, w, h))
# Update trackers and get updated positions
success, boxes = trackers.update(frame)
# Switch faces if it's time to do so
current_face_index = (current_face_index + 1) % len(face_positions)
x, y, w, h = [int(v) for v in boxes[current_face_index]]
print (f"Current Face index {current_face_index} heigth {h} width {w} total faces {len(face_positions)}")
face_center = (x + w//2, y + h//2)
if w * 16 > h * 9:
w_916 = w
h_916 = int(w * 16 / 9)
else:
h_916 = h
w_916 = int(h * 9 / 16)
#Calculate the target width and height for cropping (vertical format)
if max(h, w) < 345:
target_height = int(frame_height * CROP_RATIO_SMALL)
target_width = int(target_height * VERTICAL_RATIO)
else:
target_height = int(frame_height * CROP_RATIO_BIG)
target_width = int(target_height * VERTICAL_RATIO)
# Calculate the top-left corner of the 9:16 rectangle
x_916 = (face_center[0] - w_916 // 2)
y_916 = (face_center[1] - h_916 // 2)
crop_x = max(0, x_916 + (w_916 - target_width) // 2) # Adjust the crop region to center the face
crop_y = max(0, y_916 + (h_916 - target_height) // 2)
crop_x2 = min(crop_x + target_width, frame_width)
crop_y2 = min(crop_y + target_height, frame_height)
# Crop the frame to the face region
crop_img = frame[crop_y:crop_y2, crop_x:crop_x2]
resized = cv2.resize(crop_img, (1080, 1920), interpolation = cv2.INTER_AREA)
out.write(resized)
frame_count += 1
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
# Release everything if job is finished
cap.release()
out.release()
cv2.destroyAllWindows()
# Extract audio from original video
command = f"ffmpeg -y -hwaccel cuda -i tmp/{input_file} -vn -acodec copy tmp/output-audio.aac"
subprocess.call(command, shell=True)
# Merge audio and processed video
command = f"ffmpeg -y -hwaccel cuda -i tmp/{output_file} -i tmp/output-audio.aac -c copy tmp/final-{output_file}"
subprocess.call(command, shell=True)
except Exception as e:
print(f"Error during video cropping: {str(e)}")
def generate_viral(transcript): # Inspiredby https://github.com/NisaarAgharia/AI-Shorts-Creator
json_template = '''
{ "segments" :
[
{
"start_time": 00.00,
"end_time": 00.00,
"description": "Description of the text",
"duration":00,
},
]
}
'''
prompt = f"Given the following video transcript, analyze each part for potential virality and identify 3 most viral segments from the transcript. Each segment should have nothing less than 50 seconds in duration. The provided transcript is as follows: {transcript}. Based on your analysis, return a JSON document containing the timestamps (start and end), the description of the viral part, and its duration. The JSON document should follow this format: {json_template}. Please replace the placeholder values with the actual results from your analysis."
system = f"You are a Viral Segment Identifier, an AI system that analyzes a video's transcript and predict which segments might go viral on social media platforms. You use factors such as emotional impact, humor, unexpected content, and relevance to current trends to make your predictions. You return a structured JSON document detailing the start and end times, the description, and the duration of the potential viral segments."
messages = [
{"role": "system", "content" : system},
{"role": "user", "content": prompt}
]
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-16k",
messages=messages,
max_tokens=512,
n=1,
stop=None
)
return response.choices[0]['message']
def generate_subtitle(input_file, output_folder, results_folder):
command = f"auto_subtitle tmp/{input_file} -o {results_folder}/{output_folder} --model medium"
print (command)
subprocess.call(command, shell=True)
def generate_transcript(input_file):
command = f"auto_subtitle tmp/{input_file} --srt_only True --output_srt True -o tmp/ --model medium"
subprocess.call(command, shell=True)
# Read the contents of the input file
try:
with open(f"tmp/{os.path.basename(input_file).split('.')[0]}.srt", 'r', encoding='utf-8') as file:
transcript = file.read()
except IOError:
print("Error: Failed to read the input file.")
sys.exit(1)
return transcript
def __main__():
# Check command line argument
parser = argparse.ArgumentParser(description='Create 3 reels or tiktoks from Youtube video')
parser.add_argument('-v', '--video_id', required=False, help='Youtube video id. Ex: Cuptv7-A4p0 in https://www.youtube.com/watch?v=Cuptv7-A4p0')
parser.add_argument('-f', '--file', required=False, help='Video file to be used')
args = parser.parse_args()
if not args.video_id and not args.file:
print('Needed at least one argument. <command> --help for help')
sys.exit(1)
if args.video_id and args.file:
print('use --video_id or --file')
sys.exit(1)
# Create temp folder
try:
if os.path.exists("tmp"):
shutil.rmtree("tmp")
os.mkdir('tmp')
except OSError as error:
print(error)
filename = 'input_video.mp4'
if args.video_id:
video_id=args.video_id
url = 'https://www.youtube.com/watch?v='+video_id # Replace with your video's URL
# Download video
download_video(url,filename)
if args.file:
video_id = os.path.basename(args.file).split('.')[0]
print(video_id)
if (path.exists(args.file) == True):
command = f"cp {args.file} tmp/input_video.mp4"
subprocess.call(command, shell=True)
else:
print(f"File {args.file} does not exist")
sys.exit(1)
output_folder = 'results'
# Create outputs folder
try:
os.mkdir(f"{output_folder}")
except OSError as error:
print(error)
try:
os.mkdir(f"{output_folder}/{video_id}")
except OSError as error:
print(error)
# Verifies if output_file exists, or create it. If exists, it doesn't call OpenAI APIs
output_file = f"{output_folder}/{video_id}/content.txt"
if (path.exists(output_file) == False):
# generate transcriptions
transcript = generate_transcript(filename)
print (transcript)
viral_segments = generate_viral(transcript)
content = viral_segments["content"]
try:
with open(output_file, 'w', encoding='utf-8') as file:
file.write(content)
except IOError:
print("Error: Failed to write the output file.")
sys.exit(1)
print("Full transcription written to ", output_file)
else:
# Read the contents of the input file
try:
with open(output_file, 'r', encoding='utf-8') as file:
content = file.read()
except IOError:
print("Error: Failed to read the input file.")
sys.exit(1)
parsed_content = json.loads(content)
generate_segments(parsed_content['segments'])
# Loop through each segment
for i, segment in enumerate(parsed_content['segments']): # Replace xx with the actual number of segments
input_file = f'output{str(i).zfill(3)}.mp4'
output_file = f'output_cropped{str(i).zfill(3)}.mp4'
generate_short(input_file, output_file)
generate_subtitle(f"final-{output_file}", video_id, output_folder)
__main__()
================================================
FILE: requirements.txt
================================================
pytube
opencv-python
openai
# git+https://github.com/m1guelpf/auto-subtitle.git
python-dotenv
opencv-contrib-python
openai-whisper
================================================
FILE: utils/auto-subtitle/.gitignore
================================================
dist
.DS_Store
*.egg-info
auto_subtitle/__pycache__
================================================
FILE: utils/auto-subtitle/LICENSE
================================================
MIT License
Copyright (c) 2022 Miguel Piedrafita <soy@miguelpiedrafita.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: utils/auto-subtitle/README.md
================================================
# Automatic subtitles in your videos
This repository uses `ffmpeg` and [OpenAI's Whisper](https://openai.com/blog/whisper) to automatically generate and overlay subtitles on any video.
## Installation
To get started, you'll need Python 3.7 or newer. Install the binary by running the following command:
pip install git+https://github.com/m1guelpf/auto-subtitle.git
You'll also need to install [`ffmpeg`](https://ffmpeg.org/), which is available from most package managers:
```bash
# on Ubuntu or Debian
sudo apt update && sudo apt install ffmpeg
# on MacOS using Homebrew (https://brew.sh/)
brew install ffmpeg
# on Windows using Chocolatey (https://chocolatey.org/)
choco install ffmpeg
```
## Usage
The following command will generate a `subtitled/video.mp4` file contained the input video with overlayed subtitles.
auto_subtitle /path/to/video.mp4 -o subtitled/
The default setting (which selects the `small` model) works well for transcribing English. You can optionally use a bigger model for better results (especially with other languages). The available models are `tiny`, `tiny.en`, `base`, `base.en`, `small`, `small.en`, `medium`, `medium.en`, `large`.
auto_subtitle /path/to/video.mp4 --model medium
Adding `--task translate` will translate the subtitles into English:
auto_subtitle /path/to/video.mp4 --task translate
Run the following to view all available options:
auto_subtitle --help
## License
This script is open-source and licensed under the MIT License. For more details, check the [LICENSE](LICENSE) file.
================================================
FILE: utils/auto-subtitle/auto_subtitle/__init__.py
================================================
================================================
FILE: utils/auto-subtitle/auto_subtitle/cli.py
================================================
import os
import ffmpeg
import whisper
import argparse
import warnings
import tempfile
from .utils import filename, str2bool, write_srt
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("video", nargs="+", type=str,
help="paths to video files to transcribe")
parser.add_argument("--model", default="small",
choices=whisper.available_models(), help="name of the Whisper model to use")
parser.add_argument("--output_dir", "-o", type=str,
default=".", help="directory to save the outputs")
parser.add_argument("--output_srt", type=str2bool, default=False,
help="whether to output the .srt file along with the video files")
parser.add_argument("--srt_only", type=str2bool, default=False,
help="only generate the .srt file and not create overlayed video")
parser.add_argument("--verbose", type=str2bool, default=False,
help="whether to print out the progress and debug messages")
parser.add_argument("--task", type=str, default="transcribe", choices=[
"transcribe", "translate"], help="whether to perform X->X speech recognition ('transcribe') or X->English translation ('translate')")
parser.add_argument("--language", type=str, default="auto", choices=["auto","af","am","ar","as","az","ba","be","bg","bn","bo","br","bs","ca","cs","cy","da","de","el","en","es","et","eu","fa","fi","fo","fr","gl","gu","ha","haw","he","hi","hr","ht","hu","hy","id","is","it","ja","jw","ka","kk","km","kn","ko","la","lb","ln","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","ne","nl","nn","no","oc","pa","pl","ps","pt","ro","ru","sa","sd","si","sk","sl","sn","so","sq","sr","su","sv","sw","ta","te","tg","th","tk","tl","tr","tt","uk","ur","uz","vi","yi","yo","zh"],
help="What is the origin language of the video? If unset, it is detected automatically.")
args = parser.parse_args().__dict__
model_name: str = args.pop("model")
output_dir: str = args.pop("output_dir")
output_srt: bool = args.pop("output_srt")
srt_only: bool = args.pop("srt_only")
language: str = args.pop("language")
os.makedirs(output_dir, exist_ok=True)
if model_name.endswith(".en"):
warnings.warn(
f"{model_name} is an English-only model, forcing English detection.")
args["language"] = "en"
# if translate task used and language argument is set, then use it
elif language != "auto":
args["language"] = language
model = whisper.load_model(model_name)
audios = get_audio(args.pop("video"))
subtitles = get_subtitles(
audios, output_srt or srt_only, output_dir, lambda audio_path: model.transcribe(audio_path, **args)
)
if srt_only:
return
for path, srt_path in subtitles.items():
out_path = os.path.join(output_dir, f"{filename(path)}.mp4")
print(f"Adding subtitles to {filename(path)}...")
video = ffmpeg.input(path)
audio = video.audio
# ffmpeg.concat(
# video.filter('subtitles', srt_path, force_style="OutlineColour=&H40000000,BorderStyle=3"), audio, v=1, a=1
# ).output(out_path).run(quiet=True, overwrite_output=True)
ffmpeg.concat(
video.filter('subtitles', srt_path, force_style="Alignment=2,MarginV=40,MarginL=55,MarginR=55,Fontname=Noto Sans,Fontsize=11,PrimaryColour=&H00d7ff,Outline=1,Shadow=1,BorderStyle=1"), audio, v=1, a=1
).output(out_path).run(quiet=True, overwrite_output=True)
print(f"Saved subtitled video to {os.path.abspath(out_path)}.")
def get_audio(paths):
temp_dir = tempfile.gettempdir()
audio_paths = {}
for path in paths:
print(f"Extracting audio from {filename(path)}...")
output_path = os.path.join(temp_dir, f"{filename(path)}.wav")
ffmpeg.input(path).output(
output_path,
acodec="pcm_s16le", ac=1, ar="16k"
).run(quiet=True, overwrite_output=True)
audio_paths[path] = output_path
return audio_paths
def get_subtitles(audio_paths: list, output_srt: bool, output_dir: str, transcribe: callable):
subtitles_path = {}
for path, audio_path in audio_paths.items():
srt_path = output_dir if output_srt else tempfile.gettempdir()
srt_path = os.path.join(srt_path, f"{filename(path)}.srt")
print(
f"Generating subtitles for {filename(path)}... This might take a while."
)
warnings.filterwarnings("ignore")
result = transcribe(audio_path)
warnings.filterwarnings("default")
with open(srt_path, "w", encoding="utf-8") as srt:
write_srt(result["segments"], file=srt)
subtitles_path[path] = srt_path
return subtitles_path
if __name__ == '__main__':
main()
================================================
FILE: utils/auto-subtitle/auto_subtitle/utils.py
================================================
import os
from typing import Iterator, TextIO
def str2bool(string):
string = string.lower()
str2val = {"true": True, "false": False}
if string in str2val:
return str2val[string]
else:
raise ValueError(
f"Expected one of {set(str2val.keys())}, got {string}")
def format_timestamp(seconds: float, always_include_hours: bool = False):
assert seconds >= 0, "non-negative timestamp expected"
milliseconds = round(seconds * 1000.0)
hours = milliseconds // 3_600_000
milliseconds -= hours * 3_600_000
minutes = milliseconds // 60_000
milliseconds -= minutes * 60_000
seconds = milliseconds // 1_000
milliseconds -= seconds * 1_000
hours_marker = f"{hours}:" if always_include_hours or hours > 0 else ""
return f"{hours_marker}{minutes:02d}:{seconds:02d}.{milliseconds:03d}"
def write_srt(transcript: Iterator[dict], file: TextIO):
for i, segment in enumerate(transcript, start=1):
print(
f"{i}\n"
f"{format_timestamp(segment['start'], always_include_hours=True)} --> "
f"{format_timestamp(segment['end'], always_include_hours=True)}\n"
f"{segment['text'].strip().replace('-->', '->')}\n",
file=file,
flush=True,
)
def filename(path):
return os.path.splitext(os.path.basename(path))[0]
================================================
FILE: utils/auto-subtitle/requirements.txt
================================================
openai-whisper
================================================
FILE: utils/auto-subtitle/setup.py
================================================
from setuptools import setup, find_packages
setup(
version="1.0",
name="auto_subtitle",
packages=find_packages(),
py_modules=["auto_subtitle"],
author="Miguel Piedrafita",
install_requires=[
'openai-whisper',
],
description="Automatically generate and embed subtitles into your videos",
entry_points={
'console_scripts': ['auto_subtitle=auto_subtitle.cli:main'],
},
include_package_data=True,
)
gitextract_rfwd0vkc/
├── .gitignore
├── LICENSE
├── README.md
├── reelsfy.py
├── requirements.txt
└── utils/
└── auto-subtitle/
├── .gitignore
├── LICENSE
├── README.md
├── auto_subtitle/
│ ├── __init__.py
│ ├── cli.py
│ └── utils.py
├── requirements.txt
└── setup.py
SYMBOL INDEX (14 symbols across 3 files) FILE: reelsfy.py function download_video (line 20) | def download_video(url, filename): function generate_segments (line 29) | def generate_segments(response): function generate_short (line 50) | def generate_short(input_file, output_file): function generate_viral (line 176) | def generate_viral(transcript): # Inspiredby https://github.com/NisaarAg... function generate_subtitle (line 207) | def generate_subtitle(input_file, output_folder, results_folder): function generate_transcript (line 212) | def generate_transcript(input_file): function __main__ (line 225) | def __main__(): FILE: utils/auto-subtitle/auto_subtitle/cli.py function main (line 10) | def main(): function get_audio (line 75) | def get_audio(paths): function get_subtitles (line 94) | def get_subtitles(audio_paths: list, output_srt: bool, output_dir: str, ... FILE: utils/auto-subtitle/auto_subtitle/utils.py function str2bool (line 5) | def str2bool(string): function format_timestamp (line 16) | def format_timestamp(seconds: float, always_include_hours: bool = False): function write_srt (line 33) | def write_srt(transcript: Iterator[dict], file: TextIO): function filename (line 45) | def filename(path):
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": ".gitignore",
"chars": 3120,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2023 Edilson Osorio Jr\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 2839,
"preview": "# Reelsfy - Reels Clips Automator\n\nIntroducing Isabella Reels, the intelligent core behind Reelsfy. Inspired by the popu"
},
{
"path": "reelsfy.py",
"chars": 12424,
"preview": "import sys\nimport numpy as np\nfrom pytube import YouTube\nimport cv2\nimport subprocess\nimport openai\nimport json\nfrom dat"
},
{
"path": "requirements.txt",
"chars": 130,
"preview": "pytube\nopencv-python\nopenai\n# git+https://github.com/m1guelpf/auto-subtitle.git\npython-dotenv\nopencv-contrib-python\nopen"
},
{
"path": "utils/auto-subtitle/.gitignore",
"chars": 52,
"preview": "dist\n.DS_Store\n*.egg-info\nauto_subtitle/__pycache__\n"
},
{
"path": "utils/auto-subtitle/LICENSE",
"chars": 1101,
"preview": "MIT License\n\nCopyright (c) 2022 Miguel Piedrafita <soy@miguelpiedrafita.com>\n\nPermission is hereby granted, free of char"
},
{
"path": "utils/auto-subtitle/README.md",
"chars": 1567,
"preview": "# Automatic subtitles in your videos\n\nThis repository uses `ffmpeg` and [OpenAI's Whisper](https://openai.com/blog/whisp"
},
{
"path": "utils/auto-subtitle/auto_subtitle/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "utils/auto-subtitle/auto_subtitle/cli.py",
"chars": 4993,
"preview": "import os\nimport ffmpeg\nimport whisper\nimport argparse\nimport warnings\nimport tempfile\nfrom .utils import filename, str2"
},
{
"path": "utils/auto-subtitle/auto_subtitle/utils.py",
"chars": 1371,
"preview": "import os\nfrom typing import Iterator, TextIO\n\n\ndef str2bool(string):\n string = string.lower()\n str2val = {\"true\":"
},
{
"path": "utils/auto-subtitle/requirements.txt",
"chars": 15,
"preview": "openai-whisper\n"
},
{
"path": "utils/auto-subtitle/setup.py",
"chars": 456,
"preview": "from setuptools import setup, find_packages\n\nsetup(\n version=\"1.0\",\n name=\"auto_subtitle\",\n packages=find_packa"
}
]
About this extraction
This page contains the full source code of the eddieoz/reels-clips-automator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (28.5 KB), approximately 7.4k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.