Skip to content

Commit adbb0cd

Browse files
committed
testing tweaks
1 parent 3a4ba67 commit adbb0cd

File tree

6 files changed

+291
-41
lines changed

6 files changed

+291
-41
lines changed

.vscode/launch.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Run current Kometa file",
9+
"type": "debugpy",
10+
"justMyCode": false,
11+
"request": "launch",
12+
"program": "${file}",
13+
"console": "integratedTerminal",
14+
"cwd": "${workspaceFolder}/Kometa",
15+
"purpose": [
16+
"debug-in-terminal"
17+
],
18+
"args": [ ]
19+
},
20+
{
21+
"name": "Run current Plex file",
22+
"type": "debugpy",
23+
"justMyCode": false,
24+
"request": "launch",
25+
"program": "${file}",
26+
"console": "integratedTerminal",
27+
"cwd": "${workspaceFolder}/Plex",
28+
"purpose": [
29+
"debug-in-terminal"
30+
],
31+
"args": [ ]
32+
},
33+
{
34+
"name": "Python Debugger: Current File",
35+
"type": "debugpy",
36+
"request": "launch",
37+
"program": "${file}",
38+
"console": "integratedTerminal"
39+
}
40+
]
41+
}

Kometa/captions.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from pytube import Channel
2+
import os
3+
4+
def download_subtitles_from_channel(channel_url, output_path='.'):
5+
"""
6+
Downloads subtitles for all videos from a YouTube channel.
7+
8+
Args:
9+
channel_url (str): The URL of the YouTube channel.
10+
output_path (str): The directory to save the subtitles.
11+
"""
12+
try:
13+
c = Channel(channel_url)
14+
print(f"Processing channel: {c.channel_name}")
15+
16+
# Create output directory if it doesn't exist
17+
if not os.path.exists(output_path):
18+
os.makedirs(output_path)
19+
print(f"Created output directory: {output_path}")
20+
21+
video_count = 0
22+
caption_count = 0
23+
24+
for video in c.videos:
25+
video_count += 1
26+
print(f"\nProcessing video {video_count}: {video.title}")
27+
28+
try:
29+
# Check if captions are available
30+
if video.captions:
31+
# You can specify the language code, e.g., 'en' for English
32+
# Use video.captions to see all available caption languages
33+
if 'en' in video.captions:
34+
caption = video.captions['en']
35+
print("Downloading English captions...")
36+
37+
# Generate a safe filename
38+
filename = f"{video.title}.en.srt"
39+
safe_filename = "".join(x for x in filename if x.isalnum() or x in "._- ").strip()
40+
file_path = os.path.join(output_path, safe_filename)
41+
42+
# Save the captions as an .srt file
43+
with open(file_path, 'w', encoding='utf-8') as f:
44+
f.write(caption.generate_srt_file())
45+
caption_count += 1
46+
print(f"✅ Downloaded and saved captions to: {file_path}")
47+
else:
48+
print("⚠️ English captions not found for this video. Skipping.")
49+
else:
50+
print("⚠️ No captions available for this video. Skipping.")
51+
52+
except Exception as e:
53+
print(f"An error occurred while processing video {video.title}: {e}")
54+
55+
print(f"\n--- Script finished ---")
56+
print(f"Successfully processed {len(c.videos)} videos.")
57+
print(f"Successfully downloaded captions for {caption_count} videos.")
58+
59+
except Exception as e:
60+
print(f"An error occurred with the channel URL: {e}")
61+
62+
# --- USAGE ---
63+
# Replace with the URL of the YouTube channel you want to download from
64+
CHANNEL_URL = 'https://www.youtube.com/@freecodecamp'
65+
OUTPUT_FOLDER = './youtube_captions'
66+
67+
download_subtitles_from_channel(CHANNEL_URL, OUTPUT_FOLDER)

Plex/config.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import os
2+
3+
import yaml
4+
5+
6+
class Config:
7+
"""
8+
A class to handle configuration settings loaded from a YAML file.
9+
"""
10+
_instance = None
11+
12+
def __new__(cls, config_path="config.yaml"):
13+
if cls._instance is None:
14+
cls._instance = super(Config, cls).__new__(cls)
15+
cls._instance._initialize(config_path)
16+
return cls._instance
17+
18+
def _initialize(self, config_path):
19+
if not os.path.exists(config_path):
20+
raise FileNotFoundError(f"Configuration file not found at {config_path}")
21+
22+
try:
23+
with open(config_path, 'r') as file:
24+
self._settings = yaml.safe_load(file)
25+
except yaml.YAMLError as e:
26+
raise ValueError(f"Error parsing YAML file: {e}")
27+
28+
def __getattr__(self, name):
29+
"""
30+
Allows accessing settings like attributes (e.g., config.database.host).
31+
"""
32+
if name in self._settings:
33+
value = self._settings[name]
34+
if isinstance(value, dict):
35+
# Recursively wrap nested dictionaries
36+
return _DictWrapper(value)
37+
return value
38+
39+
# Fallback to the default __getattr__ behavior
40+
return super().__getattr__(name)
41+
42+
def get(self, key, default=None):
43+
"""
44+
Provides a safe way to get a value with an optional default,
45+
similar to dictionary's .get() method.
46+
"""
47+
keys = key.split('.')
48+
current_dict = self._settings
49+
for k in keys:
50+
if isinstance(current_dict, dict) and k in current_dict:
51+
current_dict = current_dict[k]
52+
else:
53+
return default
54+
return current_dict
55+
56+
def get_int(self, key, default=0):
57+
"""
58+
Provides a safe way to get a value with an optional default,
59+
similar to dictionary's .get() method.
60+
"""
61+
keys = key.split('.')
62+
current_dict = self._settings
63+
for k in keys:
64+
if isinstance(current_dict, dict) and k in current_dict:
65+
current_dict = current_dict[k]
66+
else:
67+
return default
68+
return current_dict
69+
70+
def get_bool(self, key, default=False):
71+
"""
72+
Provides a safe way to get a value with an optional default,
73+
similar to dictionary's .get() method.
74+
"""
75+
keys = key.split('.')
76+
current_dict = self._settings
77+
for k in keys:
78+
if isinstance(current_dict, dict) and k in current_dict:
79+
current_dict = current_dict[k]
80+
else:
81+
return default
82+
if type(current_dict) is str:
83+
current_dict = eval(current_dict)
84+
return bool(current_dict)
85+
86+
87+
class _DictWrapper:
88+
"""
89+
Helper class to enable attribute-style access for nested dictionaries.
90+
"""
91+
def __init__(self, data):
92+
self._data = data
93+
94+
def __getattr__(self, name):
95+
if name in self._data:
96+
value = self._data[name]
97+
if isinstance(value, dict):
98+
return _DictWrapper(value)
99+
return value
100+
raise AttributeError(f"'{self.__class__.__name__}' has no attribute '{name}'")
101+
102+
# Example Usage:
103+
if __name__ == "__main__":
104+
# Create a dummy config.yaml file for the example
105+
sample_config_content = """
106+
tvdb:
107+
apikey: "bed9264b-82e9-486b-af01-1bb201bcb595" # Enter TMDb API Key (REQUIRED)
108+
109+
omdb:
110+
apikey: "9e62df51" # Enter OMDb API Key (Optional)
111+
"""
112+
with open("config.yaml", "w") as f:
113+
f.write(sample_config_content)
114+
115+
try:
116+
config = Config()
117+
118+
print("--- Attribute Access ---")
119+
print(f"tvdb key: {config.tvdb.apikey}")
120+
print(f"omdb key: {config.omdb.apikey}")
121+
122+
print("\n--- 'get' Method Access ---")
123+
print(f"tvdb key: {config.get('tvdb.apikey')}")
124+
print(f"Default Value Test: {config.get('omdb.sproing', 'default_value')}")
125+
126+
except (FileNotFoundError, ValueError) as e:
127+
print(f"An error occurred: {e}")
128+
finally:
129+
# Clean up the dummy file
130+
if os.path.exists("config.yaml"):
131+
os.remove("config.yaml")

0 commit comments

Comments
 (0)