Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 3.1 #1550

Merged
merged 34 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6024ae7
final_video.py: ffmpeg-python port
Xpl0itU Dec 27, 2022
3f2138f
Fix resolution on story mode
Xpl0itU Dec 28, 2022
6f48e63
Improve bitrate
Xpl0itU Dec 28, 2022
e7dba5b
Bump bitrate to 8M
Xpl0itU Dec 28, 2022
40e8cf4
Cleanup
Xpl0itU Dec 28, 2022
7c2c6db
Improve speed and audio quality
Xpl0itU Dec 28, 2022
4d16193
Updated FFMPEG code
OpenSourceSimon Feb 2, 2023
4eeacc9
Merge branch 'develop' into develop
OpenSourceSimon Feb 3, 2023
3016ae8
Added a lot of code, needs to be optimized. Watermark almost done!
OpenSourceSimon Feb 25, 2023
3abf962
Remove fps from config
OpenSourceSimon Apr 4, 2023
16dfda6
Fix watermark and add transparent mode
OpenSourceSimon Apr 8, 2023
fcb55e0
Merge pull request #1409 from Xpl0itU/develop
OpenSourceSimon Apr 8, 2023
d959bf2
Fix TikTok error message
OpenSourceSimon Apr 8, 2023
0cc4851
Fix login
OpenSourceSimon Apr 8, 2023
cfb0e5f
Fix comma in TikTok.py
OpenSourceSimon Apr 9, 2023
16f6a12
Update Discord link
OpenSourceSimon Apr 9, 2023
39bf746
Close interest popup from Reddit
OpenSourceSimon Apr 10, 2023
792afa7
Optimize readability
OpenSourceSimon Apr 10, 2023
5b8964d
Merge branch 'master' into develop
OpenSourceSimon Apr 10, 2023
8b10343
Reformat
OpenSourceSimon Apr 10, 2023
f21727f
Updated TikTok.py
brian926 Apr 11, 2023
d8a8001
Changed url to resolve conflict
brian926 Apr 11, 2023
d3beec0
Merge branch 'develop' into master
OpenSourceSimon Apr 11, 2023
6cdc250
Merge pull request #1558 from brian926/master
OpenSourceSimon Apr 11, 2023
b0a8d0d
Add FFmpeg installer
OpenSourceSimon Apr 11, 2023
17a8175
Fix Spacy command
OpenSourceSimon Apr 11, 2023
9f61847
Typo...
OpenSourceSimon Apr 11, 2023
66baed9
Use develop branch with Dependabot
OpenSourceSimon Apr 12, 2023
88120c6
Remove unnecessary import
OpenSourceSimon Apr 12, 2023
4b4e268
Little changes
OpenSourceSimon Apr 12, 2023
d6c9daf
Fix os import
OpenSourceSimon Apr 12, 2023
785f6db
edit version number
OpenSourceSimon Apr 13, 2023
8668f2d
Fix too long path
OpenSourceSimon Apr 13, 2023
68c523f
This bot will only work with Python 3.10, due to some modules not sup…
OpenSourceSimon Apr 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
target-branch: "develop"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ I have tried to simplify the code so anyone can read it and start contributing a

Please read our [contributing guidelines](CONTRIBUTING.md) for more detailed information.

### For any questions or support join the [Discord](https://discord.gg/codingwithlewis) server
### For any questions or support join the [Discord](https://discord.gg/Vkanmh6C8V) server

## Developers and maintainers.

Expand Down
20 changes: 10 additions & 10 deletions TTS/TikTok.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"de_001", # German - Female
"de_002", # German - Male
"es_002", # Spanish - Male
"it_male_m18" # Italian - Male
"it_male_m18", # Italian - Male
# South american voices
"es_mx_002", # Spanish MX - Male
"br_001", # Portuguese BR - Female 1
Expand Down Expand Up @@ -78,17 +78,18 @@

class TikTok:
"""TikTok Text-to-Speech Wrapper"""

def __init__(self):
if not settings.config['settings']['tts']['tiktok_sessionid']:
raise TikTokTTSException(5)
headers = {
"User-Agent": "com.zhiliaoapp.musically/2022600030 (Linux; U; Android 7.1.2; es_ES; SM-G988N; "
"Build/NRD90M;tt-ok/3.12.13.1)",
"Cookie": f"sessionid={settings.config['settings']['tts']['tiktok_sessionid']}",
}

self.URI_BASE = "https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
self.max_chars = 300

self.URI_BASE = (
"https://api16-normal-c-useast1a.tiktokv.com/media/api/text/speech/invoke/"
)
self.max_chars = 200

self._session = requests.Session()
# set the headers to the session, so we don't have to do it for every request
Expand All @@ -113,7 +114,9 @@ def run(self, text: str, filepath: str, random_voice: bool = False):
try:
raw_voices = data["data"]["v_str"]
except:
print("The TikTok TTS returned an invalid response. Please try again later, and report this bug.")
print(
"The TikTok TTS returned an invalid response. Please try again later, and report this bug."
)
raise TikTokTTSException(0, "Invalid response")
decoded_voices = base64.b64decode(raw_voices)

Expand Down Expand Up @@ -160,8 +163,5 @@ def __str__(self) -> str:

if self._code == 4:
return f"Code: {self._code}, reason: the speaker doesn't exist, message: {self._message}"

if self._code == 5:
return f"You have to add session id in config to use titok TTS"

return f"Code: {self._message}, reason: unknown, message: {self._message}"
27 changes: 13 additions & 14 deletions TTS/engine_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from utils.console import print_step, print_substep
from utils.voice import sanitize_text

DEFAULT_MAX_LENGTH: int = 50 # video length variable
DEFAULT_MAX_LENGTH: int = 50 # video length variable


class TTSEngine:
Expand Down Expand Up @@ -51,17 +51,18 @@ def __init__(
self.length = 0
self.last_clip_length = last_clip_length

def add_periods(self): # adds periods to the end of paragraphs (where people often forget to put them) so tts doesn't blend sentences
def add_periods(
self,
): # adds periods to the end of paragraphs (where people often forget to put them) so tts doesn't blend sentences
for comment in self.reddit_object["comments"]:
comment["comment_body"] = comment["comment_body"].replace('\n', '. ')
if comment["comment_body"][-1] != '.':
comment["comment_body"] += '.'
comment["comment_body"] = comment["comment_body"].replace("\n", ". ")
if comment["comment_body"][-1] != ".":
comment["comment_body"] += "."

def run(self) -> Tuple[int, int]:

Path(self.path).mkdir(parents=True, exist_ok=True)
print_step("Saving Text to MP3 files...")

self.add_periods()
self.call_tts("title", process_text(self.reddit_object["thread_title"]))
# processed_text = ##self.reddit_object["thread_post"] != ""
Expand All @@ -76,12 +77,10 @@ def run(self) -> Tuple[int, int]:
"postaudio", process_text(self.reddit_object["thread_post"])
)
elif settings.config["settings"]["storymodemethod"] == 1:

for idx, text in track(enumerate(self.reddit_object["thread_post"])):
self.call_tts(f"postaudio-{idx}", process_text(text))

else:

for idx, comment in track(
enumerate(self.reddit_object["comments"]), "Saving..."
):
Expand Down Expand Up @@ -143,10 +142,10 @@ def split_post(self, text: str, idx):

def call_tts(self, filename: str, text: str):
self.tts_module.run(text, filepath=f"{self.path}/{filename}.mp3")
# try:
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
# except (MutagenError, HeaderNotFoundError):
# self.length += sox.file_info.duration(f"{self.path}/{filename}.mp3")
# try:
# self.length += MP3(f"{self.path}/{filename}.mp3").info.length
# except (MutagenError, HeaderNotFoundError):
# self.length += sox.file_info.duration(f"{self.path}/{filename}.mp3")
try:
clip = AudioFileClip(f"{self.path}/{filename}.mp3")
self.last_clip_length = clip.duration
Expand All @@ -168,7 +167,7 @@ def create_silence_mp3(self):
)


def process_text(text: str , clean : bool = True):
def process_text(text: str, clean: bool = True):
lang = settings.config["reddit"]["thread"]["post_lang"]
new_text = sanitize_text(text) if clean else text
if lang:
Expand Down
Binary file removed examples/final_video.mp4
Binary file not shown.
33 changes: 24 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from subprocess import Popen

from prawcore import ResponseException

from utils.console import print_substep
from reddit.subreddit import get_subreddit_threads
from utils import settings
from utils.cleanup import cleanup
Expand All @@ -22,8 +22,9 @@
from video_creation.final_video import make_final_video
from video_creation.screenshot_downloader import get_screenshots_of_reddit_posts
from video_creation.voices import save_text_to_mp3
from utils.ffmpeg_install import ffmpeg_install

__VERSION__ = "3.0.1"
__VERSION__ = "3.1"

print(
"""
Expand All @@ -43,7 +44,7 @@


def main(POST_ID=None) -> None:
global redditid ,reddit_object
global redditid, reddit_object
reddit_object = get_subreddit_threads(POST_ID)
redditid = id(reddit_object)
length, number_of_comments = save_text_to_mp3(reddit_object)
Expand Down Expand Up @@ -78,14 +79,25 @@ def shutdown():


if __name__ == "__main__":
assert sys.version_info >= (3, 9), "Python 3.10 or higher is required"
if sys.version_info.major != 3 or sys.version_info.minor != 10:
print("Hey! Congratulations, you've made it so far (which is pretty rare with no Python 3.10). Unfortunately, this program only works on Python 3.10. Please install Python 3.10 and try again.")
ffmpeg_install() # install ffmpeg if not installed
directory = Path().absolute()
OpenSourceSimon marked this conversation as resolved.
Show resolved Hide resolved
config = settings.check_toml(
f"{directory}/utils/.config.template.toml", "config.toml"
)
config is False and exit()
if (
not settings.config["settings"]["tts"]["tiktok_sessionid"]
or settings.config["settings"]["tts"]["tiktok_sessionid"] == ""
) and config["settings"]["tts"]["voice_choice"] == "tiktok":
print_substep(
"TikTok voice requires a sessionid! Check our documentation on how to obtain one.",
"bold red",
)
exit()
OpenSourceSimon marked this conversation as resolved.
Show resolved Hide resolved
try:
if config["reddit"]["thread"]["post_id"] :
if config["reddit"]["thread"]["post_id"]:
for index, post_id in enumerate(
config["reddit"]["thread"]["post_id"].split("+")
):
Expand All @@ -108,8 +120,11 @@ def shutdown():

shutdown()
except Exception as err:
print_step(f'Sorry, something went wrong with this version! Try again, and feel free to report this issue at GitHub or the Discord community.\n'
f'Version: {__VERSION__},Story mode: {str(config["settings"]["storymode"])}, Story mode method: {str(config["settings"]["storymodemethod"])},\n'
f'Postid : {str(config["settings"])},allownsfw :{config["settings"]["allow_nsfw"]},is_nsfw : {str(reddit_object["is_nsfw"])}'
)
config["settings"]["tts"]["tiktok_sessionid"] = "REDACTED"
print_step(
f"Sorry, something went wrong with this version! Try again, and feel free to report this issue at GitHub or the Discord community.\n"
f"Version: {__VERSION__} \n"
f"Error: {err} \n"
f'Config: {config["settings"]}'
)
raise err
30 changes: 20 additions & 10 deletions reddit/subreddit.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,38 @@ def get_subreddit_threads(POST_ID: str):
settings.config["reddit"]["thread"]["post_id"]
and len(str(settings.config["reddit"]["thread"]["post_id"]).split("+")) == 1
):
submission = reddit.submission(id=settings.config["reddit"]["thread"]["post_id"])
elif settings.config["ai"]["ai_similarity_enabled"]: # ai sorting based on comparison
submission = reddit.submission(
id=settings.config["reddit"]["thread"]["post_id"]
)
elif settings.config["ai"][
"ai_similarity_enabled"
]: # ai sorting based on comparison
threads = subreddit.hot(limit=50)
keywords = settings.config["ai"]["ai_similarity_keywords"].split(',')
keywords = settings.config["ai"]["ai_similarity_keywords"].split(",")
keywords = [keyword.strip() for keyword in keywords]
# Reformat the keywords for printing
keywords_print = ", ".join(keywords)
print(f'Sorting threads by similarity to the given keywords: {keywords_print}')
print(f"Sorting threads by similarity to the given keywords: {keywords_print}")
threads, similarity_scores = sort_by_similarity(threads, keywords)
submission, similarity_score = get_subreddit_undone(threads, subreddit, similarity_scores=similarity_scores)
submission, similarity_score = get_subreddit_undone(
threads, subreddit, similarity_scores=similarity_scores
)
else:
threads = subreddit.hot(limit=25)
submission = get_subreddit_undone(threads, subreddit)

if submission is None:
return get_subreddit_threads(POST_ID) # submission already done. rerun
return get_subreddit_threads(POST_ID) # submission already done. rerun

if settings.config["settings"]["storymode"]:
if not submission.selftext:
print_substep("You are trying to use story mode on post with no post text")
exit()
else:
# Check for the length of the post text
if len(submission.selftext) > (settings.config["settings"]["storymode_max_length"] or 2000):
if len(submission.selftext) > (
settings.config["settings"]["storymode_max_length"] or 2000
):
print_substep(
f"Post is too long ({len(submission.selftext)}), try with a different post. ({settings.config['settings']['storymode_max_length']} character limit)"
)
Expand All @@ -124,12 +132,15 @@ def get_subreddit_threads(POST_ID: str):
threadurl = f"https://reddit.com{submission.permalink}"

print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green")
print_substep(f"Thread url is : {threadurl } :thumbsup:", style="bold green")
print_substep(f"Thread url is: {threadurl} :thumbsup:", style="bold green")
print_substep(f"Thread has {upvotes} upvotes", style="bold blue")
print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue")
print_substep(f"Thread has {num_comments} comments", style="bold blue")
if similarity_score:
print_substep(f"Thread has a similarity score up to {round(similarity_score * 100)}%", style="bold blue")
print_substep(
f"Thread has a similarity score up to {round(similarity_score * 100)}%",
style="bold blue",
)

content["thread_url"] = threadurl
content["thread_title"] = submission.title
Expand Down Expand Up @@ -158,7 +169,6 @@ def get_subreddit_threads(POST_ID: str):
if len(top_level_comment.body) >= int(
settings.config["reddit"]["thread"]["min_comment_length"]
):

if (
top_level_comment.author is not None
and sanitize_text(top_level_comment.body) is not None
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ clean-text==0.6.0
unidecode==1.3.2
spacy==3.4.1
torch==1.12.1
transformers==4.25.1
transformers==4.25.1
ffmpeg-python==0.2.0
3 changes: 1 addition & 2 deletions utils/.config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ ai_similarity_keywords = {optional = true, type="str", example= 'Elon Musk, Twit

[settings]
allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true, false, ], explanation = "Whether to allow NSFW content, True or False" }
theme = { optional = false, default = "dark", example = "light", options = ["dark", "light", ], explanation = "Sets the Reddit theme, either LIGHT or DARK" }
theme = { optional = false, default = "dark", example = "light", options = ["dark", "light", "transparent", ], explanation = "Sets the Reddit theme, either LIGHT or DARK. For story mode you can also use a transparent background." }
times_to_run = { optional = false, default = 1, example = 2, explanation = "Used if you want to run multiple times. Set to an int e.g. 4 or 29 or 1", type = "int", nmin = 1, oob_error = "It's very hard to run something less than once." }
opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets the opacity of the comments when overlayed over the background", type = "float", nmin = 0, nmax = 1, oob_error = "The opacity HAS to be between 0 and 1", input_error = "The opacity HAS to be a decimal number between 0 and 1" }
transition = { optional = true, default = 0.2, example = 0.2, explanation = "Sets the transition time (in seconds) between the comments. Set to 0 if you want to disable it.", type = "float", nmin = 0, nmax = 2, oob_error = "The transition HAS to be between 0 and 2", input_error = "The opacity HAS to be a decimal number between 0 and 2" }
storymode = { optional = true, type = "bool", default = false, example = false, options = [true, false,], explanation = "Only read out title and post content, great for subreddits with stories" }
storymodemethod= { optional = true, default = 1, example = 1, explanation = "Style that's used for the storymode. Set to 0 for single picture display in whole video, set to 1 for fancy looking video ", type = "int", nmin = 0, oob_error = "It's very hard to run something less than once.", options = [0, 1] }
storymode_max_length = { optional = true, default = 1000, example = 1000, explanation = "Max length of the storymode video in characters. 200 characters are approximately 50 seconds.", type = "int", nmin = 1, oob_error = "It's very hard to make a video under a second." }
fps = { optional = false, default = 30, example = 30, explanation = "Sets the FPS of the video, 30 is default for best performance. 60 FPS is smoother.", type = "int", nmin = 1, nmax = 60, oob_error = "The FPS HAS to be between 1 and 60" }
resolution_w = { optional = false, default = 1080, example = 1440, explantation = "Sets the width in pixels of the final video" }
resolution_h = { optional = false, default = 1920, example = 2560, explantation = "Sets the height in pixels of the final video" }

Expand Down
42 changes: 29 additions & 13 deletions utils/ai_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,59 @@

# Mean Pooling - Take attention mask into account for correct averaging
def mean_pooling(model_output, attention_mask):
token_embeddings = model_output[0] # First element of model_output contains all token embeddings
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)
token_embeddings = model_output[
0
OpenSourceSimon marked this conversation as resolved.
Show resolved Hide resolved
] # First element of model_output contains all token embeddings
input_mask_expanded = (
attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
)
return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(
input_mask_expanded.sum(1), min=1e-9
)


# This function sort the given threads based on their total similarity with the given keywords
def sort_by_similarity(thread_objects, keywords):
# Initialize tokenizer + model.
tokenizer = AutoTokenizer.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')
model = AutoModel.from_pretrained('sentence-transformers/all-MiniLM-L6-v2')
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
model = AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")

# Transform the generator to a list of Submission Objects, so we can sort later based on context similarity to
# keywords
thread_objects = list(thread_objects)

threads_sentences = []
for i, thread in enumerate(thread_objects):
threads_sentences.append(' '.join([thread.title, thread.selftext]))
threads_sentences.append(" ".join([thread.title, thread.selftext]))

# Threads inference
encoded_threads = tokenizer(threads_sentences, padding=True, truncation=True, return_tensors='pt')
encoded_threads = tokenizer(
threads_sentences, padding=True, truncation=True, return_tensors="pt"
)
with torch.no_grad():
threads_embeddings = model(**encoded_threads)
threads_embeddings = mean_pooling(threads_embeddings, encoded_threads['attention_mask'])
threads_embeddings = mean_pooling(
threads_embeddings, encoded_threads["attention_mask"]
)

# Keywords inference
encoded_keywords = tokenizer(keywords, padding=True, truncation=True, return_tensors='pt')
encoded_keywords = tokenizer(
keywords, padding=True, truncation=True, return_tensors="pt"
)
with torch.no_grad():
keywords_embeddings = model(**encoded_keywords)
keywords_embeddings = mean_pooling(keywords_embeddings, encoded_keywords['attention_mask'])
keywords_embeddings = mean_pooling(
keywords_embeddings, encoded_keywords["attention_mask"]
)

# Compare every keyword w/ every thread embedding
threads_embeddings_tensor = torch.tensor(threads_embeddings)
total_scores = torch.zeros(threads_embeddings_tensor.shape[0])
cosine_similarity = torch.nn.CosineSimilarity()
for keyword_embedding in keywords_embeddings:
keyword_embedding = torch.tensor(keyword_embedding).repeat(threads_embeddings_tensor.shape[0], 1)
keyword_embedding = torch.tensor(keyword_embedding).repeat(
threads_embeddings_tensor.shape[0], 1
)
similarity = cosine_similarity(keyword_embedding, threads_embeddings_tensor)
total_scores += similarity

Expand All @@ -51,8 +67,8 @@ def sort_by_similarity(thread_objects, keywords):

thread_objects = np.array(thread_objects)[indices.numpy()].tolist()

#print('Similarity Thread Ranking')
#for i, thread in enumerate(thread_objects):
# print('Similarity Thread Ranking')
# for i, thread in enumerate(thread_objects):
# print(f'{i}) {threads_sentences[i]} score {similarity_scores[i]}')

return thread_objects, similarity_scores
Loading