WIP push for backup (Sorry for the mess)

This commit is contained in:
j1nx 2023-08-08 13:08:11 +00:00
parent c6460b9307
commit a9dc12239f
839 changed files with 3639 additions and 106287 deletions

4
.gitmodules vendored
View File

@ -1,4 +1,4 @@
[submodule "buildroot"]
path = buildroot
url = https://github.com/buildroot/buildroot.git
branch = 2023.02.x
url = https://github.com/j1nx/buildroot.git
branch = ovos-2023.02.x

View File

@ -39,6 +39,12 @@ clean:
menuconfig:
$(MAKE) -C $(BUILDROOT) BR2_EXTERNAL=../$(BUILDROOT_EXTERNAL) menuconfig
linux-menuconfig:
$(MAKE) -C $(BUILDROOT) BR2_EXTERNAL=../$(BUILDROOT_EXTERNAL) linux-menuconfig
busybox-menuconfig:
$(MAKE) -C $(BUILDROOT) BR2_EXTERNAL=../$(BUILDROOT_EXTERNAL) busybox-menuconfig
savedefconfig:
$(MAKE) -C $(BUILDROOT) BR2_EXTERNAL=../$(BUILDROOT_EXTERNAL) savedefconfig

View File

@ -2,5 +2,11 @@ set default="0"
set timeout="3"
menuentry "OpenVoiceOS" {
linux /bzImage root=PARTUUID=c0932a41-44cf-463b-8152-d43188553ed4 rootfstype=squashfs ro init=/sbin/pre-init fsck.repair=yes zram.enabled=1 zram.num_devices=4 console=ttyS0 consoleblank=0 loglevel=0 vt.global_cursor_default=0 audit=0 logo.nologo systemd.show_status=0 rootwait quiet
linux /bzImage root=PARTUUID=c0932a41-44cf-463b-8152-d43188553ed4 rootfstype=squashfs ro init=/sbin/pre-init fsck.repair=yes zram.enabled=1 zram.num_devices=3 console=tty1 cgroup_enable=cpuset cgroup_memory=1 audit=0 rootwait
}
menuentry "OpenVoiceOS SystemD Debug" {
linux /bzImage root=PARTUUID=c0932a41-44cf-463b-8152-d43188553ed4 rootfstype=squashfs ro init=/sbin/pre-init fsck.repair=yes zram.enabled=1 zram.num_devices=3 console=tty1 cgroup_enable=cpuset cgroup_memory=1 audit=0 rootwait systemd.log_level=debug
}
menuentry "OpenVoiceOS Recovery" {
linux /bzImage root=PARTUUID=c0932a41-44cf-463b-8152-d43188553ed4 rootfstype=squashfs ro init=/sbin/pre-init rootwait systemd.unit=rescue.target
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,243 @@
BR2_x86_64=y
BR2_PACKAGE_GLIBC_UTILS=y
BR2_BINUTILS_VERSION_2_39_X=y
BR2_GCC_VERSION_12_X=y
BR2_TOOLCHAIN_BUILDROOT_CXX=y
BR2_DL_DIR="../../downloads"
BR2_OPTIMIZE_2=y
BR2_ENABLE_LTO=y
BR2_GLOBAL_PATCH_DIR="../buildroot-patches/"
BR2_FORCE_HOST_BUILD=y
BR2_SSP_REGULAR=y
BR2_TARGET_GENERIC_HOSTNAME="OpenVoiceOS"
BR2_TARGET_GENERIC_ISSUE="Welcome to OpenVoiceOS"
BR2_TARGET_GENERIC_PASSWD_SHA512=y
BR2_INIT_SYSTEMD=y
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
BR2_SYSTEM_BIN_SH_BASH=y
# BR2_TARGET_GENERIC_GETTY is not set
# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set
BR2_SYSTEM_DHCP="eth0"
# BR2_ENABLE_LOCALE_PURGE is not set
BR2_GENERATE_LOCALE="en_US.UTF-8"
BR2_SYSTEM_ENABLE_NLS=y
BR2_ROOTFS_USERS_TABLES="$(BR2_EXTERNAL)/user_table.txt"
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL)/rootfs-overlay $(BR2_EXTERNAL)/board/ovos/ova/rootfs-overlay"
BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL)/board/ovos/ova/post-build.sh"
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL)/board/ovos/ova/post-image.sh"
BR2_ROOTFS_POST_SCRIPT_ARGS="--ova"
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.1.37"
BR2_LINUX_KERNEL_DEFCONFIG="x86_64"
BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL)/kernel/ovos.config $(BR2_EXTERNAL)/kernel/device-drivers.config $(BR2_EXTERNAL)/kernel/docker.config $(BR2_EXTERNAL)/board/ovos/ova/kernel.config"
BR2_LINUX_KERNEL_LZ4=y
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
BR2_PACKAGE_LINUX_TOOLS_HV=y
BR2_PACKAGE_LINUX_TOOLS_HV_KVP_DAEMON=y
BR2_PACKAGE_LINUX_TOOLS_HV_FCOPY_DAEMON=y
BR2_PACKAGE_LINUX_TOOLS_HV_VSS_DAEMON=y
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL)/busybox.config"
BR2_PACKAGE_ALSA_UTILS=y
BR2_PACKAGE_ALSA_UTILS_ALSACONF=y
BR2_PACKAGE_ALSA_UTILS_ACONNECT=y
BR2_PACKAGE_ALSA_UTILS_ALSALOOP=y
BR2_PACKAGE_ALSA_UTILS_ALSAUCM=y
BR2_PACKAGE_ALSA_UTILS_ALSATPLG=y
BR2_PACKAGE_ALSA_UTILS_AMIDI=y
BR2_PACKAGE_ALSA_UTILS_AMIXER=y
BR2_PACKAGE_ALSA_UTILS_APLAY=y
BR2_PACKAGE_ALSA_UTILS_APLAYMIDI=y
BR2_PACKAGE_ALSA_UTILS_ARECORDMIDI=y
BR2_PACKAGE_ALSA_UTILS_ASEQDUMP=y
BR2_PACKAGE_ALSA_UTILS_ASEQNET=y
BR2_PACKAGE_ALSA_UTILS_BAT=y
BR2_PACKAGE_ALSA_UTILS_IECSET=y
BR2_PACKAGE_ALSA_UTILS_SPEAKER_TEST=y
BR2_PACKAGE_PIPEWIRE=y
BR2_PACKAGE_BZIP2=y
BR2_PACKAGE_GZIP=y
BR2_PACKAGE_LZIP=y
BR2_PACKAGE_P7ZIP=y
BR2_PACKAGE_UNRAR=y
BR2_PACKAGE_UNZIP=y
BR2_PACKAGE_XZ=y
BR2_PACKAGE_ZIP=y
BR2_PACKAGE_LSOF=y
BR2_PACKAGE_MEMSTAT=y
BR2_PACKAGE_NMON=y
BR2_PACKAGE_BINUTILS=y
BR2_PACKAGE_CHECK=y
BR2_PACKAGE_DIFFUTILS=y
BR2_PACKAGE_FINDUTILS=y
BR2_PACKAGE_GIT_CRYPT=y
BR2_PACKAGE_GREP=y
BR2_PACKAGE_JO=y
BR2_PACKAGE_JQ=y
BR2_PACKAGE_LIBTOOL=y
BR2_PACKAGE_PATCH=y
BR2_PACKAGE_DOSFSTOOLS=y
BR2_PACKAGE_DOSFSTOOLS_FATLABEL=y
BR2_PACKAGE_DOSFSTOOLS_FSCK_FAT=y
BR2_PACKAGE_DOSFSTOOLS_MKFS_FAT=y
BR2_PACKAGE_E2FSPROGS=y
BR2_PACKAGE_E2FSPROGS_E2IMAGE=y
BR2_PACKAGE_E2FSPROGS_FUSE2FS=y
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
BR2_PACKAGE_FUSE_OVERLAYFS=y
BR2_PACKAGE_NFS_UTILS=y
BR2_PACKAGE_NTFS_3G=y
BR2_PACKAGE_SQUASHFS=y
# BR2_PACKAGE_SQUASHFS_GZIP is not set
BR2_PACKAGE_SQUASHFS_LZ4=y
BR2_PACKAGE_LINUX_FIRMWARE=y
BR2_PACKAGE_LINUX_FIRMWARE_I915=y
BR2_PACKAGE_LINUX_FIRMWARE_IBT=y
BR2_PACKAGE_LINUX_FIRMWARE_QUALCOMM_6174A_BT=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX_BT=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_88XX_BT=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_22000=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_22260=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_3160=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_3168=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_3945=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_4965=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_5000=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_6000G2A=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_6000G2B=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_7260=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_7265D=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_8000C=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_8265=y
BR2_PACKAGE_LINUX_FIRMWARE_IWLWIFI_9XXX=y
BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7601U=y
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT73=y
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT2XX=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_81XX=y
BR2_PACKAGE_LINUX_FIRMWARE_BNX2X=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_8169=y
BR2_PACKAGE_LINUX_FIRMWARE_USB_SERIAL_TI=y
BR2_PACKAGE_CRYPTSETUP=y
BR2_PACKAGE_GPTFDISK=y
BR2_PACKAGE_GPTFDISK_GDISK=y
BR2_PACKAGE_GPTFDISK_SGDISK=y
BR2_PACKAGE_GPTFDISK_CGDISK=y
BR2_PACKAGE_GVFS=y
BR2_PACKAGE_PARTED=y
BR2_PACKAGE_USB_MODESWITCH_DATA=y
BR2_PACKAGE_LUA=y
BR2_PACKAGE_ALSA_PLUGINS=y
BR2_PACKAGE_LIBSNDFILE=y
BR2_PACKAGE_PORTAUDIO=y
BR2_PACKAGE_SPEEX=y
BR2_PACKAGE_SPEEXDSP=y
BR2_PACKAGE_WEBRTC_AUDIO_PROCESSING=y
BR2_PACKAGE_LIBARCHIVE=y
BR2_PACKAGE_LZO=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_LIBGPGME=y
BR2_PACKAGE_LIBSSH2=y
BR2_PACKAGE_LIBOPENSSL_BIN=y
BR2_PACKAGE_LIBOPENSSL_ENGINES=y
BR2_PACKAGE_LIBLOCKFILE=y
BR2_PACKAGE_LIBNFS=y
BR2_PACKAGE_LIBSYSFS=y
BR2_PACKAGE_LOCKDEV=y
BR2_PACKAGE_PHYSFS=y
BR2_PACKAGE_WIREPLUMBER=y
BR2_PACKAGE_LIBGUDEV=y
BR2_PACKAGE_LIBYAML=y
BR2_PACKAGE_YAJL=y
BR2_PACKAGE_LIBCURL=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_LIBNDP=y
BR2_PACKAGE_LIBUNISTRING=y
BR2_PACKAGE_PCRE2=y
BR2_PACKAGE_BLUEZ_TOOLS=y
BR2_PACKAGE_BLUEZ5_UTILS=y
BR2_PACKAGE_BLUEZ5_UTILS_OBEX=y
BR2_PACKAGE_BLUEZ5_UTILS_CLIENT=y
BR2_PACKAGE_BLUEZ5_UTILS_MONITOR=y
BR2_PACKAGE_BLUEZ5_UTILS_TOOLS_HID2HCI=y
BR2_PACKAGE_CRDA=y
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IW=y
BR2_PACKAGE_NET_TOOLS=y
BR2_PACKAGE_OPENSSH=y
# BR2_PACKAGE_OPENSSH_SANDBOX is not set
BR2_PACKAGE_WGET=y
BR2_PACKAGE_WIRELESS_TOOLS=y
BR2_PACKAGE_WPA_SUPPLICANT=y
BR2_PACKAGE_WPA_SUPPLICANT_WEXT=y
BR2_PACKAGE_WPA_SUPPLICANT_AP_SUPPORT=y
BR2_PACKAGE_WPA_SUPPLICANT_MESH_NETWORKING=y
BR2_PACKAGE_WPA_SUPPLICANT_AUTOSCAN=y
BR2_PACKAGE_WPA_SUPPLICANT_HOTSPOT=y
BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG=y
BR2_PACKAGE_WPA_SUPPLICANT_WPA3=y
BR2_PACKAGE_WPA_SUPPLICANT_CLI=y
BR2_PACKAGE_WPA_SUPPLICANT_WPA_CLIENT_SO=y
BR2_PACKAGE_WPA_SUPPLICANT_PASSPHRASE=y
BR2_PACKAGE_WPA_SUPPLICANT_DBUS=y
BR2_PACKAGE_CATATONIT=y
BR2_PACKAGE_FILE=y
BR2_PACKAGE_SCREEN=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_TIME=y
BR2_PACKAGE_TINI=y
BR2_PACKAGE_WHICH=y
BR2_PACKAGE_ATTR=y
BR2_PACKAGE_DOCKER_CLI_BUILDX=y
BR2_PACKAGE_DOCKER_COMPOSE=y
BR2_PACKAGE_DOCKER_ENGINE=y
BR2_PACKAGE_DOCKER_ENGINE_EXPERIMENTAL=y
BR2_PACKAGE_EFIBOOTMGR=y
BR2_PACKAGE_HTOP=y
BR2_PACKAGE_OPENVMTOOLS=y
BR2_PACKAGE_PROCPS_NG=y
BR2_PACKAGE_SHADOW=y
BR2_PACKAGE_SHADOW_SHADOWGRP=y
BR2_PACKAGE_SHADOW_ACCOUNT_TOOLS_SETUID=y
BR2_PACKAGE_SHADOW_UTMPX=y
BR2_PACKAGE_SHADOW_SUBORDINATE_IDS=y
# BR2_PACKAGE_SYSTEMD_PSTORE is not set
BR2_PACKAGE_SYSTEMD_FIRSTBOOT=y
BR2_PACKAGE_SYSTEMD_HIBERNATE=y
# BR2_PACKAGE_SYSTEMD_HWDB is not set
BR2_PACKAGE_SYSTEMD_LOGIND=y
BR2_PACKAGE_SYSTEMD_OOMD=y
BR2_PACKAGE_SYSTEMD_RANDOMSEED=y
BR2_PACKAGE_SYSTEMD_REPART=y
BR2_PACKAGE_SYSTEMD_RFKILL=y
# BR2_PACKAGE_SYSTEMD_VCONSOLE is not set
BR2_PACKAGE_TAR=y
BR2_PACKAGE_UTIL_LINUX_HWCLOCK=y
BR2_PACKAGE_UTIL_LINUX_KILL=y
BR2_PACKAGE_UTIL_LINUX_LOGGER=y
BR2_PACKAGE_UTIL_LINUX_LOGIN=y
BR2_PACKAGE_UTIL_LINUX_LSMEM=y
BR2_PACKAGE_UTIL_LINUX_MESG=y
BR2_PACKAGE_UTIL_LINUX_MORE=y
BR2_PACKAGE_UTIL_LINUX_NOLOGIN=y
BR2_PACKAGE_UTIL_LINUX_PARTX=y
BR2_PACKAGE_UTIL_LINUX_SU=y
BR2_PACKAGE_UTIL_LINUX_ZRAMCTL=y
BR2_PACKAGE_LESS=y
BR2_PACKAGE_NANO=y
BR2_PACKAGE_VIM=y
BR2_TARGET_ROOTFS_SQUASHFS=y
BR2_TARGET_ROOTFS_SQUASHFS4_LZ4=y
# BR2_TARGET_ROOTFS_TAR is not set
BR2_TARGET_GRUB2=y
BR2_TARGET_GRUB2_X86_64_EFI=y
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux ext2 fat squash4 part_msdos part_gpt normal efi_gop regexp loadenv echo cat test configfile"
BR2_TARGET_GRUB2_INSTALL_TOOLS=y
BR2_PACKAGE_HOST_DOSFSTOOLS=y
BR2_PACKAGE_HOST_E2FSPROGS=y
BR2_PACKAGE_HOST_GENIMAGE=y
BR2_PACKAGE_HOST_MKPASSWD=y
BR2_PACKAGE_HOST_MTOOLS=y
BR2_PACKAGE_HOST_PKGCONF=y
BR2_PACKAGE_GROWDISK_SERVICE=y
BR2_PACKAGE_HOSTNAME_SERVICE=y

View File

@ -0,0 +1,85 @@
CONFIG_POSIX_MQUEUE=y
CONFIG_CFQ_GROUP_IOSCHED=y
CONFIG_CFS_BANDWIDTH=y
CONFIG_FAIR_GROUP_SCHED=y
CONFIG_NET_SCHED=y
# CONFIG_RT_GROUP_SCHED is not set
CONFIG_CGROUPS=y
CONFIG_HUGETLB_PAGE=y
CONFIG_BLK_CGROUP=y
CONFIG_BLK_DEV_THROTTLING=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_NET_CLS_CGROUP=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_NAMESPACES=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_IPC_NS=y
CONFIG_UTS_NS=y
CONFIG_NETDEVICES=y
CONFIG_DUMMY=m
CONFIG_MACVLAN=m
CONFIG_IPVLAN=m
CONFIG_VXLAN=m
CONFIG_INET=y
CONFIG_IPV6=y
CONFIG_INET_ESP=m
CONFIG_INET_XFRM_MODE_TRANSPORT=m
CONFIG_NETCONSOLE=y
CONFIG_VETH=y
CONFIG_NETFILTER=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_NAT=y
CONFIG_NF_NAT_NEEDED=y
CONFIG_NF_CONNTRACK_IPV4=y
CONFIG_IP6_NF_IPTABLES=y
CONFIG_IP6_NF_FILTER=y
CONFIG_IP6_NF_MANGLE=y
CONFIG_IP6_NF_NAT=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NETFILTER_XT_MATCH_IPVS=y
CONFIG_IP_VS=y
CONFIG_IP_VS_RR=y
CONFIG_IP_VS_NFCT=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_NAT=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_IP_NF_TARGET_REDIRECT=y
CONFIG_BRIDGE=y
CONFIG_BRIDGE_NETFILTER=y
CONFIG_XFRM=m
CONFIG_XFRM_USER=m
CONFIG_XFRM_ALGO=m
CONFIG_NET_L3_MASTER_DEV=y
CONFIG_EXT4_FS=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y
CONFIG_OVERLAY_FS=y
CONFIG_OVERLAY_FS_METACOPY=y
CONFIG_CRYPTO_CCM=m
CONFIG_CRYPTO_GCM=m
CONFIG_CRYPTO_CMAC=m
CONFIG_CRYPTO_ARC4=m
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y

View File

@ -30,8 +30,10 @@ CONFIG_SQUASHFS_LZ4=y
CONFIG_BTRFS_FS=m
CONFIG_OVERLAY_FS=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
# CONFIG_SECCOMP is not set
# CONFIG_AUDIT is not set
# CONFIG_SECURITY is not set
# CONFIG_SECURITY_SELINUX is not set
CONFIG_CRYPTO=y
CONFIG_CRYPTO_LZ4=y

View File

@ -1,22 +0,0 @@
[main]
dns=default
plugins=keyfile
autoconnect-retries-default=0
rc-manager=file
[keyfile]
unmanaged-devices=type:tun;type:veth
[logging]
backend=journal
[connection]
connection.mdns=2
connection.llmnr=2
[connectivity]
uri=http://nmcheck.gnome.org/check_network_status.txt
interval=300
[device]
wifi.scan-rand-mac-address=no

View File

@ -1,2 +0,0 @@
[main]
auth-polkit=true

View File

@ -1,3 +0,0 @@
[connection]
# Values are 0 (use default), 1 (ignore/don't touch), 2 (disable) or 3 (enable).
wifi.powersave = 2

View File

@ -1,11 +0,0 @@
[connection]
id=OpenVoiceOS default
uuid=554628d6-8290-3dea-90c1-9b3b108dc19c
type=802-3-ethernet
[ipv4]
method=auto
[ipv6]
addr-gen-mode=stable-privacy
method=auto

View File

@ -1,14 +0,0 @@
# Use PulseAudio by default
pcm.!default {
type pulse
fallback "sysdefault"
hint {
show on
description "Default ALSA Output (currently PulseAudio Sound Server)"
}
}
ctl.!default {
type pulse
fallback "sysdefault"
}

View File

@ -1,78 +0,0 @@
{
"ready_settings": ["setup", "skills"],
"confirm_listening": true,
"play_wav_cmdline": "paplay %1",
"play_mp3_cmdline": "mpg123 %1",
"ipc_path": "/dev/shm/mycroft/ipc/",
"network_tests": {
"ip_url": "https://api.ipify.org",
"dns_primary": "1.1.1.1",
"dns_secondary": "8.8.8.8",
"web_url": "http://nmcheck.gnome.org/check_network_status.txt",
"web_url_secondary": "https://checkonline.home-assistant.io/online.txt",
"captive_portal_url": "http://nmcheck.gnome.org/check_network_status.txt",
"captive_portal_text": "NetworkManager is online"
},
"gui": {
"extension": "smartspeaker",
"idle_display_skill": "skill-ovos-homescreen.openvoiceos"
},
"PHAL": {
"ovos-PHAL-plugin-system": {"sudo": false}
},
"listener": {
"mute_during_output": false,
"instant_listen": true,
"VAD": {
"module": "ovos-vad-plugin-webrtcvad",
"ovos-vad-plugin-webrtcvad": {"vad_mode": 3}
},
"retry_mic_init" : false
},
"hotwords": {
"hey_mycroft": {
"module": "ovos-precise-lite",
"model": "~/.local/share/precise-lite/wakewords/en/hey_mycroft.tflite",
"sensitivity": 0.5,
"trigger_level": 3,
"expected_duration": 3
}
},
"stt": {
"fallback_module": ""
},
"tts": {
"module": "ovos-tts-plugin-mimic3-server",
"fallback_module": "ovos-tts-plugin-mimic",
"ovos-tts-plugin-mimic3-server": {
"voice": "en_UK/apope_low"
}
},
"skills": {
"wait_for_internet": true,
"autogen_meta": false
},
"Audio": {
"backends": {
"OCP": {
"type": "ovos_common_play",
"manage_external_players": true,
"active": true,
"youtube_backend": "youtube-dl",
"ydl_backend": "auto"
},
"vlc": {
"type": "ovos_vlc",
"active": true
},
"simple": {
"type": "ovos_audio_simple",
"active": true
}
}
},
"log_level": "INFO",
"logs": {
"path": "/var/log/mycroft"
}
}

View File

@ -1,8 +0,0 @@
/* give group 'network' rights to change settings */
/* taken from https://wiki.archlinux.org/index.php/NetworkManager#Set_up_PolicyKit_permissions */
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.NetworkManager.") == 0 && subject.isInGroup("network")) {
return polkit.Result.YES;
}
});

View File

@ -1,45 +0,0 @@
[global]
workgroup = OPENVOICEOS
netbios name = OVOS
log file = /var/log/samba/log.%m
max log size = 1000
logging = file
panic action = /usr/share/samba/panic-action %d
server role = standalone server
obey pam restrictions = yes
unix password sync = yes
passwd program = /usr/bin/passwd %u
passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
pam password change = yes
map to guest = bad user
usershare allow guests = yes
[Pictures]
path = /home/mycroft/Pictures
public = yes
guest only = yes
writable = yes
force create mode = 0666
force directory mode = 0777
browseable = yes
force user = mycroft
[Documents]
path = /home/mycroft/Documents
public = yes
guest only = yes
writable = yes
force create mode = 0666
force directory mode = 0777
browseable = yes
force user = mycroft
[Music]
path = /home/mycroft/Music
public = yes
guest only = yes
writable = yes
force create mode = 0666
force directory mode = 0777
browseable = yes
force user = mycroft

View File

@ -1,6 +0,0 @@
general = {
name = "OpenVoiceOS";
output_backend = "pa";
dbus_service_bus = "session";
mpris_service_bus = "session";
};

View File

@ -1 +0,0 @@
../../../usr/lib/systemd/system/ovos.target

View File

@ -1,3 +0,0 @@
d /run/pulse 755 pulse pulse
d /run/pulse/.config 755 pulse pulse
d /run/pulse/.config/pulse 755 pulse pulse

View File

@ -1,53 +0,0 @@
# Configuration file for the usbmount package, which mounts removable
# storage devices when they are plugged in and unmounts them when they
# are removed.
# Change to zero to disable usbmount
ENABLED=1
# Mountpoints: These directories are eligible as mointpoints for
# removable storage devices. A newly plugged in device is mounted on
# the first directory in this list that exists and on which nothing is
# mounted yet.
MOUNTPOINTS="/media/usb0 /media/usb1 /media/usb2 /media/usb3
/media/usb4 /media/usb5 /media/usb6 /media/usb7"
# Filesystem types: removable storage devices are only mounted if they
# contain a filesystem type which is in this list.
FILESYSTEMS="vfat ntfs ext2 ext3 ext4 hfsplus exfat f2fs"
#############################################################################
# WARNING! #
# #
# The "sync" option may not be a good choice to use with flash drives, as #
# it forces a greater amount of writing operating on the drive. This makes #
# the writing speed considerably lower and also leads to a faster wear out #
# of the disk. #
# #
# If you omit it, don't forget to use the command "sync" to synchronize the #
# data on your disk before removing the drive or you may experience data #
# loss. #
# #
# It is highly recommended that you use the pumount command (as a regular #
# user) before unplugging the device. It makes calling the "sync" command #
# and mounting with the sync option unnecessary---this is similar to other #
# operating system's "safely disconnect the device" option. #
#############################################################################
# Mount options: Options passed to the mount command with the -o flag.
# See the warning above regarding removing "sync" from the options.
MOUNTOPTIONS="noexec,nodev,noatime,nodiratime"
# Filesystem type specific mount options: This variable contains a space
# separated list of strings, each which the form "-fstype=TYPE,OPTIONS".
#
# If a filesystem with a type listed here is mounted, the corresponding
# options are appended to those specificed in the MOUNTOPTIONS variable.
#
# For example, "-fstype=vfat,gid=floppy,dmask=0007,fmask=0117" would add
# the options "gid=floppy,dmask=0007,fmask=0117" when a vfat filesystem
# is mounted.
FS_MOUNTOPTIONS="fstype=vfat,utf8,uid=1000,gid=1000,umask=022 -fstype=ntfs-3g,nls=utf8,uid=1000,gid=1000,umask=022"
# If set to "yes", more information will be logged via the syslog
# facility.
VERBOSE=no

View File

@ -1,12 +0,0 @@
{
"stt": { "module": "google"},
"backend_port": 6712,
"geolocate": true,
"override_location": false,
"api_version": "v1",
"data_path": "~",
"record_utterances": false,
"record_wakewords": false,
"wolfram_key": "Y7R353-9HQAAL8KKA",
"owm_key": "28fed22898afd4717ce5a1535da1f78c"
}

View File

@ -1,6 +0,0 @@
general = {
name = "OpenVoiceOS";
output_backend = "pa";
dbus_service_bus = "session";
mpris_service_bus = "session";
};

View File

@ -1,90 +0,0 @@
[spotifyd]
# Your Spotify account name.
#username = "username"
# Your Spotify account password.
#password = "password"
# A command that gets executed and can be used to
# retrieve your password.
# The command should return the password on stdout.
#
# This is an alternative to the `password` field. Both
# can't be used simultaneously.
#password_cmd = "command_that_writes_password_to_stdout"
# If set to true, `spotifyd` tries to look up your
# password in the system's password storage.
#
# This is an alternative to the `password` field. Both
# can't be used simultaneously.
#use_keyring = true
#
# If set to true, `spotifyd` tries to bind to the session dbus
# and expose MPRIS controls. When running headless, without a dbus session,
# then set this to false to avoid binding errors
#
use_mpris = true
# The audio backend used to play the your music. To get
# a list of possible backends, run `spotifyd --help`.
backend = "pulseaudio"
# The alsa audio device to stream audio to. To get a
# list of valid devices, run `aplay -L`,
#device = "alsa_audio_device" # omit for macOS
# The alsa control device. By default this is the same
# name as the `device` field.
#control = "alsa_audio_device" # omit for macOS
# The alsa mixer used by `spotifyd`.
#mixer = "PCM"
# The volume controller. Each one behaves different to
# volume increases. For possible values, run
# `spotifyd --help`.
#volume_controller = "alsa" # use softvol for macOS
# A command that gets executed in your shell after each song changes.
#on_song_change_hook = "command_to_run_on_playback_events"
# The name that gets displayed under the connect tab on
# official clients. Spaces are not allowed!
device_name = "OpenVoiceOS"
# The audio bitrate. 96, 160 or 320 kbit/s
#bitrate = 160
# The directory used to cache audio data. This setting can save
# a lot of bandwidth when activated, as it will avoid re-downloading
# audio files when replaying them.
#
# Note: The file path does not get expanded. Environment variables and
# shell placeholders like $HOME or ~ don't work!
#cache_path = "cache_directory"
# If set to true, audio data does NOT get cached.
#no_audio_cache = true
# Volume on startup between 0 and 100
# NOTE: This variable's type will change in v0.4, to a number (instead of string)
#initial_volume = "90"
# If set to true, enables volume normalisation between songs.
#volume_normalisation = true
# The normalisation pregain that is applied for each song.
#normalisation_pregain = -10
# The port `spotifyd` uses to announce its service over the network.
zeroconf_port = 57621
# The proxy `spotifyd` will use to connect to spotify.
#proxy = "http://proxy.example.org:8080"
# The displayed device type in Spotify clients.
# Can be unknown, computer, tablet, smartphone, speaker, t_v,
# a_v_r (Audio/Video Receiver), s_t_b (Set-Top Box), and audio_dongle.
device_type = "speaker"

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/kdeconnectd.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/pulseaudio.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/shairport-sync.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/spotifyd.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft-audio.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft-messagebus.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft-phal.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft-skills.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/mycroft-voice.service

View File

@ -1 +0,0 @@
../../../../../../usr/lib/systemd/user/pulseaudio.socket

View File

@ -1,606 +0,0 @@
======
Colour
======
.. image:: http://img.shields.io/pypi/v/colour.svg?style=flat
:target: https://pypi.python.org/pypi/colour/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/l/gitchangelog.svg?style=flat
:target: https://github.com/vaab/gitchangelog/blob/master/LICENSE
:alt: License
.. image:: https://img.shields.io/pypi/pyversions/gitchangelog.svg?style=flat
:target: https://pypi.python.org/pypi/gitchangelog/
:alt: Compatible python versions
.. image:: http://img.shields.io/pypi/dm/colour.svg?style=flat
:target: https://pypi.python.org/pypi/colour/
:alt: Number of PyPI downloads
.. image:: http://img.shields.io/travis/vaab/colour/master.svg?style=flat
:target: https://travis-ci.org/vaab/colour/
:alt: Travis CI build status
.. image:: https://img.shields.io/appveyor/ci/vaab/colour.svg
:target: https://ci.appveyor.com/project/vaab/colour/branch/master
:alt: Appveyor CI build status
.. image:: http://img.shields.io/codecov/c/github/vaab/colour.svg?style=flat
:target: https://codecov.io/gh/vaab/colour/
:alt: Test coverage
Converts and manipulates common color representation (RGB, HSL, web, ...)
Feature
=======
- Damn simple and pythonic way to manipulate color representation (see
examples below)
- Full conversion between RGB, HSL, 6-digit hex, 3-digit hex, human color
- One object (``Color``) or bunch of single purpose function (``rgb2hex``,
``hsl2rgb`` ...)
- ``web`` format that use the smallest representation between
6-digit (e.g. ``#fa3b2c``), 3-digit (e.g. ``#fbb``), fully spelled
color (e.g. ``white``), following `W3C color naming`_ for compatible
CSS or HTML color specifications.
- smooth intuitive color scale generation choosing N color gradients.
- can pick colors for you to identify objects of your application.
.. _W3C color naming: http://www.w3.org/TR/css3-color/#svg-color
Installation
============
You don't need to download the GIT version of the code as ``colour`` is
available on the PyPI. So you should be able to run::
pip install colour
If you have downloaded the GIT sources, then you could add the ``colour.py``
directly to one of your ``site-packages`` (thanks to a symlink). Or install
the current version via traditional::
python setup.py install
And if you don't have the GIT sources but would like to get the latest
master or branch from github, you could also::
pip install git+https://github.com/vaab/colour
Or even select a specific revision (branch/tag/commit)::
pip install git+https://github.com/vaab/colour@master
Usage
=====
To get complete demo of each function, please read the source code which is
heavily documented and provide a lot of examples in doctest format.
Here is a reduced sample of a common usage scenario:
Instantiation
-------------
Let's create blue color::
>>> from colour import Color
>>> c = Color("blue")
>>> c
<Color blue>
Please note that all of these are equivalent examples to create the red color::
Color("red") ## human, web compatible representation
Color(red=1) ## default amount of blue and green is 0.0
Color("blue", hue=0) ## hue of blue is 0.66, hue of red is 0.0
Color("#f00") ## standard 3 hex digit web compatible representation
Color("#ff0000") ## standard 6 hex digit web compatible representation
Color(hue=0, saturation=1, luminance=0.5)
Color(hsl=(0, 1, 0.5)) ## full 3-uple HSL specification
Color(rgb=(1, 0, 0)) ## full 3-uple RGB specification
Color(Color("red")) ## recursion doesn't break object
Reading values
--------------
Several representations are accessible::
>>> c.hex
'#00f'
>>> c.hsl # doctest: +ELLIPSIS
(0.66..., 1.0, 0.5)
>>> c.rgb
(0.0, 0.0, 1.0)
And their different parts are also independently accessible, as the different
amount of red, blue, green, in the RGB format::
>>> c.red
0.0
>>> c.blue
1.0
>>> c.green
0.0
Or the hue, saturation and luminance of the HSL representation::
>>> c.hue # doctest: +ELLIPSIS
0.66...
>>> c.saturation
1.0
>>> c.luminance
0.5
A note on the ``.hex`` property, it'll return the smallest valid value
when possible. If you are only interested by the long value, use
``.hex_l``::
>>> c.hex_l
'#0000ff'
Modifying color objects
-----------------------
All of these properties are read/write, so let's add some red to this color::
>>> c.red = 1
>>> c
<Color magenta>
We might want to de-saturate this color::
>>> c.saturation = 0.5
>>> c
<Color #bf40bf>
And of course, the string conversion will give the web representation which is
human, or 3-digit, or 6-digit hex representation depending which is usable::
>>> "%s" % c
'#bf40bf'
>>> c.luminance = 1
>>> "%s" % c
'white'
Ranges of colors
----------------
You can get some color scale of variation between two ``Color`` objects quite
easily. Here, is the color scale of the rainbow between red and blue::
>>> red = Color("red")
>>> blue = Color("blue")
>>> list(red.range_to(blue, 5))
[<Color red>, <Color yellow>, <Color lime>, <Color cyan>, <Color blue>]
Or the different amount of gray between black and white::
>>> black = Color("black")
>>> white = Color("white")
>>> list(black.range_to(white, 6))
[<Color black>, <Color #333>, <Color #666>, <Color #999>, <Color #ccc>, <Color white>]
If you have to create graphical representation with color scale
between red and green ('lime' color is full green)::
>>> lime = Color("lime")
>>> list(red.range_to(lime, 5))
[<Color red>, <Color #ff7f00>, <Color yellow>, <Color chartreuse>, <Color lime>]
Notice how naturally, the yellow is displayed in human format and in
the middle of the scale. And that the quite unusual (but compatible)
'chartreuse' color specification has been used in place of the
hexadecimal representation.
Color comparison
----------------
Sane default
~~~~~~~~~~~~
Color comparison is a vast subject. However, it might seem quite straightforward for
you. ``Colour`` uses a configurable default way of comparing color that might suit
your needs::
>>> Color("red") == Color("#f00") == Color("blue", hue=0)
True
The default comparison algorithm focuses only on the "web" representation which is
equivalent to comparing the long hex representation (e.g. #FF0000) or to be more
specific, it is equivalent to compare the amount of red, green, and blue composition
of the RGB representation, each of these value being quantized to a 256 value scale.
This default comparison is a practical and convenient way to measure the actual
color equivalence on your screen, or in your video card memory.
But this comparison wouldn't make the difference between a black red, and a
black blue, which both are black::
>>> black_red = Color("red", luminance=0)
>>> black_blue = Color("blue", luminance=0)
>>> black_red == black_blue
True
Customization
~~~~~~~~~~~~~
But, this is not the sole way to compare two colors. As I'm quite lazy, I'm providing
you a way to customize it to your needs. Thus::
>>> from colour import RGB_equivalence, HSL_equivalence
>>> black_red = Color("red", luminance=0, equality=HSL_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=HSL_equivalence)
>>> black_red == black_blue
False
As you might have already guessed, the sane default is ``RGB_equivalence``, so::
>>> black_red = Color("red", luminance=0, equality=RGB_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=RGB_equivalence)
>>> black_red == black_blue
True
Here's how you could implement your unique comparison function::
>>> saturation_equivalence = lambda c1, c2: c1.saturation == c2.saturation
>>> red = Color("red", equality=saturation_equivalence)
>>> blue = Color("blue", equality=saturation_equivalence)
>>> white = Color("white", equality=saturation_equivalence)
>>> red == blue
True
>>> white == red
False
Note: When comparing 2 colors, *only* the equality function *of the first
color will be used*. Thus::
>>> black_red = Color("red", luminance=0, equality=RGB_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=HSL_equivalence)
>>> black_red == black_blue
True
But reverse operation is not equivalent !::
>>> black_blue == black_red
False
Equality to non-Colour objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As a side note, whatever your custom equality function is, it won't be
used if you compare to anything else than a ``Colour`` instance::
>>> red = Color("red", equality=lambda c1, c2: True)
>>> blue = Color("blue", equality=lambda c1, c2: True)
Note that these instances would compare as equal to any other color::
>>> red == blue
True
But on another non-Colour object::
>>> red == None
False
>>> red != None
True
Actually, ``Colour`` instances will, politely enough, leave
the other side of the equality have a chance to decide of the output,
(by executing its own ``__eq__``), so::
>>> class OtherColorImplem(object):
... def __init__(self, color):
... self.color = color
... def __eq__(self, other):
... return self.color == other.web
>>> alien_red = OtherColorImplem("red")
>>> red == alien_red
True
>>> blue == alien_red
False
And inequality (using ``__ne__``) are also polite::
>>> class AnotherColorImplem(OtherColorImplem):
... def __ne__(self, other):
... return self.color != other.web
>>> new_alien_red = AnotherColorImplem("red")
>>> red != new_alien_red
False
>>> blue != new_alien_red
True
Picking arbitrary color for a python object
-------------------------------------------
Basic Usage
~~~~~~~~~~~
Sometimes, you just want to pick a color for an object in your application
often to visually identify this object. Thus, the picked color should be the
same for same objects, and different for different object::
>>> foo = object()
>>> bar = object()
>>> Color(pick_for=foo) # doctest: +ELLIPSIS
<Color ...>
>>> Color(pick_for=foo) == Color(pick_for=foo)
True
>>> Color(pick_for=foo) == Color(pick_for=bar)
False
Of course, although there's a tiny probability that different strings yield the
same color, most of the time, different inputs will produce different colors.
Advanced Usage
~~~~~~~~~~~~~~
You can customize your color picking algorithm by providing a ``picker``. A
``picker`` is a callable that takes an object, and returns something that can
be instantiated as a color by ``Color``::
>>> my_picker = lambda obj: "red" if isinstance(obj, int) else "blue"
>>> Color(pick_for=3, picker=my_picker, pick_key=None)
<Color red>
>>> Color(pick_for="foo", picker=my_picker, pick_key=None)
<Color blue>
You might want to use a particular picker, but enforce how the picker will
identify two object as the same (or not). So there's a ``pick_key`` attribute
that is provided and defaults as equivalent of ``hash`` method and if hash is
not supported by your object, it'll default to the ``str`` of your object salted
with the class name.
Thus::
>>> class MyObj(str): pass
>>> my_obj_color = Color(pick_for=MyObj("foo"))
>>> my_str_color = Color(pick_for="foo")
>>> my_obj_color == my_str_color
False
Please make sure your object is hashable or "stringable" before using the
``RGB_color_picker`` picking mechanism or provide another color picker. Nearly
all python object are hashable by default so this shouldn't be an issue (e.g.
instances of ``object`` and subclasses are hashable).
Neither ``hash`` nor ``str`` are perfect solution. So feel free to use
``pick_key`` at ``Color`` instantiation time to set your way to identify
objects, for instance::
>>> a = object()
>>> b = object()
>>> Color(pick_for=a, pick_key=id) == Color(pick_for=b, pick_key=id)
False
When choosing a pick key, you should closely consider if you want your color
to be consistent between runs (this is NOT the case with the last example),
or consistent with the content of your object if it is a mutable object.
Default value of ``pick_key`` and ``picker`` ensures that the same color will
be attributed to same object between different run on different computer for
most python object.
Color factory
-------------
As you might have noticed, there are few attributes that you might want to see
attached to all of your colors as ``equality`` for equality comparison support,
or ``picker``, ``pick_key`` to configure your object color picker.
You can create a customized ``Color`` factory thanks to the ``make_color_factory``::
>>> from colour import make_color_factory, HSL_equivalence, RGB_color_picker
>>> get_color = make_color_factory(
... equality=HSL_equivalence,
... picker=RGB_color_picker,
... pick_key=str,
... )
All color created thanks to ``CustomColor`` class instead of the default one
would get the specified attributes by default::
>>> black_red = get_color("red", luminance=0)
>>> black_blue = get_color("blue", luminance=0)
Of course, these are always instances of ``Color`` class::
>>> isinstance(black_red, Color)
True
Equality was changed from normal defaults, so::
>>> black_red == black_blue
False
This because the default equivalence of ``Color`` was set to
``HSL_equivalence``.
Contributing
============
Any suggestion or issue is welcome. Push request are very welcome,
please check out the guidelines.
Push Request Guidelines
-----------------------
You can send any code. I'll look at it and will integrate it myself in
the code base and leave you as the author. This process can take time and
it'll take less time if you follow the following guidelines:
- check your code with PEP8 or pylint. Try to stick to 80 columns wide.
- separate your commits per smallest concern.
- each commit should pass the tests (to allow easy bisect)
- each functionality/bugfix commit should contain the code, tests,
and doc.
- prior minor commit with typographic or code cosmetic changes are
very welcome. These should be tagged in their commit summary with
``!minor``.
- the commit message should follow gitchangelog rules (check the git
log to get examples)
- if the commit fixes an issue or finished the implementation of a
feature, please mention it in the summary.
If you have some questions about guidelines which is not answered here,
please check the current ``git log``, you might find previous commit that
would show you how to deal with your issue.
License
=======
Copyright (c) 2012-2017 Valentin Lab.
Licensed under the `BSD License`_.
.. _BSD License: http://raw.github.com/vaab/colour/master/LICENSE
Changelog
=========
0.1.4 (2017-04-19)
------------------
Fix
~~~
- ``rgb2hsl`` would produce invalid hsl triplet when red, blue, green
component would be all very close to ``1.0``. (fixes #30) [Valentin
Lab]
Typically, saturation would shoot out of range 0.0..1.0. That could then
lead to exceptions being casts afterwards when trying to reconvert this
HSL triplet to RGB values.
0.1.3 (2017-04-08)
------------------
Fix
~~~
- Unexpected behavior with ``!=`` operator. (fixes #26) [Valentin Lab]
- Added mention of the ``hex_l`` property. (fixes #27) [Valentin Lab]
0.1.2 (2015-09-15)
------------------
Fix
~~~
- Support for corner case 1-wide ``range_to`` color scale. (fixes #18)
[Valentin Lab]
0.1.1 (2015-03-29)
------------------
Fix
~~~
- Avoid casting an exception when comparing to non-``Colour`` instances.
(fixes #14) [Riziq Sayegh]
0.0.6 (2014-11-18)
------------------
New
~~~
- Provide all missing *2* function by combination with other existing
ones (fixes #13). [Valentin Lab]
- Provide full access to any color name in HSL, RGB, HEX convenience
instances. [Valentin Lab]
Now you can call ``colour.HSL.cyan``, or ``colour.HEX.red`` for a direct encoding of
``human`` colour labels to the 3 representations.
0.0.5 (2013-09-16)
------------------
New
~~~
- Color names are case insensitive. [Chris Priest]
The color-name structure have their names capitalized. And color names
that are made of only one word will be displayed lowercased.
Fix
~~~
- Now using W3C color recommandation. [Chris Priest]
Was using X11 color scheme before, which is slightly different from
W3C web color specifications.
- Inconsistency in licence information (removed GPL mention). (fixes #8)
[Valentin Lab]
- Removed ``gitchangelog`` from ``setup.py`` require list. (fixes #9)
[Valentin Lab]
0.0.4 (2013-06-21)
------------------
New
~~~
- Added ``make_color_factory`` to customize some common color
attributes. [Valentin Lab]
- Pick color to identify any python object (fixes #6) [Jonathan Ballet]
- Equality support between colors, customizable if needed. (fixes #3)
[Valentin Lab]
0.0.3 (2013-06-19)
------------------
New
~~~
- Colour is now compatible with python3. [Ryan Leckey]
0.0.1 (2012-06-11)
------------------
- First import. [Valentin Lab]
TODO
====
- ANSI 16-color and 256-color escape sequence generation
- YUV, HSV, CMYK support

View File

@ -1,23 +0,0 @@
Copyright (c) 2012-2017, Valentin Lab
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,630 +0,0 @@
Metadata-Version: 2.0
Name: colour
Version: 0.1.5
Summary: converts and manipulates various color representation (HSL, RVB, web, X11, ...)
Home-page: http://github.com/vaab/colour
Author: Valentin LAB
Author-email: valentin.lab@kalysto.org
License: BSD 3-Clause License
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: BSD License
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Provides-Extra: test
Requires-Dist: nose; extra == 'test'
======
Colour
======
.. image:: http://img.shields.io/pypi/v/colour.svg?style=flat
:target: https://pypi.python.org/pypi/colour/
:alt: Latest PyPI version
.. image:: https://img.shields.io/pypi/l/gitchangelog.svg?style=flat
:target: https://github.com/vaab/gitchangelog/blob/master/LICENSE
:alt: License
.. image:: https://img.shields.io/pypi/pyversions/gitchangelog.svg?style=flat
:target: https://pypi.python.org/pypi/gitchangelog/
:alt: Compatible python versions
.. image:: http://img.shields.io/pypi/dm/colour.svg?style=flat
:target: https://pypi.python.org/pypi/colour/
:alt: Number of PyPI downloads
.. image:: http://img.shields.io/travis/vaab/colour/master.svg?style=flat
:target: https://travis-ci.org/vaab/colour/
:alt: Travis CI build status
.. image:: https://img.shields.io/appveyor/ci/vaab/colour.svg
:target: https://ci.appveyor.com/project/vaab/colour/branch/master
:alt: Appveyor CI build status
.. image:: http://img.shields.io/codecov/c/github/vaab/colour.svg?style=flat
:target: https://codecov.io/gh/vaab/colour/
:alt: Test coverage
Converts and manipulates common color representation (RGB, HSL, web, ...)
Feature
=======
- Damn simple and pythonic way to manipulate color representation (see
examples below)
- Full conversion between RGB, HSL, 6-digit hex, 3-digit hex, human color
- One object (``Color``) or bunch of single purpose function (``rgb2hex``,
``hsl2rgb`` ...)
- ``web`` format that use the smallest representation between
6-digit (e.g. ``#fa3b2c``), 3-digit (e.g. ``#fbb``), fully spelled
color (e.g. ``white``), following `W3C color naming`_ for compatible
CSS or HTML color specifications.
- smooth intuitive color scale generation choosing N color gradients.
- can pick colors for you to identify objects of your application.
.. _W3C color naming: http://www.w3.org/TR/css3-color/#svg-color
Installation
============
You don't need to download the GIT version of the code as ``colour`` is
available on the PyPI. So you should be able to run::
pip install colour
If you have downloaded the GIT sources, then you could add the ``colour.py``
directly to one of your ``site-packages`` (thanks to a symlink). Or install
the current version via traditional::
python setup.py install
And if you don't have the GIT sources but would like to get the latest
master or branch from github, you could also::
pip install git+https://github.com/vaab/colour
Or even select a specific revision (branch/tag/commit)::
pip install git+https://github.com/vaab/colour@master
Usage
=====
To get complete demo of each function, please read the source code which is
heavily documented and provide a lot of examples in doctest format.
Here is a reduced sample of a common usage scenario:
Instantiation
-------------
Let's create blue color::
>>> from colour import Color
>>> c = Color("blue")
>>> c
<Color blue>
Please note that all of these are equivalent examples to create the red color::
Color("red") ## human, web compatible representation
Color(red=1) ## default amount of blue and green is 0.0
Color("blue", hue=0) ## hue of blue is 0.66, hue of red is 0.0
Color("#f00") ## standard 3 hex digit web compatible representation
Color("#ff0000") ## standard 6 hex digit web compatible representation
Color(hue=0, saturation=1, luminance=0.5)
Color(hsl=(0, 1, 0.5)) ## full 3-uple HSL specification
Color(rgb=(1, 0, 0)) ## full 3-uple RGB specification
Color(Color("red")) ## recursion doesn't break object
Reading values
--------------
Several representations are accessible::
>>> c.hex
'#00f'
>>> c.hsl # doctest: +ELLIPSIS
(0.66..., 1.0, 0.5)
>>> c.rgb
(0.0, 0.0, 1.0)
And their different parts are also independently accessible, as the different
amount of red, blue, green, in the RGB format::
>>> c.red
0.0
>>> c.blue
1.0
>>> c.green
0.0
Or the hue, saturation and luminance of the HSL representation::
>>> c.hue # doctest: +ELLIPSIS
0.66...
>>> c.saturation
1.0
>>> c.luminance
0.5
A note on the ``.hex`` property, it'll return the smallest valid value
when possible. If you are only interested by the long value, use
``.hex_l``::
>>> c.hex_l
'#0000ff'
Modifying color objects
-----------------------
All of these properties are read/write, so let's add some red to this color::
>>> c.red = 1
>>> c
<Color magenta>
We might want to de-saturate this color::
>>> c.saturation = 0.5
>>> c
<Color #bf40bf>
And of course, the string conversion will give the web representation which is
human, or 3-digit, or 6-digit hex representation depending which is usable::
>>> "%s" % c
'#bf40bf'
>>> c.luminance = 1
>>> "%s" % c
'white'
Ranges of colors
----------------
You can get some color scale of variation between two ``Color`` objects quite
easily. Here, is the color scale of the rainbow between red and blue::
>>> red = Color("red")
>>> blue = Color("blue")
>>> list(red.range_to(blue, 5))
[<Color red>, <Color yellow>, <Color lime>, <Color cyan>, <Color blue>]
Or the different amount of gray between black and white::
>>> black = Color("black")
>>> white = Color("white")
>>> list(black.range_to(white, 6))
[<Color black>, <Color #333>, <Color #666>, <Color #999>, <Color #ccc>, <Color white>]
If you have to create graphical representation with color scale
between red and green ('lime' color is full green)::
>>> lime = Color("lime")
>>> list(red.range_to(lime, 5))
[<Color red>, <Color #ff7f00>, <Color yellow>, <Color chartreuse>, <Color lime>]
Notice how naturally, the yellow is displayed in human format and in
the middle of the scale. And that the quite unusual (but compatible)
'chartreuse' color specification has been used in place of the
hexadecimal representation.
Color comparison
----------------
Sane default
~~~~~~~~~~~~
Color comparison is a vast subject. However, it might seem quite straightforward for
you. ``Colour`` uses a configurable default way of comparing color that might suit
your needs::
>>> Color("red") == Color("#f00") == Color("blue", hue=0)
True
The default comparison algorithm focuses only on the "web" representation which is
equivalent to comparing the long hex representation (e.g. #FF0000) or to be more
specific, it is equivalent to compare the amount of red, green, and blue composition
of the RGB representation, each of these value being quantized to a 256 value scale.
This default comparison is a practical and convenient way to measure the actual
color equivalence on your screen, or in your video card memory.
But this comparison wouldn't make the difference between a black red, and a
black blue, which both are black::
>>> black_red = Color("red", luminance=0)
>>> black_blue = Color("blue", luminance=0)
>>> black_red == black_blue
True
Customization
~~~~~~~~~~~~~
But, this is not the sole way to compare two colors. As I'm quite lazy, I'm providing
you a way to customize it to your needs. Thus::
>>> from colour import RGB_equivalence, HSL_equivalence
>>> black_red = Color("red", luminance=0, equality=HSL_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=HSL_equivalence)
>>> black_red == black_blue
False
As you might have already guessed, the sane default is ``RGB_equivalence``, so::
>>> black_red = Color("red", luminance=0, equality=RGB_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=RGB_equivalence)
>>> black_red == black_blue
True
Here's how you could implement your unique comparison function::
>>> saturation_equivalence = lambda c1, c2: c1.saturation == c2.saturation
>>> red = Color("red", equality=saturation_equivalence)
>>> blue = Color("blue", equality=saturation_equivalence)
>>> white = Color("white", equality=saturation_equivalence)
>>> red == blue
True
>>> white == red
False
Note: When comparing 2 colors, *only* the equality function *of the first
color will be used*. Thus::
>>> black_red = Color("red", luminance=0, equality=RGB_equivalence)
>>> black_blue = Color("blue", luminance=0, equality=HSL_equivalence)
>>> black_red == black_blue
True
But reverse operation is not equivalent !::
>>> black_blue == black_red
False
Equality to non-Colour objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As a side note, whatever your custom equality function is, it won't be
used if you compare to anything else than a ``Colour`` instance::
>>> red = Color("red", equality=lambda c1, c2: True)
>>> blue = Color("blue", equality=lambda c1, c2: True)
Note that these instances would compare as equal to any other color::
>>> red == blue
True
But on another non-Colour object::
>>> red == None
False
>>> red != None
True
Actually, ``Colour`` instances will, politely enough, leave
the other side of the equality have a chance to decide of the output,
(by executing its own ``__eq__``), so::
>>> class OtherColorImplem(object):
... def __init__(self, color):
... self.color = color
... def __eq__(self, other):
... return self.color == other.web
>>> alien_red = OtherColorImplem("red")
>>> red == alien_red
True
>>> blue == alien_red
False
And inequality (using ``__ne__``) are also polite::
>>> class AnotherColorImplem(OtherColorImplem):
... def __ne__(self, other):
... return self.color != other.web
>>> new_alien_red = AnotherColorImplem("red")
>>> red != new_alien_red
False
>>> blue != new_alien_red
True
Picking arbitrary color for a python object
-------------------------------------------
Basic Usage
~~~~~~~~~~~
Sometimes, you just want to pick a color for an object in your application
often to visually identify this object. Thus, the picked color should be the
same for same objects, and different for different object::
>>> foo = object()
>>> bar = object()
>>> Color(pick_for=foo) # doctest: +ELLIPSIS
<Color ...>
>>> Color(pick_for=foo) == Color(pick_for=foo)
True
>>> Color(pick_for=foo) == Color(pick_for=bar)
False
Of course, although there's a tiny probability that different strings yield the
same color, most of the time, different inputs will produce different colors.
Advanced Usage
~~~~~~~~~~~~~~
You can customize your color picking algorithm by providing a ``picker``. A
``picker`` is a callable that takes an object, and returns something that can
be instantiated as a color by ``Color``::
>>> my_picker = lambda obj: "red" if isinstance(obj, int) else "blue"
>>> Color(pick_for=3, picker=my_picker, pick_key=None)
<Color red>
>>> Color(pick_for="foo", picker=my_picker, pick_key=None)
<Color blue>
You might want to use a particular picker, but enforce how the picker will
identify two object as the same (or not). So there's a ``pick_key`` attribute
that is provided and defaults as equivalent of ``hash`` method and if hash is
not supported by your object, it'll default to the ``str`` of your object salted
with the class name.
Thus::
>>> class MyObj(str): pass
>>> my_obj_color = Color(pick_for=MyObj("foo"))
>>> my_str_color = Color(pick_for="foo")
>>> my_obj_color == my_str_color
False
Please make sure your object is hashable or "stringable" before using the
``RGB_color_picker`` picking mechanism or provide another color picker. Nearly
all python object are hashable by default so this shouldn't be an issue (e.g.
instances of ``object`` and subclasses are hashable).
Neither ``hash`` nor ``str`` are perfect solution. So feel free to use
``pick_key`` at ``Color`` instantiation time to set your way to identify
objects, for instance::
>>> a = object()
>>> b = object()
>>> Color(pick_for=a, pick_key=id) == Color(pick_for=b, pick_key=id)
False
When choosing a pick key, you should closely consider if you want your color
to be consistent between runs (this is NOT the case with the last example),
or consistent with the content of your object if it is a mutable object.
Default value of ``pick_key`` and ``picker`` ensures that the same color will
be attributed to same object between different run on different computer for
most python object.
Color factory
-------------
As you might have noticed, there are few attributes that you might want to see
attached to all of your colors as ``equality`` for equality comparison support,
or ``picker``, ``pick_key`` to configure your object color picker.
You can create a customized ``Color`` factory thanks to the ``make_color_factory``::
>>> from colour import make_color_factory, HSL_equivalence, RGB_color_picker
>>> get_color = make_color_factory(
... equality=HSL_equivalence,
... picker=RGB_color_picker,
... pick_key=str,
... )
All color created thanks to ``CustomColor`` class instead of the default one
would get the specified attributes by default::
>>> black_red = get_color("red", luminance=0)
>>> black_blue = get_color("blue", luminance=0)
Of course, these are always instances of ``Color`` class::
>>> isinstance(black_red, Color)
True
Equality was changed from normal defaults, so::
>>> black_red == black_blue
False
This because the default equivalence of ``Color`` was set to
``HSL_equivalence``.
Contributing
============
Any suggestion or issue is welcome. Push request are very welcome,
please check out the guidelines.
Push Request Guidelines
-----------------------
You can send any code. I'll look at it and will integrate it myself in
the code base and leave you as the author. This process can take time and
it'll take less time if you follow the following guidelines:
- check your code with PEP8 or pylint. Try to stick to 80 columns wide.
- separate your commits per smallest concern.
- each commit should pass the tests (to allow easy bisect)
- each functionality/bugfix commit should contain the code, tests,
and doc.
- prior minor commit with typographic or code cosmetic changes are
very welcome. These should be tagged in their commit summary with
``!minor``.
- the commit message should follow gitchangelog rules (check the git
log to get examples)
- if the commit fixes an issue or finished the implementation of a
feature, please mention it in the summary.
If you have some questions about guidelines which is not answered here,
please check the current ``git log``, you might find previous commit that
would show you how to deal with your issue.
License
=======
Copyright (c) 2012-2017 Valentin Lab.
Licensed under the `BSD License`_.
.. _BSD License: http://raw.github.com/vaab/colour/master/LICENSE
Changelog
=========
0.1.4 (2017-04-19)
------------------
Fix
~~~
- ``rgb2hsl`` would produce invalid hsl triplet when red, blue, green
component would be all very close to ``1.0``. (fixes #30) [Valentin
Lab]
Typically, saturation would shoot out of range 0.0..1.0. That could then
lead to exceptions being casts afterwards when trying to reconvert this
HSL triplet to RGB values.
0.1.3 (2017-04-08)
------------------
Fix
~~~
- Unexpected behavior with ``!=`` operator. (fixes #26) [Valentin Lab]
- Added mention of the ``hex_l`` property. (fixes #27) [Valentin Lab]
0.1.2 (2015-09-15)
------------------
Fix
~~~
- Support for corner case 1-wide ``range_to`` color scale. (fixes #18)
[Valentin Lab]
0.1.1 (2015-03-29)
------------------
Fix
~~~
- Avoid casting an exception when comparing to non-``Colour`` instances.
(fixes #14) [Riziq Sayegh]
0.0.6 (2014-11-18)
------------------
New
~~~
- Provide all missing *2* function by combination with other existing
ones (fixes #13). [Valentin Lab]
- Provide full access to any color name in HSL, RGB, HEX convenience
instances. [Valentin Lab]
Now you can call ``colour.HSL.cyan``, or ``colour.HEX.red`` for a direct encoding of
``human`` colour labels to the 3 representations.
0.0.5 (2013-09-16)
------------------
New
~~~
- Color names are case insensitive. [Chris Priest]
The color-name structure have their names capitalized. And color names
that are made of only one word will be displayed lowercased.
Fix
~~~
- Now using W3C color recommandation. [Chris Priest]
Was using X11 color scheme before, which is slightly different from
W3C web color specifications.
- Inconsistency in licence information (removed GPL mention). (fixes #8)
[Valentin Lab]
- Removed ``gitchangelog`` from ``setup.py`` require list. (fixes #9)
[Valentin Lab]
0.0.4 (2013-06-21)
------------------
New
~~~
- Added ``make_color_factory`` to customize some common color
attributes. [Valentin Lab]
- Pick color to identify any python object (fixes #6) [Jonathan Ballet]
- Equality support between colors, customizable if needed. (fixes #3)
[Valentin Lab]
0.0.3 (2013-06-19)
------------------
New
~~~
- Colour is now compatible with python3. [Ryan Leckey]
0.0.1 (2012-06-11)
------------------
- First import. [Valentin Lab]
TODO
====
- ANSI 16-color and 256-color escape sequence generation
- YUV, HSV, CMYK support

View File

@ -1,11 +0,0 @@
colour-0.1.5.dist-info/DESCRIPTION.rst,sha256=hPBkXALLft1zfCftLr--oylXrhnxFIXvRVk3ga--bK8,17325
colour-0.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
colour-0.1.5.dist-info/LICENSE.txt,sha256=jwhqMkd7-dP7p3VMptM6CpLlsx0DZfmEdg3GigdOQng,1304
colour-0.1.5.dist-info/METADATA,sha256=1dIO3yNuvoAan1hBE9_y766TbXs9cKRjEuIUI92nUhE,18273
colour-0.1.5.dist-info/RECORD,,
colour-0.1.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
colour-0.1.5.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
colour-0.1.5.dist-info/metadata.json,sha256=OviC7TmmKwtRUjauJE5IrjD9kyga_JzrCUZQri6L1xo,1090
colour-0.1.5.dist-info/top_level.txt,sha256=XKC-SRGJZMW4pzXxl1NWLlZYzRFz7JuWiOJSkQfrWOE,7
colour.py,sha256=GNhLVa29gX2sIT4h79BbnyEJeZDa7uSRt49Cz1zJL_U,28693
colour.pyc,,

View File

@ -1,6 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@ -1 +0,0 @@
{"classifiers": ["Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", "Development Status :: 3 - Alpha", "License :: OSI Approved :: BSD License", "Intended Audience :: Developers", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6"], "extensions": {"python.details": {"contacts": [{"email": "valentin.lab@kalysto.org", "name": "Valentin LAB", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "http://github.com/vaab/colour"}}}, "extras": ["test"], "generator": "bdist_wheel (0.29.0)", "license": "BSD 3-Clause License", "metadata_version": "2.0", "name": "colour", "run_requires": [{"extra": "test", "requires": ["nose"]}], "summary": "converts and manipulates various color representation (HSL, RVB, web, X11, ...)", "version": "0.1.5"}

View File

@ -1,18 +0,0 @@
Metadata-Version: 2.1
Name: deezeridu
Version: 0.0.2
Summary: Downloads songs, albums or playlists from deezer
Home-page: https://github.com/OpenJarbas/deezeridu
Author: An0nimia
License: CC BY-NC-SA 4.0
Platform: UNKNOWN
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: mutagen
Requires-Dist: pycryptodomex
Requires-Dist: requests
Requires-Dist: tqdm
Requires-Dist: json-database (>=0.5.6)
UNKNOWN

View File

@ -1,34 +0,0 @@
deezeridu-0.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
deezeridu-0.0.2.dist-info/METADATA,sha256=JBjeAZnVtVZvYgmfElDjDUGdS9EjEwqxaiJGE541aH8,430
deezeridu-0.0.2.dist-info/RECORD,,
deezeridu-0.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
deezeridu-0.0.2.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
deezeridu-0.0.2.dist-info/top_level.txt,sha256=5OOrodHgvewBBzVAiYzCWOzPv55J_lxWSYD_H86uoaM,27
deezeridu/__init__.py,sha256=JLrWou6ghlPaCCvudqUMt0WLXNAYcLMkgd6ALyff4xY,9462
deezeridu/__init__.pyc,,
deezeridu/api.py,sha256=YRdBnqu4KMgDFyXM1Zutlkux-tREtq10-tkPdssB8Tc,6116
deezeridu/api.pyc,,
deezeridu/download.py,sha256=RH9JgXWiKMx3vWeAOL44kG4AjmTUHnaWwAG88_isdCs,16473
deezeridu/download.pyc,,
deezeridu/download_utils.py,sha256=fpJJvy5WCYR3jeN23yLZ73_nametlWNUTH5kEePAGeQ,1850
deezeridu/download_utils.pyc,,
deezeridu/exceptions.py,sha256=FyXJME7bFs0MpksfHDtbStdegMyTu3I5_V_ttPYsCmA,2152
deezeridu/exceptions.pyc,,
deezeridu/gateway.py,sha256=Hv2gqK3qaKFMiJIPbHF98eTbKU4WL_zMSuuxOR0DhGY,7321
deezeridu/gateway.pyc,,
deezeridu/models/__init__.py,sha256=MKWdOOYUJVhWjWAUvk7wEm5jC88QCRIFK5oHbDePIKw,138
deezeridu/models/__init__.pyc,,
deezeridu/models/album.py,sha256=mcaZDyZQNhpPyNpHzkYZt05u0oZJLJFiOIDBoHM_vnI,504
deezeridu/models/album.pyc,,
deezeridu/models/playlist.py,sha256=z37DLc6-lQiXthtJz0kAwxTG3wlc8ZosiL9aRo0VpLk,220
deezeridu/models/playlist.pyc,,
deezeridu/models/preferences.py,sha256=UOl-6T9tgXueV_kG5qboF_G4I-Znrne6ctZ8km9OoXw,424
deezeridu/models/preferences.pyc,,
deezeridu/models/track.py,sha256=ePJ60dobaI141N-BtIP83jUr8MxOgTU1xyQ665w5rVQ,1644
deezeridu/models/track.pyc,,
deezeridu/settings.py,sha256=ZOURy_oQ-lWQWe41Oyx-ED1wpz9zZBidnQgFpf13XWg,571
deezeridu/settings.pyc,,
deezeridu/taggers.py,sha256=-iPciR31q9qNrF_QXo3fPmaUCMxoWr8bP_DY3RS9_Zg,4359
deezeridu/taggers.pyc,,
deezeridu/utils.py,sha256=L9PxpvqbfEUMccFBvy1B1fplsm7cU9uU_TKV5HnxNuM,5432
deezeridu/utils.pyc,,

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.34.2)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1,301 +0,0 @@
#!/usr/bin/python3
from .api import API
from .gateway import Gateway
from .download import (
TrackDownloader, AlbumDownloader, PlaylistDownloader,
DownloaderJob
)
from .utils import (
create_zip, get_ids, link_is_valid,
what_kind, convert_to_date
)
from .exceptions import (
InvalidLink, TrackNotFound,
NoDataApi, AlbumNotFound, CredentialsMissing
)
from .models import (
Track, Album, Playlist,
Preferences
)
from json_database import JsonConfigXDG
class Deezer:
def __init__(
self,
arl=None,
email=None,
password=None
):
if arl:
self.__gw_api = Gateway(arl=arl)
else:
if not email or not password:
creds = JsonConfigXDG("deezer", subfolder="deezeridu")
email = creds.get("email")
password = creds.get("password")
if not email or not password:
raise CredentialsMissing
self.__gw_api = Gateway(
email=email,
password=password
)
self.__api = API()
self.__download_job = DownloaderJob(self.__api, self.__gw_api)
def download_track(
self, link_track,
output_dir,
quality_download="MP3_320",
recursive_quality=False,
recursive_download=False,
not_interface=False,
method_save=2
) -> Track:
link_is_valid(link_track)
ids = get_ids(link_track)
try:
song_metadata = self.__api.tracking(ids)
except NoDataApi:
infos = self.__gw_api.get_song_data(ids)
if not "FALLBACK" in infos:
raise TrackNotFound(link_track)
ids = infos['FALLBACK']['SNG_ID']
song_metadata = self.__api.tracking(ids)
preferences = Preferences()
preferences.link = link_track
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
track = TrackDownloader(preferences, self.__download_job).dw()
return track
def download_album(
self, link_album,
output_dir,
quality_download="MP3_320",
recursive_quality=False,
recursive_download=False,
not_interface=False,
make_zip=False,
method_save=2
) -> Album:
link_is_valid(link_album)
ids = get_ids(link_album)
try:
album_json = self.__api.get_album(ids)
except NoDataApi:
raise AlbumNotFound(link_album)
song_metadata = {
"music": [],
"artist": [],
"tracknum": [],
"discnum": [],
"bpm": [],
"duration": [],
"isrc": [],
"gain": [],
"album": album_json['title'],
"label": album_json['label'],
"year": convert_to_date(album_json['release_date']),
"upc": album_json['upc'],
"nb_tracks": album_json['nb_tracks']
}
genres = []
if "genres" in album_json:
for a in album_json['genres']['data']:
genres.append(a['name'])
song_metadata['genre'] = " & ".join(genres)
ar_album = []
for a in album_json['contributors']:
if a['role'] == "Main":
ar_album.append(a['name'])
song_metadata['ar_album'] = " & ".join(ar_album)
sm_items = song_metadata.items()
for track in album_json['tracks']['data']:
c_ids = track['id']
detas = self.__api.tracking(c_ids, album=True)
for key, item in sm_items:
if type(item) is list:
song_metadata[key].append(detas[key])
preferences = Preferences()
preferences.link = link_album
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.json_data = album_json
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.make_zip = make_zip
album = AlbumDownloader(preferences, self.__download_job).dw()
return album
def download_playlist(
self, link_playlist,
output_dir,
quality_download="MP3_320",
recursive_quality=False,
recursive_download=False,
not_interface=False,
make_zip=False,
method_save=2
) -> Playlist:
link_is_valid(link_playlist)
ids = get_ids(link_playlist)
song_metadata = []
playlist_json = self.__api.get_playlist(ids)
for track in playlist_json['tracks']['data']:
c_ids = track['id']
try:
c_song_metadata = self.__api.tracking(c_ids)
except NoDataApi:
infos = self.__gw_api.get_song_data(c_ids)
if not "FALLBACK" in infos:
c_song_metadata = f"{track['title']} - {track['artist']['name']}"
else:
c_song_metadata = self.__api.tracking(c_ids)
song_metadata.append(c_song_metadata)
preferences = Preferences()
preferences.link = link_playlist
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.json_data = playlist_json
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.make_zip = make_zip
playlist = PlaylistDownloader(preferences, self.__download_job).dw()
return playlist
def download_artist_toptracks(
self, link_artist,
output_dir,
quality_download="MP3_320",
recursive_quality=False,
recursive_download=False,
not_interface=False
):
link_is_valid(link_artist)
ids = get_ids(link_artist)
playlist_json = self.__api.get_artist_top_tracks(ids)['data']
names = [
self.download_track(
track['link'], output_dir,
quality_download, recursive_quality,
recursive_download, not_interface
)
for track in playlist_json
]
return Playlist(names)
def download(
self, link,
output_dir,
quality_download="MP3_320",
recursive_quality=False,
recursive_download=False,
not_interface=False,
make_zip=False,
method_save=2
):
link_is_valid(link)
link = what_kind(link)
if "first_result/" in link or "track/" in link:
return self.download_track(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
method_save=2
)
elif "album/" in link:
return self.download_album(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
make_zip=make_zip,
method_save=2
)
elif "artist/" in link:
return self.download_artist_toptracks(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface
)
elif "playlist/" in link:
return self.download_playlist(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
make_zip=make_zip,
method_save=2
)
smart.type = "playlist"
smart._playlist = playlist
raise InvalidLink(link)

View File

@ -1,212 +0,0 @@
#!/usr/bin/python3
from time import sleep
from requests import get as req_get
from .settings import header
from .utils import artist_sort, convert_to_date
from .exceptions import (
NoDataApi, QuotaExceeded, TrackNotFound
)
class API:
def __init__(self):
self.__api_link = "https://api.deezer.com/"
self.__cover = "https://e-cdns-images.dzcdn.net/images/cover/%s/{}-000000-80-0-0.jpg"
def __get_api(self, url, quota_exceeded=False):
json = req_get(url, headers=header).json()
if "error" in json:
if json['error']['message'] == "no data":
raise NoDataApi("No data avalaible :(")
elif json['error']['message'] == "Quota limit exceeded":
if not quota_exceeded:
sleep(0.8)
json = self.__get_api(url, True)
else:
raise QuotaExceeded
return json
def get_chart(self, index=0):
url = f"{self.__api_link}chart/{index}"
infos = self.__get_api(url)
return infos
def get_track(self, ids):
url = f"{self.__api_link}track/{ids}"
infos = self.__get_api(url)
return infos
def get_album(self, ids):
url = f"{self.__api_link}album/{ids}"
infos = self.__get_api(url)
return infos
def get_playlist(self, ids):
url = f"{self.__api_link}playlist/{ids}"
infos = self.__get_api(url)
return infos
def get_artist(self, ids):
url = f"{self.__api_link}artist/{ids}"
infos = self.__get_api(url)
return infos
def get_artist_top_tracks(self, ids, limit=25):
url = f"{self.__api_link}artist/{ids}/top?limit={limit}"
infos = self.__get_api(url)
return infos
def get_artist_top_albums(self, ids, limit=25):
url = f"{self.__api_link}artist/{ids}/albums?limit={limit}"
infos = self.__get_api(url)
return infos
def get_artist_related(self, ids):
url = f"{self.__api_link}artist/{ids}/related"
infos = self.__get_api(url)
return infos
def get_artist_radio(self, ids):
url = f"{self.__api_link}artist/{ids}/radio"
infos = self.__get_api(url)
return infos
def get_artist_top_playlists(self, ids, limit=25):
url = f"{self.__api_link}artist/{ids}/playlists?limit={limit}"
infos = self.__get_api(url)
return infos
def search(self, query):
url = f"{self.__api_link}search/?q={query}"
infos = self.__get_api(url)
if infos['total'] == 0:
raise NoDataApi(query)
return infos
def search_track(self, query):
url = f"{self.__api_link}search/track/?q={query}"
infos = self.__get_api(url)
if infos['total'] == 0:
raise NoDataApi(query)
return infos
def search_album(self, query):
url = f"{self.__api_link}search/album/?q={query}"
infos = self.__get_api(url)
if infos['total'] == 0:
raise NoDataApi(query)
return infos
def search_playlist(self, query):
url = f"{self.__api_link}search/playlist/?q={query}"
infos = self.__get_api(url)
if infos['total'] == 0:
raise NoDataApi(query)
return infos
def search_artist(self, query):
url = f"{self.__api_link}search/artist/?q={query}"
infos = self.__get_api(url)
if infos['total'] == 0:
raise NoDataApi(query)
return infos
def not_found(self, song, title):
try:
data = self.search_track(song)['data']
except NoDataApi:
raise TrackNotFound(song)
ids = None
for track in data:
if (
track['title'] == title
) or (
title in track['title_short']
):
ids = track['id']
break
if not ids:
raise TrackNotFound(song)
return str(ids)
def get_img_url(self, md5_image, size="1200x1200"):
cover = self.__cover.format(size)
image_url = cover % md5_image
return image_url
def choose_img(self, md5_image, size="1200x1200"):
image_url = self.get_img_url(md5_image, size)
image = req_get(image_url).content
if len(image) == 13:
image_url = self.get_img_url("", size)
image = req_get(image_url).content
return image
def tracking(self, ids, album=False):
datas = {}
json_track = self.get_track(ids)
if not album:
album_ids = json_track['album']['id']
json_album = self.get_album(album_ids)
genres = []
if "genres" in json_album:
for genre in json_album['genres']['data']:
genres.append(genre['name'])
datas['genre'] = " & ".join(genres)
ar_album = []
for contributor in json_album['contributors']:
if contributor['role'] == "Main":
ar_album.append(contributor['name'])
datas['ar_album'] = " & ".join(ar_album)
datas['album'] = json_album['title']
datas['label'] = json_album['label']
datas['upc'] = json_album['upc']
datas['nb_tracks'] = json_album['nb_tracks']
datas['music'] = json_track['title']
array = []
for contributor in json_track['contributors']:
if contributor['name'] != "":
array.append(contributor['name'])
array.append(
json_track['artist']['name']
)
datas['artist'] = artist_sort(array)
datas['tracknum'] = json_track['track_position']
datas['discnum'] = json_track['disk_number']
datas['year'] = convert_to_date(json_track['release_date'])
datas['bpm'] = json_track['bpm']
datas['duration'] = json_track['duration']
datas['isrc'] = json_track['isrc']
datas['gain'] = json_track['gain']
return datas

View File

@ -1,519 +0,0 @@
#!/usr/bin/python3
from copy import deepcopy
from os.path import isfile
from tqdm import tqdm
from .api import API
from .gateway import Gateway
from .settings import qualities
from .download_utils import decryptfile, gen_song_hash
from .taggers import write_tags, check_track
from .utils import (
set_path, trasform_sync_lyric,
create_zip, check_track_ids,
check_track_md5, check_track_token
)
from .exceptions import (
TrackNotFound, NoRightOnMedia, QualityNotFound
)
from .models import (
Track, Album, Playlist,
Preferences,
)
class DownloaderJob:
def __init__(
self,
api: API,
gw_api: Gateway
) -> None:
self.api = api
self.gw_api = gw_api
def __get_url(
self,
c_track: Track,
quality_download: str
) -> dict:
c_md5, c_media_version = check_track_md5(c_track)
c_ids = check_track_ids(c_track)
n_quality = qualities[quality_download]['n_quality']
c_song_hash = gen_song_hash(
c_md5, n_quality,
c_ids, c_media_version
)
c_media_url = self.gw_api.get_song_url(c_md5[0], c_song_hash)
c_media_json = {
"media": [
{
"sources": [
{
"url": c_media_url
}
]
}
]
}
return c_media_json
def check_sources(
self,
infos_dw: list,
quality_download: str
) -> list:
tracks_token = [
check_track_token(c_track)
for c_track in infos_dw
]
try:
medias = self.gw_api.get_medias_url(tracks_token, quality_download)
for a in range(
len(medias)
):
if "errors" in medias[a]:
c_media_json = self.__get_url(infos_dw[a],
quality_download)
medias[a] = c_media_json
else:
if not medias[a]['media']:
c_media_json = self.__get_url(infos_dw[a],
quality_download)
medias[a] = c_media_json
except NoRightOnMedia:
medias = []
for c_track in infos_dw:
c_media_json = self.__get_url(c_track, quality_download)
medias.append(c_media_json)
return medias
class Downloader:
def __init__(
self,
infos_dw: dict,
preferences: Preferences,
download_job: DownloaderJob,
) -> None:
self.__download_job = download_job
self.__api = download_job.api
self.__gw_api = download_job.gw_api
self.__infos_dw = infos_dw
self.__ids = preferences.ids
self.__link = preferences.link
self.__output_dir = preferences.output_dir
self.__method_save = preferences.method_save
self.__song_metadata = preferences.song_metadata
self.__not_interface = preferences.not_interface
self.__quality_download = preferences.quality_download
self.__recursive_quality = preferences.recursive_quality
self.__recursive_download = preferences.recursive_download
self.__c_quality = qualities[self.__quality_download]
self.__set_quality()
self.__set_song_path()
def __set_quality(self) -> None:
self.__file_format = self.__c_quality['f_format']
self.__song_quality = self.__c_quality['s_quality']
def __set_song_path(self) -> None:
self.__song_path = set_path(
self.__song_metadata,
self.__output_dir,
self.__song_quality,
self.__file_format,
self.__method_save
)
def __write_track(self) -> None:
self.__set_song_path()
self.__c_track = Track(
self.__song_metadata, self.__song_path,
self.__file_format, self.__song_quality,
self.__link, self.__ids
)
def easy_dw(self) -> Track:
pic = self.__infos_dw['ALB_PICTURE']
image = self.__api.choose_img(pic)
self.__song_metadata['image'] = image
song = f"{self.__song_metadata['music']} - {self.__song_metadata['artist']}"
if not self.__not_interface:
print(f"Downloading: {song}")
try:
self.download_try()
except TrackNotFound:
try:
ids = self.__api.not_found(song, self.__song_metadata['music'])
self.__infos_dw = self.__gw_api.get_song_data(ids)
media = self.__download_job.check_sources(
[self.__infos_dw], self.__quality_download
)
self.__infos_dw['media_url'] = media[0]
self.download_try()
except TrackNotFound:
self.__c_track = Track(
self.__song_metadata,
None, None,
None, None, None,
)
self.__c_track.success = False
self.__c_track.md5_image = pic
return self.__c_track
def download_try(self) -> Track:
self.__c_track = Track(
self.__song_metadata, self.__song_path,
self.__file_format, self.__song_quality,
self.__link, self.__ids
)
if isfile(self.__song_path):
if check_track(self.__c_track):
return self.__c_track
media_list = self.__infos_dw['media_url']['media']
song_link = media_list[0]['sources'][0]['url']
try:
crypted_audio = self.__gw_api.song_exist(song_link)
except TrackNotFound:
song = self.__song_metadata['music']
artist = self.__song_metadata['artist']
msg = f"\n⚠ The {song} - {artist} can't be downloaded in {self.__quality_download} quality :( ⚠\n"
if not self.__recursive_quality:
raise QualityNotFound(msg=msg)
print(msg)
for c_quality in qualities:
if self.__quality_download == c_quality:
continue
print(
f"🛈 Trying to download {song} - {artist} in {c_quality}")
media = self.__download_job.check_sources(
[self.__infos_dw], c_quality
)
self.__infos_dw['media_url'] = media[0]
c_media = self.__infos_dw['media_url']
media_list = c_media['media']
song_link = media_list[0]['sources'][0]['url']
try:
crypted_audio = self.__gw_api.song_exist(song_link)
self.__c_quality = qualities[c_quality]
self.__set_quality()
break
except TrackNotFound:
if c_quality == "MP3_128":
raise TrackNotFound("Error with this song",
self.__link)
self.__write_track()
c_crypted_audio = crypted_audio.iter_content(2048)
c_ids = check_track_ids(self.__infos_dw)
self.__c_track.set_fallback_ids(c_ids)
decryptfile(
c_crypted_audio, c_ids, self.__song_path
)
self.__add_more_tags()
write_tags(self.__c_track)
return self.__c_track
def __add_more_tags(self) -> None:
contributors = self.__infos_dw['SNG_CONTRIBUTORS']
if "author" in contributors:
self.__song_metadata['author'] = " & ".join(
contributors['author']
)
else:
self.__song_metadata['author'] = ""
if "composer" in contributors:
self.__song_metadata['composer'] = " & ".join(
contributors['composer']
)
else:
self.__song_metadata['composer'] = ""
if "lyricist" in contributors:
self.__song_metadata['lyricist'] = " & ".join(
contributors['lyricist']
)
else:
self.__song_metadata['lyricist'] = ""
if "composerlyricist" in contributors:
self.__song_metadata['composer'] = " & ".join(
contributors['composerlyricist']
)
else:
self.__song_metadata['composerlyricist'] = ""
if "version" in self.__infos_dw:
self.__song_metadata['version'] = self.__infos_dw['VERSION']
else:
self.__song_metadata['version'] = ""
self.__song_metadata['lyric'] = ""
self.__song_metadata['copyright'] = ""
self.__song_metadata['lyricist'] = ""
self.__song_metadata['lyric_sync'] = []
if self.__infos_dw['LYRICS_ID'] != 0:
need = self.__gw_api.get_lyric(self.__ids)
if "LYRICS_SYNC_JSON" in need:
self.__song_metadata['lyric_sync'] = trasform_sync_lyric(
need['LYRICS_SYNC_JSON']
)
self.__song_metadata['lyric'] = need['LYRICS_TEXT']
self.__song_metadata['copyright'] = need['LYRICS_COPYRIGHTS']
self.__song_metadata['lyricist'] = need['LYRICS_WRITERS']
class TrackDownloader:
def __init__(
self,
preferences: Preferences,
download_job: DownloaderJob
) -> None:
self.__download_job = download_job
self.__gw_api = download_job.gw_api
self.__preferences = preferences
self.__ids = self.__preferences.ids
self.__song_metadata = self.__preferences.song_metadata
self.__quality_download = self.__preferences.quality_download
def dw(self) -> Track:
infos_dw = self.__gw_api.get_song_data(self.__ids)
media = self.__download_job.check_sources(
[infos_dw], self.__quality_download
)
infos_dw['media_url'] = media[0]
track = Downloader(
infos_dw, self.__preferences, self.__download_job,
).easy_dw()
if not track.success:
song = f"{self.__song_metadata['music']} - {self.__song_metadata['artist']}"
error_msg = f"Cannot download {song}"
raise TrackNotFound(message=error_msg)
return track
class AlbumDownloader:
def __init__(
self,
preferences: Preferences,
download_job: DownloaderJob
) -> None:
self.__api = download_job.api
self.__download_job = download_job
self.__gw_api = download_job.gw_api
self.__preferences = preferences
self.__ids = self.__preferences.ids
self.__make_zip = self.__preferences.make_zip
self.__output_dir = self.__preferences.output_dir
self.__method_save = self.__preferences.method_save
self.__song_metadata = self.__preferences.song_metadata
self.__not_interface = self.__preferences.not_interface
self.__quality_download = self.__preferences.quality_download
self.__song_metadata_items = self.__song_metadata.items()
def dw(self) -> Album:
infos_dw = self.__gw_api.get_album_data(self.__ids)['data']
md5_image = infos_dw[0]['ALB_PICTURE']
image = self.__api.choose_img(md5_image)
self.__song_metadata['image'] = image
album = Album(self.__ids)
album.image = image
album.md5_image = md5_image
album.nb_tracks = self.__song_metadata['nb_tracks']
album.album_name = self.__song_metadata['album']
album.upc = self.__song_metadata['upc']
tracks = album.tracks
medias = self.__download_job.check_sources(
infos_dw, self.__quality_download
)
c_song_metadata = {}
for key, item in self.__song_metadata_items:
if type(item) is not list:
c_song_metadata[key] = self.__song_metadata[key]
t = tqdm(
range(
len(infos_dw)
),
desc=c_song_metadata['album'],
disable=self.__not_interface
)
for a in t:
for key, item in self.__song_metadata_items:
if type(item) is list:
c_song_metadata[key] = self.__song_metadata[key][a]
c_infos_dw = infos_dw[a]
c_infos_dw['media_url'] = medias[a]
song = f"{c_song_metadata['music']} - {c_song_metadata['artist']}"
t.set_description_str(song)
c_preferences = deepcopy(self.__preferences)
c_preferences.song_metadata = c_song_metadata
c_preferences.ids = c_infos_dw['SNG_ID']
try:
track = Downloader(
c_infos_dw, c_preferences, self.__download_job
).download_try()
tracks.append(track)
except TrackNotFound:
try:
ids = self.__api.not_found(song, c_song_metadata['music'])
c_song_data = self.__gw_api.get_song_data(ids)
c_media = self.__download_job.check_sources(
[c_song_data], self.__quality_download
)
c_infos_dw['media_url'] = c_media[0]
track = Downloader(
c_infos_dw, c_preferences, self.__download_job
).download_try()
tracks.append(track)
except TrackNotFound:
track = Track(
c_song_metadata,
None, None,
None, None, None,
)
track.success = False
tracks.append(track)
print(f"Track not found: {song} :(")
continue
if self.__make_zip:
song_quality = tracks[0].quality
zip_name = create_zip(
tracks,
output_dir=self.__output_dir,
song_metadata=self.__song_metadata,
song_quality=song_quality,
method_save=self.__method_save
)
album.zip_path = zip_name
return album
class PlaylistDownloader:
def __init__(
self,
preferences: Preferences,
download_job: DownloaderJob
) -> None:
self.__download_job = download_job
self.__gw_api = download_job.gw_api
self.__preferences = preferences
self.__ids = self.__preferences.ids
self.__json_data = preferences.json_data
self.__make_zip = self.__preferences.make_zip
self.__output_dir = self.__preferences.output_dir
self.__song_metadata = self.__preferences.song_metadata
self.__quality_download = self.__preferences.quality_download
def dw(self) -> Playlist:
infos_dw = self.__gw_api.get_playlist_data(self.__ids)['data']
playlist = Playlist()
tracks = playlist.tracks
medias = self.__download_job.check_sources(
infos_dw, self.__quality_download
)
for c_infos_dw, c_media, c_song_metadata in zip(
infos_dw, medias, self.__song_metadata
):
c_infos_dw['media_url'] = c_media
c_preferences = deepcopy(self.__preferences)
c_preferences.ids = c_infos_dw['SNG_ID']
c_preferences.song_metadata = c_song_metadata
c_song_metadata = c_preferences.song_metadata
if type(c_song_metadata) is str:
print(f"Track not found {c_song_metadata} :(")
continue
track = Downloader(
c_infos_dw, c_preferences, self.__download_job
).easy_dw()
if not track.success:
song = f"{c_song_metadata['music']} - {c_song_metadata['artist']}"
print(f"Cannot download {song}")
tracks.append(track)
if self.__make_zip:
playlist_title = self.__json_data['title']
zip_name = f"{self.__output_dir}/{playlist_title} [playlist {self.__ids}]"
create_zip(tracks, zip_name=zip_name)
playlist.zip_path = zip_name
return playlist

View File

@ -1,99 +0,0 @@
#!/usr/bin/python3
from binascii import (
a2b_hex as __a2b_hex,
b2a_hex as __b2a_hex
)
from hashlib import md5 as __md5
from Cryptodome.Cipher.AES import (
new as __newAES,
MODE_ECB as __MODE_ECB
)
from Cryptodome.Cipher.Blowfish import (
new as __newBlowfish,
MODE_CBC as __MODE_CBC
)
__secret_key = "g4el58wc0zvf9na1"
__secret_key2 = b"jo6aey6haid2Teih"
__idk = __a2b_hex("0001020304050607")
def md5hex(data: str):
hashed = __md5(
data.encode()
).hexdigest()
return hashed
def gen_song_hash(md5, quality, ids, media):
data = b"\xa4".join(
a.encode()
for a in [
md5, quality, ids, media
]
)
hashed = (
__md5(data)
.hexdigest()
.encode()
)
data = b"\xa4".join(
[hashed, data]
) + b"\xa4"
if len(data) % 16:
data += b"\x00" * (16 - len(data) % 16)
c = __newAES(__secret_key2, __MODE_ECB)
media_url = __b2a_hex(
c.encrypt(data)
).decode()
return media_url
def __calcbfkey(songid):
h = md5hex(songid)
bfkey = "".join(
chr(
ord(h[i]) ^ ord(h[i + 16]) ^ ord(__secret_key[i])
)
for i in range(16)
)
return bfkey
def __blowfishDecrypt(data, key):
c = __newBlowfish(
key.encode(), __MODE_CBC, __idk
)
return c.decrypt(data)
def decryptfile(content, key, name):
key = __calcbfkey(key)
decrypted_audio = open(name, "wb")
seg = 0
for data in content:
if (
(seg % 3) == 0
) and (
len(data) == 2048
):
data = __blowfishDecrypt(data, key)
decrypted_audio.write(data)
seg += 1
decrypted_audio.close()

View File

@ -1,87 +0,0 @@
#!/usr/bin/python3
class TrackNotFound(Exception):
def __init__(self, url=None, message=None):
self.url = url
if not message:
self.message = f"Track {self.url} not found :("
else:
self.message = message
super().__init__(self.message)
class AlbumNotFound(Exception):
def __init__(self, url=None):
self.url = url
self.msg = f"Album {self.url} not found :("
super().__init__(self.msg)
class InvalidLink(Exception):
def __init__(self, url):
self.url = url
self.msg = f"Invalid Link {self.url} :("
super().__init__(self.msg)
class QuotaExceeded(Exception):
def __init__(self, message=None):
if not message:
self.message = "TOO MUCH REQUESTS LIMIT YOURSELF !!! :)"
super().__init__(self.message)
class QualityNotFound(Exception):
def __init__(self, quality=None, msg=None):
self.quality = quality
if not msg:
self.msg = (
f"The {quality} quality doesn't exist :)\
\nThe qualities have to be FLAC or MP3_320 or MP3_256 or MP3_128"
)
else:
self.msg = msg
super().__init__(self.msg)
class NoRightOnMedia(Exception):
def __init__(self, msg):
self.msg = msg
super().__init__(msg)
class NoDataApi(Exception):
def __init__(self, message):
super().__init__(message)
class BadCredentials(Exception):
def __init__(
self,
arl=None,
email=None,
password=None,
msg=None
):
if msg:
self.msg = msg
else:
self.arl = arl
self.email = email
self.password = password
if arl:
self.msg = f"Wrong token: {arl} :("
else:
self.msg = f"Wrong credentials email: {self.email}, password: {self.password}"
super().__init__(self.msg)
class CredentialsMissing(Exception):
""" Deezer credentials not set! """

View File

@ -1,279 +0,0 @@
#!/usr/bin/python3
from requests import Session
from requests import (
get as req_get,
post as req_post
)
from .settings import qualities
from .download_utils import md5hex
from .exceptions import (
BadCredentials, TrackNotFound, NoRightOnMedia
)
client_id = "172365"
client_secret = "fb0bec7ccc063dab0417eb7b0d847f34"
try_link = "https://api.deezer.com/platform/generic/track/3135556"
class Gateway:
def __init__(
self,
arl=None,
email=None,
password=None
):
self.__req = Session()
self.__arl = arl
self.__email = email
self.__password = password
self.__token = "null"
self.__get_lyric = "song.getLyrics"
self.__get_song_data = "song.getData"
self.__get_user_getArl = "user.getArl"
self.__get_page_track = "deezer.pageTrack"
self.__get_user_data = "deezer.getUserData"
self.__get_album_data = "song.getListByAlbum"
self.__get_playlist_data = "playlist.getSongs"
self.__get_media_url = "https://media.deezer.com/v1/get_url"
self.__get_auth_token_url = "https://api.deezer.com/auth/token"
self.__private_api_link = "https://www.deezer.com/ajax/gw-light.php"
self.__song_server = "https://e-cdns-proxy-{}.dzcdn.net/mobile/1/{}"
self.__refresh_token()
def __login(self):
if (
(not self.__arl) and
(not self.__email) and
(not self.__password)
):
msg = f"NO LOGIN STUFF INSERTED :)))"
raise BadCredentials(msg=msg)
if self.__arl:
self.__req.cookies['arl'] = self.__arl
else:
self.__get_arl()
def __get_arl(self):
access_token = self.__get_access_token()
c_headers = {
"Authorization": f"Bearer {access_token}"
}
self.__req.get(try_link, headers=c_headers).json()
arl = self.__get_api(self.__get_user_getArl)
self.__req.cookies.get("sid")
self.__arl = arl
def __get_access_token(self):
password = md5hex(self.__password)
request_hash = md5hex(
"".join(
[
client_id, self.__email, password, client_secret
]
)
)
params = {
"app_id": client_id,
"login": self.__email,
"password": password,
"hash": request_hash
}
results = req_get(self.__get_auth_token_url, params=params).json()
if "error" in results:
raise BadCredentials(
email=self.__email,
password=self.__password
)
access_token = results['access_token']
return access_token
def __cool_api(self):
guest_sid = self.__req.cookies.get("sid")
url = "https://api.deezer.com/1.0/gateway.php"
params = {
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': guest_sid,
'input': '3',
'output': '3',
'method': 'song_getData'
}
json = {'sng_id': 302127}
json = req_post(url, params=params, json=json).json()
print(json)
def __get_api(
self, method,
json_data=None
):
params = {
"api_version": "1.0",
"api_token": self.__token,
"input": "3",
"method": method
}
results = self.__req.post(
self.__private_api_link,
params=params,
json=json_data
).json()['results']
if not results:
self.__refresh_token()
self.__get_api(method, json_data)
return results
def get_user(self):
data = self.__get_api(self.__get_user_data)
return data
def __refresh_token(self):
self.__req.cookies.clear_session_cookies()
if not self.amIlog():
self.__login()
self.am_I_log()
data = self.get_user()
self.__token = data['checkForm']
self.__license_token = self.__get_license_token()
def __get_license_token(self):
data = self.get_user()
license_token = data['USER']['OPTIONS']['license_token']
return license_token
def amIlog(self):
data = self.get_user()
user_id = data['USER']['USER_ID']
is_logged = False
if user_id != 0:
is_logged = True
return is_logged
def am_I_log(self):
if not self.amIlog():
raise BadCredentials(arl=self.__arl)
def get_song_data(self, ids):
json_data = {
"sng_id": ids
}
infos = self.__get_api(self.__get_song_data, json_data)
return infos
def get_album_data(self, ids):
json_data = {
"alb_id": ids,
"nb": -1
}
infos = self.__get_api(self.__get_album_data, json_data)
return infos
def get_lyric(self, ids):
json_data = {
"sng_id": ids
}
infos = self.__get_api(self.__get_lyric, json_data)
return infos
def get_playlist_data(self, ids):
json_data = {
"playlist_id": ids,
"nb": -1
}
infos = self.__get_api(self.__get_playlist_data, json_data)
return infos
def get_page_track(self, ids):
json_data = {
"sng_id": ids
}
infos = self.__get_api(self.__get_page_track, json_data)
return infos
def get_song_url(self, n, song_hash):
song_url = self.__song_server.format(n, song_hash)
return song_url
def song_exist(self, song_url):
crypted_audio = req_get(song_url)
if len(crypted_audio.content) == 0:
raise TrackNotFound
return crypted_audio
def get_medias_url(self, tracks_token, quality):
others_qualities = []
for c_quality in qualities:
if c_quality == quality:
continue
c_quality_set = {
"cipher": "BF_CBC_STRIPE",
"format": c_quality
}
others_qualities.append(c_quality_set)
json_data = {
"license_token": self.__license_token,
"media": [
{
"type": "FULL",
"formats": [
{
"cipher": "BF_CBC_STRIPE",
"format": quality
}
] # + others_qualities
}
],
"track_tokens": tracks_token
}
infos = req_post(
self.__get_media_url,
json=json_data
).json()
if "errors" in infos:
msg = infos['errors'][0]['message']
raise NoRightOnMedia(msg)
medias = infos['data']
return medias

View File

@ -1,6 +0,0 @@
#!/usr/bin/python3
from .album import Album
from .playlist import Playlist
from .preferences import Preferences
from .track import Track

View File

@ -1,22 +0,0 @@
#!/usr/bin/python3
class Album:
def __init__(self, ids: int) -> None:
self.__t_list = []
self.zip_path = None
self.image = None
self.album_quality = None
self.md5_image = None
self.ids = ids
self.nb_tracks = None
self.album_name = None
self.upc = None
self.__set_album_md5()
@property
def tracks(self):
return self.__t_list
def __set_album_md5(self):
self.album_md5 = f"album/{self.ids}"

View File

@ -1,11 +0,0 @@
#!/usr/bin/python3
class Playlist:
def __init__(self, tracklist=None) -> None:
self.__t_list = tracklist or []
self.zip_path = None
@property
def tracks(self):
return self.__t_list

View File

@ -1,15 +0,0 @@
#!/usr/bin/python3
class Preferences:
def __init__(self) -> None:
self.link = None
self.song_metadata = None
self.quality_download = None
self.output_dir = None
self.ids = None
self.json_data = None
self.recursive_quality = None
self.recursive_download = None
self.not_interface = None
self.method_save = None
self.make_zip = None

View File

@ -1,61 +0,0 @@
#!/usr/bin/python3
from tempfile import gettempdir
from os.path import isfile
class Track:
def __init__(
self,
tags: dict,
song_path: str,
file_format: str,
quality: str,
link: str,
ids: int
) -> None:
self.tags = tags
self.__set_tags()
self.song_name = f"{self.music} - {self.artist}"
self.song_path = song_path
self.file_format = file_format
self.quality = quality
self.link = link
self.ids = ids
self.md5_image = None
self.success = True
self.__set_track_md5()
@property
def image_path(self):
path = self.song_path + ".jpg"
if not isfile(path):
try:
with open(path, "wb") as f:
f.write(self.tags["image"])
except:
pass
return path
@property
def track_info(self):
return {
"title": self.tags.get("music") or self.song_name,
"url": self.link,
"album": self.tags.get("album"),
"genre": self.tags.get("genre"),
"artist": self.tags.get("artist"),
"duration": self.tags.get("duration", 0)
}
def __set_tags(self):
for tag, value in self.tags.items():
setattr(
self, tag, value
)
def __set_track_md5(self):
self.track_md5 = f"track/{self.ids}"
def set_fallback_ids(self, fallback_ids):
self.fallback_ids = fallback_ids
self.fallback_track_md5 = f"track/{self.fallback_ids}"

View File

@ -1,28 +0,0 @@
#!/usr/bin/python3
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0",
"Accept-Language": "en-US;q=0.5,en;q=0.3"
}
method_saves = ["0", "1", "2"]
qualities = {
"FLAC": {
"n_quality": "9",
"f_format": ".flac",
"s_quality": "FLAC"
},
"MP3_320": {
"n_quality": "3",
"f_format": ".mp3",
"s_quality": "320"
},
"MP3_128": {
"n_quality": "1",
"f_format": ".mp3",
"s_quality": "128"
}
}

View File

@ -1,228 +0,0 @@
#!/usr/bin/python3
from mutagen.flac import FLAC, Picture
from mutagen.id3 import (
ID3NoHeaderError,
ID3, APIC, USLT, SYLT,
COMM, TSRC, TRCK, TIT2,
TLEN, TEXT, TCON, TALB, TBPM,
TPE1, TYER, TDAT, TPOS, TPE2,
TPUB, TCOP, TXXX, TCOM, IPLS
)
from .models.track import Track
def __write_flac(song, data):
tag = FLAC(song)
tag.delete()
images = Picture()
images.type = 3
images.data = data['image']
tag.clear_pictures()
tag.add_picture(images)
tag['lyrics'] = data['lyric']
tag['artist'] = data['artist']
tag['title'] = data['music']
tag[
'date'] = f"{data['year'].year}/{data['year'].month}/{data['year'].day}"
tag['album'] = data['album']
tag['tracknumber'] = f"{data['tracknum']}"
tag['discnumber'] = f"{data['discnum']}"
tag['genre'] = data['genre']
tag['albumartist'] = data['ar_album']
tag['author'] = data['author']
tag['composer'] = data['composer']
tag['copyright'] = data['copyright']
tag['bpm'] = f"{data['bpm']}"
tag['length'] = f"{data['duration']}"
tag['organization'] = data['label']
tag['isrc'] = data['isrc']
tag['lyricist'] = data['lyricist']
tag['version'] = data['version']
tag.save()
def __write_mp3(song, data):
try:
audio = ID3(song)
audio.delete()
except ID3NoHeaderError:
audio = ID3()
audio.add(
APIC(
mime="image/jpeg",
type=3,
desc="album front cover",
data=data['image']
)
)
audio.add(
COMM(
lang="eng",
desc="my comment",
text="DO NOT USE FOR YOUR OWN EARNING"
)
)
audio.add(
USLT(
text=data['lyric']
)
)
audio.add(
SYLT(
type=1,
format=2,
desc="sync lyric song",
text=data['lyric_sync']
)
)
audio.add(
TSRC(
text=data['isrc']
)
)
audio.add(
TRCK(
text=f"{data['tracknum']}/{data['nb_tracks']}"
)
)
audio.add(
TIT2(
text=data['music']
)
)
audio.add(
TLEN(
text=f"{data['duration']}"
)
)
audio.add(
TEXT(
text=data['lyricist']
)
)
audio.add(
TCON(
text=data['genre']
)
)
audio.add(
TALB(
text=data['album']
)
)
audio.add(
TBPM(
text=f"{data['bpm']}"
)
)
audio.add(
TPE1(
text=data['artist']
)
)
audio.add(
TYER(
text=f"{data['year'].year}"
)
)
audio.add(
TDAT(
text=f"{data['year'].day}{data['year'].month}"
)
)
audio.add(
TPOS(
text=f"{data['discnum']}/{data['discnum']}"
)
)
audio.add(
TPE2(
text=data['ar_album']
)
)
audio.add(
TPUB(
text=data['label']
)
)
audio.add(
TCOP(
text=data['copyright']
)
)
audio.add(
TXXX(
desc="REPLAYGAIN_TRACK_GAIN",
text=f"{data['gain']}"
)
)
audio.add(
TCOM(
text=data['composer']
)
)
audio.add(
IPLS(
people=[data['author']]
)
)
audio.save(song, v2_version=3)
def write_tags(track: Track):
song = track.song_path
song_metadata = track.tags
f_format = track.file_format
if f_format == ".flac":
__write_flac(song, song_metadata)
else:
__write_mp3(song, song_metadata)
def check_track(track: Track):
song = track.song_path
f_format = track.file_format
is_ok = False
if f_format == ".flac":
tags = FLAC(song)
else:
try:
tags = ID3(song)
except ID3NoHeaderError:
return is_ok
l_tags = len(
tags.keys()
)
if l_tags > 15:
is_ok = True
return is_ok

View File

@ -1,228 +0,0 @@
#!/usr/bin/python3
from datetime import datetime
from os import makedirs
from os.path import (
isdir, basename, join
)
from urllib.parse import urlparse
from zipfile import ZipFile, ZIP_DEFLATED
from requests import get as req_get
from .settings import header
from .exceptions import InvalidLink
def link_is_valid(link):
netloc = urlparse(link).netloc
if not any(
c_link == netloc
for c_link in ["www.deezer.com", "deezer.com", "deezer.page.link"]
):
raise InvalidLink(link)
def get_ids(link):
parsed = urlparse(link)
path = parsed.path
ids = path.split("/")[-1]
return ids
def request(url):
thing = req_get(url, headers=header)
return thing
def artist_sort(array):
if len(array) > 1:
for a in array:
for b in array:
if a in b and a != b:
array.remove(b)
array = list(
dict.fromkeys(array)
)
artists = " & ".join(array)
return artists
def __check_dir(directory):
if not isdir(directory):
makedirs(directory)
def check_track_md5(infos: dict):
if "FALLBACK" in infos:
song_md5 = infos['FALLBACK']['MD5_ORIGIN']
version = infos['FALLBACK']['MEDIA_VERSION']
else:
song_md5 = infos['MD5_ORIGIN']
version = infos['MEDIA_VERSION']
return song_md5, version
def check_track_token(infos: dict):
if "FALLBACK" in infos:
track_token = infos['FALLBACK']['TRACK_TOKEN']
else:
track_token = infos['TRACK_TOKEN']
return track_token
def check_track_ids(infos: dict):
if "FALLBACK" in infos:
ids = infos['FALLBACK']['SNG_ID']
else:
ids = infos['SNG_ID']
return ids
def __var_excape(string):
string = (
string
.replace("\\", "")
.replace("/", "")
.replace(":", "")
.replace("*", "")
.replace("?", "")
.replace("\"", "")
.replace("<", "")
.replace(">", "")
.replace("|", "")
.replace("&", "")
)
return string
def convert_to_date(date):
if date == "0000-00-00":
date = "0001-01-01"
date = datetime.strptime(date, "%Y-%m-%d")
return date
def what_kind(link):
url = request(link).url
return url
def __get_dir(song_metadata, output_dir, method_save):
album = __var_excape(song_metadata['album'])
artist = __var_excape(song_metadata['ar_album'])
upc = song_metadata['upc']
if method_save == 0:
song_dir = f"{album} [{upc}]"
elif method_save == 1:
song_dir = f"{album} - {artist}"
elif method_save == 2:
song_dir = f"{album} - {artist} [{upc}]"
song_dir = song_dir[:255]
final_dir = join(output_dir, song_dir)
final_dir += "/"
return final_dir
def set_path(
song_metadata, output_dir,
song_quality, file_format, method_save
):
album = __var_excape(song_metadata['album'])
artist = __var_excape(song_metadata['artist'])
music = __var_excape(song_metadata['music'])
if method_save == 0:
discnum = song_metadata['discnumber']
tracknum = song_metadata['tracknumber']
song_name = f"{album} CD {discnum} TRACK {tracknum}"
elif method_save == 1:
song_name = f"{music} - {artist}"
elif method_save == 2:
isrc = song_metadata['isrc']
song_name = f"{music} - {artist} [{isrc}]"
song_dir = __get_dir(song_metadata, output_dir, method_save)
__check_dir(song_dir)
l_encoded = len(
song_name.encode()
)
if l_encoded > 242:
n_tronc = l_encoded - 242
n_tronc = len(song_name) - n_tronc
else:
n_tronc = 242
song_path = f"{song_dir}{song_name[:n_tronc]}"
song_path += f" ({song_quality}){file_format}"
return song_path
def create_zip(
tracks: [],
output_dir=None,
song_metadata=None,
song_quality=None,
method_save=0,
zip_name=None
):
if not zip_name:
album = __var_excape(song_metadata['album'])
song_dir = __get_dir(song_metadata, output_dir, method_save)
if method_save == 0:
zip_name = f"{song_dir}{album} ({song_quality})"
elif method_save == 1:
artist = __var_excape(song_metadata['ar_album'])
zip_name = f"{song_dir}{album} - {artist} ({song_quality})"
elif method_save == 2:
artist = __var_excape(song_metadata['ar_album'])
upc = song_metadata['upc']
zip_name = f"{song_dir}{album} - {artist} {upc} ({song_quality})"
zip_name += ".zip"
z = ZipFile(zip_name, "w", ZIP_DEFLATED)
for track in tracks:
if not track.success:
continue
c_song_path = track.song_path
song_path = basename(c_song_path)
z.write(c_song_path, song_path)
z.close()
return zip_name
def trasform_sync_lyric(lyric):
sync_array = []
for a in lyric:
if "milliseconds" in a:
arr = (
a['line'], int(a['milliseconds'])
)
sync_array.append(arr)
return sync_array

View File

@ -1,16 +0,0 @@
Metadata-Version: 2.0
Name: py-bandcamp
Version: 0.7.0
Summary: bandcamp data scrapper
Home-page: https://github.com/OpenJarbas/py_bandcamp
Author: jarbasAI
Author-email: jarbasai@mailfence.com
License: Apache2
Platform: UNKNOWN
Requires-Dist: beautifulsoup4
Requires-Dist: requests
Requires-Dist: requests-cache
UNKNOWN

View File

@ -1,16 +0,0 @@
py_bandcamp-0.7.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
py_bandcamp-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
py_bandcamp-0.7.0.dist-info/METADATA,sha256=v64yE1qTYkPrI6J2MtCwmVQdOOZbFHrdr5vQC5guAmk,324
py_bandcamp-0.7.0.dist-info/RECORD,,
py_bandcamp-0.7.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
py_bandcamp-0.7.0.dist-info/WHEEL,sha256=rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g,92
py_bandcamp-0.7.0.dist-info/metadata.json,sha256=N3uWHAUUKCiP03daPAIJfc8j6sUY8GyWfXM1r4KBukg,512
py_bandcamp-0.7.0.dist-info/top_level.txt,sha256=bbnj4Boxiv1B7Bo1GHfc5_jVs9p4egzSunq5FQtaCsQ,12
py_bandcamp/__init__.py,sha256=NI84FGwg_EouRuSqRp4JsO5MvSKbuizjOzXsWlNo_R0,9801
py_bandcamp/__init__.pyc,,
py_bandcamp/models.py,sha256=bCz1oNwDEioNvMAzy8mVmX6bX7NhcjYVKew1_ncsajI,10877
py_bandcamp/models.pyc,,
py_bandcamp/session.py,sha256=j3KLPYMn5YV6rgZNNT9MFpLJJ374HBY0f3SDnTUPnjA,101
py_bandcamp/session.pyc,,
py_bandcamp/utils.py,sha256=k5tJESDviwz5wJawRqFR-99TOcYLwilUFInPfoa19oU,2076
py_bandcamp/utils.pyc,,

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1 +0,0 @@
{"extensions": {"python.details": {"contacts": [{"email": "jarbasai@mailfence.com", "name": "jarbasAI", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/OpenJarbas/py_bandcamp"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "license": "Apache2", "metadata_version": "2.0", "name": "py-bandcamp", "run_requires": [{"requires": ["beautifulsoup4", "requests", "requests-cache"]}], "summary": "bandcamp data scrapper", "version": "0.7.0"}

View File

@ -1,248 +0,0 @@
from py_bandcamp.models import *
from py_bandcamp.session import SESSION as requests
from py_bandcamp.utils import extract_ldjson_blob, get_props, extract_blob, \
get_stream_data
class BandCamp:
@staticmethod
def tags(tag_list=True):
data = extract_blob("https://bandcamp.com/tags")
tags = {"genres": data["signup_params"]["genres"],
"subgenres": data["signup_params"]["subgenres"]}
if not tag_list:
return tags
tag_list = []
for genre in tags["subgenres"]:
tag_list.append(genre)
tag_list += [sub["norm_name"] for sub in tags["subgenres"][genre]]
return tag_list
@classmethod
def search_tag(cls, tag, page=1, pop_date=1):
tag = tag.strip().replace(" ", "-").lower()
if tag not in cls.tags():
return []
params = {"page": page, "sort_field": pop_date}
url = 'http://bandcamp.com/tag/' + str(tag)
data = extract_blob(url, params=params)
related_tags = [{"name": t["norm_name"], "score": t["relation"]}
for t in data["hub"].pop("related_tags")]
collections, dig_deeper = data["hub"].pop("tabs")
dig_deeper = dig_deeper["dig_deeper"]["results"]
collections = collections["collections"]
_to_remove = ['custom_domain', 'custom_domain_verified', "item_type",
'packages', 'slug_text', 'subdomain', 'is_preorder',
'item_id', 'num_comments', 'tralbum_id', 'band_id',
'tralbum_type', 'tag_id', 'audio_track_id']
for c in collections:
if c["name"] == "bc_dailys":
continue
for result in c["items"]:
result["image"] = "https://f4.bcbits.com/img/a{art_id}_1.jpg". \
format(art_id=result.pop("art_id"))
for _ in _to_remove:
if _ in result:
result.pop(_)
result["related_tags"] = related_tags
result["collection"] = c["name"]
if "tralbum_url" in result:
result["album_url"] = result.pop("tralbum_url")
# TODO featured track object
yield BandcampTrack(result, parse=False)
for k in dig_deeper:
for result in dig_deeper[k]["items"]:
for _ in _to_remove:
if _ in result:
result.pop(_)
result["related_tags"] = related_tags
result["collection"] = "dig_deeper"
if "tralbum_url" in result:
result["album_url"] = result.pop("tralbum_url")
# TODO featured track object
yield BandcampTrack(result, parse=False)
@classmethod
def search_albums(cls, album_name):
for album in cls.search(album_name, albums=True, tracks=False,
artists=False, labels=False):
yield album
@classmethod
def search_tracks(cls, track_name):
for t in cls.search(track_name, albums=False, tracks=True,
artists=False, labels=False):
yield t
@classmethod
def search_artists(cls, artist_name):
for a in cls.search(artist_name, albums=False, tracks=False,
artists=True, labels=False):
yield a
@classmethod
def search_labels(cls, label_name):
for a in cls.search(label_name, albums=False, tracks=False,
artists=False, labels=True):
yield a
@classmethod
def search(cls, name, page=1, albums=True, tracks=True, artists=True,
labels=False):
params = {"page": page, "q": name}
response = requests.get('http://bandcamp.com/search', params=params)
html_doc = response.content
soup = BeautifulSoup(html_doc, 'html.parser')
seen = []
for item in soup.find_all("li", class_="searchresult"):
item_type = item.find('div',
class_='itemtype').text.strip().lower()
if item_type == "album" and albums:
data = cls._parse_album(item)
elif item_type == "track" and tracks:
data = cls._parse_track(item)
elif item_type == "artist" and artists:
data = cls._parse_artist(item)
elif item_type == "label" and labels:
data = cls._parse_label(item)
else:
continue
# data["type"] = type
yield data
seen.append(data)
if not len(seen):
return # no more pages
for item in cls.search(name, page=page + 1, albums=albums,
tracks=tracks, artists=artists,
labels=labels):
if item in seen:
return # duplicate data, fail safe out of loops
yield item
@staticmethod
def get_track_lyrics(track_url):
track_page = requests.get(track_url)
track_soup = BeautifulSoup(track_page.text, 'html.parser')
track_lyrics = track_soup.find("div", {"class": "lyricsText"})
if track_lyrics:
return track_lyrics.text
return "lyrics unavailable"
@classmethod
def get_streams(cls, urls):
if not isinstance(urls, list):
urls = [urls]
direct_links = [cls.get_stream_url(url) for url in urls]
return direct_links
@classmethod
def get_stream_url(cls, url):
data = get_stream_data(url)
print(data)
for p in data['additionalProperty']:
if p['name'] == 'file_mp3-128':
return p["value"]
return url
@staticmethod
def _parse_label(item):
art = item.find("div", {"class": "art"}).find("img")
if art:
art = art["src"]
name = item.find('div', class_='heading').text.strip()
url = item.find(
'div', class_='heading').find('a')['href'].split("?")[0]
location = item.find('div', class_='subhead').text.strip()
try:
tags = item.find(
'div', class_='tags').text.replace("tags:", "").split(",")
tags = [t.strip().lower() for t in tags]
except: # sometimes missing
tags = []
data = {"name": name, "location": location,
"tags": tags, "url": url, "image": art
}
return BandcampLabel(data)
@staticmethod
def _parse_artist(item):
name = item.find('div', class_='heading').text.strip()
url = item.find(
'div', class_='heading').find('a')['href'].split("?")[0]
genre = item.find(
'div', class_='genre').text.strip().replace("genre: ", "")
location = item.find('div', class_='subhead').text.strip()
try:
tags = item.find(
'div', class_='tags').text.replace("tags:", "").split(",")
tags = [t.strip().lower() for t in tags]
except: # sometimes missing
tags = []
art = item.find("div", {"class": "art"}).find("img")["src"]
data = {"name": name, "genre": genre, "location": location,
"tags": tags, "url": url, "image": art, "albums": []
}
return BandcampArtist(data)
@staticmethod
def _parse_track(item):
track_name = item.find('div', class_='heading').text.strip()
url = item.find(
'div', class_='heading').find('a')['href'].split("?")[0]
album_name, artist = item.find(
'div', class_='subhead').text.strip().split("by")
album_name = album_name.strip().replace("from ", "")
artist = artist.strip()
released = item.find(
'div', class_='released').text.strip().replace("released ", "")
try:
tags = item.find(
'div', class_='tags').text.replace("tags:", "").split(",")
tags = [t.strip().lower() for t in tags]
except: # sometimes missing
tags = []
art = item.find("div", {"class": "art"}).find("img")["src"]
data = {"track_name": track_name, "released": released, "url": url,
"tags": tags, "album_name": album_name, "artist": artist,
"image": art
}
return BandcampTrack(data)
@staticmethod
def _parse_album(item):
art = item.find("div", {"class": "art"}).find("img")["src"]
album_name = item.find('div', class_='heading').text.strip()
url = item.find(
'div', class_='heading').find('a')['href'].split("?")[0]
length = item.find('div', class_='length').text.strip()
tracks, minutes = length.split(",")
tracks = tracks.replace(" tracks", "").replace(" track", "").strip()
minutes = minutes.replace(" minutes", "").strip()
released = item.find(
'div', class_='released').text.strip().replace("released ", "")
tags = item.find(
'div', class_='tags').text.replace("tags:", "").split(",")
tags = [t.strip().lower() for t in tags]
artist = item.find("div", {"class": "subhead"}).text.strip()
if artist.startswith("by "):
artist = artist[3:]
data = {"album_name": album_name,
"length": length,
"minutes": minutes,
"url": url,
"image": art,
"artist": artist,
"track_number": tracks,
"released": released,
"tags": tags
}
return BandcampAlbum(data, scrap=False)

View File

@ -1,406 +0,0 @@
from bs4 import BeautifulSoup
from py_bandcamp.session import SESSION as requests
from py_bandcamp.utils import extract_ldjson_blob, get_props
class BandcampTrack:
def __init__(self, data, parse=True):
self._url = data.get("url")
self._data = data or {}
self._page_data = {}
if parse:
self.parse_page()
if not self.url:
raise ValueError("bandcamp url is not set")
def parse_page(self):
self._page_data = self.get_track_data(self.url)
return self._page_data
@staticmethod
def from_url(url):
return BandcampTrack({"url": url})
@property
def url(self):
return self._url or self.data.get("url")
@property
def album(self):
return self.get_album(self.url)
@property
def artist(self):
return self.get_artist(self.url)
@property
def data(self):
for k, v in self._page_data.items():
self._data[k] = v
return self._data
@property
def title(self):
return self.data.get("title") or self.data.get("name") or \
self.url.split("/")[-1]
@property
def image(self):
return self.data.get("image")
@property
def track_num(self):
return self.data.get("tracknum")
@property
def duration(self):
return self.data.get("duration_secs") or 0
@property
def stream(self):
return self.data.get("file_mp3-128")
@staticmethod
def get_album(url):
data = extract_ldjson_blob(url, clean=True)
if data.get('inAlbum'):
return BandcampAlbum({
"title": data['inAlbum'].get('name'),
"url": data['inAlbum'].get('id', url).split("#")[0],
'type': data['inAlbum'].get("type"),
})
@staticmethod
def get_artist(url):
data = extract_ldjson_blob(url, clean=True)
d = data.get("byArtist")
if d:
return BandcampArtist({
"title": d.get('name'),
"url": d.get('id', url).split("#")[0],
'genre': d.get('genre'),
"artist_type": d.get('type')
}, scrap=False)
return None
@staticmethod
def get_track_data(url):
data = extract_ldjson_blob(url, clean=True)
kwords = data.get('keywords', "")
if isinstance(kwords, str):
kwords = kwords.split(", ")
track = {
'dateModified': data.get('dateModified'),
'datePublished': data.get('datePublished'),
"url": data.get('id') or url,
"title": data.get("name"),
"type": data.get("type"),
'image': data.get('image'),
'keywords': kwords
}
for k, v in get_props(data).items():
track[k] = v
return track
def __repr__(self):
return self.__class__.__name__ + ":" + self.title
def __str__(self):
return self.url
class BandcampAlbum:
def __init__(self, data, scrap=True):
self._url = data.get("url")
self._data = data or {}
self._page_data = {}
if scrap:
self.scrap()
if not self.url:
raise ValueError("bandcamp url is not set")
def scrap(self):
self._page_data = self.get_album_data(self.url)
return self._page_data
@staticmethod
def from_url(url):
return BandcampAlbum({"url": url})
@property
def image(self):
return self.data.get("image")
@property
def url(self):
return self._url or self.data.get("url")
@property
def title(self):
return self.data.get("title") or self.data.get("name") or \
self.url.split("/")[-1]
@property
def releases(self):
return self.get_releases(self.url)
@property
def artist(self):
return self.get_artist(self.url)
@property
def keywords(self):
return self.data.get("keywords") or []
@property
def tracks(self):
return self.get_tracks(self.url)
@property
def featured_track(self):
if not len(self.tracks):
return None
num = self.data.get('featured_track_num', 1) or 1
return self.tracks[int(num) - 1]
@property
def comments(self):
return self.get_comments(self.url)
@property
def data(self):
for k, v in self._page_data.items():
self._data[k] = v
return self._data
@staticmethod
def get_releases(url):
data = extract_ldjson_blob(url, clean=True)
releases = []
for d in data.get("albumRelease", []):
release = {
"description": d.get("description"),
'image': d.get('image'),
"title": d.get('name'),
"url": d.get('id', url).split("#")[0],
'format': d.get('musicReleaseFormat'),
}
releases.append(release)
return releases
@staticmethod
def get_artist(url):
data = extract_ldjson_blob(url, clean=True)
d = data.get("byArtist")
if d:
return BandcampArtist({
"description": d.get("description"),
'image': d.get('image'),
"title": d.get('name'),
"url": d.get('id', url).split("#")[0],
'genre': d.get('genre'),
"artist_type": d.get('type')
}, scrap=False)
return None
@staticmethod
def get_tracks(url):
data = extract_ldjson_blob(url, clean=True)
if not data.get("track"):
return []
data = data['track']
tracks = []
for d in data.get('itemListElement', []):
d = d['item']
track = {
"title": d.get('name'),
"url": d.get('id') or url,
'type': d.get('type'),
}
for k, v in get_props(d).items():
track[k] = v
tracks.append(BandcampTrack(track, parse=False))
return tracks
@staticmethod
def get_comments(url):
data = extract_ldjson_blob(url, clean=True)
comments = []
for d in data.get("comment", []):
comment = {
"text": d["text"],
'image': d["author"].get("image"),
"author": d["author"]["name"]
}
comments.append(comment)
return comments
@staticmethod
def get_album_data(url):
data = extract_ldjson_blob(url, clean=True)
props = get_props(data)
kwords = data.get('keywords', "")
if isinstance(kwords, str):
kwords = kwords.split(", ")
return {
'dateModified': data.get('dateModified'),
'datePublished': data.get('datePublished'),
'description': data.get('description'),
"url": data.get('id') or url,
"title": data.get("name"),
"type": data.get("type"),
"n_tracks": data.get('numTracks'),
'image': data.get('image'),
'featured_track_num': props.get('featured_track_num'),
'keywords': kwords
}
def __repr__(self):
return self.__class__.__name__ + ":" + self.title
def __str__(self):
return self.url
class BandcampLabel:
def __init__(self, data, scrap=True):
self._url = data.get("url")
self._data = data or {}
self._page_data = {}
if scrap:
self.scrap()
if not self.url:
raise ValueError("bandcamp url is not set")
def scrap(self):
self._page_data = {} # TODO
return self._page_data
@staticmethod
def from_url(url):
return BandcampTrack({"url": url})
@property
def url(self):
return self._url or self.data.get("url")
@property
def data(self):
for k, v in self._page_data.items():
self._data[k] = v
return self._data
@property
def name(self):
return self.data.get("title") or self.data.get("name") or \
self.url.split("/")[-1]
@property
def location(self):
return self.data.get("location")
@property
def tags(self):
return self.data.get("tags") or []
@property
def image(self):
return self.data.get("image")
def __repr__(self):
return self.__class__.__name__ + ":" + self.name
def __str__(self):
return self.url
class BandcampArtist:
def __init__(self, data, scrap=True):
self._url = data.get("url")
self._data = data or {}
self._page_data = {}
if scrap:
self.scrap()
def scrap(self):
self._page_data = {} # TODO
return self._page_data
@property
def featured_album(self):
return BandcampAlbum.from_url(self.url + "/releases")
@property
def featured_track(self):
if not self.featured_album:
return None
return self.featured_album.featured_track
@staticmethod
def get_albums(url):
albums = []
soup = BeautifulSoup(requests.get(url).text, "html.parser")
for album in soup.find_all("a"):
album_url = album.find("p", {"class": "title"})
if album_url:
title = album_url.text.strip()
art = album.find("div", {"class": "art"}).find("img")["src"]
album_url = url + album["href"]
album = BandcampAlbum({"album_name": title,
"image": art,
"url": album_url})
albums.append(album)
return albums
@property
def albums(self):
return self.get_albums(self.url)
@staticmethod
def from_url(url):
return BandcampTrack({"url": url})
@property
def url(self):
return self._url or self.data.get("url")
@property
def data(self):
for k, v in self._page_data.items():
self._data[k] = v
return self._data
@property
def name(self):
return self.data.get("title") or self.data.get("name") or \
self.url.split("/")[-1]
@property
def location(self):
return self.data.get("location")
@property
def genre(self):
return self.data.get("genre")
@property
def tags(self):
return self.data.get("tags") or []
@property
def image(self):
return self.data.get("image")
def __repr__(self):
return self.__class__.__name__ + ":" + self.name
def __str__(self):
return self.url
def __eq__(self, other):
if str(self) == str(other):
return True
return False

View File

@ -1,3 +0,0 @@
import requests_cache
SESSION = requests_cache.CachedSession(expire_after=5 * 60, backend="memory")

View File

@ -1,77 +0,0 @@
import json
from py_bandcamp.session import SESSION as requests
def extract_blob(url, params=None):
blob = requests.get(url, params=params).text
for b in blob.split("data-blob='")[1:]:
json_blob = b.split("'")[0]
return json.loads(json_blob)
for b in blob.split("data-blob=\"")[1:]:
json_blob = b.split("\"")[0].replace("&quot;", '"')
return json.loads(json_blob)
def extract_ldjson_blob(url, clean=False):
txt_string = requests.get(url).text
json_blob = txt_string. \
split('<script type="application/ld+json">')[-1]. \
split("</script>")[0]
data = json.loads(json_blob)
def _clean_list(l):
for idx, v in enumerate(l):
if isinstance(v, dict):
l[idx] = _clean_dict(v)
if isinstance(v, list):
l[idx] = _clean_list(v)
return l
def _clean_dict(d):
clean = {}
for k, v in d.items():
if isinstance(v, dict):
v = _clean_dict(v)
if isinstance(v, list):
v = _clean_list(v)
k = k.replace("@", "")
clean[k] = v
return clean
if clean:
return _clean_dict(data)
return data
def get_props(d, props=None):
props = props or []
data = {}
for p in d['additionalProperty']:
if p['name'] in props or not props:
data[p['name']] = p['value']
return data
def get_stream_data(url):
data = extract_ldjson_blob(url)
artist_data = data['byArtist']
album_data = data['inAlbum']
kws = data["keywords"]
if isinstance(kws, str):
kws = kws.split(", ")
result = {
"categories": data["@type"],
'album_name': album_data['name'],
'artist': artist_data['name'],
'image': data['image'],
"title": data['name'],
"url": url,
"tags": kws + data.get("tags", [])
}
for p in data['additionalProperty']:
if p['name'] == 'file_mp3-128':
result["stream"] = p["value"]
return result

View File

@ -1,28 +0,0 @@
Copyright (c) 2008-2022, James Bennett
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the author nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,64 +0,0 @@
Metadata-Version: 2.1
Name: webcolors
Version: 1.12
Summary: A library for working with color names and color values formats defined by HTML and CSS.
Home-page: https://github.com/ubernostrum/webcolors
Author: James Bennett
Author-email: james@b-list.org
License: BSD 3-Clause
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Utilities
Requires-Python: >=3.7
License-File: LICENSE
.. -*-restructuredtext-*-
.. image:: https://github.com/ubernostrum/webcolors/workflows/CI/badge.svg
:alt: CI status image
:target: https://github.com/ubernostrum/webcolors/actions?query=workflow%3ACI
``webcolors`` is a module for working with HTML/CSS color definitions.
Support is included for normalizing and converting between the
following formats (RGB colorspace only; conversion to/from HSL can be
handled by the ``colorsys`` module in the Python standard library):
* Specification-defined color names
* Six-digit hexadecimal
* Three-digit hexadecimal
* Integer ``rgb()`` triplet
* Percentage ``rgb()`` triplet
For example:
.. code-block:: python
>>> import webcolors
>>> webcolors.hex_to_name(u'#daa520')
u'goldenrod'
Implementations are also provided for the HTML5 color parsing and
serialization algorithms. For example, parsing the infamous
"chucknorris" string into an rgb() triplet:
.. code-block:: python
>>> import webcolors
>>> webcolors.html5_parse_legacy_color(u'chucknorris')
HTML5SimpleColor(red=192, green=0, blue=0)
Full documentation is `available online <https://webcolors.readthedocs.io/>`_.

View File

@ -1,9 +0,0 @@
webcolors-1.12.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
webcolors-1.12.dist-info/LICENSE,sha256=ii0_r1bvLUKXO599sXarGslRtQGkmhx7s0ACbx5NxIk,1523
webcolors-1.12.dist-info/METADATA,sha256=56Xnd_OybLPtSNbCG8SixpiFKH0lqki7ihJWET3Ea_o,2049
webcolors-1.12.dist-info/RECORD,,
webcolors-1.12.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
webcolors-1.12.dist-info/WHEEL,sha256=_NOXIqFgOaYmlm9RJLPQZ13BJuEIrp5jx5ptRD5uh3Y,92
webcolors-1.12.dist-info/top_level.txt,sha256=HUENGOTrUyEUebL1YSZy2ROMReykfiyMzSB-mSi72_4,10
webcolors.py,sha256=OFDSm2rv4D0Jh1qhIOCLTy3g_8SpKfWNByWbnPmPYbk,25487
webcolors.pyc,,

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.32.3)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1,781 +0,0 @@
"""
Utility functions for working with the color names and color value
formats defined by the HTML and CSS specifications for use in
documents on the Web.
See documentation (in docs/ directory of source distribution) for
details of the supported formats, conventions and conversions.
"""
import re
import string
from typing import NamedTuple, Tuple, Union
__version__ = "1.11.1"
def _reversedict(d: dict) -> dict:
"""
Internal helper for generating reverse mappings; given a
dictionary, returns a new dictionary with keys and values swapped.
"""
return {value: key for key, value in d.items()}
HEX_COLOR_RE = re.compile(r"^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$")
HTML4 = "html4"
CSS2 = "css2"
CSS21 = "css21"
CSS3 = "css3"
SUPPORTED_SPECIFICATIONS = (HTML4, CSS2, CSS21, CSS3)
SPECIFICATION_ERROR_TEMPLATE = "{{spec}} is not a supported specification for color name lookups; \
supported specifications are: {supported}.".format(
supported=",".join(SUPPORTED_SPECIFICATIONS)
)
IntegerRGB = NamedTuple("IntegerRGB", [("red", int), ("green", int), ("blue", int)])
PercentRGB = NamedTuple("PercentRGB", [("red", str), ("green", str), ("blue", str)])
HTML5SimpleColor = NamedTuple(
"HTML5SimpleColor", [("red", int), ("green", int), ("blue", int)]
)
IntTuple = Union[IntegerRGB, HTML5SimpleColor, Tuple[int, int, int]]
PercentTuple = Union[PercentRGB, Tuple[str, str, str]]
# Mappings of color names to normalized hexadecimal color values.
#################################################################
# The HTML 4 named colors.
#
# The canonical source for these color definitions is the HTML 4
# specification:
#
# http://www.w3.org/TR/html401/types.html#h-6.5
#
# The file tests/definitions.py in the source distribution of this
# module downloads a copy of the HTML 4 standard and parses out the
# color names to ensure the values below are correct.
HTML4_NAMES_TO_HEX = {
"aqua": "#00ffff",
"black": "#000000",
"blue": "#0000ff",
"fuchsia": "#ff00ff",
"green": "#008000",
"gray": "#808080",
"lime": "#00ff00",
"maroon": "#800000",
"navy": "#000080",
"olive": "#808000",
"purple": "#800080",
"red": "#ff0000",
"silver": "#c0c0c0",
"teal": "#008080",
"white": "#ffffff",
"yellow": "#ffff00",
}
# CSS 2 used the same list as HTML 4.
CSS2_NAMES_TO_HEX = HTML4_NAMES_TO_HEX
# CSS 2.1 added orange.
CSS21_NAMES_TO_HEX = {"orange": "#ffa500", **HTML4_NAMES_TO_HEX}
# The CSS 3/SVG named colors.
#
# The canonical source for these color definitions is the SVG
# specification's color list (which was adopted as CSS 3's color
# definition):
#
# http://www.w3.org/TR/SVG11/types.html#ColorKeywords
#
# CSS 3 also provides definitions of these colors:
#
# http://www.w3.org/TR/css3-color/#svg-color
#
# SVG provides the definitions as RGB triplets. CSS 3 provides them
# both as RGB triplets and as hexadecimal. Since hex values are more
# common in real-world HTML and CSS, the mapping below is to hex
# values instead. The file tests/definitions.py in the source
# distribution of this module downloads a copy of the CSS 3 color
# module and parses out the color names to ensure the values below are
# correct.
CSS3_NAMES_TO_HEX = {
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgrey": "#a9a9a9",
"darkgreen": "#006400",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkslategrey": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dimgrey": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"gold": "#ffd700",
"goldenrod": "#daa520",
"gray": "#808080",
"grey": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavender": "#e6e6fa",
"lavenderblush": "#fff0f5",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgrey": "#d3d3d3",
"lightgreen": "#90ee90",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightslategrey": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"slategrey": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32",
}
# Mappings of normalized hexadecimal color values to color names.
#################################################################
HTML4_HEX_TO_NAMES = _reversedict(HTML4_NAMES_TO_HEX)
CSS2_HEX_TO_NAMES = HTML4_HEX_TO_NAMES
CSS21_HEX_TO_NAMES = _reversedict(CSS21_NAMES_TO_HEX)
CSS3_HEX_TO_NAMES = _reversedict(CSS3_NAMES_TO_HEX)
# CSS3 defines both 'gray' and 'grey', as well as defining either
# variant for other related colors like 'darkgray'/'darkgrey'. For a
# 'forward' lookup from name to hex, this is straightforward, but a
# 'reverse' lookup from hex to name requires picking one spelling.
#
# The way in which _reversedict() generates the reverse mappings will
# pick a spelling based on the ordering of dictionary keys, which
# varies according to the version and implementation of Python in use,
# and in some Python versions is explicitly not to be relied on for
# consistency. So here we manually pick a single spelling that will
# consistently be returned. Since 'gray' was the only spelling
# supported in HTML 4, CSS1, and CSS2, 'gray' and its varients are
# chosen.
CSS3_HEX_TO_NAMES["#a9a9a9"] = "darkgray"
CSS3_HEX_TO_NAMES["#2f4f4f"] = "darkslategray"
CSS3_HEX_TO_NAMES["#696969"] = "dimgray"
CSS3_HEX_TO_NAMES["#808080"] = "gray"
CSS3_HEX_TO_NAMES["#d3d3d3"] = "lightgray"
CSS3_HEX_TO_NAMES["#778899"] = "lightslategray"
CSS3_HEX_TO_NAMES["#708090"] = "slategray"
# Normalization functions.
#################################################################
def normalize_hex(hex_value: str) -> str:
"""
Normalize a hexadecimal color value to 6 digits, lowercase.
"""
match = HEX_COLOR_RE.match(hex_value)
if match is None:
raise ValueError(
"'{}' is not a valid hexadecimal color value.".format(hex_value)
)
hex_digits = match.group(1)
if len(hex_digits) == 3:
hex_digits = "".join(2 * s for s in hex_digits)
return "#{}".format(hex_digits.lower())
def _normalize_integer_rgb(value: int) -> int:
"""
Internal normalization function for clipping integer values into
the permitted range (0-255, inclusive).
"""
return 0 if value < 0 else 255 if value > 255 else value
def normalize_integer_triplet(rgb_triplet: IntTuple) -> IntegerRGB:
"""
Normalize an integer ``rgb()`` triplet so that all values are
within the range 0-255 inclusive.
"""
return IntegerRGB._make(_normalize_integer_rgb(value) for value in rgb_triplet)
def _normalize_percent_rgb(value: str) -> str:
"""
Internal normalization function for clipping percent values into
the permitted range (0%-100%, inclusive).
"""
value = value.split("%")[0]
percent = float(value) if "." in value else int(value)
return "0%" if percent < 0 else "100%" if percent > 100 else "{}%".format(percent)
def normalize_percent_triplet(rgb_triplet: PercentTuple) -> PercentRGB:
"""
Normalize a percentage ``rgb()`` triplet so that all values are
within the range 0%-100% inclusive.
"""
return PercentRGB._make(_normalize_percent_rgb(value) for value in rgb_triplet)
# Conversions from color names to various formats.
#################################################################
def name_to_hex(name: str, spec: str = CSS3) -> str:
"""
Convert a color name to a normalized hexadecimal color value.
The optional keyword argument ``spec`` determines which
specification's list of color names will be used. The default is
CSS3.
When no color of that name exists in the given specification,
``ValueError`` is raised.
"""
if spec not in SUPPORTED_SPECIFICATIONS:
raise ValueError(SPECIFICATION_ERROR_TEMPLATE.format(spec=spec))
normalized = name.lower()
hex_value = {
CSS2: CSS2_NAMES_TO_HEX,
CSS21: CSS21_NAMES_TO_HEX,
CSS3: CSS3_NAMES_TO_HEX,
HTML4: HTML4_NAMES_TO_HEX,
}[spec].get(normalized)
if hex_value is None:
raise ValueError(
"'{name}' is not defined as a named color in {spec}".format(
name=name, spec=spec
)
)
return hex_value
def name_to_rgb(name: str, spec: str = CSS3) -> IntegerRGB:
"""
Convert a color name to a 3-tuple of integers suitable for use in
an ``rgb()`` triplet specifying that color.
"""
return hex_to_rgb(name_to_hex(name, spec=spec))
def name_to_rgb_percent(name: str, spec: str = CSS3) -> PercentRGB:
"""
Convert a color name to a 3-tuple of percentages suitable for use
in an ``rgb()`` triplet specifying that color.
"""
return rgb_to_rgb_percent(name_to_rgb(name, spec=spec))
# Conversions from hexadecimal color values to various formats.
#################################################################
def hex_to_name(hex_value: str, spec: str = CSS3) -> str:
"""
Convert a hexadecimal color value to its corresponding normalized
color name, if any such name exists.
The optional keyword argument ``spec`` determines which
specification's list of color names will be used. The default is
CSS3.
When no color name for the value is found in the given
specification, ``ValueError`` is raised.
"""
if spec not in SUPPORTED_SPECIFICATIONS:
raise ValueError(SPECIFICATION_ERROR_TEMPLATE.format(spec=spec))
normalized = normalize_hex(hex_value)
name = {
CSS2: CSS2_HEX_TO_NAMES,
CSS21: CSS21_HEX_TO_NAMES,
CSS3: CSS3_HEX_TO_NAMES,
HTML4: HTML4_HEX_TO_NAMES,
}[spec].get(normalized)
if name is None:
raise ValueError("'{}' has no defined color name in {}".format(hex_value, spec))
return name
def hex_to_rgb(hex_value: str) -> IntegerRGB:
"""
Convert a hexadecimal color value to a 3-tuple of integers
suitable for use in an ``rgb()`` triplet specifying that color.
"""
int_value = int(normalize_hex(hex_value)[1:], 16)
return IntegerRGB(int_value >> 16, int_value >> 8 & 0xFF, int_value & 0xFF)
def hex_to_rgb_percent(hex_value: str) -> PercentRGB:
"""
Convert a hexadecimal color value to a 3-tuple of percentages
suitable for use in an ``rgb()`` triplet representing that color.
"""
return rgb_to_rgb_percent(hex_to_rgb(hex_value))
# Conversions from integer rgb() triplets to various formats.
#################################################################
def rgb_to_name(rgb_triplet: IntTuple, spec: str = CSS3) -> str:
"""
Convert a 3-tuple of integers, suitable for use in an ``rgb()``
color triplet, to its corresponding normalized color name, if any
such name exists.
The optional keyword argument ``spec`` determines which
specification's list of color names will be used. The default is
CSS3.
If there is no matching name, ``ValueError`` is raised.
"""
return hex_to_name(rgb_to_hex(normalize_integer_triplet(rgb_triplet)), spec=spec)
def rgb_to_hex(rgb_triplet: IntTuple) -> str:
"""
Convert a 3-tuple of integers, suitable for use in an ``rgb()``
color triplet, to a normalized hexadecimal value for that color.
"""
return "#{:02x}{:02x}{:02x}".format(*normalize_integer_triplet(rgb_triplet))
def rgb_to_rgb_percent(rgb_triplet: IntTuple) -> PercentRGB:
"""
Convert a 3-tuple of integers, suitable for use in an ``rgb()``
color triplet, to a 3-tuple of percentages suitable for use in
representing that color.
This function makes some trade-offs in terms of the accuracy of
the final representation; for some common integer values,
special-case logic is used to ensure a precise result (e.g.,
integer 128 will always convert to '50%', integer 32 will always
convert to '12.5%'), but for all other values a standard Python
``float`` is used and rounded to two decimal places, which may
result in a loss of precision for some values.
"""
# In order to maintain precision for common values,
# special-case them.
specials = {
255: "100%",
128: "50%",
64: "25%",
32: "12.5%",
16: "6.25%",
0: "0%",
}
return PercentRGB._make(
specials.get(d, "{:.02f}%".format(d / 255.0 * 100))
for d in normalize_integer_triplet(rgb_triplet)
)
# Conversions from percentage rgb() triplets to various formats.
#################################################################
def rgb_percent_to_name(rgb_percent_triplet: PercentTuple, spec: str = CSS3) -> str:
"""
Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
color triplet, to its corresponding normalized color name, if any
such name exists.
The optional keyword argument ``spec`` determines which
specification's list of color names will be used. The default is
CSS3.
If there is no matching name, ``ValueError`` is raised.
"""
return rgb_to_name(
rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet)), spec=spec
)
def rgb_percent_to_hex(rgb_percent_triplet: PercentTuple) -> str:
"""
Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
color triplet, to a normalized hexadecimal color value for that
color.
"""
return rgb_to_hex(
rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet))
)
def _percent_to_integer(percent: str) -> int:
"""
Internal helper for converting a percentage value to an integer
between 0 and 255 inclusive.
"""
return int(round(float(percent.split("%")[0]) / 100 * 255))
def rgb_percent_to_rgb(rgb_percent_triplet: PercentTuple) -> IntegerRGB:
"""
Convert a 3-tuple of percentages, suitable for use in an ``rgb()``
color triplet, to a 3-tuple of integers suitable for use in
representing that color.
Some precision may be lost in this conversion. See the note
regarding precision for ``rgb_to_rgb_percent()`` for details.
"""
return IntegerRGB._make(
map(_percent_to_integer, normalize_percent_triplet(rgb_percent_triplet))
)
# HTML5 color algorithms.
#################################################################
# These functions are written in a way that may seem strange to
# developers familiar with Python, because they do not use the most
# efficient or idiomatic way of accomplishing their tasks. This is
# because, for compliance, these functions are written as literal
# translations into Python of the algorithms in HTML5.
#
# For ease of understanding, the relevant steps of the algorithm from
# the standard are included as comments interspersed in the
# implementation.
def html5_parse_simple_color(input: str) -> HTML5SimpleColor:
"""
Apply the simple color parsing algorithm from section 2.4.6 of
HTML5.
"""
# 1. Let input be the string being parsed.
#
# 2. If input is not exactly seven characters long, then return an
# error.
if not isinstance(input, str) or len(input) != 7:
raise ValueError(
"An HTML5 simple color must be a Unicode string "
"exactly seven characters long."
)
# 3. If the first character in input is not a U+0023 NUMBER SIGN
# character (#), then return an error.
if not input.startswith("#"):
raise ValueError(
"An HTML5 simple color must begin with the " "character '#' (U+0023)."
)
# 4. If the last six characters of input are not all ASCII hex
# digits, then return an error.
if not all(c in string.hexdigits for c in input[1:]):
raise ValueError(
"An HTML5 simple color must contain exactly six ASCII hex digits."
)
# 5. Let result be a simple color.
#
# 6. Interpret the second and third characters as a hexadecimal
# number and let the result be the red component of result.
#
# 7. Interpret the fourth and fifth characters as a hexadecimal
# number and let the result be the green component of result.
#
# 8. Interpret the sixth and seventh characters as a hexadecimal
# number and let the result be the blue component of result.
#
# 9. Return result.
return HTML5SimpleColor(
int(input[1:3], 16), int(input[3:5], 16), int(input[5:7], 16)
)
def html5_serialize_simple_color(simple_color: IntTuple) -> str:
"""
Apply the serialization algorithm for a simple color from section
2.4.6 of HTML5.
"""
red, green, blue = simple_color
# 1. Let result be a string consisting of a single "#" (U+0023)
# character.
result = "#"
# 2. Convert the red, green, and blue components in turn to
# two-digit hexadecimal numbers using lowercase ASCII hex
# digits, zero-padding if necessary, and append these numbers
# to result, in the order red, green, blue.
format_string = "{:02x}"
result += format_string.format(red)
result += format_string.format(green)
result += format_string.format(blue)
# 3. Return result, which will be a valid lowercase simple color.
return result
def html5_parse_legacy_color(input: str) -> HTML5SimpleColor:
"""
Apply the legacy color parsing algorithm from section 2.4.6 of
HTML5.
"""
# 1. Let input be the string being parsed.
if not isinstance(input, str):
raise ValueError(
"HTML5 legacy color parsing requires a Unicode string as input."
)
# 2. If input is the empty string, then return an error.
if input == "":
raise ValueError("HTML5 legacy color parsing forbids empty string as a value.")
# 3. Strip leading and trailing whitespace from input.
input = input.strip()
# 4. If input is an ASCII case-insensitive match for the string
# "transparent", then return an error.
if input.lower() == "transparent":
raise ValueError('HTML5 legacy color parsing forbids "transparent" as a value.')
# 5. If input is an ASCII case-insensitive match for one of the
# keywords listed in the SVG color keywords section of the CSS3
# Color specification, then return the simple color
# corresponding to that keyword.
keyword_hex = CSS3_NAMES_TO_HEX.get(input.lower())
if keyword_hex is not None:
return html5_parse_simple_color(keyword_hex)
# 6. If input is four characters long, and the first character in
# input is a "#" (U+0023) character, and the last three
# characters of input are all ASCII hex digits, then run these
# substeps:
if (
len(input) == 4
and input.startswith("#")
and all(c in string.hexdigits for c in input[1:])
):
# 1. Let result be a simple color.
#
# 2. Interpret the second character of input as a hexadecimal
# digit; let the red component of result be the resulting
# number multiplied by 17.
#
# 3. Interpret the third character of input as a hexadecimal
# digit; let the green component of result be the resulting
# number multiplied by 17.
#
# 4. Interpret the fourth character of input as a hexadecimal
# digit; let the blue component of result be the resulting
# number multiplied by 17.
result = HTML5SimpleColor(
int(input[1], 16) * 17, int(input[2], 16) * 17, int(input[3], 16) * 17
)
# 5. Return result.
return result
# 7. Replace any characters in input that have a Unicode code
# point greater than U+FFFF (i.e. any characters that are not
# in the basic multilingual plane) with the two-character
# string "00".
input = "".join("00" if ord(c) > 0xFFFF else c for c in input)
# 8. If input is longer than 128 characters, truncate input,
# leaving only the first 128 characters.
if len(input) > 128:
input = input[:128]
# 9. If the first character in input is a "#" (U+0023) character,
# remove it.
if input.startswith("#"):
input = input[1:]
# 10. Replace any character in input that is not an ASCII hex
# digit with the character "0" (U+0030).
input = "".join(c if c in string.hexdigits else "0" for c in input)
# 11. While input's length is zero or not a multiple of three,
# append a "0" (U+0030) character to input.
while (len(input) == 0) or (len(input) % 3 != 0):
input += "0"
# 12. Split input into three strings of equal length, to obtain
# three components. Let length be the length of those
# components (one third the length of input).
length = int(len(input) / 3)
red = input[:length]
green = input[length : length * 2]
blue = input[length * 2 :]
# 13. If length is greater than 8, then remove the leading
# length-8 characters in each component, and let length be 8.
if length > 8:
red, green, blue = (red[length - 8 :], green[length - 8 :], blue[length - 8 :])
length = 8
# 14. While length is greater than two and the first character in
# each component is a "0" (U+0030) character, remove that
# character and reduce length by one.
while (length > 2) and (red[0] == "0" and green[0] == "0" and blue[0] == "0"):
red, green, blue = (red[1:], green[1:], blue[1:])
length -= 1
# 15. If length is still greater than two, truncate each
# component, leaving only the first two characters in each.
if length > 2:
red, green, blue = (red[:2], green[:2], blue[:2])
# 16. Let result be a simple color.
#
# 17. Interpret the first component as a hexadecimal number; let
# the red component of result be the resulting number.
#
# 18. Interpret the second component as a hexadecimal number; let
# the green component of result be the resulting number.
#
# 19. Interpret the third component as a hexadecimal number; let
# the blue component of result be the resulting number.
#
# 20. Return result.
return HTML5SimpleColor(int(red, 16), int(green, 16), int(blue, 16))

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 joe tats
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More