[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\nlaunch.json\n*.mp4\n*.srt\n*.aac\ncontent.txt\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Edilson Osorio Jr\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Reelsfy - Reels Clips Automator\n\nIntroducing 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.\n\nReelsfy 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.\n\n## Features\n\n- Converts horizontal videos into vertical Reels, perfect for Instagram\n- Downloads videos directly from YouTube or uses local video files\n- Uses GPT models to identify and cut the most viral sections of the video\n- Employs computer vision to track faces during the video editing process\n- Generates captions using the Whisper ASR system\n- Uses GPU for faster processing (optional)\n\n## Prerequisites\n\n- Anaconda >= 22.11.1\n- Python >= 3.11\n- FFMPEG >= 4.4.2\n- OpenAI API Key\n- A GPU is optional but recommended for faster processing\n- Developed on Ubuntu 22.04\n\n\n## Installation\n\n1. Clone the git repository:\n\n```\n$ git clone https://github.com/eddieoz/reels-clips-automator.git\n```\n\n2. Create and activate a new conda environment:\n\n```\n$ conda create -n reels-clips-automator\n$ conda activate reels-clips-automator\n```\n\n3. Navigate to the cloned repository's folder:\n\n```\n$ cd folder\n```\n\n4. Install the required dependencies:\n\n```\n$ python -m pip install -r requirements.txt\n$ python -m pip install utils/auto-subtitle\n```\n\n5. Create a `.env` file in the root directory of the project and include your OpenAI API Key:\n\n```\nOPENAI_API_KEY='Your-OpenAI-API-key-here'\n```\n\n## Usage\n\nTo see the help:\n\n```\n$ python reelsfy.py --help\n```\n\nFor a video from YouTube:\n\n```\n$ python reelsfy.py -v <youtube video url>\n```\n\nFor a local file:\n\n```\n$ python reelsfy.py -f <video file>\n```\n\nPlease note that videos should be approximately 20 minutes long due to the total token limit of the gpt-3.5-turbo-16k model.\n\n## Support\n\nFor any queries or support, feel free to reach out:\n\n- Twitter: @eddieoz\n- YouTube: @eddieoz\n- Zaps to Nostr: eddieoz@sats4.life\n- Sats to eddieoz@zbd.gg\n\n## Contributions\n\nContributions to the project are welcome! Feel free to check out the code and submit a pull request.\n\n## License\n\nThis project is licensed under the MIT License.\n\n## Acknowledgements\n\nThis project was inspired by the work of [NisaarAgharia's AI-Shorts-Creator](https://github.com/NisaarAgharia/AI-Shorts-Creator).\n"
  },
  {
    "path": "reelsfy.py",
    "content": "import sys\nimport numpy as np\nfrom pytube import YouTube\nimport cv2\nimport subprocess\nimport openai\nimport json\nfrom datetime import datetime\nimport os\nfrom os import path\nimport shutil\n\nimport argparse\n\nfrom dotenv import load_dotenv\nload_dotenv()\nopenai.api_key = os.getenv('OPENAI_API_KEY')\n\n# Download video\ndef download_video(url, filename):\n    yt = YouTube(url)\n    video = yt.streams.filter(file_extension='mp4').get_highest_resolution()\n\n    # Download the video\n    video.download(filename=filename, output_path='tmp/')\n\n\n#Segment Video function\ndef generate_segments(response):\n  \n  for i, segment in enumerate(response):\n        print(i, segment)\n\n        start_time = segment.get(\"start_time\", 0).split('.')[0]\n        end_time = segment.get(\"end_time\", 0).split('.')[0]\n\n        pt = datetime.strptime(start_time,'%H:%M:%S')\n        start_time = pt.second + pt.minute*60 + pt.hour*3600\n\n        pt = datetime.strptime(end_time,'%H:%M:%S')\n        end_time = pt.second + pt.minute*60 + pt.hour*3600\n\n        if end_time - start_time < 50:\n            end_time += (50 - (end_time - start_time))\n\n        output_file = f\"output{str(i).zfill(3)}.mp4\"\n        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}\"\n        subprocess.call(command, shell=True)\n\ndef generate_short(input_file, output_file):\n    try:\n\n        # Interval to switch faces (in frames) (ex. 150 frames = 5 seconds, on a 30fps video)\n        switch_interval = 150\n\n        # Frame counter\n        frame_count = 0\n\n        # Index of the currently displayed face\n        current_face_index = 0\n        \n        # Constants for cropping    \n        CROP_RATIO_BIG = 1 # Adjust the ratio to control how much of the image (around face) is visible in the cropped video\n        CROP_RATIO_SMALL = 0.5\n        VERTICAL_RATIO = 9 / 16  # Aspect ratio for the vertical video\n\n        # Load pre-trained face detector from OpenCV\n        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')\n\n        # Open video file\n        cap = cv2.VideoCapture(f\"tmp/{input_file}\")\n\n        # Get the frame dimensions\n        frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n        frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n        print(f\"Image frame_height {frame_height}, frame_width {frame_width}\")\n\n        # Define the codec and create VideoWriter object\n        fourcc = cv2.VideoWriter_fourcc(*'mp4v')\n        out = cv2.VideoWriter(f\"tmp/{output_file}\", fourcc, 30, (1080, 1920))  # Adjust resolution for 9:16 aspect ratio\n\n        # success = False\n        while(cap.isOpened()):\n            # Read frame from video\n            ret, frame = cap.read()\n\n            if ret == True:\n                \n                # If we don't have any face positions, detect the faces\n                # Switch faces if it's time to do so\n                if frame_count % switch_interval == 0:\n                    # Convert color style from BGR to RGB\n                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n\n                    # Perform face detection\n                    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')\n                    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=7, minSize=(100, 100))\n           \n                    if len(faces) > 0:\n                        # Initialize trackers and variable to hold face positions\n                        trackers = cv2.legacy.MultiTracker_create()\n                        face_positions = []\n                        \n                        for (x, y, w, h) in faces:\n                            face_positions.append((x, y, w, h))\n                            tracker = cv2.legacy.TrackerKCF_create()\n                            tracker.init(frame, (x, y, w, h))\n                            trackers.add(tracker, frame, (x, y, w, h))\n\n                        # Update trackers and get updated positions\n                        success, boxes = trackers.update(frame)\n\n                    # Switch faces if it's time to do so\n                    current_face_index = (current_face_index + 1) % len(face_positions)\n                    x, y, w, h = [int(v) for v in boxes[current_face_index]]\n\n                    print (f\"Current Face index {current_face_index} heigth {h} width {w} total faces {len(face_positions)}\")\n\n                    face_center = (x + w//2, y + h//2)\n\n                    if w * 16 > h * 9:\n                        w_916 = w\n                        h_916 = int(w * 16 / 9)\n                    else:\n                        h_916 = h\n                        w_916 = int(h * 9 / 16)\n\n                    #Calculate the target width and height for cropping (vertical format)\n                    if max(h, w) < 345:\n                        target_height = int(frame_height * CROP_RATIO_SMALL)\n                        target_width = int(target_height * VERTICAL_RATIO)\n                    else:\n                        target_height = int(frame_height * CROP_RATIO_BIG)\n                        target_width = int(target_height * VERTICAL_RATIO)\n\n                # Calculate the top-left corner of the 9:16 rectangle\n                x_916 = (face_center[0] - w_916 // 2)\n                y_916 = (face_center[1] - h_916 // 2)\n\n                crop_x = max(0, x_916 + (w_916 - target_width) // 2)  # Adjust the crop region to center the face\n                crop_y = max(0, y_916 + (h_916 - target_height) // 2)\n                crop_x2 = min(crop_x + target_width, frame_width)\n                crop_y2 = min(crop_y + target_height, frame_height)\n\n\n                # Crop the frame to the face region\n                crop_img = frame[crop_y:crop_y2, crop_x:crop_x2]\n                \n                resized = cv2.resize(crop_img, (1080, 1920), interpolation = cv2.INTER_AREA)\n                \n                out.write(resized)\n\n                frame_count += 1\n\n                if cv2.waitKey(1) & 0xFF == ord('q'):\n                    break\n            else:\n                break\n\n        # Release everything if job is finished\n        cap.release()\n        out.release()\n        cv2.destroyAllWindows()\n\n        # Extract audio from original video\n        command = f\"ffmpeg -y -hwaccel cuda -i tmp/{input_file} -vn -acodec copy tmp/output-audio.aac\"\n        subprocess.call(command, shell=True)\n\n        # Merge audio and processed video\n        command = f\"ffmpeg -y -hwaccel cuda -i tmp/{output_file} -i tmp/output-audio.aac -c copy tmp/final-{output_file}\"\n        subprocess.call(command, shell=True)\n\n    except Exception as e:\n        print(f\"Error during video cropping: {str(e)}\")\n\ndef generate_viral(transcript): # Inspiredby https://github.com/NisaarAgharia/AI-Shorts-Creator \n\n    json_template = '''\n        { \"segments\" :\n            [\n                {\n                    \"start_time\": 00.00, \n                    \"end_time\": 00.00,\n                    \"description\": \"Description of the text\",\n                    \"duration\":00,\n                },    \n            ]\n        }\n    '''\n\n    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.\"\n    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.\"\n    messages = [\n        {\"role\": \"system\", \"content\" : system},\n        {\"role\": \"user\", \"content\": prompt}\n    ]\n\n    response = openai.ChatCompletion.create(\n        model=\"gpt-3.5-turbo-16k\",\n        messages=messages,\n        max_tokens=512,\n        n=1,\n        stop=None\n    )\n    return response.choices[0]['message']\n\ndef generate_subtitle(input_file, output_folder, results_folder):\n    command = f\"auto_subtitle tmp/{input_file} -o {results_folder}/{output_folder} --model medium\"\n    print (command)\n    subprocess.call(command, shell=True)\n\ndef generate_transcript(input_file):\n    command = f\"auto_subtitle tmp/{input_file} --srt_only True --output_srt True -o tmp/ --model medium\"\n    subprocess.call(command, shell=True)\n    \n    # Read the contents of the input file\n    try:\n        with open(f\"tmp/{os.path.basename(input_file).split('.')[0]}.srt\", 'r', encoding='utf-8') as file:\n            transcript = file.read()\n    except IOError:\n        print(\"Error: Failed to read the input file.\")\n        sys.exit(1)\n    return transcript\n\ndef __main__():\n\n    # Check command line argument    \n    parser = argparse.ArgumentParser(description='Create 3 reels or tiktoks from Youtube video')\n    parser.add_argument('-v', '--video_id', required=False, help='Youtube video id. Ex: Cuptv7-A4p0 in https://www.youtube.com/watch?v=Cuptv7-A4p0')\n    parser.add_argument('-f', '--file', required=False, help='Video file to be used')\n    args = parser.parse_args()\n    \n    if not args.video_id and not args.file: \n        print('Needed at least one argument. <command> --help for help')\n        sys.exit(1)\n    \n    if args.video_id and args.file:\n        print('use --video_id or --file')\n        sys.exit(1)\n\n    # Create temp folder\n    try: \n        if os.path.exists(\"tmp\"):\n            shutil.rmtree(\"tmp\")\n        os.mkdir('tmp') \n    except OSError as error: \n        print(error)\n\n    filename = 'input_video.mp4'\n    if args.video_id:\n        video_id=args.video_id\n        url = 'https://www.youtube.com/watch?v='+video_id  # Replace with your video's URL\n        # Download video\n        download_video(url,filename)\n    \n    if args.file:\n        video_id = os.path.basename(args.file).split('.')[0]\n        print(video_id)\n        if (path.exists(args.file) == True):\n            command = f\"cp {args.file} tmp/input_video.mp4\"\n            subprocess.call(command, shell=True)\n        else:\n            print(f\"File {args.file} does not exist\")\n            sys.exit(1)\n\n    output_folder = 'results'\n    \n    # Create outputs folder\n    try: \n        os.mkdir(f\"{output_folder}\") \n    except OSError as error: \n        print(error)\n    try: \n        os.mkdir(f\"{output_folder}/{video_id}\") \n    except OSError as error: \n        print(error)\n\n    # Verifies if output_file exists, or create it. If exists, it doesn't call OpenAI APIs\n    output_file = f\"{output_folder}/{video_id}/content.txt\"\n    if (path.exists(output_file) == False):\n        # generate transcriptions\n        transcript = generate_transcript(filename)\n        print (transcript)\n        \n        viral_segments = generate_viral(transcript)\n        content = viral_segments[\"content\"]\n        try:\n            with open(output_file, 'w', encoding='utf-8') as file:\n                file.write(content)\n        except IOError:\n            print(\"Error: Failed to write the output file.\")\n            sys.exit(1)\n        print(\"Full transcription written to \", output_file)\n    else:\n        # Read the contents of the input file\n        try:\n            with open(output_file, 'r', encoding='utf-8') as file:\n                content = file.read()\n        except IOError:\n            print(\"Error: Failed to read the input file.\")\n            sys.exit(1)\n\n    parsed_content = json.loads(content)\n    generate_segments(parsed_content['segments'])\n    \n    # Loop through each segment\n    for i, segment in enumerate(parsed_content['segments']):  # Replace xx with the actual number of segments\n        input_file = f'output{str(i).zfill(3)}.mp4'\n        output_file = f'output_cropped{str(i).zfill(3)}.mp4'\n        generate_short(input_file, output_file)\n        generate_subtitle(f\"final-{output_file}\", video_id, output_folder)\n\n__main__()\n"
  },
  {
    "path": "requirements.txt",
    "content": "pytube\nopencv-python\nopenai\n# git+https://github.com/m1guelpf/auto-subtitle.git\npython-dotenv\nopencv-contrib-python\nopenai-whisper"
  },
  {
    "path": "utils/auto-subtitle/.gitignore",
    "content": "dist\n.DS_Store\n*.egg-info\nauto_subtitle/__pycache__\n"
  },
  {
    "path": "utils/auto-subtitle/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Miguel Piedrafita <soy@miguelpiedrafita.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "utils/auto-subtitle/README.md",
    "content": "# Automatic subtitles in your videos\n\nThis repository uses `ffmpeg` and [OpenAI's Whisper](https://openai.com/blog/whisper) to automatically generate and overlay subtitles on any video.\n\n## Installation\n\nTo get started, you'll need Python 3.7 or newer. Install the binary by running the following command:\n\n    pip install git+https://github.com/m1guelpf/auto-subtitle.git\n\nYou'll also need to install [`ffmpeg`](https://ffmpeg.org/), which is available from most package managers:\n\n```bash\n# on Ubuntu or Debian\nsudo apt update && sudo apt install ffmpeg\n\n# on MacOS using Homebrew (https://brew.sh/)\nbrew install ffmpeg\n\n# on Windows using Chocolatey (https://chocolatey.org/)\nchoco install ffmpeg\n```\n\n## Usage\n\nThe following command will generate a `subtitled/video.mp4` file contained the input video with overlayed subtitles.\n\n    auto_subtitle /path/to/video.mp4 -o subtitled/\n\nThe 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`.\n\n    auto_subtitle /path/to/video.mp4 --model medium\n\nAdding `--task translate` will translate the subtitles into English:\n\n    auto_subtitle /path/to/video.mp4 --task translate\n\nRun the following to view all available options:\n\n    auto_subtitle --help\n\n## License\n\nThis script is open-source and licensed under the MIT License. For more details, check the [LICENSE](LICENSE) file.\n"
  },
  {
    "path": "utils/auto-subtitle/auto_subtitle/__init__.py",
    "content": ""
  },
  {
    "path": "utils/auto-subtitle/auto_subtitle/cli.py",
    "content": "import os\nimport ffmpeg\nimport whisper\nimport argparse\nimport warnings\nimport tempfile\nfrom .utils import filename, str2bool, write_srt\n\n\ndef main():\n    parser = argparse.ArgumentParser(\n        formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n    parser.add_argument(\"video\", nargs=\"+\", type=str,\n                        help=\"paths to video files to transcribe\")\n    parser.add_argument(\"--model\", default=\"small\",\n                        choices=whisper.available_models(), help=\"name of the Whisper model to use\")\n    parser.add_argument(\"--output_dir\", \"-o\", type=str,\n                        default=\".\", help=\"directory to save the outputs\")\n    parser.add_argument(\"--output_srt\", type=str2bool, default=False,\n                        help=\"whether to output the .srt file along with the video files\")\n    parser.add_argument(\"--srt_only\", type=str2bool, default=False,\n                        help=\"only generate the .srt file and not create overlayed video\")\n    parser.add_argument(\"--verbose\", type=str2bool, default=False,\n                        help=\"whether to print out the progress and debug messages\")\n\n    parser.add_argument(\"--task\", type=str, default=\"transcribe\", choices=[\n                        \"transcribe\", \"translate\"], help=\"whether to perform X->X speech recognition ('transcribe') or X->English translation ('translate')\")\n    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\"], \n    help=\"What is the origin language of the video? If unset, it is detected automatically.\")\n\n    args = parser.parse_args().__dict__\n    model_name: str = args.pop(\"model\")\n    output_dir: str = args.pop(\"output_dir\")\n    output_srt: bool = args.pop(\"output_srt\")\n    srt_only: bool = args.pop(\"srt_only\")\n    language: str = args.pop(\"language\")\n    \n    os.makedirs(output_dir, exist_ok=True)\n\n    if model_name.endswith(\".en\"):\n        warnings.warn(\n            f\"{model_name} is an English-only model, forcing English detection.\")\n        args[\"language\"] = \"en\"\n    # if translate task used and language argument is set, then use it\n    elif language != \"auto\":\n        args[\"language\"] = language\n        \n    model = whisper.load_model(model_name)\n    audios = get_audio(args.pop(\"video\"))\n    subtitles = get_subtitles(\n        audios, output_srt or srt_only, output_dir, lambda audio_path: model.transcribe(audio_path, **args)\n    )\n\n    if srt_only:\n        return\n\n    for path, srt_path in subtitles.items():\n        out_path = os.path.join(output_dir, f\"{filename(path)}.mp4\")\n\n        print(f\"Adding subtitles to {filename(path)}...\")\n\n        video = ffmpeg.input(path)\n        audio = video.audio\n\n        # ffmpeg.concat(\n        #     video.filter('subtitles', srt_path, force_style=\"OutlineColour=&H40000000,BorderStyle=3\"), audio, v=1, a=1\n        # ).output(out_path).run(quiet=True, overwrite_output=True)\n\n        ffmpeg.concat(\n            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\n            ).output(out_path).run(quiet=True, overwrite_output=True)\n        \n        print(f\"Saved subtitled video to {os.path.abspath(out_path)}.\")\n\ndef get_audio(paths):\n    temp_dir = tempfile.gettempdir()\n\n    audio_paths = {}\n\n    for path in paths:\n        print(f\"Extracting audio from {filename(path)}...\")\n        output_path = os.path.join(temp_dir, f\"{filename(path)}.wav\")\n\n        ffmpeg.input(path).output(\n            output_path,\n            acodec=\"pcm_s16le\", ac=1, ar=\"16k\"\n        ).run(quiet=True, overwrite_output=True)\n\n        audio_paths[path] = output_path\n\n    return audio_paths\n\n\ndef get_subtitles(audio_paths: list, output_srt: bool, output_dir: str, transcribe: callable):\n    subtitles_path = {}\n\n    for path, audio_path in audio_paths.items():\n        srt_path = output_dir if output_srt else tempfile.gettempdir()\n        srt_path = os.path.join(srt_path, f\"{filename(path)}.srt\")\n        \n        print(\n            f\"Generating subtitles for {filename(path)}... This might take a while.\"\n        )\n\n        warnings.filterwarnings(\"ignore\")\n        result = transcribe(audio_path)\n        warnings.filterwarnings(\"default\")\n\n        with open(srt_path, \"w\", encoding=\"utf-8\") as srt:\n            write_srt(result[\"segments\"], file=srt)\n\n        subtitles_path[path] = srt_path\n\n    return subtitles_path\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "utils/auto-subtitle/auto_subtitle/utils.py",
    "content": "import os\nfrom typing import Iterator, TextIO\n\n\ndef str2bool(string):\n    string = string.lower()\n    str2val = {\"true\": True, \"false\": False}\n\n    if string in str2val:\n        return str2val[string]\n    else:\n        raise ValueError(\n            f\"Expected one of {set(str2val.keys())}, got {string}\")\n\n\ndef format_timestamp(seconds: float, always_include_hours: bool = False):\n    assert seconds >= 0, \"non-negative timestamp expected\"\n    milliseconds = round(seconds * 1000.0)\n\n    hours = milliseconds // 3_600_000\n    milliseconds -= hours * 3_600_000\n\n    minutes = milliseconds // 60_000\n    milliseconds -= minutes * 60_000\n\n    seconds = milliseconds // 1_000\n    milliseconds -= seconds * 1_000\n\n    hours_marker = f\"{hours}:\" if always_include_hours or hours > 0 else \"\"\n    return f\"{hours_marker}{minutes:02d}:{seconds:02d}.{milliseconds:03d}\"\n\n\ndef write_srt(transcript: Iterator[dict], file: TextIO):\n    for i, segment in enumerate(transcript, start=1):\n        print(\n            f\"{i}\\n\"\n            f\"{format_timestamp(segment['start'], always_include_hours=True)} --> \"\n            f\"{format_timestamp(segment['end'], always_include_hours=True)}\\n\"\n            f\"{segment['text'].strip().replace('-->', '->')}\\n\",\n            file=file,\n            flush=True,\n        )\n\n\ndef filename(path):\n    return os.path.splitext(os.path.basename(path))[0]\n"
  },
  {
    "path": "utils/auto-subtitle/requirements.txt",
    "content": "openai-whisper\n"
  },
  {
    "path": "utils/auto-subtitle/setup.py",
    "content": "from setuptools import setup, find_packages\n\nsetup(\n    version=\"1.0\",\n    name=\"auto_subtitle\",\n    packages=find_packages(),\n    py_modules=[\"auto_subtitle\"],\n    author=\"Miguel Piedrafita\",\n    install_requires=[\n        'openai-whisper',\n    ],\n    description=\"Automatically generate and embed subtitles into your videos\",\n    entry_points={\n        'console_scripts': ['auto_subtitle=auto_subtitle.cli:main'],\n    },\n    include_package_data=True,\n)\n"
  }
]