Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cisvhat committed Feb 10, 2024
0 parents commit 7bdec6d
Show file tree
Hide file tree
Showing 12 changed files with 820 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CLIENT_SECRETS_FILE=src/client_secrets.json
SCOPES=https://www.googleapis.com/auth/youtube.upload
API_SERVICE_NAME=youtube
API_VERSION=v3
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
venv/*
temp/*
output/*
**/client_secrets.json
.pytest_cache/*
**/__pycache__/*
.DS_Store
.env
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 FujiwaraChoki

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.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Youtube-short creater with BERT-VITS2 & DALLE-3 & ChatGPT 🚀

Automate the creation of YouTube Shorts locally, simply by providing a video topic to talk about.

[![ビデオの例](docs/thumbnail.png)](https://www.youtube.com/shorts/Eah3Pb9s2Kk "例を見る")


## Installation 📥

`YoutubeShortCreator` requires Python 3.11 to run effectively. If you don't have Python installed, you can download it from [here](https://www.python.org/downloads/).

After you finished installing Python, you can install `YoutubeShortCreator` by following the steps below:

```bash
git clone https://github.com/avonx/YoutubeShortCreator.git
cd YoutubeShortCreator

# Install requirements
pip install -r requirements.txt

# Copy .env.example and fill out values
cp .env.example .env

# Run the python program
python generate_video.py

# Alternatively, run this to upload videos to youtube automatically.
python upload_video.py
```

See [`.env.example`](.env.example) for the required environment variables.

## Usage 🛠️

1. Copy the `.env.example` file to `.env` and fill in the required values
2. Run this command `python generate_video.py`
3. Enter a topic to talk about
4. Wait for the video to be generated
5. The video's location is `output/<タイトルは自動的に生成されます>.mp4`


## Automatic YouTube Uploading 🎥

To use this feature, you need to:

1. Create a project inside your Google Cloud Platform -> [GCP](https://console.cloud.google.com/).
2. Obtain `client_secret.json` from the project and add it to the Backend/ directory.
3. Enable the YouTube v3 API in your project -> [GCP-API-Library](https://console.cloud.google.com/apis/library/youtube.googleapis.com)
4. Create an `OAuth consent screen` and add yourself (the account of your YouTube channel) to the testers.
5. Enable the following scopes in the `OAuth consent screen` for your project:

```
'https://www.googleapis.com/auth/youtube.upload'
```

## License 📝

See [`LICENSE`](LICENSE) file for more information.
Binary file added docs/thumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
termcolor
openai
pytest
black
pydub
requests
google-api-python-client
google-auth
google-auth-httplib2
google-auth-oauthlib
httplib2
oauthlib
59 changes: 59 additions & 0 deletions src/generate_ideas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from openai import OpenAI
import json
from termcolor import colored

client = OpenAI()


def generate_ideas(meta_topic: str, num_ideas: int) -> dict:
"""
メタトピックについて、アイデアを生成する。
"""
# プロンプトの構築
prompt_valuables = f"""
下記のメタトピックに応じて、youtubeのショート動画のアイデアを考えてください。
主題: {meta_topic}
出力するアイデアの個数: {num_ideas}
"""

prompt_format = """
次の形式のjsonで出力するようにしてください。
"ideas": [
"アイデア1", "アイデア2", ...
]
例)
"ideas": [
"実は宇宙人はすでに紛れ込んでいる", "ドラえもんが怒った時に何が起こるか", ...
]
"""
response = client.chat.completions.create(
model="gpt-4-1106-preview",
# model="gpt-3.5-turbo-1106",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{
"role": "user",
"content": f"次の文章からJSONを生成してください。\n{prompt_valuables}{prompt_format}",
},
],
response_format={"type": "json_object"},
)

script = response.choices[0].message.content.strip()
# scriptをjsonに変換
try:
script = json.loads(script)
return script
except json.JSONDecodeError as e:
print(colored("スクリプトの生成に失敗しました。", "red"))
print(colored("エラー内容: ", "red"), e)
return None


if __name__ == "__main__":
meta_topic = "SFチックな、ちょっとオカルトな話を具体的に。"
num_ideas = 2
ideas = generate_ideas(meta_topic, num_ideas)
print(ideas)
52 changes: 52 additions & 0 deletions src/generate_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from pathlib import Path
from termcolor import colored

from generate_ideas import generate_ideas
from topic2text import generate_script
from text2voice import generate_audio_for_clips
from voice2video import process_json_data, create_combined_video_from_clips


def topic2video(video_subject, num_clips, output_file_path):
# Create or clear the temp folder
temp_folder = Path("./temp")
temp_folder.mkdir(exist_ok=True)
for file in temp_folder.glob("*"):
file.unlink()

clips = generate_script(video_subject, num_clips)
print(colored("[+] Generating script...", "yellow"))
print(clips)

updated_clips = generate_audio_for_clips(clips)
print(colored("[+] Generating audio for clips...", "yellow"))
print(updated_clips)

updated_clips = process_json_data(updated_clips)
print(colored("[+] Processing JSON data...", "yellow"))
print(updated_clips)

video_path = create_combined_video_from_clips(
updated_clips, output_file_path)
print(colored("[+] Creating combined video...", "yellow"))

# Delete temporary files
for file in temp_folder.glob("*"):
file.unlink()

return video_path, updated_clips


if __name__ == "__main__":
meta_topic = "科学に関する話題を具体的に。"
num_ideas = 3
ideas = generate_ideas(meta_topic, num_ideas)
print(colored("[+] Generating ideas...", "yellow"))
print(ideas)

for idea in ideas["ideas"]:
video_subject = idea
num_clips = 5
output_file_path = f"./output/{video_subject}.mp4"
video_path, updated_clips = topic2video(
video_subject, num_clips, output_file_path)
115 changes: 115 additions & 0 deletions src/text2voice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import requests
from pydub import AudioSegment
import io
import uuid
from pathlib import Path
from termcolor import colored

# tempフォルダのパスを定義(存在しない場合は作成)
temp_folder = Path("./temp")
temp_folder.mkdir(exist_ok=True)


def generate_audio_for_clips(clips):
"""
Generate audio files for each clip in the given list.
Args:
clips (list): A list of clips, where each clip is a dictionary containing the "text" key.
Returns:
None
"""
for clip in clips["clips"]:
filename = f"{str(uuid.uuid4())}.mp3"
filepath = temp_folder / filename # 保存するファイルパス
text = clip["text"] # 音声に変換するテキスト

combined_audio = process_and_combine_audio(text)
if combined_audio:
combined_audio.export(filepath, format="mp3")
clip["audio_path"] = str(filepath) # 生成した音声ファイルのパスをJSONに追加
else:
clip["audio_path"] = None # 音声生成に失敗した場合
return clips


def process_and_combine_audio(text: str) -> AudioSegment:
"""
Process and combine audio segments for each paragraph in the given text.
Args:
text (str): The input text.
Returns:
AudioSegment: The combined audio segment.
"""
text_no_newlines = text.replace("\n\n", "")
paragraphs = [f"{p}。" for p in text_no_newlines.split("。") if p]

combined_audio = None
for i, paragraph in enumerate(paragraphs):
print(
colored(f"[+] Processing paragraph {i+1}/{len(paragraphs)}...", "yellow"))
audio_content = get_tts_audio(paragraph)
if audio_content:
current_audio = AudioSegment.from_file(
io.BytesIO(audio_content), format="wav")
silence = AudioSegment.silent(duration=1000) # 1秒の無音を追加
combined_audio = combined_audio + silence + \
current_audio if combined_audio else current_audio
return combined_audio


def get_tts_audio(text: str) -> bytes:
"""
Get text-to-speech audio for the given text.
Args:
text (str): The text to convert to audio.
Returns:
bytes: The audio content in bytes if successful, None otherwise.
"""
url = "https://igeb37tgif2guh-5000.proxy.runpod.net/voice"
params = {
"text": text,
"encoding": "utf-8",
"model_id": 4,
"speaker_id": 0,
"sdp_ratio": 0.6,
"noise": 0.7,
"noisew": 1.1,
"length": 0.75,
"language": "JP",
"auto_split": True,
"split_interval": 0.5,
"assist_text_weight": 1,
"style": "Neutral",
"style_weight": 5,
}
response = requests.get(url, params=params)
if response.status_code == 200 and "audio/wav" in response.headers.get("Content-Type"):
return response.content
return None


if __name__ == "__main__":
# JSONデータを処理するための例
clips = {
"clips": [
{
"num": 0,
"title": "サンプルタイトル",
"text": "これはサンプルのテキストです。宇宙人はいます。会ったことがあります!",
"video_prompt": "静かな森の風景を描写する文章。",
},
# 他のクリップ...
]
}

updated_clips = generate_audio_for_clips(clips)

# 結果を確認
print(updated_clips)
Loading

0 comments on commit 7bdec6d

Please sign in to comment.