Files
freevine/utils/commands.py
2024-03-04 16:07:09 +01:00

220 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import logging
import sys
import subprocess
import shlex
import urllib3
from pathlib import Path
from urllib3.exceptions import InsecureRequestWarning
import click
import yaml
from rich.console import Console
from rich import print_json
from utils import __version__
from utils.console import custom_handler
from utils.docs.documentation import main_help
from utils.manager import service_manager
from utils.search.search import search_engine
from utils.utilities import is_url, check_version, get_binary
console = Console()
@click.group()
@click.option("--debug/--no-debug")
def cli(debug: bool):
logging.basicConfig(
level=logging.DEBUG if debug else logging.INFO,
handlers=[custom_handler],
)
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
urllib3.disable_warnings(InsecureRequestWarning)
click.echo(f"\n_freevine {__version__}\n")
# check_version(__version__)
@cli.command()
@click.option("--proxy", type=str, default=False, help="Request or specify a proxy server")
@click.argument("alias", type=str, required=True)
@click.argument("keywords", type=str, required=True)
def search(proxy: str, alias: str, keywords: str) -> None:
"""
Search one or multiple services for titles
Usage: freevine.py search bbc,all4 "KEYWORDS"
"""
if keywords is not None:
search_engine(alias, keywords, proxy)
@cli.command(short_help="Download series or movies", help=main_help)
@click.argument("url", type=str, required=False)
@click.option("--proxy", type=str, default=False, help="Request or specify a proxy server")
@click.option("--threads", type=str, default=False, help="Concurrent download fragments")
@click.option("--format", type=str, default=False, help="Specify file format")
@click.option("--muxer", type=str, default=False, help="Select muxer")
@click.option("--no-mux", is_flag=True, default=False, help="Choose to not mux files")
@click.option("--save-name", type=str, default=False, help="Name of saved file")
@click.option("--save-dir", type=str, default=False, help="Save directory")
@click.option("--sub-only", is_flag=True, default=False, help="Download only subtitles")
@click.option("--sub-no-mux", is_flag=True, default=False, help="Choose to not mux subtitles")
@click.option("--sub-no-fix", is_flag=True, default=False, help="Leave subtitles untouched")
@click.option("--use-shaka-packager", is_flag=True, default=False, help="Use shaka-packager to decrypt")
@click.option("--add-command", multiple=True, default=list, help="Add extra command to N_m3u8DL-RE")
@click.option("--slowdown", type=int, nargs=1, help="Add sleep (in seconds) between downloads")
@click.option("--no-cache", is_flag=True, default=False, help="Ignore download cache")
@click.option("--append-id", is_flag=True, default=False, help="Append video id to filename")
@click.option("-fn", "--force-numbering", is_flag=True, help="Force add numbering to episodes")
@click.option("-e", "--episode", type=str, help="Download episode(s)")
@click.option("-s", "--season", type=str, help="Download complete season")
@click.option("-c", "--complete", is_flag=True, help="Download complete series")
@click.option("-m", "--movie", is_flag=True, help="Download movie")
@click.option("-t", "--titles", is_flag=True, default=False, help="List all titles")
@click.option("-i", "--info", is_flag=True, default=False, help="Print title info")
@click.option("-sv", "--select-video", type=str, default=False, help="Select video stream")
@click.option("-sa", "--select-audio", type=str, default=False, help="Select audio stream")
@click.option("-dv", "--drop-video", type=str, default=False, help="Drop video stream")
@click.option("-da", "--drop-audio", type=str, default=False, help="Drop audio stream")
@click.option("-ss", "--select-subtitle", type=str, default=False, help="Select subtitle")
@click.option("-ds", "--drop-subtitle", type=str, default=False, help="Drop subtitle")
def get(**kwargs) -> None:
url = kwargs.get("episode") if is_url(kwargs.get("episode")) else kwargs.get("url")
Service, config = service_manager.get_service(url)
Service(config, **kwargs)
@cli.command()
@click.option("-u", "--username", type=str, required=True, help="Add profile username for a service")
@click.option("-p", "--password", type=str, required=True, help="Add profile password for a service")
@click.option("-s", "--service", type=str, required=True, help="Set service to be used with credentials")
def profile(username: str, password: str, service: str):
"""
Create a profile with user credentials
This will create a profile.yaml in service folder, which stores credentials and cache data.
"""
log = logging.getLogger()
settings = Path("utils") / "settings"
with open(settings / "services.json") as f:
services = json.load(f)
service = service.casefold()
match = next(
(
item
for item, key in services.items()
if service in {alias.casefold() for alias in key["alias"]}
),
None,
)
if not match:
log.error(f"Profile could not be set for {service}")
sys.exit(1)
key = services[match]
profile = Path(key["profile"]).resolve()
if not profile.is_file():
log.info(f"Creating new profile for {username}...")
profile.touch()
data = {"credentials": {"username": username, "password": password}}
with open(profile, "w") as f:
yaml.safe_dump(data, f, sort_keys=False)
else:
log.info("Updating profile...")
with open(profile, "r") as f:
data = yaml.safe_load(f)
data["credentials"]["username"] = username
data["credentials"]["password"] = password
with open(profile, "w") as f:
yaml.safe_dump(data, f, sort_keys=False)
log.info(f"Profile has been set for {key['alias'][0]}!")
@cli.command()
@click.argument("file", type=click.Path(exists=True), required=True)
def file(file: Path):
"""
Read commands from a text file
"""
python = get_binary("python", "python3", "py")
work_dir = Path(__file__).resolve().parent.parent
freevine = work_dir / "freevine.py"
with open(file, "r") as f:
for line in f:
args = shlex.split(line.rstrip())
subprocess.run([python, freevine] + args)
@cli.command()
@click.argument("service", type=str, required=False)
def service_info(service: str):
"""Print information about each streaming service"""
log = logging.getLogger()
settings = Path("utils") / "settings"
with open(settings / "services.json") as f:
services = json.load(f)
if service:
service = service.casefold()
match = next(
(
item
for item, key in services.items()
if service in {alias.casefold() for alias in key["alias"]}
),
None,
)
if not match:
log.error("No service found by that name")
return
print_json(data=services[match])
else:
info = {}
for item, key in services.items():
info["name"] = key["alias"][0]
info["url"] = item
print_json(data=info)
@cli.command()
def clear_cache():
"""Delete download cache"""
log = logging.getLogger()
for file in Path("services").rglob("cache.json"):
try:
file.unlink()
except Exception as e:
log.error(f"Failed to delete {file}. Reason: {e}")
log.info("Download cache has been cleared!")
cli.add_command(search)
cli.add_command(get)
cli.add_command(profile)
cli.add_command(file)
cli.add_command(service_info)
cli.add_command(clear_cache)