Compare commits
2
Commits
2de2c461e2
...
afe6490d20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afe6490d20 | ||
|
|
8bf85fea2e |
No files matched your search
@@ -6,10 +6,10 @@ import time
|
||||
|
||||
from package.database import Database
|
||||
|
||||
import uploader
|
||||
from uploader import CategoryId
|
||||
import package.youtube.uploader as uploader
|
||||
from package.youtube.uploader import CategoryId
|
||||
|
||||
import youtube_hashtags
|
||||
import package.youtube.hashtags as hashtags
|
||||
import package.transcribe_video as transcribe_video
|
||||
|
||||
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
|
||||
@@ -43,7 +43,7 @@ def top_hashtags(id: str):
|
||||
print(f"Transcribe file not found {file_srt}")
|
||||
return []
|
||||
|
||||
tags = youtube_hashtags.get_top_hashtags(file_srt)
|
||||
tags = hashtags.get_top_hashtags(file_srt)
|
||||
return tags
|
||||
|
||||
def download():
|
||||
|
||||
@@ -5,8 +5,8 @@ from datetime import datetime
|
||||
|
||||
from package.database import Database
|
||||
|
||||
import uploader
|
||||
from uploader import CategoryId
|
||||
import package.youtube.uploader as uploader
|
||||
from package.youtube.uploader import CategoryId
|
||||
|
||||
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
|
||||
DB = Database("clips")
|
||||
@@ -49,9 +49,9 @@ def top_hashtags(slug: str):
|
||||
print(f"Transcribe file not found {file_srt}")
|
||||
return []
|
||||
|
||||
import youtube_hashtags
|
||||
import package.youtube.hashtags as hashtags
|
||||
|
||||
return youtube_hashtags.get_top_hashtags(file_srt)
|
||||
return hashtags.get_top_hashtags(file_srt)
|
||||
|
||||
|
||||
def download_clips():
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
|
||||
# 3. Expose Database Tools (Added)
|
||||
# Replace 'DatabaseClient' and 'connect_to_db' with your actual class/function names
|
||||
from .build_videos import main as build_videos
|
||||
from .build_videos import build_videos
|
||||
from .config import CHANNEL_NAME, DB
|
||||
from .database import Database
|
||||
from .transcribe_video import transcribe_to_srt
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import youtube_short
|
||||
import package.youtube.shorts as yt_shorts
|
||||
import package.transcribe_video as transcribe_video
|
||||
import package.twitch.chat_vod as chat_vod
|
||||
from package.config import DB as DB
|
||||
@@ -108,16 +108,16 @@ def build_shorts():
|
||||
print(f"🚀 Processing: Clip to Youtube Short {title}")
|
||||
print("====================================================")
|
||||
|
||||
youtube_short.fit_to_9_16_letterbox(target_dir, top_txt, f"Clipped By: {creator_name}.")
|
||||
yt_shorts.fit_to_9_16_letterbox(target_dir, top_txt, f"Clipped By: {creator_name}.")
|
||||
|
||||
print("====================================================")
|
||||
print(f"✅ Processing: Clip to Youtube Short Done.")
|
||||
print("====================================================")
|
||||
|
||||
def main():
|
||||
def build_videos():
|
||||
build_shorts()
|
||||
build_transcribe()
|
||||
build_chat_video()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
build_videos()
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
# Expose components from individual modules using relative imports
|
||||
from .chat_vod import combine_twitch_vod_and_chat
|
||||
from .download_thumbnails import main as download_thumbnails
|
||||
from .download_videos import main as download_videos
|
||||
from .video_info import main as video_info
|
||||
from .download_thumbnails import download_thumbnails
|
||||
from .download_videos import download_videos
|
||||
from .video_info import video_info
|
||||
|
||||
# Explicitly define what is exposed when importing the subpackage
|
||||
__all__ = [
|
||||
|
||||
@@ -4,6 +4,9 @@ import cv2
|
||||
import package.utils as utils
|
||||
|
||||
def get_video_height(video_path: str) -> int:
|
||||
"""
|
||||
Gets the videos height
|
||||
"""
|
||||
# Open the video file
|
||||
video = cv2.VideoCapture(video_path)
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def save_image(url: str, filename: str, stream: bool = True):
|
||||
except Exception as e:
|
||||
print(f"❌ An error occurred during file writing: {e}")
|
||||
|
||||
async def main():
|
||||
async def download_thumbnails():
|
||||
# Initialize connection & automatically authorize the App token
|
||||
await config.get_twitch()
|
||||
twitch = config.TWITCH # Twitch(APP_ID, APP_SECRET)
|
||||
@@ -141,4 +141,4 @@ async def main():
|
||||
await download_clip_thumbnail(id, thumbnail_url)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
asyncio.run(download_thumbnails())
|
||||
@@ -14,7 +14,7 @@ def write_csv(data, file_name):
|
||||
# Write all rows at once
|
||||
writer.writerows(data)
|
||||
|
||||
def download():
|
||||
def download_videos():
|
||||
"""Find all undownloaded videos and clips, and download them safely."""
|
||||
undownloaded = DB.get_undownloaded()
|
||||
|
||||
@@ -96,8 +96,5 @@ def download():
|
||||
|
||||
print("Finished Downloading Pipeline.")
|
||||
|
||||
def main():
|
||||
download()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
download_videos()
|
||||
@@ -40,7 +40,7 @@ async def get_game_name_by_id(game_id: str) -> str:
|
||||
pass
|
||||
return "Unknown Game"
|
||||
|
||||
async def get_vod_game_name(vod_id: str):
|
||||
async def get_vod_game_name(vod_id: str) -> tuple:
|
||||
"""Asynchronously query Twitch GQL endpoint for VOD game metadata."""
|
||||
game_id = "0"
|
||||
game_name = "Unknown Game"
|
||||
@@ -83,7 +83,7 @@ async def get_vod_game_name(vod_id: str):
|
||||
|
||||
return game_id, game_name
|
||||
|
||||
async def get_streamer_vods():
|
||||
async def get_streamer_vods() -> list:
|
||||
await config.get_twitch()
|
||||
|
||||
print(f"Starting VOD extraction for {config.USER.display_name}...")
|
||||
@@ -121,7 +121,7 @@ async def get_streamer_vods():
|
||||
print(f"\nFinished extracting VODs. Total gathered: {len(all_vods)}")
|
||||
return all_vods
|
||||
|
||||
async def get_streamer_clips():
|
||||
async def get_streamer_clips() -> list:
|
||||
await config.get_twitch()
|
||||
|
||||
print(f"Starting clip extraction for {config.USER.display_name}...")
|
||||
@@ -151,7 +151,7 @@ async def get_streamer_clips():
|
||||
print(f"\nFinished extracting clips. Total gathered: {len(all_clips)}")
|
||||
return all_clips
|
||||
|
||||
async def main():
|
||||
async def video_info():
|
||||
print("--- Script Started ---")
|
||||
|
||||
vods_list = await get_streamer_vods()
|
||||
@@ -179,4 +179,4 @@ async def main():
|
||||
print(f"Recent VOD: '{clips_list[0]['title']}'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(video_info())
|
||||
@@ -0,0 +1,15 @@
|
||||
# package/youtube/__init__.py
|
||||
|
||||
# Expose components from individual modules using relative imports
|
||||
from .find_tags import find_tags
|
||||
from .hashtags import get_top_nouns
|
||||
from .shorts import fit_to_9_16_letterbox
|
||||
from .uploader import upload_video, CategoryId
|
||||
|
||||
# Explicitly define what is exposed when importing the subpackage
|
||||
__all__ = [
|
||||
"find_tags",
|
||||
"get_top_nouns",
|
||||
"fit_to_9_16_letterbox",
|
||||
"upload_video",
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google.auth.transport.requests import Request
|
||||
|
||||
CREDENTIALS = None
|
||||
|
||||
def load_credentials() -> Credentials:
|
||||
"""Load credentials from secrets.json"""
|
||||
try:
|
||||
with open('secrets.json', 'r') as f:
|
||||
creds_data = json.load(f)
|
||||
|
||||
credentials = Credentials(
|
||||
token=creds_data['token'],
|
||||
refresh_token=creds_data['refresh_token'],
|
||||
token_uri=creds_data['token_uri'],
|
||||
client_id=creds_data['client_id'],
|
||||
client_secret=creds_data['client_secret'],
|
||||
scopes=creds_data['scopes']
|
||||
)
|
||||
|
||||
# Refresh token if expired
|
||||
if credentials.expired:
|
||||
credentials.refresh(Request())
|
||||
|
||||
return credentials
|
||||
except FileNotFoundError:
|
||||
print("Error: secrets.json not found! Please run setup.py first.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error loading credentials: {str(e)}")
|
||||
return None
|
||||
|
||||
CREDENTIALS = load_credentials()
|
||||
@@ -1,37 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google.auth.transport.requests import Request
|
||||
from typing import Any
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
def load_credentials():
|
||||
"""Load credentials from secrets.json (Reused from your previous flow)"""
|
||||
try:
|
||||
with open('secrets.json', 'r') as f:
|
||||
creds_data = json.load(f)
|
||||
import package.youtube.config as config
|
||||
|
||||
credentials = Credentials(
|
||||
token=creds_data['token'],
|
||||
refresh_token=creds_data['refresh_token'],
|
||||
token_uri=creds_data['token_uri'],
|
||||
client_id=creds_data['client_id'],
|
||||
client_secret=creds_data['client_secret'],
|
||||
scopes=creds_data['scopes']
|
||||
)
|
||||
|
||||
if credentials.expired:
|
||||
credentials.refresh(Request())
|
||||
|
||||
return credentials
|
||||
except FileNotFoundError:
|
||||
print("Error: secrets.json not found!")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error loading credentials: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_my_uploads_playlist_id(youtube):
|
||||
def get_my_uploads_playlist_id(youtube: build) -> Any:
|
||||
"""Retrieves the system upload playlist ID for the authenticated user's channel."""
|
||||
try:
|
||||
# mine=True automatically references the authorized account
|
||||
@@ -47,7 +22,7 @@ def get_my_uploads_playlist_id(youtube):
|
||||
print(f"API Error retrieving channel details: {str(e)}")
|
||||
return None
|
||||
|
||||
def scan_channel_videos_for_tag(youtube, uploads_playlist_id: str, target_tag: str):
|
||||
def scan_channel_videos_for_tag(youtube: build, uploads_playlist_id: str, target_tag: str) -> list:
|
||||
"""
|
||||
Loops through all channel video uploads and filters those possessing the target tag.
|
||||
"""
|
||||
@@ -120,12 +95,12 @@ def scan_channel_videos_for_tag(youtube, uploads_playlist_id: str, target_tag: s
|
||||
|
||||
return matched_videos
|
||||
|
||||
def main():
|
||||
credentials = load_credentials()
|
||||
if not credentials:
|
||||
def find_tags():
|
||||
|
||||
if not config.CREDENTIALS:
|
||||
return
|
||||
|
||||
youtube = build('youtube', 'v3', credentials=credentials)
|
||||
youtube = build('youtube', 'v3', credentials=config.CREDENTIALS)
|
||||
|
||||
# 1. Fetch your dynamic uploads playlist pointer
|
||||
uploads_id = get_my_uploads_playlist_id(youtube)
|
||||
@@ -144,4 +119,4 @@ def main():
|
||||
print(f"{index}. {item['title']} -> https://youtu.be{item['id']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
find_tags()
|
||||
@@ -14,7 +14,16 @@ nltk.download('punkt_tab', quiet=True)
|
||||
nltk.download('averaged_perceptron_tagger', quiet=True) # Required for POS tagging
|
||||
nltk.download('averaged_perceptron_tagger_eng', quiet=True)
|
||||
|
||||
def extract_text_from_srt(file_path: str):
|
||||
def extract_text_from_srt(file_path: str) -> str:
|
||||
"""
|
||||
Extracts the text from the srt file.
|
||||
|
||||
Args:
|
||||
file_path (str): is the path to the str file.
|
||||
|
||||
Returns:
|
||||
returns (str): returns the cleaned up text.
|
||||
"""
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
# Remove SRT timestamps and sequence numbers
|
||||
@@ -22,7 +31,16 @@ def extract_text_from_srt(file_path: str):
|
||||
clean_text = re.sub(r'\d+', '', clean_text)
|
||||
return clean_text
|
||||
|
||||
def extract_text_from_json(file_path: str):
|
||||
def extract_text_from_json(file_path: str) -> str:
|
||||
"""
|
||||
Extracts the text from the json file.
|
||||
|
||||
Args:
|
||||
file_path (str): is the path to the json file.
|
||||
|
||||
Returns:
|
||||
returns (str): returns the cleaned up text.
|
||||
"""
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
|
||||
@@ -34,7 +52,17 @@ def extract_text_from_json(file_path: str):
|
||||
# Return a single merged string of all chat text
|
||||
return " ".join(messages)
|
||||
|
||||
def get_top_nouns(file_path: str, top_n: int = 10):
|
||||
def get_top_nouns(file_path: str, top_n: int = 10) -> list:
|
||||
"""
|
||||
Gets the top number of nouns based on past (top_n) arg.
|
||||
|
||||
Args:
|
||||
file_path (str): is the path to the text file.
|
||||
top_n (int): is the number of nouns to return.
|
||||
|
||||
Returns:
|
||||
returns (list): returns a list of nouns.
|
||||
"""
|
||||
# 1. Extract raw text
|
||||
if file_path.endswith(".srt"):
|
||||
raw_text = extract_text_from_srt(file_path)
|
||||
@@ -126,7 +126,6 @@ def fit_to_9_16_letterbox(input_path: str, top_text: str = "TOP TEXT", bottom_te
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from google.auth.transport.requests import Request
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.http import MediaFileUpload
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
import package.youtube.config as config
|
||||
|
||||
class CategoryId(Enum):
|
||||
"""Official YouTube Category IDs for API Uploads."""
|
||||
|
||||
@@ -30,34 +29,7 @@ class CategoryId(Enum):
|
||||
SCIENCE_AND_TECHNOLOGY = "28"
|
||||
NONPROFITS_AND_ACTIVISM = "29"
|
||||
|
||||
def load_credentials():
|
||||
"""Load credentials from secrets.json"""
|
||||
try:
|
||||
with open('secrets.json', 'r') as f:
|
||||
creds_data = json.load(f)
|
||||
|
||||
credentials = Credentials(
|
||||
token=creds_data['token'],
|
||||
refresh_token=creds_data['refresh_token'],
|
||||
token_uri=creds_data['token_uri'],
|
||||
client_id=creds_data['client_id'],
|
||||
client_secret=creds_data['client_secret'],
|
||||
scopes=creds_data['scopes']
|
||||
)
|
||||
|
||||
# Refresh token if expired
|
||||
if credentials.expired:
|
||||
credentials.refresh(Request())
|
||||
|
||||
return credentials
|
||||
except FileNotFoundError:
|
||||
print("Error: secrets.json not found! Please run setup.py first.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error loading credentials: {str(e)}")
|
||||
return None
|
||||
|
||||
def upload_video(file_path: str, title: str, category: CategoryId, description: str = "", privacyStatus: str = 'private', tags: list = None, release_time: datetime = None):
|
||||
def upload_video(file_path: str, title: str, category: CategoryId, description: str = "", privacyStatus: str = 'private', tags: list = None, release_time: datetime = None) -> bool:
|
||||
"""
|
||||
Upload a video to YouTube
|
||||
|
||||
@@ -82,12 +54,11 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
||||
return False
|
||||
|
||||
# Load credentials
|
||||
credentials = load_credentials()
|
||||
if not credentials:
|
||||
if not config.CREDENTIALS:
|
||||
return False
|
||||
|
||||
# Create YouTube API client
|
||||
youtube = build('youtube', 'v3', credentials=credentials)
|
||||
youtube = build('youtube', 'v3', credentials=config.CREDENTIALS)
|
||||
|
||||
# Configure the status object dynamically
|
||||
status_body = {
|
||||
@@ -197,5 +168,6 @@ def main():
|
||||
tags=parsed_tags,
|
||||
release_time=release_datetime
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,43 +5,66 @@ anyio==4.15.1
|
||||
attrs==26.1.0
|
||||
av==18.1.0
|
||||
certifi==2026.7.22
|
||||
cffi==2.1.1
|
||||
charset-normalizer==3.5.1
|
||||
click==8.5.0
|
||||
cloudpickle==3.1.2
|
||||
cryptography==50.0.1
|
||||
ctranslate2==4.8.2
|
||||
decorator==5.3.1
|
||||
defusedxml==0.7.1
|
||||
enum-tools==0.13.0
|
||||
faster-whisper==1.2.1
|
||||
filelock==3.32.6
|
||||
flatbuffers==25.12.19
|
||||
frozenlist==1.8.0
|
||||
fsspec==2026.7.0
|
||||
google-api-core==2.36.0
|
||||
google-api-python-client==2.200.0
|
||||
google-auth==2.58.0
|
||||
google-auth-httplib2==0.4.2
|
||||
google-auth-oauthlib==1.4.1
|
||||
googleapis-common-protos==1.75.3
|
||||
h11==0.16.0
|
||||
hf-xet==1.6.0
|
||||
httpcore==1.0.9
|
||||
httplib2==0.32.0
|
||||
httpx==0.28.1
|
||||
huggingface_hub==1.31.0
|
||||
idna==3.19
|
||||
ImageIO==2.37.4
|
||||
imageio-ffmpeg==0.6.0
|
||||
joblib==1.6.0
|
||||
moviepy==2.2.1
|
||||
multidict==6.8.0
|
||||
nltk==3.10.3
|
||||
numpy==2.5.3
|
||||
oauthlib==3.3.1
|
||||
onnxruntime==1.30.0
|
||||
opencv-python==5.0.0.93
|
||||
opentelemetry-api==1.44.0
|
||||
packaging==26.3
|
||||
pillow==11.3.0
|
||||
proglog==0.1.12
|
||||
propcache==0.5.2
|
||||
proto-plus==1.28.4
|
||||
protobuf==7.36.1
|
||||
pyasn1==0.6.4
|
||||
pyasn1_modules==0.4.2
|
||||
pycparser==3.0
|
||||
Pygments==2.21.0
|
||||
pyparsing==3.3.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.2.3
|
||||
PyYAML==6.0.3
|
||||
regex==2026.9.10
|
||||
requests==2.34.2
|
||||
requests-oauthlib==2.0.0
|
||||
six==1.17.0
|
||||
tokenizers==0.23.2
|
||||
tqdm==4.70.1
|
||||
twitchAPI==4.5.0
|
||||
typing_extensions==4.16.0
|
||||
uritemplate==4.2.0
|
||||
urllib3==2.7.0
|
||||
yarl==1.24.5
|
||||
Reference in new issue
Block a user