Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ downloads/
temp/
.python-version
modules/
extensions/
extensions/
dist/
7 changes: 4 additions & 3 deletions modules/example/interface.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from utils.models import *
from utils.utils import create_temp_filename
from orpheusdl.utils.models import *
from orpheusdl.utils.utils import create_temp_filename


module_information = ModuleInformation( # Only service_name and module_supported_modes are mandatory
name = 'example',
service_name = 'Example',
module_supported_modes = ModuleModes.download | ModuleModes.lyrics | ModuleModes.covers | ModuleModes.credits,
flags = ModuleFlags.hidden,
Expand Down Expand Up @@ -183,4 +184,4 @@ def search(self, query_type: DownloadTypeEnum, query: str, track_info: TrackInfo
# get_track_credits, get_track_cover, get_track_lyrics in the case of other modules using this module just for those.
# therefore, it's recommended to choose something generic like 'data' rather than specifics like 'cover_info'
# or, you could use both, keeping a data field just in case track data is given, while keeping the specifics, but that's overcomplicated
) for i in results]
) for i in results]
4 changes: 2 additions & 2 deletions moduletesting.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3

import argparse, cProfile, pstats
from orpheus.core import Orpheus
from orpheusdl.core import Orpheus

def main():
parser = argparse.ArgumentParser(description='Orpheus Module Testing Tool')
Expand Down Expand Up @@ -42,4 +42,4 @@ def main():
main()
except KeyboardInterrupt:
print('\n\t^C pressed - abort')
exit()
exit()
File renamed without changes.
92 changes: 68 additions & 24 deletions orpheus/core.py → orpheusdl/core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import importlib, json, logging, os, pickle, requests, urllib3, base64, shutil
import importlib, json, logging, os, pickle, requests, urllib3, base64, shutil, pkgutil
from datetime import datetime

from orpheus.music_downloader import Downloader
from utils.models import *
from utils.utils import *
from utils.exceptions import *
from orpheusdl.music_downloader import Downloader
from orpheusdl.utils.models import *
from orpheusdl.utils.utils import *
from orpheusdl.utils.exceptions import *

from appdirs import AppDirs

os.environ['CURL_CA_BUNDLE'] = '' # Hack to disable SSL errors for requests module for easier debugging
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Make SSL warnings hidden
Expand All @@ -27,7 +29,13 @@ def true_current_utc_timestamp():

class Orpheus:
def __init__(self, private_mode=False):
self.extensions, self.extension_list, self.module_list, self.module_settings, self.module_netloc_constants, self.loaded_modules = {}, set(), set(), {}, {}, {}
self.extensions = {}
self.extension_list = set()
self.module_list = set()
self.module_packages = {}
self.module_settings = {}
self.module_netloc_constants = {}
self.loaded_modules = {}

self.default_global_settings = {
"general": {
Expand Down Expand Up @@ -87,18 +95,33 @@ def __init__(self, private_mode=False):
}
}

self.settings_location = 'config/settings.json'
self.session_storage_location = 'config/loginstorage.bin'
dirs = AppDirs('orpheusdl', 'orpheusdl')

if os.path.isfile('./config/settings.json'):
self.settings_location = './config/settings.json'
else:
os.makedirs(dirs.user_config_dir, exist_ok=True)
self.settings_location = os.path.join(dirs.user_config_dir, 'settings.json')
print(f'Using settings file at {self.settings_location}')

if os.path.isfile('./config/loginstorage.bin'):
self.session_storage_location = 'config/loginstorage.bin'
else:
os.makedirs(dirs.user_cache_dir, exist_ok=True)
self.session_storage_location = os.path.join(dirs.user_cache_dir, 'loginstorage.bin')

if not os.path.exists('config'): os.makedirs('config')
self.settings = json.loads(open(self.settings_location, 'r').read()) if os.path.exists(self.settings_location) else {}
try:
with open(self.settings_location, 'rb') as fp:
self.settings = json.load(fp)
except FileNotFoundError:
self.settings = {}

try:
if self.settings['global']['advanced']['debug_mode']: logging.basicConfig(level=logging.DEBUG)
except KeyError:
pass

if not os.path.exists('extensions'): os.makedirs('extensions')
os.makedirs('extensions', exist_ok=True)
for extension in os.listdir('extensions'): # Loading extensions
if os.path.isdir(f'extensions/{extension}') and os.path.exists(f'extensions/{extension}/interface.py'):
class_ = getattr(importlib.import_module(f'extensions.{extension}.interface'), 'OrpheusExtension', None)
Expand All @@ -109,21 +132,34 @@ def __init__(self, private_mode=False):
raise Exception('Error loading extension: "{extension}"')

# Module preparation (not loaded yet for performance purposes)
if not os.path.exists('modules'): os.makedirs('modules')
module_list = [module.lower() for module in os.listdir('modules') if os.path.exists(f'modules/{module}/interface.py')]
if not module_list or module_list == ['example']:

# Load modules from installed packages matching "orpheusdl_module_*"
module_packages = [
module_package for _, module_package, _ in pkgutil.iter_modules() if module_package.startswith('orpheusdl_module_')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to create a problem, because Pythons setuptools runs safe_name which will convert all _ to -. So it should be orpheusdl-module-

]

# Load modules from the local "modules" directory
os.makedirs('modules', exist_ok=True)
module_packages.extend(f"modules.{module}" for module in os.listdir('modules') if os.path.exists(f'modules/{module}/interface.py'))

if not module_packages or set(module_packages) == {"modules.example"}:
print('No modules are installed, quitting')
exit()
logging.debug('Orpheus: Modules detected: ' + ", ".join(module_list))
logging.debug('Orpheus: Modules detected: ' + ", ".join(module_packages))

for module in module_list: # Loading module information into module_settings
module_information: ModuleInformation = getattr(importlib.import_module(f'modules.{module}.interface'), 'module_information', None)
for module_package in module_packages: # Loading module information into module_settings
module_information: ModuleInformation = getattr(importlib.import_module(f'{module_package}.interface'), 'module_information', None)
if module_information and not ModuleFlags.private in module_information.flags and not private_mode:
self.module_list.add(module)
self.module_settings[module] = module_information
logging.debug(f'Orpheus: {module} added as a module')
if module_information.name:
module_name = module_information.name
else:
module_name = module_package.split(".")[-1].split("_")[-1]
self.module_list.add(module_name)
self.module_packages[module_name] = module_package
self.module_settings[module_name] = module_information
logging.debug(f'Orpheus: {module_name} added as a module')
else:
raise Exception(f'Error loading module information from module: "{module}"') # TODO: replace with InvalidModuleError
raise Exception(f'Error loading module information from module: "{module_package}"') # TODO: replace with InvalidModuleError

duplicates = set()
for module in self.module_list: # Detecting duplicate url constants
Expand Down Expand Up @@ -161,7 +197,8 @@ def load_module(self, module: str):
if module not in self.module_list:
raise Exception(f'"{module}" does not exist in modules.') # TODO: replace with InvalidModuleError
if module not in self.loaded_modules:
class_ = getattr(importlib.import_module(f'modules.{module}.interface'), 'ModuleInterface', None)
module_package = self.module_packages[module]
class_ = getattr(importlib.import_module(f'{module_package}.interface'), 'ModuleInterface', None)
if class_:
class ModuleError(Exception): # TODO: get rid of this, as it is deprecated
def __init__(self, message):
Expand Down Expand Up @@ -267,7 +304,14 @@ def update_module_storage(self): # Should be refactored eventually
new_settings['modules'] = module_settings

## Sessions
sessions = pickle.load(open(self.session_storage_location, 'rb')) if os.path.exists(self.session_storage_location) else {}
try:
sessions = pickle.load(open(self.session_storage_location, 'rb'))
except FileNotFoundError:
sessions = {}
except Exception:
print("Session store was invalid, resetting sessions.")
sessions = {}


if not ('advancedmode' in sessions and 'modules' in sessions and sessions['advancedmode'] == advanced_login_mode):
sessions = {'advancedmode': advanced_login_mode, 'modules':{}}
Expand Down Expand Up @@ -375,4 +419,4 @@ def orpheus_core_download(orpheus_session: Orpheus, media_to_download, third_par
raise Exception(f'\tUnknown media type "{mediatype}"')
print()

if os.path.exists('temp'): shutil.rmtree('temp')
if os.path.exists('temp'): shutil.rmtree('temp')
File renamed without changes.
10 changes: 5 additions & 5 deletions orpheus/music_downloader.py → orpheusdl/music_downloader.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import logging, os, ffmpeg
from dataclasses import asdict

from orpheus.tagging import tag_file
from utils.models import *
from utils.utils import *
from utils.exceptions import *
from orpheusdl.tagging import tag_file
from orpheusdl.utils.models import *
from orpheusdl.utils.utils import *
from orpheusdl.utils.exceptions import *


class Downloader:
Expand Down Expand Up @@ -473,4 +473,4 @@ def set_indent_number(self, number: int):

def oprint(self, input: str, drop_level: int = 0):
if self.printing_enabled:
print(' ' * (self.indent_number - drop_level * self.multiplier) + input)
print(' ' * (self.indent_number - drop_level * self.multiplier) + input)
10 changes: 6 additions & 4 deletions orpheus.py → orpheusdl/orpheus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse, re
from urllib.parse import urlparse

from orpheus.core import *
from orpheusdl.core import *


def main():
Expand Down Expand Up @@ -195,10 +195,12 @@ def main():

orpheus_core_download(orpheus, media_to_download, tpm, sdm, path)


if __name__ == "__main__":
def run():
try:
main()
except KeyboardInterrupt:
print('\n\t^C pressed - abort')
exit()
exit()

if __name__ == "__main__":
run()
4 changes: 2 additions & 2 deletions orpheus/tagging.py → orpheusdl/tagging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from mutagen.id3 import PictureType, APIC, USLT
from PIL import Image

from utils.models import ContainerEnum, TrackInfo
from utils.exceptions import *
from orpheusdl.utils.models import ContainerEnum, TrackInfo
from orpheusdl.utils.exceptions import *

# Needed for Windows tagging support
MP4Tags._padding = 0
Expand Down
File renamed without changes.
File renamed without changes.
5 changes: 3 additions & 2 deletions utils/models.py → orpheusdl/utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from types import ClassMethodDescriptorType, FunctionType
from typing import Optional

from utils.utils import read_temporary_setting, set_temporary_setting
from orpheusdl.utils.utils import read_temporary_setting, set_temporary_setting


class CodecEnum(Flag):
Expand Down Expand Up @@ -108,6 +108,7 @@ class ManualEnum(Flag):
class ModuleInformation:
service_name: str
module_supported_modes: ModuleModes
name: Optional[str] = None
global_settings: Optional[dict] = field(default_factory=dict)
global_storage_variables: Optional[list] = None
session_settings: Optional[dict] = field(default_factory=dict)
Expand Down Expand Up @@ -275,4 +276,4 @@ class TrackDownloadInfo:
download_type: DownloadEnum
file_url: Optional[str] = None
file_url_headers: Optional[dict] = None
temp_file_path: Optional[str] = None
temp_file_path: Optional[str] = None
File renamed without changes.
Loading